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 +14 -0
- package/package.json +1 -1
- package/skills/team-brainstorm/SKILL.md +1 -0
- package/skills/team-orchestrator/SKILL.md +1 -0
- package/skills/team-review/SKILL.md +1 -1
- package/skills/team-score/SKILL.md +1 -0
- package/skills/team-security/SKILL.md +1 -0
- package/src/commands/init.js +3 -56
- package/src/commands/list.js +25 -50
- package/src/commands/setup.js +5 -80
- package/src/commands/uninstall.js +15 -29
- package/src/commands/update.js +16 -146
- package/src/lib/constants.js +14 -0
- package/src/lib/installers.js +132 -0
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
|
@@ -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
|
|
|
@@ -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}`
|
package/src/commands/init.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
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
|
}
|
package/src/commands/list.js
CHANGED
|
@@ -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: []
|
|
19
|
+
const results = { skills: [], rules: [] };
|
|
23
20
|
|
|
24
|
-
// Check symlink-based install
|
|
25
21
|
const skills = discoverSkills();
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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} 已安装`);
|
package/src/commands/setup.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
}
|
package/src/commands/update.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { join, resolve } from 'node:path';
|
|
2
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
163
|
-
const skillsDst = join(dir,
|
|
164
|
-
|
|
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
|
-
|
|
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
|
-
}
|
package/src/lib/constants.js
CHANGED
|
@@ -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
|
+
}
|