@routerhub/agent-rules 1.5.51 → 1.5.53

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/AGENTS.base.md CHANGED
@@ -108,7 +108,7 @@ Closes #456
108
108
  1. 打开 PR 页面,逐一查看 Copilot review 提出的每一条问题。
109
109
  2. 逐条判断问题是否有道理:确实存在的代码缺陷、逻辑错误、安全风险、性能问题等 → 必须修复;误报、与需求不符、风格偏好无实质影响等 → 在评论中回复解释为何不改,然后 Resolve。
110
110
  3. 对于有道理的问题,直接修改代码、提交并推送到该 PR 分支。
111
- 4. PR 评论中回复该问题(如”已修复”或简要说明修改内容),然后点击 Resolve conversation
111
+ 4. ⚠️ **强制步骤:修改代码并推送后,必须在 GitHub PR 页面上逐条点击 Resolve conversation。** 此步骤不可跳过——即使已回复评论、已推送修复代码,只要未点 Resolve,该条评论仍处于”未解决”状态,reviewer 无法判断是否已处理完毕。每修复一条,立即 Resolve 一条,不要等所有问题修完再批量操作。
112
112
  5. 所有问题处理完毕后,重新请求 Copilot review。
113
113
  6. 重复以上步骤,直到 Copilot review 不再提出新问题为止。
114
114
  - 每一条 Copilot review 评论处理完毕后,必须点击 **Resolve conversation**,不能只回复不 Resolve。不点 Resolve 会导致该评论一直处于未解决状态,无法判断是否已完成处理。
@@ -240,6 +240,55 @@ Closes #456
240
240
  - 用 `console.error` 记录错误(含函数名/模块名上下文),禁止 `console.log` 输出错误。
241
241
  - 提交前移除调试日志和临时代码。
242
242
 
243
+ ## Modal 内 Tooltip 规范
244
+
245
+ 在 Modal/弹窗内实现 tooltip 时,必须遵守以下规则,避免被 Modal 容器裁剪和消失太快两个问题。
246
+
247
+ ### 1. Tooltip 必须用 Portal 渲染到 document.body
248
+
249
+ **问题**:Modal 容器通常有 `overflow: hidden` 或 `overflow-y: auto`,用 CSS `position: absolute/fixed` 的 tooltip 会被裁掉。
250
+
251
+ **解决**:用 React Portal(`createPortal`)把 tooltip 气泡渲染到 `document.body`,完全绕开 Modal 的 overflow 裁剪。
252
+
253
+ - 用 `getBoundingClientRect()` 获取触发元素(`?` 图标)的屏幕坐标
254
+ - 气泡用 `position: fixed` + `transform: translate(...)` 精确定位在触发元素上方
255
+ - `z-index` 至少 10000,确保在所有弹窗层之上
256
+
257
+ ### 2. 延迟隐藏 + 气泡可 hover
258
+
259
+ **问题**:鼠标离开触发元素时 tooltip 立即消失,用户来不及把鼠标移到气泡上阅读内容。
260
+
261
+ **解决**:
262
+ - 隐藏加 200ms 延迟(`setTimeout`),给用户反应时间
263
+ - 气泡本身绑定 `onMouseEnter`(取消隐藏定时器)和 `onMouseLeave`(触发同样的延迟隐藏)
264
+ - CSS 上气泡容器必须 `pointer-events: auto`(不能是 `none`),否则鼠标事件不触发
265
+
266
+ ### 3. 参考实现
267
+
268
+ 实现一个可复用的 `FieldTooltip` 组件,核心结构:
269
+
270
+ ```tsx
271
+ import { createPortal } from 'react-dom';
272
+
273
+ function FieldTooltip({ text }: { text: string }) {
274
+ const iconRef = useRef<HTMLSpanElement>(null);
275
+ const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
276
+ const [visible, setVisible] = useState(false);
277
+ const [pos, setPos] = useState({ top: 0, left: 0 });
278
+
279
+ // show() → 取消定时器 → getBoundingClientRect 更新坐标 → setVisible(true)
280
+ // hide() → 200ms setTimeout → setVisible(false)
281
+ // clearHideTimer() → clearTimeout
282
+
283
+ // ? 图标: onMouseEnter={show} onMouseLeave={hide}
284
+ // Portal 气泡: onMouseEnter={clearHideTimer} onMouseLeave={hide}
285
+ // position: fixed, z-index: 10000, pointer-events: auto
286
+ }
287
+ ```
288
+
289
+ - 禁止在 Modal 内用纯 CSS `position: absolute` 的 hover tooltip
290
+ - 写完 tooltip 后必须实际验证:弹窗内滚动时气泡不被裁剪,鼠标能从图标移到气泡上阅读
291
+
243
292
  <!-- @domain: go-backend -->
244
293
 
245
294
  ## Go 语言规则
package/merge.js CHANGED
@@ -48,6 +48,26 @@ function ensureParentDir(filePath) {
48
48
  fs.mkdirSync(parentDir, { recursive: true });
49
49
  }
50
50
 
51
+ /**
52
+ * 更新 .agent-rules-version 版本标记文件
53
+ * 供 postinstall 判断已安装版本是否与上次同步版本一致,避免重复同步
54
+ */
55
+ function updateVersionMarker() {
56
+ try {
57
+ const currentVersion = JSON.parse(
58
+ fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8")
59
+ ).version;
60
+ fs.writeFileSync(
61
+ path.join(currentRoot, ".agent-rules-version"),
62
+ currentVersion,
63
+ "utf-8"
64
+ );
65
+ } catch (e) {
66
+ // 非关键操作,写入失败不阻塞主流程
67
+ console.warn("[agent-rules] 版本标记写入失败:", e.message);
68
+ }
69
+ }
70
+
51
71
  function resolveOutputPaths(config) {
52
72
  if (Array.isArray(config.outputs) && config.outputs.length > 0) {
53
73
  return config.outputs;
@@ -517,6 +537,7 @@ function initAgents() {
517
537
  const privateOutputPath = path.join(currentRoot, "AGENTS.private.md");
518
538
 
519
539
  mergeAgents(getDefaultConfig());
540
+ updateVersionMarker();
520
541
 
521
542
  if (!fs.existsSync(privateOutputPath) && fs.existsSync(privateTemplatePath)) {
522
543
  fs.copyFileSync(privateTemplatePath, privateOutputPath);
@@ -656,6 +677,7 @@ function main() {
656
677
 
657
678
  if (command === "sync") {
658
679
  mergeAgents(getDefaultConfig());
680
+ updateVersionMarker();
659
681
  return;
660
682
  }
661
683
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routerhub/agent-rules",
3
- "version": "1.5.51",
3
+ "version": "1.5.53",
4
4
  "description": "Shared Copilot agent rules and guidelines for RouterHub projects",
5
5
  "main": "AGENTS.base.md",
6
6
  "bin": {
package/postinstall.js CHANGED
@@ -59,17 +59,54 @@ if (projectRoot.includes("node_modules")) {
59
59
  process.exit(0);
60
60
  }
61
61
 
62
- // 确保下游项目 .npmrc 中关闭 frozen-lockfile,让 pnpm install 能直接在版本更新后执行
62
+ // 确保下游项目 .npmrc 中关闭 frozen-lockfile + 启用依赖 pre/post 脚本
63
+ // frozen-lockfile=false:让 pnpm install 能在版本更新后直接执行
64
+ // enable-pre-post-scripts=true:让 pnpm 始终执行依赖包的 postinstall 生命周期脚本,
65
+ // 否则 pnpm 默认不会运行依赖的 postinstall,导致 agent-rules 更新后规则文件不刷新
63
66
  const npmrcPath = path.join(projectRoot, ".npmrc");
64
67
  let npmrcContent = "";
65
68
  if (fs.existsSync(npmrcPath)) {
66
69
  npmrcContent = fs.readFileSync(npmrcPath, "utf-8");
67
70
  }
71
+ const npmrcEntries = [];
68
72
  if (!npmrcContent.includes("frozen-lockfile")) {
69
- const newEntry =
70
- "\n# 允许 pnpm install 在依赖版本更新后直接执行(由 agent-rules 自动添加)\nfrozen-lockfile=false\n";
73
+ npmrcEntries.push("# 允许 pnpm install 在依赖版本更新后直接执行(由 agent-rules 自动添加)");
74
+ npmrcEntries.push("frozen-lockfile=false");
75
+ }
76
+ if (!npmrcContent.includes("enable-pre-post-scripts")) {
77
+ npmrcEntries.push("# 确保依赖包的 postinstall 生命周期脚本始终执行(由 agent-rules 自动添加)");
78
+ npmrcEntries.push("enable-pre-post-scripts=true");
79
+ }
80
+ if (npmrcEntries.length > 0) {
81
+ const newEntry = "\n" + npmrcEntries.join("\n") + "\n";
71
82
  fs.appendFileSync(npmrcPath, newEntry);
72
- console.log("[agent-rules] 已配置 .npmrc: frozen-lockfile=false");
83
+ console.log("[agent-rules] 已配置 .npmrc: " + npmrcEntries.filter((e) => !e.startsWith("#")).join(", "));
84
+ }
85
+
86
+ // 版本标记机制:比较已安装版本与上次同步的版本
87
+ // 相同 → 跳过同步(避免每次 pnpm install 都重复生成)
88
+ // 不同/不存在 → 执行同步并更新版本标记
89
+ const versionMarkerPath = path.join(projectRoot, ".agent-rules-version");
90
+ const currentVersion = JSON.parse(
91
+ fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8")
92
+ ).version;
93
+ let needsSync = true;
94
+
95
+ if (fs.existsSync(versionMarkerPath)) {
96
+ const lastVersion = fs.readFileSync(versionMarkerPath, "utf-8").trim();
97
+ if (lastVersion === currentVersion) {
98
+ needsSync = false;
99
+ console.log("[agent-rules] 版本未变更 (" + currentVersion + "),跳过同步");
100
+ } else {
101
+ console.log("[agent-rules] 版本变更: " + lastVersion + " → " + currentVersion + ",执行同步...");
102
+ }
103
+ } else {
104
+ console.log("[agent-rules] 首次安装 (v" + currentVersion + "),执行同步...");
105
+ }
106
+
107
+ if (!needsSync) {
108
+ console.log("");
109
+ process.exit(0);
73
110
  }
74
111
 
75
112
  console.log("");
@@ -83,7 +120,9 @@ const syncProcess = spawn("node", [mergeScriptPath, "init"], {
83
120
 
84
121
  syncProcess.on("close", (code) => {
85
122
  if (code === 0) {
86
- console.log("[agent-rules] 规则文件初始化完成");
123
+ // 同步成功后写入版本标记,下次 postinstall 时比较版本号避免重复同步
124
+ fs.writeFileSync(versionMarkerPath, currentVersion, "utf-8");
125
+ console.log("[agent-rules] 规则文件初始化完成 (v" + currentVersion + ")");
87
126
  console.log("");
88
127
  } else {
89
128
  console.error("[agent-rules] 初始化失败,退出码:", code);
package/rules/frontend.md CHANGED
@@ -64,3 +64,52 @@ outputName: "frontend"
64
64
 
65
65
  - 用 `console.error` 记录错误(含函数名/模块名上下文),禁止 `console.log` 输出错误。
66
66
  - 提交前移除调试日志和临时代码。
67
+
68
+ ## Modal 内 Tooltip 规范
69
+
70
+ 在 Modal/弹窗内实现 tooltip 时,必须遵守以下规则,避免被 Modal 容器裁剪和消失太快两个问题。
71
+
72
+ ### 1. Tooltip 必须用 Portal 渲染到 document.body
73
+
74
+ **问题**:Modal 容器通常有 `overflow: hidden` 或 `overflow-y: auto`,用 CSS `position: absolute/fixed` 的 tooltip 会被裁掉。
75
+
76
+ **解决**:用 React Portal(`createPortal`)把 tooltip 气泡渲染到 `document.body`,完全绕开 Modal 的 overflow 裁剪。
77
+
78
+ - 用 `getBoundingClientRect()` 获取触发元素(`?` 图标)的屏幕坐标
79
+ - 气泡用 `position: fixed` + `transform: translate(...)` 精确定位在触发元素上方
80
+ - `z-index` 至少 10000,确保在所有弹窗层之上
81
+
82
+ ### 2. 延迟隐藏 + 气泡可 hover
83
+
84
+ **问题**:鼠标离开触发元素时 tooltip 立即消失,用户来不及把鼠标移到气泡上阅读内容。
85
+
86
+ **解决**:
87
+ - 隐藏加 200ms 延迟(`setTimeout`),给用户反应时间
88
+ - 气泡本身绑定 `onMouseEnter`(取消隐藏定时器)和 `onMouseLeave`(触发同样的延迟隐藏)
89
+ - CSS 上气泡容器必须 `pointer-events: auto`(不能是 `none`),否则鼠标事件不触发
90
+
91
+ ### 3. 参考实现
92
+
93
+ 实现一个可复用的 `FieldTooltip` 组件,核心结构:
94
+
95
+ ```tsx
96
+ import { createPortal } from 'react-dom';
97
+
98
+ function FieldTooltip({ text }: { text: string }) {
99
+ const iconRef = useRef<HTMLSpanElement>(null);
100
+ const hideTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
101
+ const [visible, setVisible] = useState(false);
102
+ const [pos, setPos] = useState({ top: 0, left: 0 });
103
+
104
+ // show() → 取消定时器 → getBoundingClientRect 更新坐标 → setVisible(true)
105
+ // hide() → 200ms setTimeout → setVisible(false)
106
+ // clearHideTimer() → clearTimeout
107
+
108
+ // ? 图标: onMouseEnter={show} onMouseLeave={hide}
109
+ // Portal 气泡: onMouseEnter={clearHideTimer} onMouseLeave={hide}
110
+ // position: fixed, z-index: 10000, pointer-events: auto
111
+ }
112
+ ```
113
+
114
+ - 禁止在 Modal 内用纯 CSS `position: absolute` 的 hover tooltip
115
+ - 写完 tooltip 后必须实际验证:弹窗内滚动时气泡不被裁剪,鼠标能从图标移到气泡上阅读
package/rules/global.md CHANGED
@@ -108,7 +108,7 @@ Closes #456
108
108
  1. 打开 PR 页面,逐一查看 Copilot review 提出的每一条问题。
109
109
  2. 逐条判断问题是否有道理:确实存在的代码缺陷、逻辑错误、安全风险、性能问题等 → 必须修复;误报、与需求不符、风格偏好无实质影响等 → 在评论中回复解释为何不改,然后 Resolve。
110
110
  3. 对于有道理的问题,直接修改代码、提交并推送到该 PR 分支。
111
- 4. PR 评论中回复该问题(如”已修复”或简要说明修改内容),然后点击 Resolve conversation
111
+ 4. ⚠️ **强制步骤:修改代码并推送后,必须在 GitHub PR 页面上逐条点击 Resolve conversation。** 此步骤不可跳过——即使已回复评论、已推送修复代码,只要未点 Resolve,该条评论仍处于”未解决”状态,reviewer 无法判断是否已处理完毕。每修复一条,立即 Resolve 一条,不要等所有问题修完再批量操作。
112
112
  5. 所有问题处理完毕后,重新请求 Copilot review。
113
113
  6. 重复以上步骤,直到 Copilot review 不再提出新问题为止。
114
114
  - 每一条 Copilot review 评论处理完毕后,必须点击 **Resolve conversation**,不能只回复不 Resolve。不点 Resolve 会导致该评论一直处于未解决状态,无法判断是否已完成处理。