@routerhub/agent-rules 1.5.142 → 1.5.143

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.
Files changed (2) hide show
  1. package/merge.js +40 -16
  2. package/package.json +1 -1
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.143",
4
4
  "description": "Shared Copilot agent rules and guidelines for RouterHub projects",
5
5
  "main": "AGENTS.base.md",
6
6
  "bin": {