summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-08-27 12:21:04 +0700
committerhathach <[email protected]>2026-08-27 12:21:04 +0700
commit92b38fc3b5e9080c96294079e6117d74429f3448 (patch)
tree268ad55d6fbfe4c50eae96eaa794efeede9e403c
parent208f82efe8b9dc7ca3eb210dcbf455a14c97cf3c (diff)
validate: add claude + codex diff-review stages (opus/high, sol/high)
The claude stage reviews the diff directly (the code-review skill is a CLI built-in, unavailable to subagents); the gate is enforced in-script from structured findings, failing only on confirmed correctness/safety bugs.
-rw-r--r--.claude/workflows/validate.js60
1 files changed, 57 insertions, 3 deletions
diff --git a/.claude/workflows/validate.js b/.claude/workflows/validate.js
index 522548ee6..dadf8a77f 100644
--- a/.claude/workflows/validate.js
+++ b/.claude/workflows/validate.js
@@ -1,11 +1,11 @@
export const meta = {
name: 'validate',
- description: 'Pre-PR software validation: unit tests + per-board build sweeps + code-size compare + PVS, in parallel, joined into one verdict',
+ description: 'Pre-PR software validation: unit tests + per-board build sweeps + code-size compare + PVS + diff reviews (claude + codex), in parallel, joined into one verdict',
whenToUse: 'Before opening or updating a PR, after any non-trivial change',
- phases: [{ title: 'Validate', detail: 'unit + builds + size + pvs in parallel' }],
+ phases: [{ title: 'Validate', detail: 'unit + builds + size + pvs + reviews in parallel' }],
}
-// args: { boards: string[], examples?: string, base?: string, skip?: ('unit'|'size'|'pvs')[] }
+// args: { boards: string[], examples?: string, base?: string, skip?: ('unit'|'size'|'pvs'|'review'|'codex')[] }
if (typeof args === 'string') { try { args = JSON.parse(args) } catch { /* not JSON: shape check below reports it */ } }
if (!args || !Array.isArray(args.boards) || args.boards.length === 0) {
throw new Error('args must be { boards: string[], examples?, base?, skip? }')
@@ -56,6 +56,26 @@ const PVS = {
},
}
+const REVIEW = {
+ type: 'object', additionalProperties: false,
+ required: ['pass', 'findings', 'detail'],
+ properties: {
+ pass: { type: 'boolean' },
+ findings: {
+ type: 'array',
+ items: {
+ type: 'object', additionalProperties: false,
+ required: ['file', 'line', 'severity', 'summary'],
+ properties: {
+ file: { type: 'string' }, line: { type: 'integer' },
+ severity: { type: 'string' }, summary: { type: 'string' },
+ },
+ },
+ },
+ detail: { type: 'string' },
+ },
+}
+
const thunks = []
if (!skip.includes('unit')) thunks.push(() =>
@@ -92,6 +112,40 @@ if (!skip.includes('pvs')) thunks.push(() =>
detail: r.pass ? r.detail : clip(`${r.detail} ${JSON.stringify(r.changedFindings)}`),
}))
+if (!skip.includes('review')) thunks.push(() =>
+ agent(
+ `Code-review this branch's diff vs ${base} (git diff ${base}...HEAD), coverage-first: walk every hunk, no spot checks. ` +
+ 'Find pass — candidate defects across all dimensions: correctness/logic, ISR & concurrency safety, ' +
+ 'memory/resource handling (bounds, leaks, no dynamic alloc), API contract & spec conformance, ' +
+ 'security of untrusted input parsing, behavior regressions; plus quality/simplification notes. ' +
+ 'Verify pass — adversarially check each candidate against the surrounding code: verdict CONFIRMED ' +
+ '(failing scenario constructed) or PLAUSIBLE (could not refute); report both, drop only refuted ones. ' +
+ 'Read-only: never apply fixes. severity = verdict plus category (e.g. "CONFIRMED correctness"). ' +
+ 'pass=false if any CONFIRMED correctness/safety/security bug survives; PLAUSIBLE and quality findings keep pass=true. ' +
+ 'detail = one-line review summary.',
+ { label: 'review', phase: 'Validate', model: 'opus', effort: 'high', schema: REVIEW },
+ ).then(r => r && {
+ stage: 'review',
+ // gate enforced here, not trusted from the agent: any CONFIRMED non-quality finding fails
+ pass: r.pass && !r.findings.some(f =>
+ /^confirmed/i.test(f.severity) && !/quality|simplification|style/i.test(f.severity)),
+ findings: r.findings, detail: r.detail,
+ }))
+
+if (!skip.includes('codex')) thunks.push(() =>
+ agent(
+ `Run a Codex review of this branch's diff vs ${base}: ` +
+ `codex review --base ${base} -c model="gpt-5.6-sol" -c model_reasoning_effort="high" ` +
+ '(Bash timeout 600000; run from the repo root). Parse its output into findings; severity = Codex\'s priority label. ' +
+ 'pass=false only if Codex reports a correctness bug (P0/P1); style-level items keep pass=true. ' +
+ 'detail = Codex\'s overall verdict line. If the codex CLI is missing or the run errors, pass=false with the error in detail.',
+ { label: 'codex', phase: 'Validate', model: 'haiku', schema: REVIEW },
+ ).then(r => r && {
+ stage: 'codex',
+ pass: r.pass && !r.findings.some(f => /\bP[01]\b/i.test(f.severity)),
+ findings: r.findings, detail: r.detail,
+ }))
+
const results = (await parallel(thunks)).filter(Boolean)
const dead = thunks.length - results.length
if (dead > 0) log(`${dead} stage agent(s) died — counted as failures`)