25 KiB
AI Unit Test Plugin V1 Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Build a WorkUtils plugin that generates JUnit 5 tests for JDK 8 Maven projects through codefree-cli, verifies 85% incremental line coverage in an isolated worktree, and safely integrates a test-only commit into a selected local branch.
Architecture: Add a biz.aitest vertical slice with small ports for processes, Git, Maven, coverage, specifications, and AI. AiTestWorkflow coordinates immutable request/result models while AiTestWindow only binds JavaFX controls to workflow operations. Every external command runs through an injectable executor so automated tests remain offline and deterministic.
Tech Stack: Java 17 WorkUtils runtime, JavaFX 17, Spring Boot 2.7, JUnit 5, Maven, Git CLI, JaCoCo XML, codefree-cli JSON protocol.
Global Constraints
- Execute this plan inline in the primary session; do not use subagents.
- Preserve the user's untracked files on
master; work only incodex/ai-test-plugin-v1. - Target projects are JDK 8, Maven, and JUnit 5; WorkUtils itself remains Java 17.
- AI V1 uses only
codefree-cliwithout--model; retain a provider interface for future providers. - Default incremental line coverage gate is exactly 85%; branch coverage is display-only.
- Auto-select a module only when exactly one Maven
artifactIdends with-start; otherwise require user selection. - Generate and modify files only below the selected module's
src/test/java. - Never modify production sources, POM files, Maven settings, or remote Git state.
- Run generation and validation in a temporary Git worktree; integrate with a test-only commit and local cherry-pick semantics.
- Use TDD for every production behavior: RED, GREEN, refactor, focused commit.
File Structure
Create the following focused packages under src/main/java/com/example/workutils/biz/aitest/:
config: settings persistence and editable test specifications.process: cross-platform, cancellable external command execution.project: project catalog and Maven module/dependency model.git: incremental line analysis, temporary worktrees, branch integration.coverage: JaCoCo XML parsing and incremental coverage calculation.ai: provider request/result contract, CodeFree CLI adapter, generated-file validation.maven: Maven commands, Surefire parsing, and error classification.application: workflow request, state, progress, and orchestration.- package root:
AiTestPluginandAiTestWindow.
Tests mirror these packages under src/test/java. XML, JSON, POM, and Git fixtures are created inside JUnit @TempDir; no permanent fixture repository is needed.
Task 1: Process execution and global AI-test settings
Files:
- Create:
src/main/java/com/example/workutils/biz/aitest/process/CommandSpec.java - Create:
src/main/java/com/example/workutils/biz/aitest/process/CommandResult.java - Create:
src/main/java/com/example/workutils/biz/aitest/process/ProcessExecutor.java - Create:
src/main/java/com/example/workutils/biz/aitest/process/DefaultProcessExecutor.java - Create:
src/main/java/com/example/workutils/biz/aitest/config/AiTestSettings.java - Create:
src/main/java/com/example/workutils/biz/aitest/config/AiTestSettingsService.java - Create:
src/main/java/com/example/workutils/biz/aitest/config/TestSpecificationService.java - Test:
src/test/java/com/example/workutils/biz/aitest/process/DefaultProcessExecutorTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/config/AiTestSettingsServiceTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/config/TestSpecificationServiceTest.java
Interfaces:
-
ProcessExecutor.execute(CommandSpec, Consumer<String>) -> CommandResult -
AiTestSettingsService.load()/save(AiTestSettings)persistsdata/ai-test/settings.properties. -
TestSpecificationService.loadDefault()/saveDefault()/resetDefault()/loadProject()/saveProject()persists UTF-8 Markdown outside business repositories. -
Step 1: Write failing process and settings tests
@Test
void injectsEnvironmentAndCapturesUtf8Output(@TempDir Path dir) {
CommandSpec spec = new CommandSpec(dir, List.of(javaCommand(), "-version"),
Map.of("AI_TEST_MARKER", "ok"), Duration.ofSeconds(10), null);
CommandResult result = executor.execute(spec, ignored -> {});
assertEquals(0, result.exitCode());
assertFalse(result.timedOut());
}
@Test
void roundTripsGlobalSettings(@TempDir Path home) throws Exception {
AiTestSettings expected = new AiTestSettings(home.resolve("jdk8"), home.resolve("maven"),
"codefree", 85, 3, Duration.ofMinutes(5), Duration.ofMinutes(15));
service(home).save(expected);
assertEquals(expected, service(home).load());
}
- Step 2: Run focused tests and verify RED
Run: mvnw.cmd -Dtest=DefaultProcessExecutorTest,AiTestSettingsServiceTest,TestSpecificationServiceTest test
Expected: test compilation fails because the new contracts do not exist.
- Step 3: Implement immutable command/settings contracts and real execution
public interface ProcessExecutor {
CommandResult execute(CommandSpec spec, Consumer<String> output);
void cancelActive();
}
public record CommandResult(int exitCode, boolean timedOut, boolean cancelled,
String stdout, String stderr, Duration duration) {
public boolean successful() { return exitCode == 0 && !timedOut && !cancelled; }
}
DefaultProcessExecutor uses ProcessBuilder, applies only the supplied environment overrides, reads stdout/stderr concurrently as UTF-8, enforces the timeout, and destroys descendants before the parent on cancellation. AiTestSettingsService uses Properties with atomic temp-file replacement. TestSpecificationService creates a Chinese JUnit 5 default template on first read and uses a stable SHA-256 project-path identifier.
- Step 4: Run focused tests and full regression tests
Run the focused command above, then mvnw.cmd test with JDK 17.
Expected: all tests pass.
- Step 5: Commit
git commit -m "feat: add AI test process and settings foundation"
Task 2: Project catalog and Maven module selection
Files:
- Create:
src/main/java/com/example/workutils/biz/aitest/project/AiTestProject.java - Create:
src/main/java/com/example/workutils/biz/aitest/project/MavenModule.java - Create:
src/main/java/com/example/workutils/biz/aitest/project/MavenProjectModel.java - Create:
src/main/java/com/example/workutils/biz/aitest/project/AiTestProjectCatalog.java - Create:
src/main/java/com/example/workutils/biz/aitest/project/MavenProjectAnalyzer.java - Create:
src/main/java/com/example/workutils/biz/aitest/project/StartModuleSelector.java - Test:
src/test/java/com/example/workutils/biz/aitest/project/MavenProjectAnalyzerTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/project/StartModuleSelectorTest.java
Interfaces:
-
AiTestProjectCatalog.load() -> List<AiTestProject>reusesFileOperationHelperproject status data. -
MavenProjectAnalyzer.analyze(Path) -> MavenProjectModelrecursively resolves reactor modules and direct reactor dependencies. -
StartModuleSelector.autoSelect(model) -> Optional<MavenModule>returns a module only for exactly oneartifactId.endsWith("-start"). -
Step 1: Write failing module parsing and selection tests
@Test
void selectsOnlyUniqueStartArtifact(@TempDir Path root) {
MavenProjectModel model = fixture(root, "order-core", "order-start");
assertEquals("order-start", selector.autoSelect(model).orElseThrow().artifactId());
}
@Test
void requiresChoiceForNoOrMultipleStartArtifacts(@TempDir Path root) {
assertTrue(selector.autoSelect(fixture(root, "order-core")).isEmpty());
assertTrue(selector.autoSelect(fixture(root, "a-start", "b-start")).isEmpty());
}
- Step 2: Verify RED with focused Maven tests
Run: mvnw.cmd -Dtest=MavenProjectAnalyzerTest,StartModuleSelectorTest test
Expected: compilation fails for missing project model types.
- Step 3: Implement secure POM parsing and module graph
Use namespace-aware JAXP with external entities and DTDs disabled. Inherit missing group/version from the reactor parent, normalize module paths under the project root, and reject module paths that escape it. Represent dependencies by reactor groupId:artifactId; expose canReach(selected, changed) through graph traversal.
- Step 4: Verify unique selection, malformed POM, path escape, and dependency reachability
Run focused tests, then full mvnw.cmd test.
- Step 5: Commit
git commit -m "feat: analyze Maven projects for AI tests"
Task 3: Git incremental lines and JaCoCo coverage
Files:
- Create:
src/main/java/com/example/workutils/biz/aitest/git/ChangedLines.java - Create:
src/main/java/com/example/workutils/biz/aitest/git/IncrementAnalysis.java - Create:
src/main/java/com/example/workutils/biz/aitest/git/GitIncrementAnalyzer.java - Create:
src/main/java/com/example/workutils/biz/aitest/coverage/JacocoLine.java - Create:
src/main/java/com/example/workutils/biz/aitest/coverage/JacocoXmlParser.java - Create:
src/main/java/com/example/workutils/biz/aitest/coverage/IncrementalCoverage.java - Create:
src/main/java/com/example/workutils/biz/aitest/coverage/IncrementalCoverageCalculator.java - Test:
src/test/java/com/example/workutils/biz/aitest/git/GitIncrementAnalyzerTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/coverage/JacocoXmlParserTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/coverage/IncrementalCoverageCalculatorTest.java
Interfaces:
-
GitIncrementAnalyzer.analyze(project, baseline, includeWorkingTree) -> IncrementAnalysis -
JacocoXmlParser.parse(module, xml) -> List<JacocoLine> -
IncrementalCoverageCalculator.calculate(analysis, lines, targetPercent) -> IncrementalCoverage -
Step 1: Write failing tests using a real temporary Git repository and literal JaCoCo XML
@Test
void countsOnlyChangedExecutableCoveredLines() {
IncrementAnalysis diff = analysis("src/main/java/a/A.java", Set.of(10, 11, 12));
List<JacocoLine> report = List.of(line(10, 0, 1), line(11, 1, 0));
IncrementalCoverage result = calculator.calculate(diff, report, 85);
assertEquals(2, result.executableLines());
assertEquals(1, result.coveredLines());
assertEquals(new BigDecimal("50.00"), result.linePercent());
assertFalse(result.passed());
}
- Step 2: Verify RED
Run: mvnw.cmd -Dtest=GitIncrementAnalyzerTest,JacocoXmlParserTest,IncrementalCoverageCalculatorTest test
- Step 3: Implement merge-base diff parsing and coverage mapping
Run Git only through ProcessExecutor: resolve baseline^{commit}, calculate merge-base, parse git diff --unified=0 --no-color, and optionally merge cached/unstaged diffs. Parse JaCoCo package/sourcefile/line entries; executable means mi + ci > 0, covered means ci > 0. Use module plus normalized source path as the key and return N/A when no executable changed lines exist.
-
Step 4: Run focused and full tests
-
Step 5: Commit
git commit -m "feat: calculate incremental JaCoCo coverage"
Task 4: CodeFree provider and generated-test safety
Files:
- Create:
src/main/java/com/example/workutils/biz/aitest/ai/TestGenerationProvider.java - Create:
src/main/java/com/example/workutils/biz/aitest/ai/TestGenerationRequest.java - Create:
src/main/java/com/example/workutils/biz/aitest/ai/TestGenerationResult.java - Create:
src/main/java/com/example/workutils/biz/aitest/ai/GeneratedTestFile.java - Create:
src/main/java/com/example/workutils/biz/aitest/ai/CodeFreeResultParser.java - Create:
src/main/java/com/example/workutils/biz/aitest/ai/CodeFreeCliTestGenerationProvider.java - Create:
src/main/java/com/example/workutils/biz/aitest/ai/GeneratedFileValidator.java - Test:
src/test/java/com/example/workutils/biz/aitest/ai/CodeFreeResultParserTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/ai/CodeFreeCliTestGenerationProviderTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/ai/GeneratedFileValidatorTest.java
Interfaces:
-
TestGenerationProvider.generate(TestGenerationRequest, Consumer<String>) -> TestGenerationResult -
CodeFreeResultParser.parse(String cliJson) -> TestGenerationResult -
GeneratedFileValidator.validateAndResolve(moduleRoot, files) -> List<ResolvedGeneratedFile> -
Step 1: Write failing protocol and path-security tests
@Test
void extractsLastSuccessfulResultRecord() {
TestGenerationResult result = parser.parse(cliArrayWithResult("{\"tests\":[]}"));
assertTrue(result.successful());
}
@Test
void rejectsProductionAndTraversalPaths(@TempDir Path module) {
assertThrows(IllegalArgumentException.class,
() -> validator.validateAndResolve(module,
List.of(file("../src/main/java/A.java"))));
}
- Step 2: Verify RED
Run: mvnw.cmd -Dtest=CodeFreeResultParserTest,CodeFreeCliTestGenerationProviderTest,GeneratedFileValidatorTest test
- Step 3: Implement CodeFree invocation and strict JSON parsing
Build the exact argument vector codefree <prompt> --channel CI --approval-mode plan --max-session-turns 1 --output-format json; never pass --model. Parse the outer JSON array and inner result JSON with Fastjson2, reject Markdown fences and missing fields, normalize every output below <module>/src/test/java, require .java, and reject symlink/path escape after resolving the nearest existing ancestor.
-
Step 4: Run focused and full tests
-
Step 5: Commit
git commit -m "feat: integrate CodeFree test generation"
Task 5: Maven execution, Surefire parsing, and AI repair classification
Files:
- Create:
src/main/java/com/example/workutils/biz/aitest/maven/MavenPhase.java - Create:
src/main/java/com/example/workutils/biz/aitest/maven/MavenFailureType.java - Create:
src/main/java/com/example/workutils/biz/aitest/maven/MavenExecutionResult.java - Create:
src/main/java/com/example/workutils/biz/aitest/maven/SurefireFailure.java - Create:
src/main/java/com/example/workutils/biz/aitest/maven/SurefireReportParser.java - Create:
src/main/java/com/example/workutils/biz/aitest/maven/MavenErrorClassifier.java - Create:
src/main/java/com/example/workutils/biz/aitest/maven/MavenRunner.java - Test:
src/test/java/com/example/workutils/biz/aitest/maven/SurefireReportParserTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/maven/MavenErrorClassifierTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/maven/MavenRunnerTest.java
Interfaces:
-
MavenRunner.run(project, module, phase, settings, listener) -> MavenExecutionResult -
MavenErrorClassifier.classify(result) -> MavenFailureType -
Step 1: Write failing error-category tests
@ParameterizedTest
@CsvSource({
"'JAVA_HOME is not defined correctly',TOOLCHAIN",
"'Non-resolvable parent POM',DEPENDENCY_OR_POM",
"'OrderServiceTest.java:[42,28] cannot find symbol',TEST_COMPILATION",
"'There are test failures',TEST_FAILURE"
})
void classifiesMavenFailures(String log, MavenFailureType expected) {
assertEquals(expected, classifier.classify(failed(log)));
}
- Step 2: Verify RED
Run: mvnw.cmd -Dtest=SurefireReportParserTest,MavenErrorClassifierTest,MavenRunnerTest test
- Step 3: Implement phased Maven commands
Resolve mvn.cmd on Windows and mvn elsewhere from Maven home, inject JDK 8, and run from the project root with -pl <module-relative-path> -am. Implement test-compile, test, and JaCoCo report phases. Parse target/surefire-reports/TEST-*.xml; classify toolchain, dependency/POM, production compilation, test compilation, test failure, timeout, coverage, and unknown. Only test compilation/failure/context/coverage categories are AI-repairable.
-
Step 4: Run focused and full tests
-
Step 5: Commit
git commit -m "feat: classify and run Maven test verification"
Task 6: Temporary worktrees and local target-branch integration
Files:
- Create:
src/main/java/com/example/workutils/biz/aitest/git/TemporaryWorktree.java - Create:
src/main/java/com/example/workutils/biz/aitest/git/TemporaryWorktreeManager.java - Create:
src/main/java/com/example/workutils/biz/aitest/git/BranchIntegrationRequest.java - Create:
src/main/java/com/example/workutils/biz/aitest/git/BranchIntegrationResult.java - Create:
src/main/java/com/example/workutils/biz/aitest/git/TargetBranchIntegrator.java - Test:
src/test/java/com/example/workutils/biz/aitest/git/TemporaryWorktreeManagerTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/git/TargetBranchIntegratorTest.java
Interfaces:
-
TemporaryWorktreeManager.create(project, includeWorkingTree) -> TemporaryWorktree -
TemporaryWorktree.close()removes the worktree or reports its retained path. -
TargetBranchIntegrator.integrate(request, verifier) -> BranchIntegrationResult -
Step 1: Write failing real-Git integration tests
@Test
void integratesOnlyVerifiedTestCommitIntoLocalBranch(@TempDir Path temp) {
RepositoryFixture repo = RepositoryFixture.create(temp);
BranchIntegrationResult result = integrator.integrate(request(repo), alwaysPass());
assertTrue(result.successful());
assertTrue(repo.fileOnBranch("target", "app-start/src/test/java/AServiceTest.java").isPresent());
assertTrue(repo.fileOnBranch("target", "app-core/src/main/java/AService.java").isPresent());
}
- Step 2: Verify RED
Run: mvnw.cmd -Dtest=TemporaryWorktreeManagerTest,TargetBranchIntegratorTest test
- Step 3: Implement detached generation and guarded local integration
Use git worktree add --detach, copy staged/unstaged binary patches only when selected, commit only validated test paths, and integrate through a temporary branch/worktree based on the recorded target HEAD. Re-run the supplied verifier before advancing the local target ref. Refuse dirty checked-out targets, target movement, conflicts, remote operations, and commits containing paths outside the allowlist.
-
Step 4: Run focused and full tests
-
Step 5: Commit
git commit -m "feat: isolate AI tests and integrate local branches"
Task 7: End-to-end workflow and history
Files:
- Create:
src/main/java/com/example/workutils/biz/aitest/application/AiTestRequest.java - Create:
src/main/java/com/example/workutils/biz/aitest/application/AiTestRun.java - Create:
src/main/java/com/example/workutils/biz/aitest/application/AiTestRunState.java - Create:
src/main/java/com/example/workutils/biz/aitest/application/AiTestProgress.java - Create:
src/main/java/com/example/workutils/biz/aitest/application/AiTestWorkflow.java - Create:
src/main/java/com/example/workutils/biz/aitest/application/AiTestHistoryService.java - Test:
src/test/java/com/example/workutils/biz/aitest/application/AiTestWorkflowTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/application/AiTestHistoryServiceTest.java
Interfaces:
-
AiTestWorkflow.analyze(request, listener) -> AiTestRun -
AiTestWorkflow.generateAndVerify(run, listener) -> AiTestRun -
AiTestWorkflow.integrate(run, targetBranch, message, listener) -> AiTestRun -
AiTestWorkflow.cancel()cancels current CodeFree/Maven work and preserves original repositories. -
Step 1: Write failing workflow tests with real domain objects and fake external ports
@Test
void repairsTestsUntilCoveragePassesWithoutExceedingThreeRounds() {
FakeProvider provider = providerReturning(compilationFailure(), passingAtCoverage(88));
AiTestRun result = workflow(provider).generateAndVerify(analyzedRun(), ignored -> {});
assertEquals(AiTestRunState.READY_FOR_REVIEW, result.state());
assertEquals(2, result.generationRounds());
assertEquals(new BigDecimal("88.00"), result.coverage().linePercent());
}
- Step 2: Verify RED
Run: mvnw.cmd -Dtest=AiTestWorkflowTest,AiTestHistoryServiceTest test
- Step 3: Implement the explicit state machine
Permit only documented transitions: environment check, analysis, confirmation, generation, Maven verification, coverage, repair, review, integration, target verification, success/failure/cancel. Generate one changed production class at a time, stop on non-repairable failures, stop after three rounds, persist only summaries/hashes, and never retain full prompts or production source.
-
Step 4: Run focused and full tests
-
Step 5: Commit
git commit -m "feat: orchestrate AI unit test generation workflow"
Task 8: JavaFX plugin UI and application registration
Files:
- Create:
src/main/java/com/example/workutils/biz/aitest/AiTestPlugin.java - Create:
src/main/java/com/example/workutils/biz/aitest/AiTestWindow.java - Modify:
src/main/java/com/example/workutils/system/ui/icon/AppIcon.java - Modify:
src/main/java/com/example/workutils/system/ui/MainWindow.java - Test:
src/test/java/com/example/workutils/biz/aitest/AiTestPluginTest.java - Test:
src/test/java/com/example/workutils/biz/aitest/AiTestWindowTest.java - Modify:
src/test/java/com/example/workutils/system/ui/NativeUiVisualAcceptanceFixture.java
Interfaces:
-
Plugin id
ai-unit-test, nameAI 单测生成, iconAI_TEST; add the id-to-icon mapping inMainWindow.NavItem.iconForPluginso existing descriptor registration remains unchanged. -
Embedded page exposes environment settings, project/module/baseline configuration, editable specifications, progress, four result tabs, review, and local-branch integration.
-
Step 1: Write failing plugin and JavaFX behavior tests
@Test
void pluginIsEmbeddedAndChecksProjectCatalogConfiguration() {
assertEquals("ai-unit-test", plugin.getId());
assertNotNull(plugin.getVisualFactory().getEmbeddedPane());
}
@Test
void uniqueStartModuleIsSelectedAndGenerateIsDisabledUntilAnalysisCompletes() {
AiTestWindow window = fixtureWithModules("order-core", "order-start");
assertEquals("order-start", window.selectedModuleArtifactId());
assertTrue(window.generateButtonDisabled());
}
- Step 2: Verify RED
Run: mvnw.cmd -Dtest=AiTestPluginTest,AiTestWindowTest test
- Step 3: Implement the UI with existing WorkUtils design tokens and async controls
Use BorderPane, cards, semantic labels, accessible text, LoadingButtonBinder, OperationStatusPane, and background execution. Keep settings in the plugin page instead of enlarging the system settings window. Present tabs for incremental code, generation plan, tests/coverage, and Diff; require explicit review before enabling integration. Add no top-level duplicate navigation controls.
- Step 4: Run focused UI tests and capture a manual visual acceptance screenshot
Run focused tests and mvnw.cmd test. Inspect the page at 1440 px and 1100 px minimum width; verify no clipping, actionable loading states, and readable logs.
- Step 5: Commit
git commit -m "feat: add AI unit test generation plugin UI"
Task 9: Release verification and documentation
Files:
-
Modify:
README.md -
Modify:
docs/项目说明.md -
Create:
docs/ai-unit-test-plugin.md -
Update tests only when verification reveals an uncovered behavior.
-
Step 1: Document prerequisites and safe operating flow
Document global JDK 8/Maven/CodeFree selection, *-start behavior, baseline semantics, 85% coverage, three repair rounds, local-only integration, and failure categories. Include the verified CodeFree one-shot command without a model argument.
- Step 2: Run focused architecture checks
Run all com.example.workutils.biz.aitest tests with JDK 17.
- Step 3: Run complete verification
mvnw.cmd test
mvnw.cmd -DskipTests package
git diff --check
git status --short
Expected: all tests and packaging pass; diff check emits no errors; status contains only intentional V1 files.
- Step 4: Perform a real CodeFree smoke test in an empty temporary directory
Invoke the configured CLI in CI/plan mode and assert a successful type=result record. Do not send business source during this smoke test.
- Step 5: Commit
git commit -m "docs: document AI unit test plugin"
- Step 6: Use verification-before-completion and finishing-a-development-branch
Review every acceptance criterion in the design spec, run fresh verification evidence, then present the verified branch for integration into master without touching the user's untracked master files.