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.
Files changed (56) hide show
  1. package/.claude/commands/team-pull.md +21 -0
  2. package/.claude/commands/team-push.md +28 -0
  3. package/.claude/commands/team-setup.md +183 -0
  4. package/.claude/commands/team-uninstall.md +107 -0
  5. package/CHANGELOG.md +41 -0
  6. package/LICENSE +21 -0
  7. package/README.md +421 -0
  8. package/bin/team-skills.js +2 -0
  9. package/hooks/hooks.json +16 -0
  10. package/hooks/session-start +34 -0
  11. package/package.json +58 -0
  12. package/scripts/check-skill-structure.js +89 -0
  13. package/skills/CLAUDE.md +121 -0
  14. package/skills/_team-rules/constitutional-rules.md +25 -0
  15. package/skills/_team-rules/four-state-protocol.md +10 -0
  16. package/skills/_team-rules/verification-protocol.md +55 -0
  17. package/skills/team-brainstorm/SKILL.md +168 -0
  18. package/skills/team-debug/SKILL.md +143 -0
  19. package/skills/team-feedback/SKILL.md +175 -0
  20. package/skills/team-finish/SKILL.md +151 -0
  21. package/skills/team-impl/SKILL.md +316 -0
  22. package/skills/team-impl/references/06-tdd-log-template.md +62 -0
  23. package/skills/team-impl/references/07-prompt-log-template.md +32 -0
  24. package/skills/team-impl/references/08-ai-decisions-template.md +16 -0
  25. package/skills/team-orchestrator/SKILL.md +584 -0
  26. package/skills/team-orchestrator/references/14-team-template.md +70 -0
  27. package/skills/team-orchestrator/references/15-brief-template.md +50 -0
  28. package/skills/team-review/SKILL.md +383 -0
  29. package/skills/team-review/references/11-review-template.md +63 -0
  30. package/skills/team-review/references/12-asset-update-template.md +45 -0
  31. package/skills/team-review/references/13-retrospective-template.md +40 -0
  32. package/skills/team-score/SKILL.md +330 -0
  33. package/skills/team-spec/SKILL.md +231 -0
  34. package/skills/team-spec/references/01-plan-template.md +67 -0
  35. package/skills/team-spec/references/02-context-template.md +46 -0
  36. package/skills/team-spec/references/04-boundary-template.md +23 -0
  37. package/skills/team-spec/references/05-risk-template.md +50 -0
  38. package/skills/team-spec/references/delta-spec-template.md +32 -0
  39. package/skills/team-spec/references/prompt-template.md +23 -0
  40. package/skills/team-spec/references/sdd-template.md +72 -0
  41. package/skills/team-test/SKILL.md +190 -0
  42. package/skills/team-test/references/09-test-matrix-template.md +40 -0
  43. package/skills/team-test/references/10-test-report-template.md +59 -0
  44. package/skills/team-verify/SKILL.md +151 -0
  45. package/skills/using-team-skills/SKILL.md +137 -0
  46. package/src/cli.js +27 -0
  47. package/src/commands/init.js +115 -0
  48. package/src/commands/list.js +150 -0
  49. package/src/commands/setup.js +125 -0
  50. package/src/commands/uninstall.js +113 -0
  51. package/src/commands/update.js +118 -0
  52. package/src/lib/constants.js +17 -0
  53. package/src/lib/fs-utils.js +117 -0
  54. package/src/lib/inventory.js +64 -0
  55. package/src/lib/logger.js +34 -0
  56. package/src/lib/manifest.js +45 -0
@@ -0,0 +1,118 @@
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';
6
+ import * as log from '../lib/logger.js';
7
+ import { dirname } from 'node:path';
8
+
9
+ export function registerUpdate(program) {
10
+ program
11
+ .command('update')
12
+ .description('Update previously init\'d skills to latest version')
13
+ .argument('[dir]', 'Project directory', '.')
14
+ .option('--force', 'Overwrite locally modified files (creates .bak backup)', false)
15
+ .option('--dry-run', 'Show what would change', false)
16
+ .action(runUpdate);
17
+ }
18
+
19
+ function runUpdate(dir, opts) {
20
+ const { force, dryRun } = opts;
21
+ const installDir = join(dir, LOCAL_INSTALL_DIR);
22
+ const manifest = readManifest(installDir);
23
+
24
+ if (!manifest) {
25
+ log.error(`未找到 ${installDir}/manifest.json。请先运行 team-skills init。`);
26
+ process.exit(1);
27
+ }
28
+
29
+ const currentVersion = getPackageVersion();
30
+ log.info(`已安装版本: ${manifest.version} | 最新版本: ${currentVersion}`);
31
+
32
+ const tag = dryRun ? '[dry-run] ' : '';
33
+ const sourceMap = buildSourceMap();
34
+
35
+ let updated = 0;
36
+ let skipped = 0;
37
+ let added = 0;
38
+
39
+ log.heading('检查文件更新');
40
+
41
+ for (const [relPath, sourceFullPath] of Object.entries(sourceMap)) {
42
+ const installedPath = join(installDir, relPath);
43
+ const sourceHash = computeFileHash(sourceFullPath);
44
+
45
+ if (!existsSync(installedPath)) {
46
+ if (!dryRun) {
47
+ ensureDir(dirname(installedPath));
48
+ fsCopyFile(sourceFullPath, installedPath);
49
+ }
50
+ log.success(`${tag}新增: ${relPath}`);
51
+ added++;
52
+ continue;
53
+ }
54
+
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;
63
+ }
64
+
65
+ if (!dryRun) {
66
+ if (force && manifestHash && installedHash !== manifestHash) {
67
+ fsCopyFile(installedPath, installedPath + '.bak');
68
+ log.info(`备份: ${relPath}.bak`);
69
+ }
70
+ fsCopyFile(sourceFullPath, installedPath);
71
+ }
72
+ log.success(`${tag}更新: ${relPath}`);
73
+ updated++;
74
+ }
75
+
76
+ for (const relPath of Object.keys(manifest.files)) {
77
+ if (!sourceMap[relPath]) {
78
+ log.warn(`源文件已删除: ${relPath}(如不再需要请手动删除)`);
79
+ }
80
+ }
81
+
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
+ }
118
+ }
@@ -0,0 +1,17 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { dirname, join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
8
+ export const PACKAGE_ROOT = join(__dirname, '..', '..');
9
+ export const DEFAULT_SKILLS_TARGET = join(homedir(), '.agents', 'skills');
10
+ export const DEFAULT_COMMANDS_TARGET = join(homedir(), '.claude', 'commands');
11
+ export const CURSOR_HOOKS_DIR = join(homedir(), '.cursor', 'hooks');
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
+ export const SKILLS_DIR = 'skills';
16
+ export const HOOKS_DIR = 'hooks';
17
+ export const COMMANDS_DIR = join('.claude', 'commands');
@@ -0,0 +1,117 @@
1
+ import {
2
+ mkdirSync, symlinkSync, unlinkSync, readlinkSync,
3
+ lstatSync, existsSync, readdirSync, statSync,
4
+ copyFileSync, readFileSync, chmodSync, rmSync,
5
+ } from 'node:fs';
6
+ import { join, dirname } from 'node:path';
7
+ import { createHash } from 'node:crypto';
8
+
9
+ export function ensureDir(dir) {
10
+ mkdirSync(dir, { recursive: true });
11
+ }
12
+
13
+ export function isSymlink(p) {
14
+ try {
15
+ return lstatSync(p).isSymbolicLink();
16
+ } catch {
17
+ return false;
18
+ }
19
+ }
20
+
21
+ export function isSymlinkTo(p, target) {
22
+ try {
23
+ return lstatSync(p).isSymbolicLink() && readlinkSync(p) === target;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ export function createSymlinkSafe(source, target, { force = false, dryRun = false } = {}) {
30
+ if (dryRun) return 'dry-run';
31
+
32
+ if (isSymlink(target)) {
33
+ if (readlinkSync(target) === source) return 'exists';
34
+ if (!force) return 'conflict';
35
+ unlinkSync(target);
36
+ } else if (existsSync(target)) {
37
+ if (!force) return 'conflict';
38
+ unlinkSync(target);
39
+ }
40
+
41
+ ensureDir(dirname(target));
42
+
43
+ const isDir = statSync(source).isDirectory();
44
+ symlinkSync(source, target, isDir ? 'dir' : 'file');
45
+ return 'created';
46
+ }
47
+
48
+ export function removeSymlinkSafe(target, expectedSource) {
49
+ if (!isSymlink(target)) return 'not-found';
50
+
51
+ if (expectedSource) {
52
+ const actual = readlinkSync(target);
53
+ if (actual !== expectedSource) return 'foreign';
54
+ }
55
+
56
+ unlinkSync(target);
57
+ return 'removed';
58
+ }
59
+
60
+ export function computeFileHash(filePath) {
61
+ const content = readFileSync(filePath);
62
+ return createHash('sha256').update(content).digest('hex');
63
+ }
64
+
65
+ export function copyRecursive(source, target) {
66
+ ensureDir(target);
67
+ const entries = readdirSync(source, { withFileTypes: true });
68
+
69
+ for (const entry of entries) {
70
+ const srcPath = join(source, entry.name);
71
+ const dstPath = join(target, entry.name);
72
+
73
+ if (entry.isDirectory()) {
74
+ copyRecursive(srcPath, dstPath);
75
+ } else {
76
+ copyFileSync(srcPath, dstPath);
77
+ try {
78
+ const srcStat = statSync(srcPath);
79
+ chmodSync(dstPath, srcStat.mode);
80
+ } catch {
81
+ // best-effort permission copy
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ export function computeDirectoryHashes(dirPath, prefix = '') {
88
+ const hashes = {};
89
+ if (!existsSync(dirPath)) return hashes;
90
+
91
+ const entries = readdirSync(dirPath, { withFileTypes: true });
92
+ for (const entry of entries) {
93
+ const fullPath = join(dirPath, entry.name);
94
+ const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
95
+
96
+ if (entry.isDirectory()) {
97
+ Object.assign(hashes, computeDirectoryHashes(fullPath, relPath));
98
+ } else {
99
+ hashes[relPath] = computeFileHash(fullPath);
100
+ }
101
+ }
102
+
103
+ return hashes;
104
+ }
105
+
106
+ export function rmdirIfEmpty(dir) {
107
+ try {
108
+ const entries = readdirSync(dir);
109
+ if (entries.length === 0) {
110
+ rmSync(dir);
111
+ return true;
112
+ }
113
+ } catch {
114
+ // ignore
115
+ }
116
+ return false;
117
+ }
@@ -0,0 +1,64 @@
1
+ import { readdirSync, statSync, existsSync } from 'node:fs';
2
+ import { join, basename } from 'node:path';
3
+ import { PACKAGE_ROOT, SKILLS_DIR, HOOKS_DIR, COMMANDS_DIR } from './constants.js';
4
+
5
+ export function discoverSkills(root = PACKAGE_ROOT) {
6
+ const skillsDir = join(root, SKILLS_DIR);
7
+ if (!existsSync(skillsDir)) return [];
8
+
9
+ return readdirSync(skillsDir)
10
+ .filter(name => {
11
+ if (name.startsWith('_') || name === 'CLAUDE.md') return false;
12
+ const full = join(skillsDir, name);
13
+ return statSync(full).isDirectory();
14
+ })
15
+ .map(name => ({
16
+ name,
17
+ path: join(skillsDir, name),
18
+ hasReferences: existsSync(join(skillsDir, name, 'references')),
19
+ }));
20
+ }
21
+
22
+ export function discoverSharedRules(root = PACKAGE_ROOT) {
23
+ const rulesDir = join(root, SKILLS_DIR, '_team-rules');
24
+ if (!existsSync(rulesDir)) return [];
25
+
26
+ return readdirSync(rulesDir)
27
+ .filter(name => name.endsWith('.md'))
28
+ .map(name => ({
29
+ name,
30
+ path: join(rulesDir, name),
31
+ }));
32
+ }
33
+
34
+ export function discoverCommands(root = PACKAGE_ROOT) {
35
+ const cmdDir = join(root, COMMANDS_DIR);
36
+ if (!existsSync(cmdDir)) return [];
37
+
38
+ return readdirSync(cmdDir)
39
+ .filter(name => name.endsWith('.md'))
40
+ .map(name => ({
41
+ name: basename(name, '.md'),
42
+ filename: name,
43
+ path: join(cmdDir, name),
44
+ }));
45
+ }
46
+
47
+ export function discoverHooks(root = PACKAGE_ROOT) {
48
+ const hooksDir = join(root, HOOKS_DIR);
49
+ const files = [];
50
+
51
+ for (const name of ['hooks.json', 'session-start']) {
52
+ const full = join(hooksDir, name);
53
+ if (existsSync(full)) {
54
+ files.push({ name, path: full });
55
+ }
56
+ }
57
+
58
+ return files;
59
+ }
60
+
61
+ export function discoverSkillsModuleClaude(root = PACKAGE_ROOT) {
62
+ const p = join(root, SKILLS_DIR, 'CLAUDE.md');
63
+ return existsSync(p) ? p : null;
64
+ }
@@ -0,0 +1,34 @@
1
+ const RESET = '\x1b[0m';
2
+ const GREEN = '\x1b[32m';
3
+ const YELLOW = '\x1b[33m';
4
+ const RED = '\x1b[31m';
5
+ const BLUE = '\x1b[34m';
6
+ const GRAY = '\x1b[90m';
7
+
8
+ export function success(msg) {
9
+ console.log(`${GREEN}✅${RESET} ${msg}`);
10
+ }
11
+
12
+ export function warn(msg) {
13
+ console.log(`${YELLOW}⚠️${RESET} ${msg}`);
14
+ }
15
+
16
+ export function error(msg) {
17
+ console.error(`${RED}❌${RESET} ${msg}`);
18
+ }
19
+
20
+ export function info(msg) {
21
+ console.log(`${BLUE}ℹ${RESET} ${msg}`);
22
+ }
23
+
24
+ export function skip(msg) {
25
+ console.log(`${GRAY}⏭${RESET} ${msg}`);
26
+ }
27
+
28
+ export function heading(msg) {
29
+ console.log(`\n${BLUE}===${RESET} ${msg} ${BLUE}===${RESET}`);
30
+ }
31
+
32
+ export function done(msg) {
33
+ console.log(`\n${GREEN}🎉${RESET} ${msg}`);
34
+ }
@@ -0,0 +1,45 @@
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
+ }