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 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
- 创建 `.team-skills/` 目录,包含 Skills、Hooks 和命令文件。后续更新:
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
- | 12 个 Agent Skills | `~/.agents/skills/` | Cursor 自动发现 |
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` | 移除所有 symlink |
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.0",
4
- "description": "AI Agent Skills framework — Spec-Driven development with directed-graph rollback, quality gates, and 100-point scoring",
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"
@@ -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 your project for version-controlled local use')
12
+ .description('Copy skills into current project for the detected IDE(s)')
16
13
  .argument('[dir]', 'Project directory', '.')
17
- .option('--no-hooks', 'Skip hooks')
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 { hooks, commands, withScore, dryRun } = opts;
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 fileCount = 0;
26
+ let count = 0;
37
27
 
38
28
  log.heading('初始化 team-skills 到项目');
39
- log.info(`目标目录: ${installDir}`);
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
- if (skillsClaude) {
60
- if (!dryRun) {
61
- fsCopyFile(skillsClaude, join(skillsDst, 'CLAUDE.md'));
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
- // Copy hooks/
68
- if (hooks !== false) {
69
- log.heading('复制 Hooks');
70
- const hookFiles = discoverHooks();
71
- const hooksDst = join(installDir, 'hooks');
72
- if (hookFiles.length > 0) {
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
- // Copy commands/
85
- if (commands !== false) {
86
- log.heading('复制 Commands');
87
- const cmds = discoverCommands();
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
- // Write manifest
102
- if (!dryRun) {
103
- const hashes = computeDirectoryHashes(installDir);
104
- const manifest = createManifest(getPackageVersion(), hashes);
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
- log.done(`初始化完成${dryRun ? ' (dry-run)' : ''}!共 ${fileCount} 个组件。`);
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
- console.log(`
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
  }
@@ -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, LOCAL_INSTALL_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: [], localInit: null };
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
  }
@@ -86,21 +86,28 @@ function runSetup(target, opts) {
86
86
  }
87
87
  }
88
88
 
89
- log.heading('验证安装');
90
- let errors = 0;
91
- for (const skill of skills) {
92
- const dest = join(target, skill.name);
93
- if (isSymlink(dest)) {
94
- log.success(`${skill.name}`);
95
- } else if (!dryRun) {
96
- log.error(`${skill.name} 未正确安装`);
97
- errors++;
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('--keep-hooks', 'Do not remove hooks')
17
- .option('--keep-commands', 'Do not remove Claude Code commands')
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 { keepHooks, keepCommands, dryRun } = opts;
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 (!keepCommands) {
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 (!keepHooks) {
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]) {
@@ -1,118 +1,136 @@
1
1
  import { join } from 'node:path';
2
- import { existsSync, readdirSync, statSync, copyFileSync as fsCopyFile } from 'node:fs';
3
- import { LOCAL_INSTALL_DIR, PACKAGE_ROOT, SKILLS_DIR, HOOKS_DIR, COMMANDS_DIR } from '../lib/constants.js';
4
- import { computeDirectoryHashes, computeFileHash, ensureDir } from '../lib/fs-utils.js';
5
- import { readManifest, writeManifest, createManifest, getPackageVersion } from '../lib/manifest.js';
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('Update previously init\'d skills to latest version')
13
+ .description('Upgrade team-skills package and update project installations')
13
14
  .argument('[dir]', 'Project directory', '.')
14
- .option('--force', 'Overwrite locally modified files (creates .bak backup)', false)
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 runUpdate(dir, opts) {
20
- const { force, dryRun } = opts;
21
- const installDir = join(dir, LOCAL_INSTALL_DIR);
22
- const manifest = readManifest(installDir);
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
- if (!manifest) {
25
- log.error(`未找到 ${installDir}/manifest.json。请先运行 team-skills init。`);
26
- process.exit(1);
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 currentVersion = getPackageVersion();
30
- log.info(`已安装版本: ${manifest.version} | 最新版本: ${currentVersion}`);
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
- const sourceMap = buildSourceMap();
77
+ let count = 0;
34
78
 
35
- let updated = 0;
36
- let skipped = 0;
37
- let added = 0;
79
+ log.heading('更新项目中的 team-skills');
80
+ log.info(`项目目录: ${dir}`);
81
+ log.info(`目标 IDE: ${ides.join(', ')}`);
38
82
 
39
- log.heading('检查文件更新');
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
- for (const [relPath, sourceFullPath] of Object.entries(sourceMap)) {
42
- const installedPath = join(installDir, relPath);
43
- const sourceHash = computeFileHash(sourceFullPath);
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
- if (!existsSync(installedPath)) {
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
- ensureDir(dirname(installedPath));
48
- fsCopyFile(sourceFullPath, installedPath);
99
+ if (existsSync(dest)) rmSync(dest, { recursive: true });
100
+ copyRecursive(skill.path, dest);
49
101
  }
50
- log.success(`${tag}新增: ${relPath}`);
51
- added++;
52
- continue;
102
+ log.success(`${tag}Skill: ${skill.name}`);
103
+ count++;
53
104
  }
54
105
 
55
- const installedHash = computeFileHash(installedPath);
56
- if (installedHash === sourceHash) continue;
57
-
58
- const manifestHash = manifest.files[relPath];
59
- if (manifestHash && installedHash !== manifestHash && !force) {
60
- log.warn(`跳过: ${relPath}(本地已修改,使用 --force 覆盖)`);
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 (!dryRun) {
66
- if (force && manifestHash && installedHash !== manifestHash) {
67
- fsCopyFile(installedPath, installedPath + '.bak');
68
- log.info(`备份: ${relPath}.bak`);
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
- for (const relPath of Object.keys(manifest.files)) {
77
- if (!sourceMap[relPath]) {
78
- log.warn(`源文件已删除: ${relPath}(如不再需要请手动删除)`);
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
- if (!dryRun) {
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
  }
@@ -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
+ }
@@ -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
- }