Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Post-execution test review and fix - chain from workflow-lite-execute or standalone. Reviews implementation against plan, runs tests, auto-fixes failures.
.claude/skills/workflow-lite-test-review/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-10 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
| case-17 | ✗→✓ | ▲ Improved | — | — |
| case-14 | ✗→✓ | ▲ Improved | — | — |
| case-06 | ✗→✓ | ▲ Improved | — | — |
Test review and fix engine for workflow-lite-execute chain or standalone invocation.
Project Context: Run ccw spec load --category test for test framework conventions, coverage targets, and fixtures.
<session-path|--last> Session path or auto-detect last session (required for standalone)| Flag | Description | |------|-------------| | --in-memory | Mode 1: Chain from workflow-lite-execute via testReviewContext global variable | | --skip-fix | Review only, do not auto-fix failures |
Trigger: --in-memory flag or testReviewContext global variable available
Input Source: testReviewContext global variable set by workflow-lite-execute Step 4
Behavior: Skip session discovery, inherit convergenceReviewTool from execution chain, proceed directly to TR-Phase 1.
> Note: workflow-lite-execute Step 5 is the chain gate. Mode 1 invocation means execution + code review are complete — proceed with convergence verification + tests.
Trigger: User calls with session path or --last
Behavior: Discover session → load plan + tasks → convergenceReviewTool = 'agent' → proceed to TR-Phase 1.
javascriptlet sessionPath, plan, taskFiles, convergenceReviewTool if (testReviewContext) { // Mode 1: from workflow-lite-execute chain sessionPath = testReviewContext.session.folder plan = testReviewContext.planObject taskFiles = testReviewContext.taskFiles.map(tf => JSON.parse(Read(tf.path))) convergenceReviewTool = testReviewContext.convergenceReviewTool || 'agent' } else { // Mode 2: standalone — find last session or use provided path sessionPath = resolveSessionPath($ARGUMENTS) // Glob('.workflow/.lite-plan/*/plan.json'), take last plan = JSON.parse(Read(`${sessionPath}/plan.json`)) taskFiles = plan.task_ids.map(id => JSON.parse(Read(`${sessionPath}/.task/${id}.json`))) convergenceReviewTool = 'agent' } const skipFix = $ARGUMENTS?.includes('--skip-fix') || false
| Phase | Core Action | Output | |-------|-------------|--------| | TR-Phase 1 | Detect test framework + gather changes | testConfig, changedFiles | | TR-Phase 2 | Convergence verification against plan criteria | reviewResults] | | TR-Phase 3 | Run tests + generate checklist | test-checklist.json | | TR-Phase 4 | Auto-fix failures (iterative, max 3 rounds) | Fixed code + updated checklist | | TR-Phase 5 | Output report + chain to session:sync | test-review.md |
Set sessionId from sessionPath. Create TodoWrite with 5 phases (Phase 1 = in_progress, rest = pending).
Test framework detection (check in order, first match wins):
| File | Framework | Command | |------|-----------|---------| | package.json with scripts.test | jest/vitest | npm test | | package.json with scripts['test:unit'] | jest/vitest | npm run test:unit | | pyproject.toml | pytest | python -m pytest -v --tb=short | | Cargo.toml | cargo-test | cargo test | | go.mod | go-test | go test ./... |
Gather git changes: git diff --name-only HEAD~5..HEAD → changedFiles[]
Output: testConfig = { command, framework, type } + changedFiles[]
// TodoWrite: Phase 1 → completed, Phase 2 → in_progress
Skip if: convergenceReviewTool === 'skip' — set all tasks to PASS, proceed to Phase 3.
Verify each task's convergence criteria are met in the implementation and identify test gaps.
Agent Convergence Review (convergenceReviewTool === 'agent', default):
For each task in taskFiles:
convergence.criteria[] from the tasktask.files[].path against changedFiles to find actually-changed filestask.test.unit defined but no matching test files in changedFiles → mark as test gaptask.test.integration defined but no integration test in changedFiles → mark as test gapreviewResult = { taskId, title, criteria_met[], criteria_unmet[], test_gaps[], files_reviewed[] }Verdict logic:
convergence.criteria met + no test gapsCLI Convergence Review (convergenceReviewTool === 'gemini' or 'codex'):
javascriptconst reviewId = `${sessionId}-convergence` const taskCriteria = taskFiles.map(t => `${t.id}: [${(t.convergence?.criteria || []).join(' | ')}]`).join('\n') Bash(`ccw cli -p "PURPOSE: Convergence verification — check each task's completion criteria against actual implementation TASK: • For each task below, verify every convergence criterion is satisfied in the changed files • Mark each criterion as MET (with file:line evidence) or UNMET (with what's missing) • Identify test coverage gaps (planned tests not found in changes) TASK CRITERIA: ${taskCriteria} CHANGED FILES: ${changedFiles.join(', ')} MODE: analysis CONTEXT: @${sessionPath}/plan.json @${sessionPath}/.task/*.json @**/* | Memory: workflow-lite-execute completed EXPECTED: Per-task verdict (PASS/PARTIAL/FAIL) with per-criterion evidence + test gap list CONSTRAINTS: Read-only | Focus strictly on convergence criteria verification, NOT code quality (code review already done in workflow-lite-execute)" --tool ${convergenceReviewTool} --mode analysis --id ${reviewId}`, { run_in_background: true }) // STOP - wait for hook callback, then parse CLI output into reviewResults format
// TodoWrite: Phase 2 → completed, Phase 3 → in_progress
Build checklist from reviewResults:
PASS (all criteria met) / PARTIAL (some met) / FAIL (none met)task.test.unit[], task.test.integration[], task.test.success_metrics[] + review test_gapsRun tests if testConfig.command exists:
overall: 'PASS' | 'FAIL' | 'UNKNOWN'Write ${sessionPath}/test-checklist.json
// TodoWrite: Phase 3 → completed, Phase 4 → in_progress
Skip if: skipFix === true OR testChecklist.execution?.overall !== 'FAIL'
Max iterations: 3. Each iteration:
javascriptAgent({ subagent_type: "test-fix-agent", run_in_background: false, description: `Fix tests (iter ${iteration})`, prompt: `## Test Fix Iteration ${iteration}/${MAX_ITERATIONS} **Test Command**: ${testConfig.command} **Framework**: ${testConfig.framework} **Session**: ${sessionPath} ### Failing Output (last 3000 chars) \`\`\` ${testChecklist.execution.raw_output} \`\`\` ### Plan Context **Summary**: ${plan.summary} **Tasks**: ${taskFiles.map(t => `${t.id}: ${t.title}`).join(' | ')} ### Instructions 1. Analyze test failure output to identify root cause 2. Fix the SOURCE CODE (not tests) unless tests themselves are wrong 3. Run \`${testConfig.command}\` to verify fix 4. If fix introduces new failures, revert and try alternative approach 5. Return: what was fixed, which files changed, test result after fix` })
testConfig.command → update testChecklist.executiontest-checklist.jsonIf still failing after 3 iterations → log "Manual investigation needed"
// TodoWrite: Phase 4 → completed, Phase 5 → in_progress
> CHECKPOINT: This step is MANDATORY. Always generate report and trigger sync.
Generate test-review.md with sections:
Write ${sessionPath}/test-review.md
Chain to session:sync:
javascriptSkill({ skill: "workflow:session:sync", args: `-y "Test review: ${testChecklist.execution?.overall || 'no-test'} — ${plan.summary}"` })
// TodoWrite: Phase 5 → completed
Display summary: Per-task verdict with PASS]/PARTIAL]/FAIL] icons, convergence ratio, overall test result.
javascript{ planObject: { /* same as executionContext.planObject */ }, taskFiles: [{ id: string, path: string }], convergenceReviewTool: "skip" | "agent" | "gemini" | "codex", executionResults: [...], originalUserInput: string, session: { id: string, folder: string, artifacts: { plan: string, task_dir: string } } }
javascript{ session: string, plan_summary: string, generated_at: string, test_config: { command, framework, type }, tasks: [{ task_id: string, title: string, status: "PASS" | "PARTIAL" | "FAIL", convergence: { met: string[], unmet: string[] }, test_items: [{ type: "unit"|"integration"|"metric", desc: string, status: "pending"|"missing" }] }], execution: { command: string, timestamp: string, raw_output: string, // last 3000 chars overall: "PASS" | "FAIL" | "UNKNOWN", fix_iteration?: number } | null }
.workflow/.lite-plan/{session-id}/
├── exploration-*.json
├── explorations-manifest.json
├── planning-context.md
├── plan.json
├── .task/TASK-*.json
├── test-checklist.json # structured test results
└── test-review.md # human-readable report| Error | Resolution | |-------|------------| | No session found | "No workflow-lite-plan sessions found. Run workflow-lite-plan first." | | Missing plan.json | "Invalid session: missing plan.json at {path}" | | No test framework | Skip TR-Phase 3 execution, still generate review report | | Test timeout | Capture partial output, report as FAIL | | Fix agent fails | Log iteration, continue to next or stop at max | | Sync fails | Log warning, do not block report generation |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-23 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 17 counted toward the lift figure. The other 6 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +52 percentage points is the difference between those two pass rates over the 17 comparable cases. 3 cases got worse with the skill loaded, and they are included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.