@xfe-repo/cli-plugin-ai-rules 2.0.9 → 2.0.10

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @xfe-repo/cli-plugin-ai-rules
2
2
 
3
+ ## 2.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - 优化插件性能
8
+
3
9
  ## 2.0.9
4
10
 
5
11
  ### Patch Changes
package/README.md CHANGED
@@ -7,9 +7,22 @@ AI 编码规则同步插件,从远程仓库拉取 AI 编码规则并注入到
7
7
  - 声明式 AI 配置管理,从 `xfe.json` 读取 `ai` 配置
8
8
  - 远程仓库规则同步,支持本地缓存
9
9
  - 多目标格式分发:Markdown、MCP JSON/TOML、技能目录
10
+ - 按 Agent、Sub-Agent、Rules、Skills、MCP 类型独立选择,避免混合列表
10
11
  - 智能冲突检测与解决
11
12
  - 增量更新规划
12
13
 
14
+ ## 快速选择
15
+
16
+ ```bash
17
+ xfe rules add --type agent
18
+ xfe rules add --type subagent
19
+ xfe rules add --type rule
20
+ xfe rules add --type skill
21
+ xfe rules add --type mcp
22
+ ```
23
+
24
+ 不传 `--type` 时先选择条目类型;传入后直接进入对应条目列表,其他类型的现有配置保持不变。
25
+
13
26
  ## 依赖插件
14
27
 
15
28
  - `plugin-git`
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { XfePlugin } from '@xfe-repo/cli-core';
8
8
  import type { AiRulesCapability, AiRulesStoreState } from './types.js';
9
- export { SyncItemType } from './types.js';
9
+ export { RulesAddType, SyncItemType } from './types.js';
10
10
  export type { AiRulesCapability, SyncEntry } from './types.js';
11
11
  declare const AI_RULES_STORE_INITIAL: AiRulesStoreState;
12
12
  declare module '@xfe-repo/cli-core' {
package/dist/index.js CHANGED
@@ -4,20 +4,39 @@
4
4
  * 声明式 AI 配置同步:从 xfe.json 读取 ai 配置,
5
5
  * 拉取远程仓库内容,按 target 注册表分发到项目 IDE 配置
6
6
  */
7
- import { aiConfigSchema, rulesAddParameters, rulesSyncParameters, rulesTargetsParameters } from './types.js';
8
- import { SyncItemType } from './types.js';
7
+ import { aiConfigSchema, rulesAddParameters, rulesSyncParameters, rulesTargetsParameters, RulesAddType, SyncItemType } from './types.js';
9
8
  import { planSync, detectConflicts, resolveConflicts, executePlan, checkSyncStatus, applyInjections as applyInjectionsToFiles, } from './pipeline.js';
10
9
  import { scanRemoteEntries, loadSelectedItems } from './scanner.js';
11
10
  import { fetchCachedRepo } from './repo-cache.js';
12
11
  import { getTargetChoices } from './targets.js';
13
12
  import { removeStaleSkillDirs } from './formats/skill-dir.js';
14
- export { SyncItemType } from './types.js';
13
+ export { RulesAddType, SyncItemType } from './types.js';
15
14
  // ─── Store ──────
16
15
  const AI_RULES_STORE_INITIAL = {
17
16
  lastSyncHash: {},
18
17
  lastRemoteHash: '',
19
18
  lastSyncTime: 0,
20
19
  };
20
+ const RULES_ADD_TYPE_CHOICES = [
21
+ { name: '主 Agent', value: RulesAddType.Agent, description: '选择写入 AGENTS.md 或 CLAUDE.md 的主助手' },
22
+ { name: 'Sub-Agent', value: RulesAddType.Subagent, description: '选择可按需调用的子助手' },
23
+ { name: '编码规则 (Rules)', value: RulesAddType.Rule, description: '选择项目编码规范' },
24
+ { name: 'AI 技能 (Skills)', value: RulesAddType.Skill, description: '选择可复用的 AI 工作流' },
25
+ { name: 'MCP 服务', value: RulesAddType.Mcp, description: '选择外部工具连接' },
26
+ ];
27
+ const ENTRY_TYPE_BY_RULES_ADD_TYPE = {
28
+ agent: SyncItemType.Agent,
29
+ subagent: SyncItemType.Agent,
30
+ rule: SyncItemType.Rule,
31
+ skill: SyncItemType.Skill,
32
+ mcp: SyncItemType.McpServer,
33
+ };
34
+ const CONFIG_KEY_BY_RULES_ADD_TYPE = {
35
+ subagent: 'subagents',
36
+ rule: 'rules',
37
+ skill: 'skills',
38
+ mcp: 'mcpServers',
39
+ };
21
40
  // ─── 插件工厂 ──────
22
41
  export function aiRulesPlugin() {
23
42
  const injectionRegistry = new Map();
@@ -69,8 +88,8 @@ export function aiRulesPlugin() {
69
88
  });
70
89
  hooks.registerCommand({
71
90
  name: 'rules:add',
72
- simpleDescription: '选择远程 AI 条目并写入配置',
73
- description: ' aiRules.source 仓库扫描可用的 agent、rule、skill 条目,并把 selectedIds 写回 xfe.json。若 selectedIds 中包含多个 agent,可通过 primaryAgent 指定主 Agent,其余 agent 会写入 subagents。',
91
+ simpleDescription: '按类型选择远程 AI 条目',
92
+ description: ' agent、subagent、rule、skill mcp 类型选择远程条目并更新 xfe.json;传入 type 可跳过类型选择,其他类型的现有配置保持不变。',
74
93
  parameters: rulesAddParameters,
75
94
  execute: (ctx) => executeAddCommand(ctx, injectionRegistry),
76
95
  });
@@ -196,40 +215,17 @@ async function executeAddCommand(ctx, injections) {
196
215
  const cachePath = await fetchCachedRepo(ctx, git, config);
197
216
  const entries = await scanRemoteEntries(cachePath);
198
217
  spinner.stop();
199
- const currentSelectedIds = collectSelectedIds(config);
200
- const selectedIds = await promptItemSelection(ctx, entries, currentSelectedIds);
201
- // 分类选中项
202
- const selectedAgents = entries.filter((e) => e.type === SyncItemType.Agent && selectedIds.has(e.id));
203
- let agentId;
204
- if (selectedAgents.length === 1) {
205
- agentId = selectedAgents[0].id;
206
- }
207
- else if (selectedAgents.length > 1) {
208
- agentId = await promptMainAgentSelection(ctx, selectedAgents, config.agent);
209
- }
210
- const { rules, skills, mcpServers, subagents } = classifySelectedIds(entries, selectedIds, agentId);
218
+ const selectionType = await promptRulesAddType(ctx);
219
+ const selectionPatch = await promptTypeSelection(ctx, entries, config, selectionType);
220
+ if (!selectionPatch)
221
+ return;
211
222
  ctx.store.setPluginState('xfe', {
212
223
  aiRules: {
213
224
  ...ctx.store.getState().xfe?.aiRules,
214
- agent: agentId,
215
- subagents,
216
- rules,
217
- skills,
218
- mcpServers,
225
+ ...selectionPatch,
219
226
  },
220
227
  });
221
- const parts = [
222
- agentId && `agent: ${agentId}`,
223
- subagents.length > 0 && `${subagents.length} subagents`,
224
- rules.length > 0 && `${rules.length} rules`,
225
- skills.length > 0 && `${skills.length} skills`,
226
- mcpServers.length > 0 && `${mcpServers.length} mcp-servers`,
227
- ].filter(Boolean);
228
- ctx.logger.success(`已写入 xfe.json: ${parts.join(', ') || '无选择'}`);
229
- if (parts.length === 0) {
230
- ctx.logger.warn('未选择任何条目,配置已清空');
231
- return;
232
- }
228
+ logSelectionResult(ctx, selectionType, selectionPatch);
233
229
  const { sync } = await ctx.prompt.ask({
234
230
  type: 'confirm',
235
231
  name: 'sync',
@@ -271,34 +267,68 @@ async function executeTargetsCommand(ctx) {
271
267
  }
272
268
  }
273
269
  // ─── 交互提示 ──────
274
- async function promptMainAgentSelection(ctx, selectedAgents, currentAgent) {
275
- const choices = selectedAgents.map((entry) => ({
276
- name: entry.id,
277
- value: entry.id,
278
- description: entry.description,
279
- }));
280
- const { primaryAgent } = await ctx.prompt.ask({
270
+ async function promptRulesAddType(ctx) {
271
+ const { type } = await ctx.prompt.ask({
281
272
  type: 'list',
273
+ name: 'type',
274
+ message: '选择要配置的 AI 条目类型:',
275
+ choices: RULES_ADD_TYPE_CHOICES,
276
+ });
277
+ if (Object.values(RulesAddType).includes(type))
278
+ return type;
279
+ throw new Error(`不支持的 AI 条目类型: ${String(type)}`);
280
+ }
281
+ async function promptTypeSelection(ctx, entries, config, selectionType) {
282
+ const selectableEntries = getSelectableEntries(entries, selectionType, config.agent);
283
+ if (selectableEntries.length === 0) {
284
+ ctx.logger.warn(`${getRulesAddTypeLabel(selectionType)} 暂无可选项`);
285
+ return undefined;
286
+ }
287
+ if (selectionType === RulesAddType.Agent) {
288
+ const agent = await promptPrimaryAgentSelection(ctx, selectableEntries, config.agent);
289
+ return {
290
+ agent,
291
+ subagents: config.subagents.filter((id) => id !== agent),
292
+ };
293
+ }
294
+ const configKey = CONFIG_KEY_BY_RULES_ADD_TYPE[selectionType];
295
+ if (!configKey)
296
+ return undefined;
297
+ const selectedIds = await promptItemSelection(ctx, selectableEntries, new Set(config[configKey]), selectionType);
298
+ const selectionPatch = {};
299
+ selectionPatch[configKey] = [...selectedIds];
300
+ return selectionPatch;
301
+ }
302
+ async function promptPrimaryAgentSelection(ctx, agents, currentAgent) {
303
+ const choices = [
304
+ { name: '不配置主 Agent', value: '', description: '清除当前主 Agent' },
305
+ ...agents.map((entry) => ({
306
+ name: entry.id,
307
+ value: entry.id,
308
+ description: getEntryChoiceDescription(entry),
309
+ })),
310
+ ];
311
+ const { primaryAgent } = await ctx.prompt.ask({
312
+ type: 'search-list',
282
313
  name: 'primaryAgent',
283
- message: '已选择多个 Agent,请指定主 Agent(输出到 AGENTS.md/CLAUDE.md):',
314
+ message: '选择主 Agent:',
284
315
  choices,
285
- default: currentAgent || undefined,
316
+ default: currentAgent ?? '',
286
317
  });
287
318
  return primaryAgent || undefined;
288
319
  }
289
- async function promptItemSelection(ctx, entries, currentIds) {
290
- if (entries.length === 0)
291
- return new Set();
320
+ async function promptItemSelection(ctx, entries, currentIds, selectionType) {
292
321
  const choices = entries.map((entry) => {
293
322
  const description = getEntryChoiceDescription(entry);
294
323
  return {
295
- name: `[${entry.type}] ${entry.id}`,
324
+ name: entry.id,
296
325
  value: entry.id,
297
326
  description,
298
327
  short: entry.id,
299
328
  };
300
329
  });
301
- const message = currentIds.size > 0 ? `选择 AI 规则 (当前已选 ${currentIds.size} 项):` : '选择 AI 规则:';
330
+ const label = getRulesAddTypeLabel(selectionType);
331
+ const message = currentIds.size > 0 ? `选择 ${label} (当前已选 ${currentIds.size} 项):` : `选择 ${label}:`;
302
332
  const { selectedIds = [] } = await ctx.prompt.ask({
303
333
  type: 'search-checkbox',
304
334
  name: 'selectedIds',
@@ -309,31 +339,22 @@ async function promptItemSelection(ctx, entries, currentIds) {
309
339
  return new Set(selectedIds);
310
340
  }
311
341
  // ─── Helpers ──────
312
- function classifySelectedIds(entries, selectedIds, agentId) {
313
- const rules = [];
314
- const skills = [];
315
- const mcpServers = [];
316
- const subagents = [];
317
- for (const entry of entries) {
318
- if (!selectedIds.has(entry.id))
319
- continue;
320
- switch (entry.type) {
321
- case SyncItemType.Rule:
322
- rules.push(entry.id);
323
- break;
324
- case SyncItemType.Skill:
325
- skills.push(entry.id);
326
- break;
327
- case SyncItemType.McpServer:
328
- mcpServers.push(entry.id);
329
- break;
330
- case SyncItemType.Agent:
331
- if (entry.id !== agentId)
332
- subagents.push(entry.id);
333
- break;
334
- }
342
+ function getSelectableEntries(entries, selectionType, currentAgent) {
343
+ const entryType = ENTRY_TYPE_BY_RULES_ADD_TYPE[selectionType];
344
+ return entries.filter((entry) => entry.type === entryType && !(selectionType === RulesAddType.Subagent && entry.id === currentAgent));
345
+ }
346
+ function getRulesAddTypeLabel(selectionType) {
347
+ return RULES_ADD_TYPE_CHOICES.find(({ value }) => value === selectionType)?.name ?? selectionType;
348
+ }
349
+ function logSelectionResult(ctx, selectionType, selectionPatch) {
350
+ const label = getRulesAddTypeLabel(selectionType);
351
+ if (selectionType === RulesAddType.Agent) {
352
+ ctx.logger.success(selectionPatch.agent ? `已更新主 Agent: ${selectionPatch.agent}` : '已清除主 Agent');
353
+ return;
335
354
  }
336
- return { rules, skills, mcpServers, subagents };
355
+ const configKey = CONFIG_KEY_BY_RULES_ADD_TYPE[selectionType];
356
+ const selectedCount = configKey ? (selectionPatch[configKey]?.length ?? 0) : 0;
357
+ ctx.logger.success(`已更新 ${label}: ${selectedCount} 项`);
337
358
  }
338
359
  function collectSelectedIds(config) {
339
360
  const ids = [...config.subagents, ...config.rules, ...config.skills, ...config.mcpServers];
@@ -345,10 +366,7 @@ function hasInjectedItems(injections) {
345
366
  return [...injections.values()].some((items) => items.length > 0);
346
367
  }
347
368
  function getEntryChoiceDescription(entry) {
348
- const descriptions = [
349
- entry.description,
350
- entry.appliesTo?.length ? `适用: ${entry.appliesTo.join(', ')}` : undefined,
351
- ].filter(Boolean);
369
+ const descriptions = [entry.description, entry.appliesTo?.length ? `适用: ${entry.appliesTo.join(', ')}` : undefined].filter(Boolean);
352
370
  if (descriptions.length === 0)
353
371
  return undefined;
354
372
  return descriptions.join(' · ');
package/dist/types.d.ts CHANGED
@@ -29,7 +29,15 @@ export declare enum McpConfigFormat {
29
29
  export declare const rulesTargetsParameters: z.ZodObject<{
30
30
  targets: z.ZodOptional<z.ZodArray<z.ZodEnum<typeof SyncTarget>>>;
31
31
  }, z.core.$strip>;
32
+ export declare enum RulesAddType {
33
+ Agent = "agent",
34
+ Subagent = "subagent",
35
+ Rule = "rule",
36
+ Skill = "skill",
37
+ Mcp = "mcp"
38
+ }
32
39
  export declare const rulesAddParameters: z.ZodObject<{
40
+ type: z.ZodOptional<z.ZodEnum<typeof RulesAddType>>;
33
41
  selectedIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
34
42
  primaryAgent: z.ZodOptional<z.ZodString>;
35
43
  sync: z.ZodOptional<z.ZodBoolean>;
package/dist/types.js CHANGED
@@ -32,12 +32,18 @@ const syncTargetSchema = z.enum(SyncTarget);
32
32
  export const rulesTargetsParameters = z.object({
33
33
  targets: z.array(syncTargetSchema).optional().describe('需要同步注入的 IDE 目标 ID,可选值: copilot、codex、cursor、claude-code'),
34
34
  });
35
+ export var RulesAddType;
36
+ (function (RulesAddType) {
37
+ RulesAddType["Agent"] = "agent";
38
+ RulesAddType["Subagent"] = "subagent";
39
+ RulesAddType["Rule"] = "rule";
40
+ RulesAddType["Skill"] = "skill";
41
+ RulesAddType["Mcp"] = "mcp";
42
+ })(RulesAddType || (RulesAddType = {}));
35
43
  export const rulesAddParameters = z.object({
36
- selectedIds: z
37
- .array(z.string())
38
- .optional()
39
- .describe('需要写入 aiRules 的远程条目 ID 列表,可混合 agent、rule、skill;MCP 调用时应直接传入'),
40
- primaryAgent: z.string().optional().describe('当 selectedIds 中包含多个 agent 时,指定主 Agent ID;其余 agent 会写入 subagents'),
44
+ type: z.enum(RulesAddType).optional().describe('需要配置的条目类型,可选值: agent、subagent、rule、skill、mcp'),
45
+ selectedIds: z.array(z.string()).optional().describe('type 为 subagent、rule、skill 或 mcp 时,需要写入 aiRules 的远程条目 ID 列表'),
46
+ primaryAgent: z.string().optional().describe('type 为 agent 时,需要写入 aiRules.agent 的主 Agent ID'),
41
47
  sync: z.boolean().optional().describe('写入配置后立即执行同步'),
42
48
  });
43
49
  export const rulesSyncParameters = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfe-repo/cli-plugin-ai-rules",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
4
4
  "description": "XFE AI Rules plugin - sync AI coding rules from remote repo to IDE configs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",