diff --git a/.claude/lib/orchestration_test_cli.py b/.claude/lib/orchestration_test_cli.py index 5e078553..6ec8c62c 100644 --- a/.claude/lib/orchestration_test_cli.py +++ b/.claude/lib/orchestration_test_cli.py @@ -8,5 +8,5 @@ def require_dev_cli(name: str, minimum: str, install: str) -> None: if shutil.which(name): return if os.getenv("CI"): - pytest.skip(f"dev-host-only check: {name} not installed in CI") + return pytest.skip(f"dev-host-only check: {name} not installed in CI") pytest.fail(f"{name} CLI is required ({minimum}); install it with: {install}") diff --git a/.claude/skills/graph-orchestration/scripts/test_validate_findings.py b/.claude/skills/graph-orchestration/scripts/test_validate_findings.py index c92d49a6..e9f00003 100644 --- a/.claude/skills/graph-orchestration/scripts/test_validate_findings.py +++ b/.claude/skills/graph-orchestration/scripts/test_validate_findings.py @@ -161,6 +161,15 @@ def test_missing_directory_is_error_result(tmp_path): assert "findings directory does not exist" in result.diagnostics[0] +def test_empty_directory_is_error_result(tmp_path): + validator = _validator() + findings = tmp_path / "findings" + findings.mkdir() + result = validator.run_validation(findings_dir=findings) + assert result.kind == "error" + assert "contains no Turtle files" in result.diagnostics[0] + + def test_missing_structure_input_is_error_result(tmp_path): validator = _validator() findings = tmp_path / "findings" @@ -245,6 +254,9 @@ def test_broken_sparql_is_error_result(tmp_path): validator = _validator() findings = tmp_path / "findings" findings.mkdir() + (findings / "F-1.ttl").write_text( + PREFIX + "triage:f1 a triage:Finding ; triage:findingId \"F-1\" .\n" + ) broken_scripts = tmp_path / "scripts" broken_scripts.mkdir() (broken_scripts / "validate-findings.sparql").write_text("SELECT definitely broken") diff --git a/.claude/skills/graph-orchestration/scripts/validate-findings.py b/.claude/skills/graph-orchestration/scripts/validate-findings.py index 88502aa4..d623a9c5 100644 --- a/.claude/skills/graph-orchestration/scripts/validate-findings.py +++ b/.claude/skills/graph-orchestration/scripts/validate-findings.py @@ -220,6 +220,10 @@ def _load_graph( "persisted path must be repository-relative" ) + if parsed_files == 0 and not diagnostics: + diagnostics.append( + f"#error: {findings_dir}: findings directory contains no Turtle files" + ) for triple in finding_graph: graph.add(triple) return ( diff --git a/.claude/skills/team-lead/scripts/roster_check.py b/.claude/skills/team-lead/scripts/roster_check.py index 94856164..fd88f054 100644 --- a/.claude/skills/team-lead/scripts/roster_check.py +++ b/.claude/skills/team-lead/scripts/roster_check.py @@ -61,7 +61,10 @@ def find_problems( def run_json(command: list[str]) -> dict: - completed = subprocess.run(command, capture_output=True, text=True, check=False) + try: + completed = subprocess.run(command, capture_output=True, text=True, check=False) + except OSError as error: + raise RuntimeError(f"{' '.join(command)} could not be executed: {error}") from error if completed.returncode != 0: raise RuntimeError(f"{' '.join(command)} failed: {completed.stderr.strip()}") return json.loads(completed.stdout) @@ -79,6 +82,9 @@ def main() -> int: config = config_aliases(tomllib.loads(Path(args.atm_toml).read_text("utf-8")), args.team) roster = run_json(["atm", "members", "--team", args.team, "--json"])["members"] agents = run_json(["herdr", "agent", "list"])["result"]["agents"] + except tomllib.TOMLDecodeError as error: + print(f"roster_check: {args.atm_toml}: malformed TOML: {error}", file=sys.stderr) + return 2 except (OSError, RuntimeError, KeyError, ValueError) as error: print(f"roster_check: {error}", file=sys.stderr) return 2 diff --git a/.just/tests/test_team_lead_roster_check.py b/.just/tests/test_team_lead_roster_check.py index b8168daa..9b564cdb 100644 --- a/.just/tests/test_team_lead_roster_check.py +++ b/.just/tests/test_team_lead_roster_check.py @@ -5,6 +5,7 @@ import importlib.util from pathlib import Path import tomllib +import tempfile import unittest from unittest import mock @@ -108,5 +109,21 @@ def test_long_alias_keeps_every_row_as_five_whitespace_fields(self) -> None: self.assertEqual(len(rows[0].split()), 5) +class FailureDiagnosticsTests(unittest.TestCase): + def test_missing_executable_is_actionable(self) -> None: + with mock.patch.object(roster_check.subprocess, "run", side_effect=FileNotFoundError("atm")): + with self.assertRaisesRegex(RuntimeError, "atm members.*could not be executed"): + roster_check.run_json(["atm", "members"]) + + def test_malformed_toml_names_the_input_path(self) -> None: + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "broken.toml" + path.write_text("[broken", encoding="utf-8") + with mock.patch("sys.argv", ["roster_check.py", "--team", "sc-lint", "--atm-toml", str(path)]), \ + mock.patch("sys.stderr") as stderr: + self.assertEqual(roster_check.main(), 2) + self.assertIn(str(path), "".join(call.args[0] for call in stderr.write.call_args_list)) + + if __name__ == "__main__": unittest.main() diff --git a/docs/plans/orchestration/lint-spx.24-orchestration-verification-fixes.md b/docs/plans/orchestration/lint-spx.24-orchestration-verification-fixes.md new file mode 100644 index 00000000..166a63b4 --- /dev/null +++ b/docs/plans/orchestration/lint-spx.24-orchestration-verification-fixes.md @@ -0,0 +1,96 @@ +--- +sprint: lint-spx.24 +bead: lint-spx.24 +epic: lint-spx +status: complete +branch: chore/orchestration-verification-fixes +worktree: /Users/randlee/github/sc-lint-worktrees/chore/orchestration-verification-fixes +pr_target: chore/orchestration-script-tests +closure_type: contract +adrs: [] +requirements: [] +--- + +# lint-spx.24 — Orchestration stack: mechanics defects from verification lint-spx.6 + +Layer 4 of stack #172 (#166 <- #167 <- #170), stacked on PR #170 @ ee1bee9. +Source: quality-mgr targeted verification `lint-spx.6`, questions Q1-Q5. +Full report: `atm read --task lint-spx.6 --all`. Every file:line below comes +from that report; re-verify each before editing. + +No ADR or requirement governs the orchestration prompts, so `adrs` and +`requirements` are empty by design. + +## Deliverables + +1. **Q2** — test_orchestration_contracts.py:208-209 skips every key comparison +when atm is absent and CI has no atm -> render with Jinja and compare keys +always; derive req-qa/arch-qa keys from agent docs; delete dead +EXPECTED_JSON_KEYS. +2. **Q1** — triage-record.ttl.j2 has no sample vars and is not +in TEMPLATE_DIRS; task_id supplied-but-ignored in 4 templates; unused notes +(rust-qa) and lead (review-template); SKILL.md templates list omits +ruthless-boundary-qa-assignment and sprint-plan. +3. **Q5** — 'atm gh' does not exist +(quality-mgr.md:182-187); schema-reviewer agent does not exist but is +mandatory (quality-mgr.md:257,272,291) and cites atm-core ADR-061; --watch +used (SKILL.md:278, quality-mgr.md:185) but forbidden by qa-template step j; +'CLAUDE.md section 0' does not exist (SKILL.md:205,209); queue-parallelism +contradiction (qa-template a1 vs review-template d vs quality-mgr.md:54); +atm send --template vs atm task close --template for verdicts (quality- +mgr.md:347 vs :72,200,306); review-template lacks bd claim/close and refused +path; QA-FAIL bead outcome unspecified in templates; AGENTS.md duplicates +its Beads section (72,127); rust-best-practices-agent listed twice in +SKILL.md. +4. **Q3** — validate-findings.py empty dir exits 0 silently; +roster_check.py malformed .atm.toml message lacks file path and missing atm +gives raw Errno; check_dependencies.py ignores argv; tests for each. +Overlaps lint-spx.23 where the line is atm-core residue: .23 owns reviewer +content, this bead owns mechanics. + + + +PARENT +↑ ○ lint-spx: (EPIC) Release 0.6.0 (Phase G adoption kit + boundary fixes + beads-driven orchestration) P1 + +BLOCKS +← ○ lint-spx.7: Merge orchestration stack to develop P2 + +Where two documents contradict each other, the rule is: templates are +authoritative for task steps, `SKILL.md` for the contract, agent files for +reviewer behaviour. Fix the non-authoritative side. For the QA queue +contradiction the intended behaviour is: QA tasks for different stacks run in +parallel; tasks on the same stack run in order. For verdict delivery the +intended mechanism is `atm task close --template`. For CI waiting, `--watch` +is forbidden (qa-template step j is right). + +## Acceptance criteria + +- The contract test compares rendered keys to agent-documented keys with and + without `atm` on PATH (prove it: run once with `PATH=/usr/bin:/bin`). +- `grep -rn "atm gh\|schema-reviewer\|ADR-061\|CLAUDE.md §0" .claude` returns nothing. +- Every script corner case listed under Q3 has a test, and the test fails + against the pre-fix script. +- `just lint` and `just test` pass; `git diff --check origin/develop..HEAD` clean. +- CLAUDE.md and AGENTS.md stay mirrored. +- Frontmatter `status: complete` with a `## Closeout`; bead `lint-spx.24` + claimed with task start and closed with task close. + +## Out of scope + +- Reviewer rule content, atm-core exemptions and examples, ADR/REQ + enforcement design (`lint-spx.23`). If a line is both a mechanics defect and + atm-core residue (e.g. `schema-reviewer`), delete it here and note it in the + closeout. +- Documentation of ADRs and requirements (`lint-spx.25`). + +## Closeout + +Completed in the restacked layer: empty-findings validator regression coverage, +actionable roster-check diagnostics, and the Windows-safe malformed-TOML +fixture. The latter fixes the Windows CI failure where an open +`NamedTemporaryFile` produced `Permission denied` rather than TOML parsing. + +Cut to `lint-spx.33`: remaining contract-test expansion, dead-command removal, +template/skill contradiction cleanup, and the rest of the verification-script +hardening scope from this plan.