@colin4k1024/tsp 2.5.0 → 2.5.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.
@@ -0,0 +1,361 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const PLUGIN_NAME = require('../team-skills-data.json').plugin.name;
6
+
7
+ /**
8
+ * 读取 Markdown 文件内容
9
+ */
10
+ function readMarkdownFile(filePath) {
11
+ try {
12
+ return fs.readFileSync(filePath, 'utf8');
13
+ } catch (error) {
14
+ console.warn(`Warning: Could not read ${filePath}: ${error.message}`);
15
+ return '';
16
+ }
17
+ }
18
+
19
+ /**
20
+ * 扫描目录中的所有 .md 文件
21
+ */
22
+ function scanMarkdownFiles(dirPath, prefix = '') {
23
+ const files = [];
24
+
25
+ if (!fs.existsSync(dirPath)) {
26
+ return files;
27
+ }
28
+
29
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
30
+
31
+ for (const entry of entries) {
32
+ const fullPath = path.join(dirPath, entry.name);
33
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
34
+
35
+ if (entry.isFile() && entry.name.endsWith('.md')) {
36
+ files.push({
37
+ path: relativePath,
38
+ fullPath,
39
+ name: entry.name,
40
+ });
41
+ } else if (entry.isDirectory()) {
42
+ files.push(...scanMarkdownFiles(fullPath, relativePath));
43
+ }
44
+ }
45
+
46
+ return files;
47
+ }
48
+
49
+ /**
50
+ * 生成规则索引部分
51
+ */
52
+ function generateRulesIndex(rulesDir) {
53
+ const lines = [];
54
+ const commonDir = path.join(rulesDir, 'common');
55
+ const zhDir = path.join(rulesDir, 'zh');
56
+
57
+ lines.push('## 规则索引');
58
+ lines.push('');
59
+ lines.push('本项目包含以下规则文件,可通过 `@path` 语法引用:');
60
+ lines.push('');
61
+
62
+ // 通用规则
63
+ if (fs.existsSync(commonDir)) {
64
+ lines.push('### 通用规则 (rules/common/)');
65
+ lines.push('');
66
+ const commonFiles = scanMarkdownFiles(commonDir, 'rules/common');
67
+ for (const file of commonFiles) {
68
+ const content = readMarkdownFile(file.fullPath);
69
+ const title = extractTitle(content, file.name);
70
+ lines.push(`- **${title}**: \`@${file.path}\``);
71
+ }
72
+ lines.push('');
73
+ }
74
+
75
+ // 中文规则
76
+ if (fs.existsSync(zhDir)) {
77
+ lines.push('### 中文规则 (rules/zh/)');
78
+ lines.push('');
79
+ const zhFiles = scanMarkdownFiles(zhDir, 'rules/zh');
80
+ for (const file of zhFiles) {
81
+ const content = readMarkdownFile(file.fullPath);
82
+ const title = extractTitle(content, file.name);
83
+ lines.push(`- **${title}**: \`@${file.path}\``);
84
+ }
85
+ lines.push('');
86
+ }
87
+
88
+ // 语言特定规则
89
+ const languageDirs = ['typescript', 'python', 'golang', 'java', 'kotlin', 'rust', 'swift', 'cpp', 'csharp', 'php', 'perl'];
90
+ for (const lang of languageDirs) {
91
+ const langDir = path.join(rulesDir, lang);
92
+ if (fs.existsSync(langDir)) {
93
+ lines.push(`### ${lang.charAt(0).toUpperCase() + lang.slice(1)} 规则 (rules/${lang}/)`);
94
+ lines.push('');
95
+ const langFiles = scanMarkdownFiles(langDir, `rules/${lang}`);
96
+ for (const file of langFiles) {
97
+ const content = readMarkdownFile(file.fullPath);
98
+ const title = extractTitle(content, file.name);
99
+ lines.push(`- **${title}**: \`@${file.path}\``);
100
+ }
101
+ lines.push('');
102
+ }
103
+ }
104
+
105
+ return lines;
106
+ }
107
+
108
+ /**
109
+ * 从 Markdown 内容中提取标题
110
+ */
111
+ function extractTitle(content, fallback) {
112
+ for (const line of content.split(/\r?\n/)) {
113
+ const trimmed = line.trim();
114
+ if (trimmed.startsWith('# ')) {
115
+ return trimmed.slice(2).trim();
116
+ }
117
+ }
118
+ return fallback.replace('.md', '');
119
+ }
120
+
121
+ /**
122
+ * 生成角色索引部分
123
+ */
124
+ function generateRolesIndex(agentsDir) {
125
+ const lines = [];
126
+ const rolesDir = path.join(agentsDir, 'roles');
127
+
128
+ if (!fs.existsSync(rolesDir)) {
129
+ return lines;
130
+ }
131
+
132
+ lines.push('## 可用角色');
133
+ lines.push('');
134
+
135
+ const roleDisplay = {
136
+ 'tech-lead': 'Tech Lead(技术负责人)',
137
+ 'product-manager': 'Product Manager(产品经理)',
138
+ 'project-manager': 'Project Manager(项目管理)',
139
+ 'architect': 'Architect(架构师)',
140
+ 'frontend-engineer': 'Frontend Engineer(前端开发)',
141
+ 'backend-engineer': 'Backend Engineer(后端开发)',
142
+ 'qa-engineer': 'QA Engineer(测试工程师)',
143
+ 'devops-engineer': 'DevOps Engineer(运维工程师)',
144
+ };
145
+
146
+ const roleFiles = fs.readdirSync(rolesDir)
147
+ .filter(name => name.endsWith('.md'))
148
+ .sort();
149
+
150
+ for (const roleFile of roleFiles) {
151
+ const roleName = path.parse(roleFile).name;
152
+ const displayName = roleDisplay[roleName] || roleName;
153
+ lines.push(`- **${displayName}**: \`plugins/${PLUGIN_NAME}/agents/roles/${roleFile}\``);
154
+ }
155
+
156
+ lines.push('');
157
+ return lines;
158
+ }
159
+
160
+ /**
161
+ * 生成命令索引部分
162
+ */
163
+ function generateCommandsIndex(commandsDir) {
164
+ const lines = [];
165
+
166
+ if (!fs.existsSync(commandsDir)) {
167
+ return lines;
168
+ }
169
+
170
+ lines.push('## 核心团队命令');
171
+ lines.push('');
172
+ lines.push('| 命令 | 用途 |');
173
+ lines.push('|------|------|');
174
+
175
+ const commandFiles = fs.readdirSync(commandsDir)
176
+ .filter(name => name.endsWith('.md'))
177
+ .sort();
178
+
179
+ const commandDescriptions = {
180
+ 'team-help': '根据当前阶段、artifacts 与阻塞项推荐下一步主链命令',
181
+ 'team-intake': '接收需求并锁定目标、范围、约束',
182
+ 'team-plan': '拆解任务、角色分工、依赖与里程碑',
183
+ 'team-execute': '驱动研发角色在边界内实施',
184
+ 'team-review': '做方案、质量、测试和放行评审',
185
+ 'team-release': '做发布准备、上线检查与回滚保障',
186
+ 'team-closeout': '在观察窗口结束后做最终收口与 backlog 回写',
187
+ 'handoff': '在角色间做结构化交接',
188
+ 'plan': '创建实现计划',
189
+ 'tdd': '测试驱动开发',
190
+ 'code-review': '代码审查',
191
+ 'build-fix': '修复构建错误',
192
+ 'verify': '验证实现',
193
+ };
194
+
195
+ for (const commandFile of commandFiles) {
196
+ const commandName = path.parse(commandFile).name;
197
+ const description = commandDescriptions[commandName] || '团队命令';
198
+ lines.push(`| \`/${commandName}\` | ${description} |`);
199
+ }
200
+
201
+ lines.push('');
202
+ return lines;
203
+ }
204
+
205
+ /**
206
+ * 生成技能索引部分
207
+ */
208
+ function generateSkillsIndex(skillsDir) {
209
+ const lines = [];
210
+
211
+ if (!fs.existsSync(skillsDir)) {
212
+ return lines;
213
+ }
214
+
215
+ lines.push('## 可用技能');
216
+ lines.push('');
217
+ lines.push('以下技能可通过 `skill` 工具加载:');
218
+ lines.push('');
219
+
220
+ const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
221
+ .filter(entry => entry.isDirectory())
222
+ .map(entry => entry.name)
223
+ .sort();
224
+
225
+ for (const skillDir of skillDirs) {
226
+ const skillFile = path.join(skillsDir, skillDir, 'SKILL.md');
227
+ if (fs.existsSync(skillFile)) {
228
+ const content = readMarkdownFile(skillFile);
229
+ const title = extractTitle(content, skillDir);
230
+ lines.push(`- **${title}**: \`skills/${skillDir}/SKILL.md\``);
231
+ }
232
+ }
233
+
234
+ lines.push('');
235
+ return lines;
236
+ }
237
+
238
+ /**
239
+ * 生成完整的 AGENTS.md 内容
240
+ */
241
+ function generateAgentsMd(root) {
242
+ const lines = [];
243
+
244
+ // 头部标记
245
+ lines.push('<!-- team-skills-platform -->');
246
+ lines.push('# Team Skills Platform — OpenCode 配置');
247
+ lines.push('');
248
+ lines.push('本文件由安装脚本自动生成,包含 TSP 平台的完整配置和规则。');
249
+ lines.push('');
250
+
251
+ // 插件根路径
252
+ lines.push('## 插件路径');
253
+ lines.push('');
254
+ lines.push(`- 插件根目录: \`~/.config/opencode/plugins/${PLUGIN_NAME}/\``);
255
+ lines.push(`- 规则目录: \`~/.config/opencode/plugins/${PLUGIN_NAME}/rules/\``);
256
+ lines.push(`- 技能目录: \`~/.config/opencode/plugins/${PLUGIN_NAME}/skills/\``);
257
+ lines.push(`- 命令目录: \`~/.config/opencode/command/\``);
258
+ lines.push(`- Agent 目录: \`~/.config/opencode/agents/\``);
259
+ lines.push('');
260
+
261
+ // 规则索引
262
+ const rulesDir = path.join(root, 'rules');
263
+ lines.push(...generateRulesIndex(rulesDir));
264
+
265
+ // 角色索引
266
+ const agentsDir = path.join(root, 'agents');
267
+ lines.push(...generateRolesIndex(agentsDir));
268
+
269
+ // 命令索引
270
+ const commandsDir = path.join(root, 'commands');
271
+ lines.push(...generateCommandsIndex(commandsDir));
272
+
273
+ // 技能索引
274
+ const skillsDir = path.join(root, 'skills');
275
+ lines.push(...generateSkillsIndex(skillsDir));
276
+
277
+ // 使用说明
278
+ lines.push('## 使用说明');
279
+ lines.push('');
280
+ lines.push('1. **引用规则**: 使用 `@rules/common/coding-style.md` 语法引用特定规则');
281
+ lines.push('2. **加载技能**: 使用 `skill` 工具加载 `SKILL.md` 文件');
282
+ lines.push('3. **执行命令**: 使用 `/team-intake` 等命令执行团队工作流');
283
+ lines.push('4. **切换角色**: 使用 `@tech-lead` 等方式切换到特定角色');
284
+ lines.push('');
285
+
286
+ // 尾部标记
287
+ lines.push(`<!-- end ${PLUGIN_NAME} -->`);
288
+
289
+ return `${lines.join('\n')}\n`;
290
+ }
291
+
292
+ /**
293
+ * 合并 AGENTS.md 内容
294
+ */
295
+ function mergeAgentsMd(targetPath, newContent) {
296
+ const markerEnd = `<!-- end ${PLUGIN_NAME} -->`;
297
+
298
+ if (!fs.existsSync(targetPath)) {
299
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
300
+ fs.writeFileSync(targetPath, newContent, 'utf8');
301
+ return;
302
+ }
303
+
304
+ const existing = fs.readFileSync(targetPath, 'utf8');
305
+ if (existing.includes('<!-- team-skills-platform -->')) {
306
+ const startIdx = existing.indexOf('<!-- team-skills-platform -->');
307
+ let endIdx = existing.indexOf(markerEnd, startIdx);
308
+ if (endIdx !== -1) {
309
+ endIdx += markerEnd.length;
310
+ if (existing[endIdx] === '\n') {
311
+ endIdx += 1;
312
+ }
313
+ fs.writeFileSync(targetPath, `${existing.slice(0, startIdx)}${newContent}`, 'utf8');
314
+ return;
315
+ }
316
+ }
317
+
318
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
319
+ fs.writeFileSync(targetPath, `${existing}${separator}${newContent}`, 'utf8');
320
+ }
321
+
322
+ /**
323
+ * 主函数
324
+ */
325
+ function main() {
326
+ const root = path.join(__dirname, '../../..');
327
+ const opencodeHome = process.argv[2] || path.join(require('os').homedir(), '.config', 'opencode');
328
+
329
+ console.log('Generating OpenCode AGENTS.md...');
330
+ console.log(`Root: ${root}`);
331
+ console.log(`Target: ${opencodeHome}`);
332
+
333
+ const content = generateAgentsMd(root);
334
+ const targetPath = path.join(opencodeHome, 'AGENTS.md');
335
+
336
+ mergeAgentsMd(targetPath, content);
337
+
338
+ console.log(`✅ Generated AGENTS.md at ${targetPath}`);
339
+ console.log(` Size: ${content.length} characters`);
340
+ console.log(` Lines: ${content.split('\n').length}`);
341
+ }
342
+
343
+ // 导出函数供其他脚本使用
344
+ module.exports = {
345
+ generateAgentsMd,
346
+ mergeAgentsMd,
347
+ generateRulesIndex,
348
+ generateRolesIndex,
349
+ generateCommandsIndex,
350
+ generateSkillsIndex,
351
+ };
352
+
353
+ // 如果直接运行此脚本
354
+ if (require.main === module) {
355
+ try {
356
+ main();
357
+ } catch (error) {
358
+ console.error('Error generating AGENTS.md:', error.message);
359
+ process.exitCode = 1;
360
+ }
361
+ }
@@ -0,0 +1,151 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const OPENCODE_HOME = path.join(os.homedir(), '.config', 'opencode');
7
+ const PLUGIN_DIR = path.join(OPENCODE_HOME, 'plugins', 'team-skills-platform');
8
+
9
+ console.log('=== OpenCode 安装验证 ===\n');
10
+
11
+ let passed = 0;
12
+ let failed = 0;
13
+
14
+ function check(description, condition) {
15
+ if (condition) {
16
+ console.log(`✅ ${description}`);
17
+ passed++;
18
+ } else {
19
+ console.log(`❌ ${description}`);
20
+ failed++;
21
+ }
22
+ }
23
+
24
+ // 检查目录结构
25
+ console.log('📁 目录结构检查:');
26
+ check('OPENCODE_HOME 目录存在', fs.existsSync(OPENCODE_HOME));
27
+ check('AGENTS.md 文件存在', fs.existsSync(path.join(OPENCODE_HOME, 'AGENTS.md')));
28
+ check('opencode.json 文件存在', fs.existsSync(path.join(OPENCODE_HOME, 'opencode.json')));
29
+ check('agents 目录存在', fs.existsSync(path.join(OPENCODE_HOME, 'agents')));
30
+ check('command 目录存在', fs.existsSync(path.join(OPENCODE_HOME, 'command')));
31
+ check('plugins 目录存在', fs.existsSync(path.join(OPENCODE_HOME, 'plugins')));
32
+ check('team-skills-platform 插件存在', fs.existsSync(PLUGIN_DIR));
33
+ check('tsp-hooks.js 插件存在', fs.existsSync(path.join(OPENCODE_HOME, 'plugins', 'tsp-hooks.js')));
34
+
35
+ console.log('\n📄 文件内容检查:');
36
+
37
+ // 检查 AGENTS.md
38
+ const agentsMdPath = path.join(OPENCODE_HOME, 'AGENTS.md');
39
+ if (fs.existsSync(agentsMdPath)) {
40
+ const content = fs.readFileSync(agentsMdPath, 'utf8');
41
+ check('AGENTS.md 包含团队技能平台标记', content.includes('<!-- team-skills-platform -->'));
42
+ check('AGENTS.md 包含规则索引', content.includes('## 规则索引'));
43
+ check('AGENTS.md 包含角色索引', content.includes('## 可用角色'));
44
+ check('AGENTS.md 包含命令索引', content.includes('## 核心团队命令'));
45
+ check('AGENTS.md 包含技能索引', content.includes('## 可用技能'));
46
+ }
47
+
48
+ // 检查 opencode.json
49
+ const configPath = path.join(OPENCODE_HOME, 'opencode.json');
50
+ if (fs.existsSync(configPath)) {
51
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
52
+ check('opencode.json 包含 instructions', Array.isArray(config.instructions));
53
+ check('opencode.json 包含 plugin', Array.isArray(config.plugin));
54
+ check('opencode.json 包含 permission', typeof config.permission === 'object');
55
+ }
56
+
57
+ console.log('\n👥 Agents 检查:');
58
+ const agentsDir = path.join(OPENCODE_HOME, 'agents');
59
+ if (fs.existsSync(agentsDir)) {
60
+ const agentFiles = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));
61
+ check(`agents 目录包含文件 (${agentFiles.length})`, agentFiles.length > 0);
62
+
63
+ // 检查是否有角色 agents
64
+ const roleAgents = ['tech-lead.md', 'product-manager.md', 'architect.md', 'frontend-engineer.md',
65
+ 'backend-engineer.md', 'qa-engineer.md', 'devops-engineer.md'];
66
+ for (const agent of roleAgents) {
67
+ check(`角色 agent ${agent} 存在`, fs.existsSync(path.join(agentsDir, agent)));
68
+ }
69
+
70
+ // 检查是否有 specialist agents
71
+ const specialistAgents = agentFiles.filter(f => f.startsWith('specialist-'));
72
+ check(`specialist agents 存在 (${specialistAgents.length})`, specialistAgents.length > 0);
73
+
74
+ // 检查 agent 文件格式
75
+ if (agentFiles.length > 0) {
76
+ const sampleAgent = fs.readFileSync(path.join(agentsDir, agentFiles[0]), 'utf8');
77
+ check('agent 文件包含 YAML front matter', sampleAgent.startsWith('---'));
78
+ }
79
+ }
80
+
81
+ console.log('\n📝 Commands 检查:');
82
+ const commandsDir = path.join(OPENCODE_HOME, 'command');
83
+ if (fs.existsSync(commandsDir)) {
84
+ const commandFiles = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
85
+ check(`commands 目录包含文件 (${commandFiles.length})`, commandFiles.length > 0);
86
+
87
+ // 检查核心命令
88
+ const coreCommands = ['team-intake.md', 'team-plan.md', 'team-execute.md', 'team-review.md',
89
+ 'team-release.md', 'handoff.md'];
90
+ for (const cmd of coreCommands) {
91
+ check(`核心命令 ${cmd} 存在`, fs.existsSync(path.join(commandsDir, cmd)));
92
+ }
93
+ }
94
+
95
+ console.log('\n🎯 Skills 检查:');
96
+ const skillsDir = path.join(PLUGIN_DIR, 'skills');
97
+ if (fs.existsSync(skillsDir)) {
98
+ const skillDirs = fs.readdirSync(skillsDir, { withFileTypes: true })
99
+ .filter(d => d.isDirectory())
100
+ .map(d => d.name);
101
+ check(`skills 目录包含目录 (${skillDirs.length})`, skillDirs.length > 0);
102
+
103
+ // 检查是否有 SKILL.md 文件
104
+ let skillsWithMd = 0;
105
+ for (const dir of skillDirs.slice(0, 10)) {
106
+ if (fs.existsSync(path.join(skillsDir, dir, 'SKILL.md'))) {
107
+ skillsWithMd++;
108
+ }
109
+ }
110
+ check(`skills 包含 SKILL.md 文件 (${skillsWithMd})`, skillsWithMd > 0);
111
+ }
112
+
113
+ console.log('\n📜 Rules 检查:');
114
+ const rulesDir = path.join(PLUGIN_DIR, 'rules');
115
+ if (fs.existsSync(rulesDir)) {
116
+ const ruleItems = fs.readdirSync(rulesDir);
117
+ check(`rules 目录包含内容 (${ruleItems.length})`, ruleItems.length > 0);
118
+
119
+ // 检查通用规则
120
+ check('common 规则目录存在', fs.existsSync(path.join(rulesDir, 'common')));
121
+ check('zh 规则目录存在', fs.existsSync(path.join(rulesDir, 'zh')));
122
+ }
123
+
124
+ console.log('\n⚡ Hooks 检查:');
125
+ const hooksPath = path.join(OPENCODE_HOME, 'plugins', 'tsp-hooks.js');
126
+ if (fs.existsSync(hooksPath)) {
127
+ const hooksContent = fs.readFileSync(hooksPath, 'utf8');
128
+ check('tsp-hooks.js 包含插件函数', hooksContent.includes('module.exports = function tspHooks'));
129
+ check('tsp-hooks.js 包含 tool.execute.before', hooksContent.includes("'tool.execute.before'"));
130
+ check('tsp-hooks.js 包含 tool.execute.after', hooksContent.includes("'tool.execute.after'"));
131
+ }
132
+
133
+ // 总结
134
+ console.log('\n=== 测试总结 ===');
135
+ console.log(`通过: ${passed}`);
136
+ console.log(`失败: ${failed}`);
137
+ console.log(`总计: ${passed + failed}`);
138
+
139
+ if (failed === 0) {
140
+ console.log('\n🎉 所有测试通过!OpenCode 安装成功。');
141
+ console.log('\n下一步:');
142
+ console.log('1. 启动 OpenCode: opencode');
143
+ console.log('2. 查看可用角色: AGENTS.md 中包含所有角色索引');
144
+ console.log('3. 执行团队命令: /team-intake, /team-plan 等');
145
+ console.log('4. 加载技能: skill frontend-engineering');
146
+ console.log('5. 引用规则: @rules/common/coding-style.md');
147
+ process.exit(0);
148
+ } else {
149
+ console.log('\n⚠️ 部分测试失败,请检查安装。');
150
+ process.exit(1);
151
+ }