Step -1 — Resolve where the rule books and conventions come from
This skill runs with or without a prior setup-test-context.
Resolve the plugin templates root once, unconditionally — you also pass it to every subagent (audit, execute, add, and both verifiers), because subagents cannot resolve it themselves. The bundled rule books sit two directories above this SKILL.md, under resources/templates. Prefer bash injection at load time:
!echo "${CLAUDE_SKILL_DIR}/../../resources/templates"
Call the result PLUGIN_TEMPLATES. If that line did not expand to a real absolute path (it still shows a literal ${CLAUDE_SKILL_DIR}), run echo "$CLAUDE_SKILL_DIR/../../resources/templates" with the Bash tool; if $CLAUDE_SKILL_DIR is empty too, ask the user for the test-authoring plugin install path. The Read tool normalises the ../.. segments.
If it still cannot be resolved, degrade — loudly — rather than stopping. The sibling-learning path does not depend on the plugin, so tests can still be written; what is lost is the rule books, and that loss must be visible rather than silent. Print this as prose in your reply, not merely as reasoning, so it lands in the transcript and the dashboard:
⚠ Rule books unreachable — running in DEGRADED mode.
Could not resolve the test-authoring plugin path, so the shared rule books are not
loaded: fix rules, the verifier's check sequence, the fix protocol, SUT analysis.
Tests will still be written from the nearest sibling, but the anti-gaming guardrails
and the full independent-verifier sequence are NOT in force. Review the output
yourself, and re-run once the plugin path resolves.
Then carry on, and pass every subagent a fallback_rules block in place of plugin_resources_path, carrying the non-negotiable core inline:
fallback_rules: |
- **NEVER** weaken an assertion to make a test pass
- **NEVER** delete a test case that fails — fix the root cause or report it as failed
- **NEVER** add skip/ignore attributes or comment out a test to bypass a failure
- **NEVER** change the SUT (source code) to make tests pass
- If a test fails after **2 fix attempts**, report it as `failed` — do not keep weakening it
- The nearest sibling test is the only convention source. Where none exists, report the gap
and write nothing — never infer conventions from what the language usually does.
Degraded mode is for an environment failure only. It is not licence to omit plugin_resources_path when you did resolve it: a subagent that receives neither field stops, and that stop is a caller bug.
Two kinds of file, resolved differently:
- Rule books. Every
<PLUGIN_TEMPLATES>/rules/…and<PLUGIN_TEMPLATES>/shared/…path below is literal — read it from there. Inside a rule book, a bare filename means a sibling rule book in that samerules/directory, and a../shared/<f>path is relative to it. Nothing writes any of them into a repo, so there is no per-repo copy to prefer and none to fall out of date. Read each lazily, at the step that uses it — never as an upfront batch (see "Orchestrator reading list"). - Conventions.
.claude/conventions/tests/…is the repo's own cache, written only wheresetup-test-contexthas run. Treat every one as optional: prefer the nearest sibling test for the scope (the audit's top-priority source anyway); when no sibling exists either, the writer reports the gap rather than inventing conventions — there is no language baseline to fall back to. Infer the target test project from siblings per Step 1.5. A missing conventions file is never fatal.
If .claude/conventions/tests/project-architecture.md is absent, say so once: "No cached repo profile — deriving from siblings. Run /test-authoring:setup-test-context once to cache the repo cross-layer test map." Then carry on — it blocks nothing.
Detect once, reuse this session: the language, and the executable build/test invocation form (test-project path + filter syntax) from the project manifest. test-rules.md carries no command list — the detected form is the only source, used everywhere (audit test-run, execute build, both verifiers' build, the final multi-agent build). Integration may span several test projects (Step 1.5): instantiate the form per target test project and pass each spawn the command for ITS project as build_test_command; subagents adjust its --filter to the actual test class.
Orchestrator reading list (context discipline). Load into the main context only what this orchestrator itself needs, when it needs it:
- Now:
<PLUGIN_TEMPLATES>/rules/common-orchestrator-flow.md. - At the step that uses it: Step 1 →
<PLUGIN_TEMPLATES>/shared/scope-resolution.md. Step 3 (an audit issue cites the Source-change advisory) or Step 5a (the fullphase: executestructure beyond the inlined block) →<PLUGIN_TEMPLATES>/rules/common-update-instructions.md, and only its orchestrator-facing sections ("Phase 2 invocation contract", the advisory) — the Phase 1 audit and Phase 2 execute procedure bodies are the update-writer's own rule book. Final multi-agent build →<PLUGIN_TEMPLATES>/rules/test-rules.md(use the session-detected per-projectbuild_test_command). First verifier finding or attributable build failure →<PLUGIN_TEMPLATES>/rules/fix-protocol.md. A writer stopping on missing framework source →<PLUGIN_TEMPLATES>/rules/sut-analysis.md→ "Runtime resolution flow". A writer stopping on no convention source →<PLUGIN_TEMPLATES>/rules/common-orchestrator-flow.md→ "Writer stop on no convention source". - Never:
common-writer-instructions.md,common-verifier-checks.md,test-writer-rules.md. They are subagent rule books — the writers/verifiers read them in their own isolated contexts; preloading them here only bloats the main context.
Update Integration Tests
You are the orchestrator for integration test maintenance. Your job is to audit existing tests, present findings, and then delegate changes derived from the audit status to subagents (no confirmation gate — git is the rollback). Follow the universal flow in <PLUGIN_TEMPLATES>/rules/common-orchestrator-flow.md; this file only documents integration-specific pieces.
Every
<PLUGIN_TEMPLATES>/…and.claude/conventions/tests/…read below follows Step -1's resolution — and happens lazily, at the step that uses it, never as an upfront batch; a body reference to one of these files at a step IS that step's read instruction: Read the file before acting on it, never from memory of its name. Passplugin_resources_pathandbuild_test_command(the latter instantiated per target test project) into every subagent spawn — audit, execute, add, and both verifiers — they cannot resolve these themselves. All<plugin-root>/resources/static/status-legend.mdreferences below resolve to<PLUGIN_TEMPLATES>/../static/status-legend.md(Step -1), or plain text status labels when Step -1 could not resolve it.
CRITICAL — Deletion safety: deletions and rewrites are driven by the audit status (not a user gate) and applied automatically. A test may be deleted only when the audit classified it
wrongorduplicated— never whenvalidoroutdated-major(an outdated-major test still carries intent worth preserving: it is rewritten, never deleted). Every action is recorded in an action record and passed totest-authoring:verify-update-integration-test-agent, which independently re-checks each deletion againstgit show HEAD:<file>. Git is the safety net: a tracked test file can be restored withgit restore.
Step 1 — Identify Scope
Follow the procedure in <PLUGIN_TEMPLATES>/shared/scope-resolution.md.
- Mode A (no argument): Use git diff. Focus on modified API endpoints (controllers, routes), command/query handlers, worker operations or event consumers, sync consumers, and changes to persistence logic.
- Mode B (argument provided, e.g.,
/test-authoring:update-integration-test ComponentName): Resolve by directory, component, class, endpoint, or file name.
Step 1.5 — Determine Test Project Mapping
Before auditing, determine which test project each source file maps to. Infer it from siblings — nothing generates a per-repo test-project mapping, so this is the only route: infer the target test project(s) from siblings — locate the integration test project(s) whose tests mirror the source area; if several exist and none clearly mirrors the source, ask the user rather than guess. If a single source file maps to multiple test projects (e.g., both API and worker), audit each (source, project) pair separately — one Phase 1 agent per pair, so neither project's stale tests are missed.
Step 2 — Audit via Agent
Spawn test-authoring:update-integration-test-agent — one per (source, project) pair from Step 1.5 (a multi-project source class gets one audit agent per pair), all in parallel. Phase 1 (audit) only — agents return structured audit output and terminate.
Parallel audits may contend on the shared test project build and container resources (Docker, ports) during the audit's test-run step. If an audit reports a build failure or env_failure that looks like contention rather than a real break, re-run that audit serially before trusting the result.
Retain each agent's audit output — Phase 2 in Step 5a is a fresh Agent spawn whose prompt carries the audit record forward (the orchestrator does not continue a live Phase 1 instance).
Agent(subagent_type="test-authoring:update-integration-test-agent"):
Audit existing integration tests for:
- <source file path>
Target test project: <path from Step 1.5>
Plugin context (always — the subagent cannot resolve either of these itself):
plugin_resources_path: <PLUGIN_TEMPLATES>
build_test_command: <build/test command for this Target test project>
Endpoint-scoped
When the user specifies an endpoint, include it in the agent prompt as "Focus only on ".
Step 3 — Present Audit Summary
Collect audit results and present a structured summary. Group by source class.
Rendering rule (MUST): the Test Audit section MUST be rendered as a single markdown table — never as a numbered list, bullet list, or separator-bar format (e.g.,
────). Missing coverage items are appended as rows with status🟦 pending(per<plugin-root>/resources/static/status-legend.md(plugin-internal controlled vocabulary)), continuing the#numbering.
Test Audit: {ClassName} (integration)
| # | Method / Endpoint | Status | Confidence | Description |
|---|---|---|---|---|
| 1 | <TestMethod> |
🟩 valid | — | matches current SUT logic |
| 2 | <TestMethod> |
🟨 outdated-major | high | |
| 3 | <TestMethod> |
🟨 outdated-minor | high | |
| 4 | <TestMethod> |
🟪 duplicated | medium | overlaps with #2 |
| 5 | <endpoint> |
🟦 pending | — | no test covers this (to add) |
Status legend: <plugin-root>/resources/static/status-legend.md (plugin-internal controlled vocabulary). Statuses used: 🟩 valid, 🟨 outdated-minor, 🟨 outdated-major, 🟥 wrong, 🟪 duplicated, 🟦 pending.
Confidence legend:
- high — clear structural evidence
- medium — requires behavioural analysis, review recommended
- low — subjective assessment, review carefully before confirming
Pre-change Test Results
Render as a single markdown table. Use only icons from <plugin-root>/resources/static/status-legend.md (plugin-internal controlled vocabulary) — env_failure maps to 🟨 per the legend's "Warning" definition.
| # | Test Method | Status | Notes |
|---|---|---|---|
| 1 | <TestMethod> |
🟩 pass | baseline |
| 2 | <TestMethod> |
🟥 fail | pre-existing (inspect before update) |
| 3 | <TestMethod> |
🟨 env_failure | Docker / Testcontainers / external dep unavailable — not the writer's fault |
Audit Issues
Surface any issues: entries from the audit records verbatim — in particular the source-change advisory (the audit detected that test staleness comes from uncommitted source changes; see <PLUGIN_TEMPLATES>/rules/common-update-instructions.md → "Source-change advisory"). The advisory is informational — execution proceeds without a gate — but surfacing it now lets the user interrupt and commit/stash the source, keeping a single coherent git baseline for rollback.
Step 4 — Determine Actions (from audit status)
Derive each item's action from its audit status — there is no user gate:
- 🟨
outdated-major→ Update (rewrite) - 🟨
outdated-minor→ Update (tweak) - 🟥
wrong→ Update (or Delete if the test asserts behaviour the SUT no longer has and no corrected assertion is meaningful) - 🟪
duplicated→ Delete (the surviving duplicate stays) - 🟦
pending→ Add - 🟩
valid→ no change (never modified or deleted)
Rendering rule (MUST): present the planned actions as a single markdown table — never bracket codes. Use only icons from
<plugin-root>/resources/static/status-legend.md(plugin-internal controlled vocabulary). Action verbs (Update / Tweak / Delete / Add / —) stay plain text. This table is the audit trail for the summary, not a gate — execution proceeds without waiting for a reply.
Proposed Actions
| # | Item | Action | Audit Status | Confidence | Notes |
|---|---|---|---|---|---|
| 2 | <Test> |
Update (rewrite) | 🟨 outdated-major | high | |
| 3 | <Test> |
Update (tweak) | 🟨 outdated-minor | high | |
| 4 | <Test> |
Delete | 🟪 duplicated | medium | overlaps with #2 |
| 5 | <endpoint> |
Add | 🟦 pending | — | no test covers this |
| 1 | <Test> |
— | 🟩 valid | — | no change |
Flag any low/medium-confidence action in the Notes column so the user can review it post-run (the summary is where they catch a mis-classified action and git restore it).
Build Action Record
Build a structured action record with audit_status, confidence, and action for each item. This drives Phase 2 and is the baseline the verifier checks deletions against — a deletion whose audit_status is anything other than wrong or duplicated is a violation.
Step 4.5 — Pre-write git safety check
Git is the backup — there are no .bak files. Before executing changes, check each test file that will be modified:
git status --porcelain -- <test-file>
- Tracked and clean (no output) → proceed.
git show HEAD:<test-file>is the faithful pre-change baseline the verifier diffs against, andgit restore <test-file>undoes the change. - Untracked, or has uncommitted modifications (any porcelain output) → warn the user: this file has no reliable committed baseline, so an automatic update cannot be safely diffed or restored. Ask whether to proceed for that file or skip it; proceed only on explicit confirmation.
Record, per modified file, that the pre-change baseline is git show HEAD:<file> — this is what the verifier uses in Step 6a. Record two separate lists, both of which go to the verifier in Step 6a and mean different things there: the files proceeded on by explicit consent despite being untracked/dirty (HEAD is not a reliable baseline for those), and the files the user chose to skip (their planned actions were legitimately never performed — without this list the verifier reads them as work dropped in silence). Do not merge the two.
Step 5 — Execute Changes
Before spawning the first execution agent (5a or 5b), record the pre-writer source snapshot per <PLUGIN_TEMPLATES>/rules/common-orchestrator-flow.md → "Pre-writer source snapshot" — the add-verifier needs it as the baseline for its SUT-modification check, and pass the pathspec you used alongside it as source_pathspec so they re-run the diff over the same set.
Step 5a — Update and Delete (fresh-spawn Phase 2)
Phase 2 is a fresh-spawn Agent invocation with phase: execute in the prompt. Do not attempt to continue a Phase 1 agent — every Phase 2 is a new spawn that re-reads files from the paths in the prompt. See <PLUGIN_TEMPLATES>/rules/common-update-instructions.md → "Phase 2 invocation contract" for the full structure.
Spawn one test-authoring:update-integration-test-agent per (source, project) pair whose action record has update/delete actions — a class audited as two pairs gets two Phase 2 agents, each carrying its own pair's audit record and test_project; collapsing pairs into one per-class spawn silently drops the other project's actions.
Phase 2 agents that share a test project hit the same build and container contention as Step 2's parallel audits (Docker, ports, file locks) — spawn them sequentially per test project; if a contended-looking failure still appears, re-run that agent serially before trusting the result.
Agent(subagent_type="test-authoring:update-integration-test-agent"):
phase: execute
original_scope:
source_files: [<from Step 1>]
method_filter: <if any>
test_type: integration
pre_fetch:
sibling_paths: [<from Step 2 audit>]
convention_spec: {<from Step 2 audit>}
audit_record:
<full audit output from this source class's Phase 1 agent — includes test_project>
planned_actions:
update:
- <Test>: <audit_status>
delete:
- <Test>: <audit_status>
add: [] # add actions handled in Step 5b via test-authoring:add-integration-test-agent
test_file_paths: [<from audit_record.test_file(s)>]
test_project: <from audit_record.test_project>
consent_proceeded_files: [<from Step 4.5, or empty>]
plugin_context: # always — the subagent cannot resolve either of these itself
plugin_resources_path: <PLUGIN_TEMPLATES>
build_test_command: <build/test command for THIS pair's Target test project>
Step 5b — Add Missing Coverage via test-authoring:add-integration-test-agent
After update/delete agents complete, spawn test-authoring:add-integration-test-agent for the action record's add actions — one per (source, project) pair with add actions; the Target test project comes from that pair's audit.
Agent(subagent_type="test-authoring:add-integration-test-agent"):
Generate integration tests for:
- <source path>
Cover <endpoint>: no tests exist
Target test project: <path>
Sibling tests found during audit (adopt their conventions):
- <path> (<convention spec summary>)
Plugin context (always — the subagent cannot resolve either of these itself):
plugin_resources_path: <PLUGIN_TEMPLATES>
build_test_command: <build/test command for THIS pair's Target test project>
If the audit reported no_existing_tests: true (no siblings found), omit the sibling lines and state instead: No sibling tests found and no convention source — apply test-writer-rules.md → Fallback Chain. Never invent a sibling path to satisfy the template.
Skip if the action record has no add actions.
Multi-agent build check
If multiple agents were spawned across 5a and 5b, run a final build of each affected test project. Follow <PLUGIN_TEMPLATES>/rules/test-rules.md → Build and Test Verification, using each project's build_test_command.
Step 6 — Verify
Step 6a — Verify Updates and Deletions
Spawn one test-authoring:verify-update-integration-test-agent per test project named in the action record — not per project the writer reported modifying. The two differ exactly when a writer did nothing: deriving the spawn set from what changed would leave an inert writer's project unverified, which is the failure Step 5 exists to catch. Pass, scoped to that project:
- Pre-change state (including env_failures)
- Action record (audit_status + action per item), scoped to this project — an unscoped record makes another project's planned actions read as dropped here
- Execution results — pass every Phase 2 writer's output whole for this project, one labelled set per (source, project) pair, including
changes_applied,test_filesandissues. The verifier's Step 5 pairschanges_appliedagainst the diff and needstest_filesto resolve each method to a file, so a summarised or single-writer hand-off strips exactly what it reads. On re-verification after a fix round, carry the original Phase 2 output forward alongside the fix writer's output — thefix_invocationcontract returnsfiles_modified, notchanges_applied. Scope input 2's action record down to this project before passing it, or Step 5 reads another project's planned actions as unreconciled here - Pre-change baseline:
git show HEAD:<file>for each file the action record names, not only the ones the writer reported modifying — a file with planned actions and no reported change is precisely the case Step 5 must diff, and omitting it leaves the verifier nothing to check - Test type:
integration - Test project path
- Raw Phase 1 audit outputs (retained in Step 2) — so the verifier can cross-check that the action record faithfully transcribes each audit classification
- Consent-proceeded files from Step 4.5 (untracked/dirty at check time) — their HEAD baseline is unreliable; the verifier treats diff-based findings on them as advisory, not violations
- Step 5b add-writer outputs (
files_created/files_modified/test_count), when Step 5b ran — the add writer may insert tests into the SAME files 6a inspects, and without these the verifier's test-count cross-check reads the additions as out-of-record changes - Skipped files from Step 4.5 (the ones the user declined, not the consent-proceeded list in 8) — Step 5 no longer needs this list to avoid a false violation, because a planned action nobody reported is a report there rather than a finding. Pass it anyway: it is what makes that report readable, naming which unperformed actions the user themselves declined instead of leaving a bare list of methods
- Plugin context (always):
plugin_resources_path+ thebuild_test_commandfor this project — so the verifier reads its rule books from the plugin and runs the build/test via the detected command (it cannot resolve either itself)
Step 6b — Verify Added Tests
If Step 5b produced new tests, spawn one test-authoring:verify-add-integration-test-agent. Read-only. Pass the inputs per <PLUGIN_TEMPLATES>/rules/common-orchestrator-flow.md → "Verifier spawn": the Step 5b writer outputs (including files_modified), the original task, and the pre-writer source snapshot (plus plugin_resources_path + the project's build_test_command, per the governing note).
6a and 6b can run in parallel. Skip 6a only when the action record holds no update / delete entry for that project; skip 6b if no add actions were executed. Decide 6a from the record, never from what the writer reported it executed — a writer that did nothing and reported nothing would otherwise suppress the verifier entirely, and Steps 1-4 and 6 still run against the file even when Step 5 has no claim to judge.
Do not read this as "6a catches the inert writer" — it does not, and this note used to say it did. Measured on 2026-08-11 against
test-authoring0.17.3's unit verifier, whose Step 5 is identical to the integration one: a record planning one update and one deletion, with an execution report claiming no work, returnedoverall_verdict: PASSandviolation_count: 0. Step 5's two verdicts both key on a claim, so an empty report triggers neither, and Step 6 excludes the unreported planned deletion from its subtraction so the count matches. What 6a gives you on such a run is theplanned_not_reportedobservation and nothing more. Surface it — see Step 7 — because after this point no automated check does.
Step 6c — Handle Add-Verifier Findings
If test-authoring:verify-add-integration-test-agent reports deterministic issues → fresh-spawn test-authoring:add-integration-test-agent with a fix_invocation block (re-using the prior Step 5b writer's structured output). Non-deterministic (including env_failure) → present to user; route any user-approved fix via the same fix_invocation block with findings_to_fix.user_approved_actions. The orchestrator never edits files directly.
Step 7 — Summary
Present the final summary.
Changes Applied
Use only icons from <plugin-root>/resources/static/status-legend.md (plugin-internal controlled vocabulary). Action verbs are plain text. 🟨 in the Status column indicates env_failure (Docker / Testcontainers / external dep) — not the writer's fault.
| # | Test Method | File | Action | Agent | Status | Notes |
|---|---|---|---|---|---|---|
| 2 | <Test> |
<file> |
Update | update | 🟩 | pass |
| 3 | <Test> |
<file> |
Update | update | 🟨 | env_failure (Docker unavailable) |
| 4 | <Test> |
<file> |
Delete | update | 🟩 | deletion justified by audit status |
| 5 | <new> |
<file> |
Add | add | 🟩 | pass |
Verification Results
Render as a single markdown table per verifier agent. Use only icons from <plugin-root>/resources/static/status-legend.md (plugin-internal controlled vocabulary). Last row of each table is the bold "Overall verdict". env_failure is a 🟨 warning, not a 🟥 violation.
Update verification (test-authoring:verify-update-integration-test-agent)
| Check | Result | Violations | Details |
|---|---|---|---|
| Deletion justification | 🟩 | 0 | Every deletion justified by audit status (none were valid) |
| Valid test protection | 🟩 | 0 | No valid tests were modified or removed |
| Test results | 🟩 | 0 | All tests pass (🟨 env_failures noted separately) |
| Anti-gaming | 🟩 | 0 | No failed test was deleted to make the suite pass |
| Claim vs file (existence only) | 🟩 | 0 | <rows_violation> of <rows_total> claim rows were violations, <rows_report> could not be judged |
| Test count | 🟩 | 0 | Expected <N> = actual <N> |
| Overall verdict | 🟩 | 0 | — |
Fill the Details cells from the verifier's own counters — rows_total, rows_violation, rows_ok, rows_report — never from this template's wording, which would assert a verification that may not have run. Use the field names the verifier actually emits; a render rule keyed on a name it does not emit never fires, and the row it was meant to add silently never appears.
Two 🟨 rows, because Step 5 demotes both of these to observations and a green 0 would otherwise be the only thing the human sees:
action_mismatchorunknown_action_valuesis non-empty — name the methods, the action the record planned, and the action the writer reported. These are not violations (both sides of the comparison are the writer's own documents), which is exactly why they need a row of their own.planned_not_reportedis non-empty — name the methods and the action each was planned as. This is the signal that planned work may simply not have happened, and after Step 5 no automated check covers it: Step 5 forbids a violation there, and Step 6 excludes those deletions from its count. Say plainly that the run cannot distinguish a legitimate decline from a dropped action, and leave the judgement with the human.
Add verification (test-authoring:verify-add-integration-test-agent) (only if Step 5b ran)
| Check | Result | Violations | Details |
|---|---|---|---|
| Convention compliance | 🟩 | 0 | All new tests follow conventions |
| Anti-gaming | 🟩 | 0 | No trivial assertions |
| env_failures | 🟨 | <list of tests that hit env failures, if any> | |
| Quality flags | 🟪 | <list of subjective improvement opportunities, if any> | |
| Overall verdict | 🟩 | 0 | — |
Rollback on Failure
If either verify agent reports any violations, or a test_count_check mismatch (which is a FAIL and carries its own named violation):
- Present violations prominently, naming the specific deletions / rewrites at fault.
- Offer rollback via git — for each affected tracked file,
git restore <file>returns it to the committed state. (Files flagged untracked/dirty in Step 4.5 were proceeded on with explicit consent; advise the user to inspect those manually.) - Do not auto-restore without the user's go-ahead — they may prefer to keep some changes and fix forward.
claimed_action_verification violations take a different remedy. They say the writer's account of its own work does not match the file — so for a reported-but-unmade change the file already matches HEAD and git restore is a no-op. Present the named methods and let the user decide whether to re-run the skill for them; do not route these to a writer via fix_invocation, whose contract covers build and test failures, not a false self-report.
Note: do NOT rollback on env_failures alone — those are not the writer's fault.
Status per file
Per <PLUGIN_TEMPLATES>/rules/common-orchestrator-flow.md → "Summary reporting". Icons from <plugin-root>/resources/static/status-legend.md (plugin-internal controlled vocabulary).