team-skills 1.0.0
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/.claude/commands/team-pull.md +21 -0
- package/.claude/commands/team-push.md +28 -0
- package/.claude/commands/team-setup.md +183 -0
- package/.claude/commands/team-uninstall.md +107 -0
- package/CHANGELOG.md +41 -0
- package/LICENSE +21 -0
- package/README.md +421 -0
- package/bin/team-skills.js +2 -0
- package/hooks/hooks.json +16 -0
- package/hooks/session-start +34 -0
- package/package.json +58 -0
- package/scripts/check-skill-structure.js +89 -0
- package/skills/CLAUDE.md +121 -0
- package/skills/_team-rules/constitutional-rules.md +25 -0
- package/skills/_team-rules/four-state-protocol.md +10 -0
- package/skills/_team-rules/verification-protocol.md +55 -0
- package/skills/team-brainstorm/SKILL.md +168 -0
- package/skills/team-debug/SKILL.md +143 -0
- package/skills/team-feedback/SKILL.md +175 -0
- package/skills/team-finish/SKILL.md +151 -0
- package/skills/team-impl/SKILL.md +316 -0
- package/skills/team-impl/references/06-tdd-log-template.md +62 -0
- package/skills/team-impl/references/07-prompt-log-template.md +32 -0
- package/skills/team-impl/references/08-ai-decisions-template.md +16 -0
- package/skills/team-orchestrator/SKILL.md +584 -0
- package/skills/team-orchestrator/references/14-team-template.md +70 -0
- package/skills/team-orchestrator/references/15-brief-template.md +50 -0
- package/skills/team-review/SKILL.md +383 -0
- package/skills/team-review/references/11-review-template.md +63 -0
- package/skills/team-review/references/12-asset-update-template.md +45 -0
- package/skills/team-review/references/13-retrospective-template.md +40 -0
- package/skills/team-score/SKILL.md +330 -0
- package/skills/team-spec/SKILL.md +231 -0
- package/skills/team-spec/references/01-plan-template.md +67 -0
- package/skills/team-spec/references/02-context-template.md +46 -0
- package/skills/team-spec/references/04-boundary-template.md +23 -0
- package/skills/team-spec/references/05-risk-template.md +50 -0
- package/skills/team-spec/references/delta-spec-template.md +32 -0
- package/skills/team-spec/references/prompt-template.md +23 -0
- package/skills/team-spec/references/sdd-template.md +72 -0
- package/skills/team-test/SKILL.md +190 -0
- package/skills/team-test/references/09-test-matrix-template.md +40 -0
- package/skills/team-test/references/10-test-report-template.md +59 -0
- package/skills/team-verify/SKILL.md +151 -0
- package/skills/using-team-skills/SKILL.md +137 -0
- package/src/cli.js +27 -0
- package/src/commands/init.js +115 -0
- package/src/commands/list.js +150 -0
- package/src/commands/setup.js +125 -0
- package/src/commands/uninstall.js +113 -0
- package/src/commands/update.js +118 -0
- package/src/lib/constants.js +17 -0
- package/src/lib/fs-utils.js +117 -0
- package/src/lib/inventory.js +64 -0
- package/src/lib/logger.js +34 -0
- package/src/lib/manifest.js +45 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: using-team-skills
|
|
3
|
+
description: Use when starting any conversation with Team Skills - establishes how to find and use team skills, with skill selection matrix
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Using Team Skills
|
|
7
|
+
|
|
8
|
+
<SUBAGENT-STOP>
|
|
9
|
+
If you were dispatched as a subagent to execute a specific task, skip this skill.
|
|
10
|
+
</SUBAGENT-STOP>
|
|
11
|
+
|
|
12
|
+
## 角色定位
|
|
13
|
+
|
|
14
|
+
你是 Team Skills 的**向导**。你的职责是帮助用户在正确的场景选择正确的 Skill,并确保用户了解 Team Skills 体系的能力边界。
|
|
15
|
+
|
|
16
|
+
### 系统提示词
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
你是一个 Team Skills 向导。你的任务是:
|
|
20
|
+
|
|
21
|
+
1. 理解用户当前场景(需求模糊/明确/已有规格/已有实现/遇到 bug 等)
|
|
22
|
+
2. 根据 Skill 选择矩阵推荐最合适的 Skill
|
|
23
|
+
3. 如果用户不确定,引导使用 team-brainstorm 先讨论
|
|
24
|
+
4. 如果用户需要完整流水线,推荐 team-orchestrator
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 推理指引
|
|
29
|
+
|
|
30
|
+
根据用户描述的当前阶段(需求/规格/实现/测试/审查/调试/完成),从选择矩阵中匹配最合适的 Skill 并说明理由。
|
|
31
|
+
|
|
32
|
+
## Iron Law
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
NO SKILL RECOMMENDATION WITHOUT SCENE ANALYSIS FIRST
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 质量职责
|
|
39
|
+
|
|
40
|
+
| 质量维度 | 产出文件 |
|
|
41
|
+
| -------- | -------- |
|
|
42
|
+
| 场景分析 | 推荐 Skill(对话中) |
|
|
43
|
+
| 推荐理由 | 推荐说明(对话中) |
|
|
44
|
+
|
|
45
|
+
## Skill 选择矩阵
|
|
46
|
+
|
|
47
|
+
| 场景 | 推荐 Skill |
|
|
48
|
+
|------|-----------|
|
|
49
|
+
| 需求模糊,先讨论再决定 | team-brainstorm |
|
|
50
|
+
| 需求明确,需完整规格 | team-spec |
|
|
51
|
+
| 规格已有,需 TDD 实现 | team-impl |
|
|
52
|
+
| 实现已有,需测试审计 | team-test |
|
|
53
|
+
| 代码 + 测试已有,需审查 | team-review |
|
|
54
|
+
| 收到代码审查反馈,需应对 | team-feedback |
|
|
55
|
+
| 遇到 bug,需根因分析 | team-debug |
|
|
56
|
+
| 声明完成,需验证门禁 | team-verify |
|
|
57
|
+
| 实现完成,需处理分支 | team-finish |
|
|
58
|
+
| 需完整交付流水线 | team-orchestrator |
|
|
59
|
+
| 评估项目协作成熟度 | team-score |
|
|
60
|
+
|
|
61
|
+
## 执行步骤
|
|
62
|
+
|
|
63
|
+
### Step 1:分析用户场景
|
|
64
|
+
|
|
65
|
+
从用户描述中判断当前所处阶段:
|
|
66
|
+
|
|
67
|
+
- 需求模糊 → 推荐 `team-brainstorm`
|
|
68
|
+
- 需求明确 → 推荐 `team-spec`
|
|
69
|
+
- 已有规格 → 推荐 `team-impl`
|
|
70
|
+
- 已有实现 → 推荐 `team-test`
|
|
71
|
+
- 已有代码 + 测试 → 推荐 `team-review`
|
|
72
|
+
- 遇到 bug → 推荐 `team-debug`
|
|
73
|
+
- 收到审查反馈 → 推荐 `team-feedback`
|
|
74
|
+
- 实现完成 → 推荐 `team-finish`
|
|
75
|
+
- 需要完整流水线 → 推荐 `team-orchestrator`
|
|
76
|
+
- 评估成熟度 → 推荐 `team-score`
|
|
77
|
+
|
|
78
|
+
### Step 2:推荐并说明理由
|
|
79
|
+
|
|
80
|
+
给出推荐 Skill 的同时,说明为什么适合当前场景。
|
|
81
|
+
|
|
82
|
+
### Step 3:可选 — 展示流程图
|
|
83
|
+
|
|
84
|
+
如果用户需要了解全貌,展示 Mermaid 流程图。
|
|
85
|
+
|
|
86
|
+
## 指令优先级
|
|
87
|
+
|
|
88
|
+
1. **用户显式指令**(CLAUDE.md、直接请求)— 最高优先级
|
|
89
|
+
2. **Team Skills** — 覆盖默认系统行为
|
|
90
|
+
3. **默认系统提示** — 最低优先级
|
|
91
|
+
|
|
92
|
+
## 使用规则
|
|
93
|
+
|
|
94
|
+
**Invoke relevant skills BEFORE any response or action.** 即使只有 1% 的可能适用,也应该加载 skill 检查。
|
|
95
|
+
|
|
96
|
+
## 自检门禁
|
|
97
|
+
|
|
98
|
+
在推荐 Skill 前执行以下自检:
|
|
99
|
+
|
|
100
|
+
- [ ] 已分析用户场景(需求/规格/实现/测试/审查/调试/完成?)
|
|
101
|
+
- [ ] 推荐理由与场景匹配
|
|
102
|
+
- [ ] 如果场景模糊,已推荐 team-brainstorm
|
|
103
|
+
- [ ] 如果用户需要完整流水线,已推荐 team-orchestrator
|
|
104
|
+
|
|
105
|
+
## 完成标志
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
状态:DONE
|
|
109
|
+
推荐 Skill:{skill-name}
|
|
110
|
+
推荐理由:{reason}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## STOP Signals
|
|
114
|
+
|
|
115
|
+
如果你发现自己即将做以下任何一件事——立即停止,重新审视:
|
|
116
|
+
|
|
117
|
+
- 不分析场景就直接推荐 Skill
|
|
118
|
+
- 场景模糊时跳过 team-brainstorm 直接推荐实现类 Skill
|
|
119
|
+
- 凭记忆推荐 Skill 而不读取当前版本的选择矩阵
|
|
120
|
+
|
|
121
|
+
## 集成关系
|
|
122
|
+
|
|
123
|
+
**被谁调用:**
|
|
124
|
+
|
|
125
|
+
- Session hook(会话启动时自动加载)
|
|
126
|
+
- 用户直接调用
|
|
127
|
+
|
|
128
|
+
**配对使用:**
|
|
129
|
+
|
|
130
|
+
- `team-brainstorm` — 需求模糊时先讨论
|
|
131
|
+
- `team-orchestrator` — 需要完整流水线时使用
|
|
132
|
+
|
|
133
|
+
## 下一步
|
|
134
|
+
|
|
135
|
+
- 根据 Skill 选择矩阵选择对应 skill 开始工作
|
|
136
|
+
- 不确定时使用 `team-brainstorm` 先讨论再决定
|
|
137
|
+
- 需要完整交付流水线时使用 `team-orchestrator`
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { registerSetup } from './commands/setup.js';
|
|
6
|
+
import { registerUninstall } from './commands/uninstall.js';
|
|
7
|
+
import { registerInit } from './commands/init.js';
|
|
8
|
+
import { registerUpdate } from './commands/update.js';
|
|
9
|
+
import { registerList } from './commands/list.js';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
13
|
+
|
|
14
|
+
const program = new Command();
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('team-skills')
|
|
18
|
+
.description('AI Agent Skills framework — install, manage, and update team skills')
|
|
19
|
+
.version(pkg.version);
|
|
20
|
+
|
|
21
|
+
registerSetup(program);
|
|
22
|
+
registerUninstall(program);
|
|
23
|
+
registerInit(program);
|
|
24
|
+
registerUpdate(program);
|
|
25
|
+
registerList(program);
|
|
26
|
+
|
|
27
|
+
program.parse();
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
import { copyFileSync as fsCopyFile } from 'node:fs';
|
|
11
|
+
|
|
12
|
+
export function registerInit(program) {
|
|
13
|
+
program
|
|
14
|
+
.command('init')
|
|
15
|
+
.description('Copy skills into your project for version-controlled local use')
|
|
16
|
+
.argument('[dir]', 'Project directory', '.')
|
|
17
|
+
.option('--no-hooks', 'Skip hooks')
|
|
18
|
+
.option('--no-commands', 'Skip command files')
|
|
19
|
+
.option('--dry-run', 'Show what would be copied', false)
|
|
20
|
+
.action(runInit);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function runInit(dir, opts) {
|
|
24
|
+
const { hooks, commands, dryRun } = opts;
|
|
25
|
+
const installDir = join(dir, LOCAL_INSTALL_DIR);
|
|
26
|
+
|
|
27
|
+
const existing = readManifest(installDir);
|
|
28
|
+
if (existing) {
|
|
29
|
+
log.error(`${installDir} 已存在(v${existing.version})。使用 team-skills update 更新。`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const tag = dryRun ? '[dry-run] ' : '';
|
|
34
|
+
let fileCount = 0;
|
|
35
|
+
|
|
36
|
+
log.heading('初始化 team-skills 到项目');
|
|
37
|
+
log.info(`目标目录: ${installDir}`);
|
|
38
|
+
|
|
39
|
+
// Copy skills/
|
|
40
|
+
log.heading('复制 Skills');
|
|
41
|
+
const skillsSrc = join(PACKAGE_ROOT, SKILLS_DIR);
|
|
42
|
+
const skillsDst = join(installDir, 'skills');
|
|
43
|
+
if (dryRun) {
|
|
44
|
+
log.info(`${tag}${skillsSrc} → ${skillsDst}`);
|
|
45
|
+
} else {
|
|
46
|
+
copyRecursive(skillsSrc, skillsDst);
|
|
47
|
+
}
|
|
48
|
+
const skills = discoverSkills();
|
|
49
|
+
const rules = discoverSharedRules();
|
|
50
|
+
fileCount += skills.length + rules.length;
|
|
51
|
+
for (const s of skills) log.success(`${tag}Skill: ${s.name}`);
|
|
52
|
+
for (const r of rules) log.success(`${tag}Rule: ${r.name}`);
|
|
53
|
+
|
|
54
|
+
// Copy skills/CLAUDE.md if exists
|
|
55
|
+
const skillsClaude = discoverSkillsModuleClaude();
|
|
56
|
+
if (skillsClaude) {
|
|
57
|
+
if (!dryRun) {
|
|
58
|
+
fsCopyFile(skillsClaude, join(skillsDst, 'CLAUDE.md'));
|
|
59
|
+
}
|
|
60
|
+
log.success(`${tag}skills/CLAUDE.md`);
|
|
61
|
+
fileCount++;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Copy hooks/
|
|
65
|
+
if (hooks !== false) {
|
|
66
|
+
log.heading('复制 Hooks');
|
|
67
|
+
const hookFiles = discoverHooks();
|
|
68
|
+
const hooksDst = join(installDir, 'hooks');
|
|
69
|
+
if (hookFiles.length > 0) {
|
|
70
|
+
if (!dryRun) ensureDir(hooksDst);
|
|
71
|
+
for (const h of hookFiles) {
|
|
72
|
+
if (!dryRun) {
|
|
73
|
+
fsCopyFile(h.path, join(hooksDst, h.name));
|
|
74
|
+
}
|
|
75
|
+
log.success(`${tag}Hook: ${h.name}`);
|
|
76
|
+
fileCount++;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Copy commands/
|
|
82
|
+
if (commands !== false) {
|
|
83
|
+
log.heading('复制 Commands');
|
|
84
|
+
const cmds = discoverCommands();
|
|
85
|
+
const cmdsDst = join(installDir, 'commands');
|
|
86
|
+
if (cmds.length > 0) {
|
|
87
|
+
if (!dryRun) ensureDir(cmdsDst);
|
|
88
|
+
for (const c of cmds) {
|
|
89
|
+
if (!dryRun) {
|
|
90
|
+
fsCopyFile(c.path, join(cmdsDst, c.filename));
|
|
91
|
+
}
|
|
92
|
+
log.success(`${tag}Command: ${c.filename}`);
|
|
93
|
+
fileCount++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Write manifest
|
|
99
|
+
if (!dryRun) {
|
|
100
|
+
const hashes = computeDirectoryHashes(installDir);
|
|
101
|
+
const manifest = createManifest(getPackageVersion(), hashes);
|
|
102
|
+
writeManifest(installDir, manifest);
|
|
103
|
+
log.success('manifest.json 已生成');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
log.done(`初始化完成${dryRun ? ' (dry-run)' : ''}!共 ${fileCount} 个组件。`);
|
|
107
|
+
|
|
108
|
+
console.log(`
|
|
109
|
+
集成说明:
|
|
110
|
+
Cursor: 将 ${installDir}/skills 设为 agent skills 目录
|
|
111
|
+
Claude Code: 将 ${installDir}/commands/*.md 链接到 .claude/commands/
|
|
112
|
+
Hooks: 将 ${installDir}/hooks/ 链接到 ~/.cursor/hooks/ 或 ~/.claude/hooks/
|
|
113
|
+
更新: 运行 team-skills update
|
|
114
|
+
`);
|
|
115
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, readlinkSync } from 'node:fs';
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_SKILLS_TARGET, DEFAULT_COMMANDS_TARGET,
|
|
5
|
+
CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR, LOCAL_INSTALL_DIR,
|
|
6
|
+
} from '../lib/constants.js';
|
|
7
|
+
import { discoverSkills, discoverSharedRules, discoverCommands, discoverHooks } from '../lib/inventory.js';
|
|
8
|
+
import { isSymlink } from '../lib/fs-utils.js';
|
|
9
|
+
import { readManifest } from '../lib/manifest.js';
|
|
10
|
+
import * as log from '../lib/logger.js';
|
|
11
|
+
|
|
12
|
+
export function registerList(program) {
|
|
13
|
+
program
|
|
14
|
+
.command('list')
|
|
15
|
+
.description('List installed skills and their status')
|
|
16
|
+
.option('--target <dir>', 'Check a specific target directory', DEFAULT_SKILLS_TARGET)
|
|
17
|
+
.option('--json', 'Output as JSON')
|
|
18
|
+
.action(runList);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function runList(opts) {
|
|
22
|
+
const { target, json } = opts;
|
|
23
|
+
const results = { skills: [], rules: [], commands: [], hooks: [], localInit: null };
|
|
24
|
+
|
|
25
|
+
// Check symlink-based install
|
|
26
|
+
const skills = discoverSkills();
|
|
27
|
+
for (const skill of skills) {
|
|
28
|
+
const dest = join(target, skill.name);
|
|
29
|
+
results.skills.push({
|
|
30
|
+
name: skill.name,
|
|
31
|
+
type: 'symlink',
|
|
32
|
+
status: getStatus(dest, skill.path),
|
|
33
|
+
path: dest,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Shared rules
|
|
38
|
+
for (const rule of discoverSharedRules()) {
|
|
39
|
+
const dest = join(target, '_team-rules', rule.name);
|
|
40
|
+
results.rules.push({
|
|
41
|
+
name: rule.name,
|
|
42
|
+
status: getStatus(dest, rule.path),
|
|
43
|
+
path: dest,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Commands
|
|
48
|
+
for (const cmd of discoverCommands()) {
|
|
49
|
+
const dest = join(DEFAULT_COMMANDS_TARGET, cmd.filename);
|
|
50
|
+
results.commands.push({
|
|
51
|
+
name: cmd.name,
|
|
52
|
+
status: getStatus(dest, cmd.path),
|
|
53
|
+
path: dest,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Hooks
|
|
58
|
+
for (const dir of [CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR]) {
|
|
59
|
+
const platform = dir.includes('.cursor') ? 'Cursor' : 'Claude Code';
|
|
60
|
+
for (const hook of discoverHooks()) {
|
|
61
|
+
const dest = join(dir, hook.name);
|
|
62
|
+
results.hooks.push({
|
|
63
|
+
name: `${platform}/${hook.name}`,
|
|
64
|
+
status: getStatus(dest, hook.path),
|
|
65
|
+
path: dest,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
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
|
+
if (json) {
|
|
82
|
+
console.log(JSON.stringify(results, null, 2));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Pretty print
|
|
87
|
+
log.heading('Agent Skills');
|
|
88
|
+
printTable(results.skills);
|
|
89
|
+
|
|
90
|
+
log.heading('共享规则');
|
|
91
|
+
printTable(results.rules);
|
|
92
|
+
|
|
93
|
+
log.heading('Claude Code 命令');
|
|
94
|
+
printTable(results.commands);
|
|
95
|
+
|
|
96
|
+
log.heading('Hooks');
|
|
97
|
+
printTable(results.hooks);
|
|
98
|
+
|
|
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
|
+
// Summary
|
|
108
|
+
const installed = results.skills.filter(s => s.status === 'ok').length;
|
|
109
|
+
const total = results.skills.length;
|
|
110
|
+
console.log(`\nSkills: ${installed}/${total} 已安装`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getStatus(dest, expectedSource) {
|
|
114
|
+
if (!existsSync(dest) && !isSymlink(dest)) return 'missing';
|
|
115
|
+
if (isSymlink(dest)) {
|
|
116
|
+
try {
|
|
117
|
+
const actual = readlinkSync(dest);
|
|
118
|
+
if (actual === expectedSource) return 'ok';
|
|
119
|
+
return 'foreign';
|
|
120
|
+
} catch {
|
|
121
|
+
return 'broken';
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return 'file';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function printTable(items) {
|
|
128
|
+
if (items.length === 0) {
|
|
129
|
+
log.skip('(无)');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const maxName = Math.max(...items.map(i => i.name.length), 4);
|
|
134
|
+
for (const item of items) {
|
|
135
|
+
const name = item.name.padEnd(maxName);
|
|
136
|
+
const icon = statusIcon(item.status);
|
|
137
|
+
console.log(` ${icon} ${name} ${item.path}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function statusIcon(status) {
|
|
142
|
+
switch (status) {
|
|
143
|
+
case 'ok': return '\x1b[32m✓\x1b[0m';
|
|
144
|
+
case 'missing': return '\x1b[90m✗\x1b[0m';
|
|
145
|
+
case 'broken': return '\x1b[31m!\x1b[0m';
|
|
146
|
+
case 'foreign': return '\x1b[33m?\x1b[0m';
|
|
147
|
+
case 'file': return '\x1b[36m◆\x1b[0m';
|
|
148
|
+
default: return ' ';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import {
|
|
3
|
+
PACKAGE_ROOT, DEFAULT_SKILLS_TARGET, DEFAULT_COMMANDS_TARGET,
|
|
4
|
+
CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR,
|
|
5
|
+
} from '../lib/constants.js';
|
|
6
|
+
import { discoverSkills, discoverSharedRules, discoverCommands, discoverHooks } from '../lib/inventory.js';
|
|
7
|
+
import { createSymlinkSafe, ensureDir, isSymlink } from '../lib/fs-utils.js';
|
|
8
|
+
import * as log from '../lib/logger.js';
|
|
9
|
+
|
|
10
|
+
export function registerSetup(program) {
|
|
11
|
+
program
|
|
12
|
+
.command('setup')
|
|
13
|
+
.description('Install skills via symlinks to global directories (developer mode)')
|
|
14
|
+
.argument('[target]', 'Target skills directory', DEFAULT_SKILLS_TARGET)
|
|
15
|
+
.option('--no-hooks', 'Skip hook installation')
|
|
16
|
+
.option('--no-commands', 'Skip Claude Code command symlinks')
|
|
17
|
+
.option('--force', 'Overwrite existing symlinks', false)
|
|
18
|
+
.option('--dry-run', 'Show what would be done without doing it', false)
|
|
19
|
+
.action(runSetup);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runSetup(target, opts) {
|
|
23
|
+
const { hooks, commands, force, dryRun } = opts;
|
|
24
|
+
const tag = dryRun ? '[dry-run] ' : '';
|
|
25
|
+
let count = 0;
|
|
26
|
+
|
|
27
|
+
log.heading('安装 Agent Skills');
|
|
28
|
+
const skills = discoverSkills();
|
|
29
|
+
for (const skill of skills) {
|
|
30
|
+
const dest = join(target, skill.name);
|
|
31
|
+
const result = createSymlinkSafe(skill.path, dest, { force, dryRun });
|
|
32
|
+
logResult(`${tag}Skill: ${skill.name}`, result, dest);
|
|
33
|
+
if (result === 'created' || result === 'dry-run') count++;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log.heading('安装共享规则 (_team-rules)');
|
|
37
|
+
const rules = discoverSharedRules();
|
|
38
|
+
const rulesTarget = join(target, '_team-rules');
|
|
39
|
+
if (!dryRun) ensureDir(rulesTarget);
|
|
40
|
+
for (const rule of rules) {
|
|
41
|
+
const dest = join(rulesTarget, rule.name);
|
|
42
|
+
const result = createSymlinkSafe(rule.path, dest, { force, dryRun });
|
|
43
|
+
logResult(`${tag}Rule: ${rule.name}`, result, dest);
|
|
44
|
+
if (result === 'created' || result === 'dry-run') count++;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (commands !== false) {
|
|
48
|
+
log.heading('安装 Commands(Skill 形式 + Claude Code 斜杠命令)');
|
|
49
|
+
const cmds = discoverCommands();
|
|
50
|
+
for (const cmd of cmds) {
|
|
51
|
+
// As Skill directory (for Cursor discovery)
|
|
52
|
+
const skillDest = join(target, cmd.name);
|
|
53
|
+
if (isSymlink(skillDest)) {
|
|
54
|
+
log.skip(`Command Skill ${cmd.name} 跳过:已存在同名 Skill 目录`);
|
|
55
|
+
} else {
|
|
56
|
+
if (!dryRun) ensureDir(skillDest);
|
|
57
|
+
const dest = join(skillDest, 'SKILL.md');
|
|
58
|
+
const result = createSymlinkSafe(cmd.path, dest, { force, dryRun });
|
|
59
|
+
logResult(`${tag}Command Skill: ${cmd.name}`, result, dest);
|
|
60
|
+
if (result === 'created' || result === 'dry-run') count++;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// As Claude Code command
|
|
64
|
+
const cmdDest = join(DEFAULT_COMMANDS_TARGET, cmd.filename);
|
|
65
|
+
if (!dryRun) ensureDir(DEFAULT_COMMANDS_TARGET);
|
|
66
|
+
const result = createSymlinkSafe(cmd.path, cmdDest, { force, dryRun });
|
|
67
|
+
logResult(`${tag}Claude Command: ${cmd.filename}`, result, cmdDest);
|
|
68
|
+
if (result === 'created' || result === 'dry-run') count++;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (hooks !== false) {
|
|
73
|
+
log.heading('安装 Hooks');
|
|
74
|
+
const hookFiles = discoverHooks();
|
|
75
|
+
for (const dir of [CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR]) {
|
|
76
|
+
const platform = dir.includes('.cursor') ? 'Cursor' : 'Claude Code';
|
|
77
|
+
if (!dryRun) ensureDir(dir);
|
|
78
|
+
for (const hook of hookFiles) {
|
|
79
|
+
const dest = join(dir, hook.name);
|
|
80
|
+
const result = createSymlinkSafe(hook.path, dest, { force, dryRun });
|
|
81
|
+
logResult(`${tag}${platform} ${hook.name}`, result, dest);
|
|
82
|
+
if (result === 'created' || result === 'dry-run') count++;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
log.heading('验证安装');
|
|
88
|
+
let errors = 0;
|
|
89
|
+
for (const skill of skills) {
|
|
90
|
+
const dest = join(target, skill.name);
|
|
91
|
+
if (isSymlink(dest)) {
|
|
92
|
+
log.success(`${skill.name}`);
|
|
93
|
+
} else if (!dryRun) {
|
|
94
|
+
log.error(`${skill.name} 未正确安装`);
|
|
95
|
+
errors++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (errors > 0) {
|
|
100
|
+
log.error(`有 ${errors} 个组件安装异常,请检查。`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
log.done(`安装完成!${dryRun ? '(dry-run)' : `共处理 ${count} 个组件。`}`);
|
|
105
|
+
if (!dryRun) {
|
|
106
|
+
console.log('\n后续可通过 team-skills setup 重新安装,或 team-skills uninstall 卸载。');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function logResult(label, result, dest) {
|
|
111
|
+
switch (result) {
|
|
112
|
+
case 'created':
|
|
113
|
+
case 'dry-run':
|
|
114
|
+
log.success(label);
|
|
115
|
+
break;
|
|
116
|
+
case 'exists':
|
|
117
|
+
log.skip(`${label}(已存在,跳过)`);
|
|
118
|
+
break;
|
|
119
|
+
case 'conflict':
|
|
120
|
+
log.warn(`${label}(已存在非 symlink,使用 --force 覆盖)`);
|
|
121
|
+
break;
|
|
122
|
+
default:
|
|
123
|
+
log.info(label);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readdirSync } from 'node:fs';
|
|
3
|
+
import {
|
|
4
|
+
PACKAGE_ROOT, DEFAULT_SKILLS_TARGET, DEFAULT_COMMANDS_TARGET,
|
|
5
|
+
CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR,
|
|
6
|
+
} from '../lib/constants.js';
|
|
7
|
+
import { discoverSkills, discoverSharedRules, discoverCommands, discoverHooks } from '../lib/inventory.js';
|
|
8
|
+
import { removeSymlinkSafe, rmdirIfEmpty } from '../lib/fs-utils.js';
|
|
9
|
+
import * as log from '../lib/logger.js';
|
|
10
|
+
|
|
11
|
+
export function registerUninstall(program) {
|
|
12
|
+
program
|
|
13
|
+
.command('uninstall')
|
|
14
|
+
.description('Remove all team-skills symlinks')
|
|
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')
|
|
18
|
+
.option('--dry-run', 'Show what would be removed', false)
|
|
19
|
+
.action(runUninstall);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runUninstall(target, opts) {
|
|
23
|
+
const { keepHooks, keepCommands, dryRun } = opts;
|
|
24
|
+
let removed = 0;
|
|
25
|
+
|
|
26
|
+
log.heading('移除 Agent Skills');
|
|
27
|
+
for (const skill of discoverSkills()) {
|
|
28
|
+
const dest = join(target, skill.name);
|
|
29
|
+
if (dryRun) {
|
|
30
|
+
log.info(`[dry-run] 将移除: ${dest}`);
|
|
31
|
+
removed++;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const result = removeSymlinkSafe(dest, skill.path);
|
|
35
|
+
if (result === 'removed') {
|
|
36
|
+
log.success(`Skill: ${skill.name}`);
|
|
37
|
+
removed++;
|
|
38
|
+
} else if (result === 'foreign') {
|
|
39
|
+
log.skip(`${skill.name}(指向其他来源,跳过)`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
log.heading('移除共享规则');
|
|
44
|
+
for (const rule of discoverSharedRules()) {
|
|
45
|
+
const dest = join(target, '_team-rules', rule.name);
|
|
46
|
+
if (dryRun) {
|
|
47
|
+
log.info(`[dry-run] 将移除: ${dest}`);
|
|
48
|
+
removed++;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const result = removeSymlinkSafe(dest, rule.path);
|
|
52
|
+
if (result === 'removed') {
|
|
53
|
+
log.success(`Rule: ${rule.name}`);
|
|
54
|
+
removed++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (!dryRun) rmdirIfEmpty(join(target, '_team-rules'));
|
|
58
|
+
|
|
59
|
+
if (!keepCommands) {
|
|
60
|
+
log.heading('移除 Command Skills + Claude Code 命令');
|
|
61
|
+
for (const cmd of discoverCommands()) {
|
|
62
|
+
// Command Skill directory
|
|
63
|
+
const skillDest = join(target, cmd.name, 'SKILL.md');
|
|
64
|
+
if (!dryRun) {
|
|
65
|
+
const result = removeSymlinkSafe(skillDest, cmd.path);
|
|
66
|
+
if (result === 'removed') {
|
|
67
|
+
log.success(`Command Skill: ${cmd.name}`);
|
|
68
|
+
removed++;
|
|
69
|
+
rmdirIfEmpty(join(target, cmd.name));
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
log.info(`[dry-run] 将移除: ${skillDest}`);
|
|
73
|
+
removed++;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Claude Code command
|
|
77
|
+
const cmdDest = join(DEFAULT_COMMANDS_TARGET, cmd.filename);
|
|
78
|
+
if (!dryRun) {
|
|
79
|
+
const result = removeSymlinkSafe(cmdDest, cmd.path);
|
|
80
|
+
if (result === 'removed') {
|
|
81
|
+
log.success(`Claude Command: ${cmd.filename}`);
|
|
82
|
+
removed++;
|
|
83
|
+
}
|
|
84
|
+
} else {
|
|
85
|
+
log.info(`[dry-run] 将移除: ${cmdDest}`);
|
|
86
|
+
removed++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!keepHooks) {
|
|
92
|
+
log.heading('移除 Hooks');
|
|
93
|
+
const hookFiles = discoverHooks();
|
|
94
|
+
for (const dir of [CURSOR_HOOKS_DIR, CLAUDE_HOOKS_DIR]) {
|
|
95
|
+
for (const hook of hookFiles) {
|
|
96
|
+
const dest = join(dir, hook.name);
|
|
97
|
+
if (!dryRun) {
|
|
98
|
+
const result = removeSymlinkSafe(dest, hook.path);
|
|
99
|
+
if (result === 'removed') {
|
|
100
|
+
log.success(`Hook: ${dest}`);
|
|
101
|
+
removed++;
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
log.info(`[dry-run] 将移除: ${dest}`);
|
|
105
|
+
removed++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
log.done(`卸载完成${dryRun ? ' (dry-run)' : ''},共移除 ${removed} 个软链接。`);
|
|
112
|
+
if (!dryRun) console.log('本仓库源文件未受影响。');
|
|
113
|
+
}
|