Skip to content

Commit 0cba96d

Browse files
Anass Rachclaude
andcommitted
Add schema-driven envelope validator with --strict mode
- schemas/qa-output.schema.json: source of truth for the machine block every agent emits (envelope + per-agent $defs.agent_extensions). - scripts/validate-qa-output.mjs: envelope lint in default mode, per-agent extension lint under --strict. Zero deps, Node 18+. Derives constants (valid agents, verdicts, summary maxLength) from the schema so there's no hand-rolled duplicate to drift. - scripts/validate-agents.mjs: refactored to the same {label, errors} reporting shape as the qa-output validator. Killed module-level state. - tests/_invalid/: negative fixtures proving both modes reject what they should (envelope-violation fixture + strict-extension-only fixture). - Golden fixtures (functional-reviewer, test-scenario-designer) now carry the machine block the validator checks against. - AGENTS.md: new ยง0 mandatory skill-check preamble with the agent trigger map; line 4 cross-platform claim corrected to "Primary: Claude Code". - .github/workflows/lint.yml: --all --strict, plus negative-fixture steps proving strict catches what envelope-only misses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7999429 commit 0cba96d

9 files changed

Lines changed: 761 additions & 63 deletions

File tree

.github/workflows/lint.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,15 @@ jobs:
2121

2222
- name: Validate agent structure
2323
run: node scripts/validate-agents.mjs
24+
25+
- name: Validate qa-output machine blocks (golden fixtures, strict)
26+
run: node scripts/validate-qa-output.mjs --all --strict
27+
28+
- name: Validate envelope-violation fixture is rejected
29+
run: node scripts/validate-qa-output.mjs --expect-fail tests/_invalid/expected-output.md
30+
31+
- name: Validate strict-extension fixture passes envelope-only
32+
run: node scripts/validate-qa-output.mjs tests/_invalid/strict-extension.md
33+
34+
- name: Validate strict-extension fixture is rejected by --strict
35+
run: node scripts/validate-qa-output.mjs --expect-fail --strict tests/_invalid/strict-extension.md

AGENTS.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,26 @@
11
# AGENTS.md — QA Orchestra Behavioral Instructions
22

33
This file defines how AI agents must behave in this project.
4-
It applies to Claude Code, Copilot, Cursor, and any agent reading this workspace.
4+
5+
**Primary support: Claude Code.** Best-effort: any agent runtime that honors AGENTS.md and reads Markdown instructions. Tool names in agent frontmatter (Agent, Bash, etc.) are Claude Code conventions; other runtimes may need a tool-mapping layer before the agents work correctly.
6+
7+
---
8+
9+
## 0. Mandatory skill check (read this first)
10+
11+
Before responding to any QA-adjacent request, state which `qa-orchestra` agent applies. If one does, invoke it. If none does, say so explicitly and only then proceed with inline reasoning. Do not silently do QA-style work (gap analysis, scenario design, test selection, bug writing) without first naming the agent that owns that work — this is how specialist agents get forgotten and the discipline drifts.
12+
13+
Quick map:
14+
- *"Does this diff implement the AC?"*`functional-reviewer`
15+
- *"What scenarios do I need?"*`test-scenario-designer`
16+
- *"Which tests will this change break?"*`smart-test-selector`
17+
- *"File this as a bug."*`bug-reporter`
18+
- *"Check the app in a browser."*`browser-validator`
19+
- *"Set up the PR branch locally."*`environment-manager`
20+
- *"Write Playwright/Cypress tests for these scenarios."*`automation-writer`
21+
- *"Walk me through manual test execution."*`manual-validator`
22+
- *"Analyze this release across repos."*`release-analyzer`
23+
- *"Coordinate the full QA pipeline."*`orchestrator`
524

625
---
726

@@ -30,7 +49,8 @@ If the user says the app is already running from the feature branch, skip the en
3049
## 3. Output discipline
3150

3251
- **Always save output to the correct file in `qa-output/`** (see CLAUDE.md agent map).
33-
- Output format is structured Markdown, copy-pasteable into GitHub Issues / Jira / Linear.
52+
- Output format is **a ```json qa-orchestra``` machine block at the top**, then structured Markdown below — see each agent's `## Output format` section for the required fields. Schema at `schemas/qa-output.schema.json`, validator at `scripts/validate-qa-output.mjs`.
53+
- The Markdown below the block stays copy-pasteable into GitHub Issues / Jira / Linear. The machine block and the prose must not contradict each other.
3454
- Never truncate output. If content is long, split into sections but deliver completely.
3555
- If you cannot produce a complete output (missing input), **stop and ask** — do not fabricate.
3656
- Screenshots from browser validation go to `qa-output/screenshots/`.
@@ -53,6 +73,8 @@ manual-validator → reads test-scenarios.md
5373

5474
Never redo upstream work. Trust the file.
5575

76+
**When reading an upstream file, parse its ```json qa-orchestra``` block — not the prose.** The prose is for humans; LLM-on-LLM prose parsing is brittle. If the block is missing or fails to parse, stop and report rather than falling back to regex.
77+
5678
---
5779

5880
## 5. Parallelism

schemas/qa-output.schema.json

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://qa-orchestra.com/schemas/qa-output.schema.json",
4+
"title": "QA Orchestra machine block",
5+
"description": "Schema for the ```json qa-orchestra fenced block that every agent must emit at the top of its qa-output/*.md report. Downstream agents read this block as source of truth; the prose below it is for humans. Kept deliberately shallow — strings for file references, not nested objects.",
6+
"type": "object",
7+
"required": ["agent", "version", "verdict", "summary"],
8+
"additionalProperties": true,
9+
"properties": {
10+
"agent": {
11+
"type": "string",
12+
"enum": [
13+
"orchestrator",
14+
"environment-manager",
15+
"functional-reviewer",
16+
"test-scenario-designer",
17+
"browser-validator",
18+
"automation-writer",
19+
"bug-reporter",
20+
"manual-validator",
21+
"release-analyzer",
22+
"smart-test-selector"
23+
]
24+
},
25+
"version": { "const": 1 },
26+
"timestamp": {
27+
"type": "string",
28+
"description": "ISO-8601 UTC timestamp. Optional — omit if the agent cannot observe wall-clock reliably."
29+
},
30+
"inputs": {
31+
"type": "array",
32+
"description": "Provenance — what the agent read. Each entry is a short {kind, ref} pair.",
33+
"items": {
34+
"type": "object",
35+
"required": ["kind", "ref"],
36+
"properties": {
37+
"kind": { "type": "string" },
38+
"ref": { "type": "string" }
39+
}
40+
}
41+
},
42+
"verdict": {
43+
"type": "string",
44+
"enum": ["pass", "pass_with_conditions", "fail", "not_applicable", "blocked"]
45+
},
46+
"summary": {
47+
"type": "string",
48+
"maxLength": 280,
49+
"description": "One-line human-readable summary. Must be consistent with the prose verdict below."
50+
},
51+
"next_actions": {
52+
"type": "array",
53+
"items": { "type": "string" }
54+
}
55+
},
56+
"$defs": {
57+
"agent_extensions": {
58+
"description": "Per-agent fields. Each agent SHOULD include its own extension, but unknown fields are allowed (additionalProperties: true) so agents can evolve without breaking the validator.",
59+
"functional-reviewer": {
60+
"risk_score": { "type": "integer", "minimum": 1, "maximum": 10 },
61+
"ac_compliance": {
62+
"type": "array",
63+
"items": {
64+
"type": "object",
65+
"required": ["id", "status"],
66+
"properties": {
67+
"id": { "type": "string" },
68+
"status": { "enum": ["covered", "partial", "missing", "ambiguous"] },
69+
"file_ref": { "type": ["string", "null"] }
70+
}
71+
}
72+
},
73+
"gaps": {
74+
"type": "array",
75+
"items": {
76+
"type": "object",
77+
"properties": {
78+
"ac": { "type": "string" },
79+
"desc": { "type": "string" }
80+
}
81+
}
82+
},
83+
"regression_risk": { "enum": ["low", "medium", "high"] }
84+
},
85+
"test-scenario-designer": {
86+
"scenarios": {
87+
"type": "array",
88+
"items": {
89+
"type": "object",
90+
"required": ["id", "type"],
91+
"properties": {
92+
"id": { "type": "string" },
93+
"type": { "enum": ["happy", "negative", "boundary", "edge"] },
94+
"priority": { "enum": ["must_test", "should_test", "nice_to_have"] },
95+
"title": { "type": "string" }
96+
}
97+
}
98+
}
99+
},
100+
"browser-validator": {
101+
"scenarios": {
102+
"type": "array",
103+
"items": {
104+
"type": "object",
105+
"required": ["id", "status"],
106+
"properties": {
107+
"id": { "type": "string" },
108+
"status": { "enum": ["pass", "fail", "skipped", "blocked"] },
109+
"evidence_path": { "type": ["string", "null"] }
110+
}
111+
}
112+
},
113+
"screenshots": { "type": "array", "items": { "type": "string" } }
114+
},
115+
"bug-reporter": {
116+
"bugs": {
117+
"type": "array",
118+
"items": {
119+
"type": "object",
120+
"required": ["id", "severity"],
121+
"properties": {
122+
"id": { "type": "string" },
123+
"severity": { "enum": ["critical", "major", "minor", "trivial"] },
124+
"ac_ref": { "type": ["string", "null"] },
125+
"file_ref": { "type": ["string", "null"] },
126+
"title": { "type": "string" }
127+
}
128+
}
129+
}
130+
},
131+
"smart-test-selector": {
132+
"affected_tests": { "type": "array", "items": { "type": "string" } },
133+
"coverage_gaps": { "type": "array", "items": { "type": "string" } }
134+
},
135+
"environment-manager": {
136+
"ready": { "type": "boolean" },
137+
"services": {
138+
"type": "array",
139+
"items": {
140+
"type": "object",
141+
"required": ["name", "status"],
142+
"properties": {
143+
"name": { "type": "string" },
144+
"url": { "type": ["string", "null"] },
145+
"status": { "enum": ["up", "down", "degraded", "unknown"] }
146+
}
147+
}
148+
}
149+
},
150+
"automation-writer": {
151+
"generated_files": { "type": "array", "items": { "type": "string" } }
152+
},
153+
"manual-validator": {
154+
"scenarios": {
155+
"type": "array",
156+
"items": {
157+
"type": "object",
158+
"required": ["id", "status"],
159+
"properties": {
160+
"id": { "type": "string" },
161+
"status": { "enum": ["pass", "fail", "skipped"] },
162+
"tester_note": { "type": ["string", "null"] }
163+
}
164+
}
165+
}
166+
},
167+
"release-analyzer": {
168+
"repos": { "type": "array", "items": { "type": "string" } },
169+
"deployment_risks": {
170+
"type": "array",
171+
"items": {
172+
"type": "object",
173+
"properties": {
174+
"risk": { "type": "string" },
175+
"severity": { "enum": ["low", "medium", "high"] }
176+
}
177+
}
178+
}
179+
},
180+
"orchestrator": {
181+
"steps": {
182+
"type": "array",
183+
"items": {
184+
"type": "object",
185+
"required": ["agent", "status"],
186+
"properties": {
187+
"agent": { "type": "string" },
188+
"status": { "enum": ["pending", "running", "done", "skipped", "failed"] },
189+
"output_ref": { "type": ["string", "null"] }
190+
}
191+
}
192+
}
193+
}
194+
}
195+
}
196+
}

0 commit comments

Comments
 (0)