opencode-agile-agent 1.0.1 → 1.0.2

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 (66) hide show
  1. package/README.md +61 -71
  2. package/bin/cli.js +344 -434
  3. package/bin/sync-templates.js +45 -0
  4. package/bin/validate-templates.js +44 -6
  5. package/package.json +2 -1
  6. package/templates/.opencode/ARCHITECTURE.md +82 -368
  7. package/templates/.opencode/README.md +110 -391
  8. package/templates/.opencode/agents/api-designer.md +45 -312
  9. package/templates/.opencode/agents/backend-specialist.md +46 -214
  10. package/templates/.opencode/agents/code-archaeologist.md +45 -260
  11. package/templates/.opencode/agents/context-gatherer.md +51 -0
  12. package/templates/.opencode/agents/database-architect.md +45 -212
  13. package/templates/.opencode/agents/debugger.md +45 -302
  14. package/templates/.opencode/agents/developer.md +45 -523
  15. package/templates/.opencode/agents/devops-engineer.md +45 -253
  16. package/templates/.opencode/agents/documentation-writer.md +45 -247
  17. package/templates/.opencode/agents/explorer-agent.md +49 -233
  18. package/templates/.opencode/agents/feature-lead.md +62 -302
  19. package/templates/.opencode/agents/frontend-specialist.md +46 -186
  20. package/templates/.opencode/agents/game-developer.md +45 -391
  21. package/templates/.opencode/agents/mobile-developer.md +45 -264
  22. package/templates/.opencode/agents/orchestrator.md +48 -463
  23. package/templates/.opencode/agents/penetration-tester.md +44 -254
  24. package/templates/.opencode/agents/performance-optimizer.md +45 -292
  25. package/templates/.opencode/agents/pr-reviewer.md +45 -468
  26. package/templates/.opencode/agents/product-manager.md +46 -225
  27. package/templates/.opencode/agents/project-planner.md +45 -248
  28. package/templates/.opencode/agents/qa-automation-engineer.md +45 -275
  29. package/templates/.opencode/agents/security-auditor.md +44 -258
  30. package/templates/.opencode/agents/seo-specialist.md +45 -266
  31. package/templates/.opencode/agents/system-analyst.md +48 -428
  32. package/templates/.opencode/agents/test-engineer.md +45 -229
  33. package/templates/.opencode/archive/README.md +24 -0
  34. package/templates/.opencode/commands/brainstorm.md +10 -0
  35. package/templates/.opencode/commands/create.md +11 -0
  36. package/templates/.opencode/commands/debug.md +10 -0
  37. package/templates/.opencode/commands/plan.md +9 -0
  38. package/templates/.opencode/commands/review.md +11 -0
  39. package/templates/.opencode/commands/status.md +9 -0
  40. package/templates/.opencode/commands/test.md +10 -0
  41. package/templates/.opencode/skills/api-patterns/SKILL.md +25 -149
  42. package/templates/.opencode/skills/brainstorming/SKILL.md +26 -242
  43. package/templates/.opencode/skills/clean-code/SKILL.md +27 -339
  44. package/templates/.opencode/skills/code-philosophy/SKILL.md +27 -499
  45. package/templates/.opencode/skills/context-archive/SKILL.md +47 -0
  46. package/templates/.opencode/skills/context-gathering/SKILL.md +51 -0
  47. package/templates/.opencode/skills/frontend-design/SKILL.md +26 -224
  48. package/templates/.opencode/skills/intelligent-routing/SKILL.md +25 -182
  49. package/templates/.opencode/skills/parallel-agents/SKILL.md +25 -261
  50. package/templates/.opencode/skills/plan-writing/SKILL.md +28 -238
  51. package/templates/.opencode/skills/redteam-validation/SKILL.md +33 -0
  52. package/templates/.opencode/skills/security-gate/SKILL.md +33 -0
  53. package/templates/.opencode/skills/systematic-debugging/SKILL.md +25 -197
  54. package/templates/.opencode/skills/testing-patterns/SKILL.md +25 -238
  55. package/templates/AGENTS.template.md +300 -426
  56. package/templates/.opencode/agents/product-owner.md +0 -264
  57. package/templates/.opencode/workflows/brainstorm.md +0 -110
  58. package/templates/.opencode/workflows/create.md +0 -108
  59. package/templates/.opencode/workflows/debug.md +0 -128
  60. package/templates/.opencode/workflows/deploy.md +0 -160
  61. package/templates/.opencode/workflows/enhance.md +0 -253
  62. package/templates/.opencode/workflows/orchestrate.md +0 -130
  63. package/templates/.opencode/workflows/plan.md +0 -163
  64. package/templates/.opencode/workflows/review.md +0 -135
  65. package/templates/.opencode/workflows/status.md +0 -102
  66. package/templates/.opencode/workflows/test.md +0 -146
package/bin/cli.js CHANGED
@@ -1,434 +1,344 @@
1
- #!/usr/bin/env node
2
-
3
- import { fileURLToPath } from 'url';
4
- import { dirname, join } from 'path';
5
- import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync } from 'fs';
6
- import { createInterface } from 'readline';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
- const templatesDir = join(__dirname, '..', 'templates');
11
-
12
- // Colors for terminal output
13
- const colors = {
14
- reset: '\x1b[0m',
15
- bright: '\x1b[1m',
16
- green: '\x1b[32m',
17
- blue: '\x1b[34m',
18
- yellow: '\x1b[33m',
19
- cyan: '\x1b[36m',
20
- red: '\x1b[31m',
21
- };
22
-
23
- const log = {
24
- info: (msg) => console.log(`${colors.blue}ℹ${colors.reset} ${msg}`),
25
- success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
26
- warn: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
27
- error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`),
28
- title: (msg) => console.log(`\n${colors.bright}${colors.cyan}${msg}${colors.reset}\n`),
29
- };
30
-
31
- // ASCII Art Banner
32
- const banner = `
33
- ${colors.cyan}╔═══════════════════════════════════════════════════════════╗
34
- ║ ║
35
- ${colors.bright}OpenCode Agile Agent - Spec-Driven Development${colors.reset}${colors.cyan} ║
36
- ║ ║
37
- ╚═══════════════════════════════════════════════════════════╝${colors.reset}
38
- `;
39
-
40
- // Create readline interface
41
- const rl = createInterface({
42
- input: process.stdin,
43
- output: process.stdout
44
- });
45
-
46
- // Promisify question
47
- const question = (prompt) => new Promise((resolve) => {
48
- rl.question(prompt, resolve);
49
- });
50
-
51
- // Detect project framework
52
- async function detectProjectFramework() {
53
- const packageJsonPath = join(process.cwd(), 'package.json');
54
-
55
- if (!existsSync(packageJsonPath)) {
56
- return { framework: 'unknown', language: 'javascript' };
57
- }
58
-
59
- try {
60
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
61
- const deps = { ...packageJson.dependencies, ...packageJson.devDependencies };
62
-
63
- // Detect framework
64
- if (deps['next']) return { framework: 'next', language: 'typescript' };
65
- if (deps['nuxt']) return { framework: 'nuxt', language: 'typescript' };
66
- if (deps['@angular/core']) return { framework: 'angular', language: 'typescript' };
67
- if (deps['svelte']) return { framework: 'svelte', language: 'typescript' };
68
- if (deps['vue'] || deps['vue2'] || deps['vue3']) return { framework: 'vue', language: 'typescript' };
69
- if (deps['react'] || deps['react-dom']) return { framework: 'react', language: 'typescript' };
70
- if (deps['express']) return { framework: 'express', language: 'typescript' };
71
- if (deps['fastify']) return { framework: 'fastify', language: 'typescript' };
72
- if (deps['nestjs'] || deps['@nestjs/core']) return { framework: 'nestjs', language: 'typescript' };
73
-
74
- // Check for TypeScript
75
- const hasTypeScript = deps['typescript'] || existsSync(join(process.cwd(), 'tsconfig.json'));
76
-
77
- return {
78
- framework: 'generic',
79
- language: hasTypeScript ? 'typescript' : 'javascript'
80
- };
81
- } catch {
82
- return { framework: 'unknown', language: 'javascript' };
83
- }
84
- }
85
-
86
- // Framework options
87
- const frameworkOptions = [
88
- { name: 'React', value: 'react' },
89
- { name: 'Vue.js', value: 'vue' },
90
- { name: 'Next.js', value: 'next' },
91
- { name: 'Nuxt.js', value: 'nuxt' },
92
- { name: 'Angular', value: 'angular' },
93
- { name: 'Svelte', value: 'svelte' },
94
- { name: 'NestJS', value: 'nestjs' },
95
- { name: 'Express', value: 'express' },
96
- { name: 'Fastify', value: 'fastify' },
97
- { name: 'Generic / Other', value: 'generic' },
98
- ];
99
-
100
- // Generate AGENTS.md based on project config
101
- function generateAgentsMd(config) {
102
- const { projectName, framework, language, styling, stateManagement, testing } = config;
103
-
104
- return `# AGENTS.md - ${projectName}
105
-
106
- > **Instructions for AI agents working on this project**
107
- >
108
- > This file defines _how_ to build. For _what_ to build, see feature requirements.
109
-
110
- ---
111
-
112
- ## 📦 Project Stack
113
-
114
- - **Framework:** ${framework.charAt(0).toUpperCase() + framework.slice(1)}
115
- - **Language:** ${language.charAt(0).toUpperCase() + language.slice(1)}
116
- - **Styling:** ${styling}
117
- - **State Management:** ${stateManagement}
118
- - **Testing:** ${testing}
119
-
120
- ---
121
-
122
- ## 📚 Core Documentation (CRITICAL)
123
-
124
- Before making architectural or styling decisions, **review** these documents:
125
-
126
- | Document | Purpose | Location |
127
- |----------|---------|----------|
128
- | API Standards | API response structure, error handling | \`docs/api-standards.md\` |
129
- | UI Standards | Styling rules, component patterns | \`docs/ui-standards.md\` |
130
- | Code Style | Linting, formatting rules | \`.eslintrc.js\`, \`.prettierrc\` |
131
- | Architecture | System design, data flow | \`docs/architecture.md\` |
132
-
133
- ---
134
-
135
- ## 🎨 Code Conventions
136
-
137
- ### File Naming
138
-
139
- | Type | Pattern | Example |
140
- |------|---------|---------|
141
- | Components | \`PascalCase.tsx\` | \`UserCard.tsx\` |
142
- | Pages | \`PascalCasePage.tsx\` | \`LoginPage.tsx\` |
143
- | Stores | \`camelCase.store.ts\` | \`auth.store.ts\` |
144
- | Hooks/Composables | \`useCamelCase.ts\` | \`useAuth.ts\` |
145
- | Types | \`camelCase.types.ts\` | \`user.types.ts\` |
146
- | Utils | \`camelCase.utils.ts\` | \`date.utils.ts\` |
147
- | API | \`camelCase.api.ts\` | \`auth.api.ts\` |
148
- | Tests | \`*.test.ts\` or \`*.spec.ts\` | \`auth.test.ts\` |
149
-
150
- ### Code Style
151
-
152
- - **Indentation:** 2 spaces
153
- - **Quotes:** single
154
- - **Semicolons:** required
155
- - **Line Width:** 100
156
- - **Trailing Commas:** es5
157
-
158
- ---
159
-
160
- ## 🔧 Project-Specific Rules
161
-
162
- <!-- ADD YOUR CUSTOM RULES HERE -->
163
-
164
- ### Rule 1: Error Handling
165
-
166
- Always use proper error typing:
167
-
168
- \`\`\`typescript
169
- // ✅ GOOD
170
- try {
171
- await saveUser(user);
172
- } catch (err: unknown) {
173
- const e = err as { response?: { data?: { message?: string } } };
174
- error.value = e.response?.data?.message ?? 'Operation failed';
175
- }
176
-
177
- // BAD
178
- catch (err: any) {
179
- error.value = err.message;
180
- }
181
- \`\`\`
182
-
183
- ### Rule 2: Type Safety
184
-
185
- Always use explicit types, never \`any\`:
186
-
187
- \`\`\`typescript
188
- // GOOD
189
- interface User {
190
- id: string;
191
- name: string;
192
- email: string;
193
- }
194
-
195
- function getUser(id: string): Promise<User> {
196
- return api.get(\`/users/\${id}\`);
197
- }
198
-
199
- // BAD
200
- function getUser(id: any): any {
201
- return api.get(\`/users/\${id}\`);
202
- }
203
- \`\`\`
204
-
205
- ---
206
-
207
- ## 🧪 Testing Standards
208
-
209
- ### Unit Tests
210
-
211
- \`\`\`typescript
212
- describe('useAuthStore', () => {
213
- beforeEach(() => {
214
- // Setup
215
- });
216
-
217
- afterEach(() => {
218
- // Cleanup
219
- });
220
-
221
- it('should login successfully', async () => {
222
- // Arrange
223
- const credentials = { email: 'test@example.com', password: 'password' };
224
-
225
- // Act
226
- await authStore.login(credentials);
227
-
228
- // Assert
229
- expect(authStore.isAuthenticated).toBe(true);
230
- expect(authStore.user).toBeDefined();
231
- });
232
- });
233
- \`\`\`
234
-
235
- ### Test Coverage
236
-
237
- - **Minimum:** 70% coverage
238
- - **Target:** 80% coverage
239
- - **Critical paths:** 100% coverage (auth, payments, etc.)
240
-
241
- ---
242
-
243
- ## 🚀 Quick Reference
244
-
245
- ### Commands
246
-
247
- \`\`\`bash
248
- # Development
249
- npm run dev # Start dev server
250
- npm run build # Production build
251
- npm run test # Run tests
252
- npm run lint # Lint check
253
- npm run format # Format code
254
- npm run type-check # TypeScript check (if applicable)
255
- \`\`\`
256
-
257
- ### Important Files
258
-
259
- \`\`\`
260
- src/
261
- ├── components/ # Reusable components
262
- ├── pages/ # Page components
263
- ├── stores/ # State management
264
- ├── api/ # API calls
265
- ├── types/ # TypeScript types
266
- ├── utils/ # Utility functions
267
- └── hooks/ # Custom hooks/composables
268
- \`\`\`
269
-
270
- ---
271
-
272
- ## 💡 Key Reminders
273
-
274
- 1. **Follow existing patterns** - Check similar files first
275
- 2. **Handle errors gracefully** - Always use try/catch/finally
276
- 3. **Think about loading states** - UX matters
277
- 4. **Use TypeScript strictly** - No \`any\` types
278
- 5. **Test your code** - Write tests for critical logic
279
-
280
- ---
281
-
282
- **Keep this file updated as the project evolves.**
283
- `;
284
- }
285
-
286
- // Main installation function
287
- async function install() {
288
- console.log(banner);
289
-
290
- log.title('🚀 OpenCode Agile Agent Installer');
291
-
292
- // Check if .opencode already exists
293
- const opencodePath = join(process.cwd(), '.opencode');
294
- if (existsSync(opencodePath)) {
295
- log.warn('.opencode directory already exists!');
296
- const overwrite = await question('Do you want to overwrite it? (y/N): ');
297
- if (overwrite.toLowerCase() !== 'y') {
298
- log.info('Installation cancelled.');
299
- rl.close();
300
- return;
301
- }
302
- }
303
-
304
- // Detect project
305
- log.info('Detecting project configuration...');
306
- const detected = await detectProjectFramework();
307
-
308
- log.info(`Detected: ${detected.framework} (${detected.language})`);
309
-
310
- // Ask for project details
311
- log.title('📝 Project Configuration');
312
-
313
- // Project name
314
- let projectName = '';
315
- try {
316
- const packageJson = JSON.parse(readFileSync(join(process.cwd(), 'package.json'), 'utf-8'));
317
- projectName = packageJson.name || 'My Project';
318
- } catch {
319
- projectName = 'My Project';
320
- }
321
-
322
- const customName = await question(`Project name (${projectName}): `);
323
- projectName = customName || projectName;
324
-
325
- // Framework selection
326
- log.info('\nAvailable frameworks:');
327
- frameworkOptions.forEach((opt, i) => {
328
- const marker = opt.value === detected.framework ? ' (detected)' : '';
329
- console.log(` ${i + 1}. ${opt.name}${marker}`);
330
- });
331
-
332
- const frameworkInput = await question(`Select framework (1-${frameworkOptions.length}) [default: ${detected.framework}]: `);
333
- let framework = detected.framework;
334
- if (frameworkInput) {
335
- const idx = parseInt(frameworkInput) - 1;
336
- if (idx >= 0 && idx < frameworkOptions.length) {
337
- framework = frameworkOptions[idx].value;
338
- }
339
- }
340
-
341
- // Language
342
- const languageInput = await question(`Language (typescript/javascript) [default: ${detected.language}]: `);
343
- const language = languageInput || detected.language;
344
-
345
- // Styling
346
- const stylingInput = await question(`Styling (tailwind/css-modules/styled-components/scss/other) [default: tailwind]: `);
347
- const styling = stylingInput || 'tailwind';
348
-
349
- // State management
350
- const stateInput = await question(`State management (pinia/zustand/redux/none) [default: none]: `);
351
- const stateManagement = stateInput || 'none';
352
-
353
- // Testing
354
- const testingInput = await question(`Testing (jest/vitest/mocha/none) [default: vitest]: `);
355
- const testing = testingInput || 'vitest';
356
-
357
- const config = { projectName, framework, language, styling, stateManagement, testing };
358
-
359
- log.title('📦 Installing OpenCode Agile Agent...');
360
-
361
- // Create .opencode directory
362
- try {
363
- // Copy templates
364
- log.info('Copying templates...');
365
-
366
- // Create .opencode directory
367
- if (!existsSync(opencodePath)) {
368
- mkdirSync(opencodePath, { recursive: true });
369
- }
370
-
371
- // Copy all template files
372
- const templateOpencode = join(templatesDir, '.opencode');
373
- if (existsSync(templateOpencode)) {
374
- cpSync(templateOpencode, opencodePath, { recursive: true, overwrite: true });
375
- log.success('Templates copied successfully');
376
- } else {
377
- log.error('Templates directory not found!');
378
- rl.close();
379
- return;
380
- }
381
-
382
- // Generate AGENTS.md
383
- log.info('Generating AGENTS.md...');
384
- const agentsMd = generateAgentsMd(config);
385
- writeFileSync(join(process.cwd(), 'AGENTS.md'), agentsMd);
386
- log.success('AGENTS.md generated');
387
-
388
- // Success message
389
- log.title('✅ Installation Complete!');
390
-
391
- console.log(`
392
- ${colors.green}OpenCode Agile Agent has been installed successfully!${colors.reset}
393
-
394
- ${colors.cyan}What's included:${colors.reset}
395
- ✓ .opencode/agents/ - Full LLM SpecKit agent set
396
- ✓ .opencode/skills/ - Reusable skill modules
397
- ✓ .opencode/workflows/ - Workflow command templates
398
- ✓ .opencode/rules/ - Shared coding and git conventions
399
- ✓ .opencode/README.md - Kit documentation
400
- ✓ AGENTS.md - Project-specific coding standards
401
-
402
- ${colors.cyan}Next steps:${colors.reset}
403
- 1. Review and customize AGENTS.md for your project
404
- 2. Start using agents with your AI assistant
405
- 3. Read .opencode/README.md for detailed workflow
406
-
407
- ${colors.cyan}Example usage:${colors.reset}
408
- "I want to implement user authentication with JWT"
409
-
410
- The agents will:
411
- → Gather requirements
412
- → Create specs and task breakdown
413
- → Implement the feature
414
- → Review code quality
415
- → ✅ Deliver production-ready code
416
-
417
- ${colors.yellow}Happy coding with AI-powered agile development! 🚀${colors.reset}
418
- `);
419
-
420
- } catch (error) {
421
- log.error(`Installation failed: ${error.message}`);
422
- console.error(error);
423
- }
424
-
425
- rl.close();
426
- }
427
-
428
- // Run installation
429
- install().catch((error) => {
430
- log.error(`Unexpected error: ${error.message}`);
431
- console.error(error);
432
- rl.close();
433
- process.exit(1);
434
- });
1
+ #!/usr/bin/env node
2
+
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join, sep } from 'path';
5
+ import { existsSync, mkdirSync, cpSync, writeFileSync, readFileSync, rmSync } from 'fs';
6
+ import { createInterface } from 'readline';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const rootDir = join(__dirname, '..');
11
+ const templatesDir = join(rootDir, 'templates');
12
+
13
+ const colors = {
14
+ reset: '\x1b[0m',
15
+ bright: '\x1b[1m',
16
+ green: '\x1b[32m',
17
+ blue: '\x1b[34m',
18
+ yellow: '\x1b[33m',
19
+ cyan: '\x1b[36m',
20
+ red: '\x1b[31m',
21
+ };
22
+
23
+ const log = {
24
+ info: (msg) => console.log(`${colors.blue}[i]${colors.reset} ${msg}`),
25
+ success: (msg) => console.log(`${colors.green}[ok]${colors.reset} ${msg}`),
26
+ warn: (msg) => console.log(`${colors.yellow}[!]${colors.reset} ${msg}`),
27
+ error: (msg) => console.log(`${colors.red}[x]${colors.reset} ${msg}`),
28
+ title: (msg) => console.log(`\n${colors.bright}${colors.cyan}${msg}${colors.reset}\n`),
29
+ };
30
+
31
+ const banner = `
32
+ ${colors.cyan}========================================${colors.reset}
33
+ ${colors.cyan} OpenCode Agile Agent Installer ${colors.reset}
34
+ ${colors.cyan} Spec-driven, one-prompt setup ${colors.reset}
35
+ ${colors.cyan}========================================${colors.reset}
36
+ `;
37
+
38
+ const rl = createInterface({
39
+ input: process.stdin,
40
+ output: process.stdout,
41
+ });
42
+
43
+ const question = (prompt) => new Promise((resolve) => {
44
+ rl.question(prompt, resolve);
45
+ });
46
+
47
+ const args = process.argv.slice(2);
48
+ if (args.includes('--help') || args.includes('-h')) {
49
+ console.log(`
50
+ OpenCode Agile Agent Installer
51
+
52
+ Usage:
53
+ opencode-agile-agent
54
+
55
+ What it does:
56
+ - Asks one yes/no question.
57
+ - Detects the project stack automatically.
58
+ - Copies .opencode and generates AGENTS.md.
59
+
60
+ Commands:
61
+ --help, -h Show this help message
62
+ `);
63
+ rl.close();
64
+ process.exit(0);
65
+ }
66
+
67
+ function shouldCopyPath(path) {
68
+ return !path.includes(`${sep}node_modules${sep}`) && !path.endsWith(`${sep}node_modules`);
69
+ }
70
+
71
+ function detectProjectContext() {
72
+ const defaults = {
73
+ projectName: 'My Project',
74
+ framework: 'Generic',
75
+ language: 'JavaScript',
76
+ styling: 'Follow existing styles',
77
+ stateManagement: 'None / follow existing patterns',
78
+ testing: 'Follow existing tests',
79
+ };
80
+
81
+ const packageJsonPath = join(rootDir, 'package.json');
82
+
83
+ if (!existsSync(packageJsonPath)) {
84
+ return defaults;
85
+ }
86
+
87
+ try {
88
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
89
+ const deps = { ...(packageJson.dependencies ?? {}), ...(packageJson.devDependencies ?? {}) };
90
+ const has = (name) => Boolean(deps[name]);
91
+
92
+ const framework = has('next')
93
+ ? 'Next.js'
94
+ : has('nuxt')
95
+ ? 'Nuxt.js'
96
+ : has('@angular/core')
97
+ ? 'Angular'
98
+ : has('svelte')
99
+ ? 'Svelte'
100
+ : has('vue') || has('vue2') || has('vue3')
101
+ ? 'Vue'
102
+ : has('react-native') || has('expo')
103
+ ? 'React Native'
104
+ : has('react') || has('react-dom')
105
+ ? 'React'
106
+ : has('express')
107
+ ? 'Express'
108
+ : has('fastify')
109
+ ? 'Fastify'
110
+ : has('nestjs') || has('@nestjs/core')
111
+ ? 'NestJS'
112
+ : 'Generic';
113
+
114
+ const language = has('typescript') || existsSync(join(rootDir, 'tsconfig.json'))
115
+ ? 'TypeScript'
116
+ : 'JavaScript';
117
+
118
+ const styling = has('tailwindcss')
119
+ ? 'Tailwind'
120
+ : has('styled-components')
121
+ ? 'Styled Components'
122
+ : has('sass') || has('scss')
123
+ ? 'SCSS'
124
+ : has('css-modules')
125
+ ? 'CSS Modules'
126
+ : 'Follow existing styles';
127
+
128
+ const stateManagement = has('pinia')
129
+ ? 'Pinia'
130
+ : has('zustand')
131
+ ? 'Zustand'
132
+ : has('redux') || has('@reduxjs/toolkit')
133
+ ? 'Redux'
134
+ : has('mobx')
135
+ ? 'MobX'
136
+ : 'None / follow existing patterns';
137
+
138
+ const testing = has('vitest')
139
+ ? 'Vitest'
140
+ : has('jest')
141
+ ? 'Jest'
142
+ : has('playwright')
143
+ ? 'Playwright'
144
+ : has('cypress')
145
+ ? 'Cypress'
146
+ : 'Follow existing tests';
147
+
148
+ return {
149
+ ...defaults,
150
+ projectName: packageJson.name || defaults.projectName,
151
+ framework,
152
+ language,
153
+ styling,
154
+ stateManagement,
155
+ testing,
156
+ };
157
+ } catch {
158
+ return defaults;
159
+ }
160
+ }
161
+
162
+ function generateAgentsMd(context) {
163
+ return `# AGENTS.md - ${context.projectName}
164
+
165
+ > Instructions for AI agents working on this project.
166
+ >
167
+ > How to build lives here; what to build comes from feature specs.
168
+
169
+ ---
170
+
171
+ ## Project Stack
172
+
173
+ - Framework: ${context.framework}
174
+ - Language: ${context.language}
175
+ - State Management: ${context.stateManagement}
176
+ - Styling: ${context.styling}
177
+ - Testing: ${context.testing}
178
+
179
+ ---
180
+
181
+ ## Core Documentation
182
+
183
+ Review these before making architectural or styling decisions:
184
+
185
+ | Document | Purpose | Location |
186
+ |----------|---------|----------|
187
+ | OpenCode README | Kit overview and flow | .opencode/README.md |
188
+ | OpenCode Architecture | Agent lifecycle and gates | .opencode/ARCHITECTURE.md |
189
+ | Agent prompts | Role-specific behavior | .opencode/agents/*.md |
190
+ | Commands | Custom slash commands | .opencode/commands/*.md |
191
+ | Rules | Shared coding standards | .opencode/rules/*.md |
192
+
193
+ ---
194
+
195
+ ## OpenCode Delivery Model
196
+
197
+ - Primary agent: @feature-lead
198
+ - First call: @context-gatherer maps the current project state before planning or proof.
199
+ - Other agents are subagents and are called with @ awareness.
200
+ - Security-sensitive work: @security-auditor first, then @penetration-tester for redteam validation when needed.
201
+
202
+ ## Compact Context Bundle
203
+
204
+ - proposal.md: why, value, scope
205
+ - goal.md: target outcome, constraints, default choice
206
+ - spec.md: contract, data flow, edge cases, risks
207
+ - task.md: ordered checklist, dependencies, owners
208
+ - important.md: facts, blockers, links, decisions
209
+
210
+ ## Archive
211
+
212
+ - Archive completed bundles in .opencode/archive/<feature-slug>/.
213
+
214
+ ## Decision Style
215
+
216
+ - Default first: choose a safe default when the downside is small.
217
+ - Ask only when scope, security, or architecture changes materially.
218
+ - Keep handoffs compact and explicit.
219
+
220
+ ---
221
+
222
+ ## Code Conventions
223
+
224
+ ### File Naming
225
+
226
+ | Type | Pattern | Example |
227
+ |------|---------|---------|
228
+ | Components | PascalCase.tsx | UserCard.tsx |
229
+ | Pages | PascalCasePage.tsx | LoginPage.tsx |
230
+ | Stores | camelCase.store.ts | auth.store.ts |
231
+ | Hooks/Composables | useCamelCase.ts | useAuth.ts |
232
+ | Types | camelCase.types.ts | user.types.ts |
233
+ | Utils | camelCase.utils.ts | date.utils.ts |
234
+ | API | camelCase.api.ts | auth.api.ts |
235
+ | Tests | *.test.ts or *.spec.ts | auth.test.ts |
236
+
237
+ ### Code Style
238
+
239
+ - Indentation: 2 spaces
240
+ - Quotes: single
241
+ - Semicolons: required
242
+ - Line Width: 100
243
+ - Trailing Commas: es5
244
+
245
+ ---
246
+
247
+ ## Project-Specific Rules
248
+
249
+ - Keep changes aligned to the compact spec bundle.
250
+ - Prefer defaults when a tradeoff is low risk.
251
+ - Surface options only when the decision changes scope, security, or architecture.
252
+
253
+ ---
254
+
255
+ ## Architecture Patterns
256
+
257
+ ### State Management
258
+
259
+ - Describe where state lives.
260
+ - Keep async work inside the owning module.
261
+ - Make loading and error states explicit.
262
+
263
+ ### API Layer
264
+
265
+ - Keep request and response shapes explicit.
266
+ - Normalize errors near the boundary.
267
+ - Version breaking contract changes.
268
+
269
+ ### Component Structure
270
+
271
+ - Keep presentation and side effects separated.
272
+ - Put validation near the boundary.
273
+ - Make loading and error states visible.
274
+
275
+ ---
276
+
277
+ ## Testing Standards
278
+
279
+ - Unit tests: pure logic and helper functions.
280
+ - Integration tests: API calls and service boundaries.
281
+ - E2E tests: critical user journeys.
282
+ - Quality gate: verify behavior before release.
283
+
284
+ ---
285
+
286
+ ## Quick Reference
287
+
288
+ - Validate templates: node bin/validate-templates.js
289
+ - Sync templates: node bin/sync-templates.js
290
+ - Entry point: @feature-lead
291
+ `;
292
+ }
293
+
294
+ async function install() {
295
+ console.log(banner);
296
+ log.title('OpenCode Agile Agent Installer');
297
+
298
+ const confirm = await question('Install the OpenCode agent kit now? [y/N]: ');
299
+ if (!/^y(es)?$/i.test(confirm.trim())) {
300
+ log.info('Install cancelled.');
301
+ rl.close();
302
+ return;
303
+ }
304
+
305
+ const context = detectProjectContext();
306
+ log.info(`Detected project: ${context.projectName} (${context.framework}, ${context.language})`);
307
+
308
+ const source = join(templatesDir, '.opencode');
309
+ const target = join(rootDir, '.opencode');
310
+
311
+ if (!existsSync(templatesDir) || !existsSync(source)) {
312
+ log.error('Template source not found.');
313
+ rl.close();
314
+ process.exitCode = 1;
315
+ return;
316
+ }
317
+
318
+ if (!existsSync(target)) {
319
+ mkdirSync(target, { recursive: true });
320
+ } else {
321
+ rmSync(target, { recursive: true, force: true });
322
+ mkdirSync(target, { recursive: true });
323
+ }
324
+
325
+ cpSync(source, target, {
326
+ recursive: true,
327
+ overwrite: true,
328
+ filter: shouldCopyPath,
329
+ });
330
+ writeFileSync(join(rootDir, 'AGENTS.md'), generateAgentsMd(context), 'utf-8');
331
+
332
+ log.success('Installed .opencode and generated AGENTS.md.');
333
+ log.title('Done');
334
+ console.log('Start with @feature-lead and review .opencode/README.md for the flow.');
335
+
336
+ rl.close();
337
+ }
338
+
339
+ install().catch((error) => {
340
+ log.error(`Unexpected error: ${error.message}`);
341
+ console.error(error);
342
+ rl.close();
343
+ process.exit(1);
344
+ });