che-qa
Auto-detects project tech stack, runs build → lint → typecheck → tests in the correct order. Invoke ONLY by che-act after Developer completes a task and scope validation passes. QA reports stay OUTSID
SKILL.mdskills/che-qa/SKILL.md
GitHubChe — QA
SHARED REFERENCES (CANONICAL — DO NOT DUPLICATE body here):
- Nx/pnpm QA run order + standard commands:
_shared_checklists/NX_PNPM_COMMON.md- CI common failure fixes classification table:
_shared_checklists/NX_PNPM_COMMON.md
Runs automated quality gates for a single task or full session. Always returns a structured, numbered report so the Developer can fix without guessing.
-0.1 STORAGE BOUNDARY PREFLIGHT (MANDATORY BEFORE FIRST WRITE)
# 1. Resolve the `che` CLI (owns the Che-home cascade + canonical path construction)
command -v che >/dev/null 2>&1 || { echo "❌ FATAL: 'che' CLI not found on PATH. Cannot write outputs without storage boundary. Aborting write."; exit 98; }
# 2. Resolve minimum inputs (SM should pass; safe fallback only if missing)
WORKTREE_ROOT="${WORKTREE_ROOT:?SM must pass WORKTREE_ROOT}"
TASK_ID="${TASK_ID:-full-session}"
TASK_SLUG="${TASK_SLUG:-qa}"
SESSION_ID="${SESSION_ID:-qa-standalone-$(date -u +%Y%m%d-%H%M%S)}"
# 3. Canonical paths + ensure dirs
eval "$(che compute_paths "$WORKTREE_ROOT" "$SESSION_ID" --cwd "$PWD")"
che ensure_dirs "$WORKTREE_ROOT" "$SESSION_ID" --cwd "$PWD"
# 4. Double-guard: outputs NEVER in worktree
che assert_outside_worktree "$CHE_SESSION_DIR" "$WORKTREE_ROOT" --label "CHE_SESSION_DIR"
che assert_outside_worktree "$CHE_WORKSPACE_SHARED" "$WORKTREE_ROOT" --label "CHE_WORKSPACE_SHARED"
# 5. Construct QA output paths ONCE
QA_RELATED_ID="T${TASK_ID}-${TASK_SLUG}"
QA_REPORT_PATH="$(che output_path "report" "qa-run" "${QA_RELATED_ID}" "session" "md")"
QA_EVIDENCE_DIR="$(dirname -- "$(che output_path "qa" ".keep" "${QA_RELATED_ID}" "session" "tmp")")"
mkdir -p "$QA_EVIDENCE_DIR"
DO NOT INVENT paths: Every report, evidence screenshot, console capture, build log stays UNDER $QA_EVIDENCE_DIR or uses $QA_REPORT_PATH. Do not create ./qa-report.md, /tmp/qa-run.md or worktree-relative paths. stage E test names lint uses $QA_EVIDENCE_DIR/test-names.txt instead of /tmp/qa-test-names.txt.
-0.1.1 EVIDENCE RETENTION POLICY (ONDA4 — TWO LOCALS, NEVER IN USER WORKTREE)
# =============================================================
# DOUBLE LOCAL POLICY — engineering-contracts §20 MORATORIUM:
# NO evidence/manifest is written inside WORKTREE_ROOT/*
# by default. Override = user explicitly asks VERBATIM.
# =============================================================
# --- LOCAL 1 — EPHEMERAL / SESSION-SCOPE (heavy, TTL 30 days) ---
# FULL-size screenshots, complete STDOUT/STDERR build logs,
# trace files, raw test diffs. Stays in session.
# Cleanup handoff: sessions older than 30 days = TTL.
SESSION_EVIDENCE_DIR="$QA_EVIDENCE_DIR" # already created above
# Mandatory substructure (create if missing):
mkdir -p "$SESSION_EVIDENCE_DIR/screenshots" "$SESSION_EVIDENCE_DIR/logs" "$SESSION_EVIDENCE_DIR/builds"
# --- LOCAL 2 — DURABLE / WORKSPACE-SHARED (light audit trail) ---
# Canonical path via contract helper type=qa scope=workspace related_id=commit_7char.
# ONLY CONTAINS:
# (i) evidence_manifest_<SHA256_MANIFEST>.json
# (ii) 1 FINAL JPEG/PNG thumbnail per AC PASS ≤200KB
CURRENT_COMMIT_7CHAR="${CURRENT_COMMIT_7CHAR:-$(cd "$WORKTREE_ROOT" && git rev-parse --short=7 HEAD 2>/dev/null || echo "HEAD-detached")}"
WORKSPACE_EVIDENCE_AUDIT_DIR="$(che output_path "qa" "audit" "commit-${CURRENT_COMMIT_7CHAR}" "workspace" "tmp")"
WORKSPACE_EVIDENCE_AUDIT_DIR="$(dirname -- "$WORKSPACE_EVIDENCE_AUDIT_DIR")"
mkdir -p "$WORKSPACE_EVIDENCE_AUDIT_DIR"
che assert_outside_worktree "$WORKSPACE_EVIDENCE_AUDIT_DIR" "$WORKTREE_ROOT" --label "WORKSPACE_EVIDENCE_AUDIT_DIR (durable hash manifest)"
MANDATORY Manifest JSON Schema (NON-EMPTY fields, except thumbnail_path if no UI):
{
"generated_at_utc": "YYYY-MM-DDTHH:MM:SSZ",
"commit_7char": "abc1234",
"session_id": "<CHE_CURRENT_SESSION_ID>",
"qa_run_passed": true,
"related_id": "T${TASK_ID}-${TASK_SLUG}",
"per_test_file_sha256": {
"/abs/path/worktree/packages/x/test.spec.ts": "<sha256 hex 64>",
"/abs/path/worktree/packages/y/api.test.ts": "<sha256>"
},
"per_evidence_sha256": {
"screenshots/build-A-fail.png": "<sha256>",
"logs/stage-D-test-fail.log": "<sha256>"
},
"per_behavior_result": {
"B-1": "PASS",
"B-2": "PASS",
"AB-1": "PASS"
},
"thumbnail_path": "commit-abc1234-thumbnail-final.jpg"
}
Thumbnail rule ≤200KB: If QA run produces Playwright/UI screenshots, copy the FINAL one (last THEN) to Local 2, with resize width=800px JPEG quality=75%. If PNG doesn't fit, reduce dimensions until it passes. If no UI → thumbnail_path = null.
0. Preconditions
Must receive from Scrum Master:
WORKTREE_ROOT(absolute path)- Task ID
- List of modified files (from Dev pre-report)
- Change type hints: e.g.
domain-function,ui-component,api-route,db-migration,config
If any missing → ABORT, go back to SM.
0.1 How to write final QA report to DISK (FAIL or PASS)
After producing structured report (FAIL Stage A-D-E template or §3 PASS template), write to file outside worktree using atomic write:
# DO NOT "cat > ./qa-report.md" (falls inside worktree!)
# DO NOT "cat > /tmp/qa-task-T1.md" (loses sortability by related_id/timestamp)
{
echo "# QA Run Report — T${TASK_ID}-${TASK_SLUG}"
echo "> Generated at: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo "> Related ID: ${QA_RELATED_ID}"
echo "> Engine: stack detected in §1"
echo
# ... paste complete FAIL template OR §3 PASS template content here ...
} | che write_file_atomic "$QA_REPORT_PATH"
Then append 1 audit trail line:
che decision_append "$WORKTREE_ROOT" "QA_RUN" "{\"related_id\":\"${QA_RELATED_ID}\",\"task_id\":\"${TASK_ID}\",\"passed\":${QA_PASSED:-false},\"report_path\":\"${QA_REPORT_PATH}\",\"evidence_dir\":\"${QA_EVIDENCE_DIR}\"}"
0.2 FINAL MANDATORY STEP — Generate Evidence Manifest SHA256 (ONDA4 Local 2 Workspace Audit)
After writing report and audit log, before returning to SM, generate Local 2 JSON manifest ($WORKSPACE_EVIDENCE_AUDIT_DIR created in §-0.1.1):
# (a) Calculate SHA256 for EACH test file changed in this task's diff
declare -A PER_TEST_FILE_SHA
while IFS= read -r f; do
[ -f "$f" ] || continue
sha="$(sha256sum "$f" | awk '{print $1}')"
PER_TEST_FILE_SHA["$f"]="$sha"
done < <(cd "$WORKTREE_ROOT" && git diff --name-only HEAD -- '*.test.*' '*.spec.*' '__tests__/**' 2>/dev/null || true)
# (b) Calculate SHA256 for EACH generated evidence file (Local 1 Session)
declare -A PER_EVIDENCE_SHA
while IFS= read -r ev; do
[ -f "$ev" ] || continue
rel_ev="${ev#$SESSION_EVIDENCE_DIR/}"
sha="$(sha256sum "$ev" | awk '{print $1}')"
PER_EVIDENCE_SHA["$rel_ev"]="$sha"
done < <(find "$SESSION_EVIDENCE_DIR" -type f 2>/dev/null || true)
# (c) Build JSON manifest (inline — minimum deps; use jq if available, else printf)
GENERATED_AT_UTC="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
MANIFEST_TMP="$(mktemp)"
{
printf '{\n'
printf ' "generated_at_utc": "%s",\n' "$GENERATED_AT_UTC"
printf ' "commit_7char": "%s",\n' "$CURRENT_COMMIT_7CHAR"
printf ' "session_id": "%s",\n' "${SESSION_ID:-qa-standalone}"
printf ' "qa_run_passed": %s,\n' "$( [ "${QA_PASSED:-false}" = "true" ] && echo true || echo false )"
printf ' "related_id": "%s",\n' "$QA_RELATED_ID"
printf ' "per_test_file_sha256": {'
first=1
for k in "${!PER_TEST_FILE_SHA[@]}"; do
[ $first -eq 0 ] && printf ','
printf '\n "%s": "%s"' "$k" "${PER_TEST_FILE_SHA[$k]}"
first=0
done
[ $first -eq 0 ] && printf '\n '; printf '},\n'
printf ' "per_evidence_sha256": {'
first=1
for k in "${!PER_EVIDENCE_SHA[@]}"; do
[ $first -eq 0 ] && printf ','
printf '\n "%s": "%s"' "$k" "${PER_EVIDENCE_SHA[$k]}"
first=0
done
[ $first -eq 0 ] && printf '\n '; printf '},\n'
printf ' "per_behavior_result": %s,\n' "${PER_BEHAVIOR_RESULT_JSON:-{\}}"
# (d) Thumbnail ≤200KB: copy last Playwright/UI screenshot final, resize
THUMB_FINAL=""
LAST_SCREENSHOT="$(find "$SESSION_EVIDENCE_DIR/screenshots" -type f \( -iname '*.png' -o -iname '*.jpg' -o -iname '*.jpeg' \) 2>/dev/null | sort | tail -1 || true)"
if [ -n "$LAST_SCREENSHOT" ] && command -v convert >/dev/null 2>&1; then
THUMB_OUT="$WORKSPACE_EVIDENCE_AUDIT_DIR/commit-${CURRENT_COMMIT_7CHAR}-thumbnail-final.jpg"
convert "$LAST_SCREENSHOT" -resize 800x -quality 75 "$THUMB_OUT" 2>/dev/null
THUMB_SIZE="$(stat -c%s "$THUMB_OUT" 2>/dev/null || echo 999999)"
if [ "$THUMB_SIZE" -gt 204800 ]; then
convert "$THUMB_OUT" -resize 640x -quality 65 "$THUMB_OUT" 2>/dev/null || true
fi
THUMB_NEW_SIZE="$(stat -c%s "$THUMB_OUT" 2>/dev/null || echo 999999)"
[ "$THUMB_NEW_SIZE" -le 204800 ] && THUMB_FINAL="$(basename -- "$THUMB_OUT")"
fi
if [ -n "$THUMB_FINAL" ]; then
printf ' "thumbnail_path": "%s"\n' "$THUMB_FINAL"
else
printf ' "thumbnail_path": null\n'
fi
printf '}\n'
} > "$MANIFEST_TMP"
# (e) Calculate SHA256 OF THE MANIFEST ITSELF (for filename + integrity)
MANIFEST_SHA="$(sha256sum "$MANIFEST_TMP" | awk '{print $1}')"
MANIFEST_SHORT_SHA="${MANIFEST_SHA:0:16}"
MANIFEST_FILENAME="evidence_manifest_${MANIFEST_SHORT_SHA}.json"
MANIFEST_FINAL_PATH="$WORKSPACE_EVIDENCE_AUDIT_DIR/$MANIFEST_FILENAME"
cat "$MANIFEST_TMP" | che write_file_atomic "$MANIFEST_FINAL_PATH"
rm -f "$MANIFEST_TMP"
# (f) ONDA4 Decision log entry with hash + paths
che decision_append "$WORKTREE_ROOT" "QA_EVIDENCE_MANIFEST" "{\"commit_7char\":\"${CURRENT_COMMIT_7CHAR}\",\"manifest_sha256\":\"${MANIFEST_SHA}\",\"manifest_path\":\"${MANIFEST_FINAL_PATH}\",\"workspace_audit_dir\":\"${WORKSPACE_EVIDENCE_AUDIT_DIR}\",\"session_evidence_dir\":\"${SESSION_EVIDENCE_DIR}\"}"
# (g) EXPORT to QA success report template (§3):
QA_EVIDENCE_MANIFEST_SHA="$MANIFEST_SHA"
QA_EVIDENCE_MANIFEST_PATH="$MANIFEST_FINAL_PATH"
1. STEP 1 — STACK AUTODETECTION
Scan worktree and build a Stack Profile (in-memory, then to report):
1.1 Language / Runtime signals
| Signal | Indicates |
|---|---|
package.json exists + has deps |
JavaScript / TypeScript project |
tsconfig.json exists |
TypeScript → needs typecheck |
Cargo.toml |
Rust → cargo test, cargo clippy |
go.mod |
Go → go test, go vet |
pyproject.toml OR requirements.txt |
Python → pytest / ruff / mypy |
Makefile |
Run whatever targets are defined |
justfile |
just <target> |
nx.json |
Nx monorepo → use Nx commands |
pnpm-workspace.yaml |
pnpm monorepo |
turbo.json |
Turborepo → turbo run <target> |
1.2 Framework / Build signals
| Signal | Commands to try |
|---|---|
next.config.* |
Next.js → next build, next lint |
vite.config.* |
Vite → vite build |
angular.json |
Angular CLI → ng build, ng test |
nest-cli.json |
Nest → nest build |
playwright.config.* |
Playwright E2E available (DO NOT run per-task unless task is E2E) |
cypress.config.* |
Cypress E2E |
1.3 Linter / Formatter signals
| Signal | Tool |
|---|---|
biome.json OR biome.jsonc |
Biome → biome check <files>, biome lint <files> |
.eslintrc* OR eslint.config.* |
ESLint → eslint <files> |
.prettierrc* |
Prettier (formatting check only per-task) |
ruff.toml OR pyproject.toml has [tool.ruff] |
Ruff |
.clippy.toml OR Rust project |
cargo clippy |
.golangci.yml |
golangci-lint |
1.4 Test runner signals
| Signal | Runner |
|---|---|
vitest.config.* OR package.json has vitest devDep |
Vitest → vitest run |
jest.config.* OR package.json has jest |
Jest → jest |
Cargo.toml with [dev-dependencies] |
Rust → cargo test |
go.mod |
Go → go test ./... |
pyproject.toml has pytest |
Python → pytest -q |
nx.json + project.json files |
nx affected:test --tui false or per-project target |
2. STEP 2 — EXECUTION ORDER (DO NOT skip or reorder)
The execution pipeline. Each stage FAILS → report immediately → DO NOT run later stages.
All commands run from WORKTREE_ROOT.
2.0 PREP: narrow down scope per task
Build an --affected / scoped list if possible:
- Nx monorepo: Prefer
nx affected:<target> --tui falseover running the whole repo. - Turbo:
turbo run <target> --filter=...[HEAD~1]or similar. - Single repo: Target modified files for lint/typecheck; run test file(s) touching them.
2.1 Stage A — Build (compile / type-level validation)
Goal: catch type and syntax errors first.
Priority order (pick FIRST that applies):
- Monorepo build of affected packages:
- Nx:
corepack pnpm nx run-many -t build --tui false --projects=<affected-projects> - Turbo:
corepack pnpm turbo build --filter=...[HEAD~1]
- Nx:
- Single project build:
- JS/TS:
corepack pnpm buildorcorepack pnpm -C <subdir> build - Rust:
cargo build --tests - Go:
go build ./... - Python:
python -m compileall src/
- JS/TS:
FAIL report format if build fails:
Stage A — BUILD FAILED
Error summary (from compiler output, first 20 lines only):
- <file>:<line>: <message>
- <file>:<line>: <message>
Root cause hypothesis: <short description in English if obvious, else "unknown">
Action needed for Dev: <specific fix direction>
2.2 Stage B — Lint / Formatting check
Goal: catch style, security-lint, dead code, and PII-lint issues.
Priority (pick FIRST that applies):
- Biome:
corepack pnpm biome check <scoped files or entire project>- If fails → also run
corepack pnpm biome lint
- If fails → also run
- ESLint + Prettier:
corepack pnpm eslint <scoped files>corepack pnpm prettier --check <scoped files>
- Rust:
cargo clippy -- -D warnings - Python:
ruff check <scoped dirs> - Go:
go vet ./... - Generic: check for
lintscript in package.json / Makefile → run it.
FAIL report format if lint fails:
Stage B — LINT FAILED
Rule violations (grouped by file, 20 max):
- <file>:<line>:<rule_id>: <message> (severity: error/warn)
Suggested auto-fix command:
- <exact command if applicable, e.g. "corepack pnpm biome check --apply">
2.3 Stage C — Typecheck (if language has type system)
Only if applicable (TS/Rust with strict, Python with mypy etc.). DO NOT rely on build alone — typecheck stage is explicit.
Priority:
- TypeScript:
corepack pnpm tsc --noEmit(or monorepo per-project:nx run-many -t typecheck --tui false) - Python (if mypy configured):
mypy <scoped files> - Rust: covered by build + clippy.
FAIL report format:
Stage C — TYPECHECK FAILED
Errors (grouped by file, top 30):
- <file>:<line>: <TS error code & message>
2.4 Stage D — Unit & Integration Tests
Goal: catch behavior regressions. DO NOT run full E2E per task (too slow).
Per-task scoping rules:
- Only run tests affected by changed files.
- Pure function / domain changes: unit tests first.
- API route / service changes: integration tests second.
- Full suite only if scope very small (≤ 3 files) OR explicitly told by SM.
Priority:
- Vitest:
- Scoped:
corepack pnpm vitest run <pattern matching changed files or their spec files> - Fallback:
corepack pnpm test
- Scoped:
- Jest: similar pattern
- Nx:
corepack pnpm nx affected:test --tui false - Rust:
cargo test(if changed crates only:-p <crate>) - Go:
go test <scoped packages> - Python:
pytest -q <scoped paths> - Generic: look for
test/test:unitscript → run it.
FAIL report format:
Stage D — TEST FAILED
Failing spec files / test cases (top 20):
- <file>: <test suite name> > <test case>
Expected: <expected>
Received: <received>
Error: <first line of stack>
Reproduction command:
- <exact command Dev can run to re-trigger failing test>
2.5 Stage E — Test Naming Behavioral Lint (Che RULE 7.9)
Goal: ensures describe() / it() / test() describe OBSERVABLE BEHAVIOR, not internal IDs. Runs ONLY if *.test.*, *.spec.* or __tests__/ modified.
🔴 HARD RULE — PROHIBITED INVERSION (NEVER do this):
❌ WRONG: Report WARN / FAIL because a test title DOES NOT CONTAIN
FLO-xxx/T<N>/AC<N>. ✅ CORRECT: Having these IDs IN THE TITLE is BAD = finding. NOT HAVING THEM and describing behavior is GOOD = compliant = NEVER report finding for missing ID.1-sentence decision:
title contains FLO-ID → BAD finding. title does NOT contain FLO-ID → GOOD (no finding).Correct traceability (always OK): JSDoc comment/** @ticket FLO-714 · @ac 3.1 */ABOVE block OR// @ticket FLO-714 | @ac 3.1 | @task T1.21st line INSIDE block.
What to detect (regex ONLY IN TITLE STRING — comments ignored):
Scan string passed to describe(...), it(...), or test(...):
- Ticket IDs in TITLE:
FLO-\d+,[A-Z]{2,}-\d+ - Task/item IDs in TITLE:
Task?\s*T\d+(\.\d+)?,Item\s*\d+ - AC/section IDs in TITLE:
AC\s*\d+,§\s*\d+(\.\d+)?,REGRA\s*\d+,SPEC[_-]\w+ - Phase/story IDs in TITLE:
Fase\s*\d+,Story\s*#?\d+,PRD\s*§
How to scan (simplest possible — grep -E):
cd <WORKTREE_ROOT>
git diff --cached --unified=0 -- <changed spec files> | grep -E '^\s*\+' \
| grep -Eo '\b(describe|it|test)\s*\(\s*["'\''"][^"'\''"]{1,240}["'\''"]' \
> /tmp/qa-test-names.txt 2>/dev/null || true
Then for each title found check anti-patterns ABOVE only. Ignore JSDoc + body comments.
Report output format (WARNINGS only when BAD titles PRESENT — NEVER flag "missing FLO prefix"):
Stage E — TEST NAMING (RULE 7.9)
Total test titles scanned: 42
Behavioral (good, NO internal IDs in TITLE): 40
Bad titles detected (WARNING — internal IDs IN TITLE STRING): 2
- <file>: it("Task T2.3 — validates AC 4.2 refund") — BAD in TITLE: contains "Task T2.3" and "AC 4.2"
- <file>: describe("FLO-513 refund process") — BAD in TITLE: contains "FLO-513"
Fix guidance: rename TITLES to describe BEHAVIOR only. Keep traceability links (@ac / @task / @ticket) in JSDoc comment above or 1-line comment inside block.
3. STEP 3 — IF ALL STAGES PASS — Success Report
QA RESULT: PASS (task <TASK-ID>)
Stack profile detected:
- Runtime: <e.g. TS/Node 22, pnpm 9 via corepack>
- Build: <Nx run-many build>
- Lint: <Biome 1.9>
- Typecheck: <tsc 5.6 --noEmit>
- Tests: <Vitest 4.0 — 42 files, 318 tests>
- Test naming lint (Stage E): 42 scanned; bad=0 ✔
Stages executed:
- [x] Build (0 errors, 0 warnings)
- [x] Lint (0 errors, 3 warnings — cosmetic, accepted)
- [x] Typecheck (0 errors)
- [x] Tests (0 failed; coverage diff: +0.4% lines)
- [x] Test naming (Stage E: clean or <N bad names → <N> warnings)
Warnings for Dev to consider (non-blocking):
- <list non-blocking issues, e.g. unused variable, TODO comment>
- <if Stage E bad names: repeat list here as warnings>
Evidence retention (ONDA4):
- Workspace audit manifest SHA256 = <QA_EVIDENCE_MANIFEST_SHA>
- Manifest JSON path = <QA_EVIDENCE_MANIFEST_PATH>
- Session full evidence (TTL 30d) = $CHE_SESSION_DIR/qa/T${TASK_ID}-${TASK_SLUG}/evidence/
Approved for Compliance stage.
4. STEP 4 — Return to Scrum Master
Always return:
- Structured report (FAIL templates or Success above).
qa_passed: true/falseboolean.- (If failed) A numbered, actionable item list for Developer.
DO NOT fix code directly. QA only reports; Developer fixes.
Appendix A: Rule when you DON'T know how to proceed
If after Step 1 (stack detection) you still cannot figure out: build command, test runner, or how to scope to affected files:
STOP → go back to Scrum Master → who will ASK user directly.
DO NOT guess. NEVER run a command that could mutate worktree (e.g. npm install, biome check --apply) without explicit confirmation.
Appendix B: Commands you MUST NEVER run
- Any command writing secrets or printing long env vars to stdout
rm -rfoutside a temp dir you created- DB migrations / seed commands against production-looking URLs (block if host contains
prod,rds.amazonaws,supabase.co,cockroachlabs.cloud,neon.tech) - Deploy commands (
vercel deploy,railway up,kubectl apply) - Git commands rewriting history (
git push --force,git rebase)- Exception:
git status,git diff,git log(read-only) are fine.
- Exception: