Auto Unit Test Generator
Constraints (apply at ALL stages)
DO:
- Run until target coverage is reached or all modules are terminal (done/stalled)
- Use measured coverage gaps as input -- never guess
- Prioritize exception handlers, error paths, retry logic, validation
- Build and pass all tests locally before every commit
- Prove a measured coverage delta each iteration
- Branch from default integration branch; atomic commits per iteration
- Stick to verified facts from repo files, commands, and recorded artifacts -- never assume
NEVER:
- Generate tests on a broken build
- Test constants, DTOs, constructors, properties, default values, or logging
- Create a test project in the same folder as an existing project
- Rename folders, move files, rename projects, or change production code
Skill structure
SKILL.md -- Pipeline (this file)
scripts/
parse-cobertura.ps1 -- Parse Cobertura XML into JSON
parse-coverage-text.ps1 -- Parse pytest/Jest/Vitest text coverage into JSON
references/
code-coverage.md -- Coverage commands, tools, parsing
test-planning.md -- Prioritization + plan format
test-generation.md -- Code gen rules, framework compat, Moq, metadata
test-quality.md -- Anti-patterns, review checklist, quality gate
test-finalization.md -- Incorporate feedback, produce final code
ci-pipeline-checklist.md -- PR/pipeline readiness
skill-templates.md -- Reusable output templates
pr-description-template.md -- Final PR description template
Reference Lookup
Load the reference file(s) when entering the listed stage.
| Stage |
Reference file(s) |
| 2, 7 |
code-coverage.md |
| 3 |
test-planning.md |
| 4 |
test-generation.md, test-quality.md |
| 5 |
test-quality.md, test-finalization.md |
| 7 (PR) |
pr-description-template.md |
| Pre-PR |
test-quality.md, test-finalization.md |
| CI |
ci-pipeline-checklist.md |
| any |
skill-templates.md |
Progress Manifest
Create .coverage-progress.json at the repo root to persist state across iterations and sessions:
Local-state only rule: .coverage-progress.json is runtime state. Never stage or commit this file.
See the "Progress Manifest Example" section in references/skill-templates.md.
Add one module entry per detected language/project. Examples: C# (dotnet test), Rust (cargo test), TypeScript (npx jest), Python (pytest), C++ (ctest).
Resuming from interruption
On every invocation, check for .coverage-progress.json at the repo root:
| Status |
Action |
in-progress |
Re-run coverage to get current state, continue from where it left off |
pending |
Start from Stage 0 for that module |
done |
Skip this module |
stalled |
Skip (report why it stalled in previous session) |
skipped |
Skip |
After completing each stage, update the manifest with current coverage numbers, status, and lastUpdated timestamp. This makes the skill resilient across sessions -- a 50-module monorepo processes over multiple sessions without losing progress.
If the file does not exist, create it during Stage 0. If a branch is recorded in the manifest, check it out before resuming.
Memory (Cross-Session Context)
Use /memories/repo/ to cache context discovered during Stage 0 so it persists across sessions. This avoids re-discovering the same information every time.
Memory path resolution (tool path vs shell path)
/memories/repo/... is the logical memory path for the memory tool.
- Local (interactive):
$env:USERPROFILE\.copilot\memories\repo\...; use APPDATA fallback only if needed.
- CI environment: Use
$env:AGENT_TEMPDIRECTORY (ADO Pipelines — available in all Azure DevOps pipeline agents) or $env:RUNNER_TEMP (GitHub Actions) as the memory root. Never write to user profile paths in CI — they may not exist or persist. Memory files are ephemeral in CI and must not be checked in.
On first run (Stage 0), save to /memories/repo/test-gen-context-<repo-name>.md:
Derive <repo-name> from the repo root folder name (e.g., for C:\repos\MyService, use test-gen-context-MyService.md). This allows multiple repos to have their own context without overwriting each other.
Use exactly ONE memory file per repo. If you find multiple files (e.g., both test-gen-context.md and test-gen-context-MyService.md), merge them into the test-gen-context-<repo-name>.md file and delete the other. Do not maintain duplicates.
See the "Memory Context Template" section in references/skill-templates.md.
On every invocation, check memory FIRST:
- Read
/memories/repo/test-gen-context-<repo-name>.md -- if it exists, check the Source Fingerprint section:
- If the memory file does not exist, run full Stage 0 discovery and save the results.
- After every iteration, if you learned something not already in memory -- a new gotcha, a convention, an exclusion, a workaround -- append it. The memory should get smarter over time so the next session starts where this one left off.
Pipeline Stages
STRICT SEQUENTIAL EXECUTION: 0 --> 1 --> [ 2 --> 3 --> 4 --> 5 --> 6 --> 7 --> loop ]
Model requirement: Both the orchestrator and all subagents MUST use Claude Opus 4.6 (1M context). When spawning subagents, explicitly specify model: "Claude Opus 4.6 (1M context)" to ensure the same model with full 1M token context window is used for iteration execution.
Context management: Each iteration generates substantial terminal output (build, test, coverage commands) that is never needed again. To prevent context rot:
- Subagent per iteration: When looping (CONTINUE), delegate the next iteration (Stages 2-7) to a subagent using Claude Opus 4.6 (1M context) model. The subagent gets a fresh context window, executes the full stage sequence, and returns ONLY the coverage scoreboard, loop decision, commit hash, and any errors. The parent context stays clean.
- Proactive compact: If subagents are unavailable, compact after every 3-5 iterations with focus: "keep coverage scoreboard, gap, next targets, completedFiles, gotchas; drop terminal output, build logs, and file contents."
- Required artifacts are never optional: Stage 3 plan table, Stage 5 verdict table, and Stage 7 scoreboard must always be emitted (by the subagent or inline), even in compact mode.
Allowed Stage Transitions (Explicit Matrix)
| Current Stage |
Allowed Next Stage(s) |
| Stage 0 |
Stage 1 |
| Stage 1 |
Stage 2 |
| Stage 2 |
Stage 3 |
| Stage 3 |
Stage 4 |
| Stage 4 |
Stage 5 |
| Stage 5 |
Stage 6 |
| Stage 6 |
Stage 7 |
| Stage 7 |
Stage 2 (next iteration) OR End (Done/Stalled) |
Transitions not listed above are invalid and must be treated as blocked.
Normative Rules (Single Source of Truth)
- R1 Sequential stages only: Do not skip stages. Stage 2 must be followed by Stage 3 before Stage 4.
- R2 Generation gate: Do not generate code before Stage 4. Stage 4 is blocked without a valid Stage 3 plan artifact.
- R3 Full-scope verification: Stage 1 and Stage 2 must run against the full project/workspace scope, not isolated modules unless dependency-complete equivalence is proven and documented.
- R4 Multi-language completeness: Stage 3 planning must cover all detected language modules. If a language has no actionable targets, record the reason explicitly.
- R5 Exploration is input, not approval: Discovery/subagent output cannot replace Stage 3 planning artifacts.
- R6 Verification is mandatory: Build verification cannot be skipped (Stages 1 and 6).
- R7 Explicit stage reporting: Every progress update must include iteration and stage labels.
- R8 Checkpoint behavior: Stage 0 and first-iteration Stage 3 checkpoints stop in interactive mode, continue in auto mode after emitting checkpoint output.
- R9 State persistence: Update
.coverage-progress.json after each completed stage.
Mandatory Stage TODO Contract
Before Stage 0, create this checklist and use it as a hard gate:
See the "Mandatory Stage TODO Checklist" section in references/skill-templates.md.
Rules:
- Only one stage can be active at a time; do not advance with an unchecked TODO.
- Each TODO is bound to its stage instructions: execute ALL required steps in that stage before marking the TODO complete.
- Mark complete only when that stage's required output and completion checks are satisfied.
- If a stage fails, mark
blocked with one-line reason and stop.
- Include TODO delta in each user-facing update.
Execution Integrity Guards (Prevent False Completion)
Only current-run evidence can close a TODO.
- Create
runId (YYYYMMDD-HHMMSS-<short-branch>) and store it in .coverage-progress.json.
- Every stage artifact must carry the same
runId.
- Memory/previous logs can prefill context but never count as proof.
- Resume only when branch +
runId + prior stage artifacts all match; else restart from earliest incomplete stage.
- Narrative text is not evidence; missing required table/artifact means stage stays unchecked.
- For each stage start/end, print:
runId, iteration, stage, TODO delta, and artifact names.
- If Stage 0 or first-iteration Stage 3 checkpoint output is missing, mark
blocked and stop. In auto mode, output is still mandatory even though user confirmation is not.
- If Stage 4 starts without a Stage 3 plan artifact for the same
runId and iteration, mark blocked and return to Stage 3.
Stage Artifact Contract (Machine-Checkable)
A stage is complete only when its required artifact exists for the current runId and iteration.
| Stage |
Required Artifact (must be emitted in output) |
| Stage 0 |
Context checkpoint table (languages/modules/commands/scope/branch/target) |
| Stage 1 |
Baseline verification summary (build/test/lint counts) |
| Stage 2 |
Coverage baseline table + priority targets table |
| Stage 3 |
Structured per-language plan table with TP IDs |
| Stage 4 |
Generated test file list mapped to TP IDs |
| Stage 5 |
Per-test verdict table (APPROVED/NEEDS REVISION/REJECT) |
| Stage 6 |
Before/after regression summary (tests + lint) |
| Stage 7 |
Coverage delta table + loop decision row |
| Pre-PR Gate |
Post-fix verdict scorecard (per-file verdicts, dimension scores, fix summary, build+test verification) |
Missing artifact => stage remains incomplete.
Stage 0: Context, Scope & Branch (runs ONCE)
Goal: Understand how the project builds, tests, and lints. Create the working branch.
Subagent usage (allowed in Stage 0):
- You MAY use a read-only subagent (for example
Explore) to accelerate file/context discovery in Stage 0.
- Scope subagent tasks to discovery only (instructions, project files, test inventory, CI pipeline files, language detection).
- Do NOT delegate code generation, file edits, commits, or stage completion decisions to a subagent.
- You remain responsible for validating findings against actual files and producing the required Stage 0 checkpoint output.
Check memory and existing progress:
- Read
/memories/repo/test-gen-context-<repo-name>.md (derive <repo-name> from the repo root folder name) -- if it exists, load cached build commands, conventions, CI details. Skip steps 2-7 below (just verify with a quick build in Stage 1).
- If memory tool access is unavailable and you must use shell checks, use the Memory path resolution rules above. Do not assume APPDATA path is authoritative.
- If
.coverage-progress.json exists, read it. If a branch is recorded, check it out (git checkout <branch>). Resume from the last incomplete stage -- do not repeat Stage 0.
- If neither exists, proceed with full discovery below.
Read repo instructions (MANDATORY first step):
.github/copilot-instructions.md -- look for the ## Repo Context for AI Automation section first; it contains pre-curated build commands, test commands, coverage commands, CI pipeline details, and known gotchas specifically for AI automation. Use these values directly without re-discovering them.
AGENTS.md -- setup, code style, gotchas
- If neither exists, read the root directory,
README.md, and project config files
Extract from the instructions:
- If
## Repo Context for AI Automation exists in copilot-instructions.md: read it fully and treat its values as authoritative. Skip manual discovery for any field already specified there.
- Build command (e.g.,
dotnet build, npm run build, cargo build)
- Test command (e.g.,
dotnet test, npx vitest run, pytest)
- Lint/format command (e.g.,
dotnet format, npx eslint ., ruff check .) -- if available
- Custom CI lint checks -- look for line-length validators, custom PowerShell/bash scripts in CI that enforce max line length or other code style rules beyond standard linters. Record these as additional lint commands.
- Max line length -- check
.editorconfig (max_line_length), CI pipeline scripts (e.g., line-length validation steps), and linter configs. Record the discovered limit in memory.
- Test framework and mocking library
- Target framework / runtime version
3b. Discover ALL languages in the repository (MANDATORY):
The skill must detect every language that has source code AND tests, not just one. Scan for:
- C#:
*.sln, *.csproj files
- Rust:
Cargo.toml, Cargo.lock files
- TypeScript/JavaScript:
package.json with test scripts, jest.config.*, vitest.config.*
- Python:
setup.py, pyproject.toml, pytest.ini, tox.ini
- C++:
CMakeLists.txt, *.vcxproj
For EACH detected language, record:
- Build command
- Test command
- Coverage collection command (see
references/code-coverage.md)
- Test framework
If the repo has multiple languages, create a module entry in .coverage-progress.json for EACH language. The skill runs through the full pipeline for each language module, and coverage is merged across all of them to match what CI reports.
Check the CI pipeline YAML to see how CI collects and merges coverage across languages. The skill must collect coverage for all languages locally and merge them using scripts/parse-cobertura.ps1 so that local and CI coverage numbers match.
Target scoping: Determine what the user wants to test. Skip: bin, obj, node_modules, generated code, lock files, DTOs, constants.
- Stage 0 scope must be recorded per language module. If multiple languages are detected, do NOT collapse scope to one language in the Stage 0 checkpoint.
Read source files: Read full content only for files to be tested and their direct dependency interfaces.
Existing test inventory: Grep for existing test files to understand test patterns, naming conventions, assertion styles, and project structure.
- CRITICAL — Source-to-test-project mapping: For every source project in scope, identify ALL existing test projects that reference or test it — regardless of naming convention. Test project names often differ from their source project (e.g.,
SystemActivities.UnitTest.csproj tests Workflow.SystemActivities.csproj). Search by <ProjectReference> targets, not just by name matching. Record the mapping in the Stage 0 checkpoint so Stage 4 never creates a duplicate test project for an already-tested source project.
CI pipeline check: Look for pipeline definitions (.yml, .yaml in .pipelines/, .azure-pipelines/, .github/workflows/). Note how tests are discovered and executed.
- CRITICAL: Identify how CI collects coverage for EACH language and how reports are merged.
- Extract the ACTUAL CI coverage commands, task names, flags, report paths, publish steps, and merge steps from pipeline files or repo automation docs. Do not substitute preferred local defaults unless CI explicitly uses them.
- Note the exact flags,
.runsettings, and exclusion patterns CI uses per language.
- Record the CI coverage merge strategy in
.coverage-progress.json (coverageMode: "merged").
- If CI merges multiple languages into a single Cobertura report, the skill MUST replicate this locally.
- If CI coverage behavior cannot be proven from pipeline files, repo automation docs, or checked-in scripts, mark coverage mode as
provisional, record the missing evidence, and tell the user that local coverage may diverge from CI until verified.
- Check
copilot-instructions.md for any repo-specific actions required when a new test project is created (e.g., CloudTest map file registration, workspace config updates, explicit project lists). Record the actions and target files.
Discover branch naming convention: Check existing branches for patterns:
git branch -a --list '*test*' '*unit*' '*coverage*' | head -20
git log --oneline -20 --format='%D' | grep -oP '[^,\s]+' | head -20
Common conventions to look for:
feature/<description> / feat/<description>
dev/<alias>/<description>
user/<alias>/<description>
dev/<description>
<alias>/<description>
Determine the best matching convention from existing active branches and recent merged branches, then use that convention for this run.
If no clear convention, default to: dev/<alias>/auto-unit-tests-<YYYYMMDD>.
<alias> should come from the repo's observed branch prefixes first.
- If no prefix is observable, use the current git user alias.
- If still unknown, use
auto.
Create working branch:
- Create from the repository default integration branch (for example
main, master, or dev) -- not from a feature branch.
- Ensure local default branch is up to date before branching.
- Branch name MUST follow the discovered convention from step 8.
git checkout <default-branch>
git pull --ff-only
git checkout -b <branch-name>
- If
<branch-name> already exists, append an incrementing suffix (for example -v2, -v3) while preserving the same convention.
Record the branch name in .coverage-progress.json.
Create or update .coverage-progress.json with all discovered modules set to pending, target coverage, and the branch name.
- Default
target is 100 unless the user explicitly requests a different target.
- Default
maxIterations is 100. The user may override with a lower value.
- Save context to memory: Write
/memories/repo/test-gen-context-<repo-name>.md (derive <repo-name> from the repo root folder name) with all discovered information (build commands, test framework, conventions, CI pipeline, .runsettings, gotchas). See the "Memory Context Template" section in references/skill-templates.md. This ensures future sessions start with full context.
[STOP] CHECKPOINT: Present to the user:
- Languages detected and modules per language
- Files to test and files excluded (per language)
- Build, test, coverage, and lint commands discovered (per language)
- Target framework per language
- CI pipeline info (including the exact coverage collection commands/tasks/settings, exclusions, report paths, and coverage merge strategy)
- Repo-specific post-creation actions from Stage 0 context (e.g., CI registration files, CloudTest map updates, workspace configs)
- Branch name created
- Coverage target
- Module manifest (for monorepos / multi-language repos)
Checkpoint validity rule:
- If CI coverage collection details are missing, inferred, or only partially known, Stage 0 must label coverage as
provisional and list the missing evidence explicitly. Do not present local coverage numbers as CI-equivalent unless the CI collection path has been verified.
Checkpoint validity rule:
- If multiple languages were detected but the checkpoint scope is reported as single-language (for example "C# module only"), Stage 0 is INCOMPLETE and must be redone before Stage 1.
In interactive mode: End your response and wait for user confirmation. In auto mode: report the checkpoint, then continue to Stage 1.
Stage 1: Pre-Flight (Build + Test + Lint)
Verify the codebase is healthy before generating anything. Run for ALL language modules discovered in Stage 0.
Note: All required commands and details are already captured in Stage 0 Repo Context memory (/memories/repo/test-gen-context-<repo-name>.md).
Clean and build locally (ALL languages): clean stale build artifacts before building (dotnet clean, cargo clean, rm -rf dist/, npm run clean, etc.) to prevent inflated coverage from outdated binaries. Then run the build command for EVERY module from repo context memory. If any build fails: diagnose, report, STOP.
Run existing tests (ALL languages): record per-language total, passed, failed, skipped. If tests fail, report and wait for acknowledgment.
Run linter/formatter (if discovered in Stage 0): record existing lint warnings/errors as baseline. Generated tests must not introduce new violations.
Record baseline state: exact counts of passing tests, failing tests, skipped tests, lint violations.
Stage 2: Coverage Baseline
Reference: Read references/code-coverage.md for tool-specific commands, parsing, and multi-language report merging.
Note: All required coverage commands and settings are already captured in Stage 0 Repo Context memory (/memories/repo/test-gen-context-<repo-name>.md).
Execution guard for stored coverage commands (mandatory): execute the stored command as-is; if quoting fails, fix only shell escaping (not command intent), and prefer $env:TEMP for coverage outputs.
- Create a temp directory for coverage output (never write into the repo).
- Honor coverage exclusions/settings first:
- If the repo has
.runsettings, coverlet config, .nycrc, or coverage.config, read exclusions before collecting coverage.
- Use the same settings as CI when running local coverage.
- Record which coverage settings file was used in
.coverage-progress.json.
- Collect coverage for EVERY language module discovered in Stage 0 (not just one):
- For each module, run its coverage command from repo context memory and produce a Cobertura XML.
- Run coverage for ALL detected languages, not just one.
- Store each language's Cobertura XML separately (e.g.,
<language>-coverage.cobertura.xml).
- Merge all Cobertura reports into a single combined report (mirrors CI behavior):
- Parse merged results into structured data.
- Record baseline: overall line/branch coverage, per-file breakdown (all languages), uncovered line ranges.
- Update
mergedBaseline in .coverage-progress.json and each module's individual baseline.
- Classify uncovered code by priority (P0-P3), while excluding known exclusions (constants, DTOs, properties, logging, simple constructors, dead code):
- P0 -- Critical: business logic with < 50% coverage
- P1 -- Critical: exception handlers, error paths, retry logic, catch blocks
- P2 -- Medium: utility methods with < 50% coverage
- P3 -- Low: branch gaps in already-covered methods
- Exclude from gap list (do NOT target these for tests):
- Constants, constant classes, constant fields
- Default value assignments
- Auto-properties (get/set only)
- DTO/POCO classes, records, and data containers (properties only, no logic)
- Logging convention code, logger providers, log formatters
- Constructors (unless they contain branching logic)
- Classes or methods marked
[Obsolete] — do not generate tests for deprecated code
- Dead code: unreachable methods, unused classes, orphaned code paths, commented-out blocks (distinguish dead from rare -- exception handlers and fallback logic are NOT dead code)
- Align local and CI coverage behavior:
- Validate CI coverage collection and merge strategy per language from pipeline config.
- Match the actual CI collector/tool choice per language before running local coverage (for example built-in
Code Coverage, XPlat Code Coverage, dotnet-coverage, Jest/Vitest coverage reporters, pytest --cov, cargo-llvm-cov).
- Match CI settings files, include/exclude patterns, output formats, report paths, and publish-time merge assumptions.
- If CI merges N language reports, local baseline must also merge N reports.
- If CI settings are unknown, record that explicitly in output and mark the result
provisional; do not claim parity with CI.
- Clean up temp directory.
Stage 2 output: Present a summary table showing where the skill will focus next:
See the "Stage 2 Output Template" section in references/skill-templates.md.
Include rows for EVERY detected language. Show ALL actionable targets (every file/module where coverage can be increased) plus any notable exclusions. Do NOT cap the list at any fixed number — the plan and each iteration must cover the maximum possible files.
COMPLETION CHECK: If your Stage 2 output does not contain the Priority Targets table, you have not completed Stage 2. The raw coverage numbers alone are not enough -- the agent and user need to see the prioritized file list before planning.
This coverage data is the PRIMARY INPUT for test planning.
If tests cannot run (build failures, missing deps), note the blocker, skip this stage, proceed without baseline. If coverage is already 90%+, focus only on remaining hotspots.
Stage 3: Plan
Reference: Read references/test-planning.md for prioritization rules, plan format, and categories.
This is a SEPARATE stage. Do NOT generate code here.
- Use coverage gaps from Stage 2 as PRIMARY input, prioritized P0-P3.
1a. Build the Stage 3 plan across ALL detected language modules in
.coverage-progress.json, not just the current/lowest-coverage language.
1b. Batch multiple files per iteration: Plan tests for 10-15 source files per iteration (grouped by module or dependency proximity). More files per iteration = fewer loop cycles = more coverage per session. Do not plan for only 1 file when multiple actionable files exist.
- Critical path first:
- Exception handlers and catch blocks --> MUST have tests
- Error return paths --> MUST have tests
- Retry/fallback logic --> MUST have tests
- Validation logic --> MUST have tests
- Happy path business logic --> standard coverage
- Do NOT plan tests for: constants, default values, properties, DTOs, logging, simple constructors, dead code, already-covered code.
3b. Behavioral deduplication (MANDATORY): Before planning tests for any source file, read the existing test files that cover it. If an existing test already validates the same scenario/behavior (even if lines are marked uncovered due to mocking differences), do NOT plan a duplicate. New tests must add incremental behavioral value — covering a genuinely untested code path or scenario — not re-test what existing tests already verify.
- If deferring a plan item, document WHY (e.g., "requires integration test", "testability blocker -- static factory with no seam", "external dependency cannot be mocked"). Every
TP-<SourceFile>-<ShortName> ID must be either implemented or have an explicit skip reason.
- ID format:
TP-<SourceFile>-<ShortName> where <SourceFile> is the short source file name (no extension, no path) and <ShortName> is a ≤5-word camel-case description of the scenario (e.g., TP-OrderService-PaymentFailurePath, TP-LeaseHook-NullInputReturnsError).
- Each test case: ID, Name, Priority, Coverage Target (file:lines), Arrange, Act, Assert.
- Map every test to a specific coverage gap.
- Flag latent bugs with WARNING.
Plan output: structured markdown table with ID, Language, Test Name, Priority (P0-P3), Coverage Target, Arrange, Act, Assert.
Stage 3 Plan Quality Gate (MANDATORY before Stage 4):
- Verify each planned test has a meaningful assertion strategy (not coverage-only / not assert-free).
- Verify planned tests target real behavioral contracts — not assumed behavior. Each test's expected outcome must be derivable from the source code, not guessed.
- Verify planned tests comply with "Do NOT generate" exclusions.
- Verify each planned test maps to a concrete Stage 2 coverage gap.
- Verify the plan includes entries for every detected language module that still has actionable uncovered targets, or explicitly records why a language has no planned items.
- If any planned item violates quality/exclusion rules, revise the plan before proceeding.
[STOP] CHECKPOINT: Show the complete per-language test plan. Include a short language coverage summary (planned items per language, or explicit "no actionable targets" reason). In interactive mode: end your response and wait for user confirmation before proceeding to Stage 4. In auto mode: report the checkpoint and continue to Stage 4 automatically.
Stage 4: Generate
Reference: Read references/test-generation.md for framework rules, namespace verification, test framework compatibility, Moq workarounds, metadata, and project file rules. Read references/test-quality.md Part 2 for anti-patterns to avoid.
Canonical Gate Check (single source): You must have a written, quality-gated Stage 3 plan for the current iteration. Required:
- TP IDs for each planned test
- Structured Stage 3 per-language plan table
- Stage 3 quality gate pass
- Current runId and iteration number
- Plan entries for all detected language modules
If any are missing, stop and return to Stage 3.
Step 0 — Think Before Generating (MANDATORY per source file):
Before writing ANY test code for a source file, reason explicitly:
- Read the source file fully — understand its purpose, dependencies, and behavioral contracts.
- State assumptions — list what you believe the method/class does and what constitutes correct vs incorrect behavior. If uncertain about any behavior, flag it — do not guess and run with it.
- Surface ambiguities — if multiple interpretations of behavior exist (e.g., "does null input throw or return empty?"), check the source code, existing tests, or documentation to resolve. If unresolvable, pick the interpretation supported by the code and note it as a comment in the test.
- Identify tradeoffs — if a simpler test approach exists (fewer mocks, less setup), prefer it. If 200 lines of setup could be 50, rewrite the approach before generating.
- Push back on the plan — if a planned test item is untestable, redundant, or would produce a low-value test, skip it with a documented reason rather than generating junk.
This step produces no file output — it is internal reasoning that makes generated code accurate on the first attempt. Skip this step only for trivially simple tests (e.g., single-line validation methods).
- Generate compilable test code implementing every planned test case.
- Follow AAA pattern (Arrange-Act-Assert) with explicit comments.
- Test names:
MethodName_Scenario_ExpectedBehavior.
3b. Plan ID comments (RECOMMENDED): Add a comment mapping each test method to its plan item: // TP-<SourceFile>-<ShortName>. This aids traceability but missing TP comments do not block the quality gate or Stage 5 review.
3c. Line length limit (MANDATORY): Keep all generated lines within the repo's max line length (from .editorconfig or CI checks). Break long lines — split string literals, chain method calls across lines, use intermediate variables. This is a common CI gate failure.
- Read actual source files before generating -- verify namespaces, types, method signatures. Never assume.
- Match the repo's test framework AND assertion library exactly -- detect from existing test projects (NUnit, MSTest, xUnit, Jest, pytest, etc.). Also detect the assertion library (FluentAssertions
.Should(), Shouldly, built-in Assert.*). Use whichever the repo already uses — do NOT mix assertion styles. Follow the version-specific API rules from test-generation.md.
- Apply Moq expression tree workarounds for optional parameters.
- If creating a new
.csproj: apply project file rules, set <ProjectUsageType>UnitTest</ProjectUsageType>.
Project placement: NEVER create a .csproj in the same folder as another project. Before creating a test project, discover the repo's existing convention:
- Search for existing
*.Tests.csproj or *.UnitTests.csproj files and note where they live relative to their source projects.
- Check if a test project already exists for the source project — search ALL existing test
.csproj files for <ProjectReference> elements pointing to the source project. Test project names frequently differ from their source (e.g., SystemActivities.UnitTest.csproj tests Workflow.SystemActivities.csproj). If an existing test project already references the source, add new tests to THAT project instead of creating a new one.
- Match that pattern exactly. Common conventions (in order of prevalence):
test/<Source>.Tests/ (separate test root: src/MyService/ --> test/MyService.Tests/)
src/<Source>.Tests/ (sibling under src: src/MyService/ --> src/MyService.Tests/)
tests/<Source>.Tests/ (plural test root)
<Source>/tests/ (tests subfolder per project)
- If no existing test projects exist, prefer the
test/ or tests/ root convention if those directories exist, otherwise use sibling folder under src/.
- Check the target directory contents before creating -- if any
.csproj already exists there, choose a different folder.
Traceability attributes (MANDATORY):
- NUnit:
[Category("AutoGenerated")] and [Category("CopilotSkill:autocoverage")]
- MSTest:
[TestCategory("AutoGenerated")] and [TestCategory("CopilotSkill:autocoverage")]
- xUnit: use
[Trait("Category", "AutoGenerated")] and [Trait("Category", "CopilotSkill:autocoverage")]
- Jest/Vitest: add
// @category AutoGenerated CopilotSkill:autocoverage at top of file
- pytest: use
@pytest.mark.auto_generated marker
Placement rule: Always place traceability attributes on each new test method, never on the class. This ensures only generated methods are tagged, regardless of whether the class is new or pre-existing.
File organization: One test class per source class, one test file per test class. Never combine test classes for unrelated source classes in a single file.
- Test file name must match the source class:
OrderService.cs → OrderServiceTests.cs.
- NEVER prefix file or class names with tool/skill/iteration identifiers (
AutoCoverage_, Iter<N>_, Generated_, etc.). Traceability goes in attributes and comments, not filenames. See references/test-generation.md "File Naming" and "Coexistence with Existing Test Files" sections.
- If a test file already exists for the source class, add methods to it or create
<SourceClass>ExtendedTests.cs — never a tool-prefixed duplicate.
- If a source file contains multiple public classes, each gets its own test file.
- Never create a single "catch-all" test file that tests multiple unrelated classes.
Do NOT generate tests that:
- Re-compute expected values from production logic (tautological)
- Exist only to increase coverage without validating behavior (coverage-only tests)
- Have no meaningful assertions (including assertion-free tests or exception-only smoke tests)
- Only assert
IsNotNull without checking values
- Mock the class under test
- Use reflection to test private methods
- Have 50+ lines of setup for one assertion
- Duplicate production code in the test
- Duplicate behavior already tested by existing tests (same scenario, same assertions on same class)
- Test constants, DTOs, records, constructors, properties, logging, or
[Obsolete] classes/methods
- Target methods with unbounded
while loops or gRPC/async stream consumers — they deadlock test runners
- Test pure delegation methods (
return await x.Method()) — only proves the mock works
- Assert on log-message string fragments — breaks on any message rewording
Stage 5: Review + Finalize
Reference: Read references/test-quality.md for the full review checklist and quality gate. Read references/test-finalization.md for the compliance checklist.
This stage has TWO mandatory parts. Completing only Part A is a VIOLATION. You MUST complete Part B before proceeding to Stage 6.
Part A -- Build: Build the test project. Fix all compilation errors (StyleCop, analyzers, missing usings, wrong APIs). Iterate until clean build. Do not proceed to Part B with broken code.
Part B -- Quality Review (MANDATORY -- build passing is NOT enough): After the build passes, evaluate EACH generated test against the checklist in test-quality.md Part 3. Check every test for:
- Reasoning accuracy — does the test demonstrate correct understanding of the source behavior? If assertions test the wrong expected values or misinterpret method contracts, the test is REJECT regardless of whether it passes.
- Assertion quality (HIGH/MEDIUM/LOW) -- are assertions validating behavior or just checking not-null?
- Coverage-only pattern -- does the test exist only to execute lines without validating outcomes?
- Assertion presence -- does the test include at least one meaningful behavioral assertion?
- Tautological patterns -- does any test re-compute expected values from production logic?
- Exclusion violations -- does any test target constants, DTOs, constructors, properties, or logging?
- Mock correctness -- is the class under test being mocked? Are mocks set up and verified properly?
- Edge cases -- null, empty, boundary inputs covered?
- Determinism -- any time/random/network dependence?
Produce a per-test verdict table:
| Test Name |
Plan Item |
Assertion Quality |
Issues |
Verdict |
Verdicts: APPROVED / NEEDS REVISION / REJECT.
Part C -- Quality Gate (MANDATORY HARD GATE before Stage 6): Run the quality gate from test-quality.md Part 4 against all generated tests in this iteration. Produce the scorecard:
| Dimension |
Target |
Actual |
Status |
| Assertion Density |
|
|
|
…(truncated)
1---2name: autocoverage3description: Use when a user asks to generate or improve unit tests with measurable coverage gains using a strict staged workflow (build -> baseline -> plan -> generate -> verify -> coverage delta). Trigger phrases: generate unit tests, improve coverage, write tests for uncovered code, make PR-ready tests. Do not use for integration/E2E testing, production refactoring, or planning-only requests without code generation intent.4---5
6# Auto Unit Test Generator
7
8## Constraints (apply at ALL stages)
9
10**DO**:
11- Run until target coverage is reached or all modules are terminal (done/stalled)
12- Use measured coverage gaps as input -- never guess
13- Prioritize exception handlers, error paths, retry logic, validation
14- Build and pass all tests locally before every commit
15- Prove a measured coverage delta each iteration
16- Branch from default integration branch; atomic commits per iteration
17- Stick to verified facts from repo files, commands, and recorded artifacts -- never assume
18
19**NEVER**:
20- Generate tests on a broken build
21- Test constants, DTOs, constructors, properties, default values, or logging
22- Create a test project in the same folder as an existing project
23- Rename folders, move files, rename projects, or change production code
24
25## Skill structure
26
27```
28SKILL.md -- Pipeline (this file)
29scripts/
30 parse-cobertura.ps1 -- Parse Cobertura XML into JSON
31 parse-coverage-text.ps1 -- Parse pytest/Jest/Vitest text coverage into JSON
32references/
33 code-coverage.md -- Coverage commands, tools, parsing
34 test-planning.md -- Prioritization + plan format
35 test-generation.md -- Code gen rules, framework compat, Moq, metadata
36 test-quality.md -- Anti-patterns, review checklist, quality gate
37 test-finalization.md -- Incorporate feedback, produce final code
38 ci-pipeline-checklist.md -- PR/pipeline readiness
39 skill-templates.md -- Reusable output templates
40 pr-description-template.md -- Final PR description template
41```
42
43## Reference Lookup
44
45Load the reference file(s) when entering the listed stage.
46
47| Stage | Reference file(s) |
48|-------|--------------------|
49| 2, 7 | [code-coverage.md](references/code-coverage.md) |
50| 3 | [test-planning.md](references/test-planning.md) |
51| 4 | [test-generation.md](references/test-generation.md), [test-quality.md](references/test-quality.md) |
52| 5 | [test-quality.md](references/test-quality.md), [test-finalization.md](references/test-finalization.md) |
53| 7 (PR) | [pr-description-template.md](references/pr-description-template.md) |
54| Pre-PR | [test-quality.md](references/test-quality.md), [test-finalization.md](references/test-finalization.md) |
55| CI | [ci-pipeline-checklist.md](references/ci-pipeline-checklist.md) |
56| any | [skill-templates.md](references/skill-templates.md) |
57
58---
59
60## Progress Manifest
61
62Create `.coverage-progress.json` at the repo root to persist state across iterations and sessions:
63
64**Local-state only rule**: `.coverage-progress.json` is runtime state. Never stage or commit this file.
65
66See the "Progress Manifest Example" section in [references/skill-templates.md](references/skill-templates.md).
67
68Add one module entry per detected language/project. Examples: C# (`dotnet test`), Rust (`cargo test`), TypeScript (`npx jest`), Python (`pytest`), C++ (`ctest`).
69
70### Resuming from interruption
71
72On every invocation, check for `.coverage-progress.json` at the repo root:
73
74| Status | Action |
75|--------|--------|
76| `in-progress` | Re-run coverage to get current state, continue from where it left off |
77| `pending` | Start from Stage 0 for that module |
78| `done` | Skip this module |
79| `stalled` | Skip (report why it stalled in previous session) |
80| `skipped` | Skip |
81
82After completing each stage, update the manifest with current coverage numbers, status, and `lastUpdated` timestamp. This makes the skill resilient across sessions -- a 50-module monorepo processes over multiple sessions without losing progress.
83
84If the file does not exist, create it during Stage 0. If a branch is recorded in the manifest, check it out before resuming.
85
86---
87
88## Memory (Cross-Session Context)
89
90Use `/memories/repo/` to cache context discovered during Stage 0 so it persists across sessions. This avoids re-discovering the same information every time.
91
92### Memory path resolution (tool path vs shell path)
93
94- `/memories/repo/...` is the logical memory path for the memory tool.
95- **Local (interactive)**: `$env:USERPROFILE\.copilot\memories\repo\...`; use APPDATA fallback only if needed.
96- **CI environment**: Use `$env:AGENT_TEMPDIRECTORY` (ADO Pipelines — available in all Azure DevOps pipeline agents) or `$env:RUNNER_TEMP` (GitHub Actions) as the memory root. Never write to user profile paths in CI — they may not exist or persist. Memory files are ephemeral in CI and must not be checked in.
97
98### On first run (Stage 0), save to `/memories/repo/test-gen-context-<repo-name>.md`:
99
100Derive `<repo-name>` from the repo root folder name (e.g., for `C:\repos\MyService`, use `test-gen-context-MyService.md`). This allows multiple repos to have their own context without overwriting each other.
101
102**Use exactly ONE memory file per repo.** If you find multiple files (e.g., both `test-gen-context.md` and `test-gen-context-MyService.md`), merge them into the `test-gen-context-<repo-name>.md` file and delete the other. Do not maintain duplicates.
103
104See the "Memory Context Template" section in [references/skill-templates.md](references/skill-templates.md).
105
106### On every invocation, check memory FIRST:
107
1081. Read `/memories/repo/test-gen-context-<repo-name>.md` -- if it exists, check the **Source Fingerprint** section:
109 - Compare the stored git commit hash of `copilot-instructions.md` and `AGENTS.md` against the current git log for those files:
110 ```
111 git log -1 --format="%H" -- .github/copilot-instructions.md
112 git log -1 --format="%H" -- AGENTS.md
113 ```
114 - If hashes match: memory is valid. Use cached context, skip full Stage 0 discovery.
115 - If hashes differ (files were updated in a newer commit): repo instructions have changed. Re-run full Stage 0 discovery and overwrite the memory (preserve the Gotchas and Known Exclusions sections -- only regenerate Build & Test, Conventions, CI Pipeline, and Project Rules).
1162. If the memory file does not exist, run full Stage 0 discovery and save the results.
1173. **After every iteration**, if you learned something not already in memory -- a new gotcha, a convention, an exclusion, a workaround -- append it. The memory should get smarter over time so the next session starts where this one left off.
118
119---
120
121## Pipeline Stages
122
123**STRICT SEQUENTIAL EXECUTION**: 0 --> 1 --> [ 2 --> 3 --> 4 --> 5 --> 6 --> 7 --> loop ]
124
125**Model requirement**: Both the orchestrator and all subagents MUST use **Claude Opus 4.6 (1M context)**. When spawning subagents, explicitly specify `model: "Claude Opus 4.6 (1M context)"` to ensure the same model with full 1M token context window is used for iteration execution.
126
127**Context management**: Each iteration generates substantial terminal output (build, test, coverage commands) that is never needed again. To prevent context rot:
128- **Subagent per iteration**: When looping (CONTINUE), delegate the next iteration (Stages 2-7) to a subagent using **Claude Opus 4.6 (1M context)** model. The subagent gets a fresh context window, executes the full stage sequence, and returns ONLY the coverage scoreboard, loop decision, commit hash, and any errors. The parent context stays clean.
129- **Proactive compact**: If subagents are unavailable, compact after every 3-5 iterations with focus: "keep coverage scoreboard, gap, next targets, completedFiles, gotchas; drop terminal output, build logs, and file contents."
130- **Required artifacts are never optional**: Stage 3 plan table, Stage 5 verdict table, and Stage 7 scoreboard must always be emitted (by the subagent or inline), even in compact mode.
131
132### Allowed Stage Transitions (Explicit Matrix)
133
134| Current Stage | Allowed Next Stage(s) |
135|---------------|------------------------|
136| Stage 0 | Stage 1 |
137| Stage 1 | Stage 2 |
138| Stage 2 | Stage 3 |
139| Stage 3 | Stage 4 |
140| Stage 4 | Stage 5 |
141| Stage 5 | Stage 6 |
142| Stage 6 | Stage 7 |
143| Stage 7 | Stage 2 (next iteration) OR End (Done/Stalled) |
144
145Transitions not listed above are invalid and must be treated as `blocked`.
146
147## Normative Rules (Single Source of Truth)
148
149- **R1 Sequential stages only**: Do not skip stages. Stage 2 must be followed by Stage 3 before Stage 4.
150- **R2 Generation gate**: Do not generate code before Stage 4. Stage 4 is blocked without a valid Stage 3 plan artifact.
151- **R3 Full-scope verification**: Stage 1 and Stage 2 must run against the full project/workspace scope, not isolated modules unless dependency-complete equivalence is proven and documented.
152- **R4 Multi-language completeness**: Stage 3 planning must cover all detected language modules. If a language has no actionable targets, record the reason explicitly.
153- **R5 Exploration is input, not approval**: Discovery/subagent output cannot replace Stage 3 planning artifacts.
154- **R6 Verification is mandatory**: Build verification cannot be skipped (Stages 1 and 6).
155- **R7 Explicit stage reporting**: Every progress update must include iteration and stage labels.
156- **R8 Checkpoint behavior**: Stage 0 and first-iteration Stage 3 checkpoints stop in interactive mode, continue in auto mode after emitting checkpoint output.
157- **R9 State persistence**: Update `.coverage-progress.json` after each completed stage.
158
159### Mandatory Stage TODO Contract
160
161Before Stage 0, create this checklist and use it as a hard gate:
162
163See the "Mandatory Stage TODO Checklist" section in [references/skill-templates.md](references/skill-templates.md).
164
165Rules:
166- Only one stage can be active at a time; do not advance with an unchecked TODO.
167- Each TODO is bound to its stage instructions: execute ALL required steps in that stage before marking the TODO complete.
168- Mark complete only when that stage's required output and completion checks are satisfied.
169- If a stage fails, mark `blocked` with one-line reason and stop.
170- Include TODO delta in each user-facing update.
171
172### Execution Integrity Guards (Prevent False Completion)
173
174Only current-run evidence can close a TODO.
175
176- Create `runId` (`YYYYMMDD-HHMMSS-<short-branch>`) and store it in `.coverage-progress.json`.
177- Every stage artifact must carry the same `runId`.
178- Memory/previous logs can prefill context but never count as proof.
179- Resume only when branch + `runId` + prior stage artifacts all match; else restart from earliest incomplete stage.
180- Narrative text is not evidence; missing required table/artifact means stage stays unchecked.
181- For each stage start/end, print: `runId`, `iteration`, `stage`, TODO delta, and artifact names.
182- If Stage 0 or first-iteration Stage 3 checkpoint output is missing, mark `blocked` and stop. In auto mode, output is still mandatory even though user confirmation is not.
183- If Stage 4 starts without a Stage 3 plan artifact for the same `runId` and iteration, mark `blocked` and return to Stage 3.
184
185### Stage Artifact Contract (Machine-Checkable)
186
187A stage is complete only when its required artifact exists for the current `runId` and `iteration`.
188
189| Stage | Required Artifact (must be emitted in output) |
190|-------|-----------------------------------------------|
191| Stage 0 | Context checkpoint table (languages/modules/commands/scope/branch/target) |
192| Stage 1 | Baseline verification summary (build/test/lint counts) |
193| Stage 2 | Coverage baseline table + priority targets table |
194| Stage 3 | Structured per-language plan table with TP IDs |
195| Stage 4 | Generated test file list mapped to TP IDs |
196| Stage 5 | Per-test verdict table (APPROVED/NEEDS REVISION/REJECT) |
197| Stage 6 | Before/after regression summary (tests + lint) |
198| Stage 7 | Coverage delta table + loop decision row |
199| Pre-PR Gate | Post-fix verdict scorecard (per-file verdicts, dimension scores, fix summary, build+test verification) |
200
201Missing artifact => stage remains incomplete.
202
203---
204
205### Stage 0: Context, Scope & Branch (runs ONCE)
206
207**Goal**: Understand how the project builds, tests, and lints. Create the working branch.
208
209**Subagent usage (allowed in Stage 0)**:
210- You MAY use a read-only subagent (for example `Explore`) to accelerate file/context discovery in Stage 0.
211- Scope subagent tasks to discovery only (instructions, project files, test inventory, CI pipeline files, language detection).
212- Do NOT delegate code generation, file edits, commits, or stage completion decisions to a subagent.
213- You remain responsible for validating findings against actual files and producing the required Stage 0 checkpoint output.
214
2151. **Check memory and existing progress**:
216 - Read `/memories/repo/test-gen-context-<repo-name>.md` (derive `<repo-name>` from the repo root folder name) -- if it exists, load cached build commands, conventions, CI details. Skip steps 2-7 below (just verify with a quick build in Stage 1).
217 - If memory tool access is unavailable and you must use shell checks, use the Memory path resolution rules above. Do not assume APPDATA path is authoritative.
218 - If `.coverage-progress.json` exists, read it. If a `branch` is recorded, check it out (`git checkout <branch>`). Resume from the last incomplete stage -- do not repeat Stage 0.
219 - If neither exists, proceed with full discovery below.
220
2212. **Read repo instructions** (MANDATORY first step):
222 - `.github/copilot-instructions.md` -- **look for the `## Repo Context for AI Automation` section first**; it contains pre-curated build commands, test commands, coverage commands, CI pipeline details, and known gotchas specifically for AI automation. Use these values directly without re-discovering them.
223 - `AGENTS.md` -- setup, code style, gotchas
224 - If neither exists, read the root directory, `README.md`, and project config files
225
2263. **Extract from the instructions**:
227 - If `## Repo Context for AI Automation` exists in `copilot-instructions.md`: read it fully and treat its values as authoritative. Skip manual discovery for any field already specified there.
228 - **Build command** (e.g., `dotnet build`, `npm run build`, `cargo build`)
229 - **Test command** (e.g., `dotnet test`, `npx vitest run`, `pytest`)
230 - **Lint/format command** (e.g., `dotnet format`, `npx eslint .`, `ruff check .`) -- if available
231 - **Custom CI lint checks** -- look for line-length validators, custom PowerShell/bash scripts in CI that enforce max line length or other code style rules beyond standard linters. Record these as additional lint commands.
232 - **Max line length** -- check `.editorconfig` (`max_line_length`), CI pipeline scripts (e.g., line-length validation steps), and linter configs. Record the discovered limit in memory.
233 - **Test framework** and mocking library
234 - **Target framework** / runtime version
235
2363b. **Discover ALL languages in the repository** (MANDATORY):
237 The skill must detect every language that has source code AND tests, not just one. Scan for:
238 - **C#**: `*.sln`, `*.csproj` files
239 - **Rust**: `Cargo.toml`, `Cargo.lock` files
240 - **TypeScript/JavaScript**: `package.json` with test scripts, `jest.config.*`, `vitest.config.*`
241 - **Python**: `setup.py`, `pyproject.toml`, `pytest.ini`, `tox.ini`
242 - **C++**: `CMakeLists.txt`, `*.vcxproj`
243
244 For EACH detected language, record:
245 - Build command
246 - Test command
247 - Coverage collection command (see `references/code-coverage.md`)
248 - Test framework
249
250 **If the repo has multiple languages, create a module entry in `.coverage-progress.json` for EACH language.** The skill runs through the full pipeline for each language module, and coverage is merged across all of them to match what CI reports.
251
252 **Check the CI pipeline YAML** to see how CI collects and merges coverage across languages. The skill must collect coverage for all languages locally and merge them using `scripts/parse-cobertura.ps1` so that local and CI coverage numbers match.
253
2544. **Target scoping**: Determine what the user wants to test. Skip: bin, obj, node_modules, generated code, lock files, DTOs, constants.
255 - Stage 0 scope must be recorded **per language module**. If multiple languages are detected, do NOT collapse scope to one language in the Stage 0 checkpoint.
256
2575. **Read source files**: Read full content only for files to be tested and their direct dependency interfaces.
258
2596. **Existing test inventory**: Grep for existing test files to understand test patterns, naming conventions, assertion styles, and project structure.
260 - **CRITICAL — Source-to-test-project mapping**: For every source project in scope, identify ALL existing test projects that reference or test it — regardless of naming convention. Test project names often differ from their source project (e.g., `SystemActivities.UnitTest.csproj` tests `Workflow.SystemActivities.csproj`). Search by `<ProjectReference>` targets, not just by name matching. Record the mapping in the Stage 0 checkpoint so Stage 4 never creates a duplicate test project for an already-tested source project.
261
2627. **CI pipeline check**: Look for pipeline definitions (`.yml`, `.yaml` in `.pipelines/`, `.azure-pipelines/`, `.github/workflows/`). Note how tests are discovered and executed.
263 - **CRITICAL**: Identify how CI collects coverage for EACH language and how reports are merged.
264 - Extract the ACTUAL CI coverage commands, task names, flags, report paths, publish steps, and merge steps from pipeline files or repo automation docs. Do not substitute preferred local defaults unless CI explicitly uses them.
265 - Note the exact flags, `.runsettings`, and exclusion patterns CI uses per language.
266 - Record the CI coverage merge strategy in `.coverage-progress.json` (`coverageMode: "merged"`).
267 - If CI merges multiple languages into a single Cobertura report, the skill MUST replicate this locally.
268 - If CI coverage behavior cannot be proven from pipeline files, repo automation docs, or checked-in scripts, mark coverage mode as `provisional`, record the missing evidence, and tell the user that local coverage may diverge from CI until verified.
269 - Check `copilot-instructions.md` for any repo-specific actions required when a new test project is created (e.g., CloudTest map file registration, workspace config updates, explicit project lists). Record the actions and target files.
270
2718. **Discover branch naming convention**: Check existing branches for patterns:
272 ```
273 git branch -a --list '*test*' '*unit*' '*coverage*' | head -20
274 git log --oneline -20 --format='%D' | grep -oP '[^,\s]+' | head -20
275 ```
276 Common conventions to look for:
277 - `feature/<description>` / `feat/<description>`
278 - `dev/<alias>/<description>`
279 - `user/<alias>/<description>`
280 - `dev/<description>`
281 - `<alias>/<description>`
282 Determine the best matching convention from existing active branches and recent merged branches, then use that convention for this run.
283 If no clear convention, default to: `dev/<alias>/auto-unit-tests-<YYYYMMDD>`.
284 - `<alias>` should come from the repo's observed branch prefixes first.
285 - If no prefix is observable, use the current git user alias.
286 - If still unknown, use `auto`.
287
2889. **Create working branch**:
289 - Create from the repository default integration branch (for example `main`, `master`, or `dev`) -- not from a feature branch.
290 - Ensure local default branch is up to date before branching.
291 - Branch name MUST follow the discovered convention from step 8.
292 ```
293 git checkout <default-branch>
294 git pull --ff-only
295 git checkout -b <branch-name>
296 ```
297 - If `<branch-name>` already exists, append an incrementing suffix (for example `-v2`, `-v3`) while preserving the same convention.
298 Record the branch name in `.coverage-progress.json`.
299
30010. **Create or update `.coverage-progress.json`** with all discovered modules set to `pending`, target coverage, and the branch name.
301 - Default `target` is **100** unless the user explicitly requests a different target.
302 - Default `maxIterations` is **100**. The user may override with a lower value.
303
30411. **Save context to memory**: Write `/memories/repo/test-gen-context-<repo-name>.md` (derive `<repo-name>` from the repo root folder name) with all discovered information (build commands, test framework, conventions, CI pipeline, .runsettings, gotchas). See the "Memory Context Template" section in [references/skill-templates.md](references/skill-templates.md). This ensures future sessions start with full context.
305
306**[STOP] CHECKPOINT**: Present to the user:
307- **Languages detected** and modules per language
308- Files to test and files excluded (per language)
309- Build, test, coverage, and lint commands discovered (per language)
310- Target framework per language
311- CI pipeline info (including the exact coverage collection commands/tasks/settings, exclusions, report paths, and coverage merge strategy)
312- Repo-specific post-creation actions from Stage 0 context (e.g., CI registration files, CloudTest map updates, workspace configs)
313- Branch name created
314- Coverage target
315- Module manifest (for monorepos / multi-language repos)
316
317Checkpoint validity rule:
318- If CI coverage collection details are missing, inferred, or only partially known, Stage 0 must label coverage as `provisional` and list the missing evidence explicitly. Do not present local coverage numbers as CI-equivalent unless the CI collection path has been verified.
319
320Checkpoint validity rule:
321- If multiple languages were detected but the checkpoint scope is reported as single-language (for example "C# module only"), Stage 0 is INCOMPLETE and must be redone before Stage 1.
322
323In interactive mode: **End your response and wait for user confirmation.** In auto mode: report the checkpoint, then continue to Stage 1.
324
325---
326
327### Stage 1: Pre-Flight (Build + Test + Lint)
328
329Verify the codebase is healthy before generating anything. Run for ALL language modules discovered in Stage 0.
330
331Note: All required commands and details are already captured in Stage 0 Repo Context memory (`/memories/repo/test-gen-context-<repo-name>.md`).
332
3331. **Clean and build locally** (ALL languages): clean stale build artifacts before building (`dotnet clean`, `cargo clean`, `rm -rf dist/`, `npm run clean`, etc.) to prevent inflated coverage from outdated binaries. Then run the build command for EVERY module from repo context memory. If any build fails: diagnose, report, STOP.
334
3352. **Run existing tests** (ALL languages): record per-language total, passed, failed, skipped. If tests fail, report and wait for acknowledgment.
336
3373. **Run linter/formatter** (if discovered in Stage 0): record existing lint warnings/errors as baseline. Generated tests must not introduce new violations.
338
3394. **Record baseline state**: exact counts of passing tests, failing tests, skipped tests, lint violations.
340
341---
342
343### Stage 2: Coverage Baseline
344
345**Reference**: Read `references/code-coverage.md` for tool-specific commands, parsing, and **multi-language report merging**.
346
347Note: All required coverage commands and settings are already captured in Stage 0 Repo Context memory (`/memories/repo/test-gen-context-<repo-name>.md`).
348
349Execution guard for stored coverage commands (mandatory): execute the stored command as-is; if quoting fails, fix only shell escaping (not command intent), and prefer `$env:TEMP` for coverage outputs.
350
3511. Create a temp directory for coverage output (never write into the repo).
3522. **Honor coverage exclusions/settings first**:
353 - If the repo has `.runsettings`, `coverlet` config, `.nycrc`, or `coverage.config`, read exclusions before collecting coverage.
354 - Use the same settings as CI when running local coverage.
355 - Record which coverage settings file was used in `.coverage-progress.json`.
3563. **Collect coverage for EVERY language module** discovered in Stage 0 (not just one):
357 - For each module, run its coverage command from repo context memory and produce a Cobertura XML.
358 - Run coverage for ALL detected languages, not just one.
359 - Store each language's Cobertura XML separately (e.g., `<language>-coverage.cobertura.xml`).
3604. **Merge all Cobertura reports** into a single combined report (mirrors CI behavior):
361 - Use `scripts/parse-cobertura.ps1` with ALL report files as input (it merges automatically):
362 ```powershell
363 $allReports = Get-ChildItem $coverageDir -Recurse -Filter "*.cobertura.xml" | ForEach-Object { $_.FullName }
364 .\scripts\parse-cobertura.ps1 @allReports
365 ```
366 - The **merged** coverage number is the baseline, not any single language's number.
3675. Parse merged results into structured data.
3686. Record baseline: overall line/branch coverage, per-file breakdown (all languages), uncovered line ranges.
3697. Update `mergedBaseline` in `.coverage-progress.json` and each module's individual `baseline`.
3708. **Classify uncovered code by priority** (P0-P3), while excluding known exclusions (constants, DTOs, properties, logging, simple constructors, dead code):
371 - P0 -- Critical: business logic with < 50% coverage
372 - P1 -- Critical: exception handlers, error paths, retry logic, catch blocks
373 - P2 -- Medium: utility methods with < 50% coverage
374 - P3 -- Low: branch gaps in already-covered methods
3759. **Exclude from gap list** (do NOT target these for tests):
376 - Constants, constant classes, constant fields
377 - Default value assignments
378 - Auto-properties (get/set only)
379 - DTO/POCO classes, records, and data containers (properties only, no logic)
380 - Logging convention code, logger providers, log formatters
381 - Constructors (unless they contain branching logic)
382 - Classes or methods marked `[Obsolete]` — do not generate tests for deprecated code
383 - Dead code: unreachable methods, unused classes, orphaned code paths, commented-out blocks (distinguish dead from rare -- exception handlers and fallback logic are NOT dead code)
38410. **Align local and CI coverage behavior**:
385 - Validate CI coverage collection and merge strategy per language from pipeline config.
386 - Match the actual CI collector/tool choice per language before running local coverage (for example built-in `Code Coverage`, `XPlat Code Coverage`, `dotnet-coverage`, Jest/Vitest coverage reporters, pytest `--cov`, `cargo-llvm-cov`).
387 - Match CI settings files, include/exclude patterns, output formats, report paths, and publish-time merge assumptions.
388 - If CI merges N language reports, local baseline must also merge N reports.
389 - If CI settings are unknown, record that explicitly in output and mark the result `provisional`; do not claim parity with CI.
39011. Clean up temp directory.
391
392**Stage 2 output**: Present a summary table showing where the skill will focus next:
393
394See the "Stage 2 Output Template" section in [references/skill-templates.md](references/skill-templates.md).
395
396Include rows for EVERY detected language. Show **ALL actionable targets** (every file/module where coverage can be increased) plus any notable exclusions. Do NOT cap the list at any fixed number — the plan and each iteration must cover the maximum possible files.
397
398**COMPLETION CHECK**: If your Stage 2 output does not contain the Priority Targets table, you have not completed Stage 2. The raw coverage numbers alone are not enough -- the agent and user need to see the prioritized file list before planning.
399
400**This coverage data is the PRIMARY INPUT for test planning.**
401
402If tests cannot run (build failures, missing deps), note the blocker, skip this stage, proceed without baseline. If coverage is already 90%+, focus only on remaining hotspots.
403
404---
405
406### Stage 3: Plan
407
408**Reference**: Read `references/test-planning.md` for prioritization rules, plan format, and categories.
409
410**This is a SEPARATE stage. Do NOT generate code here.**
411
4121. Use coverage gaps from Stage 2 as PRIMARY input, prioritized P0-P3.
4131a. Build the Stage 3 plan across ALL detected language modules in `.coverage-progress.json`, not just the current/lowest-coverage language.
4141b. **Batch multiple files per iteration**: Plan tests for 10-15 source files per iteration (grouped by module or dependency proximity). More files per iteration = fewer loop cycles = more coverage per session. Do not plan for only 1 file when multiple actionable files exist.
4152. **Critical path first**:
416 - Exception handlers and catch blocks --> MUST have tests
417 - Error return paths --> MUST have tests
418 - Retry/fallback logic --> MUST have tests
419 - Validation logic --> MUST have tests
420 - Happy path business logic --> standard coverage
4213. **Do NOT plan tests for**: constants, default values, properties, DTOs, logging, simple constructors, dead code, already-covered code.
4223b. **Behavioral deduplication (MANDATORY)**: Before planning tests for any source file, read the existing test files that cover it. If an existing test already validates the same scenario/behavior (even if lines are marked uncovered due to mocking differences), do NOT plan a duplicate. New tests must add incremental behavioral value — covering a genuinely untested code path or scenario — not re-test what existing tests already verify.
4234. **If deferring a plan item**, document WHY (e.g., "requires integration test", "testability blocker -- static factory with no seam", "external dependency cannot be mocked"). Every `TP-<SourceFile>-<ShortName>` ID must be either implemented or have an explicit skip reason.
424 - ID format: `TP-<SourceFile>-<ShortName>` where `<SourceFile>` is the short source file name (no extension, no path) and `<ShortName>` is a ≤5-word camel-case description of the scenario (e.g., `TP-OrderService-PaymentFailurePath`, `TP-LeaseHook-NullInputReturnsError`).
4255. Each test case: ID, Name, Priority, Coverage Target (file:lines), Arrange, Act, Assert.
4266. Map every test to a specific coverage gap.
4277. Flag latent bugs with **WARNING**.
428
429**Plan output**: structured markdown table with ID, Language, Test Name, Priority (P0-P3), Coverage Target, Arrange, Act, Assert.
430
431**Stage 3 Plan Quality Gate (MANDATORY before Stage 4)**:
432- Verify each planned test has a meaningful assertion strategy (not coverage-only / not assert-free).
433- Verify planned tests target real behavioral contracts — not assumed behavior. Each test's expected outcome must be derivable from the source code, not guessed.
434- Verify planned tests comply with "Do NOT generate" exclusions.
435- Verify each planned test maps to a concrete Stage 2 coverage gap.
436- Verify the plan includes entries for every detected language module that still has actionable uncovered targets, or explicitly records why a language has no planned items.
437- If any planned item violates quality/exclusion rules, revise the plan before proceeding.
438
439**[STOP] CHECKPOINT**: Show the complete per-language test plan. Include a short language coverage summary (planned items per language, or explicit "no actionable targets" reason). In interactive mode: end your response and wait for user confirmation before proceeding to Stage 4. In auto mode: report the checkpoint and continue to Stage 4 automatically.
440---
441
442### Stage 4: Generate
443
444**Reference**: Read `references/test-generation.md` for framework rules, namespace verification, test framework compatibility, Moq workarounds, metadata, and project file rules. Read `references/test-quality.md` Part 2 for anti-patterns to avoid.
445
446**Canonical Gate Check (single source)**: You must have a written, quality-gated Stage 3 plan for the current iteration. Required:
447- TP IDs for each planned test
448- Structured Stage 3 per-language plan table
449- Stage 3 quality gate pass
450- Current runId and iteration number
451- Plan entries for all detected language modules
452
453If any are missing, stop and return to Stage 3.
454
455**Step 0 — Think Before Generating (MANDATORY per source file)**:
456
457Before writing ANY test code for a source file, reason explicitly:
458
4591. **Read the source file fully** — understand its purpose, dependencies, and behavioral contracts.
4602. **State assumptions** — list what you believe the method/class does and what constitutes correct vs incorrect behavior. If uncertain about any behavior, flag it — do not guess and run with it.
4613. **Surface ambiguities** — if multiple interpretations of behavior exist (e.g., "does null input throw or return empty?"), check the source code, existing tests, or documentation to resolve. If unresolvable, pick the interpretation supported by the code and note it as a comment in the test.
4624. **Identify tradeoffs** — if a simpler test approach exists (fewer mocks, less setup), prefer it. If 200 lines of setup could be 50, rewrite the approach before generating.
4635. **Push back on the plan** — if a planned test item is untestable, redundant, or would produce a low-value test, skip it with a documented reason rather than generating junk.
464
465This step produces no file output — it is internal reasoning that makes generated code accurate on the first attempt. Skip this step only for trivially simple tests (e.g., single-line validation methods).
466
4671. Generate compilable test code implementing every planned test case.
4682. Follow AAA pattern (Arrange-Act-Assert) with explicit comments.
4693. Test names: `MethodName_Scenario_ExpectedBehavior`.
4703b. **Plan ID comments** (RECOMMENDED): Add a comment mapping each test method to its plan item: `// TP-<SourceFile>-<ShortName>`. This aids traceability but missing TP comments do not block the quality gate or Stage 5 review.
4713c. **Line length limit** (MANDATORY): Keep all generated lines within the repo's max line length (from `.editorconfig` or CI checks). Break long lines — split string literals, chain method calls across lines, use intermediate variables. This is a common CI gate failure.
4724. **Read actual source files** before generating -- verify namespaces, types, method signatures. Never assume.
4735. **Match the repo's test framework AND assertion library exactly** -- detect from existing test projects (NUnit, MSTest, xUnit, Jest, pytest, etc.). Also detect the assertion library (FluentAssertions `.Should()`, Shouldly, built-in `Assert.*`). Use whichever the repo already uses — do NOT mix assertion styles. Follow the version-specific API rules from `test-generation.md`.
4746. Apply Moq expression tree workarounds for optional parameters.
4757. If creating a new `.csproj`: apply project file rules, set `<ProjectUsageType>UnitTest</ProjectUsageType>`.
476
477**Project placement**: NEVER create a `.csproj` in the same folder as another project. Before creating a test project, discover the repo's existing convention:
4781. Search for existing `*.Tests.csproj` or `*.UnitTests.csproj` files and note where they live relative to their source projects.
4792. **Check if a test project already exists for the source project** — search ALL existing test `.csproj` files for `<ProjectReference>` elements pointing to the source project. Test project names frequently differ from their source (e.g., `SystemActivities.UnitTest.csproj` tests `Workflow.SystemActivities.csproj`). If an existing test project already references the source, add new tests to THAT project instead of creating a new one.
4803. Match that pattern exactly. Common conventions (in order of prevalence):
481 - `test/<Source>.Tests/` (separate test root: `src/MyService/` --> `test/MyService.Tests/`)
482 - `src/<Source>.Tests/` (sibling under src: `src/MyService/` --> `src/MyService.Tests/`)
483 - `tests/<Source>.Tests/` (plural test root)
484 - `<Source>/tests/` (tests subfolder per project)
4854. If no existing test projects exist, prefer the `test/` or `tests/` root convention if those directories exist, otherwise use sibling folder under `src/`.
4865. Check the target directory contents before creating -- if any `.csproj` already exists there, choose a different folder.
487
488**Traceability attributes** (MANDATORY):
489- NUnit: `[Category("AutoGenerated")]` and `[Category("CopilotSkill:autocoverage")]`
490- MSTest: `[TestCategory("AutoGenerated")]` and `[TestCategory("CopilotSkill:autocoverage")]`
491- xUnit: use `[Trait("Category", "AutoGenerated")]` and `[Trait("Category", "CopilotSkill:autocoverage")]`
492- Jest/Vitest: add `// @category AutoGenerated CopilotSkill:autocoverage` at top of file
493- pytest: use `@pytest.mark.auto_generated` marker
494
495**Placement rule**: Always place traceability attributes on each **new test method**, never on the class. This ensures only generated methods are tagged, regardless of whether the class is new or pre-existing.
496
497**File organization**: One test class per source class, one test file per test class. Never combine test classes for unrelated source classes in a single file.
498- Test file name must match the source class: `OrderService.cs` → `OrderServiceTests.cs`.
499- **NEVER prefix file or class names with tool/skill/iteration identifiers** (`AutoCoverage_`, `Iter<N>_`, `Generated_`, etc.). Traceability goes in attributes and comments, not filenames. See `references/test-generation.md` "File Naming" and "Coexistence with Existing Test Files" sections.
500- If a test file already exists for the source class, add methods to it or create `<SourceClass>ExtendedTests.cs` — never a tool-prefixed duplicate.
501- If a source file contains multiple public classes, each gets its own test file.
502- Never create a single "catch-all" test file that tests multiple unrelated classes.
503
504**Do NOT generate tests that**:
505- Re-compute expected values from production logic (tautological)
506- Exist only to increase coverage without validating behavior (coverage-only tests)
507- Have no meaningful assertions (including assertion-free tests or exception-only smoke tests)
508- Only assert `IsNotNull` without checking values
509- Mock the class under test
510- Use reflection to test private methods
511- Have 50+ lines of setup for one assertion
512- Duplicate production code in the test
513- Duplicate behavior already tested by existing tests (same scenario, same assertions on same class)
514- Test constants, DTOs, records, constructors, properties, logging, or `[Obsolete]` classes/methods
515- Target methods with unbounded `while` loops or gRPC/async stream consumers — they deadlock test runners
516- Test pure delegation methods (`return await x.Method()`) — only proves the mock works
517- Assert on log-message string fragments — breaks on any message rewording
518
519---
520
521### Stage 5: Review + Finalize
522
523**Reference**: Read `references/test-quality.md` for the full review checklist and quality gate. Read `references/test-finalization.md` for the compliance checklist.
524
525**This stage has TWO mandatory parts. Completing only Part A is a VIOLATION. You MUST complete Part B before proceeding to Stage 6.**
526
527**Part A -- Build**: Build the test project. Fix all compilation errors (StyleCop, analyzers, missing usings, wrong APIs). Iterate until clean build. Do not proceed to Part B with broken code.
528
529**Part B -- Quality Review (MANDATORY -- build passing is NOT enough)**: After the build passes, evaluate EACH generated test against the checklist in `test-quality.md` Part 3. Check every test for:
530- **Reasoning accuracy** — does the test demonstrate correct understanding of the source behavior? If assertions test the wrong expected values or misinterpret method contracts, the test is REJECT regardless of whether it passes.
531- Assertion quality (HIGH/MEDIUM/LOW) -- are assertions validating behavior or just checking not-null?
532- Coverage-only pattern -- does the test exist only to execute lines without validating outcomes?
533- Assertion presence -- does the test include at least one meaningful behavioral assertion?
534- Tautological patterns -- does any test re-compute expected values from production logic?
535- Exclusion violations -- does any test target constants, DTOs, constructors, properties, or logging?
536- Mock correctness -- is the class under test being mocked? Are mocks set up and verified properly?
537- Edge cases -- null, empty, boundary inputs covered?
538- Determinism -- any time/random/network dependence?
539
540Produce a per-test verdict table:
541
542| Test Name | Plan Item | Assertion Quality | Issues | Verdict |
543|-----------|-----------|-------------------|--------|---------|
544
545Verdicts: APPROVED / NEEDS REVISION / REJECT.
546
547**Part C -- Quality Gate (MANDATORY HARD GATE before Stage 6)**: Run the quality gate from `test-quality.md` Part 4 against all generated tests in this iteration. Produce the scorecard:
548
549| Dimension | Target | Actual | Status |
550|-----------|--------|--------|--------|
551| Assertion Density
552
553…(truncated)