Skip to content

Commit b76cf86

Browse files
committed
feat: 规则注入器添加版本跟踪机制,旧项目模板自动更新
问题:旧项目的 AGENTS.md/copilot-instructions.md 不包含 flow 模式和平台规则阅读要求, 导致 AI 生成蓝本时不知道 flow 连续流模式,每个场景都独立冷启动浪费大量时间。 根因:injectRules() 发现文件已存在就直接跳过,永远不更新旧模板内容。 修复: - 添加 TEMPLATE_VERSION 常量(当前 v2)和 extractVersion() 函数 - AGENTS.md/CLAUDE.md:检测版本号,旧版本自动替换为新模板 - IDE专属规则文件(copilot-instructions.md等):旧版本时智能替换 TestPilot 部分 - 平台模板文件:版本过旧时自动更新 - needsInjection() 也检查版本号 - 5个平台模板文件添加版本标记 v2
1 parent 63db4d8 commit b76cf86

6 files changed

Lines changed: 122 additions & 12 deletions

File tree

extension/src/rulesInjector.ts

Lines changed: 117 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,22 @@ function detectCurrentIDE(): string {
4747
return "vscode";
4848
}
4949

50+
/**
51+
* 模板版本号。每次更新模板内容时递增。
52+
* rulesInjector 会检测已注入文件的版本号,低于此版本则自动更新。
53+
*/
54+
const TEMPLATE_VERSION = 2;
55+
56+
/** 从文件内容中提取版本号,找不到返回 0(旧版无版本标记) */
57+
function extractVersion(content: string): number {
58+
const match = content.match(/<!-- TestPilot-Template-Version: (\d+) -->/);
59+
return match ? parseInt(match[1], 10) : 0;
60+
}
61+
5062
/** 规则模板内容(精简版:通用规则 + 引导AI读取平台专属规则文件) */
5163
function getTemplateContent(): string {
52-
return `# TestPilot AI — 编程AI蓝本自动生成规则
64+
return `<!-- TestPilot-Template-Version: ${TEMPLATE_VERSION} -->
65+
# TestPilot AI — 编程AI蓝本自动生成规则
5366
5467
> 本文件由 TestPilot AI 插件自动注入,指导编程AI在开发过程中自动生成和维护测试蓝本。
5568
> 你可以根据项目需求自由修改本文件内容。
@@ -291,12 +304,28 @@ function injectPlatformTemplates(
291304
const templates = getPlatformTemplates(extensionPath);
292305
const targetDir = path.join(workspaceRoot, ".testpilot", "platforms");
293306

294-
// 如果 .testpilot/platforms 目录已存在并且有内容,跳过
307+
// 检查 .testpilot/platforms 目录
295308
if (fs.existsSync(targetDir)) {
296309
const existing = fs.readdirSync(targetDir).filter((f: string) => f.endsWith(".md"));
297310
if (existing.length >= PLATFORM_FILES.length) {
298-
outputChannel?.appendLine(`[TestPilot AI] ⏭️ .testpilot/platforms/ 已有 ${existing.length} 个模板,跳过`);
299-
return created;
311+
// 所有文件都存在,检查版本号决定是否需要更新
312+
let needsUpdate = false;
313+
for (const fileName of PLATFORM_FILES) {
314+
const targetPath = path.join(targetDir, fileName);
315+
if (fs.existsSync(targetPath)) {
316+
const content = fs.readFileSync(targetPath, "utf-8");
317+
const ver = extractVersion(content);
318+
if (ver < TEMPLATE_VERSION) {
319+
needsUpdate = true;
320+
break;
321+
}
322+
}
323+
}
324+
if (!needsUpdate) {
325+
outputChannel?.appendLine(`[TestPilot AI] ⏭️ .testpilot/platforms/ 已有 ${existing.length} 个模板且版本最新,跳过`);
326+
return created;
327+
}
328+
outputChannel?.appendLine(`[TestPilot AI] 🔄 .testpilot/platforms/ 版本过旧,更新中...`);
300329
}
301330
}
302331

@@ -305,7 +334,7 @@ function injectPlatformTemplates(
305334
fs.mkdirSync(targetDir, { recursive: true });
306335
}
307336

308-
// 写入每个平台模板(不存在才创建
337+
// 写入每个平台模板(不存在则创建,版本过旧则更新
309338
for (const [fileName, content] of templates) {
310339
const targetPath = path.join(targetDir, fileName);
311340
if (!fs.existsSync(targetPath)) {
@@ -316,6 +345,18 @@ function injectPlatformTemplates(
316345
const msg = err instanceof Error ? err.message : String(err);
317346
outputChannel?.appendLine(`[TestPilot AI] ⚠️ 创建 ${fileName} 失败: ${msg}`);
318347
}
348+
} else {
349+
// 已存在:检查版本号
350+
try {
351+
const existingContent = fs.readFileSync(targetPath, "utf-8");
352+
const existingVersion = extractVersion(existingContent);
353+
if (existingVersion < TEMPLATE_VERSION) {
354+
fs.writeFileSync(targetPath, content, "utf-8");
355+
created.push(`.testpilot/platforms/${fileName} (v${existingVersion}→v${TEMPLATE_VERSION})`);
356+
}
357+
} catch {
358+
// 静默忽略
359+
}
319360
}
320361
}
321362

@@ -373,17 +414,54 @@ export function injectRules(
373414

374415
if (fs.existsSync(fullPath)) {
375416
if (isAgentsMd) {
376-
// AGENTS.md / CLAUDE.md 是 TestPilot 专属文件,已存在则跳过
377-
skipped.push(relPath);
417+
// AGENTS.md / CLAUDE.md:检查版本号,旧版本则自动更新
418+
try {
419+
const existingContent = fs.readFileSync(fullPath, "utf-8");
420+
const existingVersion = extractVersion(existingContent);
421+
if (existingVersion >= TEMPLATE_VERSION) {
422+
skipped.push(relPath);
423+
continue;
424+
}
425+
// 版本过旧,更新文件
426+
fs.writeFileSync(fullPath, template, "utf-8");
427+
created.push(relPath + ` (v${existingVersion}→v${TEMPLATE_VERSION})`);
428+
outputChannel?.appendLine(
429+
`[TestPilot AI] 🔄 ${relPath} 版本过旧(v${existingVersion}),已更新到 v${TEMPLATE_VERSION}`,
430+
);
431+
} catch (err) {
432+
const msg = err instanceof Error ? err.message : String(err);
433+
outputChannel?.appendLine(`[TestPilot AI] ⚠️ 更新 ${relPath} 失败: ${msg}`);
434+
skipped.push(relPath);
435+
}
378436
continue;
379437
}
380438

381439
// IDE 专用规则文件(如 .github/copilot-instructions.md):追加模式
382440
try {
383441
const existingContent = fs.readFileSync(fullPath, "utf-8");
384442
if (existingContent.includes(TESTPILOT_MARKER)) {
385-
// 已包含 TestPilot 规则,跳过
386-
skipped.push(relPath);
443+
// 已包含 TestPilot 规则,检查版本
444+
const existingVersion = extractVersion(existingContent);
445+
if (existingVersion >= TEMPLATE_VERSION) {
446+
skipped.push(relPath);
447+
continue;
448+
}
449+
// 版本过旧:替换 TestPilot 部分(保留用户自己的内容)
450+
const markerIndex = existingContent.indexOf(TESTPILOT_MARKER);
451+
// 向前找分隔线或版本标记
452+
let startIndex = existingContent.lastIndexOf("---", markerIndex);
453+
if (startIndex === -1) startIndex = existingContent.lastIndexOf("<!-- TestPilot", markerIndex);
454+
if (startIndex === -1) startIndex = markerIndex;
455+
const userContent = existingContent.substring(0, startIndex).trimEnd();
456+
if (userContent.length > 0) {
457+
fs.writeFileSync(fullPath, userContent + "\n\n---\n\n" + template, "utf-8");
458+
} else {
459+
fs.writeFileSync(fullPath, template, "utf-8");
460+
}
461+
created.push(relPath + ` (v${existingVersion}→v${TEMPLATE_VERSION})`);
462+
outputChannel?.appendLine(
463+
`[TestPilot AI] 🔄 ${relPath} TestPilot规则已更新到 v${TEMPLATE_VERSION}`,
464+
);
387465
continue;
388466
}
389467
// 在用户已有内容末尾追加 TestPilot 规则
@@ -444,10 +522,20 @@ export function injectRules(
444522

445523
/**
446524
* 检查工作区是否需要注入规则
447-
* 缺少 AGENTS.md 或缺少 .testpilot/platforms 目录都需要注入
525+
* 缺少 AGENTS.md.testpilot/platforms 目录不全、或版本过旧都需要注入
448526
*/
449527
export function needsInjection(workspaceRoot: string): boolean {
450-
if (!fs.existsSync(path.join(workspaceRoot, "AGENTS.md"))) {
528+
const agentsPath = path.join(workspaceRoot, "AGENTS.md");
529+
if (!fs.existsSync(agentsPath)) {
530+
return true;
531+
}
532+
// 检查 AGENTS.md 版本号
533+
try {
534+
const content = fs.readFileSync(agentsPath, "utf-8");
535+
if (extractVersion(content) < TEMPLATE_VERSION) {
536+
return true;
537+
}
538+
} catch {
451539
return true;
452540
}
453541
// 检查平台模板是否已注入
@@ -456,7 +544,24 @@ export function needsInjection(workspaceRoot: string): boolean {
456544
return true;
457545
}
458546
const existing = fs.readdirSync(platformDir).filter((f: string) => f.endsWith(".md"));
459-
return existing.length < PLATFORM_FILES.length;
547+
if (existing.length < PLATFORM_FILES.length) {
548+
return true;
549+
}
550+
// 检查平台模板版本
551+
for (const fileName of PLATFORM_FILES) {
552+
const filePath = path.join(platformDir, fileName);
553+
if (fs.existsSync(filePath)) {
554+
try {
555+
const content = fs.readFileSync(filePath, "utf-8");
556+
if (extractVersion(content) < TEMPLATE_VERSION) {
557+
return true;
558+
}
559+
} catch {
560+
return true;
561+
}
562+
}
563+
}
564+
return false;
460565
}
461566

462567
/**

extension/templates/platforms/android.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- TestPilot-Template-Version: 2 -->
12
# Android/Flutter 平台蓝本规则(platform = "android")
23

34
> 本文件定义 Android 原生应用和 Flutter 应用蓝本的完整规则。

extension/templates/platforms/desktop.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- TestPilot-Template-Version: 2 -->
12
# Windows 桌面应用平台蓝本规则(platform = "desktop")
23

34
> 本文件定义 Windows 桌面应用(WPF/WinForms/Electron/Qt 等)蓝本的完整规则。

extension/templates/platforms/ios.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- TestPilot-Template-Version: 2 -->
12
# iOS/SwiftUI 平台蓝本规则(platform = "ios")
23

34
> 本文件定义 iOS 原生应用和 SwiftUI 应用蓝本的完整规则。

extension/templates/platforms/miniprogram.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- TestPilot-Template-Version: 2 -->
12
# 微信小程序平台蓝本规则(platform = "miniprogram")
23

34
> 本文件定义微信小程序蓝本的完整规则。

extension/templates/platforms/web.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<!-- TestPilot-Template-Version: 2 -->
12
# Web 平台蓝本规则(platform = "web")
23

34
> 本文件定义 Web 应用(React/Vue/Angular/纯HTML)蓝本的完整规则。

0 commit comments

Comments
 (0)