triples-agentic 2.4.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 (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -0
  3. package/docs/workflow.md +163 -0
  4. package/install.sh +98 -0
  5. package/package.json +54 -0
  6. package/src/agents/README.md +85 -0
  7. package/src/agents/jiwoo-prd.md +84 -0
  8. package/src/agents/kaede-backend.md +95 -0
  9. package/src/agents/kotone-flutter.md +100 -0
  10. package/src/agents/lynn-testcase.md +92 -0
  11. package/src/agents/nakyoung-tasks.md +89 -0
  12. package/src/agents/seoyeon.md +76 -0
  13. package/src/agents/shion-qa.md +89 -0
  14. package/src/agents/sohyun-ios.md +97 -0
  15. package/src/agents/yeonji-android.md +98 -0
  16. package/src/agents/yooyeon-rfc.md +82 -0
  17. package/src/agents/yubin-frontend.md +88 -0
  18. package/src/bin/setup.js +640 -0
  19. package/src/hooks/README.md +102 -0
  20. package/src/hooks/dangerous-commands.json +33 -0
  21. package/src/hooks/dangerous-commands.md +18 -0
  22. package/src/knowledge/README.md +129 -0
  23. package/src/knowledge/general/boy-scout-rule.md +13 -0
  24. package/src/knowledge/general/composition-over-inheritance.md +14 -0
  25. package/src/knowledge/general/dry.md +14 -0
  26. package/src/knowledge/general/fail-fast.md +13 -0
  27. package/src/knowledge/general/kiss.md +15 -0
  28. package/src/knowledge/general/least-surprise.md +13 -0
  29. package/src/knowledge/general/slap.md +29 -0
  30. package/src/knowledge/general/solid.md +44 -0
  31. package/src/knowledge/general/tdd.md +76 -0
  32. package/src/knowledge/general/yagni.md +12 -0
  33. package/src/knowledge/mobile/android/android-architecture.md +83 -0
  34. package/src/knowledge/mobile/android/android-platform.md +60 -0
  35. package/src/knowledge/mobile/android/kotlin-concurrency.md +75 -0
  36. package/src/knowledge/mobile/android/kotlin-core.md +88 -0
  37. package/src/knowledge/mobile/flutter/dart-async.md +93 -0
  38. package/src/knowledge/mobile/flutter/dart-core.md +97 -0
  39. package/src/knowledge/mobile/flutter/flutter-architecture.md +88 -0
  40. package/src/knowledge/mobile/flutter/flutter-platform.md +79 -0
  41. package/src/knowledge/mobile/ios/ios-architecture.md +88 -0
  42. package/src/knowledge/mobile/ios/ios-platform.md +66 -0
  43. package/src/knowledge/mobile/ios/swift-concurrency.md +99 -0
  44. package/src/knowledge/mobile/ios/swift-core.md +79 -0
  45. package/src/knowledge/planning/architecture-database.md +47 -0
  46. package/src/knowledge/planning/architecture-patterns.md +64 -0
  47. package/src/knowledge/planning/architecture-security.md +61 -0
  48. package/src/knowledge/planning/estimation.md +82 -0
  49. package/src/knowledge/planning/orchestration.md +70 -0
  50. package/src/knowledge/planning/prd-quality-gates.md +38 -0
  51. package/src/knowledge/planning/prd-writing.md +59 -0
  52. package/src/knowledge/planning/product-principles.md +48 -0
  53. package/src/knowledge/planning/product-prioritization.md +45 -0
  54. package/src/knowledge/planning/rfc-quality-gates.md +38 -0
  55. package/src/knowledge/planning/rfc-writing.md +81 -0
  56. package/src/knowledge/planning/task-decomposition.md +61 -0
  57. package/src/knowledge/planning/task-readiness.md +64 -0
  58. package/src/knowledge/quality/qa-execution.md +55 -0
  59. package/src/knowledge/quality/qa-reporting.md +71 -0
  60. package/src/knowledge/quality/test-case-quality.md +61 -0
  61. package/src/knowledge/quality/test-case-writing.md +76 -0
  62. package/src/knowledge/quality/testing-strategy.md +70 -0
  63. package/src/knowledge/quality/testing-types.md +84 -0
  64. package/src/knowledge/web/backend/api-design.md +74 -0
  65. package/src/knowledge/web/backend/api-security.md +54 -0
  66. package/src/knowledge/web/backend/backend-security.md +84 -0
  67. package/src/knowledge/web/backend/backend-structure.md +78 -0
  68. package/src/knowledge/web/frontend/frontend-components.md +49 -0
  69. package/src/knowledge/web/frontend/frontend-performance.md +41 -0
  70. package/src/knowledge/web/frontend/frontend-state.md +59 -0
  71. package/src/knowledge/web/frontend/web-accessibility.md +51 -0
  72. package/src/knowledge/web/frontend/web-performance.md +51 -0
  73. package/src/knowledge/web/frontend/web-security.md +59 -0
  74. package/src/templates/prd.md +109 -0
  75. package/src/templates/rfc.md +156 -0
  76. package/src/templates/task-breakdown.md +172 -0
  77. package/src/templates/test-case.md +157 -0
@@ -0,0 +1,640 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * TripleS Agentic — Skill Plugin Setup
4
+ *
5
+ * Installs all TripleS skill files (11 agents + 40 knowledge skills)
6
+ * into your coding assistant's config directory.
7
+ *
8
+ * Usage:
9
+ * npx triples-agentic → interactive wizard
10
+ * npx triples-agentic claude → Claude Code, project-level
11
+ * npx triples-agentic claude --global → Claude Code, global (~/.claude/skills/)
12
+ * npx triples-agentic cursor --global → Cursor, global (~/.cursor/rules/)
13
+ * npx triples-agentic all → all platforms, project-level
14
+ * npx triples-agentic claude --target <dir> → install into a specific project directory
15
+ */
16
+
17
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from 'fs';
18
+ import { join, dirname } from 'path';
19
+ import { homedir } from 'os';
20
+ import { fileURLToPath } from 'url';
21
+ import { createInterface } from 'readline';
22
+
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ const __dirname = dirname(__filename);
25
+ const ROOT = join(__dirname, '..');
26
+ const AGENTS_DIR = join(ROOT, 'agents');
27
+ const KNOWLEDGE_DIR = join(ROOT, 'knowledge');
28
+ const KNOWLEDGE_GROUPS = ['planning', 'web', 'mobile/android', 'mobile/ios', 'mobile/flutter', 'quality'];
29
+ const HOME = homedir();
30
+ const HOOKS_DIR = join(ROOT, 'hooks');
31
+
32
+ // ─── Global install paths ─────────────────────────────────────────────────────
33
+
34
+ const GLOBAL_PATHS = {
35
+ claude: join(HOME, '.claude', 'skills'),
36
+ cursor: join(HOME, '.cursor', 'rules'),
37
+ windsurf: join(HOME, '.codeium', 'windsurf', 'rules'),
38
+ codex: join(HOME, '.codex'),
39
+ };
40
+
41
+ // ─── CLI args ─────────────────────────────────────────────────────────────────
42
+
43
+ const rawArgs = process.argv.slice(2);
44
+ let isGlobal = rawArgs.includes('--global');
45
+ const args = rawArgs.filter(a => a !== '--global');
46
+
47
+ const platformArg = args[0];
48
+ let projectDir = process.cwd();
49
+
50
+ const targetIdx = args.indexOf('--target');
51
+ if (targetIdx !== -1 && args[targetIdx + 1]) {
52
+ projectDir = args[targetIdx + 1];
53
+ }
54
+
55
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
56
+
57
+ function display(p) {
58
+ return p.startsWith(HOME) ? p.replace(HOME, '~') : p;
59
+ }
60
+
61
+ function ensureDir(dir) {
62
+ mkdirSync(dir, { recursive: true });
63
+ }
64
+
65
+ function writeFile(filePath, content) {
66
+ ensureDir(dirname(filePath));
67
+ writeFileSync(filePath, content, 'utf-8');
68
+ console.log(` ✓ ${display(filePath)}`);
69
+ }
70
+
71
+ /** Parse name/description from YAML frontmatter block (--- ... ---) */
72
+ function parseFrontmatter(content) {
73
+ const match = content.match(/^---\r?\n([\s\S]+?)\r?\n---/);
74
+ if (!match) return { name: '', description: '' };
75
+ const fm = match[1];
76
+ const name = (fm.match(/^name:\s*(.+)$/m) || [])[1]?.trim() ?? '';
77
+ const description = (fm.match(/^description:\s*(.+)$/m) || [])[1]?.trim() ?? '';
78
+ return { name, description };
79
+ }
80
+
81
+ /** Strip existing frontmatter block so we can re-wrap for a platform */
82
+ function stripFrontmatter(content) {
83
+ return content.replace(/^---\r?\n[\s\S]+?\r?\n---\r?\n?/, '').trimStart();
84
+ }
85
+
86
+ /** Parse <!-- key: value --> HTML comment from agent files */
87
+ function parseComment(content, key) {
88
+ const m = content.match(new RegExp(`<!-- ${key}: (.+?) -->`));
89
+ return m ? m[1].trim() : '';
90
+ }
91
+
92
+ // ─── Source collectors ────────────────────────────────────────────────────────
93
+
94
+ function allAgents() {
95
+ return readdirSync(AGENTS_DIR)
96
+ .filter(f => f.endsWith('.md'))
97
+ .map(f => {
98
+ const name = f.replace('.md', '');
99
+ const content = readFileSync(join(AGENTS_DIR, f), 'utf-8');
100
+ const persona = parseComment(content, 'persona') || 'software engineering agent';
101
+ return { name, content, persona };
102
+ })
103
+ .sort((a, b) => a.name.localeCompare(b.name));
104
+ }
105
+
106
+ /**
107
+ * Returns all knowledge skill files from every group subdirectory.
108
+ * Each entry: { name, group, content, skillName, description }
109
+ * where skillName/description come from the file's YAML frontmatter.
110
+ */
111
+ function allKnowledgeSkills() {
112
+ const skills = [];
113
+ for (const group of KNOWLEDGE_GROUPS) {
114
+ const groupDir = join(KNOWLEDGE_DIR, group);
115
+ if (!existsSync(groupDir)) continue;
116
+ readdirSync(groupDir)
117
+ .filter(f => f.endsWith('.md'))
118
+ .sort()
119
+ .forEach(f => {
120
+ const content = readFileSync(join(groupDir, f), 'utf-8');
121
+ const { name: skillName, description } = parseFrontmatter(content);
122
+ skills.push({
123
+ name: f.replace('.md', ''), // filename slug (e.g. prd-writing)
124
+ group, // planning | web | mobile | quality
125
+ content,
126
+ skillName: skillName || f.replace('.md', ''),
127
+ description: description || `${group} knowledge skill`,
128
+ });
129
+ });
130
+ }
131
+ return skills;
132
+ }
133
+
134
+ // ─── Hook loaders ─────────────────────────────────────────────────────────────
135
+
136
+ function loadHookFiles() {
137
+ if (!existsSync(HOOKS_DIR)) return [];
138
+ return readdirSync(HOOKS_DIR)
139
+ .filter(f => f.endsWith('.json'))
140
+ .sort()
141
+ .map(f => JSON.parse(readFileSync(join(HOOKS_DIR, f), 'utf-8')));
142
+ }
143
+
144
+ /** Returns Claude Code PreToolUse hook entries from src/hooks/*.json platforms.claude */
145
+ function loadClaudeHooks() {
146
+ return loadHookFiles()
147
+ .filter(h => h.platforms?.claude)
148
+ .map(h => h.platforms.claude);
149
+ }
150
+
151
+ /** Returns Codex PreToolUse hook entries from src/hooks/*.json platforms.codex */
152
+ function loadCodexHooks() {
153
+ return loadHookFiles()
154
+ .filter(h => h.platforms?.codex)
155
+ .map(h => h.platforms.codex);
156
+ }
157
+
158
+ /** Returns Windsurf hook entries from src/hooks/*.json platforms.windsurf, grouped by event */
159
+ function loadWindsurfHooks() {
160
+ const byEvent = {};
161
+ for (const h of loadHookFiles()) {
162
+ const ws = h.platforms?.windsurf;
163
+ if (!ws?.event) continue;
164
+ const { event, ...cfg } = ws;
165
+ byEvent[event] = byEvent[event] || [];
166
+ byEvent[event].push(cfg);
167
+ }
168
+ return byEvent;
169
+ }
170
+
171
+ /**
172
+ * Returns the stripped body of all src/hooks/*.md files joined together.
173
+ * Used as plain text safety rules for platforms without a hook execution model.
174
+ */
175
+ function loadSafetyRulesBody() {
176
+ if (!existsSync(HOOKS_DIR)) return '';
177
+ return readdirSync(HOOKS_DIR)
178
+ .filter(f => f.endsWith('.md'))
179
+ .sort()
180
+ .map(f => stripFrontmatter(readFileSync(join(HOOKS_DIR, f), 'utf-8')))
181
+ .join('\n\n')
182
+ .trim();
183
+ }
184
+
185
+ /** Escape a string for use inside a TOML double-quoted basic string. */
186
+ function tomlEscape(str) {
187
+ return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
188
+ }
189
+
190
+ // ─── Platform installers ──────────────────────────────────────────────────────
191
+
192
+ /** Write (or merge) PreToolUse hooks from platforms.claude into .claude/settings.json */
193
+ function installClaudeSettings(claudeDir) {
194
+ const hookEntries = loadClaudeHooks();
195
+ if (hookEntries.length === 0) return;
196
+
197
+ const settingsPath = join(claudeDir, 'settings.json');
198
+ let settings = {};
199
+ if (existsSync(settingsPath)) {
200
+ try { settings = JSON.parse(readFileSync(settingsPath, 'utf-8')); } catch {}
201
+ }
202
+ settings.hooks = settings.hooks || {};
203
+
204
+ const events = [...new Set(hookEntries.map(e => e.event).filter(Boolean))];
205
+ for (const event of events) {
206
+ const incoming = hookEntries.filter(e => e.event === event);
207
+ const existing = settings.hooks[event] || [];
208
+ const matchers = new Set(incoming.map(e => e.matcher));
209
+ settings.hooks[event] = [
210
+ ...existing.filter(e => !matchers.has(e.matcher) || !e.hooks?.some(h => h.statusMessage === 'Checking for dangerous commands...')),
211
+ ...incoming.map(({ event: _e, ...entry }) => entry),
212
+ ];
213
+ }
214
+
215
+ writeFile(settingsPath, JSON.stringify(settings, null, 2));
216
+ }
217
+
218
+ /** Write (or append) PreToolUse hooks from platforms.codex into .codex/config.toml */
219
+ function installCodexSettings(base) {
220
+ const hookEntries = loadCodexHooks().filter(e => e.event === 'PreToolUse');
221
+ if (hookEntries.length === 0) return;
222
+
223
+ const configPath = isGlobal && !base
224
+ ? join(GLOBAL_PATHS.codex, 'config.toml')
225
+ : join(base || projectDir, '.codex', 'config.toml');
226
+ let existing = existsSync(configPath) ? readFileSync(configPath, 'utf-8') : '';
227
+
228
+ // Remove previous triples-agentic block (idempotent reinstall)
229
+ existing = existing.replace(/\n?# triples-agentic hooks[\s\S]*?# end triples-agentic hooks\n?/g, '');
230
+
231
+ let block = '\n# triples-agentic hooks\n';
232
+ for (const entry of hookEntries) {
233
+ block += `\n[[hooks.PreToolUse]]\n`;
234
+ if (entry.matcher) block += `matcher = "${tomlEscape(entry.matcher)}"\n`;
235
+ for (const hook of (entry.hooks || [])) {
236
+ block += `\n[[hooks.PreToolUse.hooks]]\n`;
237
+ block += `type = "command"\n`;
238
+ block += `command = "${tomlEscape(hook.command)}"\n`;
239
+ if (hook.statusMessage) block += `statusMessage = "${tomlEscape(hook.statusMessage)}"\n`;
240
+ }
241
+ }
242
+ block += '\n# end triples-agentic hooks\n';
243
+
244
+ writeFile(configPath, existing.trimEnd() + block);
245
+ }
246
+
247
+ /** Write (or merge) Windsurf hooks from platforms.windsurf into .windsurf/hooks.json */
248
+ function installWindsurfSettings(base) {
249
+ const byEvent = loadWindsurfHooks();
250
+ if (Object.keys(byEvent).length === 0) return;
251
+
252
+ const hooksPath = join(base || projectDir, '.windsurf', 'hooks.json');
253
+ let config = { hooks: {} };
254
+ if (existsSync(hooksPath)) {
255
+ try { config = JSON.parse(readFileSync(hooksPath, 'utf-8')); } catch {}
256
+ }
257
+ config.hooks = config.hooks || {};
258
+
259
+ for (const [event, incoming] of Object.entries(byEvent)) {
260
+ const existing = (config.hooks[event] || []).filter(
261
+ e => e.command !== incoming[0]?.command // remove previous triples-agentic entry
262
+ );
263
+ config.hooks[event] = [...existing, ...incoming];
264
+ }
265
+
266
+ writeFile(hooksPath, JSON.stringify(config, null, 2));
267
+ }
268
+
269
+ function installClaude(base) {
270
+ const dest = isGlobal && !base ? GLOBAL_PATHS.claude : join(base || projectDir, '.claude', 'skills');
271
+ console.log(`\nInstalling Claude Code skills → ${display(dest)}`);
272
+
273
+ // Agents
274
+ for (const { name, content, persona } of allAgents()) {
275
+ const skill = ['---', `name: ${name}`, `description: TripleS agent — ${name} (${persona})`, '---', '', content].join('\n');
276
+ writeFile(join(dest, `${name}.md`), skill);
277
+ }
278
+
279
+ // Knowledge skills — already have correct frontmatter, install as-is
280
+ for (const { name, group, content } of allKnowledgeSkills()) {
281
+ writeFile(join(dest, 'knowledge', group, `${name}.md`), content);
282
+ }
283
+
284
+ // Settings — dangerous command hook (enforced at harness level)
285
+ installClaudeSettings(dirname(dest));
286
+ }
287
+
288
+ function installCursor(base) {
289
+ const dest = isGlobal && !base ? GLOBAL_PATHS.cursor : join(base || projectDir, '.cursor', 'rules');
290
+ console.log(`\nInstalling Cursor rules → ${display(dest)}`);
291
+
292
+ // Agents
293
+ for (const { name, content, persona } of allAgents()) {
294
+ const rule = ['---', `description: TripleS agent — ${name} (${persona})`, 'alwaysApply: false', '---', '', content].join('\n');
295
+ writeFile(join(dest, `${name}.mdc`), rule);
296
+ }
297
+
298
+ // Knowledge skills
299
+ for (const { name, group, content, description } of allKnowledgeSkills()) {
300
+ const body = stripFrontmatter(content);
301
+ const rule = ['---', `description: ${description}`, 'alwaysApply: false', '---', '', body].join('\n');
302
+ writeFile(join(dest, 'knowledge', group, `${name}.mdc`), rule);
303
+ }
304
+
305
+ // Safety guardrails — always-applied rule (text-level enforcement)
306
+ const safetyBody = loadSafetyRulesBody();
307
+ if (safetyBody) {
308
+ const safetyRule = ['---', 'description: TripleS safety guardrails — never run dangerous commands without explicit user confirmation', 'alwaysApply: true', '---', '', safetyBody].join('\n');
309
+ writeFile(join(dest, 'triples-safety.mdc'), safetyRule);
310
+ }
311
+ }
312
+
313
+ function installCopilot(base) {
314
+ const dest = join(base || projectDir, '.github', 'instructions');
315
+ console.log(`\nInstalling Copilot instructions → ${display(dest)}`);
316
+
317
+ // Agents
318
+ for (const { name, content } of allAgents()) {
319
+ const instruction = ['---', 'applyTo: "**"', '---', '', content].join('\n');
320
+ writeFile(join(dest, `${name}.instructions.md`), instruction);
321
+ }
322
+
323
+ // Knowledge skills
324
+ for (const { name, group, content } of allKnowledgeSkills()) {
325
+ const body = stripFrontmatter(content);
326
+ const instruction = ['---', 'applyTo: "**"', '---', '', body].join('\n');
327
+ writeFile(join(dest, 'knowledge', group, `${name}.instructions.md`), instruction);
328
+ }
329
+
330
+ // Safety guardrails — always-applied instruction (text-level enforcement)
331
+ const safetyBody = loadSafetyRulesBody();
332
+ if (safetyBody) {
333
+ const safetyInstruction = ['---', 'applyTo: "**"', '---', '', safetyBody].join('\n');
334
+ writeFile(join(dest, 'triples-safety.instructions.md'), safetyInstruction);
335
+ }
336
+ }
337
+
338
+ function installCodex(base) {
339
+ const dest = isGlobal && !base
340
+ ? join(GLOBAL_PATHS.codex, 'AGENTS.md')
341
+ : join(base || projectDir, 'AGENTS.md');
342
+ console.log(`\nInstalling OpenAI Codex → ${display(dest)}`);
343
+
344
+ const safetyBody = loadSafetyRulesBody();
345
+ const lines = ['# TripleS Agent Orchestrator\n\n', ...(safetyBody ? [safetyBody + '\n\n'] : []), '## Agents\n\n'];
346
+ for (const { name, content } of allAgents()) {
347
+ lines.push(`---\n\n${content}\n\n`);
348
+ }
349
+
350
+ lines.push('## Knowledge Skills\n\n');
351
+ for (const group of KNOWLEDGE_GROUPS) {
352
+ lines.push(`### ${group.charAt(0).toUpperCase() + group.slice(1)}\n\n`);
353
+ for (const { name, content } of allKnowledgeSkills().filter(s => s.group === group)) {
354
+ const body = stripFrontmatter(content);
355
+ lines.push(`#### ${name}\n\n${body}\n\n`);
356
+ }
357
+ }
358
+
359
+ writeFileSync(dest, lines.join(''), 'utf-8');
360
+ console.log(` ✓ ${display(dest)}`);
361
+
362
+ // Hooks — enforced at harness level via .codex/config.toml
363
+ installCodexSettings(base);
364
+ }
365
+
366
+ function installWindsurf(base) {
367
+ const dest = isGlobal && !base
368
+ ? join(GLOBAL_PATHS.windsurf, '.windsurfrules')
369
+ : join(base || projectDir, '.windsurfrules');
370
+ console.log(`\nInstalling Windsurf rules → ${display(dest)}`);
371
+
372
+ const safetyBody = loadSafetyRulesBody();
373
+ const lines = ['# TripleS Agent Orchestrator — Windsurf Rules\n\n', ...(safetyBody ? [safetyBody + '\n\n'] : []), '## Agents\n\n'];
374
+ for (const { name, content } of allAgents()) {
375
+ lines.push(`### ${name}\n\n${content}\n\n`);
376
+ }
377
+
378
+ lines.push('## Knowledge Skills\n\n');
379
+ for (const group of KNOWLEDGE_GROUPS) {
380
+ lines.push(`### ${group.charAt(0).toUpperCase() + group.slice(1)}\n\n`);
381
+ for (const { name, content } of allKnowledgeSkills().filter(s => s.group === group)) {
382
+ const body = stripFrontmatter(content);
383
+ lines.push(`#### ${name}\n\n${body}\n\n`);
384
+ }
385
+ }
386
+
387
+ const dir = dirname(dest);
388
+ if (dir !== '.') ensureDir(dir);
389
+ writeFileSync(dest, lines.join(''), 'utf-8');
390
+ console.log(` ✓ ${display(dest)}`);
391
+
392
+ // Hooks — enforced at harness level via .windsurf/hooks.json
393
+ installWindsurfSettings(base);
394
+ }
395
+
396
+ const INSTALLERS = {
397
+ claude: installClaude,
398
+ cursor: installCursor,
399
+ copilot: installCopilot,
400
+ codex: installCodex,
401
+ windsurf: installWindsurf,
402
+ };
403
+
404
+ const PLATFORM_LABELS = {
405
+ claude: 'Claude Code', cursor: 'Cursor AI', copilot: 'GitHub Copilot',
406
+ codex: 'OpenAI Codex', windsurf: 'Windsurf',
407
+ };
408
+
409
+ // ─── Success banner ───────────────────────────────────────────────────────────
410
+
411
+ const AGENT_COMMANDS = [
412
+ ['seoyeon', 'Engineering Manager — orchestrates all agents'],
413
+ ['jiwoo-prd', 'Senior PM — create & review PRD'],
414
+ ['yooyeon-rfc', 'Staff Engineer — create & review RFC'],
415
+ ['nakyoung-tasks', 'TPM — task breakdown with estimates'],
416
+ ['yubin-frontend', 'Principal Frontend — implement web UI'],
417
+ ['kaede-backend', 'Principal Backend — implement APIs'],
418
+ ['yeonji-android', 'Senior Android — implement Kotlin features'],
419
+ ['sohyun-ios', 'Senior iOS — implement Swift features'],
420
+ ['kotone-flutter', 'Senior Flutter — cross-platform Dart'],
421
+ ['lynn-testcase', 'QA Lead — create & review test cases'],
422
+ ['shion-qa', 'Senior QA — execute tests & Go/No-Go'],
423
+ ];
424
+
425
+ const KNOWLEDGE_SUMMARY = {
426
+ planning: ['orchestration', 'prd-writing', 'prd-quality-gates', 'product-principles',
427
+ 'product-prioritization', 'rfc-writing', 'rfc-quality-gates',
428
+ 'architecture-patterns', 'architecture-database', 'architecture-security',
429
+ 'task-decomposition', 'task-readiness', 'estimation'],
430
+ web: ['frontend-components', 'frontend-state', 'frontend-performance',
431
+ 'web-accessibility', 'web-performance', 'web-security',
432
+ 'backend-structure', 'backend-security', 'api-design', 'api-security'],
433
+ 'mobile/android': ['android-architecture', 'android-platform', 'kotlin-core', 'kotlin-concurrency'],
434
+ 'mobile/ios': ['ios-architecture', 'ios-platform', 'swift-core', 'swift-concurrency'],
435
+ 'mobile/flutter': ['flutter-architecture', 'flutter-platform', 'dart-core', 'dart-async'],
436
+ quality: ['testing-strategy', 'testing-types', 'test-case-writing', 'test-case-quality',
437
+ 'qa-execution', 'qa-reporting'],
438
+ };
439
+
440
+ // ─── Update helpers ───────────────────────────────────────────────────────────
441
+
442
+ /**
443
+ * Detect existing TripleS installations by checking sentinel files.
444
+ * Returns an array of { platform, isGlobal } objects.
445
+ */
446
+ function detectInstallations() {
447
+ const found = [];
448
+
449
+ const hasFile = (...parts) => existsSync(join(...parts));
450
+ const hasMarker = (file) => {
451
+ try { return readFileSync(file, 'utf-8').includes('TripleS Agent Orchestrator'); } catch { return false; }
452
+ };
453
+
454
+ // Claude
455
+ if (hasFile(GLOBAL_PATHS.claude, 'seoyeon.md'))
456
+ found.push({ platform: 'claude', isGlobal: true });
457
+ if (hasFile(projectDir, '.claude', 'skills', 'seoyeon.md'))
458
+ found.push({ platform: 'claude', isGlobal: false });
459
+
460
+ // Cursor
461
+ if (hasFile(GLOBAL_PATHS.cursor, 'seoyeon.mdc'))
462
+ found.push({ platform: 'cursor', isGlobal: true });
463
+ if (hasFile(projectDir, '.cursor', 'rules', 'seoyeon.mdc'))
464
+ found.push({ platform: 'cursor', isGlobal: false });
465
+
466
+ // Copilot — project-only
467
+ if (hasFile(projectDir, '.github', 'instructions', 'seoyeon.instructions.md'))
468
+ found.push({ platform: 'copilot', isGlobal: false });
469
+
470
+ // Codex
471
+ const codexGlobal = join(GLOBAL_PATHS.codex, 'AGENTS.md');
472
+ if (hasFile(codexGlobal) && hasMarker(codexGlobal))
473
+ found.push({ platform: 'codex', isGlobal: true });
474
+ const codexProject = join(projectDir, 'AGENTS.md');
475
+ if (hasFile(codexProject) && hasMarker(codexProject))
476
+ found.push({ platform: 'codex', isGlobal: false });
477
+
478
+ // Windsurf
479
+ const windsurfGlobal = join(GLOBAL_PATHS.windsurf, '.windsurfrules');
480
+ if (hasFile(windsurfGlobal) && hasMarker(windsurfGlobal))
481
+ found.push({ platform: 'windsurf', isGlobal: true });
482
+ const windsurfProject = join(projectDir, '.windsurfrules');
483
+ if (hasFile(windsurfProject) && hasMarker(windsurfProject))
484
+ found.push({ platform: 'windsurf', isGlobal: false });
485
+
486
+ return found;
487
+ }
488
+
489
+ async function runUpdate() {
490
+ const installations = detectInstallations();
491
+
492
+ if (installations.length === 0) {
493
+ console.log('\n⚠ No existing TripleS Agentic installations detected.\n');
494
+ console.log('Run the installer to get started:');
495
+ console.log(' npx triples-agentic\n');
496
+ return;
497
+ }
498
+
499
+ console.log('\n╔══════════════════════════════════════════════════╗');
500
+ console.log('║ TripleS Agentic — Update ║');
501
+ console.log('║ Software Engineering Agent Orchestrator ║');
502
+ console.log('╚══════════════════════════════════════════════════╝\n');
503
+ console.log('Detected installations:\n');
504
+ for (const { platform, isGlobal: g } of installations) {
505
+ console.log(` • ${PLATFORM_LABELS[platform]} (${g ? 'global' : 'project'})`);
506
+ }
507
+ console.log('');
508
+
509
+ const savedIsGlobal = isGlobal;
510
+ for (const { platform, isGlobal: g } of installations) {
511
+ isGlobal = g;
512
+ INSTALLERS[platform](g ? null : projectDir);
513
+ }
514
+ isGlobal = savedIsGlobal;
515
+
516
+ const totalKnowledge = Object.values(KNOWLEDGE_SUMMARY).reduce((n, g) => n + g.length, 0);
517
+ console.log(`\n✅ Updated ${installations.length} installation(s) — ${AGENT_COMMANDS.length} agents, ${totalKnowledge} knowledge skills\n`);
518
+ }
519
+
520
+ // ─── Success banner ───────────────────────────────────────────────────────────
521
+
522
+ function printSuccessBanner() {
523
+ const totalKnowledge = Object.values(KNOWLEDGE_SUMMARY).reduce((n, g) => n + g.length, 0);
524
+ console.log('\n✅ TripleS Agentic installed successfully!\n');
525
+
526
+ console.log('── Agents (invoke as slash commands) ──────────────────────────');
527
+ for (const [cmd, desc] of AGENT_COMMANDS) {
528
+ console.log(` /${cmd.padEnd(18)} ${desc}`);
529
+ }
530
+
531
+ console.log(`\n── Knowledge Skills (${totalKnowledge} skills across 4 groups) ──────────────────`);
532
+ for (const [group, skills] of Object.entries(KNOWLEDGE_SUMMARY)) {
533
+ const label = group.includes('/') ? group : group; // full path for sub-groups
534
+ console.log(` ${label.padEnd(16)} ${skills.map(s => `/${s}`).join(' ')}`);
535
+ }
536
+
537
+ console.log('\nStart the full pipeline with /seoyeon');
538
+ console.log('To update later run: npx triples-agentic update\n');
539
+ }
540
+
541
+ // ─── Interactive wizard ───────────────────────────────────────────────────────
542
+
543
+ function ask(rl, question) {
544
+ return new Promise(resolve => rl.question(question, a => resolve(a.trim())));
545
+ }
546
+
547
+ async function runWizard() {
548
+ console.log('\n╔══════════════════════════════════════════════════╗');
549
+ console.log('║ TripleS Agentic — Skill Plugin Setup ║');
550
+ console.log('║ Software Engineering Agent Orchestrator ║');
551
+ console.log('╚══════════════════════════════════════════════════╝\n');
552
+
553
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
554
+
555
+ try {
556
+ console.log('Which coding assistant are you installing for?\n');
557
+ console.log(' 1. Claude Code');
558
+ console.log(' 2. Cursor AI');
559
+ console.log(' 3. GitHub Copilot');
560
+ console.log(' 4. OpenAI Codex');
561
+ console.log(' 5. Windsurf');
562
+ console.log(' 6. All of the above');
563
+
564
+ const platformInput = await ask(rl, '\nEnter number [1-6]: ');
565
+ const platformChoices = {
566
+ '1': ['claude'], '2': ['cursor'], '3': ['copilot'],
567
+ '4': ['codex'], '5': ['windsurf'], '6': Object.keys(INSTALLERS),
568
+ };
569
+ const selectedPlatforms = platformChoices[platformInput];
570
+
571
+ if (!selectedPlatforms) {
572
+ console.error('\n❌ Invalid selection. Run again and enter a number from 1–6.');
573
+ rl.close(); process.exit(1);
574
+ }
575
+
576
+ const supportsGlobal = selectedPlatforms.some(p => GLOBAL_PATHS[p]);
577
+ let useGlobal = false;
578
+
579
+ if (supportsGlobal) {
580
+ const globalPlatforms = selectedPlatforms.filter(p => GLOBAL_PATHS[p]);
581
+ console.log('\nInstall scope?\n');
582
+ console.log(' 1. Global — applies to all your projects');
583
+ console.log(` (${globalPlatforms.map(p => `${PLATFORM_LABELS[p]}: ${display(GLOBAL_PATHS[p])}`).join(', ')})`);
584
+ console.log(' 2. Project — current directory only');
585
+ console.log(` (${display(projectDir)})`);
586
+
587
+ const scopeInput = await ask(rl, '\nEnter number [1-2]: ');
588
+ useGlobal = scopeInput === '1';
589
+ }
590
+
591
+ rl.close();
592
+
593
+ console.log('');
594
+ for (const p of selectedPlatforms) {
595
+ INSTALLERS[p](useGlobal && GLOBAL_PATHS[p] ? null : projectDir);
596
+ }
597
+
598
+ printSuccessBanner();
599
+ } catch (err) {
600
+ rl.close();
601
+ console.error('\n❌ Setup failed:', err.message);
602
+ process.exit(1);
603
+ }
604
+ }
605
+
606
+ // ─── Main ─────────────────────────────────────────────────────────────────────
607
+
608
+ async function main() {
609
+ if (!platformArg || platformArg === 'setup') {
610
+ await runWizard();
611
+ return;
612
+ }
613
+
614
+ if (platformArg === 'update') {
615
+ await runUpdate();
616
+ return;
617
+ }
618
+
619
+ if (platformArg === 'all') {
620
+ for (const installer of Object.values(INSTALLERS)) {
621
+ installer(isGlobal ? null : projectDir);
622
+ }
623
+ printSuccessBanner();
624
+ return;
625
+ }
626
+
627
+ if (!INSTALLERS[platformArg]) {
628
+ console.error(`\n❌ Unknown platform: ${platformArg}`);
629
+ console.error('Supported: claude | cursor | copilot | codex | windsurf | all | update\n');
630
+ process.exit(1);
631
+ }
632
+
633
+ INSTALLERS[platformArg](isGlobal ? null : projectDir);
634
+ printSuccessBanner();
635
+ }
636
+
637
+ main().catch(err => {
638
+ console.error('Error:', err.message);
639
+ process.exit(1);
640
+ });