Installs into .claude/skills of the current project.
Are you the author of Test Gen?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nightgauge-test-gen)
---
name: test-gen
description: Generate comprehensive test suites with coverage analysis using parallel
subagents. Supports Jest, Pytest, dotnet test, and Gradle. Use after
/feature-dev or when any codebase needs better test coverage.
license: Apache-2.0
metadata:
author: nightgauge
version: "1.0.0"
source: https://github.com/nightgauge/nightgauge
allowed-tools: Read Write Edit Glob Grep Bash Task AskUserQuestion
disable-model-invocation: true
---
# Test Generation
> Generate comprehensive test suites with parallel subagents and coverage
> analysis
## Description
This skill generates comprehensive test suites by:
1. Analyzing source code to identify testable components
2. Detecting the test framework (Jest, Pytest, dotnet test, Gradle)
3. Spawning parallel subagents for unit, integration, and E2E tests
4. Analyzing coverage to identify gaps
5. Generating edge case tests automatically
6. Running tests to verify they pass
## Invocation
| Tool | Command |
| -------------- | ----------------------------------- |
| Claude Code | `/nightgauge:test-gen` (via plugin) |
| OpenAI Codex | `$nightgauge-test-gen` |
| GitHub Copilot | Invoke via Agent Skills |
| Cursor | Invoke via Agent Skills |
## Arguments
```bash
# Generate tests for changed files (default)
/nightgauge:test-gen
# Target specific files
/nightgauge:test-gen --files "src/services/*.ts"
# Set coverage target
/nightgauge:test-gen --target-coverage 90
# Generate only specific test types
/nightgauge:test-gen --types unit,integration
# Skip E2E tests (faster)
/nightgauge:test-gen --skip-e2e
# Preview what would be generated
/nightgauge:test-gen --dry-run
```
## Prerequisites
- **Source code to test**: Either specify with `--files` or have uncommitted
changes
- **Test framework**: Will be auto-detected or you'll be asked
- **Test runner**: npm test, pytest, dotnet test, or gradle test
## Philosophy
- **Coverage-driven** — Focus on untested code paths first
- **Framework-native** — Generate idiomatic tests for each framework
- **Edge-case aware** — Automatically test boundaries, nulls, errors
- **Non-destructive** — Ask before modifying existing tests
- **Parallel execution** — Spawn subagents for faster generation
---
## Workflow
### Phase 0: Environment Preflight
### Phase 0: Environment Preflight
**PURPOSE**: Verify the nightgauge binary is available and the environment
is healthy before executing any pipeline stage. Skills halt immediately with
actionable instructions when this check fails.
Three-layer check:
1. **Binary discovery** — shell-level, runs before invoking the binary
2. **Doctor check** — binary-level, full environment health via `doctor --json`
3. **Project environment** — the checks the project declares under
`preflight.environment` (#101); opt-in, silent when none are declared
The binary discovery cascade below is **provider-neutral** (issue 4029): it works
identically under every adapter (Claude, Codex, Gemini, …) and references no
VSCode-extension-specific path. The host that spawns the skill (the VSCode
extension's `skillRunner`, or the Go `nightgauge` binary in auto/CLI mode)
resolves the binary authoritatively and exports `NIGHTGAUGE_BIN`, which
this cascade honors first. The remaining steps (PATH, the development-only repo
`bin/` and worktree canonical-repo `bin/`, `~/go/bin`) are fallbacks for direct
or terminal invocation.
This intentionally **diverges** from
`claude-plugins/nightgauge/hooks/lib/guard.sh` (issue 3262 → issue 4029): guard.sh is
Claude-Code-only and retains a trailing `~/.vscode/extensions/...` glob to serve
the standalone-terminal-Claude case (where no host exports `NIGHTGAUGE_BIN`).
Skills must stay portable Markdown and cannot reference that VSCode-only path —
so the shared resolution order (`NIGHTGAUGE_BIN` → PATH → gated repo → gated
canonical → `~/go/bin`) stays in sync, and only guard.sh carries the extra Claude-only
fallback. The `nightgauge preflight skill-portability` gate enforces that
no skill reintroduces a `.vscode/extensions` path, and that every cascade keeps
the `NIGHTGAUGE_DEV_REPO_BIN` gate (#2758): a foreign repository's
`bin/nightgauge` must never run.
After the cascade resolves `$BINARY`, the preflight prepends
`dirname($BINARY)` to `PATH` so subsequent bare `nightgauge ...`
invocations later in the same skill body resolve through PATH rather than
failing with "command not found" (issue 3262).
```bash
# Layer 1: provider-neutral binary discovery (NIGHTGAUGE_BIN → PATH → gated repo
# bin → gated canonical-repo bin → ~/go/bin). No VSCode-extension path — see issue 4029.
# Repo bins need NIGHTGAUGE_DEV_REPO_BIN=1 and a verified core checkout (#2758).
BINARY="${NIGHTGAUGE_BIN:-}"
[ -n "$BINARY" ] && [ ! -x "$BINARY" ] && BINARY=""
[ -z "$BINARY" ] && BINARY=$(command -v nightgauge 2>/dev/null || echo "")
if [ -z "$BINARY" ] && [ "${NIGHTGAUGE_DEV_REPO_BIN:-}" = "1" ]; then
for NG_ROOT in "$(git rev-parse --show-toplevel 2>/dev/null)" \
"$(cd "$(git rev-parse --git-common-dir 2>/dev/null)/.." 2>/dev/null && pwd)"; do
[ -x "$NG_ROOT/bin/nightgauge" ] || continue
grep -qx 'module github.com/nightgauge/nightgauge' "$NG_ROOT/go.mod" 2>/dev/null || continue
NG_ORIGIN="$(git -C "$NG_ROOT" config --get remote.origin.url 2>/dev/null)"
case "${NG_ORIGIN%.git}" in
git@github.com:nightgauge/nightgauge | https://github.com/nightgauge/nightgauge | \
ssh://git@github.com/nightgauge/nightgauge) BINARY="$NG_ROOT/bin/nightgauge"; break ;;
esac
done
fi
[ -z "$BINARY" ] && [ -x "$HOME/go/bin/nightgauge" ] && BINARY="$HOME/go/bin/nightgauge"
if [ -z "$BINARY" ]; then
echo "ERROR: nightgauge binary not found via NIGHTGAUGE_BIN, PATH or ~/go/bin" >&2
echo "" >&2
echo "Install via: go install github.com/nightgauge/nightgauge/cmd/nightgauge@latest" >&2
echo "Or download from: https://github.com/nightgauge/nightgauge/releases" >&2
echo "Run \`nightgauge doctor\` after installing to verify your environment." >&2
exit 1
fi
# Make the resolved binary callable via bare `nightgauge ...` later in this skill body (issue 3262).
export PATH="$(dirname "$BINARY"):$PATH"
# Per-repo GitHub token: export GH_TOKEN so every `gh` call in this skill
# authenticates as the *current repo's* configured user instead of the machine-
# global `gh auth` active account. `forge auth token` resolves per-repo (config
# github_user/token → GITHUB_TOKEN → `gh auth token --user <github_user>`), so
# concurrent sessions/workspaces owned by different GitHub users each use their
# own token. GH_TOKEN outranks GITHUB_TOKEN and the gh keyring. Only resolve when
# unset (an upstream env value — e.g. the VSCode extension's terminal env —
# wins); never fail preflight over it. Mirrors hooks/lib/guard.sh (sync, issue 3262).
if [ -z "${GH_TOKEN:-}" ]; then
IB_REPO_TOKEN="$("$BINARY" forge auth token 2>/dev/null || true)"
if [ -n "$IB_REPO_TOKEN" ]; then
export GH_TOKEN="$IB_REPO_TOKEN"
export GITHUB_TOKEN="${GITHUB_TOKEN:-$IB_REPO_TOKEN}"
fi
unset IB_REPO_TOKEN
fi
# Layer 2: full environment health check via doctor --json
DOCTOR_RESULT=$("$BINARY" doctor --json 2>/dev/null)
DOCTOR_EXIT=$?
if [ "$DOCTOR_EXIT" -eq 2 ]; then
echo "ERROR: Environment check failed — nightgauge doctor reports broken environment." >&2
FAILED_CHECKS=$(printf '%s\n' "$DOCTOR_RESULT" | jq -r '.failed_checks[]? // empty' 2>/dev/null)
if [ -n "$FAILED_CHECKS" ]; then
echo "Failing check(s): $(echo "$FAILED_CHECKS" | tr '\n' ',' | sed 's/,$//')" >&2
fi
printf '%s\n' "$DOCTOR_RESULT" | jq -r '.errors[]' >&2 2>/dev/null || true
INSTALL_MSG=$(printf '%s\n' "$DOCTOR_RESULT" | jq -r '.install_instructions // empty' 2>/dev/null)
[ -n "$INSTALL_MSG" ] && echo "$INSTALL_MSG" >&2
exit 1
fi
if [ "$DOCTOR_EXIT" -eq 1 ]; then
echo "WARNING: Environment has non-critical issues:" >&2
printf '%s\n' "$DOCTOR_RESULT" | jq -r '.warnings[]' >&2 2>/dev/null || true
# Continue — warnings do not block skill execution
fi
# Layer 3: project-declared environment checks (preflight.environment, #101).
# Opt-in: with none declared this prints nothing and continues. The verb
# writes one `[environment-halt]` line per failed check, with its remedy.
if ! "$BINARY" preflight environment >/dev/null; then
echo "[environment-halt] Project environment check failed — fix the environment, not the code." >&2
exit 1
fi
```
**Exit codes from `nightgauge doctor`**:
| Code | Meaning | Skill behavior |
| ---- | ------------------------------- | --------------------------------------- |
| 0 | Healthy | Continue |
| 1 | Degraded (warnings only) | Continue with warning printed to stderr |
| 2 | Broken (required checks failed) | Halt immediately with error details |
**Layer 3** halts the stage when any check declared under
`preflight.environment` in the workspace's `.nightgauge/config.yaml` fails
(exit 1) or the declaration is invalid (exit 2). The failure is in the
operator's environment, not the code: do not edit code to work around it, do
not run the remedy yourself, and end the stage. The pipeline records it as
`environment_halt` and holds the issue for an operator.
Do not theorize about the cause beyond what is printed here — the failing
check name (`failed_checks`) and its `.errors[]` message are the complete
diagnosis (#277).
---
### Phase 1: Context Inference
#### Step 1.1: Extract Issue from Branch
**CRITICAL**: This skill is stateless. Extract issue context from branch name:
```bash
# Get current branch
BRANCH=$(git branch --show-current)
# Extract issue number from branch (e.g., feat/42-description -> 42)
ISSUE_NUMBER=$(printf '%s\n' "$BRANCH" | grep -oE '[0-9]+' | head -1)
echo "Branch: $BRANCH"
echo "Issue: #$ISSUE_NUMBER"
```
#### Step 1.2: Parse Arguments
Check for provided arguments:
- `--files` - Specific files to generate tests for
- `--target-coverage` - Coverage percentage target (default: 80)
- `--types` - Comma-separated: unit, integration, e2e (default: all)
- `--skip-e2e` - Skip E2E tests
- `--dry-run` - Show plan without writing files
---
### Phase 2: Source Analysis
#### Step 2.1: Identify Target Files
If `--files` argument provided:
```bash
# Use the provided glob pattern
FILES=$(ls $FILES_ARG 2>/dev/null)
```
Otherwise, detect changed files:
```bash
# Get files changed compared to main
git diff --name-only $(git merge-base main HEAD)..HEAD
# Or get uncommitted changes
git diff --name-only HEAD
```
#### Step 2.2: Filter to Testable Code
Exclude non-testable files:
```bash
# Exclude test files, configs, docs, assets
printf '%s\n' "$FILES" | grep -v -E '(\.test\.|\.spec\.|__tests__|\.config\.|\.md$|\.json$|\.yaml$|\.yml$)'
```
#### Step 2.3: Analyze File Contents
For each target file:
1. Read the file content
2. Identify:
- Functions/methods that need tests
- Classes and their public interfaces
- Exported modules/components
- Dependencies to mock
#### Step 2.4: Report Analysis
```
Source Analysis Complete
========================
Files to test: 5
- src/services/UserService.ts (3 functions)
- src/services/PhotoService.ts (4 functions)
- src/utils/validation.ts (6 functions)
- src/routes/users.ts (5 endpoints)
- src/routes/photos.ts (3 endpoints)
Total testable units: 21
Existing tests: 8
Gap: 13 units (62% coverage gap)
```
---
### Phase 3: Framework Detection
#### Step 3.1: Detect Test Framework
Check for framework indicators:
**JavaScript/TypeScript:**
```bash
# Check package.json for test framework
cat package.json | grep -E '"jest"|"vitest"|"mocha"|"jasmine"'
# Check for config files
ls jest.config.* vitest.config.* .mocharc.* 2>/dev/null
```
**Python:**
```bash
# Check for pytest
cat pyproject.toml requirements.txt setup.py 2>/dev/null | grep -i pytest
# Check for unittest references
ls tests/test_*.py *_test.py 2>/dev/null
```
**.NET:**
```bash
# Check csproj for test framework
grep -r "xunit\|nunit\|mstest" *.csproj */*.csproj 2>/dev/null
```
**Java:**
```bash
# Check build files
cat build.gradle pom.xml 2>/dev/null | grep -E "junit|testng|spock"
```
#### Step 3.2: Confirm or Ask
If framework detected:
```
Detected test framework: Jest
Test command: npm test
Coverage command: npm run test:coverage
Proceeding with Jest test generation...
```
If not detected:
```json
{
"questions": [
{
"question": "Could not auto-detect test framework. Which are you using?",
"header": "Framework",
"multiSelect": false,
"options": [
{ "label": "Jest", "description": "JavaScript/TypeScript testing" },
{ "label": "Pytest", "description": "Python testing framework" },
{ "label": "xUnit/NUnit", "description": ".NET testing" },
{ "label": "JUnit/Gradle", "description": "Java testing" }
]
}
]
}
```
#### Step 3.3: Set Framework Configuration
Based on detected framework, set:
- Test file naming pattern (`.test.ts`, `_test.py`, `Tests.cs`)
- Test directory structure (`__tests__/`, `tests/`, `*.Tests/`)
- Mocking library (jest.mock, unittest.mock, Moq, Mockito)
- Assertion style
---
### Phase 4: Parallel Test Generation
#### Step 4.1: Prepare Subagent Prompts
Create focused prompts for each test type:
**Unit Test Subagent:**
```
Generate unit tests for the following files:
[file list]
Requirements:
- Test each function/method in isolation
- Mock all external dependencies
- Cover happy path + error cases
- Use [framework] patterns
- Follow naming: should_[expected]_when_[condition]
```
**Integration Test Subagent:**
```
Generate integration tests for the following components:
[component list]
Requirements:
- Test component interactions
- Use real dependencies where practical
- Test data flow between components
- Cover API contracts
```
**E2E Test Subagent (if not skipped):**
```
Generate E2E tests for the following user flows:
[flow list]
Requirements:
- Test complete user journeys
- Set up and tear down test data
- Use appropriate E2E framework (Playwright, Cypress, etc.)
```
#### Step 4.2: Launch Parallel Subagents
**Subagent Model**: When spawning parallel subagents for test generation,
specify `model: "sonnet"` on each Task invocation to use a cost-optimized model.
Sonnet provides sufficient quality for test generation while reducing token
costs compared to Opus.
```
Launching test generation subagents...
[1/3] Unit Test Subagent: Running (model: sonnet)
[2/3] Integration Test Subagent: Running (model: sonnet)
[3/3] E2E Test Subagent: Running (model: sonnet, skipped if --skip-e2e)
```
Use the `Task` tool to spawn subagents with `model: "sonnet"`:
```
Launch 3 subagents in parallel:
1. Unit test generator (model: sonnet):
- Focus: Individual functions
- Scope: All target files
- Output: test files in appropriate locations
2. Integration test generator (model: sonnet):
- Focus: Component interactions
- Scope: Service/route combinations
- Output: integration test files
3. E2E test generator (model: sonnet, unless --skip-e2e):
- Focus: User flows
- Scope: Critical paths
- Output: E2E test files
```
#### Step 4.3: Collect Results
Wait for all subagents to complete. Collect:
- Generated test files
- Test count per type
- Any warnings or issues
---
### Phase 5: Coverage Analysis
#### Step 5.1: Run Existing Tests with Coverage
```bash
# JavaScript/TypeScript
npm run test:coverage
# Python
pytest --cov=src --cov-report=json
# .NET
dotnet test --collect:"XPlat Code Coverage"
# Java/Gradle
./gradlew test jacocoTestReport
```
#### Step 5.2: Parse Coverage Report
Extract coverage data:
- Overall coverage percentage
- Per-file coverage
- Uncovered lines/branches
- Uncovered functions
#### Step 5.3: Identify Coverage Gaps
Compare to `--target-coverage` (default 80%):
```
Coverage Analysis
=================
Current coverage: 65%
Target coverage: 80%
Gap: 15%
Files needing attention:
- src/services/PhotoService.ts: 45% covered
- Uncovered: uploadPhoto(), deletePhoto()
- src/utils/validation.ts: 50% covered
- Uncovered: validateEmail(), validatePhone()
```
#### Step 5.4: Generate Additional Tests for Gaps
If coverage below target, generate tests specifically for uncovered code:
```
Generating tests for coverage gaps...
- PhotoService.uploadPhoto() -> tests/services/photo.test.ts
- PhotoService.deletePhoto() -> tests/services/photo.test.ts
- validation.validateEmail() -> tests/utils/validation.test.ts
```
---
### Phase 6: Edge Case Generation
#### Step 6.1: Identify Input Types
For each function, identify parameter types:
- String parameters
- Numeric parameters
- Boolean parameters
- Object/Array parameters
- Async operations
#### Step 6.2: Generate Edge Case Tests
**String Edge Cases:**
```typescript
// Empty string
expect(validate("")).toBe(false);
// Null/undefined
expect(validate(null)).toThrow();
// Unicode characters
expect(validate("héllo 世界")).toBe(true);
// Very long string
expect(validate("a".repeat(10000))).toBe(false);
// Special characters
expect(validate('<script>alert("xss")</script>')).toBe(false);
```
**Numeric Edge Cases:**
```typescript
// Zero
expect(calculate(0)).toBe(0);
// Negative
expect(calculate(-1)).toThrow();
// Very large
expect(calculate(Number.MAX_SAFE_INTEGER)).toBe(expected);
// Decimal precision
expect(calculate(0.1 + 0.2)).toBeCloseTo(0.3);
// NaN
expect(calculate(NaN)).toThrow();
```
**Collection Edge Cases:**
```typescript
// Empty array
expect(process([])).toEqual([]);
// Single item
expect(process([1])).toEqual([1]);
// Large array (performance)
expect(process(Array(1000).fill(1))).toBeDefined();
```
**Async Edge Cases:**
```typescript
// Timeout
jest.setTimeout(5000);
await expect(fetchWithTimeout()).rejects.toThrow("timeout");
// Network failure
mockFetch.mockRejectedValue(new Error("Network error"));
await expect(fetchData()).rejects.toThrow();
```
---
### Phase 7: Non-Destructive Check
#### Step 7.1: Check for Existing Test Files
```bash
# Find existing test files that would be modified
ls tests/**/*.test.ts tests/**/*.spec.ts 2>/dev/null
```
#### Step 7.2: Compare with Generated Tests
For each file that would be created/modified:
- Check if file already exists
- Compare generated content with existing
- Identify conflicts
#### Step 7.3: Ask User for Handling
If existing tests found:
```json
{
"questions": [
{
"question": "Found existing test files that would be modified. How would you like to proceed?",
"header": "Existing Tests",
"multiSelect": false,
"options": [
{
"label": "Add only",
"description": "Append new tests to existing files, keep all existing tests"
},
{
"label": "Replace",
"description": "Overwrite existing test files completely"
},
{
"label": "Create separate",
"description": "Use .generated.test.ts naming to keep both"
},
{
"label": "Review first",
"description": "Show me a diff of what would change"
}
]
}
]
}
```
#### Step 7.4: If Dry Run
If `--dry-run` was specified:
```
DRY RUN - No files will be written
Would create:
- tests/services/UserService.test.ts (45 lines, 8 tests)
- tests/services/PhotoService.test.ts (62 lines, 12 tests)
- tests/utils/validation.test.ts (38 lines, 15 tests)
Would modify:
- tests/routes/users.test.ts (+25 lines, +5 tests)
Total: 170 lines, 40 tests
Run without --dry-run to apply changes.
```
Exit after dry run report.
---
### Phase 8: Write Test Files
#### Step 8.1: Create Test Directories
```bash
mkdir -p tests/unit tests/integration tests/e2e
```
#### Step 8.2: Write Unit Tests
For each source file, create/update test file:
```typescript
// tests/services/UserService.test.ts
import { UserService } from "../../src/services/UserService";
import { mockUserRepository } from "../mocks/repositories";
describe("UserService", () => {
let service: UserService;
beforeEach(() => {
service = new UserService(mockUserRepository);
});
describe("createUser", () => {
it("should create user when valid data provided", async () => {
// Test implementation
});
it("should throw when email already exists", async () => {
// Test implementation
});
});
});
```
#### Step 8.3: Write Integration Tests
```typescript
// tests/integration/user-workflow.test.ts
describe("User Workflow Integration", () => {
it("should complete user registration flow", async () => {
// Create user
// Verify email
// Login
// Access protected resource
});
});
```
#### Step 8.4: Write E2E Tests (if not skipped)
```typescript
// tests/e2e/user-registration.spec.ts
import { test, expect } from "@playwright/test";
test("user can register and login", async ({ page }) => {
await page.goto("/register");
await page.fill('[name="email"]', "test@example.com");
// ...
});
```
#### Step 8.5: Report Progress
```
Progress: [3/5] files complete
✓ tests/services/UserService.test.ts - Created (8 tests)
✓ tests/services/PhotoService.test.ts - Created (12 tests)
✓ tests/utils/validation.test.ts - Created (15 tests)
⋯ tests/routes/users.test.ts - In progress
tests/routes/photos.test.ts - Pending
```
---
### Phase 9: Test Execution
#### Step 9.1: Run All Tests
```bash
# Run test command
npm test
# Or with coverage
npm run test:coverage
```
#### Step 9.2: Check Results
Parse test output:
- Number of tests passed
- Number of tests failed
- Coverage percentage
#### Step 9.3: Handle Failures
If tests fail:
```
Test Execution Results
======================
✓ 35 tests passed
✗ 5 tests failed
Failures:
1. UserService.test.ts > createUser > should throw when email exists
Expected: Error('Email already exists')
Received: Error('Validation failed')
2. validation.test.ts > validatePhone > should accept international format
Expected: true
Received: false
```
#### Step 9.4: Fix Failing Tests
For each failure:
1. Determine if test expectation is wrong
2. If test is wrong, fix the test assertion
3. If implementation is wrong, note it but don't fix (out of scope)
4. Re-run affected tests
```
Fixing test issues...
- UserService.test.ts: Updated error message expectation
- validation.test.ts: Fixed phone regex pattern in test
Re-running tests...
✓ All 40 tests passed
```
---
### Phase 10: Summary
#### Step 10.1: Generate Final Report
```
┌─────────────────────────────────────────────────────────────────┐
│ TEST GENERATION COMPLETE │
└─────────────────────────────────────────────────────────────────┘
Branch: feat/42-user-photo-upload
Issue: #42
## Tests Generated
| Type | Files | Tests | Coverage Added |
|-------------|-------|-------|----------------|
| Unit | 3 | 35 | +25% |
| Integration | 1 | 5 | +5% |
| E2E | 1 | 3 | N/A |
| Edge Cases | - | 12 | +5% |
| **Total** | **5** | **55**| **+35%** |
## Coverage
Before: 45%
After: 80%
Target: 80% ✓
## Files Created/Modified
Created:
- tests/services/UserService.test.ts
- tests/services/PhotoService.test.ts
- tests/utils/validation.test.ts
- tests/integration/user-workflow.test.ts
- tests/e2e/user-registration.spec.ts
Modified:
- tests/routes/users.test.ts (+5 tests)
## Next Steps
1. Review generated tests
2. Run `/nightgauge:pr-create` to create pull request
3. Or commit manually: git add tests/ && git commit -m "[TEST][#42] Add comprehensive test suite"
```
---
### Self-Assessment Epilogue
### Self-Assessment Epilogue
**PURPOSE**: Evaluate whether this skill's instructions matched reality during
this execution. This phase is **non-blocking** — skip entirely if any main phase
failed. A perfectly working skill produces **no output** from this phase.
> See [docs/SKILL_SELF_ASSESSMENT.md](https://github.com/nightgauge/nightgauge/blob/main/docs/SKILL_SELF_ASSESSMENT.md) for
> the full strategy, synthesis algorithm, and integration architecture.
#### When to Skip
- Any prior phase exited with an error before completing
- The skill was cancelled or timed out
- Running in `--dry-run` mode
#### Step 1: Evaluate Execution Friction
Review the execution that just completed. Answer these questions honestly:
1. **Command failures**: Did any command, script, or binary call in the skill
instructions fail? (e.g., script not found, wrong flags, missing binary)
2. **Workarounds**: Did you have to deviate from the skill instructions to
accomplish the goal? (e.g., used `gh` directly because a referenced script
didn't exist, skipped a step that referenced a nonexistent file)
3. **Stale references**: Did any file path, function name, API endpoint, or tool
referenced in the instructions not exist in the current codebase?
4. **Unclear instructions**: Were any instructions ambiguous enough that you had
to guess at the intended behavior?
5. **Missing instructions**: Was there a significant step you had to figure out
on your own that should have been documented in this skill?
**If ALL answers are "no" — write nothing and complete the skill normally.** The
goal is silence when everything works.
#### Step 2: Write Assessment Record
Only if friction was detected in Step 1. Write a single JSON file,
`assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json` in the clone's pipeline state
directory, through the binary (JSON on stdin; never a direct file write):
```bash
ISSUE_NUMBER="${NIGHTGAUGE_ISSUE_NUMBER:-$(git branch --show-current | sed -n 's#^[^/]*/\([0-9]*\)-.*#\1#p')}"
: "${ISSUE_NUMBER:?set NIGHTGAUGE_ISSUE_NUMBER or check out the issue branch}"
nightgauge layout write pipeline "assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json" <<'JSON'
{ ... the record below ... }
JSON
```
The assessment record MUST follow this schema:
```json
{
"schema_version": "1",
"skill": "{STAGE_NAME}",
"skill_file": "skills/nightgauge-{STAGE_NAME}/SKILL.md",
"issue_number": 42,
"timestamp": "2026-03-10T14:30:00Z",
"friction": [
{
"type": "command_failure",
"severity": "high",
"description": "hooks/lib/add-to-project.sh not found — script was deleted",
"skill_line_hint": "claude-plugins/nightgauge/hooks/lib/add-to-project.sh <issue-number>",
"actual_resolution": "Used gh api graphql to add issue to project board directly",
"suggested_fix": "Replace with: nightgauge project add <issue-number>"
}
]
}
```
**Friction types**: `command_failure`, `workaround`, `stale_reference`,
`unclear_instruction`, `missing_instruction`
**Severity levels**:
- `high` — instruction is **broken**. Required manual workaround to complete.
- `medium` — instruction is **misleading**. Agent adapted without user help.
- `low` — instruction is **suboptimal**. No functional impact.
**Rules**:
- **One record per execution** — multiple friction items go in the `friction`
array, not separate files.
- **Be specific** — quote the exact instruction text that was wrong. Not "some
commands didn't work" but "Step 5.2 calls `hooks/lib/add-to-project.sh` but
this script was deleted in commit 65915701."
- **Suggest the fix** — every finding MUST include `suggested_fix` with the
concrete SKILL.md change needed. Not "update the docs" but "replace
`hooks/lib/add-to-project.sh <N>` with `nightgauge project add <N>`."
- **Don't invent friction** — only report issues you actually encountered during
this execution. Do not speculate about potential problems.
#### Step 3: Validate and Complete
```bash
# Validate JSON if written
ISSUE_NUMBER="${NIGHTGAUGE_ISSUE_NUMBER:-$(git branch --show-current | sed -n 's#^[^/]*/\([0-9]*\)-.*#\1#p')}"
: "${ISSUE_NUMBER:?set NIGHTGAUGE_ISSUE_NUMBER or check out the issue branch}"
ASSESSMENT_FILE="$(nightgauge layout path pipeline "assessments/{STAGE_NAME}-${ISSUE_NUMBER}.json")"
if [ -f "$ASSESSMENT_FILE" ]; then
python3 -m json.tool "$ASSESSMENT_FILE" > /dev/null 2>&1 || \
echo "WARNING: Assessment record is not valid JSON" >&2
fi
```
---
## Error Handling
### No Testable Files Found
```
Warning: No testable files found.
Options:
1. Specify files manually with --files "src/**/*.ts"
2. Check that source files exist in expected locations
3. Verify you have uncommitted changes to test
```
### Framework Not Supported
```
Warning: Test framework not recognized.
Detected: karma (not fully supported)
Options:
1. Proceed with generic test generation
2. Specify a supported framework manually
3. Add framework support (contribution welcome!)
```
### Coverage Tool Not Available
```
Warning: Could not run coverage analysis.
The coverage command failed or is not configured.
Proceeding without coverage-driven generation.
To enable, add coverage configuration to your project.
```
### Tests Keep Failing
```
Warning: Tests still failing after 3 fix attempts.
Remaining failures:
- validation.test.ts: validateEmail pattern mismatch
This may indicate a bug in the source code, not the test.
Options:
1. Skip this test for now (mark as .skip)
2. Review the implementation
3. Cancel test generation
```
---
## Integration with Pipeline
This skill fits between `/nightgauge:feature-dev` and
`/nightgauge:pr-create`:
```
/issue-pickup → /feature-planning → /feature-dev → /nightgauge:test-gen → /pr-create
↑
YOU ARE HERE
```
**Input from /feature-dev**: Implemented code ready for testing **Output to
/pr-create**: Comprehensive test suite with passing tests
---
## Framework-Specific Notes
### Jest (JavaScript/TypeScript)
- Test files: `*.test.ts`, `*.spec.ts`
- Mocking: `jest.mock()`, `jest.spyOn()`
- Async: `async/await`, `.resolves`, `.rejects`
### Pytest (Python)
- Test files: `test_*.py`, `*_test.py`
- Mocking: `unittest.mock`, `pytest-mock`
- Fixtures: `@pytest.fixture`
### xUnit/NUnit (.NET)
- Test files: `*Tests.cs`
- Mocking: `Moq`, `NSubstitute`
- Assertions: `Assert.Equal()`, FluentAssertions
### JUnit/Gradle (Java)
- Test files: `*Test.java`
- Mocking: `Mockito`
- Assertions: `assertThat()`, `assertEquals()`
---
## Source
Part of the [Nightgauge](https://github.com/nightgauge/nightgauge) -
Issue-to-PR Pipeline.