team-skills 1.1.0 → 1.1.1
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/README.md +19 -9
- package/package.json +3 -3
- package/src/commands/init.js +48 -86
- package/src/commands/list.js +3 -24
- package/src/commands/setup.js +21 -14
- package/src/commands/uninstall.js +5 -5
- package/src/commands/update.js +103 -85
- package/src/lib/constants.js +0 -2
- package/src/lib/detect-ide.js +28 -0
- package/src/lib/manifest.js +0 -45
package/README.md
CHANGED
|
@@ -77,6 +77,12 @@ npx team-skills@latest setup
|
|
|
77
77
|
|
|
78
78
|
自动将 Skills、斜杠命令和 Hooks 以 symlink 方式安装到全局目录。
|
|
79
79
|
|
|
80
|
+
启用可选的项目评分功能(`team-score`):
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx team-skills@latest setup --with-score
|
|
84
|
+
```
|
|
85
|
+
|
|
80
86
|
如需频繁使用 CLI,可全局安装:
|
|
81
87
|
|
|
82
88
|
```bash
|
|
@@ -90,19 +96,23 @@ team-skills setup
|
|
|
90
96
|
|
|
91
97
|
```bash
|
|
92
98
|
npx team-skills@latest init
|
|
99
|
+
# 含评分功能
|
|
100
|
+
npx team-skills@latest init --with-score
|
|
93
101
|
```
|
|
94
102
|
|
|
95
|
-
|
|
103
|
+
自动检测项目中的 `.claude/` 和 `.cursor/` 目录,将对应文件复制到 IDE 能发现的位置。后续更新:
|
|
96
104
|
|
|
97
105
|
```bash
|
|
98
106
|
npx team-skills@latest update
|
|
99
107
|
```
|
|
100
108
|
|
|
109
|
+
> **提示**:Hooks 仅在全局安装(`setup`)模式下生效,`init` 不安装 hooks。
|
|
110
|
+
|
|
101
111
|
### 安装内容
|
|
102
112
|
|
|
103
113
|
| 内容 | 位置 | 说明 |
|
|
104
114
|
|------|------|------|
|
|
105
|
-
|
|
|
115
|
+
| 11 个 Agent Skills | `~/.agents/skills/` | Cursor 自动发现 |
|
|
106
116
|
| 斜杠命令 | `~/.claude/commands/` | Claude Code `/team-{name}` |
|
|
107
117
|
| 共享规则 | `~/.agents/skills/_team-rules/` | 被所有 Skill 引用 |
|
|
108
118
|
| Hooks(可选) | `~/.cursor/hooks/` | session-start 自动加载 |
|
|
@@ -116,13 +126,13 @@ npx team-skills@latest update
|
|
|
116
126
|
|
|
117
127
|
### CLI 参考
|
|
118
128
|
|
|
119
|
-
| 命令 | 说明 |
|
|
120
|
-
|
|
121
|
-
| `team-skills setup` | symlink 安装到全局目录 |
|
|
122
|
-
| `team-skills init [dir]` |
|
|
123
|
-
| `team-skills update [dir]` |
|
|
124
|
-
| `team-skills uninstall` |
|
|
125
|
-
| `team-skills list` |
|
|
129
|
+
| 命令 | 说明 | 关键选项 |
|
|
130
|
+
|------|------|----------|
|
|
131
|
+
| `team-skills setup` | symlink 安装到全局目录 | `--with-score` `--no-hooks` `--force` |
|
|
132
|
+
| `team-skills init [dir]` | 复制到项目 IDE 目录 | `--ide <claude\|cursor\|both>` `--with-score` |
|
|
133
|
+
| `team-skills update [dir]` | 升级包 + 更新项目副本 | `--skip-self` `--ide` `--with-score` |
|
|
134
|
+
| `team-skills uninstall` | 移除所有全局 symlink | `--no-hooks` `--no-commands` |
|
|
135
|
+
| `team-skills list` | 查看全局安装状态 | `--json` |
|
|
126
136
|
|
|
127
137
|
所有命令支持 `--dry-run`。
|
|
128
138
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "team-skills",
|
|
3
|
-
"version": "1.1.
|
|
4
|
-
"description": "AI Agent Skills framework — Spec-Driven development with directed-graph rollback
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "AI Agent Skills framework — Spec-Driven development with directed-graph rollback and quality gates",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"team-skills": "./bin/team-skills.js"
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"scripts": {
|
|
21
21
|
"lint": "markdownlint-cli2 '**/*.md' '#node_modules' --config .markdownlint.json && node scripts/check-skill-structure.js",
|
|
22
22
|
"format": "markdownlint-cli2 '**/*.md' '#node_modules' --config .markdownlint.json --fix",
|
|
23
|
-
"cli-test": "node bin/team-skills.js --version && node bin/team-skills.js list && node bin/team-skills.js setup --dry-run",
|
|
23
|
+
"cli-test": "node bin/team-skills.js --version && node bin/team-skills.js list && node bin/team-skills.js setup --dry-run && node bin/team-skills.js init --dry-run --ide claude /tmp/test-project && node bin/team-skills.js update --dry-run --skip-self --ide claude /tmp/test-project",
|
|
24
24
|
"prepare": "git config core.hooksPath .githooks 2>/dev/null || true",
|
|
25
25
|
"setup": "node bin/team-skills.js setup",
|
|
26
26
|
"uninstall": "node bin/team-skills.js uninstall"
|
package/src/commands/init.js
CHANGED
|
@@ -1,118 +1,80 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
3
|
-
import {
|
|
4
|
-
PACKAGE_ROOT, LOCAL_INSTALL_DIR, SKILLS_DIR, HOOKS_DIR, COMMANDS_DIR,
|
|
5
|
-
} from '../lib/constants.js';
|
|
6
|
-
import { discoverSkills, discoverSharedRules, discoverCommands, discoverHooks, discoverSkillsModuleClaude } from '../lib/inventory.js';
|
|
7
|
-
import { copyRecursive, computeDirectoryHashes, ensureDir } from '../lib/fs-utils.js';
|
|
8
|
-
import { createManifest, writeManifest, readManifest, getPackageVersion } from '../lib/manifest.js';
|
|
9
|
-
import * as log from '../lib/logger.js';
|
|
10
2
|
import { copyFileSync as fsCopyFile } from 'node:fs';
|
|
3
|
+
import { PACKAGE_ROOT } from '../lib/constants.js';
|
|
4
|
+
import { discoverSkills, discoverSharedRules, discoverCommands, discoverSkillsModuleClaude } from '../lib/inventory.js';
|
|
5
|
+
import { copyRecursive, ensureDir } from '../lib/fs-utils.js';
|
|
6
|
+
import { detectIDE } from '../lib/detect-ide.js';
|
|
7
|
+
import * as log from '../lib/logger.js';
|
|
11
8
|
|
|
12
9
|
export function registerInit(program) {
|
|
13
10
|
program
|
|
14
11
|
.command('init')
|
|
15
|
-
.description('Copy skills into
|
|
12
|
+
.description('Copy skills into current project for the detected IDE(s)')
|
|
16
13
|
.argument('[dir]', 'Project directory', '.')
|
|
17
|
-
.option('--
|
|
18
|
-
.option('--no-commands', 'Skip command files')
|
|
14
|
+
.option('--ide <type>', 'Force IDE type: claude, cursor, or both')
|
|
19
15
|
.option('--with-score', 'Include team-score skill (hidden by default)', false)
|
|
20
16
|
.option('--dry-run', 'Show what would be copied', false)
|
|
21
17
|
.action(runInit);
|
|
22
18
|
}
|
|
23
19
|
|
|
24
20
|
function runInit(dir, opts) {
|
|
25
|
-
const {
|
|
26
|
-
const installDir = join(dir, LOCAL_INSTALL_DIR);
|
|
21
|
+
const { ide, withScore, dryRun } = opts;
|
|
27
22
|
const exclude = withScore ? [] : ['team-score'];
|
|
28
|
-
|
|
29
|
-
const existing = readManifest(installDir);
|
|
30
|
-
if (existing) {
|
|
31
|
-
log.error(`${installDir} 已存在(v${existing.version})。使用 team-skills update 更新。`);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
23
|
+
const ides = detectIDE(dir, ide, { strict: true });
|
|
34
24
|
|
|
35
25
|
const tag = dryRun ? '[dry-run] ' : '';
|
|
36
|
-
let
|
|
26
|
+
let count = 0;
|
|
37
27
|
|
|
38
28
|
log.heading('初始化 team-skills 到项目');
|
|
39
|
-
log.info(
|
|
29
|
+
log.info(`项目目录: ${dir}`);
|
|
30
|
+
log.info(`目标 IDE: ${ides.join(', ')}`);
|
|
40
31
|
|
|
41
|
-
// Copy skills/
|
|
42
|
-
log.heading('复制 Skills');
|
|
43
|
-
const skillsDst = join(installDir, 'skills');
|
|
44
32
|
const skills = discoverSkills(PACKAGE_ROOT, { exclude });
|
|
45
33
|
const rules = discoverSharedRules();
|
|
46
|
-
if (!dryRun) {
|
|
47
|
-
ensureDir(skillsDst);
|
|
48
|
-
for (const s of skills) copyRecursive(s.path, join(skillsDst, s.name));
|
|
49
|
-
const rulesDst = join(skillsDst, '_team-rules');
|
|
50
|
-
ensureDir(rulesDst);
|
|
51
|
-
for (const r of rules) fsCopyFile(r.path, join(rulesDst, r.name));
|
|
52
|
-
}
|
|
53
|
-
fileCount += skills.length + rules.length;
|
|
54
|
-
for (const s of skills) log.success(`${tag}Skill: ${s.name}`);
|
|
55
|
-
for (const r of rules) log.success(`${tag}Rule: ${r.name}`);
|
|
56
|
-
|
|
57
|
-
// Copy skills/CLAUDE.md if exists
|
|
58
34
|
const skillsClaude = discoverSkillsModuleClaude();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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) copyRecursive(skill.path, dest);
|
|
45
|
+
log.success(`${tag}Skill: ${skill.name}`);
|
|
46
|
+
count++;
|
|
62
47
|
}
|
|
63
|
-
log.success(`${tag}skills/CLAUDE.md`);
|
|
64
|
-
fileCount++;
|
|
65
|
-
}
|
|
66
48
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!dryRun) ensureDir(hooksDst);
|
|
74
|
-
for (const h of hookFiles) {
|
|
75
|
-
if (!dryRun) {
|
|
76
|
-
fsCopyFile(h.path, join(hooksDst, h.name));
|
|
77
|
-
}
|
|
78
|
-
log.success(`${tag}Hook: ${h.name}`);
|
|
79
|
-
fileCount++;
|
|
80
|
-
}
|
|
49
|
+
const rulesDst = join(skillsDst, '_team-rules');
|
|
50
|
+
if (!dryRun) ensureDir(rulesDst);
|
|
51
|
+
for (const r of rules) {
|
|
52
|
+
if (!dryRun) fsCopyFile(r.path, join(rulesDst, r.name));
|
|
53
|
+
log.success(`${tag}Rule: ${r.name}`);
|
|
54
|
+
count++;
|
|
81
55
|
}
|
|
82
|
-
}
|
|
83
56
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const cmdsDst = join(installDir, 'commands');
|
|
89
|
-
if (cmds.length > 0) {
|
|
90
|
-
if (!dryRun) ensureDir(cmdsDst);
|
|
91
|
-
for (const c of cmds) {
|
|
92
|
-
if (!dryRun) {
|
|
93
|
-
fsCopyFile(c.path, join(cmdsDst, c.filename));
|
|
94
|
-
}
|
|
95
|
-
log.success(`${tag}Command: ${c.filename}`);
|
|
96
|
-
fileCount++;
|
|
97
|
-
}
|
|
57
|
+
if (skillsClaude) {
|
|
58
|
+
if (!dryRun) fsCopyFile(skillsClaude, join(skillsDst, 'CLAUDE.md'));
|
|
59
|
+
log.success(`${tag}skills/CLAUDE.md`);
|
|
60
|
+
count++;
|
|
98
61
|
}
|
|
99
62
|
}
|
|
100
63
|
|
|
101
|
-
//
|
|
102
|
-
if (
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
writeManifest(installDir, manifest);
|
|
106
|
-
log.success('manifest.json 已生成');
|
|
107
|
-
}
|
|
64
|
+
// Claude Code: commands → .claude/commands/
|
|
65
|
+
if (ides.includes('claude')) {
|
|
66
|
+
const cmdsDst = join(dir, '.claude', 'commands');
|
|
67
|
+
log.heading(`复制 Commands → ${cmdsDst}`);
|
|
108
68
|
|
|
109
|
-
|
|
69
|
+
const cmds = discoverCommands();
|
|
70
|
+
if (!dryRun) ensureDir(cmdsDst);
|
|
71
|
+
for (const c of cmds) {
|
|
72
|
+
if (!dryRun) fsCopyFile(c.path, join(cmdsDst, c.filename));
|
|
73
|
+
log.success(`${tag}Command: ${c.filename}`);
|
|
74
|
+
count++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
110
77
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
Cursor: 将 ${installDir}/skills 设为 agent skills 目录
|
|
114
|
-
Claude Code: 将 ${installDir}/commands/*.md 链接到 .claude/commands/
|
|
115
|
-
Hooks: 将 ${installDir}/hooks/ 链接到 ~/.cursor/hooks/ 或 ~/.claude/hooks/
|
|
116
|
-
更新: 运行 team-skills update
|
|
117
|
-
`);
|
|
78
|
+
log.done(`初始化完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
|
|
79
|
+
log.info('提示:Hooks 仅在全局安装 (setup) 模式下生效,init 不安装 hooks。');
|
|
118
80
|
}
|
package/src/commands/list.js
CHANGED
|
@@ -2,11 +2,10 @@ import { join } from 'node:path';
|
|
|
2
2
|
import { existsSync, readlinkSync } from 'node:fs';
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_SKILLS_TARGET, DEFAULT_COMMANDS_TARGET,
|
|
5
|
-
CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR,
|
|
5
|
+
CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR,
|
|
6
6
|
} from '../lib/constants.js';
|
|
7
7
|
import { discoverSkills, discoverSharedRules, discoverCommands, discoverHooks } from '../lib/inventory.js';
|
|
8
8
|
import { isSymlink } from '../lib/fs-utils.js';
|
|
9
|
-
import { readManifest } from '../lib/manifest.js';
|
|
10
9
|
import * as log from '../lib/logger.js';
|
|
11
10
|
|
|
12
11
|
export function registerList(program) {
|
|
@@ -20,7 +19,7 @@ export function registerList(program) {
|
|
|
20
19
|
|
|
21
20
|
function runList(opts) {
|
|
22
21
|
const { target, json } = opts;
|
|
23
|
-
const results = { skills: [], rules: [], commands: [], hooks: []
|
|
22
|
+
const results = { skills: [], rules: [], commands: [], hooks: [] };
|
|
24
23
|
|
|
25
24
|
// Check symlink-based install
|
|
26
25
|
const skills = discoverSkills();
|
|
@@ -28,7 +27,6 @@ function runList(opts) {
|
|
|
28
27
|
const dest = join(target, skill.name);
|
|
29
28
|
results.skills.push({
|
|
30
29
|
name: skill.name,
|
|
31
|
-
type: 'symlink',
|
|
32
30
|
status: getStatus(dest, skill.path),
|
|
33
31
|
path: dest,
|
|
34
32
|
});
|
|
@@ -67,17 +65,6 @@ function runList(opts) {
|
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
|
|
70
|
-
// Check for local init
|
|
71
|
-
const localManifest = readManifest(LOCAL_INSTALL_DIR);
|
|
72
|
-
if (localManifest) {
|
|
73
|
-
results.localInit = {
|
|
74
|
-
version: localManifest.version,
|
|
75
|
-
installedAt: localManifest.installedAt,
|
|
76
|
-
sourceCommit: localManifest.sourceCommit,
|
|
77
|
-
fileCount: Object.keys(localManifest.files).length,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
68
|
if (json) {
|
|
82
69
|
console.log(JSON.stringify(results, null, 2));
|
|
83
70
|
return;
|
|
@@ -96,16 +83,8 @@ function runList(opts) {
|
|
|
96
83
|
log.heading('Hooks');
|
|
97
84
|
printTable(results.hooks);
|
|
98
85
|
|
|
99
|
-
if (results.localInit) {
|
|
100
|
-
log.heading('项目内安装 (.team-skills/)');
|
|
101
|
-
log.info(`版本: ${results.localInit.version}`);
|
|
102
|
-
log.info(`安装时间: ${results.localInit.installedAt}`);
|
|
103
|
-
log.info(`来源 commit: ${results.localInit.sourceCommit}`);
|
|
104
|
-
log.info(`文件数: ${results.localInit.fileCount}`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
86
|
// Summary
|
|
108
|
-
const installed = results.skills.filter(s => s.status === 'ok').length;
|
|
87
|
+
const installed = results.skills.filter(s => s.status === 'ok' || s.status === 'file').length;
|
|
109
88
|
const total = results.skills.length;
|
|
110
89
|
console.log(`\nSkills: ${installed}/${total} 已安装`);
|
|
111
90
|
}
|
package/src/commands/setup.js
CHANGED
|
@@ -86,21 +86,28 @@ function runSetup(target, opts) {
|
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
if (!dryRun) {
|
|
90
|
+
log.heading('验证安装');
|
|
91
|
+
let errors = 0;
|
|
92
|
+
const verify = (label, dest) => {
|
|
93
|
+
if (isSymlink(dest)) {
|
|
94
|
+
log.success(label);
|
|
95
|
+
} else {
|
|
96
|
+
log.error(`${label} 未正确安装`);
|
|
97
|
+
errors++;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
for (const skill of skills) verify(`Skill: ${skill.name}`, join(target, skill.name));
|
|
101
|
+
for (const rule of rules) verify(`Rule: ${rule.name}`, join(rulesTarget, rule.name));
|
|
102
|
+
if (commands !== false) {
|
|
103
|
+
for (const cmd of discoverCommands()) {
|
|
104
|
+
verify(`Command: ${cmd.filename}`, join(DEFAULT_COMMANDS_TARGET, cmd.filename));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (errors > 0) {
|
|
108
|
+
log.error(`有 ${errors} 个组件安装异常,请检查。`);
|
|
109
|
+
process.exit(1);
|
|
98
110
|
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (errors > 0) {
|
|
102
|
-
log.error(`有 ${errors} 个组件安装异常,请检查。`);
|
|
103
|
-
process.exit(1);
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
log.done(`安装完成!${dryRun ? '(dry-run)' : `共处理 ${count} 个组件。`}`);
|
|
@@ -13,14 +13,14 @@ export function registerUninstall(program) {
|
|
|
13
13
|
.command('uninstall')
|
|
14
14
|
.description('Remove all team-skills symlinks')
|
|
15
15
|
.argument('[target]', 'Target skills directory', DEFAULT_SKILLS_TARGET)
|
|
16
|
-
.option('--
|
|
17
|
-
.option('--
|
|
16
|
+
.option('--no-hooks', 'Skip removing hooks')
|
|
17
|
+
.option('--no-commands', 'Skip removing commands')
|
|
18
18
|
.option('--dry-run', 'Show what would be removed', false)
|
|
19
19
|
.action(runUninstall);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function runUninstall(target, opts) {
|
|
23
|
-
const {
|
|
23
|
+
const { hooks, commands, dryRun } = opts;
|
|
24
24
|
let removed = 0;
|
|
25
25
|
|
|
26
26
|
log.heading('移除 Agent Skills');
|
|
@@ -56,7 +56,7 @@ function runUninstall(target, opts) {
|
|
|
56
56
|
}
|
|
57
57
|
if (!dryRun) rmdirIfEmpty(join(target, '_team-rules'));
|
|
58
58
|
|
|
59
|
-
if (
|
|
59
|
+
if (commands !== false) {
|
|
60
60
|
log.heading('移除 Command Skills + Claude Code 命令');
|
|
61
61
|
for (const cmd of discoverCommands()) {
|
|
62
62
|
// Command Skill directory
|
|
@@ -88,7 +88,7 @@ function runUninstall(target, opts) {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
if (
|
|
91
|
+
if (hooks !== false) {
|
|
92
92
|
log.heading('移除 Hooks');
|
|
93
93
|
const hookFiles = discoverHooks();
|
|
94
94
|
for (const dir of [CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR]) {
|
package/src/commands/update.js
CHANGED
|
@@ -1,118 +1,136 @@
|
|
|
1
1
|
import { join } from 'node:path';
|
|
2
|
-
import { existsSync,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
2
|
+
import { existsSync, copyFileSync as fsCopyFile, rmSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { PACKAGE_ROOT } from '../lib/constants.js';
|
|
5
|
+
import { discoverSkills, discoverSharedRules, discoverCommands, discoverSkillsModuleClaude } from '../lib/inventory.js';
|
|
6
|
+
import { copyRecursive, ensureDir } from '../lib/fs-utils.js';
|
|
7
|
+
import { detectIDE } from '../lib/detect-ide.js';
|
|
6
8
|
import * as log from '../lib/logger.js';
|
|
7
|
-
import { dirname } from 'node:path';
|
|
8
9
|
|
|
9
10
|
export function registerUpdate(program) {
|
|
10
11
|
program
|
|
11
12
|
.command('update')
|
|
12
|
-
.description('
|
|
13
|
+
.description('Upgrade team-skills package and update project installations')
|
|
13
14
|
.argument('[dir]', 'Project directory', '.')
|
|
14
|
-
.option('--
|
|
15
|
+
.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)
|
|
15
18
|
.option('--dry-run', 'Show what would change', false)
|
|
16
19
|
.action(runUpdate);
|
|
17
20
|
}
|
|
18
21
|
|
|
19
|
-
function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
function upgradeSelf(dryRun) {
|
|
23
|
+
log.heading('升级 team-skills 包');
|
|
24
|
+
try {
|
|
25
|
+
const current = execSync(
|
|
26
|
+
'npm view team-skills version --registry https://registry.npmjs.org 2>/dev/null',
|
|
27
|
+
{ encoding: 'utf8' },
|
|
28
|
+
).trim();
|
|
29
|
+
const local = execSync(
|
|
30
|
+
`node -e "console.log(require('${join(PACKAGE_ROOT, 'package.json')}').version)"`,
|
|
31
|
+
{ encoding: 'utf8' },
|
|
32
|
+
).trim();
|
|
33
|
+
if (current === local) {
|
|
34
|
+
log.skip(`已是最新版本 (${local})`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
log.info(`${local} → ${current}`);
|
|
38
|
+
if (!dryRun) {
|
|
39
|
+
execSync('npm install -g team-skills@latest --registry https://registry.npmjs.org', { stdio: 'inherit' });
|
|
40
|
+
log.success(`已升级到 ${current}`);
|
|
41
|
+
} else {
|
|
42
|
+
log.success(`[dry-run] 将升级到 ${current}`);
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
log.warn('自升级跳过(无法访问 npm registry 或非全局安装)');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
23
48
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
function cleanStaleSkills(targetDir, currentNames, dryRun) {
|
|
50
|
+
if (!existsSync(targetDir)) return;
|
|
51
|
+
const existing = readdirSync(targetDir).filter(
|
|
52
|
+
name => !name.startsWith('_') && name !== 'CLAUDE.md',
|
|
53
|
+
);
|
|
54
|
+
for (const name of existing) {
|
|
55
|
+
if (!currentNames.has(name)) {
|
|
56
|
+
const tag = dryRun ? '[dry-run] ' : '';
|
|
57
|
+
if (!dryRun) rmSync(join(targetDir, name), { recursive: true });
|
|
58
|
+
log.warn(`${tag}移除旧 Skill: ${name}`);
|
|
59
|
+
}
|
|
27
60
|
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function runUpdate(dir, opts) {
|
|
64
|
+
const { ide, withScore, skipSelf, dryRun } = opts;
|
|
65
|
+
|
|
66
|
+
if (!skipSelf) upgradeSelf(dryRun);
|
|
28
67
|
|
|
29
|
-
const
|
|
30
|
-
|
|
68
|
+
const exclude = withScore ? [] : ['team-score'];
|
|
69
|
+
const ides = detectIDE(dir, ide);
|
|
70
|
+
|
|
71
|
+
if (ides.length === 0) {
|
|
72
|
+
log.info('当前项目未检测到 IDE 配置(.claude/ 或 .cursor/),跳过项目更新。');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
31
75
|
|
|
32
76
|
const tag = dryRun ? '[dry-run] ' : '';
|
|
33
|
-
|
|
77
|
+
let count = 0;
|
|
34
78
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
79
|
+
log.heading('更新项目中的 team-skills');
|
|
80
|
+
log.info(`项目目录: ${dir}`);
|
|
81
|
+
log.info(`目标 IDE: ${ides.join(', ')}`);
|
|
38
82
|
|
|
39
|
-
|
|
83
|
+
const skills = discoverSkills(PACKAGE_ROOT, { exclude });
|
|
84
|
+
const rules = discoverSharedRules();
|
|
85
|
+
const skillsClaude = discoverSkillsModuleClaude();
|
|
86
|
+
const currentSkillNames = new Set(skills.map(s => s.name));
|
|
40
87
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
88
|
+
// Cursor skills → .cursor/skills/
|
|
89
|
+
if (ides.includes('cursor')) {
|
|
90
|
+
const skillsDst = join(dir, '.cursor', 'skills');
|
|
91
|
+
log.heading(`更新 Skills → ${skillsDst}`);
|
|
44
92
|
|
|
45
|
-
|
|
93
|
+
cleanStaleSkills(skillsDst, currentSkillNames, dryRun);
|
|
94
|
+
|
|
95
|
+
if (!dryRun) ensureDir(skillsDst);
|
|
96
|
+
for (const skill of skills) {
|
|
97
|
+
const dest = join(skillsDst, skill.name);
|
|
46
98
|
if (!dryRun) {
|
|
47
|
-
|
|
48
|
-
|
|
99
|
+
if (existsSync(dest)) rmSync(dest, { recursive: true });
|
|
100
|
+
copyRecursive(skill.path, dest);
|
|
49
101
|
}
|
|
50
|
-
log.success(`${tag}
|
|
51
|
-
|
|
52
|
-
continue;
|
|
102
|
+
log.success(`${tag}Skill: ${skill.name}`);
|
|
103
|
+
count++;
|
|
53
104
|
}
|
|
54
105
|
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
skipped++;
|
|
62
|
-
continue;
|
|
106
|
+
const rulesDst = join(skillsDst, '_team-rules');
|
|
107
|
+
if (!dryRun) ensureDir(rulesDst);
|
|
108
|
+
for (const r of rules) {
|
|
109
|
+
if (!dryRun) fsCopyFile(r.path, join(rulesDst, r.name));
|
|
110
|
+
log.success(`${tag}Rule: ${r.name}`);
|
|
111
|
+
count++;
|
|
63
112
|
}
|
|
64
113
|
|
|
65
|
-
if (
|
|
66
|
-
if (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
fsCopyFile(sourceFullPath, installedPath);
|
|
114
|
+
if (skillsClaude) {
|
|
115
|
+
if (!dryRun) fsCopyFile(skillsClaude, join(skillsDst, 'CLAUDE.md'));
|
|
116
|
+
log.success(`${tag}skills/CLAUDE.md`);
|
|
117
|
+
count++;
|
|
71
118
|
}
|
|
72
|
-
log.success(`${tag}更新: ${relPath}`);
|
|
73
|
-
updated++;
|
|
74
119
|
}
|
|
75
120
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
121
|
+
// Claude Code commands → .claude/commands/
|
|
122
|
+
if (ides.includes('claude')) {
|
|
123
|
+
const cmdsDst = join(dir, '.claude', 'commands');
|
|
124
|
+
log.heading(`更新 Commands → ${cmdsDst}`);
|
|
125
|
+
|
|
126
|
+
const cmds = discoverCommands();
|
|
127
|
+
if (!dryRun) ensureDir(cmdsDst);
|
|
128
|
+
for (const c of cmds) {
|
|
129
|
+
if (!dryRun) fsCopyFile(c.path, join(cmdsDst, c.filename));
|
|
130
|
+
log.success(`${tag}Command: ${c.filename}`);
|
|
131
|
+
count++;
|
|
79
132
|
}
|
|
80
133
|
}
|
|
81
134
|
|
|
82
|
-
|
|
83
|
-
const newHashes = computeDirectoryHashes(installDir);
|
|
84
|
-
delete newHashes['manifest.json'];
|
|
85
|
-
const newManifest = createManifest(currentVersion, newHashes);
|
|
86
|
-
writeManifest(installDir, newManifest);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
log.done(`更新完成${dryRun ? ' (dry-run)' : ''}!更新 ${updated},新增 ${added},跳过 ${skipped}。`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function buildSourceMap() {
|
|
93
|
-
const map = {};
|
|
94
|
-
|
|
95
|
-
const skillsDir = join(PACKAGE_ROOT, SKILLS_DIR);
|
|
96
|
-
if (existsSync(skillsDir)) scanRecursive(skillsDir, 'skills', map);
|
|
97
|
-
|
|
98
|
-
const hooksDir = join(PACKAGE_ROOT, HOOKS_DIR);
|
|
99
|
-
if (existsSync(hooksDir)) scanRecursive(hooksDir, 'hooks', map);
|
|
100
|
-
|
|
101
|
-
const cmdsDir = join(PACKAGE_ROOT, COMMANDS_DIR);
|
|
102
|
-
if (existsSync(cmdsDir)) scanRecursive(cmdsDir, 'commands', map);
|
|
103
|
-
|
|
104
|
-
return map;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function scanRecursive(baseDir, prefix, map) {
|
|
108
|
-
const entries = readdirSync(baseDir, { withFileTypes: true });
|
|
109
|
-
for (const entry of entries) {
|
|
110
|
-
const fullPath = join(baseDir, entry.name);
|
|
111
|
-
const relPath = `${prefix}/${entry.name}`;
|
|
112
|
-
if (entry.isDirectory()) {
|
|
113
|
-
scanRecursive(fullPath, relPath, map);
|
|
114
|
-
} else {
|
|
115
|
-
map[relPath] = fullPath;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
135
|
+
log.done(`更新完成${dryRun ? ' (dry-run)' : ''}!共 ${count} 个组件。`);
|
|
118
136
|
}
|
package/src/lib/constants.js
CHANGED
|
@@ -10,8 +10,6 @@ export const DEFAULT_SKILLS_TARGET = join(homedir(), '.agents', 'skills');
|
|
|
10
10
|
export const DEFAULT_COMMANDS_TARGET = join(homedir(), '.claude', 'commands');
|
|
11
11
|
export const CURSOR_HOOKS_DIR = join(homedir(), '.cursor', 'hooks');
|
|
12
12
|
export const CLAUDE_HOOKS_DIR = join(homedir(), '.claude', 'hooks');
|
|
13
|
-
export const LOCAL_INSTALL_DIR = '.team-skills';
|
|
14
|
-
export const MANIFEST_FILE = 'manifest.json';
|
|
15
13
|
export const SKILLS_DIR = 'skills';
|
|
16
14
|
export const HOOKS_DIR = 'hooks';
|
|
17
15
|
export const COMMANDS_DIR = join('.claude', 'commands');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import * as log from './logger.js';
|
|
4
|
+
|
|
5
|
+
export function detectIDE(projectDir, forceIDE, { strict = false } = {}) {
|
|
6
|
+
if (forceIDE) {
|
|
7
|
+
if (!['claude', 'cursor', 'both'].includes(forceIDE)) {
|
|
8
|
+
log.error(`不支持的 IDE 类型: ${forceIDE}。可选: claude, cursor, both`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
return forceIDE === 'both' ? ['claude', 'cursor'] : [forceIDE];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const detected = [];
|
|
15
|
+
if (existsSync(join(projectDir, '.claude'))) detected.push('claude');
|
|
16
|
+
if (existsSync(join(projectDir, '.cursor'))) detected.push('cursor');
|
|
17
|
+
|
|
18
|
+
if (detected.length === 0 && strict) {
|
|
19
|
+
log.error('未检测到项目级 IDE 配置(.claude/ 或 .cursor/ 目录)。');
|
|
20
|
+
log.info('请使用 --ide 指定目标 IDE:');
|
|
21
|
+
log.info(' --ide claude 仅安装 Claude Code 命令');
|
|
22
|
+
log.info(' --ide cursor 仅安装 Cursor skills');
|
|
23
|
+
log.info(' --ide both 同时安装两者');
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return detected;
|
|
28
|
+
}
|
package/src/lib/manifest.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { execSync } from 'node:child_process';
|
|
4
|
-
import { MANIFEST_FILE, PACKAGE_ROOT } from './constants.js';
|
|
5
|
-
|
|
6
|
-
export function readManifest(dir) {
|
|
7
|
-
const p = join(dir, MANIFEST_FILE);
|
|
8
|
-
if (!existsSync(p)) return null;
|
|
9
|
-
try {
|
|
10
|
-
return JSON.parse(readFileSync(p, 'utf8'));
|
|
11
|
-
} catch {
|
|
12
|
-
return null;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function writeManifest(dir, data) {
|
|
17
|
-
const p = join(dir, MANIFEST_FILE);
|
|
18
|
-
writeFileSync(p, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function createManifest(packageVersion, fileHashes) {
|
|
22
|
-
return {
|
|
23
|
-
version: packageVersion,
|
|
24
|
-
installedAt: new Date().toISOString(),
|
|
25
|
-
sourceCommit: getSourceCommit(),
|
|
26
|
-
files: fileHashes,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getSourceCommit() {
|
|
31
|
-
try {
|
|
32
|
-
return execSync('git rev-parse --short HEAD', {
|
|
33
|
-
cwd: PACKAGE_ROOT,
|
|
34
|
-
encoding: 'utf8',
|
|
35
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
36
|
-
}).trim();
|
|
37
|
-
} catch {
|
|
38
|
-
return 'unknown';
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function getPackageVersion() {
|
|
43
|
-
const pkg = JSON.parse(readFileSync(join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
44
|
-
return pkg.version;
|
|
45
|
-
}
|