From 6b07ea8b6ff770442f3db9c5b112efd59f758ccf Mon Sep 17 00:00:00 2001 From: Mateusz Lewandowski Date: Fri, 21 Aug 2026 11:54:30 +0200 Subject: [PATCH] feat(skills): add comprehensive skill unit tests and fix mypy type narrowing [ticket-033] --- project/ticket-033/README.md | 26 +++ project/ticket-033/ai-antigravity-logs.txt | 0 project/ticket-033/ai-antigravity.md | 31 ++++ project/ticket-033/changelog.md | 7 + project/ticket-033/intent.json | 76 ++++++++ project/ticket-033/preprompt.md | 12 ++ src/data2dsl_contract_v0/validate.py | 3 +- src/data2dsl_skill.py | 5 +- tests/test_skill.py | 192 +++++++++++++++++++++ 9 files changed, 349 insertions(+), 3 deletions(-) create mode 100644 project/ticket-033/README.md create mode 100644 project/ticket-033/ai-antigravity-logs.txt create mode 100644 project/ticket-033/ai-antigravity.md create mode 100644 project/ticket-033/changelog.md create mode 100644 project/ticket-033/intent.json create mode 100644 project/ticket-033/preprompt.md create mode 100644 tests/test_skill.py diff --git a/project/ticket-033/README.md b/project/ticket-033/README.md new file mode 100644 index 0000000..804a17e --- /dev/null +++ b/project/ticket-033/README.md @@ -0,0 +1,26 @@ +# Ticket 033: Mypy typecheck fix and Data2DslSkill unit test expansion + +- **ID**: ticket-033 +- **Owner**: unresolved:human +- **Status**: IN_PROGRESS +- **Workflow state**: PUBLICATION +- **Created**: 2026-08-21 + +## Goal and scope + +Fix mypy type narrowing issue in `src/data2dsl_contract_v0/validate.py` +and add comprehensive unit tests for `Data2DslSkill` agent tool interface +covering all 5 source adapter raw payload types, error scenarios, and self-test. + +## Acceptance criteria + +- [x] AC-01: `src/data2dsl_contract_v0/validate.py` passes `mypy` without union-attr errors. +- [x] AC-02: `tests/test_skill.py` covers tool definitions, self-test, raw normalization across markdown, github, curllm, code2logic, code2schema, and error modes. +- [x] AC-03: `python -m pytest tests/ -q` passes with 25/25 tests green. +- [x] AC-04: `python -m ruff check src/ tests/` reports zero errors. +- [x] AC-05: The deterministic governance gate passes. + +## Participants + +- Human participant: unresolved; no user-* file was created by this script. +- Agent participant: [ai-antigravity.md](ai-antigravity.md) diff --git a/project/ticket-033/ai-antigravity-logs.txt b/project/ticket-033/ai-antigravity-logs.txt new file mode 100644 index 0000000..e69de29 diff --git a/project/ticket-033/ai-antigravity.md b/project/ticket-033/ai-antigravity.md new file mode 100644 index 0000000..f80a968 --- /dev/null +++ b/project/ticket-033/ai-antigravity.md @@ -0,0 +1,31 @@ +--- +participant-id: agent:antigravity +participant: antigravity +role: agent +ticket: ticket-033 +--- +# Participant: antigravity (AI agent) + +## Understanding + +Fix mypy static typing error in contract validator and add full unit test suite +for `Data2DslSkill`. +SESSION_EXECUTION_AUTHORIZATION recorded from user request. + +## Execution plan + +1. Fix `_utc()` in `src/data2dsl_contract_v0/validate.py`. +2. Fix `_normalize_raw` in `src/data2dsl_skill.py` for Code2Schema. +3. Add `tests/test_skill.py` covering all tool modes and adapters. +4. Verify 25/25 tests pass, mypy passes (12 files), ruff passes. +5. Verify governance gate, push branch, and dispatch validator-agent. + +## Actual changes + +- Fixed utcoffset type narrowing in `validate.py`. +- Fixed Code2Schema response handling in `data2dsl_skill.py`. +- Added 9 unit tests in `tests/test_skill.py`. + +## Blockers + +- None inside the recorded intent; proceed without a second confirmation. diff --git a/project/ticket-033/changelog.md b/project/ticket-033/changelog.md new file mode 100644 index 0000000..ea40a8d --- /dev/null +++ b/project/ticket-033/changelog.md @@ -0,0 +1,7 @@ +# Ticket Changelog (ticket-033) + +## [0.1.0] - 2026-08-21 + +- Fixed `mypy` union-attr typecheck error in `data2dsl_contract_v0/validate.py`. +- Fixed Code2Schema normalization in `data2dsl_skill.py`. +- Added comprehensive unit test suite `tests/test_skill.py` (9 tests). diff --git a/project/ticket-033/intent.json b/project/ticket-033/intent.json new file mode 100644 index 0000000..fe6ff47 --- /dev/null +++ b/project/ticket-033/intent.json @@ -0,0 +1,76 @@ +{ + "schema": "new-project.intent/v3", + "ticket": "ticket-033", + "summary": "Mypy typecheck fix and Data2DslSkill unit test expansion", + "workstream": "application", + "classification": { + "kind": "SERVICE", + "priority": "P3", + "origin": "health" + }, + "delivery": { + "acceptedBaseSha": "f8a2a59588ff9d07bab910c208730848a53945db", + "targetBranch": "main", + "outcome": "Fix mypy union-attr type narrowing in validate.py and expand Data2DslSkill unit test coverage to 25 tests.", + "nonGoals": [ + "Change product behavior or dsl contract.", + "Modify external repositories." + ], + "complexity": "XS", + "estimatedMinutes": 10, + "budgets": { + "maxImplementationFiles": 5, + "maxAffectedComponents": 1, + "maxPublicInterfaceChanges": 0, + "maxRuntimeDependencies": 0 + }, + "architecture": { + "status": "accepted", + "decision": "Use standard type narrowing and pytest unit test suites for skill validation.", + "components": [ + { + "name": "application-quality", + "paths": ["src/**", "tests/**"] + } + ], + "responsibilityChanges": false, + "interfaceChanges": [], + "dataChanges": [], + "ui": { + "impact": "none", + "states": [], + "evidence": [] + }, + "rollback": "Revert validate.py change and test_skill.py." + }, + "runtimeDependencies": [], + "validation": [ + { + "criterion": "AC-01", + "commands": ["python -m mypy src/ tests/ --explicit-package-bases --ignore-missing-imports"], + "evidence": "Mypy static type checker reports zero errors across 12 source files." + }, + { + "criterion": "AC-02", + "commands": ["python -m pytest tests/ -q"], + "evidence": "All 25 unit and integration tests pass." + }, + { + "criterion": "AC-03", + "commands": ["python -m ruff check src/ tests/"], + "evidence": "Ruff linter passes with zero errors." + } + ] + }, + "allowedPaths": [ + "project/ticket-033/**", + "src/data2dsl_contract_v0/validate.py", + "src/data2dsl_skill.py", + "tests/**" + ], + "forbiddenPaths": ["project/ticket-*/user-*.md"], + "stacks": [], + "dependsOn": [], + "conflictsWith": [], + "integrationTicket": null +} diff --git a/project/ticket-033/preprompt.md b/project/ticket-033/preprompt.md new file mode 100644 index 0000000..253a43d --- /dev/null +++ b/project/ticket-033/preprompt.md @@ -0,0 +1,12 @@ +# Ticket preprompt + +- **Task ID**: ticket-033 +- **Task title**: Mypy typecheck fix and Data2DslSkill unit test expansion +- **Created**: 2026-08-21T09:44:51Z + +Keep executable implementation outside this governance/evidence directory. +Read a human-owned user-*.md file only when one exists. +The request to execute this work creates SESSION_EXECUTION_AUTHORIZATION; +proceed within the recorded intent without a redundant confirmation prompt. +Require new authority for destructive action, secrets, external coordination, +material objective expansion and trusted merge approval. diff --git a/src/data2dsl_contract_v0/validate.py b/src/data2dsl_contract_v0/validate.py index 1614d1d..06404bf 100644 --- a/src/data2dsl_contract_v0/validate.py +++ b/src/data2dsl_contract_v0/validate.py @@ -47,7 +47,8 @@ def _schema_validator() -> Draft202012Validator: def _utc(value: str) -> datetime: parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) - if parsed.utcoffset() is None or parsed.utcoffset().total_seconds() != 0: + offset = parsed.utcoffset() + if offset is None or offset.total_seconds() != 0: raise ContractError("window timestamps must carry UTC offset") return parsed diff --git a/src/data2dsl_skill.py b/src/data2dsl_skill.py index 57af263..841505d 100644 --- a/src/data2dsl_skill.py +++ b/src/data2dsl_skill.py @@ -66,9 +66,10 @@ def _normalize_raw(source_type: str, raw: Dict[str, Any], query: Dict[str, Any], adapter = Code2SchemaAdapter() resp = raw.get("response") if not isinstance(resp, Code2SchemaMetricResponse): + entities = raw.get("entities") if raw.get("entities") is not None else raw.get("value", ()) resp = Code2SchemaMetricResponse( - status="OK" if raw.get("value") is not None else "ERROR", - value=raw.get("value"), + status="OK" if entities is not None else "ERROR", + entities=entities if isinstance(entities, (list, tuple)) else (entities,), ) return adapter.normalize(query, resp, side=side) else: diff --git a/tests/test_skill.py b/tests/test_skill.py new file mode 100644 index 0000000..8bf46fa --- /dev/null +++ b/tests/test_skill.py @@ -0,0 +1,192 @@ +"""Tests for Data2DslSkill agent tool interface.""" + +from __future__ import annotations + +import pytest +from data2dsl_adapters import ( + Code2LogicMetricResponse, + Code2SchemaMetricResponse, + CurllmMetricResponse, + CurllmPageEvidence, +) +from data2dsl_skill import Data2DslSkill + + +@pytest.fixture +def base_query(): + return { + "schema": "autogrammar.data2dsl/query/v0", + "query_id": "skill-query-001", + "subject": { + "repository": "https://github.com/autogrammar/data2dsl", + "actor": "antigravity", + }, + "metric": { + "id": "code.commit.count", + "version": "1.0.0", + "value_kind": "integer", + "unit": "commits", + }, + "window": { + "start": "2026-08-10T00:00:00Z", + "end": "2026-08-17T00:00:00Z", + "semantics": "time-window-exact", + }, + "left_source": {"id": "markdown-work-summary", "kind": "markdown-claim"}, + "right_source": {"id": "github-diagit-metrics", "kind": "github-metrics"}, + "comparison": { + "equality": "exact", + "delta_direction": "right-minus-left", + "missing_is_zero": False, + }, + } + + +def test_skill_tool_definitions(): + tools = Data2DslSkill.get_tool_definitions() + assert len(tools) == 2 + tool_names = {t["name"] for t in tools} + assert "data2dsl_compare" in tool_names + assert "data2dsl_self_test" in tool_names + + +def test_skill_self_test(): + res = Data2DslSkill.self_test() + assert res["status"] == "PASS" + assert res["skill"] == "autogrammar.data2dsl" + assert res["version"] == "0.1.0" + + +def test_skill_execute_compare_raw_markdown_and_github_match(base_query): + md_content = "# Summary\n\n- @antigravity commits: 10 in 2026-08-10..2026-08-17\n" + res = Data2DslSkill.execute_compare( + query=base_query, + left_raw={"markdown_content": md_content, "path": "work-summary.md"}, + left_source_type="markdown", + right_raw={"commit_count": 10}, + right_source_type="github", + ) + assert res["status"] == "OK" + assert res["result"]["outcome"] == "MATCH" + assert res["result"]["delta"] is None + + +def test_skill_execute_compare_raw_markdown_and_github_conflict(base_query): + md_content = "# Summary\n\n- @antigravity commits: 12 in 2026-08-10..2026-08-17\n" + res = Data2DslSkill.execute_compare( + query=base_query, + left_raw={"markdown_content": md_content, "path": "work-summary.md"}, + left_source_type="markdown", + right_raw={"commit_count": 10}, + right_source_type="github", + ) + assert res["status"] == "OK" + assert res["result"]["outcome"] == "CONFLICT" + assert res["result"]["delta"]["kind"] == "integer" + assert res["result"]["delta"]["value"] == "-2" + + +def test_skill_execute_compare_raw_curllm(base_query): + ev = CurllmPageEvidence( + url="https://github.com/autogrammar/data2dsl/pulse", + digest_sha256="abc123def456", + ) + resp = CurllmMetricResponse(status="OK", value=8, pages=(ev,)) + + res = Data2DslSkill.execute_compare( + query=base_query, + left_raw={"response": resp}, + left_source_type="curllm", + right_raw={"response": resp}, + right_source_type="curllm", + ) + assert res["status"] == "OK" + assert res["result"]["outcome"] == "MATCH" + + +def test_skill_execute_compare_raw_curllm_unevaluable(base_query): + ev = CurllmPageEvidence( + url="https://github.com/autogrammar/data2dsl/pulse", + digest_sha256="abc123def456", + ) + resp = CurllmMetricResponse(status="OK", value=8, pages=(ev,)) + err_resp = CurllmMetricResponse(status="ERROR", value=None, error_message="Page not reachable") + + res = Data2DslSkill.execute_compare( + query=base_query, + left_raw={"response": resp}, + left_source_type="curllm", + right_raw={"response": err_resp}, + right_source_type="curllm", + ) + assert res["status"] == "OK" + assert res["result"]["outcome"] == "UNEVALUABLE" + + +def test_skill_execute_compare_raw_code2logic(base_query): + resp = Code2LogicMetricResponse(status="OK", value=15) + res = Data2DslSkill.execute_compare( + query=base_query, + left_raw={"response": resp}, + left_source_type="code2logic", + right_raw={"value": 15}, + right_source_type="code2logic", + ) + assert res["status"] == "OK" + assert res["result"]["outcome"] == "MATCH" + + +def test_skill_execute_compare_raw_code2schema(base_query): + schema_query = dict(base_query) + schema_query["metric"] = { + "id": "schema.entities", + "version": "1.0.0", + "value_kind": "string-set", + "unit": "entities", + } + resp1 = Code2SchemaMetricResponse(status="OK", entities=["User", "Account"]) + resp2 = Code2SchemaMetricResponse(status="OK", entities=["User", "Account", "Order"]) + + res = Data2DslSkill.execute_compare( + query=schema_query, + left_raw={"response": resp1}, + left_source_type="code2schema", + right_raw={"response": resp2}, + right_source_type="code2schema", + ) + assert res["status"] == "OK" + assert res["result"]["outcome"] == "CONFLICT" + assert res["result"]["delta"]["kind"] == "string-set" + assert res["result"]["delta"]["added"] == ["Order"] + assert res["result"]["delta"]["removed"] == [] + + +def test_skill_execute_compare_missing_inputs(base_query): + res_no_left = Data2DslSkill.execute_compare( + query=base_query, + right_raw={"commit_count": 10}, + right_source_type="github", + ) + assert res_no_left["status"] == "ERROR" + assert res_no_left["error_code"] == "MISSING_LEFT_OBSERVATION" + + res_no_right = Data2DslSkill.execute_compare( + query=base_query, + left_raw={"commit_count": 10}, + left_source_type="github", + ) + assert res_no_right["status"] == "ERROR" + assert res_no_right["error_code"] == "MISSING_RIGHT_OBSERVATION" + + +def test_skill_execute_compare_unknown_adapter_type(base_query): + res = Data2DslSkill.execute_compare( + query=base_query, + left_raw={"val": 10}, + left_source_type="unsupported_source", + right_raw={"commit_count": 10}, + right_source_type="github", + ) + assert res["status"] == "ERROR" + assert res["error_code"] == "COMPARISON_EXCEPTION" + assert "Unknown source adapter kind" in res["message"]