docs: plan AI unit test plugin v1
This commit is contained in:
@@ -0,0 +1,501 @@
|
||||
# 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 in `codex/ai-test-plugin-v1`.
|
||||
- Target projects are JDK 8, Maven, and JUnit 5; WorkUtils itself remains Java 17.
|
||||
- AI V1 uses only `codefree-cli` without `--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 `artifactId` ends 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: `AiTestPlugin` and `AiTestWindow`.
|
||||
|
||||
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)` persists `data/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**
|
||||
|
||||
```java
|
||||
@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**
|
||||
|
||||
```java
|
||||
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**
|
||||
|
||||
```text
|
||||
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>` reuses `FileOperationHelper` project status data.
|
||||
- `MavenProjectAnalyzer.analyze(Path) -> MavenProjectModel` recursively resolves reactor modules and direct reactor dependencies.
|
||||
- `StartModuleSelector.autoSelect(model) -> Optional<MavenModule>` returns a module only for exactly one `artifactId.endsWith("-start")`.
|
||||
|
||||
- [ ] **Step 1: Write failing module parsing and selection tests**
|
||||
|
||||
```java
|
||||
@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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```java
|
||||
@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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```java
|
||||
@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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```java
|
||||
@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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```java
|
||||
@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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```java
|
||||
@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**
|
||||
|
||||
```text
|
||||
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`, name `AI 单测生成`, icon `AI_TEST`; add the id-to-icon mapping in `MainWindow.NavItem.iconForPlugin` so 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**
|
||||
|
||||
```java
|
||||
@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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```text
|
||||
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**
|
||||
|
||||
```text
|
||||
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.
|
||||
Reference in New Issue
Block a user