@routerhub/agent-rules 1.5.142 → 1.5.144
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/merge.js +40 -16
- package/package.json +1 -1
- package/skills/deploy-test/SKILL.md +11 -0
- package/skills/loop-review/SKILL.md +2 -0
package/merge.js
CHANGED
|
@@ -74,6 +74,37 @@ function readFileIfExists(filePath) {
|
|
|
74
74
|
return null;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* 只在内容变化时写盘(不变量写入 => 磁盘 mtime 不变 => 缓存命中率更高)
|
|
79
|
+
*
|
|
80
|
+
* 为什么这样写(对应「缓存命中」优化思路):
|
|
81
|
+
* - AI 工具链(Claude Code / Copilot)按文件内容缓存,部分按 mtime 感知变化;
|
|
82
|
+
* 只要内容没变,反复执行 sync 也会触发一次「写盘 -> mtime 变化 -> 下游以为规则变了」
|
|
83
|
+
* 的假变更,实际内容一个字都没变,缓存却因此白白失效一次。
|
|
84
|
+
* - 改成「内容哈希相同则跳过写入」:内容没变,文件字节和 mtime 完全不动,
|
|
85
|
+
* 下游不会误判「规则变了」,缓存能一直续上;内容真正变了才写一次、失效一次,
|
|
86
|
+
* 这才是合理的失效。
|
|
87
|
+
* - 内容统一结尾加一个换行:消除「尾部换行符有无」这种看不见的抖动,
|
|
88
|
+
* 保证「同一份内容」永远生成出完全一样的字节,不因结尾风格差异触发缓存失效。
|
|
89
|
+
*
|
|
90
|
+
* @param {string} filePath 目标文件绝对路径
|
|
91
|
+
* @param {string} content 生成后的完整内容
|
|
92
|
+
* @returns {boolean} 是否真正写入了磁盘(false = 内容未变,跳过写入)
|
|
93
|
+
*/
|
|
94
|
+
function writeFileIfChanged(filePath, content) {
|
|
95
|
+
const normalizedContent = content.endsWith("\n") ? content : content + "\n";
|
|
96
|
+
const existing = readFileIfExists(filePath);
|
|
97
|
+
|
|
98
|
+
// 内容完全相同(含结尾换行也一致)=> 不写盘,保持 mtime 不变
|
|
99
|
+
if (existing !== null && existing === normalizedContent) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ensureParentDir(filePath);
|
|
104
|
+
fs.writeFileSync(filePath, normalizedContent, "utf-8");
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
77
108
|
/**
|
|
78
109
|
* 解析 markdown 文件的 YAML frontmatter
|
|
79
110
|
* 返回 { meta: { name, applyTo, outputName }, content: "去掉 frontmatter 后的正文" }
|
|
@@ -234,7 +265,7 @@ function generateRulesFromBase(baseRulesPath, rulesDir) {
|
|
|
234
265
|
|
|
235
266
|
const globalFrontmatter = '---\nname: "通用规则"\n---\n\n';
|
|
236
267
|
const globalPath = path.join(rulesDir, "global.md");
|
|
237
|
-
|
|
268
|
+
writeFileIfChanged(globalPath, globalFrontmatter + globalText);
|
|
238
269
|
console.log(` ✅ rules/global.md`);
|
|
239
270
|
|
|
240
271
|
// 生成各域的 rules/*.md
|
|
@@ -255,7 +286,7 @@ function generateRulesFromBase(baseRulesPath, rulesDir) {
|
|
|
255
286
|
].join("\n");
|
|
256
287
|
|
|
257
288
|
const filePath = path.join(rulesDir, meta.fileName);
|
|
258
|
-
|
|
289
|
+
writeFileIfChanged(filePath, frontmatter + domainText.trim());
|
|
259
290
|
console.log(` ✅ rules/${meta.fileName}`);
|
|
260
291
|
}
|
|
261
292
|
|
|
@@ -339,8 +370,7 @@ function generateInstructionFile(
|
|
|
339
370
|
const frontmatter = `---\napplyTo: ${JSON.stringify(applyTo)}\n---\n`;
|
|
340
371
|
const fileContent = frontmatter + "\n" + content;
|
|
341
372
|
|
|
342
|
-
|
|
343
|
-
fs.writeFileSync(outputPath, fileContent, "utf-8");
|
|
373
|
+
writeFileIfChanged(outputPath, fileContent);
|
|
344
374
|
console.log(`✅ 条件规则已生成: ${outputPath}`);
|
|
345
375
|
}
|
|
346
376
|
|
|
@@ -386,8 +416,7 @@ function generateNoReviewInstructionFile(instructionsDir) {
|
|
|
386
416
|
"Treat these files as generated artifacts and skip them entirely.",
|
|
387
417
|
].join("\n");
|
|
388
418
|
|
|
389
|
-
|
|
390
|
-
fs.writeFileSync(outputPath, frontmatter + "\n" + body + "\n", "utf-8");
|
|
419
|
+
writeFileIfChanged(outputPath, frontmatter + "\n" + body);
|
|
391
420
|
console.log(`✅ Code Review 跳过规则已生成: ${outputPath}`);
|
|
392
421
|
}
|
|
393
422
|
|
|
@@ -397,8 +426,7 @@ function syncPrTemplate(config) {
|
|
|
397
426
|
const output = config.prTemplateOutput || defaultConfig.prTemplateOutput;
|
|
398
427
|
const content = readFileIfExists(src);
|
|
399
428
|
if (!content) return;
|
|
400
|
-
|
|
401
|
-
fs.writeFileSync(output, content, "utf-8");
|
|
429
|
+
writeFileIfChanged(output, content);
|
|
402
430
|
console.log(`✅ PR 模板已同步到 ${output}`);
|
|
403
431
|
}
|
|
404
432
|
|
|
@@ -486,8 +514,7 @@ function mergeAgents(config) {
|
|
|
486
514
|
}
|
|
487
515
|
|
|
488
516
|
for (const outputPath of [agentsOutput, claudeOutput, copilotOutput]) {
|
|
489
|
-
|
|
490
|
-
fs.writeFileSync(outputPath, merged, "utf-8");
|
|
517
|
+
writeFileIfChanged(outputPath, merged);
|
|
491
518
|
console.log(`✅ 规则已合并到 ${outputPath}`);
|
|
492
519
|
}
|
|
493
520
|
|
|
@@ -510,8 +537,7 @@ function mergeAgents(config) {
|
|
|
510
537
|
if (privateDomains.global) {
|
|
511
538
|
copilotContent += "\n\n---\n\n# 项目私有规则\n\n" + privateDomains.global;
|
|
512
539
|
}
|
|
513
|
-
|
|
514
|
-
fs.writeFileSync(copilotOutput, copilotContent, "utf-8");
|
|
540
|
+
writeFileIfChanged(copilotOutput, copilotContent);
|
|
515
541
|
console.log(`✅ 全局规则已生成: ${copilotOutput}`);
|
|
516
542
|
|
|
517
543
|
// 5. 生成 .github/instructions/*.instructions.md(各域规则)
|
|
@@ -551,13 +577,11 @@ function mergeAgents(config) {
|
|
|
551
577
|
"\n\n---\n\n# 项目私有规则\n\n以下规则仅适用于本项目,会覆盖基础规则中的相同部分。\n\n" +
|
|
552
578
|
privateContent;
|
|
553
579
|
}
|
|
554
|
-
|
|
555
|
-
fs.writeFileSync(agentsOutput, agentsMerged, "utf-8");
|
|
580
|
+
writeFileIfChanged(agentsOutput, agentsMerged);
|
|
556
581
|
console.log(`✅ 全量规则已合并到 ${agentsOutput}`);
|
|
557
582
|
|
|
558
583
|
// CLAUDE.md 与 AGENTS.md 内容一致,供 Claude Code 会话自动加载
|
|
559
|
-
|
|
560
|
-
fs.writeFileSync(claudeOutput, agentsMerged, "utf-8");
|
|
584
|
+
writeFileIfChanged(claudeOutput, agentsMerged);
|
|
561
585
|
console.log(`✅ 全量规则已合并到 ${claudeOutput}`);
|
|
562
586
|
|
|
563
587
|
// 6.1 同步 Skills 到项目 .claude/skills/
|
package/package.json
CHANGED
|
@@ -40,6 +40,17 @@ git commit -m "..." # 使用有意义的提交信息
|
|
|
40
40
|
git push origin "$ORIGINAL_BRANCH"
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
+
⚠️ **若本次改动经过循环 review(`loop-review` 修复过代码),必须确认所有 review 修复已同步到当前功能分支(PR 对应分支)并推送远程,再进行 test 合并。** 核对方式:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# 确认本地功能分支与远程已同步(本地无未推送提交)
|
|
47
|
+
git status
|
|
48
|
+
git log origin/"$ORIGINAL_BRANCH".."$ORIGINAL_BRANCH" --oneline # 应无输出 = 无未推送提交
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- ⚠️ 若 review 修复还没合进当前功能分支 → 先把修复 commit 到功能分支再推送,禁止带着"修复只在 test 分支"的状态去部署。
|
|
52
|
+
- ⚠️ feature 分支与 test 分支两条线必须保持同步:**所有 review 修复代码必须先落 feature 分支(PR 分支),再合并到 test**,缺一不可。
|
|
53
|
+
|
|
43
54
|
### 3. test 分支检查
|
|
44
55
|
|
|
45
56
|
如果 `test` 分支不存在,从主分支创建:
|
|
@@ -121,6 +121,7 @@ gh api repos/$REPO/pulls/$PR/reviews?per_page=100 --jq \
|
|
|
121
121
|
- 判定「值得修」的(无论严重度)→ 每个修复单独 commit,commit 信息用中文,正文引用对应 review 来源
|
|
122
122
|
- 纯建议级且判定值得改的 → 合并为一个 commit
|
|
123
123
|
- 判定「不值得修 / 已处理 / 误报」的 → 按步骤 4 回复,切断 bot 下轮重提
|
|
124
|
+
- ⚠️ **所有修复必须直接 commit 在当前 PR 对应的 feature 分支上**(`git push origin <feature 分支>`),并推送到远程。禁止把 review 修复只放在其他地方(例如直接堆到 test 分支、或修改后不 commit)。**宗旨:feature 分支(PR)与 test 分支两条线保持同步,修复代码同时存在于 feature 分支和 test 分支,缺一不可。**
|
|
124
125
|
|
|
125
126
|
### 4. 对「不改」的建议回复(防重复循环,关键)
|
|
126
127
|
|
|
@@ -204,6 +205,7 @@ done
|
|
|
204
205
|
- ⚠️ **禁止使用 `[skip ci]` / `[skip review]`**——循环依赖「每次 push 触发 review」,跳过就断了。
|
|
205
206
|
- ⚠️ **必须设最大轮数(默认 5;用户明确要求深度循环时最多 8)和等待超时(默认 15 分钟)**,禁止无限循环 / 无限等待。
|
|
206
207
|
- ⚠️ **已修复的功能性问题单独 commit**,禁止与大量建议级改动混在一起,保证每轮 push 的 diff 可读。
|
|
208
|
+
- ⚠️ **每条 review 修复都必须同 commit 回 PR 对应的 feature 分支并推送远程**,不得只推到 test 分支或只放在工作区不 commit。feature 分支与 test 分支两条线必须同步,缺一不可(详见步骤 3)。
|
|
207
209
|
- ⚠️ **等待 bot 审查期间遵守心跳约定**:超过 1 分钟无输出主动说明在等什么。
|
|
208
210
|
- ⚠️ **commit 用中文、禁止 `git push --force`、commit 信息引用对应 review 来源**。
|
|
209
211
|
- ⚠️ **两模型报同一问题合并为一条处理**,不要重复改两遍。
|