integrate
Workflow Phase 9 — Integration and Verification. Runs the full test suite, stamps the binding verdict at .claude/state/last_test_result, optionally runs a cross-engine smoke check, and writes harness_
SKILL.md.claude/skills/integrate/SKILL.md
GitHub<!-- character:begin -->
Character
- Soul. The one who reads the verdict out loud, unchanged, whoever it disappoints.
- Motivation. Every gate downstream trusts this stamp. A verdict bent once is a verdict nobody can rely on again.
- Mantra. I never relax the criteria to make a run pass. A red suite is reported red, in its own words.
- Temperament. Impartial to the point of coldness and comfortable carrying bad news. Unmoved by how close the run came, with no stake in the outcome and every stake in the reading.
- Voice. Reports the result in the suite's own words, failure output included. No softening adverb, and no "only" in front of a failure count.
- Resolve. Every gate after me trusts this stamp. Bend it once and it is worth nothing again.
Checker config (tier-dial:read-path): this checker's floor/ceiling come from the tier dial at
.claude/hooks/lib/tier-dial.mjsviaresolveCheckerThreshold('ac-conformance'). Advisory only this slice (v1 piece 2); blocking is piece 5.
Prereq
simplify in completed AND (security in completed OR security in exceptions).
Steps
Verify prereq. Read
.claude/state/workflow.jsonand confirm.Run the binding test command and stamp the verdict (inlined verify). This used to be a
verifyskill invocation; the four mechanical operations are inlined here per.claude/skills/verify/SKILL.md(the contract doc). In order:- Read
.claude/project.json→test.cmd. If absent or empty, the verdict isFAILwith reason "project.json not configured" and step 2 stops with that verdict. - Run the command via Bash from the project root. Capture stdout, stderr, and exit code. Do not retry. Do not pass
{file}placeholders — integrate always runs the full suite. - Apply verdict rules:
PASSiff exit code 0 AND at least one test executed AND no failed/errored test; otherwiseFAIL. A timeout is FAIL. A killed process is FAIL. - Atomically write
.claude/state/last_test_resultwith exactly four lines plus a single trailing newline:
The<PASS|FAIL> <ISO-8601 UTC timestamp> <exact command> <exit code>verify_pass_guardhook reads line 1 as the binding verdict. - Emit the human-readable inline report (Verdict, Command, Exit code, Output tail) alongside the statefile write.
- Read
Read line 1 of
.claude/state/last_test_result.- PASS → continue to step 4.
- FAIL → stop. Do not proceed to documentation or commit. Surface the verdict's reason and the output tail to the user. Write
harness_stateper step 5 withstate: "yielded"andreason: "integrate failed: <one-line summary>; needs user decision". Together with the user, decide whether to loop back to/tdd(mechanical bug) or escalate to a spec change.
3.5. Code-review fan-out (D8a — enforcement oracle framework). After the binding PASS and before the smoke check, run the code-review checkers over the branch diff — this is the point where all three inputs exist (the docs/security/<slug>-*.md report from Phase 8, the /simplify verdict table from Phase 7, and code-structure) and the diff is final. Assemble ctx = {slug, rootDir, diffContent, securityReport, simplifyTable} and call runCheckerFanout({slug, rootDir, enabled, phase: 'code-review', ctx}) from .claude/skills/harness/checker-fanout.mjs. changedFiles is built by the helper, not by you — assembleContext in .claude/skills/harness/assemble-context.mjs supplies it when ctx does not, and the merged verdict carries inputState (measured when files were examined, no-input when none were). Its element type is {path, content, prior} — path is repo-relative, content is the working-tree bytes, and prior is the same file at HEAD or null when this change created it. State it rather than assume it: the paragraph named changedFiles without naming its element type, main context guessed per run, and code-structure and backlog-deferral both read file.content off a bare string and went vacuous. assertChangedFilesShape throws on a non-conforming element before any code-review checker runs. This paragraph used to ask main context for changedFiles and no helper produced it, so every archived checker-fanout-code/*.json read {"findings": [], "verdict": "CLEAN"} — the checkers had never run against real input, and a CLEAN from no input was spelled exactly like a CLEAN from a real one. Read inputState before trusting a clean verdict. It writes the parallel projection at .claude/state/checker-fanout-code/<slug>.json — never the gate-A .claude/state/checker-fanout/<slug>.json. Read the merged verdict: on BLOCKED, surface the BLOCKER findings to the user and do not proceed to commit (a code-review BLOCKER blocks the landing, per D8); on CLEAN, continue. Gated by velocity.code_review.enabled (fail-open: disabled/absent → skip this step). Checkers are mechanical .mjs run via Promise.all — no subagent (§II.A clause 6). Two non-UI oracle adapters (C5) also ride this phase, both gated off by default: mutation-score (velocity.mutation_oracle.enabled) grades test-suite strength from a score supplied by a runner injected via ctx.oracleRunner (the mutation engine is a dev-only tool, never shipped; on the baseline's own tree main context wires the runner in when the flag is on), and ac-conformance (velocity.ac_conformance.enabled) flags any approved-spec AC not referenced in the diff. Both are fail-open (disabled / no target-or-runner / no spec → no findings), so with the flags off the merged verdict is byte-unchanged. Verdict severity is binary on the tier-dial floor — BLOCKER when the threshold is mandatory, else ADVISORY (ceiling is a rounds count, not a score band).
Cross-engine smoke (optional, fires only if applicable). Run this step only if all of the following hold:
playwrightis declared in.mcp.json(check before invoking — never hallucinate the tool).- The project has a frontend surface (a
package.jsonwith a dev/start script, ordocs/site/, or a similar live-rendered target). - The diff under review touched at least one file that affects the rendered UI (anything under
src/,app/,pages/,components/,docs/site/, or styles/templates referenced from those).
When all three hold:
- Start the dev server in a background Bash with a known port.
- For each engine in
chromium,webkit,firefox(skip engines outside the project's declared browser-support floor):mcp__playwright__browser_navigateto the project's primary surfaces (home, plus any route the diff touched).mcp__playwright__browser_snapshotfor the accessibility tree; check for unexpected[disabled]/[hidden]regressions vs the prior snapshot if one exists atdocs/site/_visual/<slug>/<engine>.snapshot.json.mcp__playwright__browser_screenshotper primary surface; save todocs/site/_visual/<slug>/<engine>-<route>.png.
- Surface any console errors, 4xx/5xx network responses, or accessibility-tree diffs as integrate findings — these don't block the verify verdict but the user must triage before
/commit. - Stop the dev server.
Skip silently if any of the three conditions don't hold. Don't surface a "no playwright" warning on every run — backend-only repos shouldn't see noise.
Marker op FIRST, then write harness_state. Append
"integrate"toworkflow.json → completed. Then:- On
state: "continue"(more phases remain):echo "<slug>" > .claude/state/.harness_activeto refresh the active marker, then write.claude/state/harness_statewith{state: "continue", slug, reason}. The next pending non-excepted phase fires on the same turn via the Stop hook. - On
state: "yielded"(line 1 oflast_test_resultwas FAIL — handled in step 3):rm -f .claude/state/.harness_active, then writeharness_statewith{state: "yielded", slug, reason}. The state file carries exactly three keys; nowritten_at, notick_count. Tell the user: "Tests green<.|, with cross-engine smoke clean / N findings to triage>. Next:/document."
- On
Constraints
- Always run the full suite at this phase — not affected-tests-only. The inlined verify operations run the full project test.cmd.
- Always preserve the four-line
last_test_resultbyte format. Theverify_pass_guardhook depends on it. - Do not modify code here. If integrate fails, the fix happens in
/tdd(bug) or via a spec change (design). - Cross-engine smoke is opt-in via
.mcp.jsonpresence. Do not install playwright as a side effect of running this skill; the user (or/init-project) controls whether playwright is declared. - Cross-engine findings are advisory. They surface to the user; they do not flip the verify verdict from PASS to FAIL.
- Always write
harness_statebefore the terminal message. The Stop hook reads it to drive auto-continuation; integrate's terminal message ends the turn. - State-write discipline (binding — see
.claude/CONSTITUTION.md§2 "State-write discipline").last_test_result,harness_state, and.harness_activeare Tier 2 workflow state — not consent paths. Prefer the Write tool for thelast_test_resultandharness_statewrites; the marker refresh uses a builtin redirect and the marker delete usesrm -f(the sole external-binary exception). Never useteeorsed -i, and never resolve paths withdirname/basename/[ -f ](PATH-fragile) — use Read/Glob.