529 lines
21 KiB
Markdown
529 lines
21 KiB
Markdown
|
|
# Code Analysis Core Source Selection 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:** Make code analysis send a compact, curated set of core Java, Vue, SQL, mapper, and config files to the AI instead of a broad source dump.
|
|||
|
|
|
|||
|
|
**Architecture:** Keep the implementation inside the existing `ProjectCodeAnalysisService` boundary. The service still reads the preview source tree, but it now scores candidates, truncates each expanded file to a small per-file budget, assembles a selected-file manifest, records skipped low-value or budget-excluded files, and caps expanded source at 60KB. `AiGenerateServiceImpl` only needs prompt wording changes so the AI treats the source as a curated subset.
|
|||
|
|
|
|||
|
|
**Tech Stack:** Java 8, Spring Boot 2.5, JUnit 4, Mockito, Maven.
|
|||
|
|
|
|||
|
|
**Current status:** Code-analysis scoped implementation and focused tests are complete. The full
|
|||
|
|
`AiGenerateServiceImplTest` class is currently blocked by unrelated database-generation failures
|
|||
|
|
in the existing working tree, so the implementation has not been staged or committed.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 1: Add Failing Tests For Core Selection
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisServiceTest.java`
|
|||
|
|
- Test: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisServiceTest.java`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: Add the missing assertion import**
|
|||
|
|
|
|||
|
|
Add this import with the other static JUnit imports:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
import static org.junit.Assert.assertFalse;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: Write a failing test for scoring and compact budget**
|
|||
|
|
|
|||
|
|
Add this test after `shouldCapTotalPromptContextAndWarnWhenBudgetExceeded()`:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
@Test
|
|||
|
|
public void shouldSelectCoreFilesBeforeLowValueFilesAndKeepContextCompact()
|
|||
|
|
{
|
|||
|
|
when(frontProjectPreviewService.getStructure(eq(1L), eq(10L), eq("backend")))
|
|||
|
|
.thenReturn(nodes(
|
|||
|
|
node("backend:UserController.java", "UserController.java",
|
|||
|
|
"src/main/java/com/example/user/UserController.java", "file"),
|
|||
|
|
node("backend:UserServiceImpl.java", "UserServiceImpl.java",
|
|||
|
|
"src/main/java/com/example/user/UserServiceImpl.java", "file"),
|
|||
|
|
node("backend:UserMapper.java", "UserMapper.java",
|
|||
|
|
"src/main/java/com/example/user/UserMapper.java", "file"),
|
|||
|
|
node("backend:UserMapper.xml", "UserMapper.xml",
|
|||
|
|
"src/main/resources/mapper/UserMapper.xml", "file"),
|
|||
|
|
node("backend:User.java", "User.java",
|
|||
|
|
"src/main/java/com/example/domain/User.java", "file"),
|
|||
|
|
node("backend:AuditUtil.java", "AuditUtil.java",
|
|||
|
|
"src/main/java/com/example/common/AuditUtil.java", "file"),
|
|||
|
|
node("backend:UserServiceTest.java", "UserServiceTest.java",
|
|||
|
|
"src/test/java/com/example/user/UserServiceTest.java", "file")));
|
|||
|
|
when(frontProjectPreviewService.getStructure(eq(1L), eq(10L), eq("frontend")))
|
|||
|
|
.thenReturn(nodes(
|
|||
|
|
node("frontend:index.vue", "index.vue", "src/views/user/index.vue", "file"),
|
|||
|
|
node("frontend:user.js", "user.js", "src/api/user.js", "file"),
|
|||
|
|
node("frontend:router.js", "router.js", "src/router/index.js", "file"),
|
|||
|
|
node("frontend:index.scss", "index.scss", "src/styles/index.scss", "file")));
|
|||
|
|
when(frontProjectPreviewService.getStructure(eq(1L), eq(10L), eq("admin_frontend")))
|
|||
|
|
.thenReturn(Collections.<Map<String, Object>>emptyList());
|
|||
|
|
when(frontProjectPreviewService.getStructure(eq(1L), eq(10L), eq("sql")))
|
|||
|
|
.thenReturn(nodes(node("sql:user.sql", "user.sql", "user.sql", "file")));
|
|||
|
|
when(frontProjectPreviewService.getFileContent(eq(1L), eq(10L), any(PreviewFileRequest.class)))
|
|||
|
|
.thenAnswer(invocation ->
|
|||
|
|
{
|
|||
|
|
PreviewFileRequest request = invocation.getArgument(2);
|
|||
|
|
return content("// " + request.getCategory() + "\n" + repeat("core source line\n", 400));
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
CodeAnalysisSourceSummary summary = service.buildSourceSummary(1L, 10L);
|
|||
|
|
String context = summary.getPromptContext();
|
|||
|
|
|
|||
|
|
assertTrue(context.length() <= 65 * 1024);
|
|||
|
|
assertTrue(context.contains("Selected core source files:"));
|
|||
|
|
assertTrue(context.contains("UserController.java"));
|
|||
|
|
assertTrue(context.contains("UserServiceImpl.java"));
|
|||
|
|
assertTrue(context.contains("UserMapper.xml"));
|
|||
|
|
assertTrue(context.contains("user.sql"));
|
|||
|
|
assertTrue(context.contains("src/views/user/index.vue"));
|
|||
|
|
assertFalse(context.contains("AuditUtil.java\n//"));
|
|||
|
|
assertFalse(context.contains("index.scss\n//"));
|
|||
|
|
assertTrue(summary.getWarnings().stream().anyMatch(value -> value.contains("60KB")));
|
|||
|
|
assertTrue(summary.getSkippedFiles().stream().anyMatch(file -> file.getPath().contains("index.scss")));
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: Update the existing budget test expectation**
|
|||
|
|
|
|||
|
|
In `shouldCapTotalPromptContextAndWarnWhenBudgetExceeded()`, replace the final assertions with:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
assertTrue(summary.getPromptContext().length() <= 65 * 1024);
|
|||
|
|
assertTrue(summary.getWarnings().stream().anyMatch(value -> value.contains("60KB")));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: Update the existing large-file test expectation**
|
|||
|
|
|
|||
|
|
In `shouldTrimLargeFilesAndKeepSummaryUsable()`, replace the final assertions with:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
assertEquals(Integer.valueOf(1), summary.getAnalyzedFileCount());
|
|||
|
|
assertTrue(summary.getIncludedFiles().get(0).getContent().length() <= 21 * 1024);
|
|||
|
|
assertTrue(summary.getWarnings().stream().anyMatch(value -> value.contains("20KB")));
|
|||
|
|
assertTrue(summary.getEstimatedTokens().intValue() > 0);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: Run the focused test and verify it fails**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ruoyi-generator -Dtest=ProjectCodeAnalysisServiceTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: FAIL. The new test should fail because the current service still uses a 120KB broad prompt and does not emit the selected-core manifest or 60KB warning.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 2: Implement Deterministic Core File Selection
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisService.java`
|
|||
|
|
- Test: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisServiceTest.java`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: Replace the budget constants**
|
|||
|
|
|
|||
|
|
Replace:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private static final int LARGE_FILE_LIMIT = 100 * 1024;
|
|||
|
|
private static final int TOTAL_CONTEXT_LIMIT = 120 * 1024;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
with:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private static final int PER_FILE_CONTEXT_LIMIT = 20 * 1024;
|
|||
|
|
private static final int TOTAL_CONTEXT_LIMIT = 60 * 1024;
|
|||
|
|
private static final int SKIPPED_MANIFEST_LIMIT = 40;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: Truncate each candidate file to the per-file budget**
|
|||
|
|
|
|||
|
|
In `collectFile`, replace the large-file truncation block:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
if (content.length() > LARGE_FILE_LIMIT)
|
|||
|
|
{
|
|||
|
|
content = content.substring(0, LARGE_FILE_LIMIT)
|
|||
|
|
+ "\n\n[文件超过 100KB,代码解读仅保留前 100KB 内容]";
|
|||
|
|
truncated = true;
|
|||
|
|
summary.getWarnings().add(path + " 超过 100KB,已裁剪后参与分析");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
with:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
if (content.length() > PER_FILE_CONTEXT_LIMIT)
|
|||
|
|
{
|
|||
|
|
content = content.substring(0, PER_FILE_CONTEXT_LIMIT)
|
|||
|
|
+ "\n\n[文件超过 20KB,代码解读仅保留前 20KB 核心内容]";
|
|||
|
|
truncated = true;
|
|||
|
|
summary.getWarnings().add(path + " 超过 20KB,已裁剪后参与核心代码分析");
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: Replace `finishSummary` with selected-file assembly**
|
|||
|
|
|
|||
|
|
Replace the whole `finishSummary(CodeAnalysisSourceSummary summary)` method with:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private void finishSummary(CodeAnalysisSourceSummary summary)
|
|||
|
|
{
|
|||
|
|
boolean totalTruncated = false;
|
|||
|
|
List<CodeAnalysisSourceSummary.SourceFile> candidates = new ArrayList<CodeAnalysisSourceSummary.SourceFile>(
|
|||
|
|
summary.getIncludedFiles());
|
|||
|
|
Collections.sort(candidates, new Comparator<CodeAnalysisSourceSummary.SourceFile>()
|
|||
|
|
{
|
|||
|
|
@Override
|
|||
|
|
public int compare(CodeAnalysisSourceSummary.SourceFile left, CodeAnalysisSourceSummary.SourceFile right)
|
|||
|
|
{
|
|||
|
|
int priorityCompare = Integer.valueOf(filePriority(left)).compareTo(Integer.valueOf(filePriority(right)));
|
|||
|
|
if (priorityCompare != 0)
|
|||
|
|
{
|
|||
|
|
return priorityCompare;
|
|||
|
|
}
|
|||
|
|
return StringUtils.defaultString(left == null ? null : left.getPath())
|
|||
|
|
.compareTo(StringUtils.defaultString(right == null ? null : right.getPath()));
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
List<CodeAnalysisSourceSummary.SourceFile> selected = new ArrayList<CodeAnalysisSourceSummary.SourceFile>();
|
|||
|
|
StringBuilder source = new StringBuilder();
|
|||
|
|
for (CodeAnalysisSourceSummary.SourceFile file : candidates)
|
|||
|
|
{
|
|||
|
|
if (isLowValueFile(file))
|
|||
|
|
{
|
|||
|
|
addSkipped(summary, file.getTemplateType(), file.getPath(), "非核心代码文件");
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
String block = sourceBlock(file);
|
|||
|
|
if (source.length() + block.length() > TOTAL_CONTEXT_LIMIT)
|
|||
|
|
{
|
|||
|
|
addSkipped(summary, file.getTemplateType(), file.getPath(), "核心源码预算已满,未展开内容");
|
|||
|
|
totalTruncated = true;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
selected.add(file);
|
|||
|
|
source.append(block);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (selected.isEmpty() && !candidates.isEmpty())
|
|||
|
|
{
|
|||
|
|
CodeAnalysisSourceSummary.SourceFile fallback = candidates.get(0);
|
|||
|
|
String block = sourceBlock(fallback);
|
|||
|
|
if (block.length() > TOTAL_CONTEXT_LIMIT)
|
|||
|
|
{
|
|||
|
|
block = block.substring(0, TOTAL_CONTEXT_LIMIT)
|
|||
|
|
+ "\n\n[核心源码预算已满,仅保留首个可分析文件的前 60KB 内容]";
|
|||
|
|
fallback.setTruncated(Boolean.TRUE);
|
|||
|
|
totalTruncated = true;
|
|||
|
|
}
|
|||
|
|
selected.add(fallback);
|
|||
|
|
source.append(block);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (totalTruncated)
|
|||
|
|
{
|
|||
|
|
summary.getWarnings().add("核心源码上下文超过 60KB,已优先保留 SQL、Controller、Service、Mapper、实体和核心页面");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
String promptContext = buildPromptContext(selected, summary.getSkippedFiles(), source);
|
|||
|
|
summary.setIncludedFiles(selected);
|
|||
|
|
summary.setAnalyzedFileCount(selected.size());
|
|||
|
|
summary.setSkippedFileCount(summary.getSkippedFiles().size());
|
|||
|
|
summary.setCharCount(promptContext.length());
|
|||
|
|
summary.setEstimatedTokens(Math.max(0, (promptContext.length() + 3) / 4));
|
|||
|
|
summary.setPromptContext(promptContext.trim());
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: Add helper methods for manifest and low-value filtering**
|
|||
|
|
|
|||
|
|
Add these methods below `finishSummary`:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private String buildPromptContext(List<CodeAnalysisSourceSummary.SourceFile> selected,
|
|||
|
|
List<CodeAnalysisSourceSummary.SkippedFile> skippedFiles, StringBuilder source)
|
|||
|
|
{
|
|||
|
|
StringBuilder prompt = new StringBuilder();
|
|||
|
|
prompt.append("Selected core source files:\n");
|
|||
|
|
for (CodeAnalysisSourceSummary.SourceFile file : selected)
|
|||
|
|
{
|
|||
|
|
prompt.append("- ").append(StringUtils.defaultString(file.getTemplateType()))
|
|||
|
|
.append(" / ").append(StringUtils.defaultString(file.getPath()))
|
|||
|
|
.append(" (").append(fileRole(file)).append(file.getTruncated() ? ", truncated" : ", expanded")
|
|||
|
|
.append(")\n");
|
|||
|
|
}
|
|||
|
|
if (!skippedFiles.isEmpty())
|
|||
|
|
{
|
|||
|
|
prompt.append("\nSkipped or summarized files:\n");
|
|||
|
|
int count = 0;
|
|||
|
|
for (CodeAnalysisSourceSummary.SkippedFile file : skippedFiles)
|
|||
|
|
{
|
|||
|
|
if (count >= SKIPPED_MANIFEST_LIMIT)
|
|||
|
|
{
|
|||
|
|
prompt.append("- ... ").append(skippedFiles.size() - count).append(" more files omitted from manifest\n");
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
prompt.append("- ").append(StringUtils.defaultString(file.getTemplateType()))
|
|||
|
|
.append(" / ").append(StringUtils.defaultString(file.getPath()))
|
|||
|
|
.append(" (").append(StringUtils.defaultString(file.getReason())).append(")\n");
|
|||
|
|
count++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
prompt.append(source);
|
|||
|
|
return prompt.toString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private String sourceBlock(CodeAnalysisSourceSummary.SourceFile file)
|
|||
|
|
{
|
|||
|
|
return "\n\n## " + StringUtils.defaultString(file.getTemplateType()) + " / "
|
|||
|
|
+ StringUtils.defaultString(file.getPath()) + "\n"
|
|||
|
|
+ StringUtils.defaultString(file.getContent());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private boolean isLowValueFile(CodeAnalysisSourceSummary.SourceFile file)
|
|||
|
|
{
|
|||
|
|
String path = StringUtils.defaultString(file == null ? null : file.getPath()).toLowerCase(Locale.ROOT);
|
|||
|
|
String name = fileName(path);
|
|||
|
|
return path.contains("/test/") || name.endsWith("test.java") || name.contains(".test.")
|
|||
|
|
|| path.contains("/tests/") || path.contains("/demo/") || path.contains("/mockups/")
|
|||
|
|
|| path.contains("/audits/") || path.endsWith(".scss") || path.endsWith(".css");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private String fileRole(CodeAnalysisSourceSummary.SourceFile file)
|
|||
|
|
{
|
|||
|
|
String path = StringUtils.defaultString(file == null ? null : file.getPath()).toLowerCase(Locale.ROOT);
|
|||
|
|
String type = StringUtils.defaultString(file == null ? null : file.getTemplateType()).toLowerCase(Locale.ROOT);
|
|||
|
|
if ("sql".equals(type) || path.endsWith(".sql"))
|
|||
|
|
{
|
|||
|
|
return "table schema";
|
|||
|
|
}
|
|||
|
|
if (path.contains("controller"))
|
|||
|
|
{
|
|||
|
|
return "controller";
|
|||
|
|
}
|
|||
|
|
if (path.contains("service"))
|
|||
|
|
{
|
|||
|
|
return "service";
|
|||
|
|
}
|
|||
|
|
if (path.contains("mapper"))
|
|||
|
|
{
|
|||
|
|
return "mapper";
|
|||
|
|
}
|
|||
|
|
if (path.contains("/domain/") || path.contains("/entity/") || path.contains("/model/")
|
|||
|
|
|| path.contains("/dto/"))
|
|||
|
|
{
|
|||
|
|
return "domain model";
|
|||
|
|
}
|
|||
|
|
if (path.contains("/views/") || path.endsWith(".vue"))
|
|||
|
|
{
|
|||
|
|
return "vue page";
|
|||
|
|
}
|
|||
|
|
if (path.contains("/api/"))
|
|||
|
|
{
|
|||
|
|
return "frontend api";
|
|||
|
|
}
|
|||
|
|
if (path.contains("router"))
|
|||
|
|
{
|
|||
|
|
return "router";
|
|||
|
|
}
|
|||
|
|
return "supporting file";
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: Replace `filePriority` scoring**
|
|||
|
|
|
|||
|
|
Replace the existing `filePriority` method with:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
private int filePriority(CodeAnalysisSourceSummary.SourceFile file)
|
|||
|
|
{
|
|||
|
|
String path = StringUtils.defaultString(file == null ? null : file.getPath()).toLowerCase(Locale.ROOT);
|
|||
|
|
String type = StringUtils.defaultString(file == null ? null : file.getTemplateType()).toLowerCase(Locale.ROOT);
|
|||
|
|
if ("sql".equals(type) || path.endsWith(".sql"))
|
|||
|
|
{
|
|||
|
|
return 10;
|
|||
|
|
}
|
|||
|
|
if (path.contains("controller"))
|
|||
|
|
{
|
|||
|
|
return 20;
|
|||
|
|
}
|
|||
|
|
if (path.contains("serviceimpl") || path.contains("service"))
|
|||
|
|
{
|
|||
|
|
return 30;
|
|||
|
|
}
|
|||
|
|
if (path.contains("mapper"))
|
|||
|
|
{
|
|||
|
|
return 40;
|
|||
|
|
}
|
|||
|
|
if (path.contains("/domain/") || path.contains("/entity/") || path.contains("/model/")
|
|||
|
|
|| path.contains("/dto/"))
|
|||
|
|
{
|
|||
|
|
return 50;
|
|||
|
|
}
|
|||
|
|
if (path.contains("/views/") || path.endsWith(".vue"))
|
|||
|
|
{
|
|||
|
|
return 60;
|
|||
|
|
}
|
|||
|
|
if (path.contains("/api/") || path.contains("router"))
|
|||
|
|
{
|
|||
|
|
return 70;
|
|||
|
|
}
|
|||
|
|
if (path.endsWith("application.yml") || path.endsWith("application.yaml")
|
|||
|
|
|| path.endsWith("pom.xml") || path.endsWith("package.json"))
|
|||
|
|
{
|
|||
|
|
return 80;
|
|||
|
|
}
|
|||
|
|
if (path.endsWith(".java") || path.endsWith(".xml") || path.endsWith(".js") || path.endsWith(".ts"))
|
|||
|
|
{
|
|||
|
|
return 100;
|
|||
|
|
}
|
|||
|
|
return 200;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 6: Run the focused tests**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ruoyi-generator -Dtest=ProjectCodeAnalysisServiceTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: PASS. All `ProjectCodeAnalysisServiceTest` tests pass, including the new core selection test.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 3: Make AI Prompt Explicit About Curated Source
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Modify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java`
|
|||
|
|
- Modify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerateServiceImpl.java`
|
|||
|
|
- Test: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: Write failing assertions for prompt wording**
|
|||
|
|
|
|||
|
|
In `generateCodeAnalysisPromptRequiresFixedSectionsAndSourceGrounding()`, after:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
assertTrue(prompt.contains("不要编造未出现的模块"));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
add:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
assertTrue(prompt.contains("精选核心源码"));
|
|||
|
|
assertTrue(prompt.contains("只能依据已展开源码和文件清单"));
|
|||
|
|
assertTrue(prompt.contains("无法从精选源码确认"));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: Run the focused prompt test and verify it fails**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ruoyi-generator -Dtest=AiGenerateServiceImplTest#generateCodeAnalysisPromptRequiresFixedSectionsAndSourceGrounding test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: FAIL because the current prompt does not contain the curated-source wording.
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: Update `buildCodeAnalysisPrompt` wording**
|
|||
|
|
|
|||
|
|
In `AiGenerateServiceImpl.buildCodeAnalysisPrompt`, replace the first prompt sentence:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
prompt.append("你是资深 Java/Vue 毕设项目代码解读助手。请只依据下方源码快照和项目元信息进行解读,不要编造未出现的模块、接口、表或流程。\n");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
with:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
prompt.append("你是资深 Java/Vue 毕设项目代码解读助手。下方源码快照是系统精选核心源码子集,请只依据已展开源码和文件清单进行解读,不要编造未出现的模块、接口、表或流程;如果细节无法从精选源码确认,请明确写出无法从精选源码确认。\n");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Replace the source snapshot intro:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
prompt.append("源码快照如下,每个文件以模板类型和路径标识。请引用真实类名、方法名、组件名、表名或字段名作为依据。\n");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
with:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
prompt.append("精选核心源码如下,每个文件以模板类型和路径标识,并包含已选文件与跳过文件清单。请引用真实类名、方法名、组件名、表名或字段名作为依据,不要根据未展开文件推断实现细节。\n");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: Run the focused prompt test**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ruoyi-generator -Dtest=AiGenerateServiceImplTest#generateCodeAnalysisPromptRequiresFixedSectionsAndSourceGrounding test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: PASS.
|
|||
|
|
|
|||
|
|
- [ ] **Step 5: Run the two affected test classes**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ruoyi-generator -Dtest=ProjectCodeAnalysisServiceTest,AiGenerateServiceImplTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: PASS. Both affected test classes pass.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### Task 4: Final Verification And Commit
|
|||
|
|
|
|||
|
|
**Files:**
|
|||
|
|
- Verify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisService.java`
|
|||
|
|
- Verify: `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerateServiceImpl.java`
|
|||
|
|
- Verify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisServiceTest.java`
|
|||
|
|
- Verify: `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java`
|
|||
|
|
|
|||
|
|
- [ ] **Step 1: Check the final diff**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git diff -- ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisService.java ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerateServiceImpl.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisServiceTest.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: Diff only contains the core source selection tests, service selection logic, and prompt wording changes.
|
|||
|
|
|
|||
|
|
- [ ] **Step 2: Run verification**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
mvn -pl ruoyi-generator -Dtest=ProjectCodeAnalysisServiceTest,AiGenerateServiceImplTest test
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: PASS.
|
|||
|
|
|
|||
|
|
- [ ] **Step 3: Stage only the implementation files**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git add -- ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisService.java ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerateServiceImpl.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/ProjectCodeAnalysisServiceTest.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerateServiceImplTest.java docs/superpowers/plans/2026-07-07-code-analysis-core-selection.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- [ ] **Step 4: Commit**
|
|||
|
|
|
|||
|
|
Run:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git commit -m "feat: select core source for code analysis"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Expected: Commit succeeds and includes only the implementation files and plan file.
|