@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 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
- fs.writeFileSync(globalPath, globalFrontmatter + globalText + "\n", "utf-8");
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
- fs.writeFileSync(filePath, frontmatter + domainText.trim() + "\n", "utf-8");
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
- ensureParentDir(outputPath);
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
- ensureParentDir(outputPath);
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
- ensureParentDir(output);
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
- ensureParentDir(outputPath);
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
- ensureParentDir(copilotOutput);
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
- ensureParentDir(agentsOutput);
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
- ensureParentDir(claudeOutput);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routerhub/agent-rules",
3
- "version": "1.5.142",
3
+ "version": "1.5.144",
4
4
  "description": "Shared Copilot agent rules and guidelines for RouterHub projects",
5
5
  "main": "AGENTS.base.md",
6
6
  "bin": {
@@ -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
  - ⚠️ **两模型报同一问题合并为一条处理**,不要重复改两遍。