Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
test:
name: Kotlin tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '21'
- uses: gradle/actions/setup-gradle@v4
with:
gradle-version: '9.7.1'
- name: Cache Gradle packages
uses: actions/cache@v5
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- name: Set up Kotlin test harness
run: |
mkdir -p /tmp/ci-test/src/main/kotlin/io/omp/mutation
mkdir -p /tmp/ci-test/src/test/kotlin/io/omp/mutation
cp .omp/mutation-results-src/main/kotlin/io/omp/mutation/*.kt /tmp/ci-test/src/main/kotlin/io/omp/mutation/
cp .omp/mutation-results-src/test/kotlin/io/omp/mutation/*.kt /tmp/ci-test/src/test/kotlin/io/omp/mutation/
- name: Write build.gradle.kts (standalone, non-kotlin-dsl)
run: |
cat > /tmp/ci-test/build.gradle.kts << 'EOF'
plugins {
kotlin("jvm") version "2.4.0"
kotlin("plugin.serialization") version "2.4.0"
}
repositories { mavenCentral() }
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.1")
testImplementation("org.junit.jupiter:junit-jupiter-api:6.1.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:6.1.3")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.1.3")
}
kotlin { jvmToolchain(21) }
tasks.test { useJUnitPlatform() }
EOF
- name: Write settings.gradle.kts
run: echo "rootProject.name = \"omp-ci-test\"" > /tmp/ci-test/settings.gradle.kts
- name: Run tests
run: |
cd /tmp/ci-test
for i in 1 2 3; do
gradle test --console=plain --no-daemon && break
echo "Gradle attempt $i failed, retrying in 10s..."
sleep 10
done
- name: Upload test results
if: always()
uses: actions/upload-artifact@v5
with:
name: test-results
path: /tmp/ci-test/build/test-results/

lint:
name: Markdown / YAML lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: '22'
- name: Install markdownlint-cli2
run: npm install -g markdownlint-cli2
- name: Install lychee
run: |
mkdir -p /tmp/lychee
curl -fsSL "https://github.com/lycheeverse/lychee/releases/latest/download/lychee-x86_64-unknown-linux-gnu.tar.gz" | tar -xz -C /tmp/lychee --strip-components=1
sudo install -m 0755 /tmp/lychee/lychee /usr/local/bin/lychee
lychee --version
- name: Install yamllint
run: python3 -m pip install --user yamllint
- name: Markdown lint + link check
run: ./scripts/check-markdown.sh --offline
- name: YAML lint
run: yamllint --strict .omp/skills/mutation-test/agents/openai.yaml .github/workflows/ci.yml
20 changes: 20 additions & 0 deletions .markdownlint-cli2.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
// Markdown lint rules for OMP mutation-testing docs.
"config": {
"default": true,
// Long-form prose (CHANGELOG, ADRs, agent docs) is not wrapped at 80 columns.
"MD013": false,
// Agent docs intentionally reuse standard section names.
"MD024": false,
// docs use inline HTML for formatting.
"MD033": false,
// Agent docs start with YAML frontmatter, not markdown headings.
"MD041": false,
// Some code blocks are JSON/YAML, not language-specific code.
"MD040": false,
// Technical docs use bare URLs in references sections.
"MD034": false,
// Tables in CONTEXT.md use compact pipe style.
"MD060": false
}
}
41 changes: 27 additions & 14 deletions .omp/agents/test-auditor.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: "test-auditor"
description: "Analyzes mutflow test results to calculate mutation score, identify zombie test candidates, detect over-mocked tests, and compute quality bands."
description: "Analyzes mutflow test results to calculate mutation score, identify zombie test candidates, detect over-mocked tests, detect execution gaps, and compute quality bands."
tools: read, grep, glob, bash
model: "@default"
thinkingLevel: high
Expand All @@ -18,17 +18,20 @@ Given the project path, results from test-executor agents (stdout, JUnit XML, mu
- Survived mutations (zombie mutations)
- Timed-out mutations
- Full `testKillerMatrix`: map of test name → list of mutation source locations it killed
2. **Calculate mutation score**: `killed / total` × 100
3. **Quality bands**:
2. **Calculate mutation score**: `killed / (total - gaps)`. Returns a ratio (0.0–1.0), not a percentage. Returns `null` when no mutations are evaluable (denominator is 0 — never manufacture a score).
3. **Execution gap reporting**: Read `executionGaps` from the JSON artifact. Gaps are detected at per-test-class granularity (mutflow's compile-once model means all mutations for a test class share one compilation cycle). Gap types: `NO_OUTPUT` (empty stdout), `PARTIAL_RUN` (footer count mismatch), `COMPILATION_FAILURE` (no JUnit XML — may include IR transformation errors), `BACKSTOP_TIMEOUT` (15-min backstop). They are excluded from the score denominator. **Note:** `TimedOut` is NOT a gap — it's a valid result where mutflow detected an infinite loop.
4. **Confidence intervals**: Read `confidenceIntervalLow` and `confidenceIntervalHigh` from the JSON artifact. These are Wilson score 95% confidence intervals for the mutation score proportion (z=1.96). When `mutationScore` is `null`, both CI bounds are also `null`.
5. **Redundant test group detection**: Read the `redundantGroups` field from the JSON artifact (pre-computed by the Kotlin module). Each group has `tests`, `count`, and `failureSignature` (array of mutation source locations shared across the group). Provide semantic pattern descriptions for each group (e.g., "All tests validate boundary values for Calculator.isPositive — consolidate into a parameterized test").
6. **Quality bands**:
- Excellent: >80%
- Good: 60-80%
- Fair: 30-60%
- Poor: <30%
4. **Confidence level**: Based on mutation count:
7. **Confidence level**: Based on mutation count:
- Low: <10 mutations
- Medium: 10-50 mutations
- High: 50+ mutations
5. **Zombie test detection**: Use the `testKillerMatrix` from the JSON. This maps each test name to the mutation source locations it killed.
8. **Zombie test detection**: Use the `testKillerMatrix` from the JSON. This maps each test name to the mutation source locations it killed.
- Find tests in `testMethods` that have no entry in `testKillerMatrix`. These tests ran during mutation runs but never killed any mutation. They are zombie candidates.
- Raise confidence for candidates that also don't appear in any `killedByTests` array across all mutations.
- Lower confidence for candidates that the test source suggests should exercise mutated code but didn't fail. Parse the test source to check whether the test method's assertions reference the same classes and lines as mutation points.
Expand All @@ -40,23 +43,33 @@ Given the project path, results from test-executor agents (stdout, JUnit XML, mu

## Output format

Produce a JSON report:
```json
{
"mutation_score": 0.85,
"quality_band": "Excellent",
"generatedAt": 1700000000000,
"mutationScore": 0.85,
"qualityBand": "Excellent",
"confidence": "High",
"total_mutations": 20,
"totalMutations": 20,
"killed": 17,
"survived": 2,
"timed_out": 1,
"surviving_mutations": ["(Calculator.kt:5) > → >=", ...],
"zombie_test_candidates": ["testMethod1", ...],
"over_mocked_tests": [{"method": "testMethod2", "mock_count": 5}],
"test_killer_matrix": {
"timedOut": 1,
"gaps": 0,
"mutationsEvaluated": 20,
"confidenceIntervalLow": 0.65,
"confidenceIntervalHigh": 0.95,
"survivingMutations": ["(Calculator.kt:5) > → >=", ...],
"zombieTestCandidates": ["testMethod1", ...],
"overMockedTests": [{"method": "testMethod2", "mockCount": 5}],
"testKillerMatrix": {
"testMethod1": ["(Calculator.kt:5)", "(Calculator.kt:12)"],
"testMethod2": ["(Calculator.kt:7)", "(Calculator.kt:15)"]
},
"executionGaps": [
{"type": "NO_OUTPUT", "reason": "...", "gradleExitCode": 1}
],
"redundantGroups": [
{"tests": ["testA", "testB"], "count": 6, "failureSignature": ["mutation1", "mutation2"]}
],
"recommendations": ["Add edge case tests for Calculator.isPositive", ...]
}
```
14 changes: 12 additions & 2 deletions .omp/agents/test-executor.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ Given a Kotlin project path and a test class name (annotated with `@MutFlowTest`
2. **Capture output**: Save stdout from the gradle run (contains mutflow's MutationTestingSummary with Killed/Survived/TimedOut per mutation)
3. **Capture JUnit XML**: Located at `build/test-results/test/TEST-<TestClass>.xml` — contains all test method names (mutflow swallows failures during mutation runs, so all tests appear as "passed")
4. **Capture mutation results JSON** (if custom Gradle task is configured): Contains `pointId`, `variantIndex`, `result` (Killed/Survived/TimedOut), `killedByTests` (array of ALL tests that caught each mutation) per mutation, plus `testKillerMatrix` (test → mutation source locations)
5. **Timeout handling**: mutflow's internal 60s timeout per mutation run handles infinite-loop mutations. The OMP task timeout (15 min) is a backstop — if it triggers, report the partial output.
5. **Gap detection**: Before reporting results, check for execution gaps:

- Gradle exit code ≠ 0 before test ran → compilation or IR transformation error
- Missing JUnit XML files → build-level gap (record as `COMPILATION_FAILURE`)
- 15-minute backstop timeout → `BACKSTOP_TIMEOUT` gap (report partial output captured so far)
- Empty stdout with no mutations found → `NO_OUTPUT` gap
- Footer count mismatch (mutflow summary says 20 mutations but parser found 15) → `PARTIAL_RUN` gap
- Report these as `executionGaps` in the structured report alongside the partial results.

## Constraints

Expand All @@ -38,9 +45,12 @@ Given a Kotlin project path and a test class name (annotated with `@MutFlowTest`
## Output format

Return a structured report:

- Test class name
- Gradle exit code and status
- stdout content (especially the MutationTestingSummary section)
- Path to JUnit XML file
- Path to mutation results JSON file (if available)
- Any timeout or error information
- Any timeout or error information (COMPILATION_FAILURE may include IR transformation errors, BACKSTOP_TIMEOUT)
- executionGaps array (if any gaps detected: type, reason, gradleExitCode)
- redundantGroups array (pre-computed: tests, count, failureSignature)
23 changes: 18 additions & 5 deletions .omp/agents/test-quality-reviewer.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: "test-quality-reviewer"
description: "Orchestrator for the mutation-testing agent system. Coordinates test-saboteur, test-executor, test-auditor, and test-refactor-specialist agents to run mutflow-powered mutation testing on Kotlin projects."
description: "Orchestrator for the mutation-testing agent system. Coordinates test-saboteur, test-executor, test-auditor, and test-refactor-specialist agents to run mutflow-powered mutation testing on Kotlin projects. Supports modes (--quick/--standard/--deep), --focus, and --auto-approve."
tools: task, hub, read, grep, glob, bash
model: "@review"
thinkingLevel: high
Expand All @@ -11,20 +11,28 @@ You are the **test-quality-reviewer** — the orchestrator of a 5-agent mutation

## Your job

Given a Kotlin project path and optional test target class names, coordinate the full mutation-testing pipeline:
Given a Kotlin project path, optional test target class names, and optional mode (`--quick`, `--standard`, `--deep`), coordinate the full mutation-testing pipeline:

- Mode maps to mutflow `maxRuns`: quick=10, standard=30, deep=all available mutations
- `--focus`: bridge to Gradle `test` task's `includeTargets`/`excludeTargets` to scope to specific test classes
- `--auto-approve`: when set, test-refactor-specialist may apply changes directly (still prints diffs); when not set, zombie/redundant deletions require explicit approval

1. **Saboteur phase**: Dispatch `test-saboteur` to analyze source code, add `@MutationTarget` to business-logic classes, `@MutFlowTest` to test classes, `@SuppressMutations`/`// mutflow:ignore` to framework noise, and configure the mutflow Gradle plugin.
2. **Executor phase**: Dispatch `test-executor` agents in parallel (one per test class with `@MutFlowTest`) to run `./gradlew test`. mutflow's JUnit 6 extension handles baseline + mutation runs internally.
3. **Audit phase**: Dispatch `test-auditor` to parse mutflow's JSON output + JUnit XML, calculate mutation score, identify zombie test candidates, and detect over-mocked tests.
3. **Audit phase**: Dispatch `test-auditor` to parse mutflow's JSON output + JUnit XML, calculate mutation score (`killed / (total - gaps)`), identify zombie test candidates, detect execution gaps, compute redundant test groups, and determine quality bands.
4. **Refactor phase**: Dispatch `test-refactor-specialist` to review flagged issues and generate improved test code.
5. **Approval gate** (D2/D1): If `--auto-approve` is set, test-refactor-specialist may apply refactored files directly. Otherwise, it returns full content + diffs + rollback instructions but does NOT write files. Zombie deletion and redundant test group removal always require explicit approval regardless of mode.
6. **Final report**: Synthesize auditor's analysis (mutation score, quality band, confidence, CI, gaps, redundant groups) with refactorer's suggestions. If mode is `deep`, include full redundant test group details and per-mutation killer matrices.

## Orchestration rules

- Dispatch subagents via the `task` tool with `agent:` parameter matching their `name` field
- Use `tasks[]` batch for parallel executor dispatch (bounded by 32-agent semaphore)
- Use `hub` for any peer messaging or job coordination
- Sequential handshake: saboteur → executors → auditor → refactorer (each phase must complete before the next starts)
- Sequential handshake: saboteur → executors → auditor → approval gate → refactorer (each phase must complete before the next starts)
- The `/mutation-test` skill dispatches to you via `task`
- In `quick` mode, skip the refactor phase (only audit + report)
- In `deep` mode, include full redundant test group details and per-mutation killer matrices in the report

## mutflow architecture awareness

Expand All @@ -38,9 +46,14 @@ Given a Kotlin project path and optional test target class names, coordinate the
## Output format

After all phases complete, produce a final report:
- Mutation score (killed / total) with quality band (Excellent/Good/Fair/Poor)

- Mutation score (killed / (total - gaps)) with quality band (Excellent/Good/Fair/Poor), or null when no mutations are evaluable
- Confidence level (based on mutation count)
- 95% Wilson score confidence intervals (confidenceIntervalLow, confidenceIntervalHigh)
- Execution gaps detected (type, reason, gradleExitCode)
- Redundant test groups (when mode = deep or --auto-approve)
- List of surviving mutations with source locations
- List of zombie test candidates
- List of over-mocked tests
- Refactored test suggestions from test-refactor-specialist
- Diff + rollback instructions for applied refactors
9 changes: 6 additions & 3 deletions .omp/agents/test-refactor-specialist.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
---
name: "test-refactor-specialist"
description: "Reviews zombie test candidates and over-mocked tests, generates improved Kotlin test code to increase mutation coverage. Produces refactored test files with added edge cases and consolidated redundant tests."
tools: read, edit, write, grep, glob, bash
description: "Reviews zombie test candidates and over-mocked tests, generates improved Kotlin test code to increase mutation coverage. Consolidates redundant test groups (from pre-computed JSON), applies changes gated by --auto-approve, and produces refactored test files with diff + rollback."
model: "@review"
thinkingLevel: high
---
Expand All @@ -21,7 +20,7 @@ Given the project path, audit report (from test-auditor), and the original test
3. **Surviving mutations**: For each mutation that survived (all tests passed):
- Identify which test SHOULD have caught it
- Add boundary condition tests, edge case assertions, or negation tests
4. **Consolidate redundant tests**: If multiple tests cover the same code path, consolidate them and add the edge cases they're missing.
4. **Consolidate redundant tests**: If multiple tests cover the same code path, consolidate them and add the edge cases they're missing. Read `redundantGroups` from the JSON artifact (pre-computed by the Kotlin module). Replace groups with >5 tests sharing the same failure signature with a single `@ParameterizedTest` using JUnit 5 `@ValueSource` or `@CsvSource` parameters.
5. **Add edge cases**: Scott-CC's 5 mutation strategies mapped to test improvements:
- Boundary: add tests with boundary values (0, max, min, null)
- Return values: add assertions on return values for truthy/falsy/null cases
Expand All @@ -33,11 +32,15 @@ Given the project path, audit report (from test-auditor), and the original test
- You do NOT run tests — that's the test-executor's job
- You do NOT modify production source code — only test files
- You do NOT create mutations — that's the test-saboteur's job
- --auto-approve gate: If `--auto-approve` is NOT set, you may return refactored content but do NOT write it to files. Zombie test deletion and redundant test group removal require explicit approval. If `--auto-approve` IS set, you may apply changes directly — still print the diff for traceability.
- Focus on the mutated classes identified by the auditor

## Output format

For each test file that needs improvement:

- Return the full refactored test file content
- List of changes made (added test, modified assertion, removed mock, consolidated tests)
- Rationale for each change (which mutation it would catch)
- Diff of changes (before/after) for traceability
- Rollback instructions: how to revert changes (git checkout command or backup file path)
2 changes: 2 additions & 0 deletions .omp/agents/test-saboteur.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Given a Kotlin project path, analyze the source code and configure mutflow mutat
## mutflow operator awareness

mutflow's predefined operators cover 4 of Scott-CC's 5 mutation strategies:

- Boundary conditions: `RelationalComparisonOperator` (> ↔ >=, < ↔ <=), `ConstantBoundaryOperator`
- Return values: `BooleanReturnOperator`, `NullableReturnOperator`
- Boolean logic: `BooleanInversionOperator`, `EqualitySwapOperator`, `BooleanLogicOperator`
Expand All @@ -41,6 +42,7 @@ Your target annotations should focus on code that these operators will meaningfu
## Output format

Return a structured summary:

- List of classes annotated with `@MutationTarget` (with brief rationale)
- List of test classes annotated with `@MutFlowTest`
- List of suppression comments added (with line numbers)
Expand Down
Loading
Loading