team-skills 1.3.3 → 1.3.5

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
@@ -7,6 +7,20 @@
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.5] - 2026-06-26
11
+
12
+ ### 变更
13
+
14
+ - `team-refine` 每轮新增 Step 4:team-score 满分校验(硬门槛覆盖 + 评分维度覆盖 + 缺口修复),收敛条件同步强化
15
+ - 6 个 SKILL.md 质量修复:移除 bare `>` 空行(brainstorm)、`*none*` → `*default*` 兜底修正(review/security)、GATE 自检补充对抗性自问(brainstorm/orchestrator/score)
16
+
17
+ ## [1.3.4] - 2026-06-26
18
+
19
+ ### 变更
20
+
21
+ - 全局安装目录从 2 个扩展为 3 个:新增 `~/.cursor/skills/`(Cursor 全局 Skills 目录)
22
+ - `setup`、`update`、`uninstall`、`list` 四个命令统一使用循环模式处理 3 个全局目标(Agents / Cursor / Claude Code)
23
+
10
24
  ## [1.3.3] - 2026-06-26
11
25
 
12
26
  ### 变更
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-skills",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "AI Agent Skills framework — Spec-Driven development with directed-graph rollback and quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -152,6 +152,7 @@ NO IMPLEMENTATION WITHOUT USER APPROVED DESIGN FIRST
152
152
  - [ ] 数据流已展示并确认
153
153
  - [ ] 关键接口已展示并确认
154
154
  - [ ] 测试策略已展示并确认
155
+ - [ ] 我是否因为流程进度压力而对某个维度草草确认?
155
156
 
156
157
  **MATCH** `user_decision`:
157
158
 
@@ -887,6 +887,7 @@ TDD 强制要求:每个功能点必须先 git commit 失败测试(test: {功
887
887
  - [ ] D5.2 **ASSERT** `14-team.md §二 一致性检查全部通过或已修复`
888
888
  - [ ] D5.3 **ASSERT** `14-team.md §四 真实问题占比 > 0`
889
889
  - [ ] D5.4 **ASSERT** `14-team.md §三 每位贡献者有明确产出物和提交数`
890
+ - [ ] 我是否因为检查项太多而对某些项草草通过了?
890
891
 
891
892
  **IF** `unchecked_items > 0` → 回退对应 Step 补全(D1/D3 缺失 → **GOTO** Step 5;D2 缺失 → **GOTO** Step 2;D4 缺失 → **GOTO** Step 3;D5 缺失 → **GOTO** Step 6)。
892
893
 
@@ -184,7 +184,7 @@ NO COMPLETION CLAIMS WITHOUT CONSTITUTIONAL COMPLIANCE CHECK FIRST
184
184
  - 需要人类决策 → **H3**(有多个可行方案需要选择)
185
185
  - `P2` → 自行修复(**GOTO** Phase 3)
186
186
  - `P3` → 记录但不处理
187
- - *none* → **GOTO** Phase 4
187
+ - *default* → **GOTO** Phase 4
188
188
 
189
189
  **回退时必须提供**:
190
190
 
@@ -258,6 +258,7 @@ NO SCORE WITHOUT EVIDENCE FIRST
258
258
 
259
259
  - [ ] `7 项硬门槛已逐条判定`
260
260
  - [ ] `不通过项已在报告中醒目标注`
261
+ - [ ] 我是否对所有不通过项都给出了具体理由和证据?
261
262
 
262
263
  ### Step 3:逐项评分
263
264
 
@@ -394,6 +394,7 @@ NO AI OPERATIONS WITHOUT RED LINE CHECK FIRST
394
394
  - `权限变更` → **ASSERT** `安全负责人确认记录 EXISTS`
395
395
  - `对外发布` → **ASSERT** `业务负责人审核确认记录 EXISTS`
396
396
  - `资金操作` → **ASSERT** `财务授权人员双人确认记录 EXISTS`
397
+ - *default* → 记录操作类型,无特定确认要求
397
398
 
398
399
  **IF** 确认记录存在 → **ASSERT** `确认为实质性审核` — 非形式审查
399
400
  **ELSE** → 标记 `HITL_MISSING:{operation_type}`
@@ -1,9 +1,8 @@
1
- import { join, resolve } from 'node:path';
2
- import { copyFileSync as fsCopyFile, existsSync, rmSync } from 'node:fs';
1
+ import { resolve } from 'node:path';
3
2
  import { PACKAGE_ROOT } from '../lib/constants.js';
4
3
  import { discoverSkills, discoverSharedRules } from '../lib/inventory.js';
5
- import { copyRecursive, ensureDir } from '../lib/fs-utils.js';
6
4
  import { detectIDE } from '../lib/detect-ide.js';
5
+ import { installSkillsProject } from '../lib/installers.js';
7
6
  import * as log from '../lib/logger.js';
8
7
 
9
8
  export function registerInit(program) {
@@ -23,65 +22,13 @@ function runInit(dir, opts) {
23
22
  const exclude = withScore ? [] : ['team-score'];
24
23
  const ides = detectIDE(dir, ide, { strict: true });
25
24
 
26
- const tag = dryRun ? '[dry-run] ' : '';
27
- let count = 0;
28
-
29
25
  log.heading('初始化 team-skills 到项目');
30
26
  log.info(`项目目录: ${dir}`);
31
27
  log.info(`目标 IDE: ${ides.join(', ')}`);
32
28
 
33
29
  const skills = discoverSkills(PACKAGE_ROOT, { exclude });
34
30
  const rules = discoverSharedRules();
35
-
36
- // Cursor: skills → .cursor/skills/
37
- if (ides.includes('cursor')) {
38
- const skillsDst = join(dir, '.cursor', 'skills');
39
- log.heading(`复制 Skills → ${skillsDst}`);
40
-
41
- if (!dryRun) ensureDir(skillsDst);
42
- for (const skill of skills) {
43
- const dest = join(skillsDst, skill.name);
44
- if (!dryRun) {
45
- if (existsSync(dest)) rmSync(dest, { recursive: true });
46
- copyRecursive(skill.path, dest);
47
- }
48
- log.success(`${tag}Skill: ${skill.name}`);
49
- count++;
50
- }
51
-
52
- const rulesDst = join(skillsDst, '_team-rules');
53
- if (!dryRun) ensureDir(rulesDst);
54
- for (const r of rules) {
55
- if (!dryRun) fsCopyFile(r.path, join(rulesDst, r.name));
56
- log.success(`${tag}Rule: ${r.name}`);
57
- count++;
58
- }
59
- }
60
-
61
- // Claude Code: skills → .claude/skills/ (same structure as Cursor)
62
- if (ides.includes('claude')) {
63
- const skillsDst = join(dir, '.claude', 'skills');
64
- log.heading(`复制 Skills → ${skillsDst}`);
65
-
66
- if (!dryRun) ensureDir(skillsDst);
67
- for (const skill of skills) {
68
- const dest = join(skillsDst, skill.name);
69
- if (!dryRun) {
70
- if (existsSync(dest)) rmSync(dest, { recursive: true });
71
- copyRecursive(skill.path, dest);
72
- }
73
- log.success(`${tag}Skill: ${skill.name}`);
74
- count++;
75
- }
76
-
77
- const rulesDst = join(skillsDst, '_team-rules');
78
- if (!dryRun) ensureDir(rulesDst);
79
- for (const r of rules) {
80
- if (!dryRun) fsCopyFile(r.path, join(rulesDst, r.name));
81
- log.success(`${tag}Rule: ${r.name}`);
82
- count++;
83
- }
84
- }
31
+ const count = installSkillsProject(dir, ides, skills, rules, { dryRun });
85
32
 
86
33
  log.done(`初始化完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
87
34
  }
@@ -1,9 +1,6 @@
1
1
  import { join } from 'node:path';
2
2
  import { existsSync, readlinkSync } from 'node:fs';
3
- import {
4
- DEFAULT_SKILLS_TARGET,
5
- DEFAULT_CLAUDE_SKILLS_TARGET,
6
- } from '../lib/constants.js';
3
+ import { DEFAULT_SKILLS_TARGET, resolveTargets } from '../lib/constants.js';
7
4
  import { discoverSkills, discoverSharedRules } from '../lib/inventory.js';
8
5
  import { isSymlink } from '../lib/fs-utils.js';
9
6
  import * as log from '../lib/logger.js';
@@ -19,48 +16,29 @@ export function registerList(program) {
19
16
 
20
17
  function runList(opts) {
21
18
  const { target, json } = opts;
22
- const results = { skills: [], rules: [], skillCommands: [] };
19
+ const results = { skills: [], rules: [] };
23
20
 
24
- // Check symlink-based install
25
21
  const skills = discoverSkills();
26
- for (const skill of skills) {
27
- const dest = join(target, skill.name);
28
- results.skills.push({
29
- name: skill.name,
30
- status: getStatus(dest, skill.path),
31
- path: dest,
32
- });
33
- }
34
-
35
- // Shared rules
36
- for (const rule of discoverSharedRules()) {
37
- const dest = join(target, '_team-rules', rule.name);
38
- results.rules.push({
39
- name: rule.name,
40
- status: getStatus(dest, rule.path),
41
- path: dest,
42
- });
43
- }
44
-
45
- // Claude Code Skills
46
- const claudeSkillsTarget = DEFAULT_CLAUDE_SKILLS_TARGET;
47
- for (const skill of skills) {
48
- const dest = join(claudeSkillsTarget, skill.name);
49
- results.skillCommands.push({
50
- name: skill.name,
51
- status: getStatus(dest, skill.path),
52
- path: dest,
53
- });
54
- }
22
+ const rules = discoverSharedRules();
23
+ const targets = resolveTargets(target);
55
24
 
56
- // Claude Code shared rules
57
- for (const rule of discoverSharedRules()) {
58
- const claudeRuleDest = join(claudeSkillsTarget, '_team-rules', rule.name);
59
- results.rules.push({
60
- name: `Claude/${rule.name}`,
61
- status: getStatus(claudeRuleDest, rule.path),
62
- path: claudeRuleDest,
63
- });
25
+ for (const t of targets) {
26
+ for (const skill of skills) {
27
+ const dest = join(t.dir, skill.name);
28
+ results.skills.push({
29
+ name: `${t.label}/${skill.name}`,
30
+ status: getStatus(dest, skill.path),
31
+ path: dest,
32
+ });
33
+ }
34
+ for (const rule of rules) {
35
+ const dest = join(t.dir, '_team-rules', rule.name);
36
+ results.rules.push({
37
+ name: `${t.label}/${rule.name}`,
38
+ status: getStatus(dest, rule.path),
39
+ path: dest,
40
+ });
41
+ }
64
42
  }
65
43
 
66
44
  if (json) {
@@ -68,17 +46,14 @@ function runList(opts) {
68
46
  return;
69
47
  }
70
48
 
71
- // Pretty print
72
- log.heading('Agent Skills');
73
- printTable(results.skills);
49
+ for (const t of targets) {
50
+ log.heading(`${t.label} Skills`);
51
+ printTable(results.skills.filter(s => s.name.startsWith(`${t.label}/`)));
52
+ }
74
53
 
75
54
  log.heading('共享规则');
76
55
  printTable(results.rules);
77
56
 
78
- log.heading('Claude Code Skills');
79
- printTable(results.skillCommands);
80
-
81
- // Summary
82
57
  const installed = results.skills.filter(s => s.status === 'ok' || s.status === 'file').length;
83
58
  const total = results.skills.length;
84
59
  console.log(`\nSkills: ${installed}/${total} 已安装`);
@@ -1,9 +1,6 @@
1
- import { join } from 'node:path';
2
- import {
3
- PACKAGE_ROOT, DEFAULT_SKILLS_TARGET, DEFAULT_CLAUDE_SKILLS_TARGET,
4
- } from '../lib/constants.js';
1
+ import { PACKAGE_ROOT, DEFAULT_SKILLS_TARGET, resolveTargets } from '../lib/constants.js';
5
2
  import { discoverSkills, discoverSharedRules } from '../lib/inventory.js';
6
- import { createSymlinkSafe, ensureDir, isSymlink } from '../lib/fs-utils.js';
3
+ import { installSkillsGlobal, verifyGlobalSymlinks } from '../lib/installers.js';
7
4
  import * as log from '../lib/logger.js';
8
5
 
9
6
  export function registerSetup(program) {
@@ -17,72 +14,14 @@ export function registerSetup(program) {
17
14
 
18
15
  function runSetup(target, opts) {
19
16
  const { dryRun } = opts;
20
- const tag = dryRun ? '[dry-run] ' : '';
21
- let count = 0;
22
-
23
- // Skills → ~/.agents/skills/ (Cursor auto-discovers from here)
24
- log.heading('安装 Skills → Cursor');
25
17
  const skills = discoverSkills(PACKAGE_ROOT);
26
- for (const skill of skills) {
27
- const dest = join(target, skill.name);
28
- const result = createSymlinkSafe(skill.path, dest, { force: true, dryRun });
29
- logResult(`${tag}Skill: ${skill.name}`, result);
30
- if (result === 'created' || result === 'dry-run') count++;
31
- }
32
-
33
- // Shared rules → ~/.agents/skills/_team-rules/
34
- log.heading('安装共享规则');
35
18
  const rules = discoverSharedRules();
36
- const rulesTarget = join(target, '_team-rules');
37
- if (!dryRun) ensureDir(rulesTarget);
38
- for (const rule of rules) {
39
- const dest = join(rulesTarget, rule.name);
40
- const result = createSymlinkSafe(rule.path, dest, { force: true, dryRun });
41
- logResult(`${tag}Rule: ${rule.name}`, result);
42
- if (result === 'created' || result === 'dry-run') count++;
43
- }
44
-
45
- // Skills → ~/.claude/skills/ (Claude Code auto-discovers from here)
46
- log.heading('安装 Skills → Claude Code');
47
- const claudeSkillsTarget = DEFAULT_CLAUDE_SKILLS_TARGET;
48
- if (!dryRun) ensureDir(claudeSkillsTarget);
49
- for (const skill of skills) {
50
- const dest = join(claudeSkillsTarget, skill.name);
51
- const result = createSymlinkSafe(skill.path, dest, { force: true, dryRun });
52
- logResult(`${tag}Skill: ${skill.name}`, result);
53
- if (result === 'created' || result === 'dry-run') count++;
54
- }
19
+ const targets = resolveTargets(target);
55
20
 
56
- // Shared rules ~/.claude/skills/_team-rules/
57
- log.heading('安装 Claude Code 共享规则');
58
- const claudeRulesTarget = join(claudeSkillsTarget, '_team-rules');
59
- if (!dryRun) ensureDir(claudeRulesTarget);
60
- for (const rule of rules) {
61
- const dest = join(claudeRulesTarget, rule.name);
62
- const result = createSymlinkSafe(rule.path, dest, { force: true, dryRun });
63
- logResult(`${tag}Rule: ${rule.name}`, result);
64
- if (result === 'created' || result === 'dry-run') count++;
65
- }
21
+ const count = installSkillsGlobal(targets, skills, rules, { dryRun });
66
22
 
67
23
  if (!dryRun) {
68
- log.heading('验证安装');
69
- let errors = 0;
70
- const verify = (label, dest) => {
71
- if (isSymlink(dest)) {
72
- log.success(label);
73
- } else {
74
- log.error(`${label} 未正确安装`);
75
- errors++;
76
- }
77
- };
78
- for (const skill of skills) {
79
- verify(`Cursor Skill: ${skill.name}`, join(target, skill.name));
80
- verify(`Claude Skill: ${skill.name}`, join(claudeSkillsTarget, skill.name));
81
- }
82
- for (const rule of rules) {
83
- verify(`Rule: ${rule.name}`, join(rulesTarget, rule.name));
84
- verify(`Claude Rule: ${rule.name}`, join(claudeRulesTarget, rule.name));
85
- }
24
+ const errors = verifyGlobalSymlinks(targets, skills, rules);
86
25
  if (errors > 0) {
87
26
  log.error(`有 ${errors} 个组件安装异常,请检查。`);
88
27
  process.exit(1);
@@ -94,17 +33,3 @@ function runSetup(target, opts) {
94
33
  console.log('\n后续可通过 team-skills setup 重新安装,或 team-skills uninstall 卸载。');
95
34
  }
96
35
  }
97
-
98
- function logResult(label, result) {
99
- switch (result) {
100
- case 'created':
101
- case 'dry-run':
102
- log.success(label);
103
- break;
104
- case 'exists':
105
- log.skip(`${label}(已存在,跳过)`);
106
- break;
107
- default:
108
- log.info(label);
109
- }
110
- }
@@ -1,8 +1,5 @@
1
1
  import { join } from 'node:path';
2
- import {
3
- PACKAGE_ROOT, DEFAULT_SKILLS_TARGET,
4
- DEFAULT_CLAUDE_SKILLS_TARGET,
5
- } from '../lib/constants.js';
2
+ import { DEFAULT_SKILLS_TARGET, resolveTargets } from '../lib/constants.js';
6
3
  import { discoverSkills, discoverSharedRules } from '../lib/inventory.js';
7
4
  import { removeSymlinkSafe, rmdirIfEmpty } from '../lib/fs-utils.js';
8
5
  import * as log from '../lib/logger.js';
@@ -35,34 +32,23 @@ function runUninstall(target, opts) {
35
32
  const { dryRun } = opts;
36
33
  let removed = 0;
37
34
 
38
- // Cursor Skills → ~/.agents/skills/
39
- log.heading('移除 Cursor Skills');
40
35
  const skills = discoverSkills();
41
- for (const skill of skills) {
42
- if (remove(join(target, skill.name), skill.path, `Skill: ${skill.name}`, dryRun)) removed++;
36
+ const rules = discoverSharedRules();
37
+ const targets = resolveTargets(target);
38
+
39
+ for (const t of targets) {
40
+ log.heading(`移除 ${t.label} Skills`);
41
+ for (const skill of skills) {
42
+ if (remove(join(t.dir, skill.name), skill.path, `${t.label} Skill: ${skill.name}`, dryRun)) removed++;
43
+ }
44
+
45
+ log.heading(`移除 ${t.label} 共享规则`);
46
+ for (const rule of rules) {
47
+ if (remove(join(t.dir, '_team-rules', rule.name), rule.path, `${t.label} Rule: ${rule.name}`, dryRun)) removed++;
48
+ }
49
+ if (!dryRun) rmdirIfEmpty(join(t.dir, '_team-rules'));
43
50
  }
44
51
 
45
- // Claude Code Skills → ~/.claude/skills/
46
- log.heading('移除 Claude Code Skills');
47
- const claudeSkillsTarget = DEFAULT_CLAUDE_SKILLS_TARGET;
48
- for (const skill of skills) {
49
- if (remove(join(claudeSkillsTarget, skill.name), skill.path, `Claude Skill: ${skill.name}`, dryRun)) removed++;
50
- }
51
-
52
- // Claude Code shared rules → ~/.claude/skills/_team-rules/
53
- log.heading('移除 Claude Code 共享规则');
54
- for (const rule of discoverSharedRules()) {
55
- if (remove(join(claudeSkillsTarget, '_team-rules', rule.name), rule.path, `Claude Rule: ${rule.name}`, dryRun)) removed++;
56
- }
57
- if (!dryRun) rmdirIfEmpty(join(claudeSkillsTarget, '_team-rules'));
58
-
59
- // Shared rules
60
- log.heading('移除共享规则');
61
- for (const rule of discoverSharedRules()) {
62
- if (remove(join(target, '_team-rules', rule.name), rule.path, `Rule: ${rule.name}`, dryRun)) removed++;
63
- }
64
- if (!dryRun) rmdirIfEmpty(join(target, '_team-rules'));
65
-
66
52
  log.done(`卸载完成${dryRun ? ' (dry-run)' : ''},共移除 ${removed} 个软链接。`);
67
53
  if (!dryRun) console.log('本仓库源文件未受影响。');
68
54
  }
@@ -1,12 +1,13 @@
1
1
  import { join, resolve } from 'node:path';
2
- import { existsSync, copyFileSync as fsCopyFile, rmSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { readFileSync } from 'node:fs';
3
3
  import { execSync } from 'node:child_process';
4
- import {
5
- PACKAGE_ROOT, DEFAULT_SKILLS_TARGET, DEFAULT_CLAUDE_SKILLS_TARGET,
6
- } from '../lib/constants.js';
4
+ import { PACKAGE_ROOT, GLOBAL_TARGETS, PROJECT_IDE_DIRS } from '../lib/constants.js';
7
5
  import { discoverSkills, discoverSharedRules } from '../lib/inventory.js';
8
- import { copyRecursive, ensureDir, createSymlinkSafe, isSymlink } from '../lib/fs-utils.js';
9
6
  import { detectIDE } from '../lib/detect-ide.js';
7
+ import {
8
+ installSkillsGlobal, verifyGlobalSymlinks,
9
+ installSkillsProject, cleanStaleSkills, isGlobalTarget,
10
+ } from '../lib/installers.js';
10
11
  import * as log from '../lib/logger.js';
11
12
 
12
13
  export function registerUpdate(program) {
@@ -47,111 +48,37 @@ function upgradeSelf(dryRun) {
47
48
  }
48
49
  }
49
50
 
50
- function cleanStaleSkills(targetDir, currentNames, dryRun, exclude = []) {
51
- if (!existsSync(targetDir)) return;
52
- const existing = readdirSync(targetDir).filter(
53
- name => !name.startsWith('_') && name !== 'CLAUDE.md' && name.startsWith('team-'),
54
- );
55
- for (const name of existing) {
56
- if (!currentNames.has(name) && !exclude.includes(name)) {
57
- const tag = dryRun ? '[dry-run] ' : '';
58
- if (!dryRun) rmSync(join(targetDir, name), { recursive: true });
59
- log.warn(`${tag}移除旧 Skill: ${name}`);
60
- }
61
- }
62
- }
63
-
64
51
  function runUpdate(dir, opts) {
65
52
  dir = resolve(dir);
66
53
  const { ide, withScore, skipSelf, dryRun } = opts;
67
54
 
68
55
  if (!skipSelf) upgradeSelf(dryRun);
69
56
 
70
- const exclude = withScore ? [] : ['team-score'];
71
- const tag = dryRun ? '[dry-run] ' : '';
72
- let count = 0;
73
-
74
- // ── Global: always include team-score ──
75
57
  const allSkills = discoverSkills(PACKAGE_ROOT);
76
58
  const rules = discoverSharedRules();
77
59
  const allSkillNames = new Set(allSkills.map(s => s.name));
78
60
 
79
61
  log.heading('更新全局 Skills');
80
-
81
- // Cursor global: ~/.agents/skills/
82
- const cursorGlobal = DEFAULT_SKILLS_TARGET;
83
- log.heading(`更新 Skills → ${cursorGlobal}`);
84
- cleanStaleSkills(cursorGlobal, allSkillNames, dryRun);
85
- if (!dryRun) ensureDir(cursorGlobal);
86
- for (const skill of allSkills) {
87
- const dest = join(cursorGlobal, skill.name);
88
- const result = createSymlinkSafe(skill.path, dest, { force: true, dryRun });
89
- logGlobalResult(`${tag}Skill: ${skill.name}`, result);
90
- if (result === 'created' || result === 'dry-run') count++;
91
- }
92
- const cursorRulesDst = join(cursorGlobal, '_team-rules');
93
- if (!dryRun) ensureDir(cursorRulesDst);
94
- for (const r of rules) {
95
- const dest = join(cursorRulesDst, r.name);
96
- const result = createSymlinkSafe(r.path, dest, { force: true, dryRun });
97
- logGlobalResult(`${tag}Rule: ${r.name}`, result);
98
- if (result === 'created' || result === 'dry-run') count++;
62
+ for (const t of GLOBAL_TARGETS) {
63
+ cleanStaleSkills(t.dir, allSkillNames, { dryRun });
99
64
  }
65
+ let count = installSkillsGlobal(GLOBAL_TARGETS, allSkills, rules, { dryRun, verb: '更新' });
100
66
 
101
- // Claude Code global: ~/.claude/skills/
102
- const claudeGlobal = DEFAULT_CLAUDE_SKILLS_TARGET;
103
- log.heading(`更新 Skills → ${claudeGlobal}`);
104
- cleanStaleSkills(claudeGlobal, allSkillNames, dryRun);
105
- if (!dryRun) ensureDir(claudeGlobal);
106
- for (const skill of allSkills) {
107
- const dest = join(claudeGlobal, skill.name);
108
- const result = createSymlinkSafe(skill.path, dest, { force: true, dryRun });
109
- logGlobalResult(`${tag}Skill: ${skill.name}`, result);
110
- if (result === 'created' || result === 'dry-run') count++;
111
- }
112
- const claudeRulesDst = join(claudeGlobal, '_team-rules');
113
- if (!dryRun) ensureDir(claudeRulesDst);
114
- for (const r of rules) {
115
- const dest = join(claudeRulesDst, r.name);
116
- const result = createSymlinkSafe(r.path, dest, { force: true, dryRun });
117
- logGlobalResult(`${tag}Rule: ${r.name}`, result);
118
- if (result === 'created' || result === 'dry-run') count++;
119
- }
120
-
121
- // Verify global symlinks
122
67
  if (!dryRun) {
123
- log.heading('验证全局安装');
124
- let errors = 0;
125
- const verify = (label, dest) => {
126
- if (isSymlink(dest)) {
127
- log.success(label);
128
- } else {
129
- log.error(`${label} 未正确安装`);
130
- errors++;
131
- }
132
- };
133
- for (const skill of allSkills) {
134
- verify(`Cursor Skill: ${skill.name}`, join(cursorGlobal, skill.name));
135
- verify(`Claude Skill: ${skill.name}`, join(claudeGlobal, skill.name));
136
- }
137
- for (const r of rules) {
138
- verify(`Rule: ${r.name}`, join(cursorRulesDst, r.name));
139
- verify(`Claude Rule: ${r.name}`, join(claudeRulesDst, r.name));
140
- }
68
+ const errors = verifyGlobalSymlinks(GLOBAL_TARGETS, allSkills, rules);
141
69
  if (errors > 0) {
142
70
  log.error(`有 ${errors} 个全局组件安装异常,请检查。`);
143
71
  }
144
72
  }
145
73
 
146
- // ── Project: respect --with-score ──
147
74
  const ides = detectIDE(dir, ide);
148
-
149
75
  if (ides.length === 0) {
150
76
  log.info('当前项目未检测到 IDE 配置(.claude/ 或 .cursor/),跳过项目更新。');
151
77
  log.done(`更新完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
152
78
  return;
153
79
  }
154
80
 
81
+ const exclude = withScore ? [] : ['team-score'];
155
82
  const projectSkills = discoverSkills(PACKAGE_ROOT, { exclude });
156
83
  const projectSkillNames = new Set(projectSkills.map(s => s.name));
157
84
 
@@ -159,71 +86,14 @@ function runUpdate(dir, opts) {
159
86
  log.info(`项目目录: ${dir}`);
160
87
  log.info(`目标 IDE: ${ides.join(', ')}`);
161
88
 
162
- if (ides.includes('cursor')) {
163
- const skillsDst = join(dir, '.cursor', 'skills');
164
- log.heading(`更新 Skills → ${skillsDst}`);
165
-
166
- cleanStaleSkills(skillsDst, projectSkillNames, dryRun, exclude);
167
-
168
- if (!dryRun) ensureDir(skillsDst);
169
- for (const skill of projectSkills) {
170
- const dest = join(skillsDst, skill.name);
171
- if (!dryRun) {
172
- if (existsSync(dest)) rmSync(dest, { recursive: true });
173
- copyRecursive(skill.path, dest);
174
- }
175
- log.success(`${tag}Skill: ${skill.name}`);
176
- count++;
177
- }
178
-
179
- const rulesDst = join(skillsDst, '_team-rules');
180
- if (!dryRun) ensureDir(rulesDst);
181
- for (const r of rules) {
182
- if (!dryRun) fsCopyFile(r.path, join(rulesDst, r.name));
183
- log.success(`${tag}Rule: ${r.name}`);
184
- count++;
89
+ for (const ideName of ides) {
90
+ const skillsDst = join(dir, PROJECT_IDE_DIRS[ideName], 'skills');
91
+ if (!isGlobalTarget(skillsDst)) {
92
+ cleanStaleSkills(skillsDst, projectSkillNames, { dryRun, exclude });
185
93
  }
186
94
  }
187
95
 
188
- if (ides.includes('claude')) {
189
- const skillsDst = join(dir, '.claude', 'skills');
190
- log.heading(`更新 Skills → ${skillsDst}`);
191
-
192
- cleanStaleSkills(skillsDst, projectSkillNames, dryRun, exclude);
193
-
194
- if (!dryRun) ensureDir(skillsDst);
195
- for (const skill of projectSkills) {
196
- const dest = join(skillsDst, skill.name);
197
- if (!dryRun) {
198
- if (existsSync(dest)) rmSync(dest, { recursive: true });
199
- copyRecursive(skill.path, dest);
200
- }
201
- log.success(`${tag}Skill: ${skill.name}`);
202
- count++;
203
- }
204
-
205
- const rulesDst = join(skillsDst, '_team-rules');
206
- if (!dryRun) ensureDir(rulesDst);
207
- for (const r of rules) {
208
- if (!dryRun) fsCopyFile(r.path, join(rulesDst, r.name));
209
- log.success(`${tag}Rule: ${r.name}`);
210
- count++;
211
- }
212
- }
96
+ count += installSkillsProject(dir, ides, projectSkills, rules, { dryRun, verb: '更新' });
213
97
 
214
98
  log.done(`更新完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
215
99
  }
216
-
217
- function logGlobalResult(label, result) {
218
- switch (result) {
219
- case 'created':
220
- case 'dry-run':
221
- log.success(label);
222
- break;
223
- case 'exists':
224
- log.skip(`${label}(已存在,跳过)`);
225
- break;
226
- default:
227
- log.info(label);
228
- }
229
- }
@@ -7,5 +7,19 @@ const __dirname = dirname(__filename);
7
7
 
8
8
  export const PACKAGE_ROOT = join(__dirname, '..', '..');
9
9
  export const DEFAULT_SKILLS_TARGET = join(homedir(), '.agents', 'skills');
10
+ export const DEFAULT_CURSOR_SKILLS_TARGET = join(homedir(), '.cursor', 'skills');
10
11
  export const DEFAULT_CLAUDE_SKILLS_TARGET = join(homedir(), '.claude', 'skills');
11
12
  export const SKILLS_DIR = 'skills';
13
+
14
+ export const GLOBAL_TARGETS = [
15
+ { label: 'Agents', dir: DEFAULT_SKILLS_TARGET },
16
+ { label: 'Cursor', dir: DEFAULT_CURSOR_SKILLS_TARGET },
17
+ { label: 'Claude Code', dir: DEFAULT_CLAUDE_SKILLS_TARGET },
18
+ ];
19
+
20
+ export const PROJECT_IDE_DIRS = { cursor: '.cursor', claude: '.claude' };
21
+
22
+ export function resolveTargets(customFirst) {
23
+ if (!customFirst || customFirst === DEFAULT_SKILLS_TARGET) return GLOBAL_TARGETS;
24
+ return GLOBAL_TARGETS.map((t, i) => (i === 0 ? { ...t, dir: customFirst } : t));
25
+ }
@@ -0,0 +1,132 @@
1
+ import { join } from 'node:path';
2
+ import { existsSync, readdirSync, rmSync, copyFileSync } from 'node:fs';
3
+ import { createSymlinkSafe, ensureDir, isSymlink, copyRecursive } from './fs-utils.js';
4
+ import { GLOBAL_TARGETS, PROJECT_IDE_DIRS } from './constants.js';
5
+ import * as log from './logger.js';
6
+
7
+ const globalDirSet = new Set(GLOBAL_TARGETS.map(t => t.dir));
8
+
9
+ export function isGlobalTarget(dir) {
10
+ return globalDirSet.has(dir);
11
+ }
12
+
13
+ export function logInstallResult(label, result) {
14
+ switch (result) {
15
+ case 'created':
16
+ case 'dry-run':
17
+ log.success(label);
18
+ break;
19
+ case 'exists':
20
+ log.skip(`${label}(已存在,跳过)`);
21
+ break;
22
+ default:
23
+ log.info(label);
24
+ }
25
+ }
26
+
27
+ export function installSkillsGlobal(targets, skills, rules, { dryRun, verb = '安装' }) {
28
+ const tag = dryRun ? '[dry-run] ' : '';
29
+ let count = 0;
30
+
31
+ for (const t of targets) {
32
+ log.heading(`${verb} → ${t.label}`);
33
+ if (!dryRun) ensureDir(t.dir);
34
+
35
+ for (const skill of skills) {
36
+ const dest = join(t.dir, skill.name);
37
+ const result = createSymlinkSafe(skill.path, dest, { force: true, dryRun });
38
+ logInstallResult(`${tag}Skill: ${skill.name}`, result);
39
+ if (result === 'created' || result === 'dry-run') count++;
40
+ }
41
+
42
+ const rulesDir = join(t.dir, '_team-rules');
43
+ if (!dryRun) ensureDir(rulesDir);
44
+ for (const rule of rules) {
45
+ const dest = join(rulesDir, rule.name);
46
+ const result = createSymlinkSafe(rule.path, dest, { force: true, dryRun });
47
+ logInstallResult(`${tag}Rule: ${rule.name}`, result);
48
+ if (result === 'created' || result === 'dry-run') count++;
49
+ }
50
+ }
51
+
52
+ return count;
53
+ }
54
+
55
+ export function verifyGlobalSymlinks(targets, skills, rules) {
56
+ log.heading('验证安装');
57
+ let errors = 0;
58
+
59
+ const verify = (label, dest) => {
60
+ if (isSymlink(dest)) {
61
+ log.success(label);
62
+ } else {
63
+ log.error(`${label} 未正确安装`);
64
+ errors++;
65
+ }
66
+ };
67
+
68
+ for (const t of targets) {
69
+ for (const skill of skills) {
70
+ verify(`${t.label} Skill: ${skill.name}`, join(t.dir, skill.name));
71
+ }
72
+ for (const rule of rules) {
73
+ verify(`${t.label} Rule: ${rule.name}`, join(t.dir, '_team-rules', rule.name));
74
+ }
75
+ }
76
+
77
+ return errors;
78
+ }
79
+
80
+ export function installSkillsProject(projectDir, ides, skills, rules, { dryRun, verb = '复制' }) {
81
+ const tag = dryRun ? '[dry-run] ' : '';
82
+ let count = 0;
83
+
84
+ for (const ideName of ides) {
85
+ const ideSubdir = PROJECT_IDE_DIRS[ideName];
86
+ if (!ideSubdir) continue;
87
+
88
+ const skillsDst = join(projectDir, ideSubdir, 'skills');
89
+
90
+ if (globalDirSet.has(skillsDst)) {
91
+ log.skip(`${skillsDst} 与全局路径重叠,保留 symlinks,跳过复制`);
92
+ continue;
93
+ }
94
+
95
+ log.heading(`${verb} → ${skillsDst}`);
96
+
97
+ if (!dryRun) ensureDir(skillsDst);
98
+ for (const skill of skills) {
99
+ const dest = join(skillsDst, skill.name);
100
+ if (!dryRun) {
101
+ if (existsSync(dest)) rmSync(dest, { recursive: true });
102
+ copyRecursive(skill.path, dest);
103
+ }
104
+ log.success(`${tag}Skill: ${skill.name}`);
105
+ count++;
106
+ }
107
+
108
+ const rulesDst = join(skillsDst, '_team-rules');
109
+ if (!dryRun) ensureDir(rulesDst);
110
+ for (const r of rules) {
111
+ if (!dryRun) copyFileSync(r.path, join(rulesDst, r.name));
112
+ log.success(`${tag}Rule: ${r.name}`);
113
+ count++;
114
+ }
115
+ }
116
+
117
+ return count;
118
+ }
119
+
120
+ export function cleanStaleSkills(targetDir, currentNames, { dryRun, exclude = [] }) {
121
+ if (!existsSync(targetDir)) return;
122
+ const existing = readdirSync(targetDir).filter(
123
+ name => !name.startsWith('_') && name !== 'CLAUDE.md' && name.startsWith('team-'),
124
+ );
125
+ for (const name of existing) {
126
+ if (!currentNames.has(name) && !exclude.includes(name)) {
127
+ const tag = dryRun ? '[dry-run] ' : '';
128
+ if (!dryRun) rmSync(join(targetDir, name), { recursive: true });
129
+ log.warn(`${tag}移除旧 Skill: ${name}`);
130
+ }
131
+ }
132
+ }