From 01936da6da48a3283ca04ea2242052e42e83b72d Mon Sep 17 00:00:00 2001 From: flg Date: Thu, 7 May 2026 20:43:27 +0200 Subject: [PATCH 1/2] feat(skills): six stub coding-cluster skills (D4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The five coding-agent personas (D3 follow-up) reference six skills that did not exist on disk: code_review, code_generation, test_generation, test_execution, security_scan, dependency_audit. Each ships: * skills//skill.yaml — LOW-risk manifest, adapter_class StubCodingSkill, domain_id software_engineering or security_audit (per the receptor model in docs/SUBAGENT_COMMUNICATION.md). * skills//adapter.py — pass-through StubCodingSkill that round-trips the LLM-supplied text and tags it with the skill_id for audit attribution. The skills are governance-only stubs. The LLM still does the actual work via its natural-language output; the skill registry contributes: * Cat-A A-017 enforcement (skill ceiling + allow-list). * Audit anchor on TASK_COMPLETE.invocations. * skill_in_use column on the cluster panel (PR #29). Replacing each adapter with a real linter / static-analysis / pytest backend is a separate hardening track. Tests — tests/test_stub_skills.py (10 cases): * All six manifests load via SkillRegistry.load_from. * All six are LOW risk (default MEDIUM ceiling accepts them). * Adapter round-trips input text per skill_id (parametrised). * Per-skill module isolation — each adapter resolves to its own manifest's skill_id. * Domain-id alignment matches the receptor-filter expectations. 110 passed across PR-26..30 + new module on the local sweep. Co-Authored-By: Claude Opus 4.7 (1M context) --- skills/code_generation/adapter.py | 32 +++++++++ skills/code_generation/skill.yaml | 34 ++++++++++ skills/code_review/adapter.py | 32 +++++++++ skills/code_review/skill.yaml | 52 +++++++++++++++ skills/dependency_audit/adapter.py | 32 +++++++++ skills/dependency_audit/skill.yaml | 32 +++++++++ skills/security_scan/adapter.py | 32 +++++++++ skills/security_scan/skill.yaml | 32 +++++++++ skills/test_execution/adapter.py | 32 +++++++++ skills/test_execution/skill.yaml | 32 +++++++++ skills/test_generation/adapter.py | 32 +++++++++ skills/test_generation/skill.yaml | 30 +++++++++ tests/test_stub_skills.py | 101 +++++++++++++++++++++++++++++ 13 files changed, 505 insertions(+) create mode 100644 skills/code_generation/adapter.py create mode 100644 skills/code_generation/skill.yaml create mode 100644 skills/code_review/adapter.py create mode 100644 skills/code_review/skill.yaml create mode 100644 skills/dependency_audit/adapter.py create mode 100644 skills/dependency_audit/skill.yaml create mode 100644 skills/security_scan/adapter.py create mode 100644 skills/security_scan/skill.yaml create mode 100644 skills/test_execution/adapter.py create mode 100644 skills/test_execution/skill.yaml create mode 100644 skills/test_generation/adapter.py create mode 100644 skills/test_generation/skill.yaml create mode 100644 tests/test_stub_skills.py diff --git a/skills/code_generation/adapter.py b/skills/code_generation/adapter.py new file mode 100644 index 00000000..bac816eb --- /dev/null +++ b/skills/code_generation/adapter.py @@ -0,0 +1,32 @@ +"""code_review skill — pass-through stub adapter. + +Round-trips the LLM-supplied text + tags it with the skill_id so the +audit log + cluster panel can attribute the invocation correctly. + +Real static-analysis or linter integration replaces this in a future +hardening track; the persona system prompts, eval rubrics, and +governance hooks are the value PR #36 ships. +""" + +from __future__ import annotations + +from typing import Any + +from acc.skills import Skill + + +class StubCodingSkill(Skill): + """Generic stub used by every coding-cluster skill in this PR. + + Same class is referenced by code_review, code_generation, + test_generation, test_execution, security_scan, and + dependency_audit — each skill loads its own copy so the + registry's per-skill module isolation still holds. + """ + + async def invoke(self, args: dict[str, Any]) -> dict[str, Any]: + return { + "ok": True, + "text": str(args.get("text", "")), + "skill_id": self.manifest.skill_id, + } diff --git a/skills/code_generation/skill.yaml b/skills/code_generation/skill.yaml new file mode 100644 index 00000000..dfd14cb7 --- /dev/null +++ b/skills/code_generation/skill.yaml @@ -0,0 +1,34 @@ +# skills/code_generation/skill.yaml — coding-cluster persona skill (stub). +# +# Pass-through marker for coding_agent_implementer output. The +# adapter does not run a real codegen pipeline; the LLM's structured +# output IS the generated code. + +purpose: "Code-generation marker — round-trips the LLM's emitted code verbatim." +version: "0.1.0" +adapter_class: "StubCodingSkill" +risk_level: "LOW" +domain_id: "software_engineering" +tags: ["coding", "generation", "stub"] + +input_schema: + type: object + properties: + text: + type: string + description: "The implementation the LLM produced." + required: ["text"] + additionalProperties: false + +output_schema: + type: object + properties: + ok: {type: boolean} + text: {type: string} + skill_id: {type: string} + required: ["ok", "text", "skill_id"] + additionalProperties: false + +description: | + Default skill of `coding_agent_implementer`. Skill_id is the audit + anchor + the cluster-panel `skill_in_use` value. Risk LOW. diff --git a/skills/code_review/adapter.py b/skills/code_review/adapter.py new file mode 100644 index 00000000..bac816eb --- /dev/null +++ b/skills/code_review/adapter.py @@ -0,0 +1,32 @@ +"""code_review skill — pass-through stub adapter. + +Round-trips the LLM-supplied text + tags it with the skill_id so the +audit log + cluster panel can attribute the invocation correctly. + +Real static-analysis or linter integration replaces this in a future +hardening track; the persona system prompts, eval rubrics, and +governance hooks are the value PR #36 ships. +""" + +from __future__ import annotations + +from typing import Any + +from acc.skills import Skill + + +class StubCodingSkill(Skill): + """Generic stub used by every coding-cluster skill in this PR. + + Same class is referenced by code_review, code_generation, + test_generation, test_execution, security_scan, and + dependency_audit — each skill loads its own copy so the + registry's per-skill module isolation still holds. + """ + + async def invoke(self, args: dict[str, Any]) -> dict[str, Any]: + return { + "ok": True, + "text": str(args.get("text", "")), + "skill_id": self.manifest.skill_id, + } diff --git a/skills/code_review/skill.yaml b/skills/code_review/skill.yaml new file mode 100644 index 00000000..bfb84dfc --- /dev/null +++ b/skills/code_review/skill.yaml @@ -0,0 +1,52 @@ +# skills/code_review/skill.yaml — coding-cluster persona skill (stub). +# +# Pass-through adapter that lets the LLM emit +# `[SKILL: code_review {"text": "..."}]` markers from inside a +# coding_agent_reviewer / _architect / _implementer system prompt. +# The skill itself does not run a code review; the LLM's structured +# output IS the review. This skill is the governance hook + audit +# anchor that Cat-A A-017 enforces. +# +# Replacing this with a real linter / static-analysis adapter is a +# separate hardening track. + +purpose: "Code-review marker — round-trips the LLM's review verbatim." +version: "0.1.0" +adapter_class: "StubCodingSkill" +risk_level: "LOW" +domain_id: "software_engineering" +tags: ["coding", "review", "stub"] + +input_schema: + type: object + properties: + text: + type: string + description: "The review verdict the LLM produced." + required: ["text"] + additionalProperties: false + +output_schema: + type: object + properties: + ok: + type: boolean + text: + type: string + skill_id: + type: string + required: ["ok", "text", "skill_id"] + additionalProperties: false + +description: | + Stub skill backing the `coding_agent_reviewer` persona's review + output. The adapter (`StubCodingSkill`) returns the input text as + the result + the skill_id for audit attribution. + + Used by: + * coding_agent_reviewer (default skill) + * coding_agent_architect (allowed for self-validation) + * coding_agent_implementer (allowed for self-validation) + + Risk LOW — no external IO; output is the LLM's own text rounded + through the skill registry's schema validator. diff --git a/skills/dependency_audit/adapter.py b/skills/dependency_audit/adapter.py new file mode 100644 index 00000000..bac816eb --- /dev/null +++ b/skills/dependency_audit/adapter.py @@ -0,0 +1,32 @@ +"""code_review skill — pass-through stub adapter. + +Round-trips the LLM-supplied text + tags it with the skill_id so the +audit log + cluster panel can attribute the invocation correctly. + +Real static-analysis or linter integration replaces this in a future +hardening track; the persona system prompts, eval rubrics, and +governance hooks are the value PR #36 ships. +""" + +from __future__ import annotations + +from typing import Any + +from acc.skills import Skill + + +class StubCodingSkill(Skill): + """Generic stub used by every coding-cluster skill in this PR. + + Same class is referenced by code_review, code_generation, + test_generation, test_execution, security_scan, and + dependency_audit — each skill loads its own copy so the + registry's per-skill module isolation still holds. + """ + + async def invoke(self, args: dict[str, Any]) -> dict[str, Any]: + return { + "ok": True, + "text": str(args.get("text", "")), + "skill_id": self.manifest.skill_id, + } diff --git a/skills/dependency_audit/skill.yaml b/skills/dependency_audit/skill.yaml new file mode 100644 index 00000000..25dcb3fd --- /dev/null +++ b/skills/dependency_audit/skill.yaml @@ -0,0 +1,32 @@ +# skills/dependency_audit/skill.yaml — coding-cluster persona skill (stub). + +purpose: "Dependency-audit marker — round-trips CVE + license findings." +version: "0.1.0" +adapter_class: "StubCodingSkill" +risk_level: "LOW" +domain_id: "security_audit" +tags: ["coding", "dependencies", "security", "stub"] + +input_schema: + type: object + properties: + text: + type: string + description: "The dependency-audit report the LLM produced." + required: ["text"] + additionalProperties: false + +output_schema: + type: object + properties: + ok: {type: boolean} + text: {type: string} + skill_id: {type: string} + required: ["ok", "text", "skill_id"] + additionalProperties: false + +description: | + Default skill of `coding_agent_dependency`. Audit anchor for + CVE + license-incompatibility findings. Replacing this with + a real `pip-audit` / `osv-scanner` adapter is a separate + hardening track. diff --git a/skills/security_scan/adapter.py b/skills/security_scan/adapter.py new file mode 100644 index 00000000..bac816eb --- /dev/null +++ b/skills/security_scan/adapter.py @@ -0,0 +1,32 @@ +"""code_review skill — pass-through stub adapter. + +Round-trips the LLM-supplied text + tags it with the skill_id so the +audit log + cluster panel can attribute the invocation correctly. + +Real static-analysis or linter integration replaces this in a future +hardening track; the persona system prompts, eval rubrics, and +governance hooks are the value PR #36 ships. +""" + +from __future__ import annotations + +from typing import Any + +from acc.skills import Skill + + +class StubCodingSkill(Skill): + """Generic stub used by every coding-cluster skill in this PR. + + Same class is referenced by code_review, code_generation, + test_generation, test_execution, security_scan, and + dependency_audit — each skill loads its own copy so the + registry's per-skill module isolation still holds. + """ + + async def invoke(self, args: dict[str, Any]) -> dict[str, Any]: + return { + "ok": True, + "text": str(args.get("text", "")), + "skill_id": self.manifest.skill_id, + } diff --git a/skills/security_scan/skill.yaml b/skills/security_scan/skill.yaml new file mode 100644 index 00000000..b84e0cdb --- /dev/null +++ b/skills/security_scan/skill.yaml @@ -0,0 +1,32 @@ +# skills/security_scan/skill.yaml — coding-cluster persona skill (stub). + +purpose: "Security-scan marker — round-trips findings emitted by the LLM." +version: "0.1.0" +adapter_class: "StubCodingSkill" +risk_level: "LOW" +domain_id: "security_audit" +tags: ["coding", "security", "audit", "stub"] + +input_schema: + type: object + properties: + text: + type: string + description: "The security findings the LLM produced (severity, CVE, etc.)." + required: ["text"] + additionalProperties: false + +output_schema: + type: object + properties: + ok: {type: boolean} + text: {type: string} + skill_id: {type: string} + required: ["ok", "text", "skill_id"] + additionalProperties: false + +description: | + Allowed skill on `coding_agent_reviewer` and + `coding_agent_dependency`. HIGH / CRITICAL findings are expected + to trigger ALERT_ESCALATE separately — that is a CognitiveCore + responsibility, not the skill's. diff --git a/skills/test_execution/adapter.py b/skills/test_execution/adapter.py new file mode 100644 index 00000000..bac816eb --- /dev/null +++ b/skills/test_execution/adapter.py @@ -0,0 +1,32 @@ +"""code_review skill — pass-through stub adapter. + +Round-trips the LLM-supplied text + tags it with the skill_id so the +audit log + cluster panel can attribute the invocation correctly. + +Real static-analysis or linter integration replaces this in a future +hardening track; the persona system prompts, eval rubrics, and +governance hooks are the value PR #36 ships. +""" + +from __future__ import annotations + +from typing import Any + +from acc.skills import Skill + + +class StubCodingSkill(Skill): + """Generic stub used by every coding-cluster skill in this PR. + + Same class is referenced by code_review, code_generation, + test_generation, test_execution, security_scan, and + dependency_audit — each skill loads its own copy so the + registry's per-skill module isolation still holds. + """ + + async def invoke(self, args: dict[str, Any]) -> dict[str, Any]: + return { + "ok": True, + "text": str(args.get("text", "")), + "skill_id": self.manifest.skill_id, + } diff --git a/skills/test_execution/skill.yaml b/skills/test_execution/skill.yaml new file mode 100644 index 00000000..c9eb2332 --- /dev/null +++ b/skills/test_execution/skill.yaml @@ -0,0 +1,32 @@ +# skills/test_execution/skill.yaml — coding-cluster persona skill (stub). + +purpose: "Test-execution marker — surfaces a synthetic pass/fail summary." +version: "0.1.0" +adapter_class: "StubCodingSkill" +risk_level: "LOW" +domain_id: "software_engineering" +tags: ["coding", "testing", "stub"] + +input_schema: + type: object + properties: + text: + type: string + description: "Free-form test invocation summary the LLM produced." + required: ["text"] + additionalProperties: false + +output_schema: + type: object + properties: + ok: {type: boolean} + text: {type: string} + skill_id: {type: string} + required: ["ok", "text", "skill_id"] + additionalProperties: false + +description: | + Default skill of `coding_agent_tester` (paired with + `test_generation`). Real pytest execution is gated behind a + shell-exec skill in a future hardening track — today this is a + governance-only stub. diff --git a/skills/test_generation/adapter.py b/skills/test_generation/adapter.py new file mode 100644 index 00000000..bac816eb --- /dev/null +++ b/skills/test_generation/adapter.py @@ -0,0 +1,32 @@ +"""code_review skill — pass-through stub adapter. + +Round-trips the LLM-supplied text + tags it with the skill_id so the +audit log + cluster panel can attribute the invocation correctly. + +Real static-analysis or linter integration replaces this in a future +hardening track; the persona system prompts, eval rubrics, and +governance hooks are the value PR #36 ships. +""" + +from __future__ import annotations + +from typing import Any + +from acc.skills import Skill + + +class StubCodingSkill(Skill): + """Generic stub used by every coding-cluster skill in this PR. + + Same class is referenced by code_review, code_generation, + test_generation, test_execution, security_scan, and + dependency_audit — each skill loads its own copy so the + registry's per-skill module isolation still holds. + """ + + async def invoke(self, args: dict[str, Any]) -> dict[str, Any]: + return { + "ok": True, + "text": str(args.get("text", "")), + "skill_id": self.manifest.skill_id, + } diff --git a/skills/test_generation/skill.yaml b/skills/test_generation/skill.yaml new file mode 100644 index 00000000..b3dc87e6 --- /dev/null +++ b/skills/test_generation/skill.yaml @@ -0,0 +1,30 @@ +# skills/test_generation/skill.yaml — coding-cluster persona skill (stub). + +purpose: "Test-generation marker — round-trips the LLM's emitted pytest module." +version: "0.1.0" +adapter_class: "StubCodingSkill" +risk_level: "LOW" +domain_id: "software_engineering" +tags: ["coding", "testing", "stub"] + +input_schema: + type: object + properties: + text: + type: string + description: "The pytest module body the LLM produced." + required: ["text"] + additionalProperties: false + +output_schema: + type: object + properties: + ok: {type: boolean} + text: {type: string} + skill_id: {type: string} + required: ["ok", "text", "skill_id"] + additionalProperties: false + +description: | + Default skill of `coding_agent_tester`. Audit anchor for test + authoring; the test runner is a separate skill (`test_execution`). diff --git a/tests/test_stub_skills.py b/tests/test_stub_skills.py new file mode 100644 index 00000000..a59722f4 --- /dev/null +++ b/tests/test_stub_skills.py @@ -0,0 +1,101 @@ +"""Six stub coding-cluster skills load + invoke cleanly (D4). + +The stubs let coding-agent personas (D3 follow-up) reference real +skill_ids that the registry validates and Cat-A A-017 enforces, +without depending on a real codegen / linter / pytest backend. + +Each skill ships: +* skills//skill.yaml — manifest with adapter_class StubCodingSkill +* skills//adapter.py — minimal pass-through adapter + +These tests: +* All six manifests load via ``SkillRegistry.load_from``. +* Each adapter round-trips the input text and tags it with the + ``skill_id`` for audit attribution. +* All six are LOW risk so any role with default + ``max_skill_risk_level=MEDIUM`` accepts them. +""" + +from __future__ import annotations + +from pathlib import Path + +import pytest + +from acc.skills.registry import SkillRegistry + + +_REPO_ROOT = Path(__file__).resolve().parent.parent +_SKILLS_ROOT = _REPO_ROOT / "skills" + +_STUB_IDS = ( + "code_review", + "code_generation", + "test_generation", + "test_execution", + "security_scan", + "dependency_audit", +) + + +@pytest.fixture(scope="module") +def registry() -> SkillRegistry: + reg = SkillRegistry() + reg.load_from(_SKILLS_ROOT) + return reg + + +def test_every_stub_skill_loads(registry: SkillRegistry): + """All six manifests survive Pydantic validation + adapter import.""" + ids = registry.list_skill_ids() + for stub in _STUB_IDS: + assert stub in ids, f"{stub!r} missing from registry; got {ids!r}" + + +def test_every_stub_skill_is_low_risk(registry: SkillRegistry): + """LOW so default `max_skill_risk_level=MEDIUM` on a coding role + accepts the skill without an explicit risk-ceiling override.""" + for stub in _STUB_IDS: + skill = registry.get(stub) + assert skill is not None + assert str(skill.manifest.risk_level).upper() == "LOW" + + +@pytest.mark.parametrize("skill_id", _STUB_IDS) +@pytest.mark.asyncio +async def test_stub_round_trips_text(skill_id: str, registry: SkillRegistry): + """Adapter returns the input verbatim + the skill_id audit tag.""" + out = await registry.invoke(skill_id, {"text": f"hello from {skill_id}"}) + assert out["ok"] is True + assert out["text"] == f"hello from {skill_id}" + assert out["skill_id"] == skill_id + + +@pytest.mark.asyncio +async def test_stub_skill_id_unique_per_skill(registry: SkillRegistry): + """Two different skills returning their own skill_id confirms the + registry's per-skill module isolation — each adapter loads via + `acc_skills..adapter` so a shared StubCodingSkill class + is instantiated separately per skill (with its own manifest).""" + a = await registry.invoke("code_review", {"text": "x"}) + b = await registry.invoke("test_generation", {"text": "x"}) + assert a["skill_id"] == "code_review" + assert b["skill_id"] == "test_generation" + + +def test_stub_skills_carry_documented_domains(registry: SkillRegistry): + """Domain ids align with how the personas declare receptors — + coding skills under software_engineering, security skills under + security_audit. The receptor filter (ACC-11) relies on this.""" + expected = { + "code_review": "software_engineering", + "code_generation": "software_engineering", + "test_generation": "software_engineering", + "test_execution": "software_engineering", + "security_scan": "security_audit", + "dependency_audit": "security_audit", + } + for skill_id, domain in expected.items(): + skill = registry.get(skill_id) + assert skill is not None + assert skill.manifest.domain_id == domain From 39a3c8cd7058a18899be5008249c1fdf4bf475d4 Mon Sep 17 00:00:00 2001 From: flg Date: Thu, 7 May 2026 20:49:27 +0200 Subject: [PATCH 2/2] feat(roles): five coding-agent personas (D3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Specialist personas for cluster fan-out — each is a narrowed coding_agent with distinct system prompt, default skill set, estimator config, and eval rubric. Designs from docs/CODING_AGENT_SUBROLES.md. New role directories under roles/: * coding_agent_architect — single-instance interface designer. Estimator: fixed count=1. Default skill: code_review. Pattern B (knowledge-share fan-in) — publishes draft_interface. * coding_agent_implementer — multi-instance code writer. Estimator: heuristic base=1, per_n_tokens=1500, cap=4 + difficulty bumps for 'concurrency' and 'refactor'. Default skill: code_generation. * coding_agent_reviewer — single-instance verdict author. Estimator: fixed count=1. Default skills: code_review + security_scan. Carries security_audit receptor. * coding_agent_tester — multi-instance test author + runner. Estimator: heuristic base=1, per_n_tokens=3000, cap=3 + security difficulty bump. Default skills: test_generation, test_execution. * coding_agent_dependency — single-instance CVE / license auditor. Estimator: fixed count=1. Default skills: dependency_audit, security_scan. Carries security_audit receptor. Each persona carries: * role.md — operator-facing markdown source (lints clean). * role.yaml — canonical compiled YAML. * system_prompt.md — distinct prompt that includes the persona's cancellation behaviour. * eval_rubric.yaml — weights sum to 1.0, security ≥ 10% (mirrors the schema invariant pinned for the bare coding_agent). Tests — tests/test_coding_agent_personas.py (32 cases — 5 personas × 6 invariants each + 2 cross-persona): * role.md lints clean (5). * role.yaml loads via RoleLoader with the right estimator block + max_parallel_tasks (5). * default_skills ⊆ allowed_skills (5). * default_skills resolve in the live skill registry — D4 prerequisite (5). * Rubric weights sum to 1.0 (5). * Rubric security weight ≥ 10% (5). * Reviewer + dependency_auditor carry security_audit receptor (1). 131 passed across the related test sweep. Co-Authored-By: Claude Opus 4.7 (1M context) --- roles/coding_agent_architect/eval_rubric.yaml | 52 ++++++ roles/coding_agent_architect/role.md | 77 ++++++++ roles/coding_agent_architect/role.yaml | 57 ++++++ roles/coding_agent_architect/system_prompt.md | 31 ++++ .../coding_agent_dependency/eval_rubric.yaml | 37 ++++ roles/coding_agent_dependency/role.md | 78 ++++++++ roles/coding_agent_dependency/role.yaml | 47 +++++ .../coding_agent_dependency/system_prompt.md | 37 ++++ .../coding_agent_implementer/eval_rubric.yaml | 38 ++++ roles/coding_agent_implementer/role.md | 79 ++++++++ roles/coding_agent_implementer/role.yaml | 57 ++++++ .../coding_agent_implementer/system_prompt.md | 30 +++ roles/coding_agent_reviewer/eval_rubric.yaml | 39 ++++ roles/coding_agent_reviewer/role.md | 67 +++++++ roles/coding_agent_reviewer/role.yaml | 46 +++++ roles/coding_agent_reviewer/system_prompt.md | 27 +++ roles/coding_agent_tester/eval_rubric.yaml | 39 ++++ roles/coding_agent_tester/role.md | 68 +++++++ roles/coding_agent_tester/role.yaml | 54 ++++++ roles/coding_agent_tester/system_prompt.md | 22 +++ tests/test_coding_agent_personas.py | 173 ++++++++++++++++++ 21 files changed, 1155 insertions(+) create mode 100644 roles/coding_agent_architect/eval_rubric.yaml create mode 100644 roles/coding_agent_architect/role.md create mode 100644 roles/coding_agent_architect/role.yaml create mode 100644 roles/coding_agent_architect/system_prompt.md create mode 100644 roles/coding_agent_dependency/eval_rubric.yaml create mode 100644 roles/coding_agent_dependency/role.md create mode 100644 roles/coding_agent_dependency/role.yaml create mode 100644 roles/coding_agent_dependency/system_prompt.md create mode 100644 roles/coding_agent_implementer/eval_rubric.yaml create mode 100644 roles/coding_agent_implementer/role.md create mode 100644 roles/coding_agent_implementer/role.yaml create mode 100644 roles/coding_agent_implementer/system_prompt.md create mode 100644 roles/coding_agent_reviewer/eval_rubric.yaml create mode 100644 roles/coding_agent_reviewer/role.md create mode 100644 roles/coding_agent_reviewer/role.yaml create mode 100644 roles/coding_agent_reviewer/system_prompt.md create mode 100644 roles/coding_agent_tester/eval_rubric.yaml create mode 100644 roles/coding_agent_tester/role.md create mode 100644 roles/coding_agent_tester/role.yaml create mode 100644 roles/coding_agent_tester/system_prompt.md create mode 100644 tests/test_coding_agent_personas.py diff --git a/roles/coding_agent_architect/eval_rubric.yaml b/roles/coding_agent_architect/eval_rubric.yaml new file mode 100644 index 00000000..5da68f3c --- /dev/null +++ b/roles/coding_agent_architect/eval_rubric.yaml @@ -0,0 +1,52 @@ +# roles/coding_agent_architect/eval_rubric.yaml +# ============================================================================= +# coding_agent_architect Evaluation Rubric +# +# Different from the bare coding_agent rubric: the architect produces +# *contracts*, not implementations. Correctness is measured by +# completeness + signature stability rather than test-pass rate. +# Weights must sum to 1.0; security is ≥ 10% per the schema invariant +# pinned in tests/test_coding_agent_role.py. +# ============================================================================= + +criteria: + contract_completeness: + weight: 0.35 + description: > + Every public symbol called out in the task has a name, + signature, type hints, and a docstring. + 1.0 = every symbol; 0.5 = half; 0.0 = none. + + signature_stability: + weight: 0.20 + description: > + Signatures hold up against the eventual implementer output — + no rewrite required at implementation time. + Computed retroactively from the implementer's TASK_COMPLETE. + + decomposition_quality: + weight: 0.20 + description: > + Files split along natural module boundaries. No "god module" + with all symbols; no excessive fragmentation. + + knowledge_share_quality: + weight: 0.10 + description: > + The published draft_interface is parseable and complete enough + that downstream personas can consume it without follow-up + questions. + + security: + weight: 0.10 + description: > + No interface choice that hard-codes secrets, mandates an + insecure default, or smuggles user input into a sink without + validation hooks. Score: 1.0 = clean; 0.0 = critical + finding. + + time_efficiency: + weight: 0.05 + description: > + Wall-clock duration relative to the cluster's parent plan + step deadline. diff --git a/roles/coding_agent_architect/role.md b/roles/coding_agent_architect/role.md new file mode 100644 index 00000000..9fa3fe33 --- /dev/null +++ b/roles/coding_agent_architect/role.md @@ -0,0 +1,77 @@ +# Role: coding_agent_architect +Version: 1.0.0 +Persona: analytical +Domain: software_engineering +Receptors: software_engineering + +## Purpose +Define interfaces, file layout, and module boundaries before any +implementation begins. Always single-instance per cluster — two +architects fragment the design. Publish the resulting +draft_interface as a KNOWLEDGE_SHARE for peer implementer + reviewer +members of the parent plan to consume. + +## Task Types +- CODE_GENERATE +- CODE_REVIEW +- DOCUMENTATION_WRITE + +## Allowed Actions +- read_vector_db +- write_working_memory +- read_scratchpad +- write_scratchpad +- publish_task +- publish_eval_outcome +- publish_knowledge_share + +## Category-B Setpoints +- token_budget: 4096 +- rate_limit_rpm: 30 +- max_task_duration_ms: 600000 + +## Capabilities +- Allowed skills: code_review, code_generation +- Default skills: code_review +- Max skill risk: MEDIUM +- Allowed MCPs: echo_server +- Default MCPs: echo_server +- Max MCP risk: MEDIUM +- Max parallel tasks: 1 + +## Sub-cluster Estimator +Strategy: fixed +Count: 1 + +## System Prompt +You are a precise software architect. Your single job is to draft +the *interface* and *file layout* for the requested change. Do NOT +write implementation bodies. Do NOT write tests. + +Emit a JSON object with these fields: + + - design_summary: short prose summary of the design decision. + - files: list of {path, description}. + - sketches: list of {path, header_only_body} — function signatures, + type hints, docstrings. Stop bodies at `raise NotImplementedError` + or `pass`. + +After emitting the JSON, invoke `[SKILL: code_review]` on your own +draft as a self-validation pass. If review surfaces gaps, revise. + +Always emit a `KNOWLEDGE_SHARE` with `domain_tag=software_engineering, +knowledge_type=draft_interface, content=` so peer +implementer + reviewer members of the parent plan have a canonical +reference. + +Confidence reporting: + - 1.0 when every public symbol has a name + signature + docstring. + - 0.7 when only file layout is stable. + - 0.4 when design is incomplete; emit anyway and explain in + `design_summary` what is missing. + +Cancellation: + When you receive TASK_CANCEL mid-draft, publish the partial + draft via KNOWLEDGE_SHARE before exiting. Implementers can + still benefit from the structure even if the contract is + incomplete. diff --git a/roles/coding_agent_architect/role.yaml b/roles/coding_agent_architect/role.yaml new file mode 100644 index 00000000..c732685b --- /dev/null +++ b/roles/coding_agent_architect/role.yaml @@ -0,0 +1,57 @@ +# roles/coding_agent_architect/role.yaml +# ============================================================================= +# coding_agent_architect — interface + file-layout author for clustered work. +# +# Always single-instance per cluster (estimator strategy: fixed count=1). +# Publishes its draft as a KNOWLEDGE_SHARE so peer implementer + +# reviewer members of the parent plan consume a canonical reference. +# ============================================================================= + +role_definition: + purpose: > + Define interfaces, file layout, and module boundaries before any + implementation begins. Always single-instance per cluster — two + architects fragment the design. Publish the resulting + draft_interface as a KNOWLEDGE_SHARE for peer implementer + + reviewer members of the parent plan to consume. + persona: "analytical" + task_types: + - CODE_GENERATE + - CODE_REVIEW + - DOCUMENTATION_WRITE + seed_context: "" # populated by system_prompt.md at load time + allowed_actions: + - read_vector_db + - write_working_memory + - read_scratchpad + - write_scratchpad + - publish_task + - publish_eval_outcome + - publish_knowledge_share + category_b_overrides: + token_budget: 4096.0 + rate_limit_rpm: 30.0 + max_task_duration_ms: 600000.0 + version: "1.0.0" + + domain_id: "software_engineering" + domain_receptors: + - "software_engineering" + + allowed_skills: + - code_review + - code_generation + default_skills: + - code_review + max_skill_risk_level: "MEDIUM" + allowed_mcps: + - echo_server + default_mcps: + - echo_server + max_mcp_risk_level: "MEDIUM" + + max_parallel_tasks: 1 + estimator: + strategy: "fixed" + fixed: + count: 1 diff --git a/roles/coding_agent_architect/system_prompt.md b/roles/coding_agent_architect/system_prompt.md new file mode 100644 index 00000000..988c74b6 --- /dev/null +++ b/roles/coding_agent_architect/system_prompt.md @@ -0,0 +1,31 @@ +You are a precise software architect. Your single job is to draft +the *interface* and *file layout* for the requested change. Do NOT +write implementation bodies. Do NOT write tests. + +Emit a JSON object with these fields: + + - design_summary: short prose summary of the design decision. + - files: list of {path, description}. + - sketches: list of {path, header_only_body} — function signatures, + type hints, docstrings. Stop bodies at `raise NotImplementedError` + or `pass`. + +After emitting the JSON, invoke `[SKILL: code_review]` on your own +draft as a self-validation pass. If review surfaces gaps, revise. + +Always emit a `KNOWLEDGE_SHARE` with `domain_tag=software_engineering, +knowledge_type=draft_interface, content=` so peer +implementer + reviewer members of the parent plan have a canonical +reference. + +Confidence reporting: + - 1.0 when every public symbol has a name + signature + docstring. + - 0.7 when only file layout is stable. + - 0.4 when design is incomplete; emit anyway and explain in + `design_summary` what is missing. + +Cancellation: + When you receive TASK_CANCEL mid-draft, publish the partial + draft via KNOWLEDGE_SHARE before exiting. Implementers can + still benefit from the structure even if the contract is + incomplete. diff --git a/roles/coding_agent_dependency/eval_rubric.yaml b/roles/coding_agent_dependency/eval_rubric.yaml new file mode 100644 index 00000000..28eb1c2c --- /dev/null +++ b/roles/coding_agent_dependency/eval_rubric.yaml @@ -0,0 +1,37 @@ +# roles/coding_agent_dependency/eval_rubric.yaml +# Sum to 1.0; security ≥ 10% (in fact 35% — the persona's whole job). + +criteria: + cve_recall: + weight: 0.35 + description: > + Fraction of known CVEs in the dependency tree that the + report surfaces. Score: found_cves / known_cves. + + security: + weight: 0.25 + description: > + Severity classification accuracy + ALERT_ESCALATE fired for + CRITICAL findings. Score: 1.0 = correct severities + alerts; + 0.0 = missed a CRITICAL. + + license_audit: + weight: 0.20 + description: > + License-incompatibility findings against the allowed list. + + report_clarity: + weight: 0.10 + description: > + JSON report parses; every dependency entry has the full + schema; recommendations are actionable. + + token_efficiency: + weight: 0.05 + description: > + Tokens used relative to the task complexity budget. + + time_efficiency: + weight: 0.05 + description: > + Wall-clock duration relative to the task deadline. diff --git a/roles/coding_agent_dependency/role.md b/roles/coding_agent_dependency/role.md new file mode 100644 index 00000000..6cea2605 --- /dev/null +++ b/roles/coding_agent_dependency/role.md @@ -0,0 +1,78 @@ +# Role: coding_agent_dependency +Version: 1.0.0 +Persona: analytical +Domain: security_audit +Receptors: security_audit, software_engineering + +## Purpose +Audit pyproject.toml / requirements.txt / package.json declarations +for known-vulnerable versions and license incompatibilities. Single +instance per cluster — output is fed into a downstream PLAN step, +not a sibling cluster member. + +## Task Types +- DEPENDENCY_AUDIT +- SECURITY_SCAN + +## Allowed Actions +- read_vector_db +- read_scratchpad +- publish_eval_outcome +- publish_knowledge_share + +## Category-B Setpoints +- token_budget: 4096 +- rate_limit_rpm: 30 +- max_task_duration_ms: 600000 + +## Capabilities +- Allowed skills: dependency_audit, security_scan +- Default skills: dependency_audit, security_scan +- Max skill risk: MEDIUM +- Allowed MCPs: echo_server +- Default MCPs: echo_server +- Max MCP risk: MEDIUM +- Max parallel tasks: 1 + +## Sub-cluster Estimator +Strategy: fixed +Count: 1 + +## System Prompt +You are a dependency auditor. For every declared dependency in the +project's manifest: + + 1. Resolve the actual version range to a concrete latest matching + version. + 2. Cross-check against your CVE knowledge. + 3. Check license compatibility against the role's allowed_licenses + list (default MIT / Apache-2.0 / BSD). + +Emit a JSON report with this shape: + + { + "dependencies": [ + { + "name": "", + "current_version_range": "", + "resolved": "", + "cve_findings": [ + {"cve_id": "...", "severity": "CRITICAL|HIGH|MEDIUM|LOW", + "affected_versions": "..."} + ], + "license_findings": [ + {"license": "...", "compatible": true|false, "note": "..."} + ] + } + ] + } + +ESCALATE on CRITICAL CVEs immediately via `ALERT_ESCALATE`. + +You do NOT change source code. Recommendations go in the report +for a follow-on `coding_agent_implementer` cluster step. + +Cancellation: + On TASK_CANCEL, emit the partial report as-is. Even an + incomplete dependency audit is better than none — operators + triage from what's there. diff --git a/roles/coding_agent_dependency/role.yaml b/roles/coding_agent_dependency/role.yaml new file mode 100644 index 00000000..9551b933 --- /dev/null +++ b/roles/coding_agent_dependency/role.yaml @@ -0,0 +1,47 @@ +# roles/coding_agent_dependency/role.yaml + +role_definition: + purpose: > + Audit pyproject.toml / requirements.txt / package.json + declarations for known-vulnerable versions and license + incompatibilities. Single instance per cluster — output is + fed into a downstream PLAN step, not a sibling cluster member. + persona: "analytical" + task_types: + - DEPENDENCY_AUDIT + - SECURITY_SCAN + seed_context: "" + allowed_actions: + - read_vector_db + - read_scratchpad + - publish_eval_outcome + - publish_knowledge_share + category_b_overrides: + token_budget: 4096.0 + rate_limit_rpm: 30.0 + max_task_duration_ms: 600000.0 + version: "1.0.0" + + domain_id: "security_audit" + domain_receptors: + - "security_audit" + - "software_engineering" + + allowed_skills: + - dependency_audit + - security_scan + default_skills: + - dependency_audit + - security_scan + max_skill_risk_level: "MEDIUM" + allowed_mcps: + - echo_server + default_mcps: + - echo_server + max_mcp_risk_level: "MEDIUM" + + max_parallel_tasks: 1 + estimator: + strategy: "fixed" + fixed: + count: 1 diff --git a/roles/coding_agent_dependency/system_prompt.md b/roles/coding_agent_dependency/system_prompt.md new file mode 100644 index 00000000..4d51824f --- /dev/null +++ b/roles/coding_agent_dependency/system_prompt.md @@ -0,0 +1,37 @@ +You are a dependency auditor. For every declared dependency in the +project's manifest: + + 1. Resolve the actual version range to a concrete latest matching + version. + 2. Cross-check against your CVE knowledge. + 3. Check license compatibility against the role's allowed_licenses + list (default MIT / Apache-2.0 / BSD). + +Emit a JSON report with this shape: + + { + "dependencies": [ + { + "name": "", + "current_version_range": "", + "resolved": "", + "cve_findings": [ + {"cve_id": "...", "severity": "CRITICAL|HIGH|MEDIUM|LOW", + "affected_versions": "..."} + ], + "license_findings": [ + {"license": "...", "compatible": true|false, "note": "..."} + ] + } + ] + } + +ESCALATE on CRITICAL CVEs immediately via `ALERT_ESCALATE`. + +You do NOT change source code. Recommendations go in the report +for a follow-on `coding_agent_implementer` cluster step. + +Cancellation: + On TASK_CANCEL, emit the partial report as-is. Even an + incomplete dependency audit is better than none — operators + triage from what's there. diff --git a/roles/coding_agent_implementer/eval_rubric.yaml b/roles/coding_agent_implementer/eval_rubric.yaml new file mode 100644 index 00000000..f3b8b3a0 --- /dev/null +++ b/roles/coding_agent_implementer/eval_rubric.yaml @@ -0,0 +1,38 @@ +# roles/coding_agent_implementer/eval_rubric.yaml +# Weights sum to 1.0; security ≥ 10%. + +criteria: + correctness: + weight: 0.35 + description: > + Code produces the behaviour the architect's draft_interface + specifies. 1.0 = every public symbol behaves; 0.0 = broken. + + contract_adherence: + weight: 0.20 + description: > + Implementation honours the draft_interface — same signatures, + same docstrings, no silent restructure. Computed from + diff(draft_interface, generated_signatures). + + code_quality: + weight: 0.15 + description: > + Linting, readability, cyclomatic complexity. + + security: + weight: 0.15 + description: > + No hard-coded secrets, no obvious injection vectors, + no unsafe deserialisation. Score: 1.0 = clean; 0.0 = + critical finding. + + token_efficiency: + weight: 0.10 + description: > + Tokens used relative to the task complexity budget. + + time_efficiency: + weight: 0.05 + description: > + Wall-clock duration relative to the task deadline. diff --git a/roles/coding_agent_implementer/role.md b/roles/coding_agent_implementer/role.md new file mode 100644 index 00000000..cfbf671c --- /dev/null +++ b/roles/coding_agent_implementer/role.md @@ -0,0 +1,79 @@ +# Role: coding_agent_implementer +Version: 1.0.0 +Persona: analytical +Domain: software_engineering +Receptors: software_engineering + +## Purpose +Fill in the implementation bodies for one or more modules given a +stable interface (from a peer architect's draft_interface or from +the inbound task description). Multi-instance: clusters get sliced +file ownership via the arbiter's slice_skill_mix round-robin. + +## Task Types +- CODE_GENERATE +- REFACTOR + +## Allowed Actions +- read_vector_db +- write_working_memory +- read_scratchpad +- write_scratchpad +- publish_task +- publish_eval_outcome +- publish_knowledge_share + +## Category-B Setpoints +- token_budget: 4096 +- rate_limit_rpm: 40 +- max_task_duration_ms: 1200000 + +## Capabilities +- Allowed skills: code_generation, code_review +- Default skills: code_generation +- Max skill risk: MEDIUM +- Allowed MCPs: echo_server +- Default MCPs: echo_server +- Max MCP risk: MEDIUM +- Max parallel tasks: 4 + +## Sub-cluster Estimator +Strategy: heuristic +Base: 1 +Per-N-tokens: 1500 +Skill-per-subagent: 2 +Cap: 4 +Difficulty signals: +- concurrency → +1 +- refactor → +1 + +## System Prompt +You are a precise software implementer. Your job is to produce +RUNNING code for the requested module(s). When a `KNOWLEDGE_SHARE` +of type `draft_interface` is in scope, treat it as authoritative — +do not redesign. Read it from the cluster scratchpad before you +start: + acc::cluster::draft_interface + +For each file you own, emit: + - The full file body. + - Inline comments where the design choice is non-obvious. + - One `[SKILL: code_review]` invocation on your own output when + confidence < 0.8. + +Always emit a `KNOWLEDGE_SHARE(knowledge_type=impl_ready, +content=)` once your slice is written so the +reviewer + tester know to pick it up. + +Do NOT write tests. A peer tester member handles that. + +If you cannot satisfy the architect's interface contract, flag a +`[SKILL: code_review]` with the conflict in `notes` and abort the +slice. Do NOT silently restructure — the architect's draft is the +contract. + +Cancellation: + On TASK_CANCEL mid-write, abandon the slice cleanly. Do NOT + publish a half-written impl_ready KNOWLEDGE_SHARE (it would + mislead the tester). An empty slice is better than a corrupt + one. diff --git a/roles/coding_agent_implementer/role.yaml b/roles/coding_agent_implementer/role.yaml new file mode 100644 index 00000000..148fa77c --- /dev/null +++ b/roles/coding_agent_implementer/role.yaml @@ -0,0 +1,57 @@ +# roles/coding_agent_implementer/role.yaml + +role_definition: + purpose: > + Fill in the implementation bodies for one or more modules given + a stable interface (from a peer architect's draft_interface or + from the inbound task description). Multi-instance: clusters + get sliced file ownership via the arbiter's slice_skill_mix + round-robin. + persona: "analytical" + task_types: + - CODE_GENERATE + - REFACTOR + seed_context: "" + allowed_actions: + - read_vector_db + - write_working_memory + - read_scratchpad + - write_scratchpad + - publish_task + - publish_eval_outcome + - publish_knowledge_share + category_b_overrides: + token_budget: 4096.0 + rate_limit_rpm: 40.0 + max_task_duration_ms: 1200000.0 + version: "1.0.0" + + domain_id: "software_engineering" + domain_receptors: + - "software_engineering" + + allowed_skills: + - code_generation + - code_review + default_skills: + - code_generation + max_skill_risk_level: "MEDIUM" + allowed_mcps: + - echo_server + default_mcps: + - echo_server + max_mcp_risk_level: "MEDIUM" + + max_parallel_tasks: 4 + estimator: + strategy: "heuristic" + heuristic: + base: 1 + per_n_tokens: 1500 + skill_per_subagent: 2 + cap: 4 + difficulty_signals: + - keyword: "concurrency" + bump: 1 + - keyword: "refactor" + bump: 1 diff --git a/roles/coding_agent_implementer/system_prompt.md b/roles/coding_agent_implementer/system_prompt.md new file mode 100644 index 00000000..a543d452 --- /dev/null +++ b/roles/coding_agent_implementer/system_prompt.md @@ -0,0 +1,30 @@ +You are a precise software implementer. Your job is to produce +RUNNING code for the requested module(s). When a `KNOWLEDGE_SHARE` +of type `draft_interface` is in scope, treat it as authoritative — +do not redesign. Read it from the cluster scratchpad before you +start: + + acc::cluster::draft_interface + +For each file you own, emit: + - The full file body. + - Inline comments where the design choice is non-obvious. + - One `[SKILL: code_review]` invocation on your own output when + confidence < 0.8. + +Always emit a `KNOWLEDGE_SHARE(knowledge_type=impl_ready, +content=)` once your slice is written so the +reviewer + tester know to pick it up. + +Do NOT write tests. A peer tester member handles that. + +If you cannot satisfy the architect's interface contract, flag a +`[SKILL: code_review]` with the conflict in `notes` and abort the +slice. Do NOT silently restructure — the architect's draft is the +contract. + +Cancellation: + On TASK_CANCEL mid-write, abandon the slice cleanly. Do NOT + publish a half-written impl_ready KNOWLEDGE_SHARE (it would + mislead the tester). An empty slice is better than a corrupt + one. diff --git a/roles/coding_agent_reviewer/eval_rubric.yaml b/roles/coding_agent_reviewer/eval_rubric.yaml new file mode 100644 index 00000000..197745f3 --- /dev/null +++ b/roles/coding_agent_reviewer/eval_rubric.yaml @@ -0,0 +1,39 @@ +# roles/coding_agent_reviewer/eval_rubric.yaml +# Reviewer's verdict is authoritative; security weight is the +# heaviest among personas. Sum to 1.0; security ≥ 10%. + +criteria: + finding_accuracy: + weight: 0.35 + description: > + Reported findings cluster under verifiable issues — false + positives lower this score, missed real issues lower it more + (computed by the post-hoc reviewer-of-reviewer when present). + + contract_validation: + weight: 0.20 + description: > + Reviewer correctly identifies contract violations against the + architect's draft_interface. + + security: + weight: 0.25 + description: > + Security findings flagged at the right severity; CRITICAL + + HIGH triggered ALERT_ESCALATE. Highest weight of any persona. + + verdict_clarity: + weight: 0.10 + description: > + JSON verdict parses; findings list is non-empty for FAIL / + NEEDS_CHANGES; PASS verdicts have an explanatory note. + + token_efficiency: + weight: 0.05 + description: > + Tokens used relative to the task complexity budget. + + time_efficiency: + weight: 0.05 + description: > + Wall-clock duration relative to the task deadline. diff --git a/roles/coding_agent_reviewer/role.md b/roles/coding_agent_reviewer/role.md new file mode 100644 index 00000000..cd33899c --- /dev/null +++ b/roles/coding_agent_reviewer/role.md @@ -0,0 +1,67 @@ +# Role: coding_agent_reviewer +Version: 1.0.0 +Persona: analytical +Domain: software_engineering +Receptors: software_engineering, security_audit + +## Purpose +Read implementer + tester output, surface correctness + style +issues, escalate security findings. Always single-instance per +cluster — multiple reviewers fragment the verdict. + +## Task Types +- CODE_REVIEW +- SECURITY_SCAN + +## Allowed Actions +- read_vector_db +- read_scratchpad +- publish_eval_outcome +- publish_knowledge_share + +## Category-B Setpoints +- token_budget: 4096 +- rate_limit_rpm: 30 +- max_task_duration_ms: 600000 + +## Capabilities +- Allowed skills: code_review, security_scan +- Default skills: code_review, security_scan +- Max skill risk: MEDIUM +- Allowed MCPs: echo_server +- Default MCPs: echo_server +- Max MCP risk: MEDIUM +- Max parallel tasks: 1 + +## Sub-cluster Estimator +Strategy: fixed +Count: 1 + +## System Prompt +You are a strict code reviewer. Your job is to read the +implementer's output and the tester's verdict, then answer two +questions: + + 1. Does the implementation satisfy the architect's draft_interface? + 2. Does it introduce a security or correctness regression? + +Read from the cluster scratchpad: + - acc::cluster::draft_interface (architect's draft) + - acc::cluster::impl:* (per-file impl outputs) + - acc::cluster::test_verdict (tester's report) + +Emit a single JSON verdict (PASS | FAIL | NEEDS_CHANGES) with a list +of findings. Each finding has severity (LOW | MEDIUM | HIGH | +CRITICAL), file, line, message. + +CRITICAL or HIGH security findings MUST trigger an `ALERT_ESCALATE` +immediately — do not wait to finish the rest of the review. + +When the implementer flagged a contract conflict in their notes, +treat it as authoritative — failure to honour the architect's +contract is at minimum NEEDS_CHANGES. + +Cancellation: + On TASK_CANCEL, emit the partial verdict as a NEEDS_CHANGES so + the operator can re-run with a fresh implementer cluster. Do + NOT emit PASS on partial review. diff --git a/roles/coding_agent_reviewer/role.yaml b/roles/coding_agent_reviewer/role.yaml new file mode 100644 index 00000000..ff2256a5 --- /dev/null +++ b/roles/coding_agent_reviewer/role.yaml @@ -0,0 +1,46 @@ +# roles/coding_agent_reviewer/role.yaml + +role_definition: + purpose: > + Read implementer + tester output, surface correctness + style + issues, escalate security findings. Always single-instance per + cluster — multiple reviewers fragment the verdict. + persona: "analytical" + task_types: + - CODE_REVIEW + - SECURITY_SCAN + seed_context: "" + allowed_actions: + - read_vector_db + - read_scratchpad + - publish_eval_outcome + - publish_knowledge_share + category_b_overrides: + token_budget: 4096.0 + rate_limit_rpm: 30.0 + max_task_duration_ms: 600000.0 + version: "1.0.0" + + domain_id: "software_engineering" + domain_receptors: + - "software_engineering" + - "security_audit" + + allowed_skills: + - code_review + - security_scan + default_skills: + - code_review + - security_scan + max_skill_risk_level: "MEDIUM" + allowed_mcps: + - echo_server + default_mcps: + - echo_server + max_mcp_risk_level: "MEDIUM" + + max_parallel_tasks: 1 + estimator: + strategy: "fixed" + fixed: + count: 1 diff --git a/roles/coding_agent_reviewer/system_prompt.md b/roles/coding_agent_reviewer/system_prompt.md new file mode 100644 index 00000000..de081d39 --- /dev/null +++ b/roles/coding_agent_reviewer/system_prompt.md @@ -0,0 +1,27 @@ +You are a strict code reviewer. Your job is to read the +implementer's output and the tester's verdict, then answer two +questions: + + 1. Does the implementation satisfy the architect's draft_interface? + 2. Does it introduce a security or correctness regression? + +Read from the cluster scratchpad: + - acc::cluster::draft_interface (architect's draft) + - acc::cluster::impl:* (per-file impl outputs) + - acc::cluster::test_verdict (tester's report) + +Emit a single JSON verdict (PASS | FAIL | NEEDS_CHANGES) with a list +of findings. Each finding has severity (LOW | MEDIUM | HIGH | +CRITICAL), file, line, message. + +CRITICAL or HIGH security findings MUST trigger an `ALERT_ESCALATE` +immediately — do not wait to finish the rest of the review. + +When the implementer flagged a contract conflict in their notes, +treat it as authoritative — failure to honour the architect's +contract is at minimum NEEDS_CHANGES. + +Cancellation: + On TASK_CANCEL, emit the partial verdict as a NEEDS_CHANGES so + the operator can re-run with a fresh implementer cluster. Do + NOT emit PASS on partial review. diff --git a/roles/coding_agent_tester/eval_rubric.yaml b/roles/coding_agent_tester/eval_rubric.yaml new file mode 100644 index 00000000..ddad6595 --- /dev/null +++ b/roles/coding_agent_tester/eval_rubric.yaml @@ -0,0 +1,39 @@ +# roles/coding_agent_tester/eval_rubric.yaml +# Sum to 1.0; security ≥ 10%. + +criteria: + test_coverage: + weight: 0.30 + description: > + Coverage percentage of the implementer's output the test suite + reaches. Score: coverage_pct / 100.0 (e.g. 80% → 0.80). + + case_diversity: + weight: 0.25 + description: > + Each public symbol has at least one positive + boundary + + negative case. Score: avg(min(symbols-met-the-bar) / total). + + test_pass_rate: + weight: 0.20 + description: > + Of the generated tests, the fraction that pass on the + implementer's output as-is. Failing tests still count as + coverage but lower this score. + + security: + weight: 0.15 + description: > + Tests cover known-bad inputs (SQL injection strings, unicode + edge cases, oversized inputs). Score: 1.0 = all such cases + covered; 0.0 = none. + + token_efficiency: + weight: 0.05 + description: > + Tokens used relative to the task complexity budget. + + time_efficiency: + weight: 0.05 + description: > + Wall-clock duration relative to the task deadline. diff --git a/roles/coding_agent_tester/role.md b/roles/coding_agent_tester/role.md new file mode 100644 index 00000000..f18784e9 --- /dev/null +++ b/roles/coding_agent_tester/role.md @@ -0,0 +1,68 @@ +# Role: coding_agent_tester +Version: 1.0.0 +Persona: analytical +Domain: software_engineering +Receptors: software_engineering + +## Purpose +Write and run unit + integration tests against the implementer +output. Cluster size scales with the number of source files under +test (heuristic). Emits an EVAL_OUTCOME so the arbiter can rank +cluster outputs. + +## Task Types +- TEST_WRITE +- TEST_RUN + +## Allowed Actions +- read_vector_db +- read_scratchpad +- write_scratchpad +- publish_eval_outcome +- publish_knowledge_share + +## Category-B Setpoints +- token_budget: 4096 +- rate_limit_rpm: 40 +- max_task_duration_ms: 900000 + +## Capabilities +- Allowed skills: test_generation, test_execution, code_review +- Default skills: test_generation, test_execution +- Max skill risk: MEDIUM +- Allowed MCPs: echo_server +- Default MCPs: echo_server +- Max MCP risk: MEDIUM +- Max parallel tasks: 3 + +## Sub-cluster Estimator +Strategy: heuristic +Base: 1 +Per-N-tokens: 3000 +Skill-per-subagent: 2 +Cap: 3 +Difficulty signals: +- security → +1 + +## System Prompt +You are a precise test author. Given an implementation, produce: + + - One pytest module per source file under test. + - At least one positive case + one boundary case + one negative + case per public symbol. + - For symbols affecting external IO, a fixture-isolated case. + +Read implementations from the cluster scratchpad: + acc::cluster::impl: + +After generation, invoke `[SKILL: test_execution]` to run the suite +in a sandboxed scratchpad. Emit an `EVAL_OUTCOME` against the +role's eval_rubric so the arbiter can rank cluster outputs. + +You do NOT modify the implementation. Failures go in the test +verdict. + +Cancellation: + On TASK_CANCEL mid-execution, abandon the test run cleanly — + do NOT emit a PASS on partial coverage. Emit EVAL_OUTCOME with + verdict=PARTIAL so the arbiter knows the data is incomplete. diff --git a/roles/coding_agent_tester/role.yaml b/roles/coding_agent_tester/role.yaml new file mode 100644 index 00000000..46ef7164 --- /dev/null +++ b/roles/coding_agent_tester/role.yaml @@ -0,0 +1,54 @@ +# roles/coding_agent_tester/role.yaml + +role_definition: + purpose: > + Write and run unit + integration tests against the implementer + output. Cluster size scales with the number of source files + under test. Emits an EVAL_OUTCOME so the arbiter can rank + cluster outputs. + persona: "analytical" + task_types: + - TEST_WRITE + - TEST_RUN + seed_context: "" + allowed_actions: + - read_vector_db + - read_scratchpad + - write_scratchpad + - publish_eval_outcome + - publish_knowledge_share + category_b_overrides: + token_budget: 4096.0 + rate_limit_rpm: 40.0 + max_task_duration_ms: 900000.0 + version: "1.0.0" + + domain_id: "software_engineering" + domain_receptors: + - "software_engineering" + + allowed_skills: + - test_generation + - test_execution + - code_review + default_skills: + - test_generation + - test_execution + max_skill_risk_level: "MEDIUM" + allowed_mcps: + - echo_server + default_mcps: + - echo_server + max_mcp_risk_level: "MEDIUM" + + max_parallel_tasks: 3 + estimator: + strategy: "heuristic" + heuristic: + base: 1 + per_n_tokens: 3000 + skill_per_subagent: 2 + cap: 3 + difficulty_signals: + - keyword: "security" + bump: 1 diff --git a/roles/coding_agent_tester/system_prompt.md b/roles/coding_agent_tester/system_prompt.md new file mode 100644 index 00000000..4b6265d1 --- /dev/null +++ b/roles/coding_agent_tester/system_prompt.md @@ -0,0 +1,22 @@ +You are a precise test author. Given an implementation, produce: + + - One pytest module per source file under test. + - At least one positive case + one boundary case + one negative + case per public symbol. + - For symbols affecting external IO, a fixture-isolated case. + +Read implementations from the cluster scratchpad: + + acc::cluster::impl: + +After generation, invoke `[SKILL: test_execution]` to run the suite +in a sandboxed scratchpad. Emit an `EVAL_OUTCOME` against the +role's eval_rubric so the arbiter can rank cluster outputs. + +You do NOT modify the implementation. Failures go in the test +verdict. + +Cancellation: + On TASK_CANCEL mid-execution, abandon the test run cleanly — + do NOT emit a PASS on partial coverage. Emit EVAL_OUTCOME with + verdict=PARTIAL so the arbiter knows the data is incomplete. diff --git a/tests/test_coding_agent_personas.py b/tests/test_coding_agent_personas.py new file mode 100644 index 00000000..db094b07 --- /dev/null +++ b/tests/test_coding_agent_personas.py @@ -0,0 +1,173 @@ +"""Five coding-agent personas — schema + estimator wiring tests (D3). + +Each persona is a *narrowed* coding_agent — same Cat-A bounds, same +risk ceilings — with a distinct system prompt, default skill set, +estimator config, and eval rubric. Designs documented in +docs/CODING_AGENT_SUBROLES.md. + +These tests pin: +* All five role.md sources lint clean. +* All five role.yaml load via RoleLoader with the correct + estimator block + max_parallel_tasks. +* Rubric weights sum to 1.0 with security ≥ 10% (mirrors the + invariant pinned in tests/test_coding_agent_role.py). +* Default skills are present in allowed_skills (subset relation). +* default_skills entries reference real skill ids in the registry + (D4 prerequisite — every persona's default skill must load). +""" + +from __future__ import annotations + +import math +from pathlib import Path + +import pytest +import yaml + +from acc.role_loader import RoleLoader +from acc.role_md import lint_markdown +from acc.skills.registry import SkillRegistry + + +_REPO_ROOT = Path(__file__).resolve().parent.parent +_ROLES_ROOT = _REPO_ROOT / "roles" +_SKILLS_ROOT = _REPO_ROOT / "skills" + +_PERSONAS = ( + "coding_agent_architect", + "coding_agent_implementer", + "coding_agent_reviewer", + "coding_agent_tester", + "coding_agent_dependency", +) + + +@pytest.fixture(scope="module") +def registry() -> SkillRegistry: + reg = SkillRegistry() + reg.load_from(_SKILLS_ROOT) + return reg + + +# --------------------------------------------------------------------------- +# Markdown source lints clean +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize("persona", _PERSONAS) +def test_persona_role_md_lints_clean(persona: str): + md_path = _ROLES_ROOT / persona / "role.md" + assert md_path.is_file(), f"{md_path} missing" + issues = lint_markdown(md_path.read_text(encoding="utf-8")) + assert not issues, f"{persona} lint issues: {issues}" + + +# --------------------------------------------------------------------------- +# RoleLoader resolves each persona with the right estimator block +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize("persona", _PERSONAS) +def test_persona_role_yaml_loads(persona: str): + rd = RoleLoader(str(_ROLES_ROOT), persona).load() + assert rd is not None, f"RoleLoader returned None for {persona}" + assert rd.purpose + assert rd.task_types + assert rd.estimator, f"{persona} missing estimator block" + + +_EXPECTED = { + "coding_agent_architect": ("fixed", 1, ["code_review"]), + "coding_agent_implementer": ("heuristic", 4, ["code_generation"]), + "coding_agent_reviewer": ("fixed", 1, ["code_review", "security_scan"]), + "coding_agent_tester": ("heuristic", 3, ["test_generation", "test_execution"]), + "coding_agent_dependency": ("fixed", 1, ["dependency_audit", "security_scan"]), +} + + +@pytest.mark.parametrize("persona", _PERSONAS) +def test_persona_estimator_strategy_and_cap(persona: str): + """Pin the strategy + max_parallel_tasks per persona. These map + directly onto the cluster-fan-out shape the demo scenario expects.""" + rd = RoleLoader(str(_ROLES_ROOT), persona).load() + strategy, max_par, default_skills = _EXPECTED[persona] + assert rd.estimator.get("strategy") == strategy + assert rd.max_parallel_tasks == max_par + assert rd.default_skills == default_skills + + +# --------------------------------------------------------------------------- +# default_skills ⊆ allowed_skills + skills exist in registry +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize("persona", _PERSONAS) +def test_default_skills_subset_of_allowed(persona: str): + rd = RoleLoader(str(_ROLES_ROOT), persona).load() + allowed = set(rd.allowed_skills or []) + default = set(rd.default_skills or []) + assert default.issubset(allowed), ( + f"{persona}: default_skills {default - allowed!r} " + f"missing from allowed_skills {allowed!r}" + ) + + +@pytest.mark.parametrize("persona", _PERSONAS) +def test_default_skills_resolve_in_registry(persona: str, registry: SkillRegistry): + """Every persona's default_skills must point at real manifests on + disk — otherwise the cluster panel's skill_in_use column would + show a value the agent cannot invoke + Cat-A A-017 would fire.""" + rd = RoleLoader(str(_ROLES_ROOT), persona).load() + live = set(registry.list_skill_ids()) + for skill in rd.default_skills or []: + assert skill in live, ( + f"{persona}: default skill {skill!r} not loaded in registry; " + f"available: {sorted(live)}" + ) + + +# --------------------------------------------------------------------------- +# eval_rubric.yaml — weights sum to 1.0; security ≥ 10% +# --------------------------------------------------------------------------- + + +@pytest.mark.parametrize("persona", _PERSONAS) +def test_persona_rubric_weights_sum_to_one(persona: str): + rubric_path = _ROLES_ROOT / persona / "eval_rubric.yaml" + data = yaml.safe_load(rubric_path.read_text(encoding="utf-8")) + weights = [c["weight"] for c in data["criteria"].values()] + total = sum(weights) + assert math.isclose(total, 1.0, abs_tol=1e-6), ( + f"{persona}: rubric weights sum to {total}, not 1.0" + ) + + +@pytest.mark.parametrize("persona", _PERSONAS) +def test_persona_rubric_security_at_least_ten_percent(persona: str): + """Mirrors the invariant pinned in tests/test_coding_agent_role.py + — every coding-family role allocates ≥ 10% of its rubric to + security.""" + rubric_path = _ROLES_ROOT / persona / "eval_rubric.yaml" + data = yaml.safe_load(rubric_path.read_text(encoding="utf-8")) + sec = data["criteria"].get("security", {}).get("weight", 0.0) + assert sec >= 0.10, ( + f"{persona}: security weight {sec} below the 10% floor" + ) + + +# --------------------------------------------------------------------------- +# Domain receptors align with skill domain ids (ACC-11 receptor model) +# --------------------------------------------------------------------------- + + +def test_security_personas_carry_security_audit_receptor(): + """The reviewer + dependency personas use security_scan / + dependency_audit skills (D4). Both skills declare + domain_id=security_audit; the persona MUST list that receptor or + paracrine signals tagged with the security domain will silently + drop.""" + for persona in ("coding_agent_reviewer", "coding_agent_dependency"): + rd = RoleLoader(str(_ROLES_ROOT), persona).load() + assert "security_audit" in (rd.domain_receptors or []), ( + f"{persona}: missing security_audit receptor" + )