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 +15 -0
- package/package.json +1 -1
- package/src/commands/init.js +9 -3
- package/src/commands/setup.js +15 -18
- package/src/commands/update.js +106 -19
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
package/src/commands/init.js
CHANGED
|
@@ -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)
|
|
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)
|
|
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
|
}
|
package/src/commands/setup.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
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
|
}
|
package/src/commands/update.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
77
|
-
|
|
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,
|
|
166
|
+
cleanStaleSkills(skillsDst, projectSkillNames, dryRun, exclude);
|
|
93
167
|
|
|
94
168
|
if (!dryRun) ensureDir(skillsDst);
|
|
95
|
-
for (const skill of
|
|
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,
|
|
192
|
+
cleanStaleSkills(skillsDst, projectSkillNames, dryRun, exclude);
|
|
120
193
|
|
|
121
194
|
if (!dryRun) ensureDir(skillsDst);
|
|
122
|
-
for (const skill of
|
|
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
|
+
}
|