From 14b87ce751eec83049e5890c97811db2b78432f6 Mon Sep 17 00:00:00 2001 From: guangtao Date: Mon, 20 Jul 2026 15:40:44 -0700 Subject: [PATCH 01/20] Align Python provider contracts --- provider/asp-provider-manifest.json | 224 ++++-- provider/asp-provider-workspace-install.json | 35 + pyproject.toml | 6 + ...vider-query-pack-descriptor.v1.schema.json | 168 ++++ ...ython-semantic-capabilities.v1.schema.json | 2 + ...emantic-graph-turbo-request.v1.schema.json | 24 + .../semantic-language-registry.v1.schema.json | 748 +----------------- ...emantic-owner-item-evidence.v1.schema.json | 140 ++++ ...emantic-owner-item-evidence.v2.schema.json | 61 -- .../semantic-provider-doctor.v1.schema.json | 78 ++ src/python_lang_parser/__init__.py | 10 +- src/python_lang_parser/_pyproject_metadata.py | 24 +- src/python_lang_project_harness/_cli_agent.py | 26 +- src/python_lang_project_harness/_cli_args.py | 6 +- .../_cli_protocol.py | 2 + src/python_lang_project_harness/_cli_query.py | 39 +- .../_cli_query_arg_consume.py | 7 - .../_cli_query_args.py | 38 +- .../_cli_query_flow_lite_args.py | 2 - .../_cli_query_hook_args.py | 23 +- .../_cli_query_tree_sitter_args.py | 2 - src/python_lang_project_harness/_render.py | 3 - .../_semantic_language.py | 32 +- .../_semantic_language_catalog.py | 7 + .../_semantic_language_ids.py | 1 + .../_semantic_language_invocation.py | 71 ++ .../_semantic_language_query.py | 30 - .../_semantic_language_schemas.py | 15 + .../_semantic_provider_doctor.py | 59 ++ .../_semantic_query_pack.py | 58 ++ .../_semantic_search_cli.py | 2 +- .../_semantic_search_items.py | 68 +- .../_workspace_scope.py | 173 ++++ .../test_cli_query_direct_source_read.py | 116 --- tests/unit/harness/test_evidence_graph.py | 2 +- tests/unit/harness/test_semantic_cli.py | 14 +- .../harness/test_semantic_cli_direct_read.py | 29 - .../test_semantic_cli_direct_read_code.py | 44 -- .../harness/test_semantic_cli_graph_query.py | 42 - .../unit/harness/test_semantic_cli_lexical.py | 6 +- .../harness/test_semantic_cli_owner_items.py | 51 -- ...tic_cli_owner_local_projection_registry.py | 60 -- .../unit/harness/test_semantic_cli_policy.py | 2 +- ...est_semantic_cli_query_direct_read_code.py | 66 -- ...mantic_cli_query_direct_read_projection.py | 52 -- .../test_semantic_cli_query_hook_surface.py | 77 -- .../harness/test_semantic_cli_reasoning.py | 2 +- ...mantic_cli_structural_selector_registry.py | 48 ++ .../test_semantic_cli_tree_sitter_registry.py | 2 +- .../harness/test_semantic_provider_doctor.py | 54 ++ .../harness/test_semantic_schema_registry.py | 4 + tests/unit/harness/test_workspace_scope.py | 122 +++ .../lang_harness/test_render_assertions.py | 3 +- .../python_project_harness_compact_text.snap | 1 - ...snapshot__py_mod_r001_wildcard_import.snap | 1 - ...licy_snapshot__py_mod_r002_bare_print.snap | 1 - ...licy_snapshot__py_mod_r003_facade_all.snap | 1 - ...licy_snapshot__py_mod_r004_breakpoint.snap | 1 - ...cy_snapshot__py_mod_r006_module_bloat.snap | 1 - ...ot__py_mod_r007_reasoning_tree_shadow.snap | 1 - ...icy_snapshot__py_proj_r001_src_layout.snap | 1 - ...apshot__py_proj_r002_declared_package.snap | 1 - ...olicy_snapshot__py_proj_r003_py_typed.snap | 1 - ...pshot__py_proj_r004_typed_annotations.snap | 1 - ...y_snapshot__py_proj_r005_project_name.snap | 1 - ...napshot__py_proj_r006_requires_python.snap | 1 - ...snapshot__py_proj_r007_build_requires.snap | 1 - ...y_snapshot__py_proj_r008_import_names.snap | 1 - ...shot__py_proj_r009_entry_point_target.snap | 1 - ...cy_snapshot__py_proj_r010_pytest_gate.snap | 1 - ...cy_snapshot__py_test_r001_root_pytest.snap | 1 - ...napshot__py_test_r002_unexpected_root.snap | 1 - ...icy_snapshot__py_test_r003_unit_bloat.snap | 1 - ...licy_snapshot__python_compile_invalid.snap | 1 - ...olicy_snapshot__python_syntax_invalid.snap | 1 - tests/unit/test_parser_public_exports.py | 12 + tests/unit/test_pyproject_document.py | 51 ++ 77 files changed, 1385 insertions(+), 1680 deletions(-) create mode 100644 provider/asp-provider-workspace-install.json create mode 100644 schemas/provider-query-pack-descriptor.v1.schema.json create mode 100644 schemas/semantic-owner-item-evidence.v1.schema.json delete mode 100644 schemas/semantic-owner-item-evidence.v2.schema.json create mode 100644 schemas/semantic-provider-doctor.v1.schema.json create mode 100644 src/python_lang_project_harness/_semantic_language_invocation.py create mode 100644 src/python_lang_project_harness/_semantic_provider_doctor.py create mode 100644 src/python_lang_project_harness/_semantic_query_pack.py create mode 100644 src/python_lang_project_harness/_workspace_scope.py delete mode 100644 tests/unit/harness/test_cli_query_direct_source_read.py delete mode 100644 tests/unit/harness/test_semantic_cli_direct_read_code.py delete mode 100644 tests/unit/harness/test_semantic_cli_graph_query.py delete mode 100644 tests/unit/harness/test_semantic_cli_owner_local_projection_registry.py delete mode 100644 tests/unit/harness/test_semantic_cli_query_direct_read_code.py delete mode 100644 tests/unit/harness/test_semantic_cli_query_direct_read_projection.py delete mode 100644 tests/unit/harness/test_semantic_cli_query_hook_surface.py create mode 100644 tests/unit/harness/test_semantic_cli_structural_selector_registry.py create mode 100644 tests/unit/harness/test_semantic_provider_doctor.py create mode 100644 tests/unit/harness/test_workspace_scope.py create mode 100644 tests/unit/test_parser_public_exports.py create mode 100644 tests/unit/test_pyproject_document.py diff --git a/provider/asp-provider-manifest.json b/provider/asp-provider-manifest.json index 1710aa3..4afe97e 100644 --- a/provider/asp-provider-manifest.json +++ b/provider/asp-provider-manifest.json @@ -45,8 +45,50 @@ ] }, "searchCapabilities": { + "sourceSnapshot": { + "descriptorId": "python.source-snapshot", + "descriptorVersion": "1", + "languageId": "python", + "packetSchemaId": "asp.source-snapshot.v1", + "exactSourcePacketSchemaId": "asp.exact-source-query-result.v1", + "sourceOverlaySchemaId": "asp.source-overlay.v1", + "derivedArtifactEvidenceSchemaId": "asp.derived-source-artifact-evidence.v1", + "algorithm": "blake3-merkle-v1", + "authority": "live-parser", + "exactSelectorResolution": "pinned-live-module-graph", + "overlayMode": "merkle-delta" + }, "ownerItems": true, - "semanticFacts": true + "semanticFacts": true, + "dependencyTopology": false, + "dependencyTopologyMetadata": false, + "workspaceScope": true + }, + "semanticFactsDescriptor": { + "descriptorId": "python.semantic-facts", + "descriptorVersion": "1", + "packetSchemaIds": [ + "semantic-fact-graph.v1", + "semantic-fact-ontology.v1" + ], + "factKinds": [ + "field" + ], + "intentAxes": [ + { + "axis": "data-shape", + "terms": [ + "fields", + "collection" + ] + }, + { + "axis": "collection", + "terms": [ + "list" + ] + } + ] }, "policy": { "directSourceRead": "block", @@ -54,89 +96,101 @@ "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}" - ] - } + "queryPackDescriptor": { + "descriptorId": "python.query-pack", + "descriptorVersion": "1", + "languageId": "python", + "semanticFactsDescriptorId": "python.semantic-facts", + "termRoleOverrides": [], + "recipes": [ + { + "recipeId": "python-asyncio-runtime", + "trigger": { + "terms": [ + "asyncio", + "task", + "scheduling" + ], + "match": "any" + }, + "clauses": [ + { + "terms": [ + "asyncio", + "task", + "scheduling" + ], + "roles": [ + "concept" + ], + "intentAxes": [ + "concurrency" + ] + } + ] + }, + { + "recipeId": "python-context-lifecycle", + "trigger": { + "terms": [ + "contextmanager", + "resource", + "lifecycle" + ], + "match": "any" + }, + "clauses": [ + { + "terms": [ + "contextmanager", + "resource", + "lifecycle" + ], + "roles": [ + "concept" + ], + "intentAxes": [ + "resource-lifecycle" + ] + } + ] + }, + { + "recipeId": "python-stream-backpressure", + "trigger": { + "terms": [ + "queue", + "async-generator", + "backpressure" + ], + "match": "any" + }, + "clauses": [ + { + "terms": [ + "queue", + "async-generator", + "backpressure" + ], + "roles": [ + "concept" + ], + "intentAxes": [ + "collection", + "stream" + ] + } + ] + } + ] + }, + "routeBindings": { + "prime": "search/prime", + "owner": "search/owner", + "lexical": "search/lexical", + "query": "query/direct-source-read", + "ingest": "search/ingest", + "checkChanged": "check/changed", + "guide": "guide" } } diff --git a/provider/asp-provider-workspace-install.json b/provider/asp-provider-workspace-install.json new file mode 100644 index 0000000..1f8f9e4 --- /dev/null +++ b/provider/asp-provider-workspace-install.json @@ -0,0 +1,35 @@ +{ + "schemaId": "agent.semantic-protocols.provider-workspace-install", + "schemaVersion": "1", + "schemaAuthority": "https://tao3k.github.io/agent-semantic-protocols/schemas/", + "providerId": "py-harness", + "binary": "py-harness", + "workspaceArtifact": { + "root": "languages/python-lang-project-harness/.venv", + "entrypoint": "bin/py-harness", + "launch": { + "program": "bin/python3", + "args": [ + "bin/py-harness" + ], + "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", + "derivedPaths": [ + "languages/python-lang-project-harness/.venv" + ], + "env": {} + } +} diff --git a/pyproject.toml b/pyproject.toml index e76a41b..4ddc1d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,12 @@ packages = [ "src/python_lang_project_harness", ] +[tool.hatch.build.targets.wheel.force-include] +"provider/asp-provider-manifest.json" = "python_lang_project_harness/asp-provider-manifest.json" +"schemas/provider-query-pack-descriptor.v1.schema.json" = "python_lang_project_harness/schemas/provider-query-pack-descriptor.v1.schema.json" +"schemas/semantic-language-registry.v1.schema.json" = "python_lang_project_harness/schemas/semantic-language-registry.v1.schema.json" +"schemas/semantic-provider-doctor.v1.schema.json" = "python_lang_project_harness/schemas/semantic-provider-doctor.v1.schema.json" + [tool.uv] package = true diff --git a/schemas/provider-query-pack-descriptor.v1.schema.json b/schemas/provider-query-pack-descriptor.v1.schema.json new file mode 100644 index 0000000..1a00c82 --- /dev/null +++ b/schemas/provider-query-pack-descriptor.v1.schema.json @@ -0,0 +1,168 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-query-pack-descriptor.v1.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", + "minItems": 1, + "items": { + "$ref": "#/$defs/recipe" + } + } + }, + "$defs": { + "role": { + "enum": [ + "context", + "concept", + "symbol" + ] + }, + "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/python-semantic-capabilities.v1.schema.json b/schemas/python-semantic-capabilities.v1.schema.json index 6280740..890df76 100644 --- a/schemas/python-semantic-capabilities.v1.schema.json +++ b/schemas/python-semantic-capabilities.v1.schema.json @@ -44,7 +44,9 @@ "name": { "enum": [ "workspace-router", + "workspace-candidate-admission", "python-package-root-search", + "python-package-manager-workspace-scope", "package-prime-map", "python-reasoning-tree-prime", "graph-turbo-provider-facts", diff --git a/schemas/semantic-graph-turbo-request.v1.schema.json b/schemas/semantic-graph-turbo-request.v1.schema.json index 8941881..2da6eb3 100644 --- a/schemas/semantic-graph-turbo-request.v1.schema.json +++ b/schemas/semantic-graph-turbo-request.v1.schema.json @@ -719,6 +719,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 +792,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" } } }, diff --git a/schemas/semantic-language-registry.v1.schema.json b/schemas/semantic-language-registry.v1.schema.json index 106a53b..1c5db02 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://tao3k.github.io/agent-semantic-protocols/schemas/provider-query-pack-descriptor.v1.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,20 @@ "command": { "$ref": "#/$defs/command" }, - "view": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "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" + "invocation": { + "$ref": "#/$defs/commandTemplate" }, "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_]*$" - } - }, - "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-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-provider-doctor.v1.schema.json b/schemas/semantic-provider-doctor.v1.schema.json new file mode 100644 index 0000000..4e7cfba --- /dev/null +++ b/schemas/semantic-provider-doctor.v1.schema.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/semantic-provider-doctor.v1.schema.json", + "title": "Semantic Provider Doctor Envelope v1", + "description": "Strict bootstrap envelope returned by a provider's `agent doctor --json` command. The embedded registry is independently validated as semantic language registry v1.", + "type": "object", + "required": [ + "schemaId", + "schemaVersion", + "schemaAuthority", + "protocolId", + "protocolVersion", + "languageId", + "providerId", + "binary", + "execution", + "registrySchemaId", + "registrySchemaVersion", + "registry", + "registryDigest" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.semantic-provider-doctor" + }, + "schemaVersion": { + "const": "1" + }, + "schemaAuthority": { + "type": "string", + "const": "https://tao3k.github.io/agent-semantic-protocols/schemas/" + }, + "protocolId": { + "const": "agent.semantic-protocols.semantic-language" + }, + "protocolVersion": { + "const": "1" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "binary": { + "type": "string", + "minLength": 1 + }, + "execution": { + "$ref": "#/$defs/providerExecution" + }, + "registrySchemaId": { + "const": "agent.semantic-protocols.semantic-language-registry" + }, + "registrySchemaVersion": { + "const": "1" + }, + "registry": { + "$ref": "https://tao3k.github.io/agent-semantic-protocols/schemas/semantic-language-registry.v1.schema.json" + }, + "registryDigest": { + "type": "string", + "pattern": "^sha256:[0-9a-f]{64}$", + "description": "SHA-256 digest of the RFC 8785 JSON Canonicalization Scheme (JCS) bytes of the registry property. JSON Schema cannot compute this digest; the doctor-v1 consumer MUST verify it." + } + }, + "additionalProperties": false, + "$defs": { + "providerExecution": { + "enum": [ + "external-process", + "embedded" + ] + } + } +} 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/_cli_agent.py b/src/python_lang_project_harness/_cli_agent.py index a786996..06e5d75 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) @@ -16,7 +19,7 @@ def render_agent_guide(project_root: Path) -> str: ( "|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," + "owner-tests routes=syntax-locate,syntax-code," "query-code" ), "|routing evidence-state prime=owner-map-only pipe=ambiguous-query " @@ -35,12 +38,6 @@ def render_agent_guide(project_root: Path) -> str: 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 " @@ -84,11 +81,6 @@ def render_agent_guide(project_root: Path) -> str: 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}", @@ -119,8 +111,8 @@ def render_agent_guide(project_root: Path) -> str: "unsupported=none unsupportedReported=true" ), ( - "|rule query --code is pure code; search/read-plan returns " - "locators/frontier, not inline code" + "|rule query --selector --code is pure code; " + "search returns locators/frontier, not inline code" ), ( "|rule displayLineRange/sourceLocatorHint are display hints; " @@ -173,13 +165,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..c0dcd89 100644 --- a/src/python_lang_project_harness/_cli_args.py +++ b/src/python_lang_project_harness/_cli_args.py @@ -436,8 +436,8 @@ def help_text() -> str: " 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" + " query --selector [--workspace ] --code\n" + " Parser-materialized exact item projection; --code consumes no path argument\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" @@ -475,7 +475,7 @@ def help_text() -> str: " 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' + " asp python search lexical --query PythonSemanticSearchOptions --workspace . --view seeds\n" " py-harness check --full .\n" " py-harness evidence graph --json .\n" " py-harness evidence analyze --json .\n" diff --git a/src/python_lang_project_harness/_cli_protocol.py b/src/python_lang_project_harness/_cli_protocol.py index 7193937..179196f 100644 --- a/src/python_lang_project_harness/_cli_protocol.py +++ b/src/python_lang_project_harness/_cli_protocol.py @@ -161,8 +161,10 @@ def _render_fast_protocol_command( from ._semantic_search_lexical_fast import render_fast_lexical_seed_search from ._semantic_search_owner_fast import render_fast_owner_seed_search from ._semantic_search_prime_fast import render_fast_prime_search + from ._workspace_scope import render_workspace_scope renderers = ( + lambda: render_workspace_scope(args, project_root=project_root), lambda: render_semantic_graph_facts( args, project_root=project_root, stdin=stdin ), diff --git a/src/python_lang_project_harness/_cli_query.py b/src/python_lang_project_harness/_cli_query.py index 7e19d05..48b00eb 100644 --- a/src/python_lang_project_harness/_cli_query.py +++ b/src/python_lang_project_harness/_cli_query.py @@ -104,17 +104,6 @@ def _write_item_query_response( 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 @@ -123,24 +112,7 @@ def _selector_is_structural(selector: str | None) -> bool: 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 + return python_structural_selector_owner_path(selector) def _selector_looks_like_source_locator_hint(selector: str | None) -> bool: @@ -150,12 +122,3 @@ def _selector_looks_like_source_locator_hint(selector: str | None) -> bool: 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..70454af 100644 --- a/src/python_lang_project_harness/_cli_query_arg_consume.py +++ b/src/python_lang_project_harness/_cli_query_arg_consume.py @@ -15,8 +15,6 @@ 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 ]" ) @@ -37,7 +35,6 @@ class QueryParseState: 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 @@ -88,7 +85,6 @@ def consume_query_arg( state.render_mode = render_mode return index + 2 if arg in { - "--from-hook", "--selector", "--package", "--catalog", @@ -144,8 +140,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 +173,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..a5577a7 100644 --- a/src/python_lang_project_harness/_cli_query_args.py +++ b/src/python_lang_project_harness/_cli_query_args.py @@ -15,11 +15,7 @@ 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_hook_args import owner_path_from_query_selector from ._cli_query_tree_sitter_args import ( is_tree_sitter_query_state, tree_sitter_query_args_error, @@ -54,19 +50,6 @@ 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): @@ -93,8 +76,6 @@ def _query_args_result( 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): @@ -110,16 +91,15 @@ def _query_args_error(state: QueryParseState) -> str | None: "`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 @@ -141,12 +121,4 @@ def _query_allows_positional_workspace(state: QueryParseState) -> bool: 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) - ) + return state.names_only 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..c07c937 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 " 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..6f8c9ab 100644 --- a/src/python_lang_project_harness/_cli_query_hook_args.py +++ b/src/python_lang_project_harness/_cli_query_hook_args.py @@ -2,9 +2,6 @@ from __future__ import annotations -import re -from collections.abc import Sequence - from ._semantic_selector_identity import python_structural_selector_owner_path @@ -38,15 +35,6 @@ def normalize_query_view(value: str | None) -> tuple[str | None, str | None]: 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..3ac5641 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 " diff --git a/src/python_lang_project_harness/_render.py b/src/python_lang_project_harness/_render.py index 4e38719..ae8648d 100644 --- a/src/python_lang_project_harness/_render.py +++ b/src/python_lang_project_harness/_render.py @@ -225,9 +225,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" diff --git a/src/python_lang_project_harness/_semantic_language.py b/src/python_lang_project_harness/_semantic_language.py index c7dbc27..77f6c81 100644 --- a/src/python_lang_project_harness/_semantic_language.py +++ b/src/python_lang_project_harness/_semantic_language.py @@ -7,11 +7,14 @@ 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/owner-items") _PYTHON_AST_PATCH_METHODS = ("ast-patch/dry-run",) _PYTHON_EVIDENCE_METHODS = ("evidence/graph", "evidence/analyze") _PYTHON_AGENT_METHODS = ("agent/doctor", "agent/guide") @@ -22,9 +25,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 +35,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 +58,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 +119,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 +133,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]: @@ -151,10 +154,17 @@ def _python_search_method_descriptor(descriptor: dict[str, Any]) -> dict[str, An "semantic-fact-ontology.v1", ] rendered["input"] = "search semantic-facts " + if descriptor["view"] == "workspace-scope": + rendered["supportsCompact"] = False + rendered["outputModes"] = ["json"] + rendered["packetSchemas"] = ["semantic-workspace-scope.v1"] + rendered["input"] = "search workspace-scope --workspace " return rendered def _search_output_schema_ids(view: str) -> list[str]: + if view == "workspace-scope": + return [ids.SEMANTIC_WORKSPACE_SCOPE_SCHEMA_ID] if view == "semantic-facts": return [ids.SEMANTIC_FACT_GRAPH_SCHEMA_ID] schema_ids = [ids.SEMANTIC_SEARCH_PACKET_SCHEMA_ID] diff --git a/src/python_lang_project_harness/_semantic_language_catalog.py b/src/python_lang_project_harness/_semantic_language_catalog.py index ac307c6..827bbcc 100644 --- a/src/python_lang_project_harness/_semantic_language_catalog.py +++ b/src/python_lang_project_harness/_semantic_language_catalog.py @@ -19,6 +19,13 @@ def python_search_view_descriptors() -> list[dict[str, Any]]: _python("python-package-root-search"), ], ), + _view( + "workspace-scope", + capabilities=[ + _semantic("workspace-candidate-admission"), + _python("python-package-manager-workspace-scope"), + ], + ), _view( "prime", capabilities=[ diff --git a/src/python_lang_project_harness/_semantic_language_ids.py b/src/python_lang_project_harness/_semantic_language_ids.py index 584c254..ce4cdc5 100644 --- a/src/python_lang_project_harness/_semantic_language_ids.py +++ b/src/python_lang_project_harness/_semantic_language_ids.py @@ -17,6 +17,7 @@ SEMANTIC_TYPE_SURFACE_SCHEMA_ID = "agent.semantic-protocols.semantic-type-surface" SEMANTIC_FACT_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-fact-graph" SEMANTIC_FACT_ONTOLOGY_SCHEMA_ID = "agent.semantic-protocols.semantic-fact-ontology" +SEMANTIC_WORKSPACE_SCOPE_SCHEMA_ID = "agent.semantic-protocols.semantic-workspace-scope" SEMANTIC_DETERMINISM_READINESS_SCHEMA_ID = ( "agent.semantic-protocols.semantic-determinism-readiness" ) 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..a34764a --- /dev/null +++ b/src/python_lang_project_harness/_semantic_language_invocation.py @@ -0,0 +1,71 @@ +"""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}", + ], + "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..80b05cb 100644 --- a/src/python_lang_project_harness/_semantic_language_query.py +++ b/src/python_lang_project_harness/_semantic_language_query.py @@ -88,34 +88,4 @@ def python_query_method_descriptors() -> list[dict[str, Any]]: }, "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", - ], - "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"], - "supportsJson": True, - "supportsCompact": True, - "outputModes": ["frontier", "json", "code", "names"], - "cacheReplay": False, - "codeOutput": { - "mode": "pure-code", - "multiMatch": "deny", - "requires": ["exact-selector"], - }, - "unsupportedPatternBehavior": "diagnostic", - }, ] diff --git a/src/python_lang_project_harness/_semantic_language_schemas.py b/src/python_lang_project_harness/_semantic_language_schemas.py index e8890aa..5d41e6b 100644 --- a/src/python_lang_project_harness/_semantic_language_schemas.py +++ b/src/python_lang_project_harness/_semantic_language_schemas.py @@ -114,6 +114,11 @@ def python_semantic_language_schemas() -> list[dict[str, str]]: "schemaVersion": "1", "path": "schemas/semantic-fact-ontology.v1.schema.json", }, + { + "schemaId": ids.SEMANTIC_WORKSPACE_SCOPE_SCHEMA_ID, + "schemaVersion": "1", + "path": "schemas/semantic-workspace-scope.v1.schema.json", + }, { "schemaId": "agent.semantic-protocols.semantic-handle", "schemaVersion": "1", @@ -124,6 +129,16 @@ def python_semantic_language_schemas() -> list[dict[str, str]]: "schemaVersion": ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION, "path": "schemas/semantic-language-registry.v1.schema.json", }, + { + "schemaId": "agent.semantic-protocols.semantic-provider-doctor", + "schemaVersion": "1", + "path": "schemas/semantic-provider-doctor.v1.schema.json", + }, + { + "schemaId": "agent.semantic-protocols.provider-query-pack-descriptor", + "schemaVersion": "1", + "path": "schemas/provider-query-pack-descriptor.v1.schema.json", + }, { "schemaId": ids.PYTHON_CAPABILITIES_SCHEMA_ID, "schemaVersion": "1", 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..c1f03be --- /dev/null +++ b/src/python_lang_project_harness/_semantic_provider_doctor.py @@ -0,0 +1,59 @@ +"""Build the Python provider's canonical semantic doctor-v1 response.""" + +import json +from hashlib import sha256 +from importlib.resources import files +from pathlib import Path +from typing import Any + +from . import _semantic_language_ids as ids + + +def _provider_manifest() -> dict[str, Any]: + packaged = files("python_lang_project_harness").joinpath( + "asp-provider-manifest.json" + ) + if packaged.is_file(): + return json.loads(packaged.read_text(encoding="utf-8")) + checkout = ( + Path(__file__).resolve().parents[2] / "provider" / "asp-provider-manifest.json" + ) + return json.loads(checkout.read_text(encoding="utf-8")) + + +def _provider_identity() -> dict[str, str]: + manifest = _provider_manifest() + keys = ("languageId", "providerId", "binary", "execution") + identity = {key: manifest[key] for key in keys} + if not all(isinstance(value, str) and value for value in identity.values()): + raise ValueError("provider manifest identity fields must be non-empty strings") + return identity + + +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_search_cli.py b/src/python_lang_project_harness/_semantic_search_cli.py index d0644ec..19b8381 100644 --- a/src/python_lang_project_harness/_semantic_search_cli.py +++ b/src/python_lang_project_harness/_semantic_search_cli.py @@ -77,7 +77,7 @@ def _search_view_descriptor( def _semantic_search_usage() -> str: return ( "usage: py-harness search " - " " + " " "... [--json] [--code] [--package PATH] [--workspace ]; " "dependency/deps are manifest-first, import-usage backed, and cache hashes not raw source" ) diff --git a/src/python_lang_project_harness/_semantic_search_items.py b/src/python_lang_project_harness/_semantic_search_items.py index 6ad11ba..c283abf 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,7 @@ 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 --code", }, "queryCoverage": [ semantic_query_coverage( @@ -229,62 +219,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/_workspace_scope.py b/src/python_lang_project_harness/_workspace_scope.py new file mode 100644 index 0000000..c4a23a8 --- /dev/null +++ b/src/python_lang_project_harness/_workspace_scope.py @@ -0,0 +1,173 @@ +"""Provider-owned Python package-manager workspace admission.""" + +from __future__ import annotations + +import glob +import hashlib +import json +from pathlib import Path +from typing import Any + +from python_lang_parser import ( + PythonPyprojectParseError, + parse_python_pyproject_document, +) + +from ._cli_args import ProtocolArgs + +_LOCKFILES = ("uv.lock", "poetry.lock", "pdm.lock", "Pipfile.lock") + + +def render_workspace_scope( + args: ProtocolArgs, + *, + project_root: Path, +) -> str | None: + """Return the standalone workspace-scope fast-path when selected.""" + + if args.command != "search" or args.view != "workspace-scope": + return None + if not args.json: + raise ValueError("search workspace-scope requires --json") + return json.dumps(build_workspace_scope(project_root), sort_keys=True) + "\n" + + +def build_workspace_scope(project_root: Path) -> dict[str, Any]: + """Resolve Python workspace membership from pyproject and lock anchors.""" + + discovery_root = project_root.resolve() + root_manifest = discovery_root / "pyproject.toml" + if not root_manifest.is_file(): + raise ValueError(f"workspace scope requires {root_manifest.as_posix()}") + + root_document = _read_pyproject(root_manifest) + package_roots = _package_roots(discovery_root, root_document) + packages = [_package_entry(root) for root in package_roots] + if not packages: + raise ValueError("workspace scope resolved no Python packages") + + anchors = _anchors(discovery_root, package_roots) + workspace_name = _project_name(root_document) or discovery_root.name + packet: dict[str, Any] = { + "schemaId": "agent.semantic-protocols.semantic-workspace-scope", + "schemaVersion": "1", + "workspaceId": f"python:{workspace_name}", + "languageId": "python", + "providerId": "py-harness", + "packageManager": _package_manager(discovery_root, root_document), + "sourceExtensions": [".py", ".pyi"], + "discoveryRoot": discovery_root.as_posix(), + "anchors": anchors, + "packages": packages, + "admittedRoots": [entry["root"] for entry in packages], + } + packet["fingerprint"] = _json_fingerprint(packet) + return packet + + +def _package_roots(project_root: Path, document: dict[str, Any]) -> list[Path]: + workspace = _uv_workspace(document) + roots: set[Path] = set() + if _project_name(document) is not None: + roots.add(project_root) + if workspace is None: + return sorted(roots) + + excluded = _expanded_roots(project_root, workspace.get("exclude", [])) + for root in _expanded_roots(project_root, workspace.get("members", [])): + if root not in excluded and (root / "pyproject.toml").is_file(): + roots.add(root) + return sorted(roots) + + +def _expanded_roots(project_root: Path, patterns: object) -> set[Path]: + if not isinstance(patterns, list): + return set() + roots: set[Path] = set() + for pattern in patterns: + if not isinstance(pattern, str) or not pattern: + continue + absolute_pattern = str(project_root / pattern) + for match in glob.glob(absolute_pattern, recursive=True): + candidate = Path(match).resolve() + roots.add( + candidate.parent if candidate.name == "pyproject.toml" else candidate + ) + return roots + + +def _package_entry(root: Path) -> dict[str, str]: + manifest = root / "pyproject.toml" + document = _read_pyproject(manifest) + name = _project_name(document) + if name is None: + raise ValueError(f"Python package manifest has no [project].name: {manifest}") + root_text = root.resolve().as_posix() + identity = hashlib.sha256(root_text.encode("utf-8")).hexdigest()[:12] + return { + "packageId": f"python:{name}:{identity}", + "name": name, + "root": root_text, + "manifestPath": manifest.resolve().as_posix(), + "languageId": "python", + } + + +def _anchors(project_root: Path, package_roots: list[Path]) -> list[dict[str, str]]: + paths: dict[Path, str] = { + (root / "pyproject.toml").resolve(): "pyproject" for root in package_roots + } + root_manifest = (project_root / "pyproject.toml").resolve() + paths[root_manifest] = "pyproject" + for name in _LOCKFILES: + lockfile = (project_root / name).resolve() + if lockfile.is_file(): + paths[lockfile] = "python-lock" + return [ + { + "kind": paths[path], + "path": path.as_posix(), + "sha256": _file_fingerprint(path), + } + for path in sorted(paths) + ] + + +def _read_pyproject(path: Path) -> dict[str, Any]: + try: + return parse_python_pyproject_document(path) + except PythonPyprojectParseError as error: + raise ValueError(str(error)) from error + + +def _project_name(document: dict[str, Any]) -> str | None: + project = document.get("project") + if not isinstance(project, dict): + return None + name = project.get("name") + return name if isinstance(name, str) and name else None + + +def _uv_workspace(document: dict[str, Any]) -> dict[str, Any] | None: + tool = document.get("tool") + uv = tool.get("uv") if isinstance(tool, dict) else None + workspace = uv.get("workspace") if isinstance(uv, dict) else None + return workspace if isinstance(workspace, dict) else None + + +def _package_manager(project_root: Path, document: dict[str, Any]) -> str: + tool = document.get("tool") + if (project_root / "uv.lock").is_file() or ( + isinstance(tool, dict) and isinstance(tool.get("uv"), dict) + ): + return "uv" + return "pip" + + +def _file_fingerprint(path: Path) -> str: + return f"sha256:{hashlib.sha256(path.read_bytes()).hexdigest()}" + + +def _json_fingerprint(value: dict[str, Any]) -> str: + payload = json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8") + return f"sha256:{hashlib.sha256(payload).hexdigest()}" 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_evidence_graph.py b/tests/unit/harness/test_evidence_graph.py index 11262da..b3ae100 100644 --- a/tests/unit/harness/test_evidence_graph.py +++ b/tests/unit/harness/test_evidence_graph.py @@ -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_semantic_cli.py b/tests/unit/harness/test_semantic_cli.py index 7c1d811..a14e610 100644 --- a/tests/unit/harness/test_semantic_cli.py +++ b/tests/unit/harness/test_semantic_cli.py @@ -20,7 +20,8 @@ def test_cli_agent_doctor_json_advertises_semantic_language_provider( payload = json.loads(stdout.getvalue()) assert exit_code == 0 - registration = payload["languages"][0] + registry = payload["registry"] + registration = registry["languages"][0] assert registration["languageId"] == "python" assert registration["providerId"] == "py-harness" assert registration["binary"] == "py-harness" @@ -101,7 +102,7 @@ def test_cli_agent_guide_prints_provider_owned_searchflow(tmp_path: Path) -> Non 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" + "routes=syntax-locate,syntax-code,query-code" ) in rendered assert ( "|routing evidence-state prime=owner-map-only pipe=ambiguous-query" in rendered @@ -127,11 +128,9 @@ def test_cli_agent_guide_prints_provider_owned_searchflow(tmp_path: Path) -> Non 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 "read-frontier" not in rendered + assert "owner-local-projection" not in rendered + assert "--from-hook" not in rendered assert ( "|cmd syntax-code=asp python query --treesitter-query " "'(function_definition name: (identifier) @function.name)' " @@ -171,6 +170,7 @@ def test_python_capability_schema_covers_registry_descriptors() -> None: ) for descriptor in python_semantic_language_registration()["methodDescriptors"]: + assert descriptor["invocation"]["argv"][0] == "py-harness" for capability in descriptor.get("capabilities", []): assert capability["name"] in capability_names for ingest_surface in descriptor.get("ingestRequiredFor", []): diff --git a/tests/unit/harness/test_semantic_cli_direct_read.py b/tests/unit/harness/test_semantic_cli_direct_read.py index 0722256..832d813 100644 --- a/tests/unit/harness/test_semantic_cli_direct_read.py +++ b/tests/unit/harness/test_semantic_cli_direct_read.py @@ -3,8 +3,6 @@ import io from pathlib import Path -from pytest import CaptureFixture - from python_lang_project_harness._cli import run_cli @@ -29,33 +27,6 @@ def _write_demo_package(tmp_path: Path) -> None: ) -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: 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_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_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_items.py b/tests/unit/harness/test_semantic_cli_owner_items.py index 661227e..02f9fde 100644 --- a/tests/unit/harness/test_semantic_cli_owner_items.py +++ b/tests/unit/harness/test_semantic_cli_owner_items.py @@ -211,10 +211,6 @@ def test_cli_query_json_emits_projection_nodes_and_expand_actions( ) -> 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( @@ -284,54 +280,7 @@ def test_cli_query_json_emits_projection_nodes_and_expand_actions( 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_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_structural_selector_registry.py b/tests/unit/harness/test_semantic_cli_structural_selector_registry.py new file mode 100644 index 0000000..b313fb7 --- /dev/null +++ b/tests/unit/harness/test_semantic_cli_structural_selector_registry.py @@ -0,0 +1,48 @@ +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["codeOutput"]["mode"] == "pure-code" + assert "exact-selector" in query["codeOutput"]["requires"] + 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_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_provider_doctor.py b/tests/unit/harness/test_semantic_provider_doctor.py new file mode 100644 index 0000000..e5c63a6 --- /dev/null +++ b/tests/unit/harness/test_semantic_provider_doctor.py @@ -0,0 +1,54 @@ +"""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"]) == 34 + 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..a5f6ac3 100644 --- a/tests/unit/harness/test_semantic_schema_registry.py +++ b/tests/unit/harness/test_semantic_schema_registry.py @@ -19,6 +19,10 @@ def test_package_local_semantic_schemas_stay_synchronized() -> None: "semantic-search-packet.v1.schema.json", "semantic-query-packet.v1.schema.json", "semantic-read-packet.v1.schema.json", + "semantic-provider-doctor.v1.schema.json", + "semantic-language-registry.v1.schema.json", + "semantic-language-registry.v1.schema.json", + "provider-query-pack-descriptor.v1.schema.json", "semantic-source-location.v1.schema.json", "semantic-tree-sitter-provenance.v1.schema.json", "semantic-tree-sitter-query.v1.schema.json", diff --git a/tests/unit/harness/test_workspace_scope.py b/tests/unit/harness/test_workspace_scope.py new file mode 100644 index 0000000..2d471fa --- /dev/null +++ b/tests/unit/harness/test_workspace_scope.py @@ -0,0 +1,122 @@ +"""Python package-manager workspace scope fast-path tests.""" + +from __future__ import annotations + +import io +import json +from pathlib import Path + +from python_lang_project_harness import python_semantic_language_registration, run_cli + + +def test_workspace_scope_is_json_fast_path_and_registry_contract( + tmp_path: Path, +) -> None: + (tmp_path / "pyproject.toml").write_text( + '[project]\nname = "scope-root"\nversion = "0.1.0"\n', encoding="utf-8" + ) + (tmp_path / "uv.lock").write_text("version = 1\n", encoding="utf-8") + stdout = io.StringIO() + + assert ( + run_cli( + ["search", "workspace-scope", "--json", "--workspace", str(tmp_path)], + stdout=stdout, + cwd=tmp_path, + ) + == 0 + ) + + payload = json.loads(stdout.getvalue()) + assert payload["schemaId"] == "agent.semantic-protocols.semantic-workspace-scope" + assert payload["schemaVersion"] == "1" + assert payload["fingerprint"].startswith("sha256:") + assert payload["workspaceId"] == "python:scope-root" + assert payload["packageManager"] == "uv" + assert payload["sourceExtensions"] == [".py", ".pyi"] + assert payload["discoveryRoot"] == tmp_path.resolve().as_posix() + assert payload["admittedRoots"] == [tmp_path.resolve().as_posix()] + assert {anchor["kind"] for anchor in payload["anchors"]} == { + "pyproject", + "python-lock", + } + + registration = python_semantic_language_registration() + descriptor = next( + item + for item in registration["methodDescriptors"] + if item["method"] == "search/workspace-scope" + ) + assert descriptor["outputSchemaIds"] == [ + "agent.semantic-protocols.semantic-workspace-scope" + ] + assert descriptor["outputModes"] == ["json"] + assert any( + schema["schemaId"] == "agent.semantic-protocols.semantic-workspace-scope" + and schema["path"] == "schemas/semantic-workspace-scope.v1.schema.json" + for schema in registration["schemas"] + ) + + +def test_workspace_scope_admits_uv_member_outside_discovery_root( + tmp_path: Path, +) -> None: + root = tmp_path / "root" + sibling = tmp_path / "shared" + root.mkdir() + sibling.mkdir() + (root / "pyproject.toml").write_text( + '[project]\nname = "scope-root"\nversion = "0.1.0"\n' + '[tool.uv.workspace]\nmembers = ["../shared"]\n', + encoding="utf-8", + ) + (sibling / "pyproject.toml").write_text( + '[project]\nname = "shared-member"\nversion = "0.1.0"\n', + encoding="utf-8", + ) + stdout = io.StringIO() + + assert ( + run_cli( + ["search", "workspace-scope", "--json", "--workspace", str(root)], + stdout=stdout, + cwd=root, + ) + == 0 + ) + + payload = json.loads(stdout.getvalue()) + assert payload["schemaId"] == "agent.semantic-protocols.semantic-workspace-scope" + assert payload["admittedRoots"] == sorted( + [root.resolve().as_posix(), sibling.resolve().as_posix()] + ) + assert {package["name"] for package in payload["packages"]} == { + "scope-root", + "shared-member", + } + + +def test_workspace_scope_rejects_compact_mode(tmp_path: Path) -> None: + (tmp_path / "pyproject.toml").write_text( + '[project]\nname = "scope-root"\nversion = "0.1.0"\n', encoding="utf-8" + ) + stdout = io.StringIO() + stderr = io.StringIO() + + assert ( + run_cli( + ["search", "workspace-scope", "--workspace", str(tmp_path)], + stdout=stdout, + stderr=stderr, + cwd=tmp_path, + ) + == 3 + ) + assert "requires --json" in stderr.getvalue() + + +def test_provider_manifest_advertises_workspace_scope_capability() -> None: + manifest_path = Path(__file__).parents[3] / "provider/asp-provider-manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + + assert manifest["searchCapabilities"]["workspaceScope"] is True 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/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_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_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") From ec3bb474b509716d211b7a46b5a1da95f7f043cf Mon Sep 17 00:00:00 2001 From: guangtao Date: Mon, 20 Jul 2026 19:15:14 -0700 Subject: [PATCH 02/20] Sync canonical schema profile --- ...vider-query-pack-descriptor.v1.schema.json | 168 ------------------ ...emantic-graph-turbo-request.v1.schema.json | 5 + .../semantic-provider-doctor.v1.schema.json | 78 -------- .../_semantic_language_schemas.py | 10 -- .../harness/test_semantic_schema_registry.py | 28 +-- 5 files changed, 11 insertions(+), 278 deletions(-) delete mode 100644 schemas/provider-query-pack-descriptor.v1.schema.json delete mode 100644 schemas/semantic-provider-doctor.v1.schema.json diff --git a/schemas/provider-query-pack-descriptor.v1.schema.json b/schemas/provider-query-pack-descriptor.v1.schema.json deleted file mode 100644 index 1a00c82..0000000 --- a/schemas/provider-query-pack-descriptor.v1.schema.json +++ /dev/null @@ -1,168 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-query-pack-descriptor.v1.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", - "minItems": 1, - "items": { - "$ref": "#/$defs/recipe" - } - } - }, - "$defs": { - "role": { - "enum": [ - "context", - "concept", - "symbol" - ] - }, - "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/semantic-graph-turbo-request.v1.schema.json b/schemas/semantic-graph-turbo-request.v1.schema.json index 2da6eb3..e8f2fbe 100644 --- a/schemas/semantic-graph-turbo-request.v1.schema.json +++ b/schemas/semantic-graph-turbo-request.v1.schema.json @@ -12,6 +12,7 @@ "protocolVersion", "packetKind", "surface", + "sourceSnapshot", "queryTerms", "profile", "algorithm", @@ -137,9 +138,13 @@ "auto", "provider", "finder", + "search-overlay", "ingest" ] }, + "sourceSnapshot": { + "$ref": "https://agent-semantic-protocols.dev/schemas/source-snapshot-evidence.v1.schema.json#/$defs/sourceSnapshot" + }, "candidateSources": { "type": "array", "items": { diff --git a/schemas/semantic-provider-doctor.v1.schema.json b/schemas/semantic-provider-doctor.v1.schema.json deleted file mode 100644 index 4e7cfba..0000000 --- a/schemas/semantic-provider-doctor.v1.schema.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/semantic-provider-doctor.v1.schema.json", - "title": "Semantic Provider Doctor Envelope v1", - "description": "Strict bootstrap envelope returned by a provider's `agent doctor --json` command. The embedded registry is independently validated as semantic language registry v1.", - "type": "object", - "required": [ - "schemaId", - "schemaVersion", - "schemaAuthority", - "protocolId", - "protocolVersion", - "languageId", - "providerId", - "binary", - "execution", - "registrySchemaId", - "registrySchemaVersion", - "registry", - "registryDigest" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-provider-doctor" - }, - "schemaVersion": { - "const": "1" - }, - "schemaAuthority": { - "type": "string", - "const": "https://tao3k.github.io/agent-semantic-protocols/schemas/" - }, - "protocolId": { - "const": "agent.semantic-protocols.semantic-language" - }, - "protocolVersion": { - "const": "1" - }, - "languageId": { - "type": "string", - "minLength": 1 - }, - "providerId": { - "type": "string", - "minLength": 1 - }, - "binary": { - "type": "string", - "minLength": 1 - }, - "execution": { - "$ref": "#/$defs/providerExecution" - }, - "registrySchemaId": { - "const": "agent.semantic-protocols.semantic-language-registry" - }, - "registrySchemaVersion": { - "const": "1" - }, - "registry": { - "$ref": "https://tao3k.github.io/agent-semantic-protocols/schemas/semantic-language-registry.v1.schema.json" - }, - "registryDigest": { - "type": "string", - "pattern": "^sha256:[0-9a-f]{64}$", - "description": "SHA-256 digest of the RFC 8785 JSON Canonicalization Scheme (JCS) bytes of the registry property. JSON Schema cannot compute this digest; the doctor-v1 consumer MUST verify it." - } - }, - "additionalProperties": false, - "$defs": { - "providerExecution": { - "enum": [ - "external-process", - "embedded" - ] - } - } -} diff --git a/src/python_lang_project_harness/_semantic_language_schemas.py b/src/python_lang_project_harness/_semantic_language_schemas.py index 5d41e6b..d6b0a77 100644 --- a/src/python_lang_project_harness/_semantic_language_schemas.py +++ b/src/python_lang_project_harness/_semantic_language_schemas.py @@ -129,16 +129,6 @@ def python_semantic_language_schemas() -> list[dict[str, str]]: "schemaVersion": ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION, "path": "schemas/semantic-language-registry.v1.schema.json", }, - { - "schemaId": "agent.semantic-protocols.semantic-provider-doctor", - "schemaVersion": "1", - "path": "schemas/semantic-provider-doctor.v1.schema.json", - }, - { - "schemaId": "agent.semantic-protocols.provider-query-pack-descriptor", - "schemaVersion": "1", - "path": "schemas/provider-query-pack-descriptor.v1.schema.json", - }, { "schemaId": ids.PYTHON_CAPABILITIES_SCHEMA_ID, "schemaVersion": "1", diff --git a/tests/unit/harness/test_semantic_schema_registry.py b/tests/unit/harness/test_semantic_schema_registry.py index a5f6ac3..371d521 100644 --- a/tests/unit/harness/test_semantic_schema_registry.py +++ b/tests/unit/harness/test_semantic_schema_registry.py @@ -15,27 +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-provider-doctor.v1.schema.json", - "semantic-language-registry.v1.schema.json", - "semantic-language-registry.v1.schema.json", - "provider-query-pack-descriptor.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 From e6351a0df223ac283a8c4bc9d9db79c9f0e7441c Mon Sep 17 00:00:00 2001 From: guangtao Date: Wed, 22 Jul 2026 16:19:01 -0700 Subject: [PATCH 03/20] chore: align Python provider contracts --- provider/asp-provider-manifest.json | 2 +- provider/asp-provider-workspace-install.json | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/provider/asp-provider-manifest.json b/provider/asp-provider-manifest.json index 4afe97e..ebde024 100644 --- a/provider/asp-provider-manifest.json +++ b/provider/asp-provider-manifest.json @@ -51,7 +51,7 @@ "languageId": "python", "packetSchemaId": "asp.source-snapshot.v1", "exactSourcePacketSchemaId": "asp.exact-source-query-result.v1", - "sourceOverlaySchemaId": "asp.source-overlay.v1", + "sourceSnapshotEnvelopeSchemaId": "asp.exact-source-snapshot-envelope.v1", "derivedArtifactEvidenceSchemaId": "asp.derived-source-artifact-evidence.v1", "algorithm": "blake3-merkle-v1", "authority": "live-parser", diff --git a/provider/asp-provider-workspace-install.json b/provider/asp-provider-workspace-install.json index 1f8f9e4..751c529 100644 --- a/provider/asp-provider-workspace-install.json +++ b/provider/asp-provider-workspace-install.json @@ -27,6 +27,10 @@ "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" ], From b9a7b6d9bc999fb11700ade0f1f94ec0dcc595b4 Mon Sep 17 00:00:00 2001 From: guangtao Date: Wed, 22 Jul 2026 16:38:48 -0700 Subject: [PATCH 04/20] style: apply repository formatting --- src/python_lang_project_harness/_cli_query.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/python_lang_project_harness/_cli_query.py b/src/python_lang_project_harness/_cli_query.py index 48b00eb..c738fe2 100644 --- a/src/python_lang_project_harness/_cli_query.py +++ b/src/python_lang_project_harness/_cli_query.py @@ -44,6 +44,11 @@ def run_query_command( ) return 0 + if args.selector is None and (args.owner_path is not None or args.terms): + raise ValueError( + "python query requires an exact --selector; use `asp python search owner " + " items --query --names-only --workspace .` for discovery" + ) if args.code_only and not _selector_is_structural(args.selector): raise ValueError( "query requires parser-owned structural selector identity; " From 1bdda0c533151a10b7743b4f7aa264c2b378b2c4 Mon Sep 17 00:00:00 2001 From: guangtao Date: Thu, 23 Jul 2026 17:07:36 -0700 Subject: [PATCH 05/20] fix: align provider package schema inclusions --- provider/asp-provider-manifest.json | 1 + pyproject.toml | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/provider/asp-provider-manifest.json b/provider/asp-provider-manifest.json index ebde024..d651fd9 100644 --- a/provider/asp-provider-manifest.json +++ b/provider/asp-provider-manifest.json @@ -51,6 +51,7 @@ "languageId": "python", "packetSchemaId": "asp.source-snapshot.v1", "exactSourcePacketSchemaId": "asp.exact-source-query-result.v1", + "canonicalItemSelectorSchemaId": "asp.canonical-item-selector.v1", "sourceSnapshotEnvelopeSchemaId": "asp.exact-source-snapshot-envelope.v1", "derivedArtifactEvidenceSchemaId": "asp.derived-source-artifact-evidence.v1", "algorithm": "blake3-merkle-v1", diff --git a/pyproject.toml b/pyproject.toml index 4ddc1d8..f6ffca9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,9 +40,7 @@ packages = [ [tool.hatch.build.targets.wheel.force-include] "provider/asp-provider-manifest.json" = "python_lang_project_harness/asp-provider-manifest.json" -"schemas/provider-query-pack-descriptor.v1.schema.json" = "python_lang_project_harness/schemas/provider-query-pack-descriptor.v1.schema.json" "schemas/semantic-language-registry.v1.schema.json" = "python_lang_project_harness/schemas/semantic-language-registry.v1.schema.json" -"schemas/semantic-provider-doctor.v1.schema.json" = "python_lang_project_harness/schemas/semantic-provider-doctor.v1.schema.json" [tool.uv] package = true From cdebde8cf70e67349b091bbe8f1afc747a87f11b Mon Sep 17 00:00:00 2001 From: guangtao Date: Sat, 25 Jul 2026 19:23:31 -0700 Subject: [PATCH 06/20] fix: align exact-selector provider contract --- provider/asp-provider-manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provider/asp-provider-manifest.json b/provider/asp-provider-manifest.json index d651fd9..35b80f2 100644 --- a/provider/asp-provider-manifest.json +++ b/provider/asp-provider-manifest.json @@ -189,7 +189,7 @@ "prime": "search/prime", "owner": "search/owner", "lexical": "search/lexical", - "query": "query/direct-source-read", + "query": "query/exact-selector", "ingest": "search/ingest", "checkChanged": "check/changed", "guide": "guide" From 18c2eb02a6aa1e0ad9dc62e56069e6c0a4a1af58 Mon Sep 17 00:00:00 2001 From: guangtao Date: Sat, 25 Jul 2026 22:05:31 -0700 Subject: [PATCH 07/20] chore: sync shared structural index schema --- .../semantic-structural-index.v1.schema.json | 88 ++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/schemas/semantic-structural-index.v1.schema.json b/schemas/semantic-structural-index.v1.schema.json index 8b9516c..76fcd39 100644 --- a/schemas/semantic-structural-index.v1.schema.json +++ b/schemas/semantic-structural-index.v1.schema.json @@ -74,6 +74,12 @@ "$ref": "#/$defs/fileHash" } }, + "compileContexts": { + "type": "array", + "items": { + "$ref": "#/$defs/compileContext" + } + }, "owners": { "type": "array", "items": { @@ -90,10 +96,22 @@ "type": "integer", "minimum": 0 }, + "occurrences": { + "type": "array", + "items": { + "$ref": "#/$defs/occurrence" + } + }, + "relations": { + "type": "array", + "items": { + "$ref": "#/$defs/semanticRelation" + } + }, "syntaxFacts": { "type": "array", "items": { - "$ref": "semantic-native-syntax-fact-index.v1.schema.json#/$defs/nativeSyntaxFact" + "$ref": "https://agent-semantic-protocols.local/schemas/semantic-native-syntax-fact-index.v1.schema.json#/$defs/nativeSyntaxFact" } }, "dependencyUsages": { @@ -123,10 +141,29 @@ }, "source": { "type": "string" + }, + "compileContextDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" } }, "additionalProperties": true }, + "compileContext": { + "type": "object", + "required": ["translationUnit", "digest"], + "properties": { + "translationUnit": { + "type": "string", + "minLength": 1 + }, + "digest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + } + }, + "additionalProperties": false + }, "owner": { "type": "object", "required": ["ownerPath", "ownerKind", "sourceAuthority", "queryKeys"], @@ -189,6 +226,55 @@ }, "additionalProperties": true }, + "occurrence": { + "type": "object", + "required": ["id", "ownerPath", "name", "kind", "sourceLocator", "queryKeys"], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "ownerPath": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "sourceLocator": { + "type": "string", + "minLength": 1 + }, + "targetSymbolId": { + "type": "string", + "minLength": 1 + }, + "containerSymbolId": { + "type": "string", + "minLength": 1 + }, + "queryKeys": { + "$ref": "#/$defs/queryKeys" + } + }, + "additionalProperties": true + }, + "semanticRelation": { + "allOf": [ + { + "$ref": "#/$defs/occurrence" + }, + { + "type": "object", + "required": ["targetSymbolId"] + } + ] + }, "dependencyUsage": { "type": "object", "required": ["ownerPath", "packageName", "source", "queryKeys"], From 5c1147b7f177320fa51e4e7b43e5d7c5a5c2d935 Mon Sep 17 00:00:00 2001 From: guangtao Date: Thu, 30 Jul 2026 16:37:44 -0700 Subject: [PATCH 08/20] feat: align project scope schemas --- schemas/language-package-graph.v1.schema.json | 308 ++++++++++++++ schemas/project-resolution.v1.schema.json | 380 ++++++++++++++++++ schemas/provider-manifest.v1.schema.json | 348 ++++++++++++++++ ...oject-resolution-descriptor.v1.schema.json | 85 ++++ ...pository-candidate-snapshot.v1.schema.json | 203 ++++++++++ schemas/resolved-source-scope.v1.schema.json | 188 +++++++++ 6 files changed, 1512 insertions(+) create mode 100644 schemas/language-package-graph.v1.schema.json create mode 100644 schemas/project-resolution.v1.schema.json create mode 100644 schemas/provider-manifest.v1.schema.json create mode 100644 schemas/provider-project-resolution-descriptor.v1.schema.json create mode 100644 schemas/repository-candidate-snapshot.v1.schema.json create mode 100644 schemas/resolved-source-scope.v1.schema.json diff --git a/schemas/language-package-graph.v1.schema.json b/schemas/language-package-graph.v1.schema.json new file mode 100644 index 0000000..b3ab077 --- /dev/null +++ b/schemas/language-package-graph.v1.schema.json @@ -0,0 +1,308 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.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": { + "type": "array", + "items": { + "$ref": "#/$defs/manifest" + } + }, + "lockfiles": { + "type": "array", + "items": { + "$ref": "#/$defs/lockfile" + } + }, + "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": { + "path": { + "type": "string", + "minLength": 1 + }, + "manifest": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "kind", + "digest" + ], + "properties": { + "path": { + "$ref": "#/$defs/path" + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "digest": { + "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/project-resolution.v1.schema.json b/schemas/project-resolution.v1.schema.json new file mode 100644 index 0000000..b081970 --- /dev/null +++ b/schemas/project-resolution.v1.schema.json @@ -0,0 +1,380 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json", + "title": "Project Resolution", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "state", + "completeness", + "projectIdentity", + "repositoryCandidates", + "resolutionGeneration", + "resolvedSourceScopes", + "conflicts", + "metrics" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.project-resolution" + }, + "schemaVersion": { + "const": "1" + }, + "state": { + "enum": [ + "resolved", + "conflicted", + "project-entry-missing" + ] + }, + "completeness": { + "enum": [ + "exact", + "complete", + "partial" + ] + }, + "projectIdentity": { + "$ref": "#/$defs/projectIdentity" + }, + "repositoryCandidates": { + "$ref": "https://schemas.agent-semantic-protocols.dev/repository-candidate-snapshot.v1.schema.json" + }, + "resolutionGeneration": { + "type": "string", + "minLength": 1 + }, + "packageGraph": { + "$ref": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json" + }, + "resolvedSourceScopes": { + "type": "array", + "items": { + "$ref": "#/$defs/resolvedSourceScope" + } + }, + "conflicts": { + "type": "array", + "items": { + "$ref": "#/$defs/conflict" + } + }, + "metrics": { + "$ref": "#/$defs/metrics" + }, + "reasonKind": { + "enum": [ + "provider-project-entry-required", + "project-resolution-conflict", + "manifest-parse-failed", + "candidate-snapshot-unavailable" + ] + }, + "recommendedNext": { + "$ref": "#/$defs/recommendedNext" + } + }, + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "resolved" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "packageGraph" + ], + "properties": { + "completeness": { + "enum": [ + "exact", + "complete" + ] + }, + "conflicts": { + "maxItems": 0 + } + } + }, + "else": { + "required": [ + "reasonKind", + "recommendedNext" + ], + "properties": { + "completeness": { + "const": "partial" + } + } + } + }, + { + "if": { + "properties": { + "state": { + "const": "project-entry-missing" + } + }, + "required": [ + "state" + ] + }, + "then": { + "properties": { + "reasonKind": { + "const": "provider-project-entry-required" + }, + "resolvedSourceScopes": { + "maxItems": 0 + } + }, + "not": { + "required": [ + "packageGraph" + ] + } + } + }, + { + "if": { + "properties": { + "state": { + "const": "conflicted" + } + }, + "required": [ + "state" + ] + }, + "then": { + "properties": { + "conflicts": { + "minItems": 1 + } + } + } + } + ], + "$defs": { + "path": { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$" + }, + "projectIdentity": { + "type": "object", + "additionalProperties": false, + "required": [ + "projectId", + "projectInstanceId", + "projectEntry", + "languageId", + "providerId", + "parserIdentityDigest" + ], + "properties": { + "projectId": { + "type": "string", + "minLength": 1 + }, + "projectInstanceId": { + "type": "string", + "minLength": 1 + }, + "projectEntry": { + "$ref": "#/$defs/path" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "parserIdentityDigest": { + "type": "string", + "minLength": 1 + } + } + }, + "resolvedSourceScope": { + "type": "object", + "additionalProperties": false, + "required": [ + "scopeId", + "packageId", + "targetId", + "roots", + "extensions", + "includeAuthority", + "exclusions" + ], + "properties": { + "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 + }, + "extensions": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "minItems": 1, + "uniqueItems": true + }, + "includeAuthority": { + "enum": [ + "manifest-explicit", + "package-manager", + "policy-overlay" + ] + }, + "exclusions": { + "type": "array", + "items": { + "$ref": "#/$defs/exclusion" + } + }, + "classifications": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + }, + "providerFacts": { + "type": "object" + } + } + }, + "exclusion": { + "type": "object", + "additionalProperties": false, + "required": [ + "prefix", + "authority" + ], + "properties": { + "prefix": { + "$ref": "#/$defs/path" + }, + "authority": { + "type": "string", + "minLength": 1 + } + } + }, + "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", + "workspace-member-conflict" + ] + } + } + }, + "recommendedNext": { + "type": "object", + "additionalProperties": false, + "required": [ + "command" + ], + "properties": { + "command": { + "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-manifest.v1.schema.json b/schemas/provider-manifest.v1.schema.json new file mode 100644 index 0000000..4be68d4 --- /dev/null +++ b/schemas/provider-manifest.v1.schema.json @@ -0,0 +1,348 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-manifest.v1.schema.json", + "title": "ASP Provider Manifest v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "manifestId", + "manifestVersion", + "languageId", + "providerId", + "namespace", + "binary", + "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", + "minLength": 1 + }, + "namespace": { + "type": "string", + "minLength": 1 + }, + "binary": { + "type": "string", + "minLength": 1 + }, + "execution": { + "$ref": "#/$defs/providerExecution" + }, + "projectResolution": { + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-descriptor.v1.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://tao3k.github.io/agent-semantic-protocols/schemas/provider-query-pack-descriptor.v1.schema.json" + }, + "policy": { + "$ref": "#/$defs/hookPolicy" + }, + "routeBindings": { + "$ref": "#/$defs/hookRouteBindings" + } + }, + "oneOf": [ + { + "required": [ + "projectResolution" + ], + "not": { + "required": [ + "documentResolution" + ] + } + }, + { + "required": [ + "documentResolution" + ], + "not": { + "required": [ + "projectResolution" + ] + } + } + ], + "$defs": { + "stringArray": { + "type": "array", + "items": { + "type": "string" + } + }, + "providerExecution": { + "enum": [ + "external-process", + "embedded" + ] + }, + "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-project-resolution-descriptor.v1.schema.json b/schemas/provider-project-resolution-descriptor.v1.schema.json new file mode 100644 index 0000000..768780a --- /dev/null +++ b/schemas/provider-project-resolution-descriptor.v1.schema.json @@ -0,0 +1,85 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-descriptor.v1.schema.json", + "title": "Provider Project Resolution Descriptor", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "capabilityId", + "entryMarkers", + "manifestKinds", + "lockfileKinds", + "supportsGitCandidates", + "supportsProviderOnly", + "parserId", + "commandBinding", + "candidateSnapshotSchema", + "packageGraphSchema", + "resolvedSourceScopeSchema", + "projectResolutionSchema" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-project-resolution-descriptor" + }, + "schemaVersion": { + "const": "1" + }, + "capabilityId": { + "const": "project-resolution" + }, + "entryMarkers": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + }, + "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 + }, + "supportsGitCandidates": { + "const": true + }, + "supportsProviderOnly": { + "type": "boolean" + }, + "parserId": { + "type": "string", + "minLength": 1 + }, + "commandBinding": { + "const": "project-resolution-stdin" + }, + "candidateSnapshotSchema": { + "const": "https://schemas.agent-semantic-protocols.dev/repository-candidate-snapshot.v1.schema.json" + }, + "packageGraphSchema": { + "const": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json" + }, + "resolvedSourceScopeSchema": { + "const": "https://schemas.agent-semantic-protocols.dev/resolved-source-scope.v1.schema.json" + }, + "projectResolutionSchema": { + "const": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json" + } + } +} diff --git a/schemas/repository-candidate-snapshot.v1.schema.json b/schemas/repository-candidate-snapshot.v1.schema.json new file mode 100644 index 0000000..67b0302 --- /dev/null +++ b/schemas/repository-candidate-snapshot.v1.schema.json @@ -0,0 +1,203 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/repository-candidate-snapshot.v1.schema.json", + "title": "Repository Candidate Snapshot v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "mode", + "repositoryIdentity", + "worktreeIdentity", + "candidateGeneration", + "candidates", + "metrics" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.repository-candidate-snapshot" + }, + "schemaVersion": { + "const": "1" + }, + "mode": { + "const": "git" + }, + "repositoryIdentity": { + "$ref": "#/$defs/repositoryIdentity" + }, + "worktreeIdentity": { + "$ref": "#/$defs/worktreeIdentity" + }, + "candidateGeneration": { + "$ref": "#/$defs/candidateGeneration" + }, + "candidates": { + "type": "array", + "items": { + "$ref": "#/$defs/candidate" + } + }, + "metrics": { + "$ref": "#/$defs/metrics" + } + }, + "$defs": { + "path": { + "type": "string", + "minLength": 1, + "not": { + "pattern": "^(?:/|[A-Za-z]:[\\\\/])" + } + }, + "repositoryIdentity": { + "type": "object", + "additionalProperties": false, + "required": [ + "repositoryId", + "identityBasis", + "gitCommonDir" + ], + "properties": { + "repositoryId": { + "type": "string", + "minLength": 1 + }, + "identityBasis": { + "type": "string", + "minLength": 1 + }, + "gitCommonDir": { + "type": "string", + "minLength": 1 + }, + "remoteUrl": { + "type": [ + "string", + "null" + ] + } + } + }, + "worktreeIdentity": { + "type": "object", + "additionalProperties": false, + "required": [ + "worktreeId", + "worktreeRoot", + "gitDir" + ], + "properties": { + "worktreeId": { + "type": "string", + "minLength": 1 + }, + "worktreeRoot": { + "type": "string", + "minLength": 1 + }, + "gitDir": { + "type": "string", + "minLength": 1 + }, + "headId": { + "type": [ + "string", + "null" + ] + } + } + }, + "candidateGeneration": { + "type": "object", + "additionalProperties": false, + "required": [ + "algorithm", + "digest", + "authorities" + ], + "properties": { + "algorithm": { + "const": "blake3-path-set-v1" + }, + "digest": { + "type": "string", + "pattern": "^blake3:[0-9a-f]{64}$" + }, + "authorities": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "enum": [ + "git-index", + "git-worktree" + ] + } + } + } + }, + "candidate": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "state", + "authority" + ], + "properties": { + "path": { + "$ref": "#/$defs/path" + }, + "state": { + "enum": [ + "tracked", + "untracked" + ] + }, + "authority": { + "enum": [ + "git-index", + "git-worktree" + ] + } + } + }, + "metrics": { + "type": "object", + "additionalProperties": false, + "required": [ + "indexEntryCount", + "worktreeAdditionCount", + "candidateCount", + "fullWorkspaceReads", + "fullMerkleRebuilds", + "directDbOpens" + ], + "properties": { + "indexEntryCount": { + "type": "integer", + "minimum": 0 + }, + "worktreeAdditionCount": { + "type": "integer", + "minimum": 0 + }, + "candidateCount": { + "type": "integer", + "minimum": 0 + }, + "fullWorkspaceReads": { + "const": 0 + }, + "fullMerkleRebuilds": { + "const": 0 + }, + "directDbOpens": { + "const": 0 + } + } + } + } +} diff --git a/schemas/resolved-source-scope.v1.schema.json b/schemas/resolved-source-scope.v1.schema.json new file mode 100644 index 0000000..50a21bd --- /dev/null +++ b/schemas/resolved-source-scope.v1.schema.json @@ -0,0 +1,188 @@ +{ + "$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", + "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" + ] + } + } + } + } +} From b9042fe6dc349722a9397e4a2ee68f6c4f7b4f2e Mon Sep 17 00:00:00 2001 From: guangtao Date: Sat, 8 Aug 2026 23:33:05 -0700 Subject: [PATCH 09/20] feat: align ASP provider projection contracts --- docs/03_features/202_runner_modes.md | 4 +- docs/03_features/204_pytest.md | 2 +- provider/asp-provider-manifest.json | 90 ++-- pyproject.toml | 3 +- ...allable-skeleton-projection.v1.schema.json | 173 ++++++++ .../canonical-item-selector.v1.schema.json | 44 ++ .../exact-structural-selector.v1.schema.json | 94 +++++ schemas/language-package-graph.v1.schema.json | 36 +- schemas/project-resolution.v1.schema.json | 391 ++--------------- ...ument-resolution-descriptor.v1.schema.json | 42 ++ schemas/provider-manifest.v1.schema.json | 67 +++ ...ovider-native-exact-request.v1.schema.json | 83 ++++ ...vider-native-exact-response.v1.schema.json | 399 ++++++++++++++++++ ...oject-resolution-descriptor.v1.schema.json | 92 +--- ...-project-resolution-request.v1.schema.json | 60 +++ ...project-resolution-response.v1.schema.json | 36 ++ ...vider-query-pack-descriptor.v1.schema.json | 170 ++++++++ ...ython-semantic-capabilities.v1.schema.json | 1 - ...pository-candidate-snapshot.v1.schema.json | 203 --------- schemas/resolved-source-scope.v1.schema.json | 1 + .../semantic-assurance-case.v1.schema.json | 102 +---- .../semantic-ast-patch-receipt.v1.schema.json | 16 +- schemas/semantic-ast-patch.v1.schema.json | 16 +- .../semantic-codeql-evidence.v1.schema.json | 63 +-- ...semantic-content-compaction.v1.schema.json | 25 +- ...emantic-dependency-topology.v1.schema.json | 16 +- ...antic-determinism-readiness.v1.schema.json | 21 +- .../semantic-evidence-graph.v1.schema.json | 95 +---- schemas/semantic-fact-graph.v1.schema.json | 81 +--- schemas/semantic-fact-ontology.v1.schema.json | 118 +----- schemas/semantic-flow-lite.v1.schema.json | 106 +---- ...semantic-formal-proof-pilot.v1.schema.json | 21 +- ...emantic-graph-turbo-request.v1.schema.json | 53 ++- schemas/semantic-handle.v1.schema.json | 52 +-- ...emantic-invariant-candidate.v1.schema.json | 225 ++++++++++ ...emantic-language-projection.v1.schema.json | 57 ++- .../semantic-language-registry.v1.schema.json | 33 ++ ...ic-native-syntax-fact-index.v1.schema.json | 19 +- schemas/semantic-query-packet.v1.schema.json | 21 +- schemas/semantic-read-packet.v1.schema.json | 19 +- schemas/semantic-relation-plan.v1.schema.json | 90 +--- schemas/semantic-review-packet.v1.schema.json | 65 +-- schemas/semantic-search-packet.v1.schema.json | 76 ++-- .../semantic-source-location.v1.schema.json | 34 +- schemas/semantic-type-surface.v1.schema.json | 16 +- .../source-snapshot-evidence.v1.schema.json | 66 +++ .../_agent_snapshot.py | 4 +- .../_agent_snapshot_tree.py | 4 +- .../_callable_skeleton_projection.py | 202 +++++++++ src/python_lang_project_harness/_cli.py | 44 +- src/python_lang_project_harness/_cli_agent.py | 40 +- src/python_lang_project_harness/_cli_args.py | 27 +- .../_cli_ast_patch.py | 5 +- .../_cli_protocol.py | 29 +- src/python_lang_project_harness/_cli_query.py | 91 +--- .../_cli_query_arg_consume.py | 20 +- .../_cli_query_args.py | 44 +- .../_cli_query_flow_lite_args.py | 7 - .../_cli_query_hook_args.py | 2 +- .../_cli_query_tree_sitter_args.py | 5 - .../_cli_search_runtime.py | 28 +- .../_dependency_topology.py | 183 ++++++++ .../_exact_projection_model.py | 120 ++++++ .../_exact_source_projection.py | 180 ++++++++ src/python_lang_project_harness/_model.py | 8 +- .../_owner_search_stdin.py | 285 +++++++++++++ .../_project_evaluation.py | 4 +- .../_project_resolution.py | 143 +++++++ .../_project_resolution_backends.py | 121 ++++++ .../_project_resolution_candidates.py | 41 ++ .../_project_resolution_document.py | 125 ++++++ .../_project_resolution_graph.py | 205 +++++++++ .../_project_resolution_sources.py | 209 +++++++++ .../_projection_batch.py | 190 +++++++++ src/python_lang_project_harness/_render.py | 20 +- src/python_lang_project_harness/_runner.py | 2 +- .../_semantic_graph_facts.py | 1 - .../_semantic_language.py | 63 ++- .../_semantic_language_catalog.py | 7 - .../_semantic_language_ids.py | 1 - .../_semantic_language_invocation.py | 5 + .../_semantic_language_query.py | 45 +- .../_semantic_language_schemas.py | 5 - .../_semantic_query_packet.py | 5 +- .../_semantic_search_cli.py | 31 +- .../_semantic_search_ingest_fast.py | 1 - .../_semantic_search_items.py | 4 +- .../_semantic_search_lexical_fast.py | 1 - .../_semantic_search_owner_fast.py | 1 - .../_semantic_search_packages.py | 4 +- .../_semantic_search_prime_fast.py | 1 - .../_test_layout.py | 6 +- .../_tree_sitter_query.py | 8 +- .../_workspace_scope.py | 173 -------- .../verification/facts.py | 14 +- .../harness/project_policy/test_layout.py | 4 +- tests/unit/harness/test_cli.py | 34 +- .../test_cli_query_names_only_route.py | 31 -- .../unit/harness/test_dependency_topology.py | 85 ++++ .../harness/test_dependency_topology_cli.py | 67 +++ .../harness/test_exact_source_projection.py | 123 ++++++ tests/unit/harness/test_owner_search_stdin.py | 163 +++++++ .../harness/test_parser_boundary_contract.py | 6 +- tests/unit/harness/test_policy_contract.py | 4 +- tests/unit/harness/test_project_api.py | 18 +- tests/unit/harness/test_project_resolution.py | 239 +++++++++++ ...=> test_project_resolution_extra_paths.py} | 4 +- tests/unit/harness/test_projection_batch.py | 55 +++ .../harness/test_pyproject_package_scope.py | 9 +- tests/unit/harness/test_pytest.py | 2 +- tests/unit/harness/test_render_snapshots.py | 6 +- tests/unit/harness/test_runner_config.py | 20 +- tests/unit/harness/test_semantic_cli.py | 287 +------------ .../test_semantic_cli_benchmark_registry.py | 1 + ...est_semantic_cli_compact_query_snapshot.py | 169 -------- .../harness/test_semantic_cli_direct_read.py | 50 --- .../test_semantic_cli_flow_lite_query.py | 152 ------- ...t_semantic_cli_large_descriptor_compact.py | 200 --------- ...test_semantic_cli_owner_item_projection.py | 99 ----- .../harness/test_semantic_cli_owner_items.py | 286 ------------- .../test_semantic_cli_query_set_core.py | 141 ------- .../harness/test_semantic_cli_query_view.py | 35 -- .../test_semantic_cli_selector_roundtrip.py | 41 -- ...mantic_cli_structural_selector_registry.py | 8 +- .../test_semantic_cli_tree_sitter_query.py | 284 ------------- .../test_semantic_cli_tree_sitter_selector.py | 93 ---- .../harness/test_semantic_provider_doctor.py | 13 + .../test_semantic_search_graph_profiles.py | 16 + tests/unit/harness/test_workspace_scope.py | 122 ------ .../python_project_harness_json.snap | 2 +- uv.lock | 68 ++- 131 files changed, 4961 insertions(+), 4237 deletions(-) create mode 100644 schemas/callable-skeleton-projection.v1.schema.json create mode 100644 schemas/canonical-item-selector.v1.schema.json create mode 100644 schemas/exact-structural-selector.v1.schema.json create mode 100644 schemas/provider-document-resolution-descriptor.v1.schema.json create mode 100644 schemas/provider-native-exact-request.v1.schema.json create mode 100644 schemas/provider-native-exact-response.v1.schema.json create mode 100644 schemas/provider-project-resolution-request.v1.schema.json create mode 100644 schemas/provider-project-resolution-response.v1.schema.json create mode 100644 schemas/provider-query-pack-descriptor.v1.schema.json delete mode 100644 schemas/repository-candidate-snapshot.v1.schema.json create mode 100644 schemas/semantic-invariant-candidate.v1.schema.json create mode 100644 schemas/source-snapshot-evidence.v1.schema.json create mode 100644 src/python_lang_project_harness/_callable_skeleton_projection.py create mode 100644 src/python_lang_project_harness/_dependency_topology.py create mode 100644 src/python_lang_project_harness/_exact_projection_model.py create mode 100644 src/python_lang_project_harness/_exact_source_projection.py create mode 100644 src/python_lang_project_harness/_owner_search_stdin.py create mode 100644 src/python_lang_project_harness/_project_resolution.py create mode 100644 src/python_lang_project_harness/_project_resolution_backends.py create mode 100644 src/python_lang_project_harness/_project_resolution_candidates.py create mode 100644 src/python_lang_project_harness/_project_resolution_document.py create mode 100644 src/python_lang_project_harness/_project_resolution_graph.py create mode 100644 src/python_lang_project_harness/_project_resolution_sources.py create mode 100644 src/python_lang_project_harness/_projection_batch.py delete mode 100644 src/python_lang_project_harness/_workspace_scope.py delete mode 100644 tests/unit/harness/test_cli_query_names_only_route.py create mode 100644 tests/unit/harness/test_dependency_topology.py create mode 100644 tests/unit/harness/test_dependency_topology_cli.py create mode 100644 tests/unit/harness/test_exact_source_projection.py create mode 100644 tests/unit/harness/test_owner_search_stdin.py create mode 100644 tests/unit/harness/test_project_resolution.py rename tests/unit/harness/{test_project_scope_extra_paths.py => test_project_resolution_extra_paths.py} (92%) create mode 100644 tests/unit/harness/test_projection_batch.py delete mode 100644 tests/unit/harness/test_semantic_cli_compact_query_snapshot.py delete mode 100644 tests/unit/harness/test_semantic_cli_direct_read.py delete mode 100644 tests/unit/harness/test_semantic_cli_flow_lite_query.py delete mode 100644 tests/unit/harness/test_semantic_cli_large_descriptor_compact.py delete mode 100644 tests/unit/harness/test_semantic_cli_owner_item_projection.py delete mode 100644 tests/unit/harness/test_semantic_cli_owner_items.py delete mode 100644 tests/unit/harness/test_semantic_cli_query_set_core.py delete mode 100644 tests/unit/harness/test_semantic_cli_query_view.py delete mode 100644 tests/unit/harness/test_semantic_cli_selector_roundtrip.py delete mode 100644 tests/unit/harness/test_semantic_cli_tree_sitter_query.py delete mode 100644 tests/unit/harness/test_semantic_cli_tree_sitter_selector.py delete mode 100644 tests/unit/harness/test_workspace_scope.py 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/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 index 35b80f2..c3d2f5b 100644 --- a/provider/asp-provider-manifest.json +++ b/provider/asp-provider-manifest.json @@ -9,41 +9,15 @@ "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" - ] + "development": { + "schemaId": "agent.semantic-protocols.provider-development-descriptor", + "schemaVersion": "1", + "sourceRoot": "languages/python-lang-project-harness", + "buildBinding": "provider-workspace-install-v1", + "workspaceInstall": "provider/asp-provider-workspace-install.json", + "artifactDomain": "checkout" }, + "execution": "external-process", "searchCapabilities": { "sourceSnapshot": { "descriptorId": "python.source-snapshot", @@ -61,9 +35,8 @@ }, "ownerItems": true, "semanticFacts": true, - "dependencyTopology": false, - "dependencyTopologyMetadata": false, - "workspaceScope": true + "dependencyTopology": true, + "dependencyTopologyMetadata": false }, "semanticFactsDescriptor": { "descriptorId": "python.semantic-facts", @@ -97,6 +70,45 @@ "rawSourceSearch": "block", "agentSearchJson": "block" }, + "languageProjection": { + "schemaId": "agent.semantic-protocols.provider-language-projection-descriptor", + "schemaVersion": "1", + "commandBinding": "projection-batch-stdin", + "transport": "framed-stdin-v1", + "requestSchema": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.v1.schema.json", + "responseSchema": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.v1.schema.json", + "identitySchema": "https://schemas.agent-semantic-protocols.dev/canonical-language-item-identity.v1.schema.json" + }, + "projectResolution": { + "schemaId": "agent.semantic-protocols.provider-project-resolution-descriptor", + "schemaVersion": "1", + "capabilityId": "project-resolution", + "entryMarkers": [ + "pyproject.toml" + ], + "sourceExtensions": [ + ".py", + ".pyi" + ], + "manifestKinds": [ + "pep-621", + "uv-workspace", + "setuptools", + "hatch", + "poetry" + ], + "lockfileKinds": [ + "uv-lock", + "poetry-lock", + "pdm-lock" + ], + "parserId": "python.pyproject-toml", + "commandBinding": "project-resolution-stdin", + "requestSchema": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.v1.schema.json", + "responseSchema": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.v1.schema.json", + "packageGraphSchema": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json", + "projectResolutionSchema": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json" + }, "queryPackDescriptor": { "descriptorId": "python.query-pack", "descriptorVersion": "1", @@ -190,8 +202,10 @@ "owner": "search/owner", "lexical": "search/lexical", "query": "query/exact-selector", + "exactSelectorNative": "query/exact-selector-native-v1", "ingest": "search/ingest", "checkChanged": "check/changed", - "guide": "guide" + "guide": "guide", + "dependencyTopology": "search/dependency-topology" } } diff --git a/pyproject.toml b/pyproject.toml index f6ffca9..004c059 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 = [ @@ -46,6 +46,7 @@ packages = [ package = true [tool.pytest.ini_options] +pythonpath = ["src"] addopts = [ "--python-project-harness", ] diff --git a/schemas/callable-skeleton-projection.v1.schema.json b/schemas/callable-skeleton-projection.v1.schema.json new file mode 100644 index 0000000..b62c05c --- /dev/null +++ b/schemas/callable-skeleton-projection.v1.schema.json @@ -0,0 +1,173 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/callable-skeleton-projection.v1.schema.json", + "title": "Callable Skeleton Projection V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "projectionKind", + "languageId", + "providerId", + "rootSelector", + "rootNodeId", + "callable", + "nodes", + "relations", + "cost" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.callable-skeleton-projection" + }, + "schemaVersion": { + "const": "1" + }, + "projectionKind": { + "const": "callable-skeleton" + }, + "languageId": { + "type": "string", + "minLength": 1 + }, + "providerId": { + "type": "string", + "minLength": 1 + }, + "rootSelector": { + "$ref": "exact-structural-selector.v1.schema.json" + }, + "rootNodeId": { + "type": "string", + "minLength": 1 + }, + "callable": { + "$ref": "#/$defs/callable" + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/node" + } + }, + "relations": { + "type": "array", + "items": { + "$ref": "#/$defs/relation" + } + }, + "cost": { + "$ref": "#/$defs/cost" + }, + "omissionReasons": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + }, + "languageFacts": { + "type": "object", + "additionalProperties": true + } + }, + "$defs": { + "callable": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "displayName", "signature"], + "properties": { + "kind": {"type": "string", "minLength": 1}, + "displayName": {"type": "string", "minLength": 1}, + "signature": {"type": "string"} + } + }, + "node": { + "type": "object", + "additionalProperties": false, + "required": ["nodeId", "kind", "label", "order", "queryable"], + "properties": { + "nodeId": {"type": "string", "minLength": 1}, + "kind": { + "enum": [ + "callable", + "branch", + "arm", + "loop", + "exception", + "resource-scope", + "invocation", + "binding", + "exit", + "suspension", + "nested-declaration", + "language-extension" + ] + }, + "label": {"type": "string"}, + "order": {"type": "integer", "minimum": 0}, + "queryable": {"type": "boolean"}, + "exactSelector": { + "$ref": "exact-structural-selector.v1.schema.json" + }, + "sourceLocatorHint": { + "$ref": "#/$defs/sourceLocatorHint" + }, + "languageFacts": { + "type": "object", + "additionalProperties": true + } + }, + "allOf": [ + { + "if": { + "properties": {"queryable": {"const": true}}, + "required": ["queryable"] + }, + "then": {"required": ["exactSelector"]}, + "else": {"not": {"required": ["exactSelector"]}} + } + ] + }, + "relation": { + "type": "object", + "additionalProperties": false, + "required": ["fromNodeId", "toNodeId", "kind"], + "properties": { + "fromNodeId": {"type": "string", "minLength": 1}, + "toNodeId": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1} + } + }, + "cost": { + "type": "object", + "additionalProperties": false, + "required": ["sourceBytes", "projectedBytes", "omittedBytes"], + "properties": { + "sourceBytes": {"type": "integer", "minimum": 0}, + "projectedBytes": {"type": "integer", "minimum": 0}, + "omittedBytes": {"type": "integer", "minimum": 0}, + "estimatedSourceTokens": {"type": "integer", "minimum": 0}, + "estimatedProjectedTokens": {"type": "integer", "minimum": 0}, + "tokenEstimator": {"type": "string", "minLength": 1} + }, + "dependentRequired": { + "estimatedSourceTokens": ["estimatedProjectedTokens", "tokenEstimator"], + "estimatedProjectedTokens": ["estimatedSourceTokens", "tokenEstimator"], + "tokenEstimator": ["estimatedSourceTokens", "estimatedProjectedTokens"] + } + }, + "sourceLocatorHint": { + "type": "object", + "additionalProperties": false, + "properties": { + "displayLineStart": {"type": "integer", "minimum": 0}, + "displayLineEnd": {"type": "integer", "minimum": 0}, + "sourceByteStart": {"type": "integer", "minimum": 0}, + "sourceByteEnd": {"type": "integer", "minimum": 0} + } + } + } +} diff --git a/schemas/canonical-item-selector.v1.schema.json b/schemas/canonical-item-selector.v1.schema.json new file mode 100644 index 0000000..af4259a --- /dev/null +++ b/schemas/canonical-item-selector.v1.schema.json @@ -0,0 +1,44 @@ +{ + "$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", + "minLength": 1 + } + }, + "$defs": {} +} 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.v1.schema.json b/schemas/language-package-graph.v1.schema.json index b3ab077..a291a60 100644 --- a/schemas/language-package-graph.v1.schema.json +++ b/schemas/language-package-graph.v1.schema.json @@ -41,16 +41,10 @@ "minLength": 1 }, "manifests": { - "type": "array", - "items": { - "$ref": "#/$defs/manifest" - } + "$ref": "#/$defs/lockfileList" }, "lockfiles": { - "type": "array", - "items": { - "$ref": "#/$defs/lockfile" - } + "$ref": "#/$defs/lockfileList" }, "packages": { "type": "array", @@ -78,32 +72,14 @@ } }, "$defs": { + "lockfileList": { + "type": "array", + "items": { "$ref": "#/$defs/lockfile" } + }, "path": { "type": "string", "minLength": 1 }, - "manifest": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "kind", - "digest" - ], - "properties": { - "path": { - "$ref": "#/$defs/path" - }, - "kind": { - "type": "string", - "minLength": 1 - }, - "digest": { - "type": "string", - "minLength": 1 - } - } - }, "lockfile": { "type": "object", "additionalProperties": false, diff --git a/schemas/project-resolution.v1.schema.json b/schemas/project-resolution.v1.schema.json index b081970..937a0fc 100644 --- a/schemas/project-resolution.v1.schema.json +++ b/schemas/project-resolution.v1.schema.json @@ -1,379 +1,58 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json", - "title": "Project Resolution", + "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", - "projectIdentity", - "repositoryCandidates", - "resolutionGeneration", - "resolvedSourceScopes", - "conflicts", - "metrics" - ], + "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": { - "enum": [ - "resolved", - "conflicted", - "project-entry-missing" - ] - }, - "completeness": { - "enum": [ - "exact", - "complete", - "partial" - ] - }, - "projectIdentity": { - "$ref": "#/$defs/projectIdentity" - }, - "repositoryCandidates": { - "$ref": "https://schemas.agent-semantic-protocols.dev/repository-candidate-snapshot.v1.schema.json" - }, - "resolutionGeneration": { - "type": "string", - "minLength": 1 - }, - "packageGraph": { - "$ref": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json" - }, - "resolvedSourceScopes": { - "type": "array", - "items": { - "$ref": "#/$defs/resolvedSourceScope" - } - }, - "conflicts": { + "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.v1.schema.json" }, + "sourceScopes": { + "description": "Package and target scopes derived by the provider from this project entry; not workspace identities.", "type": "array", - "items": { - "$ref": "#/$defs/conflict" - } - }, - "metrics": { - "$ref": "#/$defs/metrics" + "items": { "$ref": "https://schemas.agent-semantic-protocols.dev/resolved-source-scope.v1.schema.json" } }, - "reasonKind": { - "enum": [ - "provider-project-entry-required", - "project-resolution-conflict", - "manifest-parse-failed", - "candidate-snapshot-unavailable" - ] - }, - "recommendedNext": { - "$ref": "#/$defs/recommendedNext" - } + "conflicts": { "type": "array", "items": { "$ref": "#/$defs/conflict" } }, + "metrics": { "$ref": "#/$defs/metrics" } }, - "allOf": [ - { - "if": { - "properties": { - "state": { - "const": "resolved" - } - }, - "required": [ - "state" - ] - }, - "then": { - "required": [ - "packageGraph" - ], - "properties": { - "completeness": { - "enum": [ - "exact", - "complete" - ] - }, - "conflicts": { - "maxItems": 0 - } - } - }, - "else": { - "required": [ - "reasonKind", - "recommendedNext" - ], - "properties": { - "completeness": { - "const": "partial" - } - } - } - }, - { - "if": { - "properties": { - "state": { - "const": "project-entry-missing" - } - }, - "required": [ - "state" - ] - }, - "then": { - "properties": { - "reasonKind": { - "const": "provider-project-entry-required" - }, - "resolvedSourceScopes": { - "maxItems": 0 - } - }, - "not": { - "required": [ - "packageGraph" - ] - } - } - }, - { - "if": { - "properties": { - "state": { - "const": "conflicted" - } - }, - "required": [ - "state" - ] - }, - "then": { - "properties": { - "conflicts": { - "minItems": 1 - } - } - } - } - ], "$defs": { - "path": { - "type": "string", - "minLength": 1, - "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$" - }, - "projectIdentity": { - "type": "object", - "additionalProperties": false, - "required": [ - "projectId", - "projectInstanceId", - "projectEntry", - "languageId", - "providerId", - "parserIdentityDigest" - ], - "properties": { - "projectId": { - "type": "string", - "minLength": 1 - }, - "projectInstanceId": { - "type": "string", - "minLength": 1 - }, - "projectEntry": { - "$ref": "#/$defs/path" - }, - "languageId": { - "type": "string", - "minLength": 1 - }, - "providerId": { - "type": "string", - "minLength": 1 - }, - "parserIdentityDigest": { - "type": "string", - "minLength": 1 - } - } - }, - "resolvedSourceScope": { - "type": "object", - "additionalProperties": false, - "required": [ - "scopeId", - "packageId", - "targetId", - "roots", - "extensions", - "includeAuthority", - "exclusions" - ], - "properties": { - "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 - }, - "extensions": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "minItems": 1, - "uniqueItems": true - }, - "includeAuthority": { - "enum": [ - "manifest-explicit", - "package-manager", - "policy-overlay" - ] - }, - "exclusions": { - "type": "array", - "items": { - "$ref": "#/$defs/exclusion" - } - }, - "classifications": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "providerFacts": { - "type": "object" - } - } - }, - "exclusion": { - "type": "object", - "additionalProperties": false, - "required": [ - "prefix", - "authority" - ], - "properties": { - "prefix": { - "$ref": "#/$defs/path" - }, - "authority": { - "type": "string", - "minLength": 1 - } - } - }, + "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": { - "enum": [ - "explicit-source-excluded", - "scope-layer-conflict", - "target-path-missing", - "workspace-member-conflict" - ] - } - } - }, - "recommendedNext": { - "type": "object", - "additionalProperties": false, - "required": [ - "command" - ], + "required": ["path", "includeAuthority", "excludeAuthority", "reasonKind"], "properties": { - "command": { - "type": "string", - "minLength": 1 - } + "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" - ], + "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 - } + "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-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-manifest.v1.schema.json b/schemas/provider-manifest.v1.schema.json index 4be68d4..c5e47e6 100644 --- a/schemas/provider-manifest.v1.schema.json +++ b/schemas/provider-manifest.v1.schema.json @@ -15,6 +15,7 @@ "providerId", "namespace", "binary", + "development", "searchCapabilities", "queryPackDescriptor", "policy", @@ -62,6 +63,9 @@ "execution": { "$ref": "#/$defs/providerExecution" }, + "development": { + "$ref": "#/$defs/providerDevelopmentDescriptor" + }, "projectResolution": { "$ref": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-descriptor.v1.schema.json" }, @@ -119,6 +123,69 @@ "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, 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..9155dfe --- /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-projection.v1.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.v1.schema.json b/schemas/provider-project-resolution-descriptor.v1.schema.json index 768780a..9998ebf 100644 --- a/schemas/provider-project-resolution-descriptor.v1.schema.json +++ b/schemas/provider-project-resolution-descriptor.v1.schema.json @@ -1,85 +1,23 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-descriptor.v1.schema.json", - "title": "Provider Project Resolution Descriptor", + "title": "Provider Project Scope Descriptor", "type": "object", "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "capabilityId", - "entryMarkers", - "manifestKinds", - "lockfileKinds", - "supportsGitCandidates", - "supportsProviderOnly", - "parserId", - "commandBinding", - "candidateSnapshotSchema", - "packageGraphSchema", - "resolvedSourceScopeSchema", - "projectResolutionSchema" - ], + "required": ["schemaId", "schemaVersion", "capabilityId", "entryMarkers", "sourceExtensions", "manifestKinds", "lockfileKinds", "parserId", "commandBinding", "requestSchema", "responseSchema", "packageGraphSchema", "projectResolutionSchema"], "properties": { - "schemaId": { - "const": "agent.semantic-protocols.provider-project-resolution-descriptor" - }, - "schemaVersion": { - "const": "1" - }, - "capabilityId": { - "const": "project-resolution" - }, - "entryMarkers": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "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 - }, - "supportsGitCandidates": { - "const": true - }, - "supportsProviderOnly": { - "type": "boolean" - }, - "parserId": { - "type": "string", - "minLength": 1 - }, - "commandBinding": { - "const": "project-resolution-stdin" - }, - "candidateSnapshotSchema": { - "const": "https://schemas.agent-semantic-protocols.dev/repository-candidate-snapshot.v1.schema.json" - }, - "packageGraphSchema": { - "const": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json" - }, - "resolvedSourceScopeSchema": { - "const": "https://schemas.agent-semantic-protocols.dev/resolved-source-scope.v1.schema.json" - }, - "projectResolutionSchema": { - "const": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json" - } + "schemaId": { "const": "agent.semantic-protocols.provider-project-resolution-descriptor" }, + "schemaVersion": { "const": "1" }, + "capabilityId": { "const": "project-resolution" }, + "entryMarkers": { "type": "array", "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 }, + "commandBinding": { "const": "project-resolution-stdin" }, + "requestSchema": { "const": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.v1.schema.json" }, + "responseSchema": { "const": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.v1.schema.json" }, + "packageGraphSchema": { "const": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json" }, + "projectResolutionSchema": { "const": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json" } } } diff --git a/schemas/provider-project-resolution-request.v1.schema.json b/schemas/provider-project-resolution-request.v1.schema.json new file mode 100644 index 0000000..bddbcd1 --- /dev/null +++ b/schemas/provider-project-resolution-request.v1.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.v1.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.v1.schema.json b/schemas/provider-project-resolution-response.v1.schema.json new file mode 100644 index 0000000..9a5237f --- /dev/null +++ b/schemas/provider-project-resolution-response.v1.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.v1.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"] }, + "scope": { "$ref": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json" }, + "failure": { "$ref": "#/$defs/failure" } + }, + "allOf": [ + { + "if": { "properties": { "state": { "const": "resolved" } }, "required": ["state"] }, + "then": { "required": ["scope"], "not": { "required": ["failure"] } }, + "else": { "required": ["failure"], "not": { "required": ["scope"] } } + } + ], + "$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.v1.schema.json b/schemas/provider-query-pack-descriptor.v1.schema.json new file mode 100644 index 0000000..ba35132 --- /dev/null +++ b/schemas/provider-query-pack-descriptor.v1.schema.json @@ -0,0 +1,170 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-query-pack-descriptor.v1.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", + "minItems": 1, + "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/python-semantic-capabilities.v1.schema.json b/schemas/python-semantic-capabilities.v1.schema.json index 890df76..f1673a9 100644 --- a/schemas/python-semantic-capabilities.v1.schema.json +++ b/schemas/python-semantic-capabilities.v1.schema.json @@ -46,7 +46,6 @@ "workspace-router", "workspace-candidate-admission", "python-package-root-search", - "python-package-manager-workspace-scope", "package-prime-map", "python-reasoning-tree-prime", "graph-turbo-provider-facts", diff --git a/schemas/repository-candidate-snapshot.v1.schema.json b/schemas/repository-candidate-snapshot.v1.schema.json deleted file mode 100644 index 67b0302..0000000 --- a/schemas/repository-candidate-snapshot.v1.schema.json +++ /dev/null @@ -1,203 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.agent-semantic-protocols.dev/repository-candidate-snapshot.v1.schema.json", - "title": "Repository Candidate Snapshot v1", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "mode", - "repositoryIdentity", - "worktreeIdentity", - "candidateGeneration", - "candidates", - "metrics" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.repository-candidate-snapshot" - }, - "schemaVersion": { - "const": "1" - }, - "mode": { - "const": "git" - }, - "repositoryIdentity": { - "$ref": "#/$defs/repositoryIdentity" - }, - "worktreeIdentity": { - "$ref": "#/$defs/worktreeIdentity" - }, - "candidateGeneration": { - "$ref": "#/$defs/candidateGeneration" - }, - "candidates": { - "type": "array", - "items": { - "$ref": "#/$defs/candidate" - } - }, - "metrics": { - "$ref": "#/$defs/metrics" - } - }, - "$defs": { - "path": { - "type": "string", - "minLength": 1, - "not": { - "pattern": "^(?:/|[A-Za-z]:[\\\\/])" - } - }, - "repositoryIdentity": { - "type": "object", - "additionalProperties": false, - "required": [ - "repositoryId", - "identityBasis", - "gitCommonDir" - ], - "properties": { - "repositoryId": { - "type": "string", - "minLength": 1 - }, - "identityBasis": { - "type": "string", - "minLength": 1 - }, - "gitCommonDir": { - "type": "string", - "minLength": 1 - }, - "remoteUrl": { - "type": [ - "string", - "null" - ] - } - } - }, - "worktreeIdentity": { - "type": "object", - "additionalProperties": false, - "required": [ - "worktreeId", - "worktreeRoot", - "gitDir" - ], - "properties": { - "worktreeId": { - "type": "string", - "minLength": 1 - }, - "worktreeRoot": { - "type": "string", - "minLength": 1 - }, - "gitDir": { - "type": "string", - "minLength": 1 - }, - "headId": { - "type": [ - "string", - "null" - ] - } - } - }, - "candidateGeneration": { - "type": "object", - "additionalProperties": false, - "required": [ - "algorithm", - "digest", - "authorities" - ], - "properties": { - "algorithm": { - "const": "blake3-path-set-v1" - }, - "digest": { - "type": "string", - "pattern": "^blake3:[0-9a-f]{64}$" - }, - "authorities": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": [ - "git-index", - "git-worktree" - ] - } - } - } - }, - "candidate": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "state", - "authority" - ], - "properties": { - "path": { - "$ref": "#/$defs/path" - }, - "state": { - "enum": [ - "tracked", - "untracked" - ] - }, - "authority": { - "enum": [ - "git-index", - "git-worktree" - ] - } - } - }, - "metrics": { - "type": "object", - "additionalProperties": false, - "required": [ - "indexEntryCount", - "worktreeAdditionCount", - "candidateCount", - "fullWorkspaceReads", - "fullMerkleRebuilds", - "directDbOpens" - ], - "properties": { - "indexEntryCount": { - "type": "integer", - "minimum": 0 - }, - "worktreeAdditionCount": { - "type": "integer", - "minimum": 0 - }, - "candidateCount": { - "type": "integer", - "minimum": 0 - }, - "fullWorkspaceReads": { - "const": 0 - }, - "fullMerkleRebuilds": { - "const": 0 - }, - "directDbOpens": { - "const": 0 - } - } - } - } -} diff --git a/schemas/resolved-source-scope.v1.schema.json b/schemas/resolved-source-scope.v1.schema.json index 50a21bd..e178a33 100644 --- a/schemas/resolved-source-scope.v1.schema.json +++ b/schemas/resolved-source-scope.v1.schema.json @@ -9,6 +9,7 @@ "packageId", "targetId", "roots", + "explicitPaths", "extensions", "includeAuthority", "exclusions" 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-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-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-graph.v1.schema.json b/schemas/semantic-fact-graph.v1.schema.json index 3d2d244..5a8f2e5 100644 --- a/schemas/semantic-fact-graph.v1.schema.json +++ b/schemas/semantic-fact-graph.v1.schema.json @@ -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-request.v1.schema.json b/schemas/semantic-graph-turbo-request.v1.schema.json index e8f2fbe..049b297 100644 --- a/schemas/semantic-graph-turbo-request.v1.schema.json +++ b/schemas/semantic-graph-turbo-request.v1.schema.json @@ -13,6 +13,7 @@ "packetKind", "surface", "sourceSnapshot", + "workspaceGeneration", "queryTerms", "profile", "algorithm", @@ -98,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" @@ -145,6 +131,9 @@ "sourceSnapshot": { "$ref": "https://agent-semantic-protocols.dev/schemas/source-snapshot-evidence.v1.schema.json#/$defs/sourceSnapshot" }, + "workspaceGeneration": { + "$ref": "#/$defs/workspaceGenerationEvidence" + }, "candidateSources": { "type": "array", "items": { @@ -319,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, @@ -837,6 +854,7 @@ "enum": [ "item-skeleton", "query-code", + "lexical-search", "fd-query", "rg-query", "owner-items", @@ -850,6 +868,7 @@ "capabilityId": { "enum": [ "query", + "lexical-search", "fd", "rg", "owner-items", @@ -1112,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 1c5db02..8c480a4 100644 --- a/schemas/semantic-language-registry.v1.schema.json +++ b/schemas/semantic-language-registry.v1.schema.json @@ -236,6 +236,9 @@ "invocation": { "$ref": "#/$defs/commandTemplate" }, + "argumentProjection": { + "$ref": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-method-argument-projection.v1.schema.json" + }, "benchmarkInvocation": { "$ref": "#/$defs/benchmarkInvocation" }, @@ -246,6 +249,36 @@ "type": "boolean" } }, + "allOf": [ + { + "if": { + "properties": { + "argumentProjection": { + "properties": { + "tokens": { + "contains": { + "type": "object", + "required": ["kind", "name"], + "properties": { + "kind": {"const": "slot"}, + "name": {"const": "owner"} + } + } + } + } + } + }, + "required": ["argumentProjection"] + }, + "then": { + "properties": { + "method": { + "pattern": "^search/owner(?:$|-)" + } + } + } + } + ], "additionalProperties": true } } diff --git a/schemas/semantic-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-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-source-location.v1.schema.json b/schemas/semantic-source-location.v1.schema.json index d2aed61..2d3b7f0 100644 --- a/schemas/semantic-source-location.v1.schema.json +++ b/schemas/semantic-source-location.v1.schema.json @@ -62,34 +62,10 @@ "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_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..c0aa065 --- /dev/null +++ b/src/python_lang_project_harness/_callable_skeleton_projection.py @@ -0,0 +1,202 @@ +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, + SKELETON_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, + "exactSelector": root_exact, + "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, + "exactSelector": exact_selector(request, selector, segment), + "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 { + "schemaId": SKELETON_SCHEMA_ID, + "schemaVersion": "1", + "projectionKind": "callable-skeleton", + "languageId": "python", + "providerId": "py-harness", + "rootSelector": root_exact, + "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..6dc9c48 100644 --- a/src/python_lang_project_harness/_cli.py +++ b/src/python_lang_project_harness/_cli.py @@ -18,7 +18,15 @@ def run_cli_from_env() -> int: args = sys.argv[1:] log = start_dev_command_log(args, Path.cwd()) try: - stdin = "" if sys.stdin.isatty() else sys.stdin.read() + stdin = ( + b"" + if sys.stdin.isatty() and args[:1] == ["projection-batch-stdin"] + else ( + sys.stdin.buffer.read() + if args[:1] == ["projection-batch-stdin"] + else ("" if sys.stdin.isatty() else sys.stdin.read()) + ) + ) exit_code = run_cli(args, stdin=stdin) log.finish(exit_code) return exit_code @@ -32,7 +40,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.""" @@ -40,6 +48,38 @@ def run_cli( selected_stdout = sys.stdout if stdout is None else stdout selected_stderr = sys.stderr if stderr is None else stderr selected_cwd = Path.cwd() if cwd is None else cwd + selected_stdin = "" if stdin is None else stdin + from ._exact_source_projection import try_run_provider_native_exact + from ._owner_search_stdin import try_run_provider_native_owner + + native_owner_exit = try_run_provider_native_owner( + args, + stdin=selected_stdin, + cwd=selected_cwd, + stdout=selected_stdout, + stderr=selected_stderr, + ) + if native_owner_exit is not None: + return native_owner_exit + native_exact_exit = try_run_provider_native_exact( + args, + stdin=selected_stdin, + cwd=selected_cwd, + stdout=selected_stdout, + stderr=selected_stderr, + ) + if native_exact_exit is not None: + return native_exact_exit + from ._project_resolution import try_run_project_resolution + + project_resolution_exit = try_run_project_resolution( + args, + stdin=selected_stdin, + cwd=selected_cwd, + stdout=selected_stdout, + ) + if project_resolution_exit is not None: + return project_resolution_exit protocol_args = ProtocolArgs.parse(args) if protocol_args is not None: return run_protocol_cli( diff --git a/src/python_lang_project_harness/_cli_agent.py b/src/python_lang_project_harness/_cli_agent.py index 06e5d75..3d9dffc 100644 --- a/src/python_lang_project_harness/_cli_agent.py +++ b/src/python_lang_project_harness/_cli_agent.py @@ -19,8 +19,7 @@ def render_agent_guide(project_root: Path) -> str: ( "|catalog reasoningProfiles=owner-query,query-deps,owner-tests," "finding-frontier,feature-cfg entries=owner-query,query-deps," - "owner-tests routes=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 " @@ -32,17 +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 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", @@ -60,23 +50,14 @@ 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" @@ -110,19 +91,12 @@ def render_agent_guide(project_root: Path) -> str: "#match?,#any-match?,#not-eq?,#not-match? " "unsupported=none unsupportedReported=true" ), - ( - "|rule query --selector --code is pure code; " - "search 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 " diff --git a/src/python_lang_project_harness/_cli_args.py b/src/python_lang_project_harness/_cli_args.py index c0dcd89..9e96a31 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 @@ -58,6 +57,8 @@ def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: return cls._parse_agent(args[1:]) if command == "ast-patch": return cls._parse_ast_patch(args[1:]) + if command == "projection-batch-stdin": + return cls(command) return None @classmethod @@ -82,7 +83,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, ) @@ -390,8 +390,8 @@ def help_text() -> str: return ( "py-harness — 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 search ... [--json] [--package PATH] [--workspace ]\n" + " asp python query --selector --projection --workspace \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" @@ -406,8 +406,8 @@ def help_text() -> str: " 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 +430,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 --selector [--workspace ] --code\n" - " Parser-materialized exact item projection; --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" @@ -472,8 +468,7 @@ def help_text() -> str: " 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" + " asp python query --selector 'python://src/python_lang_project_harness/_cli.py#item/function/run_cli' --projection source --workspace .\n" " py-harness 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" " py-harness check --full .\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 179196f..aa391ab 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,6 +28,12 @@ def run_protocol_cli( if args.command == "help": stdout.write(help_text()) return 0 + if args.command == "projection-batch-stdin": + from ._projection_batch import render_projection_batch + + frame = stdin if isinstance(stdin, bytes) else stdin.encode("utf-8") + stdout.write(render_projection_batch(frame)) + return 0 project_root = _resolve_project_root(args, cwd) if args.command == "agent": @@ -156,15 +162,17 @@ 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 from ._semantic_search_owner_fast import render_fast_owner_seed_search from ._semantic_search_prime_fast import render_fast_prime_search - from ._workspace_scope import render_workspace_scope renderers = ( - lambda: render_workspace_scope(args, project_root=project_root), lambda: render_semantic_graph_facts( args, project_root=project_root, stdin=stdin ), @@ -269,12 +277,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 c738fe2..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,86 +39,8 @@ def run_query_command( ) return 0 - if args.selector is None and (args.owner_path is not None or args.terms): - raise ValueError( - "python query requires an exact --selector; use `asp python search owner " - " items --query --names-only --workspace .` for discovery" - ) - 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_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: - return python_structural_selector_owner_path(selector) - - -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 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 70454af..ae4f3b2 100644 --- a/src/python_lang_project_harness/_cli_query_arg_consume.py +++ b/src/python_lang_project_harness/_cli_query_arg_consume.py @@ -13,9 +13,8 @@ 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 (--catalog ID | --treesitter-query EXPR) [] [--workspace ] [--json]; " + "usage: 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 ]" ) @@ -30,8 +29,6 @@ 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 @@ -60,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) @@ -121,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, ...], diff --git a/src/python_lang_project_harness/_cli_query_args.py b/src/python_lang_project_harness/_cli_query_args.py index a5577a7..323f500 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,7 +14,6 @@ flow_lite_query_protocol_args, is_flow_lite_query_state, ) -from ._cli_query_hook_args import owner_path_from_query_selector from ._cli_query_tree_sitter_args import ( is_tree_sitter_query_state, tree_sitter_query_args_error, @@ -54,24 +52,13 @@ def _query_args_result( 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 `" + ), ) @@ -85,18 +72,9 @@ 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.selector is None: return "query requires at least one --term" - 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: return "query --surface is Rust ASP search-owned; Python query accepts exact owner-local projection only" if state.render_mode is not None: @@ -104,12 +82,6 @@ def _query_args_error(state: QueryParseState) -> str | None: 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 @@ -118,7 +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: - return state.names_only 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 c07c937..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 @@ -22,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 6f8c9ab..07c9373 100644 --- a/src/python_lang_project_harness/_cli_query_hook_args.py +++ b/src/python_lang_project_harness/_cli_query_hook_args.py @@ -28,7 +28,7 @@ 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" 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 3ac5641..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 @@ -20,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: @@ -46,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/_exact_projection_model.py b/src/python_lang_project_harness/_exact_projection_model.py new file mode 100644 index 0000000..f3adb8b --- /dev/null +++ b/src/python_lang_project_harness/_exact_projection_model.py @@ -0,0 +1,120 @@ +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-projection" +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..3273aa0 --- /dev/null +++ b/src/python_lang_project_harness/_exact_source_projection.py @@ -0,0 +1,180 @@ +from __future__ import annotations + +import ast +import base64 +import json +from pathlib import Path +from typing import Any, TextIO + +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, + flag_value, + line_byte_offsets, + node_byte_span, + parse_selector, + required_int, + required_text, +) + + +def try_run_provider_native_exact( + args: list[str] | tuple[str, ...], + *, + stdin: str, + cwd: Path, + stdout: TextIO, + stderr: TextIO, +) -> int | None: + if "--asp-exact-request-stdin" not in args: + return None + try: + request = json.loads(stdin) + packet = _project_request(args, request, cwd) + except (UnicodeDecodeError, ValueError, json.JSONDecodeError) as error: + stderr.write(f"provider-native exact query failed: {error}\n") + return 2 + stdout.write(json.dumps(packet, separators=(",", ":"), sort_keys=True)) + stdout.write("\n") + return 0 + + +def _project_request( + args: list[str] | tuple[str, ...], + request: dict[str, Any], + cwd: Path, +) -> dict[str, Any]: + _validate_request_identity(args, request) + 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_identity( + args: list[str] | tuple[str, ...], request: dict[str, Any] +) -> None: + expected = { + "schemaId": REQUEST_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "providerId": "py-harness", + "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 flag, field in ( + ("--asp-provider-id", "providerId"), + ("--asp-parser-identity-digest", "parserIdentityDigest"), + ("--asp-query-pack-digest", "queryPackDigest"), + ): + if flag_value(args, flag) != required_text(request, field): + raise ValueError(f"exact request authority mismatch for {field}") + if flag_value(args, "--selector") != required_text(request, "structuralSelector"): + raise ValueError("exact request selector does not match CLI authority") + 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": "py-harness", + "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/_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/_owner_search_stdin.py b/src/python_lang_project_harness/_owner_search_stdin.py new file mode 100644 index 0000000..c7298a3 --- /dev/null +++ b/src/python_lang_project_harness/_owner_search_stdin.py @@ -0,0 +1,285 @@ +from __future__ import annotations + +import ast +import base64 +import binascii +import json +from pathlib import Path +from typing import Any, TextIO +from urllib.parse import quote + +from blake3 import blake3 + +from ._exact_projection_model import line_byte_offsets, node_byte_span + +REQUEST_SCHEMA_ID = "agent.semantic-protocols.provider-native-owner-search-request" +RESPONSE_SCHEMA_ID = "agent.semantic-protocols.provider-native-owner-search-response" +SCHEMA_VERSION = "1" + + +def try_run_provider_native_owner( + args: list[str] | tuple[str, ...], + *, + stdin: str, + cwd: Path, + stdout: TextIO, + stderr: TextIO, +) -> int | None: + del cwd + if not args or args[0] != "owner-search-stdin": + return None + try: + provider_id = _flag_value(args, "--asp-provider-id") + if provider_id is None: + raise ValueError("owner-search stdin requires --asp-provider-id") + request = json.loads(stdin) + if not isinstance(request, dict): + raise ValueError("owner-search request must be a JSON object") + source = _validate_request(request, provider_id) + response = _project_owner(request, provider_id, source) + stdout.write(json.dumps(response, separators=(",", ":"), sort_keys=True)) + return 0 + except ( + UnicodeDecodeError, + ValueError, + binascii.Error, + json.JSONDecodeError, + ) as error: + stderr.write(f"{error}\n") + return 2 + + +def _validate_request(request: dict[str, Any], provider_id: str) -> bytes: + expected_fields = { + "schemaId", + "schemaVersion", + "languageId", + "providerId", + "workspaceIdentity", + "providerWorkspaceIdentityDigest", + "ownerPath", + "sourceFingerprint", + "sourceEncoding", + "sourceBytesBase64", + "projectionMode", + "transport", + } + if set(request) != expected_fields: + raise ValueError("owner-search request fields drift") + if ( + request["schemaId"] != REQUEST_SCHEMA_ID + or request["schemaVersion"] != SCHEMA_VERSION + or request["languageId"] != "python" + or request["providerId"] != provider_id + or not _nonempty_text(request["workspaceIdentity"]) + or not _digest_text(request["providerWorkspaceIdentityDigest"]) + or not _nonempty_text(request["ownerPath"]) + or request["sourceEncoding"] != "base64" + or request["projectionMode"] != "complete-owner" + or request["transport"] != "stdin-json" + ): + raise ValueError("owner-search request identity or completeness drift") + + fingerprint = request["sourceFingerprint"] + if not isinstance(fingerprint, dict) or set(fingerprint) != { + "fileIdentity", + "sizeBytes", + "modifiedUnixNanos", + "changeTimeUnixNanos", + "contentDigest", + }: + raise ValueError("owner-search source fingerprint drift") + if ( + not _nonempty_text(fingerprint["fileIdentity"]) + or not _nonnegative_int(fingerprint["sizeBytes"]) + or not _nonnegative_int(fingerprint["modifiedUnixNanos"]) + or not _nonnegative_int(fingerprint["changeTimeUnixNanos"]) + or not _digest_text(fingerprint["contentDigest"]) + ): + raise ValueError("owner-search source fingerprint is incomplete") + + encoded = request["sourceBytesBase64"] + if not isinstance(encoded, str): + raise ValueError("owner-search source base64 must be text") + source = base64.b64decode(encoded, validate=True) + if len(source) != fingerprint["sizeBytes"]: + raise ValueError("owner-search source size drift") + if blake3(source).hexdigest() != fingerprint["contentDigest"]: + raise ValueError("owner-search source content digest drift") + source.decode("utf-8") + return source + + +def _project_owner( + request: dict[str, Any], provider_id: str, source: bytes +) -> dict[str, Any]: + source_text = source.decode("utf-8") + tree = ast.parse(source_text) + offsets = line_byte_offsets(source) + projections = _owner_projections(tree.body, request["ownerPath"], offsets, []) + projections.sort( + key=lambda projection: ( + projection["sourceByteStart"], + projection["canonicalItemSelector"]["structuralSelector"], + ) + ) + return { + "schemaId": RESPONSE_SCHEMA_ID, + "schemaVersion": SCHEMA_VERSION, + "languageId": "python", + "providerId": provider_id, + "requestedOwnerPath": request["ownerPath"], + "requestedProjectionMode": request["projectionMode"], + "sourceContentDigest": request["sourceFingerprint"]["contentDigest"], + "parsedOwnerCount": 1, + "projectionCompleteness": "complete-owner", + "projections": projections, + } + + +def _function_projection( + node: ast.FunctionDef | ast.AsyncFunctionDef, + owner_path: str, + offsets: list[int], + scopes: list[dict[str, str]], + item_kind: str, +) -> dict[str, Any]: + byte_start, byte_end = node_byte_span(node, offsets) + prefix = "async def" if isinstance(node, ast.AsyncFunctionDef) else "def" + signature = f"{prefix} {node.name}({ast.unparse(node.args)})" + if node.returns is not None: + signature += f" -> {ast.unparse(node.returns)}" + selector = _canonical_item_selector( + owner_path=owner_path, + item_kind=item_kind, + symbol=node.name, + scopes=scopes, + ) + return { + "canonicalItemSelector": selector, + "signature": signature, + "captureName": "function_definition/name", + "sourceByteStart": byte_start, + "sourceByteEnd": byte_end, + } + + +def _owner_projections( + nodes: list[ast.stmt], + owner_path: str, + offsets: list[int], + scopes: list[dict[str, str]], +) -> list[dict[str, Any]]: + projections: list[dict[str, Any]] = [] + for node in nodes: + if isinstance(node, ast.ClassDef): + byte_start, byte_end = node_byte_span(node, offsets) + projections.append( + { + "canonicalItemSelector": _canonical_item_selector( + owner_path=owner_path, + item_kind="class", + symbol=node.name, + scopes=scopes, + ), + "signature": f"class {node.name}", + "captureName": "class_definition/name", + "sourceByteStart": byte_start, + "sourceByteEnd": byte_end, + } + ) + projections.extend( + _owner_projections( + node.body, + owner_path, + offsets, + [ + *scopes, + { + "relation": "class-owner", + "kind": "class", + "symbol": node.name, + }, + ], + ) + ) + continue + if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + continue + item_kind = ( + "method" + if scopes and scopes[-1]["relation"] == "class-owner" + else "function" + ) + projections.append( + _function_projection(node, owner_path, offsets, scopes, item_kind) + ) + projections.extend( + _owner_projections( + node.body, + owner_path, + offsets, + [ + *scopes, + { + "relation": "lexical-owner", + "kind": "function", + "symbol": node.name, + }, + ], + ) + ) + return projections + + +def _canonical_item_selector( + *, + owner_path: str, + item_kind: str, + symbol: str, + scopes: list[dict[str, str]], +) -> dict[str, Any]: + identity_path = f"item/{_component(item_kind)}/{_component(symbol)}" + identity_path += "".join( + f"/scope/{_component(scope['relation'])}/{_component(scope['kind'])}/{_component(scope['symbol'])}" + for scope in scopes + ) + return { + "schemaId": "asp.canonical-item-selector.v1", + "schemaVersion": "1", + "languageId": "python", + "kind": item_kind, + "symbol": symbol, + "scopes": list(scopes), + "structuralSelector": f"python://{owner_path}#{identity_path}", + } + + +def _component(value: str) -> str: + return quote(value, safe="-._~") + + +def _flag_value(args: list[str] | tuple[str, ...], flag: str) -> str | None: + try: + index = args.index(flag) + except ValueError: + return None + if index + 1 >= len(args): + return None + return args[index + 1] + + +def _nonempty_text(value: object) -> bool: + return isinstance(value, str) and bool(value) + + +def _digest_text(value: object) -> bool: + return ( + isinstance(value, str) + and len(value) == 64 + and all(character in "0123456789abcdef" for character in value) + ) + + +def _nonnegative_int(value: object) -> bool: + return isinstance(value, int) and not isinstance(value, bool) and value >= 0 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_resolution.py b/src/python_lang_project_harness/_project_resolution.py new file mode 100644 index 0000000..b89f1a1 --- /dev/null +++ b/src/python_lang_project_harness/_project_resolution.py @@ -0,0 +1,143 @@ +"""Decode the provider ProjectResolution ABI and render typed failures.""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any, TextIO + +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 try_run_project_resolution( + args: list[str] | tuple[str, ...], + *, + stdin: str, + cwd: Path, + stdout: TextIO, +) -> int | None: + if tuple(args) != ("project-resolution-stdin",): + return None + + try: + request = _decode_request(stdin) + response = _response( + "resolved", scope=resolve_project_resolution(request, cwd=cwd) + ) + except ProjectResolutionError as error: + response = _failure( + str(error), + reason_kind=error.reason_kind, + next_action=error.next_action, + ) + except (ValueError, OSError) as error: + response = _failure(str(error)) + stdout.write(json.dumps(response, separators=(",", ":"), sort_keys=True)) + stdout.write("\n") + return 0 + + +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") != "py-harness" + ): + raise ValueError( + "project-resolution request provider identity does not match py-harness" + ) + 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": "py-harness", + "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]: + 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..d842033 --- /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": "py-harness", + "parserId": parser_id, + "candidateGenerationDigest": generation_digest, + "projectEntry": project_manifest.as_posix(), + "packageGraph": { + "schemaId": PACKAGE_GRAPH_SCHEMA_ID, + "schemaVersion": "1", + "languageId": "python", + "providerId": "py-harness", + "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..77dd15f --- /dev/null +++ b/src/python_lang_project_harness/_project_resolution_graph.py @@ -0,0 +1,205 @@ +"""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 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..220fad3 --- /dev/null +++ b/src/python_lang_project_harness/_projection_batch.py @@ -0,0 +1,190 @@ +"""Shared ASP provider projection-batch transport for Python owners.""" + +from __future__ import annotations + +import ast +import json +from dataclasses import dataclass + +from ._callable_skeleton_projection import callable_skeleton_payload, collect_segments +from ._exact_projection_model import ExactSelector + +_REQUEST_SCHEMA_ID = "asp.provider-language-projection-batch-request.v1" +_RESPONSE_SCHEMA_ID = "asp.provider-language-projection-batch-response.v1" +_IDENTITY_SCHEMA_ID = "asp.canonical-language-item-identity.v1" +_TRANSPORT = "framed-stdin-v1" + + +@dataclass(frozen=True, slots=True) +class _OwnerFrame: + path: str + digest: str + source: bytes + + +def render_projection_batch(frame: bytes) -> str: + """Decode one ASP frame and render the provider-owned projection response.""" + + header, owners = _decode_frame(frame) + projected = [_project_owner(owner, header) for owner in owners] + response = { + "schemaId": _RESPONSE_SCHEMA_ID, + "schemaVersion": "1", + "languageId": header["languageId"], + "providerId": header["providerId"], + "generationRootDigest": header["generationRootDigest"], + "owners": projected, + } + return json.dumps(response, separators=(",", ":"), ensure_ascii=False) + + +def _decode_frame(frame: bytes) -> tuple[dict[str, object], list[_OwnerFrame]]: + if len(frame) < 4: + raise ValueError("projection batch frame is missing its header length") + header_length = int.from_bytes(frame[:4], "big") + header_end = 4 + header_length + if header_end > len(frame): + raise ValueError("projection batch header exceeds the input frame") + header = json.loads(frame[4:header_end]) + if not isinstance(header, dict): + raise ValueError("projection batch header must be an object") + if ( + header.get("schemaId") != _REQUEST_SCHEMA_ID + or header.get("schemaVersion") != "1" + or header.get("languageId") != "python" + or header.get("transport") != _TRANSPORT + or not isinstance(header.get("parserIdentityDigest"), str) + or not header.get("parserIdentityDigest") + or not isinstance(header.get("queryPackDigest"), str) + or not header.get("queryPackDigest") + ): + raise ValueError("projection batch request identity mismatch") + owner_headers = header.get("owners") + if not isinstance(owner_headers, list): + raise ValueError("projection batch owners must be an array") + cursor = header_end + owners: list[_OwnerFrame] = [] + for raw_owner in owner_headers: + if not isinstance(raw_owner, dict): + raise ValueError("projection batch owner header must be an object") + path = raw_owner.get("ownerPath") + digest = raw_owner.get("sourceLeafDigest") + byte_length = raw_owner.get("byteLength") + if ( + not isinstance(path, str) + or not path + or not isinstance(digest, str) + or not digest + or not isinstance(byte_length, int) + or byte_length < 0 + ): + raise ValueError("projection batch owner header is incomplete") + owner_end = cursor + byte_length + if owner_end > len(frame): + raise ValueError(f"projection batch owner bytes are truncated: {path}") + owners.append(_OwnerFrame(path, digest, frame[cursor:owner_end])) + cursor = owner_end + if cursor != len(frame): + raise ValueError("projection batch frame has trailing bytes") + return header, 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 ae8648d..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) @@ -297,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/_semantic_graph_facts.py b/src/python_lang_project_harness/_semantic_graph_facts.py index f0b544d..9d2c4a4 100644 --- a/src/python_lang_project_harness/_semantic_graph_facts.py +++ b/src/python_lang_project_harness/_semantic_graph_facts.py @@ -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 77f6c81..268ba3e 100644 --- a/src/python_lang_project_harness/_semantic_language.py +++ b/src/python_lang_project_harness/_semantic_language.py @@ -14,7 +14,10 @@ from ._semantic_query_pack import python_query_pack_descriptor _PYTHON_CHECK_METHODS = ("check/changed", "check/full") -_PYTHON_QUERY_METHODS = ("query", "query/owner-items") +_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,7 +25,10 @@ _PYTHON_SEARCH_VIEWS = tuple( descriptor["view"] for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS ) -_PYTHON_SEARCH_METHODS = tuple(f"search/{view}" for view in _PYTHON_SEARCH_VIEWS) +_PYTHON_SEARCH_METHODS = ( + *(f"search/{view}" for view in _PYTHON_SEARCH_VIEWS), + "search/owner-native", +) def semantic_language_registry_document() -> dict[str, Any]: @@ -133,7 +139,35 @@ def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: }, ] ) - return attach_semantic_language_invocations(descriptors) + attached = attach_semantic_language_invocations(descriptors) + attached.append( + { + "method": "search/owner-native", + "command": "search", + "view": "owner-native", + "outputSchemaIds": [ + "agent.semantic-protocols.provider-native-owner-search-response" + ], + "packetSchemas": [ + "provider-native-owner-search-request.v1", + "provider-native-owner-search-response.v1", + ], + "requiresQuery": False, + "acceptsStdin": True, + "supportsPackageScope": False, + "supportsJson": True, + "supportsCompact": False, + "invocation": { + "argv": [ + "py-harness", + "owner-search-stdin", + "--asp-provider-id", + "py-harness", + ] + }, + } + ) + return attached def _python_search_method_descriptor(descriptor: dict[str, Any]) -> dict[str, Any]: @@ -154,17 +188,10 @@ def _python_search_method_descriptor(descriptor: dict[str, Any]) -> dict[str, An "semantic-fact-ontology.v1", ] rendered["input"] = "search semantic-facts " - if descriptor["view"] == "workspace-scope": - rendered["supportsCompact"] = False - rendered["outputModes"] = ["json"] - rendered["packetSchemas"] = ["semantic-workspace-scope.v1"] - rendered["input"] = "search workspace-scope --workspace " return rendered def _search_output_schema_ids(view: str) -> list[str]: - if view == "workspace-scope": - return [ids.SEMANTIC_WORKSPACE_SCOPE_SCHEMA_ID] if view == "semantic-facts": return [ids.SEMANTIC_FACT_GRAPH_SCHEMA_ID] schema_ids = [ids.SEMANTIC_SEARCH_PACKET_SCHEMA_ID] @@ -176,6 +203,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_catalog.py b/src/python_lang_project_harness/_semantic_language_catalog.py index 827bbcc..ac307c6 100644 --- a/src/python_lang_project_harness/_semantic_language_catalog.py +++ b/src/python_lang_project_harness/_semantic_language_catalog.py @@ -19,13 +19,6 @@ def python_search_view_descriptors() -> list[dict[str, Any]]: _python("python-package-root-search"), ], ), - _view( - "workspace-scope", - capabilities=[ - _semantic("workspace-candidate-admission"), - _python("python-package-manager-workspace-scope"), - ], - ), _view( "prime", capabilities=[ diff --git a/src/python_lang_project_harness/_semantic_language_ids.py b/src/python_lang_project_harness/_semantic_language_ids.py index ce4cdc5..584c254 100644 --- a/src/python_lang_project_harness/_semantic_language_ids.py +++ b/src/python_lang_project_harness/_semantic_language_ids.py @@ -17,7 +17,6 @@ SEMANTIC_TYPE_SURFACE_SCHEMA_ID = "agent.semantic-protocols.semantic-type-surface" SEMANTIC_FACT_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-fact-graph" SEMANTIC_FACT_ONTOLOGY_SCHEMA_ID = "agent.semantic-protocols.semantic-fact-ontology" -SEMANTIC_WORKSPACE_SCOPE_SCHEMA_ID = "agent.semantic-protocols.semantic-workspace-scope" SEMANTIC_DETERMINISM_READINESS_SCHEMA_ID = ( "agent.semantic-protocols.semantic-determinism-readiness" ) diff --git a/src/python_lang_project_harness/_semantic_language_invocation.py b/src/python_lang_project_harness/_semantic_language_invocation.py index a34764a..0097585 100644 --- a/src/python_lang_project_harness/_semantic_language_invocation.py +++ b/src/python_lang_project_harness/_semantic_language_invocation.py @@ -42,6 +42,11 @@ def _non_search_invocation(method: str) -> dict[str, list[str]]: "--workspace", "{workspace}", ], + "query/exact-selector-native-v1": [ + ids.PYTHON_BINARY, + "query", + "--asp-exact-request-stdin", + ], "check/changed": [ids.PYTHON_BINARY, "check", "--changed", "{workspace}"], "check/full": [ids.PYTHON_BINARY, "check", "--full", "{workspace}"], "ast-patch/dry-run": [ diff --git a/src/python_lang_project_harness/_semantic_language_query.py b/src/python_lang_project_harness/_semantic_language_query.py index 80b05cb..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,44 +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], + "view": "exact-selector", + "outputSchemaIds": [ + "agent.semantic-protocols.provider-native-exact-projection" + ], "packetSchemas": [ - "semantic-query-packet.v1", - "semantic-tree-sitter-query.v1", + "provider-native-exact-request.v1", + "provider-native-exact-response.v1", ], - "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"], + "requiresQuery": True, + "acceptsStdin": True, + "supportsPackageScope": False, "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", + "supportsCompact": False, }, ] diff --git a/src/python_lang_project_harness/_semantic_language_schemas.py b/src/python_lang_project_harness/_semantic_language_schemas.py index d6b0a77..e8890aa 100644 --- a/src/python_lang_project_harness/_semantic_language_schemas.py +++ b/src/python_lang_project_harness/_semantic_language_schemas.py @@ -114,11 +114,6 @@ def python_semantic_language_schemas() -> list[dict[str, str]]: "schemaVersion": "1", "path": "schemas/semantic-fact-ontology.v1.schema.json", }, - { - "schemaId": ids.SEMANTIC_WORKSPACE_SCOPE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-workspace-scope.v1.schema.json", - }, { "schemaId": "agent.semantic-protocols.semantic-handle", "schemaVersion": "1", 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 19b8381..82b1248 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 @@ -77,8 +75,8 @@ def _search_view_descriptor( def _semantic_search_usage() -> str: return ( "usage: py-harness 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 c283abf..1c2bb91 100644 --- a/src/python_lang_project_harness/_semantic_search_items.py +++ b/src/python_lang_project_harness/_semantic_search_items.py @@ -181,7 +181,9 @@ def _owner_item_semantic_query_packet( "patchSafety": { "level": "read-safe", "reason": "compact query packet is not a mutation authority", - "nextAction": "query --selector --code", + "nextAction": ( + "query --selector --projection source" + ), }, "queryCoverage": [ semantic_query_coverage( 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_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/_workspace_scope.py b/src/python_lang_project_harness/_workspace_scope.py deleted file mode 100644 index c4a23a8..0000000 --- a/src/python_lang_project_harness/_workspace_scope.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Provider-owned Python package-manager workspace admission.""" - -from __future__ import annotations - -import glob -import hashlib -import json -from pathlib import Path -from typing import Any - -from python_lang_parser import ( - PythonPyprojectParseError, - parse_python_pyproject_document, -) - -from ._cli_args import ProtocolArgs - -_LOCKFILES = ("uv.lock", "poetry.lock", "pdm.lock", "Pipfile.lock") - - -def render_workspace_scope( - args: ProtocolArgs, - *, - project_root: Path, -) -> str | None: - """Return the standalone workspace-scope fast-path when selected.""" - - if args.command != "search" or args.view != "workspace-scope": - return None - if not args.json: - raise ValueError("search workspace-scope requires --json") - return json.dumps(build_workspace_scope(project_root), sort_keys=True) + "\n" - - -def build_workspace_scope(project_root: Path) -> dict[str, Any]: - """Resolve Python workspace membership from pyproject and lock anchors.""" - - discovery_root = project_root.resolve() - root_manifest = discovery_root / "pyproject.toml" - if not root_manifest.is_file(): - raise ValueError(f"workspace scope requires {root_manifest.as_posix()}") - - root_document = _read_pyproject(root_manifest) - package_roots = _package_roots(discovery_root, root_document) - packages = [_package_entry(root) for root in package_roots] - if not packages: - raise ValueError("workspace scope resolved no Python packages") - - anchors = _anchors(discovery_root, package_roots) - workspace_name = _project_name(root_document) or discovery_root.name - packet: dict[str, Any] = { - "schemaId": "agent.semantic-protocols.semantic-workspace-scope", - "schemaVersion": "1", - "workspaceId": f"python:{workspace_name}", - "languageId": "python", - "providerId": "py-harness", - "packageManager": _package_manager(discovery_root, root_document), - "sourceExtensions": [".py", ".pyi"], - "discoveryRoot": discovery_root.as_posix(), - "anchors": anchors, - "packages": packages, - "admittedRoots": [entry["root"] for entry in packages], - } - packet["fingerprint"] = _json_fingerprint(packet) - return packet - - -def _package_roots(project_root: Path, document: dict[str, Any]) -> list[Path]: - workspace = _uv_workspace(document) - roots: set[Path] = set() - if _project_name(document) is not None: - roots.add(project_root) - if workspace is None: - return sorted(roots) - - excluded = _expanded_roots(project_root, workspace.get("exclude", [])) - for root in _expanded_roots(project_root, workspace.get("members", [])): - if root not in excluded and (root / "pyproject.toml").is_file(): - roots.add(root) - return sorted(roots) - - -def _expanded_roots(project_root: Path, patterns: object) -> set[Path]: - if not isinstance(patterns, list): - return set() - roots: set[Path] = set() - for pattern in patterns: - if not isinstance(pattern, str) or not pattern: - continue - absolute_pattern = str(project_root / pattern) - for match in glob.glob(absolute_pattern, recursive=True): - candidate = Path(match).resolve() - roots.add( - candidate.parent if candidate.name == "pyproject.toml" else candidate - ) - return roots - - -def _package_entry(root: Path) -> dict[str, str]: - manifest = root / "pyproject.toml" - document = _read_pyproject(manifest) - name = _project_name(document) - if name is None: - raise ValueError(f"Python package manifest has no [project].name: {manifest}") - root_text = root.resolve().as_posix() - identity = hashlib.sha256(root_text.encode("utf-8")).hexdigest()[:12] - return { - "packageId": f"python:{name}:{identity}", - "name": name, - "root": root_text, - "manifestPath": manifest.resolve().as_posix(), - "languageId": "python", - } - - -def _anchors(project_root: Path, package_roots: list[Path]) -> list[dict[str, str]]: - paths: dict[Path, str] = { - (root / "pyproject.toml").resolve(): "pyproject" for root in package_roots - } - root_manifest = (project_root / "pyproject.toml").resolve() - paths[root_manifest] = "pyproject" - for name in _LOCKFILES: - lockfile = (project_root / name).resolve() - if lockfile.is_file(): - paths[lockfile] = "python-lock" - return [ - { - "kind": paths[path], - "path": path.as_posix(), - "sha256": _file_fingerprint(path), - } - for path in sorted(paths) - ] - - -def _read_pyproject(path: Path) -> dict[str, Any]: - try: - return parse_python_pyproject_document(path) - except PythonPyprojectParseError as error: - raise ValueError(str(error)) from error - - -def _project_name(document: dict[str, Any]) -> str | None: - project = document.get("project") - if not isinstance(project, dict): - return None - name = project.get("name") - return name if isinstance(name, str) and name else None - - -def _uv_workspace(document: dict[str, Any]) -> dict[str, Any] | None: - tool = document.get("tool") - uv = tool.get("uv") if isinstance(tool, dict) else None - workspace = uv.get("workspace") if isinstance(uv, dict) else None - return workspace if isinstance(workspace, dict) else None - - -def _package_manager(project_root: Path, document: dict[str, Any]) -> str: - tool = document.get("tool") - if (project_root / "uv.lock").is_file() or ( - isinstance(tool, dict) and isinstance(tool.get("uv"), dict) - ): - return "uv" - return "pip" - - -def _file_fingerprint(path: Path) -> str: - return f"sha256:{hashlib.sha256(path.read_bytes()).hexdigest()}" - - -def _json_fingerprint(value: dict[str, Any]) -> str: - payload = json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8") - return f"sha256:{hashlib.sha256(payload).hexdigest()}" 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/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/test_cli.py b/tests/unit/harness/test_cli.py index ad83791..0f7a9ea 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 "py-harness 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"), ] 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_exact_source_projection.py b/tests/unit/harness/test_exact_source_projection.py new file mode 100644 index 0000000..1fefd54 --- /dev/null +++ b/tests/unit/harness/test_exact_source_projection.py @@ -0,0 +1,123 @@ +from __future__ import annotations + +import base64 +import io +import json + +from python_lang_project_harness._cli import run_cli + + +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": "py-harness", + "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", + } + stdout = io.StringIO() + stderr = io.StringIO() + exit_code = run_cli( + [ + "query", + "--selector", + selector, + "--json", + "--asp-provider-id", + "py-harness", + "--asp-parser-identity-digest", + parser_digest, + "--asp-query-pack-digest", + query_pack_digest, + "--asp-exact-request-stdin", + ], + stdin=json.dumps(request), + stdout=stdout, + stderr=stderr, + cwd=tmp_path, + ) + assert exit_code == 0, stderr.getvalue() + return json.loads(stdout.getvalue()) + + skeleton = invoke(root_selector, "callable-skeleton") + payload = skeleton["projectionPayload"] + assert isinstance(payload, dict) + assert payload["schemaVersion"] == "1" + nodes = payload["nodes"] + assert isinstance(nodes, list) + branch_selector = next( + node["exactSelector"]["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": "py-harness", + "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", + } + stdout = io.StringIO() + stderr = io.StringIO() + exit_code = run_cli( + [ + "query", + "--selector", + selector, + "--asp-provider-id", + "py-harness", + "--asp-parser-identity-digest", + "b" * 64, + "--asp-query-pack-digest", + "c" * 64, + "--asp-exact-request-stdin", + ], + stdin=json.dumps(request), + stdout=stdout, + stderr=stderr, + cwd=tmp_path, + ) + + assert exit_code == 0, stderr.getvalue() + packet = json.loads(stdout.getvalue()) + assert packet["sourceContentDigest"] == "asp-owned-content-identity" diff --git a/tests/unit/harness/test_owner_search_stdin.py b/tests/unit/harness/test_owner_search_stdin.py new file mode 100644 index 0000000..bb904ef --- /dev/null +++ b/tests/unit/harness/test_owner_search_stdin.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +import base64 +import json +from io import StringIO +from pathlib import Path + +from blake3 import blake3 + +from python_lang_project_harness._cli import run_cli +from python_lang_project_harness._owner_search_stdin import ( + try_run_provider_native_owner, +) +from python_lang_project_harness._semantic_language import ( + python_semantic_language_method_descriptors, +) + + +def _request(source: bytes) -> dict[str, object]: + return { + "schemaId": "agent.semantic-protocols.provider-native-owner-search-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "py-harness", + "workspaceIdentity": "workspace-test", + "providerWorkspaceIdentityDigest": "1" * 64, + "ownerPath": "src/example.py", + "sourceFingerprint": { + "fileIdentity": "resident-owner:test", + "sizeBytes": len(source), + "modifiedUnixNanos": 0, + "changeTimeUnixNanos": 0, + "contentDigest": blake3(source).hexdigest(), + }, + "sourceEncoding": "base64", + "sourceBytesBase64": base64.b64encode(source).decode("ascii"), + "projectionMode": "complete-owner", + "transport": "stdin-json", + } + + +def test_owner_search_projects_complete_top_level_function_owner() -> None: + source = ( + b"def alpha(value: int) -> int:\n" + b" def nested() -> int:\n" + b" return value\n" + b" return nested()\n\n" + b"async def beta() -> None:\n" + b" return None\n" + ) + stdout = StringIO() + stderr = StringIO() + + exit_code = try_run_provider_native_owner( + ["owner-search-stdin", "--asp-provider-id", "py-harness"], + stdin=json.dumps(_request(source)), + cwd=Path("."), + stdout=stdout, + stderr=stderr, + ) + + assert exit_code == 0 + assert stderr.getvalue() == "" + response = json.loads(stdout.getvalue()) + assert response["projectionCompleteness"] == "complete-owner" + assert response["requestedProjectionMode"] == "complete-owner" + assert [ + projection["canonicalItemSelector"]["symbol"] + for projection in response["projections"] + ] == [ + "alpha", + "nested", + "beta", + ] + assert response["projections"][0]["canonicalItemSelector"][ + "structuralSelector" + ] == ("python://src/example.py#item/function/alpha") + assert response["projections"][1]["canonicalItemSelector"]["scopes"] == [ + {"relation": "lexical-owner", "kind": "function", "symbol": "alpha"} + ] + for projection in response["projections"]: + projected = source[projection["sourceByteStart"] : projection["sourceByteEnd"]] + selector = projection["canonicalItemSelector"] + assert selector["schemaId"] == "asp.canonical-item-selector.v1" + assert selector["schemaVersion"] == "1" + assert selector["symbol"].encode() in projected + + +def test_owner_search_disambiguates_same_method_name_by_class_scope() -> None: + source = ( + b"class Alpha:\n" + b" def render(self) -> str:\n" + b" return 'alpha'\n\n" + b"class Beta:\n" + b" def render(self) -> str:\n" + b" return 'beta'\n" + ) + stdout = StringIO() + stderr = StringIO() + assert ( + try_run_provider_native_owner( + ["owner-search-stdin", "--asp-provider-id", "py-harness"], + stdin=json.dumps(_request(source)), + cwd=Path("."), + stdout=stdout, + stderr=stderr, + ) + == 0 + ) + methods = [ + projection["canonicalItemSelector"] + for projection in json.loads(stdout.getvalue())["projections"] + if projection["canonicalItemSelector"]["kind"] == "method" + ] + assert len(methods) == 2 + assert len({method["structuralSelector"] for method in methods}) == 2 + assert {method["scopes"][0]["symbol"] for method in methods} == {"Alpha", "Beta"} + assert all(method["scopes"][0]["relation"] == "class-owner" for method in methods) + + +def test_owner_search_rejects_content_digest_drift() -> None: + request = _request(b"def alpha():\n return 1\n") + request["sourceFingerprint"]["contentDigest"] = "0" * 64 # type: ignore[index] + stdout = StringIO() + stderr = StringIO() + + exit_code = try_run_provider_native_owner( + ["owner-search-stdin", "--asp-provider-id", "py-harness"], + stdin=json.dumps(request), + cwd=Path("."), + stdout=stdout, + stderr=stderr, + ) + + assert exit_code == 2 + assert stdout.getvalue() == "" + assert "content digest drift" in stderr.getvalue() + + +def test_cli_and_registry_expose_native_owner_transport() -> None: + source = b"def graph_turbo_entry():\n return 1\n" + stdout = StringIO() + stderr = StringIO() + + exit_code = run_cli( + ["owner-search-stdin", "--asp-provider-id", "py-harness"], + stdin=json.dumps(_request(source)), + cwd=Path("."), + stdout=stdout, + stderr=stderr, + ) + + assert exit_code == 0 + descriptors = { + descriptor["method"]: descriptor + for descriptor in python_semantic_language_method_descriptors() + } + assert descriptors["search/owner-native"]["invocation"]["argv"] == [ + "py-harness", + "owner-search-stdin", + "--asp-provider-id", + "py-harness", + ] 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..ce94822 --- /dev/null +++ b/tests/unit/harness/test_project_resolution.py @@ -0,0 +1,239 @@ +"""Exercise candidate-bounded Python ProjectResolution parsing.""" + +from __future__ import annotations + +import io +import json +from pathlib import Path + +from python_lang_project_harness import run_cli + + +def request(candidate_paths: list[str]) -> dict[str, object]: + return { + "schemaId": "agent.semantic-protocols.provider-project-resolution-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "py-harness", + "candidateBase": ".", + "candidateGeneration": { + "algorithm": "blake3-path-set-v1", + "digest": "blake3:" + ("0" * 64), + "authorities": ["asp-workspace-generation"], + }, + "collectionScope": {"kind": "complete-generation"}, + "candidatePaths": candidate_paths, + "policyExclusions": [], + } + + +def run_project_resolution(root: Path, payload: object) -> dict[str, object]: + stdout = io.StringIO() + stderr = io.StringIO() + exit_code = run_cli( + ["project-resolution-stdin"], + stdin=json.dumps(payload), + stdout=stdout, + stderr=stderr, + cwd=root, + ) + assert exit_code == 0 + assert stderr.getvalue() == "" + return json.loads(stdout.getvalue()) + + +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 = run_project_resolution(tmp_path, []) + assert response["state"] == "failed" + assert response["failure"]["reasonKind"] == "project-entry-invalid" + + +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["state"] == "failed" + assert response["failure"]["reasonKind"] == "project-entry-missing" + + +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_manifest_advertises_project_resolution() -> None: + project_root = Path(__file__).parents[3] + manifest = json.loads( + (project_root / "provider/asp-provider-manifest.json").read_text() + ) + descriptor = manifest["projectResolution"] + assert descriptor["commandBinding"] == "project-resolution-stdin" + assert descriptor["parserId"] == "python.pyproject-toml" + assert "supportsGitCandidates" not in descriptor + assert "candidateSnapshotSchema" not in descriptor + + +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 = run_project_resolution(tmp_path, payload) + assert failure["state"] == "failed" + assert "normalized workspace-relative" in failure["failure"]["message"] 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..d4cd37f --- /dev/null +++ b/tests/unit/harness/test_projection_batch.py @@ -0,0 +1,55 @@ +"""Focused contract tests for the ASP projection-batch provider adapter.""" + +from __future__ import annotations + +import json + +from python_lang_project_harness._cli import run_cli + + +def test_projection_batch_projects_canonical_python_items(capsys: object) -> None: + source = b"class Agent:\n def run(self):\n return 1\n\ndef top():\n return 2\n" + header = { + "schemaId": "asp.provider-language-projection-batch-request.v1", + "schemaVersion": "1", + "languageId": "python", + "providerId": "py-harness", + "workspaceIdentity": "workspace-test", + "transport": "framed-stdin-v1", + "generationRootDigest": "generation-test", + "parserIdentityDigest": "parser-test", + "queryPackDigest": "query-pack-test", + "baseGenerationRootDigest": None, + "owners": [ + { + "ownerPath": "src/example.py", + "sourceLeafDigest": "source-test", + "byteLength": len(source), + } + ], + } + header_bytes = json.dumps(header, separators=(",", ":")).encode() + frame = len(header_bytes).to_bytes(4, "big") + header_bytes + source + + assert run_cli(["projection-batch-stdin"], stdin=frame) == 0 + response = json.loads(capsys.readouterr().out) # type: ignore[attr-defined] + + assert response["schemaId"] == "asp.provider-language-projection-batch-response.v1" + assert response["generationRootDigest"] == "generation-test" + owner = response["owners"][0] + 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" + assert ( + item["projections"][0]["payload"]["schemaId"] + == "agent.semantic-protocols.callable-skeleton-projection" + ) 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_cli.py b/tests/unit/harness/test_semantic_cli.py index a14e610..aafcbb9 100644 --- a/tests/unit/harness/test_semantic_cli.py +++ b/tests/unit/harness/test_semantic_cli.py @@ -6,288 +6,31 @@ 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 - registry = payload["registry"] - registration = registry["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=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 "read-frontier" not in rendered - assert "owner-local-projection" not in rendered - assert "--from-hook" not 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 + assert "query/exact-selector-native-v1" in registration["methods"] -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"]: - assert descriptor["invocation"]["argv"][0] == "py-harness" - 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" - - -def test_cli_search_callsite_uses_parser_call_facts(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - ["search", "callsite", "build", "--workspace", str(tmp_path)], - stdout=stdout, - ) - - 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 - - -def test_cli_search_deps_routes_dependency_api_followups(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() - 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, - ) - + assert run_cli(["agent", "guide", str(tmp_path)], stdout=stdout) == 0 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", - } + 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_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", +def test_search_descriptors_publish_benchmark_invocations() -> None: + descriptors = python_semantic_language_registration()["methodDescriptors"] + assert any( + descriptor["method"] == "search/owner-native" and descriptor["acceptsStdin"] + for descriptor in descriptors ) - - 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 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 832d813..0000000 --- a/tests/unit/harness/test_semantic_cli_direct_read.py +++ /dev/null @@ -1,50 +0,0 @@ -from __future__ import annotations - -import io -from pathlib import Path - -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_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_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_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_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 02f9fde..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_items.py +++ /dev/null @@ -1,286 +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) - (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") - - (tmp_path / "pyproject.toml").write_text( - '\n[project]\nname = "demo-python"\nversion = "0.1.0"\nimport-names = ["pkg"]\n', - encoding="utf-8", - ) 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_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 index b313fb7..7514f00 100644 --- a/tests/unit/harness/test_semantic_cli_structural_selector_registry.py +++ b/tests/unit/harness/test_semantic_cli_structural_selector_registry.py @@ -20,8 +20,12 @@ def test_cli_query_registry_owns_structural_selector_projection( query = next( descriptor for descriptor in descriptors if descriptor["method"] == "query" ) - assert query["codeOutput"]["mode"] == "pure-code" - assert "exact-selector" in query["codeOutput"]["requires"] + 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 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_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_provider_doctor.py b/tests/unit/harness/test_semantic_provider_doctor.py index e5c63a6..b18f35a 100644 --- a/tests/unit/harness/test_semantic_provider_doctor.py +++ b/tests/unit/harness/test_semantic_provider_doctor.py @@ -44,6 +44,19 @@ def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( ) descriptors = registration["methodDescriptors"] assert len(descriptors) == len(registration["methods"]) == 34 + native_owner = next( + descriptor + for descriptor in descriptors + if descriptor["method"] == "search/owner-native" + ) + assert native_owner["acceptsStdin"] is True + assert native_owner["command"] == "search" + assert native_owner["invocation"]["argv"] == [ + "py-harness", + "owner-search-stdin", + "--asp-provider-id", + "py-harness", + ] assert all(descriptor["invocation"]["argv"] for descriptor in descriptors) canonical = json.dumps( registry, diff --git a/tests/unit/harness/test_semantic_search_graph_profiles.py b/tests/unit/harness/test_semantic_search_graph_profiles.py index 42beb2a..e75fb21 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": "py-harness", + "binary": "py-harness", + "namespace": "agent.semantic-protocols.languages.python.py-harness", + "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/harness/test_workspace_scope.py b/tests/unit/harness/test_workspace_scope.py deleted file mode 100644 index 2d471fa..0000000 --- a/tests/unit/harness/test_workspace_scope.py +++ /dev/null @@ -1,122 +0,0 @@ -"""Python package-manager workspace scope fast-path tests.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from python_lang_project_harness import python_semantic_language_registration, run_cli - - -def test_workspace_scope_is_json_fast_path_and_registry_contract( - tmp_path: Path, -) -> None: - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "scope-root"\nversion = "0.1.0"\n', encoding="utf-8" - ) - (tmp_path / "uv.lock").write_text("version = 1\n", encoding="utf-8") - stdout = io.StringIO() - - assert ( - run_cli( - ["search", "workspace-scope", "--json", "--workspace", str(tmp_path)], - stdout=stdout, - cwd=tmp_path, - ) - == 0 - ) - - payload = json.loads(stdout.getvalue()) - assert payload["schemaId"] == "agent.semantic-protocols.semantic-workspace-scope" - assert payload["schemaVersion"] == "1" - assert payload["fingerprint"].startswith("sha256:") - assert payload["workspaceId"] == "python:scope-root" - assert payload["packageManager"] == "uv" - assert payload["sourceExtensions"] == [".py", ".pyi"] - assert payload["discoveryRoot"] == tmp_path.resolve().as_posix() - assert payload["admittedRoots"] == [tmp_path.resolve().as_posix()] - assert {anchor["kind"] for anchor in payload["anchors"]} == { - "pyproject", - "python-lock", - } - - registration = python_semantic_language_registration() - descriptor = next( - item - for item in registration["methodDescriptors"] - if item["method"] == "search/workspace-scope" - ) - assert descriptor["outputSchemaIds"] == [ - "agent.semantic-protocols.semantic-workspace-scope" - ] - assert descriptor["outputModes"] == ["json"] - assert any( - schema["schemaId"] == "agent.semantic-protocols.semantic-workspace-scope" - and schema["path"] == "schemas/semantic-workspace-scope.v1.schema.json" - for schema in registration["schemas"] - ) - - -def test_workspace_scope_admits_uv_member_outside_discovery_root( - tmp_path: Path, -) -> None: - root = tmp_path / "root" - sibling = tmp_path / "shared" - root.mkdir() - sibling.mkdir() - (root / "pyproject.toml").write_text( - '[project]\nname = "scope-root"\nversion = "0.1.0"\n' - '[tool.uv.workspace]\nmembers = ["../shared"]\n', - encoding="utf-8", - ) - (sibling / "pyproject.toml").write_text( - '[project]\nname = "shared-member"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - assert ( - run_cli( - ["search", "workspace-scope", "--json", "--workspace", str(root)], - stdout=stdout, - cwd=root, - ) - == 0 - ) - - payload = json.loads(stdout.getvalue()) - assert payload["schemaId"] == "agent.semantic-protocols.semantic-workspace-scope" - assert payload["admittedRoots"] == sorted( - [root.resolve().as_posix(), sibling.resolve().as_posix()] - ) - assert {package["name"] for package in payload["packages"]} == { - "scope-root", - "shared-member", - } - - -def test_workspace_scope_rejects_compact_mode(tmp_path: Path) -> None: - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "scope-root"\nversion = "0.1.0"\n', encoding="utf-8" - ) - stdout = io.StringIO() - stderr = io.StringIO() - - assert ( - run_cli( - ["search", "workspace-scope", "--workspace", str(tmp_path)], - stdout=stdout, - stderr=stderr, - cwd=tmp_path, - ) - == 3 - ) - assert "requires --json" in stderr.getvalue() - - -def test_provider_manifest_advertises_workspace_scope_capability() -> None: - manifest_path = Path(__file__).parents[3] / "provider/asp-provider-manifest.json" - manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - - assert manifest["searchCapabilities"]["workspaceScope"] is True 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/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] From 03fbe0eb421dc60b487f4a877168852086dcadc1 Mon Sep 17 00:00:00 2001 From: guangtao Date: Sun, 9 Aug 2026 00:43:20 -0700 Subject: [PATCH 10/20] fix: preserve lexical header field order --- tests/fixtures/bin/asp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 { From ebd8abc7424f5cec73fc1e7d820e8ddbda4e7a9f Mon Sep 17 00:00:00 2001 From: guangtao Date: Tue, 25 Aug 2026 08:17:44 +0800 Subject: [PATCH 11/20] feat: adopt resident Python HTTP provider --- .github/workflows/ci.yml | 4 +- README.md | 28 +- docs/01_core/101_harness_boundary.md | 4 +- docs/03_features/201_rule_catalog.md | 2 +- docs/03_features/203_cli.md | 34 +- provider/asp-provider-manifest.json | 211 ------------ provider/asp-provider-registration.json | 267 +++++++++++++++ provider/asp-provider-workspace-install.json | 18 +- pyproject.toml | 6 +- schemas/.asp-schema-manager-receipt.json | 321 ++++++++++++++++++ ...ent-cancellation-probe-request.schema.json | 16 + ...nt-cancellation-probe-response.schema.json | 20 ++ schemas/asp-client-conformance.schema.json | 30 ++ ...asp-client-exact-query-request.schema.json | 16 + ...sp-client-exact-query-response.schema.json | 60 ++++ schemas/asp-client-frame.schema.json | 146 ++++++++ ...sp-client-owner-search-request.schema.json | 17 + .../asp-client-protocol-catalog.schema.json | 102 ++++++ schemas/asp-client-search-request.schema.json | 19 ++ .../asp-client-server-descriptor.schema.json | 18 + ...ient-workspace-source-mutation.schema.json | 98 ++++++ ...allable-skeleton-projection.v1.schema.json | 173 ---------- schemas/callable-skeleton.schema.json | 19 ++ .../canonical-item-selector.v1.schema.json | 1 + ...nonical-language-item-identity.schema.json | 38 +++ schemas/exact-definitions.v1.schema.json | 26 ++ ...son => language-package-graph.schema.json} | 2 +- ...language-schema-bundle-receipt.schema.json | 38 +++ ...ma.json => project-resolution.schema.json} | 4 +- schemas/provider-definitions.v1.schema.json | 36 ++ ...guage-projection-batch-request.schema.json | 92 +++++ ...uage-projection-batch-response.schema.json | 146 ++++++++ ...ema.json => provider-manifest.schema.json} | 93 +++-- ...-method-argument-projection.v1.schema.json | 154 +++++++++ ...vider-native-exact-response.v1.schema.json | 2 +- ...project-resolution-descriptor.schema.json} | 13 +- ...er-project-resolution-request.schema.json} | 2 +- ...r-project-resolution-response.schema.json} | 24 +- ...rovider-query-pack-descriptor.schema.json} | 3 +- schemas/provider-registration.schema.json | 137 ++++++++ schemas/provider-route.schema.json | 254 ++++++++++++++ ...er-runtime-contract-descriptor.schema.json | 33 ++ ...der-runtime-request-stream-ack.schema.json | 15 + ...r-runtime-request-stream-frame.schema.json | 16 + .../provider-workspace-install.schema.json | 231 +++++++++++++ ...ython-semantic-capabilities.v1.schema.json | 4 +- ...antic-assurance-definitions.v1.schema.json | 139 ++++++++ ...antic-ast-patch-definitions.v1.schema.json | 25 ++ schemas/semantic-definitions.v1.schema.json | 154 +++++++++ .../semantic-fact-definitions.v1.schema.json | 133 ++++++++ schemas/semantic-fact-graph.v1.schema.json | 2 +- ...tic-graph-turbo-definitions.v1.schema.json | 64 ++++ .../semantic-language-registry.v1.schema.json | 2 +- ...mantic-search-storage-route.v1.schema.json | 106 ++++++ .../semantic-source-location.v1.schema.json | 4 +- .../semantic-structural-index.v1.schema.json | 88 +---- .../_callable_skeleton_projection.py | 13 +- src/python_lang_project_harness/_cli.py | 48 +-- src/python_lang_project_harness/_cli_agent.py | 2 +- src/python_lang_project_harness/_cli_args.py | 52 ++- .../_cli_protocol.py | 7 - .../_cli_query_arg_consume.py | 4 +- .../_cli_query_args.py | 2 +- .../_cli_query_hook_args.py | 2 +- .../_dev_command_log.py | 12 +- .../_dev_command_log_command.py | 2 +- .../_dev_command_log_context.py | 2 +- .../_evidence_graph.py | 18 +- .../_exact_projection_model.py | 4 +- .../_exact_source_projection.py | 56 +-- .../_flow_lite_query_packet.py | 2 +- .../_owner_search_stdin.py | 285 ---------------- .../_project_policy_catalog.py | 2 +- .../_project_resolution.py | 36 +- .../_project_resolution_document.py | 4 +- .../_project_resolution_graph.py | 6 + .../_projection_batch.py | 118 ++++--- src/python_lang_project_harness/_runtime.py | 120 +++++++ .../_runtime_http.py | 212 ++++++++++++ .../_semantic_graph_fact_render.py | 2 +- .../_semantic_graph_facts.py | 2 +- .../_semantic_language.py | 35 +- .../_semantic_language_ids.py | 8 +- .../_semantic_language_invocation.py | 10 +- .../_semantic_provider_doctor.py | 31 +- .../_semantic_search_cli.py | 2 +- .../_semantic_search_model.py | 2 +- .../harness/provider_runtime_live_support.py | 178 ++++++++++ tests/unit/harness/test_cli.py | 6 +- tests/unit/harness/test_dev_command_log.py | 9 +- tests/unit/harness/test_evidence_graph.py | 4 +- .../harness/test_exact_source_projection.py | 65 +--- tests/unit/harness/test_owner_search_stdin.py | 163 --------- tests/unit/harness/test_project_resolution.py | 79 +++-- tests/unit/harness/test_projection_batch.py | 46 +-- tests/unit/harness/test_provider_runtime.py | 86 +++++ .../unit/harness/test_public_cli_identity.py | 12 + tests/unit/harness/test_semantic_agent_cli.py | 4 +- tests/unit/harness/test_semantic_cli.py | 11 +- .../unit/harness/test_semantic_graph_facts.py | 4 +- .../harness/test_semantic_provider_doctor.py | 23 +- .../test_semantic_search_graph_profiles.py | 6 +- ...ot__py_proj_r011_verification_profile.snap | 2 +- tests/unit/test_package_metadata.py | 2 +- .../tree-sitter-python/query-corpus/README.md | 2 +- 105 files changed, 4078 insertions(+), 1435 deletions(-) delete mode 100644 provider/asp-provider-manifest.json create mode 100644 provider/asp-provider-registration.json create mode 100644 schemas/.asp-schema-manager-receipt.json create mode 100644 schemas/asp-client-cancellation-probe-request.schema.json create mode 100644 schemas/asp-client-cancellation-probe-response.schema.json create mode 100644 schemas/asp-client-conformance.schema.json create mode 100644 schemas/asp-client-exact-query-request.schema.json create mode 100644 schemas/asp-client-exact-query-response.schema.json create mode 100644 schemas/asp-client-frame.schema.json create mode 100644 schemas/asp-client-owner-search-request.schema.json create mode 100644 schemas/asp-client-protocol-catalog.schema.json create mode 100644 schemas/asp-client-search-request.schema.json create mode 100644 schemas/asp-client-server-descriptor.schema.json create mode 100644 schemas/asp-client-workspace-source-mutation.schema.json delete mode 100644 schemas/callable-skeleton-projection.v1.schema.json create mode 100644 schemas/callable-skeleton.schema.json create mode 100644 schemas/canonical-language-item-identity.schema.json create mode 100644 schemas/exact-definitions.v1.schema.json rename schemas/{language-package-graph.v1.schema.json => language-package-graph.schema.json} (99%) create mode 100644 schemas/language-schema-bundle-receipt.schema.json rename schemas/{project-resolution.v1.schema.json => project-resolution.schema.json} (97%) create mode 100644 schemas/provider-definitions.v1.schema.json create mode 100644 schemas/provider-language-projection-batch-request.schema.json create mode 100644 schemas/provider-language-projection-batch-response.schema.json rename schemas/{provider-manifest.v1.schema.json => provider-manifest.schema.json} (83%) create mode 100644 schemas/provider-method-argument-projection.v1.schema.json rename schemas/{provider-project-resolution-descriptor.v1.schema.json => provider-project-resolution-descriptor.schema.json} (60%) rename schemas/{provider-project-resolution-request.v1.schema.json => provider-project-resolution-request.schema.json} (98%) rename schemas/{provider-project-resolution-response.v1.schema.json => provider-project-resolution-response.schema.json} (67%) rename schemas/{provider-query-pack-descriptor.v1.schema.json => provider-query-pack-descriptor.schema.json} (96%) create mode 100644 schemas/provider-registration.schema.json create mode 100644 schemas/provider-route.schema.json create mode 100644 schemas/provider-runtime-contract-descriptor.schema.json create mode 100644 schemas/provider-runtime-request-stream-ack.schema.json create mode 100644 schemas/provider-runtime-request-stream-frame.schema.json create mode 100644 schemas/provider-workspace-install.schema.json create mode 100644 schemas/semantic-assurance-definitions.v1.schema.json create mode 100644 schemas/semantic-ast-patch-definitions.v1.schema.json create mode 100644 schemas/semantic-definitions.v1.schema.json create mode 100644 schemas/semantic-fact-definitions.v1.schema.json create mode 100644 schemas/semantic-graph-turbo-definitions.v1.schema.json create mode 100644 schemas/semantic-search-storage-route.v1.schema.json delete mode 100644 src/python_lang_project_harness/_owner_search_stdin.py create mode 100644 src/python_lang_project_harness/_runtime.py create mode 100644 src/python_lang_project_harness/_runtime_http.py create mode 100644 tests/unit/harness/provider_runtime_live_support.py delete mode 100644 tests/unit/harness/test_owner_search_stdin.py create mode 100644 tests/unit/harness/test_provider_runtime.py create mode 100644 tests/unit/harness/test_public_cli_identity.py 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/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/provider/asp-provider-manifest.json b/provider/asp-provider-manifest.json deleted file mode 100644 index c3d2f5b..0000000 --- a/provider/asp-provider-manifest.json +++ /dev/null @@ -1,211 +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", - "development": { - "schemaId": "agent.semantic-protocols.provider-development-descriptor", - "schemaVersion": "1", - "sourceRoot": "languages/python-lang-project-harness", - "buildBinding": "provider-workspace-install-v1", - "workspaceInstall": "provider/asp-provider-workspace-install.json", - "artifactDomain": "checkout" - }, - "execution": "external-process", - "searchCapabilities": { - "sourceSnapshot": { - "descriptorId": "python.source-snapshot", - "descriptorVersion": "1", - "languageId": "python", - "packetSchemaId": "asp.source-snapshot.v1", - "exactSourcePacketSchemaId": "asp.exact-source-query-result.v1", - "canonicalItemSelectorSchemaId": "asp.canonical-item-selector.v1", - "sourceSnapshotEnvelopeSchemaId": "asp.exact-source-snapshot-envelope.v1", - "derivedArtifactEvidenceSchemaId": "asp.derived-source-artifact-evidence.v1", - "algorithm": "blake3-merkle-v1", - "authority": "live-parser", - "exactSelectorResolution": "pinned-live-module-graph", - "overlayMode": "merkle-delta" - }, - "ownerItems": true, - "semanticFacts": true, - "dependencyTopology": true, - "dependencyTopologyMetadata": false - }, - "semanticFactsDescriptor": { - "descriptorId": "python.semantic-facts", - "descriptorVersion": "1", - "packetSchemaIds": [ - "semantic-fact-graph.v1", - "semantic-fact-ontology.v1" - ], - "factKinds": [ - "field" - ], - "intentAxes": [ - { - "axis": "data-shape", - "terms": [ - "fields", - "collection" - ] - }, - { - "axis": "collection", - "terms": [ - "list" - ] - } - ] - }, - "policy": { - "directSourceRead": "block", - "bulkSourceDump": "block", - "rawSourceSearch": "block", - "agentSearchJson": "block" - }, - "languageProjection": { - "schemaId": "agent.semantic-protocols.provider-language-projection-descriptor", - "schemaVersion": "1", - "commandBinding": "projection-batch-stdin", - "transport": "framed-stdin-v1", - "requestSchema": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.v1.schema.json", - "responseSchema": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.v1.schema.json", - "identitySchema": "https://schemas.agent-semantic-protocols.dev/canonical-language-item-identity.v1.schema.json" - }, - "projectResolution": { - "schemaId": "agent.semantic-protocols.provider-project-resolution-descriptor", - "schemaVersion": "1", - "capabilityId": "project-resolution", - "entryMarkers": [ - "pyproject.toml" - ], - "sourceExtensions": [ - ".py", - ".pyi" - ], - "manifestKinds": [ - "pep-621", - "uv-workspace", - "setuptools", - "hatch", - "poetry" - ], - "lockfileKinds": [ - "uv-lock", - "poetry-lock", - "pdm-lock" - ], - "parserId": "python.pyproject-toml", - "commandBinding": "project-resolution-stdin", - "requestSchema": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.v1.schema.json", - "responseSchema": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.v1.schema.json", - "packageGraphSchema": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json", - "projectResolutionSchema": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json" - }, - "queryPackDescriptor": { - "descriptorId": "python.query-pack", - "descriptorVersion": "1", - "languageId": "python", - "semanticFactsDescriptorId": "python.semantic-facts", - "termRoleOverrides": [], - "recipes": [ - { - "recipeId": "python-asyncio-runtime", - "trigger": { - "terms": [ - "asyncio", - "task", - "scheduling" - ], - "match": "any" - }, - "clauses": [ - { - "terms": [ - "asyncio", - "task", - "scheduling" - ], - "roles": [ - "concept" - ], - "intentAxes": [ - "concurrency" - ] - } - ] - }, - { - "recipeId": "python-context-lifecycle", - "trigger": { - "terms": [ - "contextmanager", - "resource", - "lifecycle" - ], - "match": "any" - }, - "clauses": [ - { - "terms": [ - "contextmanager", - "resource", - "lifecycle" - ], - "roles": [ - "concept" - ], - "intentAxes": [ - "resource-lifecycle" - ] - } - ] - }, - { - "recipeId": "python-stream-backpressure", - "trigger": { - "terms": [ - "queue", - "async-generator", - "backpressure" - ], - "match": "any" - }, - "clauses": [ - { - "terms": [ - "queue", - "async-generator", - "backpressure" - ], - "roles": [ - "concept" - ], - "intentAxes": [ - "collection", - "stream" - ] - } - ] - } - ] - }, - "routeBindings": { - "prime": "search/prime", - "owner": "search/owner", - "lexical": "search/lexical", - "query": "query/exact-selector", - "exactSelectorNative": "query/exact-selector-native-v1", - "ingest": "search/ingest", - "checkChanged": "check/changed", - "guide": "guide", - "dependencyTopology": "search/dependency-topology" - } -} 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 index 751c529..2eeba4d 100644 --- a/provider/asp-provider-workspace-install.json +++ b/provider/asp-provider-workspace-install.json @@ -1,16 +1,26 @@ { + "$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/", - "providerId": "py-harness", - "binary": "py-harness", + "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/py-harness", + "entrypoint": "bin/asp-python", + "runtimeDependencies": [ + { + "source": "lib/libpython3.13.dylib", + "target": "lib/libpython3.13.dylib" + } + ], "launch": { "program": "bin/python3", "args": [ - "bin/py-harness" + "bin/asp-python" ], "programRelativeToArtifact": true, "argsRelativeToArtifact": true diff --git a/pyproject.toml b/pyproject.toml index 004c059..f71d32d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" @@ -39,7 +39,6 @@ packages = [ ] [tool.hatch.build.targets.wheel.force-include] -"provider/asp-provider-manifest.json" = "python_lang_project_harness/asp-provider-manifest.json" "schemas/semantic-language-registry.v1.schema.json" = "python_lang_project_harness/schemas/semantic-language-registry.v1.schema.json" [tool.uv] @@ -47,9 +46,6 @@ package = true [tool.pytest.ini_options] pythonpath = ["src"] -addopts = [ - "--python-project-harness", -] [[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-projection.v1.schema.json b/schemas/callable-skeleton-projection.v1.schema.json deleted file mode 100644 index b62c05c..0000000 --- a/schemas/callable-skeleton-projection.v1.schema.json +++ /dev/null @@ -1,173 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.dev/schemas/callable-skeleton-projection.v1.schema.json", - "title": "Callable Skeleton Projection V1", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "projectionKind", - "languageId", - "providerId", - "rootSelector", - "rootNodeId", - "callable", - "nodes", - "relations", - "cost" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.callable-skeleton-projection" - }, - "schemaVersion": { - "const": "1" - }, - "projectionKind": { - "const": "callable-skeleton" - }, - "languageId": { - "type": "string", - "minLength": 1 - }, - "providerId": { - "type": "string", - "minLength": 1 - }, - "rootSelector": { - "$ref": "exact-structural-selector.v1.schema.json" - }, - "rootNodeId": { - "type": "string", - "minLength": 1 - }, - "callable": { - "$ref": "#/$defs/callable" - }, - "nodes": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/node" - } - }, - "relations": { - "type": "array", - "items": { - "$ref": "#/$defs/relation" - } - }, - "cost": { - "$ref": "#/$defs/cost" - }, - "omissionReasons": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "languageFacts": { - "type": "object", - "additionalProperties": true - } - }, - "$defs": { - "callable": { - "type": "object", - "additionalProperties": false, - "required": ["kind", "displayName", "signature"], - "properties": { - "kind": {"type": "string", "minLength": 1}, - "displayName": {"type": "string", "minLength": 1}, - "signature": {"type": "string"} - } - }, - "node": { - "type": "object", - "additionalProperties": false, - "required": ["nodeId", "kind", "label", "order", "queryable"], - "properties": { - "nodeId": {"type": "string", "minLength": 1}, - "kind": { - "enum": [ - "callable", - "branch", - "arm", - "loop", - "exception", - "resource-scope", - "invocation", - "binding", - "exit", - "suspension", - "nested-declaration", - "language-extension" - ] - }, - "label": {"type": "string"}, - "order": {"type": "integer", "minimum": 0}, - "queryable": {"type": "boolean"}, - "exactSelector": { - "$ref": "exact-structural-selector.v1.schema.json" - }, - "sourceLocatorHint": { - "$ref": "#/$defs/sourceLocatorHint" - }, - "languageFacts": { - "type": "object", - "additionalProperties": true - } - }, - "allOf": [ - { - "if": { - "properties": {"queryable": {"const": true}}, - "required": ["queryable"] - }, - "then": {"required": ["exactSelector"]}, - "else": {"not": {"required": ["exactSelector"]}} - } - ] - }, - "relation": { - "type": "object", - "additionalProperties": false, - "required": ["fromNodeId", "toNodeId", "kind"], - "properties": { - "fromNodeId": {"type": "string", "minLength": 1}, - "toNodeId": {"type": "string", "minLength": 1}, - "kind": {"type": "string", "minLength": 1} - } - }, - "cost": { - "type": "object", - "additionalProperties": false, - "required": ["sourceBytes", "projectedBytes", "omittedBytes"], - "properties": { - "sourceBytes": {"type": "integer", "minimum": 0}, - "projectedBytes": {"type": "integer", "minimum": 0}, - "omittedBytes": {"type": "integer", "minimum": 0}, - "estimatedSourceTokens": {"type": "integer", "minimum": 0}, - "estimatedProjectedTokens": {"type": "integer", "minimum": 0}, - "tokenEstimator": {"type": "string", "minLength": 1} - }, - "dependentRequired": { - "estimatedSourceTokens": ["estimatedProjectedTokens", "tokenEstimator"], - "estimatedProjectedTokens": ["estimatedSourceTokens", "tokenEstimator"], - "tokenEstimator": ["estimatedSourceTokens", "estimatedProjectedTokens"] - } - }, - "sourceLocatorHint": { - "type": "object", - "additionalProperties": false, - "properties": { - "displayLineStart": {"type": "integer", "minimum": 0}, - "displayLineEnd": {"type": "integer", "minimum": 0}, - "sourceByteStart": {"type": "integer", "minimum": 0}, - "sourceByteEnd": {"type": "integer", "minimum": 0} - } - } - } -} 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 index af4259a..009af91 100644 --- a/schemas/canonical-item-selector.v1.schema.json +++ b/schemas/canonical-item-selector.v1.schema.json @@ -37,6 +37,7 @@ }, "structuralSelector": { "type": "string", + "description": "Canonical selector whose rightmost # separates the verbatim owner path from the item identity fragment.", "minLength": 1 } }, 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/language-package-graph.v1.schema.json b/schemas/language-package-graph.schema.json similarity index 99% rename from schemas/language-package-graph.v1.schema.json rename to schemas/language-package-graph.schema.json index a291a60..4b2f375 100644 --- a/schemas/language-package-graph.v1.schema.json +++ b/schemas/language-package-graph.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json", + "$id": "https://schemas.agent-semantic-protocols.dev/language-package-graph.schema.json", "title": "Language Package Graph", "type": "object", "additionalProperties": false, 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.v1.schema.json b/schemas/project-resolution.schema.json similarity index 97% rename from schemas/project-resolution.v1.schema.json rename to schemas/project-resolution.schema.json index 937a0fc..548b2d9 100644 --- a/schemas/project-resolution.v1.schema.json +++ b/schemas/project-resolution.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json", + "$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", @@ -19,7 +19,7 @@ "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.v1.schema.json" }, + "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", 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-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.v1.schema.json b/schemas/provider-manifest.schema.json similarity index 83% rename from schemas/provider-manifest.v1.schema.json rename to schemas/provider-manifest.schema.json index c5e47e6..6fb51d3 100644 --- a/schemas/provider-manifest.v1.schema.json +++ b/schemas/provider-manifest.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-manifest.v1.schema.json", - "title": "ASP Provider Manifest v1", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-manifest.schema.json", + "title": "ASP Provider Manifest", "type": "object", "additionalProperties": false, "required": [ @@ -15,6 +15,7 @@ "providerId", "namespace", "binary", + "runtimeContract", "development", "searchCapabilities", "queryPackDescriptor", @@ -50,7 +51,7 @@ }, "providerId": { "type": "string", - "minLength": 1 + "pattern": "^asp-[a-z0-9][a-z0-9-]*$" }, "namespace": { "type": "string", @@ -60,6 +61,9 @@ "type": "string", "minLength": 1 }, + "runtimeContract": { + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-runtime-contract-descriptor.schema.json" + }, "execution": { "$ref": "#/$defs/providerExecution" }, @@ -67,7 +71,7 @@ "$ref": "#/$defs/providerDevelopmentDescriptor" }, "projectResolution": { - "$ref": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-descriptor.v1.schema.json" + "$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" @@ -79,7 +83,7 @@ "$ref": "#/$defs/providerSemanticFactsDescriptor" }, "queryPackDescriptor": { - "$ref": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-query-pack-descriptor.v1.schema.json" + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-query-pack-descriptor.schema.json" }, "policy": { "$ref": "#/$defs/hookPolicy" @@ -88,29 +92,70 @@ "$ref": "#/$defs/hookRouteBindings" } }, - "oneOf": [ + "allOf": [ { - "required": [ - "projectResolution" - ], - "not": { - "required": [ - "documentResolution" - ] - } - }, - { - "required": [ - "documentResolution" - ], - "not": { - "required": [ - "projectResolution" - ] - } + "$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": { 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-response.v1.schema.json b/schemas/provider-native-exact-response.v1.schema.json index 9155dfe..e817db8 100644 --- a/schemas/provider-native-exact-response.v1.schema.json +++ b/schemas/provider-native-exact-response.v1.schema.json @@ -112,7 +112,7 @@ "type": "string" }, "projectionPayload": { - "$ref": "https://agent-semantic-protocols.dev/schemas/callable-skeleton-projection.v1.schema.json" + "$ref": "https://agent-semantic-protocols.dev/schemas/callable-skeleton.schema.json" }, "sourceContentDigest": { "type": "string", diff --git a/schemas/provider-project-resolution-descriptor.v1.schema.json b/schemas/provider-project-resolution-descriptor.schema.json similarity index 60% rename from schemas/provider-project-resolution-descriptor.v1.schema.json rename to schemas/provider-project-resolution-descriptor.schema.json index 9998ebf..a72c127 100644 --- a/schemas/provider-project-resolution-descriptor.v1.schema.json +++ b/schemas/provider-project-resolution-descriptor.schema.json @@ -1,23 +1,20 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-descriptor.v1.schema.json", + "$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", "commandBinding", "requestSchema", "responseSchema", "packageGraphSchema", "projectResolutionSchema"], + "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", "items": { "type": "string", "minLength": 1 }, "minItems": 1, "uniqueItems": true }, + "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 }, - "commandBinding": { "const": "project-resolution-stdin" }, - "requestSchema": { "const": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.v1.schema.json" }, - "responseSchema": { "const": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.v1.schema.json" }, - "packageGraphSchema": { "const": "https://schemas.agent-semantic-protocols.dev/language-package-graph.v1.schema.json" }, - "projectResolutionSchema": { "const": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json" } + "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.v1.schema.json b/schemas/provider-project-resolution-request.schema.json similarity index 98% rename from schemas/provider-project-resolution-request.v1.schema.json rename to schemas/provider-project-resolution-request.schema.json index bddbcd1..865ba36 100644 --- a/schemas/provider-project-resolution-request.v1.schema.json +++ b/schemas/provider-project-resolution-request.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.v1.schema.json", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.schema.json", "title": "Provider Project Scope Request", "type": "object", "additionalProperties": false, diff --git a/schemas/provider-project-resolution-response.v1.schema.json b/schemas/provider-project-resolution-response.schema.json similarity index 67% rename from schemas/provider-project-resolution-response.v1.schema.json rename to schemas/provider-project-resolution-response.schema.json index 9a5237f..dba484c 100644 --- a/schemas/provider-project-resolution-response.v1.schema.json +++ b/schemas/provider-project-resolution-response.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.v1.schema.json", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.schema.json", "title": "Provider Project Scope Response", "type": "object", "additionalProperties": false, @@ -10,15 +10,29 @@ "schemaVersion": { "const": "1" }, "languageId": { "type": "string", "minLength": 1 }, "providerId": { "type": "string", "minLength": 1 }, - "state": { "enum": ["resolved", "failed"] }, - "scope": { "$ref": "https://schemas.agent-semantic-protocols.dev/project-resolution.v1.schema.json" }, + "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"] } }, - "else": { "required": ["failure"], "not": { "required": ["scope"] } } + "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": { diff --git a/schemas/provider-query-pack-descriptor.v1.schema.json b/schemas/provider-query-pack-descriptor.schema.json similarity index 96% rename from schemas/provider-query-pack-descriptor.v1.schema.json rename to schemas/provider-query-pack-descriptor.schema.json index ba35132..fe03f0b 100644 --- a/schemas/provider-query-pack-descriptor.v1.schema.json +++ b/schemas/provider-query-pack-descriptor.schema.json @@ -1,6 +1,6 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-query-pack-descriptor.v1.schema.json", + "$id": "https://schemas.agent-semantic-protocols.dev/provider-query-pack-descriptor.schema.json", "title": "Provider Query Pack Descriptor v1", "type": "object", "additionalProperties": false, @@ -35,7 +35,6 @@ }, "recipes": { "type": "array", - "minItems": 1, "items": { "$ref": "#/$defs/recipe" } 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 f1673a9..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" 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-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-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 5a8f2e5..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"] 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-language-registry.v1.schema.json b/schemas/semantic-language-registry.v1.schema.json index 8c480a4..899905d 100644 --- a/schemas/semantic-language-registry.v1.schema.json +++ b/schemas/semantic-language-registry.v1.schema.json @@ -213,7 +213,7 @@ } }, "queryPackDescriptor": { - "$ref": "https://tao3k.github.io/agent-semantic-protocols/schemas/provider-query-pack-descriptor.v1.schema.json" + "$ref": "https://schemas.agent-semantic-protocols.dev/provider-query-pack-descriptor.schema.json" } } }, 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 2d3b7f0..89038ad 100644 --- a/schemas/semantic-source-location.v1.schema.json +++ b/schemas/semantic-source-location.v1.schema.json @@ -57,9 +57,9 @@ }, "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": { "$ref": "semantic-definitions.v1.schema.json#/$defs/location" diff --git a/schemas/semantic-structural-index.v1.schema.json b/schemas/semantic-structural-index.v1.schema.json index 76fcd39..8b9516c 100644 --- a/schemas/semantic-structural-index.v1.schema.json +++ b/schemas/semantic-structural-index.v1.schema.json @@ -74,12 +74,6 @@ "$ref": "#/$defs/fileHash" } }, - "compileContexts": { - "type": "array", - "items": { - "$ref": "#/$defs/compileContext" - } - }, "owners": { "type": "array", "items": { @@ -96,22 +90,10 @@ "type": "integer", "minimum": 0 }, - "occurrences": { - "type": "array", - "items": { - "$ref": "#/$defs/occurrence" - } - }, - "relations": { - "type": "array", - "items": { - "$ref": "#/$defs/semanticRelation" - } - }, "syntaxFacts": { "type": "array", "items": { - "$ref": "https://agent-semantic-protocols.local/schemas/semantic-native-syntax-fact-index.v1.schema.json#/$defs/nativeSyntaxFact" + "$ref": "semantic-native-syntax-fact-index.v1.schema.json#/$defs/nativeSyntaxFact" } }, "dependencyUsages": { @@ -141,29 +123,10 @@ }, "source": { "type": "string" - }, - "compileContextDigest": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" } }, "additionalProperties": true }, - "compileContext": { - "type": "object", - "required": ["translationUnit", "digest"], - "properties": { - "translationUnit": { - "type": "string", - "minLength": 1 - }, - "digest": { - "type": "string", - "pattern": "^[0-9a-f]{64}$" - } - }, - "additionalProperties": false - }, "owner": { "type": "object", "required": ["ownerPath", "ownerKind", "sourceAuthority", "queryKeys"], @@ -226,55 +189,6 @@ }, "additionalProperties": true }, - "occurrence": { - "type": "object", - "required": ["id", "ownerPath", "name", "kind", "sourceLocator", "queryKeys"], - "properties": { - "id": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "type": "string", - "minLength": 1 - }, - "name": { - "type": "string", - "minLength": 1 - }, - "kind": { - "type": "string", - "minLength": 1 - }, - "sourceLocator": { - "type": "string", - "minLength": 1 - }, - "targetSymbolId": { - "type": "string", - "minLength": 1 - }, - "containerSymbolId": { - "type": "string", - "minLength": 1 - }, - "queryKeys": { - "$ref": "#/$defs/queryKeys" - } - }, - "additionalProperties": true - }, - "semanticRelation": { - "allOf": [ - { - "$ref": "#/$defs/occurrence" - }, - { - "type": "object", - "required": ["targetSymbolId"] - } - ] - }, "dependencyUsage": { "type": "object", "required": ["ownerPath", "packageName", "source", "queryKeys"], diff --git a/src/python_lang_project_harness/_callable_skeleton_projection.py b/src/python_lang_project_harness/_callable_skeleton_projection.py index c0aa065..d148d9f 100644 --- a/src/python_lang_project_harness/_callable_skeleton_projection.py +++ b/src/python_lang_project_harness/_callable_skeleton_projection.py @@ -1,3 +1,5 @@ +"""Project callable skeletons from native Python AST owners.""" + from __future__ import annotations import ast @@ -7,7 +9,6 @@ from ._exact_projection_model import ( CANONICAL_SELECTOR_SCHEMA_ID, EXACT_SELECTOR_SCHEMA_ID, - SKELETON_SCHEMA_ID, ExactSelector, ProjectionSegment, node_byte_span, @@ -95,7 +96,7 @@ def callable_skeleton_payload( "label": function.name, "order": 0, "queryable": True, - "exactSelector": root_exact, + "selector": root_exact["selector"], "languageFacts": { "async": isinstance(function, ast.AsyncFunctionDef), "decoratorCount": len(function.decorator_list), @@ -115,7 +116,7 @@ def callable_skeleton_payload( "label": segment.label, "order": segment.ordinal, "queryable": True, - "exactSelector": exact_selector(request, selector, segment), + "selector": exact_selector(request, selector, segment)["selector"], "sourceLocatorHint": { "sourceByteStart": segment.byte_start, "sourceByteEnd": segment.byte_end, @@ -139,12 +140,6 @@ def callable_skeleton_payload( ) projected_bytes = min(source_bytes, structural_bytes) return { - "schemaId": SKELETON_SCHEMA_ID, - "schemaVersion": "1", - "projectionKind": "callable-skeleton", - "languageId": "python", - "providerId": "py-harness", - "rootSelector": root_exact, "rootNodeId": "callable:root", "callable": { "kind": selector.kind, diff --git a/src/python_lang_project_harness/_cli.py b/src/python_lang_project_harness/_cli.py index 6dc9c48..8c0f580 100644 --- a/src/python_lang_project_harness/_cli.py +++ b/src/python_lang_project_harness/_cli.py @@ -18,15 +18,13 @@ def run_cli_from_env() -> int: args = sys.argv[1:] log = start_dev_command_log(args, Path.cwd()) try: - stdin = ( - b"" - if sys.stdin.isatty() and args[:1] == ["projection-batch-stdin"] - else ( - sys.stdin.buffer.read() - if args[:1] == ["projection-batch-stdin"] - else ("" if sys.stdin.isatty() else sys.stdin.read()) - ) - ) + if args == ["serve"]: + from ._runtime import serve_provider_runtime + + exit_code = serve_provider_runtime(Path.cwd()) + log.finish(exit_code) + return exit_code + stdin = "" if sys.stdin.isatty() else sys.stdin.read() exit_code = run_cli(args, stdin=stdin) log.finish(exit_code) return exit_code @@ -48,38 +46,6 @@ def run_cli( selected_stdout = sys.stdout if stdout is None else stdout selected_stderr = sys.stderr if stderr is None else stderr selected_cwd = Path.cwd() if cwd is None else cwd - selected_stdin = "" if stdin is None else stdin - from ._exact_source_projection import try_run_provider_native_exact - from ._owner_search_stdin import try_run_provider_native_owner - - native_owner_exit = try_run_provider_native_owner( - args, - stdin=selected_stdin, - cwd=selected_cwd, - stdout=selected_stdout, - stderr=selected_stderr, - ) - if native_owner_exit is not None: - return native_owner_exit - native_exact_exit = try_run_provider_native_exact( - args, - stdin=selected_stdin, - cwd=selected_cwd, - stdout=selected_stdout, - stderr=selected_stderr, - ) - if native_exact_exit is not None: - return native_exact_exit - from ._project_resolution import try_run_project_resolution - - project_resolution_exit = try_run_project_resolution( - args, - stdin=selected_stdin, - cwd=selected_cwd, - stdout=selected_stdout, - ) - if project_resolution_exit is not None: - return project_resolution_exit protocol_args = ProtocolArgs.parse(args) if protocol_args is not None: return run_protocol_cli( diff --git a/src/python_lang_project_harness/_cli_agent.py b/src/python_lang_project_harness/_cli_agent.py index 3d9dffc..a90e199 100644 --- a/src/python_lang_project_harness/_cli_agent.py +++ b/src/python_lang_project_harness/_cli_agent.py @@ -15,7 +15,7 @@ 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," diff --git a/src/python_lang_project_harness/_cli_args.py b/src/python_lang_project_harness/_cli_args.py index 9e96a31..4f42a93 100644 --- a/src/python_lang_project_harness/_cli_args.py +++ b/src/python_lang_project_harness/_cli_args.py @@ -57,8 +57,6 @@ def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: return cls._parse_agent(args[1:]) if command == "ast-patch": return cls._parse_ast_patch(args[1:]) - if command == "projection-batch-stdin": - return cls(command) return None @classmethod @@ -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,18 +386,18 @@ 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] [--package PATH] [--workspace ]\n" + " asp-python search ... [--json] [--package PATH] [--workspace ]\n" " asp python query --selector --projection --workspace \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 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" @@ -460,22 +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" + " 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" - " py-harness query --catalog flow-lite --where 'source.call=payload sink.constructs=Action scope.fn=collect' .\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" - " 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 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_protocol.py b/src/python_lang_project_harness/_cli_protocol.py index aa391ab..a44c306 100644 --- a/src/python_lang_project_harness/_cli_protocol.py +++ b/src/python_lang_project_harness/_cli_protocol.py @@ -28,13 +28,6 @@ def run_protocol_cli( if args.command == "help": stdout.write(help_text()) return 0 - if args.command == "projection-batch-stdin": - from ._projection_batch import render_projection_batch - - frame = stdin if isinstance(stdin, bytes) else stdin.encode("utf-8") - stdout.write(render_projection_batch(frame)) - return 0 - project_root = _resolve_project_root(args, cwd) if args.command == "agent": return _run_agent_command(args, project_root=project_root, stdout=stdout) 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 ae4f3b2..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,9 +13,9 @@ from ._tree_sitter_query_predicates import SyntaxQueryPredicate QUERY_USAGE = ( - "usage: py-harness query (--catalog ID | --treesitter-query EXPR) " + "usage: asp-python 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 ]" + "or asp-python query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION' [] [--json] [--workspace ]" ) diff --git a/src/python_lang_project_harness/_cli_query_args.py b/src/python_lang_project_harness/_cli_query_args.py index 323f500..5fe4cb4 100644 --- a/src/python_lang_project_harness/_cli_query_args.py +++ b/src/python_lang_project_harness/_cli_query_args.py @@ -28,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") 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 07c9373..8fba94b 100644 --- a/src/python_lang_project_harness/_cli_query_hook_args.py +++ b/src/python_lang_project_harness/_cli_query_hook_args.py @@ -6,7 +6,7 @@ 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()) 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 index f3adb8b..a9d6fd6 100644 --- a/src/python_lang_project_harness/_exact_projection_model.py +++ b/src/python_lang_project_harness/_exact_projection_model.py @@ -1,3 +1,5 @@ +"""Define exact Python selector and projection value boundaries.""" + from __future__ import annotations import ast @@ -7,7 +9,7 @@ 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-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" diff --git a/src/python_lang_project_harness/_exact_source_projection.py b/src/python_lang_project_harness/_exact_source_projection.py index 3273aa0..51f00c7 100644 --- a/src/python_lang_project_harness/_exact_source_projection.py +++ b/src/python_lang_project_harness/_exact_source_projection.py @@ -1,10 +1,11 @@ +"""Execute resident exact-source projections with the native Python AST.""" + from __future__ import annotations import ast import base64 -import json from pathlib import Path -from typing import Any, TextIO +from typing import Any from ._callable_skeleton_projection import ( callable_skeleton_payload, @@ -16,7 +17,6 @@ ExactSelector, ProjectionSegment, find_function, - flag_value, line_byte_offsets, node_byte_span, parse_selector, @@ -25,33 +25,16 @@ ) -def try_run_provider_native_exact( - args: list[str] | tuple[str, ...], - *, - stdin: str, - cwd: Path, - stdout: TextIO, - stderr: TextIO, -) -> int | None: - if "--asp-exact-request-stdin" not in args: - return None - try: - request = json.loads(stdin) - packet = _project_request(args, request, cwd) - except (UnicodeDecodeError, ValueError, json.JSONDecodeError) as error: - stderr.write(f"provider-native exact query failed: {error}\n") - return 2 - stdout.write(json.dumps(packet, separators=(",", ":"), sort_keys=True)) - stdout.write("\n") - return 0 +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( - args: list[str] | tuple[str, ...], - request: dict[str, Any], - cwd: Path, -) -> dict[str, Any]: - _validate_request_identity(args, request) + +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") @@ -119,29 +102,18 @@ def _source_packet( ) -def _validate_request_identity( - args: list[str] | tuple[str, ...], request: dict[str, Any] -) -> None: +def _validate_request(request: dict[str, Any]) -> None: expected = { "schemaId": REQUEST_SCHEMA_ID, "schemaVersion": "1", "languageId": "python", - "providerId": "py-harness", + "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 flag, field in ( - ("--asp-provider-id", "providerId"), - ("--asp-parser-identity-digest", "parserIdentityDigest"), - ("--asp-query-pack-digest", "queryPackDigest"), - ): - if flag_value(args, flag) != required_text(request, field): - raise ValueError(f"exact request authority mismatch for {field}") - if flag_value(args, "--selector") != required_text(request, "structuralSelector"): - raise ValueError("exact request selector does not match CLI authority") for field in ( "generationIdentityDigest", "parserIdentityDigest", @@ -165,7 +137,7 @@ def _projection_packet( "schemaId": RESPONSE_SCHEMA_ID, "schemaVersion": "1", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "projectionMode": projection_kind, "requestedStructuralSelector": selector.requested, "structuralSelector": selector.requested, 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/_owner_search_stdin.py b/src/python_lang_project_harness/_owner_search_stdin.py deleted file mode 100644 index c7298a3..0000000 --- a/src/python_lang_project_harness/_owner_search_stdin.py +++ /dev/null @@ -1,285 +0,0 @@ -from __future__ import annotations - -import ast -import base64 -import binascii -import json -from pathlib import Path -from typing import Any, TextIO -from urllib.parse import quote - -from blake3 import blake3 - -from ._exact_projection_model import line_byte_offsets, node_byte_span - -REQUEST_SCHEMA_ID = "agent.semantic-protocols.provider-native-owner-search-request" -RESPONSE_SCHEMA_ID = "agent.semantic-protocols.provider-native-owner-search-response" -SCHEMA_VERSION = "1" - - -def try_run_provider_native_owner( - args: list[str] | tuple[str, ...], - *, - stdin: str, - cwd: Path, - stdout: TextIO, - stderr: TextIO, -) -> int | None: - del cwd - if not args or args[0] != "owner-search-stdin": - return None - try: - provider_id = _flag_value(args, "--asp-provider-id") - if provider_id is None: - raise ValueError("owner-search stdin requires --asp-provider-id") - request = json.loads(stdin) - if not isinstance(request, dict): - raise ValueError("owner-search request must be a JSON object") - source = _validate_request(request, provider_id) - response = _project_owner(request, provider_id, source) - stdout.write(json.dumps(response, separators=(",", ":"), sort_keys=True)) - return 0 - except ( - UnicodeDecodeError, - ValueError, - binascii.Error, - json.JSONDecodeError, - ) as error: - stderr.write(f"{error}\n") - return 2 - - -def _validate_request(request: dict[str, Any], provider_id: str) -> bytes: - expected_fields = { - "schemaId", - "schemaVersion", - "languageId", - "providerId", - "workspaceIdentity", - "providerWorkspaceIdentityDigest", - "ownerPath", - "sourceFingerprint", - "sourceEncoding", - "sourceBytesBase64", - "projectionMode", - "transport", - } - if set(request) != expected_fields: - raise ValueError("owner-search request fields drift") - if ( - request["schemaId"] != REQUEST_SCHEMA_ID - or request["schemaVersion"] != SCHEMA_VERSION - or request["languageId"] != "python" - or request["providerId"] != provider_id - or not _nonempty_text(request["workspaceIdentity"]) - or not _digest_text(request["providerWorkspaceIdentityDigest"]) - or not _nonempty_text(request["ownerPath"]) - or request["sourceEncoding"] != "base64" - or request["projectionMode"] != "complete-owner" - or request["transport"] != "stdin-json" - ): - raise ValueError("owner-search request identity or completeness drift") - - fingerprint = request["sourceFingerprint"] - if not isinstance(fingerprint, dict) or set(fingerprint) != { - "fileIdentity", - "sizeBytes", - "modifiedUnixNanos", - "changeTimeUnixNanos", - "contentDigest", - }: - raise ValueError("owner-search source fingerprint drift") - if ( - not _nonempty_text(fingerprint["fileIdentity"]) - or not _nonnegative_int(fingerprint["sizeBytes"]) - or not _nonnegative_int(fingerprint["modifiedUnixNanos"]) - or not _nonnegative_int(fingerprint["changeTimeUnixNanos"]) - or not _digest_text(fingerprint["contentDigest"]) - ): - raise ValueError("owner-search source fingerprint is incomplete") - - encoded = request["sourceBytesBase64"] - if not isinstance(encoded, str): - raise ValueError("owner-search source base64 must be text") - source = base64.b64decode(encoded, validate=True) - if len(source) != fingerprint["sizeBytes"]: - raise ValueError("owner-search source size drift") - if blake3(source).hexdigest() != fingerprint["contentDigest"]: - raise ValueError("owner-search source content digest drift") - source.decode("utf-8") - return source - - -def _project_owner( - request: dict[str, Any], provider_id: str, source: bytes -) -> dict[str, Any]: - source_text = source.decode("utf-8") - tree = ast.parse(source_text) - offsets = line_byte_offsets(source) - projections = _owner_projections(tree.body, request["ownerPath"], offsets, []) - projections.sort( - key=lambda projection: ( - projection["sourceByteStart"], - projection["canonicalItemSelector"]["structuralSelector"], - ) - ) - return { - "schemaId": RESPONSE_SCHEMA_ID, - "schemaVersion": SCHEMA_VERSION, - "languageId": "python", - "providerId": provider_id, - "requestedOwnerPath": request["ownerPath"], - "requestedProjectionMode": request["projectionMode"], - "sourceContentDigest": request["sourceFingerprint"]["contentDigest"], - "parsedOwnerCount": 1, - "projectionCompleteness": "complete-owner", - "projections": projections, - } - - -def _function_projection( - node: ast.FunctionDef | ast.AsyncFunctionDef, - owner_path: str, - offsets: list[int], - scopes: list[dict[str, str]], - item_kind: str, -) -> dict[str, Any]: - byte_start, byte_end = node_byte_span(node, offsets) - prefix = "async def" if isinstance(node, ast.AsyncFunctionDef) else "def" - signature = f"{prefix} {node.name}({ast.unparse(node.args)})" - if node.returns is not None: - signature += f" -> {ast.unparse(node.returns)}" - selector = _canonical_item_selector( - owner_path=owner_path, - item_kind=item_kind, - symbol=node.name, - scopes=scopes, - ) - return { - "canonicalItemSelector": selector, - "signature": signature, - "captureName": "function_definition/name", - "sourceByteStart": byte_start, - "sourceByteEnd": byte_end, - } - - -def _owner_projections( - nodes: list[ast.stmt], - owner_path: str, - offsets: list[int], - scopes: list[dict[str, str]], -) -> list[dict[str, Any]]: - projections: list[dict[str, Any]] = [] - for node in nodes: - if isinstance(node, ast.ClassDef): - byte_start, byte_end = node_byte_span(node, offsets) - projections.append( - { - "canonicalItemSelector": _canonical_item_selector( - owner_path=owner_path, - item_kind="class", - symbol=node.name, - scopes=scopes, - ), - "signature": f"class {node.name}", - "captureName": "class_definition/name", - "sourceByteStart": byte_start, - "sourceByteEnd": byte_end, - } - ) - projections.extend( - _owner_projections( - node.body, - owner_path, - offsets, - [ - *scopes, - { - "relation": "class-owner", - "kind": "class", - "symbol": node.name, - }, - ], - ) - ) - continue - if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - continue - item_kind = ( - "method" - if scopes and scopes[-1]["relation"] == "class-owner" - else "function" - ) - projections.append( - _function_projection(node, owner_path, offsets, scopes, item_kind) - ) - projections.extend( - _owner_projections( - node.body, - owner_path, - offsets, - [ - *scopes, - { - "relation": "lexical-owner", - "kind": "function", - "symbol": node.name, - }, - ], - ) - ) - return projections - - -def _canonical_item_selector( - *, - owner_path: str, - item_kind: str, - symbol: str, - scopes: list[dict[str, str]], -) -> dict[str, Any]: - identity_path = f"item/{_component(item_kind)}/{_component(symbol)}" - identity_path += "".join( - f"/scope/{_component(scope['relation'])}/{_component(scope['kind'])}/{_component(scope['symbol'])}" - for scope in scopes - ) - return { - "schemaId": "asp.canonical-item-selector.v1", - "schemaVersion": "1", - "languageId": "python", - "kind": item_kind, - "symbol": symbol, - "scopes": list(scopes), - "structuralSelector": f"python://{owner_path}#{identity_path}", - } - - -def _component(value: str) -> str: - return quote(value, safe="-._~") - - -def _flag_value(args: list[str] | tuple[str, ...], flag: str) -> str | None: - try: - index = args.index(flag) - except ValueError: - return None - if index + 1 >= len(args): - return None - return args[index + 1] - - -def _nonempty_text(value: object) -> bool: - return isinstance(value, str) and bool(value) - - -def _digest_text(value: object) -> bool: - return ( - isinstance(value, str) - and len(value) == 64 - and all(character in "0123456789abcdef" for character in value) - ) - - -def _nonnegative_int(value: object) -> bool: - return isinstance(value, int) and not isinstance(value, bool) and value >= 0 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 index b89f1a1..1303794 100644 --- a/src/python_lang_project_harness/_project_resolution.py +++ b/src/python_lang_project_harness/_project_resolution.py @@ -4,7 +4,7 @@ import json from pathlib import Path -from typing import Any, TextIO +from typing import Any from ._project_resolution_graph import ( ProjectResolutionError, @@ -15,32 +15,22 @@ _RESPONSE_SCHEMA_ID = "agent.semantic-protocols.provider-project-resolution-response" -def try_run_project_resolution( - args: list[str] | tuple[str, ...], - *, - stdin: str, - cwd: Path, - stdout: TextIO, -) -> int | None: - if tuple(args) != ("project-resolution-stdin",): - return None +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: - request = _decode_request(stdin) - response = _response( - "resolved", scope=resolve_project_resolution(request, cwd=cwd) - ) + return _response("resolved", scope=resolve_project_resolution(request, cwd=cwd)) except ProjectResolutionError as error: - response = _failure( + return _failure( str(error), reason_kind=error.reason_kind, next_action=error.next_action, ) except (ValueError, OSError) as error: - response = _failure(str(error)) - stdout.write(json.dumps(response, separators=(",", ":"), sort_keys=True)) - stdout.write("\n") - return 0 + return _failure(str(error)) def _decode_request(stdin: str) -> dict[str, Any]: @@ -59,10 +49,10 @@ def _decode_request(stdin: str) -> dict[str, Any]: raise ValueError("project-resolution request schema must be v1") if ( request.get("languageId") != "python" - or request.get("providerId") != "py-harness" + or request.get("providerId") != "asp-python" ): raise ValueError( - "project-resolution request provider identity does not match py-harness" + "project-resolution request provider identity does not match asp-python" ) if request.get("candidateBase") != ".": raise ValueError("project-resolution request candidateBase must be .") @@ -117,7 +107,7 @@ def _response( "schemaId": _RESPONSE_SCHEMA_ID, "schemaVersion": "1", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "state": state, } if scope is not None: @@ -133,6 +123,8 @@ def _failure( 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={ diff --git a/src/python_lang_project_harness/_project_resolution_document.py b/src/python_lang_project_harness/_project_resolution_document.py index d842033..17e9ba0 100644 --- a/src/python_lang_project_harness/_project_resolution_document.py +++ b/src/python_lang_project_harness/_project_resolution_document.py @@ -62,7 +62,7 @@ def build_scope_document( "state": "resolved", "completeness": "exact" if not unresolved else "partial", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "parserId": parser_id, "candidateGenerationDigest": generation_digest, "projectEntry": project_manifest.as_posix(), @@ -70,7 +70,7 @@ def build_scope_document( "schemaId": PACKAGE_GRAPH_SCHEMA_ID, "schemaVersion": "1", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "projectEntry": project_manifest.as_posix(), "parserId": parser_id, "manifests": [ diff --git a/src/python_lang_project_harness/_project_resolution_graph.py b/src/python_lang_project_harness/_project_resolution_graph.py index 77dd15f..240a08f 100644 --- a/src/python_lang_project_harness/_project_resolution_graph.py +++ b/src/python_lang_project_harness/_project_resolution_graph.py @@ -92,6 +92,12 @@ def _resolution_context( 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", diff --git a/src/python_lang_project_harness/_projection_batch.py b/src/python_lang_project_harness/_projection_batch.py index 220fad3..a3ade1e 100644 --- a/src/python_lang_project_harness/_projection_batch.py +++ b/src/python_lang_project_harness/_projection_batch.py @@ -3,16 +3,20 @@ from __future__ import annotations import ast -import json +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 = "asp.provider-language-projection-batch-request.v1" -_RESPONSE_SCHEMA_ID = "asp.provider-language-projection-batch-response.v1" -_IDENTITY_SCHEMA_ID = "asp.canonical-language-item-identity.v1" -_TRANSPORT = "framed-stdin-v1" +_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) @@ -22,71 +26,83 @@ class _OwnerFrame: source: bytes -def render_projection_batch(frame: bytes) -> str: - """Decode one ASP frame and render the provider-owned projection response.""" +def project_projection_batch(request: dict[str, object]) -> dict[str, object]: + """Project one structured resident-runtime request with the native AST.""" - header, owners = _decode_frame(frame) - projected = [_project_owner(owner, header) for owner in owners] + owners, _auxiliary_owners = _decode_request(request) + projected = [_project_owner(owner, request) for owner in owners] response = { "schemaId": _RESPONSE_SCHEMA_ID, "schemaVersion": "1", - "languageId": header["languageId"], - "providerId": header["providerId"], - "generationRootDigest": header["generationRootDigest"], + "languageId": request["languageId"], + "providerId": request["providerId"], + "generationRootDigest": request["generationRootDigest"], "owners": projected, } - return json.dumps(response, separators=(",", ":"), ensure_ascii=False) - - -def _decode_frame(frame: bytes) -> tuple[dict[str, object], list[_OwnerFrame]]: - if len(frame) < 4: - raise ValueError("projection batch frame is missing its header length") - header_length = int.from_bytes(frame[:4], "big") - header_end = 4 + header_length - if header_end > len(frame): - raise ValueError("projection batch header exceeds the input frame") - header = json.loads(frame[4:header_end]) - if not isinstance(header, dict): - raise ValueError("projection batch header must be an object") + return response + + +def _decode_request( + request: dict[str, object], +) -> tuple[list[_OwnerFrame], list[_OwnerFrame]]: if ( - header.get("schemaId") != _REQUEST_SCHEMA_ID - or header.get("schemaVersion") != "1" - or header.get("languageId") != "python" - or header.get("transport") != _TRANSPORT - or not isinstance(header.get("parserIdentityDigest"), str) - or not header.get("parserIdentityDigest") - or not isinstance(header.get("queryPackDigest"), str) - or not header.get("queryPackDigest") + 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") - owner_headers = header.get("owners") - if not isinstance(owner_headers, list): - raise ValueError("projection batch owners must be an array") - cursor = header_end + 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 owner_headers: + for raw_owner in value: if not isinstance(raw_owner, dict): - raise ValueError("projection batch owner header must be an object") + raise ValueError("projection batch owner must be an object") path = raw_owner.get("ownerPath") digest = raw_owner.get("sourceLeafDigest") - byte_length = raw_owner.get("byteLength") if ( not isinstance(path, str) or not path or not isinstance(digest, str) or not digest - or not isinstance(byte_length, int) - or byte_length < 0 ): - raise ValueError("projection batch owner header is incomplete") - owner_end = cursor + byte_length - if owner_end > len(frame): - raise ValueError(f"projection batch owner bytes are truncated: {path}") - owners.append(_OwnerFrame(path, digest, frame[cursor:owner_end])) - cursor = owner_end - if cursor != len(frame): - raise ValueError("projection batch frame has trailing bytes") - return header, owners + 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]: 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 9d2c4a4..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"]], diff --git a/src/python_lang_project_harness/_semantic_language.py b/src/python_lang_project_harness/_semantic_language.py index 268ba3e..b71cf1f 100644 --- a/src/python_lang_project_harness/_semantic_language.py +++ b/src/python_lang_project_harness/_semantic_language.py @@ -25,10 +25,7 @@ _PYTHON_SEARCH_VIEWS = tuple( descriptor["view"] for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS ) -_PYTHON_SEARCH_METHODS = ( - *(f"search/{view}" for view in _PYTHON_SEARCH_VIEWS), - "search/owner-native", -) +_PYTHON_SEARCH_METHODS = tuple(f"search/{view}" for view in _PYTHON_SEARCH_VIEWS) def semantic_language_registry_document() -> dict[str, Any]: @@ -139,35 +136,7 @@ def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: }, ] ) - attached = attach_semantic_language_invocations(descriptors) - attached.append( - { - "method": "search/owner-native", - "command": "search", - "view": "owner-native", - "outputSchemaIds": [ - "agent.semantic-protocols.provider-native-owner-search-response" - ], - "packetSchemas": [ - "provider-native-owner-search-request.v1", - "provider-native-owner-search-response.v1", - ], - "requiresQuery": False, - "acceptsStdin": True, - "supportsPackageScope": False, - "supportsJson": True, - "supportsCompact": False, - "invocation": { - "argv": [ - "py-harness", - "owner-search-stdin", - "--asp-provider-id", - "py-harness", - ] - }, - } - ) - return attached + return attach_semantic_language_invocations(descriptors) def _python_search_method_descriptor(descriptor: dict[str, Any]) -> dict[str, Any]: 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 index 0097585..18a6dc6 100644 --- a/src/python_lang_project_harness/_semantic_language_invocation.py +++ b/src/python_lang_project_harness/_semantic_language_invocation.py @@ -43,9 +43,15 @@ def _non_search_invocation(method: str) -> dict[str, list[str]]: "{workspace}", ], "query/exact-selector-native-v1": [ - ids.PYTHON_BINARY, + "asp", + "python", "query", - "--asp-exact-request-stdin", + "--selector", + "{selector}", + "--projection", + "{projection}", + "--workspace", + "{workspace}", ], "check/changed": [ids.PYTHON_BINARY, "check", "--changed", "{workspace}"], "check/full": [ids.PYTHON_BINARY, "check", "--full", "{workspace}"], diff --git a/src/python_lang_project_harness/_semantic_provider_doctor.py b/src/python_lang_project_harness/_semantic_provider_doctor.py index c1f03be..dd12e3d 100644 --- a/src/python_lang_project_harness/_semantic_provider_doctor.py +++ b/src/python_lang_project_harness/_semantic_provider_doctor.py @@ -2,32 +2,25 @@ import json from hashlib import sha256 -from importlib.resources import files -from pathlib import Path from typing import Any from . import _semantic_language_ids as ids -def _provider_manifest() -> dict[str, Any]: - packaged = files("python_lang_project_harness").joinpath( - "asp-provider-manifest.json" - ) - if packaged.is_file(): - return json.loads(packaged.read_text(encoding="utf-8")) - checkout = ( - Path(__file__).resolve().parents[2] / "provider" / "asp-provider-manifest.json" - ) - return json.loads(checkout.read_text(encoding="utf-8")) +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. + """ -def _provider_identity() -> dict[str, str]: - manifest = _provider_manifest() - keys = ("languageId", "providerId", "binary", "execution") - identity = {key: manifest[key] for key in keys} - if not all(isinstance(value, str) and value for value in identity.values()): - raise ValueError("provider manifest identity fields must be non-empty strings") - return identity + return { + "languageId": ids.PYTHON_LANGUAGE_ID, + "providerId": ids.PYTHON_PROVIDER_ID, + "binary": ids.PYTHON_BINARY, + "execution": "provider", + } def _jcs_bytes(value: object) -> bytes: diff --git a/src/python_lang_project_harness/_semantic_search_cli.py b/src/python_lang_project_harness/_semantic_search_cli.py index 82b1248..5848704 100644 --- a/src/python_lang_project_harness/_semantic_search_cli.py +++ b/src/python_lang_project_harness/_semantic_search_cli.py @@ -74,7 +74,7 @@ def _search_view_descriptor( def _semantic_search_usage() -> str: return ( - "usage: py-harness search " + "usage: asp-python search " " " "... [--json] [--package PATH] [--workspace ]; " "dependency/deps are manifest-first, import-usage backed, and cache hashes not raw source" 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/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 0f7a9ea..db009d4 100644 --- a/tests/unit/harness/test_cli.py +++ b/tests/unit/harness/test_cli.py @@ -15,7 +15,7 @@ def test_cli_help_advertises_exact_projection_routes() -> None: exit_code = run_cli(["--help"], stdout=stdout) rendered = stdout.getvalue() assert exit_code == 0 - assert "py-harness search ... [--json] [--package PATH]" in rendered + assert "asp-python search ... [--json] [--package PATH]" in rendered assert ( "asp python query --selector " "--projection " in rendered @@ -245,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_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 b3ae100..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: diff --git a/tests/unit/harness/test_exact_source_projection.py b/tests/unit/harness/test_exact_source_projection.py index 1fefd54..576fd9c 100644 --- a/tests/unit/harness/test_exact_source_projection.py +++ b/tests/unit/harness/test_exact_source_projection.py @@ -1,10 +1,10 @@ from __future__ import annotations import base64 -import io -import json -from python_lang_project_harness._cli import run_cli +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: @@ -23,7 +23,7 @@ def invoke(selector: str, projection_kind: str) -> dict[str, object]: "schemaId": "agent.semantic-protocols.provider-native-exact-request", "schemaVersion": "1", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "structuralSelector": selector, "ownerPath": "src/example.py", "projectionKind": projection_kind, @@ -36,38 +36,19 @@ def invoke(selector: str, projection_kind: str) -> dict[str, object]: "sourceBytesBase64": base64.b64encode(source).decode(), "transport": "stdin-json", } - stdout = io.StringIO() - stderr = io.StringIO() - exit_code = run_cli( - [ - "query", - "--selector", - selector, - "--json", - "--asp-provider-id", - "py-harness", - "--asp-parser-identity-digest", - parser_digest, - "--asp-query-pack-digest", - query_pack_digest, - "--asp-exact-request-stdin", - ], - stdin=json.dumps(request), - stdout=stdout, - stderr=stderr, - cwd=tmp_path, - ) - assert exit_code == 0, stderr.getvalue() - return json.loads(stdout.getvalue()) + return project_provider_native_exact_request(request, cwd=tmp_path) skeleton = invoke(root_selector, "callable-skeleton") payload = skeleton["projectionPayload"] assert isinstance(payload, dict) - assert payload["schemaVersion"] == "1" + 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["exactSelector"]["selector"] for node in nodes if node["kind"] == "branch" + node["selector"] for node in nodes if node["kind"] == "branch" ) branch = invoke(branch_selector, "source") @@ -84,7 +65,7 @@ def test_provider_does_not_recompute_asp_source_digest(tmp_path) -> None: "schemaId": "agent.semantic-protocols.provider-native-exact-request", "schemaVersion": "1", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "structuralSelector": selector, "ownerPath": "src/example.py", "projectionKind": "source", @@ -97,27 +78,5 @@ def test_provider_does_not_recompute_asp_source_digest(tmp_path) -> None: "sourceBytesBase64": base64.b64encode(source).decode(), "transport": "stdin-json", } - stdout = io.StringIO() - stderr = io.StringIO() - exit_code = run_cli( - [ - "query", - "--selector", - selector, - "--asp-provider-id", - "py-harness", - "--asp-parser-identity-digest", - "b" * 64, - "--asp-query-pack-digest", - "c" * 64, - "--asp-exact-request-stdin", - ], - stdin=json.dumps(request), - stdout=stdout, - stderr=stderr, - cwd=tmp_path, - ) - - assert exit_code == 0, stderr.getvalue() - packet = json.loads(stdout.getvalue()) + packet = project_provider_native_exact_request(request, cwd=tmp_path) assert packet["sourceContentDigest"] == "asp-owned-content-identity" diff --git a/tests/unit/harness/test_owner_search_stdin.py b/tests/unit/harness/test_owner_search_stdin.py deleted file mode 100644 index bb904ef..0000000 --- a/tests/unit/harness/test_owner_search_stdin.py +++ /dev/null @@ -1,163 +0,0 @@ -from __future__ import annotations - -import base64 -import json -from io import StringIO -from pathlib import Path - -from blake3 import blake3 - -from python_lang_project_harness._cli import run_cli -from python_lang_project_harness._owner_search_stdin import ( - try_run_provider_native_owner, -) -from python_lang_project_harness._semantic_language import ( - python_semantic_language_method_descriptors, -) - - -def _request(source: bytes) -> dict[str, object]: - return { - "schemaId": "agent.semantic-protocols.provider-native-owner-search-request", - "schemaVersion": "1", - "languageId": "python", - "providerId": "py-harness", - "workspaceIdentity": "workspace-test", - "providerWorkspaceIdentityDigest": "1" * 64, - "ownerPath": "src/example.py", - "sourceFingerprint": { - "fileIdentity": "resident-owner:test", - "sizeBytes": len(source), - "modifiedUnixNanos": 0, - "changeTimeUnixNanos": 0, - "contentDigest": blake3(source).hexdigest(), - }, - "sourceEncoding": "base64", - "sourceBytesBase64": base64.b64encode(source).decode("ascii"), - "projectionMode": "complete-owner", - "transport": "stdin-json", - } - - -def test_owner_search_projects_complete_top_level_function_owner() -> None: - source = ( - b"def alpha(value: int) -> int:\n" - b" def nested() -> int:\n" - b" return value\n" - b" return nested()\n\n" - b"async def beta() -> None:\n" - b" return None\n" - ) - stdout = StringIO() - stderr = StringIO() - - exit_code = try_run_provider_native_owner( - ["owner-search-stdin", "--asp-provider-id", "py-harness"], - stdin=json.dumps(_request(source)), - cwd=Path("."), - stdout=stdout, - stderr=stderr, - ) - - assert exit_code == 0 - assert stderr.getvalue() == "" - response = json.loads(stdout.getvalue()) - assert response["projectionCompleteness"] == "complete-owner" - assert response["requestedProjectionMode"] == "complete-owner" - assert [ - projection["canonicalItemSelector"]["symbol"] - for projection in response["projections"] - ] == [ - "alpha", - "nested", - "beta", - ] - assert response["projections"][0]["canonicalItemSelector"][ - "structuralSelector" - ] == ("python://src/example.py#item/function/alpha") - assert response["projections"][1]["canonicalItemSelector"]["scopes"] == [ - {"relation": "lexical-owner", "kind": "function", "symbol": "alpha"} - ] - for projection in response["projections"]: - projected = source[projection["sourceByteStart"] : projection["sourceByteEnd"]] - selector = projection["canonicalItemSelector"] - assert selector["schemaId"] == "asp.canonical-item-selector.v1" - assert selector["schemaVersion"] == "1" - assert selector["symbol"].encode() in projected - - -def test_owner_search_disambiguates_same_method_name_by_class_scope() -> None: - source = ( - b"class Alpha:\n" - b" def render(self) -> str:\n" - b" return 'alpha'\n\n" - b"class Beta:\n" - b" def render(self) -> str:\n" - b" return 'beta'\n" - ) - stdout = StringIO() - stderr = StringIO() - assert ( - try_run_provider_native_owner( - ["owner-search-stdin", "--asp-provider-id", "py-harness"], - stdin=json.dumps(_request(source)), - cwd=Path("."), - stdout=stdout, - stderr=stderr, - ) - == 0 - ) - methods = [ - projection["canonicalItemSelector"] - for projection in json.loads(stdout.getvalue())["projections"] - if projection["canonicalItemSelector"]["kind"] == "method" - ] - assert len(methods) == 2 - assert len({method["structuralSelector"] for method in methods}) == 2 - assert {method["scopes"][0]["symbol"] for method in methods} == {"Alpha", "Beta"} - assert all(method["scopes"][0]["relation"] == "class-owner" for method in methods) - - -def test_owner_search_rejects_content_digest_drift() -> None: - request = _request(b"def alpha():\n return 1\n") - request["sourceFingerprint"]["contentDigest"] = "0" * 64 # type: ignore[index] - stdout = StringIO() - stderr = StringIO() - - exit_code = try_run_provider_native_owner( - ["owner-search-stdin", "--asp-provider-id", "py-harness"], - stdin=json.dumps(request), - cwd=Path("."), - stdout=stdout, - stderr=stderr, - ) - - assert exit_code == 2 - assert stdout.getvalue() == "" - assert "content digest drift" in stderr.getvalue() - - -def test_cli_and_registry_expose_native_owner_transport() -> None: - source = b"def graph_turbo_entry():\n return 1\n" - stdout = StringIO() - stderr = StringIO() - - exit_code = run_cli( - ["owner-search-stdin", "--asp-provider-id", "py-harness"], - stdin=json.dumps(_request(source)), - cwd=Path("."), - stdout=stdout, - stderr=stderr, - ) - - assert exit_code == 0 - descriptors = { - descriptor["method"]: descriptor - for descriptor in python_semantic_language_method_descriptors() - } - assert descriptors["search/owner-native"]["invocation"]["argv"] == [ - "py-harness", - "owner-search-stdin", - "--asp-provider-id", - "py-harness", - ] diff --git a/tests/unit/harness/test_project_resolution.py b/tests/unit/harness/test_project_resolution.py index ce94822..983ce82 100644 --- a/tests/unit/harness/test_project_resolution.py +++ b/tests/unit/harness/test_project_resolution.py @@ -2,11 +2,10 @@ from __future__ import annotations -import io import json from pathlib import Path -from python_lang_project_harness import run_cli +from python_lang_project_harness._runtime import _response_frame def request(candidate_paths: list[str]) -> dict[str, object]: @@ -14,7 +13,7 @@ def request(candidate_paths: list[str]) -> dict[str, object]: "schemaId": "agent.semantic-protocols.provider-project-resolution-request", "schemaVersion": "1", "languageId": "python", - "providerId": "py-harness", + "providerId": "asp-python", "candidateBase": ".", "candidateGeneration": { "algorithm": "blake3-path-set-v1", @@ -27,19 +26,25 @@ def request(candidate_paths: list[str]) -> dict[str, object]: } -def run_project_resolution(root: Path, payload: object) -> dict[str, object]: - stdout = io.StringIO() - stderr = io.StringIO() - exit_code = run_cli( - ["project-resolution-stdin"], - stdin=json.dumps(payload), - stdout=stdout, - stderr=stderr, - cwd=root, +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, ) - assert exit_code == 0 - assert stderr.getvalue() == "" - return json.loads(stdout.getvalue()) + + +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( @@ -169,15 +174,20 @@ def test_setuptools_src_layout_is_package_manager_scope_without_provider_default def test_project_resolution_rejects_non_object_request(tmp_path: Path) -> None: - response = run_project_resolution(tmp_path, []) - assert response["state"] == "failed" - assert response["failure"]["reasonKind"] == "project-entry-invalid" + 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["state"] == "failed" - assert response["failure"]["reasonKind"] == "project-entry-missing" + 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( @@ -207,16 +217,25 @@ def test_empty_uv_workspace_aggregator_is_not_a_provider_failure( assert scope["metrics"]["dbOpens"] == 0 -def test_provider_manifest_advertises_project_resolution() -> None: +def test_provider_registration_advertises_project_resolution() -> None: project_root = Path(__file__).parents[3] manifest = json.loads( - (project_root / "provider/asp-provider-manifest.json").read_text() + (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" ) - descriptor = manifest["projectResolution"] - assert descriptor["commandBinding"] == "project-resolution-stdin" - assert descriptor["parserId"] == "python.pyproject-toml" - assert "supportsGitCandidates" not in descriptor - assert "candidateSnapshotSchema" not in descriptor def test_explicit_owner_collection_scope_is_required_and_normalized( @@ -234,6 +253,6 @@ def test_explicit_owner_collection_scope_is_required_and_normalized( "kind": "explicit-owners", "ownerPaths": ["src/../changed.py"], } - failure = run_project_resolution(tmp_path, payload) - assert failure["state"] == "failed" - assert "normalized workspace-relative" in failure["failure"]["message"] + 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_projection_batch.py b/tests/unit/harness/test_projection_batch.py index d4cd37f..01bb070 100644 --- a/tests/unit/harness/test_projection_batch.py +++ b/tests/unit/harness/test_projection_batch.py @@ -2,20 +2,17 @@ from __future__ import annotations -import json +from python_lang_project_harness._projection_batch import project_projection_batch -from python_lang_project_harness._cli import run_cli - -def test_projection_batch_projects_canonical_python_items(capsys: object) -> None: - source = b"class Agent:\n def run(self):\n return 1\n\ndef top():\n return 2\n" - header = { - "schemaId": "asp.provider-language-projection-batch-request.v1", +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": "py-harness", + "providerId": "asp-python", "workspaceIdentity": "workspace-test", - "transport": "framed-stdin-v1", "generationRootDigest": "generation-test", "parserIdentityDigest": "parser-test", "queryPackDigest": "query-pack-test", @@ -24,19 +21,28 @@ def test_projection_batch_projects_canonical_python_items(capsys: object) -> Non { "ownerPath": "src/example.py", "sourceLeafDigest": "source-test", - "byteLength": len(source), + "sourceEncoding": "utf8", + "sourceText": source, + } + ], + "auxiliaryOwners": [ + { + "ownerPath": "pyproject.toml", + "sourceLeafDigest": "config-test", + "sourceEncoding": "utf8", + "sourceText": "[project]\nname = 'fixture'\n", } ], } - header_bytes = json.dumps(header, separators=(",", ":")).encode() - frame = len(header_bytes).to_bytes(4, "big") + header_bytes + source - - assert run_cli(["projection-batch-stdin"], stdin=frame) == 0 - response = json.loads(capsys.readouterr().out) # type: ignore[attr-defined] + response = project_projection_batch(request) - assert response["schemaId"] == "asp.provider-language-projection-batch-response.v1" + 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", @@ -49,7 +55,7 @@ def test_projection_batch_projects_canonical_python_items(capsys: object) -> Non assert owner["items"][0]["projections"] == [] for item in owner["items"][1:]: assert item["projections"][0]["projectionKind"] == "callable-skeleton" - assert ( - item["projections"][0]["payload"]["schemaId"] - == "agent.semantic-protocols.callable-skeleton-projection" - ) + 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_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 aafcbb9..6bf1c56 100644 --- a/tests/unit/harness/test_semantic_cli.py +++ b/tests/unit/harness/test_semantic_cli.py @@ -14,7 +14,7 @@ def test_cli_agent_doctor_advertises_provider(tmp_path: Path) -> None: 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["providerId"] == "asp-python" assert "query/exact-selector-native-v1" in registration["methods"] @@ -30,7 +30,12 @@ def test_cli_agent_guide_uses_asp_owned_exact_projection(tmp_path: Path) -> None def test_search_descriptors_publish_benchmark_invocations() -> None: descriptors = python_semantic_language_registration()["methodDescriptors"] - assert any( - descriptor["method"] == "search/owner-native" and descriptor["acceptsStdin"] + assert all( + descriptor["method"] != "search/owner-native" for descriptor in descriptors + ) + exact = next( + descriptor for descriptor in descriptors + if descriptor["method"] == "query/exact-selector-native-v1" ) + assert exact["invocation"]["argv"][:3] == ["asp", "python", "query"] 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 index b18f35a..6099087 100644 --- a/tests/unit/harness/test_semantic_provider_doctor.py +++ b/tests/unit/harness/test_semantic_provider_doctor.py @@ -43,19 +43,22 @@ def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( registration["binary"], ) descriptors = registration["methodDescriptors"] - assert len(descriptors) == len(registration["methods"]) == 34 - native_owner = next( + assert len(descriptors) == len(registration["methods"]) == 33 + exact_query = next( descriptor for descriptor in descriptors - if descriptor["method"] == "search/owner-native" + if descriptor["method"] == "query/exact-selector-native-v1" ) - assert native_owner["acceptsStdin"] is True - assert native_owner["command"] == "search" - assert native_owner["invocation"]["argv"] == [ - "py-harness", - "owner-search-stdin", - "--asp-provider-id", - "py-harness", + 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( diff --git a/tests/unit/harness/test_semantic_search_graph_profiles.py b/tests/unit/harness/test_semantic_search_graph_profiles.py index e75fb21..bf0dd41 100644 --- a/tests/unit/harness/test_semantic_search_graph_profiles.py +++ b/tests/unit/harness/test_semantic_search_graph_profiles.py @@ -31,9 +31,9 @@ def test_compact_graph_profiles_filter_to_rendered_aliases() -> None: "protocolId": "agent.semantic-protocols.search", "protocolVersion": "1", "languageId": "python", - "providerId": "py-harness", - "binary": "py-harness", - "namespace": "agent.semantic-protocols.languages.python.py-harness", + "providerId": "asp-python", + "binary": "asp-python", + "namespace": "agent.semantic-protocols.languages.python.asp-python", "method": "search/owner", "projectRoot": ".", "view": "seeds", 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/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/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. From 65d4bbf9077466faadf806a8ac0f5866fb4e4feb Mon Sep 17 00:00:00 2001 From: guangtao Date: Fri, 28 Aug 2026 21:01:21 +0800 Subject: [PATCH 12/20] feat: align ASP provider contracts --- provider/asp-provider-registration.json | 12 +- pyproject.toml | 2 +- schemas/.asp-schema-manager-receipt.json | 32 ++++- ...ent-cancellation-probe-request.schema.json | 11 +- ...asp-client-exact-query-failure.schema.json | 42 ++++++ ...sp-client-exact-query-response.schema.json | 34 +++-- schemas/asp-client-frame.schema.json | 21 +++ .../asp-client-protocol-catalog.schema.json | 1 + ...p-client-schema-bundle-request.schema.json | 23 ++++ ...-client-schema-bundle-response.schema.json | 112 ++++++++++++++++ schemas/asp-client-work-counters.schema.json | 21 +++ ...sp-semantic-extension-envelope.schema.json | 21 +++ ...language-schema-bundle-receipt.schema.json | 38 +++++- src/python_lang_project_harness/_runtime.py | 88 ++++++++----- .../_semantic_language_schemas.py | 121 +----------------- .../harness/provider_runtime_live_support.py | 20 +++ tests/unit/harness/test_provider_runtime.py | 33 ++++- .../unit/harness/test_semantic_cli_policy.py | 5 +- .../harness/test_semantic_language_schemas.py | 30 +++-- .../harness/test_semantic_schema_registry.py | 25 ---- 20 files changed, 454 insertions(+), 238 deletions(-) create mode 100644 schemas/asp-client-exact-query-failure.schema.json create mode 100644 schemas/asp-client-schema-bundle-request.schema.json create mode 100644 schemas/asp-client-schema-bundle-response.schema.json create mode 100644 schemas/asp-client-work-counters.schema.json create mode 100644 schemas/asp-semantic-extension-envelope.schema.json delete mode 100644 tests/unit/harness/test_semantic_schema_registry.py diff --git a/provider/asp-provider-registration.json b/provider/asp-provider-registration.json index f359b9a..ff4bcc8 100644 --- a/provider/asp-provider-registration.json +++ b/provider/asp-provider-registration.json @@ -56,18 +56,18 @@ "operations": [ { "operation": "projection-batch", - "requestSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.schema.json", - "responseSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.schema.json" + "requestSchema": {"schemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.schema.json", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.schema.json", "schemaVersion": "1"} }, { "operation": "project-resolution", - "requestSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.schema.json", - "responseSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.schema.json" + "requestSchema": {"schemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.schema.json", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.schema.json", "schemaVersion": "1"} }, { "operation": "query", - "requestSchemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-request.v1.schema.json", - "responseSchemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-response.v1.schema.json" + "requestSchema": {"schemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-request.v1.schema.json", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-response.v1.schema.json", "schemaVersion": "1"} } ] }, diff --git a/pyproject.toml b/pyproject.toml index f71d32d..12b1e51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ packages = [ ] [tool.hatch.build.targets.wheel.force-include] -"schemas/semantic-language-registry.v1.schema.json" = "python_lang_project_harness/schemas/semantic-language-registry.v1.schema.json" +"schemas/python-semantic-capabilities.v1.schema.json" = "python_lang_project_harness/schemas/python-semantic-capabilities.v1.schema.json" [tool.uv] package = true diff --git a/schemas/.asp-schema-manager-receipt.json b/schemas/.asp-schema-manager-receipt.json index 3d20cb3..3d58bec 100644 --- a/schemas/.asp-schema-manager-receipt.json +++ b/schemas/.asp-schema-manager-receipt.json @@ -3,11 +3,11 @@ "schemaVersion": "1", "languageId": "python", "profileDigest": "blake3-256:38866be447e1e414aed4bacdd8a4d9be33c453e585168a6955bd9582965d9685", - "bundleDigest": "blake3-256:1aef2b560c8c30da8b7b0731f00d5bac91edd0247a33ac8f6e2874bfbdbe5f1e", + "bundleDigest": "blake3-256:eeb548457c5b19e837de9e5c1125c14f4ef763ed64b59315a2d95fe8be762af6", "schemas": [ { "name": "asp-client-cancellation-probe-request.schema.json", - "digest": "blake3-256:9a5c1addcf17ee0a317722438c5fb2c9c7b3088acc5add96324553a74ac9c286" + "digest": "blake3-256:b34c40da202136d5377f5584f095319a7d4877837cb7c57275bd8a1ab177bf92" }, { "name": "asp-client-cancellation-probe-response.schema.json", @@ -17,17 +17,21 @@ "name": "asp-client-conformance.schema.json", "digest": "blake3-256:bc12a1065c633e47a848f42a89975206c0538e5b66b0b54085468481b44145c9" }, + { + "name": "asp-client-exact-query-failure.schema.json", + "digest": "blake3-256:fc451bd39e772ed3a2ba24a2b45da38e161b9b61915f698b1afb1253a051d386" + }, { "name": "asp-client-exact-query-request.schema.json", "digest": "blake3-256:e412c1f02d08d1632e7104487d6b08d60dde6736d59254c6c2f5f45b702d4aa4" }, { "name": "asp-client-exact-query-response.schema.json", - "digest": "blake3-256:65d9e3c624b301c6b8c103c8ec739237559e8e59ebc5d832847ffdf3ff85e4cd" + "digest": "blake3-256:feb1d24eaba02eaf53c7e5e7ba4f11dea436ebee99380d221f796007fee4625c" }, { "name": "asp-client-frame.schema.json", - "digest": "blake3-256:d546eb0bc0bc4539e0542526094255e25f51d2c28ac7f6089d42ecba4187c15b" + "digest": "blake3-256:f16667311475fd65f4b849611e3df4ca2f7fe847a4730d6fa9d025753fb7cf7c" }, { "name": "asp-client-owner-search-request.schema.json", @@ -35,7 +39,15 @@ }, { "name": "asp-client-protocol-catalog.schema.json", - "digest": "blake3-256:7faec1ae7dadc5ee28b57faadc8dedea58f320cd04aa34d9f6ffe78a5159382c" + "digest": "blake3-256:8b3f99ee2cb424d3ccf994f4c77f37dd6a71078b463cb256dbb65aea02b2d443" + }, + { + "name": "asp-client-schema-bundle-request.schema.json", + "digest": "blake3-256:b04fed590c0e023686f28cd77c045726c851abd960b5aa1f9734f45a4daacf1c" + }, + { + "name": "asp-client-schema-bundle-response.schema.json", + "digest": "blake3-256:3b9b48d31828c7365420423d8d092f5dc5dd9cc1e9c9faf992145d1f785ea764" }, { "name": "asp-client-search-request.schema.json", @@ -45,10 +57,18 @@ "name": "asp-client-server-descriptor.schema.json", "digest": "blake3-256:d4e432eacba544a5eb504c1e6d1161d9599a14cfb85d62d2d5da0e1d47f6cdde" }, + { + "name": "asp-client-work-counters.schema.json", + "digest": "blake3-256:aba81ca4e8f518bb515cb7b014b5c535cab3d1634dfd9ae6ccdf24d553ba3133" + }, { "name": "asp-client-workspace-source-mutation.schema.json", "digest": "blake3-256:74b0baf814e96b3f2f47eefcc537ae53c5ac740315cdbc8c4f1079dcc2f484a8" }, + { + "name": "asp-semantic-extension-envelope.schema.json", + "digest": "blake3-256:70690d0b93dab725daa845a12fb750dc8ce3039c7f1895b2660b160b3744fda9" + }, { "name": "callable-skeleton.schema.json", "digest": "blake3-256:7559a2b84114bd27785e26cc079b9200cbf9b902e76a77afd74c47cc7f04b8dd" @@ -75,7 +95,7 @@ }, { "name": "language-schema-bundle-receipt.schema.json", - "digest": "blake3-256:43b7cb204c9d74ba05f3420940f1028555527bf82b6e010b3daf5986039ace03" + "digest": "blake3-256:d69b7aa4dcaff194739c64d0847b3f7c97f9230808ec0319de34bf61666dd4bc" }, { "name": "project-resolution.schema.json", diff --git a/schemas/asp-client-cancellation-probe-request.schema.json b/schemas/asp-client-cancellation-probe-request.schema.json index fb6d1af..c002842 100644 --- a/schemas/asp-client-cancellation-probe-request.schema.json +++ b/schemas/asp-client-cancellation-probe-request.schema.json @@ -4,13 +4,6 @@ "title": "ASP Client Cancellation Probe Request", "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion"], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-cancellation-probe-request" - }, - "schemaVersion": { - "const": "1" - } - } + "required": [], + "properties": {} } diff --git a/schemas/asp-client-exact-query-failure.schema.json b/schemas/asp-client-exact-query-failure.schema.json new file mode 100644 index 0000000..8ba60f1 --- /dev/null +++ b/schemas/asp-client-exact-query-failure.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-exact-query-failure.schema.json", + "title": "ASP Client Exact Query Failure", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "state", "operationId", "languageId", + "providerId", "phase", "reasonKind", "recommendedNext", + "residentReadElapsedMicros", "serviceElapsedMicros", "elapsedMicros", + "workCounters", "details" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-exact-query-failure" + }, + "schemaVersion": { "const": "1" }, + "state": { "const": "failed" }, + "operationId": { "type": "string", "minLength": 1 }, + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 }, + "requestedSelector": { "type": ["string", "null"] }, + "resolvedSelector": { "type": ["string", "null"] }, + "projectionKind": { "type": ["string", "null"] }, + "phase": { "type": "string", "minLength": 1 }, + "reasonKind": { "type": "string", "minLength": 1 }, + "generationDigest": { + "type": ["string", "null"], + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "rootDigest": { + "type": ["string", "null"], + "pattern": "^[0-9a-f]{64}$" + }, + "recommendedNext": {}, + "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, + "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, + "elapsedMicros": { "type": "integer", "minimum": 0 }, + "workCounters": { "$ref": "asp-client-work-counters.schema.json" }, + "details": { "type": "object" } + } +} diff --git a/schemas/asp-client-exact-query-response.schema.json b/schemas/asp-client-exact-query-response.schema.json index 0641323..2df7843 100644 --- a/schemas/asp-client-exact-query-response.schema.json +++ b/schemas/asp-client-exact-query-response.schema.json @@ -34,27 +34,23 @@ "type": "string", "pattern": "^[0-9a-f]{64}$" }, - "result": { "type": "object" }, - "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, - "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, - "elapsedMicros": { "type": "integer", "minimum": 0 }, - "workCounters": { + "result": { "type": "object", - "additionalProperties": false, - "required": [ - "databaseReadCount", - "filesystemReadCount", - "providerProcessCount", - "schedulerTaskCount", - "socketOperationCount" - ], + "additionalProperties": true, + "required": ["state", "resolvedSelector", "bytes"], "properties": { - "databaseReadCount": { "type": "integer", "minimum": 0 }, - "filesystemReadCount": { "type": "integer", "minimum": 0 }, - "providerProcessCount": { "type": "integer", "minimum": 0 }, - "schedulerTaskCount": { "type": "integer", "minimum": 0 }, - "socketOperationCount": { "type": "integer", "minimum": 0 } + "state": { "enum": ["projection", "provider-projection"] }, + "resolvedSelector": { "type": "string", "minLength": 1 }, + "bytes": { + "type": "array", + "minItems": 1, + "items": { "type": "integer", "minimum": 0, "maximum": 255 } + } } - } + }, + "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, + "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, + "elapsedMicros": { "type": "integer", "minimum": 0 }, + "workCounters": { "$ref": "asp-client-work-counters.schema.json" } } } diff --git a/schemas/asp-client-frame.schema.json b/schemas/asp-client-frame.schema.json index eac399b..8768829 100644 --- a/schemas/asp-client-frame.schema.json +++ b/schemas/asp-client-frame.schema.json @@ -5,6 +5,7 @@ "oneOf": [ { "$ref": "#/$defs/initialize" }, { "$ref": "#/$defs/request" }, + { "$ref": "#/$defs/dispatch" }, { "$ref": "#/$defs/cancel" }, { "$ref": "#/$defs/shutdown" }, { "$ref": "#/$defs/exit" }, @@ -12,6 +13,7 @@ { "$ref": "#/$defs/event" } ], "$defs": { + "clientInfo": { "$ref": "#/$defs/clientInfo" }, "base": { "type": "object", "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity"], @@ -76,6 +78,25 @@ } ] }, + "dispatch": { + "allOf": [ + { "$ref": "#/$defs/base" }, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId", "projectRoot", "clientInfo", "method", "params"], + "properties": { + "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, + "kind": { "const": "dispatch" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "requestId": { "type": "string", "minLength": 1 }, + "projectRoot": { "type": "string", "minLength": 1 }, + "clientInfo": { "$ref": "#/$defs/clientInfo" }, + "method": { "type": "string", "minLength": 1 }, + "params": {} + } + } + ] + }, "cancel": { "allOf": [ { "$ref": "#/$defs/base" }, diff --git a/schemas/asp-client-protocol-catalog.schema.json b/schemas/asp-client-protocol-catalog.schema.json index 76bd3d4..ebd628f 100644 --- a/schemas/asp-client-protocol-catalog.schema.json +++ b/schemas/asp-client-protocol-catalog.schema.json @@ -86,6 +86,7 @@ "valueType": { "enum": [ "string", + "string-array", "workspace-relative-path", "structural-selector", "presentation", diff --git a/schemas/asp-client-schema-bundle-request.schema.json b/schemas/asp-client-schema-bundle-request.schema.json new file mode 100644 index 0000000..0a7b174 --- /dev/null +++ b/schemas/asp-client-schema-bundle-request.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-schema-bundle-request.schema.json", + "title": "ASP Client Schema Bundle Request", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "languageId", "rootSetIds"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.asp-client-schema-bundle-request" }, + "schemaVersion": { "const": "1" }, + "languageId": { "type": "string", "minLength": 1 }, + "rootSetIds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "knownBundleDigest": { "$ref": "#/$defs/digest" } + }, + "$defs": { + "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" } + } +} diff --git a/schemas/asp-client-schema-bundle-response.schema.json b/schemas/asp-client-schema-bundle-response.schema.json new file mode 100644 index 0000000..092e193 --- /dev/null +++ b/schemas/asp-client-schema-bundle-response.schema.json @@ -0,0 +1,112 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-schema-bundle-response.schema.json", + "title": "ASP Client Schema Bundle Response", + "oneOf": [ + { "$ref": "#/$defs/ready" }, + { "$ref": "#/$defs/unchanged" }, + { "$ref": "#/$defs/failed" } + ], + "$defs": { + "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" }, + "schemaName": { "type": "string", "pattern": "^[^/]+\\.schema\\.json$" }, + "entry": { + "type": "object", + "additionalProperties": false, + "required": ["familyId", "schemaId", "schemaVersion", "name", "digest"], + "properties": { + "familyId": { "type": "string", "minLength": 1 }, + "schemaId": { "type": "string", "minLength": 1 }, + "schemaVersion": { "const": "1" }, + "name": { "$ref": "#/$defs/schemaName" }, + "digest": { "$ref": "#/$defs/digest" } + } + }, + "receipt": { + "type": "object", + "additionalProperties": false, + "required": ["languageId", "rootSetIds", "bundleDigest"], + "properties": { + "languageId": { "type": "string", "minLength": 1 }, + "rootSetIds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "bundleDigest": { "$ref": "#/$defs/digest" }, + "extensions": { "type": "array", "items": { "$ref": "#/$defs/extension" } } + } + }, + "entries": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/entry" } + }, + "extension": { + "type": "object", "additionalProperties": false, + "required": ["providerId", "extensionSchemaId", "extensionSchemaVersion", "extensionSchemaDigest", "capabilityDigest", "workspaceIdentity", "generationDigest"], + "properties": { + "providerId": {"type": "string", "minLength": 1}, + "extensionSchemaId": {"type": "string", "minLength": 1}, + "extensionSchemaVersion": {"const": "1"}, + "extensionSchemaDigest": {"$ref": "#/$defs/digest"}, + "capabilityDigest": {"$ref": "#/$defs/digest"}, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"$ref": "#/$defs/digest"} + } + }, + "document": { + "type": "object", + "additionalProperties": false, + "required": ["entry", "document"], + "properties": { + "entry": { "$ref": "#/$defs/entry" }, + "document": { "type": ["object", "boolean"] } + } + }, + "ready": { + "type": "object", + "additionalProperties": false, + "required": ["state", "schemaId", "schemaVersion", "receipt", "entries", "documents"], + "properties": { + "state": { "const": "ready" }, + "schemaId": { "const": "agent.semantic-protocols.asp-client-schema-bundle-response" }, + "schemaVersion": { "const": "1" }, + "receipt": { "$ref": "#/$defs/receipt" }, + "entries": { "$ref": "#/$defs/entries" }, + "documents": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/document" } + } + } + }, + "unchanged": { + "type": "object", + "additionalProperties": false, + "required": ["state", "schemaId", "schemaVersion", "receipt", "entries"], + "properties": { + "state": { "const": "unchanged" }, + "schemaId": { "const": "agent.semantic-protocols.asp-client-schema-bundle-response" }, + "schemaVersion": { "const": "1" }, + "receipt": { "$ref": "#/$defs/receipt" }, + "entries": { "$ref": "#/$defs/entries" } + } + }, + "failed": { + "type": "object", + "additionalProperties": false, + "required": ["state", "schemaId", "schemaVersion", "languageId", "reasonKind", "recommendedNext", "details"], + "properties": { + "state": { "const": "failed" }, + "schemaId": { "const": "agent.semantic-protocols.asp-client-schema-bundle-response" }, + "schemaVersion": { "const": "1" }, + "languageId": { "type": "string", "minLength": 1 }, + "reasonKind": { "type": "string", "minLength": 1 }, + "recommendedNext": true, + "details": { "type": "object" } + } + } + } +} diff --git a/schemas/asp-client-work-counters.schema.json b/schemas/asp-client-work-counters.schema.json new file mode 100644 index 0000000..a3727f9 --- /dev/null +++ b/schemas/asp-client-work-counters.schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-work-counters.schema.json", + "title": "ASP Client Work Counters", + "type": "object", + "additionalProperties": false, + "required": [ + "databaseReadCount", + "filesystemReadCount", + "providerProcessCount", + "schedulerTaskCount", + "socketOperationCount" + ], + "properties": { + "databaseReadCount": { "type": "integer", "minimum": 0 }, + "filesystemReadCount": { "type": "integer", "minimum": 0 }, + "providerProcessCount": { "type": "integer", "minimum": 0 }, + "schedulerTaskCount": { "type": "integer", "minimum": 0 }, + "socketOperationCount": { "type": "integer", "minimum": 0 } + } +} diff --git a/schemas/asp-semantic-extension-envelope.schema.json b/schemas/asp-semantic-extension-envelope.schema.json new file mode 100644 index 0000000..32e7f96 --- /dev/null +++ b/schemas/asp-semantic-extension-envelope.schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-semantic-extension-envelope.schema.json", + "title": "ASP Semantic Extension Envelope", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "providerId", "extensionSchemaId", "extensionSchemaVersion", "extensionSchemaDigest", "capabilityDigest", "workspaceIdentity", "generationDigest", "payload"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-semantic-extension-envelope"}, + "schemaVersion": {"const": "1"}, + "providerId": {"type": "string", "minLength": 1}, + "extensionSchemaId": {"type": "string", "pattern": "^https://schemas\\.agent-semantic-protocols\\.dev/.+\\.schema\\.json$"}, + "extensionSchemaVersion": {"const": "1"}, + "extensionSchemaDigest": {"$ref": "#/$defs/digest"}, + "capabilityDigest": {"$ref": "#/$defs/digest"}, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"$ref": "#/$defs/digest"}, + "payload": {} + }, + "$defs": {"digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}} +} diff --git a/schemas/language-schema-bundle-receipt.schema.json b/schemas/language-schema-bundle-receipt.schema.json index a14b363..7ff32ff 100644 --- a/schemas/language-schema-bundle-receipt.schema.json +++ b/schemas/language-schema-bundle-receipt.schema.json @@ -10,7 +10,8 @@ "languageId", "profileDigest", "bundleDigest", - "schemas" + "schemas", + "distribution" ], "properties": { "schemaId": { "const": "agent.semantic-protocols.language-schema-bundle-receipt" }, @@ -30,9 +31,42 @@ "digest": { "$ref": "#/$defs/digest" } } } + }, + "distribution": { + "type": "object", + "additionalProperties": false, + "required": ["canonicalSchemaDigest", "contractDigest", "targetLanguage", "generator", "sourceSchemas", "outputs", "syncStatus", "acceptanceReceipt"], + "properties": { + "canonicalSchemaDigest": { "$ref": "#/$defs/digest" }, + "contractDigest": { "$ref": "#/$defs/digest" }, + "targetLanguage": { "type": "string", "minLength": 1 }, + "generator": { + "type": "object", "additionalProperties": false, "required": ["identity", "version"], + "properties": { "identity": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } + }, + "sourceSchemas": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/digestEntry" } }, + "outputs": { + "type": "array", "minItems": 1, + "items": { + "type": "object", "additionalProperties": false, + "required": ["schemaId", "digest", "role"], + "properties": { + "schemaId": { "type": "string", "minLength": 1 }, + "digest": { "$ref": "#/$defs/digest" }, + "role": { "const": "derived-copy" } + } + } + }, + "syncStatus": { "enum": ["synced", "drifted", "rejected"] }, + "acceptanceReceipt": { "type": "string", "minLength": 1 } + } } }, "$defs": { - "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" } + "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" }, + "digestEntry": { + "type": "object", "additionalProperties": false, "required": ["schemaId", "digest"], + "properties": { "schemaId": { "type": "string", "minLength": 1 }, "digest": { "$ref": "#/$defs/digest" } } + } } } diff --git a/src/python_lang_project_harness/_runtime.py b/src/python_lang_project_harness/_runtime.py index 2da0a3d..4f646a5 100644 --- a/src/python_lang_project_harness/_runtime.py +++ b/src/python_lang_project_harness/_runtime.py @@ -2,6 +2,7 @@ from __future__ import annotations +import json import os from pathlib import Path from typing import Any @@ -9,23 +10,31 @@ _REQUEST_SCHEMA_ID = "agent.semantic-protocols.provider-runtime-request-frame" _RESPONSE_SCHEMA_ID = "agent.semantic-protocols.provider-runtime-response-frame" _HEALTH_SCHEMA_ID = "agent.semantic-protocols.provider-runtime-contract-receipt" -_OPERATIONS = [ - { - "operation": "projection-batch", - "requestSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.schema.json", - "responseSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.schema.json", - }, - { - "operation": "project-resolution", - "requestSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.schema.json", - "responseSchemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.schema.json", - }, - { - "operation": "query", - "requestSchemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-request.v1.schema.json", - "responseSchemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-response.v1.schema.json", - }, -] + + +def _project_projection(payload: dict[str, Any], _cwd: Path) -> dict[str, Any]: + from ._projection_batch import project_projection_batch + + return project_projection_batch(payload) + + +def _resolve_project(payload: dict[str, Any], cwd: Path) -> dict[str, Any]: + from ._project_resolution import resolve_project_resolution_request + + return resolve_project_resolution_request(payload, cwd=cwd) + + +def _query_exact_source(payload: dict[str, Any], cwd: Path) -> dict[str, Any]: + from ._exact_source_projection import project_provider_native_exact_request + + return project_provider_native_exact_request(payload, cwd=cwd) + + +_OPERATION_HANDLERS = { + "projection-batch": _project_projection, + "project-resolution": _resolve_project, + "query": _query_exact_source, +} def _required_env(name: str) -> str: @@ -35,6 +44,28 @@ def _required_env(name: str) -> str: return value +def _runtime_operations() -> list[dict[str, str]]: + name = "ASP_PROVIDER_RUNTIME_OPERATIONS_JSON" + try: + operations = json.loads(_required_env(name)) + except json.JSONDecodeError as error: + raise RuntimeError( + f"resident Python provider received invalid {name}" + ) from error + if not isinstance(operations, list) or not all( + isinstance(operation, dict) for operation in operations + ): + raise RuntimeError(f"resident Python provider received invalid {name}") + operation_names = {operation.get("operation") for operation in operations} + if operation_names != set(_OPERATION_HANDLERS) or len(operations) != len( + operation_names + ): + raise RuntimeError( + "resident Python provider runtime operations do not match supported handlers" + ) + return operations + + def _health() -> dict[str, Any]: return { "schemaId": _HEALTH_SCHEMA_ID, @@ -45,26 +76,17 @@ def _health() -> dict[str, Any]: "registrationDigest": _required_env("ASP_PROVIDER_REGISTRATION_DIGEST"), "contractDigest": _required_env("ASP_PROVIDER_RUNTIME_CONTRACT_DIGEST"), "transport": "http-json", - "operations": _OPERATIONS, + "operations": _runtime_operations(), } def _execute(operation: str, payload: dict[str, Any], cwd: Path) -> dict[str, Any]: - if operation == "projection-batch": - from ._projection_batch import project_projection_batch - - return project_projection_batch(payload) - if operation == "project-resolution": - from ._project_resolution import resolve_project_resolution_request - - return resolve_project_resolution_request(payload, cwd=cwd) - if operation == "query": - from ._exact_source_projection import project_provider_native_exact_request - - return project_provider_native_exact_request(payload, cwd=cwd) - raise RuntimeError( - f"resident Python provider operation is not admitted: {operation}" - ) + handler = _OPERATION_HANDLERS.get(operation) + if handler is None: + raise RuntimeError( + f"resident Python provider operation is not admitted: {operation}" + ) + return handler(payload, cwd) def _response_frame(request: dict[str, Any], cwd: Path) -> dict[str, Any]: diff --git a/src/python_lang_project_harness/_semantic_language_schemas.py b/src/python_lang_project_harness/_semantic_language_schemas.py index e8890aa..d9002ea 100644 --- a/src/python_lang_project_harness/_semantic_language_schemas.py +++ b/src/python_lang_project_harness/_semantic_language_schemas.py @@ -1,4 +1,4 @@ -"""Schema registrations advertised by the Python semantic-language provider.""" +"""Provider-owned schema registrations for the Python semantic language.""" from __future__ import annotations @@ -6,127 +6,12 @@ def python_semantic_language_schemas() -> list[dict[str, str]]: - """Return package-local schema registrations for agent doctor.""" + """Return only schemas owned by the Python provider.""" return [ - { - "schemaId": ids.SEMANTIC_SEARCH_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-search-packet.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_QUERY_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-query-packet.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_SOURCE_LOCATION_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-source-location.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_TREE_SITTER_PROVENANCE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-tree-sitter-provenance.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_GRAPH_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-graph.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_GRAPH_TURBO_REQUEST_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-graph-turbo-request.v1.schema.json", - }, - { - "schemaId": "agent.semantic-protocols.semantic-verification-receipt", - "schemaVersion": "1", - "path": "schemas/semantic-verification-receipt.v1.schema.json", - }, - { - "schemaId": "agent.semantic-protocols.semantic-behavior-snapshot", - "schemaVersion": "1", - "path": "schemas/semantic-behavior-snapshot.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_DETERMINISM_READINESS_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-determinism-readiness.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_DEV_COMMAND_LOG_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-dev-command-log.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_FORMAL_PROOF_PILOT_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-formal-proof-pilot.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_REVIEW_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-review-packet.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_EVIDENCE_GRAPH_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-evidence-graph.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_ASSURANCE_CASE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-assurance-case.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_AST_PATCH_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-ast-patch.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_AST_PATCH_RECEIPT_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-ast-patch-receipt.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_TREE_SITTER_QUERY_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-tree-sitter-query.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_TREE_SITTER_GRAMMAR_PROFILE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-tree-sitter-grammar-profile.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_TYPE_SURFACE_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-type-surface.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_FACT_GRAPH_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-fact-graph.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_FACT_ONTOLOGY_SCHEMA_ID, - "schemaVersion": "1", - "path": "schemas/semantic-fact-ontology.v1.schema.json", - }, - { - "schemaId": "agent.semantic-protocols.semantic-handle", - "schemaVersion": "1", - "path": "schemas/semantic-handle.v1.schema.json", - }, - { - "schemaId": ids.SEMANTIC_LANGUAGE_REGISTRY_ID, - "schemaVersion": ids.SEMANTIC_LANGUAGE_REGISTRY_VERSION, - "path": "schemas/semantic-language-registry.v1.schema.json", - }, { "schemaId": ids.PYTHON_CAPABILITIES_SCHEMA_ID, "schemaVersion": "1", "path": "schemas/python-semantic-capabilities.v1.schema.json", - }, + } ] diff --git a/tests/unit/harness/provider_runtime_live_support.py b/tests/unit/harness/provider_runtime_live_support.py index 2598d80..9d4564d 100644 --- a/tests/unit/harness/provider_runtime_live_support.py +++ b/tests/unit/harness/provider_runtime_live_support.py @@ -20,6 +20,26 @@ def environment() -> dict[str, str]: "ASP_PROVIDER_ARTIFACT_DIGEST": "blake3-256:" + "a" * 64, "ASP_PROVIDER_REGISTRATION_DIGEST": "sha256:" + "b" * 64, "ASP_PROVIDER_RUNTIME_CONTRACT_DIGEST": "blake3-256:" + "c" * 64, + "ASP_PROVIDER_RUNTIME_OPERATIONS_JSON": json.dumps( + [ + { + "operation": "projection-batch", + "requestSchemaId": "schema:projection-request", + "responseSchemaId": "schema:projection-response", + }, + { + "operation": "project-resolution", + "requestSchemaId": "schema:resolution-request", + "responseSchemaId": "schema:resolution-response", + }, + { + "operation": "query", + "requestSchemaId": "schema:query-request", + "responseSchemaId": "schema:query-response", + }, + ], + separators=(",", ":"), + ), "ASP_PROVIDER_ID": "asp-python", "ASP_PROVIDER_LANGUAGE_ID": "python", "ASP_CLIENT_SERVER_HOST": "127.0.0.1:0", diff --git a/tests/unit/harness/test_provider_runtime.py b/tests/unit/harness/test_provider_runtime.py index bb2cb58..63afa7a 100644 --- a/tests/unit/harness/test_provider_runtime.py +++ b/tests/unit/harness/test_provider_runtime.py @@ -4,8 +4,10 @@ import http.client import json +import shutil import subprocess -import sys +from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import TimeoutError as FutureTimeoutError from pathlib import Path from urllib.parse import urlsplit @@ -22,6 +24,28 @@ from python_lang_project_harness._runtime import _health, _response_frame +def _read_bootstrap(process: subprocess.Popen[str]) -> dict[str, object]: + assert process.stdout is not None + assert process.stderr is not None + with ThreadPoolExecutor(max_workers=1) as executor: + line = executor.submit(process.stdout.readline) + try: + bootstrap_line = line.result(timeout=2) + except FutureTimeoutError as error: + process.kill() + _, stderr = process.communicate(timeout=2) + raise AssertionError( + f"Python provider exceeded the 2s bootstrap deadline: stderr={stderr!r}" + ) from error + if bootstrap_line: + return json.loads(bootstrap_line) + status = process.wait(timeout=2) + stderr = process.stderr.read() + raise AssertionError( + f"Python provider exited before bootstrap: status={status} stderr={stderr!r}" + ) + + def test_resident_runtime_publishes_manifest_operations_and_structured_frames( monkeypatch: object, ) -> None: @@ -47,8 +71,10 @@ def test_resident_runtime_publishes_manifest_operations_and_structured_frames( def test_http_json_live_corpus_stream_query_concurrency_and_latency() -> None: + provider = shutil.which("asp-python") + assert provider is not None, "uv project environment omitted asp-python" process = subprocess.Popen( - [sys.executable, "-m", "python_lang_project_harness", "serve"], + [provider, "serve"], cwd=Path(__file__).parents[3], env=environment(), stdin=subprocess.DEVNULL, @@ -56,8 +82,7 @@ def test_http_json_live_corpus_stream_query_concurrency_and_latency() -> None: stderr=subprocess.PIPE, text=True, ) - assert process.stdout is not None - bootstrap = json.loads(process.stdout.readline()) + bootstrap = _read_bootstrap(process) assert ( bootstrap["schemaId"] == "agent.semantic-protocols.asp-client-server-bootstrap" ) diff --git a/tests/unit/harness/test_semantic_cli_policy.py b/tests/unit/harness/test_semantic_cli_policy.py index 72d74b8..b560269 100644 --- a/tests/unit/harness/test_semantic_cli_policy.py +++ b/tests/unit/harness/test_semantic_cli_policy.py @@ -18,9 +18,8 @@ def test_cli_agent_doctor_json_advertises_policy_search( assert exit_code == 0 registration = payload["registry"]["languages"][0] assert "search/policy" in registration["methods"] - assert any( - schema["schemaId"] == "agent.semantic-protocols.semantic-handle" - and schema["path"] == "schemas/semantic-handle.v1.schema.json" + assert all( + schema["schemaId"] != "agent.semantic-protocols.semantic-handle" for schema in registration["schemas"] ) assert any( diff --git a/tests/unit/harness/test_semantic_language_schemas.py b/tests/unit/harness/test_semantic_language_schemas.py index 47435ff..9f6cf91 100644 --- a/tests/unit/harness/test_semantic_language_schemas.py +++ b/tests/unit/harness/test_semantic_language_schemas.py @@ -1,21 +1,27 @@ -"""Focused schema registration tests for the Python provider.""" +"""Provider-owned schema registration tests for the Python provider.""" from __future__ import annotations from python_lang_project_harness import python_semantic_language_registration -def test_python_registration_advertises_semantic_fact_graph_schemas() -> None: +def test_python_registration_advertises_only_provider_owned_schemas() -> None: registration = python_semantic_language_registration() - schema_entries = { - (schema["schemaId"], schema["path"]) for schema in registration["schemas"] + + assert registration["schemas"] == [ + { + "schemaId": "agent.semantic-protocols.languages.python.asp-python.capabilities", + "schemaVersion": "1", + "path": "schemas/python-semantic-capabilities.v1.schema.json", + } + ] + + +def test_python_registration_does_not_advertise_shared_schema_ids() -> None: + registration = python_semantic_language_registration() + provider_owned_schema_ids = { + "agent.semantic-protocols.languages.python.asp-python.capabilities" } + advertised_schema_ids = {schema["schemaId"] for schema in registration["schemas"]} - assert ( - "agent.semantic-protocols.semantic-fact-graph", - "schemas/semantic-fact-graph.v1.schema.json", - ) in schema_entries - assert ( - "agent.semantic-protocols.semantic-fact-ontology", - "schemas/semantic-fact-ontology.v1.schema.json", - ) in schema_entries + assert advertised_schema_ids - provider_owned_schema_ids == set() diff --git a/tests/unit/harness/test_semantic_schema_registry.py b/tests/unit/harness/test_semantic_schema_registry.py deleted file mode 100644 index 371d521..0000000 --- a/tests/unit/harness/test_semantic_schema_registry.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Schema registry contract tests for the Python semantic provider.""" - -from __future__ import annotations - -import json -from pathlib import Path - -import pytest - - -def test_package_local_semantic_schemas_stay_synchronized() -> None: - package_root = Path(__file__).resolve().parents[3] - protocol_root = package_root.parents[1] - protocol_schema_dir = protocol_root / "schemas" - if not protocol_schema_dir.exists(): - pytest.skip("protocol repository schemas are not available") - - for package_schema_path in sorted((package_root / "schemas").glob("*.schema.json")): - protocol_schema_path = protocol_schema_dir / package_schema_path.name - if not protocol_schema_path.exists(): - continue - package_schema = json.loads(package_schema_path.read_text(encoding="utf-8")) - protocol_schema = json.loads(protocol_schema_path.read_text(encoding="utf-8")) - - assert package_schema == protocol_schema From f4d28a897427cc11a63ed7f3222b3b5406cb9c10 Mon Sep 17 00:00:00 2001 From: guangtao Date: Tue, 1 Sep 2026 19:50:46 +0800 Subject: [PATCH 13/20] feat: rename project to asp-python --- .github/workflows/release.yml | 6 +- README.md | 60 ++- development.md | 4 +- docs/01_core/101_harness_boundary.md | 30 +- docs/03_features/201_rule_catalog.md | 16 +- docs/03_features/202_runner_modes.md | 18 +- docs/03_features/203_cli.md | 90 +--- docs/03_features/204_pytest.md | 29 +- docs/03_features/205_verification.md | 12 +- provider/asp-provider-registration.json | 62 +-- provider/asp-provider-workspace-install.json | 12 +- pyproject.toml | 26 +- schemas/.asp-schema-manager-membership.json | 407 ++++++++++++++++++ schemas/.asp-schema-manager-receipt.json | 338 +-------------- .../asp-client-dispatch-failure.schema.json | 18 + ...asp-client-exact-query-request.schema.json | 6 +- ...ent-graphs-timeline-request.v1.schema.json | 15 + ...p-client-owner-search-response.schema.json | 58 +++ .../asp-client-protocol-catalog.schema.json | 25 +- ...client-query-readiness-failure.schema.json | 29 ++ ...-client-schema-bundle-response.schema.json | 16 +- ...nt-source-index-lookup-request.schema.json | 17 + ...eneration-ensure-ready-request.schema.json | 9 + .../asp-python-graphs-session.v1.schema.json | 90 ++++ ...sp-semantic-extension-envelope.schema.json | 21 - schemas/callable-skeleton.schema.json | 4 +- schemas/grpc-warm-latency-receipt.schema.json | 44 ++ ...language-schema-bundle-receipt.schema.json | 62 +-- ...ostings-work-reduction-receipt.schema.json | 15 + ...arch-payload-reduction-receipt.schema.json | 23 + schemas/provider-definitions.v1.schema.json | 9 + schemas/provider-manifest.schema.json | 2 +- schemas/provider-route.schema.json | 19 +- ...er-runtime-contract-descriptor.schema.json | 6 +- schemas/resident-search-result.v1.schema.json | 67 +++ schemas/rg-coverage-receipt.schema.json | 32 ++ schemas/runtime-client-terminal.schema.json | 23 + ...ime-provider-search-receipt.v1.schema.json | 80 ++++ ...earch-generation-change-set.v1.schema.json | 74 ++++ ...resident-evaluation-request.v1.schema.json | 47 ++ ...-resident-evaluation-result.v1.schema.json | 80 ++++ ...graph-turbo-artifact-events.v1.schema.json | 118 +++++ ...emantic-graph-turbo-request.v1.schema.json | 2 +- .../semantic-language-registry.v1.schema.json | 4 +- .../__init__.py | 26 +- .../__main__.py | 2 +- .../_agent_namespace.py | 18 +- .../_agent_namespace_index.py | 0 .../_agent_policy.py | 18 +- .../_agent_policy_catalog.py | 30 +- .../_agent_reasoning_tree.py | 12 +- .../_agent_snapshot.py | 24 +- .../_agent_snapshot_tree.py | 8 +- .../_callable_skeleton_projection.py | 0 src/asp_python/_cli.py | 62 +++ .../_cli_agent.py | 2 +- .../_cli_args.py | 198 +-------- .../_cli_ast_patch.py | 0 .../_cli_protocol.py | 21 - .../_cli_query.py | 0 .../_cli_query_arg_consume.py | 0 .../_cli_query_args.py | 0 .../_cli_query_flow_lite_args.py | 0 .../_cli_query_hook_args.py | 0 .../_cli_query_predicates.py | 0 .../_cli_query_tree_sitter_args.py | 0 .../_cli_search_runtime.py | 16 +- .../_constants.py | 0 .../_dependency_topology.py | 0 .../_dev_command_log.py | 0 .../_dev_command_log_command.py | 0 .../_dev_command_log_context.py | 0 .../_discovery.py | 6 +- .../_evidence_graph.py | 26 +- .../_evidence_graph_turbo.py | 0 .../_exact_projection_model.py | 0 .../_exact_source_projection.py | 0 .../_flow_lite_query.py | 0 .../_flow_lite_query_model.py | 0 .../_flow_lite_query_packet.py | 0 .../_flow_lite_query_projector.py | 0 .../_harness_rules.py | 4 +- .../_model.py | 32 +- .../_modern_design.py | 28 +- .../_modern_design_catalog.py | 14 +- .../_modularity.py | 26 +- .../_modularity_signals.py | 0 .../_project_config.py | 16 +- .../_project_evaluation.py | 14 +- .../_project_metadata.py | 0 .../_project_policy.py | 8 +- .../_project_policy_catalog.py | 32 +- .../_project_policy_imports.py | 18 +- .../_project_policy_layout.py | 14 +- .../_project_policy_metadata.py | 18 +- .../_project_policy_pytest_gate.py | 16 +- .../_project_policy_typed.py | 20 +- .../_project_policy_verification.py | 14 +- .../_project_resolution.py | 0 .../_project_resolution_backends.py | 0 .../_project_resolution_candidates.py | 0 .../_project_resolution_document.py | 0 .../_project_resolution_graph.py | 0 .../_project_resolution_sources.py | 0 .../_projection_batch.py | 0 .../_pytest.py | 22 +- src/asp_python/_pytest_plugin_options.py | 160 +++++++ src/asp_python/_pytest_plugin_project.py | 91 ++++ .../_python_compact.py | 6 +- .../_python_expr.py | 0 .../_python_outline.py | 2 +- .../_python_projection.py | 6 +- .../_python_projection_extras.py | 4 +- .../_python_projection_facts.py | 4 +- .../_python_projection_model.py | 0 .../_python_source.py | 0 .../_render.py | 36 +- .../_rule_packs.py | 22 +- .../_runner.py | 42 +- .../_runtime.py | 0 .../_runtime_http.py | 0 .../_semantic_graph_fact_collect.py | 0 .../_semantic_graph_fact_model.py | 0 .../_semantic_graph_fact_render.py | 0 .../_semantic_graph_fact_render_fields.py | 0 .../_semantic_graph_facts.py | 0 .../_semantic_graph_project_collect.py | 0 .../_semantic_graph_project_render.py | 0 .../_semantic_language.py | 13 +- .../_semantic_language_benchmark.py | 0 .../_semantic_language_catalog.py | 0 .../_semantic_language_ids.py | 0 .../_semantic_language_invocation.py | 2 - .../_semantic_language_knowledge.py | 0 .../_semantic_language_query.py | 0 .../_semantic_language_schemas.py | 0 .../_semantic_projection.py | 0 .../_semantic_provider_doctor.py | 0 .../_semantic_query_pack.py | 0 .../_semantic_query_packet.py | 0 .../_semantic_search.py | 0 .../_semantic_search_callsite_hits.py | 4 +- .../_semantic_search_cli.py | 0 .../_semantic_search_common.py | 0 .../_semantic_search_deps.py | 4 +- .../_semantic_search_findings.py | 6 +- .../_semantic_search_graph_render.py | 0 .../_semantic_search_hits.py | 0 .../_semantic_search_import_routes.py | 6 +- .../_semantic_search_import_test_hits.py | 6 +- .../_semantic_search_ingest.py | 0 .../_semantic_search_ingest_fast.py | 0 .../_semantic_search_item_lines.py | 4 +- .../_semantic_search_items.py | 10 +- .../_semantic_search_knowledge_facts.py | 0 .../_semantic_search_lexical_fast.py | 0 .../_semantic_search_model.py | 0 .../_semantic_search_owner_fast.py | 0 .../_semantic_search_owners.py | 0 .../_semantic_search_packages.py | 4 +- .../_semantic_search_packet.py | 4 +- .../_semantic_search_policy.py | 16 +- .../_semantic_search_prefilter.py | 0 .../_semantic_search_prefilter_file_scan.py | 0 .../_semantic_search_prefilter_path.py | 0 .../_semantic_search_prefilter_process.py | 0 .../_semantic_search_prefilter_rank.py | 0 .../_semantic_search_prefilter_result.py | 0 .../_semantic_search_prefilter_select.py | 0 .../_semantic_search_prefilter_tools.py | 0 .../_semantic_search_prime_fast.py | 0 .../_semantic_search_profiles.py | 0 ...mantic_search_public_external_type_hits.py | 4 +- ...tic_search_public_external_type_imports.py | 0 ...antic_search_public_external_type_model.py | 0 ...ic_search_public_external_type_surfaces.py | 0 .../_semantic_search_public_external_types.py | 4 +- .../_semantic_search_reasoning.py | 4 +- .../_semantic_search_render.py | 0 .../_semantic_search_render_compact.py | 0 .../_semantic_search_render_flow.py | 0 .../_semantic_search_render_lines.py | 0 .../_semantic_search_symbol_hits.py | 6 +- .../_semantic_search_text_hits.py | 6 +- .../_semantic_search_view_actions.py | 0 .../_semantic_search_view_core.py | 8 +- .../_semantic_search_view_deps_imports.py | 6 +- .../_semantic_search_view_hits.py | 6 +- .../_semantic_search_view_ingest.py | 0 .../_semantic_search_view_knowledge.py | 0 .../_semantic_search_view_lexical_queries.py | 6 +- ..._semantic_search_view_lexical_synthesis.py | 0 .../_semantic_search_views.py | 6 +- .../_semantic_selector_identity.py | 0 .../_semantic_syntax_refs.py | 0 .../_source.py | 0 .../_syntax.py | 6 +- .../_syntax_catalog.py | 12 +- .../_test_layout.py | 20 +- .../_test_layout_bloat.py | 10 +- .../_test_layout_catalog.py | 12 +- .../_test_layout_config.py | 0 .../_test_layout_entries.py | 14 +- .../_tree_sitter_query.py | 4 +- .../_tree_sitter_query_catalog.py | 0 .../_tree_sitter_query_model.py | 0 .../_tree_sitter_query_packet.py | 0 .../_tree_sitter_query_packet_rows.py | 0 .../_tree_sitter_query_predicates.py | 0 .../_tree_sitter_query_projection.py | 4 +- .../_tree_sitter_query_projection_capture.py | 0 .../_tree_sitter_query_projection_source.py | 4 +- .../_version.py | 2 +- .../agent_readability/__init__.py | 0 .../agent_readability/_boundaries.py | 0 .../agent_readability/_software_criteria.py | 0 .../agent_readability/algorithm_shape.py | 8 +- .../agent_readability/function_compactness.py | 8 +- .../agent_readability/native_idioms.py | 8 +- .../agent_readability/type_shapes.py | 6 +- .../harness-rules.md | 0 .../harness.py | 48 +-- .../py.typed | 0 .../pytest.py | 4 +- src/asp_python/pytest_plugin.py | 79 ++++ .../verification/__init__.py | 0 .../verification/facts.py | 10 +- .../verification/indices.py | 0 .../verification/model.py | 0 .../verification/obligations.py | 0 .../verification/planner.py | 12 +- .../verification/profile_index.py | 14 +- .../verification/render.py | 0 .../verification/report.py | 0 src/python_lang_parser/_project_model.py | 4 +- src/python_lang_parser/_version.py | 2 +- src/python_lang_project_harness/_cli.py | 103 ----- .../pytest_plugin.py | 195 --------- .../test_native_idiom_binding_state.py | 2 +- tests/unit/harness/harness-rules.generated.md | 4 +- .../harness/project_policy/test_catalog.py | 2 +- .../harness/project_policy/test_layout.py | 14 +- .../harness/project_policy/test_metadata.py | 2 +- .../project_policy/test_metadata_policy.py | 36 +- .../project_policy/test_typed_packages.py | 20 +- .../harness/provider_runtime_live_support.py | 32 +- tests/unit/harness/semantic_search_fixture.py | 2 +- tests/unit/harness/snapshot_support.py | 2 +- .../harness/test_agent_algorithm_policy.py | 2 +- tests/unit/harness/test_agent_policy.py | 20 +- .../harness/test_agent_policy_snapshots.py | 6 +- tests/unit/harness/test_cli.py | 303 +------------ .../unit/harness/test_dependency_topology.py | 2 +- .../harness/test_dependency_topology_cli.py | 4 +- tests/unit/harness/test_dev_command_log.py | 2 +- tests/unit/harness/test_evidence_graph.py | 5 +- .../harness/test_exact_source_projection.py | 2 +- tests/unit/harness/test_harness_rules.py | 12 +- tests/unit/harness/test_modern_design.py | 4 +- tests/unit/harness/test_modularity_catalog.py | 10 +- .../harness/test_parser_boundary_contract.py | 52 +-- tests/unit/harness/test_policy_contract.py | 8 +- tests/unit/harness/test_policy_snapshots.py | 10 +- tests/unit/harness/test_project_api.py | 56 +-- tests/unit/harness/test_project_config.py | 18 +- .../harness/test_project_fixture_scope.py | 6 +- tests/unit/harness/test_project_resolution.py | 12 +- .../test_project_resolution_extra_paths.py | 8 +- tests/unit/harness/test_projection_batch.py | 2 +- tests/unit/harness/test_provider_runtime.py | 10 +- .../unit/harness/test_public_cli_identity.py | 6 +- .../harness/test_pyproject_package_scope.py | 6 +- tests/unit/harness/test_pytest.py | 44 +- tests/unit/harness/test_pytest_plugin.py | 62 ++- .../harness/test_reasoning_tree_policy.py | 19 +- tests/unit/harness/test_render_snapshots.py | 36 +- tests/unit/harness/test_runner_config.py | 42 +- tests/unit/harness/test_semantic_agent_cli.py | 2 +- tests/unit/harness/test_semantic_cli.py | 2 +- .../harness/test_semantic_cli_ast_patch.py | 2 +- .../test_semantic_cli_benchmark_registry.py | 2 +- .../harness/test_semantic_cli_fast_prime.py | 4 +- .../unit/harness/test_semantic_cli_lexical.py | 18 +- .../test_semantic_cli_owner_item_broad.py | 2 +- .../test_semantic_cli_owner_item_inventory.py | 2 +- ...test_semantic_cli_owner_items_fast_path.py | 16 +- .../unit/harness/test_semantic_cli_policy.py | 9 +- ...test_semantic_cli_public_external_types.py | 2 +- .../harness/test_semantic_cli_query_set.py | 2 +- .../harness/test_semantic_cli_reasoning.py | 2 +- ...mantic_cli_structural_selector_registry.py | 2 +- ...est_semantic_cli_tree_sitter_predicates.py | 2 +- .../test_semantic_cli_tree_sitter_registry.py | 2 +- .../test_semantic_cli_workspace_search.py | 2 +- .../unit/harness/test_semantic_graph_facts.py | 2 +- .../harness/test_semantic_language_schemas.py | 2 +- .../harness/test_semantic_provider_doctor.py | 4 +- .../unit/harness/test_semantic_render_flow.py | 2 +- .../test_semantic_search_graph_profiles.py | 2 +- ..._semantic_search_graph_render_shell_out.py | 2 +- .../test_semantic_search_ingest_cli.py | 6 +- .../test_software_criterion_snapshots.py | 8 +- tests/unit/harness/test_test_layout_config.py | 10 +- tests/unit/harness/test_verification.py | 18 +- .../test_agent_snapshot_profile_index.py | 6 +- .../test_performance_microbench.py | 2 +- .../verification/test_policy_regressions.py | 2 +- .../verification/test_profile_index.py | 2 +- .../lang_harness/test_config_contracts.py | 4 +- .../lang_harness/test_discovery_runner.py | 28 +- .../lang_harness/test_render_assertions.py | 16 +- .../test_pyproject_metadata.py | 8 +- ...text.snap => asp_python_compact_text.snap} | 0 ...harness_json.snap => asp_python_json.snap} | 0 ...ot__py_proj_r011_verification_profile.snap | 2 +- tests/unit/test_package_metadata.py | 30 +- tests/unit/test_public_api.py | 35 +- tests/unit/test_self_hosting.py | 6 +- uv.lock | 2 +- 319 files changed, 2780 insertions(+), 2364 deletions(-) create mode 100644 schemas/.asp-schema-manager-membership.json create mode 100644 schemas/asp-client-dispatch-failure.schema.json create mode 100644 schemas/asp-client-graphs-timeline-request.v1.schema.json create mode 100644 schemas/asp-client-owner-search-response.schema.json create mode 100644 schemas/asp-client-query-readiness-failure.schema.json create mode 100644 schemas/asp-client-source-index-lookup-request.schema.json create mode 100644 schemas/asp-client-workspace-generation-ensure-ready-request.schema.json create mode 100644 schemas/asp-python-graphs-session.v1.schema.json delete mode 100644 schemas/asp-semantic-extension-envelope.schema.json create mode 100644 schemas/grpc-warm-latency-receipt.schema.json create mode 100644 schemas/lexical-postings-work-reduction-receipt.schema.json create mode 100644 schemas/owner-search-payload-reduction-receipt.schema.json create mode 100644 schemas/resident-search-result.v1.schema.json create mode 100644 schemas/rg-coverage-receipt.schema.json create mode 100644 schemas/runtime-client-terminal.schema.json create mode 100644 schemas/runtime-provider-search-receipt.v1.schema.json create mode 100644 schemas/search-generation-change-set.v1.schema.json create mode 100644 schemas/semantic-graph-resident-evaluation-request.v1.schema.json create mode 100644 schemas/semantic-graph-resident-evaluation-result.v1.schema.json create mode 100644 schemas/semantic-graph-turbo-artifact-events.v1.schema.json rename src/{python_lang_project_harness => asp_python}/__init__.py (93%) rename src/{python_lang_project_harness => asp_python}/__main__.py (61%) rename src/{python_lang_project_harness => asp_python}/_agent_namespace.py (93%) rename src/{python_lang_project_harness => asp_python}/_agent_namespace_index.py (100%) rename src/{python_lang_project_harness => asp_python}/_agent_policy.py (91%) rename src/{python_lang_project_harness => asp_python}/_agent_policy_catalog.py (93%) rename src/{python_lang_project_harness => asp_python}/_agent_reasoning_tree.py (96%) rename src/{python_lang_project_harness => asp_python}/_agent_snapshot.py (78%) rename src/{python_lang_project_harness => asp_python}/_agent_snapshot_tree.py (98%) rename src/{python_lang_project_harness => asp_python}/_callable_skeleton_projection.py (100%) create mode 100644 src/asp_python/_cli.py rename src/{python_lang_project_harness => asp_python}/_cli_agent.py (99%) rename src/{python_lang_project_harness => asp_python}/_cli_args.py (63%) rename src/{python_lang_project_harness => asp_python}/_cli_ast_patch.py (100%) rename src/{python_lang_project_harness => asp_python}/_cli_protocol.py (93%) rename src/{python_lang_project_harness => asp_python}/_cli_query.py (100%) rename src/{python_lang_project_harness => asp_python}/_cli_query_arg_consume.py (100%) rename src/{python_lang_project_harness => asp_python}/_cli_query_args.py (100%) rename src/{python_lang_project_harness => asp_python}/_cli_query_flow_lite_args.py (100%) rename src/{python_lang_project_harness => asp_python}/_cli_query_hook_args.py (100%) rename src/{python_lang_project_harness => asp_python}/_cli_query_predicates.py (100%) rename src/{python_lang_project_harness => asp_python}/_cli_query_tree_sitter_args.py (100%) rename src/{python_lang_project_harness => asp_python}/_cli_search_runtime.py (95%) rename src/{python_lang_project_harness => asp_python}/_constants.py (100%) rename src/{python_lang_project_harness => asp_python}/_dependency_topology.py (100%) rename src/{python_lang_project_harness => asp_python}/_dev_command_log.py (100%) rename src/{python_lang_project_harness => asp_python}/_dev_command_log_command.py (100%) rename src/{python_lang_project_harness => asp_python}/_dev_command_log_context.py (100%) rename src/{python_lang_project_harness => asp_python}/_discovery.py (98%) rename src/{python_lang_project_harness => asp_python}/_evidence_graph.py (88%) rename src/{python_lang_project_harness => asp_python}/_evidence_graph_turbo.py (100%) rename src/{python_lang_project_harness => asp_python}/_exact_projection_model.py (100%) rename src/{python_lang_project_harness => asp_python}/_exact_source_projection.py (100%) rename src/{python_lang_project_harness => asp_python}/_flow_lite_query.py (100%) rename src/{python_lang_project_harness => asp_python}/_flow_lite_query_model.py (100%) rename src/{python_lang_project_harness => asp_python}/_flow_lite_query_packet.py (100%) rename src/{python_lang_project_harness => asp_python}/_flow_lite_query_projector.py (100%) rename src/{python_lang_project_harness => asp_python}/_harness_rules.py (91%) rename src/{python_lang_project_harness => asp_python}/_model.py (95%) rename src/{python_lang_project_harness => asp_python}/_modern_design.py (89%) rename src/{python_lang_project_harness => asp_python}/_modern_design_catalog.py (88%) rename src/{python_lang_project_harness => asp_python}/_modularity.py (93%) rename src/{python_lang_project_harness => asp_python}/_modularity_signals.py (100%) rename src/{python_lang_project_harness => asp_python}/_project_config.py (97%) rename src/{python_lang_project_harness => asp_python}/_project_evaluation.py (87%) rename src/{python_lang_project_harness => asp_python}/_project_metadata.py (100%) rename src/{python_lang_project_harness => asp_python}/_project_policy.py (90%) rename src/{python_lang_project_harness => asp_python}/_project_policy_catalog.py (87%) rename src/{python_lang_project_harness => asp_python}/_project_policy_imports.py (94%) rename src/{python_lang_project_harness => asp_python}/_project_policy_layout.py (94%) rename src/{python_lang_project_harness => asp_python}/_project_policy_metadata.py (89%) rename src/{python_lang_project_harness => asp_python}/_project_policy_pytest_gate.py (84%) rename src/{python_lang_project_harness => asp_python}/_project_policy_typed.py (92%) rename src/{python_lang_project_harness => asp_python}/_project_policy_verification.py (87%) rename src/{python_lang_project_harness => asp_python}/_project_resolution.py (100%) rename src/{python_lang_project_harness => asp_python}/_project_resolution_backends.py (100%) rename src/{python_lang_project_harness => asp_python}/_project_resolution_candidates.py (100%) rename src/{python_lang_project_harness => asp_python}/_project_resolution_document.py (100%) rename src/{python_lang_project_harness => asp_python}/_project_resolution_graph.py (100%) rename src/{python_lang_project_harness => asp_python}/_project_resolution_sources.py (100%) rename src/{python_lang_project_harness => asp_python}/_projection_batch.py (100%) rename src/{python_lang_project_harness => asp_python}/_pytest.py (68%) create mode 100644 src/asp_python/_pytest_plugin_options.py create mode 100644 src/asp_python/_pytest_plugin_project.py rename src/{python_lang_project_harness => asp_python}/_python_compact.py (89%) rename src/{python_lang_project_harness => asp_python}/_python_expr.py (100%) rename src/{python_lang_project_harness => asp_python}/_python_outline.py (99%) rename src/{python_lang_project_harness => asp_python}/_python_projection.py (85%) rename src/{python_lang_project_harness => asp_python}/_python_projection_extras.py (93%) rename src/{python_lang_project_harness => asp_python}/_python_projection_facts.py (98%) rename src/{python_lang_project_harness => asp_python}/_python_projection_model.py (100%) rename src/{python_lang_project_harness => asp_python}/_python_source.py (100%) rename src/{python_lang_project_harness => asp_python}/_render.py (93%) rename src/{python_lang_project_harness => asp_python}/_rule_packs.py (81%) rename src/{python_lang_project_harness => asp_python}/_runner.py (88%) rename src/{python_lang_project_harness => asp_python}/_runtime.py (100%) rename src/{python_lang_project_harness => asp_python}/_runtime_http.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_graph_fact_collect.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_graph_fact_model.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_graph_fact_render.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_graph_fact_render_fields.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_graph_facts.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_graph_project_collect.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_graph_project_render.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_language.py (94%) rename src/{python_lang_project_harness => asp_python}/_semantic_language_benchmark.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_language_catalog.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_language_ids.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_language_invocation.py (93%) rename src/{python_lang_project_harness => asp_python}/_semantic_language_knowledge.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_language_query.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_language_schemas.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_projection.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_provider_doctor.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_query_pack.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_query_packet.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_callsite_hits.py (96%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_cli.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_common.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_deps.py (98%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_findings.py (88%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_graph_render.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_hits.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_import_routes.py (96%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_import_test_hits.py (95%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_ingest.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_ingest_fast.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_item_lines.py (98%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_items.py (98%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_knowledge_facts.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_lexical_fast.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_model.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_owner_fast.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_owners.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_packages.py (97%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_packet.py (98%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_policy.py (93%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prefilter.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prefilter_file_scan.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prefilter_path.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prefilter_process.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prefilter_rank.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prefilter_result.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prefilter_select.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prefilter_tools.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_prime_fast.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_profiles.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_public_external_type_hits.py (96%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_public_external_type_imports.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_public_external_type_model.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_public_external_type_surfaces.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_public_external_types.py (99%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_reasoning.py (98%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_render.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_render_compact.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_render_flow.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_render_lines.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_symbol_hits.py (97%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_text_hits.py (98%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_view_actions.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_view_core.py (98%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_view_deps_imports.py (98%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_view_hits.py (97%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_view_ingest.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_view_knowledge.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_view_lexical_queries.py (97%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_view_lexical_synthesis.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_search_views.py (97%) rename src/{python_lang_project_harness => asp_python}/_semantic_selector_identity.py (100%) rename src/{python_lang_project_harness => asp_python}/_semantic_syntax_refs.py (100%) rename src/{python_lang_project_harness => asp_python}/_source.py (100%) rename src/{python_lang_project_harness => asp_python}/_syntax.py (91%) rename src/{python_lang_project_harness => asp_python}/_syntax_catalog.py (88%) rename src/{python_lang_project_harness => asp_python}/_test_layout.py (78%) rename src/{python_lang_project_harness => asp_python}/_test_layout_bloat.py (94%) rename src/{python_lang_project_harness => asp_python}/_test_layout_catalog.py (90%) rename src/{python_lang_project_harness => asp_python}/_test_layout_config.py (100%) rename src/{python_lang_project_harness => asp_python}/_test_layout_entries.py (93%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query.py (95%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query_catalog.py (100%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query_model.py (100%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query_packet.py (100%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query_packet_rows.py (100%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query_predicates.py (100%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query_projection.py (99%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query_projection_capture.py (100%) rename src/{python_lang_project_harness => asp_python}/_tree_sitter_query_projection_source.py (97%) rename src/{python_lang_project_harness => asp_python}/_version.py (86%) rename src/{python_lang_project_harness => asp_python}/agent_readability/__init__.py (100%) rename src/{python_lang_project_harness => asp_python}/agent_readability/_boundaries.py (100%) rename src/{python_lang_project_harness => asp_python}/agent_readability/_software_criteria.py (100%) rename src/{python_lang_project_harness => asp_python}/agent_readability/algorithm_shape.py (96%) rename src/{python_lang_project_harness => asp_python}/agent_readability/function_compactness.py (95%) rename src/{python_lang_project_harness => asp_python}/agent_readability/native_idioms.py (95%) rename src/{python_lang_project_harness => asp_python}/agent_readability/type_shapes.py (95%) rename src/{python_lang_project_harness => asp_python}/harness-rules.md (100%) rename src/{python_lang_project_harness => asp_python}/harness.py (87%) rename src/{python_lang_project_harness => asp_python}/py.typed (100%) rename src/{python_lang_project_harness => asp_python}/pytest.py (57%) create mode 100644 src/asp_python/pytest_plugin.py rename src/{python_lang_project_harness => asp_python}/verification/__init__.py (100%) rename src/{python_lang_project_harness => asp_python}/verification/facts.py (96%) rename src/{python_lang_project_harness => asp_python}/verification/indices.py (100%) rename src/{python_lang_project_harness => asp_python}/verification/model.py (100%) rename src/{python_lang_project_harness => asp_python}/verification/obligations.py (100%) rename src/{python_lang_project_harness => asp_python}/verification/planner.py (97%) rename src/{python_lang_project_harness => asp_python}/verification/profile_index.py (96%) rename src/{python_lang_project_harness => asp_python}/verification/render.py (100%) rename src/{python_lang_project_harness => asp_python}/verification/report.py (100%) delete mode 100644 src/python_lang_project_harness/_cli.py delete mode 100644 src/python_lang_project_harness/pytest_plugin.py rename tests/unit/snapshots/{python_project_harness_compact_text.snap => asp_python_compact_text.snap} (100%) rename tests/unit/snapshots/{python_project_harness_json.snap => asp_python_json.snap} (100%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc13bc5..0b0b024 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,7 +29,7 @@ jobs: - os: macos-latest target: aarch64-apple-darwin env: - BINARY: py-harness + BINARY: asp-python GH_TOKEN: ${{ github.token }} RELEASE_TAG: ${{ github.event.release.tag_name || inputs.tag || github.ref_name }} TARGET: ${{ matrix.target }} @@ -73,7 +73,7 @@ jobs: set -euo pipefail rm -rf build dist package py_harness_entry.py cat > py_harness_entry.py <<'PY' - from python_lang_project_harness import run_cli_from_env + from asp_python import run_cli_from_env raise SystemExit(run_cli_from_env()) PY @@ -82,7 +82,7 @@ jobs: --name "$BINARY" \ --paths src \ --collect-submodules python_lang_parser \ - --collect-submodules python_lang_project_harness \ + --collect-submodules asp_python \ py_harness_entry.py mkdir -p package cp -R "dist/${BINARY}/." package/ diff --git a/README.md b/README.md index 1b933bd..49c95d6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# python-lang-project-harness +# asp-python -`python-lang-project-harness` is a standalone Python project harness library for +`asp-python` is a standalone Python policy and semantic tooling library for modern Python packages. It ships two library boundaries in one repo: - `python_lang_parser`: Python-native AST, compiler, tokenize, symbol-table, module-shape, public-surface, and symbol-role facts. -- `python_lang_project_harness`: project discovery, deterministic rule +- `asp_python`: project discovery, deterministic rule packs, compact rendered diagnostics, and pytest-friendly assertions. The harness is library-first. Callers pass a project root or explicit paths, @@ -23,26 +23,26 @@ the harness. ```python from pathlib import Path -from python_lang_project_harness import ( +from asp_python import ( __version__, PythonOwnerResponsibility, PythonVerificationProfileHint, PythonVerificationTaskKind, - assert_python_project_harness_clean, + assert_asp_python_clean, default_python_harness_config, plan_python_project_verification_with_config, render_python_lang_harness, render_python_reasoning_tree, render_python_verification_plan, - run_python_project_harness, + run_asp_python, ) -def test_python_project_harness_policy() -> None: - assert_python_project_harness_clean(Path(".")) +def test_asp_python_policy() -> None: + assert_asp_python_clean(Path(".")) -report = run_python_project_harness(Path(".")) +report = run_asp_python(Path(".")) print(__version__) print(render_python_lang_harness(report)) print(render_python_reasoning_tree(report)) @@ -54,11 +54,11 @@ tool/cache/build directories such as `.venv`, `__pycache__`, `build`, and they do not narrow parser coverage. The explicit path runner, `run_python_lang_harness([...])`, is useful for focused parser and syntax checks. -Use `PythonHarnessConfig` to change source-root classification, test-root +Use `AspPythonConfig` to change source-root classification, test-root classification, extra external project paths, test inclusion, or blocking severities without hardcoding project-specific policy into the library. -Project runners also read `[tool.python-lang-project-harness]` from -`pyproject.toml` when no explicit `PythonHarnessConfig` is passed, including +Project runners also read `[tool.asp-python]` from +`pyproject.toml` when no explicit `AspPythonConfig` is passed, including `disabled_rule_ids` and `blocking_rule_ids` for stable rule-id policy. Standard `[project]` metadata such as `name`, `requires-python`, `import-names`, scripts, and pytest entry points is parsed by @@ -75,32 +75,26 @@ shadows without forcing an LLM to consume the full JSON report first. In project-scoped runs, tree paths are rendered relative to the project root to avoid repeating long absolute prefixes. -`render_python_project_harness_agent_snapshot(".")` and the -`--agent-snapshot` CLI mode bundle compact policy findings, reasoning-tree -facts, verification-profile reminders, and active verification tasks into one -low-noise Agent repair surface. The snapshot uses capped module summaries, -branches, public owners, import edges, and branch-first profile candidates; it -does not print clean-run file counts or empty section summaries. +`render_asp_python_agent_snapshot(".")` bundles compact policy +findings, reasoning-tree facts, verification-profile reminders, and active +verification tasks into one low-noise library response. The snapshot uses +capped module summaries, branches, public owners, import edges, and +branch-first profile candidates. -The semantic-language console script exposes search, registry, and check -surfaces aligned with the Rust and TypeScript harnesses: +The semantic-language console script exposes search and registry surfaces. +Policy remains a dependency API consumed by pytest/build ownership: ```shell asp-python search workspace . asp-python search prime . -asp-python search lexical PythonHarnessReport owner tests . -asp-python search lexical --query-set PythonHarnessReport --query-set PythonSemanticSearchOptions owner tests . +asp-python search lexical AspPythonReport owner tests . +asp-python search lexical --query-set AspPythonReport --query-set PythonSemanticSearchOptions owner tests . asp-python search public-external-types pytest . -asp-python search callsite PythonHarnessReport . +asp-python search callsite AspPythonReport . 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 . +python -c 'from asp_python import assert_asp_python_clean; assert_asp_python_clean(".")' ``` ## Verification Planning @@ -124,7 +118,7 @@ print(render_python_verification_plan(plan)) Profile hints, dependency signals, receipts, waivers, task-kind mappings, and skill bindings are configurable through `PythonVerificationPolicy` or -`[tool.python-lang-project-harness.verification]`. Parser facts win over config +`[tool.asp-python.verification]`. Parser facts win over config hints; mismatches become `responsibility_review` tasks instead of silent trust. `build_python_verification_profile_index(...)` exposes `active_profile_hints()` so Agents can turn parser-suggested owners into config-ready verification @@ -146,7 +140,7 @@ group: [dependency-groups] test = [ "pytest>=8", - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] @@ -159,9 +153,9 @@ harness when `--python-project-harness` is enabled. Projects that prefer an explicit test file can use the public helper: ```python -from python_lang_project_harness.pytest import python_project_harness_test +from asp_python.pytest import asp_python_test -test_python_project_harness_policy = python_project_harness_test() +test_asp_python_policy = asp_python_test() ``` ## Rule Packs diff --git a/development.md b/development.md index 34b5720..81d5451 100644 --- a/development.md +++ b/development.md @@ -24,7 +24,7 @@ agent snapshot, and `git diff --check`. This repo is a standalone Python library project. It ships: - `python_lang_parser` for Python-native parser facts -- `python_lang_project_harness` for discovery, rule packs, rendering, +- `asp_python` for discovery, rule packs, rendering, and pytest embedding Keep these boundaries separate. Parser modules should not know about project @@ -57,7 +57,7 @@ Rendered output and policy diagnostics are locked under `tests/unit/snapshots`. Normal tests compare snapshots only. Refresh them intentionally: ```shell -PYTHON_HARNESS_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ +ASP_PYTHON_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ tests/unit/harness/test_render_snapshots.py \ tests/unit/harness/test_agent_policy_snapshots.py \ tests/unit/harness/test_policy_snapshots.py -q diff --git a/docs/01_core/101_harness_boundary.md b/docs/01_core/101_harness_boundary.md index 91999a0..c0caa06 100644 --- a/docs/01_core/101_harness_boundary.md +++ b/docs/01_core/101_harness_boundary.md @@ -7,7 +7,7 @@ :LAST_SYNC: 2026-04-30 :END: -`python-lang-project-harness` owns a standalone, library-first Python project +`asp-python` owns a standalone, library-first Python project harness. It keeps parser facts and project policy in separate import packages, but ships them from the same repo so downstream users do not depend on the old monorepo workspace layout. @@ -47,7 +47,7 @@ For class surfaces it owns data/type shape facts such as annotated class fields, `__init__` self-field storage, method counts, and visible anchors like `dataclass`, `Enum`, `Protocol`, `TypedDict`, `NamedTuple`, or model bases. -`python_lang_project_harness` consumes those reports. It owns rule +`asp_python` consumes those reports. It owns rule catalogs, project discovery, report aggregation, rendering, and pytest embedding. Rule packs should depend on parser facts rather than ad hoc source text matching when a structured fact exists. @@ -89,11 +89,11 @@ before choosing a repair surface. ## Runner Modes -Use `run_python_project_harness()` or `assert_python_project_harness_clean()` +Use `run_asp_python()` or `assert_asp_python_clean()` when a caller has a project root. The project runner scans all Python files under the project root by default, with cache/build/environment directories excluded. `src/` and `tests/` remain source/test classification roots for -project policy; they do not narrow parser coverage. `PythonHarnessConfig` can +project policy; they do not narrow parser coverage. `AspPythonConfig` can change source-root classification, test-root classification, extra external project paths, and test inclusion behavior. @@ -110,16 +110,16 @@ when configured-blocking findings exist: ```python from pathlib import Path -from python_lang_project_harness import assert_python_project_harness_clean +from asp_python import assert_asp_python_clean -def test_python_project_harness_policy() -> None: - assert_python_project_harness_clean(Path(".")) +def test_asp_python_policy() -> None: + assert_asp_python_clean(Path(".")) ``` -`python_project_harness_test()` returns a pytest-collectable callable for +`asp_python_test()` returns a pytest-collectable callable for projects that prefer a one-line mount. Downstream projects can import it from -`python_lang_project_harness.pytest` and assign it to a test name. +`asp_python.pytest` and assign it to a test name. The package also exposes a pytest plugin through the `pytest11` entry point. When installed as a test/dev dependency, pytest loads the plugin and accepts @@ -127,14 +127,12 @@ When installed as a test/dev dependency, pytest loads the plugin and accepts option is enabled, so installing the package does not silently add a policy gate. -## CLI Embedding +## Dependency API -`asp-python check [--json] [PROJECT_ROOT]` runs the same default project -runner. Compact text is the default output. `--json` emits the structured -`PythonHarnessReport` payload. `asp-python search ...` renders bounded -semantic-search packets from parser-owned facts. The CLI is a thin adapter over -library APIs: it does not own workflow orchestration or project-specific -policy. +`assert_asp_python_clean(PROJECT_ROOT)` is the sole project-policy +entry. The pytest plugin and downstream build/test owners import that API; +`asp-python` exposes search/evidence/agent protocol commands only and never +creates a second policy-check authority. ## Blocking And Advice diff --git a/docs/03_features/201_rule_catalog.md b/docs/03_features/201_rule_catalog.md index 170ea54..d75426d 100644 --- a/docs/03_features/201_rule_catalog.md +++ b/docs/03_features/201_rule_catalog.md @@ -85,7 +85,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 + `[tool.asp-python.verification].profile_hints`. The finding points the Agent to `asp-python --agent-snapshot`, whose compact `[verify-profile]` section is the config draft. @@ -157,7 +157,7 @@ The harness turns those facts into a compact repair hint when a public function hides its algorithm behind nested `if`/loop structure. The rule stays advisory by default so teams can tune or promote it after seeing their project shape. -The implementation lives under `python_lang_project_harness.agent_readability` +The implementation lives under `asp_python.agent_readability` because the target reader is the repair agent, not a human style reviewer. The goal is short, explicit algorithm surfaces that an LLM can use from the reasoning tree: guard clauses instead of nested `else`, `match/case` or dispatch @@ -221,7 +221,7 @@ run-summary noise. Use repair hints; it returns an empty string when there is no advice to act on. Structured consumers should use `render_python_lang_harness_json()` or the -`PythonHarnessReport.to_dict()` shape instead of parsing compact text. +`AspPythonReport.to_dict()` shape instead of parsing compact text. ## Parser-First Policy @@ -231,11 +231,11 @@ text. `python_lang_parser` owns AST, compile, tokenize, symbol-table, source line, module-shape, public-name, public-surface, symbol-role, and import-root module identity facts, standard `pyproject.toml` project metadata, and package reasoning-tree facts. -`python_lang_project_harness` owns rule catalogs, project/test layout, +`asp_python` owns rule catalogs, project/test layout, reporting, and assertion behavior. Repository tests enforce this boundary by rejecting direct `ast` or `tokenize` -usage under `src/python_lang_project_harness`. File and metadata checks may +usage under `src/asp_python`. File and metadata checks may still read non-Python policy inputs such as `python-project-harness-rules.toml`; Python project metadata should flow through parser-owned `pyproject.toml` facts. @@ -245,8 +245,8 @@ through parser-owned `pyproject.toml` facts. The compact text and JSON render contracts are covered by repository snapshots under `tests/unit/snapshots`: -- `python_project_harness_compact_text.snap` -- `python_project_harness_json.snap` +- `asp_python_compact_text.snap` +- `asp_python_json.snap` Policy snapshots are generated from real harness fixtures and normalized to `$TEMP` paths. Every current `PY-AGENT-*` rule has a compact advice snapshot. @@ -258,7 +258,7 @@ ordinary snapshot diffs. Refresh snapshots explicitly: ```shell -PYTHON_HARNESS_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ +ASP_PYTHON_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ tests/unit/harness/test_render_snapshots.py \ tests/unit/harness/test_agent_policy_snapshots.py \ tests/unit/harness/test_policy_snapshots.py -q diff --git a/docs/03_features/202_runner_modes.md b/docs/03_features/202_runner_modes.md index c98fab2..cddfc24 100644 --- a/docs/03_features/202_runner_modes.md +++ b/docs/03_features/202_runner_modes.md @@ -11,7 +11,7 @@ The harness exposes two runner modes with shared configuration. ## Project Runner -Use `run_python_project_harness()` or `assert_python_project_harness_clean()` +Use `run_asp_python()` or `assert_asp_python_clean()` when a caller has a project root. The project runner scans the whole Python project root by default, attaches `PythonProjectHarnessScope`, and runs the full default rule surface: @@ -28,12 +28,12 @@ and produce CLI exit code `2`. ## Configuration -`PythonHarnessConfig` owns project-resolution classification and parser inclusion: +`AspPythonConfig` owns project-resolution classification and parser inclusion: ```python -from python_lang_project_harness import PythonHarnessConfig +from asp_python import AspPythonConfig -config = PythonHarnessConfig( +config = AspPythonConfig( include_tests=False, source_dir_names=("lib",), test_dir_names=("checks",), @@ -83,9 +83,9 @@ visible advice. Policy can also be configured by stable rule id: ```python -from python_lang_project_harness import PythonHarnessConfig +from asp_python import AspPythonConfig -config = PythonHarnessConfig( +config = AspPythonConfig( disabled_rule_ids=frozenset({"PY-MOD-R002"}), blocking_rule_ids=frozenset({"PY-AGENT-POLICY-007"}), ) @@ -97,10 +97,10 @@ blockers without changing their catalog severity, which keeps advisory rules visible as advice while allowing a project to enforce chosen agent policy. Project runners also read the same policy from `pyproject.toml` when no -explicit `PythonHarnessConfig` is passed: +explicit `AspPythonConfig` is passed: ```toml -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] blocking_rule_ids = ["PY-AGENT-POLICY-007"] source_dir_names = ["lib"] @@ -109,7 +109,7 @@ include_tests = false ``` Explicit function parameters still win for one-call source/test/extra path -classification. Passing an explicit `PythonHarnessConfig` from Python code +classification. Passing an explicit `AspPythonConfig` from Python code opts out of project-local config loading for that call. ## Explicit-Path Runner diff --git a/docs/03_features/203_cli.md b/docs/03_features/203_cli.md index 2773cdd..bb850cb 100644 --- a/docs/03_features/203_cli.md +++ b/docs/03_features/203_cli.md @@ -7,16 +7,13 @@ :LAST_SYNC: 2026-04-30 :END: -The package exposes `asp-python` as the semantic-language provider binary and -as a thin command-line adapter over the default project harness runner: +The package exposes `asp-python` as the semantic-language provider binary. +Project policy is available only through the dependency API and pytest plugin: ```shell asp-python search ... [--json] [--package PATH] [PROJECT_ROOT] -asp-python check [--changed | --full] [--json] [PROJECT_ROOT] asp-python agent doctor [--json] [PROJECT_ROOT] asp-python agent guide [PROJECT_ROOT] -asp-python [--json | --agent-snapshot] [--no-tests] [--source-dir DIR] [--test-dir DIR] [--extra-path PATH] [--disable-rule RULE_ID] [--block-rule RULE_ID] [PROJECT_ROOT] -python -m python_lang_project_harness [--json | --agent-snapshot] [--no-tests] [--source-dir DIR] [--test-dir DIR] [--extra-path PATH] [--disable-rule RULE_ID] [--block-rule RULE_ID] [PROJECT_ROOT] ``` When `PROJECT_ROOT` is omitted, the current working directory is used. @@ -87,88 +84,19 @@ Dependency and API views are Python-native: loops. Callsite search uses parser-collected `PythonCall` facts, including dotted method calls such as `client.worker.process(...)`. -## Scope Options +## Policy Dependency API -The CLI mirrors the project runner's classification and inclusion options: - -```shell -asp-python --source-dir lib --test-dir checks --extra-path tools . -asp-python --no-tests . -``` - -`--source-dir`, `--test-dir`, and `--extra-path` can be repeated. Supplying one -or more source or test roots replaces the default classification names for -that run. The parser still scans the whole project root by default. -`--extra-path` adds an external project path or a single Python file relative -to the project root. `--no-tests` skips test parser discovery but keeps -tests-root layout policy active. - -## Policy Options - -Rule-level policy can be adjusted for one run: - -```shell -asp-python --disable-rule PY-MOD-R002 . -asp-python --block-rule PY-AGENT-POLICY-007 . -``` - -`--disable-rule` suppresses a stable rule id. `--block-rule` promotes a stable -rule id to blocking, which is useful when a project wants selected -`PY-AGENT-*` advice to fail CI without changing the default catalog severity. -Both options can be repeated. - -When these flags are omitted, the CLI reads `[tool.python-lang-project-harness]` -from the target project's `pyproject.toml`. CLI rule flags override the matching -rule-id fields for that run while preserving the rest of the project-local -config. - -## Output Modes - -Compact text is the default output for humans and repair-oriented agents: - -```shell -asp-python . -``` - -When configured-blocking findings exist, the first line is the first concrete -`[RULE] Severity:` finding rather than a run-summary header. Clean runs still -print the short `[ok]` summary. Advice-only runs start with `[advice]` and the -concrete advisory findings, without an `[ok]` preamble or issue-count line. -Project-scoped compact text renders locations relative to the project root; -use `--json` for structured original paths. - -Use `--json` when a tool needs the structured `PythonHarnessReport` payload: - -```shell -asp-python --json . -``` - -Use `--agent-snapshot` when an Agent needs capped parser facts, project -metadata, active policy findings, branch-first verification profile reminders, -and active verification tasks without clean-run counters: - -```shell -asp-python --agent-snapshot . -``` - -`--json` and `--agent-snapshot` are mutually exclusive. - -## Exit Codes - -- `0`: no configured-blocking findings -- `1`: configured-blocking findings exist -- `2`: CLI argument or project-root error - -`PY-AGENT-*` findings remain advisory by default. Advice is rendered in compact -text, but it does not change the exit code unless the caller promotes `Info` -severity through library APIs or promotes selected rule ids with -`--block-rule`. +Build/test owners call `assert_asp_python_clean` or mount the +pytest plugin. Configuration comes from +`[tool.asp-python]`; structured reports and Agent snapshots +are normal library return values. The provider CLI does not evaluate policy, +override rule severity, or rescan a project. ## Library Boundary `run_cli_from_env()` is the console-script entrypoint. `run_cli(...)` is exposed for tests and embeddings that want to provide explicit streams or a current -working directory. Both functions delegate to `run_python_project_harness()` and +working directory. Both functions delegate to `run_asp_python()` and the public renderers. :RELATIONS: diff --git a/docs/03_features/204_pytest.md b/docs/03_features/204_pytest.md index 46afd34..e0ec3b5 100644 --- a/docs/03_features/204_pytest.md +++ b/docs/03_features/204_pytest.md @@ -7,7 +7,7 @@ :LAST_SYNC: 2026-04-30 :END: -`python-lang-project-harness` is designed to be loaded by downstream Python +`asp-python` is designed to be loaded by downstream Python projects as a test/dev dependency. The pytest surface has two supported entry points: an auto-loaded pytest plugin and an explicit test helper. @@ -19,7 +19,7 @@ Add the package to the downstream test dependency group together with pytest: [dependency-groups] test = [ "pytest>=8", - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] @@ -30,7 +30,7 @@ The distribution exposes this plugin entry point: ```toml [project.entry-points.pytest11] -python_lang_project_harness = "python_lang_project_harness.pytest_plugin" +asp_python = "asp_python.pytest_plugin" ``` Pytest auto-loads the plugin when the package is installed, but the harness is @@ -39,15 +39,15 @@ as a normal library dependency while making the policy gate easy to opt into from pytest config. Project policy validates this wiring. If parser-owned `pyproject.toml` facts -show that a project depends on `python-lang-project-harness` for test/dev use, +show that a project depends on `asp-python` for test/dev use, the project must expose either `--python-project-harness` in pytest addopts or -an explicit `python_project_harness_test()` callable. This keeps the dependency +an explicit `asp_python_test()` callable. This keeps the dependency from becoming decorative metadata that CI can bypass. Project-local policy can live beside pytest config in `pyproject.toml`: ```toml -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] blocking_rule_ids = ["PY-AGENT-POLICY-007"] ``` @@ -55,8 +55,9 @@ blocking_rule_ids = ["PY-AGENT-POLICY-007"] Supported plugin options: - `--python-project-harness`: collect and run one harness item. -- `--python-project-harness-root PATH`: choose the project root; defaults to - pytest `rootdir`. +- `--python-project-harness-root PATH`: choose the project root. When omitted, + a single path-scoped pytest invocation uses the nearest real Python project + metadata; mixed or workspace-level invocations default to pytest `rootdir`. - `--python-project-harness-no-tests`: skip parsing test files while still evaluating tests-root layout. - `--python-project-harness-source-dir NAME`: add one source classification @@ -78,9 +79,9 @@ Supported plugin options: Projects that prefer a committed test file can mount the same runner directly: ```python -from python_lang_project_harness.pytest import python_project_harness_test +from asp_python.pytest import asp_python_test -test_python_project_harness_policy = python_project_harness_test() +test_asp_python_policy = asp_python_test() ``` The helper defaults to `Path(".")` and returns a pytest-collectable callable. @@ -88,11 +89,11 @@ Callers can pass the same project-resolution options used by the library runner: ```python from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import PythonHarnessConfig -from python_lang_project_harness.pytest import python_project_harness_test +from asp_python import AspPythonConfig +from asp_python.pytest import asp_python_test -test_python_project_harness_policy = python_project_harness_test( - config=PythonHarnessConfig( +test_asp_python_policy = asp_python_test( + config=AspPythonConfig( disabled_rule_ids=frozenset({"PY-MOD-R002"}), blocking_rule_ids=frozenset({"PY-AGENT-POLICY-007"}), ), diff --git a/docs/03_features/205_verification.md b/docs/03_features/205_verification.md index d1c7125..8bf5763 100644 --- a/docs/03_features/205_verification.md +++ b/docs/03_features/205_verification.md @@ -13,7 +13,7 @@ project facts to produce external obligations that an Agent skill can satisfy with receipts or complete waivers. ```python -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationProfileHint, PythonVerificationTaskKind, @@ -73,21 +73,21 @@ can patch the policy from the profile index without reparsing `pyproject.toml`. The verification policy supports profile hints, dependency signals, receipts, waivers, responsibility task-kind mappings, task contracts, skill bindings, and skill descriptors through `PythonVerificationPolicy` or -`[tool.python-lang-project-harness.verification]`. +`[tool.asp-python.verification]`. ```toml -[tool.python-lang-project-harness.verification] +[tool.asp-python.verification] profile_hints = [ { owner_path = "src/pkg/api.py", responsibilities = ["public_api"], task_kinds = ["security"], rationale = "authz-sensitive public API" }, ] -[tool.python-lang-project-harness.verification.task_contracts] +[tool.asp-python.verification.task_contracts] security = { phase = "before_release", summary = "security skill must report authz evidence", requirements = [{ label = "authz", detail = "tenant authorization result" }] } -[tool.python-lang-project-harness.verification.skill_bindings] +[tool.asp-python.verification.skill_bindings] security = { skill = "python-security-review", adapter = "bandit" } -[tool.python-lang-project-harness.verification.skill_descriptors] +[tool.asp-python.verification.skill_descriptors] python-security-review = { task_kind = "security", adapter = "bandit", summary = "run bandit plus tenant authz probes", requirements = [{ label = "bandit", detail = "bandit report artifact" }] } ``` diff --git a/provider/asp-provider-registration.json b/provider/asp-provider-registration.json index ff4bcc8..6e78b40 100644 --- a/provider/asp-provider-registration.json +++ b/provider/asp-provider-registration.json @@ -56,18 +56,18 @@ "operations": [ { "operation": "projection-batch", - "requestSchema": {"schemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-request.schema.json", "schemaVersion": "1"}, - "responseSchema": {"schemaId": "https://schemas.agent-semantic-protocols.dev/provider-language-projection-batch-response.schema.json", "schemaVersion": "1"} + "requestSchema": {"schemaId": "agent.semantic-protocols.provider-language-projection-batch-request", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "agent.semantic-protocols.provider-language-projection-batch-response", "schemaVersion": "1"} }, { "operation": "project-resolution", - "requestSchema": {"schemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-request.schema.json", "schemaVersion": "1"}, - "responseSchema": {"schemaId": "https://schemas.agent-semantic-protocols.dev/provider-project-resolution-response.schema.json", "schemaVersion": "1"} + "requestSchema": {"schemaId": "agent.semantic-protocols.provider-project-resolution-request", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "agent.semantic-protocols.provider-project-resolution-response", "schemaVersion": "1"} }, { "operation": "query", - "requestSchema": {"schemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-request.v1.schema.json", "schemaVersion": "1"}, - "responseSchema": {"schemaId": "https://agent-semantic-protocols.dev/schemas/provider-native-exact-response.v1.schema.json", "schemaVersion": "1"} + "requestSchema": {"schemaId": "agent.semantic-protocols.provider-native-exact-request", "schemaVersion": "1"}, + "responseSchema": {"schemaId": "agent.semantic-protocols.provider-native-exact-projection", "schemaVersion": "1"} } ] }, @@ -96,10 +96,13 @@ "concurrency": "shared-read", "streaming": false }, - "output": { - "schemaId": "agent.semantic-protocols.source-index-projection", - "mediaType": "application/json" - }, + "output": { + "schema": { + "schemaId": "agent.semantic-protocols.source-index-projection", + "schemaVersion": "1" + }, + "mediaType": "application/json" + }, "failureSchemaIds": [ "agent.semantic-protocols.route-failure" ], @@ -118,7 +121,7 @@ "schemaVersion": "1", "routeId": "python.search", "operation": "search", - "requestSchemaId": "agent.semantic-protocols.asp-client-search-request", + "requestSchema": {"schemaId": "agent.semantic-protocols.asp-client-search-request", "schemaVersion": "1"}, "authority": "asp-server", "target": { "languageId": "python", @@ -143,10 +146,13 @@ "concurrency": "shared-read", "streaming": false }, - "output": { - "schemaId": "agent.semantic-protocols.search-packet", - "mediaType": "application/json" - }, + "output": { + "schema": { + "schemaId": "agent.semantic-protocols.search-packet", + "schemaVersion": "1" + }, + "mediaType": "application/json" + }, "failureSchemaIds": [ "agent.semantic-protocols.route-failure" ], @@ -165,7 +171,7 @@ "schemaVersion": "1", "routeId": "python.query", "operation": "query", - "requestSchemaId": "agent.semantic-protocols.asp-client-exact-query-request", + "requestSchema": {"schemaId": "agent.semantic-protocols.asp-client-exact-query-request", "schemaVersion": "1"}, "authority": "asp-server", "target": { "languageId": "python", @@ -190,10 +196,13 @@ "concurrency": "shared-read", "streaming": false }, - "output": { - "schemaId": "agent.semantic-protocols.query-result", - "mediaType": "application/json" - }, + "output": { + "schema": { + "schemaId": "agent.semantic-protocols.query-result", + "schemaVersion": "1" + }, + "mediaType": "application/json" + }, "failureSchemaIds": [ "agent.semantic-protocols.route-failure" ], @@ -212,7 +221,7 @@ "schemaVersion": "1", "routeId": "python.search.owner", "operation": "search.owner", - "requestSchemaId": "agent.semantic-protocols.asp-client-owner-search-request", + "requestSchema": {"schemaId": "agent.semantic-protocols.asp-client-owner-search-request", "schemaVersion": "1"}, "authority": "asp-server", "target": { "languageId": "python", @@ -238,10 +247,13 @@ "concurrency": "shared-read", "streaming": false }, - "output": { - "schemaId": "agent.semantic-protocols.search-packet", - "mediaType": "application/json" - }, + "output": { + "schema": { + "schemaId": "agent.semantic-protocols.search-packet", + "schemaVersion": "1" + }, + "mediaType": "application/json" + }, "failureSchemaIds": [ "agent.semantic-protocols.route-failure" ], diff --git a/provider/asp-provider-workspace-install.json b/provider/asp-provider-workspace-install.json index 2eeba4d..508314e 100644 --- a/provider/asp-provider-workspace-install.json +++ b/provider/asp-provider-workspace-install.json @@ -9,7 +9,7 @@ "providerRegistration": "asp-provider-registration.json", "schemaBundleReceipt": "../schemas/.asp-schema-manager-receipt.json", "workspaceArtifact": { - "root": "languages/python-lang-project-harness/.venv", + "root": "languages/asp-python/.venv", "entrypoint": "bin/asp-python", "runtimeDependencies": [ { @@ -34,15 +34,15 @@ "--no-editable", "--no-cache", "--reinstall-package", - "python-lang-project-harness" + "asp-python" ], - "workingDirectory": "languages/python-lang-project-harness", + "workingDirectory": "languages/asp-python", "sourceSnapshotAnchors": [ - "languages/python-lang-project-harness/pyproject.toml", - "languages/python-lang-project-harness/uv.lock" + "languages/asp-python/pyproject.toml", + "languages/asp-python/uv.lock" ], "derivedPaths": [ - "languages/python-lang-project-harness/.venv" + "languages/asp-python/.venv" ], "env": {} } diff --git a/pyproject.toml b/pyproject.toml index 12b1e51..1befc02 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [project] -name = "python-lang-project-harness" +name = "asp-python" version = "0.1.0" -description = "Project-level Python language harness with compact diagnostics for agents" +description = "Python-native policy and semantic tooling with compact diagnostics for agents" readme = "README.md" requires-python = ">=3.12" import-names = [ "python_lang_parser", - "python_lang_project_harness", + "asp_python", ] dependencies = ["blake3>=1.0.8,<2"] @@ -16,10 +16,10 @@ pytest = [ ] [project.scripts] -asp-python = "python_lang_project_harness:run_cli_from_env" +asp-python = "asp_python:run_cli_from_env" [project.entry-points.pytest11] -python_lang_project_harness = "python_lang_project_harness.pytest_plugin" +asp_python = "asp_python.pytest_plugin" [dependency-groups] test = [ @@ -35,11 +35,11 @@ build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = [ "src/python_lang_parser", - "src/python_lang_project_harness", + "src/asp_python", ] [tool.hatch.build.targets.wheel.force-include] -"schemas/python-semantic-capabilities.v1.schema.json" = "python_lang_project_harness/schemas/python-semantic-capabilities.v1.schema.json" +"schemas/python-semantic-capabilities.v1.schema.json" = "asp_python/schemas/python-semantic-capabilities.v1.schema.json" [tool.uv] package = true @@ -47,26 +47,26 @@ package = true [tool.pytest.ini_options] pythonpath = ["src"] -[[tool.python-lang-project-harness.verification.profile_hints]] +[[tool.asp-python.verification.profile_hints]] owner_path = "pyproject.toml" responsibilities = ["pytest_gate"] verification_tasks_enabled = false rationale = "self pytest addopts and CI cover the harness gate contract" -[[tool.python-lang-project-harness.verification.profile_hints]] +[[tool.asp-python.verification.profile_hints]] owner_path = "src/python_lang_parser/__init__.py" responsibilities = ["public_api"] verification_tasks_enabled = false rationale = "self parser tests, CLI harness, and build cover the parser public facade" -[[tool.python-lang-project-harness.verification.profile_hints]] -owner_path = "src/python_lang_project_harness/__init__.py" +[[tool.asp-python.verification.profile_hints]] +owner_path = "src/asp_python/__init__.py" responsibilities = ["public_api", "cli"] verification_tasks_enabled = false rationale = "self public API tests, CLI tests, pytest gate, and build cover the harness facade" -[[tool.python-lang-project-harness.verification.profile_hints]] -owner_path = "src/python_lang_project_harness/pytest_plugin.py" +[[tool.asp-python.verification.profile_hints]] +owner_path = "src/asp_python/pytest_plugin.py" responsibilities = ["cli"] verification_tasks_enabled = false rationale = "self pytest addopts exercise the pytest plugin gate" diff --git a/schemas/.asp-schema-manager-membership.json b/schemas/.asp-schema-manager-membership.json new file mode 100644 index 0000000..c84c269 --- /dev/null +++ b/schemas/.asp-schema-manager-membership.json @@ -0,0 +1,407 @@ +{ + "languageId": "python", + "profileDigest": "blake3-256:89e99a6c866f73af8137c109ad0829deb90a705c90dbceb408d0dfb202b0309a", + "bundleDigest": "blake3-256:ceb5fb09195c300d5b5a9c882ed8ac6263fbe15c0762907d6d2ab648bf65663d", + "schemas": [ + { + "name": "asp-client-cancellation-probe-request.schema.json", + "digest": "blake3-256:b34c40da202136d5377f5584f095319a7d4877837cb7c57275bd8a1ab177bf92" + }, + { + "name": "asp-client-cancellation-probe-response.schema.json", + "digest": "blake3-256:ea85dd8bba3d5049893fb029e227c9837a55804274879e70f8c3c4ebeaac95d7" + }, + { + "name": "asp-client-conformance.schema.json", + "digest": "blake3-256:bc12a1065c633e47a848f42a89975206c0538e5b66b0b54085468481b44145c9" + }, + { + "name": "asp-client-dispatch-failure.schema.json", + "digest": "blake3-256:7fcefb16f740f471c32e91f233a82760b8e3aaf57b0dafcee08f11c3fa457f36" + }, + { + "name": "asp-client-exact-query-failure.schema.json", + "digest": "blake3-256:fc451bd39e772ed3a2ba24a2b45da38e161b9b61915f698b1afb1253a051d386" + }, + { + "name": "asp-client-exact-query-request.schema.json", + "digest": "blake3-256:addd74db755e221e488d7b3f1099fb15be025310fe657103fd476d4346f8abaa" + }, + { + "name": "asp-client-exact-query-response.schema.json", + "digest": "blake3-256:feb1d24eaba02eaf53c7e5e7ba4f11dea436ebee99380d221f796007fee4625c" + }, + { + "name": "asp-client-frame.schema.json", + "digest": "blake3-256:f16667311475fd65f4b849611e3df4ca2f7fe847a4730d6fa9d025753fb7cf7c" + }, + { + "name": "asp-client-graphs-timeline-request.v1.schema.json", + "digest": "blake3-256:dc856c6a65c352081670053d6347d7632d71cc7f90e9248b2553833f30b178df" + }, + { + "name": "asp-client-owner-search-request.schema.json", + "digest": "blake3-256:1c8f421a1bc84116f3a6f70593f15e9579b8a5e6a425d4051759001e7d1e6044" + }, + { + "name": "asp-client-owner-search-response.schema.json", + "digest": "blake3-256:9b4ba14fbc31663b8e908ade83a3070b89f9320c62fe0f47b364ec43959c35f5" + }, + { + "name": "asp-client-protocol-catalog.schema.json", + "digest": "blake3-256:e3b6411bd9e583608a41d01852b052ba1438948f485e4ec08750c7d6d454c64a" + }, + { + "name": "asp-client-query-readiness-failure.schema.json", + "digest": "blake3-256:e9119a4bdfb5674a56a28dbf0f53c99276f3a15c143cfd638a9437bc2facfd08" + }, + { + "name": "asp-client-schema-bundle-request.schema.json", + "digest": "blake3-256:b04fed590c0e023686f28cd77c045726c851abd960b5aa1f9734f45a4daacf1c" + }, + { + "name": "asp-client-schema-bundle-response.schema.json", + "digest": "blake3-256:9e42923bf92de5b10b74ec2412b1d855f46219e2153e14a4cf7560a5598675a4" + }, + { + "name": "asp-client-search-request.schema.json", + "digest": "blake3-256:f10cb97d3d72b3c081ed1f4a67dd3aef607a9906dee7ef86032aabc938ec17fa" + }, + { + "name": "asp-client-server-descriptor.schema.json", + "digest": "blake3-256:d4e432eacba544a5eb504c1e6d1161d9599a14cfb85d62d2d5da0e1d47f6cdde" + }, + { + "name": "asp-client-source-index-lookup-request.schema.json", + "digest": "blake3-256:2c4e52f1a8d8d39b8a42903ccd662c966166e28a20f9f7645b7a6d6d622a67c0" + }, + { + "name": "asp-client-work-counters.schema.json", + "digest": "blake3-256:aba81ca4e8f518bb515cb7b014b5c535cab3d1634dfd9ae6ccdf24d553ba3133" + }, + { + "name": "asp-client-workspace-generation-ensure-ready-request.schema.json", + "digest": "blake3-256:0b03a20586526b77754403fab6f74d421e2c3bbfb310d1bead22d179ea86d0a9" + }, + { + "name": "asp-client-workspace-source-mutation.schema.json", + "digest": "blake3-256:74b0baf814e96b3f2f47eefcc537ae53c5ac740315cdbc8c4f1079dcc2f484a8" + }, + { + "name": "asp-python-graphs-session.v1.schema.json", + "digest": "blake3-256:447589323236a46efb7772e68b67b72f0f23549477ebc35a8a6734c201e5c2f1" + }, + { + "name": "callable-skeleton.schema.json", + "digest": "blake3-256:4d999ee27a470459659765bff2ffd3e40c25d7422dfdda09e69733c344ff2d40" + }, + { + "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": "grpc-warm-latency-receipt.schema.json", + "digest": "blake3-256:b05cb70a17c9169e81fef1491fb6e6c972e9bab8843131af5637d52405424297" + }, + { + "name": "language-package-graph.schema.json", + "digest": "blake3-256:4c2a985b14d27fb574e989452f031c545df08312d7e656044743af851550fafd" + }, + { + "name": "language-schema-bundle-receipt.schema.json", + "digest": "blake3-256:11f17ec46eb769fc3d650236af1fc19ba02486bce478b57d2fdd62a0cab3512d" + }, + { + "name": "lexical-postings-work-reduction-receipt.schema.json", + "digest": "blake3-256:416d3bbbd584a7268632aa9852eae189501f923bd39c5af9ed5469d001c9fff5" + }, + { + "name": "owner-search-payload-reduction-receipt.schema.json", + "digest": "blake3-256:3a1aa0d332181ed7bb2dd1ebcddc306809439ffda7109bf1cfbe9318fe49b819" + }, + { + "name": "project-resolution.schema.json", + "digest": "blake3-256:bfa2f1966d9d05e0f0c05cdb635112513498dedb076edf760f0504db60a358bb" + }, + { + "name": "provider-definitions.v1.schema.json", + "digest": "blake3-256:0ff66bd35a2c1561424f0aa2b95df689cfcb6513059ea5d06245f7efb38c45ce" + }, + { + "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:350834ac053462f48b68d7fa66aabfe01a507b83dbe6fb5547b378fa5a5180c2" + }, + { + "name": "provider-runtime-contract-descriptor.schema.json", + "digest": "blake3-256:ab45c308f484d03a7c3f5106976a8621ced27c4cd5867542b27b85d1b9240bcb" + }, + { + "name": "provider-runtime-request-stream-ack.schema.json", + "digest": "blake3-256:5082d2dbfb7fb05e23c18aaf15534a3f61ba4ed1579cc9704793f02c6e47b3aa" + }, + { + "name": "provider-runtime-request-stream-frame.schema.json", + "digest": "blake3-256:958c8f4bbabfa06b7600be3067e9feb7c5257a97597bf8221a1f424302ba2336" + }, + { + "name": "provider-workspace-install.schema.json", + "digest": "blake3-256:a607ae5a7ea24b889a6bac9ee4624c03def801da7932b8c9818de176a66f856f" + }, + { + "name": "resident-search-result.v1.schema.json", + "digest": "blake3-256:66cb31e1a55f6b1ac60e898c2a0bd6a6c0f4aaa6e112625de3307c2e6e4ec3b0" + }, + { + "name": "resolved-source-scope.v1.schema.json", + "digest": "blake3-256:0dcc57d7b2f8ffc931901231a72c61f4dca1b22d8eb7ffeccb544e3aad9c5b01" + }, + { + "name": "rg-coverage-receipt.schema.json", + "digest": "blake3-256:9faa9e8e584171ff85907fbe6715fab6408e77c505e97cd484a286c17f2aa836" + }, + { + "name": "runtime-client-terminal.schema.json", + "digest": "blake3-256:bdf3e6b1bd11b201cc7802a364d678ad7a99a74cc40df2be90f76e2f6e050e70" + }, + { + "name": "runtime-provider-search-receipt.v1.schema.json", + "digest": "blake3-256:2e439b2ee83198710238aed803c9c37cd2cb06bc7a5e2c010486cdaa605df691" + }, + { + "name": "search-generation-change-set.v1.schema.json", + "digest": "blake3-256:2c367cda53c1d03672c268fe2576fff2d11b7cd349992bc0f2c96bea206a2d80" + }, + { + "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-resident-evaluation-request.v1.schema.json", + "digest": "blake3-256:b29592ceb94bfcb7c502891c6d125ea6924b955fe0206f53df444d48087ea9e6" + }, + { + "name": "semantic-graph-resident-evaluation-result.v1.schema.json", + "digest": "blake3-256:23e4d157d07f06ed33829d08d332adf7ee498b385b1789bb08c1b84cb5431fff" + }, + { + "name": "semantic-graph-turbo-artifact-events.v1.schema.json", + "digest": "blake3-256:2be62f0ff356bbeeb0eeca549018c47709c3e8cbb34c528066579c3ab397ab88" + }, + { + "name": "semantic-graph-turbo-definitions.v1.schema.json", + "digest": "blake3-256:ab404159bbec13e0b35ef35a8994009ef79fa1abd3025eefce3ddc70780c554e" + }, + { + "name": "semantic-graph-turbo-request.v1.schema.json", + "digest": "blake3-256:ef8593d37038e0eb77d163700c0079d48d29ae37b6ba727e4f49c8f13f42eef2" + }, + { + "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:dce7fd8ff010dcfc17a4f4384a7bca8f202c522400e5ed226b5b9e96cd768881" + }, + { + "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-schema-manager-receipt.json b/schemas/.asp-schema-manager-receipt.json index 3d58bec..8fe1a5d 100644 --- a/schemas/.asp-schema-manager-receipt.json +++ b/schemas/.asp-schema-manager-receipt.json @@ -1,341 +1,5 @@ { "schemaId": "agent.semantic-protocols.language-schema-bundle-receipt", "schemaVersion": "1", - "languageId": "python", - "profileDigest": "blake3-256:38866be447e1e414aed4bacdd8a4d9be33c453e585168a6955bd9582965d9685", - "bundleDigest": "blake3-256:eeb548457c5b19e837de9e5c1125c14f4ef763ed64b59315a2d95fe8be762af6", - "schemas": [ - { - "name": "asp-client-cancellation-probe-request.schema.json", - "digest": "blake3-256:b34c40da202136d5377f5584f095319a7d4877837cb7c57275bd8a1ab177bf92" - }, - { - "name": "asp-client-cancellation-probe-response.schema.json", - "digest": "blake3-256:ea85dd8bba3d5049893fb029e227c9837a55804274879e70f8c3c4ebeaac95d7" - }, - { - "name": "asp-client-conformance.schema.json", - "digest": "blake3-256:bc12a1065c633e47a848f42a89975206c0538e5b66b0b54085468481b44145c9" - }, - { - "name": "asp-client-exact-query-failure.schema.json", - "digest": "blake3-256:fc451bd39e772ed3a2ba24a2b45da38e161b9b61915f698b1afb1253a051d386" - }, - { - "name": "asp-client-exact-query-request.schema.json", - "digest": "blake3-256:e412c1f02d08d1632e7104487d6b08d60dde6736d59254c6c2f5f45b702d4aa4" - }, - { - "name": "asp-client-exact-query-response.schema.json", - "digest": "blake3-256:feb1d24eaba02eaf53c7e5e7ba4f11dea436ebee99380d221f796007fee4625c" - }, - { - "name": "asp-client-frame.schema.json", - "digest": "blake3-256:f16667311475fd65f4b849611e3df4ca2f7fe847a4730d6fa9d025753fb7cf7c" - }, - { - "name": "asp-client-owner-search-request.schema.json", - "digest": "blake3-256:1c8f421a1bc84116f3a6f70593f15e9579b8a5e6a425d4051759001e7d1e6044" - }, - { - "name": "asp-client-protocol-catalog.schema.json", - "digest": "blake3-256:8b3f99ee2cb424d3ccf994f4c77f37dd6a71078b463cb256dbb65aea02b2d443" - }, - { - "name": "asp-client-schema-bundle-request.schema.json", - "digest": "blake3-256:b04fed590c0e023686f28cd77c045726c851abd960b5aa1f9734f45a4daacf1c" - }, - { - "name": "asp-client-schema-bundle-response.schema.json", - "digest": "blake3-256:3b9b48d31828c7365420423d8d092f5dc5dd9cc1e9c9faf992145d1f785ea764" - }, - { - "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-work-counters.schema.json", - "digest": "blake3-256:aba81ca4e8f518bb515cb7b014b5c535cab3d1634dfd9ae6ccdf24d553ba3133" - }, - { - "name": "asp-client-workspace-source-mutation.schema.json", - "digest": "blake3-256:74b0baf814e96b3f2f47eefcc537ae53c5ac740315cdbc8c4f1079dcc2f484a8" - }, - { - "name": "asp-semantic-extension-envelope.schema.json", - "digest": "blake3-256:70690d0b93dab725daa845a12fb750dc8ce3039c7f1895b2660b160b3744fda9" - }, - { - "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:d69b7aa4dcaff194739c64d0847b3f7c97f9230808ec0319de34bf61666dd4bc" - }, - { - "name": "project-resolution.schema.json", - "digest": "blake3-256:bfa2f1966d9d05e0f0c05cdb635112513498dedb076edf760f0504db60a358bb" - }, - { - "name": "provider-definitions.v1.schema.json", - "digest": "blake3-256:a380166bfe786f23a33972c357d7a058d3b116f12bbc91ff18bd8ec55640b0da" - }, - { - "name": "provider-language-projection-batch-request.schema.json", - "digest": "blake3-256:9352612e33943a7953a327dcf691797cf4f177ffabe7c920d975f34b5d4f9722" - }, - { - "name": "provider-language-projection-batch-response.schema.json", - "digest": "blake3-256:5053370366b61b10844e084d9a0d622f93d7df7fa244798a69d74d5b592122ae" - }, - { - "name": "provider-method-argument-projection.v1.schema.json", - "digest": "blake3-256:4f6716b4dda5710f523cd06292a33dd91b9b6b171c022cd9ff04f3796d3765ed" - }, - { - "name": "provider-native-exact-request.v1.schema.json", - "digest": "blake3-256:57125f2235a6a8350d7f73d8bc2b5c7a89a8a793b9f9653201a7138c49b598c8" - }, - { - "name": "provider-native-exact-response.v1.schema.json", - "digest": "blake3-256:81a3c3149a15bdd3171e780e2786f7eac2be280345d50254cb620c6ef39aa25e" - }, - { - "name": "provider-project-resolution-request.schema.json", - "digest": "blake3-256:d72002bef3a99e162e3f338a38933250666f058450c68d29708086716ad513a8" - }, - { - "name": "provider-project-resolution-response.schema.json", - "digest": "blake3-256:bc489fdb7a8bf86c8460acb50a436f7a2c6233afa4cb1bd97d7acdf16d5b208c" - }, - { - "name": "provider-query-pack-descriptor.schema.json", - "digest": "blake3-256:78b4ad05a5b20488f4104bf598f6dfa5edfdf1d33eb10cf4f33a2d3307b519aa" - }, - { - "name": "provider-registration.schema.json", - "digest": "blake3-256:c21cf1d2d5b5403255f38a789e0d942b6817acf7b19f2b98363be15453de6ae7" - }, - { - "name": "provider-route.schema.json", - "digest": "blake3-256:218789334872f4f58da9b6dde26022760399ec3e5023f7f9c24f6014efff215d" - }, - { - "name": "provider-runtime-contract-descriptor.schema.json", - "digest": "blake3-256:0fd6c3e6b93f5cabd4c959e6dfdc5349aed279050438fc69108710a4b679c6a8" - }, - { - "name": "provider-runtime-request-stream-ack.schema.json", - "digest": "blake3-256:5082d2dbfb7fb05e23c18aaf15534a3f61ba4ed1579cc9704793f02c6e47b3aa" - }, - { - "name": "provider-runtime-request-stream-frame.schema.json", - "digest": "blake3-256:958c8f4bbabfa06b7600be3067e9feb7c5257a97597bf8221a1f424302ba2336" - }, - { - "name": "provider-workspace-install.schema.json", - "digest": "blake3-256:a607ae5a7ea24b889a6bac9ee4624c03def801da7932b8c9818de176a66f856f" - }, - { - "name": "resolved-source-scope.v1.schema.json", - "digest": "blake3-256:0dcc57d7b2f8ffc931901231a72c61f4dca1b22d8eb7ffeccb544e3aad9c5b01" - }, - { - "name": "semantic-assurance-case.v1.schema.json", - "digest": "blake3-256:f672ea13226296635f24c033bb6e238f6eae8ca1639d13d9f20c94fc9cb9ac3c" - }, - { - "name": "semantic-assurance-definitions.v1.schema.json", - "digest": "blake3-256:b8afa21d876917b25887799e41a7710f8dd1cbf22be26c515a91d5b97c91497e" - }, - { - "name": "semantic-ast-patch-definitions.v1.schema.json", - "digest": "blake3-256:5d462a785c510104a7f5ca25cfe2849453452798362cc23863164ab35cfab43e" - }, - { - "name": "semantic-ast-patch-receipt.v1.schema.json", - "digest": "blake3-256:234ddf5df9c58b68c7d4fdbd58e4815aeaf54018c13c6d352466568a73e6641d" - }, - { - "name": "semantic-ast-patch.v1.schema.json", - "digest": "blake3-256:d8fc49ecf5b6bc85b735cc22b7b15610eb00ab8a9e72b432bb0bb84349249701" - }, - { - "name": "semantic-behavior-snapshot.v1.schema.json", - "digest": "blake3-256:84354e29c9b8e7854df3361d29e65e7d914ac202bdc1a99d1e2a8072b7588dc2" - }, - { - "name": "semantic-codeql-evidence.v1.schema.json", - "digest": "blake3-256:33713f45901c55bf13286646c87135918f95e1534d522aaaa42d8e2fb3cf4cfc" - }, - { - "name": "semantic-content-compaction.v1.schema.json", - "digest": "blake3-256:8567552403687cfb06c97604862d754dcf044e168bbd9d66348a22fdce336ca0" - }, - { - "name": "semantic-definitions.v1.schema.json", - "digest": "blake3-256:da29ac203939d09c6b7e8e62e529ae820489817ec74e101baa8635a9eeb35df2" - }, - { - "name": "semantic-dependency-topology.v1.schema.json", - "digest": "blake3-256:b52c183a98032ea7634a956c50d1b9d3bf5ce5033635949a85d136a3feda6c05" - }, - { - "name": "semantic-determinism-readiness.v1.schema.json", - "digest": "blake3-256:775296e77bde56263c8568006fdec741292d441b855a1ede63c76624863d418d" - }, - { - "name": "semantic-dev-command-log.v1.schema.json", - "digest": "blake3-256:d42cd166c1e47a2769584f86efde7f89cab0bd24174a805552b623d39a23e6ee" - }, - { - "name": "semantic-evidence-graph.v1.schema.json", - "digest": "blake3-256:abc6ed9c3a39730d55f34d0f8ac17f9569cf191449570851c9c6905d1166c746" - }, - { - "name": "semantic-exact-selector-receipt.v1.schema.json", - "digest": "blake3-256:eabd5d76ef05a8ed48745ce6efe896a91224649fff64f469cb4b221f4e764cb9" - }, - { - "name": "semantic-fact-definitions.v1.schema.json", - "digest": "blake3-256:f49dbf083d84d1a44c8dcea72b8ed6160a36532380ce966da5110c24e44e30a5" - }, - { - "name": "semantic-fact-graph.v1.schema.json", - "digest": "blake3-256:5cb49f85016a9250b4926a9d2e685974aca681b1374b2890b0bad7720baf9dcb" - }, - { - "name": "semantic-fact-ontology.v1.schema.json", - "digest": "blake3-256:90481a76d65c4e088c221dca528a9c69e4b1f1bdcb4de9aa32dedb54f4adc017" - }, - { - "name": "semantic-flow-lite.v1.schema.json", - "digest": "blake3-256:2362a5be3afd923fe7eb9109214529b692c66c0ee97268a4c4f1866a9b407c41" - }, - { - "name": "semantic-formal-proof-pilot.v1.schema.json", - "digest": "blake3-256:f3d04cc56caa24b4a48f524810ccafe3e77838c96d600d78776108e3373202db" - }, - { - "name": "semantic-graph-turbo-definitions.v1.schema.json", - "digest": "blake3-256:ab404159bbec13e0b35ef35a8994009ef79fa1abd3025eefce3ddc70780c554e" - }, - { - "name": "semantic-graph-turbo-request.v1.schema.json", - "digest": "blake3-256:3869108c49bb06f42f1bb370b333ffca30cb8ece9a1b5d63cba92355aee9b39c" - }, - { - "name": "semantic-graph.v1.schema.json", - "digest": "blake3-256:0ecb8b9b830d2212dd6a6bf776f10f763e96e232cdfcbc2effc5f5189ea37a34" - }, - { - "name": "semantic-handle.v1.schema.json", - "digest": "blake3-256:385ae734595c8df436ad937b23bfe9ad8bb79af7bca650917a9a0ea6222f6529" - }, - { - "name": "semantic-invariant-candidate.v1.schema.json", - "digest": "blake3-256:dfbbb5dde44825022063dbc5435434d19b684f8a5e4c4f374a5ff035a1e29b43" - }, - { - "name": "semantic-language-projection.v1.schema.json", - "digest": "blake3-256:70113f49e94ac82bc3d2a1dfaec34da537516cf77a680aa05f1758cfbe57734a" - }, - { - "name": "semantic-language-registry.v1.schema.json", - "digest": "blake3-256:5d440b89b13de434b5fdc6144f0e41678bb5dd89569e28cccd13db73ff37fff7" - }, - { - "name": "semantic-native-syntax-fact-index.v1.schema.json", - "digest": "blake3-256:0f7f98c4281e7fb025b9aaf4f643f915aa5cc6f4cbbe093c66c5d9c89d0fe030" - }, - { - "name": "semantic-owner-item-evidence.v1.schema.json", - "digest": "blake3-256:5b68dd2fbb5042beb19874401a94b4371fbdacd3b1f239e8193c604c23ec580e" - }, - { - "name": "semantic-query-packet.v1.schema.json", - "digest": "blake3-256:fb181854af59044d1c25819087924118df835af466b6a869ccf36a2ccc46ed27" - }, - { - "name": "semantic-read-packet.v1.schema.json", - "digest": "blake3-256:c6141d808a7ce236cb25506fc122aedfae69379b856846ceb3b6ec3a4de19058" - }, - { - "name": "semantic-relation-plan.v1.schema.json", - "digest": "blake3-256:fa0ca667f89e068257fd82ca1e28d5d9c988b6a907c9d7c7a13eba922031c1ff" - }, - { - "name": "semantic-review-packet.v1.schema.json", - "digest": "blake3-256:5114705b02088801a5ca0e446c749dc0b515f8990fcc3e3a2284b9a6656defbd" - }, - { - "name": "semantic-search-packet.v1.schema.json", - "digest": "blake3-256:993623779091e9fa3e0689a591481cda8393c0f42d3b55dd58b53af620598d71" - }, - { - "name": "semantic-search-storage-route.v1.schema.json", - "digest": "blake3-256:cd1011b2097bfdc9b9f0a878ebadac50a7d227405ee637358a64d76763783fa0" - }, - { - "name": "semantic-source-location.v1.schema.json", - "digest": "blake3-256:a447fe4cfca0853f84c64496455b49fb8315e0abcaf841df0caecc47120a56d3" - }, - { - "name": "semantic-structural-index.v1.schema.json", - "digest": "blake3-256:9101c64b8621b7a748a47d86adcef03eedf1e786e3f3ddf47273343d545048a7" - }, - { - "name": "semantic-tree-sitter-grammar-profile.v1.schema.json", - "digest": "blake3-256:e88ed4a014b4b5d5e5c97a10bca3f5b723c7b23fec4aeae4f0e9b992c5700f77" - }, - { - "name": "semantic-tree-sitter-provenance.v1.schema.json", - "digest": "blake3-256:bae387d2ac500d1752bbfe01ebd33a49bd577fcd25b140837a4e633d21ca8a67" - }, - { - "name": "semantic-tree-sitter-query.v1.schema.json", - "digest": "blake3-256:5dd9530db1fcb34a89e4c33086128dc1df62879529126fae8dac242a1ef298b5" - }, - { - "name": "semantic-type-surface.v1.schema.json", - "digest": "blake3-256:4ac7fb4a0a1fb1230cdb66d0b674049d79d180da19883d001a8c2e4fc55d30dc" - }, - { - "name": "semantic-verification-receipt.v1.schema.json", - "digest": "blake3-256:4cc1c1dab5c0c4f3e544817e6dc05f66a86304fe315bd711e0d3feca45d4d41c" - }, - { - "name": "software-criterion-catalog.v1.schema.json", - "digest": "blake3-256:a9bcc4f2cd83ff3f26f7a8bc31e7f3032f8dc5679649ace6c6aec913345bc478" - }, - { - "name": "source-snapshot-evidence.v1.schema.json", - "digest": "blake3-256:b22f980c1c70e1927670be25e8d86c4d3b78538e81e6aad28b608ca9707f99cb" - } - ] + "schemaDigest": "blake3-256:ceb5fb09195c300d5b5a9c882ed8ac6263fbe15c0762907d6d2ab648bf65663d" } \ No newline at end of file diff --git a/schemas/asp-client-dispatch-failure.schema.json b/schemas/asp-client-dispatch-failure.schema.json new file mode 100644 index 0000000..71509be --- /dev/null +++ b/schemas/asp-client-dispatch-failure.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-dispatch-failure.schema.json", + "title": "ASP Client Dispatch Failure", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "state", "phase", "reasonKind", "budgetMs" + ], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.asp-client-dispatch-failure" }, + "schemaVersion": { "const": "1" }, + "state": { "const": "failed" }, + "phase": { "const": "runtime-client-dispatch" }, + "reasonKind": { "const": "client-request-deadline-exceeded" }, + "budgetMs": { "type": "integer", "minimum": 1 } + } +} diff --git a/schemas/asp-client-exact-query-request.schema.json b/schemas/asp-client-exact-query-request.schema.json index 4aca5a4..36b433a 100644 --- a/schemas/asp-client-exact-query-request.schema.json +++ b/schemas/asp-client-exact-query-request.schema.json @@ -10,7 +10,11 @@ "const": "agent.semantic-protocols.asp-client-exact-query-request" }, "schemaVersion": { "const": "1" }, - "selector": { "type": "string", "minLength": 1 }, + "selector": { + "type": "string", + "minLength": 1, + "description": "A canonical item selector or normalized workspace-relative owner path. ASP Server owns parsing and resolution against the admitted CompleteGeneration." + }, "projection": { "enum": ["source", "callable-skeleton"] } } } diff --git a/schemas/asp-client-graphs-timeline-request.v1.schema.json b/schemas/asp-client-graphs-timeline-request.v1.schema.json new file mode 100644 index 0000000..cec1d3d --- /dev/null +++ b/schemas/asp-client-graphs-timeline-request.v1.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/asp-client-graphs-timeline-request.v1.schema.json", + "title": "ASP Client Graphs Timeline Request v1", + "description": "Server-owned history/timeline request. Graph-Turbo is the algorithm identity; asp-python-graphs is the service owner.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "eventPacket", "arguments"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-graphs-timeline-request"}, + "schemaVersion": {"const": "1"}, + "eventPacket": {"$ref": "semantic-graph-turbo-artifact-events.v1.schema.json"}, + "arguments": {"type": "array", "items": {"type": "string"}} + } +} diff --git a/schemas/asp-client-owner-search-response.schema.json b/schemas/asp-client-owner-search-response.schema.json new file mode 100644 index 0000000..ffdc039 --- /dev/null +++ b/schemas/asp-client-owner-search-response.schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-owner-search-response.schema.json", + "title": "ASP Client Owner Search Response", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "state", + "generationDigest", + "rootDigest", + "ownerPath", + "query", + "view", + "candidateCount", + "returnedCount", + "selectors" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-owner-search-response" + }, + "schemaVersion": { "const": "1" }, + "state": { "enum": ["owner", "owner-missing"] }, + "generationDigest": { "type": "string", "minLength": 1 }, + "rootDigest": { "type": "string", "minLength": 1 }, + "ownerPath": { "type": "string", "minLength": 1 }, + "contentDigest": { "type": "string", "minLength": 1 }, + "query": { "type": "string" }, + "view": { "const": "seeds" }, + "candidateCount": { "type": "integer", "minimum": 0 }, + "returnedCount": { "type": "integer", "minimum": 0, "maximum": 100 }, + "selectors": { + "type": "array", + "maxItems": 100, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["selector", "byteStart", "byteEnd"], + "properties": { + "selector": { "type": "string", "minLength": 1 }, + "byteStart": { "type": "integer", "minimum": 0 }, + "byteEnd": { "type": "integer", "minimum": 0 } + } + } + } + }, + "allOf": [ + { + "if": { "properties": { "state": { "const": "owner" } } }, + "then": { "required": ["contentDigest"] }, + "else": { + "not": { "required": ["contentDigest"] } + } + } + ] +} diff --git a/schemas/asp-client-protocol-catalog.schema.json b/schemas/asp-client-protocol-catalog.schema.json index ebd628f..e861268 100644 --- a/schemas/asp-client-protocol-catalog.schema.json +++ b/schemas/asp-client-protocol-catalog.schema.json @@ -26,7 +26,7 @@ "type": "array", "minItems": 1, "uniqueItems": true, - "items": { "enum": ["http-json", "runtime-ipc"] } + "items": { "const": "runtime-ipc" } }, "capabilities": { "type": "object", @@ -47,15 +47,24 @@ "$defs": { "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" }, "schemaIdentifier": { "type": "string", "minLength": 1 }, + "schemaReference": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion"], + "properties": { + "schemaId": { "$ref": "#/$defs/schemaIdentifier" }, + "schemaVersion": { "const": "1" } + } + }, "method": { "type": "object", "additionalProperties": false, "required": [ "method", "routeId", - "requestSchemaId", - "responseSchemaId", - "errorSchemaIds", + "requestSchema", + "responseSchema", + "errorSchemas", "parameters", "cancellable", "streaming" @@ -63,11 +72,11 @@ "properties": { "method": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$" }, "routeId": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)+$" }, - "requestSchemaId": { "$ref": "#/$defs/schemaIdentifier" }, - "responseSchemaId": { "$ref": "#/$defs/schemaIdentifier" }, - "errorSchemaIds": { + "requestSchema": { "$ref": "#/$defs/schemaReference" }, + "responseSchema": { "$ref": "#/$defs/schemaReference" }, + "errorSchemas": { "type": "array", - "items": { "$ref": "#/$defs/schemaIdentifier" } + "items": { "$ref": "#/$defs/schemaReference" } }, "parameters": { "type": "array", diff --git a/schemas/asp-client-query-readiness-failure.schema.json b/schemas/asp-client-query-readiness-failure.schema.json new file mode 100644 index 0000000..01d856a --- /dev/null +++ b/schemas/asp-client-query-readiness-failure.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-query-readiness-failure.schema.json", + "title": "ASP Client Query Readiness Failure", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "state", "phase", "reasonKind", + "workspaceIdentity", "languageId", "providerId", "generationState", + "publicationError", "recommendedNext", "elapsedMicros", "workCounters" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-query-readiness-failure" + }, + "schemaVersion": { "const": "1" }, + "state": { "const": "failed" }, + "phase": { "const": "runtime-generation-authority" }, + "reasonKind": { "const": "query-not-ready" }, + "workspaceIdentity": { "type": "string", "minLength": 1 }, + "languageId": { "type": "string", "minLength": 1 }, + "providerId": { "type": "string", "minLength": 1 }, + "generationState": { "enum": ["unpublished", "failed"] }, + "publicationError": { "type": ["string", "null"] }, + "recommendedNext": { "type": "object" }, + "elapsedMicros": { "type": "integer", "minimum": 0 }, + "workCounters": { "$ref": "asp-client-work-counters.schema.json" } + } +} diff --git a/schemas/asp-client-schema-bundle-response.schema.json b/schemas/asp-client-schema-bundle-response.schema.json index 092e193..4619e80 100644 --- a/schemas/asp-client-schema-bundle-response.schema.json +++ b/schemas/asp-client-schema-bundle-response.schema.json @@ -34,8 +34,7 @@ "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }, - "bundleDigest": { "$ref": "#/$defs/digest" }, - "extensions": { "type": "array", "items": { "$ref": "#/$defs/extension" } } + "bundleDigest": { "$ref": "#/$defs/digest" } } }, "entries": { @@ -43,19 +42,6 @@ "minItems": 1, "items": { "$ref": "#/$defs/entry" } }, - "extension": { - "type": "object", "additionalProperties": false, - "required": ["providerId", "extensionSchemaId", "extensionSchemaVersion", "extensionSchemaDigest", "capabilityDigest", "workspaceIdentity", "generationDigest"], - "properties": { - "providerId": {"type": "string", "minLength": 1}, - "extensionSchemaId": {"type": "string", "minLength": 1}, - "extensionSchemaVersion": {"const": "1"}, - "extensionSchemaDigest": {"$ref": "#/$defs/digest"}, - "capabilityDigest": {"$ref": "#/$defs/digest"}, - "workspaceIdentity": {"type": "string", "minLength": 1}, - "generationDigest": {"$ref": "#/$defs/digest"} - } - }, "document": { "type": "object", "additionalProperties": false, diff --git a/schemas/asp-client-source-index-lookup-request.schema.json b/schemas/asp-client-source-index-lookup-request.schema.json new file mode 100644 index 0000000..9e0d3a2 --- /dev/null +++ b/schemas/asp-client-source-index-lookup-request.schema.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-source-index-lookup-request.schema.json", + "title": "ASP Client Source Index Lookup Request", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "query", "indexRoot", "limit"], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.asp-client-source-index-lookup-request" + }, + "schemaVersion": { "const": "1" }, + "query": { "type": "string", "minLength": 1 }, + "indexRoot": { "type": "string", "minLength": 1 }, + "limit": { "type": "integer", "minimum": 1, "maximum": 100 } + } +} diff --git a/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json b/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json new file mode 100644 index 0000000..e9b6d34 --- /dev/null +++ b/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json", + "title": "ASP Client Workspace Generation Ensure Ready Request", + "type": "object", + "additionalProperties": false, + "required": [], + "properties": {} +} diff --git a/schemas/asp-python-graphs-session.v1.schema.json b/schemas/asp-python-graphs-session.v1.schema.json new file mode 100644 index 0000000..76cd477 --- /dev/null +++ b/schemas/asp-python-graphs-session.v1.schema.json @@ -0,0 +1,90 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.github.io/agent-semantic-protocols/schemas/asp-python-graphs-session.v1.schema.json", + "title": "ASP Python Graphs Session Envelope v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "sessionId", + "serviceEpoch", + "requestId", + "sequence", + "messageKind", + "payloadSchemaId", + "payload" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-python-graphs-session"}, + "schemaVersion": {"const": "1"}, + "sessionId": {"type": "string", "minLength": 1}, + "serviceEpoch": {"type": "string", "minLength": 1}, + "requestId": {"type": "string", "minLength": 1}, + "clientRequestId": {"type": "string", "minLength": 1}, + "sequence": {"type": "integer", "minimum": 1}, + "messageKind": { + "enum": [ + "hello", + "open-generation", + "evaluate", + "search-evidence", + "timeline", + "release-generation", + "cancel", + "health", + "shutdown", + "receipt" + ] + }, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"$ref": "#/$defs/blake3Digest"}, + "generationToken": {"type": "integer", "minimum": 1}, + "runtimeArtifactDigest": {"$ref": "#/$defs/blake3Digest"}, + "executionArtifactDigest": {"$ref": "#/$defs/blake3Digest"}, + "deadlineUnixMillis": {"type": "integer", "minimum": 0}, + "cancellationId": {"type": "string", "minLength": 1}, + "payloadSchemaId": {"type": "string", "minLength": 1}, + "payload": {"type": "object"} + }, + "allOf": [ + { + "if": { + "properties": { + "messageKind": { + "enum": ["open-generation", "evaluate", "search-evidence", "release-generation"] + } + } + }, + "then": { + "required": ["workspaceIdentity", "generationDigest", "generationToken"] + } + }, + { + "if": {"properties": {"messageKind": {"const": "hello"}}}, + "then": { + "required": ["runtimeArtifactDigest", "executionArtifactDigest"] + } + }, + { + "if": {"properties": {"messageKind": {"const": "cancel"}}}, + "then": {"required": ["cancellationId"]} + }, + { + "if": {"properties": {"messageKind": {"const": "search-evidence"}}}, + "then": { + "properties": { + "payloadSchemaId": { + "const": "agent.semantic-protocols.asp-python-graphs-search-evidence" + } + } + } + } + ], + "$defs": { + "blake3Digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/asp-semantic-extension-envelope.schema.json b/schemas/asp-semantic-extension-envelope.schema.json deleted file mode 100644 index 32e7f96..0000000 --- a/schemas/asp-semantic-extension-envelope.schema.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://schemas.agent-semantic-protocols.dev/asp-semantic-extension-envelope.schema.json", - "title": "ASP Semantic Extension Envelope", - "type": "object", - "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "providerId", "extensionSchemaId", "extensionSchemaVersion", "extensionSchemaDigest", "capabilityDigest", "workspaceIdentity", "generationDigest", "payload"], - "properties": { - "schemaId": {"const": "agent.semantic-protocols.asp-semantic-extension-envelope"}, - "schemaVersion": {"const": "1"}, - "providerId": {"type": "string", "minLength": 1}, - "extensionSchemaId": {"type": "string", "pattern": "^https://schemas\\.agent-semantic-protocols\\.dev/.+\\.schema\\.json$"}, - "extensionSchemaVersion": {"const": "1"}, - "extensionSchemaDigest": {"$ref": "#/$defs/digest"}, - "capabilityDigest": {"$ref": "#/$defs/digest"}, - "workspaceIdentity": {"type": "string", "minLength": 1}, - "generationDigest": {"$ref": "#/$defs/digest"}, - "payload": {} - }, - "$defs": {"digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}} -} diff --git a/schemas/callable-skeleton.schema.json b/schemas/callable-skeleton.schema.json index e0791c6..e6d8592 100644 --- a/schemas/callable-skeleton.schema.json +++ b/schemas/callable-skeleton.schema.json @@ -4,10 +4,8 @@ "title": "Callable Skeleton Payload", "type": "object", "additionalProperties": false, - "required": ["rootSelector", "rootNodeId", "callable", "nodes", "relations", "cost"], + "required": ["rootNodeId", "callable", "nodes", "relations", "cost"], "properties": { - "projectionKind": {"const": "callable-skeleton"}, - "rootSelector": {}, "rootNodeId": {"type": "string", "minLength": 1}, "callable": {"type": "object"}, "nodes": {"type": "array"}, diff --git a/schemas/grpc-warm-latency-receipt.schema.json b/schemas/grpc-warm-latency-receipt.schema.json new file mode 100644 index 0000000..5beeb1f --- /dev/null +++ b/schemas/grpc-warm-latency-receipt.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/grpc-warm-latency-receipt.schema.json", + "title": "ASP Client gRPC Warm Latency Receipt", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "state", + "thresholdNanos", + "sequentialCount", + "sequentialTerminalCount", + "sequentialP95Nanos", + "concurrentCount", + "concurrentTerminalCount", + "concurrentP95Nanos" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.grpc-warm-latency-receipt" + }, + "schemaVersion": { "const": "1" }, + "state": { "enum": ["passed", "failed"] }, + "thresholdNanos": { "const": 1000000 }, + "sequentialCount": { "const": 10 }, + "sequentialTerminalCount": { "const": 10 }, + "sequentialP95Nanos": { "type": "integer", "minimum": 0 }, + "concurrentCount": { "const": 32 }, + "concurrentTerminalCount": { "const": 32 }, + "concurrentP95Nanos": { "type": "integer", "minimum": 0 } + }, + "allOf": [ + { + "if": { "properties": { "state": { "const": "passed" } } }, + "then": { + "properties": { + "sequentialP95Nanos": { "exclusiveMaximum": 1000000 }, + "concurrentP95Nanos": { "exclusiveMaximum": 1000000 } + } + } + } + ] +} diff --git a/schemas/language-schema-bundle-receipt.schema.json b/schemas/language-schema-bundle-receipt.schema.json index 7ff32ff..6622f07 100644 --- a/schemas/language-schema-bundle-receipt.schema.json +++ b/schemas/language-schema-bundle-receipt.schema.json @@ -4,69 +4,13 @@ "title": "ASP Language Schema Bundle Receipt", "type": "object", "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "languageId", - "profileDigest", - "bundleDigest", - "schemas", - "distribution" - ], + "required": ["schemaId", "schemaVersion", "schemaDigest"], "properties": { "schemaId": { "const": "agent.semantic-protocols.language-schema-bundle-receipt" }, "schemaVersion": { "const": "1" }, - "languageId": { "type": "string", "minLength": 1 }, - "profileDigest": { "$ref": "#/$defs/digest" }, - "bundleDigest": { "$ref": "#/$defs/digest" }, - "schemas": { - "type": "array", - "minItems": 1, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["name", "digest"], - "properties": { - "name": { "type": "string", "pattern": "^[^/]+\\.schema\\.json$" }, - "digest": { "$ref": "#/$defs/digest" } - } - } - }, - "distribution": { - "type": "object", - "additionalProperties": false, - "required": ["canonicalSchemaDigest", "contractDigest", "targetLanguage", "generator", "sourceSchemas", "outputs", "syncStatus", "acceptanceReceipt"], - "properties": { - "canonicalSchemaDigest": { "$ref": "#/$defs/digest" }, - "contractDigest": { "$ref": "#/$defs/digest" }, - "targetLanguage": { "type": "string", "minLength": 1 }, - "generator": { - "type": "object", "additionalProperties": false, "required": ["identity", "version"], - "properties": { "identity": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } - }, - "sourceSchemas": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/digestEntry" } }, - "outputs": { - "type": "array", "minItems": 1, - "items": { - "type": "object", "additionalProperties": false, - "required": ["schemaId", "digest", "role"], - "properties": { - "schemaId": { "type": "string", "minLength": 1 }, - "digest": { "$ref": "#/$defs/digest" }, - "role": { "const": "derived-copy" } - } - } - }, - "syncStatus": { "enum": ["synced", "drifted", "rejected"] }, - "acceptanceReceipt": { "type": "string", "minLength": 1 } - } - } + "schemaDigest": { "$ref": "#/$defs/digest" } }, "$defs": { - "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" }, - "digestEntry": { - "type": "object", "additionalProperties": false, "required": ["schemaId", "digest"], - "properties": { "schemaId": { "type": "string", "minLength": 1 }, "digest": { "$ref": "#/$defs/digest" } } - } + "digest": { "type": "string", "pattern": "^(blake3-256|sha256):[0-9a-f]{64}$" } } } diff --git a/schemas/lexical-postings-work-reduction-receipt.schema.json b/schemas/lexical-postings-work-reduction-receipt.schema.json new file mode 100644 index 0000000..8838321 --- /dev/null +++ b/schemas/lexical-postings-work-reduction-receipt.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/lexical-postings-work-reduction-receipt.schema.json", + "title": "ASP Lexical Postings Work Reduction Receipt", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "fullScanCandidates", "indexedCandidates", "reductionFactor"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.lexical-postings-work-reduction-receipt" }, + "schemaVersion": { "const": "1" }, + "fullScanCandidates": { "type": "integer", "minimum": 1 }, + "indexedCandidates": { "type": "integer", "minimum": 1 }, + "reductionFactor": { "type": "integer", "minimum": 1000 } + } +} diff --git a/schemas/owner-search-payload-reduction-receipt.schema.json b/schemas/owner-search-payload-reduction-receipt.schema.json new file mode 100644 index 0000000..e441665 --- /dev/null +++ b/schemas/owner-search-payload-reduction-receipt.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/owner-search-payload-reduction-receipt.schema.json", + "title": "ASP Owner Search Payload Reduction Receipt", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "fullBytes", + "compactBytes", + "reductionFactor" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.owner-search-payload-reduction-receipt" + }, + "schemaVersion": { "const": "1" }, + "fullBytes": { "type": "integer", "minimum": 1 }, + "compactBytes": { "type": "integer", "minimum": 1 }, + "reductionFactor": { "type": "integer", "minimum": 100 } + } +} diff --git a/schemas/provider-definitions.v1.schema.json b/schemas/provider-definitions.v1.schema.json index 5dffd90..ed75690 100644 --- a/schemas/provider-definitions.v1.schema.json +++ b/schemas/provider-definitions.v1.schema.json @@ -4,6 +4,15 @@ "title": "Provider Family Definitions v1", "description": "Shared definitions owned by the provider schema family.", "$defs": { + "schemaReference": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion"], + "properties": { + "schemaId": { "type": "string", "minLength": 1 }, + "schemaVersion": { "const": "1" } + } + }, "providerReference": { "type": "object", "additionalProperties": false, diff --git a/schemas/provider-manifest.schema.json b/schemas/provider-manifest.schema.json index 6fb51d3..23571ac 100644 --- a/schemas/provider-manifest.schema.json +++ b/schemas/provider-manifest.schema.json @@ -408,7 +408,7 @@ }, "methodId": { "type": "string", - "pattern": "^(?:guide|query|(search|query|check|proof|review|verification|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" + "pattern": "^(?:guide|query|(search|query|proof|review|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" }, "hookRouteBindings": { "type": "object", diff --git a/schemas/provider-route.schema.json b/schemas/provider-route.schema.json index 33d3a4a..3674a8c 100644 --- a/schemas/provider-route.schema.json +++ b/schemas/provider-route.schema.json @@ -38,8 +38,8 @@ "target": { "$ref": "#/$defs/target" }, - "requestSchemaId": { - "$ref": "#/$defs/schemaId" + "requestSchema": { + "$ref": "#/$defs/schemaReference" }, "inputs": { "type": "array", @@ -90,6 +90,15 @@ "minLength": 1, "pattern": "^[A-Za-z][A-Za-z0-9._:/-]*$" }, + "schemaReference": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion"], + "properties": { + "schemaId": { "$ref": "#/$defs/schemaId" }, + "schemaVersion": { "const": "1" } + } + }, "target": { "type": "object", "additionalProperties": false, @@ -196,10 +205,10 @@ "output": { "type": "object", "additionalProperties": false, - "required": ["schemaId", "mediaType"], + "required": ["schema", "mediaType"], "properties": { - "schemaId": { - "$ref": "#/$defs/schemaId" + "schema": { + "$ref": "#/$defs/schemaReference" }, "mediaType": { "const": "application/json" diff --git a/schemas/provider-runtime-contract-descriptor.schema.json b/schemas/provider-runtime-contract-descriptor.schema.json index 5b2ebe3..465cced 100644 --- a/schemas/provider-runtime-contract-descriptor.schema.json +++ b/schemas/provider-runtime-contract-descriptor.schema.json @@ -18,14 +18,14 @@ "items": { "type": "object", "additionalProperties": false, - "required": ["operation", "requestSchemaId", "responseSchemaId"], + "required": ["operation", "requestSchema", "responseSchema"], "properties": { "operation": { "type": "string", "pattern": "^[a-z][a-z0-9-]*(\\.[a-z][a-z0-9-]*)*$" }, - "requestSchemaId": { "type": "string", "minLength": 1 }, - "responseSchemaId": { "type": "string", "minLength": 1 } + "requestSchema": { "$ref": "provider-definitions.v1.schema.json#/$defs/schemaReference" }, + "responseSchema": { "$ref": "provider-definitions.v1.schema.json#/$defs/schemaReference" } } } } diff --git a/schemas/resident-search-result.v1.schema.json b/schemas/resident-search-result.v1.schema.json new file mode 100644 index 0000000..9aff9f7 --- /dev/null +++ b/schemas/resident-search-result.v1.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/resident-search-result.v1.schema.json", + "title": "Resident Search Result V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "state", + "generationDigest", + "rootDigest", + "providerDigest", + "indexArtifactDigest", + "hits", + "workCounters" + ], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.resident-search-result" }, + "schemaVersion": { "const": "1" }, + "state": { "const": "Ready" }, + "generationDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "rootDigest": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "providerDigest": { "type": "string", "minLength": 1 }, + "indexArtifactDigest": { "type": "string", "minLength": 1 }, + "hits": { + "type": "array", + "items": { "$ref": "#/$defs/hit" } + }, + "workCounters": { "$ref": "runtime-provider-search-receipt.v1.schema.json#/$defs/zeroWorkCounters" } + }, + "$defs": { + "hit": { + "type": "object", + "additionalProperties": false, + "required": [ + "ownerPath", + "ownerContentDigest", + "projectionTier", + "lineCount", + "queryKeys" + ], + "properties": { + "ownerPath": { "type": "string", "minLength": 1 }, + "ownerContentDigest": { "type": "string", "minLength": 1 }, + "languageId": { "type": "string", "minLength": 1 }, + "projectionTier": { + "enum": ["shallow-navigation", "owner-local-dynamic"] + }, + "lineCount": { "type": "integer", "minimum": 1 }, + "queryKeys": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "selector": { "type": "string", "minLength": 1 }, + "score": { "type": "integer", "minimum": 0 } + } + } + } +} diff --git a/schemas/rg-coverage-receipt.schema.json b/schemas/rg-coverage-receipt.schema.json new file mode 100644 index 0000000..47a9633 --- /dev/null +++ b/schemas/rg-coverage-receipt.schema.json @@ -0,0 +1,32 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/rg-coverage-receipt.schema.json", + "title": "ASP Explicit Ripgrep Coverage Receipt", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "state", "generationDigest", "sourceRootDigest", "providerDigest", "indexArtifactDigest", "coverageInputDigest", "committedOwnerCount", "inputBytes", "recordCount", "candidateCount"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.rg-coverage-receipt" }, + "schemaVersion": { "const": "1" }, + "state": { "enum": ["ready", "failed"] }, + "reasonKind": { "enum": ["input-budget-exceeded", "record-budget-exceeded", "candidate-budget-exceeded", "owner-set-invalid", "owner-not-admitted", "owner-content-mismatch", "record-content-mismatch"] }, + "generationDigest": { "type": "string", "minLength": 1 }, + "sourceRootDigest": { "type": "string", "minLength": 1 }, + "providerDigest": { "type": "string", "minLength": 1 }, + "indexArtifactDigest": { "type": "string", "minLength": 1 }, + "coverageInputDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$", + "description": "Digest of generation identity, committed owner identities, and the bounded executor output. It is evidence, not a cache key authority." + }, + "committedOwnerCount": { "type": "integer", "minimum": 1 }, + "inputBytes": { "type": "integer", "minimum": 0 }, + "recordCount": { "type": "integer", "minimum": 0 }, + "candidateCount": { "type": "integer", "minimum": 0 } + }, + "allOf": [{ + "if": { "properties": { "state": { "const": "failed" } } }, + "then": { "required": ["reasonKind"] }, + "else": { "not": { "required": ["reasonKind"] } } + }] +} diff --git a/schemas/runtime-client-terminal.schema.json b/schemas/runtime-client-terminal.schema.json new file mode 100644 index 0000000..94ba3f5 --- /dev/null +++ b/schemas/runtime-client-terminal.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-client-terminal.schema.json", + "title": "ASP Runtime Client Boundary Terminal", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "state", "reasonKind", "retryAdmitted", "argv", "cause"], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.runtime-client-terminal" }, + "schemaVersion": { "const": "1" }, + "state": { "const": "failed" }, + "reasonKind": { + "enum": [ + "runtime-client-connect-deadline-exceeded", + "runtime-client-session-deadline-exceeded", + "runtime-client-response-deadline-exceeded" + ] + }, + "retryAdmitted": { "const": false }, + "argv": { "type": "array", "minItems": 1, "items": { "type": "string" } }, + "cause": { "type": "string", "minLength": 1 } + } +} diff --git a/schemas/runtime-provider-search-receipt.v1.schema.json b/schemas/runtime-provider-search-receipt.v1.schema.json new file mode 100644 index 0000000..65e18ed --- /dev/null +++ b/schemas/runtime-provider-search-receipt.v1.schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-provider-search-receipt.v1.schema.json", + "title": "Runtime Provider Search Receipt v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "operationId", + "status", + "languageId", + "generationDigest", + "rootDigest", + "providerDigest", + "indexArtifactDigest", + "candidateCount", + "selectorProjectionBudget", + "projectedOwnerCount", + "selectors", + "ownerPaths", + "residentReadElapsedMicros", + "serviceElapsedMicros", + "elapsedMicros", + "workCounters" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.runtime-provider-search-receipt" + }, + "schemaVersion": { "const": "1" }, + "operationId": { "type": "string", "minLength": 1 }, + "status": { "enum": ["matches", "no-matches"] }, + "languageId": { "type": "string", "minLength": 1 }, + "generationDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "rootDigest": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "providerDigest": { "type": "string", "minLength": 1 }, + "indexArtifactDigest": { "type": "string", "minLength": 1 }, + "candidateCount": { "type": "integer", "minimum": 0 }, + "selectorProjectionBudget": { "type": "integer", "minimum": 1 }, + "projectedOwnerCount": { "type": "integer", "minimum": 0, "maximum": 16 }, + "selectors": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "ownerPaths": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string", "minLength": 1 } + }, + "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, + "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, + "elapsedMicros": { "type": "integer", "minimum": 0 }, + "workCounters": { "$ref": "#/$defs/zeroWorkCounters" } + }, + "$defs": { + "zeroWorkCounters": { + "type": "object", + "additionalProperties": false, + "required": [ + "databaseReadCount", + "filesystemReadCount", + "providerProcessCount", + "socketOperationCount", + "schedulerTaskCount" + ], + "properties": { + "databaseReadCount": { "const": 0 }, + "filesystemReadCount": { "const": 0 }, + "providerProcessCount": { "const": 0 }, + "socketOperationCount": { "const": 0 }, + "schedulerTaskCount": { "const": 0 } + } + } + } +} diff --git a/schemas/search-generation-change-set.v1.schema.json b/schemas/search-generation-change-set.v1.schema.json new file mode 100644 index 0000000..4da6c49 --- /dev/null +++ b/schemas/search-generation-change-set.v1.schema.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/search-generation-change-set.v1.schema.json", + "title": "ASP Search Generation Merkle Change Set v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "workspaceIdentity", "baseRootDigest", + "candidateRootDigest", "providerDigest", "schemaDigest", "analyzerDigest", + "changeSetDigest", "changes" + ], + "properties": { + "schemaId": { "const": "agent.semantic-protocols.search-generation-change-set" }, + "schemaVersion": { "const": "1" }, + "workspaceIdentity": { "type": "string", "minLength": 1 }, + "baseRootDigest": { "type": ["string", "null"] }, + "candidateRootDigest": { "type": "string", "minLength": 1 }, + "providerDigest": { "type": "string", "minLength": 1 }, + "schemaDigest": { "type": "string", "minLength": 1 }, + "analyzerDigest": { "type": "string", "minLength": 1 }, + "changeSetDigest": { "type": "string", "minLength": 1 }, + "changes": { + "type": "array", + "items": { "$ref": "#/$defs/change" } + } + }, + "$defs": { + "change": { + "type": "object", + "additionalProperties": false, + "required": ["ownerPath", "kind"], + "properties": { + "ownerPath": { "type": "string", "minLength": 1 }, + "kind": { "enum": ["added", "changed", "removed"] }, + "previousContentDigest": { "type": "string", "minLength": 1 }, + "contentDigest": { "type": "string", "minLength": 1 }, + "lineCount": { "type": "integer", "minimum": 1 }, + "lexicalQueryKeys": { + "type": "array", + "items": { "type": "string", "minLength": 1 }, + "uniqueItems": true + }, + "graphFragmentDigest": { "type": "string", "minLength": 1 } + }, + "allOf": [ + { + "if": { "properties": { "kind": { "const": "added" } } }, + "then": { + "required": [ + "contentDigest", "lineCount", "lexicalQueryKeys", "graphFragmentDigest" + ], + "not": { "required": ["previousContentDigest"] } + } + }, + { + "if": { "properties": { "kind": { "const": "changed" } } }, + "then": { + "required": [ + "previousContentDigest", "contentDigest", "lineCount", + "lexicalQueryKeys", "graphFragmentDigest" + ] + } + }, + { + "if": { "properties": { "kind": { "const": "removed" } } }, + "then": { + "required": ["previousContentDigest"], + "not": { "required": ["contentDigest"] } + } + } + ] + } + } +} diff --git a/schemas/semantic-graph-resident-evaluation-request.v1.schema.json b/schemas/semantic-graph-resident-evaluation-request.v1.schema.json new file mode 100644 index 0000000..5b0d9b3 --- /dev/null +++ b/schemas/semantic-graph-resident-evaluation-request.v1.schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/semantic-graph-resident-evaluation-request.v1.schema.json", + "title": "Resident graph evaluation request V1", + "type": "object", + "required": [ + "schemaId", "schemaVersion", "protocolId", "protocolVersion", + "packetKind", "languageId", "surface", "queryTerms", "profile", + "seedIds", "budget" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.semantic-graph-resident-evaluation-request"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.search"}, + "protocolVersion": {"const": "1"}, + "packetKind": {"const": "resident-graph-evaluation-request"}, + "languageId": {"type": "string", "pattern": "^[a-z][a-z0-9-]*$"}, + "surface": { + "enum": ["search-pipe", "search-rg", "search-lexical", "search-owner", "query"] + }, + "queryTerms": { + "type": "array", "maxItems": 32, "uniqueItems": true, + "items": {"type": "string", "minLength": 1, "maxLength": 256} + }, + "profile": {"enum": ["balanced", "structural", "dependency"]}, + "seedIds": { + "type": "array", "maxItems": 128, "uniqueItems": true, + "items": {"type": "string", "minLength": 1, "maxLength": 1024} + }, + "budget": { + "type": "object", + "required": ["maxDepth", "maxNodes", "maxEdges", "maxResults"], + "properties": { + "maxDepth": {"type": "integer", "minimum": 0, "maximum": 16}, + "maxNodes": {"type": "integer", "minimum": 1, "maximum": 256}, + "maxEdges": {"type": "integer", "minimum": 0, "maximum": 1024}, + "maxResults": {"type": "integer", "minimum": 1, "maximum": 100} + }, + "additionalProperties": false + } + }, + "anyOf": [ + {"properties": {"queryTerms": {"minItems": 1}}}, + {"properties": {"seedIds": {"minItems": 1}}} + ], + "additionalProperties": false +} diff --git a/schemas/semantic-graph-resident-evaluation-result.v1.schema.json b/schemas/semantic-graph-resident-evaluation-result.v1.schema.json new file mode 100644 index 0000000..cd5cad4 --- /dev/null +++ b/schemas/semantic-graph-resident-evaluation-result.v1.schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/semantic-graph-resident-evaluation-result.v1.schema.json", + "title": "Resident graph evaluation result V1", + "type": "object", + "required": [ + "schemaId", "schemaVersion", "protocolId", "protocolVersion", + "packetKind", "state", "surface", "workspaceIdentity", + "generationDigest", "rootDigest", "profile", "seedIds", + "rankedNodes", "edges", "workCounters" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.semantic-graph-resident-evaluation-result"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.search"}, + "protocolVersion": {"const": "1"}, + "packetKind": {"const": "resident-graph-evaluation-result"}, + "state": {"const": "Ready"}, + "surface": { + "enum": ["search-pipe", "search-rg", "search-lexical", "search-owner", "query"] + }, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "rootDigest": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "profile": {"enum": ["balanced", "structural", "dependency"]}, + "seedIds": { + "type": "array", "maxItems": 128, "uniqueItems": true, + "items": {"type": "string", "minLength": 1} + }, + "rankedNodes": { + "type": "array", "maxItems": 100, + "items": {"$ref": "#/$defs/rankedNode"} + }, + "edges": { + "type": "array", "maxItems": 1024, + "items": {"$ref": "#/$defs/edge"} + }, + "workCounters": {"$ref": "#/$defs/workCounters"} + }, + "$defs": { + "rankedNode": { + "type": "object", + "required": ["id", "kind", "score", "distance"], + "properties": { + "id": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "ownerPath": {"type": ["string", "null"]}, + "score": {"type": "integer", "minimum": 0}, + "distance": {"type": "integer", "minimum": 0, "maximum": 16} + }, + "additionalProperties": false + }, + "edge": { + "type": "object", + "required": ["source", "target", "relation"], + "properties": { + "source": {"type": "string", "minLength": 1}, + "target": {"type": "string", "minLength": 1}, + "relation": {"type": "string", "minLength": 1} + }, + "additionalProperties": false + }, + "workCounters": { + "type": "object", + "required": [ + "graphNodesVisited", "graphEdgesVisited", "providerRpcCount", + "durableReadCount", "generationMutationCount" + ], + "properties": { + "graphNodesVisited": {"type": "integer", "minimum": 0, "maximum": 256}, + "graphEdgesVisited": {"type": "integer", "minimum": 0, "maximum": 1024}, + "providerRpcCount": {"const": 0}, + "durableReadCount": {"const": 0}, + "generationMutationCount": {"const": 0} + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/schemas/semantic-graph-turbo-artifact-events.v1.schema.json b/schemas/semantic-graph-turbo-artifact-events.v1.schema.json new file mode 100644 index 0000000..fba61fd --- /dev/null +++ b/schemas/semantic-graph-turbo-artifact-events.v1.schema.json @@ -0,0 +1,118 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.local/schemas/semantic-graph-turbo-artifact-events.v1.schema.json", + "title": "Semantic Graph Turbo Artifact Events", + "description": "Schema-owned event stream consumed by graph-turbo timeline audits. Rust DB Engine indexes can emit this packet so timeline analysis does not need to rescan artifact directories.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "artifactDir", + "source", + "events" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.graph-turbo-artifact-events" + }, + "schemaVersion": { + "const": "1" + }, + "artifactDir": { + "type": "string", + "minLength": 1 + }, + "source": { + "$ref": "#/$defs/source" + }, + "events": { + "type": "array", + "items": { + "$ref": "#/$defs/event" + } + } + }, + "$defs": { + "source": { + "type": "object", + "additionalProperties": false, + "required": [ + "kind", + "clientDir" + ], + "properties": { + "kind": { + "enum": [ + "artifact-scan", + "db-engine" + ] + }, + "clientDir": { + "type": "string" + } + } + }, + "event": { + "type": "object", + "additionalProperties": false, + "required": [ + "timestamp", + "kind", + "language", + "method", + "target", + "query", + "projectRoot", + "projectRootArg", + "path", + "bytes" + ], + "properties": { + "timestamp": { + "type": "number", + "minimum": 0 + }, + "kind": { + "enum": [ + "command", + "prompt-output", + "query", + "search", + "search-output", + "analysis-metadata", + "tree-sitter-query" + ] + }, + "language": { + "type": "string", + "minLength": 1 + }, + "method": { + "type": "string", + "minLength": 1 + }, + "target": { + "type": "string" + }, + "query": { + "type": "string" + }, + "projectRoot": { + "type": "string" + }, + "projectRootArg": { + "type": "string" + }, + "path": { + "type": "string", + "minLength": 1 + }, + "bytes": { + "type": "integer", + "minimum": 0 + } + } + } + } +} diff --git a/schemas/semantic-graph-turbo-request.v1.schema.json b/schemas/semantic-graph-turbo-request.v1.schema.json index 049b297..ee2ebb2 100644 --- a/schemas/semantic-graph-turbo-request.v1.schema.json +++ b/schemas/semantic-graph-turbo-request.v1.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://agent-semantic-protocols.local/schemas/semantic-graph-turbo-request.v1.schema.json", "title": "Semantic Graph Turbo Request", - "description": "Schema-owned algorithm request packet consumed by asp-graph-turbo before it emits a semantic-graph-turbo-result response.", + "description": "Schema-owned graph-turbo algorithm request packet consumed by the asp-python-graphs service before it emits a semantic-graph-turbo-result response.", "type": "object", "additionalProperties": false, "required": [ diff --git a/schemas/semantic-language-registry.v1.schema.json b/schemas/semantic-language-registry.v1.schema.json index 899905d..8d5412a 100644 --- a/schemas/semantic-language-registry.v1.schema.json +++ b/schemas/semantic-language-registry.v1.schema.json @@ -44,17 +44,15 @@ }, "method": { "type": "string", - "pattern": "^(?:guide|query|(search|query|check|proof|review|verification|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" + "pattern": "^(?:guide|query|(search|query|proof|review|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" }, "command": { "enum": [ "guide", "search", "query", - "check", "proof", "review", - "verification", "evidence", "ast-patch", "agent" diff --git a/src/python_lang_project_harness/__init__.py b/src/asp_python/__init__.py similarity index 93% rename from src/python_lang_project_harness/__init__.py rename to src/asp_python/__init__.py index 8ff5716..4d1a0bd 100644 --- a/src/python_lang_project_harness/__init__.py +++ b/src/asp_python/__init__.py @@ -5,7 +5,7 @@ from importlib import import_module from typing import Any -DISTRIBUTION_NAME = "python-lang-project-harness" +DISTRIBUTION_NAME = "asp-python" _CLI_EXPORTS = frozenset({"run_cli", "run_cli_from_env"}) _HARNESS_RULES_EXPORTS = frozenset( @@ -83,10 +83,10 @@ "PythonExportContract", "PythonExportContractKind", "PythonFunctionControlFlow", - "PythonHarnessConfig", - "PythonHarnessFinding", - "PythonHarnessReport", - "PythonHarnessRule", + "AspPythonConfig", + "AspPythonFinding", + "AspPythonReport", + "AspPythonRule", "PythonImport", "PythonLangRulePack", "PythonModernDesignRulePack", @@ -143,7 +143,7 @@ "SourceLocation", "__version__", "assert_python_lang_harness_clean", - "assert_python_project_harness_clean", + "assert_asp_python_clean", "build_python_semantic_search_packet", "default_python_harness_config", "default_python_lang_rule_packs", @@ -179,21 +179,21 @@ "python_harness_rules_markdown", "python_modern_design_rules", "python_modularity_rules", - "python_project_harness_paths", - "python_project_harness_scope", - "python_project_harness_test", + "asp_python_paths", + "asp_python_scope", + "asp_python_test", "python_project_policy_rules", "python_rule_pack_descriptors", "python_semantic_language_registration", "python_syntax_rules", "python_test_layout_rules", - "read_python_project_harness_config", + "read_asp_python_config", "render_python_lang_harness", "render_python_lang_harness_advice", "render_python_lang_harness_json", "render_python_harness_rules_markdown", - "render_python_project_harness_agent_snapshot", - "render_python_project_harness_agent_snapshot_with_config", + "render_asp_python_agent_snapshot", + "render_asp_python_agent_snapshot_with_config", "render_python_reasoning_tree", "render_python_semantic_search_packet", "render_python_semantic_search_packet_json", @@ -209,7 +209,7 @@ "run_cli", "run_cli_from_env", "run_python_lang_harness", - "run_python_project_harness", + "run_asp_python", "semantic_language_registry_document", "write_python_harness_rules_to_unit_tests", "write_python_verification_reports", diff --git a/src/python_lang_project_harness/__main__.py b/src/asp_python/__main__.py similarity index 61% rename from src/python_lang_project_harness/__main__.py rename to src/asp_python/__main__.py index 53d01ac..68530d4 100644 --- a/src/python_lang_project_harness/__main__.py +++ b/src/asp_python/__main__.py @@ -1,4 +1,4 @@ -"""Module entrypoint for `python -m python_lang_project_harness`.""" +"""Module entrypoint for `python -m asp_python`.""" from __future__ import annotations diff --git a/src/python_lang_project_harness/_agent_namespace.py b/src/asp_python/_agent_namespace.py similarity index 93% rename from src/python_lang_project_harness/_agent_namespace.py rename to src/asp_python/_agent_namespace.py index f128402..0dec5bd 100644 --- a/src/python_lang_project_harness/_agent_namespace.py +++ b/src/asp_python/_agent_namespace.py @@ -19,7 +19,7 @@ PY_AGENT_R006, agent_policy_rule, ) -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._source import path_location if TYPE_CHECKING: @@ -64,10 +64,10 @@ def agent_namespace_findings( scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return compact project-wide namespace findings for agent repair.""" - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] namespace_items = collect_agent_namespace_items(modules) for spec in _NAMESPACE_CONFLICT_SPECS: findings.extend(_duplicate_namespace_findings(namespace_items, spec, pack_id)) @@ -79,10 +79,10 @@ def _duplicate_namespace_findings( namespace_items: tuple[AgentNamespaceItem, ...], spec: _NamespaceConflictSpec, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: first_seen: dict[str, AgentNamespaceItem] = {} emitted: set[tuple[str, str]] = set() - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(spec.rule_id) for item in namespace_items: if item.surface != spec.surface: @@ -96,7 +96,7 @@ def _duplicate_namespace_findings( continue emitted.add(key) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -119,9 +119,9 @@ def _repeated_namespace_segment_findings( scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: emitted_branches: set[tuple[str, tuple[str, ...]]] = set() - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R004) for report in modules: if not report.path: @@ -140,7 +140,7 @@ def _repeated_namespace_segment_findings( continue emitted_branches.add(key) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_agent_namespace_index.py b/src/asp_python/_agent_namespace_index.py similarity index 100% rename from src/python_lang_project_harness/_agent_namespace_index.py rename to src/asp_python/_agent_namespace_index.py diff --git a/src/python_lang_project_harness/_agent_policy.py b/src/asp_python/_agent_policy.py similarity index 91% rename from src/python_lang_project_harness/_agent_policy.py rename to src/asp_python/_agent_policy.py index 977b20b..d629da0 100644 --- a/src/python_lang_project_harness/_agent_policy.py +++ b/src/asp_python/_agent_policy.py @@ -19,7 +19,7 @@ agent_policy_rule, ) from ._agent_reasoning_tree import agent_reasoning_tree_findings -from ._model import PythonHarnessFinding, PythonRulePackDescriptor +from ._model import AspPythonFinding, PythonRulePackDescriptor from ._source import path_location from .agent_readability import ( agent_algorithm_shape_findings, @@ -52,13 +52,13 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="advisory", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate agent-oriented policy rules for one parsed module report.""" if not report.is_valid: return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(_module_docstring_findings(report, self.pack_id)) findings.extend(_public_callable_annotation_findings(report, self.pack_id)) findings.extend(agent_algorithm_shape_findings(report, self.pack_id)) @@ -71,7 +71,7 @@ def evaluate_project_modules( self, scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate agent-oriented namespace rules across a project scope.""" return ( @@ -83,13 +83,13 @@ def evaluate_project_modules( def _module_docstring_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if report.module_docstring or not _module_has_agent_surface(report): return () rule = agent_policy_rule(PY_AGENT_R001) path = Path(report.path or "") return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -107,14 +107,14 @@ def _module_docstring_findings( def _public_callable_annotation_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R002) for symbol in report.symbols: if not _is_public_callable(symbol) or symbol.has_annotations: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_agent_policy_catalog.py b/src/asp_python/_agent_policy_catalog.py similarity index 93% rename from src/python_lang_project_harness/_agent_policy_catalog.py rename to src/asp_python/_agent_policy_catalog.py index ec4a4f8..97ddfb2 100644 --- a/src/python_lang_project_harness/_agent_policy_catalog.py +++ b/src/asp_python/_agent_policy_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule AGENT_POLICY_PACK_ID = "python.agent_policy" PY_AGENT_R001 = "PY-AGENT-POLICY-001" @@ -27,7 +27,7 @@ "domain": "agent-policy", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R001, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -35,7 +35,7 @@ requirement="Add a concise module docstring that names the module responsibility for agent search and repair.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R002, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -43,7 +43,7 @@ requirement="Annotate public function and method boundaries so agents can reason from native syntax without guessing shapes.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R003, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -51,7 +51,7 @@ requirement="Give project-level public callables unambiguous names or move them behind a clear domain namespace so agents can resolve intent without guessing.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R004, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -59,7 +59,7 @@ requirement="Keep Python module namespaces branch-unique; rename repeated path segments so agents see one clear ownership path.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R005, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -67,7 +67,7 @@ requirement="Give project-level public classes unambiguous type names or move them behind a clear domain namespace so agents can resolve intent without guessing.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R006, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -75,7 +75,7 @@ requirement="Give project-level public values and configuration exports unambiguous names or move them behind a clear domain namespace so agents can resolve intent without guessing.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R007, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -83,7 +83,7 @@ requirement="Add a concise package docstring to branch `__init__.py` files so agents can choose the right owner subtree before editing.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R008, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -91,7 +91,7 @@ requirement="Split crowded branch packages into focused subpackages, or document the facade and owner map so agents do not treat one folder as one responsibility.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R009, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -99,7 +99,7 @@ requirement="Flatten deeply nested if/loop logic into guard clauses, explicit dispatch, match/case, or small named pipeline steps so agents can reason about the algorithm shape.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R010, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -107,7 +107,7 @@ requirement="Split long public functions with broad linear statement blocks into small named helpers or pipeline steps so agents can edit one algorithm responsibility at a time.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R011, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -115,7 +115,7 @@ requirement="Use comprehensions, generator expressions, built-ins such as sum/any/all, collections.Counter/defaultdict, or named iterator pipeline helpers when a loop only maps, filters, counts, groups, accumulates, or answers a predicate.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_AGENT_R012, pack_id=AGENT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, @@ -127,13 +127,13 @@ _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_agent_policy_rules() -> tuple[PythonHarnessRule, ...]: +def python_agent_policy_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for the default agent-oriented policy rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def agent_policy_rule(rule_id: str) -> PythonHarnessRule: +def agent_policy_rule(rule_id: str) -> AspPythonRule: """Return one agent-policy rule descriptor by stable rule id.""" return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_agent_reasoning_tree.py b/src/asp_python/_agent_reasoning_tree.py similarity index 96% rename from src/python_lang_project_harness/_agent_reasoning_tree.py rename to src/asp_python/_agent_reasoning_tree.py index 3109a29..f33373d 100644 --- a/src/python_lang_project_harness/_agent_reasoning_tree.py +++ b/src/asp_python/_agent_reasoning_tree.py @@ -8,7 +8,7 @@ from python_lang_parser import python_reasoning_tree_facts from ._agent_policy_catalog import PY_AGENT_R007, PY_AGENT_R008, agent_policy_rule -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._source import path_location if TYPE_CHECKING: @@ -32,7 +32,7 @@ def agent_reasoning_tree_findings( scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return agent advice for package-tree navigation gaps.""" facts = python_reasoning_tree_facts( @@ -46,7 +46,7 @@ def agent_reasoning_tree_findings( } intent_rule = agent_policy_rule(PY_AGENT_R007) broad_rule = agent_policy_rule(PY_AGENT_R008) - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for branch in facts.branches: if branch.has_intent_doc: findings.extend( @@ -62,7 +62,7 @@ def agent_reasoning_tree_findings( module = modules_by_path.get(branch.path) namespace = ".".join(branch.namespace) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=intent_rule.rule_id, pack_id=pack_id, severity=intent_rule.severity, @@ -98,7 +98,7 @@ def _broad_branch_findings( rule_id: str, pack_id: str, modules_by_path: dict[str, PythonModuleReport], -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: child_nodes = _branch_child_nodes(branch, facts) public_child_count = sum(node.has_public_surface for node in child_nodes) effective_lines = sum(node.effective_code_lines for node in child_nodes) @@ -114,7 +114,7 @@ def _broad_branch_findings( rule = agent_policy_rule(rule_id) namespace = ".".join(branch.namespace) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_agent_snapshot.py b/src/asp_python/_agent_snapshot.py similarity index 78% rename from src/python_lang_project_harness/_agent_snapshot.py rename to src/asp_python/_agent_snapshot.py index ce92c0e..fd87ec1 100644 --- a/src/python_lang_project_harness/_agent_snapshot.py +++ b/src/asp_python/_agent_snapshot.py @@ -10,7 +10,7 @@ render_python_lang_harness, ) from ._rule_packs import resolve_project_harness_config -from ._runner import run_python_project_harness +from ._runner import run_asp_python from .verification import ( build_python_verification_profile_index_report, plan_python_project_verification_report, @@ -19,37 +19,37 @@ ) if TYPE_CHECKING: - from ._model import PythonHarnessConfig, PythonHarnessReport + from ._model import AspPythonConfig, AspPythonReport -def render_python_project_harness_agent_snapshot(project_root: str | Path) -> str: +def render_asp_python_agent_snapshot(project_root: str | Path) -> str: """Render a compact parser-backed project snapshot for repair agents.""" - return render_python_project_harness_agent_snapshot_with_config( + return render_asp_python_agent_snapshot_with_config( project_root, None, ) -def render_python_project_harness_agent_snapshot_with_config( +def render_asp_python_agent_snapshot_with_config( project_root: str | Path, - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, ) -> str: """Render an agent snapshot using an explicit harness config.""" root = Path(project_root) selected_config = resolve_project_harness_config(root, config, rule_packs=None) - report = run_python_project_harness(root, config=selected_config) - return render_python_project_harness_agent_snapshot_report( + report = run_asp_python(root, config=selected_config) + return render_asp_python_agent_snapshot_report( report, config=selected_config, ) -def render_python_project_harness_agent_snapshot_report( - report: PythonHarnessReport, +def render_asp_python_agent_snapshot_report( + report: AspPythonReport, *, - config: PythonHarnessConfig | None = None, + config: AspPythonConfig | None = None, ) -> str: """Render an already-built project harness report as an agent snapshot.""" @@ -84,7 +84,7 @@ def render_python_project_harness_agent_snapshot_report( return "\n".join(sections) + "\n" -def _render_policy_section(report: PythonHarnessReport) -> str: +def _render_policy_section(report: AspPythonReport) -> str: rendered = render_python_lang_harness(report) if rendered.startswith("[ok]"): return "" diff --git a/src/python_lang_project_harness/_agent_snapshot_tree.py b/src/asp_python/_agent_snapshot_tree.py similarity index 98% rename from src/python_lang_project_harness/_agent_snapshot_tree.py rename to src/asp_python/_agent_snapshot_tree.py index b1040fa..1bc3adb 100644 --- a/src/python_lang_project_harness/_agent_snapshot_tree.py +++ b/src/asp_python/_agent_snapshot_tree.py @@ -19,7 +19,7 @@ PythonReasoningTreeShadow, ) - from ._model import PythonHarnessReport + from ._model import AspPythonReport @dataclass(frozen=True, slots=True) @@ -44,7 +44,7 @@ class _SourceTreeFacts: def render_python_agent_snapshot_tree( - report: PythonHarnessReport, + report: AspPythonReport, *, target: str, ) -> str: @@ -55,7 +55,7 @@ def render_python_agent_snapshot_tree( @dataclass(frozen=True, slots=True) class _SnapshotTreeRenderer: - report: PythonHarnessReport + report: AspPythonReport target: str limits: _SnapshotTreeLimits @@ -141,7 +141,7 @@ def metadata_lines(self, metadata: PythonProjectMetadata) -> list[str]: self.add_metadata_package_roots(lines, metadata) self.add_metadata_scripts(lines, metadata) self.add_metadata_entry_points(lines, metadata) - if metadata.pytest_options.enables_python_project_harness: + if metadata.pytest_options.enables_asp_python: lines.append("- pytest=python-project-harness") return lines diff --git a/src/python_lang_project_harness/_callable_skeleton_projection.py b/src/asp_python/_callable_skeleton_projection.py similarity index 100% rename from src/python_lang_project_harness/_callable_skeleton_projection.py rename to src/asp_python/_callable_skeleton_projection.py diff --git a/src/asp_python/_cli.py b/src/asp_python/_cli.py new file mode 100644 index 0000000..7039e87 --- /dev/null +++ b/src/asp_python/_cli.py @@ -0,0 +1,62 @@ +"""Command-line execution for the Python project harness.""" + +from __future__ import annotations + +import sys +from pathlib import Path +from typing import TextIO + +from ._cli_args import ProtocolArgs, help_text +from ._cli_protocol import run_protocol_cli + + +def run_cli_from_env() -> int: + """Run the CLI using process environment arguments.""" + + from ._dev_command_log import start_dev_command_log + + args = sys.argv[1:] + log = start_dev_command_log(args, Path.cwd()) + try: + if args == ["serve"]: + from ._runtime import serve_provider_runtime + + exit_code = serve_provider_runtime(Path.cwd()) + log.finish(exit_code) + return exit_code + stdin = "" if sys.stdin.isatty() else sys.stdin.read() + exit_code = run_cli(args, stdin=stdin) + log.finish(exit_code) + return exit_code + except Exception: + log.finish(2) + raise + + +def run_cli( + args: list[str] | tuple[str, ...], + *, + stdout: TextIO | None = None, + stderr: TextIO | None = None, + stdin: str | bytes | None = None, + cwd: Path | None = None, +) -> int: + """Run the default package-level Python harness CLI.""" + + selected_stdout = sys.stdout if stdout is None else stdout + selected_stderr = sys.stderr if stderr is None else stderr + selected_cwd = Path.cwd() if cwd is None else cwd + if not args or args[0] in {"--help", "-h"}: + selected_stdout.write(help_text()) + return 0 + protocol_args = ProtocolArgs.parse(args) + if protocol_args is not None: + return run_protocol_cli( + protocol_args, + stdout=selected_stdout, + stderr=selected_stderr, + stdin="" if stdin is None else stdin, + cwd=selected_cwd, + ) + selected_stderr.write(f"unknown command: {args[0]}\n") + return 2 diff --git a/src/python_lang_project_harness/_cli_agent.py b/src/asp_python/_cli_agent.py similarity index 99% rename from src/python_lang_project_harness/_cli_agent.py rename to src/asp_python/_cli_agent.py index a90e199..a813423 100644 --- a/src/python_lang_project_harness/_cli_agent.py +++ b/src/asp_python/_cli_agent.py @@ -76,7 +76,7 @@ def render_agent_guide(project_root: Path) -> str: f"|cmd pattern=asp python search pattern [term ...] {workspace} --view seeds", f"|cmd compare=asp python search compare [left right] {workspace} --view seeds", f"|pipe | asp python search ingest {root} --view seeds", - "|cmd check=asp python check --changed", + "|policy authority=asp-python-api trigger=pytest-plugin", "|rule agent hook install/runtime is owned by asp", ( "|rule selector queries do not need a trailing project root; " diff --git a/src/python_lang_project_harness/_cli_args.py b/src/asp_python/_cli_args.py similarity index 63% rename from src/python_lang_project_harness/_cli_args.py rename to src/asp_python/_cli_args.py index 4f42a93..2b7bd56 100644 --- a/src/python_lang_project_harness/_cli_args.py +++ b/src/asp_python/_cli_args.py @@ -2,13 +2,10 @@ from __future__ import annotations -from dataclasses import dataclass, field, replace +from dataclasses import dataclass from pathlib import Path -from typing import TYPE_CHECKING -if TYPE_CHECKING: - from ._model import PythonHarnessConfig - from ._tree_sitter_query_predicates import SyntaxQueryPredicate +from ._tree_sitter_query_predicates import SyntaxQueryPredicate @dataclass(slots=True) @@ -49,8 +46,6 @@ def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: return cls._parse_search(args[1:]) if command == "query": return cls._parse_query(args[1:]) - if command == "check": - return cls._parse_check(args[1:]) if command == "evidence": return cls._parse_evidence(args[1:]) if command == "agent": @@ -124,32 +119,6 @@ def _parse_ast_patch(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: project_root=positionals[0] if positionals else None, ) - @classmethod - def _parse_check(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - json_output = False - positionals: list[str] = [] - for arg in args: - if arg == "--json": - json_output = True - elif arg in {"--changed", "--full"}: - continue - elif arg in {"--help", "-h"}: - return cls( - "error", - error="usage: asp-python check [--changed | --full] [--json] [PROJECT_ROOT]", - ) - elif arg.startswith("-"): - return cls("error", error=f"unknown check option: {arg}") - else: - positionals.append(arg) - if len(positionals) > 1: - return cls("error", error="expected at most one PROJECT_ROOT argument") - return cls( - "check", - project_root=None if not positionals else Path(positionals[0]), - json=json_output, - ) - @classmethod def _parse_evidence(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: action = args[0] if args else None @@ -258,132 +227,6 @@ def _parse_agent_guide(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: ) -@dataclass(slots=True) -class CliOptions: - json: bool = False - agent_snapshot: bool = False - help: bool = False - include_tests: bool | None = None - source_dir_values: list[str] = field(default_factory=list) - test_dir_values: list[str] = field(default_factory=list) - extra_path_values: list[str] = field(default_factory=list) - disabled_rule_values: list[str] = field(default_factory=list) - blocking_rule_values: list[str] = field(default_factory=list) - paths: list[Path] = field(default_factory=list) - - @classmethod - def parse(cls, args: list[str] | tuple[str, ...]) -> CliOptions: - options = cls() - positional_only = False - index = 0 - while index < len(args): - arg = args[index] - index += 1 - if positional_only: - options.paths.append(Path(arg)) - continue - match arg: - case "--": - positional_only = True - case "--json": - options.json = True - case "--agent-snapshot": - options.agent_snapshot = True - case "--no-tests": - options.include_tests = False - case "--source-dir": - options.source_dir_values.append( - _option_value(args, index, "--source-dir") - ) - index += 1 - case "--test-dir": - options.test_dir_values.append( - _option_value(args, index, "--test-dir") - ) - index += 1 - case "--extra-path": - options.extra_path_values.append( - _option_value(args, index, "--extra-path") - ) - index += 1 - case "--disable-rule": - options.disabled_rule_values.append( - _option_value(args, index, "--disable-rule") - ) - index += 1 - case "--block-rule": - options.blocking_rule_values.append( - _option_value(args, index, "--block-rule") - ) - index += 1 - case "--help" | "-h": - options.help = True - case value if value.startswith("-"): - raise ValueError(f"unknown option: {value}") - case value: - options.paths.append(Path(value)) - if len(options.paths) > 1: - raise ValueError("expected at most one PROJECT_ROOT argument") - if options.json and options.agent_snapshot: - raise ValueError("--json and --agent-snapshot are mutually exclusive") - return options - - def project_root(self, cwd: Path | None) -> Path: - if self.paths: - return self.paths[0] - if cwd is not None: - return cwd - return Path.cwd() - - @property - def source_dir_names(self) -> tuple[str, ...] | None: - """Return explicit source roots, when the CLI supplied any.""" - - if not self.source_dir_values: - return None - return tuple(self.source_dir_values) - - @property - def test_dir_names(self) -> tuple[str, ...] | None: - """Return explicit test roots, when the CLI supplied any.""" - - if not self.test_dir_values: - return None - return tuple(self.test_dir_values) - - @property - def extra_path_names(self) -> tuple[str, ...] | None: - """Return explicit extra project paths, when the CLI supplied any.""" - - if not self.extra_path_values: - return None - return tuple(self.extra_path_values) - - def harness_config(self, project_root: Path) -> PythonHarnessConfig | None: - """Return CLI policy config when rule-level options were supplied.""" - - if not self.disabled_rule_values and not self.blocking_rule_values: - return None - from ._model import PythonHarnessConfig - from ._project_config import read_python_project_harness_config - - base_config = read_python_project_harness_config(project_root) - config = base_config if base_config is not None else PythonHarnessConfig() - return replace( - config, - disabled_rule_ids=( - frozenset(self.disabled_rule_values) - if self.disabled_rule_values - else config.disabled_rule_ids - ), - blocking_rule_ids=( - frozenset(self.blocking_rule_values) - if self.blocking_rule_values - else config.blocking_rule_ids - ), - ) - - def help_text() -> str: return ( "asp-python — Python semantic search and project harness\n\n" @@ -391,15 +234,12 @@ def help_text() -> str: " 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" + "\n" "SEARCH VIEWS\n" " search workspace Workspace package/router index\n" " search prime Project reasoning-tree map\n" @@ -434,10 +274,6 @@ def help_text() -> str: " Typed callable skeleton materialization through ASP authority\n\n" " query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION'\n" " Flow-lite ABI compatibility surface; Python executor is not enabled yet\n\n" - "CHECK\n" - " check --changed Fast lane alias; currently delegates to project check\n" - " check --full Full project harness check\n" - " check --json Structured PythonHarnessReport JSON\n\n" "EVIDENCE\n" " evidence graph --json Portable semantic-evidence-graph packet\n" " evidence analyze --json Graph-turbo request for evidence-quality ranking\n\n" @@ -449,27 +285,18 @@ def help_text() -> str: " agent doctor --json Semantic language registry document\n\n" " agent guide Print command-line search flow guide\n\n" " Hook install/runtime is owned by asp in the root toolchain.\n\n" - "DIRECT CHECK\n" - "The no-command form still runs the default package-level Python harness.\n\n" - "Compact text is the default output for humans and repair-oriented agents.\n" - "Use --json to emit the structured PythonHarnessReport JSON shape.\n" - "Use --agent-snapshot to emit parser facts for project repair agents.\n" - "Repeat --source-dir or --test-dir to customize policy root classification.\n" - "Repeat --extra-path to include external project paths.\n" - "Repeat --disable-rule or --block-rule to customize policy by rule id.\n" "\nEXAMPLES\n" " asp-python search workspace .\n" " asp-python search prime .\n" " asp-python search public-external-types pytest .\n" " asp-python search callsite PythonSemanticSearchOptions .\n" " asp python search lexical --query PythonSemanticSearchOptions --query owner --workspace .\n" - " asp-python search reasoning owner-tests --owner src/python_lang_project_harness/_cli.py .\n" - " asp-python search reasoning owner-query --owner src/python_lang_project_harness/_cli.py --query run_cli .\n" + " asp-python search reasoning owner-tests --owner src/asp_python/_cli.py .\n" + " asp-python search reasoning owner-query --owner src/asp_python/_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 --selector 'python://src/asp_python/_cli.py#item/function/run_cli' --projection source --workspace .\n" " asp-python query --catalog flow-lite --where 'source.call=payload sink.constructs=Action scope.fn=collect' .\n" " asp python 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" @@ -484,16 +311,3 @@ def _optional_arg(args: list[str] | tuple[str, ...], index: int) -> str | None: if value.startswith("-"): return None return value - - -def _option_value( - args: list[str] | tuple[str, ...], - index: int, - option_name: str, -) -> str: - if index >= len(args): - raise ValueError(f"missing value for {option_name}") - value = args[index] - if value.startswith("-"): - raise ValueError(f"missing value for {option_name}") - return value diff --git a/src/python_lang_project_harness/_cli_ast_patch.py b/src/asp_python/_cli_ast_patch.py similarity index 100% rename from src/python_lang_project_harness/_cli_ast_patch.py rename to src/asp_python/_cli_ast_patch.py diff --git a/src/python_lang_project_harness/_cli_protocol.py b/src/asp_python/_cli_protocol.py similarity index 93% rename from src/python_lang_project_harness/_cli_protocol.py rename to src/asp_python/_cli_protocol.py index a44c306..6aa426a 100644 --- a/src/python_lang_project_harness/_cli_protocol.py +++ b/src/asp_python/_cli_protocol.py @@ -193,8 +193,6 @@ def _run_harness_protocol_command( return _run_query_command( args, report=report, project_root=project_root, stdout=stdout ) - if args.command == "check": - return _run_check_command(args, report=report, stdout=stdout) return _run_search_command( args, report=report, @@ -221,25 +219,6 @@ def _run_query_command( ) -def _run_check_command( - args: ProtocolArgs, - *, - report: object, - stdout: TextIO, -) -> int: - from ._render import ( - render_python_lang_harness, - render_python_lang_harness_json, - ) - - if args.json: - stdout.write(render_python_lang_harness_json(report)) - stdout.write("\n") - else: - stdout.write(render_python_lang_harness(report)) - return 0 if report.is_clean else 1 - - def _run_search_command( args: ProtocolArgs, *, diff --git a/src/python_lang_project_harness/_cli_query.py b/src/asp_python/_cli_query.py similarity index 100% rename from src/python_lang_project_harness/_cli_query.py rename to src/asp_python/_cli_query.py diff --git a/src/python_lang_project_harness/_cli_query_arg_consume.py b/src/asp_python/_cli_query_arg_consume.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_arg_consume.py rename to src/asp_python/_cli_query_arg_consume.py diff --git a/src/python_lang_project_harness/_cli_query_args.py b/src/asp_python/_cli_query_args.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_args.py rename to src/asp_python/_cli_query_args.py diff --git a/src/python_lang_project_harness/_cli_query_flow_lite_args.py b/src/asp_python/_cli_query_flow_lite_args.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_flow_lite_args.py rename to src/asp_python/_cli_query_flow_lite_args.py diff --git a/src/python_lang_project_harness/_cli_query_hook_args.py b/src/asp_python/_cli_query_hook_args.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_hook_args.py rename to src/asp_python/_cli_query_hook_args.py diff --git a/src/python_lang_project_harness/_cli_query_predicates.py b/src/asp_python/_cli_query_predicates.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_predicates.py rename to src/asp_python/_cli_query_predicates.py diff --git a/src/python_lang_project_harness/_cli_query_tree_sitter_args.py b/src/asp_python/_cli_query_tree_sitter_args.py similarity index 100% rename from src/python_lang_project_harness/_cli_query_tree_sitter_args.py rename to src/asp_python/_cli_query_tree_sitter_args.py diff --git a/src/python_lang_project_harness/_cli_search_runtime.py b/src/asp_python/_cli_search_runtime.py similarity index 95% rename from src/python_lang_project_harness/_cli_search_runtime.py rename to src/asp_python/_cli_search_runtime.py index 399020a..69190bf 100644 --- a/src/python_lang_project_harness/_cli_search_runtime.py +++ b/src/asp_python/_cli_search_runtime.py @@ -83,18 +83,18 @@ def _run_search_harness( rule_packs=None, ) if args.command != "search": - from ._runner import run_python_project_harness + from ._runner import run_asp_python - return run_python_project_harness(project_root, config=config), None + return run_asp_python(project_root, config=config), None if config.include_hidden_dir_names: - from ._runner import run_python_project_harness + from ._runner import run_asp_python - return run_python_project_harness(project_root, config=config), None + return run_asp_python(project_root, config=config), None query_terms = _prefilter_query_terms(args) if query_terms is None: - from ._runner import run_python_project_harness + from ._runner import run_asp_python - return run_python_project_harness(project_root, config=config), None + return run_asp_python(project_root, config=config), None from ._semantic_search_prefilter import prefilter_python_text_search_paths prefilter = prefilter_python_text_search_paths( @@ -103,9 +103,9 @@ def _run_search_harness( owner_path=args.owner_path, ) if prefilter is None: - from ._runner import run_python_project_harness + from ._runner import run_asp_python - return run_python_project_harness(project_root, config=config), None + return run_asp_python(project_root, config=config), None return _run_prefiltered_text_search(project_root, prefilter.paths), ( prefilter.runtime_cost() ) diff --git a/src/python_lang_project_harness/_constants.py b/src/asp_python/_constants.py similarity index 100% rename from src/python_lang_project_harness/_constants.py rename to src/asp_python/_constants.py diff --git a/src/python_lang_project_harness/_dependency_topology.py b/src/asp_python/_dependency_topology.py similarity index 100% rename from src/python_lang_project_harness/_dependency_topology.py rename to src/asp_python/_dependency_topology.py diff --git a/src/python_lang_project_harness/_dev_command_log.py b/src/asp_python/_dev_command_log.py similarity index 100% rename from src/python_lang_project_harness/_dev_command_log.py rename to src/asp_python/_dev_command_log.py diff --git a/src/python_lang_project_harness/_dev_command_log_command.py b/src/asp_python/_dev_command_log_command.py similarity index 100% rename from src/python_lang_project_harness/_dev_command_log_command.py rename to src/asp_python/_dev_command_log_command.py diff --git a/src/python_lang_project_harness/_dev_command_log_context.py b/src/asp_python/_dev_command_log_context.py similarity index 100% rename from src/python_lang_project_harness/_dev_command_log_context.py rename to src/asp_python/_dev_command_log_context.py diff --git a/src/python_lang_project_harness/_discovery.py b/src/asp_python/_discovery.py similarity index 98% rename from src/python_lang_project_harness/_discovery.py rename to src/asp_python/_discovery.py index 3321b3d..8be4fee 100644 --- a/src/python_lang_project_harness/_discovery.py +++ b/src/asp_python/_discovery.py @@ -67,7 +67,7 @@ def _iter_python_file_candidates( return tuple(candidates) -def python_project_harness_paths( +def asp_python_paths( project_root: str | Path, *, include_tests: bool = True, @@ -77,7 +77,7 @@ def python_project_harness_paths( ) -> tuple[Path, ...]: """Return project scan paths for embedded pytest harness checks.""" - return python_project_harness_scope( + return asp_python_scope( project_root, include_tests=include_tests, source_dir_names=source_dir_names, @@ -86,7 +86,7 @@ def python_project_harness_paths( ).monitored_paths -def python_project_harness_scope( +def asp_python_scope( project_root: str | Path, *, include_tests: bool = True, diff --git a/src/python_lang_project_harness/_evidence_graph.py b/src/asp_python/_evidence_graph.py similarity index 88% rename from src/python_lang_project_harness/_evidence_graph.py rename to src/asp_python/_evidence_graph.py index 0faad49..2a8efdb 100644 --- a/src/python_lang_project_harness/_evidence_graph.py +++ b/src/asp_python/_evidence_graph.py @@ -20,10 +20,9 @@ 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", "asp-python-check-full") - action_id = _node_id("python:action", "run-asp-python-check-full") + receipt_id = _node_id("python:receipt", "policy-api") + action_id = _node_id("python:action", "attach-policy-api-receipt") gap_id = _node_id("python:gap", f"{owner_path}:receipt") - check_command = "asp-python check --full ." nodes: list[dict[str, Any]] = [ { "nodeId": owner_id, @@ -45,23 +44,26 @@ def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: "location": {"path": owner_path, "line": 1, "column": 0}, "fields": { "sourceRuleId": "PY-EVIDENCE-GRAPH", - "receiptKind": "harness-check", + "receiptKind": "policy-evaluation", }, }, { "nodeId": receipt_id, "kind": "verification-receipt", - "label": check_command, - "receiptId": "python.asp-python.check.full", + "label": "Python dependency policy API receipt", + "receiptId": "python.policy.api", "status": "needs-injection", - "summary": "Run the Python harness full check and attach the receipt before treating the claim as verified.", - "fields": {"command": check_command}, + "summary": "Attach the receipt emitted by the Python dependency policy API before treating the claim as verified.", + "fields": { + "authority": "asp-python-api", + "trigger": "pytest-plugin", + }, }, { "nodeId": action_id, "kind": "review-action", - "label": "Run asp-python check --full .", - "actionId": "python.run-asp-python-check-full", + "label": "Attach Python dependency policy API receipt", + "actionId": "python.attach-policy-api-receipt", "status": "missing", "summary": "run-receipt", "fields": { @@ -79,9 +81,9 @@ def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: { "gapId": gap_id, "ownerPath": owner_path, - "summary": "No attached asp-python full-check receipt for this evidence graph.", + "summary": "No attached Python dependency policy API receipt for this evidence graph.", "severity": "warning", - "fields": {"nextCommand": check_command}, + "fields": {"requiredReceiptId": "python.policy.api"}, } ] return { diff --git a/src/python_lang_project_harness/_evidence_graph_turbo.py b/src/asp_python/_evidence_graph_turbo.py similarity index 100% rename from src/python_lang_project_harness/_evidence_graph_turbo.py rename to src/asp_python/_evidence_graph_turbo.py diff --git a/src/python_lang_project_harness/_exact_projection_model.py b/src/asp_python/_exact_projection_model.py similarity index 100% rename from src/python_lang_project_harness/_exact_projection_model.py rename to src/asp_python/_exact_projection_model.py diff --git a/src/python_lang_project_harness/_exact_source_projection.py b/src/asp_python/_exact_source_projection.py similarity index 100% rename from src/python_lang_project_harness/_exact_source_projection.py rename to src/asp_python/_exact_source_projection.py diff --git a/src/python_lang_project_harness/_flow_lite_query.py b/src/asp_python/_flow_lite_query.py similarity index 100% rename from src/python_lang_project_harness/_flow_lite_query.py rename to src/asp_python/_flow_lite_query.py diff --git a/src/python_lang_project_harness/_flow_lite_query_model.py b/src/asp_python/_flow_lite_query_model.py similarity index 100% rename from src/python_lang_project_harness/_flow_lite_query_model.py rename to src/asp_python/_flow_lite_query_model.py diff --git a/src/python_lang_project_harness/_flow_lite_query_packet.py b/src/asp_python/_flow_lite_query_packet.py similarity index 100% rename from src/python_lang_project_harness/_flow_lite_query_packet.py rename to src/asp_python/_flow_lite_query_packet.py diff --git a/src/python_lang_project_harness/_flow_lite_query_projector.py b/src/asp_python/_flow_lite_query_projector.py similarity index 100% rename from src/python_lang_project_harness/_flow_lite_query_projector.py rename to src/asp_python/_flow_lite_query_projector.py diff --git a/src/python_lang_project_harness/_harness_rules.py b/src/asp_python/_harness_rules.py similarity index 91% rename from src/python_lang_project_harness/_harness_rules.py rename to src/asp_python/_harness_rules.py index 8c30465..b3bf476 100644 --- a/src/python_lang_project_harness/_harness_rules.py +++ b/src/asp_python/_harness_rules.py @@ -20,11 +20,11 @@ def render_python_harness_rules_markdown() -> str: """Render the source-embedded Python harness rules as markdown.""" output = [ - "# python-lang-project-harness", + "# asp-python", "", "## Harness Rules", "", - "Generated from embedded `src/python_lang_project_harness/harness-rules.md`.", + "Generated from embedded `src/asp_python/harness-rules.md`.", "", ] for line in python_harness_rules_markdown().splitlines(): diff --git a/src/python_lang_project_harness/_model.py b/src/asp_python/_model.py similarity index 95% rename from src/python_lang_project_harness/_model.py rename to src/asp_python/_model.py index b1f1b11..dab56a7 100644 --- a/src/python_lang_project_harness/_model.py +++ b/src/asp_python/_model.py @@ -52,7 +52,7 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) -class PythonHarnessRule: +class AspPythonRule: """Compact metadata for one deterministic harness rule.""" rule_id: str @@ -71,7 +71,7 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) -class PythonHarnessFinding: +class AspPythonFinding: """One deterministic Python harness finding.""" rule_id: str @@ -161,12 +161,12 @@ class PythonLangRulePack(Protocol): def descriptor(self) -> PythonRulePackDescriptor: """Return stable metadata for this rule pack.""" - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate one parsed module report.""" @dataclass(frozen=True, slots=True) -class PythonHarnessConfig: +class AspPythonConfig: """Configuration for an embedded Python language harness run.""" ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES @@ -188,7 +188,7 @@ class PythonHarnessConfig: def with_verification_policy( self, policy: PythonVerificationPolicy, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with an explicit verification policy.""" return replace(self, verification_policy=policy) @@ -196,7 +196,7 @@ def with_verification_policy( def with_verification_profile_hint( self, hint: PythonVerificationProfileHint, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification profile hint appended.""" return replace( @@ -207,7 +207,7 @@ def with_verification_profile_hint( def with_verification_dependency_signal( self, signal: PythonVerificationDependencySignal, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one dependency-to-responsibility signal.""" return replace( @@ -218,7 +218,7 @@ def with_verification_dependency_signal( def with_verification_receipt( self, receipt: PythonVerificationReceipt, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification receipt appended.""" return replace( @@ -229,7 +229,7 @@ def with_verification_receipt( def with_verification_waiver( self, waiver: PythonVerificationWaiver, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification waiver appended.""" return replace( @@ -241,7 +241,7 @@ def with_verification_task_contract( self, kind: PythonVerificationTaskKind, contract: PythonVerificationTaskContract, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification task contract override.""" return replace( @@ -256,7 +256,7 @@ def with_verification_skill_binding( self, kind: PythonVerificationTaskKind, binding: PythonVerificationSkillBinding, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification skill binding.""" return replace( @@ -270,7 +270,7 @@ def with_verification_skill_binding( def with_verification_skill_descriptor( self, descriptor: PythonVerificationSkillDescriptor, - ) -> PythonHarnessConfig: + ) -> AspPythonConfig: """Return a config with one verification skill descriptor.""" return replace( @@ -282,11 +282,11 @@ def with_verification_skill_descriptor( @dataclass(frozen=True, slots=True) -class PythonHarnessReport: +class AspPythonReport: """Aggregated Python language harness report.""" modules: tuple[PythonModuleReport, ...] - findings: tuple[PythonHarnessFinding, ...] + findings: tuple[AspPythonFinding, ...] root_paths: tuple[str, ...] blocking_severities: frozenset[PythonDiagnosticSeverity] = ( DEFAULT_BLOCKING_SEVERITIES @@ -339,7 +339,7 @@ def blocking_findings( self, *, severities: frozenset[PythonDiagnosticSeverity] | None = None, - ) -> tuple[PythonHarnessFinding, ...]: + ) -> tuple[AspPythonFinding, ...]: """Return findings that should block a pytest assertion.""" blocking_severities = ( @@ -356,7 +356,7 @@ def advisory_findings( self, *, severities: frozenset[PythonDiagnosticSeverity] | None = None, - ) -> tuple[PythonHarnessFinding, ...]: + ) -> tuple[AspPythonFinding, ...]: """Return non-blocking advisory findings for agent-guided repair.""" if severities is None: diff --git a/src/python_lang_project_harness/_modern_design.py b/src/asp_python/_modern_design.py similarity index 89% rename from src/python_lang_project_harness/_modern_design.py rename to src/asp_python/_modern_design.py index 657bf39..f4b2413 100644 --- a/src/python_lang_project_harness/_modern_design.py +++ b/src/asp_python/_modern_design.py @@ -11,7 +11,7 @@ python_module_is_package_init, ) -from ._model import PythonHarnessFinding, PythonRulePackDescriptor +from ._model import AspPythonFinding, PythonRulePackDescriptor from ._modern_design_catalog import ( MODERN_DESIGN_PACK_ID, PY_MOD_R001, @@ -43,13 +43,13 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate modern Python design rules for one parsed module report.""" if not report.is_valid: return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(_wildcard_import_findings(report, self.pack_id)) findings.extend(_bare_print_findings(report, self.pack_id)) findings.extend(_debug_breakpoint_findings(report, self.pack_id)) @@ -60,15 +60,15 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding] def _wildcard_import_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = modern_design_rule(PY_MOD_R001) for import_record in report.imports: if not import_record.is_wildcard: continue module = "." * import_record.level + (import_record.module or "") findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -87,17 +87,17 @@ def _wildcard_import_findings( def _bare_print_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not _module_has_project_surface(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = modern_design_rule(PY_MOD_R002) for call in report.calls: if call.effect != PythonCallEffect.STANDARD_OUTPUT: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -116,17 +116,17 @@ def _bare_print_findings( def _debug_breakpoint_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not _module_has_project_surface(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = modern_design_rule(PY_MOD_R004) for call in report.calls: if call.effect != PythonCallEffect.DEBUG_BREAKPOINT: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -145,7 +145,7 @@ def _debug_breakpoint_findings( def _facade_all_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not report.path or not python_module_is_package_init(report.path): return () if not _has_facade_import(report) or report.export_contract.is_static: @@ -153,7 +153,7 @@ def _facade_all_findings( rule = modern_design_rule(PY_MOD_R003) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_modern_design_catalog.py b/src/asp_python/_modern_design_catalog.py similarity index 88% rename from src/python_lang_project_harness/_modern_design_catalog.py rename to src/asp_python/_modern_design_catalog.py index 4f895a3..39c15cb 100644 --- a/src/python_lang_project_harness/_modern_design_catalog.py +++ b/src/asp_python/_modern_design_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule MODERN_DESIGN_PACK_ID = "python.modern_design" PY_MOD_R001 = "PY-MOD-R001" @@ -19,7 +19,7 @@ "domain": "modern-python", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R001, pack_id=MODERN_DESIGN_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -27,7 +27,7 @@ requirement="Import explicit names instead of `*` in project modules.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R002, pack_id=MODERN_DESIGN_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -35,7 +35,7 @@ requirement="Use a logger, returned value, or explicit test assertion instead of bare `print` in library modules.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R003, pack_id=MODERN_DESIGN_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -43,7 +43,7 @@ requirement="Declare `__all__` beside package facade imports so public exports stay explicit.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R004, pack_id=MODERN_DESIGN_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -55,13 +55,13 @@ _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_modern_design_rules() -> tuple[PythonHarnessRule, ...]: +def python_modern_design_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for the default modern-design rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def modern_design_rule(rule_id: str) -> PythonHarnessRule: +def modern_design_rule(rule_id: str) -> AspPythonRule: """Return one modern-design rule descriptor by stable rule id.""" return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_modularity.py b/src/asp_python/_modularity.py similarity index 93% rename from src/python_lang_project_harness/_modularity.py rename to src/asp_python/_modularity.py index f7ec353..90cf963 100644 --- a/src/python_lang_project_harness/_modularity.py +++ b/src/asp_python/_modularity.py @@ -14,8 +14,8 @@ ) from ._model import ( - PythonHarnessFinding, - PythonHarnessRule, + AspPythonFinding, + AspPythonRule, PythonRulePackDescriptor, ) from ._modularity_signals import ( @@ -40,7 +40,7 @@ "domain": "modularity", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R006, pack_id=MODULARITY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -48,7 +48,7 @@ requirement="Split large multi-responsibility Python modules into focused modules behind an explicit package facade.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_MOD_R007, pack_id=MODULARITY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -76,7 +76,7 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate Python module-shape rules for one parsed module report.""" if not report.is_valid: @@ -87,13 +87,13 @@ def evaluate_project_modules( self, scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate package-tree modularity rules over a parsed project.""" return _reasoning_tree_findings(scope, modules, self.pack_id) -def python_modularity_rules() -> tuple[PythonHarnessRule, ...]: +def python_modularity_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for the default Python modularity rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) @@ -102,7 +102,7 @@ def python_modularity_rules() -> tuple[PythonHarnessRule, ...]: def _file_modularity_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: shape = report.shape if shape is None: return () @@ -116,7 +116,7 @@ def _file_modularity_findings( rule = _rule(PY_MOD_R006) path = Path(report.path or "") return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -146,7 +146,7 @@ def _reasoning_tree_findings( scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: facts = python_reasoning_tree_facts( modules, import_roots=_reasoning_tree_import_roots(scope), @@ -157,12 +157,12 @@ def _reasoning_tree_findings( modules_by_path = { module.path: module for module in modules if module.path is not None } - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for shadow in facts.shadowed_module_sources: package_module = modules_by_path.get(shadow.package_init_path) namespace = ".".join(shadow.namespace) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -234,5 +234,5 @@ def _is_data_model_base(base_class: str) -> bool: } -def _rule(rule_id: str) -> PythonHarnessRule: +def _rule(rule_id: str) -> AspPythonRule: return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_modularity_signals.py b/src/asp_python/_modularity_signals.py similarity index 100% rename from src/python_lang_project_harness/_modularity_signals.py rename to src/asp_python/_modularity_signals.py diff --git a/src/python_lang_project_harness/_project_config.py b/src/asp_python/_project_config.py similarity index 97% rename from src/python_lang_project_harness/_project_config.py rename to src/asp_python/_project_config.py index 0301f37..42884dc 100644 --- a/src/python_lang_project_harness/_project_config.py +++ b/src/asp_python/_project_config.py @@ -9,7 +9,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessConfig +from ._model import AspPythonConfig from .verification import ( PythonOwnerResponsibility, PythonVerificationDependencySignal, @@ -25,18 +25,18 @@ PythonVerificationWaiver, ) -_TOOL_TABLE_NAME = "python-lang-project-harness" +_TOOL_TABLE_NAME = "asp-python" -def read_python_project_harness_config( +def read_asp_python_config( project_root: str | Path, -) -> PythonHarnessConfig | None: - """Read `[tool.python-lang-project-harness]` from `pyproject.toml`.""" +) -> AspPythonConfig | None: + """Read `[tool.asp-python]` from `pyproject.toml`.""" table = _read_project_config_table(Path(project_root) / "pyproject.toml") if not table: return None - return PythonHarnessConfig(**_harness_config_kwargs(table)) + return AspPythonConfig(**_harness_config_kwargs(table)) def read_pyproject_payload(pyproject_path: Path) -> dict[str, Any]: @@ -53,8 +53,8 @@ def read_pyproject_payload(pyproject_path: Path) -> dict[str, Any]: def apply_asp_project_discovery_config( project_root: str | Path, - config: PythonHarnessConfig, -) -> PythonHarnessConfig: + config: AspPythonConfig, +) -> AspPythonConfig: """Merge nearest `asp.toml` discovery settings into a harness config.""" table = _read_asp_discovery_table(Path(project_root)) diff --git a/src/python_lang_project_harness/_project_evaluation.py b/src/asp_python/_project_evaluation.py similarity index 87% rename from src/python_lang_project_harness/_project_evaluation.py rename to src/asp_python/_project_evaluation.py index 12512c2..1b6a229 100644 --- a/src/python_lang_project_harness/_project_evaluation.py +++ b/src/asp_python/_project_evaluation.py @@ -13,7 +13,7 @@ from python_lang_parser import PythonModuleReport from ._model import ( - PythonHarnessFinding, + AspPythonFinding, PythonLangRulePack, PythonProjectHarnessScope, ) @@ -23,10 +23,10 @@ def evaluate_project_rule_packs( scope: PythonProjectHarnessScope, rule_packs: Sequence[PythonLangRulePack], modules: Sequence[PythonModuleReport], -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Evaluate project-resolution hooks exposed by configured rule packs.""" - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for rule_pack in rule_packs: module_evaluator = getattr(rule_pack, "evaluate_project_modules", None) if module_evaluator is not None: @@ -44,9 +44,9 @@ def evaluate_project_rule_packs( def compact_project_findings( - module_findings: Sequence[PythonHarnessFinding], - project_findings: Sequence[PythonHarnessFinding], -) -> tuple[PythonHarnessFinding, ...]: + module_findings: Sequence[AspPythonFinding], + project_findings: Sequence[AspPythonFinding], +) -> tuple[AspPythonFinding, ...]: """Remove duplicate module advice covered by stricter project findings.""" typed_package_annotation_locations = { @@ -66,7 +66,7 @@ def compact_project_findings( def _finding_location_key( - finding: PythonHarnessFinding, + finding: AspPythonFinding, ) -> tuple[str | None, int, int]: return ( finding.location.path, diff --git a/src/python_lang_project_harness/_project_metadata.py b/src/asp_python/_project_metadata.py similarity index 100% rename from src/python_lang_project_harness/_project_metadata.py rename to src/asp_python/_project_metadata.py diff --git a/src/python_lang_project_harness/_project_policy.py b/src/asp_python/_project_policy.py similarity index 90% rename from src/python_lang_project_harness/_project_policy.py rename to src/asp_python/_project_policy.py index a943920..2a5633b 100644 --- a/src/python_lang_project_harness/_project_policy.py +++ b/src/asp_python/_project_policy.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding, PythonRulePackDescriptor +from ._model import AspPythonFinding, PythonRulePackDescriptor from ._project_metadata import read_python_project_metadata from ._project_policy_catalog import PROJECT_POLICY_PACK_ID from ._project_policy_imports import project_import_name_findings @@ -39,7 +39,7 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate per-module rules.""" return () @@ -48,7 +48,7 @@ def evaluate_project_modules( self, scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate project-shape rules over a parsed project.""" metadata = scope.project_metadata or read_python_project_metadata( @@ -57,7 +57,7 @@ def evaluate_project_modules( if metadata is None: return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(project_metadata_findings(metadata, self.pack_id)) findings.extend(project_layout_findings(scope, metadata, self.pack_id)) findings.extend( diff --git a/src/python_lang_project_harness/_project_policy_catalog.py b/src/asp_python/_project_policy_catalog.py similarity index 87% rename from src/python_lang_project_harness/_project_policy_catalog.py rename to src/asp_python/_project_policy_catalog.py index a0a7bf7..9a47f25 100644 --- a/src/python_lang_project_harness/_project_policy_catalog.py +++ b/src/asp_python/_project_policy_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule PROJECT_POLICY_PACK_ID = "python.project_policy" PY_PROJ_R001 = "PY-AGENT-PROJECT-001" @@ -26,7 +26,7 @@ "domain": "project-policy", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R001, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -34,7 +34,7 @@ requirement="Use a `src/` source layout so package imports resolve through the installed project shape instead of the repository root.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R002, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -42,7 +42,7 @@ requirement="Ensure each declared wheel package root exists and contains `__init__.py` so agents can map project metadata to import namespaces.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R003, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -50,7 +50,7 @@ requirement="Add `py.typed` to declared package roots that expose public parser surface so downstream agents and type checkers can trust inline types.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R004, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -58,7 +58,7 @@ requirement="Annotate public callable boundaries in `py.typed` packages so the declared typed surface remains complete and agent-readable.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R005, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -66,7 +66,7 @@ requirement="Declare `[project].name` in `pyproject.toml` so package identity is explicit for build tools, agents, and release metadata.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R006, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -74,7 +74,7 @@ requirement="Declare `[project].requires-python` so installers, CI, and agents can resolve the intended supported Python range.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R007, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -82,7 +82,7 @@ requirement="Declare `[build-system].requires` whenever `[build-system]` is present so isolated builds can install backend requirements deterministically.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R008, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -90,7 +90,7 @@ requirement="Keep `[project].import-names` and `[project].import-namespaces` aligned with parser-visible project owners so agents can trust package scope metadata.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R009, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -98,33 +98,33 @@ requirement="Keep console scripts, GUI scripts, and entry points pointed at parser-visible project modules so agent entry maps and packaging metadata agree.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R010, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, title="Harness dev dependency should mount a pytest gate", - requirement="Enable `--python-project-harness` in pytest addopts or expose `python_project_harness_test()` so the dev dependency actually gates project policy.", + requirement="Enable `--python-project-harness` in pytest addopts or expose `asp_python_test()` so the dev dependency actually gates project policy.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_PROJ_R011, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.INFO, title="Verification profile hints are not configured", - requirement="Configure `[tool.python-lang-project-harness.verification].profile_hints` from parser-suggested owners, or run `asp-python --agent-snapshot` to copy the compact `[verify-profile]` hints.", + requirement="Configure `[tool.asp-python.verification].profile_hints` from parser-suggested owners or consume the dependency API verification-profile projection.", labels=dict(_RULE_LABELS), ), ) _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_project_policy_rules() -> tuple[PythonHarnessRule, ...]: +def python_project_policy_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for default project-shape rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def project_policy_rule(rule_id: str) -> PythonHarnessRule: +def project_policy_rule(rule_id: str) -> AspPythonRule: """Return one project-policy rule descriptor by stable rule id.""" return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_project_policy_imports.py b/src/asp_python/_project_policy_imports.py similarity index 94% rename from src/python_lang_project_harness/_project_policy_imports.py rename to src/asp_python/_project_policy_imports.py index 6e95a24..e6af168 100644 --- a/src/python_lang_project_harness/_project_policy_imports.py +++ b/src/asp_python/_project_policy_imports.py @@ -7,7 +7,7 @@ from python_lang_parser import python_reasoning_tree_facts -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import PY_PROJ_R008, PY_PROJ_R009, project_policy_rule from ._source import path_location, source_line @@ -25,7 +25,7 @@ def project_import_name_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings for declared import names not backed by parser owners.""" rule = project_policy_rule(PY_PROJ_R008) @@ -36,7 +36,7 @@ def project_import_name_findings( project_metadata=metadata, ) known_namespaces = {node.namespace for node in facts.nodes if node.namespace} - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(_ambiguous_import_name_findings(metadata, pack_id)) findings.extend( _unresolved_entry_point_target_findings( @@ -49,7 +49,7 @@ def project_import_name_findings( if import_name.name == "" or import_name.namespace in known_namespaces: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -74,14 +74,14 @@ def _unresolved_entry_point_target_findings( *, known_namespaces: set[tuple[str, ...]], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: rule = project_policy_rule(PY_PROJ_R009) - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for kind, name, target, target_namespace in _entry_point_targets(metadata): if not target_namespace or target_namespace in known_namespaces: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -129,7 +129,7 @@ def _entry_point_targets( def _ambiguous_import_name_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: import_namespaces = {item.name for item in metadata.import_namespaces} ambiguous = tuple( item @@ -141,7 +141,7 @@ def _ambiguous_import_name_findings( rule = project_policy_rule(PY_PROJ_R008) return tuple( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_project_policy_layout.py b/src/asp_python/_project_policy_layout.py similarity index 94% rename from src/python_lang_project_harness/_project_policy_layout.py rename to src/asp_python/_project_policy_layout.py index 4894820..bffc186 100644 --- a/src/python_lang_project_harness/_project_policy_layout.py +++ b/src/asp_python/_project_policy_layout.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import PY_PROJ_R001, PY_PROJ_R002, project_policy_rule from ._source import path_location, source_line @@ -19,7 +19,7 @@ def project_layout_findings( scope: PythonProjectHarnessScope, metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings for project source and package-root layout.""" if not _is_packaged_project(metadata): @@ -43,13 +43,13 @@ def _src_layout_findings( scope: PythonProjectHarnessScope, metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if _uses_src_layout(scope, metadata): return () rule = project_policy_rule(PY_PROJ_R001) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -116,8 +116,8 @@ def _is_relative_to(path: Path, root: Path) -> bool: def _declared_package_root_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = project_policy_rule(PY_PROJ_R002) for package_root in metadata.package_roots: init_file = package_root / "__init__.py" @@ -127,7 +127,7 @@ def _declared_package_root_findings( package_root if package_root.exists() else metadata.pyproject_path ) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_project_policy_metadata.py b/src/asp_python/_project_policy_metadata.py similarity index 89% rename from src/python_lang_project_harness/_project_policy_metadata.py rename to src/asp_python/_project_policy_metadata.py index a6782d7..54e7954 100644 --- a/src/python_lang_project_harness/_project_policy_metadata.py +++ b/src/asp_python/_project_policy_metadata.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import ( PY_PROJ_R005, PY_PROJ_R006, @@ -20,10 +20,10 @@ def project_metadata_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings for deterministic pyproject metadata contracts.""" - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] findings.extend(_project_name_findings(metadata, pack_id)) findings.extend(_requires_python_findings(metadata, pack_id)) findings.extend(_build_requires_findings(metadata, pack_id)) @@ -33,13 +33,13 @@ def project_metadata_findings( def _project_name_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not metadata.has_project_table or metadata.project_name is not None: return () rule = project_policy_rule(PY_PROJ_R005) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -57,13 +57,13 @@ def _project_name_findings( def _requires_python_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not metadata.has_project_table or metadata.requires_python is not None: return () rule = project_policy_rule(PY_PROJ_R006) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -81,13 +81,13 @@ def _requires_python_findings( def _build_requires_findings( metadata: PythonProjectMetadata, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: if not metadata.has_build_system_table or metadata.build_requires: return () rule = project_policy_rule(PY_PROJ_R007) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_project_policy_pytest_gate.py b/src/asp_python/_project_policy_pytest_gate.py similarity index 84% rename from src/python_lang_project_harness/_project_policy_pytest_gate.py rename to src/asp_python/_project_policy_pytest_gate.py index 8ae42a2..9f3c025 100644 --- a/src/python_lang_project_harness/_project_policy_pytest_gate.py +++ b/src/asp_python/_project_policy_pytest_gate.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import PY_PROJ_R010, project_policy_rule from ._source import path_location, source_line @@ -13,26 +13,26 @@ from python_lang_parser import PythonModuleReport, PythonProjectMetadata -_DISTRIBUTION_NAME = "python-lang-project-harness" +_DISTRIBUTION_NAME = "asp-python" def project_pytest_gate_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings when a harness dependency is not wired into pytest.""" if not declares_python_harness_surface(metadata): return () - if metadata.pytest_options.enables_python_project_harness: + if metadata.pytest_options.enables_asp_python: return () if _has_explicit_harness_helper(modules): return () rule = project_policy_rule(PY_PROJ_R010) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -63,7 +63,7 @@ def declares_python_harness_surface(metadata: PythonProjectMetadata) -> bool: return True return any( entry_point.group == "pytest11" - and entry_point.target_namespace[:1] == ("python_lang_project_harness",) + and entry_point.target_namespace[:1] == ("asp_python",) for entry_point in metadata.entry_points ) @@ -73,9 +73,9 @@ def _has_explicit_harness_helper( ) -> bool: for module in modules: for call in module.calls: - if call.function == "python_project_harness_test": + if call.function == "asp_python_test": return True - if call.function.endswith(".python_project_harness_test"): + if call.function.endswith(".asp_python_test"): return True return False diff --git a/src/python_lang_project_harness/_project_policy_typed.py b/src/asp_python/_project_policy_typed.py similarity index 92% rename from src/python_lang_project_harness/_project_policy_typed.py rename to src/asp_python/_project_policy_typed.py index 82a0fe7..8204e94 100644 --- a/src/python_lang_project_harness/_project_policy_typed.py +++ b/src/asp_python/_project_policy_typed.py @@ -11,7 +11,7 @@ python_symbol_is_public_class, ) -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._project_policy_catalog import PY_PROJ_R003, PY_PROJ_R004, project_policy_rule from ._source import path_location @@ -27,7 +27,7 @@ def typed_package_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return findings for typed package marker and annotation contracts.""" return ( @@ -40,8 +40,8 @@ def _typed_package_marker_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = project_policy_rule(PY_PROJ_R003) for package_root in metadata.package_roots: if not (package_root / "__init__.py").is_file(): @@ -51,7 +51,7 @@ def _typed_package_marker_findings( if not _package_has_public_parser_surface(package_root, modules): continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -86,8 +86,8 @@ def _typed_package_annotation_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] rule = project_policy_rule(PY_PROJ_R004) for package_root in metadata.package_roots: if not (package_root / "py.typed").is_file(): @@ -113,15 +113,15 @@ def _unannotated_public_callable_findings( public_class_names: frozenset[str], pack_id: str, rule: object, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] for symbol in module.symbols: if not _is_public_callable_boundary(symbol, public_class_names): continue if symbol.has_annotations: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_project_policy_verification.py b/src/asp_python/_project_policy_verification.py similarity index 87% rename from src/python_lang_project_harness/_project_policy_verification.py rename to src/asp_python/_project_policy_verification.py index b8fdd71..376bb3f 100644 --- a/src/python_lang_project_harness/_project_policy_verification.py +++ b/src/asp_python/_project_policy_verification.py @@ -6,8 +6,8 @@ from python_lang_parser import python_reasoning_tree_facts -from ._model import PythonHarnessConfig, PythonHarnessFinding -from ._project_config import read_python_project_harness_config +from ._model import AspPythonConfig, AspPythonFinding +from ._project_config import read_asp_python_config from ._project_policy_catalog import PY_PROJ_R011, project_policy_rule from ._project_policy_pytest_gate import declares_python_harness_surface from ._source import path_location, source_line @@ -29,13 +29,13 @@ def project_verification_profile_findings( metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return Agent advice when parser facts need a verification profile.""" if not declares_python_harness_surface(metadata): return () - config = read_python_project_harness_config(scope.project_root) - selected_config = config if config is not None else PythonHarnessConfig() + config = read_asp_python_config(scope.project_root) + selected_config = config if config is not None else AspPythonConfig() if selected_config.verification_policy.profile_hints: return () @@ -45,7 +45,7 @@ def project_verification_profile_findings( rule = project_policy_rule(PY_PROJ_R011) return ( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -67,7 +67,7 @@ def _verification_owner_count( scope: PythonProjectHarnessScope, metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], - config: PythonHarnessConfig, + config: AspPythonConfig, ) -> int: facts = python_reasoning_tree_facts( modules, diff --git a/src/python_lang_project_harness/_project_resolution.py b/src/asp_python/_project_resolution.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution.py rename to src/asp_python/_project_resolution.py diff --git a/src/python_lang_project_harness/_project_resolution_backends.py b/src/asp_python/_project_resolution_backends.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_backends.py rename to src/asp_python/_project_resolution_backends.py diff --git a/src/python_lang_project_harness/_project_resolution_candidates.py b/src/asp_python/_project_resolution_candidates.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_candidates.py rename to src/asp_python/_project_resolution_candidates.py diff --git a/src/python_lang_project_harness/_project_resolution_document.py b/src/asp_python/_project_resolution_document.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_document.py rename to src/asp_python/_project_resolution_document.py diff --git a/src/python_lang_project_harness/_project_resolution_graph.py b/src/asp_python/_project_resolution_graph.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_graph.py rename to src/asp_python/_project_resolution_graph.py diff --git a/src/python_lang_project_harness/_project_resolution_sources.py b/src/asp_python/_project_resolution_sources.py similarity index 100% rename from src/python_lang_project_harness/_project_resolution_sources.py rename to src/asp_python/_project_resolution_sources.py diff --git a/src/python_lang_project_harness/_projection_batch.py b/src/asp_python/_projection_batch.py similarity index 100% rename from src/python_lang_project_harness/_projection_batch.py rename to src/asp_python/_projection_batch.py diff --git a/src/python_lang_project_harness/_pytest.py b/src/asp_python/_pytest.py similarity index 68% rename from src/python_lang_project_harness/_pytest.py rename to src/asp_python/_pytest.py index 55d4662..57a9203 100644 --- a/src/python_lang_project_harness/_pytest.py +++ b/src/asp_python/_pytest.py @@ -5,35 +5,35 @@ from pathlib import Path from typing import TYPE_CHECKING -from ._runner import assert_python_project_harness_clean +from ._runner import assert_asp_python_clean if TYPE_CHECKING: from collections.abc import Callable, Sequence from python_lang_parser import PythonDiagnosticSeverity - from ._model import PythonHarnessConfig, PythonLangRulePack + from ._model import AspPythonConfig, PythonLangRulePack -def python_project_harness_test( +def asp_python_test( project_root: str | Path = ".", *, - config: PythonHarnessConfig | None = None, + config: AspPythonConfig | None = None, rule_packs: Sequence[PythonLangRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_tests: bool | None = None, source_dir_names: Sequence[str] | None = None, test_dir_names: Sequence[str] | None = None, extra_path_names: Sequence[str] | None = None, - test_name: str = "test_python_project_harness_policy", + test_name: str = "test_asp_python_policy", include_advice: bool = True, ) -> Callable[[], None]: """Return a pytest-collectable test function for one Python project.""" root = Path(project_root) - def test_python_project_harness_policy() -> None: - assert_python_project_harness_clean( + def test_asp_python_policy() -> None: + assert_asp_python_clean( root, config=config, rule_packs=rule_packs, @@ -45,9 +45,9 @@ def test_python_project_harness_policy() -> None: include_advice=include_advice, ) - test_python_project_harness_policy.__name__ = test_name - test_python_project_harness_policy.__qualname__ = test_name - test_python_project_harness_policy.__doc__ = ( + test_asp_python_policy.__name__ = test_name + test_asp_python_policy.__qualname__ = test_name + test_asp_python_policy.__doc__ = ( "Run the Python project harness over configured project paths." ) - return test_python_project_harness_policy + return test_asp_python_policy diff --git a/src/asp_python/_pytest_plugin_options.py b/src/asp_python/_pytest_plugin_options.py new file mode 100644 index 0000000..12f969c --- /dev/null +++ b/src/asp_python/_pytest_plugin_options.py @@ -0,0 +1,160 @@ +"""Option registration and typed configuration for the pytest integration.""" + +from __future__ import annotations + +from dataclasses import replace +from typing import TYPE_CHECKING + +import pytest + +from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity + +from ._model import AspPythonConfig +from ._project_config import read_asp_python_config +from ._pytest_plugin_project import project_root + +if TYPE_CHECKING: + from collections.abc import Sequence + + +ENABLE_OPTION = "--python-project-harness" +NO_TESTS_OPTION = "--python-project-harness-no-tests" +SOURCE_DIR_OPTION = "--python-project-harness-source-dir" +TEST_DIR_OPTION = "--python-project-harness-test-dir" +EXTRA_PATH_OPTION = "--python-project-harness-extra-path" +NO_ADVICE_OPTION = "--python-project-harness-no-advice" + +_ROOT_OPTION = "--python-project-harness-root" +_DISABLE_RULE_OPTION = "--python-project-harness-disable-rule" +_BLOCK_RULE_OPTION = "--python-project-harness-block-rule" +_ERROR_ONLY_OPTION = "--python-project-harness-error-only" + + +def add_options(parser: pytest.Parser) -> None: + """Register Python project harness pytest options.""" + + group = parser.getgroup("asp-python") + for name, kwargs in ( + ( + ENABLE_OPTION, + { + "action": "store_true", + "default": False, + "help": "Collect and run the asp-python policy test.", + }, + ), + ( + _ROOT_OPTION, + { + "action": "store", + "default": None, + "metavar": "PATH", + "help": "Project root for the harness test. Defaults to pytest rootdir.", + }, + ), + ( + NO_TESTS_OPTION, + { + "action": "store_true", + "default": False, + "help": "Do not parse test files; pytest layout checks still run.", + }, + ), + ( + SOURCE_DIR_OPTION, + { + "action": "append", + "default": [], + "metavar": "NAME", + "help": "Source directory name to scan. Can be provided more than once.", + }, + ), + ( + TEST_DIR_OPTION, + { + "action": "append", + "default": [], + "metavar": "NAME", + "help": "Test directory name to scan. Can be provided more than once.", + }, + ), + ( + EXTRA_PATH_OPTION, + { + "action": "append", + "default": [], + "metavar": "NAME", + "help": "Extra project path name to scan. Can be provided more than once.", + }, + ), + ( + _DISABLE_RULE_OPTION, + { + "action": "append", + "default": [], + "metavar": "RULE_ID", + "help": "Harness rule id to suppress. Can be provided more than once.", + }, + ), + ( + _BLOCK_RULE_OPTION, + { + "action": "append", + "default": [], + "metavar": "RULE_ID", + "help": "Harness rule id to treat as blocking. Can be provided more than once.", + }, + ), + ( + _ERROR_ONLY_OPTION, + { + "action": "store_true", + "default": False, + "help": "Only fail the pytest harness item for parser errors.", + }, + ), + ( + NO_ADVICE_OPTION, + { + "action": "store_true", + "default": False, + "help": "Hide non-blocking advice from assertion output.", + }, + ), + ): + group.addoption(name, **kwargs) + + +def blocking_severities( + config: pytest.Config, +) -> frozenset[PythonDiagnosticSeverity] | None: + if config.getoption(_ERROR_ONLY_OPTION): + return frozenset({PythonDiagnosticSeverity.ERROR}) + return None + + +def harness_config(config: pytest.Config) -> AspPythonConfig | None: + disabled_rule_values = config.getoption(_DISABLE_RULE_OPTION) + blocking_rule_values = config.getoption(_BLOCK_RULE_OPTION) + if not disabled_rule_values and not blocking_rule_values: + return None + + base_config = read_asp_python_config(project_root(config)) + selected_config = base_config if base_config is not None else AspPythonConfig() + return replace( + selected_config, + disabled_rule_ids=( + frozenset(disabled_rule_values) + if disabled_rule_values + else selected_config.disabled_rule_ids + ), + blocking_rule_ids=( + frozenset(blocking_rule_values) + if blocking_rule_values + else selected_config.blocking_rule_ids + ), + ) + + +def optional_tuple(values: Sequence[str]) -> tuple[str, ...] | None: + return tuple(values) if values else None diff --git a/src/asp_python/_pytest_plugin_project.py b/src/asp_python/_pytest_plugin_project.py new file mode 100644 index 0000000..8e0797c --- /dev/null +++ b/src/asp_python/_pytest_plugin_project.py @@ -0,0 +1,91 @@ +"""Project-scope resolution for the pytest integration.""" + +from __future__ import annotations + +from pathlib import Path +from typing import TYPE_CHECKING + +import pytest + +from python_lang_parser._pyproject_metadata import parse_python_project_metadata + +if TYPE_CHECKING: + from collections.abc import Sequence + + +def project_root(config: pytest.Config) -> Path: + """Resolve the configured or uniquely targeted Python project root.""" + + configured_root = config.getoption("--python-project-harness-root") + if configured_root: + return Path(configured_root) + root = Path(config.rootpath) + return ( + _package_scoped_root( + root, + config.invocation_params.args, + invocation_dir=Path(config.invocation_params.dir), + ) + or root + ) + + +def _package_scoped_root( + pytest_root: Path, + args: Sequence[str], + *, + invocation_dir: Path, +) -> Path | None: + """Find one package root when pytest targets exactly one Python project.""" + + candidates: list[Path] = [] + for raw_arg in args: + if raw_arg.startswith("-"): + continue + raw_path = raw_arg.split("::", 1)[0] + if not raw_path: + continue + path = Path(raw_path) + if not path.is_absolute(): + path = invocation_dir / path + path = path.resolve() + if not path.exists(): + return None + candidate = _nearest_python_project(path, pytest_root.resolve()) + if candidate is None: + return None + candidates.append(candidate) + + if not candidates or any( + candidate != candidates[0] for candidate in candidates[1:] + ): + return None + candidate = candidates[0] + if candidate == pytest_root.resolve(): + return None + return candidate + + +def _nearest_python_project(path: Path, pytest_root: Path) -> Path | None: + """Return the nearest real Python project containing ``path``.""" + + start = path if path.is_dir() else path.parent + for candidate in (start, *start.parents): + if not _is_relative_to(candidate, pytest_root): + break + metadata = parse_python_project_metadata(candidate) + if metadata is not None and ( + metadata.has_project_table or metadata.has_build_system_table + ): + return candidate + if candidate == pytest_root: + break + return None + + +def _is_relative_to(path: Path, parent: Path) -> bool: + try: + path.relative_to(parent) + except ValueError: + return False + return True diff --git a/src/python_lang_project_harness/_python_compact.py b/src/asp_python/_python_compact.py similarity index 89% rename from src/python_lang_project_harness/_python_compact.py rename to src/asp_python/_python_compact.py index 72e648b..b7f88bb 100644 --- a/src/python_lang_project_harness/_python_compact.py +++ b/src/asp_python/_python_compact.py @@ -5,14 +5,14 @@ from dataclasses import dataclass from typing import Any -from python_lang_project_harness._python_outline import ( +from asp_python._python_outline import ( fallback_python_compact, ) -from python_lang_project_harness._python_projection import ( +from asp_python._python_projection import ( CompactPythonProjectionNode, collect_python_projection_node, ) -from python_lang_project_harness._python_source import parse_python_lines +from asp_python._python_source import parse_python_lines @dataclass(frozen=True) diff --git a/src/python_lang_project_harness/_python_expr.py b/src/asp_python/_python_expr.py similarity index 100% rename from src/python_lang_project_harness/_python_expr.py rename to src/asp_python/_python_expr.py diff --git a/src/python_lang_project_harness/_python_outline.py b/src/asp_python/_python_outline.py similarity index 99% rename from src/python_lang_project_harness/_python_outline.py rename to src/asp_python/_python_outline.py index d728b5f..18c021e 100644 --- a/src/python_lang_project_harness/_python_outline.py +++ b/src/asp_python/_python_outline.py @@ -4,7 +4,7 @@ import ast -from python_lang_project_harness._python_expr import ( +from asp_python._python_expr import ( _args, _aug_assign_stmt, _expr, diff --git a/src/python_lang_project_harness/_python_projection.py b/src/asp_python/_python_projection.py similarity index 85% rename from src/python_lang_project_harness/_python_projection.py rename to src/asp_python/_python_projection.py index c5cb082..fa83390 100644 --- a/src/python_lang_project_harness/_python_projection.py +++ b/src/asp_python/_python_projection.py @@ -4,12 +4,12 @@ import ast -from python_lang_project_harness._python_projection_extras import ( +from asp_python._python_projection_extras import ( append_decorator_projection_nodes, append_expression_effect_projection_nodes, ) -from python_lang_project_harness._python_projection_facts import projection_fact -from python_lang_project_harness._python_projection_model import ( +from asp_python._python_projection_facts import projection_fact +from asp_python._python_projection_model import ( CompactPythonProjectionNode, ) diff --git a/src/python_lang_project_harness/_python_projection_extras.py b/src/asp_python/_python_projection_extras.py similarity index 93% rename from src/python_lang_project_harness/_python_projection_extras.py rename to src/asp_python/_python_projection_extras.py index 90380fa..0ba3635 100644 --- a/src/python_lang_project_harness/_python_projection_extras.py +++ b/src/asp_python/_python_projection_extras.py @@ -5,8 +5,8 @@ import ast from collections.abc import Iterable -from python_lang_project_harness._python_expr import _expr -from python_lang_project_harness._python_projection_model import ( +from asp_python._python_expr import _expr +from asp_python._python_projection_model import ( CompactPythonProjectionNode, python_ast_node_read, ) diff --git a/src/python_lang_project_harness/_python_projection_facts.py b/src/asp_python/_python_projection_facts.py similarity index 98% rename from src/python_lang_project_harness/_python_projection_facts.py rename to src/asp_python/_python_projection_facts.py index a905ca1..ff11a00 100644 --- a/src/python_lang_project_harness/_python_projection_facts.py +++ b/src/asp_python/_python_projection_facts.py @@ -4,13 +4,13 @@ import ast -from python_lang_project_harness._python_expr import ( +from asp_python._python_expr import ( _args, _aug_assign_stmt, _expr, _with_items, ) -from python_lang_project_harness._python_projection_model import ( +from asp_python._python_projection_model import ( CompactPythonProjectionNode, node_fact, ) diff --git a/src/python_lang_project_harness/_python_projection_model.py b/src/asp_python/_python_projection_model.py similarity index 100% rename from src/python_lang_project_harness/_python_projection_model.py rename to src/asp_python/_python_projection_model.py diff --git a/src/python_lang_project_harness/_python_source.py b/src/asp_python/_python_source.py similarity index 100% rename from src/python_lang_project_harness/_python_source.py rename to src/asp_python/_python_source.py diff --git a/src/python_lang_project_harness/_render.py b/src/asp_python/_render.py similarity index 93% rename from src/python_lang_project_harness/_render.py rename to src/asp_python/_render.py index 8792452..5bd21e0 100644 --- a/src/python_lang_project_harness/_render.py +++ b/src/asp_python/_render.py @@ -22,7 +22,7 @@ PythonReasoningTreeNode, ) - from ._model import PythonHarnessFinding, PythonHarnessReport + from ._model import AspPythonFinding, AspPythonReport def _compact_render_fields(fields: dict[str, object]) -> dict[str, object]: @@ -50,7 +50,7 @@ def _line_protocol_field_value(value: object) -> str: def render_python_lang_harness( - report: PythonHarnessReport, + report: AspPythonReport, *, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_advice: bool = True, @@ -87,13 +87,13 @@ def render_python_lang_harness( return _render_ok_header(report) -def render_python_lang_harness_json(report: PythonHarnessReport) -> str: +def render_python_lang_harness_json(report: AspPythonReport) -> str: """Render a structured JSON diagnostic report for tool consumers.""" return json.dumps(report.to_dict(), separators=(",", ":"), sort_keys=True) -def render_python_lang_harness_advice(report: PythonHarnessReport) -> str: +def render_python_lang_harness_advice(report: AspPythonReport) -> str: """Render non-blocking advisory findings for agent-guided repair.""" advice_findings = _deduplicate_advice_findings( @@ -109,7 +109,7 @@ def render_python_lang_harness_advice(report: PythonHarnessReport) -> str: def render_python_reasoning_tree( - report: PythonHarnessReport, + report: AspPythonReport, *, max_nodes: int = 80, max_edges: int = 80, @@ -169,7 +169,7 @@ def render_python_reasoning_tree( return "\n".join(lines) + "\n" -def _render_ok_header(report: PythonHarnessReport) -> str: +def _render_ok_header(report: AspPythonReport) -> str: project_root = _report_project_root(report) target = ", ".join( _render_display_path(path, project_root=project_root) @@ -182,7 +182,7 @@ def _render_ok_header(report: PythonHarnessReport) -> str: def _render_findings( - findings: tuple[PythonHarnessFinding, ...], + findings: tuple[AspPythonFinding, ...], *, project_root: Path | None, ) -> str: @@ -196,8 +196,8 @@ def _render_findings( def _render_failure_frontier( - report: PythonHarnessReport, - findings: tuple[PythonHarnessFinding, ...], + report: AspPythonReport, + findings: tuple[AspPythonFinding, ...], *, project_root: Path | None, ) -> str: @@ -233,7 +233,7 @@ def _render_failure_frontier( def _failure_frontier_selector( - finding: PythonHarnessFinding, + finding: AspPythonFinding, *, project_root: Path | None, ) -> str | None: @@ -249,7 +249,7 @@ def _failure_frontier_text(value: str) -> str: def _render_finding( - finding: PythonHarnessFinding, + finding: AspPythonFinding, *, project_root: Path | None, ) -> str: @@ -280,7 +280,7 @@ def _render_finding( return rendered -def _software_criterion_labels(finding: PythonHarnessFinding) -> str: +def _software_criterion_labels(finding: AspPythonFinding) -> str: value = finding.labels.get("softwareCriteria") if not value: return "" @@ -298,7 +298,7 @@ def _format_software_criterion(criterion_id: str) -> str: return f"software-criterion/{criterion_id}" -def _reasoning_tree_import_roots(report: PythonHarnessReport) -> tuple[Path | str, ...]: +def _reasoning_tree_import_roots(report: AspPythonReport) -> tuple[Path | str, ...]: if report.project_resolution is None: return report.root_paths if report.project_resolution.source_paths: @@ -306,7 +306,7 @@ def _reasoning_tree_import_roots(report: PythonHarnessReport) -> tuple[Path | st return report.project_resolution.monitored_paths -def _report_project_root(report: PythonHarnessReport) -> Path | None: +def _report_project_root(report: AspPythonReport) -> Path | None: if report.project_resolution is None: return None return report.project_resolution.project_root @@ -480,10 +480,10 @@ def _render_display_path(path: Path | str, *, project_root: Path | None) -> str: def _deduplicate_advice_findings( - advice_findings: tuple[PythonHarnessFinding, ...], + advice_findings: tuple[AspPythonFinding, ...], *, - blocking_findings: tuple[PythonHarnessFinding, ...], -) -> tuple[PythonHarnessFinding, ...]: + blocking_findings: tuple[AspPythonFinding, ...], +) -> tuple[AspPythonFinding, ...]: blocking_keys = {_finding_key(finding) for finding in blocking_findings} return tuple( finding @@ -492,7 +492,7 @@ def _deduplicate_advice_findings( ) -def _finding_key(finding: PythonHarnessFinding) -> tuple[str, str | None, int, int]: +def _finding_key(finding: AspPythonFinding) -> tuple[str, str | None, int, int]: return ( finding.rule_id, finding.location.path, diff --git a/src/python_lang_project_harness/_rule_packs.py b/src/asp_python/_rule_packs.py similarity index 81% rename from src/python_lang_project_harness/_rule_packs.py rename to src/asp_python/_rule_packs.py index 1a5eca5..bb6cd90 100644 --- a/src/python_lang_project_harness/_rule_packs.py +++ b/src/asp_python/_rule_packs.py @@ -6,12 +6,12 @@ from typing import TYPE_CHECKING from ._agent_policy import PythonAgentPolicyRulePack -from ._model import PythonHarnessConfig, PythonLangRulePack, PythonRulePackDescriptor +from ._model import AspPythonConfig, PythonLangRulePack, PythonRulePackDescriptor from ._modern_design import PythonModernDesignRulePack from ._modularity import PythonModularityRulePack from ._project_config import ( apply_asp_project_discovery_config, - read_python_project_harness_config, + read_asp_python_config, ) from ._project_policy import PythonProjectPolicyRulePack from ._syntax import PythonSyntaxRulePack @@ -43,17 +43,17 @@ def python_rule_pack_descriptors() -> tuple[PythonRulePackDescriptor, ...]: ) -def default_python_harness_config() -> PythonHarnessConfig: +def default_python_harness_config() -> AspPythonConfig: """Return the default Python language harness configuration.""" - return PythonHarnessConfig(rule_packs=default_python_lang_rule_packs()) + return AspPythonConfig(rule_packs=default_python_lang_rule_packs()) def resolve_harness_config( - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, *, rule_packs: Sequence[PythonLangRulePack] | None, -) -> PythonHarnessConfig: +) -> AspPythonConfig: """Resolve caller config and one-shot rule-pack overrides.""" selected_config = default_python_harness_config() if config is None else config @@ -64,21 +64,19 @@ def resolve_harness_config( def resolve_project_harness_config( project_root: str | Path, - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, *, rule_packs: Sequence[PythonLangRulePack] | None, -) -> PythonHarnessConfig: +) -> AspPythonConfig: """Resolve config for project-root runs, including pyproject policy.""" - selected_config = ( - read_python_project_harness_config(project_root) if config is None else config - ) + selected_config = read_asp_python_config(project_root) if config is None else config resolved = resolve_harness_config(selected_config, rule_packs=rule_packs) return apply_asp_project_discovery_config(project_root, resolved) def selected_rule_packs( - config: PythonHarnessConfig, + config: AspPythonConfig, ) -> tuple[PythonLangRulePack, ...]: """Return configured rule packs, falling back to the default catalog.""" diff --git a/src/python_lang_project_harness/_runner.py b/src/asp_python/_runner.py similarity index 88% rename from src/python_lang_project_harness/_runner.py rename to src/asp_python/_runner.py index a498c88..6d9e55c 100644 --- a/src/python_lang_project_harness/_runner.py +++ b/src/asp_python/_runner.py @@ -9,11 +9,11 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity from python_lang_parser.parser import parse_python_file -from ._discovery import discover_python_files, python_project_harness_scope +from ._discovery import asp_python_scope, discover_python_files from ._model import ( - PythonHarnessConfig, - PythonHarnessFinding, - PythonHarnessReport, + AspPythonConfig, + AspPythonFinding, + AspPythonReport, PythonLangRulePack, ) @@ -21,16 +21,16 @@ from collections.abc import Sequence -def run_python_project_harness( +def run_asp_python( project_root: str | Path, *, - config: PythonHarnessConfig | None = None, + config: AspPythonConfig | None = None, rule_packs: Sequence[PythonLangRulePack] | None = None, include_tests: bool | None = None, source_dir_names: Sequence[str] | None = None, test_dir_names: Sequence[str] | None = None, extra_path_names: Sequence[str] | None = None, -) -> PythonHarnessReport: +) -> AspPythonReport: """Run the harness over conventional Python project paths.""" root = Path(project_root) @@ -48,7 +48,7 @@ def run_python_project_harness( rule_packs=rule_packs, ) selected_packs = selected_rule_packs(selected_config) - scope = python_project_harness_scope( + scope = asp_python_scope( root, include_tests=( selected_config.include_tests if include_tests is None else include_tests @@ -90,10 +90,10 @@ def run_python_project_harness( ) -def assert_python_project_harness_clean( +def assert_asp_python_clean( project_root: str | Path, *, - config: PythonHarnessConfig | None = None, + config: AspPythonConfig | None = None, rule_packs: Sequence[PythonLangRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_tests: bool | None = None, @@ -101,7 +101,7 @@ def assert_python_project_harness_clean( test_dir_names: Sequence[str] | None = None, extra_path_names: Sequence[str] | None = None, include_advice: bool = True, -) -> PythonHarnessReport: +) -> AspPythonReport: """Run the project harness and raise when configured-blocking findings exist.""" from ._rule_packs import resolve_project_harness_config @@ -111,7 +111,7 @@ def assert_python_project_harness_clean( config, rule_packs=rule_packs, ) - report = run_python_project_harness( + report = run_asp_python( project_root, config=selected_config, include_tests=include_tests, @@ -133,16 +133,16 @@ def assert_python_project_harness_clean( def run_python_lang_harness( paths: Sequence[str | Path], *, - config: PythonHarnessConfig | None = None, + config: AspPythonConfig | None = None, rule_packs: Sequence[PythonLangRulePack] | None = None, -) -> PythonHarnessReport: +) -> AspPythonReport: """Run the Python language harness over files or directories.""" if rule_packs == (): selected_config = ( replace(config, rule_packs=()) if config is not None - else PythonHarnessConfig(rule_packs=()) + else AspPythonConfig(rule_packs=()) ) selected_packs = () else: @@ -168,7 +168,7 @@ def run_python_lang_harness( for rule_pack in selected_packs for finding in rule_pack.evaluate(module) ) - return PythonHarnessReport( + return AspPythonReport( modules=modules, findings=_configured_findings(findings, config=selected_config), root_paths=tuple(str(path) for path in root_paths), @@ -181,11 +181,11 @@ def run_python_lang_harness( def assert_python_lang_harness_clean( paths: Sequence[str | Path], *, - config: PythonHarnessConfig | None = None, + config: AspPythonConfig | None = None, rule_packs: Sequence[PythonLangRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_advice: bool = True, -) -> PythonHarnessReport: +) -> AspPythonReport: """Run the harness and raise when configured-blocking findings are present.""" from ._rule_packs import resolve_harness_config @@ -204,10 +204,10 @@ def assert_python_lang_harness_clean( def _configured_findings( - findings: tuple[PythonHarnessFinding, ...], + findings: tuple[AspPythonFinding, ...], *, - config: PythonHarnessConfig, -) -> tuple[PythonHarnessFinding, ...]: + config: AspPythonConfig, +) -> tuple[AspPythonFinding, ...]: if not config.disabled_rule_ids: return findings return tuple( diff --git a/src/python_lang_project_harness/_runtime.py b/src/asp_python/_runtime.py similarity index 100% rename from src/python_lang_project_harness/_runtime.py rename to src/asp_python/_runtime.py diff --git a/src/python_lang_project_harness/_runtime_http.py b/src/asp_python/_runtime_http.py similarity index 100% rename from src/python_lang_project_harness/_runtime_http.py rename to src/asp_python/_runtime_http.py diff --git a/src/python_lang_project_harness/_semantic_graph_fact_collect.py b/src/asp_python/_semantic_graph_fact_collect.py similarity index 100% rename from src/python_lang_project_harness/_semantic_graph_fact_collect.py rename to src/asp_python/_semantic_graph_fact_collect.py diff --git a/src/python_lang_project_harness/_semantic_graph_fact_model.py b/src/asp_python/_semantic_graph_fact_model.py similarity index 100% rename from src/python_lang_project_harness/_semantic_graph_fact_model.py rename to src/asp_python/_semantic_graph_fact_model.py diff --git a/src/python_lang_project_harness/_semantic_graph_fact_render.py b/src/asp_python/_semantic_graph_fact_render.py similarity index 100% rename from src/python_lang_project_harness/_semantic_graph_fact_render.py rename to src/asp_python/_semantic_graph_fact_render.py diff --git a/src/python_lang_project_harness/_semantic_graph_fact_render_fields.py b/src/asp_python/_semantic_graph_fact_render_fields.py similarity index 100% rename from src/python_lang_project_harness/_semantic_graph_fact_render_fields.py rename to src/asp_python/_semantic_graph_fact_render_fields.py diff --git a/src/python_lang_project_harness/_semantic_graph_facts.py b/src/asp_python/_semantic_graph_facts.py similarity index 100% rename from src/python_lang_project_harness/_semantic_graph_facts.py rename to src/asp_python/_semantic_graph_facts.py diff --git a/src/python_lang_project_harness/_semantic_graph_project_collect.py b/src/asp_python/_semantic_graph_project_collect.py similarity index 100% rename from src/python_lang_project_harness/_semantic_graph_project_collect.py rename to src/asp_python/_semantic_graph_project_collect.py diff --git a/src/python_lang_project_harness/_semantic_graph_project_render.py b/src/asp_python/_semantic_graph_project_render.py similarity index 100% rename from src/python_lang_project_harness/_semantic_graph_project_render.py rename to src/asp_python/_semantic_graph_project_render.py diff --git a/src/python_lang_project_harness/_semantic_language.py b/src/asp_python/_semantic_language.py similarity index 94% rename from src/python_lang_project_harness/_semantic_language.py rename to src/asp_python/_semantic_language.py index b71cf1f..7b53a03 100644 --- a/src/python_lang_project_harness/_semantic_language.py +++ b/src/asp_python/_semantic_language.py @@ -13,7 +13,6 @@ from ._semantic_provider_doctor import _provider_identity from ._semantic_query_pack import python_query_pack_descriptor -_PYTHON_CHECK_METHODS = ("check/changed", "check/full") _PYTHON_QUERY_METHODS = ( "query", "query/exact-selector-native-v1", @@ -54,7 +53,6 @@ def python_semantic_language_registration() -> dict[str, Any]: "methods": [ *_PYTHON_SEARCH_METHODS, *_PYTHON_QUERY_METHODS, - *_PYTHON_CHECK_METHODS, *_PYTHON_AST_PATCH_METHODS, *_PYTHON_EVIDENCE_METHODS, *_PYTHON_AGENT_METHODS, @@ -73,15 +71,6 @@ def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS ] descriptors.extend(python_query_method_descriptors()) - descriptors.extend( - { - "method": method, - "command": "check", - "supportsJson": True, - "supportsCompact": True, - } - for method in _PYTHON_CHECK_METHODS - ) descriptors.extend( { "method": method, @@ -111,7 +100,7 @@ def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: "input": "provider project root", "outputSchemaIds": [ids.SEMANTIC_GRAPH_TURBO_REQUEST_SCHEMA_ID], "packetSchemas": ["semantic-graph-turbo-request.v1"], - "clients": ["asp-graph-turbo"], + "clients": ["asp-python-graphs"], "supportsJson": True, "supportsCompact": True, }, diff --git a/src/python_lang_project_harness/_semantic_language_benchmark.py b/src/asp_python/_semantic_language_benchmark.py similarity index 100% rename from src/python_lang_project_harness/_semantic_language_benchmark.py rename to src/asp_python/_semantic_language_benchmark.py diff --git a/src/python_lang_project_harness/_semantic_language_catalog.py b/src/asp_python/_semantic_language_catalog.py similarity index 100% rename from src/python_lang_project_harness/_semantic_language_catalog.py rename to src/asp_python/_semantic_language_catalog.py diff --git a/src/python_lang_project_harness/_semantic_language_ids.py b/src/asp_python/_semantic_language_ids.py similarity index 100% rename from src/python_lang_project_harness/_semantic_language_ids.py rename to src/asp_python/_semantic_language_ids.py diff --git a/src/python_lang_project_harness/_semantic_language_invocation.py b/src/asp_python/_semantic_language_invocation.py similarity index 93% rename from src/python_lang_project_harness/_semantic_language_invocation.py rename to src/asp_python/_semantic_language_invocation.py index 18a6dc6..6df8d2f 100644 --- a/src/python_lang_project_harness/_semantic_language_invocation.py +++ b/src/asp_python/_semantic_language_invocation.py @@ -53,8 +53,6 @@ def _non_search_invocation(method: str) -> dict[str, list[str]]: "--workspace", "{workspace}", ], - "check/changed": [ids.PYTHON_BINARY, "check", "--changed", "{workspace}"], - "check/full": [ids.PYTHON_BINARY, "check", "--full", "{workspace}"], "ast-patch/dry-run": [ ids.PYTHON_BINARY, "ast-patch", diff --git a/src/python_lang_project_harness/_semantic_language_knowledge.py b/src/asp_python/_semantic_language_knowledge.py similarity index 100% rename from src/python_lang_project_harness/_semantic_language_knowledge.py rename to src/asp_python/_semantic_language_knowledge.py diff --git a/src/python_lang_project_harness/_semantic_language_query.py b/src/asp_python/_semantic_language_query.py similarity index 100% rename from src/python_lang_project_harness/_semantic_language_query.py rename to src/asp_python/_semantic_language_query.py diff --git a/src/python_lang_project_harness/_semantic_language_schemas.py b/src/asp_python/_semantic_language_schemas.py similarity index 100% rename from src/python_lang_project_harness/_semantic_language_schemas.py rename to src/asp_python/_semantic_language_schemas.py diff --git a/src/python_lang_project_harness/_semantic_projection.py b/src/asp_python/_semantic_projection.py similarity index 100% rename from src/python_lang_project_harness/_semantic_projection.py rename to src/asp_python/_semantic_projection.py diff --git a/src/python_lang_project_harness/_semantic_provider_doctor.py b/src/asp_python/_semantic_provider_doctor.py similarity index 100% rename from src/python_lang_project_harness/_semantic_provider_doctor.py rename to src/asp_python/_semantic_provider_doctor.py diff --git a/src/python_lang_project_harness/_semantic_query_pack.py b/src/asp_python/_semantic_query_pack.py similarity index 100% rename from src/python_lang_project_harness/_semantic_query_pack.py rename to src/asp_python/_semantic_query_pack.py diff --git a/src/python_lang_project_harness/_semantic_query_packet.py b/src/asp_python/_semantic_query_packet.py similarity index 100% rename from src/python_lang_project_harness/_semantic_query_packet.py rename to src/asp_python/_semantic_query_packet.py diff --git a/src/python_lang_project_harness/_semantic_search.py b/src/asp_python/_semantic_search.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search.py rename to src/asp_python/_semantic_search.py diff --git a/src/python_lang_project_harness/_semantic_search_callsite_hits.py b/src/asp_python/_semantic_search_callsite_hits.py similarity index 96% rename from src/python_lang_project_harness/_semantic_search_callsite_hits.py rename to src/asp_python/_semantic_search_callsite_hits.py index 82158b2..0361ab0 100644 --- a/src/python_lang_project_harness/_semantic_search_callsite_hits.py +++ b/src/asp_python/_semantic_search_callsite_hits.py @@ -11,11 +11,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonCall - from ._model import PythonHarnessReport + from ._model import AspPythonReport def callsite_hits( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, query: str, ) -> list[dict[str, Any]]: diff --git a/src/python_lang_project_harness/_semantic_search_cli.py b/src/asp_python/_semantic_search_cli.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_cli.py rename to src/asp_python/_semantic_search_cli.py diff --git a/src/python_lang_project_harness/_semantic_search_common.py b/src/asp_python/_semantic_search_common.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_common.py rename to src/asp_python/_semantic_search_common.py diff --git a/src/python_lang_project_harness/_semantic_search_deps.py b/src/asp_python/_semantic_search_deps.py similarity index 98% rename from src/python_lang_project_harness/_semantic_search_deps.py rename to src/asp_python/_semantic_search_deps.py index ac376df..d2b1df3 100644 --- a/src/python_lang_project_harness/_semantic_search_deps.py +++ b/src/asp_python/_semantic_search_deps.py @@ -14,7 +14,7 @@ from python_lang_parser import PythonProjectDependency - from ._model import PythonHarnessReport + from ._model import AspPythonReport def dependency_node( @@ -91,7 +91,7 @@ def version_scope( def dependency_usage_hits( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, package_query: str, ) -> list[dict[str, Any]]: diff --git a/src/python_lang_project_harness/_semantic_search_findings.py b/src/asp_python/_semantic_search_findings.py similarity index 88% rename from src/python_lang_project_harness/_semantic_search_findings.py rename to src/asp_python/_semantic_search_findings.py index 050f8cf..b4d9065 100644 --- a/src/python_lang_project_harness/_semantic_search_findings.py +++ b/src/asp_python/_semantic_search_findings.py @@ -13,11 +13,11 @@ from ._semantic_search_model import MAX_FINDINGS if TYPE_CHECKING: - from ._model import PythonHarnessFinding, PythonHarnessReport + from ._model import AspPythonFinding, AspPythonReport def finding_facts( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, *, owner_paths: set[str] | None = None, @@ -25,7 +25,7 @@ def finding_facts( """Return grouped harness findings.""" counter: Counter[tuple[str, str, str, str]] = Counter() - finding_by_key: dict[tuple[str, str, str, str], PythonHarnessFinding] = {} + finding_by_key: dict[tuple[str, str, str, str], AspPythonFinding] = {} for finding in report.findings: path = semantic_search_display_path(finding.location.path or ".", project_root) if owner_paths is not None and path not in owner_paths: diff --git a/src/python_lang_project_harness/_semantic_search_graph_render.py b/src/asp_python/_semantic_search_graph_render.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_graph_render.py rename to src/asp_python/_semantic_search_graph_render.py diff --git a/src/python_lang_project_harness/_semantic_search_hits.py b/src/asp_python/_semantic_search_hits.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_hits.py rename to src/asp_python/_semantic_search_hits.py diff --git a/src/python_lang_project_harness/_semantic_search_import_routes.py b/src/asp_python/_semantic_search_import_routes.py similarity index 96% rename from src/python_lang_project_harness/_semantic_search_import_routes.py rename to src/asp_python/_semantic_search_import_routes.py index c17cbe2..e25c186 100644 --- a/src/python_lang_project_harness/_semantic_search_import_routes.py +++ b/src/asp_python/_semantic_search_import_routes.py @@ -10,11 +10,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonModuleReport - from ._project_policy_context import PythonHarnessReport + from ._project_policy_context import AspPythonReport def import_definition_routes( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, module: PythonModuleReport, terms: list[str], @@ -61,7 +61,7 @@ def _route_for_import( def _report_owner_paths( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, ) -> list[str]: owner_paths: list[str] = [] diff --git a/src/python_lang_project_harness/_semantic_search_import_test_hits.py b/src/asp_python/_semantic_search_import_test_hits.py similarity index 95% rename from src/python_lang_project_harness/_semantic_search_import_test_hits.py rename to src/asp_python/_semantic_search_import_test_hits.py index b3c991c..2fca847 100644 --- a/src/python_lang_project_harness/_semantic_search_import_test_hits.py +++ b/src/asp_python/_semantic_search_import_test_hits.py @@ -10,11 +10,11 @@ from .verification.facts import is_test_path if TYPE_CHECKING: - from ._model import PythonHarnessReport + from ._model import AspPythonReport def import_hits( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, query: str, ) -> list[dict[str, Any]]: @@ -32,7 +32,7 @@ def import_hits( def test_path_hits( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, query: str, ) -> list[dict[str, Any]]: diff --git a/src/python_lang_project_harness/_semantic_search_ingest.py b/src/asp_python/_semantic_search_ingest.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_ingest.py rename to src/asp_python/_semantic_search_ingest.py diff --git a/src/python_lang_project_harness/_semantic_search_ingest_fast.py b/src/asp_python/_semantic_search_ingest_fast.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_ingest_fast.py rename to src/asp_python/_semantic_search_ingest_fast.py diff --git a/src/python_lang_project_harness/_semantic_search_item_lines.py b/src/asp_python/_semantic_search_item_lines.py similarity index 98% rename from src/python_lang_project_harness/_semantic_search_item_lines.py rename to src/asp_python/_semantic_search_item_lines.py index bc19db2..215ecfa 100644 --- a/src/python_lang_project_harness/_semantic_search_item_lines.py +++ b/src/asp_python/_semantic_search_item_lines.py @@ -9,11 +9,11 @@ from ._semantic_search_items import owner_item_query_payload if TYPE_CHECKING: - from ._project_policy_context import PythonHarnessReport + from ._project_policy_context import AspPythonReport def owner_item_query_lines( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, owner_path: str, item_query: str, diff --git a/src/python_lang_project_harness/_semantic_search_items.py b/src/asp_python/_semantic_search_items.py similarity index 98% rename from src/python_lang_project_harness/_semantic_search_items.py rename to src/asp_python/_semantic_search_items.py index 1c2bb91..bf6403c 100644 --- a/src/python_lang_project_harness/_semantic_search_items.py +++ b/src/asp_python/_semantic_search_items.py @@ -22,11 +22,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonModuleReport, PythonSymbol - from ._model import PythonHarnessReport + from ._model import AspPythonReport def owner_item_query_payload( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, owner_path: str, item_query: str | None, @@ -81,7 +81,7 @@ def owner_item_query_payload( def owner_item_semantic_query_packet( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, owner_path: str, item_query: str, @@ -112,7 +112,7 @@ def owner_item_semantic_query_packet( def _selector_resolved_owner_items( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, owner_path: str, selector: str | None, @@ -208,7 +208,7 @@ def _owner_item_semantic_query_packet( def _module_for_owner( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, owner_path: str, ) -> PythonModuleReport | None: diff --git a/src/python_lang_project_harness/_semantic_search_knowledge_facts.py b/src/asp_python/_semantic_search_knowledge_facts.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_knowledge_facts.py rename to src/asp_python/_semantic_search_knowledge_facts.py diff --git a/src/python_lang_project_harness/_semantic_search_lexical_fast.py b/src/asp_python/_semantic_search_lexical_fast.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_lexical_fast.py rename to src/asp_python/_semantic_search_lexical_fast.py diff --git a/src/python_lang_project_harness/_semantic_search_model.py b/src/asp_python/_semantic_search_model.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_model.py rename to src/asp_python/_semantic_search_model.py diff --git a/src/python_lang_project_harness/_semantic_search_owner_fast.py b/src/asp_python/_semantic_search_owner_fast.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_owner_fast.py rename to src/asp_python/_semantic_search_owner_fast.py diff --git a/src/python_lang_project_harness/_semantic_search_owners.py b/src/asp_python/_semantic_search_owners.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_owners.py rename to src/asp_python/_semantic_search_owners.py diff --git a/src/python_lang_project_harness/_semantic_search_packages.py b/src/asp_python/_semantic_search_packages.py similarity index 97% rename from src/python_lang_project_harness/_semantic_search_packages.py rename to src/asp_python/_semantic_search_packages.py index 6945cdd..7bc2b19 100644 --- a/src/python_lang_project_harness/_semantic_search_packages.py +++ b/src/asp_python/_semantic_search_packages.py @@ -14,7 +14,7 @@ from python_lang_parser import PythonReasoningTreeFacts - from ._model import PythonHarnessReport + from ._model import AspPythonReport def project_name(facts: PythonReasoningTreeFacts) -> str: @@ -34,7 +34,7 @@ def dependencies(facts: PythonReasoningTreeFacts): def workspace_packages( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, owner_count: int, diff --git a/src/python_lang_project_harness/_semantic_search_packet.py b/src/asp_python/_semantic_search_packet.py similarity index 98% rename from src/python_lang_project_harness/_semantic_search_packet.py rename to src/asp_python/_semantic_search_packet.py index cf76261..0ef304e 100644 --- a/src/python_lang_project_harness/_semantic_search_packet.py +++ b/src/asp_python/_semantic_search_packet.py @@ -13,11 +13,11 @@ ) if TYPE_CHECKING: - from ._model import PythonHarnessReport + from ._model import AspPythonReport def build_python_semantic_search_packet( - report: PythonHarnessReport, + report: AspPythonReport, options: PythonSemanticSearchOptions, ) -> dict[str, Any]: """Build a language-neutral semantic-search packet from Python parser facts.""" diff --git a/src/python_lang_project_harness/_semantic_search_policy.py b/src/asp_python/_semantic_search_policy.py similarity index 93% rename from src/python_lang_project_harness/_semantic_search_policy.py rename to src/asp_python/_semantic_search_policy.py index 6c736ff..0ae0ef3 100644 --- a/src/python_lang_project_harness/_semantic_search_policy.py +++ b/src/asp_python/_semantic_search_policy.py @@ -6,14 +6,12 @@ from typing import Any from ._agent_policy_catalog import python_agent_policy_rules -from ._model import PythonHarnessReport, PythonHarnessRule +from ._model import AspPythonReport, AspPythonRule from ._project_policy_catalog import python_project_policy_rules from ._semantic_search_common import compact_fields, header, path_hit -PROJECT_POLICY_CATALOG_OWNER = ( - "src/python_lang_project_harness/_project_policy_catalog.py" -) -AGENT_POLICY_CATALOG_OWNER = "src/python_lang_project_harness/_agent_policy_catalog.py" +PROJECT_POLICY_CATALOG_OWNER = "src/asp_python/_project_policy_catalog.py" +AGENT_POLICY_CATALOG_OWNER = "src/asp_python/_agent_policy_catalog.py" PROJECT_POLICY_TEST_PATHS = ( "tests/unit/harness/project_policy/test_catalog.py", @@ -29,7 +27,7 @@ def policy_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: Any, project_root: Path, query: str, @@ -111,7 +109,7 @@ def _policy_handles() -> list[dict[str, Any]]: def _rule_handle( - rule: PythonHarnessRule, + rule: AspPythonRule, *, owner_path: str, test_paths: tuple[str, ...], @@ -142,7 +140,7 @@ def _rule_handle( } -def _rule_aliases(rule: PythonHarnessRule) -> list[str]: +def _rule_aliases(rule: AspPythonRule) -> list[str]: return sorted( { rule.rule_id.lower(), @@ -155,7 +153,7 @@ def _rule_aliases(rule: PythonHarnessRule) -> list[str]: ) -def _rule_query_terms(rule: PythonHarnessRule) -> list[str]: +def _rule_query_terms(rule: AspPythonRule) -> list[str]: return sorted( { rule.rule_id, diff --git a/src/python_lang_project_harness/_semantic_search_prefilter.py b/src/asp_python/_semantic_search_prefilter.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prefilter.py rename to src/asp_python/_semantic_search_prefilter.py diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_file_scan.py b/src/asp_python/_semantic_search_prefilter_file_scan.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prefilter_file_scan.py rename to src/asp_python/_semantic_search_prefilter_file_scan.py diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_path.py b/src/asp_python/_semantic_search_prefilter_path.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prefilter_path.py rename to src/asp_python/_semantic_search_prefilter_path.py diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_process.py b/src/asp_python/_semantic_search_prefilter_process.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prefilter_process.py rename to src/asp_python/_semantic_search_prefilter_process.py diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_rank.py b/src/asp_python/_semantic_search_prefilter_rank.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prefilter_rank.py rename to src/asp_python/_semantic_search_prefilter_rank.py diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_result.py b/src/asp_python/_semantic_search_prefilter_result.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prefilter_result.py rename to src/asp_python/_semantic_search_prefilter_result.py diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_select.py b/src/asp_python/_semantic_search_prefilter_select.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prefilter_select.py rename to src/asp_python/_semantic_search_prefilter_select.py diff --git a/src/python_lang_project_harness/_semantic_search_prefilter_tools.py b/src/asp_python/_semantic_search_prefilter_tools.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prefilter_tools.py rename to src/asp_python/_semantic_search_prefilter_tools.py diff --git a/src/python_lang_project_harness/_semantic_search_prime_fast.py b/src/asp_python/_semantic_search_prime_fast.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_prime_fast.py rename to src/asp_python/_semantic_search_prime_fast.py diff --git a/src/python_lang_project_harness/_semantic_search_profiles.py b/src/asp_python/_semantic_search_profiles.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_profiles.py rename to src/asp_python/_semantic_search_profiles.py diff --git a/src/python_lang_project_harness/_semantic_search_public_external_type_hits.py b/src/asp_python/_semantic_search_public_external_type_hits.py similarity index 96% rename from src/python_lang_project_harness/_semantic_search_public_external_type_hits.py rename to src/asp_python/_semantic_search_public_external_type_hits.py index 085c6bc..22dba7a 100644 --- a/src/python_lang_project_harness/_semantic_search_public_external_type_hits.py +++ b/src/asp_python/_semantic_search_public_external_type_hits.py @@ -20,11 +20,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonModuleReport - from ._model import PythonHarnessReport + from ._model import AspPythonReport def public_external_type_hits( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, package: str, ) -> list[dict[str, Any]]: diff --git a/src/python_lang_project_harness/_semantic_search_public_external_type_imports.py b/src/asp_python/_semantic_search_public_external_type_imports.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_public_external_type_imports.py rename to src/asp_python/_semantic_search_public_external_type_imports.py diff --git a/src/python_lang_project_harness/_semantic_search_public_external_type_model.py b/src/asp_python/_semantic_search_public_external_type_model.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_public_external_type_model.py rename to src/asp_python/_semantic_search_public_external_type_model.py diff --git a/src/python_lang_project_harness/_semantic_search_public_external_type_surfaces.py b/src/asp_python/_semantic_search_public_external_type_surfaces.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_public_external_type_surfaces.py rename to src/asp_python/_semantic_search_public_external_type_surfaces.py diff --git a/src/python_lang_project_harness/_semantic_search_public_external_types.py b/src/asp_python/_semantic_search_public_external_types.py similarity index 99% rename from src/python_lang_project_harness/_semantic_search_public_external_types.py rename to src/asp_python/_semantic_search_public_external_types.py index 44b8cf9..6da6707 100644 --- a/src/python_lang_project_harness/_semantic_search_public_external_types.py +++ b/src/asp_python/_semantic_search_public_external_types.py @@ -23,11 +23,11 @@ PythonReasoningTreeFacts, ) - from ._model import PythonHarnessReport + from ._model import AspPythonReport def public_external_types_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query: str, diff --git a/src/python_lang_project_harness/_semantic_search_reasoning.py b/src/asp_python/_semantic_search_reasoning.py similarity index 98% rename from src/python_lang_project_harness/_semantic_search_reasoning.py rename to src/asp_python/_semantic_search_reasoning.py index acb6ee5..26fcb64 100644 --- a/src/python_lang_project_harness/_semantic_search_reasoning.py +++ b/src/asp_python/_semantic_search_reasoning.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from ._model import PythonHarnessReport + from ._model import AspPythonReport _OWNER_TESTS_RETURNS = ["covering-tests", "test-entrypoints", "fixtures"] @@ -24,7 +24,7 @@ def reasoning_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, options: PythonSemanticSearchOptions, diff --git a/src/python_lang_project_harness/_semantic_search_render.py b/src/asp_python/_semantic_search_render.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_render.py rename to src/asp_python/_semantic_search_render.py diff --git a/src/python_lang_project_harness/_semantic_search_render_compact.py b/src/asp_python/_semantic_search_render_compact.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_render_compact.py rename to src/asp_python/_semantic_search_render_compact.py diff --git a/src/python_lang_project_harness/_semantic_search_render_flow.py b/src/asp_python/_semantic_search_render_flow.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_render_flow.py rename to src/asp_python/_semantic_search_render_flow.py diff --git a/src/python_lang_project_harness/_semantic_search_render_lines.py b/src/asp_python/_semantic_search_render_lines.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_render_lines.py rename to src/asp_python/_semantic_search_render_lines.py diff --git a/src/python_lang_project_harness/_semantic_search_symbol_hits.py b/src/asp_python/_semantic_search_symbol_hits.py similarity index 97% rename from src/python_lang_project_harness/_semantic_search_symbol_hits.py rename to src/asp_python/_semantic_search_symbol_hits.py index a29a387..5bd8236 100644 --- a/src/python_lang_project_harness/_semantic_search_symbol_hits.py +++ b/src/asp_python/_semantic_search_symbol_hits.py @@ -16,11 +16,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts, PythonSymbol - from ._model import PythonHarnessReport + from ._model import AspPythonReport def api_hits( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query: str, @@ -47,7 +47,7 @@ def api_hits( def symbol_hits( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, query: str, *, diff --git a/src/python_lang_project_harness/_semantic_search_text_hits.py b/src/asp_python/_semantic_search_text_hits.py similarity index 98% rename from src/python_lang_project_harness/_semantic_search_text_hits.py rename to src/asp_python/_semantic_search_text_hits.py index b4d4ece..86c8356 100644 --- a/src/python_lang_project_harness/_semantic_search_text_hits.py +++ b/src/asp_python/_semantic_search_text_hits.py @@ -17,11 +17,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from ._model import PythonHarnessReport + from ._model import AspPythonReport def text_hits( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query: str, @@ -46,7 +46,7 @@ def text_hits( def fuzzy_text_hits( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query: str, diff --git a/src/python_lang_project_harness/_semantic_search_view_actions.py b/src/asp_python/_semantic_search_view_actions.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_view_actions.py rename to src/asp_python/_semantic_search_view_actions.py diff --git a/src/python_lang_project_harness/_semantic_search_view_core.py b/src/asp_python/_semantic_search_view_core.py similarity index 98% rename from src/python_lang_project_harness/_semantic_search_view_core.py rename to src/asp_python/_semantic_search_view_core.py index 96b2c64..a118622 100644 --- a/src/python_lang_project_harness/_semantic_search_view_core.py +++ b/src/asp_python/_semantic_search_view_core.py @@ -29,11 +29,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from ._model import PythonHarnessReport + from ._model import AspPythonReport def workspace_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, ) -> dict[str, Any]: @@ -66,7 +66,7 @@ def workspace_payload( def prime_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, ) -> dict[str, Any]: @@ -103,7 +103,7 @@ def prime_payload( def owner_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query: str, diff --git a/src/python_lang_project_harness/_semantic_search_view_deps_imports.py b/src/asp_python/_semantic_search_view_deps_imports.py similarity index 98% rename from src/python_lang_project_harness/_semantic_search_view_deps_imports.py rename to src/asp_python/_semantic_search_view_deps_imports.py index 47ea6bf..8a63170 100644 --- a/src/python_lang_project_harness/_semantic_search_view_deps_imports.py +++ b/src/asp_python/_semantic_search_view_deps_imports.py @@ -22,11 +22,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from ._model import PythonHarnessReport + from ._model import AspPythonReport def dependency_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query: str, @@ -64,7 +64,7 @@ def dependency_payload( def import_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query: str, diff --git a/src/python_lang_project_harness/_semantic_search_view_hits.py b/src/asp_python/_semantic_search_view_hits.py similarity index 97% rename from src/python_lang_project_harness/_semantic_search_view_hits.py rename to src/asp_python/_semantic_search_view_hits.py index b6fdba5..acc8cd6 100644 --- a/src/python_lang_project_harness/_semantic_search_view_hits.py +++ b/src/asp_python/_semantic_search_view_hits.py @@ -33,11 +33,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from ._model import PythonHarnessReport + from ._model import AspPythonReport def tests_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query: str, @@ -72,7 +72,7 @@ def tests_payload( def text_payload( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, options: PythonSemanticSearchOptions, diff --git a/src/python_lang_project_harness/_semantic_search_view_ingest.py b/src/asp_python/_semantic_search_view_ingest.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_view_ingest.py rename to src/asp_python/_semantic_search_view_ingest.py diff --git a/src/python_lang_project_harness/_semantic_search_view_knowledge.py b/src/asp_python/_semantic_search_view_knowledge.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_view_knowledge.py rename to src/asp_python/_semantic_search_view_knowledge.py diff --git a/src/python_lang_project_harness/_semantic_search_view_lexical_queries.py b/src/asp_python/_semantic_search_view_lexical_queries.py similarity index 97% rename from src/python_lang_project_harness/_semantic_search_view_lexical_queries.py rename to src/asp_python/_semantic_search_view_lexical_queries.py index 8afcbfd..21789aa 100644 --- a/src/python_lang_project_harness/_semantic_search_view_lexical_queries.py +++ b/src/asp_python/_semantic_search_view_lexical_queries.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from ._model import PythonHarnessReport + from ._model import AspPythonReport from ._semantic_search_model import PythonSemanticSearchOptions @@ -25,7 +25,7 @@ def normalized_query_terms(options: PythonSemanticSearchOptions) -> list[str]: def lexical_query_hits_by_term( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query_terms: list[str], @@ -45,7 +45,7 @@ def lexical_query_hits_by_term( def fuzzy_lexical_query_hits_by_term( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, query_terms: list[str], diff --git a/src/python_lang_project_harness/_semantic_search_view_lexical_synthesis.py b/src/asp_python/_semantic_search_view_lexical_synthesis.py similarity index 100% rename from src/python_lang_project_harness/_semantic_search_view_lexical_synthesis.py rename to src/asp_python/_semantic_search_view_lexical_synthesis.py diff --git a/src/python_lang_project_harness/_semantic_search_views.py b/src/asp_python/_semantic_search_views.py similarity index 97% rename from src/python_lang_project_harness/_semantic_search_views.py rename to src/asp_python/_semantic_search_views.py index 9951242..67a549c 100644 --- a/src/python_lang_project_harness/_semantic_search_views.py +++ b/src/asp_python/_semantic_search_views.py @@ -22,11 +22,11 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from ._model import PythonHarnessReport + from ._model import AspPythonReport def payload_for_view( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, options: PythonSemanticSearchOptions, @@ -37,7 +37,7 @@ def payload_for_view( def _payload_for_view( - report: PythonHarnessReport, + report: AspPythonReport, facts: PythonReasoningTreeFacts, project_root: Path, options: PythonSemanticSearchOptions, diff --git a/src/python_lang_project_harness/_semantic_selector_identity.py b/src/asp_python/_semantic_selector_identity.py similarity index 100% rename from src/python_lang_project_harness/_semantic_selector_identity.py rename to src/asp_python/_semantic_selector_identity.py diff --git a/src/python_lang_project_harness/_semantic_syntax_refs.py b/src/asp_python/_semantic_syntax_refs.py similarity index 100% rename from src/python_lang_project_harness/_semantic_syntax_refs.py rename to src/asp_python/_semantic_syntax_refs.py diff --git a/src/python_lang_project_harness/_source.py b/src/asp_python/_source.py similarity index 100% rename from src/python_lang_project_harness/_source.py rename to src/asp_python/_source.py diff --git a/src/python_lang_project_harness/_syntax.py b/src/asp_python/_syntax.py similarity index 91% rename from src/python_lang_project_harness/_syntax.py rename to src/asp_python/_syntax.py index e8f0f1f..1644094 100644 --- a/src/python_lang_project_harness/_syntax.py +++ b/src/asp_python/_syntax.py @@ -8,7 +8,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity from ._model import ( - PythonHarnessFinding, + AspPythonFinding, PythonRulePackDescriptor, ) from ._syntax_catalog import SYNTAX_PACK_ID, syntax_rule @@ -35,14 +35,14 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Evaluate parse diagnostics for one module report.""" for diagnostic in report.diagnostics: if diagnostic.severity != PythonDiagnosticSeverity.ERROR: continue rule = syntax_rule(diagnostic.code) - yield PythonHarnessFinding( + yield AspPythonFinding( rule_id=rule.rule_id, pack_id=self.pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_syntax_catalog.py b/src/asp_python/_syntax_catalog.py similarity index 88% rename from src/python_lang_project_harness/_syntax_catalog.py rename to src/asp_python/_syntax_catalog.py index 0363f35..72babc2 100644 --- a/src/python_lang_project_harness/_syntax_catalog.py +++ b/src/asp_python/_syntax_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule SYNTAX_PACK_ID = "python.syntax" PYTHON_SYNTAX_INVALID = "python.syntax.invalid" @@ -17,7 +17,7 @@ "domain": "syntax", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PYTHON_SYNTAX_INVALID, pack_id=SYNTAX_PACK_ID, severity=PythonDiagnosticSeverity.ERROR, @@ -25,7 +25,7 @@ requirement="Python modules must parse with CPython native syntax before project rules run.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PYTHON_COMPILE_INVALID, pack_id=SYNTAX_PACK_ID, severity=PythonDiagnosticSeverity.ERROR, @@ -37,19 +37,19 @@ _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_syntax_rules() -> tuple[PythonHarnessRule, ...]: +def python_syntax_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for native Python syntax rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def syntax_rule(rule_id: str) -> PythonHarnessRule: +def syntax_rule(rule_id: str) -> AspPythonRule: """Return one syntax rule descriptor by stable rule id.""" rule = _RULE_BY_ID.get(rule_id) if rule is not None: return rule - return PythonHarnessRule( + return AspPythonRule( rule_id=rule_id, pack_id=SYNTAX_PACK_ID, severity=PythonDiagnosticSeverity.ERROR, diff --git a/src/python_lang_project_harness/_test_layout.py b/src/asp_python/_test_layout.py similarity index 78% rename from src/python_lang_project_harness/_test_layout.py rename to src/asp_python/_test_layout.py index 10fdff2..9116749 100644 --- a/src/python_lang_project_harness/_test_layout.py +++ b/src/asp_python/_test_layout.py @@ -5,8 +5,8 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -from ._discovery import python_project_harness_scope -from ._model import PythonHarnessFinding, PythonRulePackDescriptor +from ._discovery import asp_python_scope +from ._model import AspPythonFinding, PythonRulePackDescriptor from ._test_layout_bloat import bloated_unit_test_findings from ._test_layout_catalog import TEST_LAYOUT_PACK_ID from ._test_layout_entries import tests_root_entry_findings @@ -36,22 +36,20 @@ def descriptor(self) -> PythonRulePackDescriptor: default_mode="blocking", ) - def evaluate(self, report: PythonModuleReport) -> Iterable[PythonHarnessFinding]: + def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: """Module-level parser reports do not carry project layout authority.""" return () - def evaluate_project(self, project_root: Path) -> Iterable[PythonHarnessFinding]: + def evaluate_project(self, project_root: Path) -> Iterable[AspPythonFinding]: """Evaluate project-level pytest layout rules.""" - return self.evaluate_project_resolution( - python_project_harness_scope(project_root) - ) + return self.evaluate_project_resolution(asp_python_scope(project_root)) def evaluate_project_resolution( self, scope: PythonProjectHarnessScope, - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate project-level pytest layout rules for monitored test roots.""" return _test_layout_findings(scope, (), self.pack_id) @@ -60,7 +58,7 @@ def evaluate_project_modules( self, scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], - ) -> Iterable[PythonHarnessFinding]: + ) -> Iterable[AspPythonFinding]: """Evaluate pytest layout rules using parser-owned module facts.""" return _test_layout_findings(scope, modules, self.pack_id) @@ -70,8 +68,8 @@ def _test_layout_findings( scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: - findings: list[PythonHarnessFinding] = [] +) -> tuple[AspPythonFinding, ...]: + findings: list[AspPythonFinding] = [] for tests_dir in scope.test_paths: if not tests_dir.exists(): continue diff --git a/src/python_lang_project_harness/_test_layout_bloat.py b/src/asp_python/_test_layout_bloat.py similarity index 94% rename from src/python_lang_project_harness/_test_layout_bloat.py rename to src/asp_python/_test_layout_bloat.py index b11d041..81b8088 100644 --- a/src/python_lang_project_harness/_test_layout_bloat.py +++ b/src/asp_python/_test_layout_bloat.py @@ -7,7 +7,7 @@ from python_lang_parser import python_symbol_is_test_function -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._source import path_location from ._test_layout_catalog import ( MAX_UNIT_TEST_EFFECTIVE_LINES, @@ -28,7 +28,7 @@ def bloated_unit_test_findings( scope: PythonProjectHarnessScope, modules: Sequence[PythonModuleReport], pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return oversized unit-test leaf findings from parser-owned facts.""" unit_dirs = tuple( @@ -39,7 +39,7 @@ def bloated_unit_test_findings( if not unit_dirs: return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for report in sorted(modules, key=lambda item: item.path or ""): if report.path is None or not report.is_valid or report.shape is None: continue @@ -85,9 +85,9 @@ def _bloated_unit_test_finding( effective_code_lines: int, test_functions: int, source_line: str | None, -) -> PythonHarnessFinding: +) -> AspPythonFinding: rule = test_layout_rule(PY_TEST_R003) - return PythonHarnessFinding( + return AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_test_layout_catalog.py b/src/asp_python/_test_layout_catalog.py similarity index 90% rename from src/python_lang_project_harness/_test_layout_catalog.py rename to src/asp_python/_test_layout_catalog.py index 2e760d2..c384f7d 100644 --- a/src/python_lang_project_harness/_test_layout_catalog.py +++ b/src/asp_python/_test_layout_catalog.py @@ -6,7 +6,7 @@ from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity -from ._model import PythonHarnessRule +from ._model import AspPythonRule TEST_LAYOUT_PACK_ID = "python.test_layout" PY_TEST_R001 = "PY-TEST-R001" @@ -35,7 +35,7 @@ "domain": "pytest-layout", } _RULES = ( - PythonHarnessRule( + AspPythonRule( rule_id=PY_TEST_R001, pack_id=TEST_LAYOUT_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -43,7 +43,7 @@ requirement="Move pytest modules under `tests/unit/` or `tests/integration/` so the project harness owns suite shape.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_TEST_R002, pack_id=TEST_LAYOUT_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -51,7 +51,7 @@ requirement="Keep tests root limited to harness configuration and owned suite directories.", labels=dict(_RULE_LABELS), ), - PythonHarnessRule( + AspPythonRule( rule_id=PY_TEST_R003, pack_id=TEST_LAYOUT_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, @@ -63,13 +63,13 @@ _RULE_BY_ID = {rule.rule_id: rule for rule in _RULES} -def python_test_layout_rules() -> tuple[PythonHarnessRule, ...]: +def python_test_layout_rules() -> tuple[AspPythonRule, ...]: """Return compact metadata for the default pytest-layout rules.""" return tuple(replace(rule, labels=dict(rule.labels)) for rule in _RULES) -def test_layout_rule(rule_id: str) -> PythonHarnessRule: +def test_layout_rule(rule_id: str) -> AspPythonRule: """Return one pytest-layout rule descriptor by stable rule id.""" return _RULE_BY_ID[rule_id] diff --git a/src/python_lang_project_harness/_test_layout_config.py b/src/asp_python/_test_layout_config.py similarity index 100% rename from src/python_lang_project_harness/_test_layout_config.py rename to src/asp_python/_test_layout_config.py diff --git a/src/python_lang_project_harness/_test_layout_entries.py b/src/asp_python/_test_layout_entries.py similarity index 93% rename from src/python_lang_project_harness/_test_layout_entries.py rename to src/asp_python/_test_layout_entries.py index eacd9d6..a6f433f 100644 --- a/src/python_lang_project_harness/_test_layout_entries.py +++ b/src/asp_python/_test_layout_entries.py @@ -5,7 +5,7 @@ from pathlib import Path from typing import TYPE_CHECKING -from ._model import PythonHarnessFinding +from ._model import AspPythonFinding from ._source import path_location, source_line from ._test_layout_catalog import ( ALLOWED_TEST_DIR_NAMES, @@ -26,12 +26,12 @@ def tests_root_entry_findings( tests_dir: Path, pack_id: str, modules: Sequence[PythonModuleReport] = (), -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return tests-root ownership findings for one test root.""" policy = load_test_layout_policy(tests_dir) modules_by_path = _modules_by_path(modules) - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] for path in sorted(tests_dir.iterdir(), key=lambda item: item.as_posix()): name = path.name if name.startswith("."): @@ -61,9 +61,9 @@ def _root_pytest_file_finding( path: Path, pack_id: str, modules_by_path: Mapping[Path, PythonModuleReport], -) -> PythonHarnessFinding: +) -> AspPythonFinding: rule = test_layout_rule(PY_TEST_R001) - return PythonHarnessFinding( + return AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, @@ -81,9 +81,9 @@ def _unexpected_tests_root_entry_finding( path: Path, pack_id: str, modules_by_path: Mapping[Path, PythonModuleReport], -) -> PythonHarnessFinding: +) -> AspPythonFinding: rule = test_layout_rule(PY_TEST_R002) - return PythonHarnessFinding( + return AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/_tree_sitter_query.py b/src/asp_python/_tree_sitter_query.py similarity index 95% rename from src/python_lang_project_harness/_tree_sitter_query.py rename to src/asp_python/_tree_sitter_query.py index 6849909..901f2de 100644 --- a/src/python_lang_project_harness/_tree_sitter_query.py +++ b/src/asp_python/_tree_sitter_query.py @@ -16,13 +16,13 @@ if TYPE_CHECKING: from ._cli_args import ProtocolArgs - from ._model import PythonHarnessReport + from ._model import AspPythonReport def write_tree_sitter_query_response( args: ProtocolArgs, *, - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, stdout: TextIO, ) -> None: diff --git a/src/python_lang_project_harness/_tree_sitter_query_catalog.py b/src/asp_python/_tree_sitter_query_catalog.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_catalog.py rename to src/asp_python/_tree_sitter_query_catalog.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_model.py b/src/asp_python/_tree_sitter_query_model.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_model.py rename to src/asp_python/_tree_sitter_query_model.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_packet.py b/src/asp_python/_tree_sitter_query_packet.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_packet.py rename to src/asp_python/_tree_sitter_query_packet.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_packet_rows.py b/src/asp_python/_tree_sitter_query_packet_rows.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_packet_rows.py rename to src/asp_python/_tree_sitter_query_packet_rows.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_predicates.py b/src/asp_python/_tree_sitter_query_predicates.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_predicates.py rename to src/asp_python/_tree_sitter_query_predicates.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_projection.py b/src/asp_python/_tree_sitter_query_projection.py similarity index 99% rename from src/python_lang_project_harness/_tree_sitter_query_projection.py rename to src/asp_python/_tree_sitter_query_projection.py index 612d7f0..b5e422d 100644 --- a/src/python_lang_project_harness/_tree_sitter_query_projection.py +++ b/src/asp_python/_tree_sitter_query_projection.py @@ -50,11 +50,11 @@ ) if TYPE_CHECKING: - from ._model import PythonHarnessReport + from ._model import AspPythonReport def project_tree_sitter_query( - report: PythonHarnessReport, + report: AspPythonReport, project_root: Path, query_node_types: tuple[str, ...], captures: tuple[str, ...], diff --git a/src/python_lang_project_harness/_tree_sitter_query_projection_capture.py b/src/asp_python/_tree_sitter_query_projection_capture.py similarity index 100% rename from src/python_lang_project_harness/_tree_sitter_query_projection_capture.py rename to src/asp_python/_tree_sitter_query_projection_capture.py diff --git a/src/python_lang_project_harness/_tree_sitter_query_projection_source.py b/src/asp_python/_tree_sitter_query_projection_source.py similarity index 97% rename from src/python_lang_project_harness/_tree_sitter_query_projection_source.py rename to src/asp_python/_tree_sitter_query_projection_source.py index 87ab6c9..e3ad033 100644 --- a/src/python_lang_project_harness/_tree_sitter_query_projection_source.py +++ b/src/asp_python/_tree_sitter_query_projection_source.py @@ -9,7 +9,7 @@ from ._tree_sitter_query_model import SyntaxQuerySelector if TYPE_CHECKING: - from ._model import PythonHarnessReport + from ._model import AspPythonReport @dataclass(frozen=True) @@ -45,7 +45,7 @@ def resolve_selector_source( def syntax_sources( - report: PythonHarnessReport, + report: AspPythonReport, resolved_selector_source: ResolvedSelectorSource | None, ) -> list[SyntaxSource]: if resolved_selector_source is not None: diff --git a/src/python_lang_project_harness/_version.py b/src/asp_python/_version.py similarity index 86% rename from src/python_lang_project_harness/_version.py rename to src/asp_python/_version.py index c9189ed..9f9fbd8 100644 --- a/src/python_lang_project_harness/_version.py +++ b/src/asp_python/_version.py @@ -5,7 +5,7 @@ from importlib.metadata import PackageNotFoundError, version from typing import Final -_DISTRIBUTION_NAME: Final = "python-lang-project-harness" +_DISTRIBUTION_NAME: Final = "asp-python" def _installed_version() -> str: diff --git a/src/python_lang_project_harness/agent_readability/__init__.py b/src/asp_python/agent_readability/__init__.py similarity index 100% rename from src/python_lang_project_harness/agent_readability/__init__.py rename to src/asp_python/agent_readability/__init__.py diff --git a/src/python_lang_project_harness/agent_readability/_boundaries.py b/src/asp_python/agent_readability/_boundaries.py similarity index 100% rename from src/python_lang_project_harness/agent_readability/_boundaries.py rename to src/asp_python/agent_readability/_boundaries.py diff --git a/src/python_lang_project_harness/agent_readability/_software_criteria.py b/src/asp_python/agent_readability/_software_criteria.py similarity index 100% rename from src/python_lang_project_harness/agent_readability/_software_criteria.py rename to src/asp_python/agent_readability/_software_criteria.py diff --git a/src/python_lang_project_harness/agent_readability/algorithm_shape.py b/src/asp_python/agent_readability/algorithm_shape.py similarity index 96% rename from src/python_lang_project_harness/agent_readability/algorithm_shape.py rename to src/asp_python/agent_readability/algorithm_shape.py index c020d36..1048007 100644 --- a/src/python_lang_project_harness/agent_readability/algorithm_shape.py +++ b/src/asp_python/agent_readability/algorithm_shape.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from .._agent_policy_catalog import PY_AGENT_R009, agent_policy_rule -from .._model import PythonHarnessFinding +from .._model import AspPythonFinding from ._boundaries import ( agent_readability_function_is_boundary, agent_readability_public_class_scopes, @@ -29,12 +29,12 @@ def agent_algorithm_shape_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return machine-readability advice for public algorithm boundaries.""" if not agent_readability_report_is_in_scope(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R009) public_class_scopes = agent_readability_public_class_scopes(report) for symbol in report.symbols: @@ -52,7 +52,7 @@ def agent_algorithm_shape_findings( continue criterion_ids = _agent_algorithm_software_criteria(profile) findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/agent_readability/function_compactness.py b/src/asp_python/agent_readability/function_compactness.py similarity index 95% rename from src/python_lang_project_harness/agent_readability/function_compactness.py rename to src/asp_python/agent_readability/function_compactness.py index 0d53ca7..fc579d5 100644 --- a/src/python_lang_project_harness/agent_readability/function_compactness.py +++ b/src/asp_python/agent_readability/function_compactness.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from .._agent_policy_catalog import PY_AGENT_R010, agent_policy_rule -from .._model import PythonHarnessFinding +from .._model import AspPythonFinding from ._boundaries import ( agent_readability_function_is_boundary, agent_readability_public_class_scopes, @@ -29,12 +29,12 @@ def agent_function_compactness_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return machine-readability advice for broad public function bodies.""" if not agent_readability_report_is_in_scope(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R010) public_class_scopes = agent_readability_public_class_scopes(report) for symbol in report.symbols: @@ -52,7 +52,7 @@ def agent_function_compactness_findings( if not profile: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/agent_readability/native_idioms.py b/src/asp_python/agent_readability/native_idioms.py similarity index 95% rename from src/python_lang_project_harness/agent_readability/native_idioms.py rename to src/asp_python/agent_readability/native_idioms.py index 7025af4..ad6e5e4 100644 --- a/src/python_lang_project_harness/agent_readability/native_idioms.py +++ b/src/asp_python/agent_readability/native_idioms.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING from .._agent_policy_catalog import PY_AGENT_R011, agent_policy_rule -from .._model import PythonHarnessFinding +from .._model import AspPythonFinding from ._boundaries import ( agent_readability_function_is_boundary, agent_readability_public_class_scopes, @@ -24,12 +24,12 @@ def agent_native_idiom_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return repair advice for public functions that miss native Python idioms.""" if not agent_readability_report_is_in_scope(report): return () - findings: list[PythonHarnessFinding] = [] + findings: list[AspPythonFinding] = [] rule = agent_policy_rule(PY_AGENT_R011) public_class_scopes = agent_readability_public_class_scopes(report) for symbol in report.symbols: @@ -46,7 +46,7 @@ def agent_native_idiom_findings( if not profile: continue findings.append( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/agent_readability/type_shapes.py b/src/asp_python/agent_readability/type_shapes.py similarity index 95% rename from src/python_lang_project_harness/agent_readability/type_shapes.py rename to src/asp_python/agent_readability/type_shapes.py index 7469ed3..f911a52 100644 --- a/src/python_lang_project_harness/agent_readability/type_shapes.py +++ b/src/asp_python/agent_readability/type_shapes.py @@ -7,7 +7,7 @@ from python_lang_parser import python_symbol_is_public_class from .._agent_policy_catalog import PY_AGENT_R012, agent_policy_rule -from .._model import PythonHarnessFinding +from .._model import AspPythonFinding from ._boundaries import agent_readability_report_is_in_scope if TYPE_CHECKING: @@ -17,14 +17,14 @@ def agent_type_shape_findings( report: PythonModuleReport, pack_id: str, -) -> tuple[PythonHarnessFinding, ...]: +) -> tuple[AspPythonFinding, ...]: """Return repair advice for public classes that hide data shape.""" if not agent_readability_report_is_in_scope(report): return () rule = agent_policy_rule(PY_AGENT_R012) return tuple( - PythonHarnessFinding( + AspPythonFinding( rule_id=rule.rule_id, pack_id=pack_id, severity=rule.severity, diff --git a/src/python_lang_project_harness/harness-rules.md b/src/asp_python/harness-rules.md similarity index 100% rename from src/python_lang_project_harness/harness-rules.md rename to src/asp_python/harness-rules.md diff --git a/src/python_lang_project_harness/harness.py b/src/asp_python/harness.py similarity index 87% rename from src/python_lang_project_harness/harness.py rename to src/asp_python/harness.py index 0c2be85..1cabfe5 100644 --- a/src/python_lang_project_harness/harness.py +++ b/src/asp_python/harness.py @@ -5,20 +5,20 @@ from ._agent_policy import PythonAgentPolicyRulePack from ._agent_policy_catalog import python_agent_policy_rules from ._agent_snapshot import ( - render_python_project_harness_agent_snapshot, - render_python_project_harness_agent_snapshot_with_config, + render_asp_python_agent_snapshot, + render_asp_python_agent_snapshot_with_config, ) from ._cli import run_cli, run_cli_from_env from ._discovery import ( + asp_python_paths, + asp_python_scope, discover_python_files, - python_project_harness_paths, - python_project_harness_scope, ) from ._model import ( - PythonHarnessConfig, - PythonHarnessFinding, - PythonHarnessReport, - PythonHarnessRule, + AspPythonConfig, + AspPythonFinding, + AspPythonReport, + AspPythonRule, PythonLangRulePack, PythonProjectHarnessScope, PythonRulePackDescriptor, @@ -26,10 +26,10 @@ from ._modern_design import PythonModernDesignRulePack from ._modern_design_catalog import python_modern_design_rules from ._modularity import PythonModularityRulePack, python_modularity_rules -from ._project_config import read_python_project_harness_config +from ._project_config import read_asp_python_config from ._project_policy import PythonProjectPolicyRulePack from ._project_policy_catalog import python_project_policy_rules -from ._pytest import python_project_harness_test +from ._pytest import asp_python_test from ._render import ( render_python_lang_harness, render_python_lang_harness_advice, @@ -42,10 +42,10 @@ python_rule_pack_descriptors, ) from ._runner import ( + assert_asp_python_clean, assert_python_lang_harness_clean, - assert_python_project_harness_clean, + run_asp_python, run_python_lang_harness, - run_python_project_harness, ) from ._semantic_language import ( python_semantic_language_registration, @@ -107,10 +107,10 @@ __all__ = [ "PythonAgentPolicyRulePack", - "PythonHarnessConfig", - "PythonHarnessFinding", - "PythonHarnessReport", - "PythonHarnessRule", + "AspPythonConfig", + "AspPythonFinding", + "AspPythonReport", + "AspPythonRule", "PythonLangRulePack", "PythonModernDesignRulePack", "PythonModularityRulePack", @@ -145,7 +145,7 @@ "PythonVerificationTaskState", "PythonVerificationWaiver", "assert_python_lang_harness_clean", - "assert_python_project_harness_clean", + "assert_asp_python_clean", "build_python_semantic_search_packet", "default_python_harness_config", "default_python_lang_rule_packs", @@ -153,11 +153,11 @@ "python_agent_policy_rules", "python_modern_design_rules", "python_modularity_rules", - "python_project_harness_paths", - "python_project_harness_scope", - "python_project_harness_test", + "asp_python_paths", + "asp_python_scope", + "asp_python_test", "python_project_policy_rules", - "read_python_project_harness_config", + "read_asp_python_config", "python_semantic_language_registration", "python_rule_pack_descriptors", "python_syntax_rules", @@ -173,8 +173,8 @@ "render_python_lang_harness_advice", "render_python_lang_harness_json", "render_python_reasoning_tree", - "render_python_project_harness_agent_snapshot", - "render_python_project_harness_agent_snapshot_with_config", + "render_asp_python_agent_snapshot", + "render_asp_python_agent_snapshot_with_config", "render_python_semantic_search_packet", "render_python_semantic_search_packet_json", "render_python_verification_performance_index_json", @@ -190,6 +190,6 @@ "run_cli", "run_cli_from_env", "run_python_lang_harness", - "run_python_project_harness", + "run_asp_python", "semantic_language_registry_document", ] diff --git a/src/python_lang_project_harness/py.typed b/src/asp_python/py.typed similarity index 100% rename from src/python_lang_project_harness/py.typed rename to src/asp_python/py.typed diff --git a/src/python_lang_project_harness/pytest.py b/src/asp_python/pytest.py similarity index 57% rename from src/python_lang_project_harness/pytest.py rename to src/asp_python/pytest.py index 467c098..4a8ce7b 100644 --- a/src/python_lang_project_harness/pytest.py +++ b/src/asp_python/pytest.py @@ -2,8 +2,8 @@ from __future__ import annotations -from ._pytest import python_project_harness_test +from ._pytest import asp_python_test __all__ = [ - "python_project_harness_test", + "asp_python_test", ] diff --git a/src/asp_python/pytest_plugin.py b/src/asp_python/pytest_plugin.py new file mode 100644 index 0000000..3ac65e6 --- /dev/null +++ b/src/asp_python/pytest_plugin.py @@ -0,0 +1,79 @@ +"""Pytest plugin entry point for dev-dependency harness mounting.""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from ._pytest_plugin_options import ( + ENABLE_OPTION, + EXTRA_PATH_OPTION, + NO_ADVICE_OPTION, + NO_TESTS_OPTION, + SOURCE_DIR_OPTION, + TEST_DIR_OPTION, + add_options, + blocking_severities, + harness_config, + optional_tuple, +) +from ._pytest_plugin_project import project_root +from ._runner import assert_asp_python_clean + + +def pytest_addoption(parser: pytest.Parser) -> None: + """Register Python project harness pytest options.""" + + add_options(parser) + + +def pytest_collection_modifyitems( + session: pytest.Session, + config: pytest.Config, + items: list[pytest.Item], +) -> None: + """Insert one explicit harness item when the plugin option is enabled.""" + + if not config.getoption(ENABLE_OPTION): + return + item = PythonProjectHarnessItem.from_parent( + session, + name="python-project-harness", + nodeid="python-project-harness", + ) + items.insert(0, item) + + +class PythonProjectHarnessItem(pytest.Item): + """Pytest item that runs the parser-backed project harness.""" + + def runtest(self) -> None: + """Run the configured project harness and raise a compact assertion.""" + + assert_asp_python_clean( + project_root(self.config), + config=harness_config(self.config), + severities=blocking_severities(self.config), + include_tests=not self.config.getoption(NO_TESTS_OPTION), + source_dir_names=optional_tuple(self.config.getoption(SOURCE_DIR_OPTION)), + test_dir_names=optional_tuple(self.config.getoption(TEST_DIR_OPTION)), + extra_path_names=optional_tuple(self.config.getoption(EXTRA_PATH_OPTION)), + include_advice=not self.config.getoption(NO_ADVICE_OPTION), + ) + + def repr_failure( + self, + excinfo: pytest.ExceptionInfo[BaseException], + style: str | None = None, + ) -> str: + """Return compact harness assertion text without pytest traceback noise.""" + + if isinstance(excinfo.value, AssertionError): + return str(excinfo.value) + return super().repr_failure(excinfo, style=style) + + def reportinfo(self) -> tuple[Path, int, str]: + """Return stable report metadata for pytest output.""" + + return (Path("python-project-harness"), 0, "python project harness") diff --git a/src/python_lang_project_harness/verification/__init__.py b/src/asp_python/verification/__init__.py similarity index 100% rename from src/python_lang_project_harness/verification/__init__.py rename to src/asp_python/verification/__init__.py diff --git a/src/python_lang_project_harness/verification/facts.py b/src/asp_python/verification/facts.py similarity index 96% rename from src/python_lang_project_harness/verification/facts.py rename to src/asp_python/verification/facts.py index c26bd2d..6e8a818 100644 --- a/src/python_lang_project_harness/verification/facts.py +++ b/src/asp_python/verification/facts.py @@ -26,11 +26,11 @@ PythonReasoningTreeNode, ) - from .._model import PythonHarnessReport + from .._model import AspPythonReport def verification_reasoning_tree_facts( - report: PythonHarnessReport, + report: AspPythonReport, ) -> PythonReasoningTreeFacts: """Return parser-owned reasoning-tree facts for one harness report.""" @@ -43,7 +43,7 @@ def verification_reasoning_tree_facts( ) -def verification_project_root(report: PythonHarnessReport) -> Path: +def verification_project_root(report: AspPythonReport) -> Path: """Return the project root represented by a harness report.""" if report.project_resolution is not None: @@ -144,7 +144,7 @@ def parser_visible_owner_responsibilities( metadata = facts.project_metadata metadata_path = project_metadata_owner_path(facts, project_root=project_root) if metadata is not None and metadata_path is not None: - if metadata.pytest_options.enables_python_project_harness or any( + if metadata.pytest_options.enables_asp_python or any( entry.group == "pytest11" for entry in metadata.entry_points ): _append_owner_responsibilities( @@ -238,7 +238,7 @@ def _append_owner_responsibilities( def _reasoning_tree_import_roots( - report: PythonHarnessReport, + report: AspPythonReport, ) -> tuple[Path | str, ...]: if report.project_resolution is None: return report.root_paths diff --git a/src/python_lang_project_harness/verification/indices.py b/src/asp_python/verification/indices.py similarity index 100% rename from src/python_lang_project_harness/verification/indices.py rename to src/asp_python/verification/indices.py diff --git a/src/python_lang_project_harness/verification/model.py b/src/asp_python/verification/model.py similarity index 100% rename from src/python_lang_project_harness/verification/model.py rename to src/asp_python/verification/model.py diff --git a/src/python_lang_project_harness/verification/obligations.py b/src/asp_python/verification/obligations.py similarity index 100% rename from src/python_lang_project_harness/verification/obligations.py rename to src/asp_python/verification/obligations.py diff --git a/src/python_lang_project_harness/verification/planner.py b/src/asp_python/verification/planner.py similarity index 97% rename from src/python_lang_project_harness/verification/planner.py rename to src/asp_python/verification/planner.py index 6661146..a96eb5f 100644 --- a/src/python_lang_project_harness/verification/planner.py +++ b/src/asp_python/verification/planner.py @@ -6,10 +6,10 @@ from pathlib import Path from typing import TYPE_CHECKING -from .._model import PythonHarnessConfig, PythonHarnessReport +from .._model import AspPythonConfig, AspPythonReport from .._render import _render_display_path from .._rule_packs import resolve_project_harness_config -from .._runner import run_python_project_harness +from .._runner import run_asp_python from .facts import ( matched_dependency_signals, node_evidence, @@ -50,19 +50,19 @@ def plan_python_project_verification( def plan_python_project_verification_with_config( project_root: str | Path, - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, ) -> PythonVerificationPlan: """Plan verification obligations with an explicit harness config.""" root = Path(project_root) selected_config = resolve_project_harness_config(root, config, rule_packs=None) - report = run_python_project_harness(root, config=selected_config) + report = run_asp_python(root, config=selected_config) return plan_python_project_verification_report(report, selected_config) def plan_python_project_verification_report( - report: PythonHarnessReport, - config: PythonHarnessConfig, + report: AspPythonReport, + config: AspPythonConfig, ) -> PythonVerificationPlan: """Plan verification obligations from an already-built harness report.""" diff --git a/src/python_lang_project_harness/verification/profile_index.py b/src/asp_python/verification/profile_index.py similarity index 96% rename from src/python_lang_project_harness/verification/profile_index.py rename to src/asp_python/verification/profile_index.py index 8dc3fc0..f007e53 100644 --- a/src/python_lang_project_harness/verification/profile_index.py +++ b/src/asp_python/verification/profile_index.py @@ -7,7 +7,7 @@ from .._render import _render_display_path from .._rule_packs import resolve_project_harness_config -from .._runner import run_python_project_harness +from .._runner import run_asp_python from .facts import ( entry_point_owner_paths, is_test_path, @@ -32,7 +32,7 @@ if TYPE_CHECKING: from python_lang_parser import PythonReasoningTreeFacts - from .._model import PythonHarnessConfig, PythonHarnessReport + from .._model import AspPythonConfig, AspPythonReport def build_python_verification_profile_index( @@ -45,19 +45,19 @@ def build_python_verification_profile_index( def build_python_verification_profile_index_with_config( project_root: str | Path, - config: PythonHarnessConfig | None, + config: AspPythonConfig | None, ) -> PythonVerificationProfileIndex: """Build profile candidates with an explicit harness config.""" root = Path(project_root) selected_config = resolve_project_harness_config(root, config, rule_packs=None) - report = run_python_project_harness(root, config=selected_config) + report = run_asp_python(root, config=selected_config) return build_python_verification_profile_index_report(report, selected_config) def build_python_verification_profile_index_report( - report: PythonHarnessReport, - config: PythonHarnessConfig, + report: AspPythonReport, + config: AspPythonConfig, ) -> PythonVerificationProfileIndex: """Build profile candidates from an already-built harness report.""" @@ -142,7 +142,7 @@ def _append_metadata_candidates( candidate_index_by_path=candidate_index_by_path, ) if metadata_owner_path is not None and ( - metadata.pytest_options.enables_python_project_harness + metadata.pytest_options.enables_asp_python or any(entry.group == "pytest11" for entry in metadata.entry_points) ): _append_candidate( diff --git a/src/python_lang_project_harness/verification/render.py b/src/asp_python/verification/render.py similarity index 100% rename from src/python_lang_project_harness/verification/render.py rename to src/asp_python/verification/render.py diff --git a/src/python_lang_project_harness/verification/report.py b/src/asp_python/verification/report.py similarity index 100% rename from src/python_lang_project_harness/verification/report.py rename to src/asp_python/verification/report.py diff --git a/src/python_lang_parser/_project_model.py b/src/python_lang_parser/_project_model.py index c93ecfd..dbcc427 100644 --- a/src/python_lang_parser/_project_model.py +++ b/src/python_lang_parser/_project_model.py @@ -103,7 +103,7 @@ class PythonPytestOptions: addopts: tuple[str, ...] = () @property - def enables_python_project_harness(self) -> bool: + def enables_asp_python(self) -> bool: """Return whether pytest addopts mounts the project harness plugin.""" return "--python-project-harness" in self.addopts @@ -113,7 +113,7 @@ def to_dict(self) -> dict[str, object]: return { "addopts": list(self.addopts), - "enables_python_project_harness": self.enables_python_project_harness, + "enables_asp_python": self.enables_asp_python, } diff --git a/src/python_lang_parser/_version.py b/src/python_lang_parser/_version.py index a337c8f..9d75aff 100644 --- a/src/python_lang_parser/_version.py +++ b/src/python_lang_parser/_version.py @@ -5,7 +5,7 @@ from importlib.metadata import PackageNotFoundError, version from typing import Final -_DISTRIBUTION_NAME: Final = "python-lang-project-harness" +_DISTRIBUTION_NAME: Final = "asp-python" def _installed_version() -> str: diff --git a/src/python_lang_project_harness/_cli.py b/src/python_lang_project_harness/_cli.py deleted file mode 100644 index 8c0f580..0000000 --- a/src/python_lang_project_harness/_cli.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Command-line execution for the Python project harness.""" - -from __future__ import annotations - -import sys -from pathlib import Path -from typing import TextIO - -from ._cli_args import CliOptions, ProtocolArgs, help_text -from ._cli_protocol import run_protocol_cli - - -def run_cli_from_env() -> int: - """Run the CLI using process environment arguments.""" - - from ._dev_command_log import start_dev_command_log - - args = sys.argv[1:] - log = start_dev_command_log(args, Path.cwd()) - try: - if args == ["serve"]: - from ._runtime import serve_provider_runtime - - exit_code = serve_provider_runtime(Path.cwd()) - log.finish(exit_code) - return exit_code - stdin = "" if sys.stdin.isatty() else sys.stdin.read() - exit_code = run_cli(args, stdin=stdin) - log.finish(exit_code) - return exit_code - except Exception: - log.finish(2) - raise - - -def run_cli( - args: list[str] | tuple[str, ...], - *, - stdout: TextIO | None = None, - stderr: TextIO | None = None, - stdin: str | bytes | None = None, - cwd: Path | None = None, -) -> int: - """Run the default package-level Python harness CLI.""" - - selected_stdout = sys.stdout if stdout is None else stdout - selected_stderr = sys.stderr if stderr is None else stderr - selected_cwd = Path.cwd() if cwd is None else cwd - protocol_args = ProtocolArgs.parse(args) - if protocol_args is not None: - return run_protocol_cli( - protocol_args, - stdout=selected_stdout, - stderr=selected_stderr, - stdin="" if stdin is None else stdin, - cwd=selected_cwd, - ) - try: - options = CliOptions.parse(args) - if options.help: - selected_stdout.write(help_text()) - return 0 - project_root = options.project_root(cwd) - if not project_root.exists(): - raise ValueError(f"project root does not exist: {project_root}") - from ._render import render_python_lang_harness, render_python_lang_harness_json - from ._runner import run_python_project_harness - - config = options.harness_config(project_root) - report = run_python_project_harness( - project_root, - config=config, - include_tests=options.include_tests, - source_dir_names=options.source_dir_names, - test_dir_names=options.test_dir_names, - extra_path_names=options.extra_path_names, - ) - if options.json: - selected_stdout.write(render_python_lang_harness_json(report)) - selected_stdout.write("\n") - elif options.agent_snapshot: - from ._agent_snapshot import ( - render_python_project_harness_agent_snapshot_report, - ) - from ._model import PythonHarnessConfig - from ._project_config import read_python_project_harness_config - - selected_stdout.write( - render_python_project_harness_agent_snapshot_report( - report, - config=( - config - or read_python_project_harness_config(project_root) - or PythonHarnessConfig() - ), - ) - ) - else: - selected_stdout.write(render_python_lang_harness(report)) - return 0 if report.is_clean else 1 - except ValueError as error: - selected_stderr.write(f"{error}\n") - return 2 diff --git a/src/python_lang_project_harness/pytest_plugin.py b/src/python_lang_project_harness/pytest_plugin.py deleted file mode 100644 index e3c115e..0000000 --- a/src/python_lang_project_harness/pytest_plugin.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Pytest plugin entry point for dev-dependency harness mounting.""" - -from __future__ import annotations - -from dataclasses import replace -from pathlib import Path -from typing import TYPE_CHECKING - -import pytest - -from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity - -from ._model import PythonHarnessConfig -from ._project_config import read_python_project_harness_config -from ._runner import assert_python_project_harness_clean - -if TYPE_CHECKING: - from collections.abc import Sequence - - -_ENABLE_OPTION = "--python-project-harness" -_ROOT_OPTION = "--python-project-harness-root" -_NO_TESTS_OPTION = "--python-project-harness-no-tests" -_SOURCE_DIR_OPTION = "--python-project-harness-source-dir" -_TEST_DIR_OPTION = "--python-project-harness-test-dir" -_EXTRA_PATH_OPTION = "--python-project-harness-extra-path" -_DISABLE_RULE_OPTION = "--python-project-harness-disable-rule" -_BLOCK_RULE_OPTION = "--python-project-harness-block-rule" -_ERROR_ONLY_OPTION = "--python-project-harness-error-only" -_NO_ADVICE_OPTION = "--python-project-harness-no-advice" - - -def pytest_addoption(parser: pytest.Parser) -> None: - """Register Python project harness pytest options.""" - - group = parser.getgroup("python-lang-project-harness") - group.addoption( - _ENABLE_OPTION, - action="store_true", - default=False, - help="Collect and run the python-lang-project-harness policy test.", - ) - group.addoption( - _ROOT_OPTION, - action="store", - default=None, - metavar="PATH", - help="Project root for the harness test. Defaults to pytest rootdir.", - ) - group.addoption( - _NO_TESTS_OPTION, - action="store_true", - default=False, - help="Do not parse test files; pytest layout checks still run.", - ) - group.addoption( - _SOURCE_DIR_OPTION, - action="append", - default=[], - metavar="NAME", - help="Source directory name to scan. Can be provided more than once.", - ) - group.addoption( - _TEST_DIR_OPTION, - action="append", - default=[], - metavar="NAME", - help="Test directory name to scan. Can be provided more than once.", - ) - group.addoption( - _EXTRA_PATH_OPTION, - action="append", - default=[], - metavar="NAME", - help="Extra project path name to scan. Can be provided more than once.", - ) - group.addoption( - _DISABLE_RULE_OPTION, - action="append", - default=[], - metavar="RULE_ID", - help="Harness rule id to suppress. Can be provided more than once.", - ) - group.addoption( - _BLOCK_RULE_OPTION, - action="append", - default=[], - metavar="RULE_ID", - help="Harness rule id to treat as blocking. Can be provided more than once.", - ) - group.addoption( - _ERROR_ONLY_OPTION, - action="store_true", - default=False, - help="Only fail the pytest harness item for parser errors.", - ) - group.addoption( - _NO_ADVICE_OPTION, - action="store_true", - default=False, - help="Hide non-blocking advice from assertion output.", - ) - - -def pytest_collection_modifyitems( - session: pytest.Session, - config: pytest.Config, - items: list[pytest.Item], -) -> None: - """Insert one explicit harness item when the plugin option is enabled.""" - - if not config.getoption(_ENABLE_OPTION): - return - item = PythonProjectHarnessItem.from_parent( - session, - name="python-project-harness", - nodeid="python-project-harness", - ) - items.insert(0, item) - - -class PythonProjectHarnessItem(pytest.Item): - """Pytest item that runs the parser-backed project harness.""" - - def runtest(self) -> None: - """Run the configured project harness and raise a compact assertion.""" - - assert_python_project_harness_clean( - _project_root(self.config), - config=_harness_config(self.config), - severities=_blocking_severities(self.config), - include_tests=not self.config.getoption(_NO_TESTS_OPTION), - source_dir_names=_optional_tuple(self.config.getoption(_SOURCE_DIR_OPTION)), - test_dir_names=_optional_tuple(self.config.getoption(_TEST_DIR_OPTION)), - extra_path_names=_optional_tuple(self.config.getoption(_EXTRA_PATH_OPTION)), - include_advice=not self.config.getoption(_NO_ADVICE_OPTION), - ) - - def repr_failure( - self, - excinfo: pytest.ExceptionInfo[BaseException], - style: str | None = None, - ) -> str: - """Return compact harness assertion text without pytest traceback noise.""" - - if isinstance(excinfo.value, AssertionError): - return str(excinfo.value) - return super().repr_failure(excinfo, style=style) - - def reportinfo(self) -> tuple[Path, int, str]: - """Return stable report metadata for pytest output.""" - - return (Path("python-project-harness"), 0, "python project harness") - - -def _project_root(config: pytest.Config) -> Path: - configured_root = config.getoption(_ROOT_OPTION) - if configured_root: - return Path(configured_root) - return Path(config.rootpath) - - -def _blocking_severities( - config: pytest.Config, -) -> frozenset[PythonDiagnosticSeverity] | None: - if config.getoption(_ERROR_ONLY_OPTION): - return frozenset({PythonDiagnosticSeverity.ERROR}) - return None - - -def _harness_config(config: pytest.Config) -> PythonHarnessConfig | None: - disabled_rule_values = config.getoption(_DISABLE_RULE_OPTION) - blocking_rule_values = config.getoption(_BLOCK_RULE_OPTION) - if not disabled_rule_values and not blocking_rule_values: - return None - - base_config = read_python_project_harness_config(_project_root(config)) - selected_config = base_config if base_config is not None else PythonHarnessConfig() - return replace( - selected_config, - disabled_rule_ids=( - frozenset(disabled_rule_values) - if disabled_rule_values - else selected_config.disabled_rule_ids - ), - blocking_rule_ids=( - frozenset(blocking_rule_values) - if blocking_rule_values - else selected_config.blocking_rule_ids - ), - ) - - -def _optional_tuple(values: Sequence[str]) -> tuple[str, ...] | None: - return tuple(values) if values else None diff --git a/tests/unit/harness/agent_readability/test_native_idiom_binding_state.py b/tests/unit/harness/agent_readability/test_native_idiom_binding_state.py index 05c36f1..bbe51e1 100644 --- a/tests/unit/harness/agent_readability/test_native_idiom_binding_state.py +++ b/tests/unit/harness/agent_readability/test_native_idiom_binding_state.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_lang_harness +from asp_python import run_python_lang_harness if TYPE_CHECKING: from pathlib import Path diff --git a/tests/unit/harness/harness-rules.generated.md b/tests/unit/harness/harness-rules.generated.md index f2b7808..266d613 100644 --- a/tests/unit/harness/harness-rules.generated.md +++ b/tests/unit/harness/harness-rules.generated.md @@ -1,8 +1,8 @@ -# python-lang-project-harness +# asp-python ## Harness Rules -Generated from embedded `src/python_lang_project_harness/harness-rules.md`. +Generated from embedded `src/asp_python/harness-rules.md`. - **PY-AGENT-POLICY-001**: Requires library modules to declare concise intent docstrings for agent search and repair. - **PY-AGENT-POLICY-002**: Requires public callable boundaries to carry type annotations for native syntax reasoning. diff --git a/tests/unit/harness/project_policy/test_catalog.py b/tests/unit/harness/project_policy/test_catalog.py index ce758ca..fa57ee4 100644 --- a/tests/unit/harness/project_policy/test_catalog.py +++ b/tests/unit/harness/project_policy/test_catalog.py @@ -1,6 +1,6 @@ from __future__ import annotations -from python_lang_project_harness import ( +from asp_python import ( PythonProjectPolicyRulePack, python_project_policy_rules, ) diff --git a/tests/unit/harness/project_policy/test_layout.py b/tests/unit/harness/project_policy/test_layout.py index 1c3e36a..578928f 100644 --- a/tests/unit/harness/project_policy/test_layout.py +++ b/tests/unit/harness/project_policy/test_layout.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -16,7 +16,7 @@ def test_project_policy_noops_without_pyproject(tmp_path: Path) -> None: encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id.startswith("PY-PROJ-") for finding in report.findings @@ -32,7 +32,7 @@ def test_project_policy_blocks_flat_layout_with_pyproject(tmp_path: Path) -> Non (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -58,7 +58,7 @@ def test_project_policy_accepts_pyproject_declared_nested_src_layout( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["packages/python/src/tools"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert report.project_resolution is not None @@ -71,7 +71,7 @@ def test_project_policy_blocks_missing_declared_package_root( (tmp_path / "src").mkdir() _write_pyproject(tmp_path, packages='["src/missing_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -85,7 +85,7 @@ def test_project_policy_blocks_package_root_without_init(tmp_path: Path) -> None package.mkdir(parents=True) _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -103,7 +103,7 @@ def test_project_policy_deduplicates_declared_package_findings( packages='["src/missing_pkg", "src/./missing_pkg", "src/missing_pkg"]', ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings diff --git a/tests/unit/harness/project_policy/test_metadata.py b/tests/unit/harness/project_policy/test_metadata.py index 127eaa2..4cb0b05 100644 --- a/tests/unit/harness/project_policy/test_metadata.py +++ b/tests/unit/harness/project_policy/test_metadata.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness._project_metadata import ( +from asp_python._project_metadata import ( read_python_project_metadata, ) diff --git a/tests/unit/harness/project_policy/test_metadata_policy.py b/tests/unit/harness/project_policy/test_metadata_policy.py index e6567a3..c6adeca 100644 --- a/tests/unit/harness/project_policy/test_metadata_policy.py +++ b/tests/unit/harness/project_policy/test_metadata_policy.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -21,7 +21,7 @@ def test_project_policy_blocks_project_table_without_name(tmp_path: Path) -> Non """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -45,7 +45,7 @@ def test_project_policy_blocks_project_table_without_requires_python( """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -69,7 +69,7 @@ def test_project_policy_blocks_build_system_table_without_requires( """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -87,7 +87,7 @@ def test_project_policy_allows_tool_only_pyproject(tmp_path: Path) -> None: """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id.startswith("PY-PROJ-") for finding in report.findings @@ -106,12 +106,12 @@ def test_project_policy_requires_pytest_gate_for_harness_dev_dependency( [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -130,7 +130,7 @@ def test_project_policy_accepts_pytest_addopts_gate(tmp_path: Path) -> None: [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] @@ -138,7 +138,7 @@ def test_project_policy_accepts_pytest_addopts_gate(tmp_path: Path) -> None: """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id == "PY-AGENT-PROJECT-010" for finding in report.findings @@ -155,19 +155,19 @@ def test_project_policy_accepts_explicit_pytest_helper_gate(tmp_path: Path) -> N [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] """, ) tests = tmp_path / "tests" / "unit" tests.mkdir(parents=True) (tests / "test_harness_policy.py").write_text( - "from python_lang_project_harness.pytest import python_project_harness_test\n" - "test_python_project_harness_policy = python_project_harness_test()\n", + "from asp_python.pytest import asp_python_test\n" + "test_asp_python_policy = asp_python_test()\n", encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id == "PY-AGENT-PROJECT-010" for finding in report.findings @@ -186,7 +186,7 @@ def test_project_policy_advises_missing_verification_profile_hints( [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] @@ -204,7 +204,7 @@ def test_project_policy_advises_missing_verification_profile_hints( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) finding = next( finding for finding in report.findings @@ -228,13 +228,13 @@ def test_project_policy_accepts_configured_verification_profile_hint( [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] addopts = ["--python-project-harness"] -[tool.python-lang-project-harness.verification] +[tool.asp-python.verification] profile_hints = [ { owner_path = "src/pkg/__init__.py", responsibilities = ["public_api"], task_kinds = ["regression"], rationale = "package facade" }, ] @@ -251,7 +251,7 @@ def test_project_policy_accepts_configured_verification_profile_hint( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any( finding.rule_id == "PY-AGENT-PROJECT-011" for finding in report.findings diff --git a/tests/unit/harness/project_policy/test_typed_packages.py b/tests/unit/harness/project_policy/test_typed_packages.py index 2e13f91..5c088d6 100644 --- a/tests/unit/harness/project_policy/test_typed_packages.py +++ b/tests/unit/harness/project_policy/test_typed_packages.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -19,7 +19,7 @@ def test_project_policy_blocks_missing_py_typed_for_public_package( ) _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -43,7 +43,7 @@ def test_project_policy_blocks_missing_py_typed_for_public_facade_imports( ) _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -64,7 +64,7 @@ def test_project_policy_allows_private_package_without_py_typed( ) _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean @@ -79,7 +79,7 @@ def test_project_policy_accepts_src_package_with_py_typed(tmp_path: Path) -> Non (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean @@ -96,7 +96,7 @@ def test_project_policy_accepts_nested_src_package_with_py_typed( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["packages/python/src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean @@ -116,7 +116,7 @@ def test_project_policy_blocks_unannotated_public_callable_in_typed_package( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -141,7 +141,7 @@ def test_project_policy_blocks_unannotated_public_method_in_typed_package( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -166,7 +166,7 @@ def test_project_policy_allows_private_callable_without_annotations_in_typed_pac (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean @@ -186,7 +186,7 @@ def test_project_policy_accepts_annotated_method_in_typed_package( (package / "py.typed").write_text("", encoding="utf-8") _write_pyproject(tmp_path, packages='["src/example_pkg"]') - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean diff --git a/tests/unit/harness/provider_runtime_live_support.py b/tests/unit/harness/provider_runtime_live_support.py index 9d4564d..0dbd0ee 100644 --- a/tests/unit/harness/provider_runtime_live_support.py +++ b/tests/unit/harness/provider_runtime_live_support.py @@ -11,7 +11,7 @@ from pathlib import Path from urllib.parse import SplitResult -from python_lang_project_harness._runtime import _response_frame +from asp_python._runtime import _response_frame def environment() -> dict[str, str]: @@ -24,18 +24,36 @@ def environment() -> dict[str, str]: [ { "operation": "projection-batch", - "requestSchemaId": "schema:projection-request", - "responseSchemaId": "schema:projection-response", + "requestSchema": { + "schemaId": "schema:projection-request", + "schemaVersion": "1", + }, + "responseSchema": { + "schemaId": "schema:projection-response", + "schemaVersion": "1", + }, }, { "operation": "project-resolution", - "requestSchemaId": "schema:resolution-request", - "responseSchemaId": "schema:resolution-response", + "requestSchema": { + "schemaId": "schema:resolution-request", + "schemaVersion": "1", + }, + "responseSchema": { + "schemaId": "schema:resolution-response", + "schemaVersion": "1", + }, }, { "operation": "query", - "requestSchemaId": "schema:query-request", - "responseSchemaId": "schema:query-response", + "requestSchema": { + "schemaId": "schema:query-request", + "schemaVersion": "1", + }, + "responseSchema": { + "schemaId": "schema:query-response", + "schemaVersion": "1", + }, }, ], separators=(",", ":"), diff --git a/tests/unit/harness/semantic_search_fixture.py b/tests/unit/harness/semantic_search_fixture.py index ef7191f..7382cab 100644 --- a/tests/unit/harness/semantic_search_fixture.py +++ b/tests/unit/harness/semantic_search_fixture.py @@ -8,7 +8,7 @@ import pytest -from python_lang_project_harness._semantic_search_graph_render import ( +from asp_python._semantic_search_graph_render import ( SEMANTIC_AGENT_PROTOCOL_BIN_ENV, ) diff --git a/tests/unit/harness/snapshot_support.py b/tests/unit/harness/snapshot_support.py index c2c0c9b..72c9765 100644 --- a/tests/unit/harness/snapshot_support.py +++ b/tests/unit/harness/snapshot_support.py @@ -6,7 +6,7 @@ from pathlib import Path SNAPSHOT_ROOT = Path(__file__).parent.parent / "snapshots" -UPDATE_ENV_VAR = "PYTHON_HARNESS_UPDATE_SNAPSHOTS" +UPDATE_ENV_VAR = "ASP_PYTHON_UPDATE_SNAPSHOTS" def assert_snapshot( diff --git a/tests/unit/harness/test_agent_algorithm_policy.py b/tests/unit/harness/test_agent_algorithm_policy.py index 4b3a679..31c8e86 100644 --- a/tests/unit/harness/test_agent_algorithm_policy.py +++ b/tests/unit/harness/test_agent_algorithm_policy.py @@ -5,7 +5,7 @@ from snapshot_support import assert_snapshot, normalize_temp_root -from python_lang_project_harness import ( +from asp_python import ( render_python_lang_harness, run_python_lang_harness, ) diff --git a/tests/unit/harness/test_agent_policy.py b/tests/unit/harness/test_agent_policy.py index 9f45300..e3eb2de 100644 --- a/tests/unit/harness/test_agent_policy.py +++ b/tests/unit/harness/test_agent_policy.py @@ -3,15 +3,15 @@ from dataclasses import replace from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import ( +from asp_python import ( PythonAgentPolicyRulePack, python_agent_policy_rules, render_python_lang_harness, render_python_lang_harness_advice, + run_asp_python, run_python_lang_harness, - run_python_project_harness, ) +from python_lang_parser import PythonDiagnosticSeverity if TYPE_CHECKING: from pathlib import Path @@ -86,7 +86,7 @@ def test_agent_policy_blocks_duplicate_public_callable_names( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -111,7 +111,7 @@ def test_agent_policy_blocks_duplicate_public_type_names( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -136,7 +136,7 @@ def test_agent_policy_blocks_duplicate_public_value_names( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -157,7 +157,7 @@ def test_agent_policy_blocks_repeated_module_namespace_segments( '"""Domain service namespace."""\n\nVALUE = 1\n', encoding="utf-8" ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -179,7 +179,7 @@ def test_agent_policy_deduplicates_repeated_namespace_branches( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == ["PY-AGENT-POLICY-004"] @@ -199,7 +199,7 @@ def test_agent_policy_reports_broad_branch_package_surface(tmp_path: Path) -> No encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -230,7 +230,7 @@ def test_agent_policy_accepts_owner_map_documented_branch_package( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not report.findings diff --git a/tests/unit/harness/test_agent_policy_snapshots.py b/tests/unit/harness/test_agent_policy_snapshots.py index b698a56..2b8bbcb 100644 --- a/tests/unit/harness/test_agent_policy_snapshots.py +++ b/tests/unit/harness/test_agent_policy_snapshots.py @@ -5,10 +5,10 @@ from snapshot_support import assert_snapshot, normalize_temp_root -from python_lang_project_harness import ( +from asp_python import ( render_python_lang_harness, + run_asp_python, run_python_lang_harness, - run_python_project_harness, ) if TYPE_CHECKING: @@ -166,7 +166,7 @@ def _assert_project_snapshot( rule_id: str, snapshot_name: str, ) -> None: - report = run_python_project_harness(root) + report = run_asp_python(root) _assert_filtered_snapshot(root, report, rule_id, snapshot_name) diff --git a/tests/unit/harness/test_cli.py b/tests/unit/harness/test_cli.py index db009d4..a64b97c 100644 --- a/tests/unit/harness/test_cli.py +++ b/tests/unit/harness/test_cli.py @@ -1,31 +1,33 @@ from __future__ import annotations import io -import json from typing import TYPE_CHECKING -from python_lang_project_harness import run_cli +from asp_python import run_cli if TYPE_CHECKING: from pathlib import Path -def test_cli_help_advertises_exact_projection_routes() -> None: +def test_cli_help_advertises_the_current_provider_protocol() -> None: stdout = io.StringIO() + exit_code = run_cli(["--help"], stdout=stdout) + rendered = stdout.getvalue() assert exit_code == 0 - assert "asp-python search ... [--json] [--package PATH]" in rendered - assert ( - "asp python query --selector " - "--projection " in rendered - ) + assert "asp-python search " in rendered + assert "asp python query --selector" in rendered + assert "asp-python evidence graph" in rendered + assert "asp-python agent doctor" in rendered def test_cli_subcommand_help_advertises_exact_projection() -> None: for args in (["search", "--help"], ["query", "--help"]): stdout = io.StringIO() + exit_code = run_cli(args, stdout=stdout) + rendered = stdout.getvalue() assert exit_code == 0 assert "--selector " in rendered @@ -36,293 +38,18 @@ def test_cli_agent_guide_advertises_exact_source_route(tmp_path: Path) -> None: stdout = io.StringIO() exit_code = run_cli(["agent", "guide", str(tmp_path)], stdout=stdout) - rendered = stdout.getvalue() - - assert exit_code == 0 - assert ( - "asp python query --selector " - "--projection source --workspace " in rendered - ) - - -def test_cli_renders_compact_text_by_default(tmp_path: Path) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (package / "__init__.py").write_text('"""Package docs."""\n', encoding="utf-8") - - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_cli([str(tmp_path)], stdout=stdout, stderr=stderr) - - assert exit_code == 0 - assert stderr.getvalue() == "" - assert stdout.getvalue().startswith("[ok] . python") - assert "Files: 1 Parsed: 1" in stdout.getvalue() - assert str(tmp_path) not in stdout.getvalue() - - -def test_cli_json_flag_renders_structured_report(tmp_path: Path) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (package / "__init__.py").write_text('"""Package docs."""\n', encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli(["--json", str(tmp_path)], stdout=stdout) - payload = json.loads(stdout.getvalue()) - - assert exit_code == 0 - assert payload["is_clean"] is True - assert payload["file_count"] == 1 - assert payload["project_resolution"]["project_root"] == str(tmp_path) - - -def test_cli_agent_snapshot_renders_parser_backed_project_shape( - tmp_path: Path, -) -> None: - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (package / "__init__.py").write_text('"""Package docs."""\n', encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli(["--agent-snapshot", str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - rendered = stdout.getvalue() - assert rendered.startswith("[agent-snapshot] . python\n") - assert "[tree] . python" in rendered - assert "Modules: source=1" in rendered - assert "[nodes]" not in rendered - assert "[ok]" not in rendered - assert str(tmp_path) not in rendered - - -def test_cli_keeps_agent_advice_non_blocking(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text( - "def build(value):\n return value\n", encoding="utf-8" - ) - stdout = io.StringIO() - - exit_code = run_cli([str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - assert "[advice]" in stdout.getvalue() - assert "PY-AGENT-POLICY-001" in stdout.getvalue() - - -def test_cli_exits_nonzero_for_blocking_findings(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text( - 'def build() -> None:\n print("debug")\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli([str(tmp_path)], stdout=stdout) - - assert exit_code == 1 - assert "PY-MOD-R002" in stdout.getvalue() - assert stdout.getvalue().startswith("[fail] python") - assert "severity=warning" in stdout.getvalue() - assert "src/service.py" in stdout.getvalue() - assert str(tmp_path) not in stdout.getvalue() - - -def test_cli_can_disable_policy_rule_ids(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text( - 'def run() -> None:\n print("debug")\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - ["--disable-rule", "PY-MOD-R002", str(tmp_path)], - stdout=stdout, - ) assert exit_code == 0 - assert "PY-MOD-R002" not in stdout.getvalue() - - -def test_cli_loads_project_policy_config_from_pyproject(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (tmp_path / "pyproject.toml").write_text( - """ -[tool.python-lang-project-harness] -disabled_rule_ids = ["PY-MOD-R002"] -""".lstrip(), - encoding="utf-8", - ) - (src / "service.py").write_text( - 'def run() -> None:\n print("debug")\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli([str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - assert "PY-MOD-R002" not in stdout.getvalue() - - -def test_cli_uses_pyproject_declared_package_source_scope(tmp_path: Path) -> None: - default_src = tmp_path / "src" - package_source = tmp_path / "packages" / "python" / "src" - package = package_source / "tools" - tests = tmp_path / "tests" / "unit" - default_src.mkdir() - package.mkdir(parents=True) - tests.mkdir(parents=True) - (default_src / "ignored.py").write_text( - "def broken(:\n pass\n", encoding="utf-8" - ) - (package / "__init__.py").write_text( - '"""Package public API."""\n\n\ndef build(value: int) -> int:\n return value\n', - encoding="utf-8", - ) - (package / "py.typed").write_text("", encoding="utf-8") - (tests / "test_tools.py").write_text( - "def test_tools() -> None:\n assert True\n", - encoding="utf-8", - ) - (tmp_path / "pyproject.toml").write_text( - """ -[project] -name = "example-pkg" -requires-python = ">=3.12" - -[build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" - -[tool.hatch.build.targets.wheel] -packages = ["packages/python/src/tools"] -""".lstrip(), - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli(["--json", str(tmp_path)], stdout=stdout) - payload = json.loads(stdout.getvalue()) - - assert exit_code == 0 - assert payload["is_clean"] is True - assert [finding["rule_id"] for finding in payload["findings"]] == [] - assert payload["project_resolution"]["source_paths"] == [str(package_source)] - assert payload["project_resolution"]["project_paths"] == [ - str(package_source), - str(tmp_path / "tests"), - ] - - -def test_cli_can_promote_policy_rule_ids(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text( - "def build(value):\n return value\n", - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - ["--block-rule", "PY-AGENT-POLICY-001", str(tmp_path)], stdout=stdout - ) - - assert exit_code == 1 - assert "PY-AGENT-POLICY-001" in stdout.getvalue() - assert stdout.getvalue().startswith("[fail] python") - assert "severity=info" in stdout.getvalue() - - -def test_cli_help_and_argument_errors_are_stable(tmp_path: Path) -> None: - help_stdout = io.StringIO() - error_stderr = io.StringIO() - - assert run_cli(["--help"], stdout=help_stdout) == 0 - assert "asp-python search " in help_stdout.getvalue() assert ( - "asp-python [--json | --agent-snapshot] [--no-tests]" in help_stdout.getvalue() - ) - assert run_cli(["--bogus"], stderr=error_stderr) == 2 - assert "unknown option: --bogus" in error_stderr.getvalue() - assert run_cli([str(tmp_path), str(tmp_path)], stderr=io.StringIO()) == 2 - mutually_exclusive_stderr = io.StringIO() - assert ( - run_cli( - ["--json", "--agent-snapshot", str(tmp_path)], - stderr=mutually_exclusive_stderr, - ) - == 2 - ) - assert "mutually exclusive" in mutually_exclusive_stderr.getvalue() - - -def test_cli_scope_flags_customize_project_paths(tmp_path: Path) -> None: - lib = tmp_path / "lib" - tools = tmp_path / "tools" - tests = tmp_path / "tests" / "unit" - lib.mkdir() - tools.mkdir() - tests.mkdir(parents=True) - (lib / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") - (tools / "check.py").write_text('"""Check docs."""\n', encoding="utf-8") - (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli( - [ - "--source-dir", - "lib", - "--extra-path", - "tools", - "--no-tests", - str(tmp_path), - ], - stdout=stdout, + "asp python query --selector " in stdout.getvalue() ) - - assert exit_code == 0 - assert "Files: 2 Parsed: 2" in stdout.getvalue() - assert "[ok] lib, tools python" in stdout.getvalue() - - -def test_cli_no_tests_skips_test_parser_discovery(tmp_path: Path) -> None: - src = tmp_path / "src" - tests = tmp_path / "tests" / "unit" - src.mkdir() - tests.mkdir(parents=True) - (src / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") - (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - stdout = io.StringIO() - - exit_code = run_cli(["--no-tests", str(tmp_path)], stdout=stdout) - - assert exit_code == 0 - assert "Files: 1 Parsed: 1" in stdout.getvalue() - - -def test_cli_scope_flag_values_are_required() -> None: - stderr = io.StringIO() - - assert run_cli(["--source-dir"], stderr=stderr) == 2 - assert "missing value for --source-dir" in stderr.getvalue() - assert run_cli(["--disable-rule"], stderr=io.StringIO()) == 2 + assert "|policy authority=asp-python-api trigger=pytest-plugin" in stdout.getvalue() -def test_cli_defaults_to_current_working_directory(tmp_path: Path) -> None: - src = tmp_path / "src" - src.mkdir() - (src / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") +def test_cli_without_command_renders_help_instead_of_running_policy() -> None: stdout = io.StringIO() - exit_code = run_cli((), stdout=stdout, cwd=tmp_path) + exit_code = run_cli((), stdout=stdout) assert exit_code == 0 - assert stdout.getvalue().startswith("[ok] . python") - assert str(tmp_path) not in stdout.getvalue() + assert stdout.getvalue().startswith("asp-python ") diff --git a/tests/unit/harness/test_dependency_topology.py b/tests/unit/harness/test_dependency_topology.py index 2535bc8..75285ae 100644 --- a/tests/unit/harness/test_dependency_topology.py +++ b/tests/unit/harness/test_dependency_topology.py @@ -3,7 +3,7 @@ import re from pathlib import Path -from python_lang_project_harness._dependency_topology import ( +from asp_python._dependency_topology import ( build_dependency_topology_packet, ) diff --git a/tests/unit/harness/test_dependency_topology_cli.py b/tests/unit/harness/test_dependency_topology_cli.py index 3b35946..f5cba40 100644 --- a/tests/unit/harness/test_dependency_topology_cli.py +++ b/tests/unit/harness/test_dependency_topology_cli.py @@ -5,8 +5,8 @@ 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 +from asp_python._cli_args import ProtocolArgs +from asp_python._cli_protocol import run_protocol_cli def test_dependency_topology_cli_emits_canonical_packet(tmp_path: Path) -> None: diff --git a/tests/unit/harness/test_dev_command_log.py b/tests/unit/harness/test_dev_command_log.py index a019840..7594cd5 100644 --- a/tests/unit/harness/test_dev_command_log.py +++ b/tests/unit/harness/test_dev_command_log.py @@ -5,7 +5,7 @@ import json from pathlib import Path -from python_lang_project_harness._dev_command_log import start_dev_command_log +from asp_python._dev_command_log import start_dev_command_log def test_dev_command_log_records_ordered_active_context_events( diff --git a/tests/unit/harness/test_evidence_graph.py b/tests/unit/harness/test_evidence_graph.py index ac9a8f2..9596ce8 100644 --- a/tests/unit/harness/test_evidence_graph.py +++ b/tests/unit/harness/test_evidence_graph.py @@ -6,7 +6,7 @@ import json from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_evidence_graph_renders_json_contract(tmp_path: Path) -> None: @@ -39,7 +39,8 @@ 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"] == "asp-python check --full ." + assert payload["gaps"][0]["fields"] == {"requiredReceiptId": "python.policy.api"} + assert all("command" not in node.get("fields", {}) for node in payload["nodes"]) def test_cli_evidence_analyze_renders_graph_turbo_request(tmp_path: Path) -> None: diff --git a/tests/unit/harness/test_exact_source_projection.py b/tests/unit/harness/test_exact_source_projection.py index 576fd9c..8123c84 100644 --- a/tests/unit/harness/test_exact_source_projection.py +++ b/tests/unit/harness/test_exact_source_projection.py @@ -2,7 +2,7 @@ import base64 -from python_lang_project_harness._exact_source_projection import ( +from asp_python._exact_source_projection import ( project_provider_native_exact_request, ) diff --git a/tests/unit/harness/test_harness_rules.py b/tests/unit/harness/test_harness_rules.py index 991e8d0..19b82dd 100644 --- a/tests/unit/harness/test_harness_rules.py +++ b/tests/unit/harness/test_harness_rules.py @@ -4,20 +4,20 @@ import tempfile from pathlib import Path -from python_lang_project_harness._agent_policy_catalog import python_agent_policy_rules -from python_lang_project_harness._harness_rules import ( +from asp_python._agent_policy_catalog import python_agent_policy_rules +from asp_python._harness_rules import ( python_harness_rules_markdown, render_python_harness_rules_markdown, write_python_harness_rules_to_unit_tests, ) -from python_lang_project_harness._modern_design_catalog import ( +from asp_python._modern_design_catalog import ( python_modern_design_rules, ) -from python_lang_project_harness._modularity import python_modularity_rules -from python_lang_project_harness._project_policy_catalog import ( +from asp_python._modularity import python_modularity_rules +from asp_python._project_policy_catalog import ( python_project_policy_rules, ) -from python_lang_project_harness._test_layout_catalog import python_test_layout_rules +from asp_python._test_layout_catalog import python_test_layout_rules def _harness_rules_rule_ids() -> list[str]: diff --git a/tests/unit/harness/test_modern_design.py b/tests/unit/harness/test_modern_design.py index 07fc697..d9a7f86 100644 --- a/tests/unit/harness/test_modern_design.py +++ b/tests/unit/harness/test_modern_design.py @@ -2,13 +2,13 @@ from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import ( +from asp_python import ( PythonModernDesignRulePack, python_modern_design_rules, render_python_lang_harness, run_python_lang_harness, ) +from python_lang_parser import PythonDiagnosticSeverity if TYPE_CHECKING: from pathlib import Path diff --git a/tests/unit/harness/test_modularity_catalog.py b/tests/unit/harness/test_modularity_catalog.py index 6d474ec..d5e0a46 100644 --- a/tests/unit/harness/test_modularity_catalog.py +++ b/tests/unit/harness/test_modularity_catalog.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -16,7 +16,7 @@ def test_modularity_rule_pack_blocks_large_class_only_service_module( source = src / "services.py" source.write_text(_large_class_only_service_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -33,7 +33,7 @@ def test_modularity_rule_pack_allows_large_single_signal_state_module( source = src / "constants.py" source.write_text(_large_state_only_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == [] @@ -46,7 +46,7 @@ def test_modularity_rule_pack_allows_large_single_return_literal_fixture( source = src / "schema_fixture.py" source.write_text(_large_return_literal_fixture_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == [] @@ -59,7 +59,7 @@ def test_modularity_rule_pack_blocks_large_module_with_long_function( source = src / "orchestrator.py" source.write_text(_large_long_function_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) findings = [ finding for finding in report.findings if finding.rule_id == "PY-MOD-R006" diff --git a/tests/unit/harness/test_parser_boundary_contract.py b/tests/unit/harness/test_parser_boundary_contract.py index 099370c..0532ae1 100644 --- a/tests/unit/harness/test_parser_boundary_contract.py +++ b/tests/unit/harness/test_parser_boundary_contract.py @@ -11,7 +11,7 @@ def test_harness_policy_does_not_parse_python_source_directly() -> None: harness_sources = sorted( - (_PROJECT_ROOT / "src" / "python_lang_project_harness").glob("*_policy*.py") + (_PROJECT_ROOT / "src" / "asp_python").glob("*_policy*.py") ) for path in harness_sources: @@ -23,9 +23,7 @@ def test_harness_policy_does_not_parse_python_source_directly() -> None: def test_harness_semantic_roles_use_parser_symbol_helpers() -> None: - harness_sources = sorted( - (_PROJECT_ROOT / "src" / "python_lang_project_harness").rglob("*.py") - ) + harness_sources = sorted((_PROJECT_ROOT / "src" / "asp_python").rglob("*.py")) for path in harness_sources: if path.name == "__init__.py": continue @@ -33,22 +31,16 @@ def test_harness_semantic_roles_use_parser_symbol_helpers() -> None: assert "PythonSymbolKind" not in source, path test_bloat = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_test_layout_bloat.py" + _PROJECT_ROOT / "src" / "asp_python" / "_test_layout_bloat.py" ).read_text(encoding="utf-8") agent_policy = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_agent_policy.py" + _PROJECT_ROOT / "src" / "asp_python" / "_agent_policy.py" ).read_text(encoding="utf-8") typed_policy = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_project_policy_typed.py" + _PROJECT_ROOT / "src" / "asp_python" / "_project_policy_typed.py" ).read_text(encoding="utf-8") namespace_index = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_agent_namespace_index.py" + _PROJECT_ROOT / "src" / "asp_python" / "_agent_namespace_index.py" ).read_text(encoding="utf-8") assert "python_symbol_is_test_function(" in test_bloat @@ -70,9 +62,9 @@ def test_harness_semantic_roles_use_parser_symbol_helpers() -> None: def test_agent_namespace_policy_uses_parser_module_identity_helpers() -> None: - source = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_agent_namespace.py" - ).read_text(encoding="utf-8") + source = (_PROJECT_ROOT / "src" / "asp_python" / "_agent_namespace.py").read_text( + encoding="utf-8" + ) assert "python_module_namespace_parts(" in source assert "relative_to(" not in source @@ -81,13 +73,10 @@ def test_agent_namespace_policy_uses_parser_module_identity_helpers() -> None: def test_python_semantic_policy_uses_parser_name_helpers() -> None: typed_policy = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_project_policy_typed.py" + _PROJECT_ROOT / "src" / "asp_python" / "_project_policy_typed.py" ).read_text(encoding="utf-8") modern_design = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_modern_design.py" + _PROJECT_ROOT / "src" / "asp_python" / "_modern_design.py" ).read_text(encoding="utf-8") assert "python_symbol_is_public_class(" in typed_policy @@ -97,14 +86,11 @@ def test_python_semantic_policy_uses_parser_name_helpers() -> None: def test_test_layout_python_source_lines_use_parser_reports() -> None: - layout = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_test_layout.py" - ).read_text(encoding="utf-8") + layout = (_PROJECT_ROOT / "src" / "asp_python" / "_test_layout.py").read_text( + encoding="utf-8" + ) entries = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_test_layout_entries.py" + _PROJECT_ROOT / "src" / "asp_python" / "_test_layout_entries.py" ).read_text(encoding="utf-8") assert "tests_root_entry_findings(tests_dir, pack_id, modules)" in layout @@ -113,9 +99,7 @@ def test_test_layout_python_source_lines_use_parser_reports() -> None: def test_harness_pyproject_metadata_comes_from_parser_boundary() -> None: - harness_sources = sorted( - (_PROJECT_ROOT / "src" / "python_lang_project_harness").rglob("*.py") - ) + harness_sources = sorted((_PROJECT_ROOT / "src" / "asp_python").rglob("*.py")) for path in harness_sources: if path.name in { @@ -130,9 +114,7 @@ def test_harness_pyproject_metadata_comes_from_parser_boundary() -> None: def test_agent_readability_policy_consumes_parser_function_facts() -> None: - readability_root = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "agent_readability" - ) + readability_root = _PROJECT_ROOT / "src" / "asp_python" / "agent_readability" for path in sorted(readability_root.glob("*.py")): source = path.read_text(encoding="utf-8") diff --git a/tests/unit/harness/test_policy_contract.py b/tests/unit/harness/test_policy_contract.py index 37ac32c..234ef05 100644 --- a/tests/unit/harness/test_policy_contract.py +++ b/tests/unit/harness/test_policy_contract.py @@ -2,8 +2,7 @@ from pathlib import Path -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import ( +from asp_python import ( default_python_harness_config, python_agent_policy_rules, python_modern_design_rules, @@ -13,8 +12,9 @@ python_syntax_rules, python_test_layout_rules, render_python_lang_harness, - run_python_project_harness, + run_asp_python, ) +from python_lang_parser import PythonDiagnosticSeverity _PROJECT_ROOT = next( parent @@ -242,7 +242,7 @@ def test_agent_facing_snapshots_avoid_redundant_render_preambles() -> None: def test_project_is_clean_under_its_own_harness() -> None: - report = run_python_project_harness(_PROJECT_ROOT) + report = run_asp_python(_PROJECT_ROOT) rendered = render_python_lang_harness(report) assert report.is_clean, rendered diff --git a/tests/unit/harness/test_policy_snapshots.py b/tests/unit/harness/test_policy_snapshots.py index 7f67cb1..4decaf6 100644 --- a/tests/unit/harness/test_policy_snapshots.py +++ b/tests/unit/harness/test_policy_snapshots.py @@ -5,9 +5,9 @@ from snapshot_support import assert_snapshot, normalize_temp_root -from python_lang_project_harness import ( +from asp_python import ( render_python_lang_harness, - run_python_project_harness, + run_asp_python, ) if TYPE_CHECKING: @@ -261,7 +261,7 @@ def test_py_proj_r010_pytest_gate_snapshot(tmp_path: Path) -> None: [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] """.lstrip(), encoding="utf-8", @@ -291,7 +291,7 @@ def test_py_proj_r011_verification_profile_snapshot(tmp_path: Path) -> None: [dependency-groups] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", ] [tool.pytest.ini_options] @@ -334,7 +334,7 @@ def test_py_test_r003_unit_bloat_snapshot(tmp_path: Path) -> None: def _assert_project_snapshot(root: Path, rule_id: str, snapshot_name: str) -> None: - report = run_python_project_harness(root) + report = run_asp_python(root) findings = tuple( finding for finding in report.findings if finding.rule_id == rule_id ) diff --git a/tests/unit/harness/test_project_api.py b/tests/unit/harness/test_project_api.py index 3aea6d8..352c9d4 100644 --- a/tests/unit/harness/test_project_api.py +++ b/tests/unit/harness/test_project_api.py @@ -2,22 +2,22 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( PythonModularityRulePack, PythonTestLayoutRulePack, - assert_python_project_harness_clean, + asp_python_paths, + asp_python_scope, + assert_asp_python_clean, python_modularity_rules, - python_project_harness_paths, - python_project_harness_scope, python_test_layout_rules, - run_python_project_harness, + run_asp_python, ) if TYPE_CHECKING: from pathlib import Path -def test_python_project_harness_paths_use_project_root_by_default( +def test_asp_python_paths_use_project_root_by_default( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -25,11 +25,11 @@ def test_python_project_harness_paths_use_project_root_by_default( src.mkdir() tests.mkdir() - assert python_project_harness_paths(tmp_path) == (tmp_path,) - assert python_project_harness_paths(tmp_path, include_tests=False) == (src,) + assert asp_python_paths(tmp_path) == (tmp_path,) + assert asp_python_paths(tmp_path, include_tests=False) == (src,) -def test_python_project_harness_scope_exposes_project_and_classification_paths( +def test_asp_python_scope_exposes_project_and_classification_paths( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -37,7 +37,7 @@ def test_python_project_harness_scope_exposes_project_and_classification_paths( src.mkdir() tests.mkdir() - scope = python_project_harness_scope(tmp_path) + scope = asp_python_scope(tmp_path) assert scope.source_paths == (src,) assert scope.test_paths == (tests,) @@ -47,14 +47,14 @@ def test_python_project_harness_scope_exposes_project_and_classification_paths( assert scope.to_dict()["monitored_paths"] == [str(tmp_path)] -def test_python_project_harness_paths_fall_back_to_root(tmp_path: Path) -> None: +def test_asp_python_paths_fall_back_to_root(tmp_path: Path) -> None: module = tmp_path / "module.py" module.write_text("VALUE = 1\n", encoding="utf-8") - assert python_project_harness_paths(tmp_path) == (tmp_path,) + assert asp_python_paths(tmp_path) == (tmp_path,) -def test_run_python_project_harness_uses_project_paths(tmp_path: Path) -> None: +def test_run_asp_python_uses_project_paths(tmp_path: Path) -> None: src = tmp_path / "src" tests = tmp_path / "tests" / "unit" src.mkdir() @@ -65,7 +65,7 @@ def test_run_python_project_harness_uses_project_paths(tmp_path: Path) -> None: encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert report.file_count == 2 @@ -83,7 +83,7 @@ def test_run_python_project_harness_uses_project_paths(tmp_path: Path) -> None: } -def test_run_python_project_harness_monitors_src_and_tests_by_default( +def test_run_asp_python_monitors_src_and_tests_by_default( tmp_path: Path, ) -> None: src = tmp_path / "src" / "pkg" @@ -95,7 +95,7 @@ def test_run_python_project_harness_monitors_src_and_tests_by_default( source_file.write_text("VALUE = 1\n", encoding="utf-8") test_file.write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [module.path for module in report.modules] == [ str(source_file), @@ -108,7 +108,7 @@ def test_run_python_project_harness_monitors_src_and_tests_by_default( ] -def test_run_python_project_harness_can_exclude_tests_from_scope( +def test_run_asp_python_can_exclude_tests_from_scope( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -121,7 +121,7 @@ def test_run_python_project_harness_can_exclude_tests_from_scope( encoding="utf-8", ) - report = run_python_project_harness(tmp_path, include_tests=False) + report = run_asp_python(tmp_path, include_tests=False) assert report.is_clean assert [module.path for module in report.modules] == [str(src / "library.py")] @@ -130,7 +130,7 @@ def test_run_python_project_harness_can_exclude_tests_from_scope( assert report.project_resolution.monitored_paths == (src,) -def test_run_python_project_harness_does_not_fallback_into_excluded_tests( +def test_run_asp_python_does_not_fallback_into_excluded_tests( tmp_path: Path, ) -> None: package = tmp_path / "pkg" @@ -140,7 +140,7 @@ def test_run_python_project_harness_does_not_fallback_into_excluded_tests( (package / "__init__.py").write_text('"""Package docs."""\n', encoding="utf-8") (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_project_harness(tmp_path, include_tests=False) + report = run_asp_python(tmp_path, include_tests=False) assert report.is_clean assert [module.path for module in report.modules] == [str(package / "__init__.py")] @@ -161,7 +161,7 @@ def test_include_tests_false_skips_test_parsing_not_layout_policy( encoding="utf-8", ) - report = run_python_project_harness(tmp_path, include_tests=False) + report = run_asp_python(tmp_path, include_tests=False) assert report.file_count == 1 assert [finding.rule_id for finding in report.findings] == ["PY-TEST-R001"] @@ -169,14 +169,14 @@ def test_include_tests_false_skips_test_parsing_not_layout_policy( assert report.project_resolution.monitored_paths == (src,) -def test_assert_python_project_harness_clean_blocks_for_pytest(tmp_path: Path) -> None: +def test_assert_asp_python_clean_blocks_for_pytest(tmp_path: Path) -> None: src = tmp_path / "src" src.mkdir() source = src / "library.py" source.write_text('def run() -> None:\n print("debug")\n', encoding="utf-8") try: - assert_python_project_harness_clean(tmp_path) + assert_asp_python_clean(tmp_path) except AssertionError as error: message = str(error) else: @@ -197,7 +197,7 @@ def test_project_harness_blocks_root_pytest_files(tmp_path: Path) -> None: ) try: - assert_python_project_harness_clean(tmp_path) + assert_asp_python_clean(tmp_path) except AssertionError as error: message = str(error) else: @@ -214,7 +214,7 @@ def test_project_harness_blocks_unexpected_tests_root_entries(tmp_path: Path) -> unexpected = tmp_path / "tests" / "misc" unexpected.mkdir(parents=True) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -229,7 +229,7 @@ def test_project_harness_blocks_bloated_unit_test_leaf(tmp_path: Path) -> None: source = unit / "test_large_policy.py" source.write_text(_large_unit_test_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -260,7 +260,7 @@ def test_modularity_rule_pack_blocks_bloated_multi_responsibility_module( source = src / "feature.py" source.write_text(_large_multi_responsibility_module_source(), encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -288,7 +288,7 @@ def get_value(self) -> int: encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean diff --git a/tests/unit/harness/test_project_config.py b/tests/unit/harness/test_project_config.py index c931004..b8cd6b8 100644 --- a/tests/unit/harness/test_project_config.py +++ b/tests/unit/harness/test_project_config.py @@ -2,19 +2,19 @@ from typing import TYPE_CHECKING +from asp_python import read_asp_python_config from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import read_python_project_harness_config if TYPE_CHECKING: from pathlib import Path -def test_read_python_project_harness_config_from_pyproject( +def test_read_asp_python_config_from_pyproject( tmp_path: Path, ) -> None: (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] include_tests = false source_dir_names = ["lib"] test_dir_names = ["checks"] @@ -27,7 +27,7 @@ def test_read_python_project_harness_config_from_pyproject( encoding="utf-8", ) - config = read_python_project_harness_config(tmp_path) + config = read_asp_python_config(tmp_path) assert config is not None assert config.include_tests is False @@ -40,7 +40,7 @@ def test_read_python_project_harness_config_from_pyproject( assert config.blocking_severities == frozenset({PythonDiagnosticSeverity.ERROR}) -def test_read_python_project_harness_config_returns_none_without_table( +def test_read_asp_python_config_returns_none_without_table( tmp_path: Path, ) -> None: (tmp_path / "pyproject.toml").write_text( @@ -51,22 +51,22 @@ def test_read_python_project_harness_config_returns_none_without_table( encoding="utf-8", ) - assert read_python_project_harness_config(tmp_path) is None + assert read_asp_python_config(tmp_path) is None -def test_read_python_project_harness_config_rejects_invalid_values( +def test_read_asp_python_config_rejects_invalid_values( tmp_path: Path, ) -> None: (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] blocking_severities = ["critical"] """.lstrip(), encoding="utf-8", ) try: - read_python_project_harness_config(tmp_path) + read_asp_python_config(tmp_path) except ValueError as error: assert "unknown severity: critical" in str(error) else: diff --git a/tests/unit/harness/test_project_fixture_scope.py b/tests/unit/harness/test_project_fixture_scope.py index e820af2..7832104 100644 --- a/tests/unit/harness/test_project_fixture_scope.py +++ b/tests/unit/harness/test_project_fixture_scope.py @@ -2,10 +2,10 @@ from pathlib import Path -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python -def test_run_python_project_harness_skips_test_fixture_sources_by_default( +def test_run_asp_python_skips_test_fixture_sources_by_default( tmp_path: Path, ) -> None: """Keep borrowed fixture projects out of root policy scans.""" @@ -19,7 +19,7 @@ def test_run_python_project_harness_skips_test_fixture_sources_by_default( source_file.write_text('"""Library docs."""\n\nVALUE = 1\n', encoding="utf-8") fixture_file.write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert [module.path for module in report.modules] == [str(source_file)] diff --git a/tests/unit/harness/test_project_resolution.py b/tests/unit/harness/test_project_resolution.py index 983ce82..1f605ea 100644 --- a/tests/unit/harness/test_project_resolution.py +++ b/tests/unit/harness/test_project_resolution.py @@ -5,7 +5,7 @@ import json from pathlib import Path -from python_lang_project_harness._runtime import _response_frame +from asp_python._runtime import _response_frame def request(candidate_paths: list[str]) -> dict[str, object]: @@ -230,12 +230,14 @@ def test_provider_registration_advertises_project_resolution() -> None: for operation in manifest["runtimeContract"]["operations"] } project_resolution = operations["project-resolution"] - assert project_resolution["requestSchemaId"].endswith( - "/provider-project-resolution-request.schema.json" + assert project_resolution["requestSchema"]["schemaId"] == ( + "agent.semantic-protocols.provider-project-resolution-request" ) - assert project_resolution["responseSchemaId"].endswith( - "/provider-project-resolution-response.schema.json" + assert project_resolution["requestSchema"]["schemaVersion"] == "1" + assert project_resolution["responseSchema"]["schemaId"] == ( + "agent.semantic-protocols.provider-project-resolution-response" ) + assert project_resolution["responseSchema"]["schemaVersion"] == "1" def test_explicit_owner_collection_scope_is_required_and_normalized( diff --git a/tests/unit/harness/test_project_resolution_extra_paths.py b/tests/unit/harness/test_project_resolution_extra_paths.py index 9f6e1a7..2015ce3 100644 --- a/tests/unit/harness/test_project_resolution_extra_paths.py +++ b/tests/unit/harness/test_project_resolution_extra_paths.py @@ -2,13 +2,13 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path -def test_run_python_project_harness_can_include_extra_project_paths( +def test_run_asp_python_can_include_extra_project_paths( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -24,8 +24,8 @@ def test_run_python_project_harness_can_include_extra_project_paths( tool.write_text('"""Tool docs."""\n', encoding="utf-8") (shared / "shared.py").write_text('"""Shared docs."""\n', encoding="utf-8") - default_report = run_python_project_harness(tmp_path) - report = run_python_project_harness( + default_report = run_asp_python(tmp_path) + report = run_asp_python( tmp_path, extra_path_names=("../shared_tools",), ) diff --git a/tests/unit/harness/test_projection_batch.py b/tests/unit/harness/test_projection_batch.py index 01bb070..5d54f20 100644 --- a/tests/unit/harness/test_projection_batch.py +++ b/tests/unit/harness/test_projection_batch.py @@ -2,7 +2,7 @@ from __future__ import annotations -from python_lang_project_harness._projection_batch import project_projection_batch +from asp_python._projection_batch import project_projection_batch def test_projection_batch_projects_canonical_python_items() -> None: diff --git a/tests/unit/harness/test_provider_runtime.py b/tests/unit/harness/test_provider_runtime.py index 63afa7a..26b2d56 100644 --- a/tests/unit/harness/test_provider_runtime.py +++ b/tests/unit/harness/test_provider_runtime.py @@ -4,8 +4,8 @@ import http.client import json -import shutil import subprocess +import sys from concurrent.futures import ThreadPoolExecutor from concurrent.futures import TimeoutError as FutureTimeoutError from pathlib import Path @@ -21,7 +21,7 @@ post, ) -from python_lang_project_harness._runtime import _health, _response_frame +from asp_python._runtime import _health, _response_frame def _read_bootstrap(process: subprocess.Popen[str]) -> dict[str, object]: @@ -71,8 +71,10 @@ def test_resident_runtime_publishes_manifest_operations_and_structured_frames( def test_http_json_live_corpus_stream_query_concurrency_and_latency() -> None: - provider = shutil.which("asp-python") - assert provider is not None, "uv project environment omitted asp-python" + provider = Path(sys.executable).with_name("asp-python") + assert provider.is_file(), ( + "asp-python entrypoint is absent beside the active Python" + ) process = subprocess.Popen( [provider, "serve"], cwd=Path(__file__).parents[3], diff --git a/tests/unit/harness/test_public_cli_identity.py b/tests/unit/harness/test_public_cli_identity.py index 66cad63..b817d7d 100644 --- a/tests/unit/harness/test_public_cli_identity.py +++ b/tests/unit/harness/test_public_cli_identity.py @@ -1,12 +1,10 @@ from __future__ import annotations -from python_lang_project_harness._cli_args import help_text +from asp_python._cli_args import help_text -def test_public_cli_identity_is_asp_python_without_legacy_aliases() -> None: +def test_public_cli_identity_is_asp_python() -> 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 88ac56a..01ea20e 100644 --- a/tests/unit/harness/test_pyproject_package_scope.py +++ b/tests/unit/harness/test_pyproject_package_scope.py @@ -2,13 +2,13 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path -def test_run_python_project_harness_uses_pyproject_package_scope( +def test_run_asp_python_uses_pyproject_package_scope( tmp_path: Path, ) -> None: default_src = tmp_path / "src" @@ -47,7 +47,7 @@ def test_run_python_project_harness_uses_pyproject_package_scope( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert [module.path for module in report.modules] == [ diff --git a/tests/unit/harness/test_pytest.py b/tests/unit/harness/test_pytest.py index ef4ca83..65f45bd 100644 --- a/tests/unit/harness/test_pytest.py +++ b/tests/unit/harness/test_pytest.py @@ -2,17 +2,17 @@ from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import PythonHarnessConfig, python_project_harness_test -from python_lang_project_harness.pytest import ( - python_project_harness_test as facade_python_project_harness_test, +from asp_python import AspPythonConfig, asp_python_test +from asp_python.pytest import ( + asp_python_test as facade_asp_python_test, ) +from python_lang_parser import PythonDiagnosticSeverity if TYPE_CHECKING: from pathlib import Path -def test_python_project_harness_test_returns_pytest_collectable_callable( +def test_asp_python_test_returns_pytest_collectable_callable( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -27,14 +27,14 @@ def test_python_project_harness_test_returns_pytest_collectable_callable( encoding="utf-8", ) - harness_test = python_project_harness_test(tmp_path) + harness_test = asp_python_test(tmp_path) - assert harness_test.__name__ == "test_python_project_harness_policy" - assert harness_test.__qualname__ == "test_python_project_harness_policy" + assert harness_test.__name__ == "test_asp_python_policy" + assert harness_test.__qualname__ == "test_asp_python_policy" harness_test() -def test_python_project_harness_test_defaults_to_current_project_root( +def test_asp_python_test_defaults_to_current_project_root( tmp_path: Path, monkeypatch, ) -> None: @@ -50,23 +50,23 @@ def test_python_project_harness_test_defaults_to_current_project_root( monkeypatch.chdir(tmp_path) - harness_test = python_project_harness_test() + harness_test = asp_python_test() harness_test() def test_public_pytest_facade_exposes_collectable_helper() -> None: - assert facade_python_project_harness_test is python_project_harness_test + assert facade_asp_python_test is asp_python_test -def test_python_project_harness_test_blocks_with_compact_snapshot( +def test_asp_python_test_blocks_with_compact_snapshot( tmp_path: Path, ) -> None: src = tmp_path / "src" src.mkdir() source = src / "library.py" source.write_text('def run() -> None:\n print("debug")\n', encoding="utf-8") - harness_test = python_project_harness_test(tmp_path) + harness_test = asp_python_test(tmp_path) try: harness_test() @@ -82,14 +82,14 @@ def test_python_project_harness_test_blocks_with_compact_snapshot( assert "[advice]" in message -def test_python_project_harness_test_can_disable_agent_advice( +def test_asp_python_test_can_disable_agent_advice( tmp_path: Path, ) -> None: src = tmp_path / "src" src.mkdir() source = src / "library.py" source.write_text('def run() -> None:\n print("debug")\n', encoding="utf-8") - harness_test = python_project_harness_test(tmp_path, include_advice=False) + harness_test = asp_python_test(tmp_path, include_advice=False) try: harness_test() @@ -103,7 +103,7 @@ def test_python_project_harness_test_can_disable_agent_advice( assert "[advice]" not in message -def test_python_project_harness_test_honors_embedded_options( +def test_asp_python_test_honors_embedded_options( tmp_path: Path, ) -> None: lib = tmp_path / "lib" @@ -116,7 +116,7 @@ def test_python_project_harness_test_honors_embedded_options( ) (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - harness_test = python_project_harness_test( + harness_test = asp_python_test( tmp_path, severities=frozenset({PythonDiagnosticSeverity.ERROR}), include_tests=False, @@ -129,7 +129,7 @@ def test_python_project_harness_test_honors_embedded_options( harness_test() -def test_python_project_harness_test_honors_configured_project_resolution( +def test_asp_python_test_honors_configured_project_resolution( tmp_path: Path, ) -> None: lib = tmp_path / "lib" @@ -139,15 +139,15 @@ def test_python_project_harness_test_honors_configured_project_resolution( (lib / "library.py").write_text('"""Library docs."""\n', encoding="utf-8") (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - harness_test = python_project_harness_test( + harness_test = asp_python_test( tmp_path, - config=PythonHarnessConfig(source_dir_names=("lib",), include_tests=False), + config=AspPythonConfig(source_dir_names=("lib",), include_tests=False), ) harness_test() -def test_python_project_harness_test_honors_extra_project_paths( +def test_asp_python_test_honors_extra_project_paths( tmp_path: Path, ) -> None: src = tmp_path / "src" @@ -157,7 +157,7 @@ def test_python_project_harness_test_honors_extra_project_paths( (src / "library.py").write_text('"""Library docs."""\n', encoding="utf-8") (tools / "check.py").write_text('"""Check docs."""\n', encoding="utf-8") - harness_test = python_project_harness_test( + harness_test = asp_python_test( tmp_path, extra_path_names=("tools",), ) diff --git a/tests/unit/harness/test_pytest_plugin.py b/tests/unit/harness/test_pytest_plugin.py index 7b4e914..dfad75c 100644 --- a/tests/unit/harness/test_pytest_plugin.py +++ b/tests/unit/harness/test_pytest_plugin.py @@ -4,6 +4,8 @@ import sys from typing import TYPE_CHECKING +from asp_python._pytest_plugin_project import _package_scoped_root + if TYPE_CHECKING: from pathlib import Path @@ -92,6 +94,64 @@ def test_pytest_plugin_reports_compact_harness_failure( ) +def test_pytest_plugin_scopes_package_target_to_nearest_project( + tmp_path: Path, +) -> None: + package_root = tmp_path / "packages" / "python" / "graphs" + test_path = package_root / "tests" / "test_graphs.py" + test_path.parent.mkdir(parents=True) + (package_root / "pyproject.toml").write_text( + """ +[project] +name = "graphs" +version = "0.1.0" +""".lstrip(), + encoding="utf-8", + ) + test_path.write_text( + "def test_graphs() -> None:\n assert True\n", + encoding="utf-8", + ) + + assert ( + _package_scoped_root( + tmp_path, + ("packages/python/graphs/tests",), + invocation_dir=tmp_path, + ) + == package_root + ) + + +def test_pytest_plugin_keeps_workspace_scope_for_mixed_projects( + tmp_path: Path, +) -> None: + first = tmp_path / "packages" / "python" / "first" + second = tmp_path / "packages" / "python" / "second" + for package in (first, second): + (package / "tests").mkdir(parents=True) + (package / "pyproject.toml").write_text( + '[project]\nname = "package"\nversion = "0.1.0"\n', + encoding="utf-8", + ) + (package / "tests" / "test_package.py").write_text( + "def test_package() -> None:\n assert True\n", + encoding="utf-8", + ) + + assert ( + _package_scoped_root( + tmp_path, + ( + "packages/python/first/tests", + "packages/python/second/tests", + ), + invocation_dir=tmp_path, + ) + is None + ) + + def test_pytest_plugin_honors_dev_dependency_options( tmp_path: Path, ) -> None: @@ -157,7 +217,7 @@ def test_pytest_plugin_loads_project_policy_config_from_pyproject( tests.mkdir(parents=True) (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] """.lstrip(), encoding="utf-8", diff --git a/tests/unit/harness/test_reasoning_tree_policy.py b/tests/unit/harness/test_reasoning_tree_policy.py index 87a5d01..b5642d8 100644 --- a/tests/unit/harness/test_reasoning_tree_policy.py +++ b/tests/unit/harness/test_reasoning_tree_policy.py @@ -2,7 +2,7 @@ from pathlib import Path -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python _PROJECT_ROOT = next( parent @@ -26,7 +26,7 @@ def test_modularity_rule_pack_blocks_shadowed_reasoning_tree_owner( encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -45,7 +45,7 @@ def test_agent_policy_reports_branch_package_without_reasoning_tree_intent( (branch / "service.py").write_text('"""Service leaf."""\n', encoding="utf-8") (branch / "models.py").write_text('"""Model leaf."""\n', encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -68,20 +68,17 @@ def test_agent_policy_accepts_documented_reasoning_tree_branch( (branch / "service.py").write_text('"""Service leaf."""\n', encoding="utf-8") (branch / "models.py").write_text('"""Model leaf."""\n', encoding="utf-8") - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean def test_reasoning_tree_policy_uses_parser_facts() -> None: - modularity = ( - _PROJECT_ROOT / "src" / "python_lang_project_harness" / "_modularity.py" - ).read_text(encoding="utf-8") + modularity = (_PROJECT_ROOT / "src" / "asp_python" / "_modularity.py").read_text( + encoding="utf-8" + ) agent_reasoning_tree = ( - _PROJECT_ROOT - / "src" - / "python_lang_project_harness" - / "_agent_reasoning_tree.py" + _PROJECT_ROOT / "src" / "asp_python" / "_agent_reasoning_tree.py" ).read_text(encoding="utf-8") assert "python_reasoning_tree_facts(" in modularity diff --git a/tests/unit/harness/test_render_snapshots.py b/tests/unit/harness/test_render_snapshots.py index 50489f8..080f7eb 100644 --- a/tests/unit/harness/test_render_snapshots.py +++ b/tests/unit/harness/test_render_snapshots.py @@ -4,6 +4,14 @@ from snapshot_support import assert_snapshot +from asp_python import ( + AspPythonFinding, + AspPythonReport, + PythonProjectHarnessScope, + render_python_lang_harness, + render_python_lang_harness_json, + render_python_reasoning_tree, +) from python_lang_parser import ( PythonDiagnosticSeverity, PythonModuleReport, @@ -14,26 +22,18 @@ SourceLocation, parse_python_source, ) -from python_lang_project_harness import ( - PythonHarnessFinding, - PythonHarnessReport, - PythonProjectHarnessScope, - render_python_lang_harness, - render_python_lang_harness_json, - render_python_reasoning_tree, -) def test_compact_text_render_matches_snapshot() -> None: rendered = render_python_lang_harness(_snapshot_report()) - assert_snapshot("python_project_harness_compact_text", rendered) + assert_snapshot("asp_python_compact_text", rendered) def test_json_render_matches_snapshot() -> None: rendered = render_python_lang_harness_json(_snapshot_report()) - assert_snapshot("python_project_harness_json", rendered) + assert_snapshot("asp_python_json", rendered) def test_reasoning_tree_render_matches_snapshot() -> None: @@ -46,7 +46,7 @@ def test_reasoning_tree_render_uses_project_relative_paths(tmp_path: Path) -> No src = tmp_path / "src" package = src / "pkg" package.mkdir(parents=True) - report = PythonHarnessReport( + report = AspPythonReport( modules=( parse_python_source( '"""Package docs."""\n\nVALUE = 1\n', @@ -95,10 +95,10 @@ def test_compact_text_render_uses_project_relative_finding_paths( ) -> None: src = tmp_path / "src" src.mkdir() - report = PythonHarnessReport( + report = AspPythonReport( modules=(), findings=( - PythonHarnessFinding( + AspPythonFinding( rule_id="PY-MOD-R002", pack_id="python.modern_design", severity=PythonDiagnosticSeverity.WARNING, @@ -125,9 +125,9 @@ def test_compact_text_render_uses_project_relative_finding_paths( assert "path=src/library.py line=3 column=5" in rendered -def _snapshot_report() -> PythonHarnessReport: +def _snapshot_report() -> AspPythonReport: source_path = "$TEMP/src/service.py" - return PythonHarnessReport( + return AspPythonReport( modules=( PythonModuleReport( path=source_path, @@ -135,7 +135,7 @@ def _snapshot_report() -> PythonHarnessReport: ), ), findings=( - PythonHarnessFinding( + AspPythonFinding( rule_id="PY-MOD-R002", pack_id="python.modern_design", severity=PythonDiagnosticSeverity.WARNING, @@ -155,10 +155,10 @@ def _snapshot_report() -> PythonHarnessReport: ) -def _reasoning_tree_snapshot_report() -> PythonHarnessReport: +def _reasoning_tree_snapshot_report() -> AspPythonReport: root = Path("$TEMP") src = root / "src" - return PythonHarnessReport( + return AspPythonReport( modules=( parse_python_source( '"""Domain package owner."""\n\nfrom .service import build\n\n__all__ = ("build",)\n', diff --git a/tests/unit/harness/test_runner_config.py b/tests/unit/harness/test_runner_config.py index 4ba77bc..7977442 100644 --- a/tests/unit/harness/test_runner_config.py +++ b/tests/unit/harness/test_runner_config.py @@ -2,10 +2,10 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( - PythonHarnessConfig, +from asp_python import ( + AspPythonConfig, + run_asp_python, run_python_lang_harness, - run_python_project_harness, ) if TYPE_CHECKING: @@ -31,9 +31,9 @@ def test_project_runner_uses_configured_source_and_test_roots( encoding="utf-8", ) - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig( + config=AspPythonConfig( source_dir_names=("lib",), test_dir_names=("checks",), ), @@ -60,9 +60,9 @@ def test_project_runner_parameters_override_configured_roots( (lib / "included.py").write_text('"""Included docs."""\n', encoding="utf-8") (src / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig(source_dir_names=("lib",)), + config=AspPythonConfig(source_dir_names=("lib",)), source_dir_names=("src",), include_tests=False, ) @@ -89,9 +89,9 @@ def test_project_runner_parameters_override_configured_extra_paths( (examples / "demo.py").write_text('"""Example docs."""\n', encoding="utf-8") (tools / "check.py").write_text('"""Check docs."""\n', encoding="utf-8") - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig(extra_path_names=("examples",)), + config=AspPythonConfig(extra_path_names=("examples",)), extra_path_names=("tools",), ) @@ -113,9 +113,9 @@ def test_project_runner_can_exclude_tests_from_config(tmp_path: Path) -> None: (src / "service.py").write_text('"""Service docs."""\n', encoding="utf-8") (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig(include_tests=False), + config=AspPythonConfig(include_tests=False), ) assert report.is_clean @@ -133,9 +133,9 @@ def test_project_runner_can_disable_policy_rules_from_config(tmp_path: Path) -> encoding="utf-8", ) - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig(disabled_rule_ids=frozenset({"PY-MOD-R002"})), + config=AspPythonConfig(disabled_rule_ids=frozenset({"PY-MOD-R002"})), ) assert report.is_clean @@ -148,7 +148,7 @@ def test_project_runner_loads_policy_config_from_pyproject(tmp_path: Path) -> No src.mkdir() (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] """.lstrip(), encoding="utf-8", @@ -158,7 +158,7 @@ def test_project_runner_loads_policy_config_from_pyproject(tmp_path: Path) -> No encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.is_clean assert report.disabled_rule_ids == frozenset({"PY-MOD-R002"}) @@ -171,7 +171,7 @@ def test_explicit_project_config_overrides_pyproject_policy_config( src.mkdir() (tmp_path / "pyproject.toml").write_text( """ -[tool.python-lang-project-harness] +[tool.asp-python] disabled_rule_ids = ["PY-MOD-R002"] """.lstrip(), encoding="utf-8", @@ -181,7 +181,7 @@ def test_explicit_project_config_overrides_pyproject_policy_config( encoding="utf-8", ) - report = run_python_project_harness(tmp_path, config=PythonHarnessConfig()) + report = run_asp_python(tmp_path, config=AspPythonConfig()) assert not report.is_clean assert report.disabled_rule_ids == frozenset() @@ -195,11 +195,9 @@ def test_project_runner_can_promote_policy_rules_from_config(tmp_path: Path) -> encoding="utf-8", ) - report = run_python_project_harness( + report = run_asp_python( tmp_path, - config=PythonHarnessConfig( - blocking_rule_ids=frozenset({"PY-AGENT-POLICY-001"}) - ), + config=AspPythonConfig(blocking_rule_ids=frozenset({"PY-AGENT-POLICY-001"})), ) assert not report.is_clean @@ -213,7 +211,7 @@ def test_runner_rejects_missing_project_root_and_explicit_path(tmp_path: Path) - missing = tmp_path / "missing" try: - run_python_project_harness(missing) + run_asp_python(missing) except ValueError as error: assert str(error) == f"project root does not exist: {missing}" else: diff --git a/tests/unit/harness/test_semantic_agent_cli.py b/tests/unit/harness/test_semantic_agent_cli.py index a5bd882..c3beb44 100644 --- a/tests/unit/harness/test_semantic_agent_cli.py +++ b/tests/unit/harness/test_semantic_agent_cli.py @@ -5,7 +5,7 @@ import io from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_agent_install_reports_root_asp_owner( diff --git a/tests/unit/harness/test_semantic_cli.py b/tests/unit/harness/test_semantic_cli.py index 6bf1c56..0466a33 100644 --- a/tests/unit/harness/test_semantic_cli.py +++ b/tests/unit/harness/test_semantic_cli.py @@ -6,7 +6,7 @@ import json from pathlib import Path -from python_lang_project_harness import python_semantic_language_registration, run_cli +from asp_python import python_semantic_language_registration, run_cli def test_cli_agent_doctor_advertises_provider(tmp_path: Path) -> None: diff --git a/tests/unit/harness/test_semantic_cli_ast_patch.py b/tests/unit/harness/test_semantic_cli_ast_patch.py index 7135139..d5db252 100644 --- a/tests/unit/harness/test_semantic_cli_ast_patch.py +++ b/tests/unit/harness/test_semantic_cli_ast_patch.py @@ -4,7 +4,7 @@ import json from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_ast_patch_dry_run_returns_provider_unsupported_operation_receipt( diff --git a/tests/unit/harness/test_semantic_cli_benchmark_registry.py b/tests/unit/harness/test_semantic_cli_benchmark_registry.py index 96e52b3..f53490b 100644 --- a/tests/unit/harness/test_semantic_cli_benchmark_registry.py +++ b/tests/unit/harness/test_semantic_cli_benchmark_registry.py @@ -1,6 +1,6 @@ """Registry benchmark invocation contract tests.""" -from python_lang_project_harness import python_semantic_language_registration +from asp_python import python_semantic_language_registration def test_registered_search_methods_publish_public_benchmark_invocations() -> None: diff --git a/tests/unit/harness/test_semantic_cli_fast_prime.py b/tests/unit/harness/test_semantic_cli_fast_prime.py index b2f1189..ba33fb0 100644 --- a/tests/unit/harness/test_semantic_cli_fast_prime.py +++ b/tests/unit/harness/test_semantic_cli_fast_prime.py @@ -8,7 +8,7 @@ import pytest -from python_lang_project_harness import run_cli +from asp_python import run_cli FAST_SEARCH_BUDGET_SECONDS = 0.25 @@ -21,7 +21,7 @@ def test_cli_search_prime_seed_view_uses_fast_frontier( source_path.parent.mkdir(parents=True) source_path.write_text("def build():\n return 1\n", encoding="utf-8") - from python_lang_project_harness import _cli_protocol + from asp_python import _cli_protocol def fail_full_harness(*_args: object, **_kwargs: object) -> object: raise AssertionError("full harness should not run for prime seed view") diff --git a/tests/unit/harness/test_semantic_cli_lexical.py b/tests/unit/harness/test_semantic_cli_lexical.py index cf6fb65..774eb2d 100644 --- a/tests/unit/harness/test_semantic_cli_lexical.py +++ b/tests/unit/harness/test_semantic_cli_lexical.py @@ -7,7 +7,7 @@ from semantic_search_fixture import require_compact_graph_renderer, write_search_fixture -from python_lang_project_harness._cli import run_cli +from asp_python._cli import run_cli def test_cli_search_lexical_query_set(tmp_path: Path) -> None: @@ -75,8 +75,8 @@ def test_cli_search_lexical_matches_path_only_candidate(tmp_path: Path) -> None: def test_protocol_search_lexical_query_uses_fast_frontier( tmp_path: Path, ) -> None: - from python_lang_project_harness._cli_args import ProtocolArgs - from python_lang_project_harness._cli_protocol import run_protocol_cli + from asp_python._cli_args import ProtocolArgs + from asp_python._cli_protocol import run_protocol_cli write_search_fixture(tmp_path) path_owner = tmp_path / "src" / "pkg" / "hook_runtime.py" @@ -127,12 +127,12 @@ def test_protocol_search_lexical_query_uses_native_prefilter_without_tools( tmp_path: Path, monkeypatch, ) -> None: - from python_lang_project_harness import ( + from asp_python import ( _semantic_search_prefilter, _semantic_search_prefilter_file_scan, ) - from python_lang_project_harness._cli_args import ProtocolArgs - from python_lang_project_harness._cli_protocol import run_protocol_cli + from asp_python._cli_args import ProtocolArgs + from asp_python._cli_protocol import run_protocol_cli monkeypatch.setattr(_semantic_search_prefilter.shutil, "which", lambda _name: None) monkeypatch.setattr( @@ -188,12 +188,12 @@ def test_protocol_search_lexical_source_query_uses_rglob_source_without_tools( tmp_path: Path, monkeypatch, ) -> None: - from python_lang_project_harness import ( + from asp_python import ( _semantic_search_prefilter, _semantic_search_prefilter_file_scan, ) - from python_lang_project_harness._cli_args import ProtocolArgs - from python_lang_project_harness._cli_protocol import run_protocol_cli + from asp_python._cli_args import ProtocolArgs + from asp_python._cli_protocol import run_protocol_cli monkeypatch.setattr(_semantic_search_prefilter.shutil, "which", lambda _name: None) monkeypatch.setattr( diff --git a/tests/unit/harness/test_semantic_cli_owner_item_broad.py b/tests/unit/harness/test_semantic_cli_owner_item_broad.py index 8800221..9a58afb 100644 --- a/tests/unit/harness/test_semantic_cli_owner_item_broad.py +++ b/tests/unit/harness/test_semantic_cli_owner_item_broad.py @@ -3,7 +3,7 @@ import io from pathlib import Path -from python_lang_project_harness._cli import run_cli +from asp_python._cli import run_cli def _write_project(tmp_path: Path) -> None: diff --git a/tests/unit/harness/test_semantic_cli_owner_item_inventory.py b/tests/unit/harness/test_semantic_cli_owner_item_inventory.py index 483dd00..18aec93 100644 --- a/tests/unit/harness/test_semantic_cli_owner_item_inventory.py +++ b/tests/unit/harness/test_semantic_cli_owner_item_inventory.py @@ -5,7 +5,7 @@ import io from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_search_owner_items_without_query_returns_inventory( diff --git a/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py b/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py index 05f6001..7d1bff7 100644 --- a/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py +++ b/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py @@ -9,7 +9,7 @@ from semantic_search_fixture import write_search_fixture -from python_lang_project_harness import run_cli +from asp_python import run_cli OWNER_ITEMS_WARM_PATH_GATE_MS = 100.0 OWNER_WARM_PATH_GATE_MS = 100.0 @@ -26,9 +26,9 @@ def test_cli_search_owner_items_query_uses_exact_owner_fast_path( def fail_full_harness(*_args: object, **_kwargs: object) -> object: raise AssertionError("owner-items should not run the full Python harness") - from python_lang_project_harness import _runner + from asp_python import _runner - monkeypatch.setattr(_runner, "run_python_project_harness", fail_full_harness) + monkeypatch.setattr(_runner, "run_asp_python", fail_full_harness) exit_code = run_cli( [ @@ -95,9 +95,9 @@ def test_cli_search_owner_path_uses_exact_owner_fast_path( def fail_full_harness(*_args: object, **_kwargs: object) -> object: raise AssertionError("owner path search should not run the full Python harness") - from python_lang_project_harness import _runner + from asp_python import _runner - monkeypatch.setattr(_runner, "run_python_project_harness", fail_full_harness) + monkeypatch.setattr(_runner, "run_asp_python", fail_full_harness) exit_code = run_cli( [ @@ -158,7 +158,7 @@ def test_cli_search_owner_seed_view_uses_text_fast_path( def fail_full_harness(*_args: object, **_kwargs: object) -> object: raise AssertionError("owner seed view should not run the full Python harness") - from python_lang_project_harness import _cli_protocol + from asp_python import _cli_protocol monkeypatch.setattr(_cli_protocol, "_run_search_harness", fail_full_harness) @@ -195,9 +195,9 @@ def test_cli_search_dependency_uses_metadata_fast_path( def fail_full_harness(*_args: object, **_kwargs: object) -> object: raise AssertionError("dependency search should not run the full Python harness") - from python_lang_project_harness import _runner + from asp_python import _runner - monkeypatch.setattr(_runner, "run_python_project_harness", fail_full_harness) + monkeypatch.setattr(_runner, "run_asp_python", fail_full_harness) exit_code = run_cli( [ diff --git a/tests/unit/harness/test_semantic_cli_policy.py b/tests/unit/harness/test_semantic_cli_policy.py index b560269..a80a514 100644 --- a/tests/unit/harness/test_semantic_cli_policy.py +++ b/tests/unit/harness/test_semantic_cli_policy.py @@ -6,7 +6,7 @@ from semantic_search_fixture import compact_graph_renderer_available -from python_lang_project_harness._cli import run_cli +from asp_python._cli import run_cli def test_cli_agent_doctor_json_advertises_policy_search( @@ -99,10 +99,7 @@ def test_cli_search_policy_renders_semantic_handles( if compact_graph_renderer_available(): assert seeds.startswith("[search-policy] q=PY-AGENT-PROJECT-001") assert "alg=policy-handle-catalog" in seeds - assert ( - "O=owner:path(src/python_lang_project_harness/_project_policy_catalog.py)!owner" - in seeds - ) + assert "O=owner:path(src/asp_python/_project_policy_catalog.py)!owner" in seeds assert "tests/unit/harness/project_policy/test_layout.py" in seeds assert ( "|handle PY-AGENT-PROJECT-001 kind=policy-rule source=provider-policy" @@ -113,7 +110,7 @@ def test_cli_search_policy_renders_semantic_handles( assert packet["view"] == "policy" assert packet["semanticHandles"][0]["id"] == "PY-AGENT-POLICY-008" assert packet["semanticHandles"][0]["ownerPath"] == ( - "src/python_lang_project_harness/_agent_policy_catalog.py" + "src/asp_python/_agent_policy_catalog.py" ) assert ( "tests/unit/harness/test_agent_policy.py" diff --git a/tests/unit/harness/test_semantic_cli_public_external_types.py b/tests/unit/harness/test_semantic_cli_public_external_types.py index b41c1ed..edc4b78 100644 --- a/tests/unit/harness/test_semantic_cli_public_external_types.py +++ b/tests/unit/harness/test_semantic_cli_public_external_types.py @@ -8,7 +8,7 @@ from semantic_search_fixture import write_search_fixture -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_search_public_external_types_uses_public_api_facts( diff --git a/tests/unit/harness/test_semantic_cli_query_set.py b/tests/unit/harness/test_semantic_cli_query_set.py index cbd8be1..c3fbd01 100644 --- a/tests/unit/harness/test_semantic_cli_query_set.py +++ b/tests/unit/harness/test_semantic_cli_query_set.py @@ -9,7 +9,7 @@ from semantic_search_fixture import write_search_fixture -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_search_text_prefilter_large_project_records_runtime_cost( diff --git a/tests/unit/harness/test_semantic_cli_reasoning.py b/tests/unit/harness/test_semantic_cli_reasoning.py index 2e5726b..b05f361 100644 --- a/tests/unit/harness/test_semantic_cli_reasoning.py +++ b/tests/unit/harness/test_semantic_cli_reasoning.py @@ -8,7 +8,7 @@ from semantic_search_fixture import write_search_fixture -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_agent_doctor_advertises_reasoning_search(tmp_path: Path) -> None: diff --git a/tests/unit/harness/test_semantic_cli_structural_selector_registry.py b/tests/unit/harness/test_semantic_cli_structural_selector_registry.py index 7514f00..bc84c10 100644 --- a/tests/unit/harness/test_semantic_cli_structural_selector_registry.py +++ b/tests/unit/harness/test_semantic_cli_structural_selector_registry.py @@ -4,7 +4,7 @@ import json from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_query_registry_owns_structural_selector_projection( diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py b/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py index 39059cf..f874fe0 100644 --- a/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py +++ b/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py @@ -8,7 +8,7 @@ from semantic_search_fixture import write_search_fixture -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_query_inline_s_expression_applies_predicate_matrix( 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 8d50358..bb3141c 100644 --- a/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py +++ b/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py @@ -6,7 +6,7 @@ import json from pathlib import Path -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_agent_doctor_advertises_tree_sitter_query_descriptor( diff --git a/tests/unit/harness/test_semantic_cli_workspace_search.py b/tests/unit/harness/test_semantic_cli_workspace_search.py index 5ae9022..7ffe087 100644 --- a/tests/unit/harness/test_semantic_cli_workspace_search.py +++ b/tests/unit/harness/test_semantic_cli_workspace_search.py @@ -8,7 +8,7 @@ from semantic_search_fixture import write_search_fixture -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_cli_search_workspace_prime_and_text_pipe(tmp_path: Path) -> None: diff --git a/tests/unit/harness/test_semantic_graph_facts.py b/tests/unit/harness/test_semantic_graph_facts.py index 6581eee..3dafa29 100644 --- a/tests/unit/harness/test_semantic_graph_facts.py +++ b/tests/unit/harness/test_semantic_graph_facts.py @@ -5,7 +5,7 @@ import io import json -from python_lang_project_harness import run_cli +from asp_python import run_cli def test_search_semantic_facts_emits_field_type_collection_graph(tmp_path): diff --git a/tests/unit/harness/test_semantic_language_schemas.py b/tests/unit/harness/test_semantic_language_schemas.py index 9f6cf91..bf06391 100644 --- a/tests/unit/harness/test_semantic_language_schemas.py +++ b/tests/unit/harness/test_semantic_language_schemas.py @@ -2,7 +2,7 @@ from __future__ import annotations -from python_lang_project_harness import python_semantic_language_registration +from asp_python import python_semantic_language_registration def test_python_registration_advertises_only_provider_owned_schemas() -> None: diff --git a/tests/unit/harness/test_semantic_provider_doctor.py b/tests/unit/harness/test_semantic_provider_doctor.py index 6099087..54cf81d 100644 --- a/tests/unit/harness/test_semantic_provider_doctor.py +++ b/tests/unit/harness/test_semantic_provider_doctor.py @@ -5,7 +5,7 @@ from hashlib import sha256 from pathlib import Path -from python_lang_project_harness._cli import run_cli +from asp_python._cli import run_cli def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( @@ -43,7 +43,7 @@ def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( registration["binary"], ) descriptors = registration["methodDescriptors"] - assert len(descriptors) == len(registration["methods"]) == 33 + assert len(descriptors) == len(registration["methods"]) == 31 exact_query = next( descriptor for descriptor in descriptors diff --git a/tests/unit/harness/test_semantic_render_flow.py b/tests/unit/harness/test_semantic_render_flow.py index 717f419..70c41e6 100644 --- a/tests/unit/harness/test_semantic_render_flow.py +++ b/tests/unit/harness/test_semantic_render_flow.py @@ -2,7 +2,7 @@ from __future__ import annotations -from python_lang_project_harness._semantic_search_render_flow import finding_lines +from asp_python._semantic_search_render_flow import finding_lines def test_semantic_search_findings_render_path_first() -> None: diff --git a/tests/unit/harness/test_semantic_search_graph_profiles.py b/tests/unit/harness/test_semantic_search_graph_profiles.py index bf0dd41..e544787 100644 --- a/tests/unit/harness/test_semantic_search_graph_profiles.py +++ b/tests/unit/harness/test_semantic_search_graph_profiles.py @@ -6,7 +6,7 @@ import pytest -from python_lang_project_harness._semantic_search_graph_render import ( +from asp_python._semantic_search_graph_render import ( compact_graph_seed_packet_text, ) diff --git a/tests/unit/harness/test_semantic_search_graph_render_shell_out.py b/tests/unit/harness/test_semantic_search_graph_render_shell_out.py index fde2015..31a1b27 100644 --- a/tests/unit/harness/test_semantic_search_graph_render_shell_out.py +++ b/tests/unit/harness/test_semantic_search_graph_render_shell_out.py @@ -5,7 +5,7 @@ import pytest -from python_lang_project_harness._semantic_search_graph_render import ( +from asp_python._semantic_search_graph_render import ( SEMANTIC_AGENT_PROTOCOL_BIN_ENV, CompactGraphRenderError, compact_graph_seed_packet_text, diff --git a/tests/unit/harness/test_semantic_search_ingest_cli.py b/tests/unit/harness/test_semantic_search_ingest_cli.py index bbbe7ce..afabd69 100644 --- a/tests/unit/harness/test_semantic_search_ingest_cli.py +++ b/tests/unit/harness/test_semantic_search_ingest_cli.py @@ -8,8 +8,8 @@ import pytest -from python_lang_project_harness import python_semantic_language_registration, run_cli -from python_lang_project_harness._semantic_search_cli import parse_semantic_search_args +from asp_python import python_semantic_language_registration, run_cli +from asp_python._semantic_search_cli import parse_semantic_search_args FAST_INGEST_BUDGET_SECONDS = 0.25 @@ -56,7 +56,7 @@ def test_search_ingest_empty_stdin_seeds_explains_prime_route( '[project]\nname = "sample"\nversion = "0.1.0"\n', encoding="utf-8", ) - from python_lang_project_harness import _cli_protocol + from asp_python import _cli_protocol def fail_full_harness(*_args: object, **_kwargs: object) -> object: raise AssertionError("full harness should not run for empty ingest seeds") diff --git a/tests/unit/harness/test_software_criterion_snapshots.py b/tests/unit/harness/test_software_criterion_snapshots.py index cbb5057..92c9dcb 100644 --- a/tests/unit/harness/test_software_criterion_snapshots.py +++ b/tests/unit/harness/test_software_criterion_snapshots.py @@ -8,14 +8,14 @@ from syrupy.extensions.json import JSONSnapshotExtension -from python_lang_project_harness import ( +from asp_python import ( run_python_lang_harness, ) if TYPE_CHECKING: from syrupy.assertion import SnapshotAssertion - from python_lang_project_harness import PythonHarnessReport + from asp_python import AspPythonReport _SCENARIO = ( @@ -126,8 +126,8 @@ def _copy_inputs(source_dir: Path, destination_dir: Path) -> None: def _filter_software_criterion_findings( - report: PythonHarnessReport, -) -> PythonHarnessReport: + report: AspPythonReport, +) -> AspPythonReport: return replace( report, findings=tuple( diff --git a/tests/unit/harness/test_test_layout_config.py b/tests/unit/harness/test_test_layout_config.py index 22dd346..20bb13b 100644 --- a/tests/unit/harness/test_test_layout_config.py +++ b/tests/unit/harness/test_test_layout_config.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import run_python_project_harness +from asp_python import run_asp_python if TYPE_CHECKING: from pathlib import Path @@ -28,7 +28,7 @@ def test_layout_policy_requires_explanation_for_root_file_exception( ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == ["PY-TEST-R001"] _write_policy( @@ -40,7 +40,7 @@ def test_layout_policy_requires_explanation_for_root_file_exception( ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any(finding.rule_id == "PY-TEST-R001" for finding in report.findings) @@ -64,7 +64,7 @@ def test_layout_policy_requires_explanation_for_directory_exception( ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert [finding.rule_id for finding in report.findings] == ["PY-TEST-R002"] _write_policy( @@ -76,7 +76,7 @@ def test_layout_policy_requires_explanation_for_directory_exception( ] """, ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert not any(finding.rule_id == "PY-TEST-R002" for finding in report.findings) diff --git a/tests/unit/harness/test_verification.py b/tests/unit/harness/test_verification.py index c0f91cc..bd5212e 100644 --- a/tests/unit/harness/test_verification.py +++ b/tests/unit/harness/test_verification.py @@ -3,7 +3,7 @@ import json from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationDependencySignal, PythonVerificationPhase, @@ -19,8 +19,8 @@ build_python_verification_report_bundle, default_python_harness_config, plan_python_project_verification_with_config, - read_python_project_harness_config, - render_python_project_harness_agent_snapshot_with_config, + read_asp_python_config, + render_asp_python_agent_snapshot_with_config, render_python_verification_plan, render_python_verification_profile_index, render_python_verification_profile_index_json, @@ -192,7 +192,7 @@ def test_verification_policy_can_be_loaded_from_pyproject_config( _write_public_api_project( tmp_path, harness_config=""" -[tool.python-lang-project-harness.verification] +[tool.asp-python.verification] profile_hints = [ { owner_path = "src/pkg/api.py", responsibilities = ["public_api"], task_kinds = ["security"], rationale = "authz-sensitive public API" }, ] @@ -200,18 +200,18 @@ def test_verification_policy_can_be_loaded_from_pyproject_config( { package_name = "httpx", responsibilities = ["network"], task_kinds = ["stress"] }, ] -[tool.python-lang-project-harness.verification.task_contracts] +[tool.asp-python.verification.task_contracts] security = { phase = "before_release", summary = "security skill must report authz evidence", requirements = [{ label = "authz", detail = "tenant authorization result" }] } -[tool.python-lang-project-harness.verification.skill_bindings] +[tool.asp-python.verification.skill_bindings] security = { skill = "python-security-review", adapter = "bandit" } -[tool.python-lang-project-harness.verification.skill_descriptors] +[tool.asp-python.verification.skill_descriptors] python-security-review = { task_kind = "security", adapter = "bandit", summary = "run bandit plus tenant authz probes", requirements = [{ label = "bandit", detail = "bandit report artifact" }] } """, ) - config = read_python_project_harness_config(tmp_path) + config = read_asp_python_config(tmp_path) assert config is not None assert config.verification_policy.profile_hints[0].owner_path == "src/pkg/api.py" @@ -309,7 +309,7 @@ def test_agent_snapshot_includes_active_verification_tasks( .with_rationale("this public API needs a security review") ) - rendered = render_python_project_harness_agent_snapshot_with_config( + rendered = render_asp_python_agent_snapshot_with_config( tmp_path, config, ) diff --git a/tests/unit/harness/verification/test_agent_snapshot_profile_index.py b/tests/unit/harness/verification/test_agent_snapshot_profile_index.py index 69e6caa..0de473f 100644 --- a/tests/unit/harness/verification/test_agent_snapshot_profile_index.py +++ b/tests/unit/harness/verification/test_agent_snapshot_profile_index.py @@ -2,9 +2,9 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( default_python_harness_config, - render_python_project_harness_agent_snapshot_with_config, + render_asp_python_agent_snapshot_with_config, ) if TYPE_CHECKING: @@ -16,7 +16,7 @@ def test_agent_snapshot_reminds_when_verification_profile_is_unconfigured( ) -> None: _write_public_api_project(tmp_path) - rendered = render_python_project_harness_agent_snapshot_with_config( + rendered = render_asp_python_agent_snapshot_with_config( tmp_path, default_python_harness_config(), ) diff --git a/tests/unit/harness/verification/test_performance_microbench.py b/tests/unit/harness/verification/test_performance_microbench.py index e5f1327..1024962 100644 --- a/tests/unit/harness/verification/test_performance_microbench.py +++ b/tests/unit/harness/verification/test_performance_microbench.py @@ -2,7 +2,7 @@ from pathlib import Path -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationEvidence, PythonVerificationProfileHint, diff --git a/tests/unit/harness/verification/test_policy_regressions.py b/tests/unit/harness/verification/test_policy_regressions.py index 9ea7cd1..954a4ac 100644 --- a/tests/unit/harness/verification/test_policy_regressions.py +++ b/tests/unit/harness/verification/test_policy_regressions.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationDependencySignal, PythonVerificationProfileHint, diff --git a/tests/unit/harness/verification/test_profile_index.py b/tests/unit/harness/verification/test_profile_index.py index b59d89b..8b0b84d 100644 --- a/tests/unit/harness/verification/test_profile_index.py +++ b/tests/unit/harness/verification/test_profile_index.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from python_lang_project_harness import ( +from asp_python import ( PythonOwnerResponsibility, PythonVerificationProfileHint, build_python_verification_profile_index_with_config, diff --git a/tests/unit/lang_harness/test_config_contracts.py b/tests/unit/lang_harness/test_config_contracts.py index a614aa4..d0e8e80 100644 --- a/tests/unit/lang_harness/test_config_contracts.py +++ b/tests/unit/lang_harness/test_config_contracts.py @@ -3,14 +3,14 @@ import json from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity -from python_lang_project_harness import ( +from asp_python import ( default_python_harness_config, python_rule_pack_descriptors, python_syntax_rules, render_python_lang_harness_json, run_python_lang_harness, ) +from python_lang_parser import PythonDiagnosticSeverity if TYPE_CHECKING: from pathlib import Path diff --git a/tests/unit/lang_harness/test_discovery_runner.py b/tests/unit/lang_harness/test_discovery_runner.py index 6632666..e434297 100644 --- a/tests/unit/lang_harness/test_discovery_runner.py +++ b/tests/unit/lang_harness/test_discovery_runner.py @@ -2,21 +2,21 @@ from typing import TYPE_CHECKING +from asp_python import ( + AspPythonConfig, + AspPythonFinding, + PythonSyntaxRulePack, + discover_python_files, + render_python_lang_harness, + run_asp_python, + run_python_lang_harness, +) from python_lang_parser import ( PythonDiagnostic, PythonDiagnosticSeverity, PythonModuleReport, SourceLocation, ) -from python_lang_project_harness import ( - PythonHarnessConfig, - PythonHarnessFinding, - PythonSyntaxRulePack, - discover_python_files, - render_python_lang_harness, - run_python_lang_harness, - run_python_project_harness, -) if TYPE_CHECKING: from pathlib import Path @@ -66,7 +66,7 @@ def test_asp_toml_can_include_hidden_python_dirs(tmp_path: Path) -> None: encoding="utf-8", ) - report = run_python_project_harness(tmp_path) + report = run_asp_python(tmp_path) assert report.file_count == 1 assert report.modules[0].path == str(fixture) @@ -141,7 +141,7 @@ def test_run_python_lang_harness_uses_configured_discovery(tmp_path: Path) -> No report = run_python_lang_harness( [tmp_path], - config=PythonHarnessConfig(ignored_dir_names=frozenset({"generated"})), + config=AspPythonConfig(ignored_dir_names=frozenset({"generated"})), ) assert report.file_count == 0 @@ -181,7 +181,7 @@ def test_run_python_lang_harness_uses_configured_blocking_severities( ) -> None: source = tmp_path / "module.py" source.write_text("VALUE = 1\n", encoding="utf-8") - config = PythonHarnessConfig( + config = AspPythonConfig( blocking_severities=frozenset({PythonDiagnosticSeverity.ERROR}), rule_packs=(_WarningRulePack(),), ) @@ -200,9 +200,9 @@ def test_run_python_lang_harness_uses_configured_blocking_severities( class _WarningRulePack: pack_id = "test.warning" - def evaluate(self, report: PythonModuleReport) -> tuple[PythonHarnessFinding, ...]: + def evaluate(self, report: PythonModuleReport) -> tuple[AspPythonFinding, ...]: return ( - PythonHarnessFinding( + AspPythonFinding( rule_id="python.project.warning", pack_id=self.pack_id, severity=PythonDiagnosticSeverity.WARNING, diff --git a/tests/unit/lang_harness/test_render_assertions.py b/tests/unit/lang_harness/test_render_assertions.py index 3ff5fc9..c1b36ca 100644 --- a/tests/unit/lang_harness/test_render_assertions.py +++ b/tests/unit/lang_harness/test_render_assertions.py @@ -2,14 +2,14 @@ from typing import TYPE_CHECKING -from python_lang_parser import PythonDiagnosticSeverity, SourceLocation -from python_lang_project_harness import ( - PythonHarnessConfig, - PythonHarnessFinding, +from asp_python import ( + AspPythonConfig, + AspPythonFinding, assert_python_lang_harness_clean, render_python_lang_harness, run_python_lang_harness, ) +from python_lang_parser import PythonDiagnosticSeverity, SourceLocation if TYPE_CHECKING: from pathlib import Path @@ -136,7 +136,7 @@ def test_assert_python_lang_harness_clean_honors_configured_blocking_severities( ) -> None: source = tmp_path / "module.py" source.write_text("VALUE = 1\n", encoding="utf-8") - config = PythonHarnessConfig( + config = AspPythonConfig( blocking_severities=frozenset({PythonDiagnosticSeverity.ERROR}), rule_packs=(_WarningRulePack(),), ) @@ -154,7 +154,7 @@ def test_assert_python_lang_harness_clean_honors_severities_override( ) -> None: source = tmp_path / "module.py" source.write_text("VALUE = 1\n", encoding="utf-8") - config = PythonHarnessConfig( + config = AspPythonConfig( blocking_severities=frozenset({PythonDiagnosticSeverity.ERROR}), rule_packs=(_WarningRulePack(),), ) @@ -177,9 +177,9 @@ def test_assert_python_lang_harness_clean_honors_severities_override( class _WarningRulePack: pack_id = "test.warning" - def evaluate(self, report: PythonModuleReport) -> tuple[PythonHarnessFinding, ...]: + def evaluate(self, report: PythonModuleReport) -> tuple[AspPythonFinding, ...]: return ( - PythonHarnessFinding( + AspPythonFinding( rule_id="python.project.warning", pack_id=self.pack_id, severity=PythonDiagnosticSeverity.WARNING, diff --git a/tests/unit/python_lang_parser/test_pyproject_metadata.py b/tests/unit/python_lang_parser/test_pyproject_metadata.py index 67612f8..53ecd4f 100644 --- a/tests/unit/python_lang_parser/test_pyproject_metadata.py +++ b/tests/unit/python_lang_parser/test_pyproject_metadata.py @@ -37,7 +37,7 @@ def test_parse_python_project_metadata_collects_modern_project_facts( { package = "mkdocs>=1.6" }, ] test = [ - "python-lang-project-harness[pytest]>=0.1.0", + "asp-python[pytest]>=0.1.0", { dependency = "ruff>=0.13" }, ] @@ -84,8 +84,8 @@ def test_parse_python_project_metadata_collects_modern_project_facts( "extra": None, }, { - "requirement": "python-lang-project-harness[pytest]>=0.1.0", - "name": "python-lang-project-harness", + "requirement": "asp-python[pytest]>=0.1.0", + "name": "asp-python", "source": "dependency-groups", "group": "test", "extra": None, @@ -102,7 +102,7 @@ def test_parse_python_project_metadata_collects_modern_project_facts( "--import-mode=importlib", "--python-project-harness", ) - assert metadata.pytest_options.enables_python_project_harness is True + assert metadata.pytest_options.enables_asp_python is True assert metadata.wheel_packages == ("src/example_pkg",) assert metadata.package_roots == (package,) assert [item.to_dict() for item in metadata.import_names] == [ diff --git a/tests/unit/snapshots/python_project_harness_compact_text.snap b/tests/unit/snapshots/asp_python_compact_text.snap similarity index 100% rename from tests/unit/snapshots/python_project_harness_compact_text.snap rename to tests/unit/snapshots/asp_python_compact_text.snap diff --git a/tests/unit/snapshots/python_project_harness_json.snap b/tests/unit/snapshots/asp_python_json.snap similarity index 100% rename from tests/unit/snapshots/python_project_harness_json.snap rename to tests/unit/snapshots/asp_python_json.snap diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap index c6b6a3f..aa9a687 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 `asp-python --agent-snapshot` to copy the compact `[verify-profile]` hints. + | Required: Configure `[tool.asp-python.verification].profile_hints` from parser-suggested owners or consume the dependency API verification-profile projection. diff --git a/tests/unit/test_package_metadata.py b/tests/unit/test_package_metadata.py index 5ce7fd9..eaaa5a6 100644 --- a/tests/unit/test_package_metadata.py +++ b/tests/unit/test_package_metadata.py @@ -4,31 +4,29 @@ from importlib import metadata from pathlib import Path +import asp_python import python_lang_parser -import python_lang_project_harness def test_distribution_metadata_uses_project_name() -> None: - project = metadata.metadata("python-lang-project-harness") + project = metadata.metadata("asp-python") - assert project["Name"] == "python-lang-project-harness" + assert project["Name"] == "asp-python" assert project["Version"] == "0.1.0" def test_runtime_package_identity_matches_distribution_metadata() -> None: - installed_version = metadata.version("python-lang-project-harness") + installed_version = metadata.version("asp-python") - assert python_lang_project_harness.DISTRIBUTION_NAME == ( - "python-lang-project-harness" - ) - assert python_lang_project_harness.__version__ == installed_version + assert asp_python.DISTRIBUTION_NAME == ("asp-python") + assert asp_python.__version__ == installed_version assert python_lang_parser.__version__ == installed_version def test_distribution_import_packages_are_current_project_surfaces() -> None: top_level_names = { path.parts[0] - for path in metadata.files("python-lang-project-harness") or () + for path in metadata.files("asp-python") or () if path.parts and not path.parts[0].endswith(".dist-info") and path.parts[0] != ".." @@ -37,7 +35,7 @@ def test_distribution_import_packages_are_current_project_surfaces() -> None: assert top_level_names <= { "python_lang_parser", - "python_lang_project_harness", + "asp_python", } @@ -47,11 +45,11 @@ def test_distribution_exposes_console_script() -> None: for entry_point in metadata.entry_points(group="console_scripts") } - assert scripts["asp-python"] == ("python_lang_project_harness:run_cli_from_env") + assert scripts["asp-python"] == ("asp_python:run_cli_from_env") def test_distribution_exposes_pytest_optional_dependency() -> None: - project = metadata.metadata("python-lang-project-harness") + project = metadata.metadata("asp-python") assert "pytest" in project.get_all("Provides-Extra", []) assert any( @@ -66,9 +64,7 @@ def test_distribution_exposes_pytest_plugin_entry_point() -> None: for entry_point in metadata.entry_points(group="pytest11") } - assert plugins["python_lang_project_harness"] == ( - "python_lang_project_harness.pytest_plugin" - ) + assert plugins["asp_python"] == ("asp_python.pytest_plugin") def test_wheel_package_configuration_lists_current_import_packages() -> None: @@ -83,9 +79,9 @@ def test_wheel_package_configuration_lists_current_import_packages() -> None: assert pyproject["tool"]["hatch"]["build"]["targets"]["wheel"]["packages"] == [ "src/python_lang_parser", - "src/python_lang_project_harness", + "src/asp_python", ] assert pyproject["project"]["import-names"] == [ "python_lang_parser", - "python_lang_project_harness", + "asp_python", ] diff --git a/tests/unit/test_public_api.py b/tests/unit/test_public_api.py index ce831f2..98e0243 100644 --- a/tests/unit/test_public_api.py +++ b/tests/unit/test_public_api.py @@ -1,8 +1,8 @@ from __future__ import annotations +import asp_python as harness_api +import asp_python.harness as harness_facade import python_lang_parser as parser_api -import python_lang_project_harness as harness_api -import python_lang_project_harness.harness as harness_facade def test_root_package_reexports_parser_fact_models() -> None: @@ -120,8 +120,8 @@ def test_root_package_reexports_parser_fact_models() -> None: def test_root_package_reexports_embedding_harness_surface() -> None: - assert harness_api.PythonHarnessConfig is harness_facade.PythonHarnessConfig - assert harness_api.PythonHarnessReport is harness_facade.PythonHarnessReport + assert harness_api.AspPythonConfig is harness_facade.AspPythonConfig + assert harness_api.AspPythonReport is harness_facade.AspPythonReport assert ( harness_api.PythonVerificationPolicy is harness_facade.PythonVerificationPolicy ) @@ -141,10 +141,7 @@ def test_root_package_reexports_embedding_harness_surface() -> None: harness_api.default_python_harness_config is harness_facade.default_python_harness_config ) - assert ( - harness_api.python_project_harness_test - is harness_facade.python_project_harness_test - ) + assert harness_api.asp_python_test is harness_facade.asp_python_test assert ( harness_api.python_project_policy_rules is harness_facade.python_project_policy_rules @@ -166,17 +163,14 @@ def test_root_package_reexports_embedding_harness_surface() -> None: is harness_facade.render_python_reasoning_tree ) assert ( - harness_api.render_python_project_harness_agent_snapshot - is harness_facade.render_python_project_harness_agent_snapshot - ) - assert ( - harness_api.render_python_project_harness_agent_snapshot_with_config - is harness_facade.render_python_project_harness_agent_snapshot_with_config + harness_api.render_asp_python_agent_snapshot + is harness_facade.render_asp_python_agent_snapshot ) assert ( - harness_api.read_python_project_harness_config - is harness_facade.read_python_project_harness_config + harness_api.render_asp_python_agent_snapshot_with_config + is harness_facade.render_asp_python_agent_snapshot_with_config ) + assert harness_api.read_asp_python_config is harness_facade.read_asp_python_config assert ( harness_api.python_rule_pack_descriptors is harness_facade.python_rule_pack_descriptors @@ -194,13 +188,10 @@ def test_root_package_reexports_embedding_harness_surface() -> None: ) assert "render_python_lang_harness_advice" in harness_api.__all__ assert "render_python_lang_harness_json" in harness_api.__all__ - assert "render_python_project_harness_agent_snapshot" in harness_api.__all__ - assert ( - "render_python_project_harness_agent_snapshot_with_config" - in harness_api.__all__ - ) + assert "render_asp_python_agent_snapshot" in harness_api.__all__ + assert "render_asp_python_agent_snapshot_with_config" in harness_api.__all__ assert "render_python_reasoning_tree" in harness_api.__all__ - assert "read_python_project_harness_config" in harness_api.__all__ + assert "read_asp_python_config" in harness_api.__all__ assert "run_cli_from_env" in harness_api.__all__ assert "python_syntax_rules" in harness_api.__all__ assert "PythonVerificationPolicy" in harness_api.__all__ diff --git a/tests/unit/test_self_hosting.py b/tests/unit/test_self_hosting.py index 976d167..a562370 100644 --- a/tests/unit/test_self_hosting.py +++ b/tests/unit/test_self_hosting.py @@ -2,7 +2,7 @@ from pathlib import Path -from python_lang_project_harness import python_project_harness_test +from asp_python import asp_python_test _PROJECT_ROOT = next( parent @@ -11,6 +11,4 @@ ) -test_python_lang_project_harness_self_policy = python_project_harness_test( - _PROJECT_ROOT -) +test_asp_python_self_policy = asp_python_test(_PROJECT_ROOT) diff --git a/uv.lock b/uv.lock index eb971fa..047b39b 100644 --- a/uv.lock +++ b/uv.lock @@ -124,7 +124,7 @@ wheels = [ ] [[package]] -name = "python-lang-project-harness" +name = "asp-python" version = "0.1.0" source = { editable = "." } dependencies = [ From 520021465cf8c3441b84ece52fb4571c211b41d6 Mon Sep 17 00:00:00 2001 From: guangtao Date: Tue, 1 Sep 2026 20:56:16 +0800 Subject: [PATCH 14/20] ci: use API-owned Python policy gates --- .github/workflows/ci.yml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efb424a..c63bd92 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: CI on: + workflow_dispatch: pull_request: push: branches: @@ -48,12 +49,6 @@ jobs: - name: Test run: uv run --group test pytest tests -q - - name: Self harness - run: uv run --group test asp-python . - - - name: Agent snapshot - run: uv run --group test asp-python --agent-snapshot . - - name: Build package run: uv build From f5090cb498ba478fe4426d743fe4403897f057ce Mon Sep 17 00:00:00 2001 From: guangtao Date: Tue, 1 Sep 2026 21:53:42 +0800 Subject: [PATCH 15/20] fix: close native exact runtime review gaps --- src/asp_python/_exact_projection_model.py | 31 +++++++++-- src/asp_python/_exact_source_projection.py | 13 +++++ src/asp_python/_projection_batch.py | 12 +---- src/asp_python/_runtime.py | 2 +- .../harness/test_exact_source_projection.py | 52 +++++++++++++++++++ tests/unit/harness/test_provider_runtime.py | 16 ++++++ 6 files changed, 111 insertions(+), 15 deletions(-) diff --git a/src/asp_python/_exact_projection_model.py b/src/asp_python/_exact_projection_model.py index a9d6fd6..33a7be0 100644 --- a/src/asp_python/_exact_projection_model.py +++ b/src/asp_python/_exact_projection_model.py @@ -21,6 +21,7 @@ class ExactSelector: owner_path: str kind: str symbol: str + scopes: tuple[tuple[str, str, str], ...] segment_kind: str | None segment_identity: str | None @@ -43,8 +44,17 @@ def parse_selector(selector: str) -> ExactSelector: raise ValueError("exact selector must include owner and item fragment") root_fragment, segment_separator, descendant = fragment.partition("/segment/") parts = root_fragment.split("/") - if len(parts) < 3 or parts[0] != "item": + if len(parts) < 3 or parts[0] != "item" or not all(parts[:3]): raise ValueError("exact selector item fragment is invalid") + scope_parts = parts[3:] + scopes: list[tuple[str, str, str]] = [] + if len(scope_parts) % 4 != 0: + raise ValueError("exact selector item scope is invalid") + for offset in range(0, len(scope_parts), 4): + scope = scope_parts[offset : offset + 4] + if scope[0] != "scope" or not all(scope[1:]): + raise ValueError("exact selector item scope is invalid") + scopes.append((scope[1], scope[2], unquote(scope[3]))) root = f"python://{owner_path}#{root_fragment}" segment_kind = None segment_identity = None @@ -57,8 +67,9 @@ def parse_selector(selector: str) -> ExactSelector: requested=selector, root=root, owner_path=owner_path, - kind=parts[-2], - symbol=unquote(parts[-1]), + kind=parts[1], + symbol=unquote(parts[2]), + scopes=tuple(scopes), segment_kind=segment_kind, segment_identity=segment_identity, ) @@ -69,9 +80,21 @@ def find_function( ) -> ast.FunctionDef | ast.AsyncFunctionDef: if selector.kind not in {"function", "method"}: raise ValueError("callable projection requires function or method selector") + search_root = tree + for _role, owner_kind, owner_name in selector.scopes: + if owner_kind != "type": + raise ValueError("exact callable scope owner kind is unsupported") + owners = [ + node + for node in ast.iter_child_nodes(search_root) + if isinstance(node, ast.ClassDef) and node.name == owner_name + ] + if len(owners) != 1: + raise ValueError("exact callable scope owner is missing or ambiguous") + search_root = owners[0] matches = [ node - for node in ast.walk(tree) + for node in ast.walk(search_root) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name == selector.symbol ] diff --git a/src/asp_python/_exact_source_projection.py b/src/asp_python/_exact_source_projection.py index 51f00c7..f52b9e7 100644 --- a/src/asp_python/_exact_source_projection.py +++ b/src/asp_python/_exact_source_projection.py @@ -138,12 +138,25 @@ def _projection_packet( "schemaVersion": "1", "languageId": "python", "providerId": "asp-python", + "ownerPath": selector.owner_path, "projectionMode": projection_kind, "requestedStructuralSelector": selector.requested, "structuralSelector": selector.requested, "sourceContentDigest": required_text(request, "sourceDigest"), "sourceByteStart": byte_start, "sourceByteEnd": max(byte_start, byte_end - 1), + "normalizedParserFacts": { + "itemKind": selector.kind, + "itemName": selector.symbol, + "scopes": [ + { + "role": role, + "ownerKind": owner_kind, + "ownerName": owner_name, + } + for role, owner_kind, owner_name in selector.scopes + ], + }, } if projection_text is not None: packet["projectionText"] = projection_text diff --git a/src/asp_python/_projection_batch.py b/src/asp_python/_projection_batch.py index a3ade1e..66a2a90 100644 --- a/src/asp_python/_projection_batch.py +++ b/src/asp_python/_projection_batch.py @@ -8,7 +8,7 @@ from dataclasses import dataclass from ._callable_skeleton_projection import callable_skeleton_payload, collect_segments -from ._exact_projection_model import ExactSelector +from ._exact_projection_model import parse_selector _REQUEST_SCHEMA_ID = ( "agent.semantic-protocols.provider-language-projection-batch-request" @@ -150,15 +150,7 @@ def _project_item( selector = f"python://{owner.path}#item/{kind}/{node.name}{scope_path}" projections: list[dict[str, object]] = [] if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - exact_selector = ExactSelector( - requested=selector, - root=selector, - owner_path=owner.path, - kind=kind, - symbol=node.name, - segment_kind=None, - segment_identity=None, - ) + exact_selector = parse_selector(selector) projection_request = { "generationIdentityDigest": header["generationRootDigest"], "parserIdentityDigest": header["parserIdentityDigest"], diff --git a/src/asp_python/_runtime.py b/src/asp_python/_runtime.py index 4f646a5..0f013c7 100644 --- a/src/asp_python/_runtime.py +++ b/src/asp_python/_runtime.py @@ -115,7 +115,7 @@ def _response_frame(request: dict[str, Any], cwd: Path) -> dict[str, Any]: if not isinstance(payload, dict): raise RuntimeError("provider runtime payload is not an object") result = _execute(operation, payload, cwd) - except (RuntimeError, ValueError) as execution_error: + except (RuntimeError, SyntaxError, ValueError) as execution_error: error = str(execution_error) else: return { diff --git a/tests/unit/harness/test_exact_source_projection.py b/tests/unit/harness/test_exact_source_projection.py index 8123c84..199c827 100644 --- a/tests/unit/harness/test_exact_source_projection.py +++ b/tests/unit/harness/test_exact_source_projection.py @@ -53,6 +53,12 @@ def invoke(selector: str, projection_kind: str) -> dict[str, object]: branch = invoke(branch_selector, "source") assert branch["schemaVersion"] == "1" + assert branch["ownerPath"] == "src/example.py" + assert branch["normalizedParserFacts"] == { + "itemKind": "function", + "itemName": "selected", + "scopes": [], + } assert branch["requestedStructuralSelector"] == branch_selector assert branch["structuralSelector"] == branch_selector assert branch["projectionText"] == "if value > 1:\n return value" @@ -80,3 +86,49 @@ def test_provider_does_not_recompute_asp_source_digest(tmp_path) -> None: } packet = project_provider_native_exact_request(request, cwd=tmp_path) assert packet["sourceContentDigest"] == "asp-owned-content-identity" + + +def test_scoped_method_selector_resolves_the_declaring_type(tmp_path) -> None: + source = ( + b"class Agent:\n" + b" def run(self) -> int:\n" + b" return 1\n\n" + b"class Other:\n" + b" def run(self) -> int:\n" + b" return 2\n" + ) + selector = ( + "python://src/example.py#item/method/run/scope/implementation-owner/type/Agent" + ) + request = { + "schemaId": "agent.semantic-protocols.provider-native-exact-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "structuralSelector": selector, + "ownerPath": "src/example.py", + "projectionKind": "source", + "generationIdentityDigest": "a" * 64, + "parserIdentityDigest": "b" * 64, + "queryPackDigest": "c" * 64, + "sourceDigest": "d" * 64, + "sourceByteLength": len(source), + "sourceEncoding": "base64", + "sourceBytesBase64": base64.b64encode(source).decode(), + "transport": "stdin-json", + } + + packet = project_provider_native_exact_request(request, cwd=tmp_path) + + assert packet["projectionText"] == "def run(self) -> int:\n return 1" + assert packet["normalizedParserFacts"] == { + "itemKind": "method", + "itemName": "run", + "scopes": [ + { + "role": "implementation-owner", + "ownerKind": "type", + "ownerName": "Agent", + } + ], + } diff --git a/tests/unit/harness/test_provider_runtime.py b/tests/unit/harness/test_provider_runtime.py index 26b2d56..725d982 100644 --- a/tests/unit/harness/test_provider_runtime.py +++ b/tests/unit/harness/test_provider_runtime.py @@ -19,6 +19,7 @@ frame, latency_receipt, post, + projection_payload, ) from asp_python._runtime import _health, _response_frame @@ -70,6 +71,21 @@ def test_resident_runtime_publishes_manifest_operations_and_structured_frames( ) +def test_runtime_converts_live_edit_syntax_errors_to_error_frames() -> None: + response = _response_frame( + frame( + "syntax-error-1", + "projection-batch", + projection_payload("def broken(:\n pass\n"), + ), + Path("."), + ) + + assert response["requestId"] == "syntax-error-1" + assert response["outcome"] == "error" + assert "invalid syntax" in response["error"] + + def test_http_json_live_corpus_stream_query_concurrency_and_latency() -> None: provider = Path(sys.executable).with_name("asp-python") assert provider.is_file(), ( From adad838ab6ba47ffeb6ca23a27e25d98c80747e6 Mon Sep 17 00:00:00 2001 From: guangtao Date: Tue, 8 Sep 2026 09:12:11 +0800 Subject: [PATCH 16/20] refactor: align provider with client-owned search --- development.md | 27 +- provider/asp-provider-registration.json | 51 - schemas/.asp-schema-manager-membership.json | 148 ++- schemas/.asp-schema-manager-receipt.json | 2 +- ...asp-client-exact-query-failure.schema.json | 7 +- ...asp-client-exact-query-request.schema.json | 76 +- ...sp-client-exact-query-response.schema.json | 4 + schemas/asp-client-frame.schema.json | 58 +- ...sp-client-owner-search-request.schema.json | 17 - ...p-client-owner-search-response.schema.json | 58 - ...client-query-readiness-failure.schema.json | 8 +- schemas/asp-client-search-request.schema.json | 14 +- ...eneration-ensure-ready-request.schema.json | 7 +- ...pace-query-playbook-request.v1.schema.json | 23 + ...ace-search-playbook-request.v1.schema.json | 84 ++ ...kspace-syntax-query-request.v1.schema.json | 30 + ...space-syntax-query-response.v1.schema.json | 27 + .../asp-python-graphs-session.v1.schema.json | 30 +- schemas/callable-skeleton.schema.json | 3 +- schemas/content-binding.schema.json | 28 + .../content-publication-commit.v1.schema.json | 37 + ...pace-initialization-binding.v1.schema.json | 19 + schemas/language-schema-profiles.json | 1107 +++++++++++++++++ ...laybook-performance-receipt.v1.schema.json | 202 +++ ...arch-payload-reduction-receipt.schema.json | 23 - .../project-topology-library.v1.schema.json | 211 ++++ .../project-workspace-binding.v1.schema.json | 71 ++ ...uage-projection-batch-response.schema.json | 52 + schemas/provider-manifest.schema.json | 36 +- ...n-graph-performance-receipt.v1.schema.json | 58 + ...ook-materialization-receipt.v1.schema.json | 132 ++ ...ook-materialization-request.v1.schema.json | 54 + schemas/resident-search-result.v1.schema.json | 1 + ...ime-artifact-bundle-binding.v2.schema.json | 28 + ...ct-execution-closure-member.v1.schema.json | 144 +++ schemas/runtime-binary-bundle.v2.schema.json | 30 + .../runtime-execution-binding.v2.schema.json | 42 + ...earch-client-timing-witness.v1.schema.json | 56 + ...ime-search-execution-budget.v1.schema.json | 76 ++ ...workspace-execution-pointer.v1.schema.json | 30 + ...space-execution-publication.v1.schema.json | 36 + .../search-topology-settlement.v1.schema.json | 360 ++++++ ...resident-evaluation-request.v1.schema.json | 20 +- ...-resident-evaluation-result.v1.schema.json | 6 +- ...emantic-graph-turbo-request.v1.schema.json | 12 +- .../semantic-language-registry.v1.schema.json | 33 +- schemas/semantic-search-packet.v1.schema.json | 2 +- src/asp_python/__init__.py | 4 - src/asp_python/_cli_agent.py | 59 +- src/asp_python/_cli_args.py | 81 +- src/asp_python/_cli_protocol.py | 136 +- src/asp_python/_cli_search_runtime.py | 377 ------ src/asp_python/_discovery.py | 40 +- src/asp_python/_evidence_graph_turbo.py | 2 +- src/asp_python/_projection_batch.py | 26 +- src/asp_python/_runner.py | 16 +- src/asp_python/_semantic_language.py | 80 +- .../_semantic_language_benchmark.py | 104 -- src/asp_python/_semantic_language_catalog.py | 225 ---- .../_semantic_language_knowledge.py | 103 -- src/asp_python/_semantic_search.py | 17 - .../_semantic_search_callsite_hits.py | 71 -- src/asp_python/_semantic_search_cli.py | 432 ------- src/asp_python/_semantic_search_common.py | 170 --- src/asp_python/_semantic_search_deps.py | 140 --- src/asp_python/_semantic_search_findings.py | 47 - .../_semantic_search_graph_render.py | 107 -- src/asp_python/_semantic_search_hits.py | 18 - .../_semantic_search_import_routes.py | 102 -- .../_semantic_search_import_test_hits.py | 74 -- src/asp_python/_semantic_search_ingest.py | 132 -- .../_semantic_search_ingest_fast.py | 58 - src/asp_python/_semantic_search_item_lines.py | 174 --- src/asp_python/_semantic_search_items.py | 375 ------ .../_semantic_search_knowledge_facts.py | 180 --- .../_semantic_search_lexical_fast.py | 102 -- src/asp_python/_semantic_search_model.py | 37 - src/asp_python/_semantic_search_owner_fast.py | 61 - src/asp_python/_semantic_search_owners.py | 179 --- src/asp_python/_semantic_search_packages.py | 108 -- src/asp_python/_semantic_search_packet.py | 195 --- src/asp_python/_semantic_search_policy.py | 229 ---- src/asp_python/_semantic_search_prefilter.py | 152 --- .../_semantic_search_prefilter_file_scan.py | 245 ---- .../_semantic_search_prefilter_path.py | 31 - .../_semantic_search_prefilter_process.py | 26 - .../_semantic_search_prefilter_rank.py | 110 -- .../_semantic_search_prefilter_result.py | 54 - .../_semantic_search_prefilter_select.py | 109 -- .../_semantic_search_prefilter_tools.py | 202 --- src/asp_python/_semantic_search_prime_fast.py | 190 --- src/asp_python/_semantic_search_profiles.py | 103 -- ...mantic_search_public_external_type_hits.py | 82 -- ...tic_search_public_external_type_imports.py | 96 -- ...antic_search_public_external_type_model.py | 23 - ...ic_search_public_external_type_surfaces.py | 104 -- .../_semantic_search_public_external_types.py | 299 ----- src/asp_python/_semantic_search_reasoning.py | 126 -- src/asp_python/_semantic_search_render.py | 20 - .../_semantic_search_render_compact.py | 238 ---- .../_semantic_search_render_flow.py | 121 -- .../_semantic_search_render_lines.py | 186 --- .../_semantic_search_symbol_hits.py | 144 --- src/asp_python/_semantic_search_text_hits.py | 207 --- .../_semantic_search_view_actions.py | 25 - src/asp_python/_semantic_search_view_core.py | 333 ----- .../_semantic_search_view_deps_imports.py | 191 --- src/asp_python/_semantic_search_view_hits.py | 153 --- .../_semantic_search_view_ingest.py | 84 -- .../_semantic_search_view_knowledge.py | 81 -- .../_semantic_search_view_lexical_queries.py | 163 --- ..._semantic_search_view_lexical_synthesis.py | 170 --- src/asp_python/_semantic_search_views.py | 113 -- .../_tree_sitter_query_projection.py | 4 +- src/asp_python/harness.py | 10 - ...h_fixture.py => python_project_fixture.py} | 24 +- tests/unit/harness/test_cli.py | 7 +- .../harness/test_dependency_topology_cli.py | 67 - tests/unit/harness/test_evidence_graph.py | 2 +- .../harness/test_project_fixture_scope.py | 20 + tests/unit/harness/test_projection_batch.py | 48 + tests/unit/harness/test_provider_runtime.py | 9 +- .../unit/harness/test_public_cli_identity.py | 3 +- tests/unit/harness/test_runner_config.py | 13 + .../harness/test_search_playbook_boundary.py | 40 + .../test_semantic_cli_benchmark_registry.py | 11 +- .../harness/test_semantic_cli_fast_prime.py | 70 -- .../unit/harness/test_semantic_cli_lexical.py | 253 ---- .../test_semantic_cli_owner_item_broad.py | 85 -- .../test_semantic_cli_owner_item_inventory.py | 64 - ...test_semantic_cli_owner_items_fast_path.py | 248 ---- .../unit/harness/test_semantic_cli_policy.py | 118 -- ...test_semantic_cli_public_external_types.py | 82 -- .../harness/test_semantic_cli_query_set.py | 112 -- .../harness/test_semantic_cli_reasoning.py | 116 -- ...est_semantic_cli_tree_sitter_predicates.py | 6 +- .../test_semantic_cli_workspace_search.py | 153 --- .../unit/harness/test_semantic_graph_facts.py | 204 --- .../harness/test_semantic_provider_doctor.py | 5 +- .../unit/harness/test_semantic_render_flow.py | 29 - .../test_semantic_search_graph_profiles.py | 98 -- ..._semantic_search_graph_render_shell_out.py | 109 -- .../test_semantic_search_ingest_cli.py | 93 -- .../tree-sitter-python/grammar-profile.json | 2 +- 144 files changed, 3484 insertions(+), 10643 deletions(-) delete mode 100644 schemas/asp-client-owner-search-request.schema.json delete mode 100644 schemas/asp-client-owner-search-response.schema.json create mode 100644 schemas/asp-client-workspace-query-playbook-request.v1.schema.json create mode 100644 schemas/asp-client-workspace-search-playbook-request.v1.schema.json create mode 100644 schemas/asp-client-workspace-syntax-query-request.v1.schema.json create mode 100644 schemas/asp-client-workspace-syntax-query-response.v1.schema.json create mode 100644 schemas/content-binding.schema.json create mode 100644 schemas/content-publication-commit.v1.schema.json create mode 100644 schemas/host-workspace-initialization-binding.v1.schema.json create mode 100644 schemas/language-schema-profiles.json create mode 100644 schemas/large-search-playbook-performance-receipt.v1.schema.json delete mode 100644 schemas/owner-search-payload-reduction-receipt.schema.json create mode 100644 schemas/project-topology-library.v1.schema.json create mode 100644 schemas/project-workspace-binding.v1.schema.json create mode 100644 schemas/python-generation-graph-performance-receipt.v1.schema.json create mode 100644 schemas/query-playbook-materialization-receipt.v1.schema.json create mode 100644 schemas/query-playbook-materialization-request.v1.schema.json create mode 100644 schemas/runtime-artifact-bundle-binding.v2.schema.json create mode 100644 schemas/runtime-artifact-execution-closure-member.v1.schema.json create mode 100644 schemas/runtime-binary-bundle.v2.schema.json create mode 100644 schemas/runtime-execution-binding.v2.schema.json create mode 100644 schemas/runtime-search-client-timing-witness.v1.schema.json create mode 100644 schemas/runtime-search-execution-budget.v1.schema.json create mode 100644 schemas/runtime-workspace-execution-pointer.v1.schema.json create mode 100644 schemas/runtime-workspace-execution-publication.v1.schema.json create mode 100644 schemas/search-topology-settlement.v1.schema.json delete mode 100644 src/asp_python/_cli_search_runtime.py delete mode 100644 src/asp_python/_semantic_language_benchmark.py delete mode 100644 src/asp_python/_semantic_language_catalog.py delete mode 100644 src/asp_python/_semantic_language_knowledge.py delete mode 100644 src/asp_python/_semantic_search.py delete mode 100644 src/asp_python/_semantic_search_callsite_hits.py delete mode 100644 src/asp_python/_semantic_search_cli.py delete mode 100644 src/asp_python/_semantic_search_common.py delete mode 100644 src/asp_python/_semantic_search_deps.py delete mode 100644 src/asp_python/_semantic_search_findings.py delete mode 100644 src/asp_python/_semantic_search_graph_render.py delete mode 100644 src/asp_python/_semantic_search_hits.py delete mode 100644 src/asp_python/_semantic_search_import_routes.py delete mode 100644 src/asp_python/_semantic_search_import_test_hits.py delete mode 100644 src/asp_python/_semantic_search_ingest.py delete mode 100644 src/asp_python/_semantic_search_ingest_fast.py delete mode 100644 src/asp_python/_semantic_search_item_lines.py delete mode 100644 src/asp_python/_semantic_search_items.py delete mode 100644 src/asp_python/_semantic_search_knowledge_facts.py delete mode 100644 src/asp_python/_semantic_search_lexical_fast.py delete mode 100644 src/asp_python/_semantic_search_model.py delete mode 100644 src/asp_python/_semantic_search_owner_fast.py delete mode 100644 src/asp_python/_semantic_search_owners.py delete mode 100644 src/asp_python/_semantic_search_packages.py delete mode 100644 src/asp_python/_semantic_search_packet.py delete mode 100644 src/asp_python/_semantic_search_policy.py delete mode 100644 src/asp_python/_semantic_search_prefilter.py delete mode 100644 src/asp_python/_semantic_search_prefilter_file_scan.py delete mode 100644 src/asp_python/_semantic_search_prefilter_path.py delete mode 100644 src/asp_python/_semantic_search_prefilter_process.py delete mode 100644 src/asp_python/_semantic_search_prefilter_rank.py delete mode 100644 src/asp_python/_semantic_search_prefilter_result.py delete mode 100644 src/asp_python/_semantic_search_prefilter_select.py delete mode 100644 src/asp_python/_semantic_search_prefilter_tools.py delete mode 100644 src/asp_python/_semantic_search_prime_fast.py delete mode 100644 src/asp_python/_semantic_search_profiles.py delete mode 100644 src/asp_python/_semantic_search_public_external_type_hits.py delete mode 100644 src/asp_python/_semantic_search_public_external_type_imports.py delete mode 100644 src/asp_python/_semantic_search_public_external_type_model.py delete mode 100644 src/asp_python/_semantic_search_public_external_type_surfaces.py delete mode 100644 src/asp_python/_semantic_search_public_external_types.py delete mode 100644 src/asp_python/_semantic_search_reasoning.py delete mode 100644 src/asp_python/_semantic_search_render.py delete mode 100644 src/asp_python/_semantic_search_render_compact.py delete mode 100644 src/asp_python/_semantic_search_render_flow.py delete mode 100644 src/asp_python/_semantic_search_render_lines.py delete mode 100644 src/asp_python/_semantic_search_symbol_hits.py delete mode 100644 src/asp_python/_semantic_search_text_hits.py delete mode 100644 src/asp_python/_semantic_search_view_actions.py delete mode 100644 src/asp_python/_semantic_search_view_core.py delete mode 100644 src/asp_python/_semantic_search_view_deps_imports.py delete mode 100644 src/asp_python/_semantic_search_view_hits.py delete mode 100644 src/asp_python/_semantic_search_view_ingest.py delete mode 100644 src/asp_python/_semantic_search_view_knowledge.py delete mode 100644 src/asp_python/_semantic_search_view_lexical_queries.py delete mode 100644 src/asp_python/_semantic_search_view_lexical_synthesis.py delete mode 100644 src/asp_python/_semantic_search_views.py rename tests/unit/harness/{semantic_search_fixture.py => python_project_fixture.py} (66%) delete mode 100644 tests/unit/harness/test_dependency_topology_cli.py create mode 100644 tests/unit/harness/test_search_playbook_boundary.py delete mode 100644 tests/unit/harness/test_semantic_cli_fast_prime.py delete mode 100644 tests/unit/harness/test_semantic_cli_lexical.py delete mode 100644 tests/unit/harness/test_semantic_cli_owner_item_broad.py delete mode 100644 tests/unit/harness/test_semantic_cli_owner_item_inventory.py delete mode 100644 tests/unit/harness/test_semantic_cli_owner_items_fast_path.py delete mode 100644 tests/unit/harness/test_semantic_cli_policy.py delete mode 100644 tests/unit/harness/test_semantic_cli_public_external_types.py delete mode 100644 tests/unit/harness/test_semantic_cli_query_set.py delete mode 100644 tests/unit/harness/test_semantic_cli_reasoning.py delete mode 100644 tests/unit/harness/test_semantic_cli_workspace_search.py delete mode 100644 tests/unit/harness/test_semantic_graph_facts.py delete mode 100644 tests/unit/harness/test_semantic_render_flow.py delete mode 100644 tests/unit/harness/test_semantic_search_graph_profiles.py delete mode 100644 tests/unit/harness/test_semantic_search_graph_render_shell_out.py delete mode 100644 tests/unit/harness/test_semantic_search_ingest_cli.py diff --git a/development.md b/development.md index 81d5451..92f1c74 100644 --- a/development.md +++ b/development.md @@ -3,17 +3,17 @@ ## Format, Test, Lint ```shell -direnv exec . uv run --group test ruff format --check src tests -direnv exec . uv run --group test ruff check src tests -direnv exec . uv run --group test pytest tests -q -direnv exec . uv run --group test python-project-harness . -direnv exec . uv run --group test python-project-harness --agent-snapshot . -direnv exec . uv build -direnv exec . git diff --check +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test ruff format --check languages/asp-python/src languages/asp-python/tests +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test ruff check languages/asp-python/src languages/asp-python/tests +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test pytest languages/asp-python/tests -q +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test python-project-harness languages/asp-python +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test python-project-harness --agent-snapshot languages/asp-python +.devenv/devenv-profile-exec uv build languages/asp-python +.devenv/devenv-profile-exec git diff --check ``` -Use `direnv exec .` so the devenv-managed Python and `uv` environment are used -consistently. +Use `.devenv/devenv-profile-exec` from the repository root so the captured +devenv-managed Python and `uv` environment are used consistently. GitHub Actions runs the same validation surface without `direnv`: `uv sync --group test --locked`, ruff format/check, pytest, self-harness, package build, @@ -57,10 +57,11 @@ Rendered output and policy diagnostics are locked under `tests/unit/snapshots`. Normal tests compare snapshots only. Refresh them intentionally: ```shell -ASP_PYTHON_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ - tests/unit/harness/test_render_snapshots.py \ - tests/unit/harness/test_agent_policy_snapshots.py \ - tests/unit/harness/test_policy_snapshots.py -q +.devenv/devenv-profile-exec env ASP_PYTHON_UPDATE_SNAPSHOTS=1 \ + uv run --project languages/asp-python --group test pytest \ + languages/asp-python/tests/unit/harness/test_render_snapshots.py \ + languages/asp-python/tests/unit/harness/test_agent_policy_snapshots.py \ + languages/asp-python/tests/unit/harness/test_policy_snapshots.py -q ``` Review the resulting `.snap` diff before keeping it. Snapshot changes are diff --git a/provider/asp-provider-registration.json b/provider/asp-provider-registration.json index 6e78b40..f96ff67 100644 --- a/provider/asp-provider-registration.json +++ b/provider/asp-provider-registration.json @@ -215,57 +215,6 @@ "spanName": "asp.route.python.query", "attributeSlots": [] } - }, - { - "schemaId": "agent.semantic-protocols.provider-route", - "schemaVersion": "1", - "routeId": "python.search.owner", - "operation": "search.owner", - "requestSchema": {"schemaId": "agent.semantic-protocols.asp-client-owner-search-request", "schemaVersion": "1"}, - "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": { - "schema": { - "schemaId": "agent.semantic-protocols.search-packet", - "schemaVersion": "1" - }, - "mediaType": "application/json" - }, - "failureSchemaIds": [ - "agent.semantic-protocols.route-failure" - ], - "cache": { - "authority": "asp-server", - "scope": "workspace", - "keySlots": [] - }, - "telemetry": { - "spanName": "asp.route.python.search.owner", - "attributeSlots": [] - } } ], "schemas": [ diff --git a/schemas/.asp-schema-manager-membership.json b/schemas/.asp-schema-manager-membership.json index c84c269..e15f0ad 100644 --- a/schemas/.asp-schema-manager-membership.json +++ b/schemas/.asp-schema-manager-membership.json @@ -1,7 +1,7 @@ { "languageId": "python", - "profileDigest": "blake3-256:89e99a6c866f73af8137c109ad0829deb90a705c90dbceb408d0dfb202b0309a", - "bundleDigest": "blake3-256:ceb5fb09195c300d5b5a9c882ed8ac6263fbe15c0762907d6d2ab648bf65663d", + "profileDigest": "blake3-256:a1c247a2b628f4e7fe173ecaa855dfc90e1416c1d168f3b9d67c907a7d12beb2", + "bundleDigest": "blake3-256:c95fa3b9e3c775ee45275a08f1a247b564ee6da31d5cc4878aa1438c7b8c12ed", "schemas": [ { "name": "asp-client-cancellation-probe-request.schema.json", @@ -21,39 +21,31 @@ }, { "name": "asp-client-exact-query-failure.schema.json", - "digest": "blake3-256:fc451bd39e772ed3a2ba24a2b45da38e161b9b61915f698b1afb1253a051d386" + "digest": "blake3-256:0a59369cdd4dd4a305f60fd073733acddabd0593c475817f93064ba54e065cf7" }, { "name": "asp-client-exact-query-request.schema.json", - "digest": "blake3-256:addd74db755e221e488d7b3f1099fb15be025310fe657103fd476d4346f8abaa" + "digest": "blake3-256:95e9b4cf48223f31bbe86a816890e4689070790288b300799290d7041adaa7ba" }, { "name": "asp-client-exact-query-response.schema.json", - "digest": "blake3-256:feb1d24eaba02eaf53c7e5e7ba4f11dea436ebee99380d221f796007fee4625c" + "digest": "blake3-256:95e3897fce365809610d2ee3eea636cb2d21d56037721dd81fd8e1e96cc257ef" }, { "name": "asp-client-frame.schema.json", - "digest": "blake3-256:f16667311475fd65f4b849611e3df4ca2f7fe847a4730d6fa9d025753fb7cf7c" + "digest": "blake3-256:67df97458bb86ef192bfe03f6200860fa2c1b952199a92881a9d474acc04666b" }, { "name": "asp-client-graphs-timeline-request.v1.schema.json", "digest": "blake3-256:dc856c6a65c352081670053d6347d7632d71cc7f90e9248b2553833f30b178df" }, - { - "name": "asp-client-owner-search-request.schema.json", - "digest": "blake3-256:1c8f421a1bc84116f3a6f70593f15e9579b8a5e6a425d4051759001e7d1e6044" - }, - { - "name": "asp-client-owner-search-response.schema.json", - "digest": "blake3-256:9b4ba14fbc31663b8e908ade83a3070b89f9320c62fe0f47b364ec43959c35f5" - }, { "name": "asp-client-protocol-catalog.schema.json", "digest": "blake3-256:e3b6411bd9e583608a41d01852b052ba1438948f485e4ec08750c7d6d454c64a" }, { "name": "asp-client-query-readiness-failure.schema.json", - "digest": "blake3-256:e9119a4bdfb5674a56a28dbf0f53c99276f3a15c143cfd638a9437bc2facfd08" + "digest": "blake3-256:451736da76395cdb07ffc14523323657db1be90f22bd4e776282f47bc3e32fb3" }, { "name": "asp-client-schema-bundle-request.schema.json", @@ -65,7 +57,7 @@ }, { "name": "asp-client-search-request.schema.json", - "digest": "blake3-256:f10cb97d3d72b3c081ed1f4a67dd3aef607a9906dee7ef86032aabc938ec17fa" + "digest": "blake3-256:4572cbcd7247eac5019c98749971c5b8a0c1e742112c6fb299bf68827d32b5bc" }, { "name": "asp-client-server-descriptor.schema.json", @@ -81,19 +73,35 @@ }, { "name": "asp-client-workspace-generation-ensure-ready-request.schema.json", - "digest": "blake3-256:0b03a20586526b77754403fab6f74d421e2c3bbfb310d1bead22d179ea86d0a9" + "digest": "blake3-256:9f7c9a437713874b8ea5c4e701f61adf355f26bbc35ddd5a59a2d6889d1f2bd0" + }, + { + "name": "asp-client-workspace-query-playbook-request.v1.schema.json", + "digest": "blake3-256:171112801e36325b4854c1190ad2f67581a3b4a2d9849f3578ebf80be025ddd3" + }, + { + "name": "asp-client-workspace-search-playbook-request.v1.schema.json", + "digest": "blake3-256:6e4c2cf3da32a9c74999a35542bea21866e2ed9ed99bb02247393e2405cb4078" }, { "name": "asp-client-workspace-source-mutation.schema.json", "digest": "blake3-256:74b0baf814e96b3f2f47eefcc537ae53c5ac740315cdbc8c4f1079dcc2f484a8" }, + { + "name": "asp-client-workspace-syntax-query-request.v1.schema.json", + "digest": "blake3-256:5d545b03a0ed1ea888bcdb0d7566b82b4df9bc15ce6d88096e2ee89454a61dda" + }, + { + "name": "asp-client-workspace-syntax-query-response.v1.schema.json", + "digest": "blake3-256:003e5090bb807467a75fa814d92c97cca7489c6bab777f41634a38a57c5b5137" + }, { "name": "asp-python-graphs-session.v1.schema.json", - "digest": "blake3-256:447589323236a46efb7772e68b67b72f0f23549477ebc35a8a6734c201e5c2f1" + "digest": "blake3-256:74ced1d824c781b78062fd9f1f4ef4d3859adf07bb22a7cecb0e1011a99bbe7e" }, { "name": "callable-skeleton.schema.json", - "digest": "blake3-256:4d999ee27a470459659765bff2ffd3e40c25d7422dfdda09e69733c344ff2d40" + "digest": "blake3-256:094f5d077b8aeaf1be0d835246a8bf1f6c6ff26e2238e6d9147236daaa22e9bd" }, { "name": "canonical-item-selector.v1.schema.json", @@ -103,6 +111,14 @@ "name": "canonical-language-item-identity.schema.json", "digest": "blake3-256:29932815327cfba4424cc6443f04a9b3586c6ab8ce1f64d76904e9e80fc970e4" }, + { + "name": "content-binding.schema.json", + "digest": "blake3-256:d994d67092a39eb4c7cec8df470839372d06c815f9068c7e685336ad251a0b1a" + }, + { + "name": "content-publication-commit.v1.schema.json", + "digest": "blake3-256:afac75ce2bece1c60ffb9435a5c107c9687088459988dc7c3e79fd78c38aacfe" + }, { "name": "exact-definitions.v1.schema.json", "digest": "blake3-256:f2a9d369c221ad07ec3376150fec180c617d40318d4b0d49def6e68b0c5d00f1" @@ -115,6 +131,10 @@ "name": "grpc-warm-latency-receipt.schema.json", "digest": "blake3-256:b05cb70a17c9169e81fef1491fb6e6c972e9bab8843131af5637d52405424297" }, + { + "name": "host-workspace-initialization-binding.v1.schema.json", + "digest": "blake3-256:bd2fd298d4b3f8e469986a092aa40653a3bbfc59c4f51810c1829f4f6dd146a0" + }, { "name": "language-package-graph.schema.json", "digest": "blake3-256:4c2a985b14d27fb574e989452f031c545df08312d7e656044743af851550fafd" @@ -124,28 +144,44 @@ "digest": "blake3-256:11f17ec46eb769fc3d650236af1fc19ba02486bce478b57d2fdd62a0cab3512d" }, { - "name": "lexical-postings-work-reduction-receipt.schema.json", - "digest": "blake3-256:416d3bbbd584a7268632aa9852eae189501f923bd39c5af9ed5469d001c9fff5" + "name": "large-search-playbook-performance-receipt.v1.schema.json", + "digest": "blake3-256:951cee907a77fd2bc695a19b977a50508dc216eb3fe1e51f10195572880b4377" }, { - "name": "owner-search-payload-reduction-receipt.schema.json", - "digest": "blake3-256:3a1aa0d332181ed7bb2dd1ebcddc306809439ffda7109bf1cfbe9318fe49b819" + "name": "lexical-postings-work-reduction-receipt.schema.json", + "digest": "blake3-256:416d3bbbd584a7268632aa9852eae189501f923bd39c5af9ed5469d001c9fff5" }, { "name": "project-resolution.schema.json", "digest": "blake3-256:bfa2f1966d9d05e0f0c05cdb635112513498dedb076edf760f0504db60a358bb" }, + { + "name": "project-topology-library.v1.schema.json", + "digest": "blake3-256:6affc5b32ccc6530126038a2925ba224979499b093f27d3c09d548081b8860c4" + }, + { + "name": "project-workspace-binding.v1.schema.json", + "digest": "blake3-256:85a1cfdff5e432e593bb346ae447116a4bf11e5e49be10530ff1362e81603061" + }, { "name": "provider-definitions.v1.schema.json", "digest": "blake3-256:0ff66bd35a2c1561424f0aa2b95df689cfcb6513059ea5d06245f7efb38c45ce" }, + { + "name": "provider-document-resolution-descriptor.v1.schema.json", + "digest": "blake3-256:b8ac27d78e7450932817b7018f4c2b98130d64e8fb5f9425c48df39cd6d0c5b5" + }, { "name": "provider-language-projection-batch-request.schema.json", "digest": "blake3-256:9352612e33943a7953a327dcf691797cf4f177ffabe7c920d975f34b5d4f9722" }, { "name": "provider-language-projection-batch-response.schema.json", - "digest": "blake3-256:5053370366b61b10844e084d9a0d622f93d7df7fa244798a69d74d5b592122ae" + "digest": "blake3-256:1fb2e8d535c278bb46e09f391b6b1812d1242491089a6abf49603783b8c6d122" + }, + { + "name": "provider-manifest.schema.json", + "digest": "blake3-256:083ce2246c3590c626452fd06b7271031e1f989cf219af113643afd54b57ed44" }, { "name": "provider-method-argument-projection.v1.schema.json", @@ -159,6 +195,10 @@ "name": "provider-native-exact-response.v1.schema.json", "digest": "blake3-256:81a3c3149a15bdd3171e780e2786f7eac2be280345d50254cb620c6ef39aa25e" }, + { + "name": "provider-project-resolution-descriptor.schema.json", + "digest": "blake3-256:1bd422dbb2b0c8d561aff3df34f6ac0361c0725279bd64c55e63b17a15036e53" + }, { "name": "provider-project-resolution-request.schema.json", "digest": "blake3-256:d72002bef3a99e162e3f338a38933250666f058450c68d29708086716ad513a8" @@ -195,9 +235,21 @@ "name": "provider-workspace-install.schema.json", "digest": "blake3-256:a607ae5a7ea24b889a6bac9ee4624c03def801da7932b8c9818de176a66f856f" }, + { + "name": "python-generation-graph-performance-receipt.v1.schema.json", + "digest": "blake3-256:301ad2fce1050064cfd20ba63522c1261434b030fe4513e9105cf14951d7e048" + }, + { + "name": "query-playbook-materialization-receipt.v1.schema.json", + "digest": "blake3-256:05d6793b11c3b2643bf394b8773c1b9131aa17cf9ddcc588f3f592542553d83c" + }, + { + "name": "query-playbook-materialization-request.v1.schema.json", + "digest": "blake3-256:080aa7a4443f7f694d38da9cd26dee1cc86f75618159c3f91f799e28591ef6d3" + }, { "name": "resident-search-result.v1.schema.json", - "digest": "blake3-256:66cb31e1a55f6b1ac60e898c2a0bd6a6c0f4aaa6e112625de3307c2e6e4ec3b0" + "digest": "blake3-256:f725fd1b7a004f28e7e579e6b6b76626880931ff9663bbd1de42519d66afbe78" }, { "name": "resolved-source-scope.v1.schema.json", @@ -207,18 +259,54 @@ "name": "rg-coverage-receipt.schema.json", "digest": "blake3-256:9faa9e8e584171ff85907fbe6715fab6408e77c505e97cd484a286c17f2aa836" }, + { + "name": "runtime-artifact-bundle-binding.v2.schema.json", + "digest": "blake3-256:33502b4f75b3213cba8f24ba74d460525838b9627542c7fec9bdb28e35d1cbce" + }, + { + "name": "runtime-artifact-execution-closure-member.v1.schema.json", + "digest": "blake3-256:f0f47ae132953e45ac81ef407a3279d6a5f153e5f0bd60cc042755e5df5caa4c" + }, + { + "name": "runtime-binary-bundle.v2.schema.json", + "digest": "blake3-256:70ad1c9d87f4841788ba348fba007f8e12152bb74aa34a00f3e52c1a016eab91" + }, { "name": "runtime-client-terminal.schema.json", "digest": "blake3-256:bdf3e6b1bd11b201cc7802a364d678ad7a99a74cc40df2be90f76e2f6e050e70" }, + { + "name": "runtime-execution-binding.v2.schema.json", + "digest": "blake3-256:86f2829a05451dd84d180a6dab2cf621a14bd7e0c1ffcb3552ab9b7e61f6feb6" + }, { "name": "runtime-provider-search-receipt.v1.schema.json", "digest": "blake3-256:2e439b2ee83198710238aed803c9c37cd2cb06bc7a5e2c010486cdaa605df691" }, + { + "name": "runtime-search-client-timing-witness.v1.schema.json", + "digest": "blake3-256:076543ae9ba44348c9867a5454f043aeb36211742a6c299f57f33543cc071881" + }, + { + "name": "runtime-search-execution-budget.v1.schema.json", + "digest": "blake3-256:e7af7abd057b887c63490bcb143e6be322ed8e10ce244fa93e9cb043ed1107a7" + }, + { + "name": "runtime-workspace-execution-pointer.v1.schema.json", + "digest": "blake3-256:858db6ddc09da3fc1b71e45aa7cea58680bee44bfd78b0fa4c36bfb9b56b9276" + }, + { + "name": "runtime-workspace-execution-publication.v1.schema.json", + "digest": "blake3-256:889e816172637c38d87e8e0fdd8d186d0a1562149823774638f0128189d2f5d5" + }, { "name": "search-generation-change-set.v1.schema.json", "digest": "blake3-256:2c367cda53c1d03672c268fe2576fff2d11b7cd349992bc0f2c96bea206a2d80" }, + { + "name": "search-topology-settlement.v1.schema.json", + "digest": "blake3-256:9fc0cfde08823e1c52d302d1fae437b9395e7a8fb1f33dacc6a1892edcc60104" + }, { "name": "semantic-assurance-case.v1.schema.json", "digest": "blake3-256:f672ea13226296635f24c033bb6e238f6eae8ca1639d13d9f20c94fc9cb9ac3c" @@ -297,11 +385,11 @@ }, { "name": "semantic-graph-resident-evaluation-request.v1.schema.json", - "digest": "blake3-256:b29592ceb94bfcb7c502891c6d125ea6924b955fe0206f53df444d48087ea9e6" + "digest": "blake3-256:3147c5f98f8e0af8a461c74fba6d0114dab0f173fae6e2df4a43bee8e8cd1839" }, { "name": "semantic-graph-resident-evaluation-result.v1.schema.json", - "digest": "blake3-256:23e4d157d07f06ed33829d08d332adf7ee498b385b1789bb08c1b84cb5431fff" + "digest": "blake3-256:0b4c0b2ff31409e0e61745f2db72b20a7a786370efa304cb7c31cacc0059e3d4" }, { "name": "semantic-graph-turbo-artifact-events.v1.schema.json", @@ -313,7 +401,7 @@ }, { "name": "semantic-graph-turbo-request.v1.schema.json", - "digest": "blake3-256:ef8593d37038e0eb77d163700c0079d48d29ae37b6ba727e4f49c8f13f42eef2" + "digest": "blake3-256:bb79347cc1d9310d0628e2d7184b95df5784dde368e2ea4159a8574bf0cb7176" }, { "name": "semantic-graph.v1.schema.json", @@ -333,7 +421,7 @@ }, { "name": "semantic-language-registry.v1.schema.json", - "digest": "blake3-256:dce7fd8ff010dcfc17a4f4384a7bca8f202c522400e5ed226b5b9e96cd768881" + "digest": "blake3-256:8892eba517e307a1dc55dc46ed9e7114eb8c4cbd681cc19761362a37b8802361" }, { "name": "semantic-native-syntax-fact-index.v1.schema.json", @@ -361,7 +449,7 @@ }, { "name": "semantic-search-packet.v1.schema.json", - "digest": "blake3-256:993623779091e9fa3e0689a591481cda8393c0f42d3b55dd58b53af620598d71" + "digest": "blake3-256:a179736b63d468cf78e5c78f4e78b35304d94993e50402cea328be06bec8a9fb" }, { "name": "semantic-search-storage-route.v1.schema.json", diff --git a/schemas/.asp-schema-manager-receipt.json b/schemas/.asp-schema-manager-receipt.json index 8fe1a5d..f0c74d5 100644 --- a/schemas/.asp-schema-manager-receipt.json +++ b/schemas/.asp-schema-manager-receipt.json @@ -1,5 +1,5 @@ { "schemaId": "agent.semantic-protocols.language-schema-bundle-receipt", "schemaVersion": "1", - "schemaDigest": "blake3-256:ceb5fb09195c300d5b5a9c882ed8ac6263fbe15c0762907d6d2ab648bf65663d" + "schemaDigest": "blake3-256:c95fa3b9e3c775ee45275a08f1a247b564ee6da31d5cc4878aa1438c7b8c12ed" } \ No newline at end of file diff --git a/schemas/asp-client-exact-query-failure.schema.json b/schemas/asp-client-exact-query-failure.schema.json index 8ba60f1..0b49d17 100644 --- a/schemas/asp-client-exact-query-failure.schema.json +++ b/schemas/asp-client-exact-query-failure.schema.json @@ -5,8 +5,8 @@ "type": "object", "additionalProperties": false, "required": [ - "schemaId", "schemaVersion", "state", "operationId", "languageId", - "providerId", "phase", "reasonKind", "recommendedNext", + "schemaId", "schemaVersion", "state", "operationId", "projectId", "workspaceId", "languageId", + "providerId", "phase", "reasonKind", "residentReadElapsedMicros", "serviceElapsedMicros", "elapsedMicros", "workCounters", "details" ], @@ -17,6 +17,8 @@ "schemaVersion": { "const": "1" }, "state": { "const": "failed" }, "operationId": { "type": "string", "minLength": 1 }, + "projectId": { "type": "string", "minLength": 1 }, + "workspaceId": { "type": "string", "minLength": 1 }, "languageId": { "type": "string", "minLength": 1 }, "providerId": { "type": "string", "minLength": 1 }, "requestedSelector": { "type": ["string", "null"] }, @@ -32,7 +34,6 @@ "type": ["string", "null"], "pattern": "^[0-9a-f]{64}$" }, - "recommendedNext": {}, "residentReadElapsedMicros": { "type": "integer", "minimum": 0 }, "serviceElapsedMicros": { "type": "integer", "minimum": 0 }, "elapsedMicros": { "type": "integer", "minimum": 0 }, diff --git a/schemas/asp-client-exact-query-request.schema.json b/schemas/asp-client-exact-query-request.schema.json index 36b433a..155b97f 100644 --- a/schemas/asp-client-exact-query-request.schema.json +++ b/schemas/asp-client-exact-query-request.schema.json @@ -4,17 +4,79 @@ "title": "ASP Client Exact Query Request", "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "selector", "projection"], + "required": ["schemaId", "schemaVersion", "mode", "projection"], "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-exact-query-request" - }, - "schemaVersion": { "const": "1" }, + "schemaId": {"const": "agent.semantic-protocols.asp-client-exact-query-request"}, + "schemaVersion": {"const": "1"}, + "mode": {"enum": ["selector", "syntax", "playbook"]}, "selector": { "type": "string", "minLength": 1, - "description": "A canonical item selector or normalized workspace-relative owner path. ASP Server owns parsing and resolution against the admitted CompleteGeneration." + "description": "A parser-owned exact structural selector resolved against the admitted CompleteGeneration." + }, + "selectors": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "minLength": 1, + "description": "A parser-owned exact structural selector. Its producer scheme is the sole language or document authority." + } + }, + "languages": {"$ref": "#/$defs/producerExpression"}, + "documents": {"$ref": "#/$defs/producerExpression"}, + "workspace": {"$ref": "#/$defs/identity"}, + "syntax": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/producerNativeBlock"} + }, + "projection": {"enum": ["matches", "source", "callable-skeleton"]} + }, + "oneOf": [ + { + "properties": {"mode": {"const": "selector"}}, + "required": ["selector"], + "not": {"anyOf": [ + {"required": ["languages"]}, {"required": ["documents"]}, + {"required": ["syntax"]} + ]} + }, + { + "properties": {"mode": {"const": "syntax"}}, + "required": ["syntax"], + "allOf": [ + {"anyOf": [{"required": ["languages"]}, {"required": ["documents"]}]}, + {"not": {"required": ["selector"]}} + ] + }, + { + "properties": {"mode": {"const": "playbook"}}, + "required": ["selectors"], + "allOf": [ + {"not": {"required": ["selector"]}}, + {"not": {"required": ["languages"]}}, + {"not": {"required": ["documents"]}}, + {"not": {"required": ["syntax"]}} + ] + } + ], + "$defs": { + "identity": {"type": "string", "minLength": 1, "maxLength": 2048}, + "producerExpression": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$" }, - "projection": { "enum": ["source", "callable-skeleton"] } + "nativeArgv": {"type": "array", "minItems": 1, "items": {"type": "string"}}, + "producerNativeBlock": { + "type": "object", + "additionalProperties": false, + "required": ["producer", "argv"], + "properties": { + "producer": {"$ref": "#/$defs/identity"}, + "argv": {"$ref": "#/$defs/nativeArgv"} + } + } } } diff --git a/schemas/asp-client-exact-query-response.schema.json b/schemas/asp-client-exact-query-response.schema.json index 2df7843..5fa53f5 100644 --- a/schemas/asp-client-exact-query-response.schema.json +++ b/schemas/asp-client-exact-query-response.schema.json @@ -8,6 +8,8 @@ "schemaId", "schemaVersion", "operationId", + "projectId", + "workspaceId", "languageId", "providerId", "generationDigest", @@ -24,6 +26,8 @@ }, "schemaVersion": { "const": "1" }, "operationId": { "type": "string", "minLength": 1 }, + "projectId": { "type": "string", "minLength": 1 }, + "workspaceId": { "type": "string", "minLength": 1 }, "languageId": { "type": "string", "minLength": 1 }, "providerId": { "type": "string", "minLength": 1 }, "generationDigest": { diff --git a/schemas/asp-client-frame.schema.json b/schemas/asp-client-frame.schema.json index 8768829..4f260c5 100644 --- a/schemas/asp-client-frame.schema.json +++ b/schemas/asp-client-frame.schema.json @@ -5,7 +5,6 @@ "oneOf": [ { "$ref": "#/$defs/initialize" }, { "$ref": "#/$defs/request" }, - { "$ref": "#/$defs/dispatch" }, { "$ref": "#/$defs/cancel" }, { "$ref": "#/$defs/shutdown" }, { "$ref": "#/$defs/exit" }, @@ -13,10 +12,18 @@ { "$ref": "#/$defs/event" } ], "$defs": { - "clientInfo": { "$ref": "#/$defs/clientInfo" }, + "clientInfo": { + "type": "object", + "additionalProperties": false, + "required": ["name", "version"], + "properties": { + "name": { "type": "string", "minLength": 1 }, + "version": { "type": "string", "minLength": 1 } + } + }, "base": { "type": "object", - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity"], + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "projectId", "workspaceId"], "properties": { "schemaId": { "const": "agent.semantic-protocols.client.frame" }, "schemaVersion": { "const": "1" }, @@ -24,7 +31,8 @@ "protocolVersion": { "const": "1" }, "kind": { "type": "string" }, "sessionId": { "type": "string", "minLength": 1 }, - "workspaceIdentity": { "type": "string", "minLength": 1 }, + "projectId": { "type": "string", "minLength": 1 }, + "workspaceId": { "type": "string", "minLength": 1 }, "traceContext": { "type": "object", "additionalProperties": false, @@ -42,18 +50,12 @@ { "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId", "projectRoot", "clientInfo", "capabilities"], + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "projectId", "workspaceId", "requestId", "clientInfo", "capabilities"], "properties": { "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, - "kind": { "const": "initialize" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "kind": { "const": "initialize" }, "sessionId": {}, "projectId": {}, "workspaceId": {}, "traceContext": {}, "requestId": { "type": "string", "minLength": 1 }, - "projectRoot": { "type": "string", "minLength": 1 }, - "clientInfo": { - "type": "object", - "additionalProperties": false, - "required": ["name", "version"], - "properties": { "name": { "type": "string", "minLength": 1 }, "version": { "type": "string", "minLength": 1 } } - }, + "clientInfo": { "$ref": "#/$defs/clientInfo" }, "capabilities": { "type": "object" } } } @@ -65,34 +67,16 @@ { "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId", "catalogGeneration", "workspaceGeneration", "method", "params"], + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "projectId", "workspaceId", "requestId", "catalogGeneration", "workspaceGeneration", "method", "params"], "properties": { "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, - "kind": { "const": "request" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "kind": { "const": "request" }, "sessionId": {}, "projectId": {}, "workspaceId": {}, "traceContext": {}, "requestId": { "type": "string", "minLength": 1 }, "catalogGeneration": { "type": "string", "minLength": 1 }, "workspaceGeneration": { "type": "string", "minLength": 1 }, "method": { "type": "string", "minLength": 1 }, - "params": {} - } - } - ] - }, - "dispatch": { - "allOf": [ - { "$ref": "#/$defs/base" }, - { - "type": "object", - "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId", "projectRoot", "clientInfo", "method", "params"], - "properties": { - "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, - "kind": { "const": "dispatch" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, - "requestId": { "type": "string", "minLength": 1 }, - "projectRoot": { "type": "string", "minLength": 1 }, - "clientInfo": { "$ref": "#/$defs/clientInfo" }, - "method": { "type": "string", "minLength": 1 }, - "params": {} + "params": {}, + "clientTimingWitness": { "$ref": "runtime-search-client-timing-witness.v1.schema.json" } } } ] @@ -103,10 +87,10 @@ { "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "workspaceIdentity", "requestId"], + "required": ["schemaId", "schemaVersion", "protocolId", "protocolVersion", "kind", "sessionId", "projectId", "workspaceId", "requestId"], "properties": { "schemaId": {}, "schemaVersion": {}, "protocolId": {}, "protocolVersion": {}, - "kind": { "const": "cancel" }, "sessionId": {}, "workspaceIdentity": {}, "traceContext": {}, + "kind": { "const": "cancel" }, "sessionId": {}, "projectId": {}, "workspaceId": {}, "traceContext": {}, "requestId": { "type": "string", "minLength": 1 } } } diff --git a/schemas/asp-client-owner-search-request.schema.json b/schemas/asp-client-owner-search-request.schema.json deleted file mode 100644 index eab07dd..0000000 --- a/schemas/asp-client-owner-search-request.schema.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-owner-search-request.schema.json", - "title": "ASP Client Owner Search Request", - "type": "object", - "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "ownerPath", "query", "view"], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-owner-search-request" - }, - "schemaVersion": { "const": "1" }, - "ownerPath": { "type": "string", "minLength": 1 }, - "query": { "type": "string" }, - "view": { "type": "string", "minLength": 1 } - } -} diff --git a/schemas/asp-client-owner-search-response.schema.json b/schemas/asp-client-owner-search-response.schema.json deleted file mode 100644 index ffdc039..0000000 --- a/schemas/asp-client-owner-search-response.schema.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-owner-search-response.schema.json", - "title": "ASP Client Owner Search Response", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "state", - "generationDigest", - "rootDigest", - "ownerPath", - "query", - "view", - "candidateCount", - "returnedCount", - "selectors" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-owner-search-response" - }, - "schemaVersion": { "const": "1" }, - "state": { "enum": ["owner", "owner-missing"] }, - "generationDigest": { "type": "string", "minLength": 1 }, - "rootDigest": { "type": "string", "minLength": 1 }, - "ownerPath": { "type": "string", "minLength": 1 }, - "contentDigest": { "type": "string", "minLength": 1 }, - "query": { "type": "string" }, - "view": { "const": "seeds" }, - "candidateCount": { "type": "integer", "minimum": 0 }, - "returnedCount": { "type": "integer", "minimum": 0, "maximum": 100 }, - "selectors": { - "type": "array", - "maxItems": 100, - "items": { - "type": "object", - "additionalProperties": false, - "required": ["selector", "byteStart", "byteEnd"], - "properties": { - "selector": { "type": "string", "minLength": 1 }, - "byteStart": { "type": "integer", "minimum": 0 }, - "byteEnd": { "type": "integer", "minimum": 0 } - } - } - } - }, - "allOf": [ - { - "if": { "properties": { "state": { "const": "owner" } } }, - "then": { "required": ["contentDigest"] }, - "else": { - "not": { "required": ["contentDigest"] } - } - } - ] -} diff --git a/schemas/asp-client-query-readiness-failure.schema.json b/schemas/asp-client-query-readiness-failure.schema.json index 01d856a..9767290 100644 --- a/schemas/asp-client-query-readiness-failure.schema.json +++ b/schemas/asp-client-query-readiness-failure.schema.json @@ -6,8 +6,8 @@ "additionalProperties": false, "required": [ "schemaId", "schemaVersion", "state", "phase", "reasonKind", - "workspaceIdentity", "languageId", "providerId", "generationState", - "publicationError", "recommendedNext", "elapsedMicros", "workCounters" + "projectId", "workspaceId", "languageId", "providerId", "generationState", + "publicationError", "elapsedMicros", "workCounters" ], "properties": { "schemaId": { @@ -17,12 +17,12 @@ "state": { "const": "failed" }, "phase": { "const": "runtime-generation-authority" }, "reasonKind": { "const": "query-not-ready" }, - "workspaceIdentity": { "type": "string", "minLength": 1 }, + "projectId": { "type": "string", "minLength": 1 }, + "workspaceId": { "type": "string", "minLength": 1 }, "languageId": { "type": "string", "minLength": 1 }, "providerId": { "type": "string", "minLength": 1 }, "generationState": { "enum": ["unpublished", "failed"] }, "publicationError": { "type": ["string", "null"] }, - "recommendedNext": { "type": "object" }, "elapsedMicros": { "type": "integer", "minimum": 0 }, "workCounters": { "$ref": "asp-client-work-counters.schema.json" } } diff --git a/schemas/asp-client-search-request.schema.json b/schemas/asp-client-search-request.schema.json index 2f349fd..391cd12 100644 --- a/schemas/asp-client-search-request.schema.json +++ b/schemas/asp-client-search-request.schema.json @@ -4,16 +4,20 @@ "title": "ASP Client Search Request", "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "operation", "query"], + "required": ["schemaId", "schemaVersion", "intent", "query", "scope", "coverage", "maxOwners", "deadlineMs", "explain"], "properties": { "schemaId": { "const": "agent.semantic-protocols.asp-client-search-request" }, "schemaVersion": { "const": "1" }, - "operation": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$" + "intent": { + "enum": ["conceptual", "relationship", "exact-literal", "absence-proof"] }, - "query": { "type": "string" } + "query": { "type": "string", "minLength": 1 }, + "scope": { "pattern": "^(workspace|owner:.+)$" }, + "coverage": { "enum": ["candidates", "complete"] }, + "maxOwners": { "type": "integer", "minimum": 1, "maximum": 100 }, + "deadlineMs": { "type": "integer", "minimum": 1, "maximum": 5000 }, + "explain": { "enum": ["compact", "full"] } } } diff --git a/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json b/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json index e9b6d34..263abc1 100644 --- a/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json +++ b/schemas/asp-client-workspace-generation-ensure-ready-request.schema.json @@ -5,5 +5,10 @@ "type": "object", "additionalProperties": false, "required": [], - "properties": {} + "properties": { + "languageId": { + "type": "string", + "minLength": 1 + } + } } diff --git a/schemas/asp-client-workspace-query-playbook-request.v1.schema.json b/schemas/asp-client-workspace-query-playbook-request.v1.schema.json new file mode 100644 index 0000000..3299ec8 --- /dev/null +++ b/schemas/asp-client-workspace-query-playbook-request.v1.schema.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-workspace-query-playbook-request.v1.schema.json", + "title": "ASP Client Workspace Query Playbook Request V1", + "description": "One language-neutral northbound Query Playbook request. Runtime injects workspace and execution identity after transport admission.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "selectors", "projection"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-query-playbook-request"}, + "schemaVersion": {"const": "1"}, + "selectors": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9+.-]*://[^\\s#]+#item/.+$(?![\\s\\S])" + } + }, + "projection": {"enum": ["source", "callable-skeleton"]} + } +} diff --git a/schemas/asp-client-workspace-search-playbook-request.v1.schema.json b/schemas/asp-client-workspace-search-playbook-request.v1.schema.json new file mode 100644 index 0000000..473bc70 --- /dev/null +++ b/schemas/asp-client-workspace-search-playbook-request.v1.schema.json @@ -0,0 +1,84 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-search-playbook-request.v1.schema.json", + "title": "ASP Client Workspace Search Playbook Request V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "clauseOrder"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-search-playbook-request"}, + "schemaVersion": {"const": "1"}, + "languages": {"$ref": "#/$defs/producerExpression"}, + "documents": {"$ref": "#/$defs/producerExpression"}, + "workspace": {"$ref": "#/$defs/identity"}, + "fd": {"$ref": "#/$defs/nativeBlocks"}, + "rg": {"$ref": "#/$defs/nativeBlocks"}, + "tantivy": {"$ref": "#/$defs/nativeBlocks"}, + "syntax": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/producerNativeBlock"}}, + "nativeSyntax": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/exactSelector"}}, + "graph": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/graphNativeBlock"}}, + "clauseOrder": { + "description": "Agent-authored acquisition priority followed by dependent graph barriers, preserved from CLI occurrence order.", + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/clauseRef"} + } + }, + "allOf": [ + { + "anyOf": [ + {"required": ["languages"]}, + {"required": ["documents"]} + ] + }, + { + "anyOf": [ + {"required": ["fd"]}, + {"required": ["rg"]}, + {"required": ["tantivy"]}, + {"required": ["syntax"]}, + {"required": ["nativeSyntax"]} + ] + } + ], + "$defs": { + "identity": {"type": "string", "minLength": 1, "maxLength": 512}, + "exactSelector": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])" + }, + "producerExpression": { + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$" + }, + "nativeArgv": {"type": "array", "minItems": 1, "items": {"type": "string"}}, + "nativeBlocks": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/nativeArgv"}}, + "producerNativeBlock": { + "type": "object", + "additionalProperties": false, + "required": ["producer", "argv"], + "properties": { + "producer": {"$ref": "#/$defs/identity"}, + "argv": {"$ref": "#/$defs/nativeArgv"} + } + }, + "graphNativeBlock": { + "type": "object", + "additionalProperties": false, + "required": ["language", "argv"], + "properties": { + "language": {"$ref": "#/$defs/identity"}, + "argv": {"$ref": "#/$defs/nativeArgv"} + } + }, + "clauseRef": { + "type": "object", + "additionalProperties": false, + "required": ["axis", "blockIndex"], + "properties": { + "axis": {"enum": ["fd", "rg", "tantivy", "syntax", "native-syntax", "graph"]}, + "blockIndex": {"type": "integer", "minimum": 0} + } + } + } +} diff --git a/schemas/asp-client-workspace-syntax-query-request.v1.schema.json b/schemas/asp-client-workspace-syntax-query-request.v1.schema.json new file mode 100644 index 0000000..47b4b73 --- /dev/null +++ b/schemas/asp-client-workspace-syntax-query-request.v1.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-syntax-query-request.v1.schema.json", + "title": "ASP Client Workspace Syntax Query Request V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "syntax", "projection"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-syntax-query-request"}, + "schemaVersion": {"const": "1"}, + "languages": {"type": "string", "pattern": "^[^|\\s]+(\\|[^|\\s]+)*$"}, + "documents": {"type": "string", "pattern": "^[^|\\s]+(\\|[^|\\s]+)*$"}, + "workspace": {"type": "string", "minLength": 1}, + "syntax": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["producer", "argv"], + "properties": { + "producer": {"type": "string", "minLength": 1}, + "argv": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}} + } + } + }, + "projection": {"const": "matches"} + }, + "anyOf": [{"required": ["languages"]}, {"required": ["documents"]}] +} diff --git a/schemas/asp-client-workspace-syntax-query-response.v1.schema.json b/schemas/asp-client-workspace-syntax-query-response.v1.schema.json new file mode 100644 index 0000000..b9e530d --- /dev/null +++ b/schemas/asp-client-workspace-syntax-query-response.v1.schema.json @@ -0,0 +1,27 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-syntax-query-response.v1.schema.json", + "title": "ASP Client Workspace Syntax Query Response V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "state", "evidence"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-syntax-query-response"}, + "schemaVersion": {"const": "1"}, + "state": {"const": "ready"}, + "evidence": { + "type": "array", + "maxItems": 3, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["owner", "selector", "relation"], + "properties": { + "owner": {"type": "string", "minLength": 1}, + "selector": {"type": "string", "pattern": "^[^:]+://.+#item/.+$"}, + "relation": {"type": "string", "minLength": 1} + } + } + } + } +} diff --git a/schemas/asp-python-graphs-session.v1.schema.json b/schemas/asp-python-graphs-session.v1.schema.json index 76cd477..7662b95 100644 --- a/schemas/asp-python-graphs-session.v1.schema.json +++ b/schemas/asp-python-graphs-session.v1.schema.json @@ -26,11 +26,10 @@ "messageKind": { "enum": [ "hello", - "open-generation", - "evaluate", - "search-evidence", - "timeline", + "generation-graph", + "evaluate-resident", "release-generation", + "timeline", "cancel", "health", "shutdown", @@ -39,7 +38,6 @@ }, "workspaceIdentity": {"type": "string", "minLength": 1}, "generationDigest": {"$ref": "#/$defs/blake3Digest"}, - "generationToken": {"type": "integer", "minimum": 1}, "runtimeArtifactDigest": {"$ref": "#/$defs/blake3Digest"}, "executionArtifactDigest": {"$ref": "#/$defs/blake3Digest"}, "deadlineUnixMillis": {"type": "integer", "minimum": 0}, @@ -48,18 +46,6 @@ "payload": {"type": "object"} }, "allOf": [ - { - "if": { - "properties": { - "messageKind": { - "enum": ["open-generation", "evaluate", "search-evidence", "release-generation"] - } - } - }, - "then": { - "required": ["workspaceIdentity", "generationDigest", "generationToken"] - } - }, { "if": {"properties": {"messageKind": {"const": "hello"}}}, "then": { @@ -71,14 +57,14 @@ "then": {"required": ["cancellationId"]} }, { - "if": {"properties": {"messageKind": {"const": "search-evidence"}}}, - "then": { + "if": { "properties": { - "payloadSchemaId": { - "const": "agent.semantic-protocols.asp-python-graphs-search-evidence" + "messageKind": { + "enum": ["generation-graph", "evaluate-resident", "release-generation"] } } - } + }, + "then": {"required": ["workspaceIdentity", "generationDigest"]} } ], "$defs": { diff --git a/schemas/callable-skeleton.schema.json b/schemas/callable-skeleton.schema.json index e6d8592..8484898 100644 --- a/schemas/callable-skeleton.schema.json +++ b/schemas/callable-skeleton.schema.json @@ -4,8 +4,9 @@ "title": "Callable Skeleton Payload", "type": "object", "additionalProperties": false, - "required": ["rootNodeId", "callable", "nodes", "relations", "cost"], + "required": ["rootSelector", "rootNodeId", "callable", "nodes", "relations", "cost"], "properties": { + "rootSelector": {"type": "string", "minLength": 1}, "rootNodeId": {"type": "string", "minLength": 1}, "callable": {"type": "object"}, "nodes": {"type": "array"}, diff --git a/schemas/content-binding.schema.json b/schemas/content-binding.schema.json new file mode 100644 index 0000000..f4293c9 --- /dev/null +++ b/schemas/content-binding.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent.semantic.protocols/schemas/content-binding", + "title": "ASP Content Binding", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "runtimeArtifactDigest", "workspaceSnapshotDigest", "sourceGenerationDigest", "sourceIndexDigest", "schemaDigest", "providerCatalogDigest", "authorityStamp"], + "properties": { + "schemaId": {"const": "asp.content-binding"}, + "schemaVersion": {"const": "1"}, + "runtimeArtifactDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "workspaceSnapshotDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "sourceGenerationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "sourceIndexDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "schemaDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "providerCatalogDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "authorityStamp": { + "type": "object", + "additionalProperties": false, + "required": ["keyId", "canonicalDigest", "signature"], + "properties": { + "keyId": {"type": "string", "minLength": 1}, + "canonicalDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "signature": {"type": "string", "minLength": 1} + } + } + } +} diff --git a/schemas/content-publication-commit.v1.schema.json b/schemas/content-publication-commit.v1.schema.json new file mode 100644 index 0000000..f83080a --- /dev/null +++ b/schemas/content-publication-commit.v1.schema.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/content-publication-commit.v1.schema.json", + "title": "Content Publication Commit V1", + "description": "Immutable linearization record binding one complete ContentBinding to its predecessor fence. Persistence authority, not this document alone, establishes durability.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "contentBinding", + "commitDigest", + "predecessorCommitDigest", + "mutationId", + "leaseId" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.content-publication-commit"}, + "schemaVersion": {"const": "1"}, + "contentBinding": {"$ref": "content-binding.schema.json"}, + "commitDigest": {"$ref": "#/$defs/contentDigest"}, + "predecessorCommitDigest": { + "oneOf": [ + {"$ref": "#/$defs/contentDigest"}, + {"type": "null"} + ] + }, + "mutationId": {"type": "string", "minLength": 1}, + "leaseId": {"type": "string", "minLength": 1} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/host-workspace-initialization-binding.v1.schema.json b/schemas/host-workspace-initialization-binding.v1.schema.json new file mode 100644 index 0000000..3d87408 --- /dev/null +++ b/schemas/host-workspace-initialization-binding.v1.schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/host-workspace-initialization-binding.v1.schema.json", + "title": "Host Workspace Initialization Binding V1", + "description": "The independently admitted Project Workspace and Host-local worktree instance retained by Runtime workspace initialization. This is not an Agent-facing handle or a Query parameter.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "projectWorkspace", "worktreeInstanceId"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.host-workspace-initialization-binding"}, + "schemaVersion": {"const": "1"}, + "projectWorkspace": {"$ref": "project-workspace-binding.v1.schema.json"}, + "worktreeInstanceId": { + "type": "string", + "minLength": 1, + "description": "Opaque Host-local worktree identity minted and supplied by workspace initialization; never inferred from projectId, workspaceId, sessionId, or a filesystem path." + } + } +} diff --git a/schemas/language-schema-profiles.json b/schemas/language-schema-profiles.json new file mode 100644 index 0000000..deb96ca --- /dev/null +++ b/schemas/language-schema-profiles.json @@ -0,0 +1,1107 @@ +{ + "$schema": "language-schema-profile-registry.schema.json", + "schemaId": "agent.semantic-protocols.language-schema-profile-registry", + "schemaVersion": "1", + "wireArtifacts": { + "asp-client-protocol-v1": "asp-client-protocol.v1.proto" + }, + "rootSets": { + "provider-contract": [ + "provider-registration.schema.json", + "provider-manifest.schema.json", + "provider-route.schema.json", + "provider-query-pack-descriptor.schema.json", + "provider-runtime-contract-descriptor.schema.json", + "asp-client-server-descriptor.schema.json", + "provider-runtime-request-stream-frame.schema.json", + "provider-runtime-request-stream-ack.schema.json", + "provider-language-projection-batch-request.schema.json", + "provider-language-projection-batch-response.schema.json", + "provider-workspace-install.schema.json", + "asp-python-graphs-session.v1.schema.json", + "language-schema-bundle-receipt.schema.json" + ], + "client-protocol": [ + "asp-client-protocol-catalog.schema.json", + "asp-client-frame.schema.json", + "asp-client-conformance.schema.json", + "asp-client-search-request.schema.json", + "asp-client-workspace-search-playbook-request.v1.schema.json", + "asp-client-workspace-query-playbook-request.v1.schema.json", + "asp-client-workspace-syntax-query-request.v1.schema.json", + "asp-client-workspace-syntax-query-response.v1.schema.json", + "search-topology-settlement.v1.schema.json", + "large-search-playbook-performance-receipt.v1.schema.json", + "asp-client-source-index-lookup-request.schema.json", + "resident-search-result.v1.schema.json", + "asp-client-exact-query-request.schema.json", + "asp-client-exact-query-response.schema.json", + "asp-client-exact-query-failure.schema.json", + "query-playbook-materialization-receipt.v1.schema.json", + "asp-client-query-readiness-failure.schema.json", + "asp-client-dispatch-failure.schema.json", + "asp-client-work-counters.schema.json", + "asp-client-schema-bundle-request.schema.json", + "asp-client-schema-bundle-response.schema.json", + "grpc-warm-latency-receipt.schema.json", + "lexical-postings-work-reduction-receipt.schema.json", + "rg-coverage-receipt.schema.json", + "runtime-client-terminal.schema.json", + "asp-client-workspace-source-mutation.schema.json", + "asp-client-cancellation-probe-request.schema.json", + "asp-client-cancellation-probe-response.schema.json", + "asp-client-workspace-generation-ensure-ready-request.schema.json", + "asp-client-graphs-timeline-request.v1.schema.json" + ], + "project-resolution": [ + "provider-project-resolution-request.schema.json", + "provider-project-resolution-response.schema.json" + ], + "core-query": [ + "callable-skeleton.schema.json", + "exact-structural-selector.v1.schema.json", + "provider-native-exact-request.v1.schema.json", + "provider-native-exact-response.v1.schema.json", + "semantic-search-packet.v1.schema.json", + "semantic-search-storage-route.v1.schema.json", + "semantic-query-packet.v1.schema.json", + "semantic-exact-selector-receipt.v1.schema.json", + "semantic-owner-item-evidence.v1.schema.json", + "semantic-content-compaction.v1.schema.json", + "search-generation-change-set.v1.schema.json", + "semantic-read-packet.v1.schema.json", + "semantic-source-location.v1.schema.json", + "semantic-tree-sitter-provenance.v1.schema.json", + "semantic-tree-sitter-query.v1.schema.json", + "semantic-tree-sitter-grammar-profile.v1.schema.json", + "semantic-relation-plan.v1.schema.json", + "semantic-flow-lite.v1.schema.json", + "semantic-codeql-evidence.v1.schema.json", + "semantic-fact-graph.v1.schema.json", + "semantic-fact-ontology.v1.schema.json", + "semantic-dependency-topology.v1.schema.json", + "semantic-structural-index.v1.schema.json", + "semantic-native-syntax-fact-index.v1.schema.json", + "semantic-graph.v1.schema.json", + "semantic-type-surface.v1.schema.json", + "semantic-handle.v1.schema.json", + "semantic-language-registry.v1.schema.json", + "semantic-language-projection.v1.schema.json" + ], + "agent-reasoning": [ + "software-criterion-catalog.v1.schema.json", + "semantic-verification-receipt.v1.schema.json", + "semantic-behavior-snapshot.v1.schema.json", + "semantic-determinism-readiness.v1.schema.json", + "semantic-dev-command-log.v1.schema.json", + "semantic-formal-proof-pilot.v1.schema.json", + "semantic-review-packet.v1.schema.json", + "semantic-evidence-graph.v1.schema.json", + "semantic-graph-resident-evaluation-request.v1.schema.json", + "semantic-graph-resident-evaluation-result.v1.schema.json", + "python-generation-graph-performance-receipt.v1.schema.json", + "semantic-graph-turbo-request.v1.schema.json", + "semantic-assurance-case.v1.schema.json", + "semantic-ast-patch.v1.schema.json", + "semantic-ast-patch-receipt.v1.schema.json" + ] + }, + "profiles": [ + { + "languageId": "rust", + "packageRoot": "languages/asp-rust", + "bundleRoot": "languages/asp-rust/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [ + "provider-syntax-query-request.schema.json", + "provider-syntax-query-response.schema.json", + "semantic-invariant-candidate.v1.schema.json", + "semantic-compare-packet.v1.schema.json" + ], + "providerOwned": [ + "rust-ast-patch-real-project-evidence.v1.schema.json", + "rust-semantic-capabilities.v1.schema.json" + ] + }, + { + "languageId": "typescript", + "packageRoot": "languages/asp-typescript", + "bundleRoot": "languages/asp-typescript/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [ + "typescript-semantic-capabilities.v1.schema.json" + ] + }, + { + "languageId": "python", + "packageRoot": "languages/asp-python", + "bundleRoot": "languages/asp-python/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [ + "python-semantic-capabilities.v1.schema.json" + ] + }, + { + "languageId": "julia", + "packageRoot": "languages/AspJulia.jl", + "bundleRoot": "languages/AspJulia.jl/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [] + }, + { + "languageId": "gerbil-scheme", + "packageRoot": "languages/asp-gerbil-scheme", + "bundleRoot": "languages/asp-gerbil-scheme/schemas", + "rootSets": [ + "provider-contract", + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [ + "callable-skeleton.schema.json", + "exact-structural-selector.v1.schema.json", + "provider-native-exact-request.v1.schema.json", + "provider-native-exact-response.v1.schema.json", + "semantic-search-packet.v1.schema.json", + "semantic-query-packet.v1.schema.json", + "semantic-owner-item-evidence.v1.schema.json", + "semantic-extension-pattern-mapping.v1.schema.json", + "semantic-language-evidence.v1.schema.json", + "semantic-runtime-source-acquisition.v1.schema.json", + "semantic-type-proof.v1.schema.json" + ], + "providerOwned": [ + "semantic-asp-gerbil-scheme-info.v1.schema.json" + ] + }, + { + "languageId": "org", + "packageRoot": "languages/orgize", + "bundleRoot": "languages/orgize/provider/org/schemas", + "rootSets": [ + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [] + }, + { + "languageId": "md", + "packageRoot": "languages/orgize", + "bundleRoot": "languages/orgize/provider/md/schemas", + "rootSets": [ + "client-protocol", + "project-resolution", + "core-query", + "agent-reasoning" + ], + "roots": [], + "providerOwned": [] + } + ], + "families": [ + { + "familyId": "asp.schema-family.semantic-assurance", + "owner": "schemas", + "rationale": "Assurance cases, determinism readiness, evidence graphs, review packets, and formal-proof pilot contracts.", + "priority": 300, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-assurance-", + "semantic-determinism-", + "semantic-evidence-", + "semantic-review-", + "semantic-formal-proof-" + ] + }, + "definitionSchemaPath": "schemas/semantic-assurance-definitions.v1.schema.json", + "definitionVisibility": "family" + }, + { + "familyId": "asp.schema-family.semantic-proof", + "owner": "schemas", + "rationale": "Proof obligations, recipes, receipts, and formal verification contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "definitionSchemaPath": "schemas/semantic-proof-definitions.v1.schema.json", + "definitionVisibility": "family", + "namespace": { + "filenamePrefixes": [ + "semantic-proof-", + "semantic-formal-", + "axle-", + "lean-", + "fallback-reflection-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-policy", + "owner": "schemas", + "rationale": "Semantic policy facts and executable policy recipe contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "definitionSchemaPath": "schemas/semantic-policy-definitions.v1.schema.json", + "definitionVisibility": "family", + "namespace": { + "filenamePrefixes": [ + "semantic-policy-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-ast-patch", + "definitionSchemaPath": "schemas/semantic-ast-patch-definitions.v1.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Semantic AST patch request and receipt contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-ast-patch" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-graph-turbo", + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp-python-graphs-session.v1.schema.json" + ] + }, + "definitionSchemaPath": "schemas/semantic-graph-turbo-definitions.v1.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Graph Turbo request, result, lifecycle, cache, benchmark, and assurance contracts.", + "priority": 300, + "parentFamilyId": "asp.schema-family.semantic-graph", + "namespace": { + "filenamePrefixes": [ + "semantic-graph-turbo-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-graph", + "owner": "schemas", + "rationale": "Semantic graph contracts outside the narrower Graph Turbo namespace.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-graph-", + "graph-search-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/semantic-graph.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.hook", + "owner": "schemas", + "rationale": "Hook activation, policy, matching, and break-glass capability contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "hook-", + "activation-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/host-native-execution-required.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-agent", + "definitionSchemaPath": "schemas/semantic-agent-definitions.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Agent hook, session, search, policy, and runtime profile contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-agent-", + "agent-action-", + "agent-action.", + "agent-route-", + "agent-root-", + "agent-session-", + "multi-agent-", + "subagent-", + "host-agent-", + "host-session-", + "agent-semantic-project-", + "codex-session-", + "codex-child-", + "codex-collaboration-", + "codex-thread-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/codex-multi-agent-v2-host-lifecycle-event.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.agent-semantic-client", + "owner": "schemas", + "rationale": "Agent semantic client cache and receipt contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "agent-semantic-client-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-sandtable", + "owner": "schemas", + "rationale": "Semantic sandtable scenario, receipt, report, and comparison contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "definitionSchemaPath": "schemas/semantic-sandtable-definitions.v1.schema.json", + "definitionVisibility": "family", + "namespace": { + "filenamePrefixes": [ + "semantic-sandtable-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-db", + "owner": "schemas", + "rationale": "Semantic DB engine manifest, report, route, and shared definition contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "definitionSchemaPath": "schemas/semantic-db-definitions.v1.schema.json", + "definitionVisibility": "family", + "namespace": { + "filenamePrefixes": [ + "semantic-db-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-fact", + "definitionSchemaPath": "schemas/semantic-fact-definitions.v1.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Semantic fact graph, ontology, frontier, and benchmark contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-fact-" + ] + } + }, + { + "familyId": "asp.schema-family.live-corpus", + "owner": "schemas", + "rationale": "Live corpus path, synchronization, materialization, artifact, and qualification contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "asp.live-corpus-" + ] + } + }, + { + "familyId": "asp.schema-family.content-identity", + "owner": "crates/agent-semantic-content-identity", + "rationale": "Content identity owns the canonical binding between source bytes and their content-addressed identity.", + "priority": 260, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "content-binding" + ] + } + }, + { + "familyId": "asp.schema-family.source-evidence", + "owner": "schemas", + "rationale": "Source index, snapshot, overlay, derived-source, and resource synchronization evidence contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "derived-source-", + "source-index-", + "source-overlay-", + "source-overlay.", + "source-snapshot-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp.resource-sync-receipt.v1.schema.json", + "schemas/parser-read-authority.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.semantic-artifact", + "owner": "schemas", + "rationale": "Semantic artifact identity, edge, and repair-chain contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-artifact-", + "active-asp-artifact-" + ] + }, + "definitionSchemaPath": "schemas/semantic-artifact-definitions.v1.schema.json", + "definitionVisibility": "public" + }, + { + "familyId": "asp.schema-family.semantic-search", + "owner": "schemas", + "rationale": "Search packet, projection, trace, and evidence contracts under the semantic-search namespace.", + "priority": 210, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "semantic-search-", + "searchloop-", + "agent-facing-search-", + "asp-search-", + "incremental-search-", + "interactive-search-", + "large-search-", + "memory-search-", + "cold-rg-", + "content-search-", + "lexical-", + "python-generation-graph-", + "rendered-search-", + "repository-candidate-", + "search-", + "fused-search-", + "evidence-query-", + "callable-skeleton-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp.exact-selector-execution-receipt.v1.schema.json", + "schemas/projection-evidence-context.v1.schema.json", + "schemas/lexical-postings-work-reduction-receipt.schema.json", + "schemas/rg-coverage-receipt.schema.json", + "schemas/query-playbook-materialization-request.v1.schema.json", + "schemas/query-playbook-materialization-receipt.v1.schema.json", + "schemas/asp-client-workspace-search-playbook-request.v1.schema.json", + "schemas/asp-client-workspace-syntax-query-request.v1.schema.json", + "schemas/asp-client-workspace-syntax-query-response.v1.schema.json", + "schemas/search-topology-settlement.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.language", + "owner": "schemas", + "rationale": "Language package graphs and registered/resolved source-scope contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "language-", + "registered-language-", + "resolved-source-", + "python-semantic-", + "typescript-semantic-" + ] + } + }, + { + "familyId": "asp.schema-family.context-product", + "owner": "schemas", + "rationale": "Context product state, route execution, proof reuse, and blocked decision contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "context-product-" + ] + } + }, + { + "familyId": "asp.schema-family.parser-compact", + "owner": "schemas", + "rationale": "Parser compact-case and token-cost contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "parser-compact-" + ] + } + }, + { + "familyId": "asp.schema-family.schema-manager", + "owner": "packages/python/asp_schema_manager", + "rationale": "ASP Schema Manager family-registry, lifecycle-manifest, and management-report contracts.", + "priority": 300, + "namespace": { + "filenamePrefixes": [ + "asp-schema-" + ] + } + }, + { + "familyId": "asp.schema-family.software-criterion", + "owner": "schemas", + "rationale": "Software criterion catalogs and extension report contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "software-criterion-" + ] + } + }, + { + "familyId": "asp.schema-family.semantic", + "owner": "schemas", + "rationale": "Broad semantic contract namespace pending narrower family promotion.", + "priority": 100, + "namespace": { + "filenamePrefixes": [ + "semantic-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/relationship-contract.v1.schema.json" + ] + }, + "definitionSchemaPath": "schemas/semantic-definitions.v1.schema.json", + "definitionVisibility": "descendants" + }, + { + "familyId": "asp.schema-family.rust-project-harness", + "owner": "languages/asp-rust", + "rationale": "Rust project harness build gates and dependency policy contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.provider", + "namespace": { + "filenamePrefixes": [ + "rust-project-harness-", + "rust-ast-patch-" + ] + }, + "definitionSchemaPath": "schemas/rust-project-harness-definitions.v1.schema.json", + "definitionVisibility": "family" + }, + { + "familyId": "asp.schema-family.provider", + "definitionSchemaPath": "schemas/provider-definitions.v1.schema.json", + "definitionVisibility": "public", + "owner": "schemas", + "rationale": "Provider registration, dispatch, resolution, and projection contracts.", + "priority": 100, + "namespace": { + "filenamePrefixes": [ + "provider-", + "gerbil-scheme-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp.activated-ranker.v1.schema.json", + "schemas/asp-gerbil-scheme-bench.v1.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.client-storage", + "owner": "schemas", + "rationale": "Client storage, Turso synchronization, migration, MVCC, and storage receipt contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "client-storage-", + "client-db-", + "storage-", + "turso-" + ] + }, + "definitionSchemaPath": "schemas/client-storage-definitions.v1.schema.json", + "definitionVisibility": "family" + }, + { + "familyId": "asp.schema-family.workspace", + "owner": "schemas", + "rationale": "Workspace authority, generation evidence, project resolution, and workspace identity contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "workspace-", + "project-resolution." + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/asp.active-workspace-generation.v1.schema.json", + "schemas/active-generation-projection-capability.v1.schema.json", + "schemas/active-search-generation.schema.json", + "schemas/state-home-project-binding.schema.json" + ] + }, + "definitionSchemaPath": "schemas/workspace-definitions.v1.schema.json", + "definitionVisibility": "family" + }, + { + "familyId": "asp.schema-family.org-babel-runtime-validation", + "owner": "schemas", + "rationale": "Org Babel runtime validation receipts and execution-environment acceptance evidence.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "org-babel-runtime-validation-" + ] + } + }, + { + "familyId": "asp.schema-family.resident-runtime", + "owner": "schemas", + "rationale": "Global resident control/data and resident workspace lifecycle/performance contracts, distinct from runtime-server authority.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "global-resident-", + "resident-" + ] + } + }, + { + "familyId": "asp.schema-family.runtime-server", + "definitionSchemaPath": "schemas/runtime-server-definitions.v1.schema.json", + "definitionVisibility": "family", + "owner": "schemas", + "rationale": "Runtime Server authority, lifecycle, performance, and workspace contracts.", + "priority": 200, + "parentFamilyId": "asp.schema-family.runtime", + "namespace": { + "filenamePrefixes": [ + "runtime-server-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/grpc-warm-latency-receipt.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.runtime", + "owner": "schemas", + "rationale": "Runtime contracts outside the narrower Runtime Server namespace.", + "priority": 100, + "namespace": { + "filenamePrefixes": [ + "runtime-" + ] + }, + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/state-home-cleanup-receipt.schema.json", + "schemas/state-home-retention-plan.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.exact", + "owner": "schemas", + "rationale": "Exact query, selector, source, projection, and materialization contracts.", + "priority": 100, + "namespace": { + "filenamePrefixes": [ + "exact-", + "canonical-" + ] + }, + "definitionSchemaPath": "schemas/exact-definitions.v1.schema.json", + "definitionVisibility": "family", + "membershipOverrides": { + "includeSchemaPaths": [ + "schemas/callable-skeleton.schema.json" + ] + } + }, + { + "familyId": "asp.schema-family.asp-client", + "owner": "crates/agent-semantic-client", + "rationale": "Public ASP client protocol frames, requests, lifecycle, conformance, and CLI failure contracts.", + "priority": 300, + "parentFamilyId": "asp.schema-family.semantic", + "namespace": { + "filenamePrefixes": [ + "asp-client-", + "asp-cli-" + ] + } + } + ], + "referenceDecisions": [ + { + "fingerprint": "sha256:d2a44cebe27ba00bd88b6c4ff33fc15998bdb0d89bd77e0ebaff6d2dcaf019e5", + "occurrenceSetDigest": "sha256:8c9d04d708fa6b25eafdc06649771357223a9d39ddf20b10140f8820d611849e", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-assurance", + "asp.schema-family.semantic-ast-patch", + "asp.schema-family.software-criterion" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Keep the generic fields shape local until the semantic parent contract defines public ownership." + }, + { + "fingerprint": "sha256:7245937b80aac15d7d70b80e82eaa5fe6c5c4136d7d641250b268b09aa37f96a", + "occurrenceSetDigest": "sha256:08a3ccf3bc65fa35febb4210c9acde40feb8dcd19f0fc5e437dea04a620170b5", + "familyIds": [ + "asp.schema-family.rust-project-harness", + "asp.schema-family.semantic", + "asp.schema-family.semantic-assurance", + "asp.schema-family.semantic-ast-patch", + "asp.schema-family.semantic-graph", + "asp.schema-family.semantic-policy", + "asp.schema-family.software-criterion" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Promote projectPath only after all external families can consume a public semantic definition." + }, + { + "fingerprint": "sha256:c22956b8502199745da256f13a5747fe11075368a61e8f7fff5950512adaed06", + "occurrenceSetDigest": "sha256:5f831420dec56dc99250c5c693789d877e6f97fa7b5ab89a130927bd313d2200", + "familyIds": [ + "asp.schema-family.rust-project-harness", + "asp.schema-family.semantic", + "asp.schema-family.semantic-ast-patch", + "asp.schema-family.semantic-search" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Source locator promotion needs a public semantic definition and consumer migration plan." + }, + { + "fingerprint": "sha256:1f364075c230f171d7602ad4b547e1fdbd3347b1d9c915b82e8bc5211851c603", + "occurrenceSetDigest": "sha256:70084139dd6bfbb404a72faabcadfc8e053eac1295e6dd19e6d6a4c74afbbeb1", + "familyIds": [ + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-sandtable" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-agent", + "rationale": "Sandtable should reference the agent answer contract after semantic-agent definitions become public." + }, + { + "fingerprint": "sha256:61e6652dd7efec7949d7c84082718b3d029f68a15fdd908fee97c1972c865a84", + "occurrenceSetDigest": "sha256:1a0f15b0cc7d5d6ae200da598de8109075a62a7a42ab04918d589d52abf6ef9e", + "familyIds": [ + "asp.schema-family.semantic-fact", + "asp.schema-family.semantic-graph-turbo" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-fact", + "rationale": "Context metrics need an explicit semantic-fact public definition before graph-turbo migration." + }, + { + "fingerprint": "sha256:4298e3a6c7efa53e6107da8778159f6f334067d031a28db5813d71372b733c44", + "occurrenceSetDigest": "sha256:01d92f91de88a9ba8971dae708abf6e3848c5edf21016fc1897c86094fa71a9d", + "familyIds": [ + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-sandtable" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-agent", + "rationale": "Quality finding lists will move with the semantic-agent quality finding definition." + }, + { + "fingerprint": "sha256:071137bccfb1096132378b47e221f2c3144dcbd8a74c29b753a0f834bc3248a7", + "occurrenceSetDigest": "sha256:a88de522b09996eb0b6d7499234e71788995692e302dea0c1265ab99354ae958", + "familyIds": [ + "asp.schema-family.semantic-graph", + "asp.schema-family.software-criterion" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Graph owner frontiers and software criterion fixture owners share syntax but not semantics." + }, + { + "fingerprint": "sha256:5e72be959793d55b4bcf45a22fb8a652efaf763afb88ee90976c3edb5e586cfc", + "occurrenceSetDigest": "sha256:b08c6a0d1e77eadebb3e48882cae72f3f3cb1065606e66966b0ac3da36a11620", + "familyIds": [ + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-sandtable" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-agent", + "rationale": "Sandtable quality findings should consume a public semantic-agent definition." + }, + { + "fingerprint": "sha256:4df374a1de0f11d980a365f7cf475a1ee4bc5f183cbfe13545a3b6fa0931c2d9", + "occurrenceSetDigest": "sha256:e6245dc970122ab3e42e102f24380a10dcdd323e2ff88c30a9a0f6741ed60350", + "familyIds": [ + "asp.schema-family.semantic-policy", + "asp.schema-family.semantic-proof" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Policy fields and proof fields are independently owned open extension maps." + }, + { + "fingerprint": "sha256:bc994518650f8e6c90a3a7d5f98ccee5f8fc25dd0e1c1985d086ee65a2485ff9", + "occurrenceSetDigest": "sha256:73994c6fc3f4ca4a3fee4e90512e5644a45b83c2c4e0745b3146ed543b544bfc", + "familyIds": [ + "asp.schema-family.semantic-fact", + "asp.schema-family.semantic-graph-turbo" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-fact", + "rationale": "Range ownership belongs in semantic-fact after public visibility is explicit." + }, + { + "fingerprint": "sha256:8cc19f1f4f9cfd31bbcd575289d8fecbc0256f323c263d40e56422ab8ea2aeff", + "occurrenceSetDigest": "sha256:655bf74036c2581321f4c8c9f2df41aea706c8d149a5ca1ad4095b0eaa493579", + "familyIds": [ + "asp.schema-family.semantic-policy", + "asp.schema-family.semantic-proof" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Policy and proof scalar slots have distinct domain meaning despite equal JSON shape." + }, + { + "fingerprint": "sha256:59205d978b02f80380cda4e83960a29fa839edff1940d279efeac7a885438853", + "occurrenceSetDigest": "sha256:6c8df2abb67e9e36000d0916d6819e10239e175b3c25ab00af57f54ca96142f5", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-assurance" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Semantic and assurance scalar slots are domain-local value envelopes." + }, + { + "fingerprint": "sha256:981fdc978f075173c7cbab937d334511db859e72f8758ab3310cf787e3802b0a", + "occurrenceSetDigest": "sha256:987af08244da4bcebad04b149bbe18885748598494852e0ec6a82124058d7907", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-ast-patch" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Location should be promoted only with a public semantic definition and complete consumer migration." + }, + { + "fingerprint": "sha256:9fafb8a5c930d882c0221d8aade7855fba09ba30d884891607dfbf2179c2ab2f", + "occurrenceSetDigest": "sha256:3ce62c507b90752b0ce20b4842bbd52fcf90e15ed28f35f1e0dcdd36af123083", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-assurance" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Assurance fields will reference semantic fields after public ownership is established." + }, + { + "fingerprint": "sha256:f44b93d3e1a66d00bef14c09cc2f27d0efb8e552b12da07d92cd11994e8c3eb2", + "occurrenceSetDigest": "sha256:4efd316ddd677c115f41e7fa5efc5affd219cc86d27616998cd9957f3803ac37", + "familyIds": [ + "asp.schema-family.semantic-graph-turbo", + "asp.schema-family.semantic-search" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-search", + "rationale": "Delegation receipts need a shared search definition before graph-turbo references it." + }, + { + "fingerprint": "sha256:1733d812bb1b355644fb1907ba739cbfa6446038604ae4fe16614eb3421abbfd", + "occurrenceSetDigest": "sha256:e6e8f94e9650c529c0f21d3af2715c4bd2645525da6aece9bf1879f7282411e8", + "familyIds": [ + "asp.schema-family.semantic-agent", + "asp.schema-family.semantic-sandtable" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-agent", + "rationale": "Sandtable project data should reference a public semantic-agent project definition." + }, + { + "fingerprint": "sha256:a54a35ec3bf1a54c70e7f23f45a2ed23f8afe94228cf6124c6aa228c058db493", + "occurrenceSetDigest": "sha256:8e0f145390a761d262f25b117068e24ba335ebc95dc127f876753c1b76159b03", + "familyIds": [ + "asp.schema-family.language", + "asp.schema-family.provider", + "asp.schema-family.semantic-search" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Provider owner paths, language target roots, and search proof sources are distinct domain responsibilities despite sharing a non-empty path-list shape." + }, + { + "fingerprint": "sha256:811c570f79fff148fd4ecb5fdf999fc87c87c21908f3bdd3d4ddc99dda1e28dc", + "occurrenceSetDigest": "sha256:4a902b643e3cf9760b5906b00ee5f37d868ea92b2293adf415d16b6d52c6534e", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-assurance" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Invariant kinds need a semantic parent definition before assurance migration." + }, + { + "fingerprint": "sha256:82eea660dabf702a5523ee26a394fbe06202e1e955804410a76c0aa13dc42209", + "occurrenceSetDigest": "sha256:da08d9539f3d9be53710e42a95df93257651167b97d1903b16d0037d7b6d0130", + "familyIds": [ + "asp.schema-family.semantic-fact", + "asp.schema-family.semantic-graph-turbo" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-fact", + "rationale": "Source ranges should use one public semantic-fact range definition." + }, + { + "fingerprint": "sha256:0c936f70dafce1c7cc33abb734c0b0bfa6b43fb3410937c5f193aa13129cbe79", + "occurrenceSetDigest": "sha256:99b0ec69eb815786a980df13fd0a53965eeb30dbf3a7092654b49061ebf11bb4", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-ast-patch" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic", + "rationale": "Changed and test paths need a public semantic path-list definition." + }, + { + "fingerprint": "sha256:62f71a66c2be93e2dd7208a0c5ecfb4783ee86dcd753fa7b8ef10039f85a870f", + "occurrenceSetDigest": "sha256:53d80724abee8c7e08219932e4136d896b5927abeb766246e105d67d3f629bfc", + "familyIds": [ + "asp.schema-family.semantic-fact", + "asp.schema-family.semantic-graph" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-graph", + "rationale": "Graph node fields require an explicitly public semantic graph fact map." + }, + { + "fingerprint": "sha256:c59ce57a6a1272108ca7a166b9c63c8061b42cd5eefa848b151e461285ef6d51", + "occurrenceSetDigest": "sha256:94e74453dd616ac6978bc77c6f3345cfc5560287eeb13cc578b56059c6469b64", + "familyIds": [ + "asp.schema-family.provider", + "asp.schema-family.semantic" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Provider process failures and semantic failures have different lifecycle semantics." + }, + { + "fingerprint": "sha256:11465fde644bba159bfc73175a389bab91c90a46416296274ce387cdfccd9477", + "occurrenceSetDigest": "sha256:2adb2f0211ee7cea88ad11e82a20557545c4c9b9fd8d5332c65c19f7d6ee974c", + "familyIds": [ + "asp.schema-family.semantic-graph", + "asp.schema-family.semantic-search" + ], + "decision": "defer", + "reviewState": "accepted", + "owner": "asp.schema-family.semantic-graph", + "rationale": "Search graph node kinds need a public semantic-graph definition." + }, + { + "fingerprint": "sha256:e5c6d1791bd4981e88a37880d1b8d111f966294cd130fe5e6b541dd2344239f2", + "occurrenceSetDigest": "sha256:5e4c67dd434f94b5f5f37c3536120aabea0626cede00cc2c906f48bf95090b88", + "familyIds": [ + "asp.schema-family.semantic", + "asp.schema-family.semantic-agent" + ], + "decision": "intentional-duplication", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Semantic failure envelopes and agent document notes are independently owned concepts." + }, + { + "fingerprint": "sha256:cdc2dcd81e732654fa694ca6c35927e035f59839a13122496ad8fd2c53646119", + "occurrenceSetDigest": "sha256:4ab6c39ad4c67fb9b0c3de112215ab8a0deb9ee1cc8035d7a3db6d9e7ed9d045", + "familyIds": [ + "asp.schema-family.asp-client", + "asp.schema-family.provider" + ], + "decision": "extract-family-definition", + "reviewState": "accepted", + "owner": "asp.schema-family.schema-manager", + "rationale": "Structured SchemaReference is a shared manager-owned declaration used by client and provider contracts." + } + ] +} diff --git a/schemas/large-search-playbook-performance-receipt.v1.schema.json b/schemas/large-search-playbook-performance-receipt.v1.schema.json new file mode 100644 index 0000000..015c1b7 --- /dev/null +++ b/schemas/large-search-playbook-performance-receipt.v1.schema.json @@ -0,0 +1,202 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/large-search-playbook-performance-receipt.v1.schema.json", + "title": "Large Search Playbook performance receipt V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "owners", "contentGeneration", "coldRgQuery", + "bootstrapAccelerator", "deltaAccelerator", "firstTantivyOpen", "warm", "concurrent", + "fusedCache", "pythonGraph", "requestTimeExternalWork" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.large-search-playbook-performance-receipt"}, + "schemaVersion": {"const": "1"}, + "owners": {"type": "integer", "minimum": 4096}, + "contentGeneration": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "#/$defs/distribution"}, + { + "type": "object", + "required": ["stageP95Nanos"], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "stageP95Nanos": { + "type": "object", + "additionalProperties": false, + "required": [ + "fdInventory", "repositoryAdmission", "sourceByteAcquisition", + "nativeSyntax", "rustGraph", "publication" + ], + "properties": { + "fdInventory": {"$ref": "#/$defs/nanos"}, + "repositoryAdmission": {"$ref": "#/$defs/nanos"}, + "sourceByteAcquisition": {"$ref": "#/$defs/nanos"}, + "nativeSyntax": {"$ref": "#/$defs/nanos"}, + "rustGraph": {"$ref": "#/$defs/nanos"}, + "publication": {"$ref": "#/$defs/nanos"} + } + } + } + } + ] + }, + "coldRgQuery": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "#/$defs/distribution"}, + { + "type": "object", + "required": [ + "fdProcessCount", "rgProcessCount", "tantivyBuildCount", + "contentVerificationP95Nanos", "nativeSyntaxVerificationP95Nanos" + ], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "fdProcessCount": {"const": 0}, + "rgProcessCount": {"type": "integer", "minimum": 32}, + "tantivyBuildCount": {"const": 0}, + "contentVerificationP95Nanos": {"$ref": "#/$defs/nanos"}, + "nativeSyntaxVerificationP95Nanos": {"$ref": "#/$defs/nanos"} + } + } + ] + }, + "bootstrapAccelerator": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "#/$defs/distribution"}, + { + "type": "object", + "required": [ + "samples", "reusedShardCount", "rebuiltShardCount", + "shardPlanP95Nanos", "tantivyCommitP95Nanos", + "equivalenceValidationP95Nanos", "pointerPublicationP95Nanos" + ], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "reusedShardCount": {"const": 0}, + "rebuiltShardCount": {"type": "integer", "minimum": 4096}, + "shardPlanP95Nanos": {"$ref": "#/$defs/nanos"}, + "tantivyCommitP95Nanos": {"$ref": "#/$defs/nanos"}, + "equivalenceValidationP95Nanos": {"$ref": "#/$defs/nanos"}, + "pointerPublicationP95Nanos": {"$ref": "#/$defs/nanos"} + } + } + ] + }, + "deltaAccelerator": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "#/$defs/distribution"}, + { + "type": "object", + "required": [ + "samples", "changedOwnerCount", "reusedShardCount", + "rebuiltShardCount", "retiredShardCount", "unchangedOwnerRescanCount" + ], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "changedOwnerCount": {"type": "integer", "minimum": 1}, + "reusedShardCount": {"type": "integer", "minimum": 1}, + "rebuiltShardCount": {"type": "integer", "minimum": 1}, + "retiredShardCount": {"type": "integer", "minimum": 0}, + "unchangedOwnerRescanCount": {"const": 0} + } + } + ] + }, + "firstTantivyOpen": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "#/$defs/distribution"}, + { + "type": "object", + "required": ["filesystemReadCount", "fdProcessCount", "rgProcessCount", "tantivyBuildCount"], + "properties": { + "samples": {"type": "integer", "minimum": 32}, + "filesystemReadCount": {"type": "integer", "minimum": 32}, + "fdProcessCount": {"const": 0}, + "rgProcessCount": {"const": 0}, + "tantivyBuildCount": {"const": 0} + } + } + ] + }, + "warm": { + "unevaluatedProperties": false, + "allOf": [ + {"$ref": "#/$defs/distribution"}, + {"properties": {"samples": {"type": "integer", "minimum": 512}}} + ] + }, + "concurrent": { + "type": "object", + "additionalProperties": false, + "required": ["queries", "p99Nanos", "maxNanos", "wallNanos"], + "properties": { + "queries": {"type": "integer", "minimum": 32}, + "p99Nanos": {"$ref": "#/$defs/nanos"}, + "maxNanos": {"$ref": "#/$defs/nanos"}, + "wallNanos": {"$ref": "#/$defs/nanos"} + } + }, + "fusedCache": { + "type": "object", + "additionalProperties": false, + "required": [ + "hitCount", "missCount", "entryCount", "valueBytes", "capacity", + "shardCount", "externalWorkCount" + ], + "properties": { + "hitCount": {"const": 1}, + "missCount": {"const": 0}, + "entryCount": {"type": "integer", "minimum": 1}, + "valueBytes": {"type": "integer", "minimum": 1}, + "capacity": {"type": "integer", "minimum": 1}, + "shardCount": {"type": "integer", "minimum": 1}, + "externalWorkCount": {"const": 0} + } + }, + "pythonGraph": { + "type": "object", + "additionalProperties": false, + "required": ["state", "generationDigest", "receiptValidationNanos"], + "properties": { + "state": {"const": "executed"}, + "generationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "receiptValidationNanos": {"$ref": "#/$defs/nanos"} + } + }, + "requestTimeExternalWork": { + "type": "object", + "additionalProperties": false, + "required": [ + "databaseReadCount", "filesystemReadCount", "providerProcessCount", + "socketOperationCount", "schedulerTaskCount" + ], + "properties": { + "databaseReadCount": {"const": 0}, + "filesystemReadCount": {"const": 0}, + "providerProcessCount": {"const": 0}, + "socketOperationCount": {"const": 0}, + "schedulerTaskCount": {"const": 0} + } + } + }, + "$defs": { + "nanos": {"type": "integer", "minimum": 0}, + "distribution": { + "type": "object", + "required": ["samples", "p50Nanos", "p95Nanos", "p99Nanos", "maxNanos"], + "properties": { + "samples": {"type": "integer", "minimum": 1}, + "p50Nanos": {"$ref": "#/$defs/nanos"}, + "p95Nanos": {"$ref": "#/$defs/nanos"}, + "p99Nanos": {"$ref": "#/$defs/nanos"}, + "maxNanos": {"$ref": "#/$defs/nanos"} + } + } + } +} diff --git a/schemas/owner-search-payload-reduction-receipt.schema.json b/schemas/owner-search-payload-reduction-receipt.schema.json deleted file mode 100644 index e441665..0000000 --- a/schemas/owner-search-payload-reduction-receipt.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.dev/schemas/owner-search-payload-reduction-receipt.schema.json", - "title": "ASP Owner Search Payload Reduction Receipt", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "fullBytes", - "compactBytes", - "reductionFactor" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.owner-search-payload-reduction-receipt" - }, - "schemaVersion": { "const": "1" }, - "fullBytes": { "type": "integer", "minimum": 1 }, - "compactBytes": { "type": "integer", "minimum": 1 }, - "reductionFactor": { "type": "integer", "minimum": 100 } - } -} diff --git a/schemas/project-topology-library.v1.schema.json b/schemas/project-topology-library.v1.schema.json new file mode 100644 index 0000000..7eb96b4 --- /dev/null +++ b/schemas/project-topology-library.v1.schema.json @@ -0,0 +1,211 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/project-topology-library.v1.schema.json", + "title": "Reusable Project Topology Library V1", + "description": "Content-addressed structural and semantic project topology shared by Search, Query, code understanding, framework calibration, refactoring, and context recovery. Retrieval ranges and request-local ranks are deliberately excluded.", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "projectWorkspace", "sourceGenerationDigest", "providerCatalogDigest", "libraryDigest", "generation", "fromScratchRebuildReceipt", "semanticAdmissionReceipts", "identities", "segments", "nodes", "edges", "coverageCertificates", "closure", "consumers", "terminal"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.project-topology-library"}, + "schemaVersion": {"const": "1"}, + "projectWorkspace": {"$ref": "project-workspace-binding.v1.schema.json"}, + "sourceGenerationDigest": {"$ref": "#/$defs/digest"}, + "providerCatalogDigest": {"$ref": "#/$defs/digest"}, + "libraryDigest": {"$ref": "#/$defs/digest"}, + "generation": {"$ref": "#/$defs/generation"}, + "fromScratchRebuildReceipt": {"$ref": "#/$defs/fromScratchRebuildReceipt"}, + "semanticAdmissionReceipts": { + "type": "array", + "items": {"$ref": "#/$defs/semanticAdmissionReceipt"} + }, + "identities": {"$ref": "#/$defs/identities"}, + "segments": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/segment"}}, + "nodes": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/node"}}, + "edges": {"type": "array", "items": {"$ref": "#/$defs/edge"}}, + "coverageCertificates": {"type": "array", "items": {"$ref": "#/$defs/coverage"}}, + "closure": {"$ref": "#/$defs/closure"}, + "consumers": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"enum": ["search", "query", "code-understanding", "framework-calibration", "refactoring", "context-recovery"]} + }, + "terminal": {"$ref": "#/$defs/terminal"} + }, + "$defs": { + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$(?![\\s\\S])"}, + "identifier": {"type": "string", "pattern": "^[a-z][a-z0-9_.:-]*$(?![\\s\\S])"}, + "nodeId": {"type": "string", "pattern": "^[a-z][a-z0-9_-]*$(?![\\s\\S])"}, + "selector": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])"}, + "ownerLocator": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s#]+$(?![\\s\\S])"}, + "generation": { + "type": "object", + "additionalProperties": false, + "required": ["generationDigest", "parentGenerationDigest", "state", "changeSetDigest", "rebuiltSegmentIds", "removedNodeIds", "removedEdgeIds", "fromScratchEquivalentDigest"], + "properties": { + "generationDigest": {"$ref": "#/$defs/digest"}, + "parentGenerationDigest": {"oneOf": [{"$ref": "#/$defs/digest"}, {"type": "null"}]}, + "state": {"const": "complete"}, + "changeSetDigest": {"$ref": "#/$defs/digest"}, + "rebuiltSegmentIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, + "removedNodeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/nodeId"}}, + "removedEdgeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, + "fromScratchEquivalentDigest": {"$ref": "#/$defs/digest"} + } + }, + "fromScratchRebuildReceipt": { + "type": "object", + "additionalProperties": false, + "required": ["id", "sourceGenerationDigest", "inferenceProgramDigest", "topologyGenerationDigest", "recomputedLibraryDigest", "authority", "state"], + "properties": { + "id": {"$ref": "#/$defs/identifier"}, + "sourceGenerationDigest": {"$ref": "#/$defs/digest"}, + "inferenceProgramDigest": {"$ref": "#/$defs/digest"}, + "topologyGenerationDigest": {"$ref": "#/$defs/digest"}, + "recomputedLibraryDigest": {"$ref": "#/$defs/digest"}, + "authority": {"const": "project-topology-rebuild.v1"}, + "state": {"const": "admitted"} + } + }, + "semanticAdmissionReceipt": { + "type": "object", + "additionalProperties": false, + "required": ["id", "annotationNodeId", "semanticTopologyDigest", "authority", "state"], + "properties": { + "id": {"$ref": "#/$defs/identifier"}, + "annotationNodeId": {"$ref": "#/$defs/nodeId"}, + "semanticTopologyDigest": {"$ref": "#/$defs/digest"}, + "authority": {"enum": ["source-contract", "human-admission"]}, + "state": {"const": "admitted"} + } + }, + "identities": { + "type": "object", + "additionalProperties": false, + "required": ["structuralTopologyDigest", "semanticTopologyDigest", "inferenceProgramDigest", "providerGrammarDigest", "resolverDigest", "topologySchemaDigest"], + "properties": { + "structuralTopologyDigest": {"$ref": "#/$defs/digest"}, + "semanticTopologyDigest": {"$ref": "#/$defs/digest"}, + "inferenceProgramDigest": {"$ref": "#/$defs/digest"}, + "providerGrammarDigest": {"$ref": "#/$defs/digest"}, + "resolverDigest": {"$ref": "#/$defs/digest"}, + "topologySchemaDigest": {"$ref": "#/$defs/digest"} + } + }, + "segment": { + "type": "object", + "additionalProperties": false, + "required": ["id", "ownerPath", "contentDigest", "skeletonDigest", "locatorDigest", "sccDigest", "stratum", "nodeIds", "edgeIds"], + "properties": { + "id": {"$ref": "#/$defs/identifier"}, + "ownerPath": {"type": "string", "pattern": "^(?!/)(?!.*\\\\)[^\\s]+$(?![\\s\\S])"}, + "contentDigest": {"$ref": "#/$defs/digest"}, + "skeletonDigest": {"$ref": "#/$defs/digest"}, + "locatorDigest": {"$ref": "#/$defs/digest"}, + "sccDigest": {"$ref": "#/$defs/digest"}, + "stratum": {"type": "integer", "minimum": 0}, + "nodeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/nodeId"}}, + "edgeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}} + } + }, + "annotation": { + "type": "object", + "additionalProperties": false, + "required": ["text", "state", "premiseWitnesses", "producer", "bindingDigest"], + "properties": { + "text": {"type": "string", "minLength": 1, "maxLength": 600}, + "state": {"enum": ["proposed", "contested", "accepted"]}, + "premiseWitnesses": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, + "producer": {"type": "string", "minLength": 1}, + "bindingDigest": {"$ref": "#/$defs/digest"}, + "admissionReceiptRef": {"type": "string", "minLength": 1} + }, + "allOf": [{"if": {"properties": {"state": {"const": "accepted"}}, "required": ["state"]}, "then": {"required": ["admissionReceiptRef"]}, "else": {"not": {"required": ["admissionReceiptRef"]}}}] + }, + "node": { + "type": "object", + "additionalProperties": false, + "required": ["id", "segmentId", "plane", "language", "kind"], + "properties": { + "id": {"$ref": "#/$defs/nodeId"}, + "segmentId": {"oneOf": [{"$ref": "#/$defs/identifier"}, {"type": "null"}]}, + "plane": {"enum": ["structural", "declared-semantic", "synthesized-semantic"]}, + "language": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "name": {"type": "string", "minLength": 1}, + "selector": {"$ref": "#/$defs/selector"}, + "ownerLocator": {"$ref": "#/$defs/ownerLocator"}, + "annotation": {"$ref": "#/$defs/annotation"} + }, + "oneOf": [ + {"required": ["selector"], "not": {"anyOf": [{"required": ["ownerLocator"]}, {"required": ["annotation"]}]}}, + {"required": ["ownerLocator"], "not": {"anyOf": [{"required": ["selector"]}, {"required": ["annotation"]}]}}, + {"required": ["annotation"], "not": {"anyOf": [{"required": ["selector"]}, {"required": ["ownerLocator"]}]}} + ], + "allOf": [ + {"if": {"properties": {"plane": {"const": "synthesized-semantic"}}, "required": ["plane"]}, "then": {"properties": {"segmentId": {"type": "null"}}}, "else": {"properties": {"segmentId": {"$ref": "#/$defs/identifier"}}}} + ] + }, + "edge": { + "type": "object", + "additionalProperties": false, + "required": ["id", "segmentId", "from", "to", "relation", "modality", "bindingDigest", "witnesses"], + "properties": { + "id": {"$ref": "#/$defs/identifier"}, + "segmentId": {"oneOf": [{"$ref": "#/$defs/identifier"}, {"type": "null"}]}, + "from": {"$ref": "#/$defs/nodeId"}, + "to": {"$ref": "#/$defs/nodeId"}, + "relation": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])"}, + "modality": {"enum": ["parser-direct", "declared", "derived", "proposed"]}, + "bindingDigest": {"$ref": "#/$defs/digest"}, + "witnesses": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, + "proofRef": {"type": "string", "minLength": 1} + }, + "allOf": [ + {"if": {"properties": {"modality": {"enum": ["derived", "proposed"]}}, "required": ["modality"]}, "then": {"properties": {"segmentId": {"type": "null"}}}, "else": {"properties": {"segmentId": {"$ref": "#/$defs/identifier"}}}}, + {"if": {"properties": {"modality": {"const": "derived"}}, "required": ["modality"]}, "then": {"required": ["proofRef"]}, "else": {"not": {"required": ["proofRef"]}}} + ] + }, + "coverage": { + "type": "object", + "additionalProperties": false, + "required": ["id", "relation", "targetKind", "scope", "digest"], + "properties": { + "id": {"$ref": "#/$defs/identifier"}, + "relation": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])"}, + "targetKind": {"type": "string", "minLength": 1}, + "scope": {"enum": ["complete", "partial"]}, + "digest": {"$ref": "#/$defs/digest"} + } + }, + "closure": { + "type": "object", + "additionalProperties": false, + "required": ["state", "digest", "proofDagDigest", "derivedEdgeIds", "proofDependencies"], + "properties": { + "state": {"const": "stable"}, + "digest": {"$ref": "#/$defs/digest"}, + "proofDagDigest": {"$ref": "#/$defs/digest"}, + "derivedEdgeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, + "proofDependencies": {"type": "array", "items": {"$ref": "#/$defs/proofDependency"}} + } + }, + "proofDependency": { + "type": "object", + "additionalProperties": false, + "required": ["derivedEdgeId", "premiseEdgeIds"], + "properties": { + "derivedEdgeId": {"$ref": "#/$defs/identifier"}, + "premiseEdgeIds": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}} + } + }, + "terminal": { + "type": "object", + "additionalProperties": false, + "required": ["state", "terminalCount"], + "properties": {"state": {"enum": ["ready", "failed"]}, "terminalCount": {"const": 1}, "reasonKind": {"type": "string", "minLength": 1}}, + "allOf": [{"if": {"properties": {"state": {"const": "failed"}}, "required": ["state"]}, "then": {"required": ["reasonKind"]}, "else": {"not": {"required": ["reasonKind"]}}}] + } + } +} diff --git a/schemas/project-workspace-binding.v1.schema.json b/schemas/project-workspace-binding.v1.schema.json new file mode 100644 index 0000000..c99e0c3 --- /dev/null +++ b/schemas/project-workspace-binding.v1.schema.json @@ -0,0 +1,71 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/project-workspace-binding.v1.schema.json", + "title": "Project Workspace Binding V1", + "description": "A GitOps-owned logical workspace identity and repository-relative containment boundary. Source snapshots and host-local worktree instances are deliberately excluded.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "projectWorkspaceIdentity", + "workspaceRootPath", + "portability", + "repositoryAliases" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.project-workspace-binding"}, + "schemaVersion": {"const": "1"}, + "projectWorkspaceIdentity": {"$ref": "#/$defs/projectWorkspaceIdentity"}, + "workspaceRootPath": {"$ref": "#/$defs/workspaceRootPath"}, + "portability": {"enum": ["cross-machine", "local-only"]}, + "repositoryAliases": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "#/$defs/repositoryLocator"} + } + }, + "oneOf": [ + { + "properties": { + "portability": {"const": "cross-machine"}, + "projectWorkspaceIdentity": {"$ref": "#/$defs/crossMachineIdentity"} + } + }, + { + "properties": { + "portability": {"const": "local-only"}, + "projectWorkspaceIdentity": {"$ref": "#/$defs/localIdentity"}, + "repositoryAliases": {"maxItems": 0} + } + } + ], + "$defs": { + "projectWorkspaceIdentity": { + "oneOf": [ + {"$ref": "#/$defs/crossMachineIdentity"}, + {"$ref": "#/$defs/localIdentity"} + ] + }, + "workspaceKey": { + "type": "string", + "pattern": "^[a-z][a-z0-9._-]*(?:/[a-z][a-z0-9._-]*)*$(?![\\s\\S])" + }, + "workspaceRootPath": { + "type": "string", + "pattern": "^(?:\\.|(?!/)(?!.*(?:^|/)\\.\\.(?:/|$))(?!.*//)[^\\s#]+)$(?![\\s\\S])" + }, + "repositoryLocator": { + "type": "string", + "pattern": "^git\\+(?:https|ssh)://[^\\s#]+\\.git$(?![\\s\\S])" + }, + "crossMachineIdentity": { + "type": "string", + "pattern": "^git\\+(?:https|ssh)://[^\\s#]+\\.git#workspace/[a-z][a-z0-9._-]*(?:/[a-z][a-z0-9._-]*)*$(?![\\s\\S])" + }, + "localIdentity": { + "type": "string", + "pattern": "^git\\+file:///[^\\s#]+#workspace/[a-z][a-z0-9._-]*(?:/[a-z][a-z0-9._-]*)*$(?![\\s\\S])" + } + } +} diff --git a/schemas/provider-language-projection-batch-response.schema.json b/schemas/provider-language-projection-batch-response.schema.json index ad61a71..5b2b859 100644 --- a/schemas/provider-language-projection-batch-response.schema.json +++ b/schemas/provider-language-projection-batch-response.schema.json @@ -45,6 +45,8 @@ "required": [ "ownerPath", "sourceLeafDigest", + "projectionState", + "diagnostic", "items", "relations" ], @@ -57,6 +59,15 @@ "type": "string", "minLength": 1 }, + "projectionState": { + "enum": ["ready", "syntax-unavailable"] + }, + "diagnostic": { + "oneOf": [ + { "type": "null" }, + { "$ref": "#/$defs/projectionDiagnostic" } + ] + }, "items": { "type": "array", "items": { @@ -69,6 +80,47 @@ "$ref": "#/$defs/relation" } } + }, + "allOf": [ + { + "if": { + "properties": { "projectionState": { "const": "ready" } } + }, + "then": { + "properties": { "diagnostic": { "type": "null" } } + } + }, + { + "if": { + "properties": { + "projectionState": { "const": "syntax-unavailable" } + } + }, + "then": { + "properties": { + "diagnostic": { "$ref": "#/$defs/projectionDiagnostic" }, + "items": { "maxItems": 0 }, + "relations": { "maxItems": 0 } + } + } + } + ] + }, + "projectionDiagnostic": { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "reasonKind", "message"], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.provider-language-projection-diagnostic" + }, + "schemaVersion": { "const": "1" }, + "reasonKind": { "const": "source-syntax-unavailable" }, + "message": { + "type": "string", + "minLength": 1, + "maxLength": 4096 + } } }, "projectedItem": { diff --git a/schemas/provider-manifest.schema.json b/schemas/provider-manifest.schema.json index 23571ac..0344933 100644 --- a/schemas/provider-manifest.schema.json +++ b/schemas/provider-manifest.schema.json @@ -124,18 +124,6 @@ "providerId": {"const": "asp-julia"} } }, - { - "properties": { - "languageId": {"const": "md"}, - "providerId": {"const": "asp-md"} - } - }, - { - "properties": { - "languageId": {"const": "org"}, - "providerId": {"const": "asp-org"} - } - }, { "properties": { "languageId": {"const": "python"}, @@ -408,40 +396,18 @@ }, "methodId": { "type": "string", - "pattern": "^(?:guide|query|(search|query|proof|review|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" + "pattern": "^(?:guide|query|(query|proof|review|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" }, diff --git a/schemas/python-generation-graph-performance-receipt.v1.schema.json b/schemas/python-generation-graph-performance-receipt.v1.schema.json new file mode 100644 index 0000000..6b695e7 --- /dev/null +++ b/schemas/python-generation-graph-performance-receipt.v1.schema.json @@ -0,0 +1,58 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/python-generation-graph-performance-receipt.v1.schema.json", + "title": "Python generation graph performance receipt V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", "schemaVersion", "owners", "cold", "warm", "concurrent", + "artifactDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.python-generation-graph-performance-receipt"}, + "schemaVersion": {"const": "1"}, + "owners": {"type": "integer", "minimum": 4096}, + "cold": { + "allOf": [ + {"$ref": "#/$defs/distribution"}, + {"properties": {"samples": {"type": "integer", "minimum": 8}}} + ], + "unevaluatedProperties": false + }, + "warm": { + "allOf": [ + {"$ref": "#/$defs/distribution"}, + {"properties": {"samples": {"type": "integer", "minimum": 128}}} + ], + "unevaluatedProperties": false + }, + "concurrent": { + "allOf": [ + {"$ref": "#/$defs/distribution"}, + { + "required": ["queries", "wallNanos"], + "properties": { + "queries": {"type": "integer", "minimum": 32}, + "wallNanos": {"$ref": "#/$defs/nanos"} + } + } + ], + "unevaluatedProperties": false + }, + "artifactDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} + }, + "$defs": { + "nanos": {"type": "integer", "minimum": 0}, + "distribution": { + "type": "object", + "required": ["samples", "p50Nanos", "p95Nanos", "p99Nanos", "maxNanos"], + "properties": { + "samples": {"type": "integer", "minimum": 1}, + "p50Nanos": {"$ref": "#/$defs/nanos"}, + "p95Nanos": {"$ref": "#/$defs/nanos"}, + "p99Nanos": {"$ref": "#/$defs/nanos"}, + "maxNanos": {"$ref": "#/$defs/nanos"} + } + } + } +} diff --git a/schemas/query-playbook-materialization-receipt.v1.schema.json b/schemas/query-playbook-materialization-receipt.v1.schema.json new file mode 100644 index 0000000..8bce483 --- /dev/null +++ b/schemas/query-playbook-materialization-receipt.v1.schema.json @@ -0,0 +1,132 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/query-playbook-materialization-receipt.v1.schema.json", + "title": "Query Playbook Materialization Receipt V1", + "description": "One all-or-nothing Runtime-bound terminal for a selector-native Query Playbook request. It preserves caller order and contains no Search relationship, recommendation, or explanation fields.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "requestId", + "projectWorkspaceIdentity", + "worktreeInstanceId", + "runtimeExecutionBinding", + "runtimeWorkspaceExecutionPublicationDigest", + "runtimeBundleDigest", + "projection", + "requestedSelectors", + "materializations", + "terminal" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.query-playbook-materialization-receipt"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.query-playbook"}, + "protocolVersion": {"const": "1"}, + "requestId": {"type": "string", "minLength": 1}, + "projectWorkspaceIdentity": { + "$ref": "project-workspace-binding.v1.schema.json#/$defs/projectWorkspaceIdentity" + }, + "worktreeInstanceId": {"type": "string", "minLength": 1}, + "runtimeExecutionBinding": { + "$ref": "runtime-execution-binding.v2.schema.json" + }, + "runtimeWorkspaceExecutionPublicationDigest": {"$ref": "#/$defs/contentDigest"}, + "runtimeBundleDigest": {"$ref": "#/$defs/contentDigest"}, + "projection": {"enum": ["source", "callable-skeleton"]}, + "requestedSelectors": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/selector"} + }, + "materializations": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "#/$defs/materialization"} + }, + "terminal": {"$ref": "#/$defs/terminal"} + }, + "oneOf": [ + { + "properties": { + "materializations": {"minItems": 1}, + "terminal": { + "properties": {"state": {"const": "ready"}}, + "required": ["state"] + } + } + }, + { + "properties": { + "materializations": {"maxItems": 0}, + "terminal": { + "properties": {"state": {"const": "failed"}}, + "required": ["state", "reasonKind"] + } + } + } + ], + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "selector": { + "type": "string", + "pattern": "^[a-z][a-z0-9+.-]*://[^\\s#]+#item/.+$(?![\\s\\S])" + }, + "materialization": { + "type": "object", + "additionalProperties": false, + "required": [ + "selector", + "languageId", + "providerId", + "ownerPath", + "projection", + "sourceContentDigest", + "bytes" + ], + "properties": { + "selector": {"$ref": "#/$defs/selector"}, + "languageId": {"type": "string", "minLength": 1}, + "providerId": {"type": "string", "minLength": 1}, + "ownerPath": { + "type": "string", + "minLength": 1, + "pattern": "^(?!/)(?!.*(?:^|/)\\.\\.(?:/|$)).+$" + }, + "projection": {"enum": ["source", "callable-skeleton"]}, + "sourceContentDigest": {"pattern": "^[0-9a-f]{64}$"}, + "bytes": { + "type": "array", + "minItems": 1, + "items": {"type": "integer", "minimum": 0, "maximum": 255} + } + } + }, + "terminal": { + "type": "object", + "additionalProperties": false, + "required": ["state", "terminalCount"], + "properties": { + "state": {"enum": ["ready", "failed"]}, + "terminalCount": {"const": 1}, + "reasonKind": {"type": "string", "minLength": 1} + }, + "allOf": [ + { + "if": { + "properties": {"state": {"const": "ready"}}, + "required": ["state"] + }, + "then": {"not": {"required": ["reasonKind"]}} + } + ] + } + } +} diff --git a/schemas/query-playbook-materialization-request.v1.schema.json b/schemas/query-playbook-materialization-request.v1.schema.json new file mode 100644 index 0000000..c56b998 --- /dev/null +++ b/schemas/query-playbook-materialization-request.v1.schema.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/query-playbook-materialization-request.v1.schema.json", + "title": "Query Playbook Materialization Request V1", + "description": "An internal Runtime-bound packet for selector-native Query Playbook materialization. It preserves caller order and is independent of Search topology or relationships.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "requestId", + "projectWorkspaceIdentity", + "worktreeInstanceId", + "runtimeExecutionBinding", + "runtimeWorkspaceExecutionPublicationDigest", + "runtimeBundleDigest", + "selectors", + "projection" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.query-playbook-materialization-request"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.query-playbook"}, + "protocolVersion": {"const": "1"}, + "requestId": {"type": "string", "minLength": 1}, + "projectWorkspaceIdentity": { + "$ref": "project-workspace-binding.v1.schema.json#/$defs/projectWorkspaceIdentity" + }, + "worktreeInstanceId": {"type": "string", "minLength": 1}, + "runtimeExecutionBinding": { + "$ref": "runtime-execution-binding.v2.schema.json" + }, + "runtimeWorkspaceExecutionPublicationDigest": {"$ref": "#/$defs/contentDigest"}, + "runtimeBundleDigest": {"$ref": "#/$defs/contentDigest"}, + "selectors": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9+.-]*://[^\\s#]+#item/.+$(?![\\s\\S])" + } + }, + "projection": {"enum": ["source", "callable-skeleton"]} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/resident-search-result.v1.schema.json b/schemas/resident-search-result.v1.schema.json index 9aff9f7..96afbbd 100644 --- a/schemas/resident-search-result.v1.schema.json +++ b/schemas/resident-search-result.v1.schema.json @@ -56,6 +56,7 @@ "lineCount": { "type": "integer", "minimum": 1 }, "queryKeys": { "type": "array", + "maxItems": 64, "uniqueItems": true, "items": { "type": "string", "minLength": 1 } }, diff --git a/schemas/runtime-artifact-bundle-binding.v2.schema.json b/schemas/runtime-artifact-bundle-binding.v2.schema.json new file mode 100644 index 0000000..50ded02 --- /dev/null +++ b/schemas/runtime-artifact-bundle-binding.v2.schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-artifact-bundle-binding.v2.schema.json", + "title": "Runtime Artifact Bundle Binding V2", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "providerRegistrationDigest", + "providerArtifactSetDigest", + "evaluatorPolicyDigest", + "evaluatorAbiDigest", + "schemaBundleDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-bundle-binding"}, + "schemaVersion": {"const": "2"}, + "providerRegistrationDigest": {"$ref": "#/$defs/blake3Digest"}, + "providerArtifactSetDigest": {"$ref": "#/$defs/blake3Digest"}, + "evaluatorPolicyDigest": {"$ref": "#/$defs/blake3Digest"}, + "evaluatorAbiDigest": {"$ref": "#/$defs/blake3Digest"}, + "schemaBundleDigest": {"$ref": "#/$defs/blake3Digest"} + }, + "$defs": { + "blake3Digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} + } +} diff --git a/schemas/runtime-artifact-execution-closure-member.v1.schema.json b/schemas/runtime-artifact-execution-closure-member.v1.schema.json new file mode 100644 index 0000000..02795f0 --- /dev/null +++ b/schemas/runtime-artifact-execution-closure-member.v1.schema.json @@ -0,0 +1,144 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-artifact-execution-closure-member.v1.schema.json", + "title": "Runtime Artifact Execution Closure Member V1", + "description": "One canonical member of the immutable Runtime execution closure. The bundle binding contains the BLAKE3 digest of each complete member document.", + "oneOf": [ + {"$ref": "#/$defs/providerRegistration"}, + {"$ref": "#/$defs/providerArtifactSet"}, + {"$ref": "#/$defs/evaluatorPolicy"}, + {"$ref": "#/$defs/evaluatorAbi"}, + {"$ref": "#/$defs/schemaBundle"} + ], + "$defs": { + "base": { + "type": "object", + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"} + } + }, + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "identifier": {"type": "string", "minLength": 1}, + "providerRegistrationEntry": { + "type": "object", + "additionalProperties": false, + "required": ["providerId", "languageId", "registrationDigest", "artifactMember"], + "properties": { + "providerId": {"$ref": "#/$defs/identifier"}, + "languageId": {"$ref": "#/$defs/identifier"}, + "registrationDigest": {"$ref": "#/$defs/digest"}, + "artifactMember": {"$ref": "#/$defs/identifier"} + } + }, + "providerArtifactEntry": { + "type": "object", + "additionalProperties": false, + "required": ["providerId", "artifactMember", "artifactDigest"], + "properties": { + "providerId": {"$ref": "#/$defs/identifier"}, + "artifactMember": {"$ref": "#/$defs/identifier"}, + "artifactDigest": {"$ref": "#/$defs/digest"} + } + }, + "namedDigestEntry": { + "type": "object", + "additionalProperties": false, + "required": ["id", "digest"], + "properties": { + "id": {"$ref": "#/$defs/identifier"}, + "digest": {"$ref": "#/$defs/digest"} + } + }, + "languageSchemaEntry": { + "type": "object", + "additionalProperties": false, + "required": ["languageId", "schemaDigest"], + "properties": { + "languageId": {"$ref": "#/$defs/identifier"}, + "schemaDigest": {"$ref": "#/$defs/digest"} + } + }, + "providerRegistration": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "provider-registration"}, + "entries": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/providerRegistrationEntry"}} + } + } + ] + }, + "providerArtifactSet": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "provider-artifact-set"}, + "entries": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/providerArtifactEntry"}} + } + } + ] + }, + "evaluatorPolicy": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "evaluator-policy"}, + "entries": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/namedDigestEntry"}} + } + } + ] + }, + "evaluatorAbi": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "evaluator-abi"}, + "entries": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/namedDigestEntry"}} + } + } + ] + }, + "schemaBundle": { + "allOf": [ + {"$ref": "#/$defs/base"}, + { + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "memberKind", "entries"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, + "schemaVersion": {"const": "1"}, + "memberKind": {"const": "schema-bundle"}, + "entries": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/languageSchemaEntry"}} + } + } + ] + } + } +} diff --git a/schemas/runtime-binary-bundle.v2.schema.json b/schemas/runtime-binary-bundle.v2.schema.json new file mode 100644 index 0000000..dc145a8 --- /dev/null +++ b/schemas/runtime-binary-bundle.v2.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-binary-bundle.v2.schema.json", + "title": "Runtime Binary Bundle V2", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "bundleDigest", "members", "executionBinding"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-binary-bundle"}, + "schemaVersion": {"const": 2}, + "bundleDigest": {"$ref": "#/$defs/blake3Digest"}, + "members": { + "type": "object", + "additionalProperties": {"$ref": "#/$defs/blake3Digest"}, + "required": [ + "provider-registration.json", + "provider-artifact-set", + "evaluator-policy.json", + "evaluator-abi.json", + "schema-bundle.json" + ], + "minProperties": 6, + "propertyNames": {"pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"} + }, + "executionBinding": {"$ref": "runtime-artifact-bundle-binding.v2.schema.json"} + }, + "$defs": { + "blake3Digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} + } +} diff --git a/schemas/runtime-execution-binding.v2.schema.json b/schemas/runtime-execution-binding.v2.schema.json new file mode 100644 index 0000000..e5ae78b --- /dev/null +++ b/schemas/runtime-execution-binding.v2.schema.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-execution-binding.v2.schema.json", + "title": "Runtime Execution Binding V2", + "description": "One immutable Project Workspace, worktree, content, Runtime artifact, evaluator policy, publication receipt, and ABI product for Search and Query admission.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "projectWorkspace", + "worktreeInstanceId", + "publicationNonce", + "contentBinding", + "runtimeArtifactDigest", + "evaluatorPolicyDigest", + "activeArtifactReceiptDigest", + "evaluatorAbiDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-execution-binding"}, + "schemaVersion": {"const": "2"}, + "projectWorkspace": { + "$ref": "project-workspace-binding.v1.schema.json" + }, + "worktreeInstanceId": {"type": "string", "minLength": 1}, + "publicationNonce": {"type": "string", "minLength": 1}, + "contentBinding": { + "$ref": "https://agent.semantic.protocols/schemas/content-binding" + }, + "runtimeArtifactDigest": {"$ref": "#/$defs/contentDigest"}, + "evaluatorPolicyDigest": {"$ref": "#/$defs/contentDigest"}, + "activeArtifactReceiptDigest": {"$ref": "#/$defs/contentDigest"}, + "evaluatorAbiDigest": {"$ref": "#/$defs/contentDigest"} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/runtime-search-client-timing-witness.v1.schema.json b/schemas/runtime-search-client-timing-witness.v1.schema.json new file mode 100644 index 0000000..9633a98 --- /dev/null +++ b/schemas/runtime-search-client-timing-witness.v1.schema.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://tao3k.dev/schemas/runtime-search-client-timing-witness.v1.schema.json", + "title": "Runtime Search Client Timing Witness", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "sessionId", "requestId", "phases"], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.runtime-search-client-timing-witness" + }, + "schemaVersion": { "const": "1" }, + "sessionId": { "type": "string", "minLength": 1 }, + "requestId": { "type": "string", "minLength": 1 }, + "phases": { + "type": "array", + "minItems": 3, + "maxItems": 3, + "prefixItems": [ + { "$ref": "#/$defs/launcher" }, + { "$ref": "#/$defs/clientFrameEncode" }, + { "$ref": "#/$defs/ipcConnect" } + ], + "items": false + } + }, + "$defs": { + "phase": { + "type": "object", + "additionalProperties": false, + "required": ["name", "elapsedMicros"], + "properties": { + "name": { "type": "string" }, + "elapsedMicros": { "type": "integer", "minimum": 0 } + } + }, + "launcher": { + "allOf": [ + { "$ref": "#/$defs/phase" }, + { "properties": { "name": { "const": "launcher" } } } + ] + }, + "clientFrameEncode": { + "allOf": [ + { "$ref": "#/$defs/phase" }, + { "properties": { "name": { "const": "client-frame-encode" } } } + ] + }, + "ipcConnect": { + "allOf": [ + { "$ref": "#/$defs/phase" }, + { "properties": { "name": { "const": "ipc-connect" } } } + ] + } + } +} diff --git a/schemas/runtime-search-execution-budget.v1.schema.json b/schemas/runtime-search-execution-budget.v1.schema.json new file mode 100644 index 0000000..fe59554 --- /dev/null +++ b/schemas/runtime-search-execution-budget.v1.schema.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-search-execution-budget.v1.schema.json", + "title": "Runtime Search execution budget V1", + "description": "A Runtime-generation-owned Search cardinality receipt. It bounds semantic work and output; it does not configure Tokio scheduling concurrency.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "authority", + "generationDigest", + "cardinality", + "limits" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.runtime-search-execution-budget" + }, + "schemaVersion": {"const": "1"}, + "authority": {"const": "runtime-generation"}, + "generationDigest": {"$ref": "#/$defs/digest"}, + "cardinality": {"$ref": "#/$defs/cardinality"}, + "limits": {"$ref": "#/$defs/limits"} + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$(?![\\s\\S])" + }, + "cardinality": { + "type": "object", + "additionalProperties": false, + "required": [ + "indexedOwnerCount", + "corpusByteCount", + "corpusLineCount", + "graphNodeCount", + "graphEdgeCount" + ], + "properties": { + "indexedOwnerCount": {"type": "integer", "minimum": 1}, + "corpusByteCount": {"type": "integer", "minimum": 1}, + "corpusLineCount": {"type": "integer", "minimum": 1, "maximum": 4294967295}, + "graphNodeCount": {"type": "integer", "minimum": 0}, + "graphEdgeCount": {"type": "integer", "minimum": 0} + } + }, + "limits": { + "type": "object", + "additionalProperties": false, + "required": [ + "rgMatchCount", + "lexicalOwnerCount", + "syntaxSelectorCount", + "graphCandidateOwnerCount", + "graphDepth", + "graphNodeCount", + "graphEdgeCount", + "graphResultCount", + "evidenceItemCount" + ], + "properties": { + "rgMatchCount": {"type": "integer", "minimum": 1, "maximum": 4294967295}, + "lexicalOwnerCount": {"type": "integer", "minimum": 1, "maximum": 4294967295}, + "syntaxSelectorCount": {"type": "integer", "minimum": 1}, + "graphCandidateOwnerCount": {"type": "integer", "minimum": 1}, + "graphDepth": {"type": "integer", "minimum": 0, "maximum": 16}, + "graphNodeCount": {"type": "integer", "minimum": 0, "maximum": 256}, + "graphEdgeCount": {"type": "integer", "minimum": 0, "maximum": 1024}, + "graphResultCount": {"type": "integer", "minimum": 0, "maximum": 30}, + "evidenceItemCount": {"const": 30} + } + } + } +} diff --git a/schemas/runtime-workspace-execution-pointer.v1.schema.json b/schemas/runtime-workspace-execution-pointer.v1.schema.json new file mode 100644 index 0000000..cad82d3 --- /dev/null +++ b/schemas/runtime-workspace-execution-pointer.v1.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-workspace-execution-pointer.v1.schema.json", + "title": "Runtime Workspace Execution Pointer V1", + "description": "Single canonical pointer atomically binding a durable source generation to its immutable Runtime execution publication.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "workspaceIdentity", + "generationDigest", + "sourceRootDigest", + "executionPublicationDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-workspace-execution-pointer"}, + "schemaVersion": {"const": "1"}, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"$ref": "#/$defs/contentDigest"}, + "sourceRootDigest": {"$ref": "#/$defs/contentDigest"}, + "executionPublicationDigest": {"$ref": "#/$defs/contentDigest"} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/runtime-workspace-execution-publication.v1.schema.json b/schemas/runtime-workspace-execution-publication.v1.schema.json new file mode 100644 index 0000000..ab96d11 --- /dev/null +++ b/schemas/runtime-workspace-execution-publication.v1.schema.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/runtime-workspace-execution-publication.v1.schema.json", + "title": "Runtime Workspace Execution Publication V1", + "description": "Immutable sidecar atomically binding one durable workspace source generation to one RuntimeExecutionBinding V2 product.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "workspaceIdentity", + "generationDigest", + "sourceRootDigest", + "contentPublicationCommit", + "runtimeExecutionBinding", + "runtimeBundleDigest", + "publicationDigest" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.runtime-workspace-execution-publication"}, + "schemaVersion": {"const": "1"}, + "workspaceIdentity": {"type": "string", "minLength": 1}, + "generationDigest": {"$ref": "#/$defs/contentDigest"}, + "sourceRootDigest": {"$ref": "#/$defs/contentDigest"}, + "contentPublicationCommit": {"$ref": "content-publication-commit.v1.schema.json"}, + "runtimeExecutionBinding": {"$ref": "runtime-execution-binding.v2.schema.json"}, + "runtimeBundleDigest": {"$ref": "#/$defs/contentDigest"}, + "publicationDigest": {"$ref": "#/$defs/contentDigest"} + }, + "$defs": { + "contentDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } +} diff --git a/schemas/search-topology-settlement.v1.schema.json b/schemas/search-topology-settlement.v1.schema.json new file mode 100644 index 0000000..ad0237e --- /dev/null +++ b/schemas/search-topology-settlement.v1.schema.json @@ -0,0 +1,360 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/search-topology-settlement.v1.schema.json", + "title": "Search topology single-GQL settlement V1", + "description": "A request-bound Search projection of the reusable Project Topology and its single Agent-facing GQL settlement. Logical inference is an internal topology-maintenance mechanism, not the architecture authority. Shape validation does not replace selector, witness, proof, coverage, or binding admission.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "protocolId", + "protocolVersion", + "resultState", + "binding", + "inference", + "nodes", + "edges", + "coverageCertificates", + "frontiers", + "materializationSet", + "rendering", + "terminal" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.search-topology-settlement"}, + "schemaVersion": {"const": "1"}, + "protocolId": {"const": "agent.semantic-protocols.search-playbook"}, + "protocolVersion": {"const": "1"}, + "resultState": {"enum": ["materializable", "empty", "incomplete", "blocked"]}, + "binding": {"$ref": "#/$defs/binding"}, + "inference": {"$ref": "#/$defs/inference"}, + "nodes": { + "type": "array", + "items": {"$ref": "#/$defs/node"} + }, + "edges": { + "type": "array", + "items": {"$ref": "#/$defs/edge"} + }, + "coverageCertificates": { + "$ref": "project-topology-library.v1.schema.json#/properties/coverageCertificates" + }, + "frontiers": { + "type": "array", + "items": {"$ref": "#/$defs/frontier"} + }, + "materializationSet": {"$ref": "#/$defs/materializationSet"}, + "rendering": {"$ref": "#/$defs/rendering"}, + "terminal": {"$ref": "#/$defs/terminal"} + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$(?![\\s\\S])" + }, + "identifier": { + "type": "string", + "pattern": "^[a-z][a-z0-9_.:-]*$(?![\\s\\S])" + }, + "nodeId": { + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*$(?![\\s\\S])" + }, + "selector": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])" + }, + "binding": { + "type": "object", + "additionalProperties": false, + "required": [ + "projectWorkspaceIdentity", + "sourceGenerationDigest", + "providerCatalogDigest", + "topologyLibraryDigest", + "topologyGenerationDigest", + "structuralTopologyDigest", + "semanticTopologyDigest", + "inferenceProgramDigest", + "topologyClosureDigest", + "evidenceBindingDigest", + "decisionCoreDigest", + "topologyDeltaDigest" + ], + "properties": { + "projectWorkspaceIdentity": { + "$ref": "project-workspace-binding.v1.schema.json#/$defs/projectWorkspaceIdentity" + }, + "sourceGenerationDigest": {"$ref": "#/$defs/digest"}, + "providerCatalogDigest": {"$ref": "#/$defs/digest"}, + "topologyLibraryDigest": {"$ref": "#/$defs/digest"}, + "topologyGenerationDigest": {"$ref": "#/$defs/digest"}, + "structuralTopologyDigest": {"$ref": "#/$defs/digest"}, + "semanticTopologyDigest": {"$ref": "#/$defs/digest"}, + "inferenceProgramDigest": {"$ref": "#/$defs/digest"}, + "topologyClosureDigest": {"$ref": "#/$defs/digest"}, + "evidenceBindingDigest": {"$ref": "#/$defs/digest"}, + "decisionCoreDigest": {"$ref": "#/$defs/digest"}, + "topologyDeltaDigest": {"$ref": "#/$defs/digest"} + } + }, + "inference": { + "type": "object", + "additionalProperties": false, + "required": [ + "programId", + "engineProfile", + "scope", + "state", + "terminationKind", + "iterationCount", + "traversalDepth", + "derivedRelationCount", + "proofDagDigest", + "proofArtifactLocator", + "rankingReceiptDigest", + "candidateClosureReceiptDigest", + "candidateRelationSetDigest", + "nextRelationSetDigest", + "postRankingCertified" + ], + "properties": { + "programId": {"const": "project-topology-inference.v1"}, + "engineProfile": {"type": "string", "minLength": 1}, + "scope": {"enum": ["bounded", "complete"]}, + "state": {"enum": ["complete", "incomplete", "blocked"]}, + "terminationKind": {"enum": ["fixed-point", "budget-exhausted", "blocked"]}, + "iterationCount": {"type": "integer", "minimum": 0}, + "traversalDepth": {"type": "integer", "minimum": 0}, + "derivedRelationCount": {"type": "integer", "minimum": 0}, + "proofDagDigest": {"$ref": "#/$defs/digest"}, + "proofArtifactLocator": {"type": "string", "minLength": 1}, + "rankingReceiptDigest": {"$ref": "#/$defs/digest"}, + "candidateClosureReceiptDigest": {"$ref": "#/$defs/digest"}, + "candidateRelationSetDigest": {"$ref": "#/$defs/digest"}, + "nextRelationSetDigest": {"$ref": "#/$defs/digest"}, + "postRankingCertified": {"type": "boolean"}, + "reasonKind": {"$ref": "#/$defs/identifier"} + }, + "oneOf": [ + { + "properties": { + "state": {"const": "complete"}, + "terminationKind": {"const": "fixed-point"}, + "postRankingCertified": {"const": true} + }, + "not": {"required": ["reasonKind"]} + }, + { + "properties": { + "state": {"const": "incomplete"}, + "terminationKind": {"const": "budget-exhausted"}, + "postRankingCertified": {"const": false} + }, + "required": ["reasonKind"] + }, + { + "properties": { + "state": {"const": "blocked"}, + "terminationKind": {"const": "blocked"}, + "postRankingCertified": {"const": false} + }, + "required": ["reasonKind"] + } + ] + }, + "hitProjection": { + "type": "object", + "additionalProperties": false, + "properties": { + "fd": {"type": "boolean"}, + "rg": {"$ref": "#/$defs/lineRanges"}, + "tantivy": { + "type": "array", + "uniqueItems": true, + "items": {"type": "string", "minLength": 1} + }, + "native": {"type": "boolean"} + }, + "minProperties": 1 + }, + "lineRange": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "prefixItems": [ + {"type": "integer", "minimum": 1}, + {"type": "integer", "minimum": 1} + ], + "items": false + }, + "lineRanges": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/lineRange"} + }, + "projection": { + "type": "object", + "additionalProperties": false, + "required": ["rank", "depth"], + "properties": { + "rank": {"type": "integer", "minimum": 1}, + "depth": {"type": "integer", "minimum": 0}, + "hit": {"$ref": "#/$defs/hitProjection"}, + "jq": {"type": "string", "minLength": 1} + }, + "anyOf": [{"required": ["hit"]}, {"required": ["jq"]}] + }, + "semanticAnnotation": { + "$ref": "project-topology-library.v1.schema.json#/$defs/annotation" + }, + "sourceExcerpt": { + "type": "object", + "additionalProperties": false, + "required": ["path", "match", "read", "witness"], + "properties": { + "path": {"type": "string", "pattern": "^(?!/)(?!.*\\\\)[^\\s]+$(?![\\s\\S])"}, + "match": {"$ref": "#/$defs/lineRanges"}, + "read": {"$ref": "#/$defs/lineRanges"}, + "witness": {"$ref": "#/$defs/identifier"} + } + }, + "node": { + "type": "object", + "additionalProperties": false, + "required": ["id", "language", "kind"], + "properties": { + "id": {"$ref": "#/$defs/nodeId"}, + "language": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "name": {"type": "string", "minLength": 1}, + "selector": {"$ref": "#/$defs/selector"}, + "projection": {"$ref": "#/$defs/projection"}, + "annotation": {"$ref": "#/$defs/semanticAnnotation"}, + "excerpt": {"$ref": "#/$defs/sourceExcerpt"} + }, + "oneOf": [ + {"required": ["selector"], "not": {"anyOf": [{"required": ["annotation"]}, {"required": ["excerpt"]}]}}, + {"required": ["annotation"], "not": {"anyOf": [{"required": ["selector"]}, {"required": ["excerpt"]}, {"required": ["projection"]}]}}, + {"required": ["excerpt"], "not": {"anyOf": [{"required": ["selector"]}, {"required": ["annotation"]}, {"required": ["projection"]}]}} + ] + }, + "edge": { + "type": "object", + "additionalProperties": false, + "required": ["from", "to", "relation", "modality", "producerAuthority", "evidenceAuthority", "witnesses"], + "properties": { + "from": {"$ref": "#/$defs/nodeId"}, + "to": {"$ref": "#/$defs/nodeId"}, + "relation": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])"}, + "modality": {"enum": ["parser-direct", "declared", "derived", "proposed"]}, + "producerAuthority": {"enum": ["provider-parser", "source-contract", "project-topology-inference.v1", "model-proposal"]}, + "evidenceAuthority": {"enum": ["provider-witness", "contract-witness", "proof-dag", "model-premises"]}, + "witnesses": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/identifier"} + }, + "derivedBy": {"const": "project-topology-inference.v1"}, + "proofRef": {"type": "string", "minLength": 1} + }, + "allOf": [ + { + "if": {"properties": {"modality": {"const": "derived"}}, "required": ["modality"]}, + "then": {"required": ["derivedBy", "proofRef"]}, + "else": {"not": {"anyOf": [{"required": ["derivedBy"]}, {"required": ["proofRef"]}]}} + } + ] + }, + "coverageCertificate": { + "$ref": "project-topology-library.v1.schema.json#/$defs/coverage" + }, + "frontier": { + "type": "object", + "additionalProperties": false, + "required": ["anchor", "relation", "targetKind", "depth", "state", "reason"], + "properties": { + "anchor": {"$ref": "#/$defs/nodeId"}, + "relation": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])"}, + "targetKind": {"type": "string", "minLength": 1}, + "depth": {"type": "integer", "minimum": 0}, + "state": {"enum": ["unknown", "certified-missing"]}, + "reason": {"$ref": "#/$defs/identifier"}, + "coverageRef": {"type": "string", "minLength": 1} + }, + "allOf": [ + { + "if": {"properties": {"state": {"const": "certified-missing"}}, "required": ["state"]}, + "then": {"required": ["coverageRef"]} + } + ] + }, + "materializationSet": { + "type": "object", + "additionalProperties": false, + "required": ["state", "requestId", "digest", "selectors", "proofDependencies"], + "properties": { + "state": {"enum": ["available", "empty"]}, + "requestId": {"type": "string", "minLength": 1}, + "digest": {"$ref": "#/$defs/digest"}, + "selectors": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "#/$defs/selector"} + }, + "proofDependencies": { + "type": "array", + "uniqueItems": true, + "items": {"type": "string", "minLength": 1} + } + }, + "oneOf": [ + { + "properties": { + "state": {"const": "available"}, + "selectors": {"minItems": 1} + } + }, + { + "properties": { + "state": {"const": "empty"}, + "selectors": {"maxItems": 0}, + "proofDependencies": {"maxItems": 0} + } + } + ] + }, + "rendering": { + "type": "object", + "additionalProperties": false, + "required": ["format", "gqlBlockCount", "ascentSourceExposed"], + "properties": { + "format": {"const": "org-gql"}, + "gqlBlockCount": {"const": 1}, + "ascentSourceExposed": {"const": false} + } + }, + "terminal": { + "type": "object", + "additionalProperties": false, + "required": ["state", "terminalCount"], + "properties": { + "state": {"enum": ["ready", "incomplete", "failed"]}, + "terminalCount": {"const": 1}, + "reasonKind": {"$ref": "#/$defs/identifier"} + }, + "oneOf": [ + { + "properties": {"state": {"const": "ready"}}, + "not": {"required": ["reasonKind"]} + }, + { + "properties": {"state": {"enum": ["incomplete", "failed"]}}, + "required": ["reasonKind"] + } + ] + } + } +} diff --git a/schemas/semantic-graph-resident-evaluation-request.v1.schema.json b/schemas/semantic-graph-resident-evaluation-request.v1.schema.json index 5b0d9b3..fd04f90 100644 --- a/schemas/semantic-graph-resident-evaluation-request.v1.schema.json +++ b/schemas/semantic-graph-resident-evaluation-request.v1.schema.json @@ -6,7 +6,7 @@ "required": [ "schemaId", "schemaVersion", "protocolId", "protocolVersion", "packetKind", "languageId", "surface", "queryTerms", "profile", - "seedIds", "budget" + "entryNodeIds", "budget" ], "properties": { "schemaId": {"const": "agent.semantic-protocols.semantic-graph-resident-evaluation-request"}, @@ -16,15 +16,25 @@ "packetKind": {"const": "resident-graph-evaluation-request"}, "languageId": {"type": "string", "pattern": "^[a-z][a-z0-9-]*$"}, "surface": { - "enum": ["search-pipe", "search-rg", "search-lexical", "search-owner", "query"] + "enum": ["search-playbook", "query"] }, "queryTerms": { "type": "array", "maxItems": 32, "uniqueItems": true, "items": {"type": "string", "minLength": 1, "maxLength": 256} }, + "queryClauses": { + "description": "Ordered Agent-authored Graph reasoning clauses; array order is semantic priority.", + "type": "array", "maxItems": 64, + "items": {"type": "string", "minLength": 1, "maxLength": 4096} + }, "profile": {"enum": ["balanced", "structural", "dependency"]}, - "seedIds": { - "type": "array", "maxItems": 128, "uniqueItems": true, + "entryNodeIds": { + "type": "array", "maxItems": 4096, "uniqueItems": true, + "items": {"type": "string", "minLength": 1, "maxLength": 1024} + }, + "candidateNodeIds": { + "description": "Acquisition frontier that Graph may rank or filter but may not extend.", + "type": "array", "maxItems": 4096, "uniqueItems": true, "items": {"type": "string", "minLength": 1, "maxLength": 1024} }, "budget": { @@ -41,7 +51,7 @@ }, "anyOf": [ {"properties": {"queryTerms": {"minItems": 1}}}, - {"properties": {"seedIds": {"minItems": 1}}} + {"properties": {"entryNodeIds": {"minItems": 1}}} ], "additionalProperties": false } diff --git a/schemas/semantic-graph-resident-evaluation-result.v1.schema.json b/schemas/semantic-graph-resident-evaluation-result.v1.schema.json index cd5cad4..9a6dddb 100644 --- a/schemas/semantic-graph-resident-evaluation-result.v1.schema.json +++ b/schemas/semantic-graph-resident-evaluation-result.v1.schema.json @@ -6,7 +6,7 @@ "required": [ "schemaId", "schemaVersion", "protocolId", "protocolVersion", "packetKind", "state", "surface", "workspaceIdentity", - "generationDigest", "rootDigest", "profile", "seedIds", + "generationDigest", "rootDigest", "profile", "entryNodeIds", "rankedNodes", "edges", "workCounters" ], "properties": { @@ -17,13 +17,13 @@ "packetKind": {"const": "resident-graph-evaluation-result"}, "state": {"const": "Ready"}, "surface": { - "enum": ["search-pipe", "search-rg", "search-lexical", "search-owner", "query"] + "enum": ["search-playbook", "query"] }, "workspaceIdentity": {"type": "string", "minLength": 1}, "generationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, "rootDigest": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, "profile": {"enum": ["balanced", "structural", "dependency"]}, - "seedIds": { + "entryNodeIds": { "type": "array", "maxItems": 128, "uniqueItems": true, "items": {"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 ee2ebb2..9c851bd 100644 --- a/schemas/semantic-graph-turbo-request.v1.schema.json +++ b/schemas/semantic-graph-turbo-request.v1.schema.json @@ -17,7 +17,7 @@ "queryTerms", "profile", "algorithm", - "seedIds", + "entryNodeIds", "budget" ], "anyOf": [ @@ -119,7 +119,7 @@ "uniqueItems": true }, "source": { - "description": "Candidate source selection requested by search pipe. This selects candidate acquisition only; it is not an output projection.", + "description": "Candidate source selection chosen internally by the search playbook planner. This selects candidate acquisition only; it is not a public command or output projection.", "enum": [ "auto", "provider", @@ -152,7 +152,7 @@ "$ref": "#/$defs/sourceTraceEntry" } }, - "seedIds": { + "entryNodeIds": { "type": "array", "items": { "$ref": "#/$defs/nodeId" @@ -380,7 +380,7 @@ "minLength": 1 }, "seedPlan": { - "description": "First-phase seed selection diagnostics for graph-turbo ranking. This explains why seedIds were selected before traversal and rendering.", + "description": "First-phase seed selection diagnostics for graph-turbo ranking. This explains why entryNodeIds were selected before traversal and rendering.", "type": "object", "additionalProperties": false, "required": [ @@ -395,7 +395,7 @@ "queryOwnerSeedCount", "fallbackOwnerSeedCount", "selectedSeedCount", - "seedIds", + "entryNodeIds", "riskFactors", "recommendedActions" ], @@ -447,7 +447,7 @@ "type": "integer", "minimum": 0 }, - "seedIds": { + "entryNodeIds": { "type": "array", "items": { "$ref": "#/$defs/nodeId" diff --git a/schemas/semantic-language-registry.v1.schema.json b/schemas/semantic-language-registry.v1.schema.json index 8d5412a..b702186 100644 --- a/schemas/semantic-language-registry.v1.schema.json +++ b/schemas/semantic-language-registry.v1.schema.json @@ -44,12 +44,11 @@ }, "method": { "type": "string", - "pattern": "^(?:guide|query|(search|query|proof|review|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" + "pattern": "^(?:guide|query|(query|proof|review|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" }, "command": { "enum": [ "guide", - "search", "query", "proof", "review", @@ -247,36 +246,6 @@ "type": "boolean" } }, - "allOf": [ - { - "if": { - "properties": { - "argumentProjection": { - "properties": { - "tokens": { - "contains": { - "type": "object", - "required": ["kind", "name"], - "properties": { - "kind": {"const": "slot"}, - "name": {"const": "owner"} - } - } - } - } - } - }, - "required": ["argumentProjection"] - }, - "then": { - "properties": { - "method": { - "pattern": "^search/owner(?:$|-)" - } - } - } - } - ], "additionalProperties": true } } diff --git a/schemas/semantic-search-packet.v1.schema.json b/schemas/semantic-search-packet.v1.schema.json index 30a4f88..156b7d5 100644 --- a/schemas/semantic-search-packet.v1.schema.json +++ b/schemas/semantic-search-packet.v1.schema.json @@ -373,7 +373,7 @@ }, "finder": { "type": "object", - "description": "Provider-owned finder pipeline provenance for lexical/fuzzy search surfaces such as search lexical. This records normalized agent-requested finder options; it is not raw shell argv.", + "description": "Provider-owned finder pipeline provenance used internally by search playbook acquisition. This records normalized planner options; it is not raw shell argv and is never a public command surface.", "additionalProperties": false, "required": [ "engine", diff --git a/src/asp_python/__init__.py b/src/asp_python/__init__.py index 4d1a0bd..32c9587 100644 --- a/src/asp_python/__init__.py +++ b/src/asp_python/__init__.py @@ -112,7 +112,6 @@ "PythonReasoningTreeShadow", "PythonRulePackDescriptor", "PythonScope", - "PythonSemanticSearchOptions", "PythonSymbol", "PythonSymbolKind", "PythonSyntaxRulePack", @@ -144,7 +143,6 @@ "__version__", "assert_python_lang_harness_clean", "assert_asp_python_clean", - "build_python_semantic_search_packet", "default_python_harness_config", "default_python_lang_rule_packs", "discover_python_files", @@ -195,8 +193,6 @@ "render_asp_python_agent_snapshot", "render_asp_python_agent_snapshot_with_config", "render_python_reasoning_tree", - "render_python_semantic_search_packet", - "render_python_semantic_search_packet_json", "render_python_verification_performance_index_json", "render_python_verification_plan", "render_python_verification_plan_json", diff --git a/src/asp_python/_cli_agent.py b/src/asp_python/_cli_agent.py index a813423..c86252f 100644 --- a/src/asp_python/_cli_agent.py +++ b/src/asp_python/_cli_agent.py @@ -11,19 +11,11 @@ def render_agent_guide(project_root: Path) -> str: project = str(project_root) workspace = "--workspace " - root = workspace return ( "\n".join( ( f"[asp-python-guide] project={project}", - ( - "|catalog reasoningProfiles=owner-query,query-deps,owner-tests," - "finding-frontier,feature-cfg entries=owner-query,query-deps," - "owner-tests routes=syntax-locate,exact-source,callable-skeleton" - ), - "|routing evidence-state prime=owner-map-only pipe=ambiguous-query " - "owner=known-owner selector=exact-parser-id deps=known-dependency " - "tests=known-owner ingest=stdin", + "|catalog provider=native-facts routes=syntax-locate,exact-source,callable-skeleton", ( f"|route syntax-locate selectors=S:tree-sitter-query,Scope:owner-or-structural " f"returns=locator,capture,frontier code=false cmd=asp python " @@ -33,24 +25,8 @@ def render_agent_guide(project_root: Path) -> str: ), f"|route exact-source selectors=R:exact-selector returns=source cmd=asp python query --selector --projection source {workspace}", f"|route callable-skeleton selectors=R:exact-callable-selector returns=callable-skeleton cmd=asp python query --selector --projection callable-skeleton {workspace}", - f"|cmd prime=asp python search prime {root} --view seeds condition=owner-map-unknown", - f"|cmd pipe=asp python search pipe {root} --view seeds condition=ambiguous-query", - f"|cmd owner=asp python search owner {root} --view seeds", - ( - f"|cmd reasoning-owner-tests=asp python search reasoning " - f"owner-tests --owner {root} --view seeds" - ), - ( - f"|cmd reasoning-owner-query=asp python search reasoning " - f"owner-query --owner --query " - f"{root} --view seeds" - ), - ( - f"|cmd reasoning-query-deps=asp python search reasoning " - f"query-deps --query --dependency " - f"{root} --view seeds" - ), - f"|cmd catalog-json=asp python query --catalog declarations --json {root}", + f"|cmd playbook=asp python search playbook {workspace}", + f"|cmd catalog-json=asp python query --catalog declarations --json {workspace}", ( f"|cmd syntax-locate=asp python query --treesitter-query " f"'(function_definition name: (identifier) @function.name)' " @@ -58,24 +34,9 @@ def render_agent_guide(project_root: Path) -> str: ), f"|cmd exact-source=asp python query --selector --projection source {workspace}", f"|cmd callable-skeleton=asp python query --selector --projection callable-skeleton {workspace}", - ( - f"|cmd policy=asp python search policy " - f"owner tests {root} --view seeds" - ), - f"|cmd lexical=asp python search lexical owner tests {root} --view seeds", "|cmd ast-patch=asp python ast-patch dry-run --packet ", - f"|cmd evidence-graph=asp python evidence graph --json {root}", - f"|cmd evidence-analyze=asp python evidence analyze --json {root}", - f"|cmd deps=asp python search deps {root}", - f"|cmd env=asp python search env [term ...] {workspace} --view seeds", - f"|cmd runtime-source=asp python search runtime-source [term ...] {workspace} --view seeds", - f"|cmd lang=asp python search lang [term ...] {workspace} --view seeds", - f"|cmd std=asp python search std [term ...] {workspace} --view seeds", - f"|cmd capability=asp python search capability [term ...] {workspace} --view seeds", - f"|cmd extension=asp python search extension [term ...] {workspace} --view seeds", - f"|cmd pattern=asp python search pattern [term ...] {workspace} --view seeds", - f"|cmd compare=asp python search compare [left right] {workspace} --view seeds", - f"|pipe | asp python search ingest {root} --view seeds", + f"|cmd evidence-graph=asp python evidence graph --json {workspace}", + f"|cmd evidence-analyze=asp python evidence analyze --json {workspace}", "|policy authority=asp-python-api trigger=pytest-plugin", "|rule agent hook install/runtime is owned by asp", ( @@ -96,17 +57,13 @@ def render_agent_guide(project_root: Path) -> str: "|rule displayLineRange/sourceLocatorHint are display hints; " "execute structural selectors or owner/symbol routes, not line ranges" ), - "|rule Python discovery uses search --view seeds; query only materializes an exact structural selector", - ( - "|rule provider-knowledge-axes env/lang/std/pattern/runtime-source " - "return facts or explicit frontier gaps; do not fill missing " - "facts from memory" - ), + "|rule the root ASP Client owns the only public search surface; provider-local search views are removed", + "|rule native syntax facts remain provider-owned inputs to the root search playbook", ( "|rule use the asp python facade; run one command at a time; " "no raw Python source reads" ), - "|subagent give one |cmd or |pipe line; require evidence/missing/next/risk", + "|subagent give one |cmd line; require evidence/missing/next/risk", ) ) + "\n" diff --git a/src/asp_python/_cli_args.py b/src/asp_python/_cli_args.py index 2b7bd56..fb80b5e 100644 --- a/src/asp_python/_cli_args.py +++ b/src/asp_python/_cli_args.py @@ -43,7 +43,13 @@ class ProtocolArgs: def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: command = args[0] if args else None if command == "search": - return cls._parse_search(args[1:]) + return cls( + "error", + error=( + "provider-local search was removed; use " + "asp python search playbook " + ), + ) if command == "query": return cls._parse_query(args[1:]) if command == "evidence": @@ -54,31 +60,6 @@ def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: return cls._parse_ast_patch(args[1:]) return None - @classmethod - def _parse_search(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - if args and args[0] in {"--help", "-h"}: - return cls("help") - from ._semantic_search_cli import parse_semantic_search_args - - parsed = parse_semantic_search_args(args) - if parsed.error is not None: - return cls("error", error=parsed.error) - return cls( - "search", - view=parsed.view, - query=parsed.query, - item_query=parsed.item_query, - owner_path=parsed.owner_path, - dependency=parsed.dependency, - query_set=parsed.query_set, - project_root=parsed.project_root, - package_path=parsed.package_path, - workspace=parsed.workspace, - pipes=parsed.pipes, - json=parsed.json, - render_mode=parsed.render_mode, - ) - @classmethod def _parse_query(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: from ._cli_query_args import parse_query_args @@ -229,9 +210,9 @@ def _parse_agent_guide(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: def help_text() -> str: return ( - "asp-python — Python semantic search and project harness\n\n" + "asp-python — Python provider runtime and project harness\n\n" "Usage:\n" - " asp-python search ... [--json] [--package PATH] [--workspace ]\n" + " asp python search playbook [--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 evidence graph [--json] [PROJECT_ROOT]\n" @@ -240,33 +221,11 @@ def help_text() -> str: " asp-python agent doctor [--json]\n" " asp-python agent guide\n" "\n" - "SEARCH VIEWS\n" - " search workspace Workspace package/router index\n" - " search prime Project reasoning-tree map\n" - " search owner Owner graph slice\n" - " search owner items --query \n" - " Parser-owned structural selector discovery\n" - " search dependency Dependency manifest and local import usage\n" - " search deps \n" - " Versioned dependency API usage evidence\n" - " search api Parser-owned public API facts\n" - " search public-external-types \n" - " Public API type surfaces exposing a dependency\n" - " search symbol Symbol/export definitions\n" - " search callsite Parser-owned function and method callsites\n" - " search import Import owner edges\n" - " search tests Tests that import an owner\n" - " search lexical --query --query \n" - " Lexical owner/source-text candidates\n" - " search lexical --query --query owner tests\n" - " Minimal final-only lexical -> owner -> tests pipe\n" - " search reasoning owner-tests --owner \n" - " Typed graph entry returning covering tests, entrypoints, and fixtures\n" - " search reasoning owner-query --owner --query \n" - " Typed graph entry returning owner items, tests, and dependency usage\n" - " search reasoning query-deps --query --dependency \n" - " Typed graph entry returning owners, imports, and usage tests\n" - " search ingest Detect stdin shape and group hits by owner\n\n" + "SEARCH\n" + " Search is owned by the root ASP Client. The single public surface is\n" + " `asp python search playbook`, which composes raw candidates, provider\n" + " native syntax, lexical ranking, and graph expansion. Provider-local\n" + " search views are intentionally unavailable.\n\n" "QUERY\n" " asp python query --selector --projection source --workspace \n" " Exact source materialization through ASP authority\n" @@ -283,20 +242,12 @@ def help_text() -> str: "AGENT\n" " agent doctor Print semantic-language provider readiness\n" " agent doctor --json Semantic language registry document\n\n" - " agent guide Print command-line search flow guide\n\n" + " agent guide Print provider role and playbook guidance\n\n" " Hook install/runtime is owned by asp in the root toolchain.\n\n" "\nEXAMPLES\n" - " asp-python search workspace .\n" - " asp-python search prime .\n" - " asp-python search public-external-types pytest .\n" - " asp-python search callsite PythonSemanticSearchOptions .\n" - " asp python search lexical --query PythonSemanticSearchOptions --query owner --workspace .\n" - " asp-python search reasoning owner-tests --owner src/asp_python/_cli.py .\n" - " asp-python search reasoning owner-query --owner src/asp_python/_cli.py --query run_cli .\n" - " asp-python search reasoning query-deps --query Session --dependency requests .\n" + " asp python search playbook PythonSemanticSearchOptions --workspace .\n" " asp python query --selector 'python://src/asp_python/_cli.py#item/function/run_cli' --projection source --workspace .\n" " asp-python query --catalog flow-lite --where 'source.call=payload sink.constructs=Action scope.fn=collect' .\n" - " asp python search lexical --query PythonSemanticSearchOptions --workspace . --view seeds\n" " asp-python evidence graph --json .\n" " asp-python evidence analyze --json .\n" " asp-python agent doctor --json .\n" diff --git a/src/asp_python/_cli_protocol.py b/src/asp_python/_cli_protocol.py index 6aa426a..5d0a6b5 100644 --- a/src/asp_python/_cli_protocol.py +++ b/src/asp_python/_cli_protocol.py @@ -11,7 +11,6 @@ render_agent_guide, ) from ._cli_args import ProtocolArgs, help_text -from ._cli_search_runtime import _run_search_harness def run_protocol_cli( @@ -39,19 +38,10 @@ def run_protocol_cli( ) try: - fast_exit = _run_fast_protocol_command( - args, - project_root=project_root, - stdout=stdout, - stdin=stdin, - ) - if fast_exit is not None: - return fast_exit - return _run_harness_protocol_command( - args, - project_root=project_root, - stdout=stdout, - stdin=stdin, + if args.command != "query": + raise ValueError("unsupported provider command") + return _run_query_protocol_command( + args, project_root=project_root, stdout=stdout ) except ValueError as error: stderr.write(f"{error}\n") @@ -131,127 +121,23 @@ def _run_ast_patch_command( ) -def _run_fast_protocol_command( - args: ProtocolArgs, - *, - project_root: Path, - stdout: TextIO, - stdin: str, -) -> int | None: - rendered = _render_fast_protocol_command( - args, - project_root=project_root, - stdin=stdin, - ) - if rendered is None: - return None - stdout.write(rendered) - return 0 - - -def _render_fast_protocol_command( - args: ProtocolArgs, - *, - project_root: Path, - stdin: str, -) -> str | None: - if args.command == "search" and args.view == "dependency-topology": - from ._dependency_topology import render_dependency_topology_packet - - return render_dependency_topology_packet(project_root) - from ._semantic_graph_facts import render_semantic_graph_facts - from ._semantic_search_ingest_fast import render_fast_empty_ingest_search - from ._semantic_search_lexical_fast import render_fast_lexical_seed_search - from ._semantic_search_owner_fast import render_fast_owner_seed_search - from ._semantic_search_prime_fast import render_fast_prime_search - - renderers = ( - lambda: render_semantic_graph_facts( - args, project_root=project_root, stdin=stdin - ), - lambda: render_fast_empty_ingest_search(args, project_root, stdin), - lambda: render_fast_prime_search(args, project_root), - lambda: render_fast_owner_seed_search(args, project_root), - lambda: render_fast_lexical_seed_search(args, project_root), - ) - for render in renderers: - rendered = render() - if rendered is not None: - return rendered - return None - - -def _run_harness_protocol_command( +def _run_query_protocol_command( args: ProtocolArgs, *, project_root: Path, stdout: TextIO, - stdin: str, -) -> int: - report, runtime_cost = _run_search_harness(project_root, args) - if args.command == "query": - return _run_query_command( - args, report=report, project_root=project_root, stdout=stdout - ) - return _run_search_command( - args, - report=report, - runtime_cost=runtime_cost, - stdout=stdout, - stdin=stdin, - ) - - -def _run_query_command( - args: ProtocolArgs, - *, - report: object, - project_root: Path, - stdout: TextIO, ) -> int: from ._cli_query import run_query_command + from ._rule_packs import resolve_project_harness_config + from ._runner import run_asp_python + report = run_asp_python( + project_root, + config=resolve_project_harness_config(project_root, None, rule_packs=None), + ) return run_query_command( args, report=report, project_root=project_root, stdout=stdout, ) - - -def _run_search_command( - args: ProtocolArgs, - *, - report: object, - runtime_cost: dict[str, object] | None, - stdout: TextIO, - stdin: str, -) -> int: - from ._semantic_search import ( - PythonSemanticSearchOptions, - build_python_semantic_search_packet, - render_python_semantic_search_packet, - render_python_semantic_search_packet_json, - ) - - packet = build_python_semantic_search_packet( - report, - PythonSemanticSearchOptions( - view=args.view or "prime", - query=args.query, - item_query=args.item_query, - query_set=args.query_set, - owner_path=args.owner_path, - dependency=args.dependency, - pipes=args.pipes, - render_mode=args.render_mode, - stdin=stdin, - runtime_cost=runtime_cost, - ), - ) - stdout.write( - render_python_semantic_search_packet_json(packet) - if args.json - else render_python_semantic_search_packet(packet) - ) - return 0 diff --git a/src/asp_python/_cli_search_runtime.py b/src/asp_python/_cli_search_runtime.py deleted file mode 100644 index 69190bf..0000000 --- a/src/asp_python/_cli_search_runtime.py +++ /dev/null @@ -1,377 +0,0 @@ -"""Search runtime helpers for the Python harness CLI.""" - -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path - -from ._cli_args import ProtocolArgs - -_KNOWLEDGE_VIEWS = frozenset( - { - "env", - "runtime-source", - "lang", - "std", - "capability", - "extension", - "pattern", - "compare", - } -) -_QUERY_PREFILTER_VIEWS = frozenset( - { - "api", - "callsite", - "import", - "public-external-types", - "policy", - "symbol", - "tests", - } -) - - -def _run_search_harness( - project_root: Path, - args: ProtocolArgs, -) -> tuple[object, dict[str, object] | None]: - owner_items_report = _run_exact_owner_items_search(project_root, args) - if owner_items_report is not None: - return owner_items_report, { - "reason": "owner-items-exact-owner-prefilter", - "fields": { - "paths": 1, - "ownerPath": _owner_items_query_path(args) or "", - }, - } - owner_report = _run_exact_owner_search(project_root, args) - if owner_report is not None: - return owner_report, { - "reason": "owner-exact-path-prefilter", - "fields": { - "paths": 1, - "ownerPath": _owner_query_path(args) or "", - }, - } - dependency_report = _run_metadata_dependency_search(project_root, args) - if dependency_report is not None: - return dependency_report, { - "reason": "dependency-metadata-prefilter", - "fields": { - "paths": 0, - "dependency": args.query or "", - }, - } - workspace_seed_report = _run_workspace_seed_metadata_search(project_root, args) - if workspace_seed_report is not None: - return workspace_seed_report, { - "reason": "workspace-seed-metadata-route", - "fields": {"paths": 0, "view": args.view or ""}, - } - metadata_report = _run_metadata_only_search(project_root, args) - if metadata_report is not None: - return metadata_report, { - "reason": "knowledge-metadata-route", - "fields": {"paths": 0, "view": args.view or ""}, - } - from ._rule_packs import resolve_project_harness_config - - config = resolve_project_harness_config( - project_root, - None, - rule_packs=None, - ) - if args.command != "search": - from ._runner import run_asp_python - - return run_asp_python(project_root, config=config), None - if config.include_hidden_dir_names: - from ._runner import run_asp_python - - return run_asp_python(project_root, config=config), None - query_terms = _prefilter_query_terms(args) - if query_terms is None: - from ._runner import run_asp_python - - return run_asp_python(project_root, config=config), None - from ._semantic_search_prefilter import prefilter_python_text_search_paths - - prefilter = prefilter_python_text_search_paths( - project_root, - query_terms, - owner_path=args.owner_path, - ) - if prefilter is None: - from ._runner import run_asp_python - - return run_asp_python(project_root, config=config), None - return _run_prefiltered_text_search(project_root, prefilter.paths), ( - prefilter.runtime_cost() - ) - - -def _prefilter_query_terms(args: ProtocolArgs) -> tuple[str, ...] | None: - if args.view == "lexical": - return args.query_set or ( - () if args.query is None else tuple(args.query.split()) - ) - if ( - args.view == "reasoning" - and args.query == "query-deps" - and args.item_query is not None - and args.dependency is not None - ): - return (args.item_query, args.dependency) - if args.view in _QUERY_PREFILTER_VIEWS and args.query is not None: - return (args.query,) - return None - - -def _run_exact_owner_items_search( - project_root: Path, - args: ProtocolArgs, -) -> _TextSearchReport | None: - owner_path = _exact_owner_items_path(project_root, args) - if owner_path is None: - return None - from python_lang_parser.parser import parse_python_file - - return _TextSearchReport( - modules=(parse_python_file(owner_path),), - project_resolution=_fast_owner_items_scope(project_root, owner_path), - root_paths=(str(owner_path),), - ) - - -def _run_exact_owner_search( - project_root: Path, - args: ProtocolArgs, -) -> _TextSearchReport | None: - owner_path = _exact_owner_path(project_root, args) - if owner_path is None: - return None - from python_lang_parser.parser import parse_python_file - - paths = _exact_owner_related_paths(project_root, owner_path) - return _TextSearchReport( - modules=tuple(parse_python_file(path) for path in paths), - project_resolution=_fast_text_search_scope(project_root), - root_paths=tuple(str(path) for path in paths), - ) - - -def _run_metadata_dependency_search( - project_root: Path, - args: ProtocolArgs, -) -> _TextSearchReport | None: - if ( - args.command != "search" - or args.view not in {"dependency", "deps"} - or args.query is None - or "::" in args.query - ): - return None - from ._project_metadata import read_python_project_metadata - - return _TextSearchReport( - modules=(), - project_resolution=_TextSearchScope( - project_root=project_root, - project_metadata=read_python_project_metadata(project_root), - fallback_paths=(project_root,), - ), - root_paths=(str(project_root),), - ) - - -def _run_metadata_only_search( - project_root: Path, args: ProtocolArgs -) -> _TextSearchReport | None: - if args.command != "search" or args.view not in _KNOWLEDGE_VIEWS: - return None - from ._project_metadata import read_python_project_metadata - - return _TextSearchReport( - modules=(), - project_resolution=_TextSearchScope( - project_root=project_root, - project_metadata=read_python_project_metadata(project_root), - fallback_paths=(project_root,), - ), - root_paths=(str(project_root),), - ) - - -def _run_workspace_seed_metadata_search( - project_root: Path, args: ProtocolArgs -) -> _TextSearchReport | None: - if ( - args.command != "search" - or args.view != "workspace" - or args.render_mode != "seeds" - ): - return None - from ._project_metadata import read_python_project_metadata - - return _TextSearchReport( - modules=(), - project_resolution=_TextSearchScope( - project_root=project_root, - project_metadata=read_python_project_metadata(project_root), - fallback_paths=(project_root,), - ), - root_paths=(str(project_root),), - ) - - -def _exact_owner_items_path(project_root: Path, args: ProtocolArgs) -> Path | None: - if ( - args.command != "search" - or args.view != "owner" - or "items" not in args.pipes - or _owner_items_query_path(args) is None - ): - return None - raw_path = Path(_owner_items_query_path(args) or "") - owner_path = raw_path if raw_path.is_absolute() else project_root / raw_path - try: - resolved_root = project_root.resolve() - resolved_owner = owner_path.resolve() - resolved_owner.relative_to(resolved_root) - except ValueError: - return None - if not resolved_owner.is_file() or resolved_owner.suffix != ".py": - return None - return resolved_owner - - -def _exact_owner_path(project_root: Path, args: ProtocolArgs) -> Path | None: - if ( - args.command != "search" - or args.view != "owner" - or args.pipes - or _owner_query_path(args) is None - ): - return None - return _resolve_project_python_file( - project_root, Path(_owner_query_path(args) or "") - ) - - -def _owner_items_query_path(args: ProtocolArgs) -> str | None: - return args.owner_path or args.query - - -def _owner_query_path(args: ProtocolArgs) -> str | None: - return args.query - - -def _resolve_project_python_file(project_root: Path, raw_path: Path) -> Path | None: - owner_path = raw_path if raw_path.is_absolute() else project_root / raw_path - try: - resolved_root = project_root.resolve() - resolved_owner = owner_path.resolve() - resolved_owner.relative_to(resolved_root) - except ValueError: - return None - if not resolved_owner.is_file() or resolved_owner.suffix != ".py": - return None - return resolved_owner - - -def _exact_owner_related_paths( - project_root: Path, owner_path: Path -) -> tuple[Path, ...]: - project_root = project_root.resolve() - owner_path = owner_path.resolve() - paths = [owner_path] - try: - owner_module = owner_path.relative_to(project_root).with_suffix("") - except ValueError: - return tuple(paths) - owner_parts = tuple(part for part in owner_module.parts if part != "__init__") - if owner_parts[:1] == ("src",): - owner_parts = owner_parts[1:] - module_tail = ".".join(owner_parts) - if not module_tail: - return tuple(paths) - import_markers = ( - f"import {module_tail}", - f"from {module_tail} import", - f"from .{owner_path.stem} import", - ) - for path in sorted(project_root.rglob("*.py")): - if path == owner_path or any(part.startswith(".") for part in path.parts): - continue - try: - text = path.read_text(encoding="utf-8") - except OSError: - continue - if any(marker in text for marker in import_markers): - paths.append(path) - return tuple(dict.fromkeys(paths)) - - -@dataclass(frozen=True, slots=True) -class _TextSearchReport: - modules: tuple[object, ...] - project_resolution: _TextSearchScope - findings: tuple[object, ...] = () - root_paths: tuple[str, ...] = () - - -@dataclass(frozen=True, slots=True) -class _TextSearchScope: - project_root: Path - source_paths: tuple[Path, ...] = () - test_paths: tuple[Path, ...] = () - project_metadata: object | None = None - project_paths: tuple[Path, ...] = () - extra_paths: tuple[Path, ...] = () - include_tests: bool = True - fallback_paths: tuple[Path, ...] = () - - @property - def monitored_paths(self) -> tuple[Path, ...]: - selected = ( - (*self.source_paths, *self.test_paths, *self.extra_paths) - if self.include_tests - else (*self.source_paths, *self.extra_paths) - ) - return selected or self.fallback_paths - - -def _run_prefiltered_text_search( - project_root: Path, - paths: tuple[Path, ...], -) -> _TextSearchReport: - from python_lang_parser.parser import parse_python_file - - return _TextSearchReport( - modules=tuple(parse_python_file(path) for path in paths), - project_resolution=_fast_text_search_scope(project_root), - root_paths=tuple(str(path) for path in paths), - ) - - -def _fast_text_search_scope(project_root: Path) -> _TextSearchScope: - source_paths = tuple( - path for name in ("src",) for path in (project_root / name,) if path.exists() - ) - test_paths = tuple( - path for name in ("tests",) for path in (project_root / name,) if path.exists() - ) - return _TextSearchScope( - project_root=project_root, - source_paths=source_paths, - test_paths=test_paths, - fallback_paths=(project_root,), - ) - - -def _fast_owner_items_scope(project_root: Path, owner_path: Path) -> _TextSearchScope: - return _TextSearchScope( - project_root=project_root, - fallback_paths=(owner_path,), - ) diff --git a/src/asp_python/_discovery.py b/src/asp_python/_discovery.py index 8be4fee..ca5ceeb 100644 --- a/src/asp_python/_discovery.py +++ b/src/asp_python/_discovery.py @@ -55,11 +55,8 @@ def _iter_python_file_candidates( continue if path.is_dir(): candidates.extend( - candidate - for candidate in path.rglob("*.py") - if is_scannable_python_file( - candidate, - scan_root=path, + _iter_python_directory_candidates( + path, ignored_dir_names=ignored_dir_names, include_hidden_dir_names=include_hidden_dir_names, ) @@ -67,6 +64,39 @@ def _iter_python_file_candidates( return tuple(candidates) +def _iter_python_directory_candidates( + scan_root: Path, + *, + ignored_dir_names: frozenset[str], + include_hidden_dir_names: frozenset[str], +) -> tuple[Path, ...]: + """Walk a root without descending into ignored project environments.""" + + candidates: list[Path] = [] + for directory, dir_names, file_names in scan_root.walk(): + dir_names[:] = sorted( + name + for name in dir_names + if not _ignored_path_part( + name, + ignored_dir_names, + include_hidden_dir_names, + ) + ) + for file_name in sorted(file_names): + if not file_name.endswith(".py"): + continue + candidate = directory / file_name + if is_scannable_python_file( + candidate, + scan_root=scan_root, + ignored_dir_names=ignored_dir_names, + include_hidden_dir_names=include_hidden_dir_names, + ): + candidates.append(candidate) + return tuple(candidates) + + def asp_python_paths( project_root: str | Path, *, diff --git a/src/asp_python/_evidence_graph_turbo.py b/src/asp_python/_evidence_graph_turbo.py index 7f908e9..00309bd 100644 --- a/src/asp_python/_evidence_graph_turbo.py +++ b/src/asp_python/_evidence_graph_turbo.py @@ -40,7 +40,7 @@ def build_python_evidence_analysis_request(project_root: Path) -> dict[str, Any] "queryTerms": ["python evidence quality"], "profile": "evidence-quality", "algorithm": "typed-ppr-diverse", - "seedIds": _analysis_seed_ids(analysis_graph), + "entryNodeIds": _analysis_seed_ids(analysis_graph), "budget": 8, "producer": graph["producer"], "project": _analysis_project(project_root.resolve(), graph), diff --git a/src/asp_python/_projection_batch.py b/src/asp_python/_projection_batch.py index 66a2a90..7fc0c16 100644 --- a/src/asp_python/_projection_batch.py +++ b/src/asp_python/_projection_batch.py @@ -30,7 +30,7 @@ def project_projection_batch(request: dict[str, object]) -> dict[str, object]: """Project one structured resident-runtime request with the native AST.""" owners, _auxiliary_owners = _decode_request(request) - projected = [_project_owner(owner, request) for owner in owners] + projected = [_project_owner_isolated(owner, request) for owner in owners] response = { "schemaId": _RESPONSE_SCHEMA_ID, "schemaVersion": "1", @@ -42,6 +42,28 @@ def project_projection_batch(request: dict[str, object]) -> dict[str, object]: return response +def _project_owner_isolated( + owner: _OwnerFrame, header: dict[str, object] +) -> dict[str, object]: + try: + return _project_owner(owner, header) + except (SyntaxError, UnicodeDecodeError) as error: + message = str(error).strip() or "Python parser rejected the source owner" + return { + "ownerPath": owner.path, + "sourceLeafDigest": owner.digest, + "projectionState": "syntax-unavailable", + "diagnostic": { + "schemaId": "agent.semantic-protocols.provider-language-projection-diagnostic", + "schemaVersion": "1", + "reasonKind": "source-syntax-unavailable", + "message": message[:4096], + }, + "items": [], + "relations": [], + } + + def _decode_request( request: dict[str, object], ) -> tuple[list[_OwnerFrame], list[_OwnerFrame]]: @@ -128,6 +150,8 @@ def _project_owner(owner: _OwnerFrame, header: dict[str, object]) -> dict[str, o return { "ownerPath": owner.path, "sourceLeafDigest": owner.digest, + "projectionState": "ready", + "diagnostic": None, "items": items, "relations": [], } diff --git a/src/asp_python/_runner.py b/src/asp_python/_runner.py index 6d9e55c..f9b86d0 100644 --- a/src/asp_python/_runner.py +++ b/src/asp_python/_runner.py @@ -2,11 +2,13 @@ from __future__ import annotations +from concurrent.futures import ThreadPoolExecutor from dataclasses import replace from pathlib import Path from typing import TYPE_CHECKING from python_lang_parser._diagnostic_model import PythonDiagnosticSeverity +from python_lang_parser.model import PythonModuleReport from python_lang_parser.parser import parse_python_file from ._discovery import asp_python_scope, discover_python_files @@ -154,9 +156,8 @@ def run_python_lang_harness( for path in root_paths: if not path.exists(): raise ValueError(f"harness path does not exist: {path}") - modules = tuple( - parse_python_file(path) - for path in discover_python_files( + modules = _parse_python_files( + discover_python_files( root_paths, ignored_dir_names=selected_config.ignored_dir_names, include_hidden_dir_names=selected_config.include_hidden_dir_names, @@ -178,6 +179,15 @@ def run_python_lang_harness( ) +def _parse_python_files(paths: Sequence[Path]) -> tuple[PythonModuleReport, ...]: + """Parse independent files concurrently while retaining discovery order.""" + + if len(paths) < 2: + return tuple(parse_python_file(path) for path in paths) + with ThreadPoolExecutor(thread_name_prefix="asp-python-parse") as executor: + return tuple(executor.map(parse_python_file, paths)) + + def assert_python_lang_harness_clean( paths: Sequence[str | Path], *, diff --git a/src/asp_python/_semantic_language.py b/src/asp_python/_semantic_language.py index 7b53a03..879e8d6 100644 --- a/src/asp_python/_semantic_language.py +++ b/src/asp_python/_semantic_language.py @@ -5,8 +5,6 @@ from typing import Any from . import _semantic_language_ids as ids -from ._semantic_language_benchmark import python_search_benchmark_invocation -from ._semantic_language_catalog import python_search_view_descriptors from ._semantic_language_invocation import attach_semantic_language_invocations from ._semantic_language_query import python_query_method_descriptors from ._semantic_language_schemas import python_semantic_language_schemas @@ -20,11 +18,6 @@ _PYTHON_AST_PATCH_METHODS = ("ast-patch/dry-run",) _PYTHON_EVIDENCE_METHODS = ("evidence/graph", "evidence/analyze") _PYTHON_AGENT_METHODS = ("agent/doctor", "agent/guide") -_PYTHON_SEARCH_VIEW_DESCRIPTORS = python_search_view_descriptors() -_PYTHON_SEARCH_VIEWS = tuple( - descriptor["view"] for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS -) -_PYTHON_SEARCH_METHODS = tuple(f"search/{view}" for view in _PYTHON_SEARCH_VIEWS) def semantic_language_registry_document() -> dict[str, Any]: @@ -51,7 +44,6 @@ def python_semantic_language_registration() -> dict[str, Any]: "namespace": ids.PYTHON_PROVIDER_NAMESPACE, "displayName": "Python", "methods": [ - *_PYTHON_SEARCH_METHODS, *_PYTHON_QUERY_METHODS, *_PYTHON_AST_PATCH_METHODS, *_PYTHON_EVIDENCE_METHODS, @@ -66,10 +58,7 @@ def python_semantic_language_registration() -> dict[str, Any]: def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: """Return method descriptors for the Python provider registry.""" - descriptors = [ - _python_search_method_descriptor(descriptor) - for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS - ] + descriptors: list[dict[str, Any]] = [] descriptors.extend(python_query_method_descriptors()) descriptors.extend( { @@ -126,70 +115,3 @@ def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: ] ) return attach_semantic_language_invocations(descriptors) - - -def _python_search_method_descriptor(descriptor: dict[str, Any]) -> dict[str, Any]: - rendered = { - **descriptor, - "benchmarkInvocation": python_search_benchmark_invocation( - str(descriptor["view"]) - ), - "outputSchemaIds": _search_output_schema_ids(descriptor["view"]), - "supportsJson": True, - "supportsCompact": True, - } - if descriptor["view"] == "semantic-facts": - rendered["supportsCompact"] = False - rendered["outputModes"] = ["json"] - rendered["packetSchemas"] = [ - "semantic-fact-graph.v1", - "semantic-fact-ontology.v1", - ] - rendered["input"] = "search semantic-facts " - return rendered - - -def _search_output_schema_ids(view: str) -> list[str]: - if view == "semantic-facts": - return [ids.SEMANTIC_FACT_GRAPH_SCHEMA_ID] - schema_ids = [ids.SEMANTIC_SEARCH_PACKET_SCHEMA_ID] - if view == "public-external-types": - schema_ids.append(ids.SEMANTIC_TYPE_SURFACE_SCHEMA_ID) - if view == "policy": - schema_ids.append("agent.semantic-protocols.semantic-handle") - return schema_ids - - -def python_semantic_search_view_descriptor(view: str) -> dict[str, Any] | None: - if view == "dependency-topology": - return { - "method": "search/dependency-topology", - "command": "search", - "view": "dependency-topology", - "requiresQuery": False, - "acceptsStdin": False, - "supportsPackageScope": True, - "capabilities": [ - { - "languageId": "python", - "namespace": "semantic", - "name": "dependency-topology", - } - ], - } - """Return the registry descriptor for one search view.""" - - return next( - ( - descriptor - for descriptor in _PYTHON_SEARCH_VIEW_DESCRIPTORS - if descriptor["view"] == view - ), - None, - ) - - -def is_python_semantic_search_view(view: str) -> bool: - """Return whether a view is implemented by the Python provider.""" - - return python_semantic_search_view_descriptor(view) is not None diff --git a/src/asp_python/_semantic_language_benchmark.py b/src/asp_python/_semantic_language_benchmark.py deleted file mode 100644 index 2542f94..0000000 --- a/src/asp_python/_semantic_language_benchmark.py +++ /dev/null @@ -1,104 +0,0 @@ -"""Provider-owned large-library benchmark invocation templates.""" - -from __future__ import annotations - -from collections.abc import Callable -from typing import Any - -_QUERY_VIEWS = { - "api", - "public-external-types", - "policy", - "symbol", - "callsite", - "import", - "pattern", - "compare", -} - - -def python_search_benchmark_invocation(view: str) -> dict[str, Any]: - """Return the parser-valid public search command for one registry view.""" - builder = _SPECIAL_VIEW_BUILDERS.get(view) - if builder is not None: - return builder() - if view in {"dependency", "deps"}: - return _seed_invocation(view, "{dependency}") - if view in _QUERY_VIEWS: - return _seed_invocation(view, "{query}") - return _seed_invocation(view) - - -def _owner_invocation() -> dict[str, Any]: - return _seed_invocation("owner", "{owner}", "items", "--query", "{query}") - - -def _lexical_invocation() -> dict[str, Any]: - return _seed_invocation("lexical", "--query", "{query}", "--query", "{dependency}") - - -def _tests_invocation() -> dict[str, Any]: - return _seed_invocation("tests", "{owner}") - - -def _reasoning_invocation() -> dict[str, Any]: - return _seed_invocation( - "reasoning", - "query-deps", - "--query", - "{query}", - "--dependency", - "{dependency}", - ) - - -def _ingest_invocation() -> dict[str, Any]: - return { - **_seed_invocation("ingest"), - "stdinTemplate": "{owner}:1:{query}\\n", - } - - -def _semantic_facts_invocation() -> dict[str, Any]: - return { - "args": ["search", "semantic-facts", "{query}", *_workspace(), "--json"], - "expectsJson": True, - "maxElapsedMs": 15_000, - } - - -def _extension_invocation() -> dict[str, Any]: - return _seed_invocation("extension", "{dependency}") - - -def _public_external_types_invocation() -> dict[str, Any]: - return _seed_invocation("public-external-types", "{dependency}") - - -def _policy_invocation() -> dict[str, Any]: - return _seed_invocation("policy", "PY-AGENT-POLICY-001") - - -def _seed_invocation(view: str, *args: str) -> dict[str, Any]: - return { - "args": ["search", view, *args, *_workspace(), "--view", "seeds"], - "expectsJson": False, - "maxElapsedMs": 15_000, - } - - -def _workspace() -> list[str]: - return ["--workspace", "{workspace}"] - - -_SPECIAL_VIEW_BUILDERS: dict[str, Callable[[], dict[str, Any]]] = { - "owner": _owner_invocation, - "lexical": _lexical_invocation, - "tests": _tests_invocation, - "reasoning": _reasoning_invocation, - "ingest": _ingest_invocation, - "semantic-facts": _semantic_facts_invocation, - "extension": _extension_invocation, - "public-external-types": _public_external_types_invocation, - "policy": _policy_invocation, -} diff --git a/src/asp_python/_semantic_language_catalog.py b/src/asp_python/_semantic_language_catalog.py deleted file mode 100644 index ac307c6..0000000 --- a/src/asp_python/_semantic_language_catalog.py +++ /dev/null @@ -1,225 +0,0 @@ -"""Search method descriptors for the Python semantic-language provider.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Any - -_PYTHON_LANGUAGE_ID = "python" - - -def python_search_view_descriptors() -> list[dict[str, Any]]: - """Return implemented Python search view descriptors.""" - - descriptors = [ - _view( - "workspace", - capabilities=[ - _semantic("workspace-router"), - _python("python-package-root-search"), - ], - ), - _view( - "prime", - capabilities=[ - _semantic("package-prime-map"), - _python("python-reasoning-tree-prime"), - _python("python-entry-point-search"), - ], - ), - _view( - "owner", - requires_query=True, - accepted_pipes=["items"], - capabilities=[ - _semantic("reasoning-owner-search"), - _python("parser-visible-module-owner-search"), - _python("python-owner-item-query"), - _python("pytest-test-owner-search"), - _semantic("path-owner-fallback"), - ], - fallbacks=[ - { - "name": "owner-top-items", - "trigger": "item-query-miss", - "appliesToPipes": ["items"], - "maxItems": 4, - } - ], - ingest_required_for=[_ingest("non-parser-path")], - ), - _view( - "dependency", - requires_query=True, - capabilities=[ - _semantic("dependency-manifest-search"), - _python("dependency-local-usage-search"), - ], - ), - _view( - "deps", - requires_query=True, - capabilities=[ - _semantic("dependency-manifest-search"), - _python("dependency-local-usage-search"), - _semantic("dependency-version-scope"), - _python("dependency-api-token-usage-search"), - ], - ), - _view( - "api", - requires_query=True, - capabilities=[ - _python("exported-api-shape-search"), - _python("public-function-api-shape-search"), - _python("public-data-api-shape-search"), - _semantic("dependency-version-scope"), - ], - ingest_required_for=[_ingest("external-api-docs")], - ), - _view( - "public-external-types", - requires_query=True, - capabilities=[ - _semantic("dependency-manifest-search"), - _python("public-external-type-search"), - _python("public-api-type-text-search"), - ], - ), - _view( - "policy", - requires_query=True, - accepted_pipes=["owner", "tests"], - capabilities=[ - _semantic("policy-rule-handle-search"), - _python("python-project-policy-rule-handle-search"), - _python("python-agent-policy-rule-handle-search"), - ], - ), - _view( - "symbol", - requires_query=True, - capabilities=[_python("symbol-definition-search")], - ), - _view( - "callsite", - requires_query=True, - capabilities=[_python("owner-callsite-search")], - ), - _view( - "import", - requires_query=True, - capabilities=[_python("import-edge-search")], - ), - _view( - "tests", - requires_query=True, - capabilities=[_python("pytest-test-owner-search")], - ), - _view( - "lexical", - requires_query=True, - accepted_pipes=["owner", "tests"], - supports_query_set=True, - accepted_query_set_selectors=["lexical-set"], - query_set_scopes=["project", "owner"], - capabilities=[ - _semantic("lexical-candidate-search"), - _python("parser-visible-source-lexical-search"), - ], - ingest_required_for=[ - _ingest("non-parser-text"), - _ingest("docs-text"), - _ingest("schema-json"), - _ingest("generated-artifact"), - ], - ), - _view( - "reasoning", - requires_query=True, - capabilities=[ - _semantic("reasoning-owner-search"), - _semantic("dependency-manifest-search"), - _python("python-owner-item-query"), - _python("pytest-test-owner-search"), - _python("dependency-local-usage-search"), - ], - ), - _view( - "ingest", - accepts_stdin=True, - accepted_pipes=["items", "tests"], - capabilities=[ - _semantic("external-candidate-ingest"), - _semantic("stdin-shape-detection"), - _semantic("owner-grouped-ingest"), - ], - ), - _view( - "semantic-facts", - requires_query=True, - accepts_stdin=True, - capabilities=[ - _semantic("graph-turbo-provider-facts"), - _python("python-ast-field-type-collection-facts"), - ], - ), - ] - from ._semantic_language_knowledge import python_knowledge_search_view_descriptors - - return ( - descriptors[:-2] + python_knowledge_search_view_descriptors() + descriptors[-2:] - ) - - -def _view( - view: str, - *, - capabilities: Sequence[dict[str, str]], - requires_query: bool = False, - accepts_stdin: bool = False, - accepted_pipes: Sequence[str] = (), - supports_query_set: bool = False, - accepted_query_set_selectors: Sequence[str] = (), - query_set_scopes: Sequence[str] = (), - fallbacks: Sequence[dict[str, object]] = (), - ingest_required_for: Sequence[dict[str, str]] = (), -) -> dict[str, Any]: - descriptor: dict[str, Any] = { - "method": f"search/{view}", - "command": "search", - "view": view, - "requiresQuery": requires_query, - "acceptsStdin": accepts_stdin, - "supportsPackageScope": True, - "capabilities": list(capabilities), - } - if accepted_pipes: - descriptor["acceptedPipes"] = list(accepted_pipes) - if supports_query_set: - descriptor["supportsQuerySet"] = True - if accepted_query_set_selectors: - descriptor["acceptedQuerySetSelectors"] = list(accepted_query_set_selectors) - if query_set_scopes: - descriptor["querySetScopes"] = list(query_set_scopes) - if fallbacks: - descriptor["fallbacks"] = list(fallbacks) - if ingest_required_for: - descriptor["ingestRequiredFor"] = list(ingest_required_for) - return descriptor - - -def _semantic(name: str) -> dict[str, str]: - return _capability("semantic", name) - - -def _python(name: str) -> dict[str, str]: - return _capability(_PYTHON_LANGUAGE_ID, name) - - -def _ingest(name: str) -> dict[str, str]: - return _capability(_PYTHON_LANGUAGE_ID, name) - - -def _capability(namespace: str, name: str) -> dict[str, str]: - return {"languageId": _PYTHON_LANGUAGE_ID, "namespace": namespace, "name": name} diff --git a/src/asp_python/_semantic_language_knowledge.py b/src/asp_python/_semantic_language_knowledge.py deleted file mode 100644 index e753fc8..0000000 --- a/src/asp_python/_semantic_language_knowledge.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Knowledge-axis search descriptors for the Python semantic-language provider.""" - -from __future__ import annotations - -from collections.abc import Sequence -from typing import Any - -_PYTHON_LANGUAGE_ID = "python" - - -def python_knowledge_search_view_descriptors() -> list[dict[str, Any]]: - """Return provider-owned language and ecosystem knowledge search views.""" - - return [ - _view( - "env", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-project-environment-facts"), - ], - ), - _view( - "runtime-source", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-runtime-source-frontier"), - ], - ), - _view( - "lang", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-language-semantics-facts"), - ], - ), - _view( - "std", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-standard-api-facts"), - ], - ), - _view( - "capability", - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-provider-capability-facts"), - ], - ), - _view( - "extension", - requires_query=True, - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-ecosystem-extension-facts"), - ], - ), - _view( - "pattern", - requires_query=True, - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-executable-pattern-facts"), - ], - ), - _view( - "compare", - requires_query=True, - capabilities=[ - _semantic("provider-knowledge-axis"), - _python("python-semantic-comparison-facts"), - ], - ), - ] - - -def _view( - view: str, - *, - capabilities: Sequence[dict[str, str]], - requires_query: bool = False, -) -> dict[str, Any]: - return { - "method": f"search/{view}", - "command": "search", - "view": view, - "requiresQuery": requires_query, - "acceptsStdin": False, - "supportsPackageScope": True, - "capabilities": list(capabilities), - } - - -def _semantic(name: str) -> dict[str, str]: - return _capability("semantic", name) - - -def _python(name: str) -> dict[str, str]: - return _capability(_PYTHON_LANGUAGE_ID, name) - - -def _capability(namespace: str, name: str) -> dict[str, str]: - return {"languageId": _PYTHON_LANGUAGE_ID, "namespace": namespace, "name": name} diff --git a/src/asp_python/_semantic_search.py b/src/asp_python/_semantic_search.py deleted file mode 100644 index e35dd8e..0000000 --- a/src/asp_python/_semantic_search.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Public semantic-search facade for the Python provider.""" - -from __future__ import annotations - -from ._semantic_search_model import PythonSemanticSearchOptions -from ._semantic_search_packet import build_python_semantic_search_packet -from ._semantic_search_render import ( - render_python_semantic_search_packet, - render_python_semantic_search_packet_json, -) - -__all__ = [ - "PythonSemanticSearchOptions", - "build_python_semantic_search_packet", - "render_python_semantic_search_packet", - "render_python_semantic_search_packet_json", -] diff --git a/src/asp_python/_semantic_search_callsite_hits.py b/src/asp_python/_semantic_search_callsite_hits.py deleted file mode 100644 index 0361ab0..0000000 --- a/src/asp_python/_semantic_search_callsite_hits.py +++ /dev/null @@ -1,71 +0,0 @@ -"""Callsite hit builders for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import dedupe_hits, location_from_source -from ._semantic_search_deps import module_owner_path - -if TYPE_CHECKING: - from python_lang_parser import PythonCall - - from ._model import AspPythonReport - - -def callsite_hits( - report: AspPythonReport, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return parser-owned Python callsite hits.""" - - query_folded = query.casefold() - hits = [ - callsite_hit(call, module_owner_path(module, project_root), project_root) - for module in report.modules - for call in module.calls - if _call_matches(call, query_folded) - ] - return dedupe_hits(hits) - - -def callsite_hit( - call: PythonCall, - owner_path: str, - project_root: Path, -) -> dict[str, Any]: - """Return one callsite hit.""" - - fields: dict[str, Any] = { - "scope": call.scope or "module", - "effect": call.effect.value, - "positional": call.positional_count, - } - if call.keyword_names: - fields["keywords"] = list(call.keyword_names) - if call.expression: - fields["expr"] = call.expression - return { - "kind": "callsite", - "ownerPath": owner_path, - "location": location_from_source(call.location, project_root), - "score": _callsite_score(call), - "reason": "call-expression", - "symbol": call.function, - "fields": fields, - } - - -def _call_matches(call: PythonCall, query_folded: str) -> bool: - if not query_folded: - return False - haystacks = (call.function, call.function.rsplit(".", 1)[-1]) - return any(query_folded in item.casefold() for item in haystacks if item) - - -def _callsite_score(call: PythonCall) -> int: - if "." not in call.function: - return 4 - return 3 diff --git a/src/asp_python/_semantic_search_cli.py b/src/asp_python/_semantic_search_cli.py deleted file mode 100644 index 5848704..0000000 --- a/src/asp_python/_semantic_search_cli.py +++ /dev/null @@ -1,432 +0,0 @@ -"""CLI parsing helpers for Python semantic-search commands.""" - -from __future__ import annotations - -from dataclasses import dataclass, field -from pathlib import Path - -from ._semantic_language import python_semantic_search_view_descriptor - - -@dataclass(slots=True) -class ParsedSemanticSearchArgs: - view: str | None = None - query: str | None = None - item_query: str | None = None - project_root: Path | None = None - package_path: Path | None = None - workspace: bool = False - owner_path: str | None = None - dependency: str | None = None - query_set: tuple[str, ...] = () - pipes: tuple[str, ...] = () - json: bool = False - render_mode: str | None = None - error: str | None = None - - -@dataclass(slots=True) -class _SearchOptionState: - positionals: list[str] = field(default_factory=list) - query_set: list[str] = field(default_factory=list) - item_query: str | None = None - json: bool = False - render_mode: str | None = None - package_path: Path | None = None - workspace: bool = False - workspace_root: Path | None = None - owner_path: str | None = None - dependency: str | None = None - - -@dataclass(slots=True) -class _ConsumedOption: - advance: int = 1 - error: str | None = None - - -def parse_semantic_search_args( - args: list[str] | tuple[str, ...], -) -> ParsedSemanticSearchArgs: - view, descriptor, error = _search_view_descriptor(args) - if error is not None or view is None or descriptor is None: - return ParsedSemanticSearchArgs(error=error) - state, error = _parse_search_option_state(view, args[1:]) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - error = _validate_search_option_state(view, state) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - return _search_args_for_descriptor(view, descriptor, state) - - -def _search_view_descriptor( - args: list[str] | tuple[str, ...], -) -> tuple[str | None, dict[str, object] | None, str | None]: - view = args[0] if args else None - if view is None or view in {"--help", "-h"}: - return None, None, _semantic_search_usage() - descriptor = python_semantic_search_view_descriptor(view) - if descriptor is None: - return view, None, f"unknown search view: {view}" - return view, descriptor, None - - -def _semantic_search_usage() -> str: - return ( - "usage: asp-python search " - " " - "... [--json] [--package PATH] [--workspace ]; " - "dependency/deps are manifest-first, import-usage backed, and cache hashes not raw source" - ) - - -def _validate_search_option_state( - view: str, - state: _SearchOptionState, -) -> str | None: - if state.query_set and not _search_view_supports_query_bundle(view): - return f"search {view} does not support repeated --query" - if state.item_query is not None and view not in {"owner", "reasoning"}: - return "--query is only supported by search owner items" - if state.owner_path is not None and view not in {"lexical", "reasoning"}: - return "--owner is only supported by search lexical or reasoning" - if state.dependency is not None and view != "reasoning": - return "--dependency is only supported by search reasoning" - return None - - -def _search_args_for_descriptor( - view: str, - descriptor: dict[str, object], - state: _SearchOptionState, -) -> ParsedSemanticSearchArgs: - accepted_pipes = descriptor.get("acceptedPipes", ()) - normalized_pipes = ( - accepted_pipes if isinstance(accepted_pipes, tuple | list) else () - ) - if view == "lexical": - return _optional_query_args(view, state, normalized_pipes) - if descriptor["requiresQuery"]: - return _required_query_args(view, descriptor, state) - return _project_only_args(view, descriptor, state) - - -def _parse_search_option_state( - view: str, - args: list[str] | tuple[str, ...], -) -> tuple[_SearchOptionState, str | None]: - state = _SearchOptionState() - index = 0 - while index < len(args): - consumed = _consume_search_option(view, args, index, state) - if consumed.error is not None: - return state, consumed.error - index += consumed.advance - return state, None - - -def _consume_search_option( - view: str, - args: list[str] | tuple[str, ...], - index: int, - state: _SearchOptionState, -) -> _ConsumedOption: - arg = args[index] - if _is_flag_like_literal_search_query( - view, state.positionals, state.query_set, arg - ): - state.positionals.append(arg) - return _ConsumedOption() - - match arg: - case "--json": - state.json = True - case "--view": - value = _optional_arg(args, index + 1) - if value not in {"graph", "hits", "both", "seeds"}: - return _ConsumedOption( - error="--view requires graph, hits, both, or seeds", - ) - state.render_mode = value - return _ConsumedOption(advance=2) - case "--package": - value = _optional_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--package requires a package path") - state.package_path = Path(value) - return _ConsumedOption(advance=2) - case "--workspace": - value = _optional_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--workspace requires a workspace root") - state.workspace = True - state.workspace_root = Path(value) - return _ConsumedOption(advance=2) - case "--owner": - value = _literal_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--owner requires an owner path") - state.owner_path = value - return _ConsumedOption(advance=2) - case "--dependency": - value = _literal_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--dependency requires a dependency") - state.dependency = value - return _ConsumedOption(advance=2) - case "--query": - value = _literal_arg(args, index + 1) - if value is None: - return _ConsumedOption(error="--query requires a query term") - if view == "lexical": - state.query_set.append(value) - return _ConsumedOption(advance=2) - state.item_query = value - return _ConsumedOption(advance=2) - case _ if arg.startswith("-"): - return _ConsumedOption(error=f"unknown search option: {arg}") - case _: - state.positionals.append(arg) - return _ConsumedOption() - - -def _required_query_args( - view: str, - descriptor: dict[str, object], - state: _SearchOptionState, -) -> ParsedSemanticSearchArgs: - if _search_view_accepts_optional_terms(view): - return _required_term_query_args(view, state) - - query = ( - ",".join(state.query_set) - if state.query_set - else (state.positionals[0] if state.positionals else None) - ) - if query is None: - return ParsedSemanticSearchArgs(error=f"search {view} requires a query") - - accepted_pipes = descriptor.get("acceptedPipes", ()) - pipes, project_root, error = _parse_search_pipe_positionals( - state.positionals if state.query_set else state.positionals[1:], - accepted_pipes if isinstance(accepted_pipes, tuple | list) else (), - ) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - if project_root is not None: - return ParsedSemanticSearchArgs( - error="search does not accept positional WORKSPACE; use --workspace ", - ) - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - query=query, - item_query=state.item_query, - owner_path=state.owner_path, - dependency=state.dependency, - query_set=tuple(state.query_set), - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - pipes=tuple(pipes), - json=state.json, - render_mode=state.render_mode, - ) - - -def _required_term_query_args( - view: str, - state: _SearchOptionState, -) -> ParsedSemanticSearchArgs: - query = " ".join(state.positionals) - if not query: - return ParsedSemanticSearchArgs(error=f"search {view} requires a query") - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - query=query, - item_query=state.item_query, - owner_path=state.owner_path, - dependency=state.dependency, - query_set=tuple(state.query_set), - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - json=state.json, - render_mode=state.render_mode, - ) - - -def _project_only_args( - view: str, - descriptor: dict[str, object], - state: _SearchOptionState, -) -> ParsedSemanticSearchArgs: - accepted_pipes = descriptor.get("acceptedPipes", ()) - normalized_pipes = ( - accepted_pipes if isinstance(accepted_pipes, tuple | list) else () - ) - if _search_view_accepts_optional_terms(view): - return _optional_query_args( - view, - state, - normalized_pipes, - ) - pipes, project_root, error = _parse_search_pipe_positionals( - state.positionals, - normalized_pipes, - ) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - if project_root is not None: - return ParsedSemanticSearchArgs( - error="search does not accept positional WORKSPACE; use --workspace ", - ) - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - pipes=tuple(pipes), - json=state.json, - render_mode=state.render_mode, - ) - - -def _optional_query_args( - view: str, - state: _SearchOptionState, - accepted_pipes: list[str] | tuple[str, ...], -) -> ParsedSemanticSearchArgs: - if view == "lexical": - if state.query_set: - query = ",".join(state.query_set) - positionals = state.positionals - elif state.positionals: - query = state.positionals[0] - positionals = state.positionals[1:] - else: - query = None - positionals = [] - pipes, project_root, error = _parse_search_pipe_positionals( - positionals, - accepted_pipes, - ) - if error is not None: - return ParsedSemanticSearchArgs(error=error) - if project_root is not None: - return ParsedSemanticSearchArgs( - error="search does not accept positional WORKSPACE; use --workspace ", - ) - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - query=query, - owner_path=state.owner_path, - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - query_set=tuple(state.query_set), - pipes=tuple(pipes), - json=state.json, - render_mode=state.render_mode, - ) - project_root = ( - str(state.workspace_root) if state.workspace_root is not None else None - ) - return ParsedSemanticSearchArgs( - view=view, - query=" ".join(state.positionals) if state.positionals else None, - project_root=None if project_root is None else Path(project_root), - package_path=state.package_path, - workspace=state.workspace, - json=state.json, - render_mode=state.render_mode, - ) - - -def _parse_search_pipe_positionals( - positionals: list[str], - accepted_pipes: list[str] | tuple[str, ...], -) -> tuple[list[str], str | None, str | None]: - pipes: list[str] = [] - index = 0 - while index < len(positionals) and index < len(accepted_pipes): - if positionals[index] != accepted_pipes[index]: - break - pipes.append(positionals[index]) - index += 1 - remaining = positionals[index:] - if len(remaining) > 1: - looks_like_out_of_order_pipe = ( - index < len(accepted_pipes) and remaining[0] in accepted_pipes - ) - if not accepted_pipes or not looks_like_out_of_order_pipe: - return ( - pipes, - remaining[0], - "search does not accept positional WORKSPACE; use --workspace ", - ) - return ( - pipes, - remaining[0], - f"expected pipes ({','.join(accepted_pipes)}) before workspace selector", - ) - return pipes, (remaining[0] if remaining else None), None - - -def _optional_arg(args: list[str] | tuple[str, ...], index: int) -> str | None: - if index >= len(args): - return None - value = args[index] - if value.startswith("-"): - return None - return value - - -def _literal_arg(args: list[str] | tuple[str, ...], index: int) -> str | None: - return None if index >= len(args) else args[index] - - -def _is_flag_like_literal_search_query( - view: str, - positionals: list[str], - query_set: list[str], - arg: str, -) -> bool: - return ( - view == "lexical" - and not positionals - and not query_set - and arg.startswith("-") - and not arg.startswith("--") - and arg != "-h" - ) - - -def _search_view_supports_query_bundle(view: str) -> bool: - return view == "lexical" - - -def _search_view_accepts_optional_terms(view: str) -> bool: - return view in { - "env", - "runtime-source", - "lang", - "std", - "capability", - "extension", - "pattern", - "compare", - "lexical", - } diff --git a/src/asp_python/_semantic_search_common.py b/src/asp_python/_semantic_search_common.py deleted file mode 100644 index 0145d07..0000000 --- a/src/asp_python/_semantic_search_common.py +++ /dev/null @@ -1,170 +0,0 @@ -"""Small helpers shared by Python semantic-search modules.""" - -from __future__ import annotations - -import json -import re -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._render import _render_display_path -from ._semantic_search_model import Fields, FieldValue - -if TYPE_CHECKING: - from collections.abc import Iterable - - from python_lang_parser import SourceLocation - - -def semantic_search_display_path(path: str | Path, project_root: Path) -> str: - """Return an agent-facing path relative to the project root when possible.""" - - return _render_display_path(path, project_root=project_root) - - -def location( - path: str, - line: int | None = None, - column: int | None = None, -) -> dict[str, Any]: - """Return a schema-compatible semantic-search location.""" - - payload: dict[str, Any] = {"path": path} - if line is not None and line > 0: - payload["line"] = line - if column is not None: - payload["column"] = max(1, column) - return payload - - -def location_from_source( - source_location: SourceLocation, - project_root: Path, -) -> dict[str, Any]: - """Return a packet location from a parser source location.""" - - return location( - semantic_search_display_path(source_location.path or ".", project_root), - source_location.line, - source_location.column, - ) - - -def header(view: str, fields: Fields) -> dict[str, Any]: - """Return a semantic-search packet header.""" - - return {"kind": f"search-{view}", "fields": compact_fields(fields)} - - -def compact_fields(fields: Fields) -> Fields: - """Drop empty fields from compact and JSON payloads.""" - - return { - key: value - for key, value in fields.items() - if value is not None and value != [] and value != "" - } - - -def dedupe(values: Iterable[str]) -> list[str]: - """Return values in first-seen order.""" - - seen: set[str] = set() - result: list[str] = [] - for value in values: - if value in seen: - continue - seen.add(value) - result.append(value) - return result - - -def path_hit( - owner_path: str, - path: str, - *, - kind: str = "path", - symbol: str | None = None, - score: int = 2, - reason: str = "path", - fields: Fields | None = None, -) -> dict[str, Any]: - """Return a simple path-like search hit.""" - - hit: dict[str, Any] = { - "kind": kind, - "ownerPath": owner_path, - "location": {"path": path}, - "score": score, - "reason": reason, - } - if symbol is not None: - hit["symbol"] = symbol - if fields: - hit["fields"] = fields - return hit - - -def dedupe_hits(hits: Iterable[dict[str, Any]]) -> list[dict[str, Any]]: - """Return deterministic unique hits.""" - - seen: set[tuple[str, str, str, str]] = set() - result: list[dict[str, Any]] = [] - for hit in hits: - key = ( - hit["kind"], - hit["ownerPath"], - hit.get("symbol", ""), - json.dumps(hit["location"], sort_keys=True), - ) - if key in seen: - continue - seen.add(key) - result.append(hit) - return sorted( - result, key=lambda hit: (-hit["score"], hit["ownerPath"], hit["kind"]) - ) - - -def render_fields(fields: Fields) -> str: - """Render compact `key=value` fields.""" - - return " ".join( - f"{key}={escape_field_value(value)}" - for key, value in fields.items() - if value != [] and value != "" - ) - - -def escape_field_value(value: FieldValue) -> str: - """Render one compact field value.""" - - if isinstance(value, list): - return ",".join(escape_scalar(item) for item in value) - return escape_scalar(value) - - -def escape_scalar(value: str | int | float | bool) -> str: - """Render one compact scalar.""" - - text = str(value).lower() if isinstance(value, bool) else str(value) - if re.search(r"[\s,=]", text): - return json.dumps(text) - return text - - -def render_location(search_location: dict[str, Any]) -> str: - """Render a compact location.""" - - fields: Fields = {"path": search_location["path"]} - if "line" in search_location and "column" in search_location: - fields["line"] = search_location["line"] - fields["column"] = search_location["column"] - return render_fields(fields) - if "line" in search_location: - fields["line"] = search_location["line"] - elif "lineRange" in search_location: - line_range = str(search_location["lineRange"]) - start, _, end = line_range.partition(":") - fields["line"] = start if start and (not end or start == end) else line_range - return render_fields(fields) diff --git a/src/asp_python/_semantic_search_deps.py b/src/asp_python/_semantic_search_deps.py deleted file mode 100644 index d2b1df3..0000000 --- a/src/asp_python/_semantic_search_deps.py +++ /dev/null @@ -1,140 +0,0 @@ -"""Dependency-specific Python semantic-search facts.""" - -from __future__ import annotations - -import re -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import location_from_source -from ._semantic_search_model import Fields - -if TYPE_CHECKING: - from collections.abc import Sequence - - from python_lang_parser import PythonProjectDependency - - from ._model import AspPythonReport - - -def dependency_node( - dependency: PythonProjectDependency, - *, - parts: dict[str, str] | None = None, -) -> dict[str, Any]: - """Return one dependency node for a search packet.""" - - fields: Fields = { - "requirement": dependency.requirement, - "source": dependency.source, - "versionScope": version_scope(parts or {}, [dependency]), - } - if dependency.group: - fields["group"] = dependency.group - if dependency.extra: - fields["extra"] = dependency.extra - if parts is not None and parts.get("requestedVersion"): - fields["requestedVersion"] = parts["requestedVersion"] - if parts is not None and parts.get("apiQuery"): - fields["apiQuery"] = parts["apiQuery"] - return {"id": f"D:{dependency.name}", "kind": "dependency", "fields": fields} - - -def dependency_query_parts(query: str) -> dict[str, str]: - """Parse `` into compact fields.""" - - package_part, _, api_query = query.partition("::") - package_name = package_part - requested_version = "" - if "@" in package_part and not package_part.startswith("@"): - package_name, requested_version = package_part.rsplit("@", 1) - elif "==" in package_part: - package_name, requested_version = package_part.split("==", 1) - return { - "package": normalize_dependency_name(package_name.strip()), - "requestedVersion": requested_version.strip(), - "apiQuery": api_query.strip(), - } - - -def normalize_dependency_name(value: str) -> str: - """Normalize Python dependency names for query matching.""" - - return re.sub(r"[-_.]+", "-", value).casefold() - - -def dependency_matches(dependency: PythonProjectDependency, query: str) -> bool: - """Return whether a metadata dependency matches a normalized query.""" - - return query in { - normalize_dependency_name(dependency.name), - normalize_dependency_name(dependency.requirement.split()[0]), - } - - -def version_scope( - parts: dict[str, str], - matches: Sequence[PythonProjectDependency], -) -> str: - """Return the current/external/unknown version scope label.""" - - if not matches: - return "external" if parts.get("requestedVersion") else "unknown" - requested = parts.get("requestedVersion", "") - if not requested: - return "current" - return ( - "current" - if any(requested in item.requirement for item in matches) - else "external" - ) - - -def dependency_usage_hits( - report: AspPythonReport, - project_root: Path, - package_query: str, -) -> list[dict[str, Any]]: - """Return local import usage hits for a dependency name.""" - - hits: list[dict[str, Any]] = [] - for module in report.modules: - owner_path = module_owner_path(module, project_root) - for import_record in module.imports: - root_name = import_root(import_record.module, import_record.names) - if root_name is None: - continue - if normalize_dependency_name(root_name) != package_query: - continue - hits.append( - { - "kind": "dependency", - "ownerPath": owner_path, - "location": location_from_source( - import_record.location, project_root - ), - "score": 3, - "reason": "import-usage", - "symbol": root_name, - "fields": {"scope": import_record.scope or "module"}, - } - ) - return hits - - -def module_owner_path(module, project_root: Path) -> str: - """Return a display path for a parsed module.""" - - from ._semantic_search_common import semantic_search_display_path - - return semantic_search_display_path(module.path or ".", project_root) - - -def import_root(module: str | None, names) -> str | None: - """Return the root package named by a Python import statement.""" - - if module: - return module.split(".", 1)[0] - if names: - return names[0].split(".", 1)[0] - return None diff --git a/src/asp_python/_semantic_search_findings.py b/src/asp_python/_semantic_search_findings.py deleted file mode 100644 index b4d9065..0000000 --- a/src/asp_python/_semantic_search_findings.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Finding facts for Python semantic-search packets.""" - -from __future__ import annotations - -from collections import Counter -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import ( - location_from_source, - semantic_search_display_path, -) -from ._semantic_search_model import MAX_FINDINGS - -if TYPE_CHECKING: - from ._model import AspPythonFinding, AspPythonReport - - -def finding_facts( - report: AspPythonReport, - project_root: Path, - *, - owner_paths: set[str] | None = None, -) -> list[dict[str, Any]]: - """Return grouped harness findings.""" - - counter: Counter[tuple[str, str, str, str]] = Counter() - finding_by_key: dict[tuple[str, str, str, str], AspPythonFinding] = {} - for finding in report.findings: - path = semantic_search_display_path(finding.location.path or ".", project_root) - if owner_paths is not None and path not in owner_paths: - continue - key = (finding.rule_id, finding.severity.value, finding.title, path) - counter[key] += 1 - finding_by_key[key] = finding - return [ - { - "ruleId": finding_by_key[key].rule_id, - "severity": finding_by_key[key].severity.value, - "count": count, - "title": finding_by_key[key].title, - "location": location_from_source( - finding_by_key[key].location, project_root - ), - } - for key, count in counter.most_common(MAX_FINDINGS) - ] diff --git a/src/asp_python/_semantic_search_graph_render.py b/src/asp_python/_semantic_search_graph_render.py deleted file mode 100644 index 879e731..0000000 --- a/src/asp_python/_semantic_search_graph_render.py +++ /dev/null @@ -1,107 +0,0 @@ -"""Delegate Python compact graph rendering to the shared protocol binary.""" - -from __future__ import annotations - -import json -import os -import subprocess -from collections.abc import Callable -from typing import Any - -from ._semantic_search_common import escape_field_value -from ._semantic_search_render_lines import render_next_action - -DEFAULT_GRAPH_SEED_LIMIT = 8 -SEMANTIC_AGENT_PROTOCOL_BIN_ENV = "SEMANTIC_AGENT_PROTOCOL_BIN" -GRAPH_NATIVE_NEXT_ACTION_KINDS = frozenset({"owner", "tests"}) - - -class CompactGraphRenderError(RuntimeError): - """Raised when the shared compact graph renderer cannot produce output.""" - - -def compact_graph_seed_packet_text( - packet: dict[str, Any], - render_fields: Callable[[dict[str, Any]], str], -) -> str: - del render_fields - rendered = render_compact_graph_packet( - graph_render_packet(packet), - seed_limit=DEFAULT_GRAPH_SEED_LIMIT, - ) - flow_lines = compact_graph_flow_lines(packet) - if not flow_lines: - return rendered - return f"{rendered.rstrip()}\n" + "\n".join(flow_lines) + "\n" - - -def graph_render_packet(packet: dict[str, Any]) -> dict[str, Any]: - """Return the packet projection expected by the graph-only renderer.""" - - next_actions = [ - action - for action in packet.get("nextActions", []) - if action.get("kind") in GRAPH_NATIVE_NEXT_ACTION_KINDS - ] - if len(next_actions) == len(packet.get("nextActions", [])): - return packet - return {**packet, "nextActions": next_actions} - - -def compact_graph_flow_lines(packet: dict[str, Any]) -> list[str]: - """Render non-graph flow hints after compact graph output.""" - - lines = [ - f"|note kind={note['kind']} message={escape_field_value(note['message'])}" - for note in packet.get("notes", []) - ] - non_graph_actions = [ - action - for action in packet.get("nextActions", []) - if action.get("kind") not in GRAPH_NATIVE_NEXT_ACTION_KINDS - ] - if non_graph_actions: - lines.append( - "|next " - + ",".join(render_next_action(action) for action in non_graph_actions) - ) - return lines - - -def render_compact_graph_packet( - packet: dict[str, Any], - *, - seed_limit: int = DEFAULT_GRAPH_SEED_LIMIT, -) -> str: - command = [ - os.environ.get(SEMANTIC_AGENT_PROTOCOL_BIN_ENV, "asp"), - "graph", - "render", - "--packet", - "-", - "--view", - "seeds", - "--seeds", - str(seed_limit), - ] - try: - completed = subprocess.run( - command, - input=json.dumps(packet, separators=(",", ":")), - text=True, - capture_output=True, - check=True, - ) - except FileNotFoundError as exc: - raise CompactGraphRenderError( - "asp graph renderer not found; " - f"set {SEMANTIC_AGENT_PROTOCOL_BIN_ENV} or install " - "asp on PATH" - ) from exc - except subprocess.CalledProcessError as exc: - stderr = exc.stderr.strip() - detail = f": {stderr}" if stderr else "" - raise CompactGraphRenderError( - f"asp graph render failed with exit code {exc.returncode}{detail}" - ) from exc - return completed.stdout diff --git a/src/asp_python/_semantic_search_hits.py b/src/asp_python/_semantic_search_hits.py deleted file mode 100644 index 73da3ec..0000000 --- a/src/asp_python/_semantic_search_hits.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Public hit-builder facade for Python semantic search.""" - -from __future__ import annotations - -from ._semantic_search_callsite_hits import callsite_hits -from ._semantic_search_import_test_hits import import_hits, test_path_hits -from ._semantic_search_symbol_hits import api_hits, symbol_hit, symbol_hits -from ._semantic_search_text_hits import text_hits - -__all__ = [ - "api_hits", - "callsite_hits", - "import_hits", - "symbol_hit", - "symbol_hits", - "test_path_hits", - "text_hits", -] diff --git a/src/asp_python/_semantic_search_import_routes.py b/src/asp_python/_semantic_search_import_routes.py deleted file mode 100644 index e25c186..0000000 --- a/src/asp_python/_semantic_search_import_routes.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Import-definition route candidates for Python owner item queries.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import semantic_search_display_path - -if TYPE_CHECKING: - from python_lang_parser import PythonModuleReport - - from ._project_policy_context import AspPythonReport - - -def import_definition_routes( - report: AspPythonReport, - project_root: Path, - module: PythonModuleReport, - terms: list[str], -) -> list[dict[str, str]]: - owner_paths = tuple(_report_owner_paths(report, project_root)) - routes: list[dict[str, str]] = [] - seen: set[tuple[str, str, str]] = set() - for term in terms: - routes.extend(_routes_for_term(term, module.imports, owner_paths, seen)) - return routes - - -def _routes_for_term( - term: str, - imports: object, - owner_paths: tuple[str, ...], - seen: set[tuple[str, str, str]], -) -> list[dict[str, str]]: - routes: list[dict[str, str]] = [] - for imported in imports: - route = _route_for_import(term, imported, owner_paths) - if route is None: - continue - key = (route["term"], route["ownerPath"], route["query"]) - if key in seen: - continue - seen.add(key) - routes.append(route) - return routes - - -def _route_for_import( - term: str, - imported: Any, - owner_paths: tuple[str, ...], -) -> dict[str, str] | None: - source_name = _import_source_name_for_term(imported, term) - if source_name is None: - return None - owner_path = _import_target_owner(owner_paths, imported) - if owner_path is None: - return None - return {"term": term, "ownerPath": owner_path, "query": source_name} - - -def _report_owner_paths( - report: AspPythonReport, - project_root: Path, -) -> list[str]: - owner_paths: list[str] = [] - for report_module in report.modules: - path = report_module.path - if path is None: - continue - owner_paths.append(semantic_search_display_path(path, project_root)) - return owner_paths - - -def _import_source_name_for_term(imported: Any, term: str) -> str | None: - names = tuple(str(name) for name in getattr(imported, "names", ())) - source_names = tuple(str(name) for name in getattr(imported, "source_names", ())) - if term in names: - index = names.index(term) - if index < len(source_names) and source_names[index] != "*": - return source_names[index] - return term - if term in source_names and term != "*": - return term - return None - - -def _import_target_owner(owner_paths: tuple[str, ...], imported: Any) -> str | None: - module_name = getattr(imported, "module", None) - if not isinstance(module_name, str) or not module_name: - return None - candidates = _module_owner_suffixes(module_name) - return next( - (owner_path for owner_path in owner_paths if owner_path.endswith(candidates)), - None, - ) - - -def _module_owner_suffixes(module_name: str) -> tuple[str, str]: - module_suffix = module_name.replace(".", "/") - return (f"{module_suffix}.py", f"{module_suffix}/__init__.py") diff --git a/src/asp_python/_semantic_search_import_test_hits.py b/src/asp_python/_semantic_search_import_test_hits.py deleted file mode 100644 index 2fca847..0000000 --- a/src/asp_python/_semantic_search_import_test_hits.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Import and test hit builders for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import location_from_source, path_hit -from ._semantic_search_deps import module_owner_path -from .verification.facts import is_test_path - -if TYPE_CHECKING: - from ._model import AspPythonReport - - -def import_hits( - report: AspPythonReport, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return raw import-statement hits.""" - - query_folded = query.casefold() - return [ - _import_hit( - import_record, module_owner_path(module, project_root), project_root - ) - for module in report.modules - for import_record in module.imports - if _import_matches(import_record, query_folded) - ] - - -def test_path_hits( - report: AspPythonReport, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return test path/function hits.""" - - query_folded = query.casefold() - return [ - path_hit(owner_path, owner_path, kind="test", score=3, reason="test-path") - for module in report.modules - if is_test_path(owner_path := module_owner_path(module, project_root)) - if _test_module_matches(module, owner_path, query_folded) - ] - - -def _import_hit(import_record, owner_path: str, project_root: Path) -> dict[str, Any]: - return { - "kind": "import", - "ownerPath": owner_path, - "location": location_from_source(import_record.location, project_root), - "score": 3, - "reason": "import-statement", - "symbol": import_record.module or ",".join(import_record.names), - "fields": {"scope": import_record.scope or "module"}, - } - - -def _import_matches(import_record, query_folded: str) -> bool: - haystacks = [ - import_record.module or "", - *import_record.names, - *import_record.source_names, - ] - return any(query_folded in item.casefold() for item in haystacks) - - -def _test_module_matches(module, owner_path: str, query_folded: str) -> bool: - if query_folded in owner_path.casefold(): - return True - return any(query_folded in symbol.name.casefold() for symbol in module.symbols) diff --git a/src/asp_python/_semantic_search_ingest.py b/src/asp_python/_semantic_search_ingest.py deleted file mode 100644 index 0e114bd..0000000 --- a/src/asp_python/_semantic_search_ingest.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Stdin ingest helpers for Python semantic search.""" - -from __future__ import annotations - -import os -import re -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import location - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - -def ingest_hits( - facts: PythonReasoningTreeFacts, - project_root: Path, - stdin: str, -) -> tuple[dict[str, Any], list[dict[str, Any]]]: - """Detect stdin shape and return owner-grouped hits.""" - - detection, records = detect_ingest_records(stdin) - owner_paths = { - _display_path(node.path, project_root) for node in facts.nodes if node.is_valid - } - hits: list[dict[str, Any]] = [] - for record in records: - owner_path = ingest_owner_path(record["path"], owner_paths) - hits.append( - { - "kind": "text", - "ownerPath": owner_path, - "location": location(record["path"], record.get("line")), - "score": 2, - "reason": f"ingest-{detection['source']}", - "snippet": record.get("text", ""), - "fields": {"source": "ingest"}, - } - ) - return detection, hits - - -def detect_ingest_records(stdin: str) -> tuple[dict[str, Any], list[dict[str, Any]]]: - """Detect rg/vimgrep/path-list stdin shapes.""" - - byte_count = len(stdin.encode()) - line_count = ( - 0 if not stdin else stdin.count("\n") + (0 if stdin.endswith("\n") else 1) - ) - source = "unknown" - records: list[dict[str, Any]] = [] - if "\0" in stdin: - source = "path-list-nul" - records = [{"path": path} for path in stdin.split("\0") if path] - else: - lines = [line for line in stdin.splitlines() if line.strip()] - if lines and all(looks_like_path(line) for line in lines): - source = "path-list" - records = [{"path": line.strip()} for line in lines] - else: - parsed = [parse_rg_line(line) for line in lines] - records = [record for record in parsed if record is not None] - if records: - source = ( - "vimgrep" - if any("column" in record for record in records) - else "rg-n" - ) - elif lines and all(line.lstrip().startswith("{") for line in lines[:3]): - source = "rg-json" - sample = {"sample": stdin[:160]} if stdin else {} - return { - "source": source, - "lineCount": line_count, - "byteCount": byte_count, - **sample, - }, records - - -def parse_rg_line(line: str) -> dict[str, Any] | None: - """Parse `rg -n` or vimgrep-like output.""" - - match = re.match( - r"^(?P.*?):(?P\d+)(?::(?P\d+))?:(?P.*)$", line - ) - if match is None: - return None - return { - "path": match.group("path"), - "line": int(match.group("line")), - **( - {"column": int(match.group("column"))} - if match.group("column") is not None - else {} - ), - "text": match.group("text").strip(), - } - - -def looks_like_path(line: str) -> bool: - """Return whether a line looks like a plain path list entry.""" - - stripped = line.strip() - return ( - stripped.endswith(".py") or os.sep in stripped or stripped.startswith(".") - ) and ":" not in stripped - - -def ingest_owner_path(path: str, owner_paths: set[str]) -> str: - """Map an ingested path back to a parser-visible owner when possible.""" - - normalized = path.removeprefix("./") - if normalized in owner_paths: - return normalized - matches = sorted( - ( - owner_path - for owner_path in owner_paths - if normalized == owner_path - or normalized.startswith(owner_path.rstrip("/") + "/") - ), - key=len, - reverse=True, - ) - return matches[0] if matches else normalized - - -def _display_path(path: str, project_root: Path) -> str: - from ._semantic_search_common import semantic_search_display_path - - return semantic_search_display_path(path, project_root) diff --git a/src/asp_python/_semantic_search_ingest_fast.py b/src/asp_python/_semantic_search_ingest_fast.py deleted file mode 100644 index 985489c..0000000 --- a/src/asp_python/_semantic_search_ingest_fast.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Fast empty-stdin guidance for Python search ingest seed view.""" - -from __future__ import annotations - -from pathlib import Path - -from ._cli_args import ProtocolArgs - -_STDIN_REQUIRED_NOTE = ( - '|note kind=stdin-required message="search ingest consumes stdin candidate ' - 'paths; use search prime --view seeds for project discovery"' -) -_EMPTY_STDIN_NEXT = ( - '|next prime:"search prime --view seeds"(scope=project-discovery),' - 'ingest:"pipe candidate paths into search ingest items tests --view seeds"' - "(scope=stdin-candidates)" -) - - -def render_fast_empty_ingest_search( - args: ProtocolArgs, - project_root: Path, - stdin: str, -) -> str | None: - """Render empty ingest seed guidance without running the full harness.""" - - del project_root - if not _supports_fast_empty_ingest(args, stdin): - return None - return _render_fast_empty_ingest_seed_text(args) - - -def _supports_fast_empty_ingest(args: ProtocolArgs, stdin: str) -> bool: - return ( - args.command == "search" - and args.view == "ingest" - and args.render_mode == "seeds" - and not args.json - and stdin == "" - and args.query is None - and args.item_query is None - and args.owner_path is None - and not args.query_set - ) - - -def _render_fast_empty_ingest_seed_text(args: ProtocolArgs) -> str: - root = args.project_root.as_posix() if args.project_root is not None else "." - lines = [ - f"[search-ingest] root={root or '.'} alg=seed-frontier", - "legend: ID=kind:role(value)!next; edge SRC>{DST:rel}; frontier ID.next", - "aliases: graph:{G=search}", - "G>{}", - "rank= frontier=", - _STDIN_REQUIRED_NOTE, - _EMPTY_STDIN_NEXT, - ] - return "\n".join(lines) + "\n" diff --git a/src/asp_python/_semantic_search_item_lines.py b/src/asp_python/_semantic_search_item_lines.py deleted file mode 100644 index 215ecfa..0000000 --- a/src/asp_python/_semantic_search_item_lines.py +++ /dev/null @@ -1,174 +0,0 @@ -"""Line rendering for Python owner item query results.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import compact_fields, render_fields -from ._semantic_search_items import owner_item_query_payload - -if TYPE_CHECKING: - from ._project_policy_context import AspPythonReport - - -def owner_item_query_lines( - report: AspPythonReport, - project_root: Path, - owner_path: str, - item_query: str, - *, - names_only: bool = False, -) -> str: - """Render compact owner item query lines for the top-level query command.""" - - payload = owner_item_query_payload(report, project_root, owner_path, item_query) - return owner_item_payload_lines(owner_path, item_query, payload, names_only) - - -def owner_item_payload_lines( - owner_path: str, - item_query: str, - payload: dict[str, Any], - names_only: bool = False, -) -> str: - """Render compact owner item query lines from a precomputed payload.""" - - lines = [_header_line(owner_path, item_query, payload, names_only)] - lines.append(_query_line(item_query, payload, names_only)) - lines.extend(_note_lines(payload)) - lines.extend(_item_lines(payload["items"], names_only)) - return "\n".join(lines) - - -def _header_line( - owner_path: str, item_query: str, payload: dict[str, Any], names_only: bool -) -> str: - return _search_owner_header_line( - owner_path, item_query, payload, names_only - ).replace( - "[search-owner]", - "[query-item]", - 1, - ) - - -def _search_owner_header_line( - owner_path: str, - item_query: str, - payload: dict[str, Any], - names_only: bool, -) -> str: - fields = payload["fields"] - output_fields = compact_fields( - { - "q": owner_path, - "pkg": ".", - "own": 1, - "item": len(payload["items"]), - "itemQuery": item_query, - "output": "names" if names_only else None, - "fallback": fields.get("fallback"), - } - ) - return f"[search-owner] {render_fields(output_fields)}" - - -def _query_line( - item_query: str, - payload: dict[str, Any], - names_only: bool, -) -> str: - fields = payload["fields"] - item_count = fields.get("item") - query_fields = compact_fields( - { - "itemQuery": item_query, - "status": fields.get("itemStatus"), - "match": fields.get("itemMatch"), - "item": item_count, - "reason": "parser-item-query", - "output": "names" if names_only else None, - "next": fields.get("next") - or ( - "revise-query" - if fields.get("itemStatus") == "miss" or not item_count - else "select-item" - if names_only and isinstance(item_count, int) and item_count > 1 - else "query-code" - ), - } - ) - return f"|query {render_fields(query_fields)}" - - -def _note_lines(payload: dict[str, Any]) -> list[str]: - lines: list[str] = [] - for note in payload.get("notes", []): - if not isinstance(note, dict): - continue - lines.append( - "|note " - + render_fields( - compact_fields( - { - "kind": note.get("kind"), - "message": note.get("message"), - } - ) - ) - ) - return lines - - -def _item_lines(items: list[dict[str, Any]], names_only: bool) -> list[str]: - return [_item_summary_line(item) for item in items] - - -def _item_summary_line(item: dict[str, Any]) -> str: - item_fields = item.get("fields", {}) - item_name = item["name"] - return f"|item {item['name']} " + render_fields( - compact_fields( - { - "kind": item.get("kind"), - "public": True if item_fields.get("public") is True else None, - "doc": True if item_fields.get("doc") is True else None, - "next": f"syntax:{item_name}", - "read": item_fields.get("read"), - "syn": _syntax_atom_for_kind(item.get("kind")), - "tsqRef": "semantic-tree-sitter-query/python-owner-items.v1", - } - ) - ) - - -def _syntax_atom_for_kind(kind: object) -> str | None: - if kind == "function": - return "function_definition/name" - if kind == "class": - return "class_definition/name" - if kind == "import": - return "import_statement/name" - if kind == "import-from": - return "import_from_statement/name" - return None - - -def _item_code_line(item: dict[str, Any], names_only: bool) -> str | None: - item_fields = item.get("fields", {}) - location = item.get("location", {}) - code = item_fields.get("code") - if names_only or not isinstance(code, str) or not code: - return None - return "|code " + render_fields( - compact_fields( - { - "path": location.get("path"), - "lineRange": location.get("lineRange"), - "reason": "item-query", - "truncated": False, - "text": code, - } - ) - ) diff --git a/src/asp_python/_semantic_search_items.py b/src/asp_python/_semantic_search_items.py deleted file mode 100644 index bf6403c..0000000 --- a/src/asp_python/_semantic_search_items.py +++ /dev/null @@ -1,375 +0,0 @@ -"""Parser-owned compact item extraction for Python owner searches.""" - -from __future__ import annotations - -from collections.abc import Iterable -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from . import _semantic_language_ids as ids -from ._python_compact import compact_python_item -from ._semantic_query_packet import ( - semantic_import_route_next, - semantic_query_coverage, - semantic_query_match, - semantic_query_match_mode, -) -from ._semantic_search_common import compact_fields, semantic_search_display_path -from ._semantic_search_import_routes import import_definition_routes -from ._semantic_search_model import MAX_OWNER_QUERY_ITEMS -from ._semantic_selector_identity import python_structural_selector_identity - -if TYPE_CHECKING: - from python_lang_parser import PythonModuleReport, PythonSymbol - - from ._model import AspPythonReport - - -def owner_item_query_payload( - report: AspPythonReport, - project_root: Path, - owner_path: str, - item_query: str | None, -) -> dict[str, Any]: - """Return compact parser item facts for one owner path.""" - - module = _module_for_owner(report, project_root, owner_path) - if module is None: - return { - "items": [], - "fields": {"item": 0, "itemStatus": "miss", "itemMatch": "none"}, - "notes": [{"kind": "owner-not-found", "message": owner_path}], - } - - symbols = _sorted_symbols(module) - terms = _query_terms(item_query) - selected, match = _select_symbols(module, symbols, terms) - import_routes = ( - import_definition_routes(report, project_root, module, terms) - if terms and match != "exact" - else [] - ) - fallback = False - if import_routes: - selected = [] - match = "candidate" - elif not selected: - selected = [symbol for symbol in symbols if symbol.is_top_level][ - :MAX_OWNER_QUERY_ITEMS - ] - match = "none" if terms else "top-items" - fallback = bool(terms) - - items = [ - _item_record(module, project_root, owner_path, symbol) - for symbol in selected[:MAX_OWNER_QUERY_ITEMS] - ] - fields: dict[str, object] = { - "item": len(items), - "itemQuery": item_query, - "itemStatus": "hit" if items and not fallback else "miss", - "itemMatch": match if items or import_routes else "none", - "fallback": "owner-top-items" if fallback and items else None, - "next": semantic_import_route_next(import_routes[0]) if import_routes else None, - } - return { - "items": items, - "fields": compact_fields(fields), - "notes": _item_query_notes(item_query, owner_path, items, import_routes), - "importRoutes": import_routes, - } - - -def owner_item_semantic_query_packet( - report: AspPythonReport, - project_root: Path, - owner_path: str, - item_query: str, - *, - output_mode: str, - selector: str | None = None, -) -> dict[str, Any]: - """Return a semantic-query-packet for owner-local Python item lookup.""" - - payload = owner_item_query_payload(report, project_root, owner_path, item_query) - items, fields, import_routes = _selector_resolved_owner_items( - report, - project_root, - owner_path, - selector, - payload, - ) - return _owner_item_semantic_query_packet( - payload, - project_root, - owner_path, - item_query, - output_mode, - items, - fields, - import_routes, - ) - - -def _selector_resolved_owner_items( - report: AspPythonReport, - project_root: Path, - owner_path: str, - selector: str | None, - payload: dict[str, Any], -) -> tuple[list[dict[str, Any]], dict[str, Any], list[Any]]: - items = payload["items"] - fields = payload["fields"] - import_routes = payload.get("importRoutes", []) - module = _module_for_owner(report, project_root, owner_path) - selector_identity = python_structural_selector_identity(selector) - if selector_identity is not None: - selector_owner_path, selector_kind, selector_name = selector_identity - items = ( - [ - _item_record(module, project_root, owner_path, symbol) - for symbol in _sorted_symbols(module) - if symbol.kind.value == selector_kind - and symbol.qualified_name == selector_name - ] - if selector_owner_path == owner_path and module is not None - else [] - ) - fields = { - **fields, - "item": len(items), - "itemStatus": "hit" if items else "miss", - "itemMatch": "exact" if items else "none", - } - import_routes = [] - return items, fields, import_routes - - -def _owner_item_semantic_query_packet( - payload: dict[str, Any], - project_root: Path, - owner_path: str, - item_query: str, - output_mode: str, - items: list[dict[str, Any]], - fields: dict[str, Any], - import_routes: list[Any], -) -> dict[str, Any]: - terms = _query_terms(item_query) - from ._semantic_syntax_refs import ( - annotate_python_owner_item_syntax_refs, - attach_python_syntax_refs, - ) - - syntax_refs = annotate_python_owner_item_syntax_refs(items) - packet = { - "schemaId": ids.SEMANTIC_QUERY_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "protocolId": ids.SEMANTIC_LANGUAGE_PROTOCOL_ID, - "protocolVersion": ids.SEMANTIC_LANGUAGE_PROTOCOL_VERSION, - "languageId": ids.PYTHON_LANGUAGE_ID, - "providerId": ids.PYTHON_PROVIDER_ID, - "binary": ids.PYTHON_BINARY, - "namespace": ids.PYTHON_PROVIDER_NAMESPACE, - "method": "query/owner-items", - "projectRoot": str(project_root), - "ownerPath": owner_path, - "query": item_query, - "queryTerms": terms, - "matchMode": semantic_query_match_mode(str(fields.get("itemMatch", "none"))), - "outputMode": output_mode, - "patchSafety": { - "level": "read-safe", - "reason": "compact query packet is not a mutation authority", - "nextAction": ( - "query --selector --projection source" - ), - }, - "queryCoverage": [ - semantic_query_coverage( - term, - items, - str(fields.get("itemMatch", "none")), - import_routes if isinstance(import_routes, list) else [], - ) - for term in terms - ], - "matches": [ - semantic_query_match(item, include_code=output_mode != "names") - for item in items - ], - "truncated": any( - bool(item.get("fields", {}).get("truncated")) for item in items - ), - "notes": payload.get("notes", []), - } - attach_python_syntax_refs(packet, syntax_refs) - return packet - - -def _module_for_owner( - report: AspPythonReport, - project_root: Path, - owner_path: str, -) -> PythonModuleReport | None: - for module in report.modules: - if ( - module.path is not None - and semantic_search_display_path(module.path, project_root) == owner_path - ): - return module - return None - - -def _sorted_symbols(module: PythonModuleReport) -> list[PythonSymbol]: - return sorted( - module.symbols, - key=lambda symbol: ( - symbol.location.line, - symbol.location.column, - symbol.qualified_name, - ), - ) - - -def _query_terms(item_query: str | None) -> list[str]: - if item_query is None: - return [] - return [term.strip() for term in item_query.split("|") if term.strip()] - - -def _item_query_notes( - item_query: str | None, - owner_path: str, - items: list[dict[str, Any]], - import_routes: list[dict[str, str]], -) -> list[dict[str, str]]: - if import_routes: - route = import_routes[0] - return [ - { - "kind": "imported-definition", - "message": ( - f"{item_query or owner_path} is imported in {owner_path}; " - f"next={semantic_import_route_next(route)}" - ), - } - ] - if items: - return [] - return [{"kind": "item-not-found", "message": item_query or owner_path}] - - -def _select_symbols( - module: PythonModuleReport, - symbols: list[PythonSymbol], - terms: list[str], -) -> tuple[list[PythonSymbol], str]: - if not terms: - return [symbol for symbol in symbols if symbol.is_top_level], "top-items" - exact = _dedupe_symbols( - symbol - for term in terms - for symbol in symbols - if term in {symbol.name, symbol.qualified_name} - ) - if exact: - return exact, "exact" - folded_terms = [term.casefold() for term in terms] - contains = _dedupe_symbols( - symbol - for term in folded_terms - for symbol in symbols - if term in _symbol_query_text(module, symbol) - ) - return contains, "fallback-contains" if contains else "none" - - -def _symbol_query_text(module: PythonModuleReport, symbol: PythonSymbol) -> str: - end_line = symbol.end_line or symbol.location.line - code, _, _ = _compact_code( - module, - str(symbol.location.path or ""), - symbol.location.line, - end_line, - ) - return "\n".join((symbol.name, symbol.qualified_name, code)).casefold() - - -def _dedupe_symbols(symbols: Iterable[PythonSymbol]) -> list[PythonSymbol]: - selected: list[PythonSymbol] = [] - seen: set[tuple[str, int, int]] = set() - for symbol in symbols: - key = (symbol.qualified_name, symbol.location.line, symbol.location.column) - if key in seen: - continue - seen.add(key) - selected.append(symbol) - return selected - - -def _item_record( - module: PythonModuleReport, - project_root: Path, - owner_path: str, - symbol: PythonSymbol, -) -> dict[str, Any]: - end_line = symbol.end_line or symbol.location.line - line_range = f"{symbol.location.line}:{end_line}" - source_locator_hint = f"{owner_path}:{symbol.location.line}:{end_line}" - structural_selector = _structural_selector(owner_path, symbol) - code, truncated, projection_nodes = _compact_code( - module, - owner_path, - symbol.location.line, - end_line, - ) - return { - "name": symbol.qualified_name, - "kind": symbol.kind.value, - "ownerPath": owner_path, - "location": { - "path": owner_path, - "lineRange": line_range, - }, - "fields": compact_fields( - { - "public": symbol.is_public, - "doc": bool(symbol.docstring), - "structuralSelector": structural_selector, - "displayLineRange": line_range, - "sourceLocatorHint": source_locator_hint, - "read": source_locator_hint, - "reason": "item-query", - "truncated": truncated, - "code": code, - "projectionNodes": projection_nodes, - "sourcePath": semantic_search_display_path( - symbol.location.path or owner_path, project_root - ), - } - ), - } - - -def _structural_selector(owner_path: str, symbol: PythonSymbol) -> str: - return f"python://{owner_path}#item/{symbol.kind.value}/{symbol.qualified_name}" - - -def _compact_code( - module: PythonModuleReport, - owner_path: str, - start_line: int, - end_line: int, - *, - max_lines: int = 80, -) -> tuple[str, bool, list[dict[str, Any]]]: - raw_lines = module.source_lines[start_line - 1 : end_line] - compact = compact_python_item(raw_lines, owner_path, start_line) - if compact.projection_nodes: - return compact.code, False, compact.projection_nodes - - truncated = len(raw_lines) > max_lines - selected_lines = raw_lines[:max_lines] - compact = compact_python_item(selected_lines, owner_path, start_line) - return compact.code, truncated, compact.projection_nodes diff --git a/src/asp_python/_semantic_search_knowledge_facts.py b/src/asp_python/_semantic_search_knowledge_facts.py deleted file mode 100644 index 9a649d8..0000000 --- a/src/asp_python/_semantic_search_knowledge_facts.py +++ /dev/null @@ -1,180 +0,0 @@ -"""Provider-owned fact catalog for Python language knowledge axes.""" - -from __future__ import annotations - -from pathlib import Path -from typing import Any - -AxisDetail = dict[str, str] -KnowledgeFact = dict[str, Any] - -_AXIS_DETAILS: dict[str, AxisDetail] = { - "env": { - "authority": "project-environment", - "summary": "Python environment facts from pyproject, package metadata, and import roots.", - "next": "search lang import packaging", - }, - "runtime-source": { - "authority": "local-source", - "summary": "Python provider has no runtime checkout resolver; use owner/query/deps evidence.", - "next": "search deps ", - }, - "lang": { - "authority": "language-rules", - "summary": "Python syntax and runtime semantics visible to ast/tokenize/symtable facts.", - "next": "query guide treesitter", - }, - "std": { - "authority": "standard-library", - "summary": "Python standard-library API and idiom facts for agent code generation.", - "next": "search api ", - }, - "capability": { - "authority": "provider-registry", - "summary": "Python provider method and capability registry facts.", - "next": "guide", - }, - "extension": { - "authority": "ecosystem-extension", - "summary": "Framework or package-specific Python ecosystem extension evidence.", - "next": "search deps ", - }, - "pattern": { - "authority": "executable-pattern", - "summary": "Executable syntax and API patterns backed by owner/deps/tree-sitter evidence.", - "next": "search owner items --query ", - }, - "compare": { - "authority": "semantic-comparison", - "summary": "Compare Python project, dependency, or syntax axes using provider-owned facts.", - "next": "run each side through matching provider axis", - }, -} - -_LANG_FACTS = [ - ("module-import", {"syntax": "import/from", "selector": "query --catalog imports"}), - ("decorator", {"syntax": "@decorator", "selector": "query --catalog declarations"}), - ("class-protocol", {"syntax": "class/Protocol", "selector": "search api Protocol"}), -] - -_STD_FACTS = [ - ("pathlib", {"symbol": "pathlib.Path", "pattern": "filesystem path API"}), - ("dataclasses", {"symbol": "dataclasses.dataclass", "pattern": "record modeling"}), - ("typing", {"symbol": "typing", "pattern": "type contracts and protocols"}), - ("itertools", {"symbol": "itertools", "pattern": "iterator composition"}), -] - -_CAPABILITY_FACTS = [ - ("owner-items", {"command": "search owner items"}), - ("deps", {"command": "search deps "}), - ("tree-sitter", {"command": "query --treesitter-query "}), -] - -_PATTERN_FACTS = [ - ( - "declaration-to-owner-query", - { - "command": "query --catalog declarations then search owner items", - "qualitySignal": "parser-owned declaration before code read", - }, - ), - ( - "dependency-api-usage", - { - "command": "search deps ::", - "qualitySignal": "dependency and local usage evidence", - }, - ), -] - - -def axis_detail(axis: str) -> AxisDetail: - """Return stable packet metadata for a provider knowledge axis.""" - - return _AXIS_DETAILS.get(axis, _AXIS_DETAILS["capability"]) - - -def knowledge_facts( - project_root: Path, axis: str, terms: list[str] -) -> list[KnowledgeFact]: - """Return provider-owned facts for the requested axis.""" - - if axis == "env": - return _env_facts(project_root, terms) - if axis == "runtime-source": - return [] - if axis == "extension": - return _extension_facts(project_root, terms) - if axis == "compare": - return _compare_facts(axis, terms) - return _filter_facts(_static_axis_facts(axis), terms) - - -def _static_axis_facts(axis: str) -> list[KnowledgeFact]: - if axis == "lang": - return _facts(axis, _LANG_FACTS) - if axis == "std": - return _facts(axis, _STD_FACTS) - if axis == "capability": - return _facts(axis, _CAPABILITY_FACTS) - if axis == "pattern": - return _facts(axis, _PATTERN_FACTS) - return [] - - -def _env_facts(project_root: Path, terms: list[str]) -> list[KnowledgeFact]: - candidates = (project_root / "pyproject.toml", project_root / "setup.cfg") - facts = [ - _fact( - path.name, - "env", - path=str(path.relative_to(project_root)), - source="project-config", - ) - for path in candidates - if path.exists() - ] - return _filter_facts(facts, terms) - - -def _extension_facts(project_root: Path, terms: list[str]) -> list[KnowledgeFact]: - pyproject = project_root / "pyproject.toml" - if not pyproject.exists(): - return [] - text = pyproject.read_text(encoding="utf-8", errors="replace") - return [ - _fact("pyproject", "extension", source="pyproject.toml", match=term) - for term in terms - if term and term in text.lower() - ] - - -def _compare_facts(axis: str, terms: list[str]) -> list[KnowledgeFact]: - return [ - _fact( - "compare-query", - axis, - left=terms[0] if len(terms) > 0 else "-", - right=terms[1] if len(terms) > 1 else "-", - route="run each side through the matching provider axis and compare facts", - ) - ] - - -def _facts(axis: str, rows: list[tuple[str, dict[str, str]]]) -> list[KnowledgeFact]: - return [_fact(fact_id, axis, **fields) for fact_id, fields in rows] - - -def _fact(fact_id: str, axis: str, **fields: str) -> KnowledgeFact: - return {"id": fact_id, "fields": {"axis": axis, **fields}} - - -def _filter_facts(facts: list[KnowledgeFact], terms: list[str]) -> list[KnowledgeFact]: - if not terms: - return facts - return [fact for fact in facts if _matches_terms(str(fact), terms)] - - -def _matches_terms(value: str, terms: list[str]) -> bool: - normalized = value.lower() - return any(term in normalized for term in terms) diff --git a/src/asp_python/_semantic_search_lexical_fast.py b/src/asp_python/_semantic_search_lexical_fast.py deleted file mode 100644 index 88ad828..0000000 --- a/src/asp_python/_semantic_search_lexical_fast.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Fast compact frontiers for Python lexical seed views.""" - -from __future__ import annotations - -from pathlib import Path - -from ._cli_args import ProtocolArgs -from ._semantic_search_prefilter import prefilter_python_text_search_paths - - -def render_fast_lexical_seed_search( - args: ProtocolArgs, project_root: Path -) -> str | None: - """Render lexical owner seeds without parsing candidate modules.""" - - if not _supports_fast_lexical_seed_search(args): - return None - query_terms = _fast_lexical_query_terms(args) - prefilter = prefilter_python_text_search_paths( - project_root, - query_terms, - owner_path=args.owner_path, - ) - if prefilter is None or not prefilter.paths: - return None - owners = tuple(_relative_owner_path(path, project_root) for path in prefilter.paths) - return _render_fast_lexical_seed_text(query_terms, owners, prefilter.runtime_cost()) - - -def _supports_fast_lexical_seed_search(args: ProtocolArgs) -> bool: - return ( - args.command == "search" - and args.view == "lexical" - and args.render_mode == "seeds" - and not args.json - and bool(_fast_lexical_query_terms(args)) - and args.pipes in {("owner",), ("owner", "tests")} - and args.item_query is None - and args.dependency is None - ) - - -def _fast_lexical_query_terms(args: ProtocolArgs) -> tuple[str, ...]: - if args.query_set: - return args.query_set - if args.query: - return (args.query,) - return () - - -def _relative_owner_path(path: Path, root: Path) -> str: - try: - return path.relative_to(root).as_posix() - except ValueError: - return path.as_posix() - - -def _render_fast_lexical_seed_text( - query_terms: tuple[str, ...], - owners: tuple[str, ...], - runtime_cost: dict[str, object], -) -> str: - query = ",".join(query_terms) - declarations = [f"Q=query:term({query})!lexical"] - edges = ["Q:matches"] - rank = ["Q"] - for index, owner in enumerate(owners, start=1): - suffix = "" if index == 1 else str(index) - owner_id = f"O{suffix}" - test_id = f"T{suffix}" - declarations.append(f"{owner_id}=owner:path({owner})!owner") - declarations.append(f"{test_id}=test:path({owner})!tests") - edges.extend((f"{owner_id}:selects", f"{test_id}:covers")) - rank.extend((owner_id, test_id)) - return "\n".join( - [ - ( - f"[search-lexical] q={query} querySet={len(query_terms)} " - "selector=lexical-set view=hits alg=query-set-owner-resolution" - ), - "legend: ID=kind:role(value)!next; edge SRC>{DST:rel}; frontier ID.next", - "aliases: graph:{G=search,Q=query,O=owner,T=test}", - ";".join(declarations), - f"G>{{{','.join(edges)}}}", - f"rank={','.join(rank)} frontier={_frontier(rank)}", - "entries=owner-query(O,Q=>items+tests+dependency-usage),owner-tests(O=>covering-tests+test-entrypoints+fixtures)", - f'|note kind=runtime-prefilter message="{runtime_cost["reason"]}"', - "", - ] - ) - - -def _frontier(rank: list[str]) -> str: - return ",".join(f"{item}.{_frontier_kind(item)}" for item in rank) - - -def _frontier_kind(item: str) -> str: - if item == "Q": - return "lexical" - if item.startswith("T"): - return "tests" - return "owner" diff --git a/src/asp_python/_semantic_search_model.py b/src/asp_python/_semantic_search_model.py deleted file mode 100644 index f6630a5..0000000 --- a/src/asp_python/_semantic_search_model.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Shared semantic-search model aliases for the Python provider.""" - -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any - -FieldValue = str | int | float | bool | list[str | int | float | bool] -Fields = dict[str, FieldValue] - -MAX_PRIME_OWNERS = 8 -MAX_PRIME_EDGES = 24 -MAX_WORKSPACE_PACKAGES = 24 -MAX_WORKSPACE_EDGES = 8 -MAX_FINDINGS = 8 -MAX_LEXICAL_HITS = 12 -MAX_SYMBOL_HITS = 20 -MAX_IMPORT_HITS = 30 -MAX_DEPENDENCY_HITS = 24 -MAX_TEST_HITS = 8 -MAX_OWNER_QUERY_ITEMS = 4 - - -@dataclass(frozen=True, slots=True) -class PythonSemanticSearchOptions: - """Options parsed from an `asp-python search` invocation.""" - - view: str - query: str | None = None - item_query: str | None = None - query_set: tuple[str, ...] = () - owner_path: str | None = None - dependency: str | None = None - pipes: tuple[str, ...] = () - render_mode: str | None = None - stdin: str = "" - runtime_cost: dict[str, Any] | None = None diff --git a/src/asp_python/_semantic_search_owner_fast.py b/src/asp_python/_semantic_search_owner_fast.py deleted file mode 100644 index 5d1a26c..0000000 --- a/src/asp_python/_semantic_search_owner_fast.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Fast compact frontiers for Python exact-owner seed view.""" - -from __future__ import annotations - -from pathlib import Path - -from ._cli_args import ProtocolArgs - - -def render_fast_owner_seed_search(args: ProtocolArgs, project_root: Path) -> str | None: - """Render exact owner seeds without parsing the owner file.""" - - owner_path = _fast_owner_path(args, project_root) - if owner_path is None: - return None - owner = _relative_owner_path(owner_path, project_root) - return "\n".join( - [ - f"[search-owner] q={owner} alg=fast-exact-owner-frontier", - "legend: ID=kind:role(value)!next; edge SRC>{DST:rel}; frontier ID.next", - "aliases: graph:{G=search,O=owner,T=test}", - f"O=owner:path({owner})!owner;T=test:path({owner})!tests", - "G>{O:selects,T:covers}", - "rank=O,T frontier=O.owner,T.tests", - "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)", - "", - ] - ) - - -def _fast_owner_path(args: ProtocolArgs, project_root: Path) -> Path | None: - if ( - args.command != "search" - or args.view != "owner" - or args.render_mode != "seeds" - or args.json - or args.query is None - or args.item_query is not None - or args.owner_path is not None - or args.query_set - or args.pipes - ): - return None - raw_path = Path(args.query) - owner_path = raw_path if raw_path.is_absolute() else project_root / raw_path - try: - resolved_root = project_root.resolve() - resolved_owner = owner_path.resolve() - resolved_owner.relative_to(resolved_root) - except (OSError, ValueError): - return None - if not resolved_owner.is_file() or resolved_owner.suffix != ".py": - return None - return resolved_owner - - -def _relative_owner_path(path: Path, root: Path) -> str: - try: - return path.relative_to(root.resolve()).as_posix() - except ValueError: - return path.as_posix() diff --git a/src/asp_python/_semantic_search_owners.py b/src/asp_python/_semantic_search_owners.py deleted file mode 100644 index 412eabb..0000000 --- a/src/asp_python/_semantic_search_owners.py +++ /dev/null @@ -1,179 +0,0 @@ -"""Owner and import-edge facts for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import location, semantic_search_display_path -from ._semantic_search_model import Fields -from .verification.facts import is_test_path - -if TYPE_CHECKING: - from collections.abc import Iterable - - from python_lang_parser import ( - PythonReasoningTreeFacts, - PythonReasoningTreeNode, - ) - - -def owner_nodes(facts: PythonReasoningTreeFacts) -> tuple[PythonReasoningTreeNode, ...]: - """Return parser-valid reasoning-tree owner nodes.""" - - return tuple(node for node in facts.nodes if node.is_valid) - - -def ranked_owner_records( - facts: PythonReasoningTreeFacts, - project_root: Path, -) -> list[dict[str, Any]]: - """Return owners ranked for a prime packet.""" - - records = [owner_record(node, project_root) for node in owner_nodes(facts)] - return sorted( - records, - key=lambda owner: ( - 1 if owner["fields"].get("surface") == "test" else 0, - 0 if owner["public"] else 1, - -int(owner["fields"].get("lines", 0)), - owner["path"].count("/"), - owner["path"], - ), - ) - - -def owner_record(node: PythonReasoningTreeNode, project_root: Path) -> dict[str, Any]: - """Return one semantic-search owner record.""" - - path = semantic_search_display_path(node.path, project_root) - exports = list(node.public_names) - fields: Fields = { - "kind": node.kind, - "surface": "test" if is_test_path(path) else "source", - "doc": node.has_intent_doc, - "lines": node.effective_code_lines, - "exportKind": node.export_contract_kind, - } - if node.child_names: - fields["children"] = list(node.child_names[:8]) - return { - "path": path, - "namespace": ".".join(node.namespace), - "role": _owner_role(node, path), - "public": node.has_public_surface, - "exports": exports, - "nextActions": [ - {"kind": "owner", "target": path}, - {"kind": "tests", "target": path}, - *( - {"kind": "text", "target": name, "ownerPath": path} - for name in exports[:2] - ), - ], - "fields": fields, - } - - -def matching_owner_nodes( - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[PythonReasoningTreeNode]: - """Return owner nodes matching a path, namespace, or public export.""" - - query_folded = query.casefold() - matches = [] - for node in facts.nodes: - path = semantic_search_display_path(node.path, project_root) - namespace = ".".join(node.namespace) - if ( - query_folded in path.casefold() - or query_folded in namespace.casefold() - or any(query_folded in name.casefold() for name in node.public_names) - ): - matches.append(node) - return sorted( - matches, key=lambda node: semantic_search_display_path(node.path, project_root) - ) - - -def owners_for_paths( - facts: PythonReasoningTreeFacts, - project_root: Path, - paths: Iterable[str], -) -> list[dict[str, Any]]: - """Return owner records for display paths.""" - - wanted = set(paths) - return [ - owner_record(node, project_root) - for node in facts.nodes - if semantic_search_display_path(node.path, project_root) in wanted - ] - - -def import_edges( - facts: PythonReasoningTreeFacts, - project_root: Path, - *, - limit: int, -) -> list[dict[str, Any]]: - """Return parser-resolved import edges.""" - - edges = [] - for edge in facts.import_edges[:limit]: - importer = semantic_search_display_path(edge.importer_path, project_root) - imported = semantic_search_display_path(edge.imported_path, project_root) - edges.append( - { - "from": f"O:{importer}", - "kind": "import", - "to": f"O:{imported}", - "location": location(importer, edge.line, edge.column), - "fields": { - "import": edge.import_name, - "bound": edge.bound_name, - "scope": edge.scope or "module", - "relative": edge.is_relative, - }, - } - ) - return edges - - -def test_edges( - facts: PythonReasoningTreeFacts, - project_root: Path, - owner_paths: set[str], -) -> list[dict[str, Any]]: - """Return test-import edges for selected owners.""" - - edges = [] - for edge in facts.import_edges: - importer = semantic_search_display_path(edge.importer_path, project_root) - imported = semantic_search_display_path(edge.imported_path, project_root) - if not is_test_path(importer): - continue - if owner_paths and imported not in owner_paths: - continue - edges.append( - { - "from": f"O:{imported}", - "kind": "test", - "to": f"O:{importer}", - "location": location(importer, edge.line, edge.column), - "fields": {"import": edge.import_name, "scope": edge.scope or "module"}, - } - ) - return edges - - -def _owner_role(node: PythonReasoningTreeNode, path: str) -> str: - if is_test_path(path): - return "test" - if node.parent_namespace is None: - return f"root,{node.kind}" - if node.has_public_surface: - return f"public,{node.kind}" - return node.kind diff --git a/src/asp_python/_semantic_search_packages.py b/src/asp_python/_semantic_search_packages.py deleted file mode 100644 index 7bc2b19..0000000 --- a/src/asp_python/_semantic_search_packages.py +++ /dev/null @@ -1,108 +0,0 @@ -"""Workspace package and dependency facts for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import semantic_search_display_path -from ._semantic_search_model import MAX_WORKSPACE_PACKAGES -from .verification.facts import is_test_path - -if TYPE_CHECKING: - from collections.abc import Sequence - - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import AspPythonReport - - -def project_name(facts: PythonReasoningTreeFacts) -> str: - """Return the project package name used in packet headers.""" - - metadata = facts.project_metadata - if metadata is None or metadata.project_name is None: - return "python-project" - return metadata.project_name - - -def dependencies(facts: PythonReasoningTreeFacts): - """Return parser-owned project dependency declarations.""" - - metadata = facts.project_metadata - return () if metadata is None else metadata.dependencies - - -def workspace_packages( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - owner_count: int, -) -> list[dict[str, Any]]: - """Return ranked workspace package/root facts.""" - - metadata = facts.project_metadata - packages: list[dict[str, Any]] = [ - { - "id": ".", - "fields": { - "name": project_name(facts), - "role": "workspace-root", - "packages": owner_count, - "dependencies": len(dependencies(facts)), - "next": ["prime:."], - }, - } - ] - roots = () if metadata is None else metadata.package_roots - for path in roots: - shown = semantic_search_display_path(path, project_root) - packages.append(_workspace_package(shown, name=Path(shown).name)) - if len(packages) == 1 and report.project_resolution is not None: - packages.extend( - _workspace_package( - semantic_search_display_path(path, project_root), name=path.name - ) - for path in report.project_resolution.source_paths - ) - return _dedupe_packages(packages)[:MAX_WORKSPACE_PACKAGES] - - -def _workspace_package(path: str, *, name: str) -> dict[str, Any]: - return { - "id": path, - "fields": { - "name": name, - "role": "workspace-package", - "surface": _workspace_package_surface(path), - "next": [f"prime:{path}"], - }, - } - - -def _workspace_package_surface(path: str) -> str: - if is_test_path(path): - return "test" - if path == "docs" or path.startswith("docs/"): - return "docs" - if path == "examples" or path.startswith(("examples/", "demo/", "demos/")): - return "demo" - return "source" - - -def _dedupe_packages(packages: Sequence[dict[str, Any]]) -> list[dict[str, Any]]: - seen: set[str] = set() - result = [] - for package in packages: - if package["id"] in seen: - continue - seen.add(package["id"]) - result.append(package) - return sorted( - result, - key=lambda package: ( - 0 if package["id"] == "." else 1, - package["id"].count("/"), - package["id"], - ), - ) diff --git a/src/asp_python/_semantic_search_packet.py b/src/asp_python/_semantic_search_packet.py deleted file mode 100644 index 0ef304e..0000000 --- a/src/asp_python/_semantic_search_packet.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Top-level semantic-search packet assembly for the Python provider.""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Any - -from . import _semantic_language_ids as ids -from ._semantic_search_model import PythonSemanticSearchOptions -from ._semantic_search_views import payload_for_view -from .verification.facts import ( - verification_project_root, - verification_reasoning_tree_facts, -) - -if TYPE_CHECKING: - from ._model import AspPythonReport - - -def build_python_semantic_search_packet( - report: AspPythonReport, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Build a language-neutral semantic-search packet from Python parser facts.""" - - facts = verification_reasoning_tree_facts(report) - project_root = verification_project_root(report) - payload = payload_for_view(report, facts, project_root, options) - packet = _base_python_search_packet(project_root, options, payload) - _attach_query(packet, options) - _attach_query_set(packet, options) - _attach_package_name(packet, facts) - _attach_reasoning_profiles(packet, options) - _attach_runtime_cost(packet, options) - _attach_payload_optionals(packet, payload) - _attach_syntax_refs(packet, payload) - return _normalize_packet_locations(packet) - - -def _attach_query(packet: dict[str, Any], options: PythonSemanticSearchOptions) -> None: - if options.query is not None: - packet["query"] = options.query - - -def _attach_query_set( - packet: dict[str, Any], options: PythonSemanticSearchOptions -) -> None: - query_terms = [ - { - "value": term, - "kind": "text", - "selector": "lexical" if options.view == "lexical" else "exact", - } - for term in _normalized_query_set(options.query_set) - ] - if query_terms: - packet["querySet"] = query_terms - scope = ( - {"ownerPath": options.owner_path} - if options.owner_path is not None - else None - ) - packet["queryComposition"] = { - "mode": "query-set", - "view": options.view, - "selector": "lexical-set" if options.view == "lexical" else "exact-set", - **({} if scope is None else {"scope": scope}), - "merge": [ - "nodes", - "edges", - "owners", - "hits", - "typeSurfaces", - "nextActions", - "notes", - ], - } - - -def _attach_package_name(packet: dict[str, Any], facts: Any) -> None: - if facts.project_metadata is not None and facts.project_metadata.project_name: - packet["packageName"] = facts.project_metadata.project_name - - -def _attach_reasoning_profiles( - packet: dict[str, Any], options: PythonSemanticSearchOptions -) -> None: - if (options.render_mode or "both") in {"graph", "seeds", "both", "facts"}: - from ._semantic_search_profiles import python_reasoning_profiles - - packet["reasoningProfiles"] = python_reasoning_profiles() - - -def _attach_runtime_cost( - packet: dict[str, Any], options: PythonSemanticSearchOptions -) -> None: - if options.runtime_cost is not None: - packet["runtimeCost"] = options.runtime_cost - packet["notes"] = [ - *packet["notes"], - { - "kind": "runtime-prefilter", - "message": str(options.runtime_cost.get("reason", "")), - "fields": options.runtime_cost.get("fields", {}), - }, - ] - - -def _attach_payload_optionals(packet: dict[str, Any], payload: dict[str, Any]) -> None: - for optional_key in ( - "inputDetection", - "packages", - "items", - "typeSurfaces", - "semanticHandles", - "queryCoverage", - "ownerResolution", - "runtimeCost", - "searchSynthesis", - "avoidNextActions", - ): - if payload.get(optional_key) is not None: - packet[optional_key] = payload[optional_key] - - -def _attach_syntax_refs(packet: dict[str, Any], payload: dict[str, Any]) -> None: - items = payload.get("items") - if not isinstance(items, list): - return - - from ._semantic_syntax_refs import ( - annotate_python_owner_item_syntax_refs, - attach_python_syntax_refs, - ) - - syntax_refs = annotate_python_owner_item_syntax_refs(items) - attach_python_syntax_refs(packet, syntax_refs) - - -def _base_python_search_packet( - project_root: Any, - options: PythonSemanticSearchOptions, - payload: dict[str, Any], -) -> dict[str, Any]: - return { - "schemaId": ids.SEMANTIC_SEARCH_PACKET_SCHEMA_ID, - "schemaVersion": "1", - "protocolId": ids.SEMANTIC_LANGUAGE_PROTOCOL_ID, - "protocolVersion": ids.SEMANTIC_LANGUAGE_PROTOCOL_VERSION, - "languageId": ids.PYTHON_LANGUAGE_ID, - "providerId": ids.PYTHON_PROVIDER_ID, - "binary": ids.PYTHON_BINARY, - "namespace": ids.PYTHON_PROVIDER_NAMESPACE, - "method": f"search/{options.view}", - "projectRoot": str(project_root), - "view": options.view, - "renderMode": options.render_mode or "both", - "header": payload["header"], - "nodes": payload.get("nodes", []), - "edges": payload.get("edges", []), - "owners": payload.get("owners", []), - "hits": payload.get("hits", []), - "findings": payload.get("findings", []), - "nextActions": payload.get("nextActions", []), - "notes": payload.get("notes", []), - } - - -def _normalize_packet_locations(value: Any) -> Any: - if isinstance(value, list): - return [_normalize_packet_locations(item) for item in value] - if not isinstance(value, dict): - return value - - normalized = {key: _normalize_packet_locations(item) for key, item in value.items()} - line = normalized.pop("line", None) - if "path" in normalized and isinstance(line, int): - end_line = normalized.pop("endLine", None) - normalized.pop("column", None) - normalized.pop("endColumn", None) - if not isinstance(end_line, int): - end_line = line - normalized["lineRange"] = f"{line}:{end_line}" - return normalized - - -def _normalized_query_set(query_set: tuple[str, ...]) -> list[str]: - terms: list[str] = [] - seen: set[str] = set() - for raw_term in query_set: - term = raw_term.strip() - if not term or term in seen: - continue - seen.add(term) - terms.append(term) - return terms diff --git a/src/asp_python/_semantic_search_policy.py b/src/asp_python/_semantic_search_policy.py deleted file mode 100644 index 0ae0ef3..0000000 --- a/src/asp_python/_semantic_search_policy.py +++ /dev/null @@ -1,229 +0,0 @@ -"""Own semantic search policy findings for Python agent search and repair.""" - -from __future__ import annotations - -from pathlib import Path -from typing import Any - -from ._agent_policy_catalog import python_agent_policy_rules -from ._model import AspPythonReport, AspPythonRule -from ._project_policy_catalog import python_project_policy_rules -from ._semantic_search_common import compact_fields, header, path_hit - -PROJECT_POLICY_CATALOG_OWNER = "src/asp_python/_project_policy_catalog.py" -AGENT_POLICY_CATALOG_OWNER = "src/asp_python/_agent_policy_catalog.py" - -PROJECT_POLICY_TEST_PATHS = ( - "tests/unit/harness/project_policy/test_catalog.py", - "tests/unit/harness/project_policy/test_layout.py", - "tests/unit/harness/test_policy_contract.py", - "tests/unit/harness/test_policy_snapshots.py", -) -AGENT_POLICY_TEST_PATHS = ( - "tests/unit/harness/test_agent_policy.py", - "tests/unit/harness/test_agent_policy_snapshots.py", - "tests/unit/harness/test_policy_contract.py", -) - - -def policy_payload( - report: AspPythonReport, - facts: Any, - project_root: Path, - query: str, - *, - pipes: tuple[str, ...] = (), -) -> dict[str, Any]: - """Build semantic handles for provider-owned Python policy rules.""" - del report, facts, project_root - handles = [ - handle for handle in _policy_handles() if _matches_policy_query(handle, query) - ] - owner_paths = _handle_owner_paths(handles) - test_paths = _handle_test_paths(handles) - hits = [ - path_hit(path, path, kind="policy", score=5, reason="policy-handle") - for path in owner_paths - ] - return { - "header": header( - "policy", - { - "q": query, - "handle": len(handles), - "owner": len(owner_paths), - "tests": len(test_paths), - "pipes": list(pipes), - }, - ), - "semanticHandles": handles, - "hits": hits, - "nextActions": _policy_next_actions(owner_paths, test_paths), - "queryCoverage": [ - { - "value": query, - "kind": "custom", - "selector": "exact", - "status": "hit" if handles else "miss", - "hitCount": len(handles), - "ownerPaths": owner_paths, - "fields": {"selectedHits": len(handles)}, - } - ], - "searchSynthesis": { - "algorithm": "policy-handle-catalog", - "scope": "policy", - "summary": ( - "resolved provider-owned policy handles" - if handles - else "no provider-owned policy handle matched query" - ), - "selectedOwners": len(owner_paths), - "testFrontier": test_paths, - }, - "notes": [] if handles else [{"kind": "policy-not-found", "message": query}], - } - - -def _policy_handles() -> list[dict[str, Any]]: - handles: list[dict[str, Any]] = [] - handles.extend( - _rule_handle( - rule, - owner_path=PROJECT_POLICY_CATALOG_OWNER, - test_paths=PROJECT_POLICY_TEST_PATHS, - domain="project-policy", - ) - for rule in python_project_policy_rules() - ) - handles.extend( - _rule_handle( - rule, - owner_path=AGENT_POLICY_CATALOG_OWNER, - test_paths=AGENT_POLICY_TEST_PATHS, - domain="agent-policy", - ) - for rule in python_agent_policy_rules() - ) - return handles - - -def _rule_handle( - rule: AspPythonRule, - *, - owner_path: str, - test_paths: tuple[str, ...], - domain: str, -) -> dict[str, Any]: - rule_terms = _rule_query_terms(rule) - return { - "id": rule.rule_id, - "kind": "policy-rule", - "source": "provider-policy", - "title": rule.title, - "languageName": "python", - "qualifiedName": f"{rule.pack_id}.{rule.rule_id}", - "aliases": _rule_aliases(rule), - "labels": sorted({domain, *rule.labels.values()}), - "status": "advisory", - "ownerPath": owner_path, - "testPaths": list(test_paths), - "locations": [{"path": owner_path}], - "queryTerms": rule_terms, - "fields": compact_fields( - { - "packId": rule.pack_id, - "severity": rule.severity.value, - "requirement": rule.requirement, - } - ), - } - - -def _rule_aliases(rule: AspPythonRule) -> list[str]: - return sorted( - { - rule.rule_id.lower(), - rule.rule_id.replace("-", "_"), - rule.rule_id.lower().replace("-", "_"), - rule.pack_id, - rule.labels.get("domain", ""), - } - - {""} - ) - - -def _rule_query_terms(rule: AspPythonRule) -> list[str]: - return sorted( - { - rule.rule_id, - rule.rule_id.lower(), - rule.rule_id.replace("-", "_"), - rule.pack_id, - rule.title, - rule.requirement, - *rule.labels.values(), - } - - {""} - ) - - -def _matches_policy_query(handle: dict[str, Any], query: str) -> bool: - needle = query.casefold().strip() - if not needle: - return True - haystack = [ - handle["id"], - handle["title"], - handle.get("qualifiedName", ""), - *handle.get("aliases", []), - *handle.get("labels", []), - *handle.get("queryTerms", []), - *(str(value) for value in handle.get("fields", {}).values()), - ] - return any(needle in value.casefold() for value in haystack if value) - - -def _handle_owner_paths(handles: list[dict[str, Any]]) -> list[str]: - return _dedupe( - [ - path - for handle in handles - for path in ( - handle.get("implementationOwnerPath"), - handle.get("ownerPath"), - ) - if isinstance(path, str) - ] - ) - - -def _handle_test_paths(handles: list[dict[str, Any]]) -> list[str]: - return _dedupe( - [ - path - for handle in handles - for path in handle.get("testPaths", []) - if isinstance(path, str) - ] - ) - - -def _policy_next_actions( - owner_paths: list[str], - test_paths: list[str], -) -> list[dict[str, str]]: - actions = [{"kind": "owner", "target": path} for path in owner_paths] - actions.extend({"kind": "tests", "target": path} for path in test_paths) - return actions[:8] - - -def _dedupe(values: list[str]) -> list[str]: - seen: set[str] = set() - unique: list[str] = [] - for value in values: - if value in seen: - continue - seen.add(value) - unique.append(value) - return unique diff --git a/src/asp_python/_semantic_search_prefilter.py b/src/asp_python/_semantic_search_prefilter.py deleted file mode 100644 index bc288f1..0000000 --- a/src/asp_python/_semantic_search_prefilter.py +++ /dev/null @@ -1,152 +0,0 @@ -"""Fast candidate-file pruning for Python semantic lexical search.""" - -from __future__ import annotations - -import shutil -import time -from pathlib import Path -from typing import TYPE_CHECKING - -from ._semantic_search_prefilter_file_scan import ( - python_file_path_matches_by_term, -) -from ._semantic_search_prefilter_path import path_only_term_capped_matches -from ._semantic_search_prefilter_result import ( - MIN_PREFILTER_FILES, - PythonSearchPrefilterResult, -) -from ._semantic_search_prefilter_select import ( - normalized_terms, - selected_paths, - source_matched_files, - source_only_term_capped_matches, - term_capped_matches, -) -from ._semantic_search_prefilter_tools import ( - source_match_scores_by_term, -) - -if TYPE_CHECKING: - from collections.abc import Sequence - - -def prefilter_python_text_search_paths( - project_root: Path, - query_terms: Sequence[str], - *, - owner_path: str | None = None, -) -> PythonSearchPrefilterResult | None: - """Return parser input files preselected by path and source text.""" - - terms = normalized_terms(query_terms) - rg = shutil.which("rg") - if not terms and owner_path is None: - return None - return _prefilter_with_tools( - project_root, - rg, - terms, - owner_path=owner_path, - ) - - -def _prefilter_with_tools( - project_root: Path, - rg: str | None, - terms: tuple[str, ...], - *, - owner_path: str | None, -) -> PythonSearchPrefilterResult | None: - started = time.perf_counter() - path_match_scan = python_file_path_matches_by_term( - project_root, - terms, - rg=rg, - ) - path_only_term_capped = path_only_term_capped_matches( - project_root, - terms, - path_match_scan, - ) - if path_only_term_capped is not None: - selected = selected_paths( - project_root, - path_only_term_capped, - terms, - owner_path, - ) - elapsed_ms = round((time.perf_counter() - started) * 1000) - return PythonSearchPrefilterResult( - paths=selected, - total_files=path_match_scan.total_files, - term_capped_files=len(set(path_only_term_capped)), - matched_files=len(selected), - elapsed_ms=elapsed_ms, - tool=path_match_scan.tool, - reason=( - f"{path_match_scan.tool} path prefilter selected parser input files " - "for lexical query" - ), - query_terms=len(terms), - source_search_passes=0, - file_list_passes=1, - candidate_file_basis="path-matched-files", - ) - - source_scores_by_term = source_match_scores_by_term(project_root, rg, terms) - source_tool = "rg" if rg is not None else "rglob-source" - matched_source_files = source_matched_files(source_scores_by_term) - source_only_term_capped = source_only_term_capped_matches( - project_root, - terms, - source_scores_by_term, - matched_source_files, - ) - if source_only_term_capped is not None: - selected = selected_paths( - project_root, - source_only_term_capped, - terms, - owner_path, - ) - elapsed_ms = round((time.perf_counter() - started) * 1000) - return PythonSearchPrefilterResult( - paths=selected, - total_files=len(matched_source_files), - term_capped_files=len(set(source_only_term_capped)), - matched_files=len(selected), - elapsed_ms=elapsed_ms, - tool=source_tool, - reason=f"{source_tool} source prefilter selected parser input files for lexical query", - query_terms=len(terms), - source_search_passes=1 if terms else 0, - file_list_passes=0, - candidate_file_basis="source-matched-files", - ) - - if path_match_scan.total_files <= MIN_PREFILTER_FILES: - return None - term_capped = term_capped_matches( - project_root, - terms, - path_match_scan.matches_by_term, - source_scores_by_term, - ) - selected = selected_paths(project_root, term_capped, terms, owner_path) - elapsed_ms = round((time.perf_counter() - started) * 1000) - return PythonSearchPrefilterResult( - paths=selected, - total_files=path_match_scan.total_files, - term_capped_files=len(set(term_capped)), - matched_files=len(selected), - elapsed_ms=elapsed_ms, - tool=f"{path_match_scan.tool}+{source_tool}", - reason=( - f"{path_match_scan.tool}/{source_tool} prefilter selected parser input " - "files for lexical query" - ), - query_terms=len(terms), - source_search_passes=1 if terms else 0, - file_list_passes=1, - candidate_file_basis="all-python-files", - ) diff --git a/src/asp_python/_semantic_search_prefilter_file_scan.py b/src/asp_python/_semantic_search_prefilter_file_scan.py deleted file mode 100644 index 67dcc03..0000000 --- a/src/asp_python/_semantic_search_prefilter_file_scan.py +++ /dev/null @@ -1,245 +0,0 @@ -"""Python file-list scanning for semantic-search prefilters.""" - -from __future__ import annotations - -import shutil -from dataclasses import dataclass -from pathlib import Path -from typing import TYPE_CHECKING - -from ._constants import IGNORED_DIR_NAMES, INCLUDE_HIDDEN_DIR_NAMES -from ._semantic_search_prefilter_process import run_prefilter_command - -if TYPE_CHECKING: - from collections.abc import Sequence - - -@dataclass(frozen=True, slots=True) -class PythonFilePathMatchScan: - """A single file-list pass with path matches grouped by query term.""" - - total_files: int - matches_by_term: dict[str, set[str]] - tool: str - - -def list_python_files( - project_root: Path, - *, - ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES, - include_hidden_dir_names: frozenset[str] = INCLUDE_HIDDEN_DIR_NAMES, -) -> tuple[Path, ...]: - """Return scannable Python files below a project root.""" - - fd = shutil.which("fd") or shutil.which("fdfind") - if fd is not None: - process = run_prefilter_command( - _fd_python_files_command(fd, ignored_dir_names), - cwd=project_root, - ) - if process.returncode == 0: - return trusted_tool_paths_from_output(project_root, process.stdout) - return tuple( - sorted( - ( - path.resolve() - for path in project_root.rglob("*.py") - if not _ignored( - path, project_root, ignored_dir_names, include_hidden_dir_names - ) - ), - key=lambda path: path.as_posix(), - ) - ) - - -def python_file_path_matches_by_term( - project_root: Path, - terms: Sequence[str], - *, - rg: str | None = None, - ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES, - include_hidden_dir_names: frozenset[str] = INCLUDE_HIDDEN_DIR_NAMES, -) -> PythonFilePathMatchScan: - """Return Python file counts and path matches from one file-list pass.""" - - normalized_terms = tuple(dict.fromkeys(term for term in terms if term)) - if rg is not None: - process = run_prefilter_command( - _rg_python_files_command(rg, ignored_dir_names), - cwd=project_root, - ) - if process.returncode == 0: - return _path_match_scan_from_output( - project_root, - process.stdout, - normalized_terms, - tool="rg", - ) - fd = shutil.which("fd") or shutil.which("fdfind") - if fd is not None: - process = run_prefilter_command( - _fd_python_files_command(fd, ignored_dir_names), - cwd=project_root, - ) - if process.returncode == 0: - return _path_match_scan_from_output( - project_root, - process.stdout, - normalized_terms, - tool="fd+rg", - ) - return _path_match_scan_from_rglob( - project_root, - normalized_terms, - ignored_dir_names=ignored_dir_names, - include_hidden_dir_names=include_hidden_dir_names, - ) - - -def paths_from_output(project_root: Path, stdout: str) -> tuple[Path, ...]: - """Return existing Python files named by command output.""" - - paths: list[Path] = [] - for line in stdout.splitlines(): - if not line: - continue - path = Path(line) - if not path.is_absolute(): - path = project_root / path - resolved = path.resolve() - if resolved.is_file() and resolved.suffix == ".py": - paths.append(resolved) - return tuple(sorted(set(paths), key=lambda path: path.as_posix())) - - -def trusted_tool_paths_from_output(project_root: Path, stdout: str) -> tuple[Path, ...]: - """Return Python files from a trusted fd/rg file-list command.""" - - paths = [ - path if path.is_absolute() else project_root / path - for line in stdout.splitlines() - if line - for path in (Path(line),) - ] - return tuple(sorted(set(paths), key=lambda path: path.as_posix())) - - -def _path_match_scan_from_output( - project_root: Path, - stdout: str, - terms: Sequence[str], - *, - tool: str, -) -> PythonFilePathMatchScan: - folded_terms = tuple((term, term.casefold()) for term in terms) - matches_by_term: dict[str, set[str]] = {term: set() for term in terms} - total_files = 0 - for line in stdout.splitlines(): - if not line: - continue - total_files += 1 - for term, folded_term in folded_terms: - if _path_contains_term(line, folded_term): - matches_by_term[term].add(line) - return PythonFilePathMatchScan( - total_files=total_files, - matches_by_term=matches_by_term, - tool=tool, - ) - - -def _path_match_scan_from_rglob( - project_root: Path, - terms: Sequence[str], - *, - ignored_dir_names: frozenset[str], - include_hidden_dir_names: frozenset[str], -) -> PythonFilePathMatchScan: - folded_terms = tuple((term, term.casefold()) for term in terms) - matches_by_term: dict[str, set[str]] = {term: set() for term in terms} - total_files = 0 - for path in project_root.rglob("*.py"): - if _ignored(path, project_root, ignored_dir_names, include_hidden_dir_names): - continue - total_files += 1 - relative = path.relative_to(project_root).as_posix() - for term, folded_term in folded_terms: - if _path_contains_term(relative, folded_term): - matches_by_term[term].add(relative) - return PythonFilePathMatchScan( - total_files=total_files, - matches_by_term=matches_by_term, - tool="rglob", - ) - - -def _path_contains_term(path: str, folded_term: str) -> bool: - folded_path = path.casefold() - if folded_term in folded_path: - return True - compact_term = _compact_path_token(folded_term) - return len(compact_term) >= 3 and compact_term in _compact_path_token(folded_path) - - -def _compact_path_token(value: str) -> str: - return "".join(char for char in value if char.isalnum()) - - -def _fd_python_files_command(fd: str, ignored_dir_names: frozenset[str]) -> list[str]: - return [ - fd, - "--color", - "never", - "-t", - "f", - "-e", - "py", - *(_fd_excludes(ignored_dir_names)), - ".", - ".", - ] - - -def _rg_python_files_command(rg: str, ignored_dir_names: frozenset[str]) -> list[str]: - return [ - rg, - "--color", - "never", - "--files", - "--glob", - "*.py", - *(_rg_excludes(ignored_dir_names)), - ".", - ] - - -def _fd_excludes(ignored_dir_names: frozenset[str]) -> tuple[str, ...]: - args: list[str] = [] - for name in sorted(ignored_dir_names): - args.extend(("-E", name)) - return tuple(args) - - -def _rg_excludes(ignored_dir_names: frozenset[str]) -> tuple[str, ...]: - args: list[str] = [] - for name in sorted(ignored_dir_names): - args.extend(("--glob", f"!{name}/**")) - return tuple(args) - - -def _ignored( - path: Path, - project_root: Path, - ignored_dir_names: frozenset[str], - include_hidden_dir_names: frozenset[str], -) -> bool: - return any( - part in ignored_dir_names - or ( - part.startswith(".") - and part not in {".", ".."} - and part not in include_hidden_dir_names - ) - for part in path.relative_to(project_root).parts - ) diff --git a/src/asp_python/_semantic_search_prefilter_path.py b/src/asp_python/_semantic_search_prefilter_path.py deleted file mode 100644 index 1307576..0000000 --- a/src/asp_python/_semantic_search_prefilter_path.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Path-only candidate pruning for Python semantic-search prefilters.""" - -from __future__ import annotations - -from pathlib import Path - -from ._semantic_search_prefilter_file_scan import PythonFilePathMatchScan -from ._semantic_search_prefilter_rank import ranked_term_matches - - -def path_only_term_capped_matches( - project_root: Path, - terms: tuple[str, ...], - path_match_scan: PythonFilePathMatchScan, -) -> tuple[str, ...] | None: - """Return path-ranked matches when every query term appears in owner paths.""" - - if not terms or not all( - path_match_scan.matches_by_term.get(term) for term in terms - ): - return None - matched: list[str] = [] - for term in terms: - term_matches = ranked_term_matches( - project_root, - term, - path_match_scan.matches_by_term.get(term, set()), - {}, - ) - matched.extend(path for path, _score in term_matches) - return tuple(matched) diff --git a/src/asp_python/_semantic_search_prefilter_process.py b/src/asp_python/_semantic_search_prefilter_process.py deleted file mode 100644 index 330e311..0000000 --- a/src/asp_python/_semantic_search_prefilter_process.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Subprocess helpers for semantic-search prefilter tools.""" - -from __future__ import annotations - -import subprocess -from pathlib import Path - - -def run_prefilter_command( - command: list[str], - *, - cwd: Path, -) -> subprocess.CompletedProcess[str]: - """Run one bounded filesystem prefilter command.""" - - try: - return subprocess.run( - command, - cwd=cwd, - text=True, - capture_output=True, - timeout=5, - check=False, - ) - except subprocess.TimeoutExpired: - return subprocess.CompletedProcess(command, 124, "", "") diff --git a/src/asp_python/_semantic_search_prefilter_rank.py b/src/asp_python/_semantic_search_prefilter_rank.py deleted file mode 100644 index 1819247..0000000 --- a/src/asp_python/_semantic_search_prefilter_rank.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Ranking and caps for Python semantic-search prefilter candidates.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Sequence - -MAX_PREFILTER_FILES_PER_TERM = 2 -MAX_PREFILTER_FILES_TOTAL = 6 - - -def path_matches( - project_root: Path, - paths: Sequence[Path], - terms: Sequence[str], -) -> set[Path]: - """Return files whose owner path matches at least one query term.""" - - folded_terms = tuple(term.casefold() for term in terms) - matches: set[Path] = set() - for path in paths: - relative = relative_posix(path, project_root).casefold() - if any(term in relative for term in folded_terms): - matches.add(path) - return matches - - -def ranked_term_matches( - _project_root: Path, - term: str, - path_match_set: set[str], - source_scores: dict[str, int], -) -> list[tuple[str, int]]: - """Return one term's candidates ranked before parser parsing.""" - - best_scores = {path: 1 for path in path_match_set} - for path, score in source_scores.items(): - best_scores[path] = min(best_scores.get(path, score), score) - return sorted( - best_scores.items(), - key=lambda item: (item[1], path_key_rank(item[0], (term,))), - ) - - -def ranked_capped_matches( - _project_root: Path, - paths: Sequence[str], -) -> tuple[str, ...]: - """Return globally capped candidate paths.""" - - return tuple(sorted(set(paths), key=lambda path: path_key_rank(path, ())))[ - :MAX_PREFILTER_FILES_TOTAL - ] - - -def path_rank( - project_root: Path, - path: Path, - terms: Sequence[str], -) -> tuple[int, int, int, int, str]: - """Rank source owners before tests, shallow files, and long paths.""" - - relative = relative_posix(path, project_root) - return path_key_rank(relative, terms) - - -def path_key_rank( - relative: str, - terms: Sequence[str], -) -> tuple[int, int, int, int, str]: - """Rank project-relative source keys before path objects are needed.""" - - folded = relative.casefold() - folded_terms = tuple(term.casefold() for term in terms) - return ( - 1 if _is_test_path(relative) else 0, - 0 if folded_terms and any(term in folded for term in folded_terms) else 1, - relative.count("/"), - len(relative), - relative, - ) - - -def relative_posix(path: Path, project_root: Path) -> str: - """Return a stable path key relative to the search root.""" - - root_key = project_root.as_posix().rstrip("/") - path_key = path.as_posix() - if path_key == root_key: - return "." - prefix = f"{root_key}/" - if path_key.startswith(prefix): - return path_key.removeprefix(prefix) - try: - return path.relative_to(project_root).as_posix() - except ValueError: - return path_key - - -def _is_test_path(relative_path: str) -> bool: - return ( - relative_path == "tests" - or relative_path.startswith("tests/") - or "/tests/" in relative_path - or relative_path.startswith("test_") - or "/test_" in relative_path - ) diff --git a/src/asp_python/_semantic_search_prefilter_result.py b/src/asp_python/_semantic_search_prefilter_result.py deleted file mode 100644 index 44972a1..0000000 --- a/src/asp_python/_semantic_search_prefilter_result.py +++ /dev/null @@ -1,54 +0,0 @@ -"""Runtime metadata for Python semantic-search prefilters.""" - -from __future__ import annotations - -from dataclasses import dataclass -from pathlib import Path - -from ._semantic_search_prefilter_rank import ( - MAX_PREFILTER_FILES_PER_TERM, - MAX_PREFILTER_FILES_TOTAL, -) - -MIN_PREFILTER_FILES = 128 - - -@dataclass(frozen=True, slots=True) -class PythonSearchPrefilterResult: - """Files selected before parser-owned fact extraction.""" - - paths: tuple[Path, ...] - total_files: int - term_capped_files: int - matched_files: int - elapsed_ms: int - tool: str - reason: str - query_terms: int - source_search_passes: int - file_list_passes: int - candidate_file_basis: str - - def runtime_cost(self) -> dict[str, object]: - """Return schema-owned runtime-cost metadata for the search packet.""" - - return { - "cacheStatus": "disabled", - "elapsedMs": self.elapsed_ms, - "sourceFilesParsed": self.matched_files, - "reason": self.reason, - "fields": { - "prefilterTool": self.tool, - "candidateFiles": self.total_files, - "minCandidateFiles": MIN_PREFILTER_FILES, - "termCappedFiles": self.term_capped_files, - "matchedFiles": self.matched_files, - "maxFilesPerTerm": MAX_PREFILTER_FILES_PER_TERM, - "maxFilesTotal": MAX_PREFILTER_FILES_TOTAL, - "mode": "text-query-prefilter", - "queryTerms": self.query_terms, - "sourceSearchPasses": self.source_search_passes, - "fileListPasses": self.file_list_passes, - "candidateFileBasis": self.candidate_file_basis, - }, - } diff --git a/src/asp_python/_semantic_search_prefilter_select.py b/src/asp_python/_semantic_search_prefilter_select.py deleted file mode 100644 index 1351c31..0000000 --- a/src/asp_python/_semantic_search_prefilter_select.py +++ /dev/null @@ -1,109 +0,0 @@ -"""Candidate selection helpers for Python semantic-search prefilters.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING - -from ._semantic_search_prefilter_rank import ( - MAX_PREFILTER_FILES_PER_TERM, - path_key_rank, - ranked_capped_matches, - ranked_term_matches, -) -from ._semantic_search_prefilter_result import MIN_PREFILTER_FILES - -if TYPE_CHECKING: - from collections.abc import Sequence - - -def normalized_terms(query_terms: Sequence[str]) -> tuple[str, ...]: - return tuple(dict.fromkeys(term.strip() for term in query_terms if term.strip())) - - -def source_only_term_capped_matches( - project_root: Path, - terms: tuple[str, ...], - source_scores_by_term: dict[str, dict[str, int]], - source_matched_files: frozenset[str], -) -> tuple[str, ...] | None: - if not _source_only_prefilter_is_sufficient( - terms, - source_scores_by_term, - source_matched_files, - ): - return None - matched: list[str] = [] - for term in terms: - term_matches = ranked_term_matches( - project_root, - term, - set(), - source_scores_by_term.get(term, {}), - ) - matched.extend( - path for path, _score in term_matches[:MAX_PREFILTER_FILES_PER_TERM] - ) - return tuple(matched) - - -def term_capped_matches( - project_root: Path, - terms: tuple[str, ...], - path_matches_by_term: dict[str, set[str]], - source_scores_by_term: dict[str, dict[str, int]], -) -> tuple[str, ...]: - matched: list[str] = [] - for term in terms: - term_matches = ranked_term_matches( - project_root, - term, - path_matches_by_term.get(term, set()), - source_scores_by_term.get(term, {}), - ) - matched.extend( - path for path, _score in term_matches[:MAX_PREFILTER_FILES_PER_TERM] - ) - return tuple(matched) - - -def selected_paths( - project_root: Path, - term_capped: tuple[str, ...], - terms: tuple[str, ...], - owner_path: str | None, -) -> tuple[Path, ...]: - matched = ranked_capped_matches(project_root, term_capped) - if owner_path is not None: - owner_candidate = (project_root / owner_path).resolve() - if owner_candidate.is_file() and owner_candidate.suffix == ".py": - matched = (*matched, owner_path) - return tuple( - project_root / path - for path in sorted(set(matched), key=lambda path: path_key_rank(path, terms)) - ) - - -def source_matched_files( - source_scores_by_term: dict[str, dict[str, int]], -) -> frozenset[str]: - return frozenset( - path - for source_scores in source_scores_by_term.values() - for path in source_scores - ) - - -def _source_only_prefilter_is_sufficient( - terms: tuple[str, ...], - source_scores_by_term: dict[str, dict[str, int]], - source_matched_files: frozenset[str], -) -> bool: - return ( - bool(terms) - and len(source_matched_files) > MIN_PREFILTER_FILES - and all( - len(source_scores_by_term.get(term, {})) >= MAX_PREFILTER_FILES_PER_TERM - for term in terms - ) - ) diff --git a/src/asp_python/_semantic_search_prefilter_tools.py b/src/asp_python/_semantic_search_prefilter_tools.py deleted file mode 100644 index 31728a1..0000000 --- a/src/asp_python/_semantic_search_prefilter_tools.py +++ /dev/null @@ -1,202 +0,0 @@ -"""Filesystem-backed candidate discovery for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from collections.abc import Sequence - -from ._constants import IGNORED_DIR_NAMES -from ._semantic_search_prefilter_file_scan import list_python_files -from ._semantic_search_prefilter_process import run_prefilter_command - - -def source_match_scores( - project_root: Path, rg: str | None, term: str -) -> dict[str, int]: - """Return candidate files scored by parser-likely source hits.""" - - return source_match_scores_by_term(project_root, rg, (term,)).get(term, {}) - - -def source_match_scores_by_term( - project_root: Path, - rg: str | None, - terms: Sequence[str], - *, - ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES, -) -> dict[str, dict[str, int]]: - """Return source-hit scores for all query terms using one rg scan.""" - - normalized_terms = tuple(dict.fromkeys(term for term in terms if term)) - if not normalized_terms: - return {} - folded_terms = tuple((term, term.casefold()) for term in normalized_terms) - if rg is None: - return _source_match_scores_by_term_rglob( - project_root, - folded_terms, - ignored_dir_names, - ) - process = run_prefilter_command( - _rg_source_command(rg, normalized_terms, ignored_dir_names), - cwd=project_root, - ) - if process.returncode not in {0, 1}: - return {} - scores_by_term: dict[str, dict[str, int]] = {term: {} for term in normalized_terms} - for line in process.stdout.splitlines(): - _merge_source_line_scores( - scores_by_term, - project_root, - line, - folded_terms, - ) - return scores_by_term - - -def _source_match_scores_by_term_rglob( - project_root: Path, - folded_terms: Sequence[tuple[str, str]], - ignored_dir_names: frozenset[str], -) -> dict[str, dict[str, int]]: - scores_by_term: dict[str, dict[str, int]] = { - term: {} for term, _folded_term in folded_terms - } - for path in list_python_files(project_root, ignored_dir_names=ignored_dir_names): - relative = path.relative_to(project_root).as_posix() - _merge_source_file_scores(scores_by_term, relative, path, folded_terms) - return scores_by_term - - -def _merge_source_file_scores( - scores_by_term: dict[str, dict[str, int]], - relative_path: str, - path: Path, - folded_terms: Sequence[tuple[str, str]], -) -> None: - try: - with path.open("r", encoding="utf-8", errors="ignore") as source_file: - for source in source_file: - for term, folded_term in _matching_terms(source, folded_terms): - term_scores = scores_by_term[term] - score = _source_line_score(source, folded_term) - term_scores[relative_path] = min( - term_scores.get(relative_path, score), - score, - ) - except OSError: - return - - -def _merge_source_line_scores( - scores_by_term: dict[str, dict[str, int]], - project_root: Path, - line: str, - folded_terms: Sequence[tuple[str, str]], -) -> None: - parsed = _python_source_line(project_root, line) - if parsed is None: - return - resolved, source = parsed - for term, folded_term in _matching_terms(source, folded_terms): - term_scores = scores_by_term[term] - score = _source_line_score(source, folded_term) - term_scores[resolved] = min(term_scores.get(resolved, score), score) - - -def _python_source_line(_project_root: Path, line: str) -> tuple[str, str] | None: - path, source = _split_rg_line(line) - if path is None: - return None - if not path.endswith(".py"): - return None - return path, source - - -def _matching_terms( - source: str, - folded_terms: Sequence[tuple[str, str]], -) -> tuple[tuple[str, str], ...]: - folded_source = source.casefold() - return tuple( - (term, folded_term) - for term, folded_term in folded_terms - if folded_term in folded_source - ) - - -def _rg_source_command( - rg: str, - terms: Sequence[str], - ignored_dir_names: frozenset[str], -) -> list[str]: - expressions: list[str] = [] - for term in terms: - expressions.extend(("-e", term)) - return [ - rg, - "--color", - "never", - "-i", - "-F", - "-n", - "--max-count", - "50", - "--glob", - "*.py", - *(_rg_excludes(ignored_dir_names)), - *expressions, - ".", - ] - - -def _rg_excludes(ignored_dir_names: frozenset[str]) -> tuple[str, ...]: - args: list[str] = [] - for name in sorted(ignored_dir_names): - args.extend(("--glob", f"!{name}/**")) - return tuple(args) - - -def _split_rg_line(line: str) -> tuple[str | None, str]: - first = line.find(":") - if first == -1: - return None, "" - second = line.find(":", first + 1) - if second == -1: - return None, "" - return line[:first], line[second + 1 :] - - -def _source_line_score(source_line: str, folded_term: str) -> int: - stripped = source_line.lstrip() - folded = stripped.casefold() - if _starts_with_definition(folded, folded_term): - return 0 - if folded.startswith(folded_term) and folded[ - len(folded_term) : - ].lstrip().startswith("="): - return 1 - if "__all__" in source_line: - return 2 - return 3 - - -def _starts_with_definition(folded_source_line: str, folded_term: str) -> bool: - for prefix in ("class ", "def "): - if not folded_source_line.startswith(prefix): - continue - name_start = len(prefix) - if not folded_source_line.startswith(folded_term, name_start): - continue - name_end = name_start + len(folded_term) - return name_end >= len(folded_source_line) or not _is_identifier_character( - folded_source_line[name_end] - ) - return False - - -def _is_identifier_character(character: str) -> bool: - return character == "_" or character.isalnum() diff --git a/src/asp_python/_semantic_search_prime_fast.py b/src/asp_python/_semantic_search_prime_fast.py deleted file mode 100644 index 724332c..0000000 --- a/src/asp_python/_semantic_search_prime_fast.py +++ /dev/null @@ -1,190 +0,0 @@ -"""Fast compact frontiers for Python search prime seed view.""" - -from __future__ import annotations - -import os -from collections import deque -from pathlib import Path - -from ._cli_args import ProtocolArgs - -_MAX_FAST_PRIME_OWNERS = 12 -_MAX_FAST_PRIME_DIRS = 128 -_MAX_FAST_PRIME_FILES = 512 -_SKIPPED_DIR_NAMES = frozenset( - { - ".cache", - ".git", - ".hg", - ".mypy_cache", - ".pytest_cache", - ".ruff_cache", - ".tox", - ".venv", - "__pycache__", - "build", - "dist", - "node_modules", - "target", - "venv", - } -) -_PREFERRED_DIRS = ( - "src", - "tests", - "test", - "scripts", - "tools", -) - - -def render_fast_prime_search(args: ProtocolArgs, project_root: Path) -> str | None: - """Render prime seeds without running the full project harness.""" - - if not _supports_fast_prime_search(args): - return None - owners = _discover_python_owner_paths(project_root) - return _render_fast_prime_seed_text(project_root, owners) - - -def _supports_fast_prime_search(args: ProtocolArgs) -> bool: - return ( - args.command == "search" - and args.view == "prime" - and args.render_mode == "seeds" - and not args.json - and args.query is None - and args.item_query is None - and args.owner_path is None - and not args.query_set - and not args.pipes - ) - - -def _discover_python_owner_paths(project_root: Path) -> tuple[str, ...]: - root = project_root.resolve() - owners: list[str] = [] - seen: set[Path] = set() - for start in _candidate_roots(root): - _scan_python_files(start, root=root, owners=owners, seen=seen) - if len(owners) >= _MAX_FAST_PRIME_OWNERS: - break - return tuple(owners[:_MAX_FAST_PRIME_OWNERS]) - - -def _candidate_roots(root: Path) -> tuple[Path, ...]: - selected: list[Path] = [] - for name in _PREFERRED_DIRS: - candidate = root / name - if candidate.exists(): - selected.append(candidate) - if root not in selected: - selected.append(root) - return tuple(selected) - - -def _scan_python_files( - start: Path, - *, - root: Path, - owners: list[str], - seen: set[Path], -) -> None: - if len(owners) >= _MAX_FAST_PRIME_OWNERS: - return - queue: deque[Path] = deque((start,)) - visited_dirs = 0 - visited_files = 0 - while queue and len(owners) < _MAX_FAST_PRIME_OWNERS: - directory = queue.popleft() - try: - resolved_dir = directory.resolve() - except OSError: - continue - if resolved_dir in seen: - continue - seen.add(resolved_dir) - if directory.name in _SKIPPED_DIR_NAMES: - continue - visited_dirs += 1 - if visited_dirs > _MAX_FAST_PRIME_DIRS: - return - try: - entries = list(os.scandir(directory)) - except OSError: - continue - dirs: list[Path] = [] - files: list[str] = [] - for entry in entries: - name = entry.name - if name.startswith(".") and name not in {"."}: - continue - try: - if entry.is_dir(follow_symlinks=False): - if name not in _SKIPPED_DIR_NAMES: - dirs.append(Path(entry.path)) - continue - if entry.is_file(follow_symlinks=False) and name.endswith(".py"): - files.append(entry.path) - except OSError: - continue - for file_path in sorted(files): - visited_files += 1 - if visited_files > _MAX_FAST_PRIME_FILES: - return - owners.append(_relative_owner_path(Path(file_path), root)) - if len(owners) >= _MAX_FAST_PRIME_OWNERS: - return - queue.extend(sorted(dirs, key=lambda path: path.as_posix())) - - -def _relative_owner_path(path: Path, root: Path) -> str: - try: - return path.relative_to(root).as_posix() - except ValueError: - return path.as_posix() - - -def _render_fast_prime_seed_text(project_root: Path, owners: tuple[str, ...]) -> str: - root_label = project_root.name or "." - lines = [ - ( - f"[search-prime] root={root_label} alg=fast-prime-frontier-v1 " - f"budget=owners:{_MAX_FAST_PRIME_OWNERS} mode=seeds" - ), - ( - "|decision purpose=decision-primer answer=false code=false " - "capabilities=pipe,lexical,fd-query,rg-query,owner-items,selector-code,treesitter-query " - "ladder=pipe>lexical>fd-query|rg-query>owner-items>selector-code " - "history=asp-artifacts:directReadRisk,repeatedPrime,repeatedPipe,bestPath " - "risk=broad-direct-read,manual-window-scan,repeat-prime " - "next=\"asp python search pipe '' --workspace . --view seeds\"" - ).replace( - "--workspace . --view seeds", "--workspace --view seeds" - ), - "legend: ID=kind:role(value)!next; entries profile(selectors=>returns); frontier ID.next", - "aliases: graph:{G=search,O=owner}", - ] - owner_ids = [f"O{index}" for index, _ in enumerate(owners, start=1)] - if owners: - lines.append( - ";".join( - f"{owner_id}=owner:path({owner})!owner" - for owner_id, owner in zip(owner_ids, owners, strict=True) - ) - ) - lines.append( - "G>{" + ",".join(f"{owner_id}:selects" for owner_id in owner_ids) + "}" - ) - else: - lines.append("G>{}") - rank = ",".join(owner_ids) - frontier = ",".join(f"{owner_id}.owner" for owner_id in owner_ids) - lines.extend( - [ - f"rank={rank} frontier={frontier}", - "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)", - "omit=items,blocks,code,full-json reason=fast-seeds-frontier", - ] - ) - return "\n".join(lines) + "\n" diff --git a/src/asp_python/_semantic_search_profiles.py b/src/asp_python/_semantic_search_profiles.py deleted file mode 100644 index d3b7547..0000000 --- a/src/asp_python/_semantic_search_profiles.py +++ /dev/null @@ -1,103 +0,0 @@ -"""Typed reasoning profile catalog for Python semantic search.""" - -from __future__ import annotations - -from typing import Any - - -def python_reasoning_profiles() -> list[dict[str, Any]]: - return [ - { - "profile": "owner-query", - "description": "Combine an owner and query term to find matching items, tests, and dependency usage.", - "selectors": [ - { - "kind": "owner", - "alias": "O", - "targetRole": "path", - "required": True, - }, - { - "kind": "query", - "alias": "Q", - "targetRole": "term", - "required": True, - }, - ], - "returns": ["items", "tests", "dependency-usage"], - "frontier": ["O.items", "Q.owner", "Q.tests"], - "fields": {"source": "search-guide"}, - }, - { - "profile": "query-deps", - "description": "Combine a query term and dependency handle to find owners, imports, usage, and tests.", - "selectors": [ - { - "kind": "query", - "alias": "Q", - "targetRole": "term", - "required": True, - }, - { - "kind": "dependency", - "alias": "D", - "targetRole": "pkg", - "required": True, - }, - ], - "returns": ["owners", "imports", "usage-tests"], - "frontier": ["Q.owner", "D.public-api", "D.tests"], - "fields": {"source": "search-guide"}, - }, - { - "profile": "owner-tests", - "description": "Use an owner to inspect covering tests, entrypoints, and fixtures.", - "selectors": [ - { - "kind": "owner", - "alias": "O", - "targetRole": "path", - "required": True, - }, - ], - "returns": ["covering-tests", "test-entrypoints", "fixtures"], - "frontier": ["O.tests", "T.owner"], - "fields": {"source": "search-guide"}, - }, - { - "profile": "finding-frontier", - "description": "Combine a finding and optional owner to find affected owners, tests, and verification actions.", - "selectors": [ - { - "kind": "finding", - "alias": "F", - "targetRole": "finding", - "required": True, - }, - { - "kind": "owner", - "alias": "O", - "targetRole": "path", - "required": False, - }, - ], - "returns": ["affected-owners", "tests", "verification-actions"], - "frontier": ["F.owner", "F.tests", "O.policy"], - "fields": {"source": "search-guide"}, - }, - { - "profile": "feature-cfg", - "description": "Use a feature or cfg gate to find guarded owners and verification surfaces.", - "selectors": [ - { - "kind": "feature", - "alias": "F", - "targetRole": "feature", - "required": True, - }, - ], - "returns": ["cfg-gates", "owners", "verification-surfaces"], - "frontier": ["F.cfg", "F.owner", "F.tests"], - "fields": {"source": "search-guide"}, - }, - ] diff --git a/src/asp_python/_semantic_search_public_external_type_hits.py b/src/asp_python/_semantic_search_public_external_type_hits.py deleted file mode 100644 index 22dba7a..0000000 --- a/src/asp_python/_semantic_search_public_external_type_hits.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Hit extraction for Python public external type search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import dedupe_hits, location_from_source -from ._semantic_search_deps import module_owner_path -from ._semantic_search_public_external_type_imports import ( - dependency_import_contexts, - surface_import_context, -) -from ._semantic_search_public_external_type_model import ( - PublicExternalTypeImportContext, - PublicExternalTypeSurface, -) -from ._semantic_search_public_external_type_surfaces import public_type_surfaces - -if TYPE_CHECKING: - from python_lang_parser import PythonModuleReport - - from ._model import AspPythonReport - - -def public_external_type_hits( - report: AspPythonReport, - project_root: Path, - package: str, -) -> list[dict[str, Any]]: - """Return public API surfaces that expose a dependency type.""" - - hits = [ - hit - for module in report.modules - for hit in _module_public_external_type_hits(module, project_root, package) - ] - return dedupe_hits(hits) - - -def _module_public_external_type_hits( - module: PythonModuleReport, - project_root: Path, - package: str, -) -> list[dict[str, Any]]: - contexts = dependency_import_contexts(module, package) - if not contexts: - return [] - owner_path = module_owner_path(module, project_root) - return [ - _surface_hit(surface, owner_path, project_root, context, direct=direct) - for surface in public_type_surfaces(module) - for context, direct in [surface_import_context(surface, contexts)] - if context is not None - ] - - -def _surface_hit( - surface: PublicExternalTypeSurface, - owner_path: str, - project_root: Path, - context: PublicExternalTypeImportContext, - *, - direct: bool, -) -> dict[str, Any]: - return { - "kind": "api", - "ownerPath": owner_path, - "location": location_from_source(surface.location, project_root), - "score": 9 if direct else 4, - "reason": "public-external-type" if direct else "possible-public-external-type", - "symbol": surface.symbol, - "fields": { - "source": "native-parser", - "dependency": context.dependency, - "confidence": "direct" if direct else "possible", - "apiKind": surface.api_kind, - "surface": surface.surface, - "typeText": surface.type_text, - "importSpecifier": context.import_specifier, - }, - } diff --git a/src/asp_python/_semantic_search_public_external_type_imports.py b/src/asp_python/_semantic_search_public_external_type_imports.py deleted file mode 100644 index 0801957..0000000 --- a/src/asp_python/_semantic_search_public_external_type_imports.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Import-context matching for Python public external type search.""" - -from __future__ import annotations - -import re -from typing import TYPE_CHECKING - -from ._semantic_search_deps import import_root, normalize_dependency_name -from ._semantic_search_public_external_type_model import ( - PublicExternalTypeImportContext, - PublicExternalTypeSurface, -) - -if TYPE_CHECKING: - from python_lang_parser import PythonImport, PythonModuleReport - - -def dependency_import_contexts( - module: PythonModuleReport, - package: str, -) -> list[PublicExternalTypeImportContext]: - """Return imports in a module that refer to the queried dependency.""" - - return [ - context - for import_record in module.imports - for context in [_import_context(import_record, package)] - if context is not None - ] - - -def surface_import_context( - surface: PublicExternalTypeSurface, - contexts: list[PublicExternalTypeImportContext], -) -> tuple[PublicExternalTypeImportContext | None, bool]: - """Return the matching import context and whether attribution is direct.""" - - direct = next( - ( - context - for context in contexts - if any( - _contains_reference(surface.type_text, name) - for name in context.direct_names - ) - ), - None, - ) - if direct is not None: - return direct, True - possible = next( - ( - context - for context in contexts - if any( - _contains_reference(surface.type_text, name) - for name in context.imported_names - ) - ), - None, - ) - return (possible, False) if possible is not None else (None, False) - - -def _import_context( - import_record: PythonImport, - package: str, -) -> PublicExternalTypeImportContext | None: - root = import_root(import_record.module, import_record.source_names) - if root is None or normalize_dependency_name(root) != package: - return None - return PublicExternalTypeImportContext( - dependency=package, - import_specifier=import_record.module or root, - direct_names=_direct_import_names(import_record, root), - imported_names=tuple( - name - for name in (*import_record.names, *import_record.source_names) - if name != "*" - ), - ) - - -def _direct_import_names(import_record: PythonImport, root: str) -> tuple[str, ...]: - names = [root] - if import_record.module is None: - names.extend(import_record.names) - else: - names.append(import_record.module) - return tuple(dict.fromkeys(name for name in names if name and name != "*")) - - -def _contains_reference(text: str, name: str) -> bool: - if not name: - return False - return bool(re.search(rf"(? list[PublicExternalTypeSurface]: - """Return parser-owned public type surfaces for one module.""" - - surfaces: list[PublicExternalTypeSurface] = [] - for symbol in module.symbols: - if symbol.is_public and symbol.is_top_level: - surfaces.extend(_symbol_type_surfaces(module, symbol)) - for assignment in module.assignments: - if assignment.is_public and assignment.is_top_level: - surface = _assignment_type_surface(module, assignment) - if surface is not None: - surfaces.append(surface) - return surfaces - - -def _symbol_type_surfaces( - module: PythonModuleReport, - symbol: PythonSymbol, -) -> list[PublicExternalTypeSurface]: - surfaces: list[PublicExternalTypeSurface] = [] - header_text = _symbol_header_text(module, symbol) - if header_text: - surfaces.append( - PublicExternalTypeSurface( - symbol=symbol.qualified_name, - api_kind=symbol.kind.value, - surface="signature", - type_text=header_text, - location=symbol.location, - ) - ) - surfaces.extend( - PublicExternalTypeSurface( - symbol=symbol.qualified_name, - api_kind=symbol.kind.value, - surface="decorator", - type_text=decorator, - location=symbol.location, - ) - for decorator in symbol.decorators - ) - surfaces.extend( - PublicExternalTypeSurface( - symbol=symbol.qualified_name, - api_kind=symbol.kind.value, - surface="base", - type_text=base_class, - location=symbol.location, - ) - for base_class in symbol.base_classes - ) - return surfaces - - -def _assignment_type_surface( - module: PythonModuleReport, - assignment: PythonAssignmentTarget, -) -> PublicExternalTypeSurface | None: - source_line = module.source_line(assignment.location.line) - if source_line is None or ":" not in source_line: - return None - return PublicExternalTypeSurface( - symbol=assignment.name, - api_kind="assignment", - surface="annotation", - type_text=source_line.strip()[:200], - location=assignment.location, - ) - - -def _symbol_header_text(module: PythonModuleReport, symbol: PythonSymbol) -> str: - start = max(1, symbol.location.line) - end = min(symbol.end_line or start, start + 8) - lines = module.source_lines[start - 1 : end] - if not lines: - return "" - balance = 0 - selected: list[str] = [] - for line in lines: - stripped = line.strip() - selected.append(stripped) - balance += stripped.count("(") + stripped.count("[") + stripped.count("{") - balance -= stripped.count(")") + stripped.count("]") + stripped.count("}") - if stripped.endswith(":") and balance <= 0: - break - if len(selected) >= 8: - break - return " ".join(selected)[:240] diff --git a/src/asp_python/_semantic_search_public_external_types.py b/src/asp_python/_semantic_search_public_external_types.py deleted file mode 100644 index 6da6707..0000000 --- a/src/asp_python/_semantic_search_public_external_types.py +++ /dev/null @@ -1,299 +0,0 @@ -"""Public API surfaces that expose external dependency types.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import ( - header, -) -from ._semantic_search_deps import ( - dependency_matches, - normalize_dependency_name, -) -from ._semantic_search_model import MAX_SYMBOL_HITS -from ._semantic_search_owners import owners_for_paths -from ._semantic_search_packages import dependencies -from ._semantic_search_public_external_type_hits import public_external_type_hits -from ._semantic_search_view_hits import hit_next_actions - -if TYPE_CHECKING: - from python_lang_parser import ( - PythonReasoningTreeFacts, - ) - - from ._model import AspPythonReport - - -def public_external_types_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> dict[str, Any]: - """Build public external type/API payloads.""" - - package = normalize_dependency_name(query.strip()) - manifest_matches = _manifest_matches(facts, package) - hits = ( - [] - if not package - else public_external_type_hits(report, project_root, package)[:MAX_SYMBOL_HITS] - ) - type_surfaces = _public_external_type_surfaces(hits, package) - owners = owners_for_paths(facts, project_root, [hit["ownerPath"] for hit in hits]) - confirmed = sum(1 for hit in hits if hit["reason"] == "public-external-type") - possible = sum( - 1 for hit in hits if hit["reason"] == "possible-public-external-type" - ) - return { - "header": header( - "public-external-types", - { - "q": query, - "package": package, - "manifest": len(manifest_matches), - "own": len(owners), - "hit": len(hits), - "source": "native-parser", - "view": "hits", - }, - ), - "nodes": [ - { - "id": f"D:{package}", - "kind": "dependency", - "fields": { - "manifest": len(manifest_matches), - "confirmed": confirmed, - "possible": possible, - }, - } - ] - if package - else [], - "owners": owners, - "hits": hits, - "typeSurfaces": type_surfaces, - "nextActions": ( - ([] if not package else [{"kind": "dependency", "target": package}]) - + hit_next_actions(hits) - )[:8], - "notes": _public_external_type_notes(query, hits), - } - - -def _manifest_matches(facts: PythonReasoningTreeFacts, package: str): - return [item for item in dependencies(facts) if dependency_matches(item, package)] - - -def _public_external_type_surfaces( - hits: list[dict[str, Any]], - package: str, -) -> list[dict[str, Any]]: - surfaces: list[dict[str, Any]] = [] - for index, hit in enumerate(hits): - surface_doc = _public_external_type_surface(hit, package, index) - if surface_doc is None: - continue - surfaces.append(surface_doc) - return surfaces - - -def _public_external_type_surface( - hit: dict[str, Any], - package: str, - index: int, -) -> dict[str, Any] | None: - parts = _public_external_type_hit_parts(hit, package) - if parts is None: - return None - surface_doc = _public_external_type_surface_doc( - parts["owner_path"], - parts["name"], - parts["type_text"], - parts["surface"], - parts["api_kind"], - package, - parts["import_specifier"], - parts["fields"], - index, - ) - _attach_public_external_type_location(surface_doc, hit) - return surface_doc - - -def _public_external_type_hit_parts( - hit: dict[str, Any], - package: str, -) -> dict[str, Any] | None: - fields = hit.get("fields", {}) - if not isinstance(fields, dict): - return None - owner_path = _string_field(hit.get("ownerPath")) - if owner_path is None: - return None - symbol = _string_field(hit.get("symbol")) - surface = _string_field(fields.get("surface")) - api_kind = _string_field(fields.get("apiKind")) - type_text = _string_field(fields.get("typeText")) or symbol or "unknown" - import_specifier = _string_field(fields.get("importSpecifier")) or package - name = symbol or type_text - return { - "fields": fields, - "owner_path": owner_path, - "name": name, - "type_text": type_text, - "surface": surface, - "api_kind": api_kind, - "import_specifier": import_specifier, - } - - -def _attach_public_external_type_location( - surface_doc: dict[str, Any], - hit: dict[str, Any], -) -> None: - location = hit.get("location") - if isinstance(location, dict): - surface_doc["location"] = location - - -def _public_external_type_surface_doc( - owner_path: str, - name: str, - type_text: str, - surface: str | None, - api_kind: str | None, - package: str, - import_specifier: str, - fields: dict[Any, Any], - index: int, -) -> dict[str, Any]: - return { - "id": f"PY:{owner_path}:{name}:{surface or index}", - "name": name, - "languageName": name, - "qualifiedName": type_text, - "kind": _type_surface_kind(api_kind, surface), - "role": _type_surface_role(api_kind, surface), - "ownerPath": owner_path, - "visibility": "public", - "external": True, - "source": _string_field(fields.get("source")) or "native-parser", - "package": package, - "module": import_specifier, - "symbol": name, - "carrier": _public_external_type_carrier( - type_text, - api_kind, - package, - import_specifier, - ), - "fields": _type_surface_fields(fields, package), - } - - -def _public_external_type_carrier( - type_text: str, - api_kind: str | None, - package: str, - import_specifier: str, -) -> dict[str, Any]: - return { - "name": type_text, - "languageName": type_text, - "qualifiedName": type_text, - "carrier": _carrier_kind(type_text, api_kind), - "package": package, - "module": import_specifier, - "versionScope": "external", - "external": True, - } - - -def _type_surface_fields( - fields: dict[Any, Any], - package: str, -) -> dict[str, Any]: - surface_fields: dict[str, Any] = {"dependency": package} - for key, value in fields.items(): - if not isinstance(key, str): - continue - if isinstance(value, (str, int, float, bool)): - surface_fields[key] = value - elif isinstance(value, list) and all( - isinstance(item, (str, int, float, bool)) for item in value - ): - surface_fields[key] = value - return surface_fields - - -def _string_field(value: Any) -> str | None: - return value if isinstance(value, str) and value else None - - -def _type_surface_kind(api_kind: str | None, surface: str | None) -> str: - if api_kind == "class": - return "class" - if api_kind == "function": - return "function" - if api_kind == "type": - return "alias" - if surface and surface.startswith("field:"): - return "object" - return "unknown" - - -def _type_surface_role(api_kind: str | None, surface: str | None) -> str: - if surface and surface.startswith("param:"): - return "api-input" - if surface in {"return", "success"}: - return "api-output" - if surface == "error": - return "api-error" - if surface and surface.startswith("field:"): - return "api-field" - if surface == "alias" or api_kind == "type": - return "public-type-alias" - return "external-dependency" - - -def _carrier_kind(type_text: str, api_kind: str | None) -> str: - stripped = type_text.strip() - lowered = stripped.lower() - if "|" in stripped or lowered.startswith("typing.union["): - return "union" - if lowered.startswith(("list[", "tuple[", "set[", "frozenset[", "sequence[")): - return "array" - if lowered.startswith(("dict[", "mapping[", "mutablemapping[")): - return "map" - if api_kind == "class" or lowered.startswith("class "): - return "class" - if api_kind == "function" or lowered.startswith("def "): - return "function" - if stripped in {"str", "int", "float", "bool", "bytes", "None"}: - return "primitive" - return "external" - - -def _public_external_type_notes( - query: str, - hits: list[dict[str, Any]], -) -> list[dict[str, str]]: - if not query.strip(): - return [ - { - "kind": "empty-query", - "message": "public-external-types search requires a dependency package query", - } - ] - if not hits: - return [ - { - "kind": "not-found", - "message": f"public external type surfaces not found: {query}", - } - ] - return [] diff --git a/src/asp_python/_semantic_search_reasoning.py b/src/asp_python/_semantic_search_reasoning.py deleted file mode 100644 index 26fcb64..0000000 --- a/src/asp_python/_semantic_search_reasoning.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Typed reasoning entry payloads for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header -from ._semantic_search_model import PythonSemanticSearchOptions -from ._semantic_search_owners import owners_for_paths -from ._semantic_search_view_core import owner_payload -from ._semantic_search_view_deps_imports import dependency_payload -from ._semantic_search_view_hits import tests_payload - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import AspPythonReport - - -_OWNER_TESTS_RETURNS = ["covering-tests", "test-entrypoints", "fixtures"] -_OWNER_QUERY_RETURNS = ["items", "tests", "dependency-usage"] -_QUERY_DEPS_RETURNS = ["owners", "imports", "usage-tests"] - - -def reasoning_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Build payloads for explicit graph reasoning entries.""" - - profile = options.query or "" - match profile: - case "owner-tests": - owner = _required(options.owner_path, "--owner", profile) - payload = tests_payload(report, facts, project_root, owner) - _ensure_owner_selector(payload, facts, project_root, owner) - return _with_reasoning_header( - payload, - profile=profile, - owner_path=owner, - returns=_OWNER_TESTS_RETURNS, - ) - case "owner-query": - owner = _required(options.owner_path, "--owner", profile) - query = _required(options.item_query, "--query", profile) - payload = owner_payload( - report, - facts, - project_root, - owner, - pipes=("items",), - item_query=query, - ) - return _with_reasoning_header( - payload, - profile=profile, - owner_path=owner, - query=query, - returns=_OWNER_QUERY_RETURNS, - ) - case "query-deps": - query = _required(options.item_query, "--query", profile) - dependency = _required(options.dependency, "--dependency", profile) - dep_query = f"{dependency}::{query}" - payload = dependency_payload(report, facts, project_root, dep_query, "deps") - return _with_reasoning_header( - payload, - profile=profile, - query=query, - dependency=dependency, - returns=_QUERY_DEPS_RETURNS, - ) - case _: - raise ValueError( - "unknown reasoning profile: " - f"{profile}; expected owner-tests, owner-query, or query-deps" - ) - - -def _with_reasoning_header( - payload: dict[str, Any], - *, - profile: str, - returns: list[str], - owner_path: str | None = None, - query: str | None = None, - dependency: str | None = None, -) -> dict[str, Any]: - payload["header"] = header( - "reasoning", - { - "profile": profile, - "ownerPath": owner_path, - "query": query, - "dependency": dependency, - "returns": returns, - "owner": len(payload.get("owners", [])), - "hit": len(payload.get("hits", [])), - "item": len(payload.get("items", [])), - }, - ) - return payload - - -def _ensure_owner_selector( - payload: dict[str, Any], - facts: PythonReasoningTreeFacts, - project_root: Path, - owner_path: str, -) -> None: - existing_paths = {owner["path"] for owner in payload.get("owners", [])} - if owner_path in existing_paths: - return - payload["owners"] = [ - *payload.get("owners", []), - *owners_for_paths(facts, project_root, [owner_path]), - ] - - -def _required(value: str | None, flag: str, profile: str) -> str: - if value is None or not value: - raise ValueError(f"search reasoning {profile} requires {flag}") - return value diff --git a/src/asp_python/_semantic_search_render.py b/src/asp_python/_semantic_search_render.py deleted file mode 100644 index f5b63b7..0000000 --- a/src/asp_python/_semantic_search_render.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Compact and JSON renderers for Python semantic-search packets.""" - -from __future__ import annotations - -import json -from typing import Any - -from ._semantic_search_render_compact import render_compact_packet - - -def render_python_semantic_search_packet_json(packet: dict[str, Any]) -> str: - """Render a semantic-search packet as compact JSON.""" - - return json.dumps(packet, separators=(",", ":"), sort_keys=False) + "\n" - - -def render_python_semantic_search_packet(packet: dict[str, Any]) -> str: - """Render a compact line-oriented semantic-search packet.""" - - return render_compact_packet(packet) diff --git a/src/asp_python/_semantic_search_render_compact.py b/src/asp_python/_semantic_search_render_compact.py deleted file mode 100644 index ad8b8be..0000000 --- a/src/asp_python/_semantic_search_render_compact.py +++ /dev/null @@ -1,238 +0,0 @@ -"""Compact text rendering orchestration for Python search packets.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_search_common import compact_fields, render_fields -from ._semantic_search_render_flow import ( - avoid_next_action_lines, - finding_lines, - note_lines, - seed_packet_text, - synthesis_lines, -) -from ._semantic_search_render_lines import ( - edge_lines, - hit_lines, - node_lines, - owner_lines, - package_lines, - query_coverage_lines, - render_next_action, -) - - -def render_compact_packet(packet: dict[str, Any]) -> str: - if packet["renderMode"] == "seeds": - return seed_packet_text(packet) - return "\n".join(_compact_packet_lines(packet)) + "\n" - - -def _compact_packet_lines(packet: dict[str, Any]) -> list[str]: - if _is_item_inventory_packet(packet): - return _item_inventory_packet_lines(packet) - owner_by_path = {owner["path"]: owner for owner in packet["owners"]} - lines = [ - f"[{packet['header']['kind']}] {render_fields(packet['header']['fields'])}" - ] - _extend_payload_lines(packet, owner_by_path, lines) - _extend_flow_lines(packet, lines) - return lines - - -def _is_item_inventory_packet(packet: dict[str, Any]) -> bool: - fields = packet["header"]["fields"] - return fields.get("itemQuery") is None and ( - bool(packet.get("items")) or fields.get("itemStatus") is not None - ) - - -def _item_inventory_packet_lines(packet: dict[str, Any]) -> list[str]: - fields = packet["header"]["fields"] - header_fields = compact_fields( - { - "profile": fields.get("profile"), - "ownerPath": fields.get("ownerPath"), - "query": fields.get("query"), - "dependency": fields.get("dependency"), - "returns": fields.get("returns"), - "q": fields.get("q"), - "owner": fields.get("owner"), - "item": len(packet.get("items", [])), - "pipes": fields.get("pipes"), - } - ) - lines = [f"[{packet['header']['kind']}] {render_fields(header_fields)}"] - lines.extend(_item_inventory_owner_lines(packet)) - lines.extend(item_lines(packet)) - lines.extend( - line for line in note_lines(packet) if "kind=item-not-found" not in line - ) - lines.extend(runtime_cost_lines(packet)) - return lines - - -def _item_inventory_owner_lines(packet: dict[str, Any]) -> list[str]: - lines: list[str] = [] - for owner in packet["owners"]: - fields = { - "role": owner["role"], - "public": owner["public"], - "exp": owner.get("exports", [])[:4], - **owner["fields"], - } - lines.append(f"|owner {owner['path']} {render_fields(fields)}".rstrip()) - return lines - - -def _extend_payload_lines( - packet: dict[str, Any], - owner_by_path: dict[str, dict[str, Any]], - lines: list[str], -) -> None: - from ._semantic_search_render_lines import handle_lines - - lines.extend(item_query_lines(packet)) - lines.extend(query_coverage_lines(packet)) - lines.extend(package_lines(packet)) - lines.extend(node_lines(packet)) - lines.extend(owner_lines(packet)) - lines.extend(handle_lines(packet)) - lines.extend(item_lines(packet)) - lines.extend(code_lines(packet)) - lines.extend(hit_lines(packet, owner_by_path)) - if packet["view"] not in {"workspace", "prime"}: - lines.extend(edge_lines(packet)) - lines.extend(finding_lines(packet)) - - -def _extend_flow_lines(packet: dict[str, Any], lines: list[str]) -> None: - lines.extend(note_lines(packet)) - lines.extend(runtime_cost_lines(packet)) - lines.extend(synthesis_lines(packet)) - lines.extend(avoid_next_action_lines(packet)) - lines.extend( - f"|next {','.join(render_next_action(action) for action in packet['nextActions'])}" - for _ in [None] - if packet["nextActions"] - ) - - -def runtime_cost_lines(packet: dict[str, Any]) -> list[str]: - runtime_cost = packet.get("runtimeCost") - if not isinstance(runtime_cost, dict): - return [] - fields = compact_fields( - { - "cache": runtime_cost.get("cacheStatus"), - "elapsedMs": runtime_cost.get("elapsedMs"), - "parseMs": runtime_cost.get("parseMs"), - "sourceFiles": runtime_cost.get("sourceFilesParsed"), - "packages": runtime_cost.get("packagesScanned"), - "reused": runtime_cost.get("parserFactsReused"), - **runtime_cost.get("fields", {}), - "reason": runtime_cost.get("reason"), - } - ) - return [f"|runtime {render_fields(fields)}"] - - -def item_query_lines(packet: dict[str, Any]) -> list[str]: - fields = packet["header"]["fields"] - item_query = fields.get("itemQuery") - if item_query is None: - return [] - rendered = compact_fields( - { - "itemQuery": item_query, - "status": fields.get("itemStatus"), - "match": fields.get("itemMatch"), - "item": fields.get("item"), - "reason": "parser-item-query", - "output": "names" if _item_query_names_only(fields) else None, - "next": _item_query_next(fields), - } - ) - return [f"|query {render_fields(rendered)}"] - - -def item_lines(packet: dict[str, Any]) -> list[str]: - lines: list[str] = [] - for item in packet.get("items", []): - item_fields = item.get("fields", {}) - item_name = item["name"] - fields = compact_fields( - { - "kind": item.get("kind"), - "public": True if item_fields.get("public") is True else None, - "doc": True if item_fields.get("doc") is True else None, - "next": f"syntax:{item_name}", - "structuralSelector": item_fields.get("structuralSelector"), - "displayLineRange": item_fields.get("displayLineRange"), - "sourceLocatorHint": item_fields.get("sourceLocatorHint"), - "read": item_fields.get("read"), - "syn": _syntax_atom_for_kind(item.get("kind")), - "tsqRef": "semantic-tree-sitter-query/python-owner-items.v1", - } - ) - lines.append(f"|item {item['name']} {render_fields(fields)}") - return lines - - -def _syntax_atom_for_kind(kind: object) -> str | None: - if kind == "function": - return "function_definition/name" - if kind == "class": - return "class_definition/name" - if kind == "import": - return "import_statement/name" - if kind == "import-from": - return "import_from_statement/name" - return None - - -def code_lines(packet: dict[str, Any]) -> list[str]: - if packet["header"]["fields"].get("itemQuery") is not None: - return [] - lines: list[str] = [] - for item in packet.get("items", []): - item_fields = item.get("fields", {}) - code = item_fields.get("code") - if not isinstance(code, str) or not code: - continue - location = item.get("location", {}) - line_range = location.get("lineRange") - if not isinstance(line_range, str) and location.get("line") is not None: - line_range = f"{location['line']}:{location['line']}" - fields = compact_fields( - { - "path": location.get("path"), - "lineRange": line_range, - "reason": item_fields.get("reason"), - "truncated": item_fields.get("truncated"), - "text": code, - } - ) - lines.append(f"|code {render_fields(fields)}") - return lines - - -def _item_query_names_only(fields: dict[str, Any]) -> bool: - if fields.get("itemQuery") is None: - return False - item = fields.get("item") - item_count = item if isinstance(item, int) else 0 - if fields.get("itemStatus") == "miss": - return True - return item_count > 1 and fields.get("itemMatch") != "exact" - - -def _item_query_next(fields: dict[str, Any]) -> str: - item = fields.get("item") - item_count = item if isinstance(item, int) else 0 - if fields.get("itemStatus") == "miss" or item_count == 0: - return "revise-query" - if _item_query_names_only(fields) and item_count > 1: - return "select-item" - return "query-code" diff --git a/src/asp_python/_semantic_search_render_flow.py b/src/asp_python/_semantic_search_render_flow.py deleted file mode 100644 index 06c1ebf..0000000 --- a/src/asp_python/_semantic_search_render_flow.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Flow, note, and synthesis renderers for semantic-search packets.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_search_common import escape_field_value, escape_scalar, render_fields -from ._semantic_search_render_lines import ( - render_next_action, -) - - -def finding_lines(packet: dict[str, Any]) -> list[str]: - lines: list[str] = [] - for finding in packet["findings"]: - location = finding["location"] - fields: dict[str, Any] = { - "path": location["path"], - } - if "line" in location: - fields["line"] = location["line"] - if "column" in location: - fields["column"] = location["column"] - fields["node"] = f"O:{location['path']}" - fields["severity"] = finding["severity"] - lines.append( - f"|find {finding['ruleId']} x{finding['count']} {render_fields(fields)}".rstrip() - ) - return lines - - -def note_lines(packet: dict[str, Any]) -> list[str]: - return [ - f"|note kind={note['kind']} message={escape_field_value(note['message'])}" - for note in packet["notes"] - ] - - -def synthesis_lines(packet: dict[str, Any]) -> list[str]: - synthesis = packet.get("searchSynthesis") - if not synthesis: - return [] - fields = { - "algorithm": synthesis.get("algorithm", ""), - "scope": synthesis.get("scope", ""), - "summary": synthesis.get("summary", ""), - "ownerPath": synthesis.get("ownerPath", ""), - "selectedOwners": synthesis.get("selectedOwners", ""), - "selectedEdges": synthesis.get("selectedEdges", ""), - "incomingOwners": synthesis.get("incomingOwners", ""), - "outgoingOwners": synthesis.get("outgoingOwners", ""), - "highImpactOwners": synthesis.get("highImpactOwners", []), - "frontierOwners": synthesis.get("frontierOwners", []), - "editFrontier": synthesis.get("editFrontier", []), - "testFrontier": synthesis.get("testFrontier", []), - "windowSet": [ - render_next_action(action) for action in synthesis.get("windowSet", []) - ], - "findingOwners": synthesis.get("findingOwners", []), - } - lines = [f"|synthesis {render_fields(fields)}".rstrip()] - seeds = synthesis.get("seeds", []) - if seeds: - lines.extend(_seed_action_lines(seeds)) - return lines - - -def avoid_next_action_lines(packet: dict[str, Any]) -> list[str]: - return [ - f"|avoid {render_next_action(action)} reason={escape_scalar(action['reason'])}" - for action in packet.get("avoidNextActions", []) - ] - - -def seed_packet_text(packet: dict[str, Any]) -> str: - from ._semantic_search_graph_render import compact_graph_seed_packet_text - - return compact_graph_seed_packet_text(packet, render_fields) - - -def _seed_lines(packet: dict[str, Any]) -> list[str]: - groups = _seed_groups(packet) - return [f"|seed {kind}:{','.join(values)}" for kind, values in groups.items()] - - -def _seed_action_lines(actions: list[dict[str, Any]]) -> list[str]: - groups: dict[str, list[str]] = {} - for action in actions: - kind = action.get("kind") - target = action.get("target") - if isinstance(kind, str) and isinstance(target, str): - _add_seed(groups, kind, target) - return [f"|seed {kind}:{','.join(values)}" for kind, values in groups.items()] - - -def _seed_groups(packet: dict[str, Any]) -> dict[str, list[str]]: - groups: dict[str, list[str]] = {} - for owner in packet["owners"]: - _add_seed(groups, "owner", owner["path"]) - for export_name in owner.get("exports", [])[:4]: - _add_seed(groups, "symbol", export_name) - for hit in packet["hits"]: - _add_seed(groups, "owner", hit["ownerPath"]) - if "symbol" in hit: - _add_seed(groups, "symbol", hit["symbol"]) - for handle in packet.get("semanticHandles", []): - owner_path = handle.get("implementationOwnerPath") or handle.get("ownerPath") - if isinstance(owner_path, str): - _add_seed(groups, "owner", owner_path) - for test_path in handle.get("testPaths", [])[:4]: - _add_seed(groups, "tests", test_path) - for action in packet["nextActions"]: - _add_seed(groups, action["kind"], action["target"]) - return groups - - -def _add_seed(groups: dict[str, list[str]], kind: str, target: str) -> None: - values = groups.setdefault(kind, []) - if len(values) >= 8 or target in values: - return - values.append(target) diff --git a/src/asp_python/_semantic_search_render_lines.py b/src/asp_python/_semantic_search_render_lines.py deleted file mode 100644 index 917ed67..0000000 --- a/src/asp_python/_semantic_search_render_lines.py +++ /dev/null @@ -1,186 +0,0 @@ -"""Line renderers for Python semantic-search packet facts.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_search_common import escape_scalar, render_fields, render_location -from ._semantic_search_model import Fields -from .verification.facts import is_test_path - -COMPACT_PIPE_OWNER_LINES = 4 -COMPACT_PIPE_HIT_LINES = 6 -COMPACT_PIPE_EDGE_LINES = 4 - - -def package_lines(packet: dict[str, Any]) -> list[str]: - return [ - f"|package {package['id']} {render_fields(package['fields'])}".rstrip() - for package in packet.get("packages", []) - ] - - -def node_lines(packet: dict[str, Any]) -> list[str]: - allowed = {"owner", "dependency", "test", "symbol", "package"} - return [ - f"|{node['kind']} {node.get('path') or node['id']} {render_fields(node['fields'])}".rstrip() - for node in packet["nodes"] - if node["kind"] in allowed - ] - - -def owner_lines(packet: dict[str, Any]) -> list[str]: - lines = [] - for owner in _compact_items(packet, packet["owners"], COMPACT_PIPE_OWNER_LINES): - fields: Fields = { - "role": owner["role"], - "public": owner["public"], - "exp": owner.get("exports", [])[:4], - **owner["fields"], - } - owner_next = render_owner_next_actions( - owner["path"], - owner.get("nextActions", []), - ) - if owner_next: - fields["next"] = owner_next - lines.append(f"|owner {owner['path']} {render_fields(fields)}".rstrip()) - return lines - - -def hit_lines( - packet: dict[str, Any], - owner_by_path: dict[str, dict[str, Any]], -) -> list[str]: - lines = [] - for hit in _compact_hits(packet): - owner_path = hit["ownerPath"] - location = hit["location"] - owner_role = owner_by_path.get(owner_path, {}).get("role") - fields: Fields = { - "kind": hit["kind"], - "score": hit["score"], - "reason": hit["reason"], - **({"symbol": hit["symbol"]} if "symbol" in hit else {}), - **({"owner": owner_path} if owner_path != location.get("path") else {}), - **_hit_evidence_fields(packet, owner_role, hit), - **hit.get("fields", {}), - } - line_kind = "api" if hit["kind"] == "api" else "hit" - lines.append( - f"|{line_kind} {render_location(location)} {render_fields(fields)}".rstrip() - ) - return lines - - -def query_coverage_lines(packet: dict[str, Any]) -> list[str]: - lines = [] - for query in packet.get("queryCoverage", []): - fields: Fields = { - "status": query["status"], - "hit": query["hitCount"], - "selected": query.get("fields", {}).get("selectedHits", 0), - **({"surface": query["surfaces"][:4]} if query.get("surfaces") else {}), - **({"owner": query["ownerPaths"][:4]} if query.get("ownerPaths") else {}), - } - lines.append(f"|query {escape_scalar(query['value'])} {render_fields(fields)}") - return lines - - -def handle_lines(packet: dict[str, Any]) -> list[str]: - lines = [] - for handle in packet.get("semanticHandles", []): - raw_fields = { - "kind": handle["kind"], - "source": handle["source"], - "title": handle["title"], - "owner": handle.get("ownerPath"), - "implementation": handle.get("implementationOwnerPath"), - "tests": handle.get("testPaths", [])[:4], - **handle.get("fields", {}), - } - fields = { - key: value - for key, value in raw_fields.items() - if value is not None and value != [] and value != "" - } - lines.append(f"|handle {handle['id']} {render_fields(fields)}".rstrip()) - return lines - - -def edge_lines(packet: dict[str, Any]) -> list[str]: - lines = [] - for edge in _compact_items(packet, packet["edges"], COMPACT_PIPE_EDGE_LINES): - fields = ( - f" {render_fields(edge.get('fields', {}))}" if edge.get("fields") else "" - ) - lines.append(f"|edge {edge['from']} -{edge['kind']}-> {edge['to']}{fields}") - return lines - - -def render_owner_next_actions( - owner_path: str, - actions: list[dict[str, Any]], -) -> list[str]: - return [ - render_next_action(action, context_owner_path=owner_path) - for action in actions - if action["kind"] != "owner" or action["target"] != owner_path - ] - - -def render_next_action( - action: dict[str, Any], - *, - context_owner_path: str | None = None, -) -> str: - suffix = "" - if action.get("ownerPath") and action["ownerPath"] != context_owner_path: - suffix = f"(owner={action['ownerPath']})" - elif action.get("scope"): - suffix = f"(scope={action['scope']})" - return f"{action['kind']}:{escape_scalar(action['target'])}{suffix}" - - -def _compact_hits(packet: dict[str, Any]) -> list[dict[str, Any]]: - hits = packet["hits"] - if packet["view"] != "text": - return _compact_items(packet, hits, COMPACT_PIPE_HIT_LINES) - return sorted(hits, key=_text_hit_compact_rank)[:COMPACT_PIPE_HIT_LINES] - - -def _text_hit_compact_rank(hit: dict[str, Any]) -> tuple[int, int]: - fields = hit.get("fields", {}) - if fields.get("source") == "parser-visible-source": - return (0, -int(hit.get("score", 0))) - if hit.get("kind") == "symbol": - return (1, -int(hit.get("score", 0))) - if hit.get("kind") == "export": - return (2, -int(hit.get("score", 0))) - return (3, -int(hit.get("score", 0))) - - -def _compact_items( - packet: dict[str, Any], - items: list[dict[str, Any]], - limit: int, -) -> list[dict[str, Any]]: - if packet["view"] in {"text", "ingest"}: - return items[:limit] - return items - - -def _hit_evidence_fields( - packet: dict[str, Any], - owner_role: str | None, - hit: dict[str, Any], -) -> Fields: - if packet["view"] not in {"text", "ingest"}: - return {} - owner_path = hit["ownerPath"] - fields: Fields = {"surface": "test" if is_test_path(owner_path) else "source"} - if owner_role: - fields["ownerRole"] = owner_role - if hit.get("snippet"): - fields["text"] = hit["snippet"] - return fields diff --git a/src/asp_python/_semantic_search_symbol_hits.py b/src/asp_python/_semantic_search_symbol_hits.py deleted file mode 100644 index 5bd8236..0000000 --- a/src/asp_python/_semantic_search_symbol_hits.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Symbol and API hit builders for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import ( - dedupe_hits, - location_from_source, - path_hit, - semantic_search_display_path, -) -from ._semantic_search_deps import module_owner_path - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts, PythonSymbol - - from ._model import AspPythonReport - - -def api_hits( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return public export and public symbol hits.""" - - query_folded = query.casefold() - hits = [ - path_hit( - semantic_search_display_path(node.path, project_root), - semantic_search_display_path(node.path, project_root), - kind="api", - symbol=name, - score=4, - reason="public-export", - fields={"namespace": ".".join(node.namespace)}, - ) - for node in facts.nodes - for name in node.public_names - if query_folded in name.casefold() - ] - hits.extend(symbol_hits(report, project_root, query, public_only=True)) - return dedupe_hits(hits) - - -def symbol_hits( - report: AspPythonReport, - project_root: Path, - query: str, - *, - public_only: bool = False, -) -> list[dict[str, Any]]: - """Return parser symbol/export hits.""" - - query_folded = query.casefold() - hits = [ - hit - for module in report.modules - for hit in ( - *_module_symbol_hits( - module, - project_root, - query_folded, - public_only=public_only, - ), - *_module_export_hits(module, project_root, query_folded), - ) - ] - return dedupe_hits(hits) - - -def symbol_hit( - symbol: PythonSymbol, - owner_path: str, - project_root: Path, - *, - kind: str, -) -> dict[str, Any]: - """Return one symbol hit.""" - - return { - "kind": kind, - "ownerPath": owner_path, - "location": location_from_source(symbol.location, project_root), - "score": 4, - "reason": "symbol-name", - "symbol": symbol.name, - "fields": { - "symbolKind": symbol.kind.value, - "qualified": symbol.qualified_name, - "public": symbol.is_public, - }, - } - - -def _module_symbol_hits( - module, - project_root: Path, - query_folded: str, - *, - public_only: bool, -) -> list[dict[str, Any]]: - owner_path = module_owner_path(module, project_root) - kind = "api" if public_only else "symbol" - return [ - symbol_hit(symbol, owner_path, project_root, kind=kind) - for symbol in module.symbols - if _symbol_matches(symbol, query_folded, public_only=public_only) - ] - - -def _module_export_hits( - module, - project_root: Path, - query_folded: str, -) -> list[dict[str, Any]]: - owner_path = module_owner_path(module, project_root) - return [ - path_hit( - owner_path, - owner_path, - kind="export", - symbol=export_name, - score=3, - reason="export-name", - ) - for export_name in module.export_candidates - if query_folded in export_name.casefold() - ] - - -def _symbol_matches( - symbol: PythonSymbol, - query_folded: str, - *, - public_only: bool, -) -> bool: - if public_only and not symbol.is_public: - return False - names = (symbol.name, symbol.qualified_name) - return any(query_folded in name.casefold() for name in names) diff --git a/src/asp_python/_semantic_search_text_hits.py b/src/asp_python/_semantic_search_text_hits.py deleted file mode 100644 index 86c8356..0000000 --- a/src/asp_python/_semantic_search_text_hits.py +++ /dev/null @@ -1,207 +0,0 @@ -"""Text hit builders for Python semantic search.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import ( - dedupe_hits, - location, - path_hit, - semantic_search_display_path, -) -from ._semantic_search_deps import module_owner_path -from ._semantic_search_symbol_hits import symbol_hits - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import AspPythonReport - - -def text_hits( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return owner path, export, symbol, and source-line text hits.""" - - if not query: - return [] - query_folded = query.casefold() - hits = [ - hit - for node in facts.nodes - for hit in _node_text_hits(node, project_root, query_folded) - ] - hits.extend(symbol_hits(report, project_root, query)) - hits.extend( - hit - for module in report.modules - for hit in _module_source_line_hits(module, project_root, query_folded) - ) - return dedupe_hits(hits) - - -def fuzzy_text_hits( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - """Return owner path, export, symbol, and source-line fuzzy hits.""" - if not query: - return [] - query_folded = query.casefold() - hits = [ - hit - for node in facts.nodes - for hit in _node_fuzzy_text_hits(node, project_root, query_folded) - ] - hits.extend(symbol_hits(report, project_root, query)) - hits.extend( - hit - for module in report.modules - for hit in _module_fuzzy_source_line_hits(module, project_root, query_folded) - ) - return dedupe_hits(hits) - - -def _node_text_hits( - node, project_root: Path, query_folded: str -) -> list[dict[str, Any]]: - owner_path = semantic_search_display_path(node.path, project_root) - hits = _node_path_hits(node, owner_path, query_folded) - hits.extend( - path_hit( - owner_path, - owner_path, - kind="export", - symbol=export_name, - score=4, - reason="export-name", - ) - for export_name in node.public_names - if query_folded in export_name.casefold() - ) - return hits - - -def _node_fuzzy_text_hits( - node, project_root: Path, query_folded: str -) -> list[dict[str, Any]]: - owner_path = semantic_search_display_path(node.path, project_root) - hits = _node_fuzzy_path_hits(node, owner_path, query_folded) - export_hits = [] - for export_name in node.public_names: - score = _fuzzy_score(export_name, query_folded) - if score is None: - continue - export_hits.append( - path_hit( - owner_path, - owner_path, - kind="export", - symbol=export_name, - score=score, - reason="export-name-fuzzy", - ) - ) - hits.extend(sorted(export_hits, key=lambda hit: -hit["score"])[:6]) - return hits - - -def _node_path_hits(node, owner_path: str, query_folded: str) -> list[dict[str, Any]]: - namespace = ".".join(node.namespace) - if ( - query_folded not in owner_path.casefold() - and query_folded not in namespace.casefold() - ): - return [] - return [path_hit(owner_path, owner_path, score=3, reason="owner-path")] - - -def _node_fuzzy_path_hits( - node, owner_path: str, query_folded: str -) -> list[dict[str, Any]]: - namespace = ".".join(node.namespace) - score = max( - _fuzzy_score(owner_path, query_folded) or 0, - _fuzzy_score(namespace, query_folded) or 0, - ) - if score == 0: - return [] - return [path_hit(owner_path, owner_path, score=score, reason="owner-path-fuzzy")] - - -def _module_source_line_hits( - module, - project_root: Path, - query_folded: str, -) -> list[dict[str, Any]]: - owner_path = module_owner_path(module, project_root) - return [ - { - "kind": "text", - "ownerPath": owner_path, - "location": location(owner_path, line_number), - "score": 2, - "reason": "source-text", - "snippet": source_line.strip()[:160], - "fields": {"source": "parser-visible-source"}, - } - for line_number, source_line in enumerate(module.source_lines, start=1) - if query_folded in source_line.casefold() - ] - - -def _module_fuzzy_source_line_hits( - module, - project_root: Path, - query_folded: str, -) -> list[dict[str, Any]]: - owner_path = module_owner_path(module, project_root) - hits: list[dict[str, Any]] = [] - for line_number, source_line in enumerate(module.source_lines, start=1): - score = _fuzzy_score(source_line, query_folded) - if score is None: - continue - hits.append( - { - "kind": "text", - "ownerPath": owner_path, - "location": location(owner_path, line_number), - "score": score, - "reason": "source-text-fuzzy", - "snippet": source_line.strip()[:160], - "fields": {"source": "parser-visible-source", "matchMode": "fuzzy"}, - } - ) - return hits - - -def _fuzzy_score(candidate: str, query_folded: str) -> int | None: - query = "".join(query_folded.casefold().split()) - if not query: - return None - candidate_folded = candidate.casefold() - exact_index = candidate_folded.find(query) - if exact_index >= 0: - return 12 + len(query) - positions: list[int] = [] - cursor = 0 - for char in query: - index = candidate_folded.find(char, cursor) - if index < 0: - return None - positions.append(index) - cursor = index + len(char) - if not positions: - return None - span = positions[-1] - positions[0] + 1 - if span > max(len(query) * 3, len(query) + 12): - return None - compactness = max(0, len(positions) * 2 - (span - len(positions))) - return 4 + compactness diff --git a/src/asp_python/_semantic_search_view_actions.py b/src/asp_python/_semantic_search_view_actions.py deleted file mode 100644 index 200b3f3..0000000 --- a/src/asp_python/_semantic_search_view_actions.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Shared next-action helpers for Python semantic-search views.""" - -from __future__ import annotations - -from typing import Any - - -def hit_next_actions(hits: list[dict[str, Any]]) -> list[dict[str, Any]]: - """Return bounded owner/tests follow-up actions for hits.""" - - actions: list[dict[str, Any]] = [] - seen: set[tuple[str, str]] = set() - for hit in hits: - for action in ( - {"kind": "owner", "target": hit["ownerPath"]}, - {"kind": "tests", "target": hit["ownerPath"]}, - ): - key = (action["kind"], action["target"]) - if key in seen: - continue - seen.add(key) - actions.append(action) - if len(actions) >= 8: - return actions - return actions diff --git a/src/asp_python/_semantic_search_view_core.py b/src/asp_python/_semantic_search_view_core.py deleted file mode 100644 index a118622..0000000 --- a/src/asp_python/_semantic_search_view_core.py +++ /dev/null @@ -1,333 +0,0 @@ -"""Core workspace, prime, and owner semantic-search views.""" - -from __future__ import annotations - -from collections.abc import Iterable -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header, path_hit -from ._semantic_search_deps import dependency_node -from ._semantic_search_findings import finding_facts -from ._semantic_search_items import owner_item_query_payload -from ._semantic_search_model import ( - MAX_DEPENDENCY_HITS, - MAX_IMPORT_HITS, - MAX_PRIME_EDGES, - MAX_PRIME_OWNERS, - MAX_WORKSPACE_EDGES, -) -from ._semantic_search_owners import ( - import_edges, - matching_owner_nodes, - owner_nodes, - owner_record, - ranked_owner_records, -) -from ._semantic_search_packages import dependencies, project_name, workspace_packages - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import AspPythonReport - - -def workspace_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, -) -> dict[str, Any]: - """Build the workspace/router packet payload.""" - - packages = workspace_packages(report, facts, project_root, len(owner_nodes(facts))) - edges = import_edges(facts, project_root, limit=MAX_WORKSPACE_EDGES) - owners = _workspace_owner_records(packages) - return { - "header": header( - "workspace", - { - "mode": "workspace-index", - "package": project_name(facts), - "packages": len(packages), - "shown": len(packages), - "edge": len(edges), - "external": len(dependencies(facts)), - "find": len(report.findings), - }, - ), - "packages": packages, - "owners": owners, - "edges": edges, - "findings": finding_facts(report, project_root), - "nextActions": [ - {"kind": "owner", "target": item["path"]} for item in owners[:8] - ], - } - - -def prime_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, -) -> dict[str, Any]: - """Build the prime project map payload.""" - - owners = ranked_owner_records(facts, project_root)[:MAX_PRIME_OWNERS] - dep_nodes = [dependency_node(item) for item in dependencies(facts)] - edges = import_edges(facts, project_root, limit=MAX_PRIME_EDGES) - findings = finding_facts(report, project_root) - return { - "header": header( - "prime", - { - "mode": "prime-map", - "package": project_name(facts), - "owner": len(owner_nodes(facts)), - "shown": len(owners), - "edge": len(edges), - "external": len(dep_nodes), - "find": len(report.findings), - }, - ), - "nodes": dep_nodes[:MAX_DEPENDENCY_HITS], - "owners": owners, - "edges": edges, - "findings": findings, - "nextActions": _prime_next_actions(owners, dep_nodes), - "searchSynthesis": _prime_graph_synthesis( - owners, - edges, - findings, - ), - } - - -def owner_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, - *, - pipes: tuple[str, ...] = (), - item_query: str | None = None, -) -> dict[str, Any]: - """Build an owner slice payload.""" - - matches = matching_owner_nodes(facts, project_root, query) - owners = [owner_record(node, project_root) for node in matches[:MAX_PRIME_OWNERS]] - owner_paths = {owner["path"] for owner in owners} - edges = [ - edge - for edge in import_edges(facts, project_root, limit=MAX_IMPORT_HITS) - if edge["from"].removeprefix("O:") in owner_paths - or edge["to"].removeprefix("O:") in owner_paths - ] - findings = finding_facts(report, project_root, owner_paths=owner_paths) - item_payload = ( - owner_item_query_payload(report, project_root, query, item_query) - if "items" in pipes - else {"items": [], "fields": {}, "notes": []} - ) - return { - "header": header( - "owner", - { - "q": query, - "owner": len(owners), - "edge": len(edges), - **item_payload["fields"], - }, - ), - "owners": owners, - "edges": edges, - "items": item_payload["items"], - "hits": [ - path_hit(owner["path"], owner["path"], score=4, reason="owner-match") - for owner in owners - ], - "findings": findings, - "nextActions": _owner_next_actions(owners), - "searchSynthesis": _owner_graph_synthesis(owners, edges, findings), - "notes": [ - *([] if owners else [{"kind": "owner-not-found", "message": query}]), - *item_payload["notes"], - ], - } - - -def _prime_next_actions( - owners: list[dict[str, Any]], - dep_nodes: list[dict[str, Any]], -) -> list[dict[str, Any]]: - actions: list[dict[str, Any]] = [] - for owner in owners[:3]: - actions.append({"kind": "owner", "target": owner["path"]}) - if owner.get("exports"): - actions.append( - { - "kind": "text", - "target": owner["exports"][0], - "ownerPath": owner["path"], - } - ) - actions.extend( - {"kind": "deps", "target": dependency} - for dependency in _unique_dependencies(dep_nodes) - ) - return actions[:8] - - -def _workspace_owner_records(packages: list[dict[str, Any]]) -> list[dict[str, Any]]: - owners: list[dict[str, Any]] = [] - for package in packages: - package_id = str(package["id"]) - fields = dict(package.get("fields", {})) - owners.append( - { - "path": package_id, - "namespace": "." if package_id == "." else package_id.replace("/", "."), - "role": fields.get("role", "workspace-package"), - "public": True, - "exports": [], - "nextActions": [{"kind": "owner", "target": package_id}], - "fields": { - "kind": "package", - "surface": fields.get("surface", "workspace"), - "name": fields.get("name", package_id), - }, - } - ) - return owners - - -def _owner_next_actions(owners: list[dict[str, Any]]) -> list[dict[str, Any]]: - actions: list[dict[str, Any]] = [] - for owner in owners[:8]: - actions.append({"kind": "tests", "target": owner["path"]}) - actions.extend( - {"kind": "text", "target": name, "ownerPath": owner["path"]} - for name in owner.get("exports", [])[:2] - ) - return actions - - -def _unique_dependencies(dep_nodes: list[dict[str, Any]]) -> list[str]: - dependencies: list[str] = [] - seen: set[str] = set() - for node in dep_nodes: - dependency = node["id"].removeprefix("D:") - if dependency in seen: - continue - seen.add(dependency) - dependencies.append(dependency) - return dependencies - - -def _prime_graph_synthesis( - owners: list[dict[str, Any]], - edges: list[dict[str, Any]], - findings: list[dict[str, Any]], -) -> dict[str, Any] | None: - selected_owners = [owner["path"] for owner in owners] - if not selected_owners: - return None - frontier_owners = _frontier_owner_paths(selected_owners, edges) - seeds = [{"kind": "owner", "target": path} for path in frontier_owners[:4]] - return _compact_synthesis( - { - "algorithm": "owner-rank-frontier", - "scope": "prime", - "summary": "owner-graph-frontier", - "selectedOwners": len(selected_owners), - "selectedEdges": len(edges), - "highImpactOwners": selected_owners[:4], - "frontierOwners": frontier_owners[:4], - "findingOwners": _finding_owner_paths(findings), - "seeds": seeds, - } - ) - - -def _owner_graph_synthesis( - owners: list[dict[str, Any]], - edges: list[dict[str, Any]], - findings: list[dict[str, Any]], -) -> dict[str, Any] | None: - selected_owners = [owner["path"] for owner in owners] - if not selected_owners: - return None - incoming_owners, outgoing_owners = _incoming_outgoing_owner_paths( - selected_owners, - edges, - ) - frontier_owners = _dedupe([*incoming_owners, *outgoing_owners])[:4] - synthesis: dict[str, Any] = { - "algorithm": "bounded-reachability-depth1", - "scope": "owner", - "summary": "owner-graph-frontier", - "selectedOwners": len(selected_owners), - "selectedEdges": len(edges), - "incomingOwners": len(incoming_owners), - "outgoingOwners": len(outgoing_owners), - "frontierOwners": frontier_owners, - "findingOwners": _finding_owner_paths(findings), - "seeds": [{"kind": "owner", "target": path} for path in frontier_owners], - } - if len(selected_owners) == 1: - synthesis["ownerPath"] = selected_owners[0] - return _compact_synthesis(synthesis) - - -def _frontier_owner_paths( - selected_owners: list[str], - edges: list[dict[str, Any]], -) -> list[str]: - incoming_owners, outgoing_owners = _incoming_outgoing_owner_paths( - selected_owners, - edges, - ) - return _dedupe([*incoming_owners, *outgoing_owners]) - - -def _incoming_outgoing_owner_paths( - selected_owners: list[str], - edges: list[dict[str, Any]], -) -> tuple[list[str], list[str]]: - selected = set(selected_owners) - incoming: list[str] = [] - outgoing: list[str] = [] - for edge in edges: - source = _owner_id_path(edge["from"]) - target = _owner_id_path(edge["to"]) - if source in selected and target not in selected: - outgoing.append(target) - if target in selected and source not in selected: - incoming.append(source) - return _dedupe(incoming), _dedupe(outgoing) - - -def _owner_id_path(owner_id: str) -> str: - return owner_id.removeprefix("O:") - - -def _finding_owner_paths(findings: list[dict[str, Any]]) -> list[str]: - return _dedupe(finding["location"]["path"] for finding in findings)[:4] - - -def _dedupe(values: Iterable[str]) -> list[str]: - result: list[str] = [] - seen: set[str] = set() - for value in values: - if value in seen: - continue - seen.add(value) - result.append(value) - return result - - -def _compact_synthesis(synthesis: dict[str, Any]) -> dict[str, Any]: - return { - key: value - for key, value in synthesis.items() - if value is not None and value != [] and value != "" - } diff --git a/src/asp_python/_semantic_search_view_deps_imports.py b/src/asp_python/_semantic_search_view_deps_imports.py deleted file mode 100644 index 8a63170..0000000 --- a/src/asp_python/_semantic_search_view_deps_imports.py +++ /dev/null @@ -1,191 +0,0 @@ -"""Dependency and import semantic-search views for Python.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header -from ._semantic_search_deps import ( - dependency_matches, - dependency_node, - dependency_query_parts, - dependency_usage_hits, - version_scope, -) -from ._semantic_search_hits import import_hits -from ._semantic_search_model import MAX_DEPENDENCY_HITS, MAX_IMPORT_HITS -from ._semantic_search_owners import import_edges, owners_for_paths -from ._semantic_search_packages import dependencies -from ._semantic_search_view_hits import hit_next_actions - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import AspPythonReport - - -def dependency_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, - view: str, -) -> dict[str, Any]: - """Build dependency/deps payloads.""" - - parts = dependency_query_parts(query) - matches = [ - item - for item in dependencies(facts) - if dependency_matches(item, parts["package"]) - ] - include_usage = bool(parts["apiQuery"]) - usage_hits = ( - dependency_usage_hits(report, project_root, parts["package"]) - if include_usage - else [] - ) - nodes = [dependency_node(item, parts=parts) for item in matches] - scope = version_scope(parts, matches) - hits = usage_hits[:MAX_DEPENDENCY_HITS] - owners = owners_for_paths(facts, project_root, [hit["ownerPath"] for hit in hits]) - return { - "header": header( - view, - _dependency_header_fields(query, parts, nodes, usage_hits, scope, view), - ), - "nodes": nodes[:MAX_DEPENDENCY_HITS], - "owners": owners, - "hits": hits, - "nextActions": _dependency_next_actions(parts, query, hits, scope, view), - "notes": _dependency_notes(query, nodes, usage_hits, scope, view), - } - - -def import_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> dict[str, Any]: - """Build import edge/search payloads.""" - - hits = import_hits(report, project_root, query)[:MAX_IMPORT_HITS] - edge_matches = _matching_import_edges(facts, project_root, query) - paths = [hit["ownerPath"] for hit in hits] + _edge_owner_paths(edge_matches) - return { - "header": header( - "import", {"q": query, "edge": len(edge_matches), "hit": len(hits)} - ), - "owners": owners_for_paths(facts, project_root, paths), - "edges": edge_matches, - "hits": hits, - "nextActions": hit_next_actions(hits), - "notes": [] - if edge_matches or hits - else [{"kind": "not-found", "message": query}], - } - - -def _matching_import_edges( - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> list[dict[str, Any]]: - query_folded = query.casefold() - return [ - edge - for edge in import_edges(facts, project_root, limit=MAX_IMPORT_HITS) - if query_folded in edge["from"].casefold() - or query_folded in edge["to"].casefold() - or query_folded in str(edge.get("fields", {}).get("import", "")).casefold() - ] - - -def _edge_owner_paths(edges: list[dict[str, Any]]) -> list[str]: - paths: list[str] = [] - for edge in edges: - paths.extend((edge["from"].removeprefix("O:"), edge["to"].removeprefix("O:"))) - return paths - - -def _dependency_header_fields( - query: str, - parts: dict[str, str], - nodes: list[dict[str, Any]], - usage_hits: list[dict[str, Any]], - scope: str, - view: str, -) -> dict[str, Any]: - fields: dict[str, Any] = { - "q": query, - "manifest": len(nodes), - "usage": len(usage_hits), - "versionScope": scope, - "topology": "asp-owned", - } - if view == "deps": - fields.update( - { - "dep": 1 if parts["package"] else 0, - "package": parts["package"], - "api": parts["apiQuery"], - "hit": len(nodes) + len(usage_hits), - "view": "hits", - } - ) - if parts["requestedVersion"]: - fields["requestedVersion"] = parts["requestedVersion"] - return fields - - -def _dependency_next_actions( - parts: dict[str, str], - query: str, - hits: list[dict[str, Any]], - scope: str, - view: str, -) -> list[dict[str, Any]]: - package = parts["package"] - api_query = parts["apiQuery"] - if view == "deps": - actions = [ - {"kind": "dependency", "target": package}, - {"kind": "public-external-types", "target": package}, - ] - if api_query: - actions.append({"kind": "api", "target": query}) - if api_query and scope == "current": - actions.extend( - ( - {"kind": "text", "target": api_query}, - {"kind": "tests", "target": api_query}, - ) - ) - return [action for action in actions if action["target"]] - return [ - *hit_next_actions(hits)[:4], - {"kind": "public-external-types", "target": package}, - {"kind": "import", "target": query or package}, - ] - - -def _dependency_notes( - query: str, - nodes: list[dict[str, Any]], - usage_hits: list[dict[str, Any]], - scope: str, - view: str, -) -> list[dict[str, str]]: - notes: list[dict[str, str]] = [] - if not nodes and not usage_hits: - notes.append({"kind": "not-found", "message": query}) - if view == "deps" and scope == "external": - notes.append( - { - "kind": "fact-scope", - "message": "requested dependency version is outside the current workspace metadata; local usage is not attributed to that version", - } - ) - return notes diff --git a/src/asp_python/_semantic_search_view_hits.py b/src/asp_python/_semantic_search_view_hits.py deleted file mode 100644 index acc8cd6..0000000 --- a/src/asp_python/_semantic_search_view_hits.py +++ /dev/null @@ -1,153 +0,0 @@ -"""Hit-oriented semantic-search views for Python.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import dedupe, header, path_hit -from ._semantic_search_hits import test_path_hits -from ._semantic_search_model import ( - MAX_TEST_HITS, - PythonSemanticSearchOptions, -) -from ._semantic_search_owners import ( - matching_owner_nodes, - owner_record, - owners_for_paths, - test_edges, -) -from ._semantic_search_view_actions import hit_next_actions -from ._semantic_search_view_lexical_queries import ( - fair_merged_text_hits, - lexical_query_hits_by_term, - normalized_query_terms, -) -from ._semantic_search_view_lexical_synthesis import ( - avoid_next_actions, - owner_resolution, - query_coverage, - search_synthesis, -) - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import AspPythonReport - - -def tests_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, -) -> dict[str, Any]: - """Build tests-for-owner payloads.""" - - owner_paths = { - owner_record(node, project_root)["path"] - for node in matching_owner_nodes(facts, project_root, query) - } - edges = test_edges(facts, project_root, owner_paths) - hits = [ - path_hit( - edge["to"].removeprefix("O:"), - edge["to"].removeprefix("O:"), - kind="test", - score=4, - reason="test-import", - ) - for edge in edges - ] - if not hits and not owner_paths: - hits = test_path_hits(report, project_root, query) - return generic_hits_payload( - "tests", - hits[:MAX_TEST_HITS], - facts, - project_root, - query, - edges=edges[:MAX_TEST_HITS], - ) - - -def text_payload( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Build parser-visible lexical search payloads.""" - - query_terms = normalized_query_terms(options) - hits_by_term = lexical_query_hits_by_term( - report, facts, project_root, query_terms, options.owner_path - ) - hits = fair_merged_text_hits(hits_by_term) - owner_paths = dedupe( - [ - *(hit["ownerPath"] for hit in hits), - *([] if options.owner_path is None else [options.owner_path]), - ] - ) - owners = ( - owners_for_paths(facts, project_root, owner_paths) - if "owner" in options.pipes - else [] - ) - edges = ( - test_edges(facts, project_root, set(owner_paths)) - if "tests" in options.pipes - else [] - ) - return { - "header": header( - "lexical", - { - "q": options.query or ",".join(query_terms), - "querySet": len(query_terms) if options.query_set else None, - "selector": "lexical-set" if options.query_set else None, - "mode": "lexical", - "backend": "provider", - "scopeOwner": options.owner_path, - "own": len(owner_paths), - "hit": len(hits), - "view": "hits", - "pipes": list(options.pipes), - }, - ), - "owners": owners, - "edges": edges[:MAX_TEST_HITS], - "hits": hits, - "nextActions": hit_next_actions(hits), - "queryCoverage": query_coverage(hits_by_term, hits), - "ownerResolution": owner_resolution(owner_paths), - "searchSynthesis": search_synthesis(query_terms, hits, owner_paths), - "avoidNextActions": avoid_next_actions(query_terms, owner_paths), - "notes": [] - if hits - else [{"kind": "not-found", "message": options.query or ",".join(query_terms)}], - } - - -def generic_hits_payload( - view: str, - hits: list[dict[str, Any]], - facts: PythonReasoningTreeFacts, - project_root: Path, - query: str, - *, - edges: list[dict[str, Any]] | None = None, -) -> dict[str, Any]: - """Build a generic hit-oriented payload.""" - - return { - "header": header(view, {"q": query, "hit": len(hits)}), - "owners": owners_for_paths( - facts, project_root, [hit["ownerPath"] for hit in hits] - ), - "edges": edges or [], - "hits": hits, - "nextActions": hit_next_actions(hits), - "notes": [] if hits else [{"kind": "not-found", "message": query}], - } diff --git a/src/asp_python/_semantic_search_view_ingest.py b/src/asp_python/_semantic_search_view_ingest.py deleted file mode 100644 index 2611353..0000000 --- a/src/asp_python/_semantic_search_view_ingest.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Stdin ingest semantic-search view for Python.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header -from ._semantic_search_ingest import ingest_hits -from ._semantic_search_model import MAX_LEXICAL_HITS -from ._semantic_search_owners import owners_for_paths -from ._semantic_search_view_actions import hit_next_actions - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - -def ingest_payload( - facts: PythonReasoningTreeFacts, - project_root: Path, - stdin: str, -) -> dict[str, Any]: - """Build stdin ingest payloads.""" - - detection, hits = ingest_hits(facts, project_root, stdin) - hits = hits[:MAX_LEXICAL_HITS] - next_actions = hit_next_actions(hits) - notes = _ingest_notes(detection, hits) - if not hits and _empty_stdin(detection): - next_actions = _empty_stdin_actions() - return { - "header": header( - "ingest", - { - "source": detection["source"], - "hit": len(hits), - "bytes": detection["byteCount"], - }, - ), - "inputDetection": detection, - "owners": owners_for_paths( - facts, project_root, [hit["ownerPath"] for hit in hits] - ), - "hits": hits, - "nextActions": next_actions, - "notes": notes, - } - - -def _ingest_notes( - detection: dict[str, Any], hits: list[dict[str, Any]] -) -> list[dict[str, Any]]: - if hits: - return [] - if _empty_stdin(detection): - return [ - { - "kind": "stdin-required", - "message": ( - "search ingest consumes stdin candidate paths; " - "use search prime --view seeds for project discovery" - ), - } - ] - return [{"kind": "unrecognized-input", "message": "stdin produced no path hits"}] - - -def _empty_stdin(detection: dict[str, Any]) -> bool: - return detection["byteCount"] == 0 and detection["lineCount"] == 0 - - -def _empty_stdin_actions() -> list[dict[str, str]]: - return [ - { - "kind": "prime", - "target": "search prime --view seeds", - "scope": "project-discovery", - }, - { - "kind": "ingest", - "target": "pipe candidate paths into search ingest items tests --view seeds", - "scope": "stdin-candidates", - }, - ] diff --git a/src/asp_python/_semantic_search_view_knowledge.py b/src/asp_python/_semantic_search_view_knowledge.py deleted file mode 100644 index 032d35f..0000000 --- a/src/asp_python/_semantic_search_view_knowledge.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Provider-owned language and ecosystem knowledge search axes.""" - -from __future__ import annotations - -from pathlib import Path -from typing import Any - -from ._semantic_search_common import header, path_hit -from ._semantic_search_knowledge_facts import axis_detail, knowledge_facts -from ._semantic_search_model import PythonSemanticSearchOptions - - -def knowledge_payload( - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Return a semantic-search payload for provider knowledge axes.""" - - axis = options.view - detail = axis_detail(axis) - query = options.query or "" - terms = _query_terms(query) - facts = knowledge_facts(project_root, axis, terms) - hits = [ - path_hit( - ".", - ".", - kind="text", - symbol=str(fact["id"]), - score=2 if terms else 1, - reason=f"{axis}:{detail['authority']}", - fields={"axis": axis, "authority": detail["authority"], **fact["fields"]}, - ) - for fact in facts[:12] - ] - missing = not facts - return { - "header": header( - axis, - { - "q": query, - "evidenceGrade": "unknown" if missing else "fact", - "authority": detail["authority"], - "fact": len(facts), - "hit": len(hits), - }, - ), - "packages": facts, - "nodes": [ - { - "id": f"knowledge:{axis}:{fact['id']}", - "kind": "fact", - "fields": {"axis": axis, **fact["fields"]}, - } - for fact in facts - ], - "edges": [], - "owners": [], - "hits": hits, - "findings": [], - "nextActions": [ - {"kind": "lexical", "target": query or axis}, - {"kind": "owner", "target": "."}, - ], - "notes": [ - { - "kind": "fact-scope", - "message": ( - f"{axis} search did not find a provider-owned fact for the query; " - "refine the axis query or route through owner/deps/tree-sitter evidence" - if missing - else detail["summary"] - ), - }, - {"kind": "next-step", "message": detail["next"]}, - ], - } - - -def _query_terms(query: str) -> list[str]: - return [term for term in query.lower().split() if term] diff --git a/src/asp_python/_semantic_search_view_lexical_queries.py b/src/asp_python/_semantic_search_view_lexical_queries.py deleted file mode 100644 index 21789aa..0000000 --- a/src/asp_python/_semantic_search_view_lexical_queries.py +++ /dev/null @@ -1,163 +0,0 @@ -"""Text query-set hit selection helpers for Python semantic search.""" - -from __future__ import annotations - -import json -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import dedupe -from ._semantic_search_hits import text_hits -from ._semantic_search_model import MAX_LEXICAL_HITS -from .verification.facts import is_test_path - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import AspPythonReport - from ._semantic_search_model import PythonSemanticSearchOptions - - -def normalized_query_terms(options: PythonSemanticSearchOptions) -> list[str]: - if not options.query_set: - return [] if options.query is None else [options.query] - return dedupe(term.strip() for term in options.query_set if term.strip()) - - -def lexical_query_hits_by_term( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query_terms: list[str], - owner_path: str | None, -) -> dict[str, list[dict[str, Any]]]: - return { - term: sorted( - ( - _with_owner_surface(hit) - for hit in text_hits(report, facts, project_root, term) - if owner_path is None or hit["ownerPath"] == owner_path - ), - key=_text_hit_rank, - ) - for term in query_terms - } - - -def fuzzy_lexical_query_hits_by_term( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - query_terms: list[str], - owner_path: str | None, -) -> dict[str, list[dict[str, Any]]]: - from ._semantic_search_text_hits import fuzzy_text_hits - - return { - term: sorted( - ( - _with_owner_surface(hit) - for hit in fuzzy_text_hits(report, facts, project_root, term) - if owner_path is None or hit["ownerPath"] == owner_path - ), - key=_text_hit_rank, - ) - for term in query_terms - } - - -def fair_merged_text_hits( - hits_by_term: dict[str, list[dict[str, Any]]], -) -> list[dict[str, Any]]: - merged: dict[tuple[str, str, str, str], dict[str, Any]] = {} - ordered_keys: list[tuple[str, str, str, str]] = [] - ordered_terms = sorted( - hits_by_term, - key=lambda term: (-_term_specificity(term), term.casefold()), - ) - depth = max((len(hits) for hits in hits_by_term.values()), default=0) - for depth_index in range(depth): - _merge_text_hits_at_depth( - hits_by_term, - ordered_terms, - depth_index, - merged, - ordered_keys, - ) - return [merged[key] for key in ordered_keys] - - -def _merge_text_hits_at_depth( - hits_by_term: dict[str, list[dict[str, Any]]], - ordered_terms: list[str], - depth_index: int, - merged: dict[tuple[str, str, str, str], dict[str, Any]], - ordered_keys: list[tuple[str, str, str, str]], -) -> None: - for term in ordered_terms: - hits = hits_by_term[term] - if depth_index < len(hits): - _merge_text_hit(term, hits[depth_index], merged, ordered_keys) - - -def _merge_text_hit( - term: str, - hit: dict[str, Any], - merged: dict[tuple[str, str, str, str], dict[str, Any]], - ordered_keys: list[tuple[str, str, str, str]], -) -> None: - key = _hit_key(hit) - current = merged.get(key) - if current is not None: - _merge_duplicate_text_hit(term, hit, current) - return - if len(ordered_keys) >= MAX_LEXICAL_HITS: - return - fields = {**hit.get("fields", {}), "queryTerms": [term]} - merged[key] = {**hit, "fields": fields} - ordered_keys.append(key) - - -def _merge_duplicate_text_hit( - term: str, - hit: dict[str, Any], - current: dict[str, Any], -) -> None: - fields = dict(current.get("fields", {})) - fields["queryTerms"] = dedupe([*fields.get("queryTerms", []), term]) - current["fields"] = fields - current["score"] = max(int(current["score"]), int(hit["score"])) - - -def _text_hit_rank(hit: dict[str, Any]) -> tuple[int, str, str, str]: - return ( - -int(hit["score"]), - hit["ownerPath"], - hit["kind"], - json.dumps(hit["location"], sort_keys=True), - ) - - -def _hit_key(hit: dict[str, Any]) -> tuple[str, str, str, str]: - return ( - hit["kind"], - hit["ownerPath"], - hit.get("symbol", ""), - json.dumps(hit["location"], sort_keys=True), - ) - - -def _term_specificity(term: str) -> int: - compact = term.strip() - structural = sum( - 1 - for character in compact - if character in {".", "_", "/", '"', "'", "(", ")", "[", "]", ":"} - ) - return len(set(compact.casefold())) + len(compact) + (structural * 8) - - -def _with_owner_surface(hit: dict[str, Any]) -> dict[str, Any]: - owner_path = hit["ownerPath"] - surface = "test-source" if is_test_path(owner_path) else "real-source" - return {**hit, "surface": surface, "realOwner": True} diff --git a/src/asp_python/_semantic_search_view_lexical_synthesis.py b/src/asp_python/_semantic_search_view_lexical_synthesis.py deleted file mode 100644 index 3d44246..0000000 --- a/src/asp_python/_semantic_search_view_lexical_synthesis.py +++ /dev/null @@ -1,170 +0,0 @@ -"""Text query-set coverage and next-action synthesis helpers.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_search_common import dedupe - - -def query_coverage( - hits_by_term: dict[str, list[dict[str, Any]]], - selected_hits: list[dict[str, Any]], -) -> list[dict[str, Any]]: - selected_counts = _selected_query_term_counts(selected_hits) - return [ - _query_term_coverage(term, hits, selected_counts.get(term, 0)) - for term, hits in hits_by_term.items() - ] - - -def owner_resolution(owner_paths: list[str]) -> list[dict[str, Any]]: - return [ - { - "target": owner_path, - "status": "workspace-owner", - "realOwner": True, - "ownerPath": owner_path, - "reason": "parser-visible owner selected by lexical search", - } - for owner_path in owner_paths[:8] - ] - - -def search_synthesis( - query_terms: list[str], - hits: list[dict[str, Any]], - owner_paths: list[str], -) -> dict[str, Any] | None: - if not query_terms: - return None - ranked_owners = _rank_synthesis_owners(hits, owner_paths) - edit_frontier = [ - owner_path - for owner_path in ranked_owners - if not _is_test_owner_path(owner_path) - ][:4] - test_frontier = [ - owner_path for owner_path in ranked_owners if _is_test_owner_path(owner_path) - ][:4] - window_set = [ - *({"kind": "owner", "target": owner_path} for owner_path in edit_frontier), - *({"kind": "tests", "target": owner_path} for owner_path in test_frontier), - ][:8] - return { - "algorithm": "query-set-owner-resolution", - "scope": "query-set", - "summary": ( - f"query-set compressed {len(query_terms)} query terms into " - f"{len(owner_paths)} parser-visible owners" - ), - "selectedOwners": len(ranked_owners), - **({"editFrontier": edit_frontier} if edit_frontier else {}), - **({"testFrontier": test_frontier} if test_frontier else {}), - **({"windowSet": window_set} if window_set else {}), - "seeds": window_set, - "fields": { - "querySet": len(query_terms), - "owners": len(owner_paths), - "hits": len(hits), - }, - } - - -def avoid_next_actions( - query_terms: list[str], - owner_paths: list[str], -) -> list[dict[str, Any]]: - owner_path_set = set(owner_paths) - return [ - { - "kind": "owner", - "target": term, - "reason": "query-term-not-parser-visible-owner", - } - for term in query_terms - if _looks_like_project_path(term) and term not in owner_path_set - ][:8] - - -def _selected_query_term_counts( - selected_hits: list[dict[str, Any]], -) -> dict[str, int]: - selected_counts: dict[str, int] = {} - for hit in selected_hits: - for term in hit.get("fields", {}).get("queryTerms", []): - selected_counts[term] = selected_counts.get(term, 0) + 1 - return selected_counts - - -def _query_term_coverage( - term: str, - hits: list[dict[str, Any]], - selected_count: int, -) -> dict[str, Any]: - hit_count = len(hits) - return { - "value": term, - "kind": "text", - "selector": "exact", - "status": _coverage_status(hit_count, selected_count), - "hitCount": hit_count, - "surfaces": dedupe(hit["surface"] for hit in hits), - "ownerPaths": dedupe(hit["ownerPath"] for hit in hits)[:8], - "fields": {"selectedHits": selected_count}, - } - - -def _coverage_status(hit_count: int, selected_count: int) -> str: - if hit_count and selected_count < hit_count: - return "partial" - if hit_count: - return "hit" - return "miss" - - -def _rank_synthesis_owners( - hits: list[dict[str, Any]], - owner_paths: list[str], -) -> list[str]: - term_counts: dict[str, set[str]] = {owner_path: set() for owner_path in owner_paths} - best_scores: dict[str, int] = {owner_path: 0 for owner_path in owner_paths} - for hit in hits: - owner_path = hit["ownerPath"] - term_counts.setdefault(owner_path, set()).update( - hit.get("fields", {}).get("queryTerms", []) - ) - best_scores[owner_path] = max(best_scores.get(owner_path, 0), int(hit["score"])) - return sorted( - owner_paths, - key=lambda owner_path: ( - -len(term_counts.get(owner_path, set())), - -best_scores.get(owner_path, 0), - owner_path, - ), - ) - - -def _is_test_owner_path(owner_path: str) -> bool: - return ( - owner_path.startswith("tests/") - or owner_path.startswith("test/") - or "/tests/" in owner_path - or "/test/" in owner_path - or "/__tests__/" in owner_path - or owner_path.endswith("_test.py") - or owner_path.endswith("_test.pyi") - or owner_path.endswith(".test.py") - or owner_path.endswith(".spec.py") - ) - - -def _looks_like_project_path(term: str) -> bool: - return ( - "/" in term - and " " not in term - and "\\" not in term - and ":" not in term - and not term.startswith("/") - and all(part not in {"", ".", ".."} for part in term.split("/")) - ) diff --git a/src/asp_python/_semantic_search_views.py b/src/asp_python/_semantic_search_views.py deleted file mode 100644 index 67a549c..0000000 --- a/src/asp_python/_semantic_search_views.py +++ /dev/null @@ -1,113 +0,0 @@ -"""Semantic-search view dispatcher for Python packets.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING, Any - -from ._semantic_search_common import header -from ._semantic_search_hits import api_hits, callsite_hits, symbol_hits -from ._semantic_search_model import MAX_SYMBOL_HITS, PythonSemanticSearchOptions -from ._semantic_search_public_external_types import public_external_types_payload -from ._semantic_search_view_core import owner_payload, prime_payload, workspace_payload -from ._semantic_search_view_deps_imports import dependency_payload, import_payload -from ._semantic_search_view_hits import ( - generic_hits_payload, - tests_payload, - text_payload, -) -from ._semantic_search_view_ingest import ingest_payload -from ._semantic_search_view_knowledge import knowledge_payload - -if TYPE_CHECKING: - from python_lang_parser import PythonReasoningTreeFacts - - from ._model import AspPythonReport - - -def payload_for_view( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Dispatch one semantic-search view.""" - - return _payload_for_view(report, facts, project_root, options) - - -def _payload_for_view( - report: AspPythonReport, - facts: PythonReasoningTreeFacts, - project_root: Path, - options: PythonSemanticSearchOptions, -) -> dict[str, Any]: - """Dispatch one semantic-search view behind the thin public facade.""" - - query = options.query or "" - match options.view: - case "workspace": - return workspace_payload(report, facts, project_root) - case "prime": - return prime_payload(report, facts, project_root) - case "owner": - return owner_payload( - report, - facts, - project_root, - query, - pipes=options.pipes, - item_query=options.item_query, - ) - case "dependency" | "deps": - return dependency_payload(report, facts, project_root, query, options.view) - case "api": - hits = api_hits(report, facts, project_root, query)[:MAX_SYMBOL_HITS] - return generic_hits_payload("api", hits, facts, project_root, query) - case "public-external-types": - return public_external_types_payload(report, facts, project_root, query) - case "policy": - from ._semantic_search_policy import policy_payload - - return policy_payload( - report, - facts, - project_root, - query, - pipes=options.pipes, - ) - case "symbol": - hits = symbol_hits(report, project_root, query)[:MAX_SYMBOL_HITS] - return generic_hits_payload("symbol", hits, facts, project_root, query) - case "callsite": - hits = callsite_hits(report, project_root, query)[:MAX_SYMBOL_HITS] - return generic_hits_payload("callsite", hits, facts, project_root, query) - case "import": - return import_payload(report, facts, project_root, query) - case "tests": - return tests_payload(report, facts, project_root, query) - case "lexical": - return text_payload(report, facts, project_root, options) - case "reasoning": - from ._semantic_search_reasoning import reasoning_payload - - return reasoning_payload(report, facts, project_root, options) - case ( - "env" - | "runtime-source" - | "lang" - | "std" - | "capability" - | "extension" - | "pattern" - | "compare" - ): - return knowledge_payload(project_root, options) - case "ingest": - return ingest_payload(facts, project_root, options.stdin) - case _: - return { - "header": header( - options.view, {"error": f"unknown view {options.view}"} - ) - } diff --git a/src/asp_python/_tree_sitter_query_projection.py b/src/asp_python/_tree_sitter_query_projection.py index b5e422d..f7318c4 100644 --- a/src/asp_python/_tree_sitter_query_projection.py +++ b/src/asp_python/_tree_sitter_query_projection.py @@ -7,7 +7,7 @@ from typing import TYPE_CHECKING from ._python_expr import _expr -from ._semantic_search_common import semantic_search_display_path +from ._render import _render_display_path from ._tree_sitter_query_model import ( LEAF_TREE_SITTER_QUERY_NODES, MAX_SYNTAX_QUERY_ROWS, @@ -114,7 +114,7 @@ def _module_syntax_query_rows( ) -> list[SyntaxQueryRow]: if module.path is None: return [] - owner_path = semantic_search_display_path(module.path, project_root) + owner_path = _render_display_path(module.path, project_root=project_root) effective = effective_selector( owner_path, selector, diff --git a/src/asp_python/harness.py b/src/asp_python/harness.py index 1cabfe5..36453ee 100644 --- a/src/asp_python/harness.py +++ b/src/asp_python/harness.py @@ -51,12 +51,6 @@ python_semantic_language_registration, semantic_language_registry_document, ) -from ._semantic_search import ( - PythonSemanticSearchOptions, - build_python_semantic_search_packet, - render_python_semantic_search_packet, - render_python_semantic_search_packet_json, -) from ._syntax import PythonSyntaxRulePack from ._syntax_catalog import python_syntax_rules from ._test_layout import PythonTestLayoutRulePack @@ -117,7 +111,6 @@ "PythonProjectHarnessScope", "PythonProjectPolicyRulePack", "PythonRulePackDescriptor", - "PythonSemanticSearchOptions", "PythonSyntaxRulePack", "PythonTestLayoutRulePack", "PythonOwnerResponsibility", @@ -146,7 +139,6 @@ "PythonVerificationWaiver", "assert_python_lang_harness_clean", "assert_asp_python_clean", - "build_python_semantic_search_packet", "default_python_harness_config", "default_python_lang_rule_packs", "discover_python_files", @@ -175,8 +167,6 @@ "render_python_reasoning_tree", "render_asp_python_agent_snapshot", "render_asp_python_agent_snapshot_with_config", - "render_python_semantic_search_packet", - "render_python_semantic_search_packet_json", "render_python_verification_performance_index_json", "render_python_verification_plan", "render_python_verification_plan_json", diff --git a/tests/unit/harness/semantic_search_fixture.py b/tests/unit/harness/python_project_fixture.py similarity index 66% rename from tests/unit/harness/semantic_search_fixture.py rename to tests/unit/harness/python_project_fixture.py index 7382cab..487daee 100644 --- a/tests/unit/harness/semantic_search_fixture.py +++ b/tests/unit/harness/python_project_fixture.py @@ -1,31 +1,11 @@ -"""Fixtures for semantic-search CLI tests.""" +"""Provider-native Python project fixtures.""" from __future__ import annotations -import os -import shutil from pathlib import Path -import pytest -from asp_python._semantic_search_graph_render import ( - SEMANTIC_AGENT_PROTOCOL_BIN_ENV, -) - - -def compact_graph_renderer_available() -> bool: - configured_bin = os.environ.get(SEMANTIC_AGENT_PROTOCOL_BIN_ENV) - if configured_bin is not None: - return Path(configured_bin).exists() - return shutil.which("asp") is not None - - -def require_compact_graph_renderer() -> None: - if not compact_graph_renderer_available(): - pytest.skip("asp graph renderer is not available") - - -def write_search_fixture(project_root: Path) -> None: +def write_python_project_fixture(project_root: Path) -> None: package = project_root / "src" / "pkg" tests = project_root / "tests" package.mkdir(parents=True) diff --git a/tests/unit/harness/test_cli.py b/tests/unit/harness/test_cli.py index a64b97c..fc74662 100644 --- a/tests/unit/harness/test_cli.py +++ b/tests/unit/harness/test_cli.py @@ -16,14 +16,15 @@ def test_cli_help_advertises_the_current_provider_protocol() -> None: rendered = stdout.getvalue() assert exit_code == 0 - assert "asp-python search " in rendered + assert "asp python search playbook " in rendered + assert "asp-python search " not in rendered assert "asp python query --selector" in rendered assert "asp-python evidence graph" in rendered assert "asp-python agent doctor" in rendered def test_cli_subcommand_help_advertises_exact_projection() -> None: - for args in (["search", "--help"], ["query", "--help"]): + for args in (["query", "--help"],): stdout = io.StringIO() exit_code = run_cli(args, stdout=stdout) @@ -43,6 +44,8 @@ def test_cli_agent_guide_advertises_exact_source_route(tmp_path: Path) -> None: assert ( "asp python query --selector " in stdout.getvalue() ) + assert "|cmd playbook=asp python search playbook " in stdout.getvalue() + assert "search prime" not in stdout.getvalue() assert "|policy authority=asp-python-api trigger=pytest-plugin" in stdout.getvalue() diff --git a/tests/unit/harness/test_dependency_topology_cli.py b/tests/unit/harness/test_dependency_topology_cli.py deleted file mode 100644 index f5cba40..0000000 --- a/tests/unit/harness/test_dependency_topology_cli.py +++ /dev/null @@ -1,67 +0,0 @@ -from __future__ import annotations - -import io -import json -import re -from pathlib import Path - -from asp_python._cli_args import ProtocolArgs -from asp_python._cli_protocol import run_protocol_cli - - -def test_dependency_topology_cli_emits_canonical_packet(tmp_path: Path) -> None: - (tmp_path / "requirements.txt").write_text( - "requests>=2.31\n", - encoding="utf-8", - ) - args = ProtocolArgs.parse( - [ - "search", - "dependency-topology", - "--json", - "--workspace", - str(tmp_path), - ] - ) - assert args is not None - stdout = io.StringIO() - stderr = io.StringIO() - - exit_code = run_protocol_cli( - args, - stdout=stdout, - stderr=stderr, - stdin="", - cwd=tmp_path, - ) - - assert exit_code == 0 - assert stderr.getvalue() == "" - packet = json.loads(stdout.getvalue()) - assert packet["packetKind"] == "dependency-topology" - assert re.fullmatch(r"sha256:[0-9a-f]{64}", packet["fingerprint"]) - assert packet["graph"]["nodes"] == [ - { - "id": "dependency:requests", - "kind": "dependency", - "value": "requests", - "path": "requirements.txt", - "fields": { - "dependencyName": "requests", - "manifestPath": "requirements.txt", - }, - }, - { - "id": "dependency-version:requests", - "kind": "dependency-version", - "value": ">=2.31", - "fields": {"version": ">=2.31"}, - }, - ] - assert packet["graph"]["edges"] == [ - { - "source": "dependency:requests", - "target": "dependency-version:requests", - "relation": "version_locked", - } - ] diff --git a/tests/unit/harness/test_evidence_graph.py b/tests/unit/harness/test_evidence_graph.py index 9596ce8..d1144c4 100644 --- a/tests/unit/harness/test_evidence_graph.py +++ b/tests/unit/harness/test_evidence_graph.py @@ -69,7 +69,7 @@ def test_cli_evidence_analyze_renders_graph_turbo_request(tmp_path: Path) -> Non assert payload["summary"]["nodes"] == 4 assert payload["summary"]["gaps"] == 1 assert payload["graphs"][0]["graphId"] == "python.evidence.graph" - assert payload["seedIds"] == ["python:owner:pyproject.toml"] + assert payload["entryNodeIds"] == ["python:owner:pyproject.toml"] assert any( edge["relation"] == "requires-evidence" for edge in payload["graphs"][0]["edges"] diff --git a/tests/unit/harness/test_project_fixture_scope.py b/tests/unit/harness/test_project_fixture_scope.py index 7832104..e976b08 100644 --- a/tests/unit/harness/test_project_fixture_scope.py +++ b/tests/unit/harness/test_project_fixture_scope.py @@ -2,7 +2,10 @@ from pathlib import Path +import pytest + from asp_python import run_asp_python +from asp_python._discovery import discover_python_files def test_run_asp_python_skips_test_fixture_sources_by_default( @@ -23,3 +26,20 @@ def test_run_asp_python_skips_test_fixture_sources_by_default( assert report.is_clean assert [module.path for module in report.modules] == [str(source_file)] + + +def test_discovery_prunes_ignored_directories_before_descending( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + source = tmp_path / "src" / "library.py" + + def observed_walk(_root: Path): + root_directories = [".venv", "src", "target"] + yield tmp_path, root_directories, [] + assert root_directories == ["src"] + yield tmp_path / "src", [], ["library.py"] + + monkeypatch.setattr(Path, "walk", observed_walk) + + assert discover_python_files([tmp_path]) == (source,) diff --git a/tests/unit/harness/test_projection_batch.py b/tests/unit/harness/test_projection_batch.py index 5d54f20..227e354 100644 --- a/tests/unit/harness/test_projection_batch.py +++ b/tests/unit/harness/test_projection_batch.py @@ -44,6 +44,8 @@ def test_projection_batch_projects_canonical_python_items() -> None: owner = response["owners"][0] assert len(response["owners"]) == 1 assert owner["sourceLeafDigest"] == "source-test" + assert owner["projectionState"] == "ready" + assert owner["diagnostic"] is None assert [item["selector"] for item in owner["items"]] == [ "python://src/example.py#item/class/Agent", "python://src/example.py#item/method/run/scope/implementation-owner/type/Agent", @@ -59,3 +61,49 @@ def test_projection_batch_projects_canonical_python_items() -> None: assert "schemaId" not in payload assert "schemaVersion" not in payload assert "projectionKind" not in payload + + +def test_projection_batch_isolates_one_syntax_unavailable_owner() -> None: + request = { + "schemaId": "agent.semantic-protocols.provider-language-projection-batch-request", + "schemaVersion": "1", + "languageId": "python", + "providerId": "asp-python", + "workspaceIdentity": "workspace-test", + "generationRootDigest": "generation-test", + "parserIdentityDigest": "parser-test", + "queryPackDigest": "query-pack-test", + "baseGenerationRootDigest": None, + "owners": [ + { + "ownerPath": "src/ready.py", + "sourceLeafDigest": "ready-source", + "sourceEncoding": "utf8", + "sourceText": "def ready():\n return 1\n", + }, + { + "ownerPath": "src/unavailable.py", + "sourceLeafDigest": "unavailable-source", + "sourceEncoding": "utf8", + "sourceText": "def unavailable(:\n", + }, + ], + "auxiliaryOwners": [], + } + + response = project_projection_batch(request) + + ready, unavailable = response["owners"] + assert ready["projectionState"] == "ready" + assert ready["diagnostic"] is None + assert ready["items"] + assert unavailable["projectionState"] == "syntax-unavailable" + assert unavailable["diagnostic"] == { + "schemaId": "agent.semantic-protocols.provider-language-projection-diagnostic", + "schemaVersion": "1", + "reasonKind": "source-syntax-unavailable", + "message": unavailable["diagnostic"]["message"], + } + assert 0 < len(unavailable["diagnostic"]["message"]) <= 4096 + assert unavailable["items"] == [] + assert unavailable["relations"] == [] diff --git a/tests/unit/harness/test_provider_runtime.py b/tests/unit/harness/test_provider_runtime.py index 725d982..cd8ada8 100644 --- a/tests/unit/harness/test_provider_runtime.py +++ b/tests/unit/harness/test_provider_runtime.py @@ -71,7 +71,7 @@ def test_resident_runtime_publishes_manifest_operations_and_structured_frames( ) -def test_runtime_converts_live_edit_syntax_errors_to_error_frames() -> None: +def test_runtime_isolates_live_edit_syntax_errors_in_owner_results() -> None: response = _response_frame( frame( "syntax-error-1", @@ -82,8 +82,11 @@ def test_runtime_converts_live_edit_syntax_errors_to_error_frames() -> None: ) assert response["requestId"] == "syntax-error-1" - assert response["outcome"] == "error" - assert "invalid syntax" in response["error"] + assert response["outcome"] == "ready" + owner = response["payload"]["owners"][0] + assert owner["projectionState"] == "syntax-unavailable" + assert owner["diagnostic"]["reasonKind"] == "source-syntax-unavailable" + assert "invalid syntax" in owner["diagnostic"]["message"] def test_http_json_live_corpus_stream_query_concurrency_and_latency() -> None: diff --git a/tests/unit/harness/test_public_cli_identity.py b/tests/unit/harness/test_public_cli_identity.py index b817d7d..ac5109e 100644 --- a/tests/unit/harness/test_public_cli_identity.py +++ b/tests/unit/harness/test_public_cli_identity.py @@ -7,4 +7,5 @@ def test_public_cli_identity_is_asp_python() -> None: rendered = help_text() assert rendered.startswith("asp-python ") - assert "asp-python search" in rendered + assert "asp python search playbook" in rendered + assert "asp-python search " not in rendered diff --git a/tests/unit/harness/test_runner_config.py b/tests/unit/harness/test_runner_config.py index 7977442..fc8f1d5 100644 --- a/tests/unit/harness/test_runner_config.py +++ b/tests/unit/harness/test_runner_config.py @@ -223,3 +223,16 @@ def test_runner_rejects_missing_project_root_and_explicit_path(tmp_path: Path) - assert str(error) == f"harness path does not exist: {missing}" else: raise AssertionError("missing harness path should fail") + + +def test_concurrent_parser_retains_deterministic_discovery_order( + tmp_path: Path, +) -> None: + last = tmp_path / "z_last.py" + first = tmp_path / "a_first.py" + last.write_text("LAST = 1\n", encoding="utf-8") + first.write_text("FIRST = 1\n", encoding="utf-8") + + report = run_python_lang_harness([tmp_path], rule_packs=()) + + assert [module.path for module in report.modules] == [str(first), str(last)] diff --git a/tests/unit/harness/test_search_playbook_boundary.py b/tests/unit/harness/test_search_playbook_boundary.py new file mode 100644 index 0000000..10ff207 --- /dev/null +++ b/tests/unit/harness/test_search_playbook_boundary.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +import pytest + +from asp_python import python_semantic_language_registration +from asp_python._cli_args import ProtocolArgs + + +@pytest.mark.parametrize("operation", ["prime", "owner", "lexical", "ingest", "pipe"]) +def test_provider_local_search_operations_are_hard_cut(operation: str) -> None: + parsed = ProtocolArgs.parse(["search", operation, "fixture"]) + + assert parsed is not None + assert parsed.command == "error" + assert parsed.error == ( + "provider-local search was removed; use asp python search playbook " + ) + + +def test_public_playbook_is_owned_by_the_asp_client() -> None: + parsed = ProtocolArgs.parse(["search", "playbook", "native syntax"]) + + assert parsed is not None + assert parsed.command == "error" + assert parsed.error == ( + "provider-local search was removed; use asp python search playbook " + ) + + +def test_provider_registry_exposes_no_search_orchestration_method() -> None: + registration = python_semantic_language_registration() + assert [ + method for method in registration["methods"] if method.startswith("search/") + ] == [] + descriptors = [ + descriptor + for descriptor in registration["methodDescriptors"] + if descriptor["method"].startswith("search/") + ] + assert descriptors == [] diff --git a/tests/unit/harness/test_semantic_cli_benchmark_registry.py b/tests/unit/harness/test_semantic_cli_benchmark_registry.py index f53490b..45c2864 100644 --- a/tests/unit/harness/test_semantic_cli_benchmark_registry.py +++ b/tests/unit/harness/test_semantic_cli_benchmark_registry.py @@ -3,19 +3,12 @@ from asp_python import python_semantic_language_registration -def test_registered_search_methods_publish_public_benchmark_invocations() -> None: +def test_registry_publishes_no_search_orchestration_invocation() -> None: descriptors = python_semantic_language_registration()["methodDescriptors"] search_descriptors = [ descriptor for descriptor in descriptors if descriptor["method"].startswith("search/") - and "benchmarkInvocation" in descriptor ] - assert search_descriptors - for descriptor in search_descriptors: - invocation = descriptor["benchmarkInvocation"] - assert invocation["args"][:2] == ["search", descriptor["view"]] - assert "{workspace}" in invocation["args"] - assert isinstance(invocation["expectsJson"], bool) - assert invocation["maxElapsedMs"] > 0 + assert search_descriptors == [] diff --git a/tests/unit/harness/test_semantic_cli_fast_prime.py b/tests/unit/harness/test_semantic_cli_fast_prime.py deleted file mode 100644 index ba33fb0..0000000 --- a/tests/unit/harness/test_semantic_cli_fast_prime.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Fast semantic CLI prime frontier tests.""" - -from __future__ import annotations - -import io -import time -from pathlib import Path - -import pytest - -from asp_python import run_cli - -FAST_SEARCH_BUDGET_SECONDS = 0.25 - - -def test_cli_search_prime_seed_view_uses_fast_frontier( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - source_path = tmp_path / "src" / "pkg" / "service.py" - source_path.parent.mkdir(parents=True) - source_path.write_text("def build():\n return 1\n", encoding="utf-8") - - from asp_python import _cli_protocol - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("full harness should not run for prime seed view") - - monkeypatch.setattr(_cli_protocol, "_run_search_harness", fail_full_harness) - stdout = io.StringIO() - - started_at = time.perf_counter() - exit_code = run_cli( - ["search", "prime", "--view", "seeds", "--workspace", str(tmp_path)], - stdout=stdout, - ) - elapsed = time.perf_counter() - started_at - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-prime]") - assert "alg=fast-prime-frontier-v1" in rendered - assert "|decision purpose=decision-primer" in rendered - assert "answer=false code=false" in rendered - assert ( - "capabilities=pipe,lexical,fd-query,rg-query,owner-items,selector-code,treesitter-query" - in rendered - ) - assert "ladder=pipe>lexical>fd-query|rg-query>owner-items>selector-code" in rendered - assert ( - "history=asp-artifacts:directReadRisk,repeatedPrime,repeatedPipe,bestPath" - in rendered - ) - assert "risk=broad-direct-read,manual-window-scan,repeat-prime" in rendered - assert ( - "next=\"asp python search pipe '' --workspace --view seeds\"" - in rendered - ) - assert "owner:path(src/pkg/service.py)" in rendered - assert "frontier=O1.owner" in rendered - assert ( - "legend: ID=kind:role(value)!next; entries profile(selectors=>returns); frontier ID.next" - in rendered - ) - assert ( - "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)" in rendered - ) - assert "A1=owner-items" not in rendered - assert "recommendedNext=owner-items" not in rendered - assert elapsed < FAST_SEARCH_BUDGET_SECONDS diff --git a/tests/unit/harness/test_semantic_cli_lexical.py b/tests/unit/harness/test_semantic_cli_lexical.py deleted file mode 100644 index 774eb2d..0000000 --- a/tests/unit/harness/test_semantic_cli_lexical.py +++ /dev/null @@ -1,253 +0,0 @@ -"""Semantic CLI lexical protocol tests.""" - -from __future__ import annotations - -import io -from pathlib import Path - -from semantic_search_fixture import require_compact_graph_renderer, write_search_fixture - -from asp_python._cli import run_cli - - -def test_cli_search_lexical_query_set(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - exit_code = run_cli( - [ - "search", - "lexical", - "--query", - "build", - "--query", - "Session", - "owner", - "tests", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith('[search-lexical] q="build,Session" querySet=2') - assert "selector=lexical-set" in rendered - for line in rendered.splitlines(): - if line.startswith("|seed "): - assert ",owner:" not in line - assert ",tests:" not in line - - -def test_cli_search_lexical_matches_path_only_candidate(tmp_path: Path) -> None: - require_compact_graph_renderer() - write_search_fixture(tmp_path) - path_owner = tmp_path / "src" / "pkg" / "hook_runtime.py" - path_owner.write_text( - '"""Path-only lexical owner."""\n\ndef execute() -> None:\n pass\n', - encoding="utf-8", - ) - stdout = io.StringIO() - exit_code = run_cli( - [ - "search", - "lexical", - "--query", - "hookruntime", - "--query", - "execute", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-lexical] q=hookruntime,execute") - assert "O=owner:path(src/pkg/hook_runtime.py)!owner" in rendered - assert "rank=Q,O,T frontier=Q.lexical,O.owner,T.tests" in rendered - assert "|seed " not in rendered - - -def test_protocol_search_lexical_query_uses_fast_frontier( - tmp_path: Path, -) -> None: - from asp_python._cli_args import ProtocolArgs - from asp_python._cli_protocol import run_protocol_cli - - write_search_fixture(tmp_path) - path_owner = tmp_path / "src" / "pkg" / "hook_runtime.py" - path_owner.write_text( - '"""Path-only lexical owner."""\n\ndef execute() -> None:\n pass\n', - encoding="utf-8", - ) - args = ProtocolArgs.parse( - [ - "search", - "lexical", - "--query", - "hookruntime", - "--query", - "execute", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ] - ) - stdout = io.StringIO() - stderr = io.StringIO() - assert args is not None - exit_code = run_protocol_cli( - args, - stdout=stdout, - stderr=stderr, - stdin="", - cwd=tmp_path, - ) - rendered = stdout.getvalue() - assert stderr.getvalue() == "" - assert exit_code == 0 - assert rendered.startswith( - "[search-lexical] q=hookruntime,execute querySet=2 selector=lexical-set " - "view=hits alg=query-set-owner-resolution" - ) - assert "Q=query:term(hookruntime,execute)!lexical" in rendered - assert "entries=owner-query(O,Q=>items+tests+dependency-usage)" in rendered - assert "rank=Q,O,T frontier=Q.lexical,O.owner,T.tests" in rendered - assert "|seed " not in rendered - - -def test_protocol_search_lexical_query_uses_native_prefilter_without_tools( - tmp_path: Path, - monkeypatch, -) -> None: - from asp_python import ( - _semantic_search_prefilter, - _semantic_search_prefilter_file_scan, - ) - from asp_python._cli_args import ProtocolArgs - from asp_python._cli_protocol import run_protocol_cli - - monkeypatch.setattr(_semantic_search_prefilter.shutil, "which", lambda _name: None) - monkeypatch.setattr( - _semantic_search_prefilter_file_scan.shutil, - "which", - lambda _name: None, - ) - write_search_fixture(tmp_path) - path_owner = tmp_path / "src" / "pkg" / "hook_runtime.py" - path_owner.write_text( - '"""Path-only lexical owner."""\n\ndef execute() -> None:\n pass\n', - encoding="utf-8", - ) - args = ProtocolArgs.parse( - [ - "search", - "lexical", - "--query", - "hookruntime", - "--query", - "execute", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ] - ) - stdout = io.StringIO() - stderr = io.StringIO() - assert args is not None - exit_code = run_protocol_cli( - args, - stdout=stdout, - stderr=stderr, - stdin="", - cwd=tmp_path, - ) - rendered = stdout.getvalue() - assert stderr.getvalue() == "" - assert exit_code == 0 - assert rendered.startswith( - "[search-lexical] q=hookruntime,execute querySet=2 selector=lexical-set " - "view=hits alg=query-set-owner-resolution" - ) - assert "Q=query:term(hookruntime,execute)!lexical" in rendered - assert "O=owner:path(src/pkg/hook_runtime.py)!owner" in rendered - assert "entries=owner-query(O,Q=>items+tests+dependency-usage)" in rendered - - -def test_protocol_search_lexical_source_query_uses_rglob_source_without_tools( - tmp_path: Path, - monkeypatch, -) -> None: - from asp_python import ( - _semantic_search_prefilter, - _semantic_search_prefilter_file_scan, - ) - from asp_python._cli_args import ProtocolArgs - from asp_python._cli_protocol import run_protocol_cli - - monkeypatch.setattr(_semantic_search_prefilter.shutil, "which", lambda _name: None) - monkeypatch.setattr( - _semantic_search_prefilter_file_scan.shutil, - "which", - lambda _name: None, - ) - write_search_fixture(tmp_path) - for index in range(130): - filler = tmp_path / "src" / "pkg" / f"filler_{index:03d}.py" - filler.write_text(f"VALUE_{index} = {index}\n", encoding="utf-8") - query_owner = tmp_path / "src" / "pkg" / "cli_query.py" - query_owner.write_text( - "def run_query_command() -> None:\n pass\n", - encoding="utf-8", - ) - protocol_owner = tmp_path / "src" / "pkg" / "cli_protocol.py" - protocol_owner.write_text( - "from .cli_query import run_query_command\n", - encoding="utf-8", - ) - args = ProtocolArgs.parse( - [ - "search", - "lexical", - "--query", - "run_query_command", - "--query", - "command", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ] - ) - stdout = io.StringIO() - stderr = io.StringIO() - assert args is not None - exit_code = run_protocol_cli( - args, - stdout=stdout, - stderr=stderr, - stdin="", - cwd=tmp_path, - ) - rendered = stdout.getvalue() - assert stderr.getvalue() == "" - assert exit_code == 0 - assert rendered.startswith( - "[search-lexical] q=run_query_command,command querySet=2" - ) - assert "Q=query:term(run_query_command,command)!lexical" in rendered - assert "O=owner:path(src/pkg/cli_query.py)!owner" in rendered - assert "entries=owner-query(O,Q=>items+tests+dependency-usage)" in rendered - assert "rglob-source" in rendered diff --git a/tests/unit/harness/test_semantic_cli_owner_item_broad.py b/tests/unit/harness/test_semantic_cli_owner_item_broad.py deleted file mode 100644 index 9a58afb..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_item_broad.py +++ /dev/null @@ -1,85 +0,0 @@ -from __future__ import annotations - -import io -from pathlib import Path - -from asp_python._cli import run_cli - - -def _write_project(tmp_path: Path) -> None: - (tmp_path / "pyproject.toml").write_text( - "[project]\nname = 'sample'\nversion = '0.1.0'\n", - encoding="utf-8", - ) - package = tmp_path / "src" / "pkg" - package.mkdir(parents=True) - (package / "__init__.py").write_text("", encoding="utf-8") - (package / "service.py").write_text( - "\n".join( - [ - "def item_query_payload():", - " return None", - "def candidate_route():", - " return None", - "def fallback_mode():", - " return None", - "def owner_top_items():", - " return None", - ] - ), - encoding="utf-8", - ) - - -def test_owner_item_broad_fallback_uses_names_only(tmp_path: Path) -> None: - _write_project(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "item_query|candidate|fallback|owner_top", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - rendered = stdout.getvalue() - - assert exit_code == 0 - assert "match=fallback-contains" in rendered - assert "output=names" in rendered - assert "next=select-item" in rendered - assert "|item item_query_payload kind=function" in rendered - assert "|code path=src/pkg/service.py" not in rendered - - -def test_owner_item_miss_fallback_does_not_dump_code(tmp_path: Path) -> None: - _write_project(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "render_semantic_query_json|projection_from_code_line", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - rendered = stdout.getvalue() - - assert exit_code == 0 - assert "status=miss" in rendered - assert "output=names" in rendered - assert "next=revise-query" in rendered - assert "|item item_query_payload kind=function" in rendered - assert "|code path=src/pkg/service.py" not in rendered diff --git a/tests/unit/harness/test_semantic_cli_owner_item_inventory.py b/tests/unit/harness/test_semantic_cli_owner_item_inventory.py deleted file mode 100644 index 18aec93..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_item_inventory.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Owner item inventory tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -from pathlib import Path - -from asp_python import run_cli - - -def test_cli_search_owner_items_without_query_returns_inventory( - tmp_path: Path, -) -> None: - _write_fixture(tmp_path) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert "[search-owner] q=src/pkg/service.py owner=1 item=" in rendered - assert "|owner src/pkg/service.py " in rendered - assert "|item SessionClient kind=class" in rendered - assert " itemStatus=" not in rendered - assert "|code " not in rendered - assert "|hit " not in rendered - assert "|synthesis " not in rendered - assert "|next " not in rendered - assert " next=syntax:SessionClient " in rendered - assert " tsqRef=semantic-tree-sitter-query/python-owner-items.v1" in rendered - - -def _write_fixture(root: Path) -> None: - (root / "pyproject.toml").write_text( - '[project]\nname = "owner-item-inventory-fixture"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - package_dir = root / "src" / "pkg" - package_dir.mkdir(parents=True) - (package_dir / "__init__.py").write_text("", encoding="utf-8") - (package_dir / "service.py").write_text( - "\n".join( - [ - "class SessionClient:", - " def fetch_user(self, user_id: str) -> str:", - " return user_id", - "", - "def build_user(user_id: str) -> dict[str, str]:", - " return {'id': user_id}", - "", - ] - ), - encoding="utf-8", - ) diff --git a/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py b/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py deleted file mode 100644 index 7d1bff7..0000000 --- a/tests/unit/harness/test_semantic_cli_owner_items_fast_path.py +++ /dev/null @@ -1,248 +0,0 @@ -"""Owner item fast-path tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -import json -import time -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from asp_python import run_cli - -OWNER_ITEMS_WARM_PATH_GATE_MS = 100.0 -OWNER_WARM_PATH_GATE_MS = 100.0 -DEPENDENCY_WARM_PATH_GATE_MS = 100.0 - - -def test_cli_search_owner_items_query_uses_exact_owner_fast_path( - tmp_path: Path, - monkeypatch, -) -> None: - write_search_fixture(tmp_path) - json_stdout = io.StringIO() - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("owner-items should not run the full Python harness") - - from asp_python import _runner - - monkeypatch.setattr(_runner, "run_asp_python", fail_full_harness) - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "fetch|build", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - packet = json.loads(json_stdout.getvalue()) - assert exit_code == 0 - assert packet["runtimeCost"]["reason"] == "owner-items-exact-owner-prefilter" - assert packet["runtimeCost"]["fields"] == { - "ownerPath": "src/pkg/service.py", - "paths": 1, - } - assert [item["name"] for item in packet["items"]] == ["fetch", "build"] - - -def test_cli_search_owner_items_query_stays_inside_warm_path_gate( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - args = [ - "search", - "owner", - "src/pkg/service.py", - "items", - "--query", - "fetch|build", - "--json", - "--workspace", - str(tmp_path), - ] - - warmup_stdout = io.StringIO() - assert run_cli(args, stdout=warmup_stdout) == 0 - assert "owner-items-exact-owner-prefilter" in warmup_stdout.getvalue() - - timed_stdout = io.StringIO() - started_at = time.perf_counter() - exit_code = run_cli(args, stdout=timed_stdout) - elapsed_ms = (time.perf_counter() - started_at) * 1000 - - assert exit_code == 0 - assert "owner-items-exact-owner-prefilter" in timed_stdout.getvalue() - assert elapsed_ms < OWNER_ITEMS_WARM_PATH_GATE_MS - - -def test_cli_search_owner_path_uses_exact_owner_fast_path( - tmp_path: Path, - monkeypatch, -) -> None: - write_search_fixture(tmp_path) - json_stdout = io.StringIO() - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("owner path search should not run the full Python harness") - - from asp_python import _runner - - monkeypatch.setattr(_runner, "run_asp_python", fail_full_harness) - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - packet = json.loads(json_stdout.getvalue()) - assert exit_code == 0 - assert packet["runtimeCost"]["reason"] == "owner-exact-path-prefilter" - assert packet["runtimeCost"]["fields"] == { - "ownerPath": "src/pkg/service.py", - "paths": 1, - } - assert [owner["path"] for owner in packet["owners"]] == ["src/pkg/service.py"] - - -def test_cli_search_owner_path_stays_inside_warm_path_gate( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - args = [ - "search", - "owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ] - - warmup_stdout = io.StringIO() - assert run_cli(args, stdout=warmup_stdout) == 0 - assert "owner-exact-path-prefilter" in warmup_stdout.getvalue() - - timed_stdout = io.StringIO() - started_at = time.perf_counter() - exit_code = run_cli(args, stdout=timed_stdout) - elapsed_ms = (time.perf_counter() - started_at) * 1000 - - assert exit_code == 0 - assert "owner-exact-path-prefilter" in timed_stdout.getvalue() - assert elapsed_ms < OWNER_WARM_PATH_GATE_MS - - -def test_cli_search_owner_seed_view_uses_text_fast_path( - tmp_path: Path, - monkeypatch, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("owner seed view should not run the full Python harness") - - from asp_python import _cli_protocol - - monkeypatch.setattr(_cli_protocol, "_run_search_harness", fail_full_harness) - - exit_code = run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-owner]") - assert "alg=fast-exact-owner-frontier" in rendered - assert "O=owner:path(src/pkg/service.py)!owner" in rendered - assert ( - "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)" in rendered - ) - - -def test_cli_search_dependency_uses_metadata_fast_path( - tmp_path: Path, - monkeypatch, -) -> None: - write_search_fixture(tmp_path) - json_stdout = io.StringIO() - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("dependency search should not run the full Python harness") - - from asp_python import _runner - - monkeypatch.setattr(_runner, "run_asp_python", fail_full_harness) - - exit_code = run_cli( - [ - "search", - "deps", - "requests", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - packet = json.loads(json_stdout.getvalue()) - assert exit_code == 0 - assert packet["runtimeCost"]["reason"] == "dependency-metadata-prefilter" - assert packet["runtimeCost"]["fields"] == { - "dependency": "requests", - "paths": 0, - } - assert [node["id"] for node in packet["nodes"]] == ["D:requests"] - - -def test_cli_search_dependency_stays_inside_warm_path_gate( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - args = [ - "search", - "deps", - "requests", - "--json", - "--workspace", - str(tmp_path), - ] - - warmup_stdout = io.StringIO() - assert run_cli(args, stdout=warmup_stdout) == 0 - assert "dependency-metadata-prefilter" in warmup_stdout.getvalue() - - timed_stdout = io.StringIO() - started_at = time.perf_counter() - exit_code = run_cli(args, stdout=timed_stdout) - elapsed_ms = (time.perf_counter() - started_at) * 1000 - - assert exit_code == 0 - assert "dependency-metadata-prefilter" in timed_stdout.getvalue() - assert elapsed_ms < DEPENDENCY_WARM_PATH_GATE_MS diff --git a/tests/unit/harness/test_semantic_cli_policy.py b/tests/unit/harness/test_semantic_cli_policy.py deleted file mode 100644 index a80a514..0000000 --- a/tests/unit/harness/test_semantic_cli_policy.py +++ /dev/null @@ -1,118 +0,0 @@ -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import compact_graph_renderer_available - -from asp_python._cli import run_cli - - -def test_cli_agent_doctor_json_advertises_policy_search( - tmp_path: Path, -) -> None: - stdout = io.StringIO() - exit_code = run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) - payload = json.loads(stdout.getvalue()) - assert exit_code == 0 - registration = payload["registry"]["languages"][0] - assert "search/policy" in registration["methods"] - assert all( - schema["schemaId"] != "agent.semantic-protocols.semantic-handle" - for schema in registration["schemas"] - ) - assert any( - descriptor["method"] == "search/policy" - and descriptor["acceptedPipes"] == ["owner", "tests"] - and descriptor["outputSchemaIds"] - == [ - "agent.semantic-protocols.semantic-search-packet", - "agent.semantic-protocols.semantic-handle", - ] - and any( - capability["name"] == "python-project-policy-rule-handle-search" - for capability in descriptor["capabilities"] - ) - for descriptor in registration["methodDescriptors"] - ) - - -def test_cli_search_policy_renders_semantic_handles( - tmp_path: Path, -) -> None: - seeds_stdout = io.StringIO() - compact_stdout = io.StringIO() - json_stdout = io.StringIO() - if compact_graph_renderer_available(): - assert ( - run_cli( - [ - "search", - "policy", - "PY-AGENT-PROJECT-001", - "owner", - "tests", - "--view", - "seeds", - "--workspace", - str(tmp_path), - ], - stdout=seeds_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "policy", - "src layout", - "owner", - "tests", - "--workspace", - str(tmp_path), - ], - stdout=compact_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "policy", - "PY-AGENT-POLICY-008", - "owner", - "tests", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - == 0 - ) - seeds = seeds_stdout.getvalue() - compact = compact_stdout.getvalue() - packet = json.loads(json_stdout.getvalue()) - if compact_graph_renderer_available(): - assert seeds.startswith("[search-policy] q=PY-AGENT-PROJECT-001") - assert "alg=policy-handle-catalog" in seeds - assert "O=owner:path(src/asp_python/_project_policy_catalog.py)!owner" in seeds - assert "tests/unit/harness/project_policy/test_layout.py" in seeds - assert ( - "|handle PY-AGENT-PROJECT-001 kind=policy-rule source=provider-policy" - in compact - ) - assert 'title="Python project should use src layout"' in compact - assert "implementation=None" not in compact - assert packet["view"] == "policy" - assert packet["semanticHandles"][0]["id"] == "PY-AGENT-POLICY-008" - assert packet["semanticHandles"][0]["ownerPath"] == ( - "src/asp_python/_agent_policy_catalog.py" - ) - assert ( - "tests/unit/harness/test_agent_policy.py" - in packet["semanticHandles"][0]["testPaths"] - ) diff --git a/tests/unit/harness/test_semantic_cli_public_external_types.py b/tests/unit/harness/test_semantic_cli_public_external_types.py deleted file mode 100644 index edc4b78..0000000 --- a/tests/unit/harness/test_semantic_cli_public_external_types.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Public external type surface CLI tests.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from asp_python import run_cli - - -def test_cli_search_public_external_types_uses_public_api_facts( - tmp_path: Path, -) -> None: - write_search_fixture(tmp_path) - stdout = io.StringIO() - json_stdout = io.StringIO() - - exit_code = run_cli( - ["search", "public-external-types", "requests", "--workspace", str(tmp_path)], - stdout=stdout, - ) - json_exit_code = run_cli( - [ - "search", - "public-external-types", - "requests", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=json_stdout, - ) - - rendered = stdout.getvalue() - assert exit_code == 0 - assert rendered.startswith("[search-public-external-types] q=requests") - assert "package=requests" in rendered - assert "|api path=src/pkg/service.py line=6" in rendered - assert "reason=public-external-type" in rendered - assert "confidence=direct" in rendered - assert "|api path=src/pkg/service.py line=9" in rendered - assert "reason=possible-public-external-type" in rendered - assert "confidence=possible" in rendered - - packet = json.loads(json_stdout.getvalue()) - assert json_exit_code == 0 - assert packet["method"] == "search/public-external-types" - assert packet["view"] == "public-external-types" - assert packet["header"]["fields"]["package"] == "requests" - type_surfaces = packet["typeSurfaces"] - assert len(type_surfaces) == len(packet["hits"]) - assert any( - surface["kind"] == "class" - and surface["role"] == "external-dependency" - and surface["package"] == "requests" - and surface["carrier"]["name"] == "class SessionClient(requests.Session):" - and surface["carrier"]["carrier"] == "class" - and surface["carrier"]["external"] is True - and surface["fields"]["confidence"] == "direct" - for surface in type_surfaces - ) - assert any( - surface["kind"] == "function" - and surface["carrier"]["name"] == "def fetch() -> Response:" - and surface["fields"]["confidence"] == "possible" - for surface in type_surfaces - ) - assert any( - hit["reason"] == "public-external-type" - and hit["fields"]["dependency"] == "requests" - and hit["fields"]["confidence"] == "direct" - for hit in packet["hits"] - ) - assert any( - hit["reason"] == "possible-public-external-type" - and hit["fields"]["dependency"] == "requests" - and hit["fields"]["confidence"] == "possible" - for hit in packet["hits"] - ) diff --git a/tests/unit/harness/test_semantic_cli_query_set.py b/tests/unit/harness/test_semantic_cli_query_set.py deleted file mode 100644 index c3fbd01..0000000 --- a/tests/unit/harness/test_semantic_cli_query_set.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Semantic CLI query-set protocol tests.""" - -from __future__ import annotations - -import io -import json -import shutil -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from asp_python import run_cli - - -def test_cli_search_text_prefilter_large_project_records_runtime_cost( - tmp_path: Path, -) -> None: - if shutil.which("rg") is None: - return - write_search_fixture(tmp_path) - generated = tmp_path / "src" / "pkg" / "generated" - generated.mkdir() - for index in range(140): - (generated / f"candidate_{index}.py").write_text( - f'def large_need_{index}() -> str:\n return "LargeNeedle-{index}"\n', - encoding="utf-8", - ) - - stdout = io.StringIO() - exit_code = run_cli( - [ - "search", - "lexical", - "--query", - "LargeNeedle", - "--query", - "large_need", - "--query", - "generated", - "--owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - runtime_cost = packet["runtimeCost"] - fields = runtime_cost["fields"] - assert runtime_cost["sourceFilesParsed"] == fields["matchedFiles"] - assert fields["candidateFiles"] > 128 - assert fields["minCandidateFiles"] == 128 - assert fields["mode"] == "text-query-prefilter" - assert fields["queryTerms"] == 3 - assert fields["sourceSearchPasses"] == 1 - assert fields["fileListPasses"] == 1 - assert fields["candidateFileBasis"] == "all-python-files" - assert fields["matchedFiles"] <= 17 - assert any(note["kind"] == "runtime-prefilter" for note in packet["notes"]) - - -def test_cli_search_text_prefilter_skips_file_list_for_source_rich_terms( - tmp_path: Path, -) -> None: - if shutil.which("rg") is None: - return - write_search_fixture(tmp_path) - generated = tmp_path / "src" / "pkg" / "generated" - generated.mkdir() - for index in range(140): - (generated / f"source_rich_{index}.py").write_text( - "\n".join( - ( - f"def LargeNeedle_{index}() -> str:", - f' large_need = "generated-{index}"', - " return large_need", - "", - ) - ), - encoding="utf-8", - ) - - stdout = io.StringIO() - exit_code = run_cli( - [ - "search", - "lexical", - "--query", - "LargeNeedle", - "--query", - "large_need", - "--query", - "generated", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - ) - - assert exit_code == 0 - packet = json.loads(stdout.getvalue()) - fields = packet["runtimeCost"]["fields"] - assert fields["candidateFiles"] > 128 - assert fields["candidateFileBasis"] == "source-matched-files" - assert fields["sourceSearchPasses"] == 1 - assert fields["fileListPasses"] == 0 - assert fields["prefilterTool"] == "rg" - assert fields["matchedFiles"] <= 48 diff --git a/tests/unit/harness/test_semantic_cli_reasoning.py b/tests/unit/harness/test_semantic_cli_reasoning.py deleted file mode 100644 index b05f361..0000000 --- a/tests/unit/harness/test_semantic_cli_reasoning.py +++ /dev/null @@ -1,116 +0,0 @@ -"""Executable reasoning-entry tests for the Python semantic CLI.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from asp_python import run_cli - - -def test_cli_agent_doctor_advertises_reasoning_search(tmp_path: Path) -> None: - stdout = io.StringIO() - - assert run_cli(["agent", "doctor", "--json", str(tmp_path)], stdout=stdout) == 0 - - registration = json.loads(stdout.getvalue())["registry"]["languages"][0] - assert "search/reasoning" in registration["methods"] - assert any( - descriptor["method"] == "search/reasoning" - and descriptor["supportsCompact"] is True - and descriptor["supportsJson"] is True - for descriptor in registration["methodDescriptors"] - ) - - -def test_cli_agent_guide_prints_reasoning_entry_commands(tmp_path: Path) -> None: - stdout = io.StringIO() - - assert run_cli(["agent", "guide", str(tmp_path)], stdout=stdout) == 0 - - rendered = stdout.getvalue() - assert ( - "|cmd reasoning-owner-tests=asp python search reasoning owner-tests " - "--owner --workspace --view seeds" - ) in rendered - assert ( - "|cmd reasoning-owner-query=asp python search reasoning owner-query " - "--owner --query --workspace --view seeds" - ) in rendered - assert ( - "|cmd reasoning-query-deps=asp python search reasoning query-deps " - "--query --dependency --workspace --view seeds" - ) in rendered - - -def test_cli_search_reasoning_profiles_are_executable(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - owner_tests_stdout = io.StringIO() - owner_query_stdout = io.StringIO() - query_deps_stdout = io.StringIO() - - assert ( - run_cli( - [ - "search", - "reasoning", - "owner-tests", - "--owner", - "src/pkg/service.py", - "--workspace", - str(tmp_path), - ], - stdout=owner_tests_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "reasoning", - "owner-query", - "--owner", - "src/pkg/service.py", - "--query", - "build", - "--workspace", - str(tmp_path), - ], - stdout=owner_query_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "reasoning", - "query-deps", - "--query", - "Session", - "--dependency", - "requests", - "--workspace", - str(tmp_path), - ], - stdout=query_deps_stdout, - ) - == 0 - ) - - owner_tests = owner_tests_stdout.getvalue() - owner_query = owner_query_stdout.getvalue() - query_deps = query_deps_stdout.getvalue() - assert owner_tests.startswith("[search-reasoning] profile=owner-tests") - assert "returns=covering-tests,test-entrypoints,fixtures" in owner_tests - assert "|hit path=tests/test_service.py" in owner_tests - assert owner_query.startswith("[search-reasoning] profile=owner-query") - assert "returns=items,tests,dependency-usage" in owner_query - assert "|item build" in owner_query - assert query_deps.startswith("[search-reasoning] profile=query-deps") - assert "returns=owners,imports,usage-tests" in query_deps - assert "dependency=requests" in query_deps diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py b/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py index f874fe0..cfe6246 100644 --- a/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py +++ b/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py @@ -6,7 +6,7 @@ import json from pathlib import Path -from semantic_search_fixture import write_search_fixture +from python_project_fixture import write_python_project_fixture from asp_python import run_cli @@ -14,7 +14,7 @@ def test_cli_query_inline_s_expression_applies_predicate_matrix( tmp_path: Path, ) -> None: - write_search_fixture(tmp_path) + write_python_project_fixture(tmp_path) cases = [ ( "#eq?", @@ -88,7 +88,7 @@ def test_cli_query_inline_s_expression_applies_predicate_matrix( def test_cli_query_inline_s_expression_renders_multi_path_corpus_locators( tmp_path: Path, ) -> None: - write_search_fixture(tmp_path) + write_python_project_fixture(tmp_path) (tmp_path / "src" / "pkg" / "extra.py").write_text( "def alpha() -> str:\n return 'alpha'\n", encoding="utf-8", diff --git a/tests/unit/harness/test_semantic_cli_workspace_search.py b/tests/unit/harness/test_semantic_cli_workspace_search.py deleted file mode 100644 index 7ffe087..0000000 --- a/tests/unit/harness/test_semantic_cli_workspace_search.py +++ /dev/null @@ -1,153 +0,0 @@ -"""Semantic CLI workspace search protocol tests.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from semantic_search_fixture import write_search_fixture - -from asp_python import run_cli - - -def test_cli_search_workspace_prime_and_text_pipe(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - - workspace_stdout = io.StringIO() - prime_stdout = io.StringIO() - prime_json_stdout = io.StringIO() - owner_stdout = io.StringIO() - owner_json_stdout = io.StringIO() - text_stdout = io.StringIO() - - assert ( - run_cli( - ["search", "workspace", "--workspace", str(tmp_path)], - stdout=workspace_stdout, - ) - == 0 - ) - assert ( - run_cli(["search", "prime", "--workspace", str(tmp_path)], stdout=prime_stdout) - == 0 - ) - assert ( - run_cli( - ["search", "prime", "--json", "--workspace", str(tmp_path)], - stdout=prime_json_stdout, - ) - == 0 - ) - assert ( - run_cli( - ["search", "owner", "src/pkg/service.py", "--workspace", str(tmp_path)], - stdout=owner_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "owner", - "src/pkg/service.py", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=owner_json_stdout, - ) - == 0 - ) - assert ( - run_cli( - [ - "search", - "lexical", - "build", - "owner", - "tests", - "--workspace", - str(tmp_path), - ], - stdout=text_stdout, - ) - == 0 - ) - - workspace = workspace_stdout.getvalue() - prime = prime_stdout.getvalue() - owner = owner_stdout.getvalue() - text = text_stdout.getvalue() - assert workspace.startswith("[search-workspace]") - assert "|package . name=demo-python role=workspace-root" in workspace - assert ( - "|package src/pkg name=pkg role=workspace-package surface=source" in workspace - ) - assert prime.startswith("[search-prime]") - assert '|dependency D:requests requirement="requests>=2"' in prime - assert "|owner src/pkg/service.py" in prime - assert "|synthesis algorithm=owner-rank-frontier scope=prime" in prime - assert "highImpactOwners=" in prime - assert "src/pkg/service.py" in prime - assert "text:build(owner=" in prime - assert prime.count("deps:requests") == 1 - assert "symbol:build" not in prime - prime_packet = json.loads(prime_json_stdout.getvalue()) - assert prime_packet["searchSynthesis"]["algorithm"] == "owner-rank-frontier" - assert prime_packet["searchSynthesis"]["scope"] == "prime" - assert "src/pkg/service.py" in prime_packet["searchSynthesis"]["highImpactOwners"] - assert owner.startswith("[search-owner] q=src/pkg/service.py") - assert "|synthesis algorithm=bounded-reachability-depth1 scope=owner" in owner - assert "ownerPath=src/pkg/service.py" in owner - owner_packet = json.loads(owner_json_stdout.getvalue()) - assert owner_packet["searchSynthesis"]["algorithm"] == "bounded-reachability-depth1" - assert owner_packet["searchSynthesis"]["scope"] == "owner" - assert owner_packet["searchSynthesis"]["ownerPath"] == "src/pkg/service.py" - assert owner_packet["searchSynthesis"]["incomingOwners"] >= 1 - assert text.startswith("[search-lexical] q=build") - assert "|owner src/pkg/service.py" in text - assert "|edge O:src/pkg/service.py -test-> O:tests/test_service.py" in text - - -def test_cli_search_workspace_seeds_uses_metadata_route(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - - stdout = io.StringIO() - assert ( - run_cli( - ["search", "workspace", "--view", "seeds", "--workspace", str(tmp_path)], - stdout=stdout, - ) - == 0 - ) - - workspace = stdout.getvalue() - assert workspace.startswith("[search-workspace]") - assert ( - "|note kind=runtime-prefilter message=workspace-seed-metadata-route" - in workspace - ) - - -def test_cli_search_deps_exposes_manifest_topology_only(tmp_path: Path) -> None: - write_search_fixture(tmp_path) - - deps_stdout = io.StringIO() - assert ( - run_cli( - ["search", "deps", "requests", "--workspace", str(tmp_path)], - stdout=deps_stdout, - ) - == 0 - ) - - deps = deps_stdout.getvalue() - assert deps.startswith("[search-deps] q=requests") - assert "manifest=1" in deps - assert "usage=0" in deps - assert "topology=asp-owned" in deps - assert '|dependency D:requests requirement="requests>=2"' in deps - assert "|owner src/pkg/service.py" not in deps - assert "|hit path=src/pkg/service.py" not in deps diff --git a/tests/unit/harness/test_semantic_graph_facts.py b/tests/unit/harness/test_semantic_graph_facts.py deleted file mode 100644 index 3dafa29..0000000 --- a/tests/unit/harness/test_semantic_graph_facts.py +++ /dev/null @@ -1,204 +0,0 @@ -"""Validate provider-owned Python graph facts.""" - -from __future__ import annotations - -import io -import json - -from asp_python import run_cli - - -def test_search_semantic_facts_emits_field_type_collection_graph(tmp_path): - source = tmp_path / "models.py" - source.write_text( - "from dataclasses import dataclass\n\n" - "@dataclass\n" - "class Bag:\n" - " items: list[str]\n" - " counts: dict[str, int]\n\n" - "class Runtime:\n" - " def __init__(self):\n" - " self.values: list[int] = []\n", - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "semantic-facts", - "list collection fields", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - cwd=tmp_path, - stdin="models.py:5:1:items\n", - ) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - assert payload["schemaId"] == "agent.semantic-protocols.semantic-fact-graph" - assert payload["languageId"] == "python" - assert payload["providerId"] == "asp-python" - assert payload["query"] == "list collection fields" - nodes = payload["nodes"] - edges = payload["edges"] - assert any( - node["kind"] == "field" - and node["symbol"] == "items" - and node["fields"]["typeValue"] == "list[str]" - and node["fields"]["collectionKind"] == "list" - and node["fields"]["languageId"] == "python" - and node["fields"]["providerId"] == "asp-python" - and node["fields"]["semanticFactKind"] == "field" - and node["fields"]["provenance"] == "parser" - and node["fields"]["confidence"] == "exact" - and node["fields"]["freshness"] == "fresh" - and node["fields"]["collectionFamily"] == "sequence" - and node["fields"]["collectionImpl"] == "list" - and node["fields"]["field"]["ownerKind"] == "class" - and node["fields"]["field"]["name"] == "items" - and node["fields"]["field"]["ownerPath"] == "models.py" - and "append" in node["fields"]["field"]["access"] - and node["fields"]["contextLocator"] == "models.py:4:6" - for node in nodes - ) - assert any( - node["kind"] == "type" - and node["value"] == "list[str]" - and node["fields"]["semanticFactKind"] == "type" - and node["fields"]["type"]["name"] == "list[str]" - and node["fields"]["type"]["element"] == "str" - for node in nodes - ) - assert any( - node["kind"] == "collection" - and node["symbol"] == "list" - and node["fields"]["semanticFactKind"] == "collection" - and node["fields"]["collection"]["family"] == "sequence" - and node["fields"]["collection"]["impl"] == "list" - and node["fields"]["collection"]["elementType"] == "str" - for node in nodes - ) - assert any( - node["kind"] == "field" - and node["symbol"] == "counts" - and node["fields"]["collectionFamily"] == "map" - and node["fields"]["field"]["access"] == ["read", "write", "validate"] - for node in nodes - ) - assert any( - node["kind"] == "type" - and node["value"] == "dict[str, int]" - and node["fields"]["type"]["key"] == "str" - and node["fields"]["type"]["value"] == "int" - for node in nodes - ) - assert any( - node["kind"] == "collection" - and node["symbol"] == "dict" - and node["fields"]["collection"]["family"] == "map" - and node["fields"]["collection"]["keyType"] == "str" - and node["fields"]["collection"]["valueType"] == "int" - for node in nodes - ) - assert any(edge["relation"] == "has_type" for edge in edges) - assert any(edge["relation"] == "collection_of" for edge in edges) - - -def test_search_semantic_facts_emits_package_build_dependency_and_tests(tmp_path): - (tmp_path / "pyproject.toml").write_text( - "[project]\n" - 'name = "fact-pkg"\n' - 'version = "0.1.0"\n' - 'dependencies = ["requests>=2", "attrs"]\n' - "\n" - "[project.optional-dependencies]\n" - 'dev = ["pytest>=8"]\n', - encoding="utf-8", - ) - (tmp_path / "model.py").write_text( - "class Cache:\n entries: list[str]\n", - encoding="utf-8", - ) - tests_dir = tmp_path / "tests" - tests_dir.mkdir() - (tests_dir / "test_api.py").write_text( - "def test_one():\n assert True\n\ndef helper():\n pass\n", - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - [ - "search", - "semantic-facts", - "field pytest requests dependency", - "--json", - "--workspace", - str(tmp_path), - ], - stdout=stdout, - cwd=tmp_path, - stdin="model.py:2:1:entries\n", - ) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - nodes = payload["nodes"] - edges = payload["edges"] - assert any( - node["kind"] == "package" - and node["value"] == "fact-pkg" - and node["action"] == "package" - and node["fields"]["semanticFactKind"] == "package" - and node["fields"]["manifestPath"] == "pyproject.toml" - for node in nodes - ) - assert any( - node["kind"] == "build" - and node["action"] == "build" - and node["fields"]["semanticFactKind"] == "build" - and node["fields"]["command"] == "uv run --project . pytest" - for node in nodes - ) - assert any( - node["kind"] == "dependency" - and node["value"] == "requests" - and node["action"] == "deps" - and node["fields"]["semanticFactKind"] == "dependency" - and node["fields"]["dependencyKind"] == "normal" - and node["fields"]["versionReq"] == ">=2" - for node in nodes - ) - assert any( - node["kind"] == "dependency" - and node["value"] == "pytest" - and node["fields"]["dependencyKind"] == "dev" - and node["fields"]["extra"] == "dev" - for node in nodes - ) - assert any( - node["kind"] == "test" - and node["path"] == "tests/test_api.py" - and node["action"] == "tests" - and node["fields"]["semanticFactKind"] == "test" - and node["fields"]["functionCount"] == 1 - for node in nodes - ) - for relation in ["builds", "depends_on", "tests", "belongs_to"]: - assert any(edge["relation"] == relation for edge in edges), relation - field_node = next( - node - for node in nodes - if node["kind"] == "field" and node["symbol"] == "entries" - ) - package_node = next(node for node in nodes if node["kind"] == "package") - assert any( - edge["source"] == field_node["id"] - and edge["target"] == package_node["id"] - and edge["relation"] == "belongs_to" - for edge in edges - ) diff --git a/tests/unit/harness/test_semantic_provider_doctor.py b/tests/unit/harness/test_semantic_provider_doctor.py index 54cf81d..b6d43c4 100644 --- a/tests/unit/harness/test_semantic_provider_doctor.py +++ b/tests/unit/harness/test_semantic_provider_doctor.py @@ -43,7 +43,10 @@ def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( registration["binary"], ) descriptors = registration["methodDescriptors"] - assert len(descriptors) == len(registration["methods"]) == 31 + assert len(descriptors) == len(registration["methods"]) == 7 + assert not any( + descriptor["method"].startswith("search/") for descriptor in descriptors + ) exact_query = next( descriptor for descriptor in descriptors diff --git a/tests/unit/harness/test_semantic_render_flow.py b/tests/unit/harness/test_semantic_render_flow.py deleted file mode 100644 index 70c41e6..0000000 --- a/tests/unit/harness/test_semantic_render_flow.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Semantic search flow renderer contract tests.""" - -from __future__ import annotations - -from asp_python._semantic_search_render_flow import finding_lines - - -def test_semantic_search_findings_render_path_first() -> None: - lines = finding_lines( - { - "findings": [ - { - "ruleId": "PY-AGENT-POLICY-001", - "count": 1, - "location": { - "path": "src/pkg/service.py", - "line": 3, - "column": 1, - }, - "severity": "info", - } - ] - } - ) - - assert lines == [ - "|find PY-AGENT-POLICY-001 x1 path=src/pkg/service.py line=3 column=1 node=O:src/pkg/service.py severity=info" - ] - assert "at=O:" not in lines[0] diff --git a/tests/unit/harness/test_semantic_search_graph_profiles.py b/tests/unit/harness/test_semantic_search_graph_profiles.py deleted file mode 100644 index e544787..0000000 --- a/tests/unit/harness/test_semantic_search_graph_profiles.py +++ /dev/null @@ -1,98 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path -from typing import Any - -import pytest - -from asp_python._semantic_search_graph_render import ( - compact_graph_seed_packet_text, -) - - -def _render_fields(fields: dict[str, Any]) -> str: - return " ".join(f"{key}={value}" for key, value in fields.items()) - - -def test_compact_graph_profiles_filter_to_rendered_aliases() -> None: - workspace_renderer = ( - Path(os.environ["SEMANTIC_AGENT_PROTOCOL_BIN"]) - if os.environ.get("SEMANTIC_AGENT_PROTOCOL_BIN") - else Path(__file__).resolve().parents[5] / ".bin" / "asp" - ) - if not workspace_renderer.exists(): - pytest.skip("workspace graph renderer is not built") - os.environ["SEMANTIC_AGENT_PROTOCOL_BIN"] = str(workspace_renderer) - - packet: dict[str, Any] = { - "schemaId": "agent.semantic-protocols.semantic-search-packet", - "schemaVersion": "1", - "protocolId": "agent.semantic-protocols.search", - "protocolVersion": "1", - "languageId": "python", - "providerId": "asp-python", - "binary": "asp-python", - "namespace": "agent.semantic-protocols.languages.python.asp-python", - "method": "search/owner", - "projectRoot": ".", - "view": "seeds", - "renderMode": "compact", - "header": {"kind": "search-owner", "fields": {}}, - "nodes": [], - "edges": [], - "nextActions": [ - {"kind": "owner", "target": "src/pkg/service.py"}, - {"kind": "tests", "target": "tests/test_service.py"}, - ], - "owners": [], - "hits": [], - "findings": [], - "notes": [], - "searchSynthesis": {"algorithm": "seed-frontier"}, - "reasoningProfiles": [ - { - "profile": "owner-query", - "selectors": [ - {"kind": "owner", "alias": "O", "required": True}, - {"kind": "query", "alias": "Q", "required": True}, - ], - "returns": ["items", "tests", "dependency-usage"], - }, - { - "profile": "query-deps", - "selectors": [ - {"kind": "query", "alias": "Q", "required": True}, - {"kind": "dependency", "alias": "D", "required": True}, - ], - "returns": ["owners", "imports", "usage-tests"], - }, - { - "profile": "owner-tests", - "selectors": [ - {"kind": "owner", "alias": "O", "required": True}, - ], - "returns": [ - "covering-tests", - "test-entrypoints", - "fixtures", - ], - }, - { - "profile": "finding-frontier", - "selectors": [ - {"kind": "finding", "alias": "F", "required": True}, - {"kind": "owner", "alias": "O", "required": False}, - ], - "returns": ["affected-owners", "tests", "verification-actions"], - }, - ], - } - - output = compact_graph_seed_packet_text(packet, _render_fields) - - assert "aliases: graph:{G=search,O=owner,T=test}" in output - assert "entries=owner-tests(O=>covering-tests+test-entrypoints+fixtures)" in output - assert "owner-query(" not in output - assert "query-deps(" not in output - assert "finding-frontier(" not in output diff --git a/tests/unit/harness/test_semantic_search_graph_render_shell_out.py b/tests/unit/harness/test_semantic_search_graph_render_shell_out.py deleted file mode 100644 index 31a1b27..0000000 --- a/tests/unit/harness/test_semantic_search_graph_render_shell_out.py +++ /dev/null @@ -1,109 +0,0 @@ -from __future__ import annotations - -import json -from pathlib import Path - -import pytest - -from asp_python._semantic_search_graph_render import ( - SEMANTIC_AGENT_PROTOCOL_BIN_ENV, - CompactGraphRenderError, - compact_graph_seed_packet_text, - render_compact_graph_packet, -) - - -def test_compact_graph_renderer_shells_out_to_protocol_bin( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - argv_path = tmp_path / "argv.txt" - stdin_path = tmp_path / "stdin.json" - protocol_bin = tmp_path / "semantic-agent-protocol" - protocol_bin.write_text( - "#!/bin/sh\n" - 'printf "%s\\n" "$@" > "$ASP_ARGV_PATH"\n' - 'cat > "$ASP_STDIN_PATH"\n' - 'printf "[search-lexical] q=test\\n"\n' - ) - protocol_bin.chmod(0o755) - monkeypatch.setenv(SEMANTIC_AGENT_PROTOCOL_BIN_ENV, str(protocol_bin)) - monkeypatch.setenv("ASP_ARGV_PATH", str(argv_path)) - monkeypatch.setenv("ASP_STDIN_PATH", str(stdin_path)) - - packet = {"header": {"kind": "search-lexical"}} - - output = render_compact_graph_packet(packet, seed_limit=3) - - assert output == "[search-lexical] q=test\n" - assert argv_path.read_text().splitlines() == [ - "graph", - "render", - "--packet", - "-", - "--view", - "seeds", - "--seeds", - "3", - ] - assert json.loads(stdin_path.read_text()) == packet - - -def test_compact_graph_renderer_missing_bin_does_not_fallback( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - monkeypatch.setenv( - SEMANTIC_AGENT_PROTOCOL_BIN_ENV, - str(tmp_path / "missing-semantic-agent-protocol"), - ) - - with pytest.raises(CompactGraphRenderError, match="not found"): - render_compact_graph_packet({"header": {"kind": "search-lexical"}}) - - -def test_compact_graph_seed_packet_appends_non_graph_flow_lines( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - stdin_path = tmp_path / "stdin.json" - protocol_bin = tmp_path / "semantic-agent-protocol" - protocol_bin.write_text( - "#!/bin/sh\n" - 'cat > "$ASP_STDIN_PATH"\n' - 'printf "[search-ingest] root=. alg=seed-frontier\\n"\n' - 'printf "G>{}\\n"\n' - 'printf "rank= frontier=\\n"\n', - encoding="utf-8", - ) - protocol_bin.chmod(0o755) - monkeypatch.setenv(SEMANTIC_AGENT_PROTOCOL_BIN_ENV, str(protocol_bin)) - monkeypatch.setenv("ASP_STDIN_PATH", str(stdin_path)) - - packet = { - "header": {"kind": "search-ingest"}, - "notes": [ - { - "kind": "stdin-required", - "message": "search ingest consumes stdin candidate paths", - } - ], - "nextActions": [ - {"kind": "owner", "target": "src/service.py"}, - { - "kind": "prime", - "target": "search prime --view seeds", - "scope": "project-discovery", - }, - ], - } - - output = compact_graph_seed_packet_text(packet, lambda _fields: "") - - rendered_packet = json.loads(stdin_path.read_text()) - assert rendered_packet["nextActions"] == [ - {"kind": "owner", "target": "src/service.py"} - ] - assert "|note kind=stdin-required" in output - assert "|next prime:" in output - assert "owner:path(search prime" not in output diff --git a/tests/unit/harness/test_semantic_search_ingest_cli.py b/tests/unit/harness/test_semantic_search_ingest_cli.py deleted file mode 100644 index afabd69..0000000 --- a/tests/unit/harness/test_semantic_search_ingest_cli.py +++ /dev/null @@ -1,93 +0,0 @@ -"""Semantic search ingest CLI parsing tests.""" - -from __future__ import annotations - -import time -from io import StringIO -from pathlib import Path - -import pytest - -from asp_python import python_semantic_language_registration, run_cli -from asp_python._semantic_search_cli import parse_semantic_search_args - -FAST_INGEST_BUDGET_SECONDS = 0.25 - - -def test_search_ingest_descriptor_accepts_items_tests_pipes() -> None: - descriptors = python_semantic_language_registration()["methodDescriptors"] - - assert any( - descriptor["method"] == "search/ingest" - and descriptor["acceptedPipes"] == ["items", "tests"] - and descriptor["acceptsStdin"] is True - for descriptor in descriptors - ) - - -def test_search_ingest_accepts_pipes_with_explicit_workspace() -> None: - parsed = parse_semantic_search_args( - ["ingest", "items", "tests", "--view", "seeds", "--workspace", "."] - ) - - assert parsed.error is None - assert parsed.view == "ingest" - assert parsed.pipes == ("items", "tests") - assert parsed.project_root == Path(".") - assert parsed.render_mode == "seeds" - - -def test_search_ingest_rejects_positional_workspace_after_pipes() -> None: - parsed = parse_semantic_search_args( - ["ingest", "items", "tests", "extra", "--view", "seeds", "."] - ) - - assert ( - parsed.error - == "search does not accept positional WORKSPACE; use --workspace " - ) - - -def test_search_ingest_empty_stdin_seeds_explains_prime_route( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "sample"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - from asp_python import _cli_protocol - - def fail_full_harness(*_args: object, **_kwargs: object) -> object: - raise AssertionError("full harness should not run for empty ingest seeds") - - monkeypatch.setattr(_cli_protocol, "_run_search_harness", fail_full_harness) - stdout = StringIO() - - started_at = time.perf_counter() - exit_code = run_cli( - [ - "search", - "ingest", - "items", - "tests", - "--view", - "seeds", - "--workspace", - ".", - ], - stdout=stdout, - stdin="", - cwd=tmp_path, - ) - elapsed = time.perf_counter() - started_at - - output = stdout.getvalue() - assert exit_code == 0 - assert output.startswith("[search-ingest]") - assert "|note kind=stdin-required" in output - assert "search ingest consumes stdin candidate paths" in output - assert "search prime --view seeds" in output - assert "|next prime:" in output - assert "owner:path(search prime" not in output - assert elapsed < FAST_INGEST_BUDGET_SECONDS diff --git a/tree-sitter/tree-sitter-python/grammar-profile.json b/tree-sitter/tree-sitter-python/grammar-profile.json index cfaf165..8506788 100644 --- a/tree-sitter/tree-sitter-python/grammar-profile.json +++ b/tree-sitter/tree-sitter-python/grammar-profile.json @@ -13,7 +13,7 @@ "owner": "main-asp", "repository": "https://github.com/tao3k/agent-semantic-protocols", "revision": "4e3417721d576716bcf82b7cbf8b9c2dc9a2b32a", - "contractFingerprint": "sha256:679a7adbe48d53d703da3802757ca12b7c89e0c5feb58b4a17b3b8ef022e3325", + "contractFingerprint": "sha256:53ae5428ef4f95b69d547682a158c7b98f474d21122151c82b4d1bdfbf083355", "queryCorpusValidator": "asp-tree-sitter-validate-python-query-corpus" }, "queryCorpus": { From 456d672fc2e52bd2da8a22c31da7383b64e5992d Mon Sep 17 00:00:00 2001 From: guangtao Date: Tue, 8 Sep 2026 14:16:45 +0800 Subject: [PATCH 17/20] schema: sync playbook performance receipt --- schemas/.asp-schema-manager-membership.json | 4 ++-- schemas/.asp-schema-manager-receipt.json | 2 +- ...-search-playbook-performance-receipt.v1.schema.json | 10 ++++++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/schemas/.asp-schema-manager-membership.json b/schemas/.asp-schema-manager-membership.json index e15f0ad..bcef1bf 100644 --- a/schemas/.asp-schema-manager-membership.json +++ b/schemas/.asp-schema-manager-membership.json @@ -1,7 +1,7 @@ { "languageId": "python", "profileDigest": "blake3-256:a1c247a2b628f4e7fe173ecaa855dfc90e1416c1d168f3b9d67c907a7d12beb2", - "bundleDigest": "blake3-256:c95fa3b9e3c775ee45275a08f1a247b564ee6da31d5cc4878aa1438c7b8c12ed", + "bundleDigest": "blake3-256:fcaf570a9c0bc9b693256307444ff8c509b547a74fc3d5ae3b29ae16be19bce6", "schemas": [ { "name": "asp-client-cancellation-probe-request.schema.json", @@ -145,7 +145,7 @@ }, { "name": "large-search-playbook-performance-receipt.v1.schema.json", - "digest": "blake3-256:951cee907a77fd2bc695a19b977a50508dc216eb3fe1e51f10195572880b4377" + "digest": "blake3-256:06a8a9c3fdcd7394b11eef3b7a00c4fd3f1d8a582a19ad6955efda59284e4db2" }, { "name": "lexical-postings-work-reduction-receipt.schema.json", diff --git a/schemas/.asp-schema-manager-receipt.json b/schemas/.asp-schema-manager-receipt.json index f0c74d5..0948869 100644 --- a/schemas/.asp-schema-manager-receipt.json +++ b/schemas/.asp-schema-manager-receipt.json @@ -1,5 +1,5 @@ { "schemaId": "agent.semantic-protocols.language-schema-bundle-receipt", "schemaVersion": "1", - "schemaDigest": "blake3-256:c95fa3b9e3c775ee45275a08f1a247b564ee6da31d5cc4878aa1438c7b8c12ed" + "schemaDigest": "blake3-256:fcaf570a9c0bc9b693256307444ff8c509b547a74fc3d5ae3b29ae16be19bce6" } \ No newline at end of file diff --git a/schemas/large-search-playbook-performance-receipt.v1.schema.json b/schemas/large-search-playbook-performance-receipt.v1.schema.json index 015c1b7..24383cf 100644 --- a/schemas/large-search-playbook-performance-receipt.v1.schema.json +++ b/schemas/large-search-playbook-performance-receipt.v1.schema.json @@ -55,8 +55,10 @@ "properties": { "samples": {"type": "integer", "minimum": 32}, "fdProcessCount": {"const": 0}, - "rgProcessCount": {"type": "integer", "minimum": 32}, + "rgProcessCount": {"const": 0}, "tantivyBuildCount": {"const": 0}, + "p99Nanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000}, + "maxNanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000}, "contentVerificationP95Nanos": {"$ref": "#/$defs/nanos"}, "nativeSyntaxVerificationP95Nanos": {"$ref": "#/$defs/nanos"} } @@ -128,7 +130,11 @@ "unevaluatedProperties": false, "allOf": [ {"$ref": "#/$defs/distribution"}, - {"properties": {"samples": {"type": "integer", "minimum": 512}}} + {"properties": { + "samples": {"type": "integer", "minimum": 512}, + "p99Nanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000}, + "maxNanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000} + }} ] }, "concurrent": { From 745c1bb0f96b281e5d463cb086085d5245be8d7f Mon Sep 17 00:00:00 2001 From: guangtao Date: Wed, 9 Sep 2026 16:39:24 +0800 Subject: [PATCH 18/20] refactor: align ASP Python public boundary --- README.md | 30 +- development.md | 20 +- ...boundary.md => 101_asp_python_boundary.md} | 20 +- docs/03_features/201_rule_catalog.md | 38 +- .../202_agent_pythonic_policy_research.md | 14 +- docs/03_features/202_runner_modes.md | 12 +- docs/03_features/203_cli.md | 2 +- docs/03_features/204_pytest.md | 30 +- docs/03_features/205_verification.md | 8 +- docs/index.md | 8 +- provider/asp-provider-workspace-install.json | 2 +- pyproject.toml | 6 +- schemas/.asp-schema-manager-membership.json | 50 +- schemas/.asp-schema-manager-receipt.json | 2 +- schemas/asp-client-search-request.schema.json | 23 - ...ace-search-playbook-request.v1.schema.json | 47 +- ...kspace-syntax-query-request.v1.schema.json | 9 +- schemas/language-schema-profiles.json | 2 - ...laybook-performance-receipt.v1.schema.json | 25 +- ...-topology-inference-receipt.v1.schema.json | 151 +++ .../project-topology-library.v1.schema.json | 957 +++++++++++++++--- schemas/provider-manifest.schema.json | 14 +- ...n-graph-performance-receipt.v1.schema.json | 19 +- ...ct-execution-closure-member.v1.schema.json | 10 +- ...ident-request-plane-receipt.v1.schema.json | 73 ++ .../search-topology-settlement.v1.schema.json | 737 +++++++++++--- .../semantic-assurance-case.v1.schema.json | 295 ------ schemas/semantic-definitions.v1.schema.json | 4 +- .../semantic-evidence-graph.v1.schema.json | 263 ----- schemas/semantic-graph.v1.schema.json | 32 +- ...emantic-language-projection.v1.schema.json | 2 +- .../semantic-language-registry.v1.schema.json | 3 +- ...semantic-search-definitions.v1.schema.json | 101 ++ src/asp_python/__init__.py | 40 +- src/asp_python/_agent_namespace.py | 8 +- src/asp_python/_agent_policy.py | 4 +- src/asp_python/_agent_reasoning_tree.py | 6 +- src/asp_python/_agent_snapshot.py | 14 +- src/asp_python/_agent_snapshot_tree.py | 2 +- src/asp_python/_asp_rules.py | 42 + src/asp_python/_cli.py | 4 +- src/asp_python/_cli_agent.py | 4 +- src/asp_python/_cli_args.py | 40 +- src/asp_python/_cli_protocol.py | 44 +- src/asp_python/_cli_query_args.py | 2 +- src/asp_python/_cli_query_hook_args.py | 2 +- src/asp_python/_constants.py | 2 +- src/asp_python/_discovery.py | 12 +- src/asp_python/_evidence_graph.py | 216 ---- src/asp_python/_evidence_graph_turbo.py | 138 --- src/asp_python/_harness_rules.py | 44 - src/asp_python/_model.py | 28 +- src/asp_python/_modularity.py | 8 +- src/asp_python/_project_config.py | 8 +- src/asp_python/_project_evaluation.py | 8 +- src/asp_python/_project_metadata.py | 2 +- src/asp_python/_project_policy.py | 4 +- src/asp_python/_project_policy_catalog.py | 4 +- src/asp_python/_project_policy_imports.py | 6 +- src/asp_python/_project_policy_layout.py | 10 +- src/asp_python/_project_policy_pytest_gate.py | 18 +- .../_project_policy_verification.py | 10 +- src/asp_python/_pytest.py | 10 +- src/asp_python/_pytest_plugin_options.py | 32 +- src/asp_python/_pytest_plugin_project.py | 2 +- src/asp_python/_render.py | 8 +- src/asp_python/_rule_packs.py | 32 +- src/asp_python/_runner.py | 46 +- src/asp_python/_semantic_language.py | 24 - src/asp_python/_semantic_language_ids.py | 5 - .../_semantic_language_invocation.py | 14 - src/asp_python/_source.py | 2 +- src/asp_python/_syntax.py | 2 +- src/asp_python/_test_layout.py | 12 +- src/asp_python/_test_layout_bloat.py | 4 +- src/asp_python/_test_layout_catalog.py | 4 +- src/asp_python/_test_layout_config.py | 2 +- src/asp_python/_version.py | 2 +- src/asp_python/{harness.py => api.py} | 38 +- .../{harness-rules.md => asp-rules.md} | 4 +- src/asp_python/pytest_plugin.py | 26 +- src/asp_python/verification/__init__.py | 2 +- src/asp_python/verification/facts.py | 4 +- src/asp_python/verification/model.py | 8 +- src/asp_python/verification/planner.py | 10 +- src/asp_python/verification/profile_index.py | 8 +- src/python_lang_parser/_project_model.py | 2 +- .../test_native_idiom_binding_state.py | 4 +- .../asp-rules.generated.md} | 8 +- .../project_policy/test_catalog.py | 0 .../project_policy/test_layout.py | 0 .../project_policy/test_metadata.py | 0 .../project_policy/test_metadata_policy.py | 8 +- .../project_policy/test_typed_packages.py | 0 .../provider_runtime_live_support.py | 0 .../python_project_fixture.py | 0 .../control_flow_v1/benchmark.toml | 2 +- .../control_flow_v1/expect/findings.json | 0 .../control_flow_v1/inputs/criterion.py | 0 .../control_flow_v1/scenario.toml | 0 .../snapshot_support.py | 2 +- .../test_agent_algorithm_policy.py | 36 +- .../test_agent_policy.py | 22 +- .../test_agent_policy_snapshots.py | 10 +- .../test_asp_rules.py} | 38 +- .../unit/{harness => asp_python}/test_cli.py | 2 +- .../test_dependency_topology.py | 0 .../test_dev_command_log.py | 0 .../test_exact_source_projection.py | 0 .../test_modern_design.py | 26 +- .../test_modularity_catalog.py | 0 .../test_parser_boundary_contract.py | 18 +- .../test_policy_contract.py | 10 +- .../test_policy_snapshots.py | 8 +- .../test_project_api.py | 10 +- .../test_project_config.py | 2 +- .../test_project_fixture_scope.py | 0 .../test_project_resolution.py | 0 .../test_project_resolution_extra_paths.py | 0 .../test_projection_batch.py | 0 .../test_provider_runtime.py | 0 .../test_public_cli_identity.py | 0 .../test_pyproject_package_scope.py | 0 .../{harness => asp_python}/test_pytest.py | 40 +- .../test_pytest_plugin.py | 24 +- .../test_reasoning_tree_policy.py | 0 .../test_render_snapshots.py | 18 +- .../test_runner_config.py | 10 +- .../test_search_playbook_boundary.py | 0 .../test_semantic_agent_cli.py | 2 +- .../test_semantic_cli.py | 2 +- .../test_semantic_cli_ast_patch.py | 0 .../test_semantic_cli_benchmark_registry.py | 0 ...mantic_cli_structural_selector_registry.py | 0 ...est_semantic_cli_tree_sitter_predicates.py | 0 .../test_semantic_cli_tree_sitter_registry.py | 0 .../test_semantic_language_schemas.py | 0 .../test_semantic_provider_doctor.py | 4 +- .../test_software_criterion_snapshots.py | 7 +- .../test_test_layout_config.py | 2 +- .../test_verification.py | 20 +- .../test_agent_snapshot_profile_index.py | 4 +- .../test_performance_microbench.py | 4 +- .../verification/test_policy_regressions.py | 6 +- .../verification/test_profile_index.py | 8 +- .../test_config_contracts.py | 14 +- .../test_discovery_runner.py | 18 +- .../test_render_assertions.py | 50 +- tests/unit/harness/test_evidence_graph.py | 112 -- .../test_pyproject_metadata.py | 4 +- ...snapshot__py_agent_r001_module_intent.snap | 2 +- ...t__py_agent_r002_callable_annotations.snap | 2 +- ...shot__py_agent_r003_callable_conflict.snap | 2 +- ...hot__py_agent_r004_repeated_namespace.snap | 2 +- ...snapshot__py_agent_r005_type_conflict.snap | 2 +- ...napshot__py_agent_r006_value_conflict.snap | 2 +- ...snapshot__py_agent_r007_branch_intent.snap | 2 +- ...napshot__py_agent_r008_branch_surface.snap | 2 +- ...apshot__py_agent_r009_algorithm_shape.snap | 2 +- ...t__py_agent_r010_function_compactness.snap | 2 +- ..._snapshot__py_agent_r011_native_idiom.snap | 2 +- ...cy_snapshot__py_agent_r012_type_shape.snap | 2 +- ...snapshot__py_mod_r001_wildcard_import.snap | 2 +- ...licy_snapshot__py_mod_r002_bare_print.snap | 2 +- ...licy_snapshot__py_mod_r003_facade_all.snap | 2 +- ...licy_snapshot__py_mod_r004_breakpoint.snap | 2 +- ...cy_snapshot__py_mod_r006_module_bloat.snap | 2 +- ...ot__py_mod_r007_reasoning_tree_shadow.snap | 2 +- ...icy_snapshot__py_proj_r001_src_layout.snap | 2 +- ...apshot__py_proj_r002_declared_package.snap | 2 +- ...olicy_snapshot__py_proj_r003_py_typed.snap | 2 +- ...pshot__py_proj_r004_typed_annotations.snap | 2 +- ...y_snapshot__py_proj_r005_project_name.snap | 2 +- ...napshot__py_proj_r006_requires_python.snap | 2 +- ...snapshot__py_proj_r007_build_requires.snap | 2 +- ...y_snapshot__py_proj_r008_import_names.snap | 2 +- ...shot__py_proj_r009_entry_point_target.snap | 2 +- ...cy_snapshot__py_proj_r010_pytest_gate.snap | 8 +- ...ot__py_proj_r011_verification_profile.snap | 2 +- ...cy_snapshot__py_test_r001_root_pytest.snap | 2 +- ...napshot__py_test_r002_unexpected_root.snap | 2 +- ...icy_snapshot__py_test_r003_unit_bloat.snap | 2 +- ...licy_snapshot__python_compile_invalid.snap | 2 +- ...olicy_snapshot__python_syntax_invalid.snap | 2 +- tests/unit/test_public_api.py | 236 +++-- 185 files changed, 2605 insertions(+), 2302 deletions(-) rename docs/01_core/{101_harness_boundary.md => 101_asp_python_boundary.md} (89%) delete mode 100644 schemas/asp-client-search-request.schema.json create mode 100644 schemas/project-topology-inference-receipt.v1.schema.json create mode 100644 schemas/runtime-resident-request-plane-receipt.v1.schema.json delete mode 100644 schemas/semantic-assurance-case.v1.schema.json delete mode 100644 schemas/semantic-evidence-graph.v1.schema.json create mode 100644 schemas/semantic-search-definitions.v1.schema.json create mode 100644 src/asp_python/_asp_rules.py delete mode 100644 src/asp_python/_evidence_graph.py delete mode 100644 src/asp_python/_evidence_graph_turbo.py delete mode 100644 src/asp_python/_harness_rules.py rename src/asp_python/{harness.py => api.py} (89%) rename src/asp_python/{harness-rules.md => asp-rules.md} (93%) rename tests/unit/{harness => asp_python}/agent_readability/test_native_idiom_binding_state.py (90%) rename tests/unit/{harness/harness-rules.generated.md => asp_python/asp-rules.generated.md} (91%) rename tests/unit/{harness => asp_python}/project_policy/test_catalog.py (100%) rename tests/unit/{harness => asp_python}/project_policy/test_layout.py (100%) rename tests/unit/{harness => asp_python}/project_policy/test_metadata.py (100%) rename tests/unit/{harness => asp_python}/project_policy/test_metadata_policy.py (97%) rename tests/unit/{harness => asp_python}/project_policy/test_typed_packages.py (100%) rename tests/unit/{harness => asp_python}/provider_runtime_live_support.py (100%) rename tests/unit/{harness => asp_python}/python_project_fixture.py (100%) rename tests/unit/{harness => asp_python}/scenarios/software_criteria/control_flow_v1/benchmark.toml (92%) rename tests/unit/{harness => asp_python}/scenarios/software_criteria/control_flow_v1/expect/findings.json (100%) rename tests/unit/{harness => asp_python}/scenarios/software_criteria/control_flow_v1/inputs/criterion.py (100%) rename tests/unit/{harness => asp_python}/scenarios/software_criteria/control_flow_v1/scenario.toml (100%) rename tests/unit/{harness => asp_python}/snapshot_support.py (94%) rename tests/unit/{harness => asp_python}/test_agent_algorithm_policy.py (87%) rename tests/unit/{harness => asp_python}/test_agent_policy.py (94%) rename tests/unit/{harness => asp_python}/test_agent_policy_snapshots.py (95%) rename tests/unit/{harness/test_harness_rules.py => asp_python/test_asp_rules.py} (57%) rename tests/unit/{harness => asp_python}/test_cli.py (97%) rename tests/unit/{harness => asp_python}/test_dependency_topology.py (100%) rename tests/unit/{harness => asp_python}/test_dev_command_log.py (100%) rename tests/unit/{harness => asp_python}/test_exact_source_projection.py (100%) rename tests/unit/{harness => asp_python}/test_modern_design.py (88%) rename tests/unit/{harness => asp_python}/test_modularity_catalog.py (100%) rename tests/unit/{harness => asp_python}/test_parser_boundary_contract.py (89%) rename tests/unit/{harness => asp_python}/test_policy_contract.py (97%) rename tests/unit/{harness => asp_python}/test_policy_snapshots.py (98%) rename tests/unit/{harness => asp_python}/test_project_api.py (96%) rename tests/unit/{harness => asp_python}/test_project_config.py (96%) rename tests/unit/{harness => asp_python}/test_project_fixture_scope.py (100%) rename tests/unit/{harness => asp_python}/test_project_resolution.py (100%) rename tests/unit/{harness => asp_python}/test_project_resolution_extra_paths.py (100%) rename tests/unit/{harness => asp_python}/test_projection_batch.py (100%) rename tests/unit/{harness => asp_python}/test_provider_runtime.py (100%) rename tests/unit/{harness => asp_python}/test_public_cli_identity.py (100%) rename tests/unit/{harness => asp_python}/test_pyproject_package_scope.py (100%) rename tests/unit/{harness => asp_python}/test_pytest.py (78%) rename tests/unit/{harness => asp_python}/test_pytest_plugin.py (90%) rename tests/unit/{harness => asp_python}/test_reasoning_tree_policy.py (100%) rename tests/unit/{harness => asp_python}/test_render_snapshots.py (94%) rename tests/unit/{harness => asp_python}/test_runner_config.py (96%) rename tests/unit/{harness => asp_python}/test_search_playbook_boundary.py (100%) rename tests/unit/{harness => asp_python}/test_semantic_agent_cli.py (94%) rename tests/unit/{harness => asp_python}/test_semantic_cli.py (95%) rename tests/unit/{harness => asp_python}/test_semantic_cli_ast_patch.py (100%) rename tests/unit/{harness => asp_python}/test_semantic_cli_benchmark_registry.py (100%) rename tests/unit/{harness => asp_python}/test_semantic_cli_structural_selector_registry.py (100%) rename tests/unit/{harness => asp_python}/test_semantic_cli_tree_sitter_predicates.py (100%) rename tests/unit/{harness => asp_python}/test_semantic_cli_tree_sitter_registry.py (100%) rename tests/unit/{harness => asp_python}/test_semantic_language_schemas.py (100%) rename tests/unit/{harness => asp_python}/test_semantic_provider_doctor.py (94%) rename tests/unit/{harness => asp_python}/test_software_criterion_snapshots.py (96%) rename tests/unit/{harness => asp_python}/test_test_layout_config.py (96%) rename tests/unit/{harness => asp_python}/test_verification.py (95%) rename tests/unit/{harness => asp_python}/verification/test_agent_snapshot_profile_index.py (95%) rename tests/unit/{harness => asp_python}/verification/test_performance_microbench.py (97%) rename tests/unit/{harness => asp_python}/verification/test_policy_regressions.py (94%) rename tests/unit/{harness => asp_python}/verification/test_profile_index.py (93%) rename tests/unit/{lang_harness => asp_python_paths}/test_config_contracts.py (87%) rename tests/unit/{lang_harness => asp_python_paths}/test_discovery_runner.py (92%) rename tests/unit/{lang_harness => asp_python_paths}/test_render_assertions.py (75%) delete mode 100644 tests/unit/harness/test_evidence_graph.py diff --git a/README.md b/README.md index 49c95d6..bce47e4 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,15 @@ modern Python packages. It ships two library boundaries in one repo: - `asp_python`: project discovery, deterministic rule packs, compact rendered diagnostics, and pytest-friendly assertions. -The harness is library-first. Callers pass a project root or explicit paths, +ASP Python is library-first. Callers pass a project root or explicit paths, then decide whether to assert, render compact text, or inspect the structured report. Compact text is the default agent repair surface; JSON is available for -tooling through `render_python_lang_harness_json()`. +tooling through `render_asp_python_report_json()`. -`python_lang_parser` is the semantic foundation. Harness policy consumes parser +`python_lang_parser` is the semantic foundation. ASP Python policy consumes parser reports and parser-owned `pyproject.toml` metadata instead of re-parsing Python source or guessing package scope in the rule layer; tests-root layout stays in -the harness. +ASP Python. ## Quick Use @@ -29,9 +29,9 @@ from asp_python import ( PythonVerificationProfileHint, PythonVerificationTaskKind, assert_asp_python_clean, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, - render_python_lang_harness, + render_asp_python_report, render_python_reasoning_tree, render_python_verification_plan, run_asp_python, @@ -44,7 +44,7 @@ def test_asp_python_policy() -> None: report = run_asp_python(Path(".")) print(__version__) -print(render_python_lang_harness(report)) +print(render_asp_python_report(report)) print(render_python_reasoning_tree(report)) ``` @@ -52,7 +52,7 @@ The project runner scans the whole Python project root by default, excluding tool/cache/build directories such as `.venv`, `__pycache__`, `build`, and `dist`. Conventional source and test roots still classify project policy, but they do not narrow parser coverage. The explicit path runner, -`run_python_lang_harness([...])`, is useful for focused parser and syntax +`run_asp_python_paths([...])`, is useful for focused parser and syntax checks. Use `AspPythonConfig` to change source-root classification, test-root classification, extra external project paths, test inclusion, or blocking @@ -65,7 +65,7 @@ Standard `[project]` metadata such as `name`, `requires-python`, `python_lang_parser` and appears in project policy and reasoning-tree facts. When `include_tests=False`, test files are not parsed, but tests-root layout policy still runs. Explained local exceptions can live in -`tests/python-project-harness-rules.toml`. +`tests/asp-python-rules.toml`. For agent repair loops, `render_python_reasoning_tree(report)` emits a compact package/module owner tree from parser-owned facts. It shows package branches, @@ -99,12 +99,12 @@ python -c 'from asp_python import assert_asp_python_clean; assert_asp_python_cle ## Verification Planning -Verification is a library-first Agent contract. The harness does not execute +Verification is a library-first Agent contract. ASP Python does not execute benchmark, security, stress, or chaos tools. It plans parser-backed obligations that external skills can satisfy with receipts or complete waivers: ```python -config = default_python_harness_config().with_verification_profile_hint( +config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -133,7 +133,7 @@ responsibilities. ## Pytest Dev Dependency -Downstream projects can load the harness through their test/dev dependency +Downstream projects can load ASP Python through their test/dev dependency group: ```toml @@ -144,12 +144,12 @@ test = [ ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] ``` The pytest plugin is exposed through the package `pytest11` entry point. It is loaded by pytest when the dev dependency is installed, but it only runs the -harness when `--python-project-harness` is enabled. Projects that prefer an +ASP Python policy gate when `--asp-python` is enabled. Projects that prefer an explicit test file can use the public helper: ```python @@ -189,4 +189,4 @@ Detailed package material lives under [`docs/`](docs/index.md). GitHub Actions runs the package contract on every pull request and on pushes to the default branch: `uv sync --group test --locked`, ruff format/check, pytest, -self-harness, agent snapshot, wheel/sdist build, and diff hygiene. +ASP Python self-check, agent snapshot, wheel/sdist build, and diff hygiene. diff --git a/development.md b/development.md index 92f1c74..4752992 100644 --- a/development.md +++ b/development.md @@ -6,8 +6,8 @@ .devenv/devenv-profile-exec uv run --project languages/asp-python --group test ruff format --check languages/asp-python/src languages/asp-python/tests .devenv/devenv-profile-exec uv run --project languages/asp-python --group test ruff check languages/asp-python/src languages/asp-python/tests .devenv/devenv-profile-exec uv run --project languages/asp-python --group test pytest languages/asp-python/tests -q -.devenv/devenv-profile-exec uv run --project languages/asp-python --group test python-project-harness languages/asp-python -.devenv/devenv-profile-exec uv run --project languages/asp-python --group test python-project-harness --agent-snapshot languages/asp-python +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test asp-python languages/asp-python +.devenv/devenv-profile-exec uv run --project languages/asp-python --group test asp-python --agent-snapshot languages/asp-python .devenv/devenv-profile-exec uv build languages/asp-python .devenv/devenv-profile-exec git diff --check ``` @@ -16,7 +16,7 @@ Use `.devenv/devenv-profile-exec` from the repository root so the captured devenv-managed Python and `uv` environment are used consistently. GitHub Actions runs the same validation surface without `direnv`: `uv sync ---group test --locked`, ruff format/check, pytest, self-harness, package build, +--group test --locked`, ruff format/check, pytest, ASP Python self-check, package build, agent snapshot, and `git diff --check`. ## Library Boundary @@ -28,12 +28,12 @@ This repo is a standalone Python library project. It ships: and pytest embedding Keep these boundaries separate. Parser modules should not know about project -policy, pytest, or agent repair wording. Harness modules should consume parser +policy, pytest, or agent repair wording. ASP Python modules should consume parser reports and emit deterministic findings. ## Self-Applied Policy -`tests/unit/test_self_hosting.py` mounts the project harness against this repo. +`tests/unit/test_self_hosting.py` mounts the ASP Python against this repo. When adding tests, keep behavior coverage under `tests/unit` and avoid scattered `tests/test_*.py` files at the test root. @@ -41,14 +41,14 @@ Default assertions block on `Warning` and `Error`. `PY-AGENT-*` rules stay `Info`: rendered by default as repair advice, but non-blocking unless a caller opts into stricter severity selection. -The CLI is part of that same contract. Keep `python-project-harness` as a thin +The CLI is part of that same contract. Keep `asp-python` as a thin adapter over the library runner and renderers. ## Renderer Contract Compact text is the primary agent-facing repair surface. It should remain small: rule id, location, optional source line, pointer label, and one `Required:` -contract line. Use `render_python_lang_harness_json()` for tooling that needs +contract line. Use `render_asp_python_report_json()` for tooling that needs the full structured payload. ## Snapshot Workflow @@ -59,9 +59,9 @@ Normal tests compare snapshots only. Refresh them intentionally: ```shell .devenv/devenv-profile-exec env ASP_PYTHON_UPDATE_SNAPSHOTS=1 \ uv run --project languages/asp-python --group test pytest \ - languages/asp-python/tests/unit/harness/test_render_snapshots.py \ - languages/asp-python/tests/unit/harness/test_agent_policy_snapshots.py \ - languages/asp-python/tests/unit/harness/test_policy_snapshots.py -q + languages/asp-python/tests/unit/asp_python/test_render_snapshots.py \ + languages/asp-python/tests/unit/asp_python/test_agent_policy_snapshots.py \ + languages/asp-python/tests/unit/asp_python/test_policy_snapshots.py -q ``` Review the resulting `.snap` diff before keeping it. Snapshot changes are diff --git a/docs/01_core/101_harness_boundary.md b/docs/01_core/101_asp_python_boundary.md similarity index 89% rename from docs/01_core/101_harness_boundary.md rename to docs/01_core/101_asp_python_boundary.md index c0caa06..6b7208c 100644 --- a/docs/01_core/101_harness_boundary.md +++ b/docs/01_core/101_asp_python_boundary.md @@ -1,4 +1,4 @@ -# Harness Boundary +# ASP Python Boundary :PROPERTIES: :ID: 5fa0fe2dac2c4668b1ad949a8590d0098679cb1b @@ -8,7 +8,7 @@ :END: `asp-python` owns a standalone, library-first Python project -harness. It keeps parser facts and project policy in separate import packages, +ASP Python. It keeps parser facts and project policy in separate import packages, but ships them from the same repo so downstream users do not depend on the old monorepo workspace layout. @@ -22,7 +22,7 @@ This repository may: 3. evaluate deterministic rule packs over parser reports and project scope 4. render compact diagnostics for humans and repair-oriented agents 5. expose structured reports and JSON rendering for tooling -6. expose pytest-friendly assertion helpers and collectable harness callables +6. expose pytest-friendly assertion helpers and collectable ASP Python callables 7. expose a thin CLI over the default project runner This repository must not own: @@ -53,15 +53,15 @@ embedding. Rule packs should depend on parser facts rather than ad hoc source text matching when a structured fact exists. Parser-backed policy is the default architectural rule. Python semantic checks -must not re-parse source inside the harness layer; they should use +must not re-parse source inside ASP Python layer; they should use `PythonModuleReport` facts such as imports, calls, symbols, assignments, export contracts, source lines, and module shape. Standard Python project metadata -should flow through parser-owned `pyproject.toml` facts. Harness-owned policy -inputs such as tests-root policy TOML may still be read in the harness layer, +should flow through parser-owned `pyproject.toml` facts. ASP Python-owned policy +inputs such as tests-root policy TOML may still be read in ASP Python layer, but they must not infer Python semantics from raw text. `PY-TEST-R003` follows the same boundary: unit-test bloat is calculated from -parser-owned module shape and parser-classified test symbols. The harness owns +parser-owned module shape and parser-classified test symbols. ASP Python owns the pytest layout contract, while `python_lang_parser` owns Python syntax, tokenization, AST, compile validation, source-line capture, public-name policy, module public-surface classification, symbol-role classification, and @@ -76,7 +76,7 @@ resolves project-internal import edges from parser import records and import roots, so agents can see which modules depend on a subtree before editing. The tree nodes carry parser-owned export candidates and `__all__` contract kind, so the agent sees public API names without re-reading source text. The -harness turns those facts into `PY-MOD-R007` and `PY-AGENT-POLICY-007` findings; it +ASP Python turns those facts into `PY-MOD-R007` and `PY-AGENT-POLICY-007` findings; it does not re-parse Python source to infer tree shape or dependency direction. Project policy uses the same parser metadata to keep declared import names and entry point targets aligned with parser-visible project owners. @@ -97,7 +97,7 @@ project policy; they do not narrow parser coverage. `AspPythonConfig` can change source-root classification, test-root classification, extra external project paths, and test inclusion behavior. -Use `run_python_lang_harness()` or `assert_python_lang_harness_clean()` for +Use `run_asp_python_paths()` or `assert_asp_python_paths_clean()` for explicit files or directories. This runner is useful for focused parser checks and editor integrations; project-scoped rule packs only emit findings when a project scope is available. @@ -123,7 +123,7 @@ projects that prefer a one-line mount. Downstream projects can import it from The package also exposes a pytest plugin through the `pytest11` entry point. When installed as a test/dev dependency, pytest loads the plugin and accepts -`--python-project-harness`. The plugin only inserts the harness item when that +`--asp-python`. The plugin only inserts the ASP Python item when that option is enabled, so installing the package does not silently add a policy gate. diff --git a/docs/03_features/201_rule_catalog.md b/docs/03_features/201_rule_catalog.md index d75426d..d39bbc3 100644 --- a/docs/03_features/201_rule_catalog.md +++ b/docs/03_features/201_rule_catalog.md @@ -7,7 +7,7 @@ :LAST_SYNC: 2026-05-05 :END: -The harness exposes deterministic rule metadata through compact library +ASP Python exposes deterministic rule metadata through compact library functions: - `python_rule_pack_descriptors()` @@ -52,7 +52,7 @@ policy can also emit `Info` configuration work orders for Agents. resolve to parser-visible project module owners. - `PY-AGENT-PROJECT-009`: console script, GUI script, and entry point targets should resolve to parser-visible project modules. -- `PY-AGENT-PROJECT-010`: projects that declare the harness as a test/dev dependency +- `PY-AGENT-PROJECT-010`: projects that declare ASP Python as a test/dev dependency should mount a parser-visible pytest gate. - `PY-MOD-R001`: wildcard imports must become explicit imports. - `PY-MOD-R002`: library modules should not use bare `print`. @@ -64,11 +64,11 @@ policy can also emit `Info` configuration work orders for Agents. avoiding `module.py` plus `module/__init__.py` reasoning-tree shadows. - `PY-TEST-R001`: pytest modules should not be scattered in the tests root. - `PY-TEST-R002`: tests root entries should be owned suite directories or - harness configuration files. + ASP Python configuration files. - `PY-TEST-R003`: large unit-test leaves should split into folder-first suites. Project-local pytest-layout exceptions live in -`tests/python-project-harness-rules.toml`; each exception needs a non-empty +`tests/asp-python-rules.toml`; each exception needs a non-empty explanation before it suppresses `PY-TEST-*` findings. `PY-MOD-R006` is a mixed-signal modularity gate, not a line-count gate. The @@ -83,7 +83,7 @@ not fail only because they are long. Some project-policy findings are intentionally `Info`: they are configuration work orders for the repair Agent, not immediate merge blockers. -- `PY-AGENT-PROJECT-011`: projects that declare the harness as a test/dev dependency +- `PY-AGENT-PROJECT-011`: projects that declare ASP Python as a test/dev dependency and expose parser-visible verification owners should configure `[tool.asp-python.verification].profile_hints`. The finding points the Agent to `asp-python --agent-snapshot`, whose compact @@ -129,7 +129,7 @@ enough to use as the first repair prompt. ## Reasoning Tree Policy -The harness treats a Python project as an agent reasoning tree: import roots +ASP Python treats a Python project as an agent reasoning tree: import roots lead to package branches, package branches lead to modules, and modules expose the parser-owned public surface. `python_lang_parser` owns the tree facts: tree nodes, child names, public/internal surface flags, module/package owner @@ -150,10 +150,10 @@ Packages that already expose an explicit public facade are treated as having an owner map. `PY-AGENT-POLICY-009` is backed by parser-owned function control-flow facts, not by -harness string scanning. The parser records branch count, loop count, maximum +ASP Python string scanning. The parser records branch count, loop count, maximum nesting, loop nesting, terminal `else` opportunities, and repeated literal dispatch chains for each function symbol during the normal AST collection pass. -The harness turns those facts into a compact repair hint when a public function +ASP Python turns those facts into a compact repair hint when a public function hides its algorithm behind nested `if`/loop structure. The rule stays advisory by default so teams can tune or promote it after seeing their project shape. @@ -162,7 +162,7 @@ because the target reader is the repair agent, not a human style reviewer. The goal is short, explicit algorithm surfaces that an LLM can use from the reasoning tree: guard clauses instead of nested `else`, `match/case` or dispatch tables instead of literal branch ladders, and small named pipeline steps instead -of one broad loop body. Performance remains parser-first: the harness only +of one broad loop body. Performance remains parser-first: ASP Python only consumes `PythonFunctionControlFlow` facts and does not run a second AST parse. `PY-AGENT-POLICY-010` complements `PY-AGENT-POLICY-009`: the former catches long flat procedure-like public functions, while the latter catches nested control-flow @@ -170,7 +170,7 @@ shape. This keeps the advice compact and avoids telling the agent the same thing twice. `PY-AGENT-POLICY-011` is the native-Python idiom layer. It is backed by parser-owned -function facts for simple accumulator loops and predicate loops, so the harness +function facts for simple accumulator loops and predicate loops, so ASP Python can advise comprehensions, generator expressions, built-ins, or iterator pipeline helpers without parsing source in the policy layer. The rule is conservative: it targets module-level functions and public methods where a loop @@ -214,13 +214,13 @@ the file/parsed count needed for CI confidence. In project-scoped reports, compact text renders paths relative to the project root; JSON keeps the structured original paths for tooling. -`render_python_lang_harness()` includes advice by default. A report with only +`render_asp_python_report()` includes advice by default. A report with only `Info` findings is still clean, but its advice remains visible without run-summary noise. Use -`render_python_lang_harness_advice()` when a caller wants only non-blocking +`render_asp_python_report_advice()` when a caller wants only non-blocking repair hints; it returns an empty string when there is no advice to act on. -Structured consumers should use `render_python_lang_harness_json()` or the +Structured consumers should use `render_asp_python_report_json()` or the `AspPythonReport.to_dict()` shape instead of parsing compact text. ## Parser-First Policy @@ -237,7 +237,7 @@ reporting, and assertion behavior. Repository tests enforce this boundary by rejecting direct `ast` or `tokenize` usage under `src/asp_python`. File and metadata checks may still read non-Python policy inputs such as -`python-project-harness-rules.toml`; Python project metadata should flow +`asp-python-rules.toml`; Python project metadata should flow through parser-owned `pyproject.toml` facts. ## Snapshot Coverage @@ -248,7 +248,7 @@ under `tests/unit/snapshots`: - `asp_python_compact_text.snap` - `asp_python_json.snap` -Policy snapshots are generated from real harness fixtures and normalized to +Policy snapshots are generated from real ASP Python fixtures and normalized to `$TEMP` paths. Every current `PY-AGENT-*` rule has a compact advice snapshot. The blocking policy surface also has snapshots for native syntax, `PY-MOD-*`, `PY-PROJ-*`, and `PY-TEST-*` findings. This keeps rule titles, @@ -259,11 +259,11 @@ Refresh snapshots explicitly: ```shell ASP_PYTHON_UPDATE_SNAPSHOTS=1 direnv exec . uv run --group test pytest \ - tests/unit/harness/test_render_snapshots.py \ - tests/unit/harness/test_agent_policy_snapshots.py \ - tests/unit/harness/test_policy_snapshots.py -q + tests/unit/asp_python/test_render_snapshots.py \ + tests/unit/asp_python/test_agent_policy_snapshots.py \ + tests/unit/asp_python/test_policy_snapshots.py -q ``` :RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md) :END: diff --git a/docs/03_features/202_agent_pythonic_policy_research.md b/docs/03_features/202_agent_pythonic_policy_research.md index 8cd7a1d..8cb8683 100644 --- a/docs/03_features/202_agent_pythonic_policy_research.md +++ b/docs/03_features/202_agent_pythonic_policy_research.md @@ -8,7 +8,7 @@ Python idioms that keep code small enough for an LLM to edit reliably. ## Target Reader Shift -The harness target reader has changed. The primary reader is no longer a human +ASP Python target reader has changed. The primary reader is no longer a human reviewer looking for pleasant style; it is an Agent or large language model that must choose a small, correct edit surface from a whole Python project. A human can tolerate incidental ceremony and remember local context outside the @@ -32,7 +32,7 @@ edits: - verification anchors: compact snapshot sections, task contracts, receipts, waivers, and responsibility-review tasks. -The harness should therefore reject policy ideas that are merely aesthetic. A +ASP Python should therefore reject policy ideas that are merely aesthetic. A new Agent rule needs four properties: it must be backed by parser-owned facts, it must reduce the model's search or edit surface, it must render as compact actionable advice rather than redundant explanation, and it must self-apply to @@ -100,13 +100,13 @@ Modern Python also has native constructs that remove common LLM boilerplate: GitHub practice shows the adjacent tool baseline. Current mature Python repos such as `pydantic/pydantic`, `pytest-dev/pytest`, `encode/httpx`, and `psf/black` centralize project metadata in `pyproject.toml` and commonly wire -pytest, ruff, mypy, or pyright. That means this harness should not duplicate +pytest, ruff, mypy, or pyright. That means ASP Python should not duplicate style or type-check rules. Its useful scope is the parser-backed project and algorithm contract that an LLM sees before it edits code. -## Harness Thesis +## ASP Python Thesis -The harness should classify Python quality in three layers: +ASP Python should classify Python quality in three layers: 1. Tool substrate: packaging metadata, pytest gate, ruff, and type-checker configuration. Parser facts expose this layer, but normal tools enforce it. @@ -141,7 +141,7 @@ The next policy step is not another size threshold. It is native-idiom advice: when the parser sees a simple module-level function or public method manually building a list, set, or dict in a loop, manually counting/grouping into a dictionary, manually summing numeric values, or returning a boolean through a -trivial predicate loop, the harness should ask the agent to use a comprehension, +trivial predicate loop, ASP Python should ask the agent to use a comprehension, generator expression, built-in such as `sum`/`any`/`all`, `collections.Counter`, `collections.defaultdict`, or named iterator pipeline. This is advisory because explicit loops remain correct for side effects, @@ -150,7 +150,7 @@ measured. ## Candidate Matrix -| Candidate | Evidence | Harness action | +| Candidate | Evidence | ASP Python action | | --- | --- | --- | | Map/filter/list/set/dict build loops | Python Functional HOWTO on comprehensions and generator expressions | Implemented by parser fact `manual_collection_loop_count` | | Predicate search loops | Python built-ins and Functional HOWTO predicate guidance | Implemented by parser fact `manual_predicate_loop_count` | diff --git a/docs/03_features/202_runner_modes.md b/docs/03_features/202_runner_modes.md index cddfc24..a1b54be 100644 --- a/docs/03_features/202_runner_modes.md +++ b/docs/03_features/202_runner_modes.md @@ -7,13 +7,13 @@ :LAST_SYNC: 2026-04-30 :END: -The harness exposes two runner modes with shared configuration. +ASP Python exposes two runner modes with shared configuration. ## Project Runner Use `run_asp_python()` or `assert_asp_python_clean()` when a caller has a project root. The project runner scans the whole Python -project root by default, attaches `PythonProjectHarnessScope`, and runs the +project root by default, attaches `AspPythonProjectScope`, and runs the full default rule surface: 1. `python.syntax` @@ -53,7 +53,7 @@ for module-resolution policy. `source_dir_names` and `test_dir_names` classify roots for project and pytest-layout policy; they do not narrow parser coverage. `extra_path_names` can add an external project path or a single Python file outside the root. Extra paths are relative to the -project root and are part of `PythonProjectHarnessScope.monitored_paths` when +project root and are part of `AspPythonProjectScope.monitored_paths` when they exist. `include_tests=False` removes test roots from parser discovery, while keeping @@ -61,7 +61,7 @@ tests-root layout policy active. Callers can skip expensive or broken test parsing without hiding suite-shape drift. Explained local pytest-layout exceptions can be declared in -`tests/python-project-harness-rules.toml`: +`tests/asp-python-rules.toml`: ```toml [tests] @@ -114,7 +114,7 @@ opts out of project-local config loading for that call. ## Explicit-Path Runner -Use `run_python_lang_harness()` or `assert_python_lang_harness_clean()` for +Use `run_asp_python_paths()` or `assert_asp_python_paths_clean()` for explicit files or directories. Requested paths must exist. This runner does not attach a project scope, so project-resolution evaluators stay quiet. File-local rule packs can still run when they only need parser facts. @@ -134,5 +134,5 @@ project-scoped reports, rendered paths are project-relative. Use it before repair-oriented agents choose which subtree to edit. :RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md), [Rule Catalog](201_rule_catalog.md), [CLI](203_cli.md) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md), [Rule Catalog](201_rule_catalog.md), [CLI](203_cli.md) :END: diff --git a/docs/03_features/203_cli.md b/docs/03_features/203_cli.md index bb850cb..cf87bf5 100644 --- a/docs/03_features/203_cli.md +++ b/docs/03_features/203_cli.md @@ -100,5 +100,5 @@ working directory. Both functions delegate to `run_asp_python()` and the public renderers. :RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md), [Rule Catalog](201_rule_catalog.md) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md), [Rule Catalog](201_rule_catalog.md) :END: diff --git a/docs/03_features/204_pytest.md b/docs/03_features/204_pytest.md index e0ec3b5..e1de1f2 100644 --- a/docs/03_features/204_pytest.md +++ b/docs/03_features/204_pytest.md @@ -23,7 +23,7 @@ test = [ ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] ``` The distribution exposes this plugin entry point: @@ -33,14 +33,14 @@ The distribution exposes this plugin entry point: asp_python = "asp_python.pytest_plugin" ``` -Pytest auto-loads the plugin when the package is installed, but the harness is -quiet unless `--python-project-harness` is enabled. This keeps the package safe +Pytest auto-loads the plugin when the package is installed, but ASP Python is +quiet unless `--asp-python` is enabled. This keeps the package safe as a normal library dependency while making the policy gate easy to opt into from pytest config. Project policy validates this wiring. If parser-owned `pyproject.toml` facts show that a project depends on `asp-python` for test/dev use, -the project must expose either `--python-project-harness` in pytest addopts or +the project must expose either `--asp-python` in pytest addopts or an explicit `asp_python_test()` callable. This keeps the dependency from becoming decorative metadata that CI can bypass. @@ -54,24 +54,24 @@ blocking_rule_ids = ["PY-AGENT-POLICY-007"] Supported plugin options: -- `--python-project-harness`: collect and run one harness item. -- `--python-project-harness-root PATH`: choose the project root. When omitted, +- `--asp-python`: collect and run one ASP Python item. +- `--asp-python-root PATH`: choose the project root. When omitted, a single path-scoped pytest invocation uses the nearest real Python project metadata; mixed or workspace-level invocations default to pytest `rootdir`. -- `--python-project-harness-no-tests`: skip parsing test files while still +- `--asp-python-no-tests`: skip parsing test files while still evaluating tests-root layout. -- `--python-project-harness-source-dir NAME`: add one source classification +- `--asp-python-source-dir NAME`: add one source classification root name; can be repeated. -- `--python-project-harness-test-dir NAME`: add one test classification root +- `--asp-python-test-dir NAME`: add one test classification root name; can be repeated. -- `--python-project-harness-extra-path NAME`: add one external project path; +- `--asp-python-extra-path NAME`: add one external project path; can be repeated. -- `--python-project-harness-disable-rule RULE_ID`: suppress one stable rule +- `--asp-python-disable-rule RULE_ID`: suppress one stable rule id; can be repeated. -- `--python-project-harness-block-rule RULE_ID`: promote one stable rule id to +- `--asp-python-block-rule RULE_ID`: promote one stable rule id to blocking; can be repeated. -- `--python-project-harness-error-only`: fail only on parser errors. -- `--python-project-harness-no-advice`: hide non-blocking advice in assertion +- `--asp-python-error-only`: fail only on parser errors. +- `--asp-python-no-advice`: hide non-blocking advice in assertion output. ## Explicit Test Helper @@ -109,5 +109,5 @@ roots rather than narrowing parser coverage. The pytest layer does not own Python parsing, source scanning semantics, or policy-specific AST logic. :RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md), [Runner Modes](202_runner_modes.md), [CLI](203_cli.md) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md), [Runner Modes](202_runner_modes.md), [CLI](203_cli.md) :END: diff --git a/docs/03_features/205_verification.md b/docs/03_features/205_verification.md index 8bf5763..2b0086a 100644 --- a/docs/03_features/205_verification.md +++ b/docs/03_features/205_verification.md @@ -7,7 +7,7 @@ :LAST_SYNC: 2026-05-03 :END: -Python verification planning is a library-first Agent contract. The harness +Python verification planning is a library-first Agent contract. ASP Python does not run benchmark, security, stress, or chaos tools. It uses parser-owned project facts to produce external obligations that an Agent skill can satisfy with receipts or complete waivers. @@ -17,12 +17,12 @@ from asp_python import ( PythonOwnerResponsibility, PythonVerificationProfileHint, PythonVerificationTaskKind, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, render_python_verification_plan, ) -config = default_python_harness_config().with_verification_profile_hint( +config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -97,5 +97,5 @@ Agents can call `render_python_verification_skill_contracts(plan)` only when they need to expand the referenced contract. :RELATIONS: -:LINKS: [Harness Boundary](../01_core/101_harness_boundary.md), [CLI](203_cli.md) +:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md), [CLI](203_cli.md) :END: diff --git a/docs/index.md b/docs/index.md index f2db3b9..fa29b72 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -# Python Lang Project Harness: Map Of Content +# ASP Python: Map Of Content :PROPERTIES: :ID: efd68dc9011b23c38164eb503920b77bdfdd6c68 @@ -7,14 +7,14 @@ :LAST_SYNC: 2026-04-30 :END: -Documentation surface for the standalone Python language project harness. The +Documentation surface for ASP Python. The README stays compact; durable package details live here so runner modes, rule catalogs, and embedding contracts can evolve without turning the package entrypoint into a catch-all reference page. ## 01_core: Architecture And Foundation -- [Harness Boundary](01_core/101_harness_boundary.md): package ownership, +- [ASP Python Boundary](01_core/101_asp_python_boundary.md): package ownership, parser boundary, project runner, explicit-path runner, pytest embedding, and non-goals. @@ -33,7 +33,7 @@ entrypoint into a catch-all reference page. verification tasks, profile hints, receipts, waivers, and report artifacts. :RELATIONS: -:LINKS: [Harness Boundary](01_core/101_harness_boundary.md), [Rule Catalog](03_features/201_rule_catalog.md), [Runner Modes](03_features/202_runner_modes.md), [CLI](03_features/203_cli.md), [Pytest Dev Dependency](03_features/204_pytest.md), [Verification Planning](03_features/205_verification.md) +:LINKS: [ASP Python Boundary](01_core/101_asp_python_boundary.md), [Rule Catalog](03_features/201_rule_catalog.md), [Runner Modes](03_features/202_runner_modes.md), [CLI](03_features/203_cli.md), [Pytest Dev Dependency](03_features/204_pytest.md), [Verification Planning](03_features/205_verification.md) :END: --- diff --git a/provider/asp-provider-workspace-install.json b/provider/asp-provider-workspace-install.json index 508314e..c63182a 100644 --- a/provider/asp-provider-workspace-install.json +++ b/provider/asp-provider-workspace-install.json @@ -7,7 +7,7 @@ "providerId": "asp-python", "binary": "asp-python", "providerRegistration": "asp-provider-registration.json", - "schemaBundleReceipt": "../schemas/.asp-schema-manager-receipt.json", + "schemaBundleReceipt": "schemas/.asp-schema-manager-receipt.json", "workspaceArtifact": { "root": "languages/asp-python/.venv", "entrypoint": "bin/asp-python", diff --git a/pyproject.toml b/pyproject.toml index 1befc02..d1591f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,19 +51,19 @@ pythonpath = ["src"] owner_path = "pyproject.toml" responsibilities = ["pytest_gate"] verification_tasks_enabled = false -rationale = "self pytest addopts and CI cover the harness gate contract" +rationale = "self pytest addopts and CI cover the ASP Python gate contract" [[tool.asp-python.verification.profile_hints]] owner_path = "src/python_lang_parser/__init__.py" responsibilities = ["public_api"] verification_tasks_enabled = false -rationale = "self parser tests, CLI harness, and build cover the parser public facade" +rationale = "self parser tests, ASP Python CLI, and build cover the parser public facade" [[tool.asp-python.verification.profile_hints]] owner_path = "src/asp_python/__init__.py" responsibilities = ["public_api", "cli"] verification_tasks_enabled = false -rationale = "self public API tests, CLI tests, pytest gate, and build cover the harness facade" +rationale = "self public API tests, CLI tests, pytest gate, and build cover the ASP Python facade" [[tool.asp-python.verification.profile_hints]] owner_path = "src/asp_python/pytest_plugin.py" diff --git a/schemas/.asp-schema-manager-membership.json b/schemas/.asp-schema-manager-membership.json index bcef1bf..f6787f0 100644 --- a/schemas/.asp-schema-manager-membership.json +++ b/schemas/.asp-schema-manager-membership.json @@ -1,7 +1,7 @@ { "languageId": "python", "profileDigest": "blake3-256:a1c247a2b628f4e7fe173ecaa855dfc90e1416c1d168f3b9d67c907a7d12beb2", - "bundleDigest": "blake3-256:fcaf570a9c0bc9b693256307444ff8c509b547a74fc3d5ae3b29ae16be19bce6", + "bundleDigest": "blake3-256:d4e8839cbd9febb33729833f242fea1806ebca7e429f7ef95e444c8d96c65e50", "schemas": [ { "name": "asp-client-cancellation-probe-request.schema.json", @@ -55,10 +55,6 @@ "name": "asp-client-schema-bundle-response.schema.json", "digest": "blake3-256:9e42923bf92de5b10b74ec2412b1d855f46219e2153e14a4cf7560a5598675a4" }, - { - "name": "asp-client-search-request.schema.json", - "digest": "blake3-256:4572cbcd7247eac5019c98749971c5b8a0c1e742112c6fb299bf68827d32b5bc" - }, { "name": "asp-client-server-descriptor.schema.json", "digest": "blake3-256:d4e432eacba544a5eb504c1e6d1161d9599a14cfb85d62d2d5da0e1d47f6cdde" @@ -81,7 +77,7 @@ }, { "name": "asp-client-workspace-search-playbook-request.v1.schema.json", - "digest": "blake3-256:6e4c2cf3da32a9c74999a35542bea21866e2ed9ed99bb02247393e2405cb4078" + "digest": "blake3-256:1d2ffadb2d9a101ff544b3bdd356aeaee490ad847f46076c58f8fb78569e4440" }, { "name": "asp-client-workspace-source-mutation.schema.json", @@ -89,7 +85,7 @@ }, { "name": "asp-client-workspace-syntax-query-request.v1.schema.json", - "digest": "blake3-256:5d545b03a0ed1ea888bcdb0d7566b82b4df9bc15ce6d88096e2ee89454a61dda" + "digest": "blake3-256:10e706b9e1fc701ca240810f97cd4aaacf7d94e598ff9ba2668b0fc47c7ba82c" }, { "name": "asp-client-workspace-syntax-query-response.v1.schema.json", @@ -145,7 +141,7 @@ }, { "name": "large-search-playbook-performance-receipt.v1.schema.json", - "digest": "blake3-256:06a8a9c3fdcd7394b11eef3b7a00c4fd3f1d8a582a19ad6955efda59284e4db2" + "digest": "blake3-256:fc1ad6a88802f416a49e73c6b020a237f0bae61a5b3f385e82981c31956796c0" }, { "name": "lexical-postings-work-reduction-receipt.schema.json", @@ -155,9 +151,13 @@ "name": "project-resolution.schema.json", "digest": "blake3-256:bfa2f1966d9d05e0f0c05cdb635112513498dedb076edf760f0504db60a358bb" }, + { + "name": "project-topology-inference-receipt.v1.schema.json", + "digest": "blake3-256:a55c11817cde68db3a30092f06467ae49ff43508ccd155d15432df5d57cb52bb" + }, { "name": "project-topology-library.v1.schema.json", - "digest": "blake3-256:6affc5b32ccc6530126038a2925ba224979499b093f27d3c09d548081b8860c4" + "digest": "blake3-256:bb4d6d756d488b210d2ab104c60db812616bf100c150864c4531256052a4830c" }, { "name": "project-workspace-binding.v1.schema.json", @@ -181,7 +181,7 @@ }, { "name": "provider-manifest.schema.json", - "digest": "blake3-256:083ce2246c3590c626452fd06b7271031e1f989cf219af113643afd54b57ed44" + "digest": "blake3-256:5beb3ddeeac806d3dc69285b36b77b2cdf262be154d1c9f11fa8db780907c0f9" }, { "name": "provider-method-argument-projection.v1.schema.json", @@ -237,7 +237,7 @@ }, { "name": "python-generation-graph-performance-receipt.v1.schema.json", - "digest": "blake3-256:301ad2fce1050064cfd20ba63522c1261434b030fe4513e9105cf14951d7e048" + "digest": "blake3-256:a643303b31fadc141b265c8e073b24ad69c7edd95a773bd044ac4ab5c18e8135" }, { "name": "query-playbook-materialization-receipt.v1.schema.json", @@ -265,7 +265,7 @@ }, { "name": "runtime-artifact-execution-closure-member.v1.schema.json", - "digest": "blake3-256:f0f47ae132953e45ac81ef407a3279d6a5f153e5f0bd60cc042755e5df5caa4c" + "digest": "blake3-256:8d18d295e3e6651d665a01efc2e53e8b434261927579259d2faf48074f0d8077" }, { "name": "runtime-binary-bundle.v2.schema.json", @@ -283,6 +283,10 @@ "name": "runtime-provider-search-receipt.v1.schema.json", "digest": "blake3-256:2e439b2ee83198710238aed803c9c37cd2cb06bc7a5e2c010486cdaa605df691" }, + { + "name": "runtime-resident-request-plane-receipt.v1.schema.json", + "digest": "blake3-256:57b822406c38a630efba30c231d09ced69d5a0f1cda34fd688a5a042816eb8da" + }, { "name": "runtime-search-client-timing-witness.v1.schema.json", "digest": "blake3-256:076543ae9ba44348c9867a5454f043aeb36211742a6c299f57f33543cc071881" @@ -305,11 +309,7 @@ }, { "name": "search-topology-settlement.v1.schema.json", - "digest": "blake3-256:9fc0cfde08823e1c52d302d1fae437b9395e7a8fb1f33dacc6a1892edcc60104" - }, - { - "name": "semantic-assurance-case.v1.schema.json", - "digest": "blake3-256:f672ea13226296635f24c033bb6e238f6eae8ca1639d13d9f20c94fc9cb9ac3c" + "digest": "blake3-256:0417bbaeaf787da8ab63931270b8d99d3dcfc0db64418bd4ca26b4170cd867ba" }, { "name": "semantic-assurance-definitions.v1.schema.json", @@ -341,7 +341,7 @@ }, { "name": "semantic-definitions.v1.schema.json", - "digest": "blake3-256:da29ac203939d09c6b7e8e62e529ae820489817ec74e101baa8635a9eeb35df2" + "digest": "blake3-256:6567efcdc71e72f1bb66c536dc746d5240c3e4566b098f5f7c1ef8e6a01291b2" }, { "name": "semantic-dependency-topology.v1.schema.json", @@ -355,10 +355,6 @@ "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" @@ -405,7 +401,7 @@ }, { "name": "semantic-graph.v1.schema.json", - "digest": "blake3-256:0ecb8b9b830d2212dd6a6bf776f10f763e96e232cdfcbc2effc5f5189ea37a34" + "digest": "blake3-256:86bec566b06fa54de2f786aa1b21ae8216a6b46a391e5995f4b76718d9fe0522" }, { "name": "semantic-handle.v1.schema.json", @@ -417,11 +413,11 @@ }, { "name": "semantic-language-projection.v1.schema.json", - "digest": "blake3-256:70113f49e94ac82bc3d2a1dfaec34da537516cf77a680aa05f1758cfbe57734a" + "digest": "blake3-256:e1e62111d74a6fe408130e1de0d3a8c2d7a16bfff2ff7e16eb842742cb5ffac5" }, { "name": "semantic-language-registry.v1.schema.json", - "digest": "blake3-256:8892eba517e307a1dc55dc46ed9e7114eb8c4cbd681cc19761362a37b8802361" + "digest": "blake3-256:a76fc564023e63cab6b81131e96d9472788fee6c5a8f2dadd553e56df0c8be29" }, { "name": "semantic-native-syntax-fact-index.v1.schema.json", @@ -447,6 +443,10 @@ "name": "semantic-review-packet.v1.schema.json", "digest": "blake3-256:5114705b02088801a5ca0e446c749dc0b515f8990fcc3e3a2284b9a6656defbd" }, + { + "name": "semantic-search-definitions.v1.schema.json", + "digest": "blake3-256:e9766a7280a5fe3960ab4c478b20a82c1d8bb06a9ea52b0eededc69a33495159" + }, { "name": "semantic-search-packet.v1.schema.json", "digest": "blake3-256:a179736b63d468cf78e5c78f4e78b35304d94993e50402cea328be06bec8a9fb" diff --git a/schemas/.asp-schema-manager-receipt.json b/schemas/.asp-schema-manager-receipt.json index 0948869..4412b5e 100644 --- a/schemas/.asp-schema-manager-receipt.json +++ b/schemas/.asp-schema-manager-receipt.json @@ -1,5 +1,5 @@ { "schemaId": "agent.semantic-protocols.language-schema-bundle-receipt", "schemaVersion": "1", - "schemaDigest": "blake3-256:fcaf570a9c0bc9b693256307444ff8c509b547a74fc3d5ae3b29ae16be19bce6" + "schemaDigest": "blake3-256:d4e8839cbd9febb33729833f242fea1806ebca7e429f7ef95e444c8d96c65e50" } \ No newline at end of file diff --git a/schemas/asp-client-search-request.schema.json b/schemas/asp-client-search-request.schema.json deleted file mode 100644 index 391cd12..0000000 --- a/schemas/asp-client-search-request.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-search-request.schema.json", - "title": "ASP Client Search Request", - "type": "object", - "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "intent", "query", "scope", "coverage", "maxOwners", "deadlineMs", "explain"], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.asp-client-search-request" - }, - "schemaVersion": { "const": "1" }, - "intent": { - "enum": ["conceptual", "relationship", "exact-literal", "absence-proof"] - }, - "query": { "type": "string", "minLength": 1 }, - "scope": { "pattern": "^(workspace|owner:.+)$" }, - "coverage": { "enum": ["candidates", "complete"] }, - "maxOwners": { "type": "integer", "minimum": 1, "maximum": 100 }, - "deadlineMs": { "type": "integer", "minimum": 1, "maximum": 5000 }, - "explain": { "enum": ["compact", "full"] } - } -} diff --git a/schemas/asp-client-workspace-search-playbook-request.v1.schema.json b/schemas/asp-client-workspace-search-playbook-request.v1.schema.json index 473bc70..ac6a4f1 100644 --- a/schemas/asp-client-workspace-search-playbook-request.v1.schema.json +++ b/schemas/asp-client-workspace-search-playbook-request.v1.schema.json @@ -5,44 +5,39 @@ "type": "object", "additionalProperties": false, "required": ["schemaId", "schemaVersion", "clauseOrder"], + "dependentRequired": { + "rg": ["tantivy"], + "tantivy": ["rg"] + }, "properties": { "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-search-playbook-request"}, "schemaVersion": {"const": "1"}, "languages": {"$ref": "#/$defs/producerExpression"}, "documents": {"$ref": "#/$defs/producerExpression"}, - "workspace": {"$ref": "#/$defs/identity"}, - "fd": {"$ref": "#/$defs/nativeBlocks"}, + "workspace": { + "description": "Explicit registered cross-workspace identity. Filesystem paths are not workspace identities.", + "$ref": "#/$defs/registeredName" + }, "rg": {"$ref": "#/$defs/nativeBlocks"}, "tantivy": {"$ref": "#/$defs/nativeBlocks"}, - "syntax": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/producerNativeBlock"}}, - "nativeSyntax": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/exactSelector"}}, + "syntax": {"description": "Registered structural queries whose admitted matches establish selector scope inside the fused rg/Tantivy file context.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/producerNativeBlock"}}, + "nativeSyntax": {"description": "Exact selector queries whose singleton matches establish structural scope inside the fused rg/Tantivy file context.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/exactSelector"}}, "graph": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/graphNativeBlock"}}, "clauseOrder": { - "description": "Agent-authored acquisition priority followed by dependent graph barriers, preserved from CLI occurrence order.", + "description": "Exact CLI occurrence order of registered layout inputs. This preserves repeated-block identity but does not create execution edges; the admitted Search Layout owns shared-scope and serial composition.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/clauseRef"} } }, - "allOf": [ - { - "anyOf": [ - {"required": ["languages"]}, - {"required": ["documents"]} - ] - }, - { - "anyOf": [ - {"required": ["fd"]}, - {"required": ["rg"]}, - {"required": ["tantivy"]}, - {"required": ["syntax"]}, - {"required": ["nativeSyntax"]} - ] - } - ], + "allOf": [{"required": ["rg", "tantivy"]}], "$defs": { - "identity": {"type": "string", "minLength": 1, "maxLength": 512}, + "registeredName": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*$" + }, "exactSelector": { "type": "string", "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])" @@ -58,7 +53,7 @@ "additionalProperties": false, "required": ["producer", "argv"], "properties": { - "producer": {"$ref": "#/$defs/identity"}, + "producer": {"$ref": "#/$defs/registeredName"}, "argv": {"$ref": "#/$defs/nativeArgv"} } }, @@ -67,7 +62,7 @@ "additionalProperties": false, "required": ["language", "argv"], "properties": { - "language": {"$ref": "#/$defs/identity"}, + "language": {"enum": ["gql", "pgql"]}, "argv": {"$ref": "#/$defs/nativeArgv"} } }, @@ -76,7 +71,7 @@ "additionalProperties": false, "required": ["axis", "blockIndex"], "properties": { - "axis": {"enum": ["fd", "rg", "tantivy", "syntax", "native-syntax", "graph"]}, + "axis": {"enum": ["rg", "tantivy", "syntax", "native-syntax", "graph"]}, "blockIndex": {"type": "integer", "minimum": 0} } } diff --git a/schemas/asp-client-workspace-syntax-query-request.v1.schema.json b/schemas/asp-client-workspace-syntax-query-request.v1.schema.json index 47b4b73..45c7dbf 100644 --- a/schemas/asp-client-workspace-syntax-query-request.v1.schema.json +++ b/schemas/asp-client-workspace-syntax-query-request.v1.schema.json @@ -10,7 +10,11 @@ "schemaVersion": {"const": "1"}, "languages": {"type": "string", "pattern": "^[^|\\s]+(\\|[^|\\s]+)*$"}, "documents": {"type": "string", "pattern": "^[^|\\s]+(\\|[^|\\s]+)*$"}, - "workspace": {"type": "string", "minLength": 1}, + "workspace": { + "description": "Explicit registered cross-workspace identity. Filesystem paths are not workspace identities.", + "type": "string", + "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*$" + }, "syntax": { "type": "array", "minItems": 1, @@ -25,6 +29,5 @@ } }, "projection": {"const": "matches"} - }, - "anyOf": [{"required": ["languages"]}, {"required": ["documents"]}] + } } diff --git a/schemas/language-schema-profiles.json b/schemas/language-schema-profiles.json index deb96ca..e50bc20 100644 --- a/schemas/language-schema-profiles.json +++ b/schemas/language-schema-profiles.json @@ -96,12 +96,10 @@ "semantic-dev-command-log.v1.schema.json", "semantic-formal-proof-pilot.v1.schema.json", "semantic-review-packet.v1.schema.json", - "semantic-evidence-graph.v1.schema.json", "semantic-graph-resident-evaluation-request.v1.schema.json", "semantic-graph-resident-evaluation-result.v1.schema.json", "python-generation-graph-performance-receipt.v1.schema.json", "semantic-graph-turbo-request.v1.schema.json", - "semantic-assurance-case.v1.schema.json", "semantic-ast-patch.v1.schema.json", "semantic-ast-patch-receipt.v1.schema.json" ] diff --git a/schemas/large-search-playbook-performance-receipt.v1.schema.json b/schemas/large-search-playbook-performance-receipt.v1.schema.json index 24383cf..634576e 100644 --- a/schemas/large-search-playbook-performance-receipt.v1.schema.json +++ b/schemas/large-search-playbook-performance-receipt.v1.schema.json @@ -16,7 +16,7 @@ "contentGeneration": { "unevaluatedProperties": false, "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, { "type": "object", "required": ["stageP95Nanos"], @@ -45,7 +45,7 @@ "coldRgQuery": { "unevaluatedProperties": false, "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, { "type": "object", "required": [ @@ -68,7 +68,7 @@ "bootstrapAccelerator": { "unevaluatedProperties": false, "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, { "type": "object", "required": [ @@ -91,7 +91,7 @@ "deltaAccelerator": { "unevaluatedProperties": false, "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, { "type": "object", "required": [ @@ -112,7 +112,7 @@ "firstTantivyOpen": { "unevaluatedProperties": false, "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, { "type": "object", "required": ["filesystemReadCount", "fdProcessCount", "rgProcessCount", "tantivyBuildCount"], @@ -129,7 +129,7 @@ "warm": { "unevaluatedProperties": false, "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, {"properties": { "samples": {"type": "integer", "minimum": 512}, "p99Nanos": {"type": "integer", "minimum": 0, "exclusiveMaximum": 1000000}, @@ -192,17 +192,6 @@ } }, "$defs": { - "nanos": {"type": "integer", "minimum": 0}, - "distribution": { - "type": "object", - "required": ["samples", "p50Nanos", "p95Nanos", "p99Nanos", "maxNanos"], - "properties": { - "samples": {"type": "integer", "minimum": 1}, - "p50Nanos": {"$ref": "#/$defs/nanos"}, - "p95Nanos": {"$ref": "#/$defs/nanos"}, - "p99Nanos": {"$ref": "#/$defs/nanos"}, - "maxNanos": {"$ref": "#/$defs/nanos"} - } - } + "nanos": {"type": "integer", "minimum": 0} } } diff --git a/schemas/project-topology-inference-receipt.v1.schema.json b/schemas/project-topology-inference-receipt.v1.schema.json new file mode 100644 index 0000000..a982dc4 --- /dev/null +++ b/schemas/project-topology-inference-receipt.v1.schema.json @@ -0,0 +1,151 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/project-topology-inference-receipt.v1.schema.json", + "title": "Project Topology Inference Receipt V1", + "description": "Complete bounded MRR/Ascent topology closure retaining relation and rule identity for every derived result.", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "state", + "generationIdentity", + "rulePackIdentity", + "inputEdges", + "relationships", + "mrrClosureDigest", + "ascentSemanticDigest", + "mrrMaterializationDigest", + "receiptDigest", + "terminal" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.project-topology-inference-receipt" + }, + "schemaVersion": { + "const": "1" + }, + "state": { + "const": "admitted" + }, + "generationIdentity": { + "type": "string", + "minLength": 1 + }, + "rulePackIdentity": { + "const": "project-topology-reachability" + }, + "inputEdges": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "relation", + "from", + "to" + ], + "properties": { + "id": { + "type": "string", + "minLength": 1 + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + } + } + } + }, + "relationships": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "relation", + "from", + "to", + "ruleId", + "premiseEdgeIds" + ], + "properties": { + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "from": { + "type": "string", + "minLength": 1 + }, + "to": { + "type": "string", + "minLength": 1 + }, + "ruleId": { + "type": "string", + "minLength": 1 + }, + "premiseEdgeIds": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + }, + "uniqueItems": true + } + } + } + }, + "mrrClosureDigest": { + "$ref": "#/$defs/digest" + }, + "ascentSemanticDigest": { + "$ref": "#/$defs/digest" + }, + "mrrMaterializationDigest": { + "$ref": "#/$defs/digest" + }, + "receiptDigest": { + "$ref": "#/$defs/digest" + }, + "terminal": { + "type": "object", + "additionalProperties": false, + "required": [ + "state", + "terminalCount", + "reasonKind" + ], + "properties": { + "state": { + "const": "admitted" + }, + "terminalCount": { + "const": 1 + }, + "reasonKind": { + "type": "null" + } + } + } + }, + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + }, + "$comment": "V1 semantic admission additionally requires deterministic ordering, unique input edge identities, every premiseEdgeIds entry to resolve in inputEdges, and the receipt to be independently admitted before Runtime attachment." +} diff --git a/schemas/project-topology-library.v1.schema.json b/schemas/project-topology-library.v1.schema.json index 7eb96b4..d72218c 100644 --- a/schemas/project-topology-library.v1.schema.json +++ b/schemas/project-topology-library.v1.schema.json @@ -2,210 +2,931 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.agent-semantic-protocols.dev/project-topology-library.v1.schema.json", "title": "Reusable Project Topology Library V1", - "description": "Content-addressed structural and semantic project topology shared by Search, Query, code understanding, framework calibration, refactoring, and context recovery. Retrieval ranges and request-local ranks are deliberately excluded.", + "description": "Content-addressed structural and semantic project topology with source-owned expected relations, coverage, and frontiers, shared by Search, Query, code understanding, framework calibration, refactoring, and context recovery. Retrieval ranges and request-local ranks are deliberately excluded.", "type": "object", "additionalProperties": false, - "required": ["schemaId", "schemaVersion", "projectWorkspace", "sourceGenerationDigest", "providerCatalogDigest", "libraryDigest", "generation", "fromScratchRebuildReceipt", "semanticAdmissionReceipts", "identities", "segments", "nodes", "edges", "coverageCertificates", "closure", "consumers", "terminal"], + "required": [ + "schemaId", + "schemaVersion", + "projectWorkspace", + "sourceGenerationDigest", + "providerCatalogDigest", + "libraryDigest", + "generation", + "fromScratchRebuildReceipt", + "semanticAdmissionReceipts", + "identities", + "segments", + "nodes", + "edges", + "expectedRelations", + "coverageCertificates", + "frontiers", + "closure", + "consumers", + "terminal" + ], "properties": { - "schemaId": {"const": "agent.semantic-protocols.project-topology-library"}, - "schemaVersion": {"const": "1"}, - "projectWorkspace": {"$ref": "project-workspace-binding.v1.schema.json"}, - "sourceGenerationDigest": {"$ref": "#/$defs/digest"}, - "providerCatalogDigest": {"$ref": "#/$defs/digest"}, - "libraryDigest": {"$ref": "#/$defs/digest"}, - "generation": {"$ref": "#/$defs/generation"}, - "fromScratchRebuildReceipt": {"$ref": "#/$defs/fromScratchRebuildReceipt"}, + "schemaId": { + "const": "agent.semantic-protocols.project-topology-library" + }, + "schemaVersion": { + "const": "1" + }, + "projectWorkspace": { + "$ref": "project-workspace-binding.v1.schema.json" + }, + "sourceGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "providerCatalogDigest": { + "$ref": "#/$defs/digest" + }, + "libraryDigest": { + "$ref": "#/$defs/digest" + }, + "generation": { + "$ref": "#/$defs/generation" + }, + "fromScratchRebuildReceipt": { + "$ref": "#/$defs/fromScratchRebuildReceipt" + }, "semanticAdmissionReceipts": { "type": "array", - "items": {"$ref": "#/$defs/semanticAdmissionReceipt"} - }, - "identities": {"$ref": "#/$defs/identities"}, - "segments": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/segment"}}, - "nodes": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/node"}}, - "edges": {"type": "array", "items": {"$ref": "#/$defs/edge"}}, - "coverageCertificates": {"type": "array", "items": {"$ref": "#/$defs/coverage"}}, - "closure": {"$ref": "#/$defs/closure"}, + "items": { + "$ref": "#/$defs/semanticAdmissionReceipt" + } + }, + "identities": { + "$ref": "#/$defs/identities" + }, + "segments": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/segment" + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/node" + } + }, + "edges": { + "type": "array", + "items": { + "$ref": "#/$defs/edge" + } + }, + "coverageCertificates": { + "type": "array", + "items": { + "$ref": "#/$defs/coverage" + } + }, + "closure": { + "$ref": "#/$defs/closure" + }, "consumers": { "type": "array", "minItems": 1, "uniqueItems": true, - "items": {"enum": ["search", "query", "code-understanding", "framework-calibration", "refactoring", "context-recovery"]} + "items": { + "enum": [ + "search", + "query", + "code-understanding", + "framework-calibration", + "refactoring", + "context-recovery" + ] + } + }, + "terminal": { + "$ref": "#/$defs/terminal" }, - "terminal": {"$ref": "#/$defs/terminal"} + "expectedRelations": { + "type": "array", + "items": { + "$ref": "#/$defs/expectedRelation" + } + }, + "frontiers": { + "type": "array", + "items": { + "$ref": "#/$defs/frontier" + } + } }, "$defs": { - "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$(?![\\s\\S])"}, - "identifier": {"type": "string", "pattern": "^[a-z][a-z0-9_.:-]*$(?![\\s\\S])"}, - "nodeId": {"type": "string", "pattern": "^[a-z][a-z0-9_-]*$(?![\\s\\S])"}, - "selector": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])"}, - "ownerLocator": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s#]+$(?![\\s\\S])"}, + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$(?![\\s\\S])" + }, + "identifier": { + "type": "string", + "pattern": "^[a-z][a-z0-9_.:-]*$(?![\\s\\S])" + }, + "nodeId": { + "type": "string", + "pattern": "^[a-z][a-z0-9_-]*$(?![\\s\\S])" + }, + "selector": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])" + }, + "ownerLocator": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s#]+$(?![\\s\\S])" + }, "generation": { "type": "object", "additionalProperties": false, - "required": ["generationDigest", "parentGenerationDigest", "state", "changeSetDigest", "rebuiltSegmentIds", "removedNodeIds", "removedEdgeIds", "fromScratchEquivalentDigest"], + "required": [ + "generationDigest", + "parentGenerationDigest", + "state", + "changeSetDigest", + "rebuiltSegmentIds", + "removedNodeIds", + "removedEdgeIds", + "fromScratchEquivalentDigest" + ], "properties": { - "generationDigest": {"$ref": "#/$defs/digest"}, - "parentGenerationDigest": {"oneOf": [{"$ref": "#/$defs/digest"}, {"type": "null"}]}, - "state": {"const": "complete"}, - "changeSetDigest": {"$ref": "#/$defs/digest"}, - "rebuiltSegmentIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, - "removedNodeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/nodeId"}}, - "removedEdgeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, - "fromScratchEquivalentDigest": {"$ref": "#/$defs/digest"} + "generationDigest": { + "$ref": "#/$defs/digest" + }, + "parentGenerationDigest": { + "oneOf": [ + { + "$ref": "#/$defs/digest" + }, + { + "type": "null" + } + ] + }, + "state": { + "const": "complete" + }, + "changeSetDigest": { + "$ref": "#/$defs/digest" + }, + "rebuiltSegmentIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "removedNodeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/nodeId" + } + }, + "removedEdgeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "fromScratchEquivalentDigest": { + "$ref": "#/$defs/digest" + } } }, "fromScratchRebuildReceipt": { "type": "object", "additionalProperties": false, - "required": ["id", "sourceGenerationDigest", "inferenceProgramDigest", "topologyGenerationDigest", "recomputedLibraryDigest", "authority", "state"], + "required": [ + "id", + "sourceGenerationDigest", + "inferenceProgramDigest", + "topologyGenerationDigest", + "recomputedLibraryDigest", + "authority", + "state" + ], "properties": { - "id": {"$ref": "#/$defs/identifier"}, - "sourceGenerationDigest": {"$ref": "#/$defs/digest"}, - "inferenceProgramDigest": {"$ref": "#/$defs/digest"}, - "topologyGenerationDigest": {"$ref": "#/$defs/digest"}, - "recomputedLibraryDigest": {"$ref": "#/$defs/digest"}, - "authority": {"const": "project-topology-rebuild.v1"}, - "state": {"const": "admitted"} + "id": { + "$ref": "#/$defs/identifier" + }, + "sourceGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "inferenceProgramDigest": { + "$ref": "#/$defs/digest" + }, + "topologyGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "recomputedLibraryDigest": { + "$ref": "#/$defs/digest" + }, + "authority": { + "const": "project-topology-rebuild.v1" + }, + "state": { + "const": "admitted" + } } }, "semanticAdmissionReceipt": { "type": "object", "additionalProperties": false, - "required": ["id", "annotationNodeId", "semanticTopologyDigest", "authority", "state"], + "required": [ + "id", + "annotationNodeId", + "semanticTopologyDigest", + "authority", + "state" + ], "properties": { - "id": {"$ref": "#/$defs/identifier"}, - "annotationNodeId": {"$ref": "#/$defs/nodeId"}, - "semanticTopologyDigest": {"$ref": "#/$defs/digest"}, - "authority": {"enum": ["source-contract", "human-admission"]}, - "state": {"const": "admitted"} + "id": { + "$ref": "#/$defs/identifier" + }, + "annotationNodeId": { + "$ref": "#/$defs/nodeId" + }, + "semanticTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "authority": { + "enum": [ + "source-contract", + "human-admission" + ] + }, + "state": { + "const": "admitted" + } } }, "identities": { "type": "object", "additionalProperties": false, - "required": ["structuralTopologyDigest", "semanticTopologyDigest", "inferenceProgramDigest", "providerGrammarDigest", "resolverDigest", "topologySchemaDigest"], + "required": [ + "structuralTopologyDigest", + "semanticTopologyDigest", + "inferenceProgramDigest", + "providerGrammarDigest", + "resolverDigest", + "topologySchemaDigest" + ], "properties": { - "structuralTopologyDigest": {"$ref": "#/$defs/digest"}, - "semanticTopologyDigest": {"$ref": "#/$defs/digest"}, - "inferenceProgramDigest": {"$ref": "#/$defs/digest"}, - "providerGrammarDigest": {"$ref": "#/$defs/digest"}, - "resolverDigest": {"$ref": "#/$defs/digest"}, - "topologySchemaDigest": {"$ref": "#/$defs/digest"} + "structuralTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "semanticTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "inferenceProgramDigest": { + "$ref": "#/$defs/digest" + }, + "providerGrammarDigest": { + "$ref": "#/$defs/digest" + }, + "resolverDigest": { + "$ref": "#/$defs/digest" + }, + "topologySchemaDigest": { + "$ref": "#/$defs/digest" + } } }, "segment": { "type": "object", "additionalProperties": false, - "required": ["id", "ownerPath", "contentDigest", "skeletonDigest", "locatorDigest", "sccDigest", "stratum", "nodeIds", "edgeIds"], + "required": [ + "id", + "ownerPath", + "contentDigest", + "skeletonDigest", + "locatorDigest", + "sccDigest", + "stratum", + "nodeIds", + "edgeIds" + ], "properties": { - "id": {"$ref": "#/$defs/identifier"}, - "ownerPath": {"type": "string", "pattern": "^(?!/)(?!.*\\\\)[^\\s]+$(?![\\s\\S])"}, - "contentDigest": {"$ref": "#/$defs/digest"}, - "skeletonDigest": {"$ref": "#/$defs/digest"}, - "locatorDigest": {"$ref": "#/$defs/digest"}, - "sccDigest": {"$ref": "#/$defs/digest"}, - "stratum": {"type": "integer", "minimum": 0}, - "nodeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/nodeId"}}, - "edgeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}} + "id": { + "$ref": "#/$defs/identifier" + }, + "ownerPath": { + "type": "string", + "pattern": "^(?!/)(?!.*\\\\)[^\\s]+$(?![\\s\\S])" + }, + "contentDigest": { + "$ref": "#/$defs/digest" + }, + "skeletonDigest": { + "$ref": "#/$defs/digest" + }, + "locatorDigest": { + "$ref": "#/$defs/digest" + }, + "sccDigest": { + "$ref": "#/$defs/digest" + }, + "stratum": { + "type": "integer", + "minimum": 0 + }, + "nodeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/nodeId" + } + }, + "edgeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + } } }, "annotation": { "type": "object", "additionalProperties": false, - "required": ["text", "state", "premiseWitnesses", "producer", "bindingDigest"], + "required": [ + "text", + "state", + "premiseWitnesses", + "producer", + "bindingDigest" + ], "properties": { - "text": {"type": "string", "minLength": 1, "maxLength": 600}, - "state": {"enum": ["proposed", "contested", "accepted"]}, - "premiseWitnesses": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, - "producer": {"type": "string", "minLength": 1}, - "bindingDigest": {"$ref": "#/$defs/digest"}, - "admissionReceiptRef": {"type": "string", "minLength": 1} + "text": { + "type": "string", + "minLength": 1, + "maxLength": 600 + }, + "state": { + "enum": [ + "proposed", + "contested", + "accepted" + ] + }, + "premiseWitnesses": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "producer": { + "type": "string", + "minLength": 1 + }, + "bindingDigest": { + "$ref": "#/$defs/digest" + }, + "admissionReceiptRef": { + "type": "string", + "minLength": 1 + } }, - "allOf": [{"if": {"properties": {"state": {"const": "accepted"}}, "required": ["state"]}, "then": {"required": ["admissionReceiptRef"]}, "else": {"not": {"required": ["admissionReceiptRef"]}}}] + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "accepted" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "admissionReceiptRef" + ] + }, + "else": { + "not": { + "required": [ + "admissionReceiptRef" + ] + } + } + } + ] }, "node": { "type": "object", "additionalProperties": false, - "required": ["id", "segmentId", "plane", "language", "kind"], + "required": [ + "id", + "segmentId", + "plane", + "language", + "kind" + ], "properties": { - "id": {"$ref": "#/$defs/nodeId"}, - "segmentId": {"oneOf": [{"$ref": "#/$defs/identifier"}, {"type": "null"}]}, - "plane": {"enum": ["structural", "declared-semantic", "synthesized-semantic"]}, - "language": {"type": "string", "minLength": 1}, - "kind": {"type": "string", "minLength": 1}, - "name": {"type": "string", "minLength": 1}, - "selector": {"$ref": "#/$defs/selector"}, - "ownerLocator": {"$ref": "#/$defs/ownerLocator"}, - "annotation": {"$ref": "#/$defs/annotation"} + "id": { + "$ref": "#/$defs/nodeId" + }, + "segmentId": { + "oneOf": [ + { + "$ref": "#/$defs/identifier" + }, + { + "type": "null" + } + ] + }, + "plane": { + "enum": [ + "structural", + "declared-semantic", + "synthesized-semantic" + ] + }, + "language": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "selector": { + "$ref": "#/$defs/selector" + }, + "ownerLocator": { + "$ref": "#/$defs/ownerLocator" + }, + "annotation": { + "$ref": "#/$defs/annotation" + } }, "oneOf": [ - {"required": ["selector"], "not": {"anyOf": [{"required": ["ownerLocator"]}, {"required": ["annotation"]}]}}, - {"required": ["ownerLocator"], "not": {"anyOf": [{"required": ["selector"]}, {"required": ["annotation"]}]}}, - {"required": ["annotation"], "not": {"anyOf": [{"required": ["selector"]}, {"required": ["ownerLocator"]}]}} + { + "required": [ + "selector" + ], + "not": { + "anyOf": [ + { + "required": [ + "ownerLocator" + ] + }, + { + "required": [ + "annotation" + ] + } + ] + } + }, + { + "required": [ + "ownerLocator" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "annotation" + ] + } + ] + } + }, + { + "required": [ + "annotation" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "ownerLocator" + ] + } + ] + } + } ], "allOf": [ - {"if": {"properties": {"plane": {"const": "synthesized-semantic"}}, "required": ["plane"]}, "then": {"properties": {"segmentId": {"type": "null"}}}, "else": {"properties": {"segmentId": {"$ref": "#/$defs/identifier"}}}} + { + "if": { + "properties": { + "plane": { + "const": "synthesized-semantic" + } + }, + "required": [ + "plane" + ] + }, + "then": { + "properties": { + "segmentId": { + "type": "null" + } + } + }, + "else": { + "properties": { + "segmentId": { + "$ref": "#/$defs/identifier" + } + } + } + } ] }, "edge": { "type": "object", "additionalProperties": false, - "required": ["id", "segmentId", "from", "to", "relation", "modality", "bindingDigest", "witnesses"], + "required": [ + "id", + "segmentId", + "from", + "to", + "relation", + "modality", + "bindingDigest", + "witnesses" + ], "properties": { - "id": {"$ref": "#/$defs/identifier"}, - "segmentId": {"oneOf": [{"$ref": "#/$defs/identifier"}, {"type": "null"}]}, - "from": {"$ref": "#/$defs/nodeId"}, - "to": {"$ref": "#/$defs/nodeId"}, - "relation": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])"}, - "modality": {"enum": ["parser-direct", "declared", "derived", "proposed"]}, - "bindingDigest": {"$ref": "#/$defs/digest"}, - "witnesses": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, - "proofRef": {"type": "string", "minLength": 1} + "id": { + "$ref": "#/$defs/identifier" + }, + "segmentId": { + "oneOf": [ + { + "$ref": "#/$defs/identifier" + }, + { + "type": "null" + } + ] + }, + "from": { + "$ref": "#/$defs/nodeId" + }, + "to": { + "$ref": "#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "modality": { + "enum": [ + "parser-direct", + "declared", + "derived", + "proposed" + ] + }, + "bindingDigest": { + "$ref": "#/$defs/digest" + }, + "witnesses": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "proofRef": { + "type": "string", + "minLength": 1 + } }, "allOf": [ - {"if": {"properties": {"modality": {"enum": ["derived", "proposed"]}}, "required": ["modality"]}, "then": {"properties": {"segmentId": {"type": "null"}}}, "else": {"properties": {"segmentId": {"$ref": "#/$defs/identifier"}}}}, - {"if": {"properties": {"modality": {"const": "derived"}}, "required": ["modality"]}, "then": {"required": ["proofRef"]}, "else": {"not": {"required": ["proofRef"]}}} + { + "if": { + "properties": { + "modality": { + "enum": [ + "derived", + "proposed" + ] + } + }, + "required": [ + "modality" + ] + }, + "then": { + "properties": { + "segmentId": { + "type": "null" + } + } + }, + "else": { + "properties": { + "segmentId": { + "$ref": "#/$defs/identifier" + } + } + } + }, + { + "if": { + "properties": { + "modality": { + "const": "derived" + } + }, + "required": [ + "modality" + ] + }, + "then": { + "required": [ + "proofRef" + ] + }, + "else": { + "not": { + "required": [ + "proofRef" + ] + } + } + } ] }, "coverage": { "type": "object", "additionalProperties": false, - "required": ["id", "relation", "targetKind", "scope", "digest"], + "required": [ + "id", + "relation", + "targetKind", + "scope", + "digest" + ], "properties": { - "id": {"$ref": "#/$defs/identifier"}, - "relation": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])"}, - "targetKind": {"type": "string", "minLength": 1}, - "scope": {"enum": ["complete", "partial"]}, - "digest": {"$ref": "#/$defs/digest"} + "id": { + "$ref": "#/$defs/identifier" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "targetKind": { + "type": "string", + "minLength": 1 + }, + "scope": { + "enum": [ + "complete", + "partial" + ] + }, + "digest": { + "$ref": "#/$defs/digest" + } } }, "closure": { "type": "object", "additionalProperties": false, - "required": ["state", "digest", "proofDagDigest", "derivedEdgeIds", "proofDependencies"], + "required": [ + "state", + "digest", + "proofDagDigest", + "derivedEdgeIds", + "proofDependencies" + ], "properties": { - "state": {"const": "stable"}, - "digest": {"$ref": "#/$defs/digest"}, - "proofDagDigest": {"$ref": "#/$defs/digest"}, - "derivedEdgeIds": {"type": "array", "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}}, - "proofDependencies": {"type": "array", "items": {"$ref": "#/$defs/proofDependency"}} + "state": { + "const": "stable" + }, + "digest": { + "$ref": "#/$defs/digest" + }, + "proofDagDigest": { + "$ref": "#/$defs/digest" + }, + "derivedEdgeIds": { + "type": "array", + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + }, + "proofDependencies": { + "type": "array", + "items": { + "$ref": "#/$defs/proofDependency" + } + } } }, "proofDependency": { "type": "object", "additionalProperties": false, - "required": ["derivedEdgeId", "premiseEdgeIds"], + "required": [ + "derivedEdgeId", + "premiseEdgeIds" + ], "properties": { - "derivedEdgeId": {"$ref": "#/$defs/identifier"}, - "premiseEdgeIds": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/identifier"}} + "derivedEdgeId": { + "$ref": "#/$defs/identifier" + }, + "premiseEdgeIds": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "$ref": "#/$defs/identifier" + } + } } }, "terminal": { "type": "object", "additionalProperties": false, - "required": ["state", "terminalCount"], - "properties": {"state": {"enum": ["ready", "failed"]}, "terminalCount": {"const": 1}, "reasonKind": {"type": "string", "minLength": 1}}, - "allOf": [{"if": {"properties": {"state": {"const": "failed"}}, "required": ["state"]}, "then": {"required": ["reasonKind"]}, "else": {"not": {"required": ["reasonKind"]}}}] + "required": [ + "state", + "terminalCount" + ], + "properties": { + "state": { + "enum": [ + "ready", + "failed" + ] + }, + "terminalCount": { + "const": 1 + }, + "reasonKind": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "failed" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "reasonKind" + ] + }, + "else": { + "not": { + "required": [ + "reasonKind" + ] + } + } + } + ] + }, + "expectedRelation": { + "type": "object", + "additionalProperties": false, + "required": [ + "id", + "anchor", + "target", + "relation", + "targetKind", + "depth", + "coverage" + ], + "properties": { + "id": { + "$ref": "project-topology-library.v1.schema.json#/$defs/identifier" + }, + "anchor": { + "$ref": "project-topology-library.v1.schema.json#/$defs/nodeId" + }, + "target": { + "$ref": "project-topology-library.v1.schema.json#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "targetKind": { + "type": "string", + "minLength": 1 + }, + "depth": { + "type": "integer", + "minimum": 0 + }, + "coverage": { + "enum": [ + "none", + "partial", + "complete" + ] + } + } + }, + "frontier": { + "type": "object", + "additionalProperties": false, + "required": [ + "anchor", + "target", + "relation", + "targetKind", + "depth", + "state", + "reason" + ], + "properties": { + "anchor": { + "$ref": "project-topology-library.v1.schema.json#/$defs/nodeId" + }, + "target": { + "$ref": "project-topology-library.v1.schema.json#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "targetKind": { + "type": "string", + "minLength": 1 + }, + "depth": { + "type": "integer", + "minimum": 0 + }, + "state": { + "enum": [ + "unknown", + "certified-missing" + ] + }, + "reason": { + "$ref": "project-topology-library.v1.schema.json#/$defs/identifier" + }, + "coverageRef": { + "type": "string", + "minLength": 1 + } + }, + "allOf": [ + { + "if": { + "properties": { + "state": { + "const": "certified-missing" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "coverageRef" + ] + } + } + ] } - } + }, + "$comment": "Semantic admission requires frontier set equality with unresolved expectations, positive-witness suppression, and complete coverage for certified-missing." } diff --git a/schemas/provider-manifest.schema.json b/schemas/provider-manifest.schema.json index 0344933..2e8d7bf 100644 --- a/schemas/provider-manifest.schema.json +++ b/schemas/provider-manifest.schema.json @@ -124,6 +124,18 @@ "providerId": {"const": "asp-julia"} } }, + { + "properties": { + "languageId": {"const": "md"}, + "providerId": {"const": "asp-md"} + } + }, + { + "properties": { + "languageId": {"const": "org"}, + "providerId": {"const": "asp-org"} + } + }, { "properties": { "languageId": {"const": "python"}, @@ -396,7 +408,7 @@ }, "methodId": { "type": "string", - "pattern": "^(?:guide|query|(query|proof|review|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" + "pattern": "^(?:guide|query|(query|proof|review|ast-patch|agent)/[a-z][a-z0-9_-]*)$" }, "hookRouteBindings": { "type": "object", diff --git a/schemas/python-generation-graph-performance-receipt.v1.schema.json b/schemas/python-generation-graph-performance-receipt.v1.schema.json index 6b695e7..fb67399 100644 --- a/schemas/python-generation-graph-performance-receipt.v1.schema.json +++ b/schemas/python-generation-graph-performance-receipt.v1.schema.json @@ -14,21 +14,21 @@ "owners": {"type": "integer", "minimum": 4096}, "cold": { "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, {"properties": {"samples": {"type": "integer", "minimum": 8}}} ], "unevaluatedProperties": false }, "warm": { "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, {"properties": {"samples": {"type": "integer", "minimum": 128}}} ], "unevaluatedProperties": false }, "concurrent": { "allOf": [ - {"$ref": "#/$defs/distribution"}, + {"$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/performanceDistribution"}, { "required": ["queries", "wallNanos"], "properties": { @@ -42,17 +42,6 @@ "artifactDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} }, "$defs": { - "nanos": {"type": "integer", "minimum": 0}, - "distribution": { - "type": "object", - "required": ["samples", "p50Nanos", "p95Nanos", "p99Nanos", "maxNanos"], - "properties": { - "samples": {"type": "integer", "minimum": 1}, - "p50Nanos": {"$ref": "#/$defs/nanos"}, - "p95Nanos": {"$ref": "#/$defs/nanos"}, - "p99Nanos": {"$ref": "#/$defs/nanos"}, - "maxNanos": {"$ref": "#/$defs/nanos"} - } - } + "nanos": {"type": "integer", "minimum": 0} } } diff --git a/schemas/runtime-artifact-execution-closure-member.v1.schema.json b/schemas/runtime-artifact-execution-closure-member.v1.schema.json index 02795f0..1ed5bd9 100644 --- a/schemas/runtime-artifact-execution-closure-member.v1.schema.json +++ b/schemas/runtime-artifact-execution-closure-member.v1.schema.json @@ -51,6 +51,12 @@ "digest": {"$ref": "#/$defs/digest"} } }, + "namedDigestEntries": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/namedDigestEntry"} + }, "languageSchemaEntry": { "type": "object", "additionalProperties": false, @@ -103,7 +109,7 @@ "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, "schemaVersion": {"const": "1"}, "memberKind": {"const": "evaluator-policy"}, - "entries": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/namedDigestEntry"}} + "entries": {"$ref": "#/$defs/namedDigestEntries"} } } ] @@ -119,7 +125,7 @@ "schemaId": {"const": "agent.semantic-protocols.runtime-artifact-execution-closure-member"}, "schemaVersion": {"const": "1"}, "memberKind": {"const": "evaluator-abi"}, - "entries": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"$ref": "#/$defs/namedDigestEntry"}} + "entries": {"$ref": "#/$defs/namedDigestEntries"} } } ] diff --git a/schemas/runtime-resident-request-plane-receipt.v1.schema.json b/schemas/runtime-resident-request-plane-receipt.v1.schema.json new file mode 100644 index 0000000..90eec89 --- /dev/null +++ b/schemas/runtime-resident-request-plane-receipt.v1.schema.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/runtime-resident-request-plane-receipt.v1.schema.json", + "title": "Runtime Resident Request Plane Receipt v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "operation", + "requestTemperature", + "state", + "generationDigest", + "elapsedMicros", + "generationLookupCount", + "generationWaitCount", + "generationBuildCount", + "filesystemReadCount", + "databaseReadCount", + "providerProcessCount", + "parserInvocationCount", + "secondaryRuntimeRpcCount", + "socketDiscoveryCount", + "terminalWaitCount" + ], + "properties": { + "schemaId": { + "const": "agent.semantic-protocols.runtime-resident-request-plane-receipt" + }, + "schemaVersion": { "const": "1" }, + "operation": { "enum": ["search", "query"] }, + "requestTemperature": { "enum": ["cold", "warm"] }, + "state": { "enum": ["ready", "query-not-ready"] }, + "generationDigest": { + "type": ["string", "null"], + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "elapsedMicros": { + "type": "integer", + "minimum": 0, + "exclusiveMaximum": 1000 + }, + "generationLookupCount": { "const": 1 }, + "generationWaitCount": { "const": 0 }, + "generationBuildCount": { "const": 0 }, + "filesystemReadCount": { "const": 0 }, + "databaseReadCount": { "const": 0 }, + "providerProcessCount": { "const": 0 }, + "parserInvocationCount": { "const": 0 }, + "secondaryRuntimeRpcCount": { "const": 0 }, + "socketDiscoveryCount": { "const": 0 }, + "terminalWaitCount": { "const": 0 } + }, + "allOf": [ + { + "if": { + "properties": { "state": { "const": "ready" } }, + "required": ["state"] + }, + "then": { + "properties": { + "generationDigest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + } + } + }, + "else": { + "properties": { "generationDigest": { "const": null } } + } + } + ] +} diff --git a/schemas/search-topology-settlement.v1.schema.json b/schemas/search-topology-settlement.v1.schema.json index ad0237e..cc4c267 100644 --- a/schemas/search-topology-settlement.v1.schema.json +++ b/schemas/search-topology-settlement.v1.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.agent-semantic-protocols.dev/search-topology-settlement.v1.schema.json", "title": "Search topology single-GQL settlement V1", - "description": "A request-bound Search projection of the reusable Project Topology and its single Agent-facing GQL settlement. Logical inference is an internal topology-maintenance mechanism, not the architecture authority. Shape validation does not replace selector, witness, proof, coverage, or binding admission.", + "description": "A request-bound Search projection whose selector-bearing GQL nodes, direct and derived relations, and frontiers are the complete Agent-facing reasoning surface. Query selectors remain on their owning GQL nodes; there is no duplicate MaterializationSet handoff.", "type": "object", "additionalProperties": false, "required": [ @@ -17,36 +17,63 @@ "edges", "coverageCertificates", "frontiers", - "materializationSet", "rendering", "terminal" ], "properties": { - "schemaId": {"const": "agent.semantic-protocols.search-topology-settlement"}, - "schemaVersion": {"const": "1"}, - "protocolId": {"const": "agent.semantic-protocols.search-playbook"}, - "protocolVersion": {"const": "1"}, - "resultState": {"enum": ["materializable", "empty", "incomplete", "blocked"]}, - "binding": {"$ref": "#/$defs/binding"}, - "inference": {"$ref": "#/$defs/inference"}, + "schemaId": { + "const": "agent.semantic-protocols.search-topology-settlement" + }, + "schemaVersion": { + "const": "1" + }, + "protocolId": { + "const": "agent.semantic-protocols.search-playbook" + }, + "protocolVersion": { + "const": "1" + }, + "resultState": { + "enum": [ + "queryable", + "empty", + "incomplete", + "blocked" + ] + }, + "binding": { + "$ref": "#/$defs/binding" + }, + "inference": { + "$ref": "#/$defs/inference" + }, "nodes": { "type": "array", - "items": {"$ref": "#/$defs/node"} + "items": { + "$ref": "#/$defs/node" + } }, "edges": { "type": "array", - "items": {"$ref": "#/$defs/edge"} + "items": { + "$ref": "#/$defs/edge" + } }, "coverageCertificates": { "$ref": "project-topology-library.v1.schema.json#/properties/coverageCertificates" }, "frontiers": { "type": "array", - "items": {"$ref": "#/$defs/frontier"} + "items": { + "$ref": "#/$defs/frontier" + } + }, + "rendering": { + "$ref": "#/$defs/rendering" }, - "materializationSet": {"$ref": "#/$defs/materializationSet"}, - "rendering": {"$ref": "#/$defs/rendering"}, - "terminal": {"$ref": "#/$defs/terminal"} + "terminal": { + "$ref": "#/$defs/terminal" + } }, "$defs": { "digest": { @@ -86,17 +113,39 @@ "projectWorkspaceIdentity": { "$ref": "project-workspace-binding.v1.schema.json#/$defs/projectWorkspaceIdentity" }, - "sourceGenerationDigest": {"$ref": "#/$defs/digest"}, - "providerCatalogDigest": {"$ref": "#/$defs/digest"}, - "topologyLibraryDigest": {"$ref": "#/$defs/digest"}, - "topologyGenerationDigest": {"$ref": "#/$defs/digest"}, - "structuralTopologyDigest": {"$ref": "#/$defs/digest"}, - "semanticTopologyDigest": {"$ref": "#/$defs/digest"}, - "inferenceProgramDigest": {"$ref": "#/$defs/digest"}, - "topologyClosureDigest": {"$ref": "#/$defs/digest"}, - "evidenceBindingDigest": {"$ref": "#/$defs/digest"}, - "decisionCoreDigest": {"$ref": "#/$defs/digest"}, - "topologyDeltaDigest": {"$ref": "#/$defs/digest"} + "sourceGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "providerCatalogDigest": { + "$ref": "#/$defs/digest" + }, + "topologyLibraryDigest": { + "$ref": "#/$defs/digest" + }, + "topologyGenerationDigest": { + "$ref": "#/$defs/digest" + }, + "structuralTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "semanticTopologyDigest": { + "$ref": "#/$defs/digest" + }, + "inferenceProgramDigest": { + "$ref": "#/$defs/digest" + }, + "topologyClosureDigest": { + "$ref": "#/$defs/digest" + }, + "evidenceBindingDigest": { + "$ref": "#/$defs/digest" + }, + "decisionCoreDigest": { + "$ref": "#/$defs/digest" + }, + "topologyDeltaDigest": { + "$ref": "#/$defs/digest" + } } }, "inference": { @@ -120,47 +169,121 @@ "postRankingCertified" ], "properties": { - "programId": {"const": "project-topology-inference.v1"}, - "engineProfile": {"type": "string", "minLength": 1}, - "scope": {"enum": ["bounded", "complete"]}, - "state": {"enum": ["complete", "incomplete", "blocked"]}, - "terminationKind": {"enum": ["fixed-point", "budget-exhausted", "blocked"]}, - "iterationCount": {"type": "integer", "minimum": 0}, - "traversalDepth": {"type": "integer", "minimum": 0}, - "derivedRelationCount": {"type": "integer", "minimum": 0}, - "proofDagDigest": {"$ref": "#/$defs/digest"}, - "proofArtifactLocator": {"type": "string", "minLength": 1}, - "rankingReceiptDigest": {"$ref": "#/$defs/digest"}, - "candidateClosureReceiptDigest": {"$ref": "#/$defs/digest"}, - "candidateRelationSetDigest": {"$ref": "#/$defs/digest"}, - "nextRelationSetDigest": {"$ref": "#/$defs/digest"}, - "postRankingCertified": {"type": "boolean"}, - "reasonKind": {"$ref": "#/$defs/identifier"} + "programId": { + "const": "project-topology-inference.v1" + }, + "engineProfile": { + "type": "string", + "minLength": 1 + }, + "scope": { + "enum": [ + "bounded", + "complete" + ] + }, + "state": { + "enum": [ + "complete", + "incomplete", + "blocked" + ] + }, + "terminationKind": { + "enum": [ + "fixed-point", + "budget-exhausted", + "blocked" + ] + }, + "iterationCount": { + "type": "integer", + "minimum": 0 + }, + "traversalDepth": { + "type": "integer", + "minimum": 0 + }, + "derivedRelationCount": { + "type": "integer", + "minimum": 0 + }, + "proofDagDigest": { + "$ref": "#/$defs/digest" + }, + "proofArtifactLocator": { + "type": "string", + "minLength": 1 + }, + "rankingReceiptDigest": { + "$ref": "#/$defs/digest" + }, + "candidateClosureReceiptDigest": { + "$ref": "#/$defs/digest" + }, + "candidateRelationSetDigest": { + "$ref": "#/$defs/digest" + }, + "nextRelationSetDigest": { + "$ref": "#/$defs/digest" + }, + "postRankingCertified": { + "type": "boolean" + }, + "reasonKind": { + "$ref": "#/$defs/identifier" + } }, "oneOf": [ { "properties": { - "state": {"const": "complete"}, - "terminationKind": {"const": "fixed-point"}, - "postRankingCertified": {"const": true} + "state": { + "const": "complete" + }, + "terminationKind": { + "const": "fixed-point" + }, + "postRankingCertified": { + "const": true + } }, - "not": {"required": ["reasonKind"]} + "not": { + "required": [ + "reasonKind" + ] + } }, { "properties": { - "state": {"const": "incomplete"}, - "terminationKind": {"const": "budget-exhausted"}, - "postRankingCertified": {"const": false} + "state": { + "const": "incomplete" + }, + "terminationKind": { + "const": "budget-exhausted" + }, + "postRankingCertified": { + "const": false + } }, - "required": ["reasonKind"] + "required": [ + "reasonKind" + ] }, { "properties": { - "state": {"const": "blocked"}, - "terminationKind": {"const": "blocked"}, - "postRankingCertified": {"const": false} + "state": { + "const": "blocked" + }, + "terminationKind": { + "const": "blocked" + }, + "postRankingCertified": { + "const": false + } }, - "required": ["reasonKind"] + "required": [ + "reasonKind" + ] } ] }, @@ -168,43 +291,59 @@ "type": "object", "additionalProperties": false, "properties": { - "fd": {"type": "boolean"}, - "rg": {"$ref": "#/$defs/lineRanges"}, + "rg": { + "$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/lineRanges" + }, "tantivy": { "type": "array", "uniqueItems": true, - "items": {"type": "string", "minLength": 1} + "items": { + "type": "string", + "minLength": 1 + } }, - "native": {"type": "boolean"} + "native": { + "type": "boolean" + } }, "minProperties": 1 }, - "lineRange": { - "type": "array", - "minItems": 2, - "maxItems": 2, - "prefixItems": [ - {"type": "integer", "minimum": 1}, - {"type": "integer", "minimum": 1} - ], - "items": false - }, - "lineRanges": { - "type": "array", - "minItems": 1, - "items": {"$ref": "#/$defs/lineRange"} - }, "projection": { "type": "object", "additionalProperties": false, - "required": ["rank", "depth"], + "required": [ + "rank", + "depth" + ], "properties": { - "rank": {"type": "integer", "minimum": 1}, - "depth": {"type": "integer", "minimum": 0}, - "hit": {"$ref": "#/$defs/hitProjection"}, - "jq": {"type": "string", "minLength": 1} + "rank": { + "type": "integer", + "minimum": 1 + }, + "depth": { + "type": "integer", + "minimum": 0 + }, + "hit": { + "$ref": "#/$defs/hitProjection" + }, + "jq": { + "type": "string", + "minLength": 1 + } }, - "anyOf": [{"required": ["hit"]}, {"required": ["jq"]}] + "anyOf": [ + { + "required": [ + "hit" + ] + }, + { + "required": [ + "jq" + ] + } + ] }, "semanticAnnotation": { "$ref": "project-topology-library.v1.schema.json#/$defs/annotation" @@ -212,59 +351,279 @@ "sourceExcerpt": { "type": "object", "additionalProperties": false, - "required": ["path", "match", "read", "witness"], + "required": [ + "path", + "match", + "read", + "witness" + ], "properties": { - "path": {"type": "string", "pattern": "^(?!/)(?!.*\\\\)[^\\s]+$(?![\\s\\S])"}, - "match": {"$ref": "#/$defs/lineRanges"}, - "read": {"$ref": "#/$defs/lineRanges"}, - "witness": {"$ref": "#/$defs/identifier"} + "path": { + "type": "string", + "pattern": "^(?!/)(?!.*\\\\)[^\\s]+$(?![\\s\\S])" + }, + "match": { + "$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/lineRanges" + }, + "read": { + "$ref": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json#/$defs/lineRanges" + }, + "witness": { + "$ref": "#/$defs/identifier" + } } }, "node": { "type": "object", "additionalProperties": false, - "required": ["id", "language", "kind"], + "required": [ + "id", + "language", + "kind" + ], "properties": { - "id": {"$ref": "#/$defs/nodeId"}, - "language": {"type": "string", "minLength": 1}, - "kind": {"type": "string", "minLength": 1}, - "name": {"type": "string", "minLength": 1}, - "selector": {"$ref": "#/$defs/selector"}, - "projection": {"$ref": "#/$defs/projection"}, - "annotation": {"$ref": "#/$defs/semanticAnnotation"}, - "excerpt": {"$ref": "#/$defs/sourceExcerpt"} + "id": { + "$ref": "#/$defs/nodeId" + }, + "language": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "minLength": 1 + }, + "name": { + "type": "string", + "minLength": 1 + }, + "selector": { + "$ref": "#/$defs/selector" + }, + "ownerLocator": { + "$ref": "project-topology-library.v1.schema.json#/$defs/ownerLocator" + }, + "projection": { + "$ref": "#/$defs/projection" + }, + "annotation": { + "$ref": "#/$defs/semanticAnnotation" + }, + "excerpt": { + "$ref": "#/$defs/sourceExcerpt" + } }, "oneOf": [ - {"required": ["selector"], "not": {"anyOf": [{"required": ["annotation"]}, {"required": ["excerpt"]}]}}, - {"required": ["annotation"], "not": {"anyOf": [{"required": ["selector"]}, {"required": ["excerpt"]}, {"required": ["projection"]}]}}, - {"required": ["excerpt"], "not": {"anyOf": [{"required": ["selector"]}, {"required": ["annotation"]}, {"required": ["projection"]}]}} + { + "required": [ + "selector" + ], + "not": { + "anyOf": [ + { + "required": [ + "ownerLocator" + ] + }, + { + "required": [ + "annotation" + ] + }, + { + "required": [ + "excerpt" + ] + } + ] + } + }, + { + "required": [ + "ownerLocator" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "annotation" + ] + }, + { + "required": [ + "excerpt" + ] + }, + { + "required": [ + "projection" + ] + } + ] + } + }, + { + "required": [ + "annotation" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "ownerLocator" + ] + }, + { + "required": [ + "excerpt" + ] + }, + { + "required": [ + "projection" + ] + } + ] + } + }, + { + "required": [ + "excerpt" + ], + "not": { + "anyOf": [ + { + "required": [ + "selector" + ] + }, + { + "required": [ + "ownerLocator" + ] + }, + { + "required": [ + "annotation" + ] + }, + { + "required": [ + "projection" + ] + } + ] + } + } ] }, "edge": { "type": "object", "additionalProperties": false, - "required": ["from", "to", "relation", "modality", "producerAuthority", "evidenceAuthority", "witnesses"], + "required": [ + "from", + "to", + "relation", + "modality", + "producerAuthority", + "evidenceAuthority", + "witnesses" + ], "properties": { - "from": {"$ref": "#/$defs/nodeId"}, - "to": {"$ref": "#/$defs/nodeId"}, - "relation": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])"}, - "modality": {"enum": ["parser-direct", "declared", "derived", "proposed"]}, - "producerAuthority": {"enum": ["provider-parser", "source-contract", "project-topology-inference.v1", "model-proposal"]}, - "evidenceAuthority": {"enum": ["provider-witness", "contract-witness", "proof-dag", "model-premises"]}, + "from": { + "$ref": "#/$defs/nodeId" + }, + "to": { + "$ref": "#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "modality": { + "enum": [ + "parser-direct", + "declared", + "derived", + "proposed" + ] + }, + "producerAuthority": { + "enum": [ + "provider-parser", + "source-contract", + "project-topology-inference.v1", + "model-proposal" + ] + }, + "evidenceAuthority": { + "enum": [ + "provider-witness", + "contract-witness", + "proof-dag", + "model-premises" + ] + }, "witnesses": { "type": "array", "minItems": 1, "uniqueItems": true, - "items": {"$ref": "#/$defs/identifier"} + "items": { + "$ref": "#/$defs/identifier" + } + }, + "derivedBy": { + "const": "project-topology-inference.v1" }, - "derivedBy": {"const": "project-topology-inference.v1"}, - "proofRef": {"type": "string", "minLength": 1} + "proofRef": { + "type": "string", + "minLength": 1 + } }, "allOf": [ { - "if": {"properties": {"modality": {"const": "derived"}}, "required": ["modality"]}, - "then": {"required": ["derivedBy", "proofRef"]}, - "else": {"not": {"anyOf": [{"required": ["derivedBy"]}, {"required": ["proofRef"]}]}} + "if": { + "properties": { + "modality": { + "const": "derived" + } + }, + "required": [ + "modality" + ] + }, + "then": { + "required": [ + "derivedBy", + "proofRef" + ] + }, + "else": { + "not": { + "anyOf": [ + { + "required": [ + "derivedBy" + ] + }, + { + "required": [ + "proofRef" + ] + } + ] + } + } } ] }, @@ -274,54 +633,64 @@ "frontier": { "type": "object", "additionalProperties": false, - "required": ["anchor", "relation", "targetKind", "depth", "state", "reason"], - "properties": { - "anchor": {"$ref": "#/$defs/nodeId"}, - "relation": {"type": "string", "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])"}, - "targetKind": {"type": "string", "minLength": 1}, - "depth": {"type": "integer", "minimum": 0}, - "state": {"enum": ["unknown", "certified-missing"]}, - "reason": {"$ref": "#/$defs/identifier"}, - "coverageRef": {"type": "string", "minLength": 1} - }, - "allOf": [ - { - "if": {"properties": {"state": {"const": "certified-missing"}}, "required": ["state"]}, - "then": {"required": ["coverageRef"]} - } - ] - }, - "materializationSet": { - "type": "object", - "additionalProperties": false, - "required": ["state", "requestId", "digest", "selectors", "proofDependencies"], + "required": [ + "anchor", + "target", + "relation", + "targetKind", + "depth", + "state", + "reason" + ], "properties": { - "state": {"enum": ["available", "empty"]}, - "requestId": {"type": "string", "minLength": 1}, - "digest": {"$ref": "#/$defs/digest"}, - "selectors": { - "type": "array", - "uniqueItems": true, - "items": {"$ref": "#/$defs/selector"} + "anchor": { + "$ref": "search-topology-settlement.v1.schema.json#/$defs/nodeId" }, - "proofDependencies": { - "type": "array", - "uniqueItems": true, - "items": {"type": "string", "minLength": 1} + "target": { + "$ref": "search-topology-settlement.v1.schema.json#/$defs/nodeId" + }, + "relation": { + "type": "string", + "pattern": "^[A-Z][A-Z0-9_]*$(?![\\s\\S])" + }, + "targetKind": { + "type": "string", + "minLength": 1 + }, + "depth": { + "type": "integer", + "minimum": 0 + }, + "state": { + "enum": [ + "unknown", + "certified-missing" + ] + }, + "reason": { + "$ref": "search-topology-settlement.v1.schema.json#/$defs/identifier" + }, + "coverageRef": { + "type": "string", + "minLength": 1 } }, - "oneOf": [ - { - "properties": { - "state": {"const": "available"}, - "selectors": {"minItems": 1} - } - }, + "allOf": [ { - "properties": { - "state": {"const": "empty"}, - "selectors": {"maxItems": 0}, - "proofDependencies": {"maxItems": 0} + "if": { + "properties": { + "state": { + "const": "certified-missing" + } + }, + "required": [ + "state" + ] + }, + "then": { + "required": [ + "coverageRef" + ] } } ] @@ -329,30 +698,70 @@ "rendering": { "type": "object", "additionalProperties": false, - "required": ["format", "gqlBlockCount", "ascentSourceExposed"], + "required": [ + "format", + "gqlBlockCount", + "ascentSourceExposed" + ], "properties": { - "format": {"const": "org-gql"}, - "gqlBlockCount": {"const": 1}, - "ascentSourceExposed": {"const": false} + "format": { + "const": "org-gql" + }, + "gqlBlockCount": { + "const": 1 + }, + "ascentSourceExposed": { + "const": false + } } }, "terminal": { "type": "object", "additionalProperties": false, - "required": ["state", "terminalCount"], + "required": [ + "state", + "terminalCount" + ], "properties": { - "state": {"enum": ["ready", "incomplete", "failed"]}, - "terminalCount": {"const": 1}, - "reasonKind": {"$ref": "#/$defs/identifier"} + "state": { + "enum": [ + "ready", + "incomplete", + "failed" + ] + }, + "terminalCount": { + "const": 1 + }, + "reasonKind": { + "$ref": "#/$defs/identifier" + } }, "oneOf": [ { - "properties": {"state": {"const": "ready"}}, - "not": {"required": ["reasonKind"]} + "properties": { + "state": { + "const": "ready" + } + }, + "not": { + "required": [ + "reasonKind" + ] + } }, { - "properties": {"state": {"enum": ["incomplete", "failed"]}}, - "required": ["reasonKind"] + "properties": { + "state": { + "enum": [ + "incomplete", + "failed" + ] + } + }, + "required": [ + "reasonKind" + ] } ] } diff --git a/schemas/semantic-assurance-case.v1.schema.json b/schemas/semantic-assurance-case.v1.schema.json deleted file mode 100644 index 114bc4a..0000000 --- a/schemas/semantic-assurance-case.v1.schema.json +++ /dev/null @@ -1,295 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-assurance-case.v1.schema.json", - "title": "Semantic Assurance Case", - "description": "Reviewer-first assurance-case artifact derived from a semantic evidence graph.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "protocolId", - "protocolVersion", - "caseSetId", - "producer", - "project", - "summary", - "cases" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-assurance-case" - }, - "schemaVersion": { - "const": "1" - }, - "protocolId": { - "const": "agent.semantic-protocols.assurance-case" - }, - "protocolVersion": { - "const": "1" - }, - "caseSetId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "producer": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" - }, - "project": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/project" - }, - "summary": { - "$ref": "#/$defs/summary" - }, - "cases": { - "type": "array", - "items": { - "$ref": "#/$defs/case" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - }, - "$defs": { - "scalar": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" }, - { - "type": "array", - "items": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" } - ] - } - } - ] - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - }, - "projectPath": { - "type": "string", - "minLength": 1, - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" - }, - "summary": { - "type": "object", - "additionalProperties": false, - "required": ["cases", "claims", "supportedClaims", "openGaps", "staleItems"], - "properties": { - "cases": { - "type": "integer", - "minimum": 0 - }, - "claims": { - "type": "integer", - "minimum": 0 - }, - "supportedClaims": { - "type": "integer", - "minimum": 0 - }, - "openGaps": { - "type": "integer", - "minimum": 0 - }, - "staleItems": { - "type": "integer", - "minimum": 0 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "caseStatus": { - "enum": ["supported", "needs-review", "blocked", "unknown"] - }, - "claimKind": { - "enum": [ - "invariant", - "proof", - "review", - "owner", - "behavior", - "determinism", - "custom" - ] - }, - "claim": { - "type": "object", - "additionalProperties": false, - "required": ["claimId", "kind", "statement"], - "properties": { - "claimId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "kind": { - "$ref": "#/$defs/claimKind" - }, - "statement": { - "type": "string", - "minLength": 1 - }, - "targetNodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "severity": { - "enum": ["info", "warning", "error"] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "nodeRefList": { - "type": "array", - "items": { - "$ref": "#/$defs/nodeRef" - } - }, - "nodeRef": { - "type": "object", - "additionalProperties": false, - "required": ["nodeId", "kind", "label"], - "properties": { - "nodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "kind": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeKind" - }, - "label": { - "type": "string", - "minLength": 1 - }, - "status": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeStatus" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "actionRef": { - "type": "object", - "additionalProperties": false, - "required": ["nodeId", "summary"], - "properties": { - "nodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "actionId": { - "type": "string", - "minLength": 1 - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "priority": { - "enum": ["p0", "p1", "p2", "p3"] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "gap": { - "type": "object", - "additionalProperties": false, - "required": ["gapId", "summary"], - "properties": { - "gapId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "sourceGapId": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "severity": { - "enum": ["info", "warning", "error"] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "case": { - "type": "object", - "additionalProperties": false, - "required": ["caseId", "claim", "status"], - "properties": { - "caseId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "claim": { - "$ref": "#/$defs/claim" - }, - "status": { - "$ref": "#/$defs/caseStatus" - }, - "subjectNodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "supportedBy": { - "$ref": "#/$defs/nodeRefList" - }, - "observedBy": { - "$ref": "#/$defs/nodeRefList" - }, - "reviewedBy": { - "$ref": "#/$defs/nodeRefList" - }, - "waivedBy": { - "$ref": "#/$defs/nodeRefList" - }, - "actions": { - "type": "array", - "items": { - "$ref": "#/$defs/actionRef" - } - }, - "gaps": { - "type": "array", - "items": { - "$ref": "#/$defs/gap" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - } - } -} diff --git a/schemas/semantic-definitions.v1.schema.json b/schemas/semantic-definitions.v1.schema.json index 9696a69..3cd444e 100644 --- a/schemas/semantic-definitions.v1.schema.json +++ b/schemas/semantic-definitions.v1.schema.json @@ -6,7 +6,9 @@ "$defs": { "projectPath": { "type": "string", - "minLength": 1 + "description": "Canonical project-root-relative path. This is a path value, not a display locator: no rank prefixes, URI schemes, command prefixes, line ranges, columns, absolute paths, parent segments, backslashes, or numeric node slots.", + "minLength": 1, + "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" }, "lineRange": { "type": "string", diff --git a/schemas/semantic-evidence-graph.v1.schema.json b/schemas/semantic-evidence-graph.v1.schema.json deleted file mode 100644 index 3220059..0000000 --- a/schemas/semantic-evidence-graph.v1.schema.json +++ /dev/null @@ -1,263 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-evidence-graph.v1.schema.json", - "title": "Semantic Evidence Graph", - "description": "Portable graph artifact that links review packets, invariants, receipts, behavior snapshots, determinism readiness, proof pilots, waivers, and reviewer actions.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "protocolId", - "protocolVersion", - "graphId", - "producer", - "project", - "summary", - "nodes", - "edges" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-evidence-graph" - }, - "schemaVersion": { - "const": "1" - }, - "protocolId": { - "const": "agent.semantic-protocols.evidence-graph" - }, - "protocolVersion": { - "const": "1" - }, - "graphId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "producer": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/producer" - }, - "project": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/project" - }, - "summary": { - "$ref": "#/$defs/summary" - }, - "nodes": { - "type": "array", - "items": { - "$ref": "#/$defs/node" - } - }, - "edges": { - "type": "array", - "items": { - "$ref": "#/$defs/edge" - } - }, - "gaps": { - "type": "array", - "items": { - "$ref": "#/$defs/gap" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - }, - "$defs": { - "scalar": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" }, - { - "type": "array", - "items": { - "oneOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" } - ] - } - } - ] - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - }, - "projectPath": { - "type": "string", - "minLength": 1, - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" - }, - "summary": { - "type": "object", - "additionalProperties": false, - "required": ["nodes", "edges", "owners", "claims", "staleItems", "gaps"], - "properties": { - "nodes": { - "type": "integer", - "minimum": 0 - }, - "edges": { - "type": "integer", - "minimum": 0 - }, - "owners": { - "type": "integer", - "minimum": 0 - }, - "claims": { - "type": "integer", - "minimum": 0 - }, - "staleItems": { - "type": "integer", - "minimum": 0 - }, - "gaps": { - "type": "integer", - "minimum": 0 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "node": { - "type": "object", - "additionalProperties": false, - "required": ["nodeId", "kind", "label"], - "properties": { - "nodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "kind": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeKind" - }, - "label": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "candidateId": { - "type": "string", - "minLength": 1 - }, - "receiptId": { - "type": "string", - "minLength": 1 - }, - "snapshotId": { - "type": "string", - "minLength": 1 - }, - "readinessId": { - "type": "string", - "minLength": 1 - }, - "proofId": { - "type": "string", - "minLength": 1 - }, - "packetId": { - "type": "string", - "minLength": 1 - }, - "waiverId": { - "type": "string", - "minLength": 1 - }, - "actionId": { - "type": "string", - "minLength": 1 - }, - "status": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/nodeStatus" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "location": { - "$ref": "semantic-assurance-definitions.v1.schema.json#/$defs/location" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "edgeKind": { - "enum": [ - "derived-from", - "requires-evidence", - "verified-by", - "observed-by", - "waived-by", - "reviewed-by", - "suggests-action", - "supports-claim" - ] - }, - "edge": { - "type": "object", - "additionalProperties": false, - "required": ["edgeId", "kind", "fromNodeId", "toNodeId"], - "properties": { - "edgeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "kind": { - "$ref": "#/$defs/edgeKind" - }, - "fromNodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "toNodeId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "label": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "gap": { - "type": "object", - "additionalProperties": false, - "required": ["gapId", "summary"], - "properties": { - "gapId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "severity": { - "enum": ["info", "warning", "error"] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - } - } -} diff --git a/schemas/semantic-graph.v1.schema.json b/schemas/semantic-graph.v1.schema.json index 508ef0b..e5e1858 100644 --- a/schemas/semantic-graph.v1.schema.json +++ b/schemas/semantic-graph.v1.schema.json @@ -314,10 +314,7 @@ } }, "seeds": { - "type": "array", - "items": { - "$ref": "#/$defs/nextAction" - } + "$ref": "#/$defs/nextActionList" }, "fields": { "$ref": "#/$defs/fields" @@ -368,10 +365,16 @@ } } }, + "nextActionList": { + "type": "array", + "items": { + "$ref": "#/$defs/nextAction" + } + }, "nextAction": { "type": "object", "additionalProperties": false, - "required": ["kind", "target"], + "required": ["kind", "target", "command"], "properties": { "kind": { "type": "string", @@ -388,6 +391,25 @@ "type": "string", "minLength": 1 }, + "command": { + "type": "object", + "additionalProperties": false, + "required": ["executable", "argv"], + "properties": { + "executable": { + "type": "string", + "minLength": 1 + }, + "argv": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + } + } + }, "fields": { "$ref": "#/$defs/fields" } diff --git a/schemas/semantic-language-projection.v1.schema.json b/schemas/semantic-language-projection.v1.schema.json index f78cd80..70671eb 100644 --- a/schemas/semantic-language-projection.v1.schema.json +++ b/schemas/semantic-language-projection.v1.schema.json @@ -2,7 +2,7 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://agent-semantic-protocols.local/schemas/semantic-language-projection.v1.schema.json", "title": "Semantic Language Projection", - "description": "Query-free, parser-owned projection artifact published by a language harness for ASP lifecycle import.", + "description": "Query-free, parser-owned projection artifact published by an ASP language provider for ASP lifecycle import.", "type": "object", "additionalProperties": false, "required": [ diff --git a/schemas/semantic-language-registry.v1.schema.json b/schemas/semantic-language-registry.v1.schema.json index b702186..2020c0c 100644 --- a/schemas/semantic-language-registry.v1.schema.json +++ b/schemas/semantic-language-registry.v1.schema.json @@ -44,7 +44,7 @@ }, "method": { "type": "string", - "pattern": "^(?:guide|query|(query|proof|review|evidence|ast-patch|agent)/[a-z][a-z0-9_-]*)$" + "pattern": "^(?:guide|query|(query|proof|review|ast-patch|agent)/[a-z][a-z0-9_-]*)$" }, "command": { "enum": [ @@ -52,7 +52,6 @@ "query", "proof", "review", - "evidence", "ast-patch", "agent" ] diff --git a/schemas/semantic-search-definitions.v1.schema.json b/schemas/semantic-search-definitions.v1.schema.json new file mode 100644 index 0000000..ebb2af6 --- /dev/null +++ b/schemas/semantic-search-definitions.v1.schema.json @@ -0,0 +1,101 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.agent-semantic-protocols.dev/semantic-search-definitions.v1.schema.json", + "title": "Semantic Search Family Definitions V1", + "description": "Shared definitions owned by the semantic-search schema family.", + "$defs": { + "digest": { + "type": "string", + "pattern": "^blake3-256:[0-9a-f]{64}$" + }, + "generationIdentity": { + "type": "object", + "additionalProperties": false, + "required": [ + "projectId", + "workspaceId", + "sourceRootDigest", + "providerDigest", + "schemaDigest", + "generationCandidateDigest" + ], + "properties": { + "projectId": { + "type": "string", + "minLength": 1 + }, + "workspaceId": { + "type": "string", + "minLength": 1 + }, + "sourceRootDigest": { + "$ref": "#/$defs/digest" + }, + "providerDigest": { + "$ref": "#/$defs/digest" + }, + "schemaDigest": { + "$ref": "#/$defs/digest" + }, + "generationCandidateDigest": { + "$ref": "#/$defs/digest" + } + } + }, + "nanos": { + "type": "integer", + "minimum": 0 + }, + "performanceDistribution": { + "type": "object", + "required": [ + "samples", + "p50Nanos", + "p95Nanos", + "p99Nanos", + "maxNanos" + ], + "properties": { + "samples": { + "type": "integer", + "minimum": 1 + }, + "p50Nanos": { + "$ref": "#/$defs/nanos" + }, + "p95Nanos": { + "$ref": "#/$defs/nanos" + }, + "p99Nanos": { + "$ref": "#/$defs/nanos" + }, + "maxNanos": { + "$ref": "#/$defs/nanos" + } + } + }, + "lineRange": { + "type": "array", + "minItems": 2, + "maxItems": 2, + "prefixItems": [ + { + "type": "integer", + "minimum": 1 + }, + { + "type": "integer", + "minimum": 1 + } + ], + "items": false + }, + "lineRanges": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/lineRange" + } + } + } +} diff --git a/src/asp_python/__init__.py b/src/asp_python/__init__.py index 32c9587..a7dc2fe 100644 --- a/src/asp_python/__init__.py +++ b/src/asp_python/__init__.py @@ -1,4 +1,4 @@ -"""Project-level Python language harness helpers.""" +"""Project-level ASP Python helpers.""" from __future__ import annotations @@ -8,11 +8,11 @@ DISTRIBUTION_NAME = "asp-python" _CLI_EXPORTS = frozenset({"run_cli", "run_cli_from_env"}) -_HARNESS_RULES_EXPORTS = frozenset( +_ASP_RULES_EXPORTS = frozenset( { - "python_harness_rules_markdown", - "render_python_harness_rules_markdown", - "write_python_harness_rules_to_unit_tests", + "asp_python_rules_markdown", + "render_asp_python_rules_markdown", + "write_asp_python_rules_to_unit_tests", } ) @@ -88,7 +88,7 @@ "AspPythonReport", "AspPythonRule", "PythonImport", - "PythonLangRulePack", + "AspPythonRulePack", "PythonModernDesignRulePack", "PythonModularityRulePack", "PythonModuleReport", @@ -97,7 +97,7 @@ "PythonOwnerResponsibility", "PythonProjectDependency", "PythonProjectEntryPoint", - "PythonProjectHarnessScope", + "AspPythonProjectScope", "PythonProjectImportName", "PythonProjectMetadata", "PythonProjectPolicyRulePack", @@ -141,10 +141,10 @@ "PythonVerificationWaiver", "SourceLocation", "__version__", - "assert_python_lang_harness_clean", + "assert_asp_python_paths_clean", "assert_asp_python_clean", - "default_python_harness_config", - "default_python_lang_rule_packs", + "default_asp_python_config", + "default_asp_python_rule_packs", "discover_python_files", "build_python_verification_performance_index", "build_python_verification_profile_index", @@ -174,7 +174,7 @@ "python_symbol_is_test_function", "python_symbol_is_top_level_callable", "python_agent_policy_rules", - "python_harness_rules_markdown", + "asp_python_rules_markdown", "python_modern_design_rules", "python_modularity_rules", "asp_python_paths", @@ -186,10 +186,10 @@ "python_syntax_rules", "python_test_layout_rules", "read_asp_python_config", - "render_python_lang_harness", - "render_python_lang_harness_advice", - "render_python_lang_harness_json", - "render_python_harness_rules_markdown", + "render_asp_python_report", + "render_asp_python_report_advice", + "render_asp_python_report_json", + "render_asp_python_rules_markdown", "render_asp_python_agent_snapshot", "render_asp_python_agent_snapshot_with_config", "render_python_reasoning_tree", @@ -204,10 +204,10 @@ "render_python_verification_task_index_json", "run_cli", "run_cli_from_env", - "run_python_lang_harness", + "run_asp_python_paths", "run_asp_python", "semantic_language_registry_document", - "write_python_harness_rules_to_unit_tests", + "write_asp_python_rules_to_unit_tests", "write_python_verification_reports", ] @@ -219,12 +219,12 @@ def __getattr__(name: str) -> Any: return _load_export("._version", name) if name in _CLI_EXPORTS: return _load_export("._cli", name) - if name in _HARNESS_RULES_EXPORTS: - return _load_export("._harness_rules", name) + if name in _ASP_RULES_EXPORTS: + return _load_export("._asp_rules", name) if name in _PARSER_EXPORTS: return _load_export("python_lang_parser", name) if name in __all__: - return _load_export(".harness", name) + return _load_export(".api", name) raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/asp_python/_agent_namespace.py b/src/asp_python/_agent_namespace.py index 0dec5bd..0071ca5 100644 --- a/src/asp_python/_agent_namespace.py +++ b/src/asp_python/_agent_namespace.py @@ -1,4 +1,4 @@ -"""Project-level namespace policy for agent-oriented Python harness runs.""" +"""Project-level namespace policy for agent-oriented ASP Python runs.""" from __future__ import annotations @@ -27,7 +27,7 @@ from python_lang_parser import PythonModuleReport - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope @dataclass(frozen=True, slots=True) @@ -61,7 +61,7 @@ class _NamespaceConflictSpec: def agent_namespace_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, ) -> tuple[AspPythonFinding, ...]: @@ -116,7 +116,7 @@ def _duplicate_namespace_findings( def _repeated_namespace_segment_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, ) -> tuple[AspPythonFinding, ...]: diff --git a/src/asp_python/_agent_policy.py b/src/asp_python/_agent_policy.py index d629da0..41d9d36 100644 --- a/src/asp_python/_agent_policy.py +++ b/src/asp_python/_agent_policy.py @@ -33,7 +33,7 @@ from python_lang_parser import PythonModuleReport, PythonSymbol - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope @dataclass(frozen=True, slots=True) @@ -69,7 +69,7 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: def evaluate_project_modules( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], ) -> Iterable[AspPythonFinding]: """Evaluate agent-oriented namespace rules across a project scope.""" diff --git a/src/asp_python/_agent_reasoning_tree.py b/src/asp_python/_agent_reasoning_tree.py index f33373d..7744aec 100644 --- a/src/asp_python/_agent_reasoning_tree.py +++ b/src/asp_python/_agent_reasoning_tree.py @@ -21,7 +21,7 @@ PythonReasoningTreeNode, ) - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope _MAX_AGENT_BRANCH_CHILDREN = 6 _MIN_AGENT_BRANCH_PUBLIC_CHILDREN = 4 @@ -29,7 +29,7 @@ def agent_reasoning_tree_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, ) -> tuple[AspPythonFinding, ...]: @@ -193,7 +193,7 @@ def _module_documents_owner_map(module: PythonModuleReport | None) -> bool: return "owner map" in normalized -def _reasoning_tree_import_roots(scope: PythonProjectHarnessScope) -> tuple[Path, ...]: +def _reasoning_tree_import_roots(scope: AspPythonProjectScope) -> tuple[Path, ...]: if scope.source_paths: return scope.source_paths return scope.monitored_paths diff --git a/src/asp_python/_agent_snapshot.py b/src/asp_python/_agent_snapshot.py index fd87ec1..6ced4bf 100644 --- a/src/asp_python/_agent_snapshot.py +++ b/src/asp_python/_agent_snapshot.py @@ -1,4 +1,4 @@ -"""Agent-facing project snapshot renderer for Python harness runs.""" +"""Agent-facing project snapshot renderer for ASP Python runs.""" from __future__ import annotations @@ -7,9 +7,9 @@ from ._agent_snapshot_tree import render_python_agent_snapshot_tree from ._render import ( - render_python_lang_harness, + render_asp_python_report, ) -from ._rule_packs import resolve_project_harness_config +from ._rule_packs import resolve_asp_python_project_config from ._runner import run_asp_python from .verification import ( build_python_verification_profile_index_report, @@ -35,10 +35,10 @@ def render_asp_python_agent_snapshot_with_config( project_root: str | Path, config: AspPythonConfig | None, ) -> str: - """Render an agent snapshot using an explicit harness config.""" + """Render an agent snapshot using an explicit ASP Python config.""" root = Path(project_root) - selected_config = resolve_project_harness_config(root, config, rule_packs=None) + selected_config = resolve_asp_python_project_config(root, config, rule_packs=None) report = run_asp_python(root, config=selected_config) return render_asp_python_agent_snapshot_report( report, @@ -51,7 +51,7 @@ def render_asp_python_agent_snapshot_report( *, config: AspPythonConfig | None = None, ) -> str: - """Render an already-built project harness report as an agent snapshot.""" + """Render an already-built ASP Python report as an agent snapshot.""" project_root = ( None @@ -85,7 +85,7 @@ def render_asp_python_agent_snapshot_report( def _render_policy_section(report: AspPythonReport) -> str: - rendered = render_python_lang_harness(report) + rendered = render_asp_python_report(report) if rendered.startswith("[ok]"): return "" return "[policy]\n" + rendered diff --git a/src/asp_python/_agent_snapshot_tree.py b/src/asp_python/_agent_snapshot_tree.py index 1bc3adb..269843f 100644 --- a/src/asp_python/_agent_snapshot_tree.py +++ b/src/asp_python/_agent_snapshot_tree.py @@ -142,7 +142,7 @@ def metadata_lines(self, metadata: PythonProjectMetadata) -> list[str]: self.add_metadata_scripts(lines, metadata) self.add_metadata_entry_points(lines, metadata) if metadata.pytest_options.enables_asp_python: - lines.append("- pytest=python-project-harness") + lines.append("- pytest=asp-python") return lines def add_metadata_identity( diff --git a/src/asp_python/_asp_rules.py b/src/asp_python/_asp_rules.py new file mode 100644 index 0000000..99b4e0c --- /dev/null +++ b/src/asp_python/_asp_rules.py @@ -0,0 +1,42 @@ +"""Render source-embedded ASP Python rule fixtures.""" + +from __future__ import annotations + +from importlib.resources import files +from pathlib import Path + +_ASP_RULES_RESOURCE = "asp-rules.md" + + +def asp_python_rules_markdown() -> str: + """Return the source-embedded ASP Python rule list.""" + + return files(__package__).joinpath(_ASP_RULES_RESOURCE).read_text(encoding="utf-8") + + +def render_asp_python_rules_markdown() -> str: + """Render the source-embedded ASP Python rules as markdown.""" + + output = [ + "# asp-python", + "", + "## ASP Python Rules", + "", + "Generated from embedded `src/asp_python/asp-rules.md`.", + "", + ] + for line in asp_python_rules_markdown().splitlines(): + if item := line.removeprefix("- "): + if ": " in item: + rule_id, sentence = item.split(": ", 1) + output.append(f"- **{rule_id}**: {sentence}") + return "\n".join(output) + "\n" + + +def write_asp_python_rules_to_unit_tests(unit_test_dir: Path) -> Path: + """Write the generated ASP Python rules into a downstream unit test directory.""" + + output_path = unit_test_dir / "asp-rules.generated.md" + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(render_asp_python_rules_markdown(), encoding="utf-8") + return output_path diff --git a/src/asp_python/_cli.py b/src/asp_python/_cli.py index 7039e87..d7b0afe 100644 --- a/src/asp_python/_cli.py +++ b/src/asp_python/_cli.py @@ -1,4 +1,4 @@ -"""Command-line execution for the Python project harness.""" +"""Command-line execution for the ASP Python.""" from __future__ import annotations @@ -41,7 +41,7 @@ def run_cli( stdin: str | bytes | None = None, cwd: Path | None = None, ) -> int: - """Run the default package-level Python harness CLI.""" + """Run the default package-level ASP Python CLI.""" selected_stdout = sys.stdout if stdout is None else stdout selected_stderr = sys.stderr if stderr is None else stderr diff --git a/src/asp_python/_cli_agent.py b/src/asp_python/_cli_agent.py index c86252f..db83a7e 100644 --- a/src/asp_python/_cli_agent.py +++ b/src/asp_python/_cli_agent.py @@ -1,4 +1,4 @@ -"""Agent-facing guide and doctor rendering for the Python harness CLI.""" +"""Agent-facing guide and doctor rendering for the ASP Python CLI.""" from __future__ import annotations @@ -35,8 +35,6 @@ def render_agent_guide(project_root: Path) -> str: f"|cmd exact-source=asp python query --selector --projection source {workspace}", f"|cmd callable-skeleton=asp python query --selector --projection callable-skeleton {workspace}", "|cmd ast-patch=asp python ast-patch dry-run --packet ", - f"|cmd evidence-graph=asp python evidence graph --json {workspace}", - f"|cmd evidence-analyze=asp python evidence analyze --json {workspace}", "|policy authority=asp-python-api trigger=pytest-plugin", "|rule agent hook install/runtime is owned by asp", ( diff --git a/src/asp_python/_cli_args.py b/src/asp_python/_cli_args.py index fb80b5e..9f049d6 100644 --- a/src/asp_python/_cli_args.py +++ b/src/asp_python/_cli_args.py @@ -1,4 +1,4 @@ -"""Argument parsing helpers for the Python harness CLI.""" +"""Argument parsing helpers for the ASP Python CLI.""" from __future__ import annotations @@ -52,8 +52,6 @@ def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: ) if command == "query": return cls._parse_query(args[1:]) - if command == "evidence": - return cls._parse_evidence(args[1:]) if command == "agent": return cls._parse_agent(args[1:]) if command == "ast-patch": @@ -100,33 +98,6 @@ def _parse_ast_patch(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: project_root=positionals[0] if positionals else None, ) - @classmethod - def _parse_evidence(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: - action = args[0] if args else None - if action in {"--help", "-h"}: - return cls("help") - if action not in {"graph", "analyze", "analysis"}: - return cls("error", error="expected evidence ") - json_output = False - positionals: list[str] = [] - for arg in args[1:]: - if arg == "--json": - json_output = True - elif arg in {"--help", "-h"}: - return cls("help") - elif arg.startswith("-"): - return cls("error", error=f"unknown evidence option: {arg}") - else: - positionals.append(arg) - if len(positionals) > 1: - return cls("error", error="expected at most one PROJECT_ROOT argument") - return cls( - "evidence", - action="analyze" if action == "analysis" else action, - project_root=None if not positionals else Path(positionals[0]), - json=json_output, - ) - @classmethod def _parse_agent(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: action = args[0] if args else "doctor" @@ -210,13 +181,11 @@ def _parse_agent_guide(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs: def help_text() -> str: return ( - "asp-python — Python provider runtime and project harness\n\n" + "asp-python — Python provider runtime and ASP Python\n\n" "Usage:\n" " asp python search playbook [--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 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" @@ -233,9 +202,6 @@ def help_text() -> str: " 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" - "EVIDENCE\n" - " evidence graph --json Portable semantic-evidence-graph packet\n" - " evidence analyze --json Graph-turbo request for evidence-quality ranking\n\n" "AST PATCH\n" " ast-patch dry-run --packet \n" " Provider-native structural patch receipt; never mutates files\n\n" @@ -248,8 +214,6 @@ def help_text() -> str: " asp python search playbook PythonSemanticSearchOptions --workspace .\n" " asp python query --selector 'python://src/asp_python/_cli.py#item/function/run_cli' --projection source --workspace .\n" " asp-python query --catalog flow-lite --where 'source.call=payload sink.constructs=Action scope.fn=collect' .\n" - " asp-python 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/asp_python/_cli_protocol.py b/src/asp_python/_cli_protocol.py index 5d0a6b5..dee395f 100644 --- a/src/asp_python/_cli_protocol.py +++ b/src/asp_python/_cli_protocol.py @@ -1,4 +1,4 @@ -"""Protocol command dispatch for the Python harness CLI.""" +"""Protocol command dispatch for the ASP Python CLI.""" from __future__ import annotations @@ -30,8 +30,6 @@ def run_protocol_cli( project_root = _resolve_project_root(args, cwd) if args.command == "agent": return _run_agent_command(args, project_root=project_root, stdout=stdout) - if args.command == "evidence": - return _run_evidence_command(args, project_root=project_root, stdout=stdout) if args.command == "ast-patch": return _run_ast_patch_command( args, project_root=project_root, stdout=stdout, stdin=stdin @@ -71,42 +69,6 @@ def _run_agent_command( return 0 -def _run_evidence_command( - args: ProtocolArgs, - *, - project_root: Path, - stdout: TextIO, -) -> int: - from ._evidence_graph import ( - build_python_evidence_graph, - render_python_evidence_graph, - render_python_evidence_graph_json, - ) - from ._evidence_graph_turbo import ( - build_python_evidence_analysis_request, - render_python_evidence_analysis_request, - render_python_evidence_analysis_request_json, - ) - - if args.action == "graph": - graph = build_python_evidence_graph(project_root) - stdout.write( - render_python_evidence_graph_json(graph) - if args.json - else render_python_evidence_graph(graph) - ) - return 0 - if args.action == "analyze": - request = build_python_evidence_analysis_request(project_root) - stdout.write( - render_python_evidence_analysis_request_json(request) - if args.json - else render_python_evidence_analysis_request(request) - ) - return 0 - raise ValueError("expected evidence ") - - def _run_ast_patch_command( args: ProtocolArgs, *, @@ -128,12 +90,12 @@ def _run_query_protocol_command( stdout: TextIO, ) -> int: from ._cli_query import run_query_command - from ._rule_packs import resolve_project_harness_config + from ._rule_packs import resolve_asp_python_project_config from ._runner import run_asp_python report = run_asp_python( project_root, - config=resolve_project_harness_config(project_root, None, rule_packs=None), + config=resolve_asp_python_project_config(project_root, None, rule_packs=None), ) return run_query_command( args, diff --git a/src/asp_python/_cli_query_args.py b/src/asp_python/_cli_query_args.py index 5fe4cb4..f234fe2 100644 --- a/src/asp_python/_cli_query_args.py +++ b/src/asp_python/_cli_query_args.py @@ -1,4 +1,4 @@ -"""Query command argument parsing for the Python harness CLI.""" +"""Query command argument parsing for the ASP Python CLI.""" from __future__ import annotations diff --git a/src/asp_python/_cli_query_hook_args.py b/src/asp_python/_cli_query_hook_args.py index 8fba94b..e339c76 100644 --- a/src/asp_python/_cli_query_hook_args.py +++ b/src/asp_python/_cli_query_hook_args.py @@ -1,4 +1,4 @@ -"""Hook query option helpers for the Python harness CLI.""" +"""Hook query option helpers for the ASP Python CLI.""" from __future__ import annotations diff --git a/src/asp_python/_constants.py b/src/asp_python/_constants.py index 4a67178..2c4beac 100644 --- a/src/asp_python/_constants.py +++ b/src/asp_python/_constants.py @@ -1,4 +1,4 @@ -"""Shared constants for the Python language harness.""" +"""Shared constants for the ASP Python.""" from __future__ import annotations diff --git a/src/asp_python/_discovery.py b/src/asp_python/_discovery.py index ca5ceeb..456180b 100644 --- a/src/asp_python/_discovery.py +++ b/src/asp_python/_discovery.py @@ -1,4 +1,4 @@ -"""Python project path discovery for embedded harness runs.""" +"""Python project path discovery for embedded ASP Python runs.""" from __future__ import annotations @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING from ._constants import IGNORED_DIR_NAMES, INCLUDE_HIDDEN_DIR_NAMES -from ._model import PythonProjectHarnessScope +from ._model import AspPythonProjectScope from ._project_metadata import read_python_project_metadata if TYPE_CHECKING: @@ -105,7 +105,7 @@ def asp_python_paths( test_dir_names: Sequence[str] = ("tests",), extra_path_names: Sequence[str] = (), ) -> tuple[Path, ...]: - """Return project scan paths for embedded pytest harness checks.""" + """Return project scan paths for embedded ASP Python pytest checks.""" return asp_python_scope( project_root, @@ -125,7 +125,7 @@ def asp_python_scope( extra_path_names: Sequence[str] = (), ignored_dir_names: Iterable[str] | None = None, include_hidden_dir_names: Iterable[str] | None = None, -) -> PythonProjectHarnessScope: +) -> AspPythonProjectScope: """Return the default project-wide monitoring scope.""" root = Path(project_root) @@ -154,7 +154,7 @@ def asp_python_scope( ignored_dir_names=ignored_names, include_hidden_dir_names=included_hidden_names, ) - return PythonProjectHarnessScope( + return AspPythonProjectScope( project_root=root, project_metadata=metadata, project_paths=project_paths, @@ -243,7 +243,7 @@ def is_scannable_python_file( ignored_dir_names: frozenset[str], include_hidden_dir_names: frozenset[str] = INCLUDE_HIDDEN_DIR_NAMES, ) -> bool: - """Return whether a Python file belongs to the harness-owned scan scope.""" + """Return whether a Python file belongs to the ASP Python-owned scan scope.""" relative_parts = _scan_relative_parts(path, scan_root) return not any( diff --git a/src/asp_python/_evidence_graph.py b/src/asp_python/_evidence_graph.py deleted file mode 100644 index 2a8efdb..0000000 --- a/src/asp_python/_evidence_graph.py +++ /dev/null @@ -1,216 +0,0 @@ -"""Provider-owned evidence graph packets for Python projects.""" - -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any - -_EVIDENCE_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-evidence-graph" -_EVIDENCE_GRAPH_PROTOCOL_ID = "agent.semantic-protocols.evidence-graph" -_LANGUAGE_ID = "python" -_PROVIDER_ID = "asp-python" -_NAMESPACE = "agent.semantic-protocols.languages.python.asp-python" - - -def build_python_evidence_graph(project_root: Path) -> dict[str, Any]: - """Return a portable evidence graph for a Python project.""" - - root = project_root.resolve() - owner_path = _select_owner_path(root) - owner_id = _node_id("python:owner", owner_path) - claim_id = _node_id("python:claim", owner_path) - receipt_id = _node_id("python:receipt", "policy-api") - action_id = _node_id("python:action", "attach-policy-api-receipt") - gap_id = _node_id("python:gap", f"{owner_path}:receipt") - nodes: list[dict[str, Any]] = [ - { - "nodeId": owner_id, - "kind": "owner", - "label": owner_path, - "ownerPath": owner_path, - "status": "current", - "location": {"path": owner_path, "line": 1, "column": 0}, - "fields": {"languageId": _LANGUAGE_ID, "source": "provider-project"}, - }, - { - "nodeId": claim_id, - "kind": "invariant-candidate", - "label": "Python provider behavior needs executable evidence", - "ownerPath": owner_path, - "candidateId": "python.evidence.project-harness", - "status": "needs-injection", - "summary": "Project-level Python policy and semantic search behavior should be linked to verification receipts.", - "location": {"path": owner_path, "line": 1, "column": 0}, - "fields": { - "sourceRuleId": "PY-EVIDENCE-GRAPH", - "receiptKind": "policy-evaluation", - }, - }, - { - "nodeId": receipt_id, - "kind": "verification-receipt", - "label": "Python dependency policy API receipt", - "receiptId": "python.policy.api", - "status": "needs-injection", - "summary": "Attach the receipt emitted by the Python dependency policy API before treating the claim as verified.", - "fields": { - "authority": "asp-python-api", - "trigger": "pytest-plugin", - }, - }, - { - "nodeId": action_id, - "kind": "review-action", - "label": "Attach Python dependency policy API receipt", - "actionId": "python.attach-policy-api-receipt", - "status": "missing", - "summary": "run-receipt", - "fields": { - "priority": "p0", - "targetId": "python.evidence.project-harness", - }, - }, - ] - edges = [ - _edge("python:edge:owner-claim", "supports-claim", owner_id, claim_id), - _edge("python:edge:claim-receipt", "requires-evidence", claim_id, receipt_id), - _edge("python:edge:action-claim", "requires-evidence", action_id, claim_id), - ] - gaps = [ - { - "gapId": gap_id, - "ownerPath": owner_path, - "summary": "No attached Python dependency policy API receipt for this evidence graph.", - "severity": "warning", - "fields": {"requiredReceiptId": "python.policy.api"}, - } - ] - return { - "schemaId": _EVIDENCE_GRAPH_SCHEMA_ID, - "schemaVersion": "1", - "protocolId": _EVIDENCE_GRAPH_PROTOCOL_ID, - "protocolVersion": "1", - "graphId": "python.evidence.graph", - "producer": _producer(), - "project": _project(root), - "summary": _summary(nodes, edges, gaps), - "nodes": nodes, - "edges": edges, - "gaps": gaps, - "fields": { - "next": "pipe JSON to `asp graph render --packet - --view seeds`", - }, - } - - -def render_python_evidence_graph(graph: dict[str, Any]) -> str: - """Render an agent-facing compact evidence graph summary.""" - - summary = graph["summary"] - return ( - "evidence-graph " - f"nodes={summary['nodes']} edges={summary['edges']} " - f"owners={summary['owners']} claims={summary['claims']} " - f"stale-items={summary['staleItems']} gaps={summary['gaps']}\n" - ) - - -def render_python_evidence_graph_json(graph: dict[str, Any]) -> str: - """Render evidence graph JSON.""" - - return json.dumps(graph, separators=(",", ":")) + "\n" - - -def _summary( - nodes: list[dict[str, Any]], - edges: list[dict[str, Any]], - gaps: list[dict[str, Any]], -) -> dict[str, int]: - return { - "nodes": len(nodes), - "edges": len(edges), - "owners": sum(1 for node in nodes if node["kind"] == "owner"), - "claims": sum(1 for node in nodes if node["kind"] == "invariant-candidate"), - "staleItems": sum( - 1 for node in nodes if node.get("status") in {"stale", "expired"} - ), - "gaps": len(gaps), - } - - -def _producer() -> dict[str, str]: - return { - "languageId": _LANGUAGE_ID, - "providerId": _PROVIDER_ID, - "namespace": _NAMESPACE, - } - - -def _project(root: Path) -> dict[str, Any]: - project: dict[str, Any] = {"root": str(root), "fields": {}} - package_name = _package_name(root) - if package_name is not None: - project["package"] = package_name - return project - - -def _edge( - edge_id: str, - kind: str, - from_node_id: str, - to_node_id: str, -) -> dict[str, str]: - return { - "edgeId": edge_id, - "kind": kind, - "fromNodeId": from_node_id, - "toNodeId": to_node_id, - } - - -def _select_owner_path(root: Path) -> str: - for candidate in ("pyproject.toml", "setup.cfg", "setup.py"): - if (root / candidate).is_file(): - return candidate - for source_root in ("src", "."): - base = root / source_root - if not base.exists(): - continue - for path in sorted(base.rglob("*.py")): - if any(part.startswith(".") for part in path.relative_to(root).parts): - continue - return _relative_path(root, path) - return "." - - -def _package_name(root: Path) -> str | None: - pyproject = root / "pyproject.toml" - if not pyproject.is_file(): - return None - from ._project_config import read_pyproject_payload - - value = read_pyproject_payload(pyproject) - name = value.get("project", {}).get("name") - return str(name) if name else None - - -def _relative_path(root: Path, path: Path) -> str: - return path.resolve().relative_to(root.resolve()).as_posix() - - -def _node_id(prefix: str, raw: str) -> str: - return f"{prefix}:{_sanitize_id_part(raw)}" - - -def _sanitize_id_part(raw: str) -> str: - output = "".join( - character.lower() - if character.isascii() - and (character.isalnum() or character in {".", "_", ":", "-"}) - else "." - for character in raw - ) - while ".." in output: - output = output.replace("..", ".") - return output.strip(".") or "root" diff --git a/src/asp_python/_evidence_graph_turbo.py b/src/asp_python/_evidence_graph_turbo.py deleted file mode 100644 index 00309bd..0000000 --- a/src/asp_python/_evidence_graph_turbo.py +++ /dev/null @@ -1,138 +0,0 @@ -"""Graph-turbo request projection for Python evidence graphs.""" - -from __future__ import annotations - -import json -from pathlib import Path -from typing import Any - -from ._evidence_graph import build_python_evidence_graph - -_GRAPH_TURBO_REQUEST_SCHEMA_ID = "agent.semantic-protocols.semantic-graph-turbo-request" -_SEMANTIC_LANGUAGE_PROTOCOL_ID = "agent.semantic-protocols.semantic-language" - - -def build_python_evidence_analysis_request(project_root: Path) -> dict[str, Any]: - """Return a graph-turbo request for the Python evidence graph.""" - - graph = build_python_evidence_graph(project_root) - analysis_graph = _analysis_graph(graph) - summary = { - "graphs": 1, - "nodes": graph["summary"]["nodes"], - "edges": graph["summary"]["edges"], - "owners": graph["summary"]["owners"], - "claims": graph["summary"]["claims"], - "staleItems": graph["summary"]["staleItems"], - "gaps": graph["summary"]["gaps"], - } - return { - "schemaId": _GRAPH_TURBO_REQUEST_SCHEMA_ID, - "schemaVersion": "1", - "protocolId": _SEMANTIC_LANGUAGE_PROTOCOL_ID, - "protocolVersion": "1", - "packetKind": "graph-turbo-request", - "requestId": ( - "python.evidence.analysis." - f"graphs-{summary['graphs']}.nodes-{summary['nodes']}.gaps-{summary['gaps']}" - ), - "surface": "evidence-analyze", - "queryTerms": ["python evidence quality"], - "profile": "evidence-quality", - "algorithm": "typed-ppr-diverse", - "entryNodeIds": _analysis_seed_ids(analysis_graph), - "budget": 8, - "producer": graph["producer"], - "project": _analysis_project(project_root.resolve(), graph), - "summary": summary, - "graphs": [analysis_graph], - "fields": { - "next": "pipe JSON to `asp graph render --packet - --view seeds`", - }, - } - - -def render_python_evidence_analysis_request(request: dict[str, Any]) -> str: - """Render an agent-facing compact graph-turbo request summary.""" - - summary = request["summary"] - return ( - "evidence-analysis " - f"profile={request['profile']} graphs={summary['graphs']} " - f"nodes={summary['nodes']} edges={summary['edges']} " - f"owners={summary['owners']} claims={summary['claims']} " - f"stale-items={summary['staleItems']} gaps={summary['gaps']} " - 'next="asp graph render --packet - --view seeds"\n' - ) - - -def render_python_evidence_analysis_request_json(request: dict[str, Any]) -> str: - """Render graph-turbo request JSON.""" - - return json.dumps(request, separators=(",", ":")) + "\n" - - -def _analysis_graph(graph: dict[str, Any]) -> dict[str, Any]: - return { - "graphId": graph["graphId"], - "summary": graph["summary"], - "nodes": [_analysis_node(node) for node in graph["nodes"]], - "edges": [_analysis_edge(edge) for edge in graph["edges"]], - "gaps": graph.get("gaps", []), - } - - -def _analysis_node(node: dict[str, Any]) -> dict[str, Any]: - location = node.get("location") if isinstance(node.get("location"), dict) else {} - path = node.get("ownerPath") or location.get("path") - line = location.get("line") - rendered: dict[str, Any] = { - "id": node["nodeId"], - "kind": node["kind"], - "role": _node_role(str(node["kind"])), - "value": node["label"], - "fields": dict(node.get("fields", {})), - } - if path is not None: - rendered["path"] = path - rendered["ownerPath"] = node.get("ownerPath", path) - if isinstance(line, int): - rendered["locator"] = f"{path}:{line}:{line}" - rendered["startLine"] = line - rendered["endLine"] = line - for key in ("candidateId", "receiptId", "actionId", "summary", "status"): - if key in node: - rendered["fields"][key] = str(node[key]) - return rendered - - -def _analysis_edge(edge: dict[str, Any]) -> dict[str, Any]: - return { - "source": edge["fromNodeId"], - "target": edge["toNodeId"], - "relation": edge["kind"], - "fields": {"edgeId": edge["edgeId"]}, - } - - -def _analysis_seed_ids(graph: dict[str, Any]) -> list[str]: - seeds = [str(node["id"]) for node in graph["nodes"] if node.get("kind") == "owner"] - if seeds: - return seeds - nodes = graph["nodes"] - return [str(nodes[0]["id"])] if nodes else [] - - -def _analysis_project(root: Path, graph: dict[str, Any]) -> dict[str, Any]: - project = graph.get("project", {}) - package = project.get("package") if isinstance(project, dict) else None - return {"root": str(root), "package": package, "fields": {}} - - -def _node_role(kind: str) -> str: - return { - "owner": "path", - "invariant-candidate": "claim", - "verification-receipt": "receipt", - "review-action": "action", - }.get(kind, "evidence") diff --git a/src/asp_python/_harness_rules.py b/src/asp_python/_harness_rules.py deleted file mode 100644 index b3bf476..0000000 --- a/src/asp_python/_harness_rules.py +++ /dev/null @@ -1,44 +0,0 @@ -"""Render source-embedded Python harness rule fixtures.""" - -from __future__ import annotations - -from importlib.resources import files -from pathlib import Path - -_HARNESS_RULES_RESOURCE = "harness-rules.md" - - -def python_harness_rules_markdown() -> str: - """Return the source-embedded harness rule list.""" - - return ( - files(__package__).joinpath(_HARNESS_RULES_RESOURCE).read_text(encoding="utf-8") - ) - - -def render_python_harness_rules_markdown() -> str: - """Render the source-embedded Python harness rules as markdown.""" - - output = [ - "# asp-python", - "", - "## Harness Rules", - "", - "Generated from embedded `src/asp_python/harness-rules.md`.", - "", - ] - for line in python_harness_rules_markdown().splitlines(): - if item := line.removeprefix("- "): - if ": " in item: - rule_id, sentence = item.split(": ", 1) - output.append(f"- **{rule_id}**: {sentence}") - return "\n".join(output) + "\n" - - -def write_python_harness_rules_to_unit_tests(unit_test_dir: Path) -> Path: - """Write the generated harness rules into a downstream unit test directory.""" - - output_path = unit_test_dir / "harness-rules.generated.md" - output_path.parent.mkdir(parents=True, exist_ok=True) - output_path.write_text(render_python_harness_rules_markdown(), encoding="utf-8") - return output_path diff --git a/src/asp_python/_model.py b/src/asp_python/_model.py index dab56a7..213359f 100644 --- a/src/asp_python/_model.py +++ b/src/asp_python/_model.py @@ -1,4 +1,4 @@ -"""Data model for embedded Python language harness reports.""" +"""Data model for embedded ASP Python reports.""" from __future__ import annotations @@ -36,7 +36,7 @@ @dataclass(frozen=True, slots=True) class PythonRulePackDescriptor: - """Stable metadata for one Python language harness rule pack.""" + """Stable metadata for one ASP Python rule pack.""" id: str version: str @@ -53,7 +53,7 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) class AspPythonRule: - """Compact metadata for one deterministic harness rule.""" + """Compact metadata for one deterministic ASP Python rule.""" rule_id: str pack_id: str @@ -72,7 +72,7 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) class AspPythonFinding: - """One deterministic Python harness finding.""" + """One deterministic ASP Python finding.""" rule_id: str pack_id: str @@ -94,8 +94,8 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) -class PythonProjectHarnessScope: - """Concrete project paths monitored by an embedded Python harness run.""" +class AspPythonProjectScope: + """Concrete project paths monitored by an embedded ASP Python run.""" project_root: Path project_metadata: PythonProjectMetadata | None = None @@ -153,8 +153,8 @@ def _dedupe_paths(paths: Iterable[Path]) -> tuple[Path, ...]: return tuple(deduped) -class PythonLangRulePack(Protocol): - """Protocol for Python language harness rule packs.""" +class AspPythonRulePack(Protocol): + """Protocol for ASP Python rule packs.""" pack_id: str @@ -167,7 +167,7 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: @dataclass(frozen=True, slots=True) class AspPythonConfig: - """Configuration for an embedded Python language harness run.""" + """Configuration for an embedded ASP Python run.""" ignored_dir_names: frozenset[str] = IGNORED_DIR_NAMES include_hidden_dir_names: frozenset[str] = INCLUDE_HIDDEN_DIR_NAMES @@ -183,7 +183,7 @@ class AspPythonConfig: verification_policy: PythonVerificationPolicy = field( default_factory=PythonVerificationPolicy ) - rule_packs: tuple[PythonLangRulePack, ...] | None = None + rule_packs: tuple[AspPythonRulePack, ...] | None = None def with_verification_policy( self, @@ -283,7 +283,7 @@ def with_verification_skill_descriptor( @dataclass(frozen=True, slots=True) class AspPythonReport: - """Aggregated Python language harness report.""" + """Aggregated ASP Python report.""" modules: tuple[PythonModuleReport, ...] findings: tuple[AspPythonFinding, ...] @@ -291,7 +291,7 @@ class AspPythonReport: blocking_severities: frozenset[PythonDiagnosticSeverity] = ( DEFAULT_BLOCKING_SEVERITIES ) - project_resolution: PythonProjectHarnessScope | None = None + project_resolution: AspPythonProjectScope | None = None disabled_rule_ids: frozenset[str] = frozenset() blocking_rule_ids: frozenset[str] = frozenset() @@ -380,10 +380,10 @@ def assert_clean( """Raise `AssertionError` when blocking findings are present.""" if self.blocking_findings(severities=severities): - from ._render import render_python_lang_harness + from ._render import render_asp_python_report raise AssertionError( - render_python_lang_harness( + render_asp_python_report( self, severities=severities, include_advice=include_advice, diff --git a/src/asp_python/_modularity.py b/src/asp_python/_modularity.py index 90cf963..5121d0c 100644 --- a/src/asp_python/_modularity.py +++ b/src/asp_python/_modularity.py @@ -29,7 +29,7 @@ from python_lang_parser import PythonModuleReport, PythonSymbol - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope MODULARITY_PACK_ID = "python.modularity" PY_MOD_R006 = "PY-MOD-R006" @@ -85,7 +85,7 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: def evaluate_project_modules( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], ) -> Iterable[AspPythonFinding]: """Evaluate package-tree modularity rules over a parsed project.""" @@ -143,7 +143,7 @@ def _file_modularity_findings( def _reasoning_tree_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, ) -> tuple[AspPythonFinding, ...]: @@ -183,7 +183,7 @@ def _reasoning_tree_findings( return tuple(findings) -def _reasoning_tree_import_roots(scope: PythonProjectHarnessScope) -> tuple[Path, ...]: +def _reasoning_tree_import_roots(scope: AspPythonProjectScope) -> tuple[Path, ...]: if scope.source_paths: return scope.source_paths return scope.monitored_paths diff --git a/src/asp_python/_project_config.py b/src/asp_python/_project_config.py index 42884dc..96f9ed7 100644 --- a/src/asp_python/_project_config.py +++ b/src/asp_python/_project_config.py @@ -1,4 +1,4 @@ -"""Project-local pyproject configuration for Python harness policy.""" +"""Project-local pyproject configuration for ASP Python policy.""" from __future__ import annotations @@ -36,7 +36,7 @@ def read_asp_python_config( table = _read_project_config_table(Path(project_root) / "pyproject.toml") if not table: return None - return AspPythonConfig(**_harness_config_kwargs(table)) + return AspPythonConfig(**_asp_python_config_kwargs(table)) def read_pyproject_payload(pyproject_path: Path) -> dict[str, Any]: @@ -55,7 +55,7 @@ def apply_asp_project_discovery_config( project_root: str | Path, config: AspPythonConfig, ) -> AspPythonConfig: - """Merge nearest `asp.toml` discovery settings into a harness config.""" + """Merge nearest `asp.toml` discovery settings into a ASP Python config.""" table = _read_asp_discovery_table(Path(project_root)) if not table: @@ -82,7 +82,7 @@ def _read_project_config_table(pyproject_path: Path) -> dict[str, Any] | None: return table -def _harness_config_kwargs(table: dict[str, Any]) -> dict[str, object]: +def _asp_python_config_kwargs(table: dict[str, Any]) -> dict[str, object]: kwargs: dict[str, object] = {} _put_bool(kwargs, table, "include_tests") _put_string_tuple(kwargs, table, "source_dir_names") diff --git a/src/asp_python/_project_evaluation.py b/src/asp_python/_project_evaluation.py index 1b6a229..92f3a49 100644 --- a/src/asp_python/_project_evaluation.py +++ b/src/asp_python/_project_evaluation.py @@ -14,14 +14,14 @@ from ._model import ( AspPythonFinding, - PythonLangRulePack, - PythonProjectHarnessScope, + AspPythonProjectScope, + AspPythonRulePack, ) def evaluate_project_rule_packs( - scope: PythonProjectHarnessScope, - rule_packs: Sequence[PythonLangRulePack], + scope: AspPythonProjectScope, + rule_packs: Sequence[AspPythonRulePack], modules: Sequence[PythonModuleReport], ) -> tuple[AspPythonFinding, ...]: """Evaluate project-resolution hooks exposed by configured rule packs.""" diff --git a/src/asp_python/_project_metadata.py b/src/asp_python/_project_metadata.py index b646dbb..838aaea 100644 --- a/src/asp_python/_project_metadata.py +++ b/src/asp_python/_project_metadata.py @@ -1,4 +1,4 @@ -"""Harness adapter for parser-owned Python project metadata.""" +"""ASP Python adapter for parser-owned Python project metadata.""" from __future__ import annotations diff --git a/src/asp_python/_project_policy.py b/src/asp_python/_project_policy.py index 2a5633b..bd98fdf 100644 --- a/src/asp_python/_project_policy.py +++ b/src/asp_python/_project_policy.py @@ -20,7 +20,7 @@ from python_lang_parser import PythonModuleReport - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope @dataclass(frozen=True, slots=True) @@ -46,7 +46,7 @@ def evaluate(self, report: PythonModuleReport) -> Iterable[AspPythonFinding]: def evaluate_project_modules( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], ) -> Iterable[AspPythonFinding]: """Evaluate project-shape rules over a parsed project.""" diff --git a/src/asp_python/_project_policy_catalog.py b/src/asp_python/_project_policy_catalog.py index 9a47f25..19d0ebb 100644 --- a/src/asp_python/_project_policy_catalog.py +++ b/src/asp_python/_project_policy_catalog.py @@ -102,8 +102,8 @@ rule_id=PY_PROJ_R010, pack_id=PROJECT_POLICY_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, - title="Harness dev dependency should mount a pytest gate", - requirement="Enable `--python-project-harness` in pytest addopts or expose `asp_python_test()` so the dev dependency actually gates project policy.", + title="ASP Python dev dependency should mount a pytest gate", + requirement="Enable `--asp-python` in pytest addopts or expose `asp_python_test()` so the dev dependency actually gates project policy.", labels=dict(_RULE_LABELS), ), AspPythonRule( diff --git a/src/asp_python/_project_policy_imports.py b/src/asp_python/_project_policy_imports.py index e6af168..f4b35b8 100644 --- a/src/asp_python/_project_policy_imports.py +++ b/src/asp_python/_project_policy_imports.py @@ -16,12 +16,12 @@ from python_lang_parser import PythonModuleReport - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope from ._project_metadata import PythonProjectMetadata def project_import_name_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, @@ -161,7 +161,7 @@ def _ambiguous_import_name_findings( def _reasoning_tree_import_roots( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, ) -> tuple[Path, ...]: roots: list[Path] = [] diff --git a/src/asp_python/_project_policy_layout.py b/src/asp_python/_project_policy_layout.py index bffc186..029b505 100644 --- a/src/asp_python/_project_policy_layout.py +++ b/src/asp_python/_project_policy_layout.py @@ -11,12 +11,12 @@ if TYPE_CHECKING: from pathlib import Path - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope from ._project_metadata import PythonProjectMetadata def project_layout_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, pack_id: str, ) -> tuple[AspPythonFinding, ...]: @@ -40,7 +40,7 @@ def _is_packaged_project(metadata: PythonProjectMetadata) -> bool: def _src_layout_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, pack_id: str, ) -> tuple[AspPythonFinding, ...]: @@ -65,7 +65,7 @@ def _src_layout_findings( def _uses_src_layout( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, ) -> bool: src_roots = _src_layout_roots(scope, metadata) @@ -80,7 +80,7 @@ def _uses_src_layout( def _src_layout_roots( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, ) -> tuple[Path, ...]: src_roots: list[Path] = [] diff --git a/src/asp_python/_project_policy_pytest_gate.py b/src/asp_python/_project_policy_pytest_gate.py index 9f3c025..fa7ae6e 100644 --- a/src/asp_python/_project_policy_pytest_gate.py +++ b/src/asp_python/_project_policy_pytest_gate.py @@ -1,4 +1,4 @@ -"""Project policy for parser-visible pytest harness gates.""" +"""Project policy for parser-visible ASP Python pytest gates.""" from __future__ import annotations @@ -21,13 +21,13 @@ def project_pytest_gate_findings( modules: Sequence[PythonModuleReport], pack_id: str, ) -> tuple[AspPythonFinding, ...]: - """Return findings when a harness dependency is not wired into pytest.""" + """Return findings when a ASP Python dependency is not wired into pytest.""" - if not declares_python_harness_surface(metadata): + if not declares_asp_python_surface(metadata): return () if metadata.pytest_options.enables_asp_python: return () - if _has_explicit_harness_helper(modules): + if _has_explicit_asp_python_helper(modules): return () rule = project_policy_rule(PY_PROJ_R010) @@ -38,20 +38,20 @@ def project_pytest_gate_findings( severity=rule.severity, title=rule.title, summary=( - f"{metadata.pyproject_path.name} declares the Python project harness " + f"{metadata.pyproject_path.name} declares the ASP Python " "surface without a parser-visible pytest gate." ), location=path_location(metadata.pyproject_path), requirement=rule.requirement, source_line=source_line(str(metadata.pyproject_path), 1), - label="mount the parser-backed harness in pytest", + label="mount the parser-backed ASP Python in pytest", labels=dict(rule.labels), ), ) -def declares_python_harness_surface(metadata: PythonProjectMetadata) -> bool: - """Return whether project metadata declares this harness as a dev surface.""" +def declares_asp_python_surface(metadata: PythonProjectMetadata) -> bool: + """Return whether project metadata declares ASP Python as a dev surface.""" distribution_name = _canonical_distribution_name(_DISTRIBUTION_NAME) if _canonical_distribution_name(metadata.project_name or "") == distribution_name: @@ -68,7 +68,7 @@ def declares_python_harness_surface(metadata: PythonProjectMetadata) -> bool: ) -def _has_explicit_harness_helper( +def _has_explicit_asp_python_helper( modules: Sequence[PythonModuleReport], ) -> bool: for module in modules: diff --git a/src/asp_python/_project_policy_verification.py b/src/asp_python/_project_policy_verification.py index 376bb3f..1714c0f 100644 --- a/src/asp_python/_project_policy_verification.py +++ b/src/asp_python/_project_policy_verification.py @@ -9,7 +9,7 @@ from ._model import AspPythonConfig, AspPythonFinding from ._project_config import read_asp_python_config from ._project_policy_catalog import PY_PROJ_R011, project_policy_rule -from ._project_policy_pytest_gate import declares_python_harness_surface +from ._project_policy_pytest_gate import declares_asp_python_surface from ._source import path_location, source_line from .verification.facts import ( is_test_path, @@ -21,18 +21,18 @@ from python_lang_parser import PythonModuleReport, PythonProjectMetadata - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope def project_verification_profile_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], pack_id: str, ) -> tuple[AspPythonFinding, ...]: """Return Agent advice when parser facts need a verification profile.""" - if not declares_python_harness_surface(metadata): + if not declares_asp_python_surface(metadata): return () config = read_asp_python_config(scope.project_root) selected_config = config if config is not None else AspPythonConfig() @@ -64,7 +64,7 @@ def project_verification_profile_findings( def _verification_owner_count( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, metadata: PythonProjectMetadata, modules: Sequence[PythonModuleReport], config: AspPythonConfig, diff --git a/src/asp_python/_pytest.py b/src/asp_python/_pytest.py index 57a9203..10e5c74 100644 --- a/src/asp_python/_pytest.py +++ b/src/asp_python/_pytest.py @@ -1,4 +1,4 @@ -"""Pytest-facing helpers for embedding the Python project harness.""" +"""Pytest-facing helpers for embedding the ASP Python.""" from __future__ import annotations @@ -12,14 +12,14 @@ from python_lang_parser import PythonDiagnosticSeverity - from ._model import AspPythonConfig, PythonLangRulePack + from ._model import AspPythonConfig, AspPythonRulePack def asp_python_test( project_root: str | Path = ".", *, config: AspPythonConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_tests: bool | None = None, source_dir_names: Sequence[str] | None = None, @@ -47,7 +47,5 @@ def test_asp_python_policy() -> None: test_asp_python_policy.__name__ = test_name test_asp_python_policy.__qualname__ = test_name - test_asp_python_policy.__doc__ = ( - "Run the Python project harness over configured project paths." - ) + test_asp_python_policy.__doc__ = "Run the ASP Python over configured project paths." return test_asp_python_policy diff --git a/src/asp_python/_pytest_plugin_options.py b/src/asp_python/_pytest_plugin_options.py index 12f969c..e877f51 100644 --- a/src/asp_python/_pytest_plugin_options.py +++ b/src/asp_python/_pytest_plugin_options.py @@ -17,21 +17,21 @@ from collections.abc import Sequence -ENABLE_OPTION = "--python-project-harness" -NO_TESTS_OPTION = "--python-project-harness-no-tests" -SOURCE_DIR_OPTION = "--python-project-harness-source-dir" -TEST_DIR_OPTION = "--python-project-harness-test-dir" -EXTRA_PATH_OPTION = "--python-project-harness-extra-path" -NO_ADVICE_OPTION = "--python-project-harness-no-advice" +ENABLE_OPTION = "--asp-python" +NO_TESTS_OPTION = "--asp-python-no-tests" +SOURCE_DIR_OPTION = "--asp-python-source-dir" +TEST_DIR_OPTION = "--asp-python-test-dir" +EXTRA_PATH_OPTION = "--asp-python-extra-path" +NO_ADVICE_OPTION = "--asp-python-no-advice" -_ROOT_OPTION = "--python-project-harness-root" -_DISABLE_RULE_OPTION = "--python-project-harness-disable-rule" -_BLOCK_RULE_OPTION = "--python-project-harness-block-rule" -_ERROR_ONLY_OPTION = "--python-project-harness-error-only" +_ROOT_OPTION = "--asp-python-root" +_DISABLE_RULE_OPTION = "--asp-python-disable-rule" +_BLOCK_RULE_OPTION = "--asp-python-block-rule" +_ERROR_ONLY_OPTION = "--asp-python-error-only" def add_options(parser: pytest.Parser) -> None: - """Register Python project harness pytest options.""" + """Register ASP Python pytest options.""" group = parser.getgroup("asp-python") for name, kwargs in ( @@ -49,7 +49,7 @@ def add_options(parser: pytest.Parser) -> None: "action": "store", "default": None, "metavar": "PATH", - "help": "Project root for the harness test. Defaults to pytest rootdir.", + "help": "Project root for ASP Python test. Defaults to pytest rootdir.", }, ), ( @@ -93,7 +93,7 @@ def add_options(parser: pytest.Parser) -> None: "action": "append", "default": [], "metavar": "RULE_ID", - "help": "Harness rule id to suppress. Can be provided more than once.", + "help": "ASP Python rule id to suppress. Can be provided more than once.", }, ), ( @@ -102,7 +102,7 @@ def add_options(parser: pytest.Parser) -> None: "action": "append", "default": [], "metavar": "RULE_ID", - "help": "Harness rule id to treat as blocking. Can be provided more than once.", + "help": "ASP Python rule id to treat as blocking. Can be provided more than once.", }, ), ( @@ -110,7 +110,7 @@ def add_options(parser: pytest.Parser) -> None: { "action": "store_true", "default": False, - "help": "Only fail the pytest harness item for parser errors.", + "help": "Only fail the ASP Python pytest item for parser errors.", }, ), ( @@ -133,7 +133,7 @@ def blocking_severities( return None -def harness_config(config: pytest.Config) -> AspPythonConfig | None: +def asp_python_config(config: pytest.Config) -> AspPythonConfig | None: disabled_rule_values = config.getoption(_DISABLE_RULE_OPTION) blocking_rule_values = config.getoption(_BLOCK_RULE_OPTION) if not disabled_rule_values and not blocking_rule_values: diff --git a/src/asp_python/_pytest_plugin_project.py b/src/asp_python/_pytest_plugin_project.py index 8e0797c..9ee8387 100644 --- a/src/asp_python/_pytest_plugin_project.py +++ b/src/asp_python/_pytest_plugin_project.py @@ -16,7 +16,7 @@ def project_root(config: pytest.Config) -> Path: """Resolve the configured or uniquely targeted Python project root.""" - configured_root = config.getoption("--python-project-harness-root") + configured_root = config.getoption("--asp-python-root") if configured_root: return Path(configured_root) root = Path(config.rootpath) diff --git a/src/asp_python/_render.py b/src/asp_python/_render.py index 5bd21e0..e2fc5dc 100644 --- a/src/asp_python/_render.py +++ b/src/asp_python/_render.py @@ -1,4 +1,4 @@ -"""Compact snapshot rendering for Python harness diagnostics.""" +"""Compact snapshot rendering for ASP Python diagnostics.""" from __future__ import annotations @@ -49,7 +49,7 @@ def _line_protocol_field_value(value: object) -> str: return str(value) -def render_python_lang_harness( +def render_asp_python_report( report: AspPythonReport, *, severities: frozenset[PythonDiagnosticSeverity] | None = None, @@ -87,13 +87,13 @@ def render_python_lang_harness( return _render_ok_header(report) -def render_python_lang_harness_json(report: AspPythonReport) -> str: +def render_asp_python_report_json(report: AspPythonReport) -> str: """Render a structured JSON diagnostic report for tool consumers.""" return json.dumps(report.to_dict(), separators=(",", ":"), sort_keys=True) -def render_python_lang_harness_advice(report: AspPythonReport) -> str: +def render_asp_python_report_advice(report: AspPythonReport) -> str: """Render non-blocking advisory findings for agent-guided repair.""" advice_findings = _deduplicate_advice_findings( diff --git a/src/asp_python/_rule_packs.py b/src/asp_python/_rule_packs.py index bb6cd90..dcf19bc 100644 --- a/src/asp_python/_rule_packs.py +++ b/src/asp_python/_rule_packs.py @@ -1,4 +1,4 @@ -"""Default rule-pack configuration for Python project harness runs.""" +"""Default rule-pack configuration for ASP Python runs.""" from __future__ import annotations @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING from ._agent_policy import PythonAgentPolicyRulePack -from ._model import AspPythonConfig, PythonLangRulePack, PythonRulePackDescriptor +from ._model import AspPythonConfig, AspPythonRulePack, PythonRulePackDescriptor from ._modern_design import PythonModernDesignRulePack from ._modularity import PythonModularityRulePack from ._project_config import ( @@ -22,7 +22,7 @@ from pathlib import Path -def default_python_lang_rule_packs() -> tuple[PythonLangRulePack, ...]: +def default_asp_python_rule_packs() -> tuple[AspPythonRulePack, ...]: """Return the default deterministic Python language rule packs.""" return ( @@ -36,50 +36,50 @@ def default_python_lang_rule_packs() -> tuple[PythonLangRulePack, ...]: def python_rule_pack_descriptors() -> tuple[PythonRulePackDescriptor, ...]: - """Return stable metadata for default Python harness rule packs.""" + """Return stable metadata for default ASP Python rule packs.""" return tuple( - rule_pack.descriptor() for rule_pack in default_python_lang_rule_packs() + rule_pack.descriptor() for rule_pack in default_asp_python_rule_packs() ) -def default_python_harness_config() -> AspPythonConfig: - """Return the default Python language harness configuration.""" +def default_asp_python_config() -> AspPythonConfig: + """Return the default ASP Python configuration.""" - return AspPythonConfig(rule_packs=default_python_lang_rule_packs()) + return AspPythonConfig(rule_packs=default_asp_python_rule_packs()) -def resolve_harness_config( +def resolve_asp_python_config( config: AspPythonConfig | None, *, - rule_packs: Sequence[PythonLangRulePack] | None, + rule_packs: Sequence[AspPythonRulePack] | None, ) -> AspPythonConfig: """Resolve caller config and one-shot rule-pack overrides.""" - selected_config = default_python_harness_config() if config is None else config + selected_config = default_asp_python_config() if config is None else config if rule_packs is None: return selected_config return replace(selected_config, rule_packs=tuple(rule_packs)) -def resolve_project_harness_config( +def resolve_asp_python_project_config( project_root: str | Path, config: AspPythonConfig | None, *, - rule_packs: Sequence[PythonLangRulePack] | None, + rule_packs: Sequence[AspPythonRulePack] | None, ) -> AspPythonConfig: """Resolve config for project-root runs, including pyproject policy.""" selected_config = read_asp_python_config(project_root) if config is None else config - resolved = resolve_harness_config(selected_config, rule_packs=rule_packs) + resolved = resolve_asp_python_config(selected_config, rule_packs=rule_packs) return apply_asp_project_discovery_config(project_root, resolved) def selected_rule_packs( config: AspPythonConfig, -) -> tuple[PythonLangRulePack, ...]: +) -> tuple[AspPythonRulePack, ...]: """Return configured rule packs, falling back to the default catalog.""" if config.rule_packs is not None: return config.rule_packs - return default_python_lang_rule_packs() + return default_asp_python_rule_packs() diff --git a/src/asp_python/_runner.py b/src/asp_python/_runner.py index f9b86d0..b881030 100644 --- a/src/asp_python/_runner.py +++ b/src/asp_python/_runner.py @@ -1,4 +1,4 @@ -"""Runner API for embedding the Python language harness in pytest.""" +"""Runner API for embedding the ASP Python in pytest.""" from __future__ import annotations @@ -16,7 +16,7 @@ AspPythonConfig, AspPythonFinding, AspPythonReport, - PythonLangRulePack, + AspPythonRulePack, ) if TYPE_CHECKING: @@ -27,13 +27,13 @@ def run_asp_python( project_root: str | Path, *, config: AspPythonConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, include_tests: bool | None = None, source_dir_names: Sequence[str] | None = None, test_dir_names: Sequence[str] | None = None, extra_path_names: Sequence[str] | None = None, ) -> AspPythonReport: - """Run the harness over conventional Python project paths.""" + """Run ASP Python over conventional Python project paths.""" root = Path(project_root) if not root.exists(): @@ -42,9 +42,9 @@ def run_asp_python( compact_project_findings, evaluate_project_rule_packs, ) - from ._rule_packs import resolve_project_harness_config, selected_rule_packs + from ._rule_packs import resolve_asp_python_project_config, selected_rule_packs - selected_config = resolve_project_harness_config( + selected_config = resolve_asp_python_project_config( root, config, rule_packs=rule_packs, @@ -73,7 +73,7 @@ def run_asp_python( ignored_dir_names=selected_config.ignored_dir_names, include_hidden_dir_names=selected_config.include_hidden_dir_names, ) - report = run_python_lang_harness( + report = run_asp_python_paths( scope.monitored_paths, config=selected_config, ) @@ -96,7 +96,7 @@ def assert_asp_python_clean( project_root: str | Path, *, config: AspPythonConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_tests: bool | None = None, source_dir_names: Sequence[str] | None = None, @@ -104,11 +104,11 @@ def assert_asp_python_clean( extra_path_names: Sequence[str] | None = None, include_advice: bool = True, ) -> AspPythonReport: - """Run the project harness and raise when configured-blocking findings exist.""" + """Run the ASP Python and raise when configured-blocking findings exist.""" - from ._rule_packs import resolve_project_harness_config + from ._rule_packs import resolve_asp_python_project_config - selected_config = resolve_project_harness_config( + selected_config = resolve_asp_python_project_config( Path(project_root), config, rule_packs=rule_packs, @@ -132,13 +132,13 @@ def assert_asp_python_clean( return report -def run_python_lang_harness( +def run_asp_python_paths( paths: Sequence[str | Path], *, config: AspPythonConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, ) -> AspPythonReport: - """Run the Python language harness over files or directories.""" + """Run the ASP Python over files or directories.""" if rule_packs == (): selected_config = ( @@ -148,14 +148,14 @@ def run_python_lang_harness( ) selected_packs = () else: - from ._rule_packs import resolve_harness_config, selected_rule_packs + from ._rule_packs import resolve_asp_python_config, selected_rule_packs - selected_config = resolve_harness_config(config, rule_packs=rule_packs) + selected_config = resolve_asp_python_config(config, rule_packs=rule_packs) selected_packs = selected_rule_packs(selected_config) root_paths = tuple(Path(path) for path in paths) for path in root_paths: if not path.exists(): - raise ValueError(f"harness path does not exist: {path}") + raise ValueError(f"ASP Python path does not exist: {path}") modules = _parse_python_files( discover_python_files( root_paths, @@ -188,20 +188,20 @@ def _parse_python_files(paths: Sequence[Path]) -> tuple[PythonModuleReport, ...] return tuple(executor.map(parse_python_file, paths)) -def assert_python_lang_harness_clean( +def assert_asp_python_paths_clean( paths: Sequence[str | Path], *, config: AspPythonConfig | None = None, - rule_packs: Sequence[PythonLangRulePack] | None = None, + rule_packs: Sequence[AspPythonRulePack] | None = None, severities: frozenset[PythonDiagnosticSeverity] | None = None, include_advice: bool = True, ) -> AspPythonReport: - """Run the harness and raise when configured-blocking findings are present.""" + """Run ASP Python and raise when configured-blocking findings are present.""" - from ._rule_packs import resolve_harness_config + from ._rule_packs import resolve_asp_python_config - selected_config = resolve_harness_config(config, rule_packs=rule_packs) - report = run_python_lang_harness(paths, config=selected_config) + selected_config = resolve_asp_python_config(config, rule_packs=rule_packs) + report = run_asp_python_paths(paths, config=selected_config) report.assert_clean( severities=( severities diff --git a/src/asp_python/_semantic_language.py b/src/asp_python/_semantic_language.py index 879e8d6..fe4bda1 100644 --- a/src/asp_python/_semantic_language.py +++ b/src/asp_python/_semantic_language.py @@ -16,7 +16,6 @@ "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") @@ -46,7 +45,6 @@ def python_semantic_language_registration() -> dict[str, Any]: "methods": [ *_PYTHON_QUERY_METHODS, *_PYTHON_AST_PATCH_METHODS, - *_PYTHON_EVIDENCE_METHODS, *_PYTHON_AGENT_METHODS, ], "methodDescriptors": python_semantic_language_method_descriptors(), @@ -73,28 +71,6 @@ def python_semantic_language_method_descriptors() -> list[dict[str, Any]]: } for method in _PYTHON_AST_PATCH_METHODS ) - descriptors.extend( - [ - { - "method": "evidence/graph", - "command": "evidence", - "input": "provider project root", - "outputSchemaIds": [ids.SEMANTIC_EVIDENCE_GRAPH_SCHEMA_ID], - "supportsJson": True, - "supportsCompact": True, - }, - { - "method": "evidence/analyze", - "command": "evidence", - "input": "provider project root", - "outputSchemaIds": [ids.SEMANTIC_GRAPH_TURBO_REQUEST_SCHEMA_ID], - "packetSchemas": ["semantic-graph-turbo-request.v1"], - "clients": ["asp-python-graphs"], - "supportsJson": True, - "supportsCompact": True, - }, - ] - ) descriptors.extend( [ { diff --git a/src/asp_python/_semantic_language_ids.py b/src/asp_python/_semantic_language_ids.py index f89ecec..4d78e92 100644 --- a/src/asp_python/_semantic_language_ids.py +++ b/src/asp_python/_semantic_language_ids.py @@ -11,9 +11,6 @@ "agent.semantic-protocols.semantic-tree-sitter-provenance" ) SEMANTIC_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-graph" -SEMANTIC_GRAPH_TURBO_REQUEST_SCHEMA_ID = ( - "agent.semantic-protocols.semantic-graph-turbo-request" -) SEMANTIC_TYPE_SURFACE_SCHEMA_ID = "agent.semantic-protocols.semantic-type-surface" SEMANTIC_FACT_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-fact-graph" SEMANTIC_FACT_ONTOLOGY_SCHEMA_ID = "agent.semantic-protocols.semantic-fact-ontology" @@ -25,8 +22,6 @@ "agent.semantic-protocols.semantic-formal-proof-pilot" ) SEMANTIC_REVIEW_PACKET_SCHEMA_ID = "agent.semantic-protocols.semantic-review-packet" -SEMANTIC_EVIDENCE_GRAPH_SCHEMA_ID = "agent.semantic-protocols.semantic-evidence-graph" -SEMANTIC_ASSURANCE_CASE_SCHEMA_ID = "agent.semantic-protocols.semantic-assurance-case" SEMANTIC_AST_PATCH_SCHEMA_ID = "agent.semantic-protocols.semantic-ast-patch" SEMANTIC_AST_PATCH_RECEIPT_SCHEMA_ID = ( "agent.semantic-protocols.semantic-ast-patch-receipt" diff --git a/src/asp_python/_semantic_language_invocation.py b/src/asp_python/_semantic_language_invocation.py index 6df8d2f..54d2cb5 100644 --- a/src/asp_python/_semantic_language_invocation.py +++ b/src/asp_python/_semantic_language_invocation.py @@ -60,20 +60,6 @@ def _non_search_invocation(method: str) -> dict[str, list[str]]: "--packet", "{packet}", ], - "evidence/graph": [ - ids.PYTHON_BINARY, - "evidence", - "graph", - "--json", - "{workspace}", - ], - "evidence/analyze": [ - ids.PYTHON_BINARY, - "evidence", - "analyze", - "--json", - "{workspace}", - ], "agent/doctor": [ids.PYTHON_BINARY, "agent", "doctor", "--json"], "agent/guide": [ids.PYTHON_BINARY, "agent", "guide"], } diff --git a/src/asp_python/_source.py b/src/asp_python/_source.py index 4f49448..71ca49c 100644 --- a/src/asp_python/_source.py +++ b/src/asp_python/_source.py @@ -1,4 +1,4 @@ -"""Shared file-location helpers for deterministic harness rules.""" +"""Shared file-location helpers for deterministic ASP Python rules.""" from __future__ import annotations diff --git a/src/asp_python/_syntax.py b/src/asp_python/_syntax.py index 1644094..cb1b62f 100644 --- a/src/asp_python/_syntax.py +++ b/src/asp_python/_syntax.py @@ -21,7 +21,7 @@ @dataclass(frozen=True, slots=True) class PythonSyntaxRulePack: - """Rule pack that turns parser diagnostics into harness findings.""" + """Rule pack that turns parser diagnostics into ASP Python findings.""" pack_id: str = SYNTAX_PACK_ID diff --git a/src/asp_python/_test_layout.py b/src/asp_python/_test_layout.py index 9116749..aa0174d 100644 --- a/src/asp_python/_test_layout.py +++ b/src/asp_python/_test_layout.py @@ -1,4 +1,4 @@ -"""Pytest layout rule pack aligned with the project harness.""" +"""Pytest layout rule pack aligned with the ASP Python.""" from __future__ import annotations @@ -17,12 +17,12 @@ from python_lang_parser import PythonModuleReport - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope @dataclass(frozen=True, slots=True) class PythonTestLayoutRulePack: - """Project-level pytest layout rules aligned with the Rust unit harness gate.""" + """Project-level pytest layout rules aligned with the Rust unit ASP Python gate.""" pack_id: str = TEST_LAYOUT_PACK_ID @@ -48,7 +48,7 @@ def evaluate_project(self, project_root: Path) -> Iterable[AspPythonFinding]: def evaluate_project_resolution( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, ) -> Iterable[AspPythonFinding]: """Evaluate project-level pytest layout rules for monitored test roots.""" @@ -56,7 +56,7 @@ def evaluate_project_resolution( def evaluate_project_modules( self, - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], ) -> Iterable[AspPythonFinding]: """Evaluate pytest layout rules using parser-owned module facts.""" @@ -65,7 +65,7 @@ def evaluate_project_modules( def _test_layout_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, ) -> tuple[AspPythonFinding, ...]: diff --git a/src/asp_python/_test_layout_bloat.py b/src/asp_python/_test_layout_bloat.py index 81b8088..d141989 100644 --- a/src/asp_python/_test_layout_bloat.py +++ b/src/asp_python/_test_layout_bloat.py @@ -21,11 +21,11 @@ from python_lang_parser import PythonModuleReport, PythonSymbol - from ._model import PythonProjectHarnessScope + from ._model import AspPythonProjectScope def bloated_unit_test_findings( - scope: PythonProjectHarnessScope, + scope: AspPythonProjectScope, modules: Sequence[PythonModuleReport], pack_id: str, ) -> tuple[AspPythonFinding, ...]: diff --git a/src/asp_python/_test_layout_catalog.py b/src/asp_python/_test_layout_catalog.py index c384f7d..2bce4c9 100644 --- a/src/asp_python/_test_layout_catalog.py +++ b/src/asp_python/_test_layout_catalog.py @@ -40,7 +40,7 @@ pack_id=TEST_LAYOUT_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, title="Pytest file is scattered in tests root", - requirement="Move pytest modules under `tests/unit/` or `tests/integration/` so the project harness owns suite shape.", + requirement="Move pytest modules under `tests/unit/` or `tests/integration/` so the ASP Python owns suite shape.", labels=dict(_RULE_LABELS), ), AspPythonRule( @@ -48,7 +48,7 @@ pack_id=TEST_LAYOUT_PACK_ID, severity=PythonDiagnosticSeverity.WARNING, title="Unexpected tests root entry", - requirement="Keep tests root limited to harness configuration and owned suite directories.", + requirement="Keep tests root limited to ASP Python configuration and owned suite directories.", labels=dict(_RULE_LABELS), ), AspPythonRule( diff --git a/src/asp_python/_test_layout_config.py b/src/asp_python/_test_layout_config.py index 761c88f..247ca09 100644 --- a/src/asp_python/_test_layout_config.py +++ b/src/asp_python/_test_layout_config.py @@ -7,7 +7,7 @@ from pathlib import Path from typing import Any -TEST_LAYOUT_POLICY_CONFIG = "python-project-harness-rules.toml" +TEST_LAYOUT_POLICY_CONFIG = "asp-python-rules.toml" @dataclass(frozen=True, slots=True) diff --git a/src/asp_python/_version.py b/src/asp_python/_version.py index 9f9fbd8..61e3e3a 100644 --- a/src/asp_python/_version.py +++ b/src/asp_python/_version.py @@ -1,4 +1,4 @@ -"""Installed package identity for the Python project harness.""" +"""Installed package identity for the ASP Python.""" from __future__ import annotations diff --git a/src/asp_python/harness.py b/src/asp_python/api.py similarity index 89% rename from src/asp_python/harness.py rename to src/asp_python/api.py index 36453ee..daf43a6 100644 --- a/src/asp_python/harness.py +++ b/src/asp_python/api.py @@ -1,4 +1,4 @@ -"""Public facade for the embedded Python language project harness.""" +"""Public facade for ASP Python.""" from __future__ import annotations @@ -17,10 +17,10 @@ from ._model import ( AspPythonConfig, AspPythonFinding, + AspPythonProjectScope, AspPythonReport, AspPythonRule, - PythonLangRulePack, - PythonProjectHarnessScope, + AspPythonRulePack, PythonRulePackDescriptor, ) from ._modern_design import PythonModernDesignRulePack @@ -31,21 +31,21 @@ from ._project_policy_catalog import python_project_policy_rules from ._pytest import asp_python_test from ._render import ( - render_python_lang_harness, - render_python_lang_harness_advice, - render_python_lang_harness_json, + render_asp_python_report, + render_asp_python_report_advice, + render_asp_python_report_json, render_python_reasoning_tree, ) from ._rule_packs import ( - default_python_harness_config, - default_python_lang_rule_packs, + default_asp_python_config, + default_asp_python_rule_packs, python_rule_pack_descriptors, ) from ._runner import ( assert_asp_python_clean, - assert_python_lang_harness_clean, + assert_asp_python_paths_clean, run_asp_python, - run_python_lang_harness, + run_asp_python_paths, ) from ._semantic_language import ( python_semantic_language_registration, @@ -105,10 +105,10 @@ "AspPythonFinding", "AspPythonReport", "AspPythonRule", - "PythonLangRulePack", + "AspPythonRulePack", "PythonModernDesignRulePack", "PythonModularityRulePack", - "PythonProjectHarnessScope", + "AspPythonProjectScope", "PythonProjectPolicyRulePack", "PythonRulePackDescriptor", "PythonSyntaxRulePack", @@ -137,10 +137,10 @@ "PythonVerificationTaskKind", "PythonVerificationTaskState", "PythonVerificationWaiver", - "assert_python_lang_harness_clean", + "assert_asp_python_paths_clean", "assert_asp_python_clean", - "default_python_harness_config", - "default_python_lang_rule_packs", + "default_asp_python_config", + "default_asp_python_rule_packs", "discover_python_files", "python_agent_policy_rules", "python_modern_design_rules", @@ -161,9 +161,9 @@ "build_python_verification_task_index", "plan_python_project_verification", "plan_python_project_verification_with_config", - "render_python_lang_harness", - "render_python_lang_harness_advice", - "render_python_lang_harness_json", + "render_asp_python_report", + "render_asp_python_report_advice", + "render_asp_python_report_json", "render_python_reasoning_tree", "render_asp_python_agent_snapshot", "render_asp_python_agent_snapshot_with_config", @@ -179,7 +179,7 @@ "write_python_verification_reports", "run_cli", "run_cli_from_env", - "run_python_lang_harness", + "run_asp_python_paths", "run_asp_python", "semantic_language_registry_document", ] diff --git a/src/asp_python/harness-rules.md b/src/asp_python/asp-rules.md similarity index 93% rename from src/asp_python/harness-rules.md rename to src/asp_python/asp-rules.md index c2dddb6..3e844c3 100644 --- a/src/asp_python/harness-rules.md +++ b/src/asp_python/asp-rules.md @@ -25,8 +25,8 @@ - PY-AGENT-PROJECT-007: Requires build-system tables to declare build requirements. - PY-AGENT-PROJECT-008: Requires declared import names to resolve to parser-visible project modules. - PY-AGENT-PROJECT-009: Requires entry point targets to resolve to parser-visible project modules. -- PY-AGENT-PROJECT-010: Requires harness dev dependencies to mount an actual pytest gate. +- PY-AGENT-PROJECT-010: Requires ASP Python dev dependencies to mount an actual pytest gate. - PY-AGENT-PROJECT-011: Requires verification profile hints or agent snapshot guidance for parser-suggested owners. - PY-TEST-R001: Moves pytest modules out of the tests root and into owned suites. -- PY-TEST-R002: Keeps tests root limited to harness configuration and owned suite directories. +- PY-TEST-R002: Keeps tests root limited to ASP Python configuration and owned suite directories. - PY-TEST-R003: Splits oversized unit test leaves into focused folder-first suites. diff --git a/src/asp_python/pytest_plugin.py b/src/asp_python/pytest_plugin.py index 3ac65e6..9c411cc 100644 --- a/src/asp_python/pytest_plugin.py +++ b/src/asp_python/pytest_plugin.py @@ -1,4 +1,4 @@ -"""Pytest plugin entry point for dev-dependency harness mounting.""" +"""Pytest plugin entry point for ASP Python dev-dependency mounting.""" from __future__ import annotations @@ -14,8 +14,8 @@ SOURCE_DIR_OPTION, TEST_DIR_OPTION, add_options, + asp_python_config, blocking_severities, - harness_config, optional_tuple, ) from ._pytest_plugin_project import project_root @@ -23,7 +23,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: - """Register Python project harness pytest options.""" + """Register ASP Python pytest options.""" add_options(parser) @@ -33,27 +33,27 @@ def pytest_collection_modifyitems( config: pytest.Config, items: list[pytest.Item], ) -> None: - """Insert one explicit harness item when the plugin option is enabled.""" + """Insert one explicit ASP Python item when the plugin option is enabled.""" if not config.getoption(ENABLE_OPTION): return - item = PythonProjectHarnessItem.from_parent( + item = AspPythonPytestItem.from_parent( session, - name="python-project-harness", - nodeid="python-project-harness", + name="asp-python", + nodeid="asp-python", ) items.insert(0, item) -class PythonProjectHarnessItem(pytest.Item): - """Pytest item that runs the parser-backed project harness.""" +class AspPythonPytestItem(pytest.Item): + """Pytest item that runs the parser-backed ASP Python.""" def runtest(self) -> None: - """Run the configured project harness and raise a compact assertion.""" + """Run the configured ASP Python and raise a compact assertion.""" assert_asp_python_clean( project_root(self.config), - config=harness_config(self.config), + config=asp_python_config(self.config), severities=blocking_severities(self.config), include_tests=not self.config.getoption(NO_TESTS_OPTION), source_dir_names=optional_tuple(self.config.getoption(SOURCE_DIR_OPTION)), @@ -67,7 +67,7 @@ def repr_failure( excinfo: pytest.ExceptionInfo[BaseException], style: str | None = None, ) -> str: - """Return compact harness assertion text without pytest traceback noise.""" + """Return compact ASP Python assertion text without pytest traceback noise.""" if isinstance(excinfo.value, AssertionError): return str(excinfo.value) @@ -76,4 +76,4 @@ def repr_failure( def reportinfo(self) -> tuple[Path, int, str]: """Return stable report metadata for pytest output.""" - return (Path("python-project-harness"), 0, "python project harness") + return (Path("asp-python"), 0, "ASP Python") diff --git a/src/asp_python/verification/__init__.py b/src/asp_python/verification/__init__.py index 762ab3a..afffeb6 100644 --- a/src/asp_python/verification/__init__.py +++ b/src/asp_python/verification/__init__.py @@ -1,4 +1,4 @@ -"""Public verification planning surface for Python project harnesses.""" +"""Public verification planning surface for ASP Pythones.""" from __future__ import annotations diff --git a/src/asp_python/verification/facts.py b/src/asp_python/verification/facts.py index 6e8a818..aef8d48 100644 --- a/src/asp_python/verification/facts.py +++ b/src/asp_python/verification/facts.py @@ -32,7 +32,7 @@ def verification_reasoning_tree_facts( report: AspPythonReport, ) -> PythonReasoningTreeFacts: - """Return parser-owned reasoning-tree facts for one harness report.""" + """Return parser-owned reasoning-tree facts for one ASP Python report.""" scope = report.project_resolution return python_reasoning_tree_facts( @@ -44,7 +44,7 @@ def verification_reasoning_tree_facts( def verification_project_root(report: AspPythonReport) -> Path: - """Return the project root represented by a harness report.""" + """Return the project root represented by a ASP Python report.""" if report.project_resolution is not None: return report.project_resolution.project_root diff --git a/src/asp_python/verification/model.py b/src/asp_python/verification/model.py index 69cf409..67e274e 100644 --- a/src/asp_python/verification/model.py +++ b/src/asp_python/verification/model.py @@ -1,4 +1,4 @@ -"""Library-first verification planning model for Python project harnesses.""" +"""Library-first verification planning model for ASP Pythones.""" from __future__ import annotations @@ -19,7 +19,7 @@ class PythonOwnerResponsibility(StrEnum): class PythonVerificationTaskKind(StrEnum): - """External task classes planned by the harness but executed by skills.""" + """External task classes planned by ASP Python but executed by skills.""" PERFORMANCE = "performance" SECURITY = "security" @@ -341,7 +341,7 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) class PythonVerificationTask: - """One external verification obligation planned by the harness.""" + """One external verification obligation planned by ASP Python.""" owner_path: str owner_namespace: tuple[str, ...] @@ -415,7 +415,7 @@ def to_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) class PythonVerificationPolicy: - """Configurable verification policy attached to a harness config.""" + """Configurable verification policy attached to a ASP Python config.""" profile_hints: tuple[PythonVerificationProfileHint, ...] = () dependency_signals: tuple[PythonVerificationDependencySignal, ...] = () diff --git a/src/asp_python/verification/planner.py b/src/asp_python/verification/planner.py index a96eb5f..70e5414 100644 --- a/src/asp_python/verification/planner.py +++ b/src/asp_python/verification/planner.py @@ -1,4 +1,4 @@ -"""Parser-backed verification planner for Python project harnesses.""" +"""Parser-backed verification planner for ASP Pythones.""" from __future__ import annotations @@ -8,7 +8,7 @@ from .._model import AspPythonConfig, AspPythonReport from .._render import _render_display_path -from .._rule_packs import resolve_project_harness_config +from .._rule_packs import resolve_asp_python_project_config from .._runner import run_asp_python from .facts import ( matched_dependency_signals, @@ -52,10 +52,10 @@ def plan_python_project_verification_with_config( project_root: str | Path, config: AspPythonConfig | None, ) -> PythonVerificationPlan: - """Plan verification obligations with an explicit harness config.""" + """Plan verification obligations with an explicit ASP Python config.""" root = Path(project_root) - selected_config = resolve_project_harness_config(root, config, rule_packs=None) + selected_config = resolve_asp_python_project_config(root, config, rule_packs=None) report = run_asp_python(root, config=selected_config) return plan_python_project_verification_report(report, selected_config) @@ -64,7 +64,7 @@ def plan_python_project_verification_report( report: AspPythonReport, config: AspPythonConfig, ) -> PythonVerificationPlan: - """Plan verification obligations from an already-built harness report.""" + """Plan verification obligations from an already-built ASP Python report.""" project_root = verification_project_root(report) policy = config.verification_policy diff --git a/src/asp_python/verification/profile_index.py b/src/asp_python/verification/profile_index.py index f007e53..40c2dcd 100644 --- a/src/asp_python/verification/profile_index.py +++ b/src/asp_python/verification/profile_index.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING from .._render import _render_display_path -from .._rule_packs import resolve_project_harness_config +from .._rule_packs import resolve_asp_python_project_config from .._runner import run_asp_python from .facts import ( entry_point_owner_paths, @@ -47,10 +47,10 @@ def build_python_verification_profile_index_with_config( project_root: str | Path, config: AspPythonConfig | None, ) -> PythonVerificationProfileIndex: - """Build profile candidates with an explicit harness config.""" + """Build profile candidates with an explicit ASP Python config.""" root = Path(project_root) - selected_config = resolve_project_harness_config(root, config, rule_packs=None) + selected_config = resolve_asp_python_project_config(root, config, rule_packs=None) report = run_asp_python(root, config=selected_config) return build_python_verification_profile_index_report(report, selected_config) @@ -59,7 +59,7 @@ def build_python_verification_profile_index_report( report: AspPythonReport, config: AspPythonConfig, ) -> PythonVerificationProfileIndex: - """Build profile candidates from an already-built harness report.""" + """Build profile candidates from an already-built ASP Python report.""" project_root = verification_project_root(report) policy = config.verification_policy diff --git a/src/python_lang_parser/_project_model.py b/src/python_lang_parser/_project_model.py index dbcc427..01a1c00 100644 --- a/src/python_lang_parser/_project_model.py +++ b/src/python_lang_parser/_project_model.py @@ -106,7 +106,7 @@ class PythonPytestOptions: def enables_asp_python(self) -> bool: """Return whether pytest addopts mounts the project harness plugin.""" - return "--python-project-harness" in self.addopts + return "--asp-python" in self.addopts def to_dict(self) -> dict[str, object]: """Return a JSON-compatible representation.""" diff --git a/tests/unit/harness/agent_readability/test_native_idiom_binding_state.py b/tests/unit/asp_python/agent_readability/test_native_idiom_binding_state.py similarity index 90% rename from tests/unit/harness/agent_readability/test_native_idiom_binding_state.py rename to tests/unit/asp_python/agent_readability/test_native_idiom_binding_state.py index bbe51e1..fccd1a2 100644 --- a/tests/unit/harness/agent_readability/test_native_idiom_binding_state.py +++ b/tests/unit/asp_python/agent_readability/test_native_idiom_binding_state.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING -from asp_python import run_python_lang_harness +from asp_python import run_asp_python_paths if TYPE_CHECKING: from pathlib import Path @@ -35,7 +35,7 @@ def collect(values: list[int]) -> tuple[list[int], dict[int, int], int]: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert not any( finding.rule_id == "PY-AGENT-POLICY-011" for finding in report.findings diff --git a/tests/unit/harness/harness-rules.generated.md b/tests/unit/asp_python/asp-rules.generated.md similarity index 91% rename from tests/unit/harness/harness-rules.generated.md rename to tests/unit/asp_python/asp-rules.generated.md index 266d613..1c1faa8 100644 --- a/tests/unit/harness/harness-rules.generated.md +++ b/tests/unit/asp_python/asp-rules.generated.md @@ -1,8 +1,8 @@ # asp-python -## Harness Rules +## ASP Python Rules -Generated from embedded `src/asp_python/harness-rules.md`. +Generated from embedded `src/asp_python/asp-rules.md`. - **PY-AGENT-POLICY-001**: Requires library modules to declare concise intent docstrings for agent search and repair. - **PY-AGENT-POLICY-002**: Requires public callable boundaries to carry type annotations for native syntax reasoning. @@ -31,8 +31,8 @@ Generated from embedded `src/asp_python/harness-rules.md`. - **PY-AGENT-PROJECT-007**: Requires build-system tables to declare build requirements. - **PY-AGENT-PROJECT-008**: Requires declared import names to resolve to parser-visible project modules. - **PY-AGENT-PROJECT-009**: Requires entry point targets to resolve to parser-visible project modules. -- **PY-AGENT-PROJECT-010**: Requires harness dev dependencies to mount an actual pytest gate. +- **PY-AGENT-PROJECT-010**: Requires ASP Python dev dependencies to mount an actual pytest gate. - **PY-AGENT-PROJECT-011**: Requires verification profile hints or agent snapshot guidance for parser-suggested owners. - **PY-TEST-R001**: Moves pytest modules out of the tests root and into owned suites. -- **PY-TEST-R002**: Keeps tests root limited to harness configuration and owned suite directories. +- **PY-TEST-R002**: Keeps tests root limited to ASP Python configuration and owned suite directories. - **PY-TEST-R003**: Splits oversized unit test leaves into focused folder-first suites. diff --git a/tests/unit/harness/project_policy/test_catalog.py b/tests/unit/asp_python/project_policy/test_catalog.py similarity index 100% rename from tests/unit/harness/project_policy/test_catalog.py rename to tests/unit/asp_python/project_policy/test_catalog.py diff --git a/tests/unit/harness/project_policy/test_layout.py b/tests/unit/asp_python/project_policy/test_layout.py similarity index 100% rename from tests/unit/harness/project_policy/test_layout.py rename to tests/unit/asp_python/project_policy/test_layout.py diff --git a/tests/unit/harness/project_policy/test_metadata.py b/tests/unit/asp_python/project_policy/test_metadata.py similarity index 100% rename from tests/unit/harness/project_policy/test_metadata.py rename to tests/unit/asp_python/project_policy/test_metadata.py diff --git a/tests/unit/harness/project_policy/test_metadata_policy.py b/tests/unit/asp_python/project_policy/test_metadata_policy.py similarity index 97% rename from tests/unit/harness/project_policy/test_metadata_policy.py rename to tests/unit/asp_python/project_policy/test_metadata_policy.py index c6adeca..1d85f72 100644 --- a/tests/unit/harness/project_policy/test_metadata_policy.py +++ b/tests/unit/asp_python/project_policy/test_metadata_policy.py @@ -134,7 +134,7 @@ def test_project_policy_accepts_pytest_addopts_gate(tmp_path: Path) -> None: ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] """, ) @@ -161,7 +161,7 @@ def test_project_policy_accepts_explicit_pytest_helper_gate(tmp_path: Path) -> N ) tests = tmp_path / "tests" / "unit" tests.mkdir(parents=True) - (tests / "test_harness_policy.py").write_text( + (tests / "test_asp_python_policy.py").write_text( "from asp_python.pytest import asp_python_test\n" "test_asp_python_policy = asp_python_test()\n", encoding="utf-8", @@ -190,7 +190,7 @@ def test_project_policy_advises_missing_verification_profile_hints( ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] """, ) package = tmp_path / "src" / "pkg" @@ -232,7 +232,7 @@ def test_project_policy_accepts_configured_verification_profile_hint( ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] [tool.asp-python.verification] profile_hints = [ diff --git a/tests/unit/harness/project_policy/test_typed_packages.py b/tests/unit/asp_python/project_policy/test_typed_packages.py similarity index 100% rename from tests/unit/harness/project_policy/test_typed_packages.py rename to tests/unit/asp_python/project_policy/test_typed_packages.py diff --git a/tests/unit/harness/provider_runtime_live_support.py b/tests/unit/asp_python/provider_runtime_live_support.py similarity index 100% rename from tests/unit/harness/provider_runtime_live_support.py rename to tests/unit/asp_python/provider_runtime_live_support.py diff --git a/tests/unit/harness/python_project_fixture.py b/tests/unit/asp_python/python_project_fixture.py similarity index 100% rename from tests/unit/harness/python_project_fixture.py rename to tests/unit/asp_python/python_project_fixture.py diff --git a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/benchmark.toml b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/benchmark.toml similarity index 92% rename from tests/unit/harness/scenarios/software_criteria/control_flow_v1/benchmark.toml rename to tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/benchmark.toml index 5817178..0ef3819 100644 --- a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/benchmark.toml +++ b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/benchmark.toml @@ -1,5 +1,5 @@ harness = "pytest" -test = "tests/unit/harness/test_software_criterion_snapshots.py" +test = "tests/unit/asp_python/test_software_criterion_snapshots.py" snapshot = "software_criteria/control_flow_v1" phase = "cold" target_total = "25ms" diff --git a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/expect/findings.json b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/expect/findings.json similarity index 100% rename from tests/unit/harness/scenarios/software_criteria/control_flow_v1/expect/findings.json rename to tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/expect/findings.json diff --git a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/inputs/criterion.py b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/inputs/criterion.py similarity index 100% rename from tests/unit/harness/scenarios/software_criteria/control_flow_v1/inputs/criterion.py rename to tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/inputs/criterion.py diff --git a/tests/unit/harness/scenarios/software_criteria/control_flow_v1/scenario.toml b/tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/scenario.toml similarity index 100% rename from tests/unit/harness/scenarios/software_criteria/control_flow_v1/scenario.toml rename to tests/unit/asp_python/scenarios/software_criteria/control_flow_v1/scenario.toml diff --git a/tests/unit/harness/snapshot_support.py b/tests/unit/asp_python/snapshot_support.py similarity index 94% rename from tests/unit/harness/snapshot_support.py rename to tests/unit/asp_python/snapshot_support.py index 72c9765..ca6a089 100644 --- a/tests/unit/harness/snapshot_support.py +++ b/tests/unit/asp_python/snapshot_support.py @@ -1,4 +1,4 @@ -"""Snapshot assertion helpers for harness policy and renderer tests.""" +"""Snapshot assertion helpers for ASP Python policy and renderer tests.""" from __future__ import annotations diff --git a/tests/unit/harness/test_agent_algorithm_policy.py b/tests/unit/asp_python/test_agent_algorithm_policy.py similarity index 87% rename from tests/unit/harness/test_agent_algorithm_policy.py rename to tests/unit/asp_python/test_agent_algorithm_policy.py index 31c8e86..7647dc8 100644 --- a/tests/unit/harness/test_agent_algorithm_policy.py +++ b/tests/unit/asp_python/test_agent_algorithm_policy.py @@ -6,8 +6,8 @@ from snapshot_support import assert_snapshot, normalize_temp_root from asp_python import ( - render_python_lang_harness, - run_python_lang_harness, + render_asp_python_report, + run_asp_python_paths, ) if TYPE_CHECKING: @@ -39,7 +39,7 @@ def classify(kind: str, rows: list[object]) -> int: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) filtered = replace( report, findings=tuple( @@ -48,7 +48,7 @@ def classify(kind: str, rows: list[object]) -> int: if finding.rule_id == "PY-AGENT-POLICY-009" ), ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), tmp_path) + rendered = normalize_temp_root(render_asp_python_report(filtered), tmp_path) assert filtered.findings, "expected PY-AGENT-POLICY-009 finding" assert ( @@ -58,7 +58,7 @@ def classify(kind: str, rows: list[object]) -> int: assert_snapshot( "unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape", rendered, - source="tests/unit/harness/test_agent_algorithm_policy.py", + source="tests/unit/asp_python/test_agent_algorithm_policy.py", ) @@ -80,7 +80,7 @@ def classify(enabled: bool, ready: bool, valid: bool, active: bool) -> int: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) findings = tuple( finding @@ -115,7 +115,7 @@ def classify(groups: list[list[int]]) -> int: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) findings = tuple( finding @@ -149,7 +149,7 @@ def classify(kind: str) -> int: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert not any( finding.rule_id == "PY-AGENT-POLICY-009" for finding in report.findings @@ -160,7 +160,7 @@ def test_py_agent_r010_function_compactness_snapshot(tmp_path: Path) -> None: source = tmp_path / "service.py" source.write_text(_broad_linear_function_source(), encoding="utf-8") - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) filtered = replace( report, findings=tuple( @@ -169,7 +169,7 @@ def test_py_agent_r010_function_compactness_snapshot(tmp_path: Path) -> None: if finding.rule_id == "PY-AGENT-POLICY-010" ), ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), tmp_path) + rendered = normalize_temp_root(render_asp_python_report(filtered), tmp_path) assert filtered.findings, "expected PY-AGENT-POLICY-010 finding" assert ( @@ -179,7 +179,7 @@ def test_py_agent_r010_function_compactness_snapshot(tmp_path: Path) -> None: assert_snapshot( "unit_test__agent_policy_snapshot__py_agent_r010_function_compactness", rendered, - source="tests/unit/harness/test_agent_algorithm_policy.py", + source="tests/unit/asp_python/test_agent_algorithm_policy.py", ) @@ -216,7 +216,7 @@ def has_admin(values: list[str]) -> bool: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) filtered = replace( report, findings=tuple( @@ -225,7 +225,7 @@ def has_admin(values: list[str]) -> bool: if finding.rule_id == "PY-AGENT-POLICY-011" ), ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), tmp_path) + rendered = normalize_temp_root(render_asp_python_report(filtered), tmp_path) assert filtered.findings, "expected PY-AGENT-POLICY-011 finding" assert ( @@ -235,7 +235,7 @@ def has_admin(values: list[str]) -> bool: assert_snapshot( "unit_test__agent_policy_snapshot__py_agent_r011_native_idiom", rendered, - source="tests/unit/harness/test_agent_algorithm_policy.py", + source="tests/unit/asp_python/test_agent_algorithm_policy.py", ) @@ -258,7 +258,7 @@ def __repr__(self) -> str: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) filtered = replace( report, findings=tuple( @@ -267,13 +267,13 @@ def __repr__(self) -> str: if finding.rule_id == "PY-AGENT-POLICY-012" ), ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), tmp_path) + rendered = normalize_temp_root(render_asp_python_report(filtered), tmp_path) assert filtered.findings, "expected PY-AGENT-POLICY-012 finding" assert_snapshot( "unit_test__agent_policy_snapshot__py_agent_r012_type_shape", rendered, - source="tests/unit/harness/test_agent_algorithm_policy.py", + source="tests/unit/asp_python/test_agent_algorithm_policy.py", ) @@ -295,7 +295,7 @@ class CustomerRecord: encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert not any( finding.rule_id == "PY-AGENT-POLICY-012" for finding in report.findings diff --git a/tests/unit/harness/test_agent_policy.py b/tests/unit/asp_python/test_agent_policy.py similarity index 94% rename from tests/unit/harness/test_agent_policy.py rename to tests/unit/asp_python/test_agent_policy.py index e3eb2de..9fc460c 100644 --- a/tests/unit/harness/test_agent_policy.py +++ b/tests/unit/asp_python/test_agent_policy.py @@ -6,10 +6,10 @@ from asp_python import ( PythonAgentPolicyRulePack, python_agent_policy_rules, - render_python_lang_harness, - render_python_lang_harness_advice, + render_asp_python_report, + render_asp_python_report_advice, run_asp_python, - run_python_lang_harness, + run_asp_python_paths, ) from python_lang_parser import PythonDiagnosticSeverity @@ -21,12 +21,12 @@ def test_agent_policy_reports_compact_repairable_snapshot(tmp_path: Path) -> Non source = tmp_path / "service.py" source.write_text("def build(value):\n return value\n", encoding="utf-8") - report = run_python_lang_harness([source]) - output = render_python_lang_harness_advice(report) + report = run_asp_python_paths([source]) + output = render_asp_python_report_advice(report) output = output.replace(str(source), "$TMP/service.py") assert report.is_clean - rendered = render_python_lang_harness(report) + rendered = render_asp_python_report(report) assert rendered.startswith("[advice]\n[PY-AGENT-POLICY-001]") assert "[ok]" not in rendered assert ( @@ -55,10 +55,10 @@ def test_agent_policy_accepts_documented_annotated_public_callable( encoding="utf-8", ) - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert report.is_clean - assert render_python_lang_harness_advice(report) == "" + assert render_asp_python_report_advice(report) == "" def test_agent_policy_skips_test_modules(tmp_path: Path) -> None: @@ -67,7 +67,7 @@ def test_agent_policy_skips_test_modules(tmp_path: Path) -> None: source = tests / "test_service.py" source.write_text("def test_value():\n assert True\n", encoding="utf-8") - report = run_python_lang_harness([tmp_path]) + report = run_asp_python_paths([tmp_path]) assert report.is_clean @@ -241,7 +241,7 @@ def test_agent_policy_advice_can_be_promoted_to_blocking( source = tmp_path / "service.py" source.write_text("def build(value):\n return value\n", encoding="utf-8") - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) assert report.is_clean assert [finding.rule_id for finding in report.advisory_findings()] == [ @@ -259,7 +259,7 @@ def test_agent_policy_advice_can_be_promoted_to_blocking( "PY-AGENT-POLICY-002", ] promoted = replace(report, blocking_rule_ids=frozenset({"PY-AGENT-POLICY-001"})) - advice = render_python_lang_harness_advice(promoted) + advice = render_asp_python_report_advice(promoted) assert "[PY-AGENT-POLICY-001]" not in advice assert advice.startswith("[PY-AGENT-POLICY-002]") diff --git a/tests/unit/harness/test_agent_policy_snapshots.py b/tests/unit/asp_python/test_agent_policy_snapshots.py similarity index 95% rename from tests/unit/harness/test_agent_policy_snapshots.py rename to tests/unit/asp_python/test_agent_policy_snapshots.py index 2b8bbcb..fcf0629 100644 --- a/tests/unit/harness/test_agent_policy_snapshots.py +++ b/tests/unit/asp_python/test_agent_policy_snapshots.py @@ -6,9 +6,9 @@ from snapshot_support import assert_snapshot, normalize_temp_root from asp_python import ( - render_python_lang_harness, + render_asp_python_report, run_asp_python, - run_python_lang_harness, + run_asp_python_paths, ) if TYPE_CHECKING: @@ -157,7 +157,7 @@ def _assert_lang_snapshot( rule_id: str, snapshot_name: str, ) -> None: - report = run_python_lang_harness(paths) + report = run_asp_python_paths(paths) _assert_filtered_snapshot(root, report, rule_id, snapshot_name) @@ -183,9 +183,9 @@ def _assert_filtered_snapshot( ), ) assert filtered.findings, f"expected at least one {rule_id} finding" - rendered = normalize_temp_root(render_python_lang_harness(filtered), root) + rendered = normalize_temp_root(render_asp_python_report(filtered), root) assert_snapshot( f"unit_test__agent_policy_snapshot__{snapshot_name}", rendered, - source="tests/unit/harness/test_agent_policy_snapshots.py", + source="tests/unit/asp_python/test_agent_policy_snapshots.py", ) diff --git a/tests/unit/harness/test_harness_rules.py b/tests/unit/asp_python/test_asp_rules.py similarity index 57% rename from tests/unit/harness/test_harness_rules.py rename to tests/unit/asp_python/test_asp_rules.py index 19b82dd..90b4e54 100644 --- a/tests/unit/harness/test_harness_rules.py +++ b/tests/unit/asp_python/test_asp_rules.py @@ -5,10 +5,10 @@ from pathlib import Path from asp_python._agent_policy_catalog import python_agent_policy_rules -from asp_python._harness_rules import ( - python_harness_rules_markdown, - render_python_harness_rules_markdown, - write_python_harness_rules_to_unit_tests, +from asp_python._asp_rules import ( + asp_python_rules_markdown, + render_asp_python_rules_markdown, + write_asp_python_rules_to_unit_tests, ) from asp_python._modern_design_catalog import ( python_modern_design_rules, @@ -20,9 +20,9 @@ from asp_python._test_layout_catalog import python_test_layout_rules -def _harness_rules_rule_ids() -> list[str]: +def _asp_rules_rule_ids() -> list[str]: rule_ids: list[str] = [] - for line in python_harness_rules_markdown().splitlines(): + for line in asp_python_rules_markdown().splitlines(): rule_id, _ = line.removeprefix("- ").split(": ", 1) rule_ids.append(rule_id) return rule_ids @@ -39,9 +39,9 @@ def _catalog_rule_ids() -> list[str]: return [rule.rule_id for rule in rules] -def test_harness_rules_markdown_is_plain_rule_id_list() -> None: +def test_asp_rules_markdown_is_plain_rule_id_list() -> None: count = 0 - for index, line in enumerate(python_harness_rules_markdown().splitlines(), start=1): + for index, line in enumerate(asp_python_rules_markdown().splitlines(), start=1): assert line.startswith("- "), index rule_id, sentence = line.removeprefix("- ").split(": ", 1) @@ -62,24 +62,22 @@ def test_harness_rules_markdown_is_plain_rule_id_list() -> None: assert count == 32 -def test_harness_rules_ids_match_rule_catalog() -> None: - assert sorted(_harness_rules_rule_ids()) == sorted(_catalog_rule_ids()) +def test_asp_rules_ids_match_rule_catalog() -> None: + assert sorted(_asp_rules_rule_ids()) == sorted(_catalog_rule_ids()) -def test_generated_harness_rules_matches_unit_fixture() -> None: +def test_generated_asp_rules_matches_unit_fixture() -> None: unit_dir = Path(__file__).resolve().parent - fixture = unit_dir / "harness-rules.generated.md" + fixture = unit_dir / "asp-rules.generated.md" if os.environ.get("UPDATE_HARNESS_RULES"): - write_python_harness_rules_to_unit_tests(unit_dir) + write_asp_python_rules_to_unit_tests(unit_dir) - assert fixture.read_text(encoding="utf-8") == render_python_harness_rules_markdown() + assert fixture.read_text(encoding="utf-8") == render_asp_python_rules_markdown() -def test_harness_rules_writer_targets_requested_unit_dir() -> None: +def test_asp_rules_writer_targets_requested_unit_dir() -> None: with tempfile.TemporaryDirectory() as directory: - output = write_python_harness_rules_to_unit_tests(Path(directory)) + output = write_asp_python_rules_to_unit_tests(Path(directory)) - assert output == Path(directory) / "harness-rules.generated.md" - assert ( - output.read_text(encoding="utf-8") == render_python_harness_rules_markdown() - ) + assert output == Path(directory) / "asp-rules.generated.md" + assert output.read_text(encoding="utf-8") == render_asp_python_rules_markdown() diff --git a/tests/unit/harness/test_cli.py b/tests/unit/asp_python/test_cli.py similarity index 97% rename from tests/unit/harness/test_cli.py rename to tests/unit/asp_python/test_cli.py index fc74662..fc11467 100644 --- a/tests/unit/harness/test_cli.py +++ b/tests/unit/asp_python/test_cli.py @@ -19,7 +19,7 @@ def test_cli_help_advertises_the_current_provider_protocol() -> None: assert "asp python search playbook " in rendered assert "asp-python search " not in rendered assert "asp python query --selector" in rendered - assert "asp-python evidence graph" in rendered + assert "asp-python evidence" not in rendered assert "asp-python agent doctor" in rendered diff --git a/tests/unit/harness/test_dependency_topology.py b/tests/unit/asp_python/test_dependency_topology.py similarity index 100% rename from tests/unit/harness/test_dependency_topology.py rename to tests/unit/asp_python/test_dependency_topology.py diff --git a/tests/unit/harness/test_dev_command_log.py b/tests/unit/asp_python/test_dev_command_log.py similarity index 100% rename from tests/unit/harness/test_dev_command_log.py rename to tests/unit/asp_python/test_dev_command_log.py diff --git a/tests/unit/harness/test_exact_source_projection.py b/tests/unit/asp_python/test_exact_source_projection.py similarity index 100% rename from tests/unit/harness/test_exact_source_projection.py rename to tests/unit/asp_python/test_exact_source_projection.py diff --git a/tests/unit/harness/test_modern_design.py b/tests/unit/asp_python/test_modern_design.py similarity index 88% rename from tests/unit/harness/test_modern_design.py rename to tests/unit/asp_python/test_modern_design.py index d9a7f86..b2a8c8b 100644 --- a/tests/unit/harness/test_modern_design.py +++ b/tests/unit/asp_python/test_modern_design.py @@ -5,8 +5,8 @@ from asp_python import ( PythonModernDesignRulePack, python_modern_design_rules, - render_python_lang_harness, - run_python_lang_harness, + render_asp_python_report, + run_asp_python_paths, ) from python_lang_parser import PythonDiagnosticSeverity @@ -23,8 +23,8 @@ def test_modern_design_rule_pack_reports_numbered_rules_in_compact_snapshot( encoding="utf-8", ) - output = render_python_lang_harness( - run_python_lang_harness([source], rule_packs=(PythonModernDesignRulePack(),)) + output = render_asp_python_report( + run_asp_python_paths([source], rule_packs=(PythonModernDesignRulePack(),)) ) output = output.replace(str(source), "$TMP/module.py") @@ -60,9 +60,7 @@ def test_modern_design_rule_pack_requires_all_for_package_facade( init_file.write_text("from .api import Runner\n", encoding="utf-8") (package / "api.py").write_text("class Runner:\n pass\n", encoding="utf-8") - report = run_python_lang_harness( - [package], rule_packs=(PythonModernDesignRulePack(),) - ) + report = run_asp_python_paths([package], rule_packs=(PythonModernDesignRulePack(),)) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -80,9 +78,7 @@ def test_modern_design_rule_pack_accepts_explicit_facade_all(tmp_path: Path) -> ) (package / "api.py").write_text("class Runner:\n pass\n", encoding="utf-8") - report = run_python_lang_harness( - [package], rule_packs=(PythonModernDesignRulePack(),) - ) + report = run_asp_python_paths([package], rule_packs=(PythonModernDesignRulePack(),)) assert report.is_clean @@ -97,9 +93,7 @@ def test_modern_design_rule_pack_rejects_dynamic_facade_all(tmp_path: Path) -> N ) (package / "api.py").write_text("class Runner:\n pass\n", encoding="utf-8") - report = run_python_lang_harness( - [package], rule_packs=(PythonModernDesignRulePack(),) - ) + report = run_asp_python_paths([package], rule_packs=(PythonModernDesignRulePack(),)) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -118,9 +112,7 @@ def test_modern_design_rule_pack_rejects_augmented_facade_all(tmp_path: Path) -> ) (package / "api.py").write_text("class Runner:\n pass\n", encoding="utf-8") - report = run_python_lang_harness( - [package], rule_packs=(PythonModernDesignRulePack(),) - ) + report = run_asp_python_paths([package], rule_packs=(PythonModernDesignRulePack(),)) assert [ (finding.rule_id, finding.location.path) for finding in report.findings @@ -138,7 +130,7 @@ def test_modern_design_rule_pack_skips_prints_in_tests(tmp_path: Path) -> None: encoding="utf-8", ) - report = run_python_lang_harness( + report = run_asp_python_paths( [tmp_path], rule_packs=(PythonModernDesignRulePack(),) ) diff --git a/tests/unit/harness/test_modularity_catalog.py b/tests/unit/asp_python/test_modularity_catalog.py similarity index 100% rename from tests/unit/harness/test_modularity_catalog.py rename to tests/unit/asp_python/test_modularity_catalog.py diff --git a/tests/unit/harness/test_parser_boundary_contract.py b/tests/unit/asp_python/test_parser_boundary_contract.py similarity index 89% rename from tests/unit/harness/test_parser_boundary_contract.py rename to tests/unit/asp_python/test_parser_boundary_contract.py index 0532ae1..e7c7306 100644 --- a/tests/unit/harness/test_parser_boundary_contract.py +++ b/tests/unit/asp_python/test_parser_boundary_contract.py @@ -9,12 +9,12 @@ ) -def test_harness_policy_does_not_parse_python_source_directly() -> None: - harness_sources = sorted( +def test_asp_python_policy_does_not_parse_python_source_directly() -> None: + asp_python_sources = sorted( (_PROJECT_ROOT / "src" / "asp_python").glob("*_policy*.py") ) - for path in harness_sources: + for path in asp_python_sources: source = path.read_text(encoding="utf-8") assert "import ast" not in source, path assert "import tokenize" not in source, path @@ -22,9 +22,9 @@ def test_harness_policy_does_not_parse_python_source_directly() -> None: assert "tokenize." not in source, path -def test_harness_semantic_roles_use_parser_symbol_helpers() -> None: - harness_sources = sorted((_PROJECT_ROOT / "src" / "asp_python").rglob("*.py")) - for path in harness_sources: +def test_asp_python_semantic_roles_use_parser_symbol_helpers() -> None: + asp_python_sources = sorted((_PROJECT_ROOT / "src" / "asp_python").rglob("*.py")) + for path in asp_python_sources: if path.name == "__init__.py": continue source = path.read_text(encoding="utf-8") @@ -98,10 +98,10 @@ def test_test_layout_python_source_lines_use_parser_reports() -> None: assert "return report.source_line(line)" in entries -def test_harness_pyproject_metadata_comes_from_parser_boundary() -> None: - harness_sources = sorted((_PROJECT_ROOT / "src" / "asp_python").rglob("*.py")) +def test_asp_python_pyproject_metadata_comes_from_parser_boundary() -> None: + asp_python_sources = sorted((_PROJECT_ROOT / "src" / "asp_python").rglob("*.py")) - for path in harness_sources: + for path in asp_python_sources: if path.name in { "_project_config.py", "_project_resolution_candidates.py", diff --git a/tests/unit/harness/test_policy_contract.py b/tests/unit/asp_python/test_policy_contract.py similarity index 97% rename from tests/unit/harness/test_policy_contract.py rename to tests/unit/asp_python/test_policy_contract.py index 234ef05..cfb0cfe 100644 --- a/tests/unit/harness/test_policy_contract.py +++ b/tests/unit/asp_python/test_policy_contract.py @@ -3,7 +3,7 @@ from pathlib import Path from asp_python import ( - default_python_harness_config, + default_asp_python_config, python_agent_policy_rules, python_modern_design_rules, python_modularity_rules, @@ -11,7 +11,7 @@ python_rule_pack_descriptors, python_syntax_rules, python_test_layout_rules, - render_python_lang_harness, + render_asp_python_report, run_asp_python, ) from python_lang_parser import PythonDiagnosticSeverity @@ -73,7 +73,7 @@ def test_default_policy_blocks_only_warning_and_error() -> None: - config = default_python_harness_config() + config = default_asp_python_config() assert config.blocking_severities == { PythonDiagnosticSeverity.ERROR, @@ -241,9 +241,9 @@ def test_agent_facing_snapshots_avoid_redundant_render_preambles() -> None: assert line not in tree_text -def test_project_is_clean_under_its_own_harness() -> None: +def test_project_is_clean_under_its_own_asp_python() -> None: report = run_asp_python(_PROJECT_ROOT) - rendered = render_python_lang_harness(report) + rendered = render_asp_python_report(report) assert report.is_clean, rendered assert "[fail]" not in rendered diff --git a/tests/unit/harness/test_policy_snapshots.py b/tests/unit/asp_python/test_policy_snapshots.py similarity index 98% rename from tests/unit/harness/test_policy_snapshots.py rename to tests/unit/asp_python/test_policy_snapshots.py index 4decaf6..9d14ea1 100644 --- a/tests/unit/harness/test_policy_snapshots.py +++ b/tests/unit/asp_python/test_policy_snapshots.py @@ -6,7 +6,7 @@ from snapshot_support import assert_snapshot, normalize_temp_root from asp_python import ( - render_python_lang_harness, + render_asp_python_report, run_asp_python, ) @@ -295,7 +295,7 @@ def test_py_proj_r011_verification_profile_snapshot(tmp_path: Path) -> None: ] [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] """.lstrip(), encoding="utf-8", ) @@ -342,11 +342,11 @@ def _assert_project_snapshot(root: Path, rule_id: str, snapshot_name: str) -> No assert len(filtered.findings) == 1, ( f"expected one {rule_id} finding, got {filtered.findings!r}" ) - rendered = normalize_temp_root(render_python_lang_harness(filtered), root) + rendered = normalize_temp_root(render_asp_python_report(filtered), root) assert_snapshot( f"unit_test__policy_snapshot__{snapshot_name}", rendered, - source="tests/unit/harness/test_policy_snapshots.py", + source="tests/unit/asp_python/test_policy_snapshots.py", ) diff --git a/tests/unit/harness/test_project_api.py b/tests/unit/asp_python/test_project_api.py similarity index 96% rename from tests/unit/harness/test_project_api.py rename to tests/unit/asp_python/test_project_api.py index 352c9d4..6d7f5f2 100644 --- a/tests/unit/harness/test_project_api.py +++ b/tests/unit/asp_python/test_project_api.py @@ -180,7 +180,7 @@ def test_assert_asp_python_clean_blocks_for_pytest(tmp_path: Path) -> None: except AssertionError as error: message = str(error) else: - raise AssertionError("project harness should block package warnings") + raise AssertionError("ASP Python should block package warnings") assert "rule=PY-MOD-R002 severity=warning" in message assert "|message Library module uses bare print" in message @@ -188,7 +188,7 @@ def test_assert_asp_python_clean_blocks_for_pytest(tmp_path: Path) -> None: assert str(source) not in message -def test_project_harness_blocks_root_pytest_files(tmp_path: Path) -> None: +def test_asp_python_blocks_root_pytest_files(tmp_path: Path) -> None: tests = tmp_path / "tests" tests.mkdir() source = tests / "test_scattered.py" @@ -201,7 +201,7 @@ def test_project_harness_blocks_root_pytest_files(tmp_path: Path) -> None: except AssertionError as error: message = str(error) else: - raise AssertionError("project harness should block root pytest files") + raise AssertionError("ASP Python should block root pytest files") assert "rule=PY-TEST-R001 severity=warning" in message assert "|message Pytest file is scattered in tests root" in message @@ -210,7 +210,7 @@ def test_project_harness_blocks_root_pytest_files(tmp_path: Path) -> None: assert str(source) not in message -def test_project_harness_blocks_unexpected_tests_root_entries(tmp_path: Path) -> None: +def test_asp_python_blocks_unexpected_tests_root_entries(tmp_path: Path) -> None: unexpected = tmp_path / "tests" / "misc" unexpected.mkdir(parents=True) @@ -223,7 +223,7 @@ def test_project_harness_blocks_unexpected_tests_root_entries(tmp_path: Path) -> ] -def test_project_harness_blocks_bloated_unit_test_leaf(tmp_path: Path) -> None: +def test_asp_python_blocks_bloated_unit_test_leaf(tmp_path: Path) -> None: unit = tmp_path / "tests" / "unit" unit.mkdir(parents=True) source = unit / "test_large_policy.py" diff --git a/tests/unit/harness/test_project_config.py b/tests/unit/asp_python/test_project_config.py similarity index 96% rename from tests/unit/harness/test_project_config.py rename to tests/unit/asp_python/test_project_config.py index b8cd6b8..d0f6d77 100644 --- a/tests/unit/harness/test_project_config.py +++ b/tests/unit/asp_python/test_project_config.py @@ -70,4 +70,4 @@ def test_read_asp_python_config_rejects_invalid_values( except ValueError as error: assert "unknown severity: critical" in str(error) else: - raise AssertionError("invalid project harness config should fail") + raise AssertionError("invalid ASP Python config should fail") diff --git a/tests/unit/harness/test_project_fixture_scope.py b/tests/unit/asp_python/test_project_fixture_scope.py similarity index 100% rename from tests/unit/harness/test_project_fixture_scope.py rename to tests/unit/asp_python/test_project_fixture_scope.py diff --git a/tests/unit/harness/test_project_resolution.py b/tests/unit/asp_python/test_project_resolution.py similarity index 100% rename from tests/unit/harness/test_project_resolution.py rename to tests/unit/asp_python/test_project_resolution.py diff --git a/tests/unit/harness/test_project_resolution_extra_paths.py b/tests/unit/asp_python/test_project_resolution_extra_paths.py similarity index 100% rename from tests/unit/harness/test_project_resolution_extra_paths.py rename to tests/unit/asp_python/test_project_resolution_extra_paths.py diff --git a/tests/unit/harness/test_projection_batch.py b/tests/unit/asp_python/test_projection_batch.py similarity index 100% rename from tests/unit/harness/test_projection_batch.py rename to tests/unit/asp_python/test_projection_batch.py diff --git a/tests/unit/harness/test_provider_runtime.py b/tests/unit/asp_python/test_provider_runtime.py similarity index 100% rename from tests/unit/harness/test_provider_runtime.py rename to tests/unit/asp_python/test_provider_runtime.py diff --git a/tests/unit/harness/test_public_cli_identity.py b/tests/unit/asp_python/test_public_cli_identity.py similarity index 100% rename from tests/unit/harness/test_public_cli_identity.py rename to tests/unit/asp_python/test_public_cli_identity.py diff --git a/tests/unit/harness/test_pyproject_package_scope.py b/tests/unit/asp_python/test_pyproject_package_scope.py similarity index 100% rename from tests/unit/harness/test_pyproject_package_scope.py rename to tests/unit/asp_python/test_pyproject_package_scope.py diff --git a/tests/unit/harness/test_pytest.py b/tests/unit/asp_python/test_pytest.py similarity index 78% rename from tests/unit/harness/test_pytest.py rename to tests/unit/asp_python/test_pytest.py index 65f45bd..41f3241 100644 --- a/tests/unit/harness/test_pytest.py +++ b/tests/unit/asp_python/test_pytest.py @@ -27,11 +27,11 @@ def test_asp_python_test_returns_pytest_collectable_callable( encoding="utf-8", ) - harness_test = asp_python_test(tmp_path) + asp_python_test_case = asp_python_test(tmp_path) - assert harness_test.__name__ == "test_asp_python_policy" - assert harness_test.__qualname__ == "test_asp_python_policy" - harness_test() + assert asp_python_test_case.__name__ == "test_asp_python_policy" + assert asp_python_test_case.__qualname__ == "test_asp_python_policy" + asp_python_test_case() def test_asp_python_test_defaults_to_current_project_root( @@ -50,9 +50,9 @@ def test_asp_python_test_defaults_to_current_project_root( monkeypatch.chdir(tmp_path) - harness_test = asp_python_test() + asp_python_test_case = asp_python_test() - harness_test() + asp_python_test_case() def test_public_pytest_facade_exposes_collectable_helper() -> None: @@ -66,14 +66,14 @@ def test_asp_python_test_blocks_with_compact_snapshot( src.mkdir() source = src / "library.py" source.write_text('def run() -> None:\n print("debug")\n', encoding="utf-8") - harness_test = asp_python_test(tmp_path) + asp_python_test_case = asp_python_test(tmp_path) try: - harness_test() + asp_python_test_case() except AssertionError as error: message = str(error) else: - raise AssertionError("pytest harness callable should block policy findings") + raise AssertionError("ASP Python pytest callable should block policy findings") assert "rule=PY-MOD-R002 severity=warning" in message assert "|message Library module uses bare print" in message @@ -89,14 +89,14 @@ def test_asp_python_test_can_disable_agent_advice( src.mkdir() source = src / "library.py" source.write_text('def run() -> None:\n print("debug")\n', encoding="utf-8") - harness_test = asp_python_test(tmp_path, include_advice=False) + asp_python_test_case = asp_python_test(tmp_path, include_advice=False) try: - harness_test() + asp_python_test_case() except AssertionError as error: message = str(error) else: - raise AssertionError("pytest harness callable should block policy findings") + raise AssertionError("ASP Python pytest callable should block policy findings") assert "rule=PY-MOD-R002 severity=warning" in message assert "|message Library module uses bare print" in message @@ -116,7 +116,7 @@ def test_asp_python_test_honors_embedded_options( ) (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - harness_test = asp_python_test( + asp_python_test_case = asp_python_test( tmp_path, severities=frozenset({PythonDiagnosticSeverity.ERROR}), include_tests=False, @@ -124,9 +124,9 @@ def test_asp_python_test_honors_embedded_options( test_name="test_custom_python_project_policy", ) - assert harness_test.__name__ == "test_custom_python_project_policy" - assert harness_test.__qualname__ == "test_custom_python_project_policy" - harness_test() + assert asp_python_test_case.__name__ == "test_custom_python_project_policy" + assert asp_python_test_case.__qualname__ == "test_custom_python_project_policy" + asp_python_test_case() def test_asp_python_test_honors_configured_project_resolution( @@ -139,12 +139,12 @@ def test_asp_python_test_honors_configured_project_resolution( (lib / "library.py").write_text('"""Library docs."""\n', encoding="utf-8") (tests / "test_bad.py").write_text("def broken(:\n pass\n", encoding="utf-8") - harness_test = asp_python_test( + asp_python_test_case = asp_python_test( tmp_path, config=AspPythonConfig(source_dir_names=("lib",), include_tests=False), ) - harness_test() + asp_python_test_case() def test_asp_python_test_honors_extra_project_paths( @@ -157,9 +157,9 @@ def test_asp_python_test_honors_extra_project_paths( (src / "library.py").write_text('"""Library docs."""\n', encoding="utf-8") (tools / "check.py").write_text('"""Check docs."""\n', encoding="utf-8") - harness_test = asp_python_test( + asp_python_test_case = asp_python_test( tmp_path, extra_path_names=("tools",), ) - harness_test() + asp_python_test_case() diff --git a/tests/unit/harness/test_pytest_plugin.py b/tests/unit/asp_python/test_pytest_plugin.py similarity index 90% rename from tests/unit/harness/test_pytest_plugin.py rename to tests/unit/asp_python/test_pytest_plugin.py index dfad75c..fa06486 100644 --- a/tests/unit/harness/test_pytest_plugin.py +++ b/tests/unit/asp_python/test_pytest_plugin.py @@ -15,7 +15,7 @@ def test_pytest_plugin_collects_harness_item_from_dev_dependency( ) -> None: _write_clean_project(tmp_path) - result = _run_pytest_plugin(tmp_path, "--python-project-harness") + result = _run_pytest_plugin(tmp_path, "--asp-python") assert result.returncode == 0, result.stdout + result.stderr assert "2 passed" in result.stdout @@ -39,7 +39,7 @@ def test_pytest_plugin_can_be_enabled_from_pyproject_addopts( (tmp_path / "pyproject.toml").write_text( """ [tool.pytest.ini_options] -addopts = ["--python-project-harness"] +addopts = ["--asp-python"] """.lstrip(), encoding="utf-8", ) @@ -57,7 +57,7 @@ def test_pytest_plugin_runs_without_downstream_test_files( src.mkdir() (src / "library.py").write_text('"""Library docs."""\n', encoding="utf-8") - result = _run_pytest_plugin(tmp_path, "--python-project-harness") + result = _run_pytest_plugin(tmp_path, "--asp-python") assert result.returncode == 0, result.stdout + result.stderr assert "1 passed" in result.stdout @@ -79,7 +79,7 @@ def test_pytest_plugin_reports_compact_harness_failure( encoding="utf-8", ) - result = _run_pytest_plugin(tmp_path, "--python-project-harness") + result = _run_pytest_plugin(tmp_path, "--asp-python") assert result.returncode == 1 assert "rule=PY-MOD-R002 severity=warning" in result.stdout @@ -87,7 +87,7 @@ def test_pytest_plugin_reports_compact_harness_failure( assert "src/library.py" in result.stdout assert str(tmp_path) not in result.stdout assert "../" not in result.stdout - assert "FAILED python-project-harness" in result.stdout + assert "FAILED asp-python" in result.stdout assert ( "|repair replace bare print with a project-owned reporting surface" in result.stdout @@ -174,10 +174,10 @@ def test_pytest_plugin_honors_dev_dependency_options( result = _run_pytest_plugin( tmp_path, - "--python-project-harness", - "--python-project-harness-source-dir=lib", - "--python-project-harness-no-tests", - "--python-project-harness-error-only", + "--asp-python", + "--asp-python-source-dir=lib", + "--asp-python-no-tests", + "--asp-python-error-only", ) assert result.returncode == 0, result.stdout + result.stderr @@ -201,8 +201,8 @@ def test_pytest_plugin_honors_policy_rule_options( result = _run_pytest_plugin( tmp_path, - "--python-project-harness", - "--python-project-harness-disable-rule=PY-MOD-R002", + "--asp-python", + "--asp-python-disable-rule=PY-MOD-R002", ) assert result.returncode == 0, result.stdout + result.stderr @@ -231,7 +231,7 @@ def test_pytest_plugin_loads_project_policy_config_from_pyproject( encoding="utf-8", ) - result = _run_pytest_plugin(tmp_path, "--python-project-harness") + result = _run_pytest_plugin(tmp_path, "--asp-python") assert result.returncode == 0, result.stdout + result.stderr diff --git a/tests/unit/harness/test_reasoning_tree_policy.py b/tests/unit/asp_python/test_reasoning_tree_policy.py similarity index 100% rename from tests/unit/harness/test_reasoning_tree_policy.py rename to tests/unit/asp_python/test_reasoning_tree_policy.py diff --git a/tests/unit/harness/test_render_snapshots.py b/tests/unit/asp_python/test_render_snapshots.py similarity index 94% rename from tests/unit/harness/test_render_snapshots.py rename to tests/unit/asp_python/test_render_snapshots.py index 080f7eb..0b2dc48 100644 --- a/tests/unit/harness/test_render_snapshots.py +++ b/tests/unit/asp_python/test_render_snapshots.py @@ -6,10 +6,10 @@ from asp_python import ( AspPythonFinding, + AspPythonProjectScope, AspPythonReport, - PythonProjectHarnessScope, - render_python_lang_harness, - render_python_lang_harness_json, + render_asp_python_report, + render_asp_python_report_json, render_python_reasoning_tree, ) from python_lang_parser import ( @@ -25,13 +25,13 @@ def test_compact_text_render_matches_snapshot() -> None: - rendered = render_python_lang_harness(_snapshot_report()) + rendered = render_asp_python_report(_snapshot_report()) assert_snapshot("asp_python_compact_text", rendered) def test_json_render_matches_snapshot() -> None: - rendered = render_python_lang_harness_json(_snapshot_report()) + rendered = render_asp_python_report_json(_snapshot_report()) assert_snapshot("asp_python_json", rendered) @@ -55,7 +55,7 @@ def test_reasoning_tree_render_uses_project_relative_paths(tmp_path: Path) -> No ), findings=(), root_paths=(str(tmp_path),), - project_resolution=PythonProjectHarnessScope( + project_resolution=AspPythonProjectScope( project_root=tmp_path, project_metadata=PythonProjectMetadata( project_root=tmp_path, @@ -111,7 +111,7 @@ def test_compact_text_render_uses_project_relative_finding_paths( ), ), root_paths=(str(tmp_path),), - project_resolution=PythonProjectHarnessScope( + project_resolution=AspPythonProjectScope( project_root=tmp_path, project_paths=(tmp_path,), source_paths=(src,), @@ -119,7 +119,7 @@ def test_compact_text_render_uses_project_relative_finding_paths( ), ) - rendered = render_python_lang_harness(report) + rendered = render_asp_python_report(report) assert str(tmp_path) not in rendered assert "path=src/library.py line=3 column=5" in rendered @@ -175,7 +175,7 @@ def _reasoning_tree_snapshot_report() -> AspPythonReport: ), findings=(), root_paths=(str(root),), - project_resolution=PythonProjectHarnessScope( + project_resolution=AspPythonProjectScope( project_root=root, project_metadata=PythonProjectMetadata( project_root=root, diff --git a/tests/unit/harness/test_runner_config.py b/tests/unit/asp_python/test_runner_config.py similarity index 96% rename from tests/unit/harness/test_runner_config.py rename to tests/unit/asp_python/test_runner_config.py index fc8f1d5..7563e0b 100644 --- a/tests/unit/harness/test_runner_config.py +++ b/tests/unit/asp_python/test_runner_config.py @@ -5,7 +5,7 @@ from asp_python import ( AspPythonConfig, run_asp_python, - run_python_lang_harness, + run_asp_python_paths, ) if TYPE_CHECKING: @@ -218,11 +218,11 @@ def test_runner_rejects_missing_project_root_and_explicit_path(tmp_path: Path) - raise AssertionError("missing project root should fail") try: - run_python_lang_harness([missing]) + run_asp_python_paths([missing]) except ValueError as error: - assert str(error) == f"harness path does not exist: {missing}" + assert str(error) == f"ASP Python path does not exist: {missing}" else: - raise AssertionError("missing harness path should fail") + raise AssertionError("missing ASP Python path should fail") def test_concurrent_parser_retains_deterministic_discovery_order( @@ -233,6 +233,6 @@ def test_concurrent_parser_retains_deterministic_discovery_order( last.write_text("LAST = 1\n", encoding="utf-8") first.write_text("FIRST = 1\n", encoding="utf-8") - report = run_python_lang_harness([tmp_path], rule_packs=()) + report = run_asp_python_paths([tmp_path], rule_packs=()) assert [module.path for module in report.modules] == [str(first), str(last)] diff --git a/tests/unit/harness/test_search_playbook_boundary.py b/tests/unit/asp_python/test_search_playbook_boundary.py similarity index 100% rename from tests/unit/harness/test_search_playbook_boundary.py rename to tests/unit/asp_python/test_search_playbook_boundary.py diff --git a/tests/unit/harness/test_semantic_agent_cli.py b/tests/unit/asp_python/test_semantic_agent_cli.py similarity index 94% rename from tests/unit/harness/test_semantic_agent_cli.py rename to tests/unit/asp_python/test_semantic_agent_cli.py index c3beb44..9d1df64 100644 --- a/tests/unit/harness/test_semantic_agent_cli.py +++ b/tests/unit/asp_python/test_semantic_agent_cli.py @@ -1,4 +1,4 @@ -"""Semantic agent CLI tests for the Python harness provider.""" +"""Semantic agent CLI tests for the ASP Python provider.""" from __future__ import annotations diff --git a/tests/unit/harness/test_semantic_cli.py b/tests/unit/asp_python/test_semantic_cli.py similarity index 95% rename from tests/unit/harness/test_semantic_cli.py rename to tests/unit/asp_python/test_semantic_cli.py index 0466a33..3f1412b 100644 --- a/tests/unit/harness/test_semantic_cli.py +++ b/tests/unit/asp_python/test_semantic_cli.py @@ -1,4 +1,4 @@ -"""Semantic CLI protocol tests for the Python harness provider.""" +"""Semantic CLI protocol tests for the ASP Python provider.""" from __future__ import annotations diff --git a/tests/unit/harness/test_semantic_cli_ast_patch.py b/tests/unit/asp_python/test_semantic_cli_ast_patch.py similarity index 100% rename from tests/unit/harness/test_semantic_cli_ast_patch.py rename to tests/unit/asp_python/test_semantic_cli_ast_patch.py diff --git a/tests/unit/harness/test_semantic_cli_benchmark_registry.py b/tests/unit/asp_python/test_semantic_cli_benchmark_registry.py similarity index 100% rename from tests/unit/harness/test_semantic_cli_benchmark_registry.py rename to tests/unit/asp_python/test_semantic_cli_benchmark_registry.py diff --git a/tests/unit/harness/test_semantic_cli_structural_selector_registry.py b/tests/unit/asp_python/test_semantic_cli_structural_selector_registry.py similarity index 100% rename from tests/unit/harness/test_semantic_cli_structural_selector_registry.py rename to tests/unit/asp_python/test_semantic_cli_structural_selector_registry.py diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py b/tests/unit/asp_python/test_semantic_cli_tree_sitter_predicates.py similarity index 100% rename from tests/unit/harness/test_semantic_cli_tree_sitter_predicates.py rename to tests/unit/asp_python/test_semantic_cli_tree_sitter_predicates.py diff --git a/tests/unit/harness/test_semantic_cli_tree_sitter_registry.py b/tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py similarity index 100% rename from tests/unit/harness/test_semantic_cli_tree_sitter_registry.py rename to tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py diff --git a/tests/unit/harness/test_semantic_language_schemas.py b/tests/unit/asp_python/test_semantic_language_schemas.py similarity index 100% rename from tests/unit/harness/test_semantic_language_schemas.py rename to tests/unit/asp_python/test_semantic_language_schemas.py diff --git a/tests/unit/harness/test_semantic_provider_doctor.py b/tests/unit/asp_python/test_semantic_provider_doctor.py similarity index 94% rename from tests/unit/harness/test_semantic_provider_doctor.py rename to tests/unit/asp_python/test_semantic_provider_doctor.py index b6d43c4..16cd507 100644 --- a/tests/unit/harness/test_semantic_provider_doctor.py +++ b/tests/unit/asp_python/test_semantic_provider_doctor.py @@ -1,4 +1,4 @@ -"""Validate the Python provider doctor-v2 response contract.""" +"""Validate the ASP Python provider doctor response contract.""" import io import json @@ -43,7 +43,7 @@ def test_cli_agent_doctor_json_validates_v1_envelope_and_registry( registration["binary"], ) descriptors = registration["methodDescriptors"] - assert len(descriptors) == len(registration["methods"]) == 7 + assert len(descriptors) == len(registration["methods"]) == 5 assert not any( descriptor["method"].startswith("search/") for descriptor in descriptors ) diff --git a/tests/unit/harness/test_software_criterion_snapshots.py b/tests/unit/asp_python/test_software_criterion_snapshots.py similarity index 96% rename from tests/unit/harness/test_software_criterion_snapshots.py rename to tests/unit/asp_python/test_software_criterion_snapshots.py index 92c9dcb..e95a0b7 100644 --- a/tests/unit/harness/test_software_criterion_snapshots.py +++ b/tests/unit/asp_python/test_software_criterion_snapshots.py @@ -9,7 +9,7 @@ from syrupy.extensions.json import JSONSnapshotExtension from asp_python import ( - run_python_lang_harness, + run_asp_python_paths, ) if TYPE_CHECKING: @@ -41,7 +41,7 @@ def test_py_software_criterion_control_flow_v1_snapshot( ) -> None: _copy_inputs(_SCENARIO / "inputs", tmp_path) - report = run_python_lang_harness([tmp_path / "criterion.py"]) + report = run_asp_python_paths([tmp_path / "criterion.py"]) filtered = _filter_software_criterion_findings(report) findings = [ { @@ -105,7 +105,8 @@ def test_py_software_criterion_control_flow_v1_scenario_benchmark_contract() -> assert benchmark["harness"] == "pytest" assert ( - benchmark["test"] == "tests/unit/harness/test_software_criterion_snapshots.py" + benchmark["test"] + == "tests/unit/asp_python/test_software_criterion_snapshots.py" ) _assert_benchmark_durations(benchmark) comparison = benchmark["input_expected_comparison"] diff --git a/tests/unit/harness/test_test_layout_config.py b/tests/unit/asp_python/test_test_layout_config.py similarity index 96% rename from tests/unit/harness/test_test_layout_config.py rename to tests/unit/asp_python/test_test_layout_config.py index 20bb13b..3c91fa6 100644 --- a/tests/unit/harness/test_test_layout_config.py +++ b/tests/unit/asp_python/test_test_layout_config.py @@ -81,7 +81,7 @@ def test_layout_policy_requires_explanation_for_directory_exception( def _write_policy(tests_dir: Path, content: str) -> None: - (tests_dir / "python-project-harness-rules.toml").write_text( + (tests_dir / "asp-python-rules.toml").write_text( content.lstrip(), encoding="utf-8", ) diff --git a/tests/unit/harness/test_verification.py b/tests/unit/asp_python/test_verification.py similarity index 95% rename from tests/unit/harness/test_verification.py rename to tests/unit/asp_python/test_verification.py index bd5212e..fcb5e3c 100644 --- a/tests/unit/harness/test_verification.py +++ b/tests/unit/asp_python/test_verification.py @@ -17,7 +17,7 @@ PythonVerificationTaskKind, build_python_verification_profile_index_with_config, build_python_verification_report_bundle, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, read_asp_python_config, render_asp_python_agent_snapshot_with_config, @@ -38,7 +38,7 @@ def test_verification_profile_hint_plans_external_task( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -61,7 +61,7 @@ def test_verification_receipt_satisfies_matching_task( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - base_config = default_python_harness_config().with_verification_profile_hint( + base_config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -90,7 +90,7 @@ def test_verification_profile_index_uses_parser_and_dependency_facts( tmp_path, dependencies='dependencies = ["httpx>=0.28"]', ) - config = default_python_harness_config().with_verification_dependency_signal( + config = default_asp_python_config().with_verification_dependency_signal( PythonVerificationDependencySignal( "httpx", (PythonOwnerResponsibility.NETWORK,), @@ -129,7 +129,7 @@ def test_verification_report_bundle_and_writer_emit_modular_artifacts( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -191,7 +191,7 @@ def test_verification_policy_can_be_loaded_from_pyproject_config( ) -> None: _write_public_api_project( tmp_path, - harness_config=""" + asp_python_config=""" [tool.asp-python.verification] profile_hints = [ { owner_path = "src/pkg/api.py", responsibilities = ["public_api"], task_kinds = ["security"], rationale = "authz-sensitive public API" }, @@ -260,7 +260,7 @@ def test_verification_skill_binding_renders_contract_reference( .with_rationale("this public API changes tenant authorization") ) config = ( - default_python_harness_config() + default_asp_python_config() .with_verification_profile_hint(hint) .with_verification_skill_binding( PythonVerificationTaskKind.SECURITY, @@ -300,7 +300,7 @@ def test_agent_snapshot_includes_active_verification_tasks( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", (PythonOwnerResponsibility.PUBLIC_API,), @@ -325,7 +325,7 @@ def _write_public_api_project( project_root: Path, *, dependencies: str = "", - harness_config: str = "", + asp_python_config: str = "", ) -> None: package = project_root / "src" / "pkg" package.mkdir(parents=True) @@ -353,7 +353,7 @@ def _write_public_api_project( [tool.hatch.build.targets.wheel] packages = ["src/pkg"] -{harness_config} +{asp_python_config} """.lstrip(), encoding="utf-8", ) diff --git a/tests/unit/harness/verification/test_agent_snapshot_profile_index.py b/tests/unit/asp_python/verification/test_agent_snapshot_profile_index.py similarity index 95% rename from tests/unit/harness/verification/test_agent_snapshot_profile_index.py rename to tests/unit/asp_python/verification/test_agent_snapshot_profile_index.py index 0de473f..2a1ed03 100644 --- a/tests/unit/harness/verification/test_agent_snapshot_profile_index.py +++ b/tests/unit/asp_python/verification/test_agent_snapshot_profile_index.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING from asp_python import ( - default_python_harness_config, + default_asp_python_config, render_asp_python_agent_snapshot_with_config, ) @@ -18,7 +18,7 @@ def test_agent_snapshot_reminds_when_verification_profile_is_unconfigured( rendered = render_asp_python_agent_snapshot_with_config( tmp_path, - default_python_harness_config(), + default_asp_python_config(), ) assert "[verify-profile] profile_hints" in rendered diff --git a/tests/unit/harness/verification/test_performance_microbench.py b/tests/unit/asp_python/verification/test_performance_microbench.py similarity index 97% rename from tests/unit/harness/verification/test_performance_microbench.py rename to tests/unit/asp_python/verification/test_performance_microbench.py index 1024962..f5b02a6 100644 --- a/tests/unit/harness/verification/test_performance_microbench.py +++ b/tests/unit/asp_python/verification/test_performance_microbench.py @@ -11,7 +11,7 @@ PythonVerificationSkillDescriptor, PythonVerificationTaskKind, build_python_verification_performance_index, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, ) @@ -21,7 +21,7 @@ def test_python_package_microbench_gate_is_verification_owned( ) -> None: _write_public_api_project(tmp_path) config = ( - default_python_harness_config() + default_asp_python_config() .with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/api.py", diff --git a/tests/unit/harness/verification/test_policy_regressions.py b/tests/unit/asp_python/verification/test_policy_regressions.py similarity index 94% rename from tests/unit/harness/verification/test_policy_regressions.py rename to tests/unit/asp_python/verification/test_policy_regressions.py index 954a4ac..a9d95f9 100644 --- a/tests/unit/harness/verification/test_policy_regressions.py +++ b/tests/unit/asp_python/verification/test_policy_regressions.py @@ -7,7 +7,7 @@ PythonVerificationDependencySignal, PythonVerificationProfileHint, PythonVerificationTaskKind, - default_python_harness_config, + default_asp_python_config, plan_python_project_verification_with_config, ) @@ -22,7 +22,7 @@ def test_dependency_signal_uses_pep503_distribution_names( tmp_path, dependencies='dependencies = ["zope.interface>=6"]', ) - config = default_python_harness_config().with_verification_dependency_signal( + config = default_asp_python_config().with_verification_dependency_signal( PythonVerificationDependencySignal( "zope-interface", (PythonOwnerResponsibility.NETWORK,), @@ -44,7 +44,7 @@ def test_profile_hint_uses_configured_responsibility_task_mapping( tmp_path: Path, ) -> None: _write_public_api_project(tmp_path) - base_config = default_python_harness_config() + base_config = default_asp_python_config() policy = base_config.verification_policy.with_responsibility_task_kinds( PythonOwnerResponsibility.PUBLIC_API, (PythonVerificationTaskKind.SECURITY,), diff --git a/tests/unit/harness/verification/test_profile_index.py b/tests/unit/asp_python/verification/test_profile_index.py similarity index 93% rename from tests/unit/harness/verification/test_profile_index.py rename to tests/unit/asp_python/verification/test_profile_index.py index 8b0b84d..f536a06 100644 --- a/tests/unit/harness/verification/test_profile_index.py +++ b/tests/unit/asp_python/verification/test_profile_index.py @@ -6,7 +6,7 @@ PythonOwnerResponsibility, PythonVerificationProfileHint, build_python_verification_profile_index_with_config, - default_python_harness_config, + default_asp_python_config, render_python_verification_profile_index, ) @@ -19,7 +19,7 @@ def test_profile_index_aggregates_public_branch_owners(tmp_path: Path) -> None: index = build_python_verification_profile_index_with_config( tmp_path, - default_python_harness_config(), + default_asp_python_config(), ) rendered = render_python_verification_profile_index(index) @@ -37,7 +37,7 @@ def test_profile_index_renders_configured_responsibilities_for_drift( tmp_path: Path, ) -> None: _write_public_branch_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/__init__.py", (PythonOwnerResponsibility.CLI,), @@ -59,7 +59,7 @@ def test_profile_index_renders_configured_responsibilities_for_drift( def test_profile_index_omits_configured_candidates(tmp_path: Path) -> None: _write_public_branch_project(tmp_path) - config = default_python_harness_config().with_verification_profile_hint( + config = default_asp_python_config().with_verification_profile_hint( PythonVerificationProfileHint( "src/pkg/__init__.py", (PythonOwnerResponsibility.PUBLIC_API,), diff --git a/tests/unit/lang_harness/test_config_contracts.py b/tests/unit/asp_python_paths/test_config_contracts.py similarity index 87% rename from tests/unit/lang_harness/test_config_contracts.py rename to tests/unit/asp_python_paths/test_config_contracts.py index d0e8e80..3c0011b 100644 --- a/tests/unit/lang_harness/test_config_contracts.py +++ b/tests/unit/asp_python_paths/test_config_contracts.py @@ -4,11 +4,11 @@ from typing import TYPE_CHECKING from asp_python import ( - default_python_harness_config, + default_asp_python_config, python_rule_pack_descriptors, python_syntax_rules, - render_python_lang_harness_json, - run_python_lang_harness, + render_asp_python_report_json, + run_asp_python_paths, ) from python_lang_parser import PythonDiagnosticSeverity @@ -16,8 +16,8 @@ from pathlib import Path -def test_default_python_harness_config_uses_default_rule_packs() -> None: - config = default_python_harness_config() +def test_default_asp_python_config_uses_default_rule_packs() -> None: + config = default_asp_python_config() assert config.ignored_dir_names assert config.blocking_severities == { @@ -44,8 +44,8 @@ def test_rule_pack_descriptors_and_json_renderer_are_stable(tmp_path: Path) -> N source = tmp_path / "module.py" source.write_text('"""Module docs."""\n\nVALUE = 1\n', encoding="utf-8") - report = run_python_lang_harness([source]) - payload = json.loads(render_python_lang_harness_json(report)) + report = run_asp_python_paths([source]) + payload = json.loads(render_asp_python_report_json(report)) assert [descriptor.id for descriptor in python_rule_pack_descriptors()] == [ "python.syntax", diff --git a/tests/unit/lang_harness/test_discovery_runner.py b/tests/unit/asp_python_paths/test_discovery_runner.py similarity index 92% rename from tests/unit/lang_harness/test_discovery_runner.py rename to tests/unit/asp_python_paths/test_discovery_runner.py index e434297..6f306a5 100644 --- a/tests/unit/lang_harness/test_discovery_runner.py +++ b/tests/unit/asp_python_paths/test_discovery_runner.py @@ -7,9 +7,9 @@ AspPythonFinding, PythonSyntaxRulePack, discover_python_files, - render_python_lang_harness, + render_asp_python_report, run_asp_python, - run_python_lang_harness, + run_asp_python_paths, ) from python_lang_parser import ( PythonDiagnostic, @@ -112,7 +112,7 @@ def test_discover_python_files_deduplicates_nested_scan_roots( assert discover_python_files([tmp_path, src]) == (source,) -def test_run_python_lang_harness_collects_parse_findings(tmp_path: Path) -> None: +def test_run_asp_python_paths_collects_parse_findings(tmp_path: Path) -> None: good = tmp_path / "good.py" bad = tmp_path / "bad.py" good.write_text( @@ -120,7 +120,7 @@ def test_run_python_lang_harness_collects_parse_findings(tmp_path: Path) -> None ) bad.write_text("def broken(:\n pass\n", encoding="utf-8") - report = run_python_lang_harness([tmp_path]) + report = run_asp_python_paths([tmp_path]) assert report.file_count == 2 assert report.parsed_count == 1 @@ -133,13 +133,13 @@ def test_run_python_lang_harness_collects_parse_findings(tmp_path: Path) -> None assert report.to_dict()["is_clean"] is False -def test_run_python_lang_harness_uses_configured_discovery(tmp_path: Path) -> None: +def test_run_asp_python_paths_uses_configured_discovery(tmp_path: Path) -> None: generated = tmp_path / "generated" generated.mkdir() ignored = generated / "debug.py" ignored.write_text('def run():\n print("debug")\n', encoding="utf-8") - report = run_python_lang_harness( + report = run_asp_python_paths( [tmp_path], config=AspPythonConfig(ignored_dir_names=frozenset({"generated"})), ) @@ -176,7 +176,7 @@ def test_syntax_rule_pack_handles_unknown_parser_error_codes() -> None: assert findings[0].summary == "permission denied" -def test_run_python_lang_harness_uses_configured_blocking_severities( +def test_run_asp_python_paths_uses_configured_blocking_severities( tmp_path: Path, ) -> None: source = tmp_path / "module.py" @@ -186,7 +186,7 @@ def test_run_python_lang_harness_uses_configured_blocking_severities( rule_packs=(_WarningRulePack(),), ) - report = run_python_lang_harness([source], config=config) + report = run_asp_python_paths([source], config=config) assert [finding.rule_id for finding in report.findings] == [ "python.project.warning" @@ -194,7 +194,7 @@ def test_run_python_lang_harness_uses_configured_blocking_severities( assert report.blocking_findings() == () assert report.is_clean assert report.to_dict()["blocking_severities"] == ["error"] - assert render_python_lang_harness(report).startswith("[ok]") + assert render_asp_python_report(report).startswith("[ok]") class _WarningRulePack: diff --git a/tests/unit/lang_harness/test_render_assertions.py b/tests/unit/asp_python_paths/test_render_assertions.py similarity index 75% rename from tests/unit/lang_harness/test_render_assertions.py rename to tests/unit/asp_python_paths/test_render_assertions.py index c1b36ca..cc9db06 100644 --- a/tests/unit/lang_harness/test_render_assertions.py +++ b/tests/unit/asp_python_paths/test_render_assertions.py @@ -5,9 +5,9 @@ from asp_python import ( AspPythonConfig, AspPythonFinding, - assert_python_lang_harness_clean, - render_python_lang_harness, - run_python_lang_harness, + assert_asp_python_paths_clean, + render_asp_python_report, + run_asp_python_paths, ) from python_lang_parser import PythonDiagnosticSeverity, SourceLocation @@ -17,13 +17,13 @@ from python_lang_parser import PythonModuleReport -def test_render_python_lang_harness_uses_compact_source_diagnostic( +def test_render_asp_python_report_uses_compact_source_diagnostic( tmp_path: Path, ) -> None: bad = tmp_path / "bad.py" bad.write_text("def broken(:\n pass\n", encoding="utf-8") - output = render_python_lang_harness(run_python_lang_harness([bad])) + output = render_asp_python_report(run_asp_python_paths([bad])) assert output.startswith("[fail] python blockingFindings=1 parsed=0/1") assert "|failureFrontier rule=python.syntax.invalid severity=error" in output @@ -36,15 +36,15 @@ def test_render_python_lang_harness_uses_compact_source_diagnostic( assert "Evidence:" not in output -def test_render_python_lang_harness_attaches_agent_advice_by_default( +def test_render_asp_python_report_attaches_agent_advice_by_default( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("def run(value):\n print(value)\n", encoding="utf-8") - report = run_python_lang_harness([source]) + report = run_asp_python_paths([source]) - default_output = render_python_lang_harness(report) - quiet_output = render_python_lang_harness(report, include_advice=False) + default_output = render_asp_python_report(report) + quiet_output = render_asp_python_report(report, include_advice=False) assert default_output.startswith("[fail] python blockingFindings=1 parsed=1/1") assert "|failureFrontier rule=PY-MOD-R002 severity=warning" in default_output @@ -61,33 +61,33 @@ def test_render_python_lang_harness_attaches_agent_advice_by_default( assert "PY-AGENT" not in quiet_output -def test_assert_python_lang_harness_clean_blocks_for_pytest(tmp_path: Path) -> None: +def test_assert_asp_python_paths_clean_blocks_for_pytest(tmp_path: Path) -> None: bad = tmp_path / "bad.py" bad.write_text("def broken(:\n pass\n", encoding="utf-8") try: - assert_python_lang_harness_clean([bad]) + assert_asp_python_paths_clean([bad]) except AssertionError as error: message = str(error) else: - raise AssertionError("harness should block invalid Python source") + raise AssertionError("ASP Python should block invalid Python source") assert "[fail] python blockingFindings=1 parsed=0/1" in message assert "python.syntax.invalid" in message -def test_assert_python_lang_harness_clean_includes_agent_advice_by_default( +def test_assert_asp_python_paths_clean_includes_agent_advice_by_default( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("def run(value):\n print(value)\n", encoding="utf-8") try: - assert_python_lang_harness_clean([source]) + assert_asp_python_paths_clean([source]) except AssertionError as error: message = str(error) else: - raise AssertionError("harness should block warning findings") + raise AssertionError("ASP Python should block warning findings") assert "[advice]" in message assert ( @@ -96,42 +96,42 @@ def test_assert_python_lang_harness_clean_includes_agent_advice_by_default( ) -def test_assert_python_lang_harness_clean_can_disable_agent_advice( +def test_assert_asp_python_paths_clean_can_disable_agent_advice( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("def run(value):\n print(value)\n", encoding="utf-8") try: - assert_python_lang_harness_clean([source], include_advice=False) + assert_asp_python_paths_clean([source], include_advice=False) except AssertionError as error: message = str(error) else: - raise AssertionError("harness should block warning findings") + raise AssertionError("ASP Python should block warning findings") assert "[fail] python blockingFindings=1 parsed=1/1" in message assert "|failureFrontier rule=PY-MOD-R002 severity=warning" in message assert "[advice]" not in message -def test_assert_python_lang_harness_clean_blocks_warning_findings( +def test_assert_asp_python_paths_clean_blocks_warning_findings( tmp_path: Path, ) -> None: source = tmp_path / "module.py" source.write_text("VALUE = 1\n", encoding="utf-8") try: - assert_python_lang_harness_clean([source], rule_packs=(_WarningRulePack(),)) + assert_asp_python_paths_clean([source], rule_packs=(_WarningRulePack(),)) except AssertionError as error: message = str(error) else: - raise AssertionError("harness should block warning findings") + raise AssertionError("ASP Python should block warning findings") assert "[fail] python blockingFindings=1 parsed=1/1" in message assert "|failureFrontier rule=python.project.warning severity=warning" in message -def test_assert_python_lang_harness_clean_honors_configured_blocking_severities( +def test_assert_asp_python_paths_clean_honors_configured_blocking_severities( tmp_path: Path, ) -> None: source = tmp_path / "module.py" @@ -141,7 +141,7 @@ def test_assert_python_lang_harness_clean_honors_configured_blocking_severities( rule_packs=(_WarningRulePack(),), ) - report = assert_python_lang_harness_clean([source], config=config) + report = assert_asp_python_paths_clean([source], config=config) assert [finding.rule_id for finding in report.findings] == [ "python.project.warning" @@ -149,7 +149,7 @@ def test_assert_python_lang_harness_clean_honors_configured_blocking_severities( assert report.is_clean -def test_assert_python_lang_harness_clean_honors_severities_override( +def test_assert_asp_python_paths_clean_honors_severities_override( tmp_path: Path, ) -> None: source = tmp_path / "module.py" @@ -160,7 +160,7 @@ def test_assert_python_lang_harness_clean_honors_severities_override( ) try: - assert_python_lang_harness_clean( + assert_asp_python_paths_clean( [source], config=config, severities=frozenset({PythonDiagnosticSeverity.WARNING}), diff --git a/tests/unit/harness/test_evidence_graph.py b/tests/unit/harness/test_evidence_graph.py deleted file mode 100644 index d1144c4..0000000 --- a/tests/unit/harness/test_evidence_graph.py +++ /dev/null @@ -1,112 +0,0 @@ -"""Evidence graph CLI tests for the Python harness provider.""" - -from __future__ import annotations - -import io -import json -from pathlib import Path - -from asp_python import run_cli - - -def test_cli_evidence_graph_renders_json_contract(tmp_path: Path) -> None: - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "evidence-fixture"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - ["evidence", "graph", "--json", str(tmp_path)], - stdout=stdout, - cwd=tmp_path, - ) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - assert payload["schemaId"] == "agent.semantic-protocols.semantic-evidence-graph" - assert payload["protocolId"] == "agent.semantic-protocols.evidence-graph" - assert payload["producer"]["languageId"] == "python" - assert payload["producer"]["providerId"] == "asp-python" - assert payload["project"]["package"] == "evidence-fixture" - assert payload["summary"] == { - "nodes": 4, - "edges": 3, - "owners": 1, - "claims": 1, - "staleItems": 0, - "gaps": 1, - } - assert any(node["kind"] == "owner" for node in payload["nodes"]) - assert any(edge["kind"] == "requires-evidence" for edge in payload["edges"]) - assert payload["gaps"][0]["fields"] == {"requiredReceiptId": "python.policy.api"} - assert all("command" not in node.get("fields", {}) for node in payload["nodes"]) - - -def test_cli_evidence_analyze_renders_graph_turbo_request(tmp_path: Path) -> None: - (tmp_path / "pyproject.toml").write_text( - '[project]\nname = "analysis-fixture"\nversion = "0.1.0"\n', - encoding="utf-8", - ) - stdout = io.StringIO() - - exit_code = run_cli( - ["evidence", "analyze", "--json", str(tmp_path)], - stdout=stdout, - cwd=tmp_path, - ) - - assert exit_code == 0 - payload = json.loads(stdout.getvalue()) - assert ( - payload["schemaId"] == "agent.semantic-protocols.semantic-graph-turbo-request" - ) - assert payload["packetKind"] == "graph-turbo-request" - assert payload["surface"] == "evidence-analyze" - assert payload["profile"] == "evidence-quality" - assert payload["producer"]["languageId"] == "python" - assert payload["summary"]["graphs"] == 1 - assert payload["summary"]["nodes"] == 4 - assert payload["summary"]["gaps"] == 1 - assert payload["graphs"][0]["graphId"] == "python.evidence.graph" - assert payload["entryNodeIds"] == ["python:owner:pyproject.toml"] - assert any( - edge["relation"] == "requires-evidence" - for edge in payload["graphs"][0]["edges"] - ) - - -def test_agent_registry_advertises_evidence_methods(tmp_path: Path) -> None: - stdout = io.StringIO() - - exit_code = run_cli( - ["agent", "doctor", "--json", str(tmp_path)], - stdout=stdout, - cwd=tmp_path, - ) - - assert exit_code == 0 - registry = json.loads(stdout.getvalue()) - language = registry["registry"]["languages"][0] - assert "evidence/graph" in language["methods"] - assert "evidence/analyze" in language["methods"] - analyze = next( - descriptor - for descriptor in language["methodDescriptors"] - if descriptor["method"] == "evidence/analyze" - ) - assert analyze["command"] == "evidence" - assert analyze["outputSchemaIds"] == [ - "agent.semantic-protocols.semantic-graph-turbo-request" - ] - - -def test_agent_guide_advertises_evidence_commands(tmp_path: Path) -> None: - stdout = io.StringIO() - - exit_code = run_cli(["agent", "guide"], stdout=stdout, cwd=tmp_path) - - assert exit_code == 0 - guide = stdout.getvalue() - assert "evidence graph --json" in guide - assert "evidence analyze --json" in guide diff --git a/tests/unit/python_lang_parser/test_pyproject_metadata.py b/tests/unit/python_lang_parser/test_pyproject_metadata.py index 53ecd4f..fe43568 100644 --- a/tests/unit/python_lang_parser/test_pyproject_metadata.py +++ b/tests/unit/python_lang_parser/test_pyproject_metadata.py @@ -42,7 +42,7 @@ def test_parse_python_project_metadata_collects_modern_project_facts( ] [tool.pytest.ini_options] -addopts = ["--import-mode=importlib", "--python-project-harness"] +addopts = ["--import-mode=importlib", "--asp-python"] [build-system] requires = ["hatchling"] @@ -100,7 +100,7 @@ def test_parse_python_project_metadata_collects_modern_project_facts( ] assert metadata.pytest_options.addopts == ( "--import-mode=importlib", - "--python-project-harness", + "--asp-python", ) assert metadata.pytest_options.enables_asp_python is True assert metadata.wheel_packages == ("src/example_pkg",) diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r001_module_intent.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r001_module_intent.snap index 3f6175b..87e80ca 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r001_module_intent.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r001_module_intent.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r002_callable_annotations.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r002_callable_annotations.snap index f88c8b7..c920101 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r002_callable_annotations.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r002_callable_annotations.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r003_callable_conflict.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r003_callable_conflict.snap index 29564eb..753b544 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r003_callable_conflict.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r003_callable_conflict.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r004_repeated_namespace.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r004_repeated_namespace.snap index 4d5190f..f20555b 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r004_repeated_namespace.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r004_repeated_namespace.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r005_type_conflict.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r005_type_conflict.snap index f9c3b7c..532e64f 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r005_type_conflict.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r005_type_conflict.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r006_value_conflict.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r006_value_conflict.snap index fb63d12..a900386 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r006_value_conflict.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r006_value_conflict.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r007_branch_intent.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r007_branch_intent.snap index 012510d..0ed7873 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r007_branch_intent.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r007_branch_intent.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r008_branch_surface.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r008_branch_surface.snap index d6a68fd..3be53d0 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r008_branch_surface.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r008_branch_surface.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_policy_snapshots.py +source: tests/unit/asp_python/test_agent_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape.snap index a517553..ccb26a1 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r009_algorithm_shape.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_algorithm_policy.py +source: tests/unit/asp_python/test_agent_algorithm_policy.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r010_function_compactness.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r010_function_compactness.snap index f4e9662..9c62b71 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r010_function_compactness.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r010_function_compactness.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_algorithm_policy.py +source: tests/unit/asp_python/test_agent_algorithm_policy.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r011_native_idiom.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r011_native_idiom.snap index 26c4445..1ce1963 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r011_native_idiom.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r011_native_idiom.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_algorithm_policy.py +source: tests/unit/asp_python/test_agent_algorithm_policy.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r012_type_shape.snap b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r012_type_shape.snap index 104c43a..b050bc1 100644 --- a/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r012_type_shape.snap +++ b/tests/unit/snapshots/unit_test__agent_policy_snapshot__py_agent_r012_type_shape.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_agent_algorithm_policy.py +source: tests/unit/asp_python/test_agent_algorithm_policy.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r001_wildcard_import.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r001_wildcard_import.snap index a70b799..c8fd11e 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r001_wildcard_import.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r001_wildcard_import.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r002_bare_print.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r002_bare_print.snap index 1fc7f07..86bbefb 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r002_bare_print.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r002_bare_print.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r003_facade_all.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r003_facade_all.snap index 862d8d6..2a2aec4 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r003_facade_all.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r003_facade_all.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=2/2 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r004_breakpoint.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r004_breakpoint.snap index 5f794cc..7f526b8 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r004_breakpoint.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r004_breakpoint.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r006_module_bloat.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r006_module_bloat.snap index 593c797..5725be2 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r006_module_bloat.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r006_module_bloat.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r007_reasoning_tree_shadow.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r007_reasoning_tree_shadow.snap index 006934c..b6c7d1a 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r007_reasoning_tree_shadow.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_mod_r007_reasoning_tree_shadow.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=2/2 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r001_src_layout.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r001_src_layout.snap index ede3cca..5a63cf8 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r001_src_layout.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r001_src_layout.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r002_declared_package.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r002_declared_package.snap index 65b6f3d..676880a 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r002_declared_package.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r002_declared_package.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r003_py_typed.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r003_py_typed.snap index e610792..21f2dd6 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r003_py_typed.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r003_py_typed.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r004_typed_annotations.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r004_typed_annotations.snap index 457930f..c7f9d7a 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r004_typed_annotations.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r004_typed_annotations.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=2/2 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r005_project_name.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r005_project_name.snap index 1566116..63acb88 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r005_project_name.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r005_project_name.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r006_requires_python.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r006_requires_python.snap index b15ad6c..b992b51 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r006_requires_python.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r006_requires_python.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r007_build_requires.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r007_build_requires.snap index f5346a8..e6a4bf3 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r007_build_requires.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r007_build_requires.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r008_import_names.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r008_import_names.snap index 57daa5e..6c49385 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r008_import_names.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r008_import_names.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r009_entry_point_target.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r009_entry_point_target.snap index ff7f5ef..75b4873 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r009_entry_point_target.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r009_entry_point_target.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r010_pytest_gate.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r010_pytest_gate.snap index 44f7e26..553b93c 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r010_pytest_gate.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r010_pytest_gate.snap @@ -1,10 +1,10 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 |failureFrontier rule=PY-AGENT-PROJECT-010 severity=warning path=pyproject.toml line=1 column=1 -|message Harness dev dependency should mount a pytest gate -|summary pyproject.toml declares the Python project harness surface without a parser-visible pytest gate. -|repair mount the parser-backed harness in pytest +|message ASP Python dev dependency should mount a pytest gate +|summary pyproject.toml declares the ASP Python surface without a parser-visible pytest gate. +|repair mount the parser-backed ASP Python in pytest |hotBlock selector=pyproject.toml:1:1 reason=blocking-finding diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap index aa9a687..875f605 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_proj_r011_verification_profile.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [advice] diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r001_root_pytest.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r001_root_pytest.snap index ed4c281..e1144d5 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r001_root_pytest.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r001_root_pytest.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r002_unexpected_root.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r002_unexpected_root.snap index 25e1432..f948c77 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r002_unexpected_root.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r002_unexpected_root.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/0 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r003_unit_bloat.snap b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r003_unit_bloat.snap index 50da0d6..e4c458f 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r003_unit_bloat.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__py_test_r003_unit_bloat.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=1/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__python_compile_invalid.snap b/tests/unit/snapshots/unit_test__policy_snapshot__python_compile_invalid.snap index 2a4bebf..3e41ec0 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__python_compile_invalid.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__python_compile_invalid.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/1 diff --git a/tests/unit/snapshots/unit_test__policy_snapshot__python_syntax_invalid.snap b/tests/unit/snapshots/unit_test__policy_snapshot__python_syntax_invalid.snap index a88e71d..5587ee3 100644 --- a/tests/unit/snapshots/unit_test__policy_snapshot__python_syntax_invalid.snap +++ b/tests/unit/snapshots/unit_test__policy_snapshot__python_syntax_invalid.snap @@ -1,5 +1,5 @@ --- -source: tests/unit/harness/test_policy_snapshots.py +source: tests/unit/asp_python/test_policy_snapshots.py expression: rendered --- [fail] python blockingFindings=1 parsed=0/1 diff --git a/tests/unit/test_public_api.py b/tests/unit/test_public_api.py index 98e0243..e203295 100644 --- a/tests/unit/test_public_api.py +++ b/tests/unit/test_public_api.py @@ -1,201 +1,213 @@ from __future__ import annotations -import asp_python as harness_api -import asp_python.harness as harness_facade +import asp_python as asp_python_api +import asp_python.api as asp_python_facade import python_lang_parser as parser_api def test_root_package_reexports_parser_fact_models() -> None: - assert harness_api.PythonCallEffect is parser_api.PythonCallEffect - assert harness_api.PythonClassShape is parser_api.PythonClassShape - assert harness_api.PythonExportContract is parser_api.PythonExportContract - assert harness_api.PythonExportContractKind is parser_api.PythonExportContractKind - assert harness_api.PythonFunctionControlFlow is parser_api.PythonFunctionControlFlow - assert harness_api.PythonModuleShape is parser_api.PythonModuleShape - assert harness_api.PythonProjectDependency is parser_api.PythonProjectDependency - assert harness_api.PythonProjectEntryPoint is parser_api.PythonProjectEntryPoint - assert harness_api.PythonProjectImportName is parser_api.PythonProjectImportName - assert harness_api.PythonProjectMetadata is parser_api.PythonProjectMetadata - assert harness_api.PythonProjectScript is parser_api.PythonProjectScript - assert harness_api.PythonPytestOptions is parser_api.PythonPytestOptions - assert harness_api.PythonReasoningTreeFacts is parser_api.PythonReasoningTreeFacts - assert ( - harness_api.PythonReasoningTreeImportEdge + assert asp_python_api.PythonCallEffect is parser_api.PythonCallEffect + assert asp_python_api.PythonClassShape is parser_api.PythonClassShape + assert asp_python_api.PythonExportContract is parser_api.PythonExportContract + assert ( + asp_python_api.PythonExportContractKind is parser_api.PythonExportContractKind + ) + assert ( + asp_python_api.PythonFunctionControlFlow is parser_api.PythonFunctionControlFlow + ) + assert asp_python_api.PythonModuleShape is parser_api.PythonModuleShape + assert asp_python_api.PythonProjectDependency is parser_api.PythonProjectDependency + assert asp_python_api.PythonProjectEntryPoint is parser_api.PythonProjectEntryPoint + assert asp_python_api.PythonProjectImportName is parser_api.PythonProjectImportName + assert asp_python_api.PythonProjectMetadata is parser_api.PythonProjectMetadata + assert asp_python_api.PythonProjectScript is parser_api.PythonProjectScript + assert asp_python_api.PythonPytestOptions is parser_api.PythonPytestOptions + assert ( + asp_python_api.PythonReasoningTreeFacts is parser_api.PythonReasoningTreeFacts + ) + assert ( + asp_python_api.PythonReasoningTreeImportEdge is parser_api.PythonReasoningTreeImportEdge ) - assert harness_api.PythonReasoningTreeNode is parser_api.PythonReasoningTreeNode + assert asp_python_api.PythonReasoningTreeNode is parser_api.PythonReasoningTreeNode assert ( - harness_api.python_reasoning_tree_facts + asp_python_api.python_reasoning_tree_facts is parser_api.python_reasoning_tree_facts ) assert ( - harness_api.parse_python_project_metadata + asp_python_api.parse_python_project_metadata is parser_api.parse_python_project_metadata ) assert ( - harness_api.python_module_namespace_parts + asp_python_api.python_module_namespace_parts is parser_api.python_module_namespace_parts ) assert ( - harness_api.python_module_name_from_path + asp_python_api.python_module_name_from_path is parser_api.python_module_name_from_path ) assert ( - harness_api.python_module_is_package_init + asp_python_api.python_module_is_package_init is parser_api.python_module_is_package_init ) - assert harness_api.python_name_is_public is parser_api.python_name_is_public - assert harness_api.python_scope_is_public is parser_api.python_scope_is_public + assert asp_python_api.python_name_is_public is parser_api.python_name_is_public + assert asp_python_api.python_scope_is_public is parser_api.python_scope_is_public assert ( - harness_api.python_assignment_is_public_top_level + asp_python_api.python_assignment_is_public_top_level is parser_api.python_assignment_is_public_top_level ) assert ( - harness_api.python_module_has_public_surface + asp_python_api.python_module_has_public_surface is parser_api.python_module_has_public_surface ) assert ( - harness_api.python_module_has_public_symbol_surface + asp_python_api.python_module_has_public_symbol_surface is parser_api.python_module_has_public_symbol_surface ) - assert harness_api.python_symbol_is_callable is parser_api.python_symbol_is_callable - assert harness_api.python_symbol_is_class is parser_api.python_symbol_is_class assert ( - harness_api.python_symbol_is_public_callable + asp_python_api.python_symbol_is_callable is parser_api.python_symbol_is_callable + ) + assert asp_python_api.python_symbol_is_class is parser_api.python_symbol_is_class + assert ( + asp_python_api.python_symbol_is_public_callable is parser_api.python_symbol_is_public_callable ) assert ( - harness_api.python_symbol_is_public_callable_boundary + asp_python_api.python_symbol_is_public_callable_boundary is parser_api.python_symbol_is_public_callable_boundary ) assert ( - harness_api.python_symbol_is_public_class + asp_python_api.python_symbol_is_public_class is parser_api.python_symbol_is_public_class ) assert ( - harness_api.python_symbol_is_public_top_level + asp_python_api.python_symbol_is_public_top_level is parser_api.python_symbol_is_public_top_level ) assert ( - harness_api.python_symbol_is_top_level_callable + asp_python_api.python_symbol_is_top_level_callable is parser_api.python_symbol_is_top_level_callable ) assert ( - harness_api.python_symbol_is_test_function + asp_python_api.python_symbol_is_test_function is parser_api.python_symbol_is_test_function ) - assert "python_module_namespace_parts" in harness_api.__all__ - assert "python_module_name_from_path" in harness_api.__all__ - assert "python_module_is_package_init" in harness_api.__all__ - assert "python_name_is_public" in harness_api.__all__ - assert "PythonReasoningTreeFacts" in harness_api.__all__ - assert "PythonProjectMetadata" in harness_api.__all__ - assert "PythonProjectDependency" in harness_api.__all__ - assert "PythonPytestOptions" in harness_api.__all__ - assert "parse_python_project_metadata" in harness_api.__all__ - assert "PythonReasoningTreeImportEdge" in harness_api.__all__ - assert "PythonReasoningTreeNode" in harness_api.__all__ + assert "python_module_namespace_parts" in asp_python_api.__all__ + assert "python_module_name_from_path" in asp_python_api.__all__ + assert "python_module_is_package_init" in asp_python_api.__all__ + assert "python_name_is_public" in asp_python_api.__all__ + assert "PythonReasoningTreeFacts" in asp_python_api.__all__ + assert "PythonProjectMetadata" in asp_python_api.__all__ + assert "PythonProjectDependency" in asp_python_api.__all__ + assert "PythonPytestOptions" in asp_python_api.__all__ + assert "parse_python_project_metadata" in asp_python_api.__all__ + assert "PythonReasoningTreeImportEdge" in asp_python_api.__all__ + assert "PythonReasoningTreeNode" in asp_python_api.__all__ assert "PythonProjectMetadata" in parser_api.__all__ assert "PythonClassShape" in parser_api.__all__ - assert "PythonClassShape" in harness_api.__all__ + assert "PythonClassShape" in asp_python_api.__all__ assert "PythonProjectDependency" in parser_api.__all__ assert "PythonPytestOptions" in parser_api.__all__ assert "PythonReasoningTreeImportEdge" in parser_api.__all__ assert "PythonFunctionControlFlow" in parser_api.__all__ - assert "PythonFunctionControlFlow" in harness_api.__all__ + assert "PythonFunctionControlFlow" in asp_python_api.__all__ assert "parse_python_project_metadata" in parser_api.__all__ - assert "python_reasoning_tree_facts" in harness_api.__all__ - assert "python_scope_is_public" in harness_api.__all__ - assert "python_assignment_is_public_top_level" in harness_api.__all__ - assert "python_module_has_public_surface" in harness_api.__all__ - assert "python_module_has_public_symbol_surface" in harness_api.__all__ - assert "python_symbol_is_callable" in harness_api.__all__ - assert "python_symbol_is_class" in harness_api.__all__ - assert "python_symbol_is_public_callable" in harness_api.__all__ - assert "python_symbol_is_public_callable_boundary" in harness_api.__all__ - assert "python_symbol_is_public_class" in harness_api.__all__ - assert "python_symbol_is_public_top_level" in harness_api.__all__ - assert "python_symbol_is_top_level_callable" in harness_api.__all__ - assert "python_symbol_is_test_function" in harness_api.__all__ + assert "python_reasoning_tree_facts" in asp_python_api.__all__ + assert "python_scope_is_public" in asp_python_api.__all__ + assert "python_assignment_is_public_top_level" in asp_python_api.__all__ + assert "python_module_has_public_surface" in asp_python_api.__all__ + assert "python_module_has_public_symbol_surface" in asp_python_api.__all__ + assert "python_symbol_is_callable" in asp_python_api.__all__ + assert "python_symbol_is_class" in asp_python_api.__all__ + assert "python_symbol_is_public_callable" in asp_python_api.__all__ + assert "python_symbol_is_public_callable_boundary" in asp_python_api.__all__ + assert "python_symbol_is_public_class" in asp_python_api.__all__ + assert "python_symbol_is_public_top_level" in asp_python_api.__all__ + assert "python_symbol_is_top_level_callable" in asp_python_api.__all__ + assert "python_symbol_is_test_function" in asp_python_api.__all__ -def test_root_package_reexports_embedding_harness_surface() -> None: - assert harness_api.AspPythonConfig is harness_facade.AspPythonConfig - assert harness_api.AspPythonReport is harness_facade.AspPythonReport +def test_root_package_reexports_asp_python_surface() -> None: + assert asp_python_api.AspPythonConfig is asp_python_facade.AspPythonConfig + assert asp_python_api.AspPythonReport is asp_python_facade.AspPythonReport + assert ( + asp_python_api.PythonVerificationPolicy + is asp_python_facade.PythonVerificationPolicy + ) assert ( - harness_api.PythonVerificationPolicy is harness_facade.PythonVerificationPolicy + asp_python_api.PythonVerificationProfileHint + is asp_python_facade.PythonVerificationProfileHint ) assert ( - harness_api.PythonVerificationProfileHint - is harness_facade.PythonVerificationProfileHint + asp_python_api.PythonVerificationTaskKind + is asp_python_facade.PythonVerificationTaskKind ) assert ( - harness_api.PythonVerificationTaskKind - is harness_facade.PythonVerificationTaskKind + asp_python_api.PythonProjectPolicyRulePack + is asp_python_facade.PythonProjectPolicyRulePack ) assert ( - harness_api.PythonProjectPolicyRulePack - is harness_facade.PythonProjectPolicyRulePack + asp_python_api.default_asp_python_config + is asp_python_facade.default_asp_python_config ) + assert asp_python_api.asp_python_test is asp_python_facade.asp_python_test assert ( - harness_api.default_python_harness_config - is harness_facade.default_python_harness_config + asp_python_api.python_project_policy_rules + is asp_python_facade.python_project_policy_rules ) - assert harness_api.asp_python_test is harness_facade.asp_python_test assert ( - harness_api.python_project_policy_rules - is harness_facade.python_project_policy_rules + asp_python_api.render_asp_python_report + is asp_python_facade.render_asp_python_report ) assert ( - harness_api.render_python_lang_harness - is harness_facade.render_python_lang_harness + asp_python_api.render_asp_python_report_advice + is asp_python_facade.render_asp_python_report_advice ) assert ( - harness_api.render_python_lang_harness_advice - is harness_facade.render_python_lang_harness_advice + asp_python_api.render_asp_python_report_json + is asp_python_facade.render_asp_python_report_json ) assert ( - harness_api.render_python_lang_harness_json - is harness_facade.render_python_lang_harness_json + asp_python_api.render_python_reasoning_tree + is asp_python_facade.render_python_reasoning_tree ) assert ( - harness_api.render_python_reasoning_tree - is harness_facade.render_python_reasoning_tree + asp_python_api.render_asp_python_agent_snapshot + is asp_python_facade.render_asp_python_agent_snapshot ) assert ( - harness_api.render_asp_python_agent_snapshot - is harness_facade.render_asp_python_agent_snapshot + asp_python_api.render_asp_python_agent_snapshot_with_config + is asp_python_facade.render_asp_python_agent_snapshot_with_config ) assert ( - harness_api.render_asp_python_agent_snapshot_with_config - is harness_facade.render_asp_python_agent_snapshot_with_config + asp_python_api.read_asp_python_config + is asp_python_facade.read_asp_python_config ) - assert harness_api.read_asp_python_config is harness_facade.read_asp_python_config assert ( - harness_api.python_rule_pack_descriptors - is harness_facade.python_rule_pack_descriptors + asp_python_api.python_rule_pack_descriptors + is asp_python_facade.python_rule_pack_descriptors ) - assert harness_api.python_syntax_rules is harness_facade.python_syntax_rules - assert harness_api.run_cli is harness_facade.run_cli - assert harness_api.run_cli_from_env is harness_facade.run_cli_from_env + assert asp_python_api.python_syntax_rules is asp_python_facade.python_syntax_rules + assert asp_python_api.run_cli is asp_python_facade.run_cli + assert asp_python_api.run_cli_from_env is asp_python_facade.run_cli_from_env assert ( - harness_api.plan_python_project_verification - is harness_facade.plan_python_project_verification + asp_python_api.plan_python_project_verification + is asp_python_facade.plan_python_project_verification ) assert ( - harness_api.render_python_verification_plan - is harness_facade.render_python_verification_plan + asp_python_api.render_python_verification_plan + is asp_python_facade.render_python_verification_plan ) - assert "render_python_lang_harness_advice" in harness_api.__all__ - assert "render_python_lang_harness_json" in harness_api.__all__ - assert "render_asp_python_agent_snapshot" in harness_api.__all__ - assert "render_asp_python_agent_snapshot_with_config" in harness_api.__all__ - assert "render_python_reasoning_tree" in harness_api.__all__ - assert "read_asp_python_config" in harness_api.__all__ - assert "run_cli_from_env" in harness_api.__all__ - assert "python_syntax_rules" in harness_api.__all__ - assert "PythonVerificationPolicy" in harness_api.__all__ - assert "PythonVerificationProfileHint" in harness_api.__all__ - assert "PythonVerificationTaskKind" in harness_api.__all__ - assert "plan_python_project_verification" in harness_api.__all__ - assert "render_python_verification_plan" in harness_api.__all__ + assert "render_asp_python_report_advice" in asp_python_api.__all__ + assert "render_asp_python_report_json" in asp_python_api.__all__ + assert "render_asp_python_agent_snapshot" in asp_python_api.__all__ + assert "render_asp_python_agent_snapshot_with_config" in asp_python_api.__all__ + assert "render_python_reasoning_tree" in asp_python_api.__all__ + assert "read_asp_python_config" in asp_python_api.__all__ + assert "run_cli_from_env" in asp_python_api.__all__ + assert "python_syntax_rules" in asp_python_api.__all__ + assert "PythonVerificationPolicy" in asp_python_api.__all__ + assert "PythonVerificationProfileHint" in asp_python_api.__all__ + assert "PythonVerificationTaskKind" in asp_python_api.__all__ + assert "plan_python_project_verification" in asp_python_api.__all__ + assert "render_python_verification_plan" in asp_python_api.__all__ From 805f6ca4b247157399cd2a78b7e924d479ae5980 Mon Sep 17 00:00:00 2001 From: guangtao Date: Sun, 13 Sep 2026 01:40:08 +0800 Subject: [PATCH 19/20] fix(query): bind the current ASP V1 contract --- tree-sitter/tree-sitter-python/grammar-profile.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tree-sitter/tree-sitter-python/grammar-profile.json b/tree-sitter/tree-sitter-python/grammar-profile.json index 8506788..cf69788 100644 --- a/tree-sitter/tree-sitter-python/grammar-profile.json +++ b/tree-sitter/tree-sitter-python/grammar-profile.json @@ -13,7 +13,7 @@ "owner": "main-asp", "repository": "https://github.com/tao3k/agent-semantic-protocols", "revision": "4e3417721d576716bcf82b7cbf8b9c2dc9a2b32a", - "contractFingerprint": "sha256:53ae5428ef4f95b69d547682a158c7b98f474d21122151c82b4d1bdfbf083355", + "contractFingerprint": "sha256:403556a55568509092dcb59625e0782614f3e0a42540996f64c15c2f3903d419", "queryCorpusValidator": "asp-tree-sitter-validate-python-query-corpus" }, "queryCorpus": { From c3afb2a3c70a1c016a7cf0fbbfa2fd92bbc81fcb Mon Sep 17 00:00:00 2001 From: guangtao Date: Sun, 13 Sep 2026 09:19:31 +0800 Subject: [PATCH 20/20] refactor(query): adopt enhanced V1 syntax contracts --- README.md | 14 +- docs/03_features/203_cli.md | 104 - provider/asp-provider-workspace-install.json | 2 +- schemas/.asp-schema-manager-membership.json | 50 +- schemas/.asp-schema-manager-receipt.json | 2 +- ...pace-query-playbook-request.v1.schema.json | 17 +- ...ace-search-playbook-request.v1.schema.json | 74 +- ...syntax-plan-context-request.v1.schema.json | 13 + ...yntax-plan-context-response.v1.schema.json | 14 + ...kspace-syntax-query-request.v1.schema.json | 4 +- ...space-syntax-query-response.v1.schema.json | 65 +- ...tter-query-capability-table.v1.schema.json | 124 + ...sitter-query-operator-table.v1.schema.json | 110 + schemas/language-schema-profiles.json | 2 - ...-topology-inference-receipt.v1.schema.json | 1 + .../resident-syntax-query-plan.v1.schema.json | 238 ++ ...tic-graph-turbo-definitions.v1.schema.json | 1 - ...emantic-graph-turbo-request.v1.schema.json | 17 +- schemas/semantic-graph.v1.schema.json | 8 +- schemas/semantic-handle.v1.schema.json | 2 +- ...emantic-invariant-candidate.v1.schema.json | 225 -- schemas/semantic-search-packet.v1.schema.json | 2803 ----------------- ...tree-sitter-grammar-profile.v1.schema.json | 3 + schemas/semantic-type-surface.v1.schema.json | 2 +- .../_callable_skeleton_projection.py | 1 + src/asp_python/_cli_agent.py | 29 +- src/asp_python/_cli_args.py | 22 +- src/asp_python/_cli_ast_patch.py | 2 +- src/asp_python/_cli_query.py | 2 +- src/asp_python/_cli_query_args.py | 2 +- src/asp_python/_cli_query_hook_args.py | 2 +- src/asp_python/_dev_command_log_command.py | 62 +- .../_semantic_graph_fact_collect.py | 215 -- src/asp_python/_semantic_graph_fact_render.py | 187 -- .../_semantic_graph_fact_render_fields.py | 81 - src/asp_python/_semantic_graph_facts.py | 78 - .../_semantic_graph_project_collect.py | 151 - .../_semantic_graph_project_render.py | 198 -- src/asp_python/_semantic_language_ids.py | 1 - src/asp_python/_semantic_query_packet.py | 7 +- src/asp_python/_tree_sitter_query_catalog.py | 4 +- tests/fixtures/bin/asp | 506 --- tests/unit/asp_python/test_cli.py | 10 +- tests/unit/asp_python/test_dev_command_log.py | 2 +- .../test_exact_source_projection.py | 1 + .../asp_python/test_public_cli_identity.py | 2 +- .../test_search_playbook_boundary.py | 40 - tests/unit/asp_python/test_semantic_cli.py | 2 +- .../test_semantic_cli_tree_sitter_registry.py | 3 +- 49 files changed, 686 insertions(+), 4819 deletions(-) delete mode 100644 docs/03_features/203_cli.md create mode 100644 schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json create mode 100644 schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json create mode 100644 schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json create mode 100644 schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json create mode 100644 schemas/resident-syntax-query-plan.v1.schema.json delete mode 100644 schemas/semantic-invariant-candidate.v1.schema.json delete mode 100644 schemas/semantic-search-packet.v1.schema.json delete mode 100644 src/asp_python/_semantic_graph_fact_collect.py delete mode 100644 src/asp_python/_semantic_graph_fact_render.py delete mode 100644 src/asp_python/_semantic_graph_fact_render_fields.py delete mode 100644 src/asp_python/_semantic_graph_facts.py delete mode 100644 src/asp_python/_semantic_graph_project_collect.py delete mode 100644 src/asp_python/_semantic_graph_project_render.py delete mode 100755 tests/fixtures/bin/asp delete mode 100644 tests/unit/asp_python/test_search_playbook_boundary.py diff --git a/README.md b/README.md index bce47e4..327a73e 100644 --- a/README.md +++ b/README.md @@ -81,17 +81,13 @@ verification tasks into one low-noise library response. The snapshot uses capped module summaries, branches, public owners, import edges, and branch-first profile candidates. -The semantic-language console script exposes search and registry surfaces. -Policy remains a dependency API consumed by pytest/build ownership: +The provider console script exposes Query and registry surfaces. Public source +discovery is owned by the Runtime Search Playbook. Policy remains a dependency +API consumed by pytest/build ownership: ```shell -asp-python search workspace . -asp-python search prime . -asp-python search lexical AspPythonReport owner tests . -asp-python search lexical --query-set AspPythonReport --query-set PythonSemanticSearchOptions owner tests . -asp-python search public-external-types pytest . -asp-python search callsite AspPythonReport . -asp-python search deps pytest . +asp search playbook --language python --rg -n -e AspPythonReport . --tantivy 'title:AspPythonReport^2 OR body:AspPythonReport' +asp query playbook --language python --selector '' --projection source --workspace . asp-python agent doctor --json . asp-python agent guide . python -c 'from asp_python import assert_asp_python_clean; assert_asp_python_clean(".")' diff --git a/docs/03_features/203_cli.md b/docs/03_features/203_cli.md deleted file mode 100644 index cf87bf5..0000000 --- a/docs/03_features/203_cli.md +++ /dev/null @@ -1,104 +0,0 @@ -# CLI - -:PROPERTIES: -:ID: 7f8492d9dbe34f9795a04b2a6f9d72e102c7cf21 -:TYPE: FEATURE -:STATUS: ACTIVE -:LAST_SYNC: 2026-04-30 -:END: - -The package exposes `asp-python` as the semantic-language provider binary. -Project policy is available only through the dependency API and pytest plugin: - -```shell -asp-python search ... [--json] [--package PATH] [PROJECT_ROOT] -asp-python agent doctor [--json] [PROJECT_ROOT] -asp-python agent guide [PROJECT_ROOT] -``` - -When `PROJECT_ROOT` is omitted, the current working directory is used. - -## Semantic Language Identity - -The public semantic-language identity is -`languageId=python`, `providerId=asp-python`, `binary=asp-python`, and -`namespace=agent.semantic-protocols.languages.python.asp-python`. -`asp-python agent doctor --json` emits a `semantic-language-registry.v1` -document with method descriptors, `capabilities`, `ingestRequiredFor`, and -schema registrations for: - -- `schemas/semantic-search-packet.v1.schema.json` -- `schemas/semantic-language-registry.v1.schema.json` -- `schemas/python-semantic-capabilities.v1.schema.json` - -The common registry schema owns the capability descriptor shape. The Python -provider owns its capability vocabulary in the Python-local schema. - -`asp-python agent guide` emits the provider-owned searchflow guide consumed by -root hook deny messages. The root `semantic-agent-hook` only points agents to -this command; the Python provider owns the actual prime, owner, text, ingest, -check, and subagent guidance. - -## Search Views - -`search workspace` is the workspace/router entry point. It emits package-root -facts, dependency counts, import edges, and `next=prime:` targets. - -`search prime` is the low-token project map. It ranks public source owners -before test owners, emits dependency nodes, import edges, findings, and next -actions for `owner`, owner-scoped `text`, and de-duplicated `deps` follow-up -queries. Compact owner lines show only the first few public exports and text -seeds; the JSON packet keeps the full owner export list. - -`search lexical ` searches parser-visible owner paths, namespaces, public -exports, symbols, and source text. `search lexical owner tests` keeps the -matched owners and their importing tests in the same packet, so an agent can -avoid a broad second pass after finding a rule id, symbol, or source token. -Repeated same-axis text probes can be combined with -`search lexical --query-set --query-set [owner tests] [--owner ]`. -Each term is reported in `querySet`, and the packet records -`queryComposition` with `selector=exact-set`. `--owner ` scopes the -query-set to one parser-visible owner. Comma-separated text remains a literal -query, not query-set syntax; a first text query that looks like a flag is also -accepted as a literal query. - -`search ingest` accepts stdin from tools such as `rg -n`, vimgrep output, -plain path lists, or NUL path lists. It detects the shape and groups the input -back to parser-visible owners when possible. - -Dependency and API views are Python-native: - -- `search dependency ` and `search deps ` - combine `pyproject.toml` dependency facts with local import usage. Version - scope is rendered as `current`, `external`, or `unknown`. The `deps` view - also advertises follow-up routes for `dependency`, `public-external-types`, - `api`, and, for current-version API queries, `text` and `tests`. -- `search api ` searches public exports and public symbol shapes. -- `search public-external-types ` searches public functions, classes, - and annotated assignments whose parser-owned signature/base/decorator/type - text exposes that dependency. Direct package/alias references are reported as - `public-external-type`; named imports are reported as - `possible-public-external-type`. -- `search symbol `, `search callsite `, `search import `, - and `search tests ` expose focused parser facts for follow-up repair - loops. Callsite search uses parser-collected `PythonCall` facts, including - dotted method calls such as `client.worker.process(...)`. - -## Policy Dependency API - -Build/test owners call `assert_asp_python_clean` or mount the -pytest plugin. Configuration comes from -`[tool.asp-python]`; structured reports and Agent snapshots -are normal library return values. The provider CLI does not evaluate policy, -override rule severity, or rescan a project. - -## Library Boundary - -`run_cli_from_env()` is the console-script entrypoint. `run_cli(...)` is exposed -for tests and embeddings that want to provide explicit streams or a current -working directory. Both functions delegate to `run_asp_python()` and -the public renderers. - -:RELATIONS: -:LINKS: [ASP Python Boundary](../01_core/101_asp_python_boundary.md), [Rule Catalog](201_rule_catalog.md) -:END: diff --git a/provider/asp-provider-workspace-install.json b/provider/asp-provider-workspace-install.json index c63182a..508314e 100644 --- a/provider/asp-provider-workspace-install.json +++ b/provider/asp-provider-workspace-install.json @@ -7,7 +7,7 @@ "providerId": "asp-python", "binary": "asp-python", "providerRegistration": "asp-provider-registration.json", - "schemaBundleReceipt": "schemas/.asp-schema-manager-receipt.json", + "schemaBundleReceipt": "../schemas/.asp-schema-manager-receipt.json", "workspaceArtifact": { "root": "languages/asp-python/.venv", "entrypoint": "bin/asp-python", diff --git a/schemas/.asp-schema-manager-membership.json b/schemas/.asp-schema-manager-membership.json index f6787f0..b6770ff 100644 --- a/schemas/.asp-schema-manager-membership.json +++ b/schemas/.asp-schema-manager-membership.json @@ -1,7 +1,7 @@ { "languageId": "python", "profileDigest": "blake3-256:a1c247a2b628f4e7fe173ecaa855dfc90e1416c1d168f3b9d67c907a7d12beb2", - "bundleDigest": "blake3-256:d4e8839cbd9febb33729833f242fea1806ebca7e429f7ef95e444c8d96c65e50", + "bundleDigest": "blake3-256:5f087ca57ea83ca4943780b03bda861dd6bfd0ba81524ab9f9991a5ec765db9d", "schemas": [ { "name": "asp-client-cancellation-probe-request.schema.json", @@ -73,23 +73,31 @@ }, { "name": "asp-client-workspace-query-playbook-request.v1.schema.json", - "digest": "blake3-256:171112801e36325b4854c1190ad2f67581a3b4a2d9849f3578ebf80be025ddd3" + "digest": "blake3-256:bc7510bad29790b74a91ec1070451c311909f5e154e2980d25a337171f649332" }, { "name": "asp-client-workspace-search-playbook-request.v1.schema.json", - "digest": "blake3-256:1d2ffadb2d9a101ff544b3bdd356aeaee490ad847f46076c58f8fb78569e4440" + "digest": "blake3-256:f32ea2abb85995ed2e2b5cbc9182de75b6e6b59228851184d80407d7fca4fa06" }, { "name": "asp-client-workspace-source-mutation.schema.json", "digest": "blake3-256:74b0baf814e96b3f2f47eefcc537ae53c5ac740315cdbc8c4f1079dcc2f484a8" }, + { + "name": "asp-client-workspace-syntax-plan-context-request.v1.schema.json", + "digest": "blake3-256:5de15bb202116b438456fce1077baf739360c3a01791f9e6f6c97a6f7efed961" + }, + { + "name": "asp-client-workspace-syntax-plan-context-response.v1.schema.json", + "digest": "blake3-256:ef83ac19ddf8dc9dbd874272e9de12bd861ca82bface838fab5d8d9a28fa5517" + }, { "name": "asp-client-workspace-syntax-query-request.v1.schema.json", - "digest": "blake3-256:10e706b9e1fc701ca240810f97cd4aaacf7d94e598ff9ba2668b0fc47c7ba82c" + "digest": "blake3-256:f37f7e4f5ba66e48d3b0f6d2b7b6bd8770e4164b290f90f2b05a9da21be5f2c4" }, { "name": "asp-client-workspace-syntax-query-response.v1.schema.json", - "digest": "blake3-256:003e5090bb807467a75fa814d92c97cca7489c6bab777f41634a38a57c5b5137" + "digest": "blake3-256:ea0a59d79d102ff37d34583d2075962fad175dd93c64eee582843863d38c5b16" }, { "name": "asp-python-graphs-session.v1.schema.json", @@ -115,6 +123,14 @@ "name": "content-publication-commit.v1.schema.json", "digest": "blake3-256:afac75ce2bece1c60ffb9435a5c107c9687088459988dc7c3e79fd78c38aacfe" }, + { + "name": "enhanced-tree-sitter-query-capability-table.v1.schema.json", + "digest": "blake3-256:14089324f44d5f9725cad72280575cc753c03892eff81e790ece6f9a4e4a4f6d" + }, + { + "name": "enhanced-tree-sitter-query-operator-table.v1.schema.json", + "digest": "blake3-256:eef530969846082023b91405104663a9a28fe56ce449ee1368f6916c10c5e2a7" + }, { "name": "exact-definitions.v1.schema.json", "digest": "blake3-256:f2a9d369c221ad07ec3376150fec180c617d40318d4b0d49def6e68b0c5d00f1" @@ -251,6 +267,10 @@ "name": "resident-search-result.v1.schema.json", "digest": "blake3-256:f725fd1b7a004f28e7e579e6b6b76626880931ff9663bbd1de42519d66afbe78" }, + { + "name": "resident-syntax-query-plan.v1.schema.json", + "digest": "blake3-256:b106844f168b1d0d7226050294b83395a3c4819d9496c289e3d8e24f0bb5613c" + }, { "name": "resolved-source-scope.v1.schema.json", "digest": "blake3-256:0dcc57d7b2f8ffc931901231a72c61f4dca1b22d8eb7ffeccb544e3aad9c5b01" @@ -393,23 +413,19 @@ }, { "name": "semantic-graph-turbo-definitions.v1.schema.json", - "digest": "blake3-256:ab404159bbec13e0b35ef35a8994009ef79fa1abd3025eefce3ddc70780c554e" + "digest": "blake3-256:75fd86c5e08a03949ab0f82bae5a3c91439494c8ffdc7543f5e02aca49b26fd5" }, { "name": "semantic-graph-turbo-request.v1.schema.json", - "digest": "blake3-256:bb79347cc1d9310d0628e2d7184b95df5784dde368e2ea4159a8574bf0cb7176" + "digest": "blake3-256:4d684bf1ffe87d634cc04fd8006c4b2cab4facad7fc4830df460efbbd3aeb8ba" }, { "name": "semantic-graph.v1.schema.json", - "digest": "blake3-256:86bec566b06fa54de2f786aa1b21ae8216a6b46a391e5995f4b76718d9fe0522" + "digest": "blake3-256:294ada02587507054162c766fc932eae693c99337c869661c4627adcb2ff43c1" }, { "name": "semantic-handle.v1.schema.json", - "digest": "blake3-256:385ae734595c8df436ad937b23bfe9ad8bb79af7bca650917a9a0ea6222f6529" - }, - { - "name": "semantic-invariant-candidate.v1.schema.json", - "digest": "blake3-256:dfbbb5dde44825022063dbc5435434d19b684f8a5e4c4f374a5ff035a1e29b43" + "digest": "blake3-256:ebdeb44c571eac4a69a4bfb7f5871e9f0c0063819aac8967eb0c25849eaf7d08" }, { "name": "semantic-language-projection.v1.schema.json", @@ -447,10 +463,6 @@ "name": "semantic-search-definitions.v1.schema.json", "digest": "blake3-256:e9766a7280a5fe3960ab4c478b20a82c1d8bb06a9ea52b0eededc69a33495159" }, - { - "name": "semantic-search-packet.v1.schema.json", - "digest": "blake3-256:a179736b63d468cf78e5c78f4e78b35304d94993e50402cea328be06bec8a9fb" - }, { "name": "semantic-search-storage-route.v1.schema.json", "digest": "blake3-256:cd1011b2097bfdc9b9f0a878ebadac50a7d227405ee637358a64d76763783fa0" @@ -465,7 +477,7 @@ }, { "name": "semantic-tree-sitter-grammar-profile.v1.schema.json", - "digest": "blake3-256:e88ed4a014b4b5d5e5c97a10bca3f5b723c7b23fec4aeae4f0e9b992c5700f77" + "digest": "blake3-256:822c1ad9b0221b319a1a53e439591bdd0035462be3b34aa51f95db4d0c6dd47e" }, { "name": "semantic-tree-sitter-provenance.v1.schema.json", @@ -477,7 +489,7 @@ }, { "name": "semantic-type-surface.v1.schema.json", - "digest": "blake3-256:4ac7fb4a0a1fb1230cdb66d0b674049d79d180da19883d001a8c2e4fc55d30dc" + "digest": "blake3-256:c266f095fa652b0ebc442cca930baed8fed2113f003c9980489d1183de91f8df" }, { "name": "semantic-verification-receipt.v1.schema.json", diff --git a/schemas/.asp-schema-manager-receipt.json b/schemas/.asp-schema-manager-receipt.json index 4412b5e..f5285dd 100644 --- a/schemas/.asp-schema-manager-receipt.json +++ b/schemas/.asp-schema-manager-receipt.json @@ -1,5 +1,5 @@ { "schemaId": "agent.semantic-protocols.language-schema-bundle-receipt", "schemaVersion": "1", - "schemaDigest": "blake3-256:d4e8839cbd9febb33729833f242fea1806ebca7e429f7ef95e444c8d96c65e50" + "schemaDigest": "blake3-256:5f087ca57ea83ca4943780b03bda861dd6bfd0ba81524ab9f9991a5ec765db9d" } \ No newline at end of file diff --git a/schemas/asp-client-workspace-query-playbook-request.v1.schema.json b/schemas/asp-client-workspace-query-playbook-request.v1.schema.json index 3299ec8..12b2c5f 100644 --- a/schemas/asp-client-workspace-query-playbook-request.v1.schema.json +++ b/schemas/asp-client-workspace-query-playbook-request.v1.schema.json @@ -2,22 +2,17 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://schemas.agent-semantic-protocols.dev/asp-client-workspace-query-playbook-request.v1.schema.json", "title": "ASP Client Workspace Query Playbook Request V1", - "description": "One language-neutral northbound Query Playbook request. Runtime injects workspace and execution identity after transport admission.", + "description": "The sole public Query request. Code selectors are declared through --language and document selectors through --documents.", "type": "object", "additionalProperties": false, "required": ["schemaId", "schemaVersion", "selectors", "projection"], "properties": { "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-query-playbook-request"}, "schemaVersion": {"const": "1"}, - "selectors": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9+.-]*://[^\\s#]+#item/.+$(?![\\s\\S])" - } - }, + "language": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$"}, + "documents": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$"}, + "selectors": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "pattern": "^[a-z][a-z0-9+.-]*://[^\\s#]+#item/.+$(?![\\s\\S])"}}, "projection": {"enum": ["source", "callable-skeleton"]} - } + }, + "anyOf": [{"required": ["language"]}, {"required": ["documents"]}] } diff --git a/schemas/asp-client-workspace-search-playbook-request.v1.schema.json b/schemas/asp-client-workspace-search-playbook-request.v1.schema.json index ac6a4f1..b43f9cd 100644 --- a/schemas/asp-client-workspace-search-playbook-request.v1.schema.json +++ b/schemas/asp-client-workspace-search-playbook-request.v1.schema.json @@ -2,78 +2,36 @@ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-search-playbook-request.v1.schema.json", "title": "ASP Client Workspace Search Playbook Request V1", + "description": "The normalized northbound Search request produced from one admitted Scheme composition expression. Code and document producer axes are projected into language and documents; Scheme source and POO implementation objects do not cross this boundary.", "type": "object", "additionalProperties": false, "required": ["schemaId", "schemaVersion", "clauseOrder"], - "dependentRequired": { - "rg": ["tantivy"], - "tantivy": ["rg"] - }, + "dependentRequired": {"rg": ["tantivy"], "tantivy": ["rg"]}, "properties": { "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-search-playbook-request"}, "schemaVersion": {"const": "1"}, - "languages": {"$ref": "#/$defs/producerExpression"}, + "language": {"$ref": "#/$defs/producerExpression"}, "documents": {"$ref": "#/$defs/producerExpression"}, - "workspace": { - "description": "Explicit registered cross-workspace identity. Filesystem paths are not workspace identities.", - "$ref": "#/$defs/registeredName" - }, + "workspace": {"description": "Explicit registered cross-workspace identity. Filesystem paths are not workspace identities.", "$ref": "#/$defs/registeredName"}, "rg": {"$ref": "#/$defs/nativeBlocks"}, "tantivy": {"$ref": "#/$defs/nativeBlocks"}, - "syntax": {"description": "Registered structural queries whose admitted matches establish selector scope inside the fused rg/Tantivy file context.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/producerNativeBlock"}}, + "syntax": {"description": "Compiled resident structural query plans whose admitted matches establish selector scope inside the fused rg/Tantivy file context. Tree-sitter Query source does not cross this normalized boundary.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/residentSyntaxBlock"}}, "nativeSyntax": {"description": "Exact selector queries whose singleton matches establish structural scope inside the fused rg/Tantivy file context.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/exactSelector"}}, "graph": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/graphNativeBlock"}}, - "clauseOrder": { - "description": "Exact CLI occurrence order of registered layout inputs. This preserves repeated-block identity but does not create execution edges; the admitted Search Layout owns shared-scope and serial composition.", - "type": "array", - "minItems": 1, - "items": {"$ref": "#/$defs/clauseRef"} - } + "clauseOrder": {"description": "Exact source order of leaves in the admitted V1 composition. This preserves repeated-leaf identity but does not independently create execution edges; the admitted Search Layout owns shared-scope and serial composition.", "type": "array", "minItems": 1, "items": {"$ref": "#/$defs/clauseRef"}} }, - "allOf": [{"required": ["rg", "tantivy"]}], + "allOf": [ + {"anyOf": [{"required": ["language"]}, {"required": ["documents"]}]}, + {"required": ["rg", "tantivy"]} + ], "$defs": { - "registeredName": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*$" - }, - "exactSelector": { - "type": "string", - "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])" - }, - "producerExpression": { - "type": "string", - "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$" - }, + "registeredName": {"type": "string", "minLength": 1, "maxLength": 512, "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*$"}, + "exactSelector": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9+.-]*://[^\\s]+#item/[^\\s]+$(?![\\s\\S])"}, + "producerExpression": {"description": "One or more registered producers from the axis named by the enclosing field.", "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]*(\\|[A-Za-z0-9][A-Za-z0-9._+-]*)*$"}, "nativeArgv": {"type": "array", "minItems": 1, "items": {"type": "string"}}, "nativeBlocks": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/nativeArgv"}}, - "producerNativeBlock": { - "type": "object", - "additionalProperties": false, - "required": ["producer", "argv"], - "properties": { - "producer": {"$ref": "#/$defs/registeredName"}, - "argv": {"$ref": "#/$defs/nativeArgv"} - } - }, - "graphNativeBlock": { - "type": "object", - "additionalProperties": false, - "required": ["language", "argv"], - "properties": { - "language": {"enum": ["gql", "pgql"]}, - "argv": {"$ref": "#/$defs/nativeArgv"} - } - }, - "clauseRef": { - "type": "object", - "additionalProperties": false, - "required": ["axis", "blockIndex"], - "properties": { - "axis": {"enum": ["rg", "tantivy", "syntax", "native-syntax", "graph"]}, - "blockIndex": {"type": "integer", "minimum": 0} - } - } + "residentSyntaxBlock": {"type": "object", "additionalProperties": false, "required": ["producer", "plan"], "properties": {"producer": {"$ref": "#/$defs/registeredName"}, "plan": {"$ref": "resident-syntax-query-plan.v1.schema.json"}}}, + "graphNativeBlock": {"type": "object", "additionalProperties": false, "required": ["language", "argv"], "properties": {"language": {"enum": ["gql", "pgql"]}, "argv": {"$ref": "#/$defs/nativeArgv"}}}, + "clauseRef": {"type": "object", "additionalProperties": false, "required": ["axis", "blockIndex"], "properties": {"axis": {"enum": ["rg", "tantivy", "syntax", "native-syntax", "graph"]}, "blockIndex": {"type": "integer", "minimum": 0}}} } } diff --git a/schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json b/schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json new file mode 100644 index 0000000..b554425 --- /dev/null +++ b/schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-syntax-plan-context-request.v1.schema.json", + "title": "ASP Client Workspace Syntax Plan Context Request V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "producer"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-syntax-plan-context-request"}, + "schemaVersion": {"const": "1"}, + "producer": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._+:/-]*$"} + } +} diff --git a/schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json b/schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json new file mode 100644 index 0000000..0b0db93 --- /dev/null +++ b/schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/asp-client-workspace-syntax-plan-context-response.v1.schema.json", + "title": "ASP Client Workspace Syntax Plan Context Response V1", + "type": "object", + "additionalProperties": false, + "required": ["schemaId", "schemaVersion", "generationDigest", "capability"], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.asp-client-workspace-syntax-plan-context-response"}, + "schemaVersion": {"const": "1"}, + "generationDigest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "capability": {"$ref": "enhanced-tree-sitter-query-capability-table.v1.schema.json"} + } +} diff --git a/schemas/asp-client-workspace-syntax-query-request.v1.schema.json b/schemas/asp-client-workspace-syntax-query-request.v1.schema.json index 45c7dbf..f7465e6 100644 --- a/schemas/asp-client-workspace-syntax-query-request.v1.schema.json +++ b/schemas/asp-client-workspace-syntax-query-request.v1.schema.json @@ -21,10 +21,10 @@ "items": { "type": "object", "additionalProperties": false, - "required": ["producer", "argv"], + "required": ["producer", "plan"], "properties": { "producer": {"type": "string", "minLength": 1}, - "argv": {"type": "array", "minItems": 1, "items": {"type": "string", "minLength": 1}} + "plan": {"$ref": "resident-syntax-query-plan.v1.schema.json"} } } }, diff --git a/schemas/asp-client-workspace-syntax-query-response.v1.schema.json b/schemas/asp-client-workspace-syntax-query-response.v1.schema.json index b9e530d..cba234c 100644 --- a/schemas/asp-client-workspace-syntax-query-response.v1.schema.json +++ b/schemas/asp-client-workspace-syntax-query-response.v1.schema.json @@ -15,13 +15,74 @@ "items": { "type": "object", "additionalProperties": false, - "required": ["owner", "selector", "relation"], + "required": ["owner", "selector", "capture", "relation", "selected"], "properties": { "owner": {"type": "string", "minLength": 1}, "selector": {"type": "string", "pattern": "^[^:]+://.+#item/.+$"}, - "relation": {"type": "string", "minLength": 1} + "capture": {"type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_.-]*$"}, + "relation": {"type": "string", "minLength": 1}, + "selected": {"$ref": "#/$defs/selection"} } } } + }, + "$defs": { + "selection": { + "type": "object", + "additionalProperties": false, + "minProperties": 1, + "properties": { + "kind": {"type": "string", "minLength": 1}, + "name": {"type": "string", "minLength": 1}, + "selector": {"type": "string", "pattern": "^[^:]+://.+#item/.+$"}, + "byteRange": { + "type": "array", + "prefixItems": [ + {"type": "integer", "minimum": 0}, + {"type": "integer", "minimum": 0} + ], + "minItems": 2, + "maxItems": 2 + }, + "scopes": { + "type": "array", + "items": {"$ref": "#/$defs/scope"} + }, + "queryKeys": { + "type": "array", + "uniqueItems": true, + "items": {"type": "string", "minLength": 1} + }, + "projections": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "#/$defs/projection"} + }, + "relations": { + "type": "array", + "uniqueItems": true, + "items": {"$ref": "provider-definitions.v1.schema.json#/$defs/relation"} + } + } + }, + "scope": { + "type": "object", + "additionalProperties": false, + "required": ["relation", "kind", "symbol"], + "properties": { + "relation": {"type": "string", "minLength": 1}, + "kind": {"type": "string", "minLength": 1}, + "symbol": {"type": "string", "minLength": 1} + } + }, + "projection": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "digest"], + "properties": { + "kind": {"type": "string", "minLength": 1}, + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"} + } + } } } diff --git a/schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json b/schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json new file mode 100644 index 0000000..bda2e20 --- /dev/null +++ b/schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json @@ -0,0 +1,124 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/enhanced-tree-sitter-query-capability-table.v1.schema.json", + "title": "Provider Enhanced Tree-sitter Query Capability Table V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "languageId", + "providerId", + "parserAbi", + "queryGrammar", + "operatorTableDigest", + "tableDigest", + "rows" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.enhanced-tree-sitter-query-capability-table"}, + "schemaVersion": {"const": "1"}, + "languageId": {"$ref": "#/$defs/identity"}, + "providerId": {"$ref": "#/$defs/identity"}, + "parserAbi": {"$ref": "#/$defs/versionedIdentity"}, + "queryGrammar": {"$ref": "#/$defs/versionedIdentity"}, + "operatorTableDigest": {"$ref": "#/$defs/digest"}, + "tableDigest": {"$ref": "#/$defs/digest"}, + "rows": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/row"} + } + }, + "$defs": { + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "identity": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._+:/-]*$"}, + "versionedIdentity": { + "type": "object", + "additionalProperties": false, + "required": ["id", "version", "digest"], + "properties": { + "id": {"$ref": "#/$defs/identity"}, + "version": {"type": "string", "minLength": 1}, + "digest": {"$ref": "#/$defs/digest"} + } + }, + "lowering": { + "type": "object", + "additionalProperties": false, + "required": ["residentFactPath", "constraintKind"], + "properties": { + "residentFactPath": { + "enum": [ + "kind", + "name", + "selector", + "byte-range", + "scopes.relation", + "scopes.kind", + "scopes.symbol", + "queryKeys", + "projections.kind", + "relations" + ] + }, + "constraintKind": {"enum": ["scalar", "set", "range", "relation", "capture"]}, + "residentValue": {"type": "string", "minLength": 1} + } + }, + "equivalenceEvidence": { + "type": "object", + "additionalProperties": false, + "required": ["method", "corpusDigest", "receiptDigest"], + "properties": { + "method": {"const": "native-parser-tree-sitter-differential-v1"}, + "corpusDigest": {"$ref": "#/$defs/digest"}, + "receiptDigest": {"$ref": "#/$defs/digest"} + } + }, + "row": { + "type": "object", + "additionalProperties": false, + "required": ["rowId", "kind", "sourceName", "publicationState"], + "properties": { + "rowId": {"$ref": "#/$defs/identity"}, + "kind": { + "enum": [ + "node-type", + "field", + "capture-binding", + "fact-path", + "projection-kind", + "relation-kind" + ] + }, + "sourceName": {"type": "string", "minLength": 1}, + "publicationState": {"enum": ["runtime", "provider-local", "not-materialized"]}, + "lowering": {"$ref": "#/$defs/lowering"}, + "equivalenceEvidence": {"$ref": "#/$defs/equivalenceEvidence"} + }, + "allOf": [ + { + "if": {"properties": {"publicationState": {"const": "runtime"}}, "required": ["publicationState"]}, + "then": {"required": ["lowering", "equivalenceEvidence"]} + }, + { + "if": { + "properties": { + "kind": {"const": "node-type"}, + "publicationState": {"const": "runtime"} + }, + "required": ["kind", "publicationState"] + }, + "then": { + "properties": {"lowering": {"required": ["residentValue"]}} + } + }, + { + "if": {"properties": {"publicationState": {"enum": ["provider-local", "not-materialized"]}}, "required": ["publicationState"]}, + "then": {"not": {"required": ["equivalenceEvidence"]}} + } + ] + } + } +} diff --git a/schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json b/schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json new file mode 100644 index 0000000..cd211a0 --- /dev/null +++ b/schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json @@ -0,0 +1,110 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/enhanced-tree-sitter-query-operator-table.v1.schema.json", + "title": "Enhanced Tree-sitter Query Operator Table V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "profileId", + "owner", + "declarationDigest", + "operators", + "recoveries" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.enhanced-tree-sitter-query-operator-table"}, + "schemaVersion": {"const": "1"}, + "profileId": {"const": "mrr.enhanced-tree-sitter-query.v1"}, + "owner": {"const": "mrr-gerbil-aot"}, + "declarationDigest": {"$ref": "#/$defs/digest"}, + "operators": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/operator"} + }, + "recoveries": { + "type": "array", + "minItems": 1, + "items": {"$ref": "#/$defs/recovery"} + } + }, + "$defs": { + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "identity": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._+-]*$"}, + "operand": { + "type": "object", + "additionalProperties": false, + "required": ["position", "kind", "domain", "cardinality"], + "properties": { + "position": {"type": "integer", "minimum": 0}, + "kind": {"enum": ["capture", "string"]}, + "domain": { + "enum": [ + "item-capture", + "scalar-fact-path", + "set-fact-path", + "range-fact-path", + "literal", + "regex", + "direction", + "relation-kind", + "endpoint-selector", + "range-mode", + "canonical-u64", + "result-field" + ] + }, + "cardinality": {"enum": ["one", "optional", "one-or-more"]} + } + }, + "operator": { + "type": "object", + "additionalProperties": false, + "required": [ + "spelling", + "kind", + "minimumArity", + "maximumArity", + "operands", + "lowering", + "failureCode" + ], + "properties": { + "spelling": {"type": "string", "pattern": "^#asp-[a-z][a-z-]*[?!]$"}, + "kind": {"enum": ["predicate", "directive"]}, + "minimumArity": {"type": "integer", "minimum": 1}, + "maximumArity": {"type": ["integer", "null"], "minimum": 1}, + "operands": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/operand"}}, + "lowering": { + "enum": [ + "scalar-eq", + "scalar-not-eq", + "scalar-match", + "scalar-not-match", + "set-any-eq", + "set-none-eq", + "set-any-match", + "set-none-match", + "range", + "related", + "not-related", + "select" + ] + }, + "failureCode": {"$ref": "#/$defs/identity"} + } + }, + "recovery": { + "type": "object", + "additionalProperties": false, + "required": ["site", "code", "strategy"], + "properties": { + "site": {"$ref": "#/$defs/identity"}, + "code": {"$ref": "#/$defs/identity"}, + "strategy": {"const": "reject"} + } + } + } +} diff --git a/schemas/language-schema-profiles.json b/schemas/language-schema-profiles.json index e50bc20..13d7675 100644 --- a/schemas/language-schema-profiles.json +++ b/schemas/language-schema-profiles.json @@ -62,7 +62,6 @@ "exact-structural-selector.v1.schema.json", "provider-native-exact-request.v1.schema.json", "provider-native-exact-response.v1.schema.json", - "semantic-search-packet.v1.schema.json", "semantic-search-storage-route.v1.schema.json", "semantic-query-packet.v1.schema.json", "semantic-exact-selector-receipt.v1.schema.json", @@ -189,7 +188,6 @@ "exact-structural-selector.v1.schema.json", "provider-native-exact-request.v1.schema.json", "provider-native-exact-response.v1.schema.json", - "semantic-search-packet.v1.schema.json", "semantic-query-packet.v1.schema.json", "semantic-owner-item-evidence.v1.schema.json", "semantic-extension-pattern-mapping.v1.schema.json", diff --git a/schemas/project-topology-inference-receipt.v1.schema.json b/schemas/project-topology-inference-receipt.v1.schema.json index a982dc4..27f3c6d 100644 --- a/schemas/project-topology-inference-receipt.v1.schema.json +++ b/schemas/project-topology-inference-receipt.v1.schema.json @@ -149,3 +149,4 @@ }, "$comment": "V1 semantic admission additionally requires deterministic ordering, unique input edge identities, every premiseEdgeIds entry to resolve in inputEdges, and the receipt to be independently admitted before Runtime attachment." } + diff --git a/schemas/resident-syntax-query-plan.v1.schema.json b/schemas/resident-syntax-query-plan.v1.schema.json new file mode 100644 index 0000000..bf7d2a6 --- /dev/null +++ b/schemas/resident-syntax-query-plan.v1.schema.json @@ -0,0 +1,238 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agent-semantic-protocols.dev/schemas/resident-syntax-query-plan.v1.schema.json", + "title": "Resident Syntax Query Plan V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaId", + "schemaVersion", + "profileId", + "planDigest", + "queryDigest", + "languageId", + "providerId", + "parserAbiDigest", + "queryGrammarDigest", + "operatorTableDigest", + "capabilityTableDigest", + "generationDigest", + "patterns", + "selectedFields", + "requiredCapabilityRows", + "regexPrograms" + ], + "properties": { + "schemaId": {"const": "agent.semantic-protocols.resident-syntax-query-plan"}, + "schemaVersion": {"const": "1"}, + "profileId": {"const": "asp.enhanced-tree-sitter-query.v1"}, + "planDigest": {"$ref": "#/$defs/digest"}, + "queryDigest": {"$ref": "#/$defs/digest"}, + "languageId": {"$ref": "#/$defs/identity"}, + "providerId": {"$ref": "#/$defs/identity"}, + "parserAbiDigest": {"$ref": "#/$defs/digest"}, + "queryGrammarDigest": {"$ref": "#/$defs/digest"}, + "operatorTableDigest": {"$ref": "#/$defs/digest"}, + "capabilityTableDigest": {"$ref": "#/$defs/digest"}, + "generationDigest": {"$ref": "#/$defs/digest"}, + "patterns": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/pattern"}}, + "selectedFields": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/resultField"} + }, + "requiredCapabilityRows": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": {"$ref": "#/$defs/identity"} + }, + "regexPrograms": { + "type": "array", + "items": {"$ref": "#/$defs/regexProgram"} + } + }, + "$defs": { + "digest": {"type": "string", "pattern": "^blake3-256:[0-9a-f]{64}$"}, + "identity": {"type": "string", "pattern": "^[A-Za-z][A-Za-z0-9._+:/-]*$"}, + "resultField": { + "enum": ["kind", "name", "selector", "byte-range", "scopes", "queryKeys", "projections", "relations"] + }, + "cardinality": { + "type": "object", + "additionalProperties": false, + "required": ["minimum", "maximum"], + "properties": { + "minimum": {"type": "integer", "minimum": 0}, + "maximum": {"type": ["integer", "null"], "minimum": 1} + } + }, + "capture": { + "type": "object", + "additionalProperties": false, + "required": ["name", "residentFactPath", "cardinality", "capabilityRowId"], + "properties": { + "name": {"type": "string", "pattern": "^[A-Za-z_][A-Za-z0-9_.-]*$"}, + "residentFactPath": {"$ref": "#/$defs/factPath"}, + "cardinality": {"$ref": "#/$defs/cardinality"}, + "capabilityRowId": {"$ref": "#/$defs/identity"} + } + }, + "factPath": { + "enum": [ + "kind", + "name", + "selector", + "byte-range", + "scopes.relation", + "scopes.kind", + "scopes.symbol", + "queryKeys", + "projections.kind", + "relations" + ] + }, + "origin": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capabilityRowId"], + "properties": { + "kind": {"enum": ["node", "field", "capture", "anchor", "quantifier", "standard-predicate", "asp-predicate"]}, + "capabilityRowId": {"$ref": "#/$defs/identity"} + } + }, + "condition": { + "oneOf": [ + {"$ref": "#/$defs/trueCondition"}, + {"$ref": "#/$defs/compositeCondition"}, + {"$ref": "#/$defs/scalarCondition"}, + {"$ref": "#/$defs/setCondition"}, + {"$ref": "#/$defs/rangeCondition"}, + {"$ref": "#/$defs/relationCondition"} + ] + }, + "trueCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "origin"], + "properties": { + "kind": {"const": "true"}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "compositeCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "terms"], + "properties": { + "kind": {"enum": ["all", "any"]}, + "terms": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/condition"}} + } + }, + "scalarCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capture", "factPath", "operator", "value", "origin"], + "allOf": [ + { + "if": {"properties": {"operator": {"enum": ["any-of", "not-any-of"]}}, "required": ["operator"]}, + "then": {"properties": {"value": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string"}}}}, + "else": {"properties": {"value": {"type": "string"}}} + }, + { + "if": {"properties": {"operator": {"enum": ["match", "not-match", "any-match", "any-not-match"]}}, "required": ["operator"]}, + "then": {"required": ["regexProgramId"]}, + "else": {"not": {"required": ["regexProgramId"]}} + } + ], + "properties": { + "kind": {"const": "scalar"}, + "capture": {"type": "string", "minLength": 1}, + "factPath": {"enum": ["kind", "name", "selector"]}, + "operator": {"enum": ["eq", "not-eq", "match", "not-match", "any-eq", "any-not-eq", "any-match", "any-not-match", "any-of", "not-any-of"]}, + "value": { + "oneOf": [ + {"type": "string"}, + {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string"}} + ] + }, + "regexProgramId": {"$ref": "#/$defs/identity"}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "setCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capture", "factPath", "operator", "value", "origin"], + "allOf": [ + { + "if": {"properties": {"operator": {"enum": ["any-match", "none-match"]}}, "required": ["operator"]}, + "then": {"required": ["regexProgramId"]}, + "else": {"not": {"required": ["regexProgramId"]}} + } + ], + "properties": { + "kind": {"const": "set"}, + "capture": {"type": "string", "minLength": 1}, + "factPath": {"enum": ["scopes.relation", "scopes.kind", "scopes.symbol", "queryKeys", "projections.kind"]}, + "operator": {"enum": ["any-eq", "none-eq", "any-match", "none-match"]}, + "value": {"type": "string"}, + "regexProgramId": {"$ref": "#/$defs/identity"}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "rangeCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capture", "mode", "start", "end", "origin"], + "properties": { + "kind": {"const": "range"}, + "capture": {"type": "string", "minLength": 1}, + "mode": {"enum": ["within", "contains", "overlaps"]}, + "start": {"type": "string", "pattern": "^(0|[1-9][0-9]*)$"}, + "end": {"type": "string", "pattern": "^(0|[1-9][0-9]*)$"}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "relationCondition": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "capture", "operator", "direction", "relationKind", "origin"], + "properties": { + "kind": {"const": "relation"}, + "capture": {"type": "string", "minLength": 1}, + "operator": {"enum": ["related", "not-related"]}, + "direction": {"enum": ["in", "out", "either"]}, + "relationKind": {"type": "string", "minLength": 1}, + "endpointSelector": {"type": "string", "minLength": 1}, + "origin": {"$ref": "#/$defs/origin"} + } + }, + "pattern": { + "type": "object", + "additionalProperties": false, + "required": ["index", "captures", "structure", "predicates"], + "properties": { + "index": {"type": "integer", "minimum": 0}, + "captures": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/capture"}}, + "structure": {"$ref": "#/$defs/condition"}, + "predicates": {"type": "array", "items": {"$ref": "#/$defs/condition"}} + } + }, + "regexProgram": { + "type": "object", + "additionalProperties": false, + "required": ["id", "engineId", "engineVersion", "syntaxProfile", "encoding", "program", "programDigest"], + "properties": { + "id": {"$ref": "#/$defs/identity"}, + "engineId": {"$ref": "#/$defs/identity"}, + "engineVersion": {"type": "string", "minLength": 1}, + "syntaxProfile": {"const": "tree-sitter-rust-regex-v1"}, + "encoding": {"const": "base64"}, + "program": {"type": "string", "pattern": "^[A-Za-z0-9+/]*={0,2}$"}, + "programDigest": {"$ref": "#/$defs/digest"} + } + } + } +} diff --git a/schemas/semantic-graph-turbo-definitions.v1.schema.json b/schemas/semantic-graph-turbo-definitions.v1.schema.json index 43b3578..54315e6 100644 --- a/schemas/semantic-graph-turbo-definitions.v1.schema.json +++ b/schemas/semantic-graph-turbo-definitions.v1.schema.json @@ -9,7 +9,6 @@ "owner-query", "query-deps", "owner-tests", - "prime", "read-frontier", "failure-frontier", "field-impact", diff --git a/schemas/semantic-graph-turbo-request.v1.schema.json b/schemas/semantic-graph-turbo-request.v1.schema.json index 9c851bd..0b335c8 100644 --- a/schemas/semantic-graph-turbo-request.v1.schema.json +++ b/schemas/semantic-graph-turbo-request.v1.schema.json @@ -49,13 +49,10 @@ "const": "graph-turbo-request" }, "surface": { - "description": "ASP search surface that produced this graph-turbo request. This is the cache and receipt boundary for ranking semantics, not an agent-facing graph command.", + "description": "ASP public surface that produced this graph-turbo request. This is the cache and receipt boundary for ranking semantics, not an agent-facing graph command.", "enum": [ - "search-pipe", - "search-rg", - "search-fd", - "search-lexical", - "search-ingest", + "search-playbook", + "query", "evidence-analyze" ] }, @@ -226,7 +223,7 @@ "uniqueItems": true }, "actionFrontier": { - "description": "Typed action facts projected from the search-pipe frontier. These facts are the materializer input for prompt-visible nextCommand text; they must not carry materialized shell strings or argv.", + "description": "Typed action facts projected from the Search Playbook frontier. These facts are the materializer input for prompt-visible next-action text; they must not carry materialized shell strings or argv.", "type": "array", "items": { "$ref": "#/$defs/actionFrontierEntry" @@ -801,7 +798,7 @@ "properties": { "kind": { "enum": [ - "search-owner-items" + "query-owner-items" ] }, "languageId": { @@ -861,7 +858,7 @@ "treesitter-query", "multi-clause-rg-query", "scoped-rg-query", - "search-deps", + "dependency-query", "syntax" ] }, @@ -873,7 +870,7 @@ "rg", "owner-items", "treesitter-query", - "search-deps" + "query" ] }, "target": { diff --git a/schemas/semantic-graph.v1.schema.json b/schemas/semantic-graph.v1.schema.json index e5e1858..7e25f8a 100644 --- a/schemas/semantic-graph.v1.schema.json +++ b/schemas/semantic-graph.v1.schema.json @@ -60,16 +60,14 @@ "scope": { "enum": [ "workspace", - "prime", + "search-playbook", "owner", "dependency", "policy", "query", "query-set", - "lexical", "text", "tests", - "ingest", "custom" ] }, @@ -241,15 +239,13 @@ "scope": { "enum": [ "workspace", - "prime", + "search-playbook", "owner", "dependency", "policy", "query", "query-set", - "lexical", "tests", - "ingest", "failure", "custom" ] diff --git a/schemas/semantic-handle.v1.schema.json b/schemas/semantic-handle.v1.schema.json index 07f2e33..ce90fc9 100644 --- a/schemas/semantic-handle.v1.schema.json +++ b/schemas/semantic-handle.v1.schema.json @@ -47,7 +47,7 @@ "scope": { "enum": [ "workspace", - "prime", + "search-playbook", "owner", "policy", "schema", diff --git a/schemas/semantic-invariant-candidate.v1.schema.json b/schemas/semantic-invariant-candidate.v1.schema.json deleted file mode 100644 index a349d05..0000000 --- a/schemas/semantic-invariant-candidate.v1.schema.json +++ /dev/null @@ -1,225 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-invariant-candidate.v1.schema.json", - "title": "Semantic Invariant Candidate", - "description": "Language-neutral candidate invariant raised from parser-owned findings before receipt, proof, or review evaluation.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "candidates" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-invariant-candidate" - }, - "schemaVersion": { - "const": "1" - }, - "candidates": { - "type": "array", - "items": { - "$ref": "#/$defs/invariantCandidate" - } - } - }, - "$defs": { - "scalar": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - } - } - ] - }, - "fields": { - "type": "object", - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - }, - "projectPath": { - "type": "string", - "minLength": 1, - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*)$" - }, - "location": { - "type": "object", - "additionalProperties": false, - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "lineRange": { - "type": "string", - "description": "Compact source line range as start:end, for example 10:43.", - "pattern": "^[1-9][0-9]*:[1-9][0-9]*$" - } - } - }, - "severity": { - "enum": [ - "info", - "warning", - "error" - ] - }, - "status": { - "enum": [ - "candidate", - "accepted", - "verified", - "waived", - "stale" - ] - }, - "invariantKind": { - "enum": [ - "primitive-identifier-boundary", - "public-data-primitive-fields", - "anonymous-tuple-api-surface", - "primitive-type-alias-boundary", - "stringly-state-boundary", - "parser-fact", - "public-api-shape", - "module-reasoning-tree", - "dependency-graph-acyclicity", - "custom" - ] - }, - "receiptKind": { - "enum": [ - "cargo-check", - "cargo-test", - "clippy", - "expect-test", - "proptest", - "cargo-fuzz", - "kani", - "creusot", - "verus", - "waiver" - ] - }, - "evidence": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "summary" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "location": { - "$ref": "#/$defs/location" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "invariantCandidate": { - "type": "object", - "additionalProperties": false, - "required": [ - "invariantId", - "sourceRuleId", - "rulePackId", - "kind", - "status", - "severity", - "title", - "hypothesis", - "location", - "evidence", - "requiredReceipts" - ], - "properties": { - "invariantId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.:-]*$" - }, - "sourceRuleId": { - "type": "string", - "minLength": 1 - }, - "rulePackId": { - "type": "string", - "minLength": 1 - }, - "kind": { - "$ref": "#/$defs/invariantKind" - }, - "status": { - "$ref": "#/$defs/status" - }, - "severity": { - "$ref": "#/$defs/severity" - }, - "title": { - "type": "string", - "minLength": 1 - }, - "hypothesis": { - "type": "string", - "minLength": 1 - }, - "location": { - "$ref": "#/$defs/location" - }, - "evidence": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/evidence" - } - }, - "requiredReceipts": { - "type": "array", - "items": { - "$ref": "#/$defs/receiptKind" - } - }, - "proofTargets": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/invariantKind" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - } - } -} diff --git a/schemas/semantic-search-packet.v1.schema.json b/schemas/semantic-search-packet.v1.schema.json deleted file mode 100644 index 156b7d5..0000000 --- a/schemas/semantic-search-packet.v1.schema.json +++ /dev/null @@ -1,2803 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://agent-semantic-protocols.local/schemas/semantic-search-packet.v1.schema.json", - "title": "Semantic Search Packet", - "description": "Language-neutral bounded semantic search packet emitted by semantic language providers.", - "type": "object", - "additionalProperties": false, - "required": [ - "schemaId", - "schemaVersion", - "protocolId", - "protocolVersion", - "languageId", - "providerId", - "binary", - "namespace", - "method", - "projectRoot", - "view", - "renderMode", - "header", - "nodes", - "edges", - "owners", - "hits", - "findings", - "nextActions", - "notes" - ], - "properties": { - "schemaId": { - "const": "agent.semantic-protocols.semantic-search-packet" - }, - "schemaVersion": { - "const": "1" - }, - "protocolId": { - "const": "agent.semantic-protocols.semantic-language" - }, - "protocolVersion": { - "const": "1" - }, - "languageId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "providerId": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "binary": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "namespace": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" - }, - "method": { - "type": "string", - "pattern": "^search/[a-z][a-z0-9_-]*$" - }, - "projectRoot": { - "type": "string", - "minLength": 1 - }, - "packageName": { - "type": "string", - "minLength": 1 - }, - "projectPackage": { - "$ref": "#/$defs/projectPackage" - }, - "extensions": { - "type": "array", - "items": { - "$ref": "#/$defs/providerExtension" - } - }, - "view": { - "enum": [ - "workspace", - "prime", - "owner", - "dependency", - "deps", - "features", - "targets", - "symbol", - "callsite", - "import", - "query", - "cfg", - "patterns", - "pattern", - "docs", - "api", - "public-external-types", - "policy", - "tests", - "lexical", - "reasoning", - "env", - "runtime-source", - "compiler-evidence", - "lang", - "std", - "proof", - "capability", - "extension", - "compare", - "ingest", - "failure" - ] - }, - "renderMode": { - "enum": [ - "graph", - "hits", - "both", - "seeds", - "facts" - ] - }, - "query": { - "type": "string" - }, - "querySet": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/queryTerm" - } - }, - "queryComposition": { - "$ref": "#/$defs/queryComposition" - }, - "queryCoverage": { - "type": "array", - "items": { - "$ref": "#/$defs/queryCoverage" - } - }, - "ownerResolution": { - "type": "array", - "items": { - "$ref": "#/$defs/ownerResolution" - } - }, - "sourceCoverage": { - "type": "array", - "items": { - "$ref": "#/$defs/sourceCoverage" - } - }, - "testResolution": { - "type": "array", - "items": { - "$ref": "#/$defs/testResolution" - } - }, - "runtimeCost": { - "$ref": "#/$defs/runtimeCost" - }, - "cache": { - "$ref": "#/$defs/cache" - }, - "searchSynthesis": { - "$ref": "#/$defs/searchSynthesis" - }, - "routeGraph": { - "$ref": "#/$defs/routeGraph" - }, - "actionFrontier": { - "type": "array", - "items": { - "$ref": "#/$defs/routeAction" - } - }, - "finder": { - "$ref": "#/$defs/finder" - }, - "noOutput": { - "$ref": "#/$defs/noOutput" - }, - "avoidNextActions": { - "$ref": "#/$defs/avoidNextActionList" - }, - "header": { - "$ref": "#/$defs/header" - }, - "inputDetection": { - "$ref": "#/$defs/inputDetection" - }, - "packages": { - "type": "array", - "items": { - "$ref": "#/$defs/fact" - } - }, - "nodes": { - "type": "array", - "items": { - "$ref": "#/$defs/node" - } - }, - "edges": { - "type": "array", - "items": { - "$ref": "#/$defs/edge" - } - }, - "owners": { - "type": "array", - "items": { - "$ref": "#/$defs/owner" - } - }, - "items": { - "type": "array", - "items": { - "$ref": "#/$defs/item" - } - }, - "typeSurfaces": { - "type": "array", - "items": { - "$ref": "semantic-type-surface.v1.schema.json#/$defs/typeSurface" - } - }, - "invariantCandidates": { - "type": "array", - "items": { - "$ref": "semantic-invariant-candidate.v1.schema.json#/$defs/invariantCandidate" - } - }, - "semanticHandles": { - "type": "array", - "items": { - "$ref": "semantic-handle.v1.schema.json#/$defs/semanticHandle" - } - }, - "syntaxQueryRef": { - "$ref": "semantic-tree-sitter-provenance.v1.schema.json#/$defs/syntaxQueryRef" - }, - "syntaxMatchRefs": { - "$ref": "semantic-tree-sitter-provenance.v1.schema.json#/$defs/syntaxMatchRefs" - }, - "syntaxCaptureRefs": { - "$ref": "semantic-tree-sitter-provenance.v1.schema.json#/$defs/syntaxCaptureRefs" - }, - "syntaxAnchor": { - "$ref": "semantic-tree-sitter-provenance.v1.schema.json#/$defs/syntaxAnchor" - }, - "reasoningProfiles": { - "type": "array", - "items": { - "$ref": "#/$defs/reasoningProfile" - } - }, - "nativeSyntaxFacts": { - "type": "array", - "items": { - "$ref": "semantic-native-syntax-fact-index.v1.schema.json#/$defs/nativeSyntaxFact" - } - }, - "hits": { - "type": "array", - "items": { - "$ref": "#/$defs/hit" - } - }, - "findings": { - "type": "array", - "items": { - "$ref": "#/$defs/finding" - } - }, - "nextActions": { - "$ref": "#/$defs/nextActionList" - }, - "delegationHints": { - "type": "array", - "items": { - "$ref": "#/$defs/delegationHint" - } - }, - "notes": { - "type": "array", - "items": { - "$ref": "#/$defs/note" - } - } - }, - "$defs": { - "nextActionList": { - "type": "array", - "items": { - "$ref": "#/$defs/nextAction" - } - }, - "avoidNextActionList": { - "type": "array", - "items": { - "$ref": "#/$defs/avoidNextAction" - } - }, - "scalar": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - }, - { - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "number" - }, - { - "type": "boolean" - } - ] - } - } - ] - }, - "stringList": { - "type": "array", - "items": { - "type": "string" - } - }, - "fields": { - "type": "object", - "properties": { - "requestedVersion": { - "type": "string", - "minLength": 1 - }, - "currentWorkspaceVersion": { - "type": "string", - "minLength": 1 - }, - "apiQuery": { - "type": "string", - "minLength": 1 - }, - "subpath": { - "type": "string", - "minLength": 1 - }, - "versionScope": { - "enum": [ - "current", - "external", - "unknown" - ] - } - }, - "additionalProperties": { - "$ref": "#/$defs/scalar" - } - }, - "finder": { - "type": "object", - "description": "Provider-owned finder pipeline provenance used internally by search playbook acquisition. This records normalized planner options; it is not raw shell argv and is never a public command surface.", - "additionalProperties": false, - "required": [ - "engine", - "surface", - "options", - "acceptedArgs", - "rejectedArgs" - ], - "properties": { - "engine": { - "enum": [ - "lexical", - "rg", - "rg-lexical", - "provider" - ] - }, - "surface": { - "enum": [ - "search-lexical", - "search-ingest", - "search-pattern", - "search-custom" - ] - }, - "pipelineId": { - "type": "string", - "minLength": 1 - }, - "options": { - "$ref": "#/$defs/finderOptions" - }, - "acceptedArgs": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "rejectedArgs": { - "type": "array", - "items": { - "$ref": "#/$defs/finderRejectedArg" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "finderOptions": { - "type": "object", - "description": "Normalized, provider-approved finder options that can be mapped to headless finder behavior across language providers.", - "additionalProperties": false, - "properties": { - "matchMode": { - "enum": [ - "fuzzy", - "exact" - ] - }, - "caseMode": { - "enum": [ - "smart", - "ignore", - "respect" - ] - }, - "nth": { - "type": "string", - "minLength": 1 - }, - "delimiter": { - "type": "string", - "minLength": 1 - }, - "tiebreak": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "score", - "begin", - "end", - "index", - "length", - "chunk", - "pathname" - ] - } - }, - "scheme": { - "enum": [ - "default", - "path", - "history" - ] - }, - "nativeArgs": { - "type": "array", - "description": "The provider-normalized lexical-native argument tokens accepted for this headless run.", - "items": { - "type": "string", - "minLength": 1 - } - } - } - }, - "finderRejectedArg": { - "type": "object", - "additionalProperties": false, - "required": [ - "value", - "reason" - ], - "properties": { - "value": { - "type": "string", - "minLength": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - } - } - }, - "surfaceKind": { - "enum": [ - "real-source", - "test-source", - "test-fixture-string", - "generated-source", - "external-source", - "unknown" - ] - }, - "targetRole": { - "enum": [ - "path", - "range", - "symbol", - "term", - "pkg", - "dep", - "test", - "finding", - "failure", - "hot", - "evidence", - "result", - "self", - "feature", - "cfg", - "value", - "custom" - ] - }, - "projectPath": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/projectPath" - }, - "projectPackage": { - "type": "object", - "description": "Provider-owned package manifest summary for the active project root.", - "additionalProperties": false, - "required": [ - "path", - "name" - ], - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "name": { - "type": "string", - "minLength": 1 - }, - "dependencies": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "providerExtension": { - "type": "object", - "description": "Provider-owned optional package extension capability surfaced through the shared search packet envelope.", - "additionalProperties": false, - "required": [ - "name", - "activation", - "capabilities" - ], - "properties": { - "name": { - "type": "string", - "minLength": 1 - }, - "activation": { - "type": "string", - "minLength": 1 - }, - "dependencyMode": { - "description": "How the package manager relation activates this provider extension.", - "enum": [ - "required", - "optional", - "implicit", - "unknown" - ] - }, - "packageManager": { - "type": "string", - "minLength": 1 - }, - "package": { - "type": "string", - "minLength": 1 - }, - "dependencies": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - } - }, - "capabilities": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "windowTarget": { - "description": "A window-set target is either a canonical project path or a graph frontier token such as feature:cli, cfg:unix, or import:serde. It is not a display locator or rank-prefixed path.", - "anyOf": [ - { - "$ref": "#/$defs/projectPath" - }, - { - "type": "string", - "minLength": 1, - "pattern": "^[a-z][a-z0-9_-]*:[^\\s:\\\\][^\\s\\\\]*$" - } - ] - }, - "queryTerm": { - "type": "object", - "additionalProperties": false, - "required": [ - "value", - "kind", - "selector" - ], - "allOf": [ - { - "if": { - "properties": { - "kind": { - "enum": [ - "owner", - "path" - ] - } - }, - "required": [ - "kind" - ] - }, - "then": { - "properties": { - "value": { - "$ref": "#/$defs/projectPath" - } - } - } - } - ], - "properties": { - "value": { - "type": "string", - "minLength": 1 - }, - "kind": { - "enum": [ - "dependency", - "owner", - "path", - "symbol", - "text", - "feature", - "cfg", - "api", - "custom" - ] - }, - "selector": { - "enum": [ - "exact", - "prefix", - "fuzzy", - "stdin-path" - ] - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "queryComposition": { - "type": "object", - "additionalProperties": false, - "required": [ - "mode", - "view", - "selector", - "merge" - ], - "properties": { - "mode": { - "enum": [ - "single", - "query-set" - ] - }, - "view": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "selector": { - "enum": [ - "single", - "exact-set", - "prefix-set", - "lexical-set", - "stdin-path-set" - ] - }, - "scope": { - "$ref": "#/$defs/queryScope" - }, - "clauses": { - "type": "array", - "description": "Provider-planned semantic query clauses. Explicit query-set clauses and provider-synthesized clauses share this shape.", - "items": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "terms" - ], - "properties": { - "id": { - "type": "integer", - "minimum": 1 - }, - "role": { - "enum": [ - "path", - "package", - "symbol", - "concept", - "context" - ] - }, - "terms": { - "$ref": "#/$defs/stringList" - } - } - } - }, - "termRoles": { - "type": "object", - "description": "Normalized query term role map used by clause planning and action ranking.", - "additionalProperties": { - "enum": [ - "context", - "concept", - "symbol" - ] - } - }, - "driftSuppression": { - "type": "object", - "description": "Search-pipe safety decision that prevents weak or cross-package evidence from selecting code reads.", - "additionalProperties": false, - "properties": { - "packageCohesion": { - "enum": [ - "low", - "medium", - "high" - ] - }, - "weakTerms": { - "$ref": "#/$defs/stringList" - }, - "risks": { - "$ref": "#/$defs/stringList" - }, - "selectorReadsAllowed": { - "type": "boolean" - }, - "reason": { - "type": "string" - } - } - }, - "merge": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "enum": [ - "packages", - "nodes", - "edges", - "owners", - "items", - "typeSurfaces", - "invariantCandidates", - "nativeSyntaxFacts", - "hits", - "findings", - "nextActions", - "delegationHints", - "notes" - ] - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "queryCoverage": { - "type": "object", - "additionalProperties": false, - "required": [ - "value", - "kind", - "status", - "hitCount" - ], - "properties": { - "value": { - "type": "string", - "minLength": 1 - }, - "kind": { - "enum": [ - "dependency", - "owner", - "path", - "symbol", - "text", - "feature", - "cfg", - "api", - "custom" - ] - }, - "selector": { - "enum": [ - "exact", - "prefix", - "fuzzy", - "stdin-path" - ] - }, - "status": { - "enum": [ - "hit", - "miss", - "partial", - "error" - ] - }, - "hitCount": { - "type": "integer", - "minimum": 0 - }, - "surfaces": { - "type": "array", - "uniqueItems": true, - "items": { - "$ref": "#/$defs/surfaceKind" - } - }, - "ownerPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "fixturePaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "ownerResolution": { - "type": "object", - "additionalProperties": false, - "required": [ - "target", - "status", - "realOwner" - ], - "properties": { - "target": { - "$ref": "#/$defs/projectPath" - }, - "status": { - "enum": [ - "workspace-owner", - "fixture-path", - "missing", - "ambiguous", - "external", - "unknown" - ] - }, - "realOwner": { - "type": "boolean" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "fixturePath": { - "$ref": "#/$defs/projectPath" - }, - "fixtureOwner": { - "$ref": "#/$defs/projectPath" - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "sourceCoverage": { - "type": "object", - "additionalProperties": false, - "required": [ - "scope", - "status" - ], - "properties": { - "scope": { - "$ref": "#/$defs/queryScope" - }, - "status": { - "enum": [ - "complete", - "partial", - "missing", - "unknown" - ] - }, - "coverageKind": { - "enum": [ - "parser-source", - "config-root", - "workspace-package", - "generated", - "unknown" - ] - }, - "configPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "sourceRoots": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "coveredRoots": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "missingRoots": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "coveredOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "missingOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "sourceFiles": { - "type": "integer", - "minimum": 0 - }, - "visibleOwners": { - "type": "integer", - "minimum": 0 - }, - "missingOwnersCount": { - "type": "integer", - "minimum": 0 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "sourceTrace": { - "type": "object", - "additionalProperties": false, - "required": [ - "source", - "status" - ], - "properties": { - "source": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "status": { - "enum": [ - "empty", - "used", - "partial", - "skipped", - "unknown" - ] - }, - "candidateCount": { - "type": "integer", - "minimum": 0 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "testResolution": { - "type": "object", - "additionalProperties": false, - "required": [ - "targetOwner", - "status" - ], - "properties": { - "targetOwner": { - "$ref": "#/$defs/projectPath" - }, - "status": { - "enum": [ - "linked", - "missing", - "partial", - "noisy", - "ambiguous", - "unsupported", - "unknown" - ] - }, - "scope": { - "$ref": "#/$defs/queryScope" - }, - "testPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "candidateTestPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "unrelatedTestPaths": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "candidateCount": { - "type": "integer", - "minimum": 0 - }, - "selectedCount": { - "type": "integer", - "minimum": 0 - }, - "noiseCount": { - "type": "integer", - "minimum": 0 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "searchSynthesis": { - "type": "object", - "additionalProperties": false, - "required": [ - "algorithm", - "scope" - ], - "properties": { - "algorithm": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "scope": { - "enum": [ - "workspace", - "prime", - "owner", - "dependency", - "policy", - "query", - "query-set", - "lexical", - "tests", - "ingest", - "failure", - "custom" - ] - }, - "summary": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "selectedOwners": { - "type": "integer", - "minimum": 0 - }, - "selectedEdges": { - "type": "integer", - "minimum": 0 - }, - "incomingOwners": { - "type": "integer", - "minimum": 0 - }, - "outgoingOwners": { - "type": "integer", - "minimum": 0 - }, - "highImpactOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "frontierOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "editFrontier": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "testFrontier": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "windowSet": { - "type": "array", - "items": { - "$ref": "#/$defs/windowSetTarget" - } - }, - "findingOwners": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - }, - "seeds": { - "$ref": "#/$defs/nextActionList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "windowSetTarget": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "target" - ], - "properties": { - "kind": { - "enum": [ - "owner", - "tests", - "read", - "query", - "lexical", - "deps", - "dependency", - "docs", - "docs-use", - "crate-source", - "features", - "cfg", - "import", - "items", - "code", - "symbol", - "finding", - "custom" - ] - }, - "target": { - "$ref": "#/$defs/windowTarget" - }, - "query": { - "type": "string", - "minLength": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "read": { - "type": "string", - "description": "Compatibility source transport locator for exact graph/code frontier actions, formatted as project/path:start:end. This is not the semantic selector identity for syntax exploration.", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*:[1-9][0-9]*$" - }, - "structuralSelector": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/structuralSelector" - }, - "displayLineRange": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/lineRange" - }, - "sourceLocatorHint": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/sourceLocator" - }, - "projection": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/projection" - }, - "codePolicy": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/codePolicy" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "avoidNextAction": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "target", - "reason" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "target": { - "type": "string", - "minLength": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "read": { - "type": "string", - "description": "Canonical source read locator for graph/code frontier actions, formatted as project/path:start:end.", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*:[1-9][0-9]*$" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "noOutput": { - "type": "object", - "description": "Compact no-candidate receipt for prompt-facing search views.", - "additionalProperties": false, - "required": [ - "reason", - "sourceTrace", - "nextActions", - "avoidNextActions" - ], - "properties": { - "reason": { - "enum": [ - "no-candidates", - "query-too-broad" - ] - }, - "sourceTrace": { - "type": "array", - "items": { - "$ref": "#/$defs/sourceTrace" - } - }, - "nextActions": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/nextAction" - } - }, - "avoidNextActions": { - "$ref": "#/$defs/avoidNextActionList" - }, - "queryBudget": { - "$ref": "#/$defs/queryBudget" - } - } - }, - "queryBudget": { - "type": "object", - "description": "Shared wrapper admission receipt for queries rejected before backend execution.", - "additionalProperties": false, - "required": [ - "blocked", - "reason", - "termCount", - "genericTerms", - "refineHint" - ], - "properties": { - "blocked": { - "type": "boolean", - "const": true - }, - "reason": { - "enum": [ - "query-too-broad" - ] - }, - "termCount": { - "type": "integer", - "minimum": 1 - }, - "genericTerms": { - "$ref": "#/$defs/stringList" - }, - "specificTerms": { - "$ref": "#/$defs/stringList" - }, - "refineHint": { - "type": "string", - "minLength": 1 - }, - "exampleCommand": { - "type": "string", - "minLength": 1 - } - } - }, - "runtimeCost": { - "type": "object", - "additionalProperties": false, - "required": [ - "cacheStatus" - ], - "properties": { - "cacheStatus": { - "enum": [ - "cold", - "warm", - "reused", - "disabled", - "unknown" - ] - }, - "elapsedMs": { - "type": "integer", - "minimum": 0 - }, - "parseMs": { - "type": "integer", - "minimum": 0 - }, - "sourceFilesParsed": { - "type": "integer", - "minimum": 0 - }, - "packagesScanned": { - "type": "integer", - "minimum": 0 - }, - "parserFactsReused": { - "type": "boolean" - }, - "indexId": { - "type": "string", - "minLength": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "cache": { - "type": "object", - "description": "Provider-owned cache invalidation facts for replay-safe search packets. Providers decide which workspace files affect the packet; clients only validate and compare these hashes.", - "additionalProperties": false, - "required": [ - "fileHashes" - ], - "properties": { - "fileHashes": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/fileHash" - } - }, - "rawSourceStored": { - "const": false - } - } - }, - "fileHash": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "sha256" - ], - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "sha256": { - "type": "string", - "pattern": "^[a-f0-9]{64}$" - } - } - }, - "queryScope": { - "type": "object", - "additionalProperties": false, - "properties": { - "projectRoot": { - "type": "string", - "minLength": 1 - }, - "packageName": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "roots": { - "type": "array", - "items": { - "$ref": "#/$defs/projectPath" - } - } - } - }, - "location": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/location" - }, - "header": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "fields" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^search-[a-z0-9_-]+$" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "inputDetection": { - "type": "object", - "additionalProperties": false, - "required": [ - "source", - "lineCount", - "byteCount" - ], - "properties": { - "source": { - "enum": [ - "rg-n", - "vimgrep", - "rg-json", - "path-list", - "path-list-nul", - "diff-paths", - "unknown" - ] - }, - "lineCount": { - "type": "integer", - "minimum": 0 - }, - "byteCount": { - "type": "integer", - "minimum": 0 - }, - "sample": { - "type": "string" - }, - "unsupportedLanguage": { - "$ref": "#/$defs/unsupportedLanguageDetection", - "description": "Advisory emitted when a user requested a language facade that is not active or not a protocol language id. It is a fail-closed routing hint, not provider evidence." - } - } - }, - "unsupportedLanguageDetection": { - "type": "object", - "additionalProperties": false, - "required": [ - "requestedFacade", - "activeFacades", - "knownFacades", - "recoveryCommands" - ], - "properties": { - "requestedFacade": { - "type": "string", - "minLength": 1 - }, - "activeFacades": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "knownFacades": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "uniqueItems": true - }, - "suggestedFacade": { - "type": "string", - "minLength": 1, - "description": "Present only when the requested facade has a protocol-owned or configuration-owned explicit mapping to one active language facade. Do not derive this from substrings, suffixes, or hyphen components." - }, - "recoveryCommands": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - }, - "reason": { - "type": "string", - "minLength": 1 - } - } - }, - "fact": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "fields" - ], - "properties": { - "id": { - "type": "string", - "minLength": 1 - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "node": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "kind", - "fields" - ], - "properties": { - "id": { - "type": "string", - "minLength": 1 - }, - "kind": { - "enum": [ - "package", - "owner", - "dependency", - "test", - "finding", - "failure", - "assert", - "hot", - "key", - "evidence", - "symbol", - "config", - "tsconfig", - "extension", - "build_tool", - "test_surface", - "custom" - ] - }, - "path": { - "$ref": "#/$defs/projectPath" - }, - "rank": { - "type": "number" - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "edge": { - "type": "object", - "additionalProperties": false, - "required": [ - "from", - "kind", - "to" - ], - "properties": { - "from": { - "type": "string", - "minLength": 1 - }, - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "to": { - "type": "string", - "minLength": 1 - }, - "label": { - "type": "string" - }, - "location": { - "$ref": "#/$defs/location" - }, - "weight": { - "type": "number" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "owner": { - "type": "object", - "additionalProperties": false, - "required": [ - "path", - "role", - "public", - "fields" - ], - "properties": { - "path": { - "$ref": "#/$defs/projectPath" - }, - "namespace": { - "type": "string" - }, - "role": { - "type": "string" - }, - "public": { - "type": "boolean" - }, - "exports": { - "type": "array", - "items": { - "type": "string" - } - }, - "nextActions": { - "$ref": "#/$defs/nextActionList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "item": { - "type": "object", - "additionalProperties": false, - "required": [ - "name", - "kind", - "ownerPath", - "fields" - ], - "properties": { - "name": { - "type": "string" - }, - "kind": { - "type": "string" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "location": { - "$ref": "#/$defs/location" - }, - "structuralSelector": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/structuralSelector" - }, - "displayLineRange": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/lineRange" - }, - "sourceLocatorHint": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/sourceLocator" - }, - "projection": { - "$ref": "semantic-source-location.v1.schema.json#/$defs/projection" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "hit": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "ownerPath", - "location", - "score", - "reason" - ], - "properties": { - "kind": { - "type": "string" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "symbol": { - "type": "string" - }, - "location": { - "$ref": "#/$defs/location" - }, - "score": { - "type": "number" - }, - "reason": { - "type": "string" - }, - "snippet": { - "type": "string" - }, - "surface": { - "$ref": "#/$defs/surfaceKind" - }, - "realOwner": { - "type": "boolean" - }, - "fixturePath": { - "$ref": "#/$defs/projectPath" - }, - "fixtureOwner": { - "$ref": "#/$defs/projectPath" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "finding": { - "type": "object", - "additionalProperties": false, - "required": [ - "ruleId", - "severity", - "count", - "location" - ], - "properties": { - "ruleId": { - "type": "string" - }, - "severity": { - "enum": [ - "info", - "warning", - "error" - ] - }, - "count": { - "type": "integer", - "minimum": 1 - }, - "title": { - "type": "string" - }, - "location": { - "$ref": "#/$defs/location" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeGraph": { - "type": "object", - "description": "Agent-facing search route topology derived from current evidence. This is the machine-owned source for compact route render output.", - "additionalProperties": false, - "required": [ - "profile", - "evidenceState", - "routes", - "chosenRouteId", - "actionFrontier" - ], - "properties": { - "profile": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "evidenceState": { - "$ref": "#/$defs/routeEvidenceState" - }, - "routes": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/routeDecision" - } - }, - "chosenRouteId": { - "$ref": "#/$defs/routeId" - }, - "actionFrontier": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - } - }, - "recommendedNext": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "omit": { - "$ref": "#/$defs/stringList" - }, - "avoid": { - "$ref": "#/$defs/stringList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeDecision": { - "type": "object", - "additionalProperties": false, - "required": [ - "id", - "kind", - "preconditions", - "projection", - "codePolicy" - ], - "properties": { - "id": { - "$ref": "#/$defs/routeId" - }, - "kind": { - "enum": [ - "known-selector", - "known-owner", - "known-symbol", - "known-dependency", - "failure-frontier", - "broad-query", - "unknown-workspace", - "no-asp", - "custom" - ] - }, - "preconditions": { - "$ref": "#/$defs/stringList" - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "projection": { - "$ref": "#/$defs/routeProjection" - }, - "codePolicy": { - "$ref": "#/$defs/routeCodePolicy" - }, - "requiresExact": { - "type": "boolean" - }, - "cost": { - "enum": [ - "low", - "medium", - "high" - ] - }, - "actions": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - } - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "avoid": { - "$ref": "#/$defs/stringList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeAction": { - "type": "object", - "description": "Typed route action fact. Renderers may derive display commands from this object, but providers must not store command strings or argv here.", - "additionalProperties": false, - "required": [ - "id", - "kind", - "routeId", - "targetRole", - "projection", - "codePolicy" - ], - "properties": { - "id": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - }, - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "routeId": { - "$ref": "#/$defs/routeId" - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "target": { - "type": "string", - "minLength": 1 - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "selector": { - "type": "string", - "minLength": 1 - }, - "query": { - "type": "string", - "minLength": 1 - }, - "projection": { - "$ref": "#/$defs/routeProjection" - }, - "codePolicy": { - "$ref": "#/$defs/routeCodePolicy" - }, - "requiresExact": { - "type": "boolean" - }, - "displayLineRange": { - "type": "string", - "description": "Display-only line span. It is not an executable selector.", - "pattern": "^[1-9][0-9]*:[1-9][0-9]*$" - }, - "sourceLocatorHint": { - "type": "string", - "description": "Display-only source locator hint. Agents must execute owner/symbol/structural selectors instead.", - "pattern": "^(?:\\.|(?!/)(?![A-Za-z]:)(?![0-9]+:)(?!\\.\\.?($|/))(?!.*(^|/)\\.\\.?($|/))(?!.*//)[^\\s:\\\\]+(?:/[^\\s:\\\\]+)*):[1-9][0-9]*:[1-9][0-9]*$" - }, - "avoid": { - "$ref": "#/$defs/stringList" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeEvidenceState": { - "type": "object", - "additionalProperties": false, - "properties": { - "anchors": { - "type": "array", - "uniqueItems": true, - "items": { - "enum": [ - "ownerPath", - "symbol", - "structuralSelector", - "dependency", - "failure", - "workspace", - "query", - "none" - ] - } - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "symbol": { - "type": "string", - "minLength": 1 - }, - "structuralSelector": { - "type": "string", - "minLength": 1 - }, - "dependency": { - "type": "string", - "minLength": 1 - }, - "failure": { - "type": "string", - "minLength": 1 - }, - "query": { - "type": "string", - "minLength": 1 - }, - "workspaceKnown": { - "type": "boolean" - }, - "intentRequiresExactCode": { - "type": "boolean" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "routeId": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9_]*$" - }, - "routeProjection": { - "enum": [ - "none", - "metadata", - "names", - "skeleton", - "outline", - "topology", - "dependency", - "tests", - "failure", - "code" - ] - }, - "routeCodePolicy": { - "enum": [ - "disabled", - "metadata-only", - "names-only", - "exact-only", - "requires-exact-code" - ] - }, - "nextAction": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "target", - "command" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "target": { - "type": "string", - "minLength": 1 - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "scope": { - "type": "string" - }, - "ownerPath": { - "$ref": "#/$defs/projectPath" - }, - "command": { - "type": "object", - "additionalProperties": false, - "description": "Canonical executable action. Consumers execute executable plus argv directly and must not reconstruct commands from prose, fields, or historical CLI spellings.", - "required": [ - "executable", - "argv" - ], - "properties": { - "executable": { - "type": "string", - "minLength": 1 - }, - "argv": { - "type": "array", - "minItems": 1, - "items": { - "type": "string", - "minLength": 1 - } - } - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "delegationHint": { - "type": "object", - "additionalProperties": false, - "required": [ - "profile", - "decision", - "runtimeOwner", - "readOnly", - "noCode", - "targetActions", - "reason", - "receipt" - ], - "properties": { - "profile": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "fanout": { - "const": "parallel" - }, - "instances": { - "const": "targetActions" - }, - "branchPrompt": { - "const": "reasoning-tree" - }, - "stateOwner": { - "const": "parent" - }, - "fanin": { - "const": "receipt" - }, - "iterative": { - "type": "boolean" - }, - "decision": { - "const": "advisory" - }, - "runtimeOwner": { - "const": "agent-client" - }, - "modelClass": { - "enum": [ - "cheap", - "standard", - "strong", - "inherit", - "custom" - ] - }, - "readOnly": { - "type": "boolean" - }, - "noCode": { - "type": "boolean" - }, - "targetActions": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^A[1-9][0-9]*\\.[a-z][a-z0-9_-]*$" - } - }, - "maxCommands": { - "type": "integer", - "minimum": 1 - }, - "maxTurns": { - "type": "integer", - "minimum": 1 - }, - "reason": { - "type": "string", - "minLength": 1 - }, - "receipt": { - "$ref": "#/$defs/delegationReceipt" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "delegationReceipt": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "requiredFields" - ], - "properties": { - "kind": { - "const": "search-subagent" - }, - "requiredFields": { - "type": "array", - "minItems": 5, - "uniqueItems": true, - "items": { - "enum": [ - "role", - "action", - "evidence", - "missing", - "next", - "risk" - ] - } - } - } - }, - "reasoningProfileName": { - "enum": [ - "owner-query", - "query-deps", - "owner-tests", - "finding-frontier", - "feature-cfg" - ] - }, - "reasoningProfile": { - "type": "object", - "additionalProperties": false, - "required": [ - "profile", - "selectors", - "returns" - ], - "properties": { - "profile": { - "$ref": "#/$defs/reasoningProfileName" - }, - "description": { - "type": "string", - "minLength": 1 - }, - "selectors": { - "type": "array", - "minItems": 1, - "items": { - "$ref": "#/$defs/reasoningProfileSelector" - } - }, - "returns": { - "type": "array", - "minItems": 1, - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.-]*$" - } - }, - "frontier": { - "type": "array", - "items": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*\\.[a-z][a-z0-9_-]*$" - } - }, - "avoid": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9_.-]*$" - } - }, - "fields": { - "$ref": "#/$defs/fields" - } - }, - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileOwnerQueryContract" - }, - { - "$ref": "#/$defs/reasoningProfileQueryDepsContract" - }, - { - "$ref": "#/$defs/reasoningProfileOwnerTestsContract" - }, - { - "$ref": "#/$defs/reasoningProfileFindingFrontierContract" - }, - { - "$ref": "#/$defs/reasoningProfileFeatureCfgContract" - } - ] - }, - "reasoningProfileCatalog": { - "const": [ - { - "profile": "owner-query", - "selectors": [ - { - "kind": "owner", - "required": true - }, - { - "kind": "query", - "required": true - } - ], - "returns": [ - "items", - "tests", - "dependency-usage" - ] - }, - { - "profile": "query-deps", - "selectors": [ - { - "kind": "query", - "required": true - }, - { - "kind": "dependency", - "required": true - } - ], - "returns": [ - "owners", - "imports", - "usage-tests" - ] - }, - { - "profile": "owner-tests", - "selectors": [ - { - "kind": "owner", - "required": true - } - ], - "returns": [ - "covering-tests", - "test-entrypoints", - "fixtures" - ] - }, - { - "profile": "finding-frontier", - "selectors": [ - { - "kind": "finding", - "required": true - }, - { - "kind": "owner", - "required": false - } - ], - "returns": [ - "affected-owners", - "tests", - "verification-actions" - ] - }, - { - "profile": "feature-cfg", - "selectors": [ - { - "kind": "feature", - "required": true - } - ], - "returns": [ - "cfg-gates", - "owners", - "verification-surfaces" - ] - } - ] - }, - "reasoningProfileOwnerQueryContract": { - "if": { - "properties": { - "profile": { - "const": "owner-query" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileOwnerSelector" - }, - { - "$ref": "#/$defs/reasoningProfileQuerySelector" - } - ], - "items": false, - "minItems": 2, - "maxItems": 2 - }, - "returns": { - "const": [ - "items", - "tests", - "dependency-usage" - ] - } - } - } - }, - "reasoningProfileQueryDepsContract": { - "if": { - "properties": { - "profile": { - "const": "query-deps" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileQuerySelector" - }, - { - "$ref": "#/$defs/reasoningProfileDependencySelector" - } - ], - "items": false, - "minItems": 2, - "maxItems": 2 - }, - "returns": { - "type": "array", - "minItems": 3, - "maxItems": 5, - "prefixItems": [ - { - "const": "owners" - }, - { - "const": "imports" - } - ], - "items": { - "enum": [ - "usage-tests", - "local-docs", - "docs-use", - "crate-source" - ] - } - } - } - } - }, - "reasoningProfileOwnerTestsContract": { - "if": { - "properties": { - "profile": { - "const": "owner-tests" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileOwnerSelector" - } - ], - "items": false, - "minItems": 1, - "maxItems": 1 - }, - "returns": { - "const": [ - "covering-tests", - "test-entrypoints", - "fixtures" - ] - } - } - } - }, - "reasoningProfileFindingFrontierContract": { - "if": { - "properties": { - "profile": { - "const": "finding-frontier" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "oneOf": [ - { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileFindingSelector" - } - ], - "items": false, - "minItems": 1, - "maxItems": 1 - }, - { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileFindingSelector" - }, - { - "$ref": "#/$defs/reasoningProfileOwnerSelector" - } - ], - "items": false, - "minItems": 2, - "maxItems": 2 - } - ] - }, - "returns": { - "const": [ - "affected-owners", - "tests", - "verification-actions" - ] - } - } - } - }, - "reasoningProfileFeatureCfgContract": { - "if": { - "properties": { - "profile": { - "const": "feature-cfg" - } - }, - "required": [ - "profile" - ] - }, - "then": { - "properties": { - "selectors": { - "prefixItems": [ - { - "$ref": "#/$defs/reasoningProfileFeatureSelector" - } - ], - "items": false, - "minItems": 1, - "maxItems": 1 - }, - "returns": { - "const": [ - "cfg-gates", - "owners", - "verification-surfaces" - ] - } - } - } - }, - "reasoningProfileOwnerSelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "owner" - } - } - } - ] - }, - "reasoningProfileQuerySelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "query" - } - } - } - ] - }, - "reasoningProfileDependencySelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "dependency" - } - } - } - ] - }, - "reasoningProfileFindingSelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "finding" - } - } - } - ] - }, - "reasoningProfileFeatureSelector": { - "allOf": [ - { - "$ref": "#/$defs/reasoningProfileSelector" - }, - { - "properties": { - "kind": { - "const": "feature" - } - } - } - ] - }, - "reasoningProfileSelector": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "alias" - ], - "properties": { - "kind": { - "enum": [ - "owner", - "query", - "dependency", - "test", - "finding", - "import", - "feature", - "custom" - ] - }, - "alias": { - "type": "string", - "pattern": "^[A-Z][A-Z0-9]*$" - }, - "target": { - "type": "string", - "minLength": 1 - }, - "targetRole": { - "$ref": "#/$defs/targetRole" - }, - "required": { - "type": "boolean" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - }, - "note": { - "type": "object", - "additionalProperties": false, - "required": [ - "kind", - "message" - ], - "properties": { - "kind": { - "type": "string", - "pattern": "^[a-z][a-z0-9_-]*$" - }, - "message": { - "type": "string" - }, - "fields": { - "$ref": "#/$defs/fields" - } - } - } - } -} diff --git a/schemas/semantic-tree-sitter-grammar-profile.v1.schema.json b/schemas/semantic-tree-sitter-grammar-profile.v1.schema.json index 4515a38..cce0eb9 100644 --- a/schemas/semantic-tree-sitter-grammar-profile.v1.schema.json +++ b/schemas/semantic-tree-sitter-grammar-profile.v1.schema.json @@ -53,6 +53,9 @@ "corpusProfilePath": { "$ref": "#/$defs/projectPath" }, + "enhancedQueryCapabilityTablePath": { + "$ref": "#/$defs/projectPath" + }, "queryCorpus": { "$ref": "#/$defs/queryCorpus" }, diff --git a/schemas/semantic-type-surface.v1.schema.json b/schemas/semantic-type-surface.v1.schema.json index 6f64e38..e09e9aa 100644 --- a/schemas/semantic-type-surface.v1.schema.json +++ b/schemas/semantic-type-surface.v1.schema.json @@ -47,7 +47,7 @@ "scope": { "enum": [ "workspace", - "prime", + "search-playbook", "owner", "dependency", "api", diff --git a/src/asp_python/_callable_skeleton_projection.py b/src/asp_python/_callable_skeleton_projection.py index d148d9f..366532a 100644 --- a/src/asp_python/_callable_skeleton_projection.py +++ b/src/asp_python/_callable_skeleton_projection.py @@ -140,6 +140,7 @@ def callable_skeleton_payload( ) projected_bytes = min(source_bytes, structural_bytes) return { + "rootSelector": root_exact["selector"], "rootNodeId": "callable:root", "callable": { "kind": selector.kind, diff --git a/src/asp_python/_cli_agent.py b/src/asp_python/_cli_agent.py index db83a7e..d5c31f8 100644 --- a/src/asp_python/_cli_agent.py +++ b/src/asp_python/_cli_agent.py @@ -17,23 +17,22 @@ def render_agent_guide(project_root: Path) -> str: f"[asp-python-guide] project={project}", "|catalog provider=native-facts routes=syntax-locate,exact-source,callable-skeleton", ( - f"|route syntax-locate selectors=S:tree-sitter-query,Scope:owner-or-structural " - f"returns=locator,capture,frontier code=false cmd=asp python " - f"query --treesitter-query " - f"'(function_definition name: (identifier) @function.name)' " - f"--selector {workspace}" + "|route syntax-locate selectors=S:tree-sitter-query,Scope:owner-or-structural " + "returns=locator,capture,frontier cmd=asp search playbook " + "--language python --rg -n -e '' . " + "--tantivy 'title:^2 OR body:' --syntax python " + "'(function_definition name: (identifier) @function.name)'" ), - f"|route exact-source selectors=R:exact-selector returns=source cmd=asp 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 playbook=asp python search playbook {workspace}", - f"|cmd catalog-json=asp python query --catalog declarations --json {workspace}", + f"|route exact-source selectors=R:exact-selector returns=source cmd=asp query playbook --language python --selector --projection source {workspace}", + f"|route callable-skeleton selectors=R:exact-callable-selector returns=callable-skeleton cmd=asp query playbook --language python --selector --projection callable-skeleton {workspace}", + "|cmd playbook=asp search playbook --language python --rg -n -e . --tantivy 'title:^2 OR body:'", ( - f"|cmd syntax-locate=asp python query --treesitter-query " - f"'(function_definition name: (identifier) @function.name)' " - f"--selector {workspace}" + "|cmd syntax-locate=asp search playbook --language python " + "--rg -n -e '' . --tantivy 'title:^2 OR body:' " + "--syntax python '(function_definition name: (identifier) @function.name)'" ), - f"|cmd exact-source=asp python query --selector --projection source {workspace}", - f"|cmd callable-skeleton=asp python query --selector --projection callable-skeleton {workspace}", + f"|cmd exact-source=asp query playbook --language python --selector --projection source {workspace}", + f"|cmd callable-skeleton=asp query playbook --language python --selector --projection callable-skeleton {workspace}", "|cmd ast-patch=asp python ast-patch dry-run --packet ", "|policy authority=asp-python-api trigger=pytest-plugin", "|rule agent hook install/runtime is owned by asp", @@ -86,7 +85,7 @@ def render_agent_doctor(project_root: Path) -> str: ), f"|namespace {ids.PYTHON_PROVIDER_NAMESPACE}", f"|method {','.join(registration['methods'])}", - "|schema semantic-search-packet.v1", + "|schema semantic-query-packet.v1", ) ) + "\n" diff --git a/src/asp_python/_cli_args.py b/src/asp_python/_cli_args.py index 9f049d6..4d7b04e 100644 --- a/src/asp_python/_cli_args.py +++ b/src/asp_python/_cli_args.py @@ -42,14 +42,6 @@ class ProtocolArgs: @classmethod def parse(cls, args: list[str] | tuple[str, ...]) -> ProtocolArgs | None: command = args[0] if args else None - if command == "search": - return cls( - "error", - error=( - "provider-local search was removed; use " - "asp python search playbook " - ), - ) if command == "query": return cls._parse_query(args[1:]) if command == "agent": @@ -183,8 +175,8 @@ def help_text() -> str: return ( "asp-python — Python provider runtime and ASP Python\n\n" "Usage:\n" - " asp python search playbook [--workspace ]\n" - " asp python query --selector --projection --workspace \n" + " asp search playbook --language python --rg -n -e . --tantivy 'title:^2 OR body:'\n" + " asp query playbook --language python --selector --projection --workspace \n" " asp-python query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION' [--json] [--workspace ]\n" " asp-python ast-patch dry-run --packet \n" " asp-python agent doctor [--json]\n" @@ -192,13 +184,13 @@ def help_text() -> str: "\n" "SEARCH\n" " Search is owned by the root ASP Client. The single public surface is\n" - " `asp python search playbook`, which composes raw candidates, provider\n" + " `asp search playbook --language python`, which composes raw candidates, provider\n" " native syntax, lexical ranking, and graph expansion. Provider-local\n" " search views are intentionally unavailable.\n\n" "QUERY\n" - " asp python query --selector --projection source --workspace \n" + " asp query playbook --language python --selector --projection source --workspace \n" " Exact source materialization through ASP authority\n" - " asp python query --selector --projection callable-skeleton --workspace \n" + " asp query playbook --language python --selector --projection callable-skeleton --workspace \n" " Typed callable skeleton materialization through ASP authority\n\n" " query --catalog flow-lite --where 'source.call=NAME sink.constructs=TYPE scope.fn=FUNCTION'\n" " Flow-lite ABI compatibility surface; Python executor is not enabled yet\n\n" @@ -211,8 +203,8 @@ def help_text() -> str: " agent guide Print provider role and playbook guidance\n\n" " Hook install/runtime is owned by asp in the root toolchain.\n\n" "\nEXAMPLES\n" - " asp python search playbook PythonSemanticSearchOptions --workspace .\n" - " asp python query --selector 'python://src/asp_python/_cli.py#item/function/run_cli' --projection source --workspace .\n" + " asp search playbook --language python --rg -n -e PythonSemanticSearchOptions . --tantivy 'title:PythonSemanticSearchOptions^2 OR body:PythonSemanticSearchOptions'\n" + " asp query playbook --language python --selector 'python://src/asp_python/_cli.py#item/function/run_cli' --projection source --workspace .\n" " asp-python query --catalog flow-lite --where 'source.call=payload sink.constructs=Action scope.fn=collect' .\n" " asp-python agent doctor --json .\n" " asp-python agent guide\n" diff --git a/src/asp_python/_cli_ast_patch.py b/src/asp_python/_cli_ast_patch.py index 606ae26..e4e770b 100644 --- a/src/asp_python/_cli_ast_patch.py +++ b/src/asp_python/_cli_ast_patch.py @@ -118,7 +118,7 @@ def _receipt( "failureKind": failure_kind, "failures": failures, "next": ( - "asp python query --selector " + "asp query playbook --language python --selector " f"--projection source --workspace {project_root}" ), } diff --git a/src/asp_python/_cli_query.py b/src/asp_python/_cli_query.py index f58e87c..7e57713 100644 --- a/src/asp_python/_cli_query.py +++ b/src/asp_python/_cli_query.py @@ -40,7 +40,7 @@ def run_query_command( return 0 raise ValueError( - "exact source projection is ASP-owned; use `asp python query " + "exact source projection is ASP-owned; use `asp query playbook --language python " "--selector --projection " "source|callable-skeleton --workspace `" ) diff --git a/src/asp_python/_cli_query_args.py b/src/asp_python/_cli_query_args.py index f234fe2..673bbc8 100644 --- a/src/asp_python/_cli_query_args.py +++ b/src/asp_python/_cli_query_args.py @@ -55,7 +55,7 @@ def _query_args_result( return args_type( "error", error=( - "exact source projection is ASP-owned; use `asp python query " + "exact source projection is ASP-owned; use `asp query playbook --language python " "--selector --projection " "source|callable-skeleton --workspace `" ), diff --git a/src/asp_python/_cli_query_hook_args.py b/src/asp_python/_cli_query_hook_args.py index e339c76..7b64fed 100644 --- a/src/asp_python/_cli_query_hook_args.py +++ b/src/asp_python/_cli_query_hook_args.py @@ -6,7 +6,7 @@ def normalize_query_surfaces(value: str | None) -> tuple[tuple[str, ...], str | None]: - """Normalize shared hook query surfaces into asp-python search pipes.""" + """Normalize shared hook query surfaces into Python exact-query selectors.""" if value is None: return (), "--surface requires owner,tests style surfaces" surfaces = tuple(surface.strip() for surface in value.split(",") if surface.strip()) diff --git a/src/asp_python/_dev_command_log_command.py b/src/asp_python/_dev_command_log_command.py index d269f08..d2f2f8c 100644 --- a/src/asp_python/_dev_command_log_command.py +++ b/src/asp_python/_dev_command_log_command.py @@ -22,21 +22,6 @@ "--view", } -SEARCH_PIPES = { - "dependency", - "deps", - "docs", - "features", - "lexical", - "items", - "owner", - "owners", - "prime", - "symbol", - "tests", - "workspace", -} - @dataclass(frozen=True, slots=True) class NormalizedCommand: @@ -81,20 +66,10 @@ def normalize_command(argv: list[str]) -> NormalizedCommand: query_set_count = sum( 1 for arg in args if arg == "--query" or arg.startswith("--query=") ) - pipes = tuple(sorted({normalize_token(arg) for arg in args} & SEARCH_PIPES)) - view = ( - normalize_token(first_positional_after(args, namespace_index) or "") - if namespace == "search" - else None - ) - if view == "unknown": - view = None - method = command_method(namespace, view, args, namespace_index) - query = option_value(args, "--query") or first_query_positional( - args, - namespace_index, - view, - ) + pipes: tuple[str, ...] = () + view = None + method = command_method(namespace, args, namespace_index) + query = option_value(args, "--query") return NormalizedCommand( namespace=namespace, method=method, @@ -124,12 +99,9 @@ def command_payload(command: NormalizedCommand) -> dict[str, Any]: def command_method( namespace: str, - view: str | None, args: list[str], namespace_index: int, ) -> str: - if namespace == "search" and view is not None: - return f"search/{view}" if namespace == "agent": subcommand = first_positional_after(args, namespace_index) or "" return f"agent/{normalize_token(subcommand)}" @@ -150,32 +122,6 @@ def first_positional_after(args: list[str], start: int) -> str | None: return None -def first_query_positional( - args: list[str], - namespace_index: int, - view: str | None, -) -> str | None: - skip_next = False - skipped_view = view is None - for arg in args[max(0, namespace_index + 1) :]: - if skip_next: - skip_next = False - continue - if option_takes_value(arg): - skip_next = "=" not in arg - continue - if arg.startswith("-"): - continue - token = normalize_token(arg) - if not skipped_view and token == view: - skipped_view = True - continue - if token in SEARCH_PIPES: - continue - return arg - return None - - def option_value(args: list[str], name: str) -> str | None: for index, arg in enumerate(args): if arg == name: diff --git a/src/asp_python/_semantic_graph_fact_collect.py b/src/asp_python/_semantic_graph_fact_collect.py deleted file mode 100644 index f9c7c36..0000000 --- a/src/asp_python/_semantic_graph_fact_collect.py +++ /dev/null @@ -1,215 +0,0 @@ -"""Collect Python field/type facts from parser-owned AST.""" - -from __future__ import annotations - -import ast -from pathlib import Path - -from ._semantic_graph_fact_model import FieldFact, collection_kind - -SKIP_DIRS = { - ".git", - ".hg", - ".mypy_cache", - ".pytest_cache", - ".ruff_cache", - "__pycache__", - "build", - "dist", - "node_modules", - "target", - "venv", - ".venv", -} - - -def collect_field_facts(project_root: Path, query: str, stdin: str) -> list[FieldFact]: - terms = query_terms(query) - paths = candidate_paths(project_root, stdin) or python_source_paths(project_root) - facts: list[FieldFact] = [] - seen: set[tuple[str, str, str, int]] = set() - for path in paths: - for fact in field_facts_for_path(project_root, path): - key = (fact.path, fact.container_name, fact.field_name, fact.line) - if key in seen or not fact_matches_terms(fact, terms): - continue - seen.add(key) - facts.append(fact) - if len(facts) >= 64: - return facts - return facts - - -def candidate_paths(project_root: Path, stdin: str) -> list[Path]: - paths: list[Path] = [] - seen: set[Path] = set() - for line in stdin.splitlines(): - path_text = line.split(":", 1)[0].strip() - if not path_text: - continue - path = Path(path_text) - absolute = path if path.is_absolute() else project_root / path - if absolute.suffix != ".py" or not absolute.exists() or absolute in seen: - continue - seen.add(absolute) - paths.append(absolute) - return paths - - -def python_source_paths(project_root: Path) -> list[Path]: - paths: list[Path] = [] - for path in project_root.rglob("*.py"): - if any(part in SKIP_DIRS for part in path.relative_to(project_root).parts): - continue - paths.append(path) - return sorted(paths) - - -def field_facts_for_path(project_root: Path, path: Path) -> list[FieldFact]: - try: - source = path.read_text(encoding="utf-8") - module = ast.parse(source) - except (OSError, SyntaxError, UnicodeDecodeError): - return [] - relative_path = path.relative_to(project_root).as_posix() - facts: list[FieldFact] = [] - for node in ast.walk(module): - if isinstance(node, ast.ClassDef): - facts.extend(class_field_facts(relative_path, node)) - return facts - - -def class_field_facts(path: str, class_node: ast.ClassDef) -> list[FieldFact]: - facts: list[FieldFact] = [] - for item in class_node.body: - if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name): - type_value = annotation_text(item.annotation) - if type_value is not None: - facts.append( - field_fact( - path, - class_node.name, - item.target.id, - type_value, - item.lineno, - class_node.lineno, - getattr(class_node, "end_lineno", item.lineno), - ) - ) - if isinstance(item, ast.FunctionDef) and item.name == "__init__": - facts.extend(init_self_field_facts(path, class_node, item)) - return facts - - -def init_self_field_facts( - path: str, - class_node: ast.ClassDef, - init_node: ast.FunctionDef, -) -> list[FieldFact]: - facts: list[FieldFact] = [] - for item in ast.walk(init_node): - if isinstance(item, ast.AnnAssign): - fact = self_field_fact(path, class_node, init_node, item) - if fact is not None: - facts.append(fact) - return facts - - -def self_field_fact( - path: str, - class_node: ast.ClassDef, - init_node: ast.FunctionDef, - item: ast.AnnAssign, -) -> FieldFact | None: - target = item.target - if not ( - isinstance(target, ast.Attribute) - and isinstance(target.value, ast.Name) - and target.value.id == "self" - ): - return None - type_value = annotation_text(item.annotation) - if type_value is None: - return None - return field_fact( - path, - class_node.name, - target.attr, - type_value, - item.lineno, - init_node.lineno, - getattr(init_node, "end_lineno", item.lineno), - ) - - -def field_fact( - path: str, - container_name: str, - field_name: str, - type_value: str, - line: int, - context_start: int, - context_end: int, -) -> FieldFact: - return FieldFact( - path=path, - container_name=container_name, - field_name=field_name, - type_value=type_value, - collection_kind=collection_kind(type_value), - line=line, - context_start=context_start, - context_end=context_end, - ) - - -def annotation_text(annotation: ast.expr) -> str | None: - try: - return ast.unparse(annotation) - except (AttributeError, ValueError): - return None - - -def query_terms(query: str) -> set[str]: - normalized = "".join( - character.lower() if character == "_" or character.isalnum() else " " - for character in query - ) - return {term for term in normalized.split() if term} - - -def fact_matches_terms(fact: FieldFact, terms: set[str]) -> bool: - if not terms or terms & semantic_shape_terms(): - return True - text = " ".join( - part - for part in ( - fact.container_name, - fact.field_name, - fact.type_value, - fact.collection_kind or "", - ) - if part - ).lower() - return any(term in text for term in terms) - - -def semantic_shape_terms() -> set[str]: - return { - "field", - "fields", - "type", - "types", - "scalar", - "scalars", - "collection", - "collections", - "list", - "lists", - "map", - "maps", - "set", - "sets", - "dict", - "tuple", - } diff --git a/src/asp_python/_semantic_graph_fact_render.py b/src/asp_python/_semantic_graph_fact_render.py deleted file mode 100644 index 832b710..0000000 --- a/src/asp_python/_semantic_graph_fact_render.py +++ /dev/null @@ -1,187 +0,0 @@ -"""Render Python data-shape facts as graph-turbo nodes and edges.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_graph_fact_model import FieldFact -from ._semantic_graph_fact_render_fields import ( - render_collection_fact, - render_collection_family, - render_field_fact, - render_type_fact, -) - -LANGUAGE_ID = "python" -PROVIDER_ID = "asp-python" - - -def graph_payload( - query: str, facts: list[FieldFact] -) -> dict[str, list[dict[str, Any]]]: - nodes: list[dict[str, Any]] = [] - edges: list[dict[str, Any]] = [] - collection_ids: set[str] = set() - for fact in facts: - field_id = field_id_for(fact) - type_id = type_id_for(fact) - locator = f"{fact.path}:{fact.line}:{fact.line}" - fields = graph_fields(fact) - nodes.append(field_node(fact, field_id, locator, fields)) - nodes.append(type_node(fact, type_id, locator, fields)) - edges.append({"source": field_id, "target": type_id, "relation": "has_type"}) - append_collection(nodes, edges, collection_ids, fact, field_id, type_id) - if query: - edges.append( - { - "source": stable_id("query", query), - "target": field_id, - "relation": "matches", - } - ) - return {"nodes": nodes, "edges": edges} - - -def graph_fields(fact: FieldFact) -> dict[str, Any]: - family = render_collection_family(fact.collection_kind) - fields: dict[str, Any] = { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "field", - "provenance": "parser", - "confidence": "exact", - "freshness": "fresh", - "containerName": fact.container_name, - "fieldName": fact.field_name, - "typeValue": fact.type_value, - "elementShape": "collection" if fact.collection_kind else "scalar", - "contextLocator": f"{fact.path}:{fact.context_start}:{fact.context_end}", - "contextStartLine": fact.context_start, - "contextEndLine": fact.context_end, - "field": render_field_fact(fact, family), - } - if fact.collection_kind is not None: - fields["collectionKind"] = fact.collection_kind - fields["collectionFamily"] = family - fields["collectionImpl"] = fact.collection_kind - return fields - - -def field_node( - fact: FieldFact, - field_id: str, - locator: str, - fields: dict[str, Any], -) -> dict[str, Any]: - return { - "id": field_id, - "kind": "field", - "role": "class-field", - "value": f"{fact.field_name}: {fact.type_value}", - "action": "code", - "path": fact.path, - "ownerPath": fact.path, - "symbol": fact.field_name, - "startLine": fact.line, - "endLine": fact.line, - "locator": locator, - "matchText": f"{fact.container_name}.{fact.field_name}: {fact.type_value}", - "fields": fields, - } - - -def type_node( - fact: FieldFact, - type_id: str, - locator: str, - fields: dict[str, Any], -) -> dict[str, Any]: - type_fields = { - **fields, - "semanticFactKind": "type", - "type": render_type_fact(fact), - } - type_fields.pop("field", None) - return { - "id": type_id, - "kind": "type", - "role": "field-type", - "value": fact.type_value, - "action": "evidence", - "path": fact.path, - "ownerPath": fact.path, - "symbol": fact.type_value.split("[", 1)[0], - "startLine": fact.line, - "endLine": fact.line, - "locator": locator, - "fields": type_fields, - } - - -def append_collection( - nodes: list[dict[str, Any]], - edges: list[dict[str, Any]], - collection_ids: set[str], - fact: FieldFact, - field_id: str, - type_id: str, -) -> None: - if fact.collection_kind is None: - return - collection_id = f"collection:{fact.collection_kind}" - if collection_id not in collection_ids: - collection_ids.add(collection_id) - nodes.append( - { - "id": collection_id, - "kind": "collection", - "role": "family", - "value": fact.collection_kind, - "action": "evidence", - "symbol": fact.collection_kind, - "fields": { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "collection", - "provenance": "parser", - "confidence": "exact", - "freshness": "fresh", - "collectionFamily": render_collection_family(fact.collection_kind), - "collectionImpl": fact.collection_kind, - "collectionKind": fact.collection_kind, - "collection": render_collection_fact(fact), - }, - } - ) - edges.append( - {"source": field_id, "target": collection_id, "relation": "collection_of"} - ) - edges.append( - {"source": type_id, "target": collection_id, "relation": "collection_of"} - ) - - -def field_id_for(fact: FieldFact) -> str: - return stable_id( - "field", - f"{fact.path}:{fact.container_name}:{fact.field_name}:{fact.line}", - ) - - -def type_id_for(fact: FieldFact) -> str: - return stable_id( - "type", - f"{fact.path}:{fact.field_name}:{fact.type_value}:{fact.line}", - ) - - -def stable_id(kind: str, value: str) -> str: - rendered = [kind, ":"] - for character in value: - if character.isalnum(): - rendered.append(character.lower()) - elif character in {"/", ".", "_", "-"}: - rendered.append(character) - else: - rendered.append("-") - return "".join(rendered).strip("-") diff --git a/src/asp_python/_semantic_graph_fact_render_fields.py b/src/asp_python/_semantic_graph_fact_render_fields.py deleted file mode 100644 index d677e6f..0000000 --- a/src/asp_python/_semantic_graph_fact_render_fields.py +++ /dev/null @@ -1,81 +0,0 @@ -"""Field payload helpers for Python semantic graph rendering.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_graph_fact_model import FieldFact - - -def render_field_fact(fact: FieldFact, family: str | None) -> dict[str, Any]: - return { - "ownerKind": "class", - "name": fact.field_name, - "ownerPath": fact.path, - "access": render_access_modes(family), - } - - -def render_type_fact(fact: FieldFact) -> dict[str, str]: - args = render_collection_type_args(fact.type_value) - rendered: dict[str, str] = {"name": fact.type_value} - if render_collection_family(fact.collection_kind) == "map": - if args: - rendered["key"] = args[0] - if len(args) > 1: - rendered["value"] = args[1] - elif args: - rendered["element"] = args[0] - return rendered - - -def render_collection_fact(fact: FieldFact) -> dict[str, Any]: - family = render_collection_family(fact.collection_kind) - rendered: dict[str, Any] = { - "family": family, - "impl": fact.collection_kind, - "mutation": render_mutation_modes(family), - } - args = render_collection_type_args(fact.type_value) - if family == "map": - if args: - rendered["keyType"] = args[0] - if len(args) > 1: - rendered["valueType"] = args[1] - elif args: - rendered["elementType"] = args[0] - return rendered - - -def render_collection_family(collection_kind: str | None) -> str | None: - if collection_kind in {"list", "tuple"}: - return "sequence" - if collection_kind == "dict": - return "map" - if collection_kind == "set": - return "set" - return None - - -def render_access_modes(family: str | None) -> list[str]: - if family == "map": - return ["read", "write", "validate"] - return ["read", "append", "validate"] - - -def render_mutation_modes(family: str | None) -> list[str]: - if family == "map": - return ["insert", "remove", "update"] - if family == "set": - return ["insert", "remove"] - return ["append", "remove"] - - -def render_collection_type_args(type_value: str) -> list[str]: - if "[" not in type_value or not type_value.endswith("]"): - return [] - return [ - part.strip() - for part in type_value.split("[", 1)[1].removesuffix("]").split(",") - if part.strip() - ] diff --git a/src/asp_python/_semantic_graph_facts.py b/src/asp_python/_semantic_graph_facts.py deleted file mode 100644 index b9d4335..0000000 --- a/src/asp_python/_semantic_graph_facts.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Provider-owned graph facts for Python data-shape queries.""" - -from __future__ import annotations - -import json -from pathlib import Path - -from ._cli_args import ProtocolArgs -from ._semantic_graph_fact_collect import collect_field_facts -from ._semantic_graph_fact_render import graph_payload -from ._semantic_graph_project_collect import collect_project_facts -from ._semantic_graph_project_render import project_graph_payload - - -def render_semantic_graph_facts( - args: ProtocolArgs, - *, - project_root: Path, - stdin: str, -) -> str | None: - """Render graph-turbo provider facts for Python field/type/collection queries.""" - - if not _supports_semantic_graph_facts(args): - return None - field_payload = graph_payload( - args.query or "", - collect_field_facts(project_root, args.query or "", stdin), - ) - project_payload = project_graph_payload(collect_project_facts(project_root)) - package_bridge_edges = _package_bridge_edges(field_payload, project_payload) - payload = { - "schemaId": "agent.semantic-protocols.semantic-fact-graph", - "schemaVersion": "1", - "protocolId": "agent.semantic-protocols.semantic-language", - "protocolVersion": "1", - "languageId": "python", - "providerId": "asp-python", - "projectRoot": project_root.as_posix(), - "query": args.query or "", - "nodes": [*field_payload["nodes"], *project_payload["nodes"]], - "edges": [ - *field_payload["edges"], - *project_payload["edges"], - *package_bridge_edges, - ], - } - return json.dumps(payload, sort_keys=True) + "\n" - - -def _supports_semantic_graph_facts(args: ProtocolArgs) -> bool: - return ( - args.command == "search" - and args.view == "semantic-facts" - and args.json - and args.query is not None - ) - - -def _package_bridge_edges( - field_payload: dict[str, list[dict[str, object]]], - project_payload: dict[str, list[dict[str, object]]], -) -> list[dict[str, str]]: - package_id = next( - ( - str(node["id"]) - for node in project_payload["nodes"] - if node.get("kind") == "package" and isinstance(node.get("id"), str) - ), - None, - ) - if package_id is None: - return [] - return [ - {"source": str(node["id"]), "target": package_id, "relation": "belongs_to"} - for node in field_payload["nodes"] - if node.get("kind") in {"field", "hot", "owner"} - and isinstance(node.get("id"), str) - ] diff --git a/src/asp_python/_semantic_graph_project_collect.py b/src/asp_python/_semantic_graph_project_collect.py deleted file mode 100644 index ad74728..0000000 --- a/src/asp_python/_semantic_graph_project_collect.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Collect Python package/build/test graph facts from project metadata.""" - -from __future__ import annotations - -import ast -from pathlib import Path -from typing import Any - -from ._project_config import read_pyproject_payload -from ._semantic_graph_fact_collect import SKIP_DIRS -from ._semantic_graph_fact_model import ( - DependencyFact, - PackageFact, - ProjectFact, - TestFact, - display_path, -) - -DEPENDENCY_LIMIT = 64 -TEST_LIMIT = 64 -PYTHON_SUFFIX = ".py" - - -def collect_project_facts(project_root: Path) -> ProjectFact | None: - manifest_path = project_root / "pyproject.toml" - pyproject = _read_pyproject(manifest_path) - project = pyproject.get("project", {}) if isinstance(pyproject, dict) else {} - if not isinstance(project, dict): - return None - package_name = project.get("name") - if not isinstance(package_name, str) or not package_name.strip(): - return None - package = PackageFact( - name=package_name.strip(), - manifest_path=display_path(project_root, manifest_path), - ) - return ProjectFact( - package=package, - dependencies=tuple(_dependency_facts(project, package.manifest_path)), - tests=tuple(_test_facts(project_root)), - ) - - -def _read_pyproject(path: Path) -> dict[str, Any]: - return read_pyproject_payload(path) - - -def _dependency_facts( - project: dict[str, Any], - manifest_path: str, -) -> list[DependencyFact]: - facts: list[DependencyFact] = [] - dependencies = project.get("dependencies", []) - if isinstance(dependencies, list): - facts.extend( - fact - for dependency in dependencies - if isinstance(dependency, str) - if (fact := _dependency_fact(dependency, "normal", manifest_path)) - is not None - ) - optional = project.get("optional-dependencies", {}) - if isinstance(optional, dict): - for extra, dependencies in sorted(optional.items()): - if not isinstance(extra, str) or not isinstance(dependencies, list): - continue - dependency_kind = "dev" if extra in {"dev", "test", "tests"} else "optional" - facts.extend( - fact - for dependency in dependencies - if isinstance(dependency, str) - if ( - fact := _dependency_fact( - dependency, - dependency_kind, - manifest_path, - extra=extra, - ) - ) - is not None - ) - return sorted(facts, key=lambda fact: (fact.dependency_kind, fact.dependency_name))[ - :DEPENDENCY_LIMIT - ] - - -def _dependency_fact( - requirement: str, - dependency_kind: str, - manifest_path: str, - *, - extra: str | None = None, -) -> DependencyFact | None: - dependency_name = _dependency_name(requirement) - if dependency_name is None: - return None - version_req = requirement[len(dependency_name) :].strip() - return DependencyFact( - package_name=dependency_name.replace("_", "-"), - dependency_name=dependency_name, - dependency_kind=dependency_kind, - manifest_path=manifest_path, - version_req=version_req or None, - extra=extra, - ) - - -def _dependency_name(requirement: str) -> str | None: - name_chars: list[str] = [] - for character in requirement.strip(): - if character.isalnum() or character in {"_", ".", "-"}: - name_chars.append(character) - continue - break - name = "".join(name_chars) - return name or None - - -def _test_facts(project_root: Path) -> list[TestFact]: - tests_dir = project_root / "tests" - if not tests_dir.exists(): - return [] - facts: list[TestFact] = [] - for path in sorted(tests_dir.rglob("*")): - if not path.is_file() or path.suffix != PYTHON_SUFFIX: - continue - if any(part in SKIP_DIRS for part in path.relative_to(project_root).parts): - continue - function_count = _test_function_count(path) - facts.append( - TestFact( - path=display_path(project_root, path), - name=path.stem, - function_count=function_count, - ) - ) - if len(facts) >= TEST_LIMIT: - break - return facts - - -def _test_function_count(path: Path) -> int: - try: - module = ast.parse(path.read_text(encoding="utf-8")) - except (OSError, SyntaxError, UnicodeDecodeError): - return 0 - return sum( - 1 - for node in ast.walk(module) - if isinstance(node, ast.FunctionDef) and node.name.startswith("test_") - ) diff --git a/src/asp_python/_semantic_graph_project_render.py b/src/asp_python/_semantic_graph_project_render.py deleted file mode 100644 index c22af4f..0000000 --- a/src/asp_python/_semantic_graph_project_render.py +++ /dev/null @@ -1,198 +0,0 @@ -"""Render Python package/build/test facts as semantic graph nodes.""" - -from __future__ import annotations - -from typing import Any - -from ._semantic_graph_fact_model import DependencyFact, ProjectFact, TestFact -from ._semantic_graph_fact_render import LANGUAGE_ID, PROVIDER_ID, stable_id - - -def project_graph_payload( - project: ProjectFact | None, -) -> dict[str, list[dict[str, Any]]]: - if project is None: - return {"nodes": [], "edges": []} - nodes = [_package_node(project), _build_node(project)] - edges = [ - { - "source": _package_id_for(project.package.name), - "target": _build_id_for(project.package.name), - "relation": "builds", - } - ] - for dependency in project.dependencies: - dependency_id = _dependency_id_for(project.package.name, dependency) - nodes.append(_dependency_node(project, dependency, dependency_id)) - edges.append( - { - "source": _package_id_for(project.package.name), - "target": dependency_id, - "relation": "depends_on", - } - ) - for test in project.tests: - test_id = _test_id_for(project.package.name, test) - nodes.append(_test_node(project, test, test_id)) - edges.append( - { - "source": _build_id_for(project.package.name), - "target": test_id, - "relation": "tests", - } - ) - edges.append( - { - "source": test_id, - "target": _package_id_for(project.package.name), - "relation": "belongs_to", - } - ) - return {"nodes": nodes, "edges": edges} - - -def _package_node(project: ProjectFact) -> dict[str, Any]: - manifest_path = project.package.manifest_path - return { - "id": _package_id_for(project.package.name), - "kind": "package", - "role": "python-project", - "value": project.package.name, - "action": "package", - "path": manifest_path, - "ownerPath": manifest_path, - "startLine": 1, - "endLine": 1, - "locator": f"{manifest_path}:1:1", - "matchText": project.package.name, - "fields": { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "package", - "provenance": "parser", - "confidence": "exact", - "freshness": "fresh", - "packageName": project.package.name, - "manifestPath": manifest_path, - }, - } - - -def _build_node(project: ProjectFact) -> dict[str, Any]: - manifest_path = project.package.manifest_path - command = _test_command() - return { - "id": _build_id_for(project.package.name), - "kind": "build", - "role": "pytest", - "value": command, - "action": "build", - "path": manifest_path, - "ownerPath": manifest_path, - "startLine": 1, - "endLine": 1, - "locator": f"{manifest_path}:1:1", - "matchText": command, - "fields": { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "build", - "provenance": "build", - "confidence": "exact", - "freshness": "fresh", - "packageName": project.package.name, - "manifestPath": manifest_path, - "tool": "pytest", - "command": command, - }, - } - - -def _dependency_node( - project: ProjectFact, - dependency: DependencyFact, - dependency_id: str, -) -> dict[str, Any]: - fields: dict[str, Any] = { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "dependency", - "provenance": "parser", - "confidence": "exact", - "freshness": "fresh", - "packageName": project.package.name, - "manifestPath": dependency.manifest_path, - "dependencyName": dependency.dependency_name, - "dependencyPackageName": dependency.package_name, - "dependencyKind": dependency.dependency_kind, - } - if dependency.version_req is not None: - fields["versionReq"] = dependency.version_req - if dependency.extra is not None: - fields["extra"] = dependency.extra - return { - "id": dependency_id, - "kind": "dependency", - "role": dependency.dependency_kind, - "value": dependency.package_name, - "action": "deps", - "path": dependency.manifest_path, - "ownerPath": dependency.manifest_path, - "startLine": 1, - "endLine": 1, - "locator": f"{dependency.manifest_path}:1:1", - "matchText": dependency.package_name, - "fields": fields, - } - - -def _test_node(project: ProjectFact, test: TestFact, test_id: str) -> dict[str, Any]: - return { - "id": test_id, - "kind": "test", - "role": "pytest-target", - "value": test.name, - "action": "tests", - "path": test.path, - "ownerPath": test.path, - "startLine": 1, - "endLine": 1, - "locator": f"{test.path}:1:1", - "matchText": test.name, - "fields": { - "languageId": LANGUAGE_ID, - "providerId": PROVIDER_ID, - "semanticFactKind": "test", - "provenance": "test", - "confidence": "exact", - "freshness": "fresh", - "packageName": project.package.name, - "testName": test.name, - "testPath": test.path, - "functionCount": test.function_count, - "command": _test_command(), - }, - } - - -def _package_id_for(package_name: str) -> str: - return stable_id("package", package_name) - - -def _build_id_for(package_name: str) -> str: - return stable_id("build", f"{_test_command()}:{package_name}") - - -def _dependency_id_for(package_name: str, dependency: DependencyFact) -> str: - return stable_id( - "dependency", - f"{package_name}:{dependency.dependency_kind}:{dependency.package_name}", - ) - - -def _test_id_for(package_name: str, test: TestFact) -> str: - return stable_id("test", f"{package_name}:{test.path}") - - -def _test_command() -> str: - return "uv run --project . pytest" diff --git a/src/asp_python/_semantic_language_ids.py b/src/asp_python/_semantic_language_ids.py index 4d78e92..8e8fefc 100644 --- a/src/asp_python/_semantic_language_ids.py +++ b/src/asp_python/_semantic_language_ids.py @@ -4,7 +4,6 @@ SEMANTIC_LANGUAGE_REGISTRY_VERSION = "1" SEMANTIC_LANGUAGE_PROTOCOL_ID = "agent.semantic-protocols.semantic-language" SEMANTIC_LANGUAGE_PROTOCOL_VERSION = "1" -SEMANTIC_SEARCH_PACKET_SCHEMA_ID = "agent.semantic-protocols.semantic-search-packet" SEMANTIC_QUERY_PACKET_SCHEMA_ID = "agent.semantic-protocols.semantic-query-packet" SEMANTIC_SOURCE_LOCATION_SCHEMA_ID = "agent.semantic-protocols.semantic-source-location" SEMANTIC_TREE_SITTER_PROVENANCE_SCHEMA_ID = ( diff --git a/src/asp_python/_semantic_query_packet.py b/src/asp_python/_semantic_query_packet.py index c0b35ff..67b3076 100644 --- a/src/asp_python/_semantic_query_packet.py +++ b/src/asp_python/_semantic_query_packet.py @@ -2,6 +2,7 @@ from __future__ import annotations +import shlex from typing import Any from ._semantic_projection import semantic_query_projection @@ -126,9 +127,11 @@ def _routes_for_term(import_routes: list[Any], term: str) -> list[dict[str, str] def semantic_import_route_next(route: dict[str, str]) -> str: + owner = shlex.quote(route["ownerPath"]) + query = shlex.quote(route["query"]) return ( - "asp python search owner " - f"{route['ownerPath']} items --query {route['query']} --workspace . --view seeds" + "asp search playbook --language python " + f"--rg -n -e {query} {owner} --tantivy term {query}" ) diff --git a/src/asp_python/_tree_sitter_query_catalog.py b/src/asp_python/_tree_sitter_query_catalog.py index 3cf5620..6344c30 100644 --- a/src/asp_python/_tree_sitter_query_catalog.py +++ b/src/asp_python/_tree_sitter_query_catalog.py @@ -188,8 +188,8 @@ def resolved_tree_sitter_query(args: Any) -> dict[str, Any]: source = args.tree_sitter_query.strip() if not args.asp_syntax_query_node_types: raise ValueError( - "tree-sitter query projection requires ASP-compiled query plan; use " - "`asp python query --treesitter-query ...` so ASP owns query ABI compilation" + "tree-sitter query projection requires an ASP-compiled query plan; use the " + "`--syntax python ...` block of `asp search playbook` so ASP owns query ABI compilation" ) return { "input": source, diff --git a/tests/fixtures/bin/asp b/tests/fixtures/bin/asp deleted file mode 100755 index 4f017d0..0000000 --- a/tests/fixtures/bin/asp +++ /dev/null @@ -1,506 +0,0 @@ -#!/usr/bin/env node - -import { stdin, stderr } from "node:process"; - -const args = process.argv.slice(2); - -if (args[0] === "--help" || args.includes("--help")) { - process.stdout.write("asp test fixture\n"); - process.exit(0); -} - -if ( - args[0] !== "graph" || - args[1] !== "render" || - !args.includes("--packet") || - !args.includes("-") || - !args.includes("--view") || - !args.includes("seeds") -) { - stderr.write("test asp fixture only supports graph render --packet - --view seeds\n"); - process.exit(2); -} - -const packet = JSON.parse(await readStdin()); -const kind = packet.header?.kind ?? packet.method ?? "search"; -const fields = packet.header?.fields ?? {}; -const synthesis = packet.searchSynthesis ?? {}; -const seeds = collectSeeds(packet); -const aliases = assignAliases(seeds); -const query = fields.q ?? fields.query ?? packet.query ?? firstQuerySetValue(packet); -const algorithm = selectAlgorithm(kind, synthesis.algorithm ?? fields.alg); -const headerFields = { ...fields }; -delete headerFields.q; -delete headerFields.query; -delete headerFields.alg; -if ((kind === "search-prime" || kind === "search-workspace") && headerFields.root === undefined) { - headerFields.root = "."; -} - -const leadingHeaderFields = orderedHeaderFields(kind, headerFields, query, algorithm); - -const lines = [ - `[${kind}] ${formatFields(leadingHeaderFields)}` - .replace(/\s+/gu, " ") - .trimEnd(), - "legend: ID=kind:role(value)!next; edge SRC>{DST:rel}; frontier ID.next", - `aliases: graph:{G=search${aliasLegend(aliases)}}`, -]; - -if (aliases.length > 0) { - lines.push(aliases.map((alias) => alias.rendered).join(";")); - lines.push(`G>{${aliases.map((alias) => `${alias.id}:${alias.edge}`).join(",")}}`); - lines.push( - `rank=${aliases.map((alias) => alias.id).join(",")} frontier=${aliases - .map((alias) => `${alias.id}.${alias.next}`) - .join(",")}`, - ); -} else { - lines.push("G>{}"); - lines.push("rank= frontier="); -} - -const entries = renderedEntries(packet.reasoningProfiles, aliases); -if (entries.length > 0) { - lines.push(`entries=${entries.join(",")}`); -} - -const profiles = renderedProfiles(aliases); -if (profiles.length > 0) { - lines.push(`profiles=${profiles.join(",")}`); -} - -const avoid = synthesis.avoid ?? packet.avoid ?? defaultAvoid(packet.header?.kind); -if (avoid !== undefined) { - lines.push(`avoid=${formatValue(avoid)}`); -} - -process.stdout.write(`${lines.join("\n")}\n`); - -async function readStdin() { - let input = ""; - stdin.setEncoding("utf8"); - for await (const chunk of stdin) { - input += chunk; - } - return input; -} - -function collectSeeds(packet) { - const querySeeds = []; - const ownerSeeds = []; - const testSeeds = []; - const symbolSeeds = []; - const packageSeeds = []; - const dependencySeeds = []; - const findingSeeds = []; - const otherSeeds = []; - - const query = packet.query ?? firstQuerySetValue(packet); - if ((packet.header?.kind === "search-lexical" || packet.header?.kind === "search-query") && query !== undefined) { - querySeeds.push({ kind: "query", target: query }); - } - - for (const action of packet.nextActions ?? []) { - addSeed(action, { - querySeeds, - ownerSeeds, - testSeeds, - symbolSeeds, - packageSeeds, - dependencySeeds, - findingSeeds, - otherSeeds, - }); - } - for (const resolution of packet.ownerResolution ?? []) { - if (resolution.ownerPath !== undefined) { - pushOwnerOrTest(resolution.ownerPath, ownerSeeds, testSeeds); - } - } - for (const node of asArray(packet.nodes)) { - addNodeSeed(node, { - querySeeds, - ownerSeeds, - testSeeds, - symbolSeeds, - packageSeeds, - dependencySeeds, - findingSeeds, - otherSeeds, - }); - } - for (const windowTarget of packet.searchSynthesis?.windowSet ?? []) { - if (windowTarget.target !== undefined) { - pushOwnerOrTest(windowTarget.target, ownerSeeds, testSeeds); - } - } - for (const path of [ - ...(packet.searchSynthesis?.highImpactOwners ?? []), - ...(packet.searchSynthesis?.editFrontier ?? []), - ...(packet.searchSynthesis?.frontierOwners ?? []), - ]) { - pushOwnerOrTest(path, ownerSeeds, testSeeds); - } - - const synthesisTests = []; - const synthesisSymbols = []; - const synthesisOthers = []; - for (const seed of packet.searchSynthesis?.seeds ?? []) { - if (seed.kind === "owner") { - ownerSeeds.push(seed); - } else if (seed.kind === "tests" || seed.kind === "test") { - synthesisTests.push(seed); - } else if (seed.kind === "symbol") { - synthesisSymbols.push(seed); - } else if (seed.kind === "package" || seed.kind === "pkg") { - packageSeeds.push(seed); - } else if (seed.kind === "dependency" || seed.kind === "deps") { - dependencySeeds.push(seed); - } else if (seed.kind === "finding") { - findingSeeds.push(seed); - } else if (seed.kind === "lexical" || seed.kind === "query") { - querySeeds.push(seed); - } else { - synthesisOthers.push(seed); - } - } - for (const owner of packet.owners ?? []) { - const path = owner.path ?? owner.target; - if (path === undefined) { - continue; - } - if (owner.role === "test" || isTestPath(path)) { - testSeeds.push({ kind: "tests", target: path }); - } else { - ownerSeeds.push({ kind: "owner", target: path }); - } - } - for (const hit of packet.hits ?? []) { - const path = hit.ownerPath ?? hit.path ?? hit.target; - if (path !== undefined) { - pushOwnerOrTest(path, ownerSeeds, testSeeds); - } - } - for (const finding of packet.findings ?? []) { - const target = finding.ruleId ?? finding.id ?? finding.code ?? finding.target; - if (target !== undefined) { - findingSeeds.push({ kind: "finding", target }); - } - } - for (const handle of packet.semanticHandles ?? []) { - if (handle.ownerPath !== undefined) { - pushOwnerOrTest(handle.ownerPath, ownerSeeds, testSeeds); - } - for (const testPath of handle.testPaths ?? []) { - testSeeds.push({ kind: "tests", target: testPath }); - } - } - for (const path of packet.searchSynthesis?.testFrontier ?? []) { - testSeeds.push({ kind: "tests", target: path }); - } - - return dedupeSeeds([ - ...querySeeds, - ...packageSeeds, - ...ownerSeeds, - ...testSeeds, - ...interleaveTestsAndSymbols(synthesisTests, synthesisSymbols), - ...symbolSeeds, - ...dependencySeeds, - ...findingSeeds, - ...otherSeeds, - ...synthesisOthers, - ]); -} - -function pushOwnerOrTest(path, ownerSeeds, testSeeds) { - if (isTestPath(path)) { - testSeeds.push({ kind: "tests", target: path }); - } else { - ownerSeeds.push({ kind: "owner", target: path }); - } -} - -function addSeed(seed, groups) { - if (seed?.target === undefined || seed?.kind === undefined) { - return; - } - if (seed.kind === "owner" || seed.kind === "ingest") { - if (isTestPath(seed.target)) { - groups.testSeeds.push({ kind: "tests", target: seed.target }); - } else { - groups.ownerSeeds.push({ kind: "owner", target: seed.target }); - } - } else if (seed.kind === "tests" || seed.kind === "test") { - groups.testSeeds.push({ kind: "tests", target: seed.target }); - } else if (seed.kind === "symbol") { - groups.symbolSeeds.push(seed); - } else if (seed.kind === "package" || seed.kind === "pkg") { - groups.packageSeeds.push(seed); - } else if (seed.kind === "dependency" || seed.kind === "deps") { - groups.dependencySeeds.push(seed); - } else if (seed.kind === "finding") { - groups.findingSeeds.push(seed); - } else if (seed.kind === "query" && seed.targetRole === "path") { - const target = seed.ownerPath ?? seed.target; - if (isTestPath(target)) { - groups.testSeeds.push({ kind: "tests", target }); - } else { - groups.ownerSeeds.push({ kind: "owner", target }); - } - } else if (seed.kind === "query" || seed.kind === "lexical") { - groups.querySeeds.push(seed); - } else { - groups.otherSeeds.push(seed); - } -} - -function addNodeSeed(node, groups) { - if (node === undefined || node === null) { - return; - } - const kind = node.kind ?? node.type ?? node.nodeKind; - const target = node.target ?? node.value ?? node.path ?? node.pkg ?? node.name ?? node.id; - if (kind === "search" || node.id === "G") { - return; - } - if (kind === undefined || target === undefined) { - return; - } - addSeed({ kind, target, targetRole: node.role }, groups); -} - -function interleaveTestsAndSymbols(testSeeds, symbolSeeds) { - const maxLength = Math.max(testSeeds.length, symbolSeeds.length); - const result = []; - for (let index = 0; index < maxLength; index += 1) { - if (testSeeds[index] !== undefined) { - result.push(testSeeds[index]); - } - if (symbolSeeds[index] !== undefined) { - result.push(symbolSeeds[index]); - } - } - return result; -} - -function dedupeSeeds(seeds) { - const seen = new Set(); - const result = []; - for (const seed of seeds) { - if (seed?.target === undefined || seed?.kind === undefined) { - continue; - } - const key = `${seed.kind}\0${seed.target}`; - if (seen.has(key)) { - continue; - } - seen.add(key); - result.push(seed); - } - return result; -} - -function assignAliases(seeds) { - const counts = new Map(); - return seeds.map((seed) => { - const descriptor = describeSeed(seed); - const count = counts.get(descriptor.prefix) ?? 0; - counts.set(descriptor.prefix, count + 1); - const id = count === 0 ? descriptor.prefix : `${descriptor.prefix}${count + 1}`; - return { - id, - prefix: descriptor.prefix, - kind: descriptor.kind, - next: descriptor.next, - edge: descriptor.edge, - rendered: `${id}=${descriptor.kind}:${descriptor.role}(${escapeRoleValue(seed.target)})!${descriptor.next}`, - }; - }); -} - -function describeSeed(seed) { - switch (seed.kind) { - case "lexical": - case "query": - return { prefix: "Q", kind: "query", role: "term", next: "lexical", edge: "matches" }; - case "owner": - case "ingest": - return { prefix: "O", kind: "owner", role: "path", next: "owner", edge: "selects" }; - case "tests": - case "test": - return { prefix: "T", kind: "test", role: "path", next: "tests", edge: "covers" }; - case "symbol": - return { prefix: "S", kind: "symbol", role: "symbol", next: "symbol", edge: "selects" }; - case "dependency": - case "deps": - return { prefix: "D", kind: "dependency", role: "dep", next: "dependency", edge: "selects" }; - case "finding": - return { prefix: "F", kind: "finding", role: "rule", next: "owner", edge: "selects" }; - case "package": - case "pkg": - return { prefix: "P", kind: "package", role: "pkg", next: "owner", edge: "selects" }; - default: - return { - prefix: "N", - kind: seed.kind, - role: "target", - next: seed.kind, - edge: "selects", - }; - } -} - -function aliasLegend(aliases) { - const seen = new Set(); - const parts = []; - for (const alias of aliases) { - if (seen.has(alias.prefix)) { - continue; - } - seen.add(alias.prefix); - parts.push(`${alias.prefix}=${alias.kind}`); - } - return parts.length === 0 ? "" : `,${parts.join(",")}`; -} - -function renderedEntries(reasoningProfiles, aliases) { - if (Array.isArray(reasoningProfiles) && reasoningProfiles.length > 0) { - return reasoningProfiles - .filter((profile) => profileSelectorsSatisfied(profile, aliases)) - .map((profile) => { - const selectors = profile.selectors ?? []; - const selectorAliases = selectors - .filter((selector) => aliasForSelector(selector, aliases) !== undefined) - .map((selector) => aliasForSelector(selector, aliases)); - return `${profile.profile}(${dedupeStrings(selectorAliases).join(",")}=>${(profile.returns ?? []).join("+")})`; - }); - } - - const hasOwner = aliases.some((alias) => alias.kind === "owner"); - const hasQuery = aliases.some((alias) => alias.kind === "query"); - const entries = []; - if (hasOwner && hasQuery) { - entries.push("owner-query(O,Q=>items+tests+dependency-usage)"); - } - if (hasOwner) { - entries.push("owner-tests(O=>covering-tests+test-entrypoints+fixtures)"); - } - return entries; -} - -function renderedProfiles(aliases) { - if (aliases.some((alias) => alias.kind === "owner") && !aliases.some((alias) => alias.kind === "query")) { - return ["owner-tests(O)"]; - } - return []; -} - -function profileSelectorsSatisfied(profile, aliases) { - return (profile.selectors ?? []) - .filter((selector) => selector.required !== false) - .every((selector) => aliasForSelector(selector, aliases) !== undefined); -} - -function aliasForSelector(selector, aliases) { - const byAlias = aliases.find((alias) => selector.alias !== undefined && alias.prefix === selector.alias); - if (byAlias !== undefined) { - return byAlias.prefix; - } - const byKind = aliases.find((alias) => selector.kind !== undefined && alias.kind === selector.kind); - return byKind?.prefix; -} - -function firstQuerySetValue(packet) { - if (Array.isArray(packet.querySet) && packet.querySet.length > 1) { - return packet.querySet.map((entry) => entry.value).join(","); - } - return packet.querySet?.[0]?.value; -} - -function formatFields(fields) { - return Object.entries(fields) - .filter(([, value]) => value !== undefined) - .map(([key, value]) => `${key}=${formatValue(value)}`) - .join(" "); -} - -function formatValue(value) { - if (Array.isArray(value)) { - return value.map((entry) => String(entry)).join(","); - } - return String(value); -} - -function escapeRoleValue(value) { - return String(value).replaceAll(")", "\\)"); -} - -function withoutKeys(object, keys) { - const keySet = new Set(keys); - return Object.fromEntries(Object.entries(object).filter(([key]) => !keySet.has(key))); -} - -function asArray(value) { - return Array.isArray(value) ? value : []; -} - -function dedupeStrings(values) { - return [...new Set(values.filter((value) => value !== undefined))]; -} - -function defaultAvoid(kind) { - return kind === "search-lexical" || kind === "search-query" - ? "broad-lexical,raw-read,repeat-glob" - : undefined; -} - -function orderedHeaderFields(kind, headerFields, query, algorithm) { - if (kind === "search-lexical" || kind === "search-query") { - const leading = { - ...(query === undefined ? {} : { q: query }), - ...(headerFields.querySet === undefined ? {} : { querySet: headerFields.querySet }), - ...(headerFields.selector === undefined ? {} : { selector: headerFields.selector }), - ...(headerFields.view === undefined ? {} : { view: headerFields.view }), - ...(algorithm === undefined ? {} : { alg: algorithm }), - }; - return { - ...leading, - ...withoutKeys(headerFields, ["view", "querySet", "selector"]), - }; - } - if (kind === "search-prime" || kind === "search-workspace") { - const leading = { - ...(query === undefined ? {} : { q: query }), - ...(headerFields.root === undefined ? {} : { root: headerFields.root }), - ...(algorithm === undefined ? {} : { alg: algorithm }), - }; - return { - ...leading, - ...withoutKeys(headerFields, ["root"]), - }; - } - const leading = { - ...(query === undefined ? {} : { q: query }), - ...(headerFields.view === undefined ? {} : { view: headerFields.view }), - ...(headerFields.root === undefined ? {} : { root: headerFields.root }), - ...(algorithm === undefined ? {} : { alg: algorithm }), - }; - return { - ...leading, - ...withoutKeys(headerFields, ["view", "root"]), - }; -} - -function selectAlgorithm(kind, algorithm) { - if (kind === "search-prime") { - return "budgeted-prime-frontier-v1"; - } - return algorithm; -} - -function isTestPath(path) { - const text = String(path); - return text.startsWith("test/") || text.startsWith("tests/") || text.includes("/test_") || text.endsWith("_test.rs"); -} diff --git a/tests/unit/asp_python/test_cli.py b/tests/unit/asp_python/test_cli.py index fc11467..ad16b85 100644 --- a/tests/unit/asp_python/test_cli.py +++ b/tests/unit/asp_python/test_cli.py @@ -16,9 +16,9 @@ def test_cli_help_advertises_the_current_provider_protocol() -> None: rendered = stdout.getvalue() assert exit_code == 0 - assert "asp python search playbook " in rendered + assert "asp search playbook --language python" in rendered assert "asp-python search " not in rendered - assert "asp python query --selector" in rendered + assert "asp query playbook --language python --selector" in rendered assert "asp-python evidence" not in rendered assert "asp-python agent doctor" in rendered @@ -42,10 +42,10 @@ def test_cli_agent_guide_advertises_exact_source_route(tmp_path: Path) -> None: assert exit_code == 0 assert ( - "asp python query --selector " in stdout.getvalue() + "asp query playbook --language python --selector " + in stdout.getvalue() ) - assert "|cmd playbook=asp python search playbook " in stdout.getvalue() - assert "search prime" not in stdout.getvalue() + assert "|cmd playbook=asp search playbook --language python" in stdout.getvalue() assert "|policy authority=asp-python-api trigger=pytest-plugin" in stdout.getvalue() diff --git a/tests/unit/asp_python/test_dev_command_log.py b/tests/unit/asp_python/test_dev_command_log.py index 7594cd5..a44e206 100644 --- a/tests/unit/asp_python/test_dev_command_log.py +++ b/tests/unit/asp_python/test_dev_command_log.py @@ -46,7 +46,7 @@ def test_dev_command_log_records_ordered_active_context_events( monkeypatch.delenv("AGENT_HOOK_RUN_ID", raising=False) log = start_dev_command_log( - ["search", "lexical", "metadata", str(project)], project + ["query", "--selector", "python://src/demo.py#item/function/run"], project ) log.finish(0) diff --git a/tests/unit/asp_python/test_exact_source_projection.py b/tests/unit/asp_python/test_exact_source_projection.py index 199c827..596b8f9 100644 --- a/tests/unit/asp_python/test_exact_source_projection.py +++ b/tests/unit/asp_python/test_exact_source_projection.py @@ -41,6 +41,7 @@ def invoke(selector: str, projection_kind: str) -> dict[str, object]: skeleton = invoke(root_selector, "callable-skeleton") payload = skeleton["projectionPayload"] assert isinstance(payload, dict) + assert payload["rootSelector"] == root_selector assert "schemaId" not in payload assert "schemaVersion" not in payload assert "projectionKind" not in payload diff --git a/tests/unit/asp_python/test_public_cli_identity.py b/tests/unit/asp_python/test_public_cli_identity.py index ac5109e..8f2675f 100644 --- a/tests/unit/asp_python/test_public_cli_identity.py +++ b/tests/unit/asp_python/test_public_cli_identity.py @@ -7,5 +7,5 @@ def test_public_cli_identity_is_asp_python() -> None: rendered = help_text() assert rendered.startswith("asp-python ") - assert "asp python search playbook" in rendered + assert "asp search playbook --language python" in rendered assert "asp-python search " not in rendered diff --git a/tests/unit/asp_python/test_search_playbook_boundary.py b/tests/unit/asp_python/test_search_playbook_boundary.py deleted file mode 100644 index 10ff207..0000000 --- a/tests/unit/asp_python/test_search_playbook_boundary.py +++ /dev/null @@ -1,40 +0,0 @@ -from __future__ import annotations - -import pytest - -from asp_python import python_semantic_language_registration -from asp_python._cli_args import ProtocolArgs - - -@pytest.mark.parametrize("operation", ["prime", "owner", "lexical", "ingest", "pipe"]) -def test_provider_local_search_operations_are_hard_cut(operation: str) -> None: - parsed = ProtocolArgs.parse(["search", operation, "fixture"]) - - assert parsed is not None - assert parsed.command == "error" - assert parsed.error == ( - "provider-local search was removed; use asp python search playbook " - ) - - -def test_public_playbook_is_owned_by_the_asp_client() -> None: - parsed = ProtocolArgs.parse(["search", "playbook", "native syntax"]) - - assert parsed is not None - assert parsed.command == "error" - assert parsed.error == ( - "provider-local search was removed; use asp python search playbook " - ) - - -def test_provider_registry_exposes_no_search_orchestration_method() -> None: - registration = python_semantic_language_registration() - assert [ - method for method in registration["methods"] if method.startswith("search/") - ] == [] - descriptors = [ - descriptor - for descriptor in registration["methodDescriptors"] - if descriptor["method"].startswith("search/") - ] - assert descriptors == [] diff --git a/tests/unit/asp_python/test_semantic_cli.py b/tests/unit/asp_python/test_semantic_cli.py index 3f1412b..f2707a7 100644 --- a/tests/unit/asp_python/test_semantic_cli.py +++ b/tests/unit/asp_python/test_semantic_cli.py @@ -31,7 +31,7 @@ def test_cli_agent_guide_uses_asp_owned_exact_projection(tmp_path: Path) -> None def test_search_descriptors_publish_benchmark_invocations() -> None: descriptors = python_semantic_language_registration()["methodDescriptors"] assert all( - descriptor["method"] != "search/owner-native" for descriptor in descriptors + descriptor["method"] != "search/playbook-native" for descriptor in descriptors ) exact = next( descriptor diff --git a/tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py b/tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py index bb3141c..7761519 100644 --- a/tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py +++ b/tests/unit/asp_python/test_semantic_cli_tree_sitter_registry.py @@ -52,7 +52,6 @@ def test_agent_guide_lists_tree_sitter_query_entrypoint(tmp_path: Path) -> None: assert exit_code == 0 assert ( - "|cmd catalog-json=asp python query --catalog declarations --json --workspace " - in stdout.getvalue() + "|cmd syntax-locate=asp search playbook --language python" in stdout.getvalue() ) assert "syntax predicates supported=#eq?,#any-eq?,#any-of?" in stdout.getvalue()