team-skills 1.3.2 → 1.3.3

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,21 @@
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.3.3] - 2026-06-26
11
+
12
+ ### 变更
13
+
14
+ - `update` 命令新增全局安装阶段:同步更新 `~/.agents/skills/` 和 `~/.claude/skills/`(含 team-score)
15
+ - `setup` 全局安装始终包含 team-score,移除 `--with-score` 参数
16
+ - `setup`、`init`、`update` 统一为默认覆盖模式,移除 `--force` 参数
17
+ - `init` 覆盖行为与 `update` 对齐:先清理再复制(`rmSync` + `copyRecursive`)
18
+
19
+ ### 修复
20
+
21
+ - `update` 不再误删项目中已安装的 team-score(`cleanStaleSkills` 跳过被排除的 skill)
22
+ - `setup` 验证阶段补全 Claude Code 共享规则检查(之前仅验证 Cursor 路径)
23
+ - `update` 全局安装新增 symlink 验证阶段
24
+
10
25
  ## [1.3.2] - 2026-06-26
11
26
 
12
27
  ### 新增
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "team-skills",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
4
4
  "description": "AI Agent Skills framework — Spec-Driven development with directed-graph rollback and quality gates",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,5 +1,5 @@
1
1
  import { join, resolve } from 'node:path';
2
- import { copyFileSync as fsCopyFile } from 'node:fs';
2
+ import { copyFileSync as fsCopyFile, existsSync, rmSync } from 'node:fs';
3
3
  import { PACKAGE_ROOT } from '../lib/constants.js';
4
4
  import { discoverSkills, discoverSharedRules } from '../lib/inventory.js';
5
5
  import { copyRecursive, ensureDir } from '../lib/fs-utils.js';
@@ -41,7 +41,10 @@ function runInit(dir, opts) {
41
41
  if (!dryRun) ensureDir(skillsDst);
42
42
  for (const skill of skills) {
43
43
  const dest = join(skillsDst, skill.name);
44
- if (!dryRun) copyRecursive(skill.path, dest);
44
+ if (!dryRun) {
45
+ if (existsSync(dest)) rmSync(dest, { recursive: true });
46
+ copyRecursive(skill.path, dest);
47
+ }
45
48
  log.success(`${tag}Skill: ${skill.name}`);
46
49
  count++;
47
50
  }
@@ -63,7 +66,10 @@ function runInit(dir, opts) {
63
66
  if (!dryRun) ensureDir(skillsDst);
64
67
  for (const skill of skills) {
65
68
  const dest = join(skillsDst, skill.name);
66
- if (!dryRun) copyRecursive(skill.path, dest);
69
+ if (!dryRun) {
70
+ if (existsSync(dest)) rmSync(dest, { recursive: true });
71
+ copyRecursive(skill.path, dest);
72
+ }
67
73
  log.success(`${tag}Skill: ${skill.name}`);
68
74
  count++;
69
75
  }
@@ -11,25 +11,22 @@ export function registerSetup(program) {
11
11
  .command('setup')
12
12
  .description('Install skills via symlinks to global directories')
13
13
  .argument('[target]', 'Target skills directory', DEFAULT_SKILLS_TARGET)
14
- .option('--with-score', 'Include team-score skill (hidden by default)', false)
15
- .option('--force', 'Overwrite existing symlinks', false)
16
14
  .option('--dry-run', 'Show what would be done without doing it', false)
17
15
  .action(runSetup);
18
16
  }
19
17
 
20
18
  function runSetup(target, opts) {
21
- const { withScore, force, dryRun } = opts;
19
+ const { dryRun } = opts;
22
20
  const tag = dryRun ? '[dry-run] ' : '';
23
- const exclude = withScore ? [] : ['team-score'];
24
21
  let count = 0;
25
22
 
26
23
  // Skills → ~/.agents/skills/ (Cursor auto-discovers from here)
27
24
  log.heading('安装 Skills → Cursor');
28
- const skills = discoverSkills(PACKAGE_ROOT, { exclude });
25
+ const skills = discoverSkills(PACKAGE_ROOT);
29
26
  for (const skill of skills) {
30
27
  const dest = join(target, skill.name);
31
- const result = createSymlinkSafe(skill.path, dest, { force, dryRun });
32
- logResult(`${tag}Skill: ${skill.name}`, result, dest);
28
+ const result = createSymlinkSafe(skill.path, dest, { force: true, dryRun });
29
+ logResult(`${tag}Skill: ${skill.name}`, result);
33
30
  if (result === 'created' || result === 'dry-run') count++;
34
31
  }
35
32
 
@@ -40,8 +37,8 @@ function runSetup(target, opts) {
40
37
  if (!dryRun) ensureDir(rulesTarget);
41
38
  for (const rule of rules) {
42
39
  const dest = join(rulesTarget, rule.name);
43
- const result = createSymlinkSafe(rule.path, dest, { force, dryRun });
44
- logResult(`${tag}Rule: ${rule.name}`, result, dest);
40
+ const result = createSymlinkSafe(rule.path, dest, { force: true, dryRun });
41
+ logResult(`${tag}Rule: ${rule.name}`, result);
45
42
  if (result === 'created' || result === 'dry-run') count++;
46
43
  }
47
44
 
@@ -51,8 +48,8 @@ function runSetup(target, opts) {
51
48
  if (!dryRun) ensureDir(claudeSkillsTarget);
52
49
  for (const skill of skills) {
53
50
  const dest = join(claudeSkillsTarget, skill.name);
54
- const result = createSymlinkSafe(skill.path, dest, { force, dryRun });
55
- logResult(`${tag}Skill: ${skill.name}`, result, dest);
51
+ const result = createSymlinkSafe(skill.path, dest, { force: true, dryRun });
52
+ logResult(`${tag}Skill: ${skill.name}`, result);
56
53
  if (result === 'created' || result === 'dry-run') count++;
57
54
  }
58
55
 
@@ -62,8 +59,8 @@ function runSetup(target, opts) {
62
59
  if (!dryRun) ensureDir(claudeRulesTarget);
63
60
  for (const rule of rules) {
64
61
  const dest = join(claudeRulesTarget, rule.name);
65
- const result = createSymlinkSafe(rule.path, dest, { force, dryRun });
66
- logResult(`${tag}Rule: ${rule.name}`, result, dest);
62
+ const result = createSymlinkSafe(rule.path, dest, { force: true, dryRun });
63
+ logResult(`${tag}Rule: ${rule.name}`, result);
67
64
  if (result === 'created' || result === 'dry-run') count++;
68
65
  }
69
66
 
@@ -82,7 +79,10 @@ function runSetup(target, opts) {
82
79
  verify(`Cursor Skill: ${skill.name}`, join(target, skill.name));
83
80
  verify(`Claude Skill: ${skill.name}`, join(claudeSkillsTarget, skill.name));
84
81
  }
85
- for (const rule of rules) verify(`Rule: ${rule.name}`, join(rulesTarget, rule.name));
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
+ }
86
86
  if (errors > 0) {
87
87
  log.error(`有 ${errors} 个组件安装异常,请检查。`);
88
88
  process.exit(1);
@@ -95,7 +95,7 @@ function runSetup(target, opts) {
95
95
  }
96
96
  }
97
97
 
98
- function logResult(label, result, dest) {
98
+ function logResult(label, result) {
99
99
  switch (result) {
100
100
  case 'created':
101
101
  case 'dry-run':
@@ -104,9 +104,6 @@ function logResult(label, result, dest) {
104
104
  case 'exists':
105
105
  log.skip(`${label}(已存在,跳过)`);
106
106
  break;
107
- case 'conflict':
108
- log.warn(`${label}(已存在非 symlink,使用 --force 覆盖)`);
109
- break;
110
107
  default:
111
108
  log.info(label);
112
109
  }
@@ -1,20 +1,22 @@
1
1
  import { join, resolve } from 'node:path';
2
2
  import { existsSync, copyFileSync as fsCopyFile, rmSync, readdirSync, readFileSync } from 'node:fs';
3
3
  import { execSync } from 'node:child_process';
4
- import { PACKAGE_ROOT } from '../lib/constants.js';
4
+ import {
5
+ PACKAGE_ROOT, DEFAULT_SKILLS_TARGET, DEFAULT_CLAUDE_SKILLS_TARGET,
6
+ } from '../lib/constants.js';
5
7
  import { discoverSkills, discoverSharedRules } from '../lib/inventory.js';
6
- import { copyRecursive, ensureDir } from '../lib/fs-utils.js';
8
+ import { copyRecursive, ensureDir, createSymlinkSafe, isSymlink } from '../lib/fs-utils.js';
7
9
  import { detectIDE } from '../lib/detect-ide.js';
8
10
  import * as log from '../lib/logger.js';
9
11
 
10
12
  export function registerUpdate(program) {
11
13
  program
12
14
  .command('update')
13
- .description('Upgrade team-skills package and update project installations')
15
+ .description('Upgrade team-skills package and update global & project installations')
14
16
  .argument('[dir]', 'Project directory', '.')
15
17
  .option('--ide <type>', 'Force IDE type: claude, cursor, or both')
16
- .option('--with-score', 'Include team-score skill (hidden by default)', false)
17
- .option('--skip-self', 'Skip self-upgrade, only update project', false)
18
+ .option('--with-score', 'Include team-score skill in project (hidden by default)', false)
19
+ .option('--skip-self', 'Skip self-upgrade, only update installations', false)
18
20
  .option('--dry-run', 'Show what would change', false)
19
21
  .action(runUpdate);
20
22
  }
@@ -45,13 +47,13 @@ function upgradeSelf(dryRun) {
45
47
  }
46
48
  }
47
49
 
48
- function cleanStaleSkills(targetDir, currentNames, dryRun) {
50
+ function cleanStaleSkills(targetDir, currentNames, dryRun, exclude = []) {
49
51
  if (!existsSync(targetDir)) return;
50
52
  const existing = readdirSync(targetDir).filter(
51
53
  name => !name.startsWith('_') && name !== 'CLAUDE.md' && name.startsWith('team-'),
52
54
  );
53
55
  for (const name of existing) {
54
- if (!currentNames.has(name)) {
56
+ if (!currentNames.has(name) && !exclude.includes(name)) {
55
57
  const tag = dryRun ? '[dry-run] ' : '';
56
58
  if (!dryRun) rmSync(join(targetDir, name), { recursive: true });
57
59
  log.warn(`${tag}移除旧 Skill: ${name}`);
@@ -66,33 +68,105 @@ function runUpdate(dir, opts) {
66
68
  if (!skipSelf) upgradeSelf(dryRun);
67
69
 
68
70
  const exclude = withScore ? [] : ['team-score'];
71
+ const tag = dryRun ? '[dry-run] ' : '';
72
+ let count = 0;
73
+
74
+ // ── Global: always include team-score ──
75
+ const allSkills = discoverSkills(PACKAGE_ROOT);
76
+ const rules = discoverSharedRules();
77
+ const allSkillNames = new Set(allSkills.map(s => s.name));
78
+
79
+ 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++;
99
+ }
100
+
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
+ 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
+ }
141
+ if (errors > 0) {
142
+ log.error(`有 ${errors} 个全局组件安装异常,请检查。`);
143
+ }
144
+ }
145
+
146
+ // ── Project: respect --with-score ──
69
147
  const ides = detectIDE(dir, ide);
70
148
 
71
149
  if (ides.length === 0) {
72
150
  log.info('当前项目未检测到 IDE 配置(.claude/ 或 .cursor/),跳过项目更新。');
151
+ log.done(`更新完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
73
152
  return;
74
153
  }
75
154
 
76
- const tag = dryRun ? '[dry-run] ' : '';
77
- let count = 0;
155
+ const projectSkills = discoverSkills(PACKAGE_ROOT, { exclude });
156
+ const projectSkillNames = new Set(projectSkills.map(s => s.name));
78
157
 
79
158
  log.heading('更新项目中的 team-skills');
80
159
  log.info(`项目目录: ${dir}`);
81
160
  log.info(`目标 IDE: ${ides.join(', ')}`);
82
161
 
83
- const skills = discoverSkills(PACKAGE_ROOT, { exclude });
84
- const rules = discoverSharedRules();
85
- const currentSkillNames = new Set(skills.map(s => s.name));
86
-
87
- // Cursor: skills → .cursor/skills/
88
162
  if (ides.includes('cursor')) {
89
163
  const skillsDst = join(dir, '.cursor', 'skills');
90
164
  log.heading(`更新 Skills → ${skillsDst}`);
91
165
 
92
- cleanStaleSkills(skillsDst, currentSkillNames, dryRun);
166
+ cleanStaleSkills(skillsDst, projectSkillNames, dryRun, exclude);
93
167
 
94
168
  if (!dryRun) ensureDir(skillsDst);
95
- for (const skill of skills) {
169
+ for (const skill of projectSkills) {
96
170
  const dest = join(skillsDst, skill.name);
97
171
  if (!dryRun) {
98
172
  if (existsSync(dest)) rmSync(dest, { recursive: true });
@@ -111,15 +185,14 @@ function runUpdate(dir, opts) {
111
185
  }
112
186
  }
113
187
 
114
- // Claude Code: skills → .claude/skills/ (same structure as Cursor)
115
188
  if (ides.includes('claude')) {
116
189
  const skillsDst = join(dir, '.claude', 'skills');
117
190
  log.heading(`更新 Skills → ${skillsDst}`);
118
191
 
119
- cleanStaleSkills(skillsDst, currentSkillNames, dryRun);
192
+ cleanStaleSkills(skillsDst, projectSkillNames, dryRun, exclude);
120
193
 
121
194
  if (!dryRun) ensureDir(skillsDst);
122
- for (const skill of skills) {
195
+ for (const skill of projectSkills) {
123
196
  const dest = join(skillsDst, skill.name);
124
197
  if (!dryRun) {
125
198
  if (existsSync(dest)) rmSync(dest, { recursive: true });
@@ -140,3 +213,17 @@ function runUpdate(dir, opts) {
140
213
 
141
214
  log.done(`更新完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
142
215
  }
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
+ }