@routerhub/agent-rules 1.5.190 → 1.5.191

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
@@ -14,6 +14,17 @@
14
14
  - **Skill**:多步骤操作流程,需按需调用,如部署、发 PR、Figma 还原、TDD 流程。新建 `skills/技能名/SKILL.md`。
15
15
  - **判断标准**:「这件事每次写代码都要遵守吗?」→ 是 = 规则,否(只有特定场景才触发)= Skill。
16
16
 
17
+ ## ⚠️ 规则文件分层与个人级启停(机制说明)
18
+
19
+ agent-rules 生成的规则文件分两层,行为与归属不同,评审/发版/接入时请勿混淆:
20
+
21
+ - **个人级:`CLAUDE.md` / `AGENTS.md`(不入库,每机本地生成)**。这两个文件是「本地 AI 自动读取」的规则,agent-rules 只是文件生成器、不是运行时拦截器——文件一旦 git 入库,谁 clone 都带上,无法按人关掉。因此改为不入库(`.gitignore` 忽略),是否生成取决于**本机开发者的 git 身份**(`git config user.name` / `user.email` 任一包含包内 `enabled-users.json` 白名单字符串,忽略大小写)。白名单初始为空数组 = **默认停用**;停用 = 不生成 + 清理本机残留的带签名产物(头部 `# Copilot Agent Rules - Base`)+ 加入 `.gitignore`,只删 agent-rules 生成的、开发者自己的同名文件不动。
22
+ - **团队级:`.github/*`、`.claude/skills/*`、`AGENTS.private.md`(入库,始终生成)**。供 GitHub 云端 PR review、团队共享,不参与个人启停。
23
+
24
+ 成员想启用/停用这套个人规则,只取决于「在不在白名单」,与具体项目无关。想启用的人把 `git config user.name` / `git config user.email` 的输出发给 agent-rules 管理员加入白名单即可。
25
+
26
+ **发版/评审方注意**:升级 PR 中出现「`git rm --cached` 删除已跟踪的 CLAUDE.md / AGENTS.md + .gitignore 追加忽略」是本机制的存量迁移(个人文件从入库改为本地生成),属正常流程,禁止按「误删文件」报错;个人级文件不再入库,规则评审请以团队级产物 `.github/**` 为准。
27
+
17
28
  ## 语言与内容
18
29
 
19
30
  - 始终使用中文回答,代码注释使用中文。
package/README.zh-CN.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  `@routerhub/agent-rules` 用来把通用规则和项目私有规则合并成项目中可直接使用的规则文件。
4
4
 
5
- 它主要提供两类能力:
5
+ 它提供两类能力:
6
6
 
7
- 1. 生成项目可用的 `AGENTS.md` `.github/copilot-instructions.md`
7
+ 1. 按「个人级 / 团队级」两层生成项目可用的规则文件(详见下方「规则启停」章节):`CLAUDE.md` / `AGENTS.md` 按本机 git 身份白名单本地生成、默认停用;`.github/*`、skills 等团队级文件始终生成
8
8
  2. 监听 `AGENTS.private.md` 变更并自动同步
9
9
 
10
10
  ## 安装
@@ -85,6 +85,54 @@ pnpm update @routerhub/agent-rules --latest
85
85
 
86
86
  > 直接 `pnpm i` 会按 `pnpm-lock.yaml` 中的锁定版本安装,必须加 `--latest` 才能拿到最新版本。
87
87
 
88
+ ## 规则启停(按人白名单)
89
+
90
+ 生成的规则文件分两层:
91
+
92
+ | 层级 | 文件 | 是否入库 | 何时生成 |
93
+ |---|---|---|---|
94
+ | 个人级 | `CLAUDE.md` / `AGENTS.md`(本地 AI 自动读取) | **不入库**(`.gitignore` 忽略,随安装本地生成) | **仅当本机 git 身份命中白名单** |
95
+ | 团队级 | `.github/copilot-instructions.md`、`.github/instructions/*`、`.github/PULL_REQUEST_TEMPLATE.md`、`.claude/skills/*`、`AGENTS.private.md` | 随仓库入库分发 | 始终生成,不受个人启停影响 |
96
+
97
+ **为什么个人级文件要「不入库 + 每机生成」**:Claude Code / Cursor / Copilot 是「工作区里有这个文件就读」,agent-rules 只是文件生成器、不是运行时拦截器。若个人级文件写死在 git 仓库里,谁 clone 下来都有这套规则,无法按人关掉。所以 `CLAUDE.md` / `AGENTS.md` 改成由每台机器的安装过程按白名单决定「生成 / 不生成」。
98
+
99
+ ### 白名单判定规则
100
+
101
+ - 白名单载体:npm 包内 `enabled-users.json`(发布到 `node_modules/@routerhub/agent-rules/enabled-users.json`),由 agent-rules 管理员维护。
102
+ - 判定:**本机 git 身份 `user.name` 或 `user.email` 任一「包含」名单中任一字符串(不区分大小写、包含即命中)→ 启用**;未命中 → 默认停用。
103
+ - 与仓库无关:只判断「这台电脑的开发者是谁」,不按项目 / git remote 判断,仓库分发逻辑不变。
104
+ - **默认停用**:名单初始为空数组(先谁也不放)。不想用这套规则的人什么都不用做——只要不在名单里,个人级规则就不会生成(团队级 `.github` 云上 review 规则仍保留,属团队共享,不由个人启停控制)。
105
+ - 逃生门:`AGENT_RULES_ENABLED=1` 强制启用 / `=0` 强制停用(测试与临时场景,环境变量级别,优先级高于白名单)。
106
+
107
+ ### 停用时的行为
108
+
109
+ - 不生成 `CLAUDE.md` / `AGENTS.md`;
110
+ - 自动把这两个文件加入项目 `.gitignore`,防止后续误提交入库;
111
+ - 清理本机历史安装残留:只删除「存在 + 未被 git 跟踪 + 文件头为 `# Copilot Agent Rules - Base`」的 agent-rules 产物,**开发者自己的同名文件绝不删除**;仍被 git 跟踪的旧产物会提示等待仓库升级移除(升级时自动完成,见下文)。
112
+
113
+ ### 如何获取本机 git 身份(想启用的人自查用)
114
+
115
+ 在目标项目根目录(或任意能取到本机身份的目录)执行:
116
+
117
+ ```bash
118
+ git config user.name
119
+ git config user.email
120
+ ```
121
+
122
+ - 不带 `--global` 时,显示**当前所在项目生效的值**(项目级 `user.name/email` 优先,项目没配则回退到全局 `~/.gitconfig`)。
123
+ - 想确认自己机器全局配置的,再加 `--global`:
124
+
125
+ ```bash
126
+ git config --global user.name
127
+ git config --global user.email
128
+ ```
129
+
130
+ 把上面命令输出的字符串发给 agent-rules 管理员,管理员把其中之一(或其子串)加入 `enabled-users.json` 并发版;对方执行一次 `pnpm update @routerhub/agent-rules --latest`(或重新 `pnpm install`)后,个人级规则即在本机生效。
131
+
132
+ ### 存量仓库升级迁移
133
+
134
+ 此前版本 `CLAUDE.md` / `AGENTS.md` 是随仓库 git 跟踪分发的。升级到本版本后,agent-rules 的自动升级 PR(8 个下游仓库)会**一次完成迁移**:把这两个文件移出 git 跟踪(`git rm --cached`,工作区文件保留),并同步 `.gitignore`。合并升级 PR 后,个人规则即按本机身份启停。
135
+
88
136
  ## 注意事项
89
137
 
90
138
  - 私有规则应只写项目特有的限制、接口地址、组件路径和业务规范
@@ -0,0 +1 @@
1
+ []
package/merge.js CHANGED
@@ -12,13 +12,15 @@
12
12
  * update 自动更新 @routerhub/agent-rules 到最新版本并重新生成规则文件
13
13
  * watch 监听 AGENTS.private.md 变化并自动同步
14
14
  *
15
- * 输出文件:
16
- * AGENTS.md 全量合并(兼容 Cursor/Claude Code)
17
- * CLAUDE.md 全量合并(Claude Code 自动加载)
18
- * .github/copilot-instructions.md 全局规则(VS Code Copilot)
19
- * .github/instructions/*.instructions.md 按域条件加载(VS Code Copilot applyTo)
20
- * .github/PULL_REQUEST_TEMPLATE.md 统一 PR 模板(GitHub 新建 PR 时预填)
21
- * .claude/skills/* 同步 Skills 到项目本地
15
+ * 输出文件分两层:
16
+ * 个人级(不入库、按本机 git 身份白名单本地生成,默认停用):
17
+ * AGENTS.md 全量合并(兼容 Cursor/Claude Code
18
+ * CLAUDE.md 全量合并(Claude Code 自动加载)
19
+ * 团队级(始终生成、随仓库分发共享):
20
+ * .github/copilot-instructions.md 全局规则(VS Code Copilot)
21
+ * .github/instructions/*.instructions.md 按域条件加载(VS Code Copilot applyTo)
22
+ * .github/PULL_REQUEST_TEMPLATE.md 统一 PR 模板(GitHub 新建 PR 时预填)
23
+ * .claude/skills/* 同步 Skills 到项目本地
22
24
  */
23
25
 
24
26
  const fs = require("fs");
@@ -105,6 +107,168 @@ function writeFileIfChanged(filePath, content) {
105
107
  return true;
106
108
  }
107
109
 
110
+ /* ===== 个人级启停(按人白名单)=====
111
+ *
112
+ * AGENTS.md / CLAUDE.md 是「本地 AI 自动读取」的个人级规则文件,不入库、随安装生成。
113
+ * 是否生成取决于「当前机器开发者的本地 git 身份」(user.name / user.email)是否命中
114
+ * enabled-users.json 白名单中的任一字符串(不区分大小写、包含即命中)。默认白名单为
115
+ * 空数组 → 所有人默认停用。停用 = 不再生成 + 清理本机残留的 agent-rules 个人文件。
116
+ * 团队级文件(.github/*、.claude/skills/*、AGENTS.private.md)始终生成,不参与个人启停。
117
+ * 逃生门:环境变量 AGENT_RULES_ENABLED=1/0 可强制启用/停用(测试与临时场景)。
118
+ */
119
+
120
+ // agent-rules 生成文件的签名头部,用于安全识别「我们的产物」,避免误删开发者自己的文件
121
+ const PERSONAL_OUTPUT_SIGNATURE = "# Copilot Agent Rules - Base";
122
+
123
+ // 读取当前开发者的本地 git 身份(项目级配置优先,缺失时回退全局;均无则返回空串)
124
+ function getGitIdentity() {
125
+ const { spawnSync } = require("child_process");
126
+ const read = (key) => {
127
+ try {
128
+ const result = spawnSync("git", ["config", "--get", key], {
129
+ cwd: currentRoot,
130
+ encoding: "utf8",
131
+ });
132
+ if (result.status === 0) {
133
+ return (result.stdout || "").trim();
134
+ }
135
+ } catch (e) {
136
+ // 忽略:目录不在 git 仓库或 git 不可用,视为拿不到该身份
137
+ }
138
+ return "";
139
+ };
140
+ return { name: read("user.name"), email: read("user.email") };
141
+ }
142
+
143
+ // 读取随包分发的白名单(agent-rules 包根目录 enabled-users.json)
144
+ function loadEnabledUsers() {
145
+ const usersPath = path.join(packageRoot, "enabled-users.json");
146
+ try {
147
+ if (fs.existsSync(usersPath)) {
148
+ const parsed = JSON.parse(fs.readFileSync(usersPath, "utf8"));
149
+ if (Array.isArray(parsed)) {
150
+ return parsed.map((s) => String(s).trim()).filter(Boolean);
151
+ }
152
+ }
153
+ } catch (e) {
154
+ console.warn(
155
+ `⚠️ 白名单 ${usersPath} 解析失败,默认按「停用」处理: ${e.message}`,
156
+ );
157
+ }
158
+ return [];
159
+ }
160
+
161
+ // 个人规则(CLAUDE.md / AGENTS.md)是否对当前机器启用,返回 { enabled, reason } 便于打印原因
162
+ function resolvePersonalEnabled() {
163
+ const force = process.env.AGENT_RULES_ENABLED;
164
+ if (force === "1" || force === "true") {
165
+ return { enabled: true, reason: "环境变量 AGENT_RULES_ENABLED=1 强制启用" };
166
+ }
167
+ if (force === "0" || force === "false") {
168
+ return { enabled: false, reason: "环境变量 AGENT_RULES_ENABLED=0 强制停用" };
169
+ }
170
+
171
+ const { name, email } = getGitIdentity();
172
+ const identities = [name, email].map((s) => s.toLowerCase()).filter(Boolean);
173
+ // 拿不到任何 git 身份(CI / bot / 非 git 目录且无全局配置)→ 默认停用
174
+ if (identities.length === 0) {
175
+ return { enabled: false, reason: "当前目录未取到 git 身份(CI / bot / 非开发者)" };
176
+ }
177
+
178
+ const enabledUsers = loadEnabledUsers();
179
+ if (enabledUsers.length === 0) {
180
+ return { enabled: false, reason: "enabled-users.json 白名单为空(默认停用)" };
181
+ }
182
+ // git 身份(name/email)「包含」白名单任一字符串(不区分大小写)即启用
183
+ const hit = enabledUsers.some((candidate) => {
184
+ const key = candidate.toLowerCase();
185
+ return identities.some((identity) => identity.includes(key));
186
+ });
187
+ return hit
188
+ ? { enabled: true, reason: "本机 git 身份命中 enabled-users.json 白名单" }
189
+ : { enabled: false, reason: "本机 git 身份未命中 enabled-users.json 白名单" };
190
+ }
191
+
192
+ function isPersonalEnabled() {
193
+ return resolvePersonalEnabled().enabled;
194
+ }
195
+
196
+ // 文件是否已被 git 跟踪(true 时删除会改变 git 状态,不应由生成器擅自处理)
197
+ function isGitTracked(filePath) {
198
+ const { spawnSync } = require("child_process");
199
+ const relPath = path.relative(currentRoot, filePath);
200
+ try {
201
+ const result = spawnSync(
202
+ "git",
203
+ ["ls-files", "--error-unmatch", "--", relPath],
204
+ { cwd: currentRoot, encoding: "utf8" },
205
+ );
206
+ return result.status === 0;
207
+ } catch (e) {
208
+ return false;
209
+ }
210
+ }
211
+
212
+ // 停用时清理个人规则文件:只删「存在 + untracked + 头部为 agent-rules 签名」的产物;
213
+ // tracked 产物提示走仓库迁移(生成器不擅动 git 状态);签名不匹配(可能已被开发者
214
+ // 替换/改写)绝不删除,只删我们生成的、别人的不动。
215
+ function cleanupPersonalOutput(filePath) {
216
+ if (!fs.existsSync(filePath)) {
217
+ return;
218
+ }
219
+ const content = readFileIfExists(filePath);
220
+ if (content === null || !content.startsWith(PERSONAL_OUTPUT_SIGNATURE)) {
221
+ console.log(
222
+ ` ⏭️ 跳过 ${filePath}:内容不是 agent-rules 生成产物(可能是开发者自己的文件),未删除`,
223
+ );
224
+ return;
225
+ }
226
+ if (isGitTracked(filePath)) {
227
+ console.log(
228
+ ` ⏭️ 跳过 ${filePath}:该文件仍被 git 跟踪,请等待仓库升级移除(或由管理员手动删除后提交),生成器不擅动 git 状态`,
229
+ );
230
+ return;
231
+ }
232
+ fs.unlinkSync(filePath);
233
+ console.log(` 🗑️ 已删除停用的个人规则文件 ${filePath}`);
234
+ }
235
+
236
+ // 幂等:把个人规则文件加入项目 .gitignore,防止后续误提交入库
237
+ function ensureIgnorePersonalOutputs() {
238
+ const gitignorePath = path.join(currentRoot, ".gitignore");
239
+ const header =
240
+ "# agent-rules:个人规则文件(CLAUDE.md / AGENTS.md)不入库,随安装按白名单本地生成";
241
+ const patterns = ["/AGENTS.md", "/CLAUDE.md"];
242
+
243
+ const existed = fs.existsSync(gitignorePath);
244
+ const existing = readFileIfExists(gitignorePath) || "";
245
+ const currentLines = existing.split(/\r?\n/);
246
+ const missing = patterns.filter((pattern) => {
247
+ const bare = pattern.replace(/^\//, "");
248
+ return !currentLines.some((line) => {
249
+ const trimmed = line.trim();
250
+ return trimmed === pattern || trimmed === bare;
251
+ });
252
+ });
253
+ if (missing.length === 0) {
254
+ return;
255
+ }
256
+
257
+ const block = header + "\n" + missing.join("\n") + "\n";
258
+ let nextContent;
259
+ if (!existed) {
260
+ nextContent = block;
261
+ } else if (existing.endsWith("\n")) {
262
+ nextContent = existing + block;
263
+ } else {
264
+ nextContent = existing + "\n" + block;
265
+ }
266
+ fs.writeFileSync(gitignorePath, nextContent, "utf-8");
267
+ console.log(
268
+ ` ℹ️ 已把个人规则文件加入 ${gitignorePath}(/AGENTS.md、/CLAUDE.md)`,
269
+ );
270
+ }
271
+
108
272
  /**
109
273
  * 解析 markdown 文件的 YAML frontmatter
110
274
  * 返回 { meta: { name, applyTo, outputName }, content: "去掉 frontmatter 后的正文" }
@@ -487,6 +651,17 @@ function mergeAgents(config) {
487
651
  console.log(` 规则目录: ${rulesDir}`);
488
652
  console.log(` 私有规则: ${privateRulesPath}`);
489
653
 
654
+ // 个人级启停:决定 CLAUDE.md / AGENTS.md 是否本地生成(按本机 git 身份白名单)
655
+ const { enabled: personalEnabled, reason: personalReason } =
656
+ resolvePersonalEnabled();
657
+ console.log(
658
+ ` 个人规则文件(CLAUDE.md / AGENTS.md): ${
659
+ personalEnabled ? "✅ 启用" : "⏸️ 停用"
660
+ } —— ${personalReason}`,
661
+ );
662
+ // 幂等:无论启停都确保个人文件被 .gitignore 忽略,防止被误提交入库
663
+ ensureIgnorePersonalOutputs();
664
+
490
665
  // 同步统一 PR 模板到 .github/PULL_REQUEST_TEMPLATE.md(独立于 rules/ 是否为空)
491
666
  syncPrTemplate(config);
492
667
 
@@ -513,10 +688,18 @@ function mergeAgents(config) {
513
688
  privateRules;
514
689
  }
515
690
 
516
- for (const outputPath of [agentsOutput, claudeOutput, copilotOutput]) {
517
- writeFileIfChanged(outputPath, merged);
518
- console.log(`✅ 规则已合并到 ${outputPath}`);
691
+ // 个人级输出(CLAUDE.md / AGENTS.md)受白名单启停;团队级 copilot 始终生成
692
+ if (personalEnabled) {
693
+ for (const outputPath of [agentsOutput, claudeOutput]) {
694
+ writeFileIfChanged(outputPath, merged);
695
+ console.log(`✅ 规则已合并到 ${outputPath}`);
696
+ }
697
+ } else {
698
+ cleanupPersonalOutput(agentsOutput);
699
+ cleanupPersonalOutput(claudeOutput);
519
700
  }
701
+ writeFileIfChanged(copilotOutput, merged);
702
+ console.log(`✅ 规则已合并到 ${copilotOutput}`);
520
703
 
521
704
  // 同步 Skills
522
705
  syncSkills(config);
@@ -577,12 +760,19 @@ function mergeAgents(config) {
577
760
  "\n\n---\n\n# 项目私有规则\n\n以下规则仅适用于本项目,会覆盖基础规则中的相同部分。\n\n" +
578
761
  privateContent;
579
762
  }
580
- writeFileIfChanged(agentsOutput, agentsMerged);
581
- console.log(`✅ 全量规则已合并到 ${agentsOutput}`);
582
-
583
- // CLAUDE.md 与 AGENTS.md 内容一致,供 Claude Code 会话自动加载
584
- writeFileIfChanged(claudeOutput, agentsMerged);
585
- console.log(`✅ 全量规则已合并到 ${claudeOutput}`);
763
+ // 6. 个人级输出(CLAUDE.md / AGENTS.md):仅当本机 git 身份命中白名单才生成;
764
+ // 停用则清理本机残留(untracked + 签名的产物),团队级文件不受影响
765
+ if (personalEnabled) {
766
+ writeFileIfChanged(agentsOutput, agentsMerged);
767
+ console.log(`✅ 全量规则已合并到 ${agentsOutput}`);
768
+
769
+ // CLAUDE.md 与 AGENTS.md 内容一致,供 Claude Code 会话自动加载
770
+ writeFileIfChanged(claudeOutput, agentsMerged);
771
+ console.log(`✅ 全量规则已合并到 ${claudeOutput}`);
772
+ } else {
773
+ cleanupPersonalOutput(agentsOutput);
774
+ cleanupPersonalOutput(claudeOutput);
775
+ }
586
776
 
587
777
  // 6.1 同步 Skills 到项目 .claude/skills/
588
778
  syncSkills(config);
@@ -603,9 +793,10 @@ function initAgents() {
603
793
  }
604
794
 
605
795
  console.log("✅ 初始化完成,后续执行 agent-rules sync 即可刷新规则文件");
606
- console.log(" 输出文件:");
607
- console.log(" - CLAUDE.md(全量,Claude Code 会话自动加载)");
608
- console.log(" - AGENTS.md(全量,兼容 Cursor/Claude Code)");
796
+ console.log(" 个人级文件(按本机 git 身份白名单启停,默认停用):");
797
+ console.log(" - CLAUDE.md / AGENTS.md(停用时不会生成,并清理本机残留)");
798
+ console.log(" 查看当前 git 身份:git config user.name / git config user.email");
799
+ console.log(" 团队级文件(始终生成、随仓库分发):");
609
800
  console.log(" - .github/copilot-instructions.md(全局规则)");
610
801
  console.log(" - .github/instructions/*.instructions.md(按域条件加载)");
611
802
  console.log(" - .claude/skills/*(Claude Code Skills 技能文件)");
@@ -754,13 +945,14 @@ function main() {
754
945
  );
755
946
  console.log(" agent-rules help 查看帮助");
756
947
  console.log("");
757
- console.log("输出文件:");
948
+ console.log("输出文件(个人级,按本机 git 身份白名单启停,默认停用):");
758
949
  console.log(
759
950
  " CLAUDE.md 全量合并(Claude Code 会话自动加载)",
760
951
  );
761
952
  console.log(
762
953
  " AGENTS.md 全量合并(兼容 Cursor/Claude Code)",
763
954
  );
955
+ console.log("输出文件(团队级,始终生成):");
764
956
  console.log(
765
957
  " .github/copilot-instructions.md 全局规则(VS Code Copilot)",
766
958
  );
@@ -770,6 +962,17 @@ function main() {
770
962
  console.log(
771
963
  " .claude/skills/* Claude Code Skills(创建PR / 部署 / Figma / TDD / 写文档等)",
772
964
  );
965
+ console.log("");
966
+ console.log("个人级启停说明:");
967
+ console.log(
968
+ " 本机 git 身份(git config user.name / user.email)包含 enabled-users.json 白名单任一字符串即启用",
969
+ );
970
+ console.log(
971
+ " 查看当前身份: git config user.name / git config user.email",
972
+ );
973
+ console.log(
974
+ " 强制开关(测试/临时): AGENT_RULES_ENABLED=1 强制启用 / =0 强制停用",
975
+ );
773
976
  return;
774
977
  }
775
978
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@routerhub/agent-rules",
3
- "version": "1.5.190",
3
+ "version": "1.5.191",
4
4
  "description": "Shared Copilot agent rules and guidelines for RouterHub projects",
5
5
  "main": "AGENTS.base.md",
6
6
  "bin": {
@@ -16,7 +16,8 @@
16
16
  "README.zh-CN.md",
17
17
  "postinstall.js",
18
18
  "package.json",
19
- "CHANGELOG.md"
19
+ "CHANGELOG.md",
20
+ "enabled-users.json"
20
21
  ],
21
22
  "scripts": {
22
23
  "build": "node --check merge.js && node --check postinstall.js",