diff --git a/docs/superpowers/plans/2026-08-04-ai-unit-test-plugin-v1.md b/docs/superpowers/plans/2026-08-04-ai-unit-test-plugin-v1.md new file mode 100644 index 0000000..5d0a4d5 --- /dev/null +++ b/docs/superpowers/plans/2026-08-04-ai-unit-test-plugin-v1.md @@ -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) -> 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 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` reuses `FileOperationHelper` project status data. +- `MavenProjectAnalyzer.analyze(Path) -> MavenProjectModel` recursively resolves reactor modules and direct reactor dependencies. +- `StartModuleSelector.autoSelect(model) -> Optional` 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` +- `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 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) -> TestGenerationResult` +- `CodeFreeResultParser.parse(String cliJson) -> TestGenerationResult` +- `GeneratedFileValidator.validateAndResolve(moduleRoot, files) -> List` + +- [ ] **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 --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 `/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 -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.