opencode-agile-agent 1.0.4 → 1.2.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.
Files changed (70) hide show
  1. package/README.md +78 -14
  2. package/bin/cli.js +180 -254
  3. package/bin/sync-templates.js +1 -7
  4. package/bin/validate-templates.js +17 -19
  5. package/package.json +1 -1
  6. package/templates/.opencode/ARCHITECTURE.md +94 -64
  7. package/templates/.opencode/README.md +115 -63
  8. package/templates/.opencode/agents/archiver.md +45 -0
  9. package/templates/.opencode/agents/backend-specialist.md +43 -46
  10. package/templates/.opencode/agents/context-gatherer.md +26 -26
  11. package/templates/.opencode/agents/debugger.md +45 -45
  12. package/templates/.opencode/agents/developer.md +54 -45
  13. package/templates/.opencode/agents/devops-engineer.md +42 -45
  14. package/templates/.opencode/agents/feature-lead.md +81 -50
  15. package/templates/.opencode/agents/frontend-specialist.md +44 -46
  16. package/templates/.opencode/agents/performance-optimizer.md +45 -45
  17. package/templates/.opencode/agents/pr-reviewer.md +46 -45
  18. package/templates/.opencode/agents/project-planner.md +41 -45
  19. package/templates/.opencode/agents/retrospective-writer.md +48 -0
  20. package/templates/.opencode/agents/security-auditor.md +39 -45
  21. package/templates/.opencode/agents/system-analyst.md +43 -43
  22. package/templates/.opencode/agents/test-engineer.md +44 -44
  23. package/templates/.opencode/bun.lock +18 -0
  24. package/templates/.opencode/commands/archive.md +15 -0
  25. package/templates/.opencode/commands/assign-models.md +39 -0
  26. package/templates/.opencode/commands/brainstorm.md +5 -2
  27. package/templates/.opencode/commands/check-progress.md +21 -0
  28. package/templates/.opencode/commands/create.md +8 -3
  29. package/templates/.opencode/commands/plan.md +7 -2
  30. package/templates/.opencode/commands/reframe.md +17 -0
  31. package/templates/.opencode/commands/review.md +9 -3
  32. package/templates/.opencode/commands/rubber-duck.md +14 -0
  33. package/templates/.opencode/commands/status.md +3 -0
  34. package/templates/.opencode/commands/test.md +8 -3
  35. package/templates/.opencode/config.template.json +160 -20
  36. package/templates/.opencode/package-lock.json +115 -0
  37. package/templates/.opencode/package.json +6 -0
  38. package/templates/.opencode/plugins/session-artifacts.ts +611 -0
  39. package/templates/.opencode/skills/archive-writing/SKILL.md +36 -0
  40. package/templates/.opencode/skills/artifact-discipline/SKILL.md +30 -0
  41. package/templates/.opencode/skills/clarify-first/SKILL.md +34 -0
  42. package/templates/.opencode/skills/context-archive/SKILL.md +10 -26
  43. package/templates/.opencode/skills/context-gathering/SKILL.md +2 -0
  44. package/templates/.opencode/skills/intelligent-routing/SKILL.md +10 -2
  45. package/templates/.opencode/skills/plan-writing/SKILL.md +5 -5
  46. package/templates/.opencode/templates/brief.template.md +25 -0
  47. package/templates/.opencode/templates/notes.template.md +13 -0
  48. package/templates/.opencode/templates/review-summary.template.md +6 -0
  49. package/templates/.opencode/templates/session-summary.template.md +7 -0
  50. package/templates/.opencode/templates/spec.template.md +17 -0
  51. package/templates/.opencode/templates/status.template.yaml +14 -0
  52. package/templates/.opencode/templates/task.template.md +5 -0
  53. package/templates/opencode.json +12 -0
  54. package/templates/.opencode/agents/api-designer.md +0 -45
  55. package/templates/.opencode/agents/code-archaeologist.md +0 -45
  56. package/templates/.opencode/agents/database-architect.md +0 -45
  57. package/templates/.opencode/agents/documentation-writer.md +0 -45
  58. package/templates/.opencode/agents/explorer-agent.md +0 -55
  59. package/templates/.opencode/agents/game-developer.md +0 -45
  60. package/templates/.opencode/agents/mobile-developer.md +0 -45
  61. package/templates/.opencode/agents/orchestrator.md +0 -48
  62. package/templates/.opencode/agents/penetration-tester.md +0 -46
  63. package/templates/.opencode/agents/product-manager.md +0 -46
  64. package/templates/.opencode/agents/qa-automation-engineer.md +0 -46
  65. package/templates/.opencode/agents/seo-specialist.md +0 -45
  66. package/templates/.opencode/archive/README.md +0 -24
  67. package/templates/.opencode/commands/debug.md +0 -10
  68. package/templates/.opencode/skills/parallel-agents/SKILL.md +0 -38
  69. package/templates/.opencode/skills/redteam-validation/SKILL.md +0 -33
  70. package/templates/AGENTS.template.md +0 -300
package/README.md CHANGED
@@ -7,10 +7,53 @@ Scaffold the OpenCode spec-driven agent kit into any project with one confirmati
7
7
  - `npx opencode-agile-agent@latest`
8
8
  - `npx create-opencode-agile`
9
9
 
10
+ ## Example Command Flow
11
+
12
+ ### Start a new project
13
+ 1. Run `npx opencode-agile-agent` inside the repo you want to set up.
14
+ 2. Ask the main agent to clarify the project direction first.
15
+ 3. Use `/brainstorm` if the idea is still fuzzy.
16
+ 4. Use `/plan` once the scope is clear enough to turn into `brief.md`, `spec.md`, `task.md`, `notes.md`, and `status.yaml`.
17
+ 5. Use `/create` when you want the first implementation pass to begin.
18
+ 6. Use `/test` and `/review` before considering the first slice done.
19
+ 7. Use `/archive` when the finished slice is approved and worth preserving as a summary.
20
+
21
+ Example:
22
+ ```text
23
+ /brainstorm build a small SaaS app for tracking invoices for freelancers
24
+ /plan turn this into an MVP with auth, invoice CRUD, and dashboard
25
+ /create implement the MVP from the approved plan
26
+ /test cover the critical flows
27
+ /review check if this is safe to ship
28
+ /archive summarize what shipped in the approved MVP slice
29
+ ```
30
+
31
+ ### Start a new feature in an existing project
32
+ 1. Start with `/brainstorm` if the request is ambiguous.
33
+ 2. Use `/plan` to define the feature boundary and acceptance criteria.
34
+ 3. Use `/create` to implement the approved feature slice.
35
+ 4. Use `/test` to verify behavior and nearby paths.
36
+ 5. Use `/review` for the final quality gate.
37
+ 6. Use `/archive` after approval to store a finished work summary.
38
+ 7. Use `/status` anytime you want a compact progress snapshot.
39
+
40
+ Example:
41
+ ```text
42
+ /plan add team invitations with email, role selection, and acceptance flow
43
+ /create implement the invitations feature
44
+ /test verify invite creation, email sending, and acceptance flow
45
+ /review check for regressions, dead code, and spec drift
46
+ /archive summarize the approved invitations slice
47
+ ```
48
+
10
49
  ## Install Flow
11
50
  - The installer asks one yes/no question.
12
51
  - Framework, language, and project name are auto-detected.
13
- - Confirmed installs copy `.opencode` into the current project root and generate `AGENTS.md` there.
52
+ - Confirmed installs merge `.opencode` into the current project root instead of replacing it.
53
+ - If `templates/opencode.json` exists, the installer also creates or merges `opencode.json` in the project root.
54
+ - If the project already uses `opencode.jsonc` (and not `opencode.json`), the installer skips config installation to avoid creating a competing config file.
55
+ - Missing files are created. Existing `.md`, `.txt`, and `.gitignore` files receive appended template content when it is not already present.
56
+ - Existing `.json` files are merged key-wise without clobbering existing values. Other structured files like `.ts` and `.yaml` are left in place and are not overwritten.
14
57
  - Declining exits immediately.
15
58
 
16
59
  ## Local Install
@@ -18,37 +61,58 @@ Scaffold the OpenCode spec-driven agent kit into any project with one confirmati
18
61
  - The installer writes to the current working directory, not the package directory.
19
62
 
20
63
  ## What Gets Installed
21
- - 25 agents
22
- - 14 skills
23
- - 7 commands
64
+ - 15 agents
65
+ - 15 skills
66
+ - 11 commands
67
+ - 1 runtime plugin
24
68
  - Shared rules, docs, and project config
25
69
 
70
+ ## Plannotator Integration
71
+
72
+ - The template includes `templates/opencode.json` with `@plannotator/opencode@latest` configured.
73
+ - With Plannotator enabled, `/plan` will use `submit_plan` (when available) to open a browser UI for plan approval and feedback.
74
+
26
75
  ## Custom Commands
27
76
  - `.opencode/commands/*.md` holds the slash commands.
28
77
  - Each command uses Markdown frontmatter plus a prompt body, matching OpenCode's command format.
29
- - The command set is `brainstorm`, `create`, `debug`, `plan`, `review`, `status`, and `test`.
78
+ - The command set is `archive`, `assign-models`, `brainstorm`, `check-progress`, `create`, `plan`, `reframe`, `review`, `rubber-duck`, `status`, and `test`.
30
79
 
31
80
  ## Design Notes
32
81
  - Skills are compact, philosophy-first, and loaded by intent.
33
- - `security-gate` decides when a change needs a security gate or redteam phase.
34
- - `redteam-validation` simulates attacker behavior and proves exploitability.
35
- - `qa-automation-engineer` is support-only for harness and CI plumbing, not the default test path.
36
- - `orchestrator` is optional; default routing stays with `feature-lead` and the owning specialists.
37
- - The compact planning bundle is proposal.md, goal.md, spec.md, task.md, and important.md.
82
+ - `artifact-discipline` and `clean-code` are the core quality spine.
83
+ - `session-artifacts` is the runtime spine that preserves active feature state and safe handoffs.
84
+ - `retrospective-writer` captures reusable lessons from real failures and asks whether they should become a skill or rule.
85
+ - `archiver` is a dedicated archive-summary agent, not just a final step bolted onto the lead.
86
+ - `session_artifact_repo_delta` guards summaries and reviews against drift from the real git state.
87
+ - The compact planning bundle is brief.md, spec.md, task.md, notes.md, and status.yaml.
38
88
  - `@feature-lead` is the primary entry point; the rest are subagents.
39
- - Completed feature bundles are archived in `.opencode/archive/<feature-slug>/`.
89
+ - `.opencode/artifacts/` is local runtime state and is git-ignored by the installed template.
90
+ - Completed work summaries are archived in `.opencode/archive/<feature-slug>.md`.
91
+
92
+ ## Flow Notes
93
+ - The default command spine is `/brainstorm -> /plan -> /create -> /test -> /review -> /archive`.
94
+ - The flow is not rigid. You can reuse commands mid-stream when the work needs them.
95
+ - Examples:
96
+ - `/brainstorm` again when implementation reveals ambiguity.
97
+ - `/plan` again when scope changes materially.
98
+ - `/test` on an intermediate risky slice.
99
+ - `/review` before the whole feature is finished.
100
+ - `/archive` for one completed slice while larger work continues.
40
101
 
41
102
  ## Spec-Driven Flow
42
103
  - `@feature-lead` is the primary entry point.
104
+ - `session_artifact_current` restores active feature state before deeper work.
43
105
  - `@context-gatherer` maps the current project before any planning or proof.
44
- - `@project-planner` and `@system-analyst` build `proposal.md`, `goal.md`, `spec.md`, `task.md`, and `important.md`.
106
+ - `@project-planner` and `@system-analyst` build `brief.md`, `spec.md`, `task.md`, `notes.md`, and `status.yaml`.
45
107
  - `@developer` implements the approved spec.
46
- - `@test-engineer`, `@security-auditor`, `@penetration-tester`, and `@pr-reviewer` close the loop.
47
- - `@feature-lead` archives the completed bundle in `.opencode/archive/<feature-slug>/`.
108
+ - `@test-engineer`, `@retrospective-writer`, `@security-auditor`, and `@pr-reviewer` close the loop.
109
+ - When a resolved failure exposes a durable lesson, ask whether to promote it into a reusable skill or rule.
110
+ - `@feature-lead` archives the completed work summary in `.opencode/archive/<feature-slug>.md` through the artifact finalizer.
48
111
 
49
112
  ## Template Source Of Truth
50
113
  - `templates/.opencode` mirrors the project kit.
51
114
  - Use `node bin/sync-templates.js` after editing `.opencode`.
115
+ - `AGENTS.md` is generated per installed project and is not stored as a reusable template.
52
116
 
53
117
  ## Requirements
54
118
  - Node.js 16+
package/bin/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
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';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, extname, join, sep } from 'path';
5
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';
6
+ import { createInterface } from 'readline';
7
7
 
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
@@ -45,18 +45,18 @@ const question = (prompt) => new Promise((resolve) => {
45
45
  rl.question(prompt, resolve);
46
46
  });
47
47
 
48
- const args = process.argv.slice(2);
49
- if (args.includes('--help') || args.includes('-h')) {
50
- console.log(`
51
- OpenCode Agile Agent Installer
48
+ const args = process.argv.slice(2);
49
+ if (args.includes('--help') || args.includes('-h')) {
50
+ console.log(`
51
+ OpenCode Agile Agent Installer
52
52
 
53
53
  Usage:
54
54
  opencode-agile-agent
55
55
 
56
- What it does:
57
- - Asks one yes/no question.
58
- - Detects the project stack automatically.
59
- - Copies .opencode and generates AGENTS.md.
56
+ What it does:
57
+ - Asks one yes/no question.
58
+ - Merges .opencode into the current project.
59
+ - Optionally merges opencode.json into the project root.
60
60
 
61
61
  Commands:
62
62
  --help, -h Show this help message
@@ -65,234 +65,168 @@ Commands:
65
65
  process.exit(0);
66
66
  }
67
67
 
68
- function shouldCopyPath(path) {
69
- return !path.includes(`${sep}node_modules${sep}`) && !path.endsWith(`${sep}node_modules`);
70
- }
71
-
72
- function detectProjectContext() {
73
- const defaults = {
74
- projectName: 'My Project',
75
- framework: 'Generic',
76
- language: 'JavaScript',
77
- styling: 'Follow existing styles',
78
- stateManagement: 'None / follow existing patterns',
79
- testing: 'Follow existing tests',
80
- };
81
-
82
- const packageJsonPath = join(projectRoot, 'package.json');
83
-
84
- if (!existsSync(packageJsonPath)) {
85
- return defaults;
86
- }
87
-
88
- try {
89
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
90
- const deps = { ...(packageJson.dependencies ?? {}), ...(packageJson.devDependencies ?? {}) };
91
- const has = (name) => Boolean(deps[name]);
92
-
93
- const framework = has('next')
94
- ? 'Next.js'
95
- : has('nuxt')
96
- ? 'Nuxt.js'
97
- : has('@angular/core')
98
- ? 'Angular'
99
- : has('svelte')
100
- ? 'Svelte'
101
- : has('vue') || has('vue2') || has('vue3')
102
- ? 'Vue'
103
- : has('react-native') || has('expo')
104
- ? 'React Native'
105
- : has('react') || has('react-dom')
106
- ? 'React'
107
- : has('express')
108
- ? 'Express'
109
- : has('fastify')
110
- ? 'Fastify'
111
- : has('nestjs') || has('@nestjs/core')
112
- ? 'NestJS'
113
- : 'Generic';
114
-
115
- const language = has('typescript') || existsSync(join(rootDir, 'tsconfig.json'))
116
- ? 'TypeScript'
117
- : 'JavaScript';
118
-
119
- const styling = has('tailwindcss')
120
- ? 'Tailwind'
121
- : has('styled-components')
122
- ? 'Styled Components'
123
- : has('sass') || has('scss')
124
- ? 'SCSS'
125
- : has('css-modules')
126
- ? 'CSS Modules'
127
- : 'Follow existing styles';
128
-
129
- const stateManagement = has('pinia')
130
- ? 'Pinia'
131
- : has('zustand')
132
- ? 'Zustand'
133
- : has('redux') || has('@reduxjs/toolkit')
134
- ? 'Redux'
135
- : has('mobx')
136
- ? 'MobX'
137
- : 'None / follow existing patterns';
138
-
139
- const testing = has('vitest')
140
- ? 'Vitest'
141
- : has('jest')
142
- ? 'Jest'
143
- : has('playwright')
144
- ? 'Playwright'
145
- : has('cypress')
146
- ? 'Cypress'
147
- : 'Follow existing tests';
148
-
149
- return {
150
- ...defaults,
151
- projectName: packageJson.name || defaults.projectName,
152
- framework,
153
- language,
154
- styling,
155
- stateManagement,
156
- testing,
157
- };
158
- } catch {
159
- return defaults;
160
- }
161
- }
162
-
163
- function generateAgentsMd(context) {
164
- return `# AGENTS.md - ${context.projectName}
165
-
166
- > Instructions for AI agents working on this project.
167
- >
168
- > How to build lives here; what to build comes from feature specs.
169
-
170
- ---
171
-
172
- ## Project Stack
173
-
174
- - Framework: ${context.framework}
175
- - Language: ${context.language}
176
- - State Management: ${context.stateManagement}
177
- - Styling: ${context.styling}
178
- - Testing: ${context.testing}
179
-
180
- ---
181
-
182
- ## Core Documentation
183
-
184
- Review these before making architectural or styling decisions:
185
-
186
- | Document | Purpose | Location |
187
- |----------|---------|----------|
188
- | OpenCode README | Kit overview and flow | .opencode/README.md |
189
- | OpenCode Architecture | Agent lifecycle and gates | .opencode/ARCHITECTURE.md |
190
- | Agent prompts | Role-specific behavior | .opencode/agents/*.md |
191
- | Commands | Custom slash commands | .opencode/commands/*.md |
192
- | Rules | Shared coding standards | .opencode/rules/*.md |
193
-
194
- ---
195
-
196
- ## OpenCode Delivery Model
68
+ function shouldCopyPath(path) {
69
+ return !path.includes(`${sep}node_modules${sep}`) && !path.endsWith(`${sep}node_modules`);
70
+ }
197
71
 
198
- - Primary agent: @feature-lead
199
- - First call: @context-gatherer maps the current project state before planning or proof.
200
- - Other agents are subagents and are called with @ awareness.
201
- - Security-sensitive work: @security-auditor first, then @penetration-tester for redteam validation when needed.
72
+ function isAppendableTextFile(path) {
73
+ return path.endsWith('.md') || path.endsWith('.txt') || path.endsWith('.gitignore');
74
+ }
202
75
 
203
- ## Compact Context Bundle
76
+ function isJsonFile(path) {
77
+ return extname(path) === '.json';
78
+ }
204
79
 
205
- - proposal.md: why, value, scope
206
- - goal.md: target outcome, constraints, default choice
207
- - spec.md: contract, data flow, edge cases, risks
208
- - task.md: ordered checklist, dependencies, owners
209
- - important.md: facts, blockers, links, decisions
80
+ function appendUniqueContent(targetPath, sourceContent) {
81
+ const targetContent = readFileSync(targetPath, 'utf8');
82
+ const trimmedSource = sourceContent.trim();
83
+ if (!trimmedSource || targetContent.includes(trimmedSource)) {
84
+ return 'skipped';
85
+ }
210
86
 
211
- ## Archive
87
+ const nextContent = `${targetContent.trimEnd()}\n\n${trimmedSource}\n`;
88
+ writeFileSync(targetPath, nextContent);
89
+ return 'appended';
90
+ }
212
91
 
213
- - Archive completed bundles in .opencode/archive/<feature-slug>/.
92
+ function isPlainObject(value) {
93
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
94
+ }
214
95
 
215
- ## Decision Style
216
-
217
- - Default first: choose a safe default when the downside is small.
218
- - Ask only when scope, security, or architecture changes materially.
219
- - Keep handoffs compact and explicit.
220
-
221
- ---
222
-
223
- ## Code Conventions
224
-
225
- ### File Naming
226
-
227
- | Type | Pattern | Example |
228
- |------|---------|---------|
229
- | Components | PascalCase.tsx | UserCard.tsx |
230
- | Pages | PascalCasePage.tsx | LoginPage.tsx |
231
- | Stores | camelCase.store.ts | auth.store.ts |
232
- | Hooks/Composables | useCamelCase.ts | useAuth.ts |
233
- | Types | camelCase.types.ts | user.types.ts |
234
- | Utils | camelCase.utils.ts | date.utils.ts |
235
- | API | camelCase.api.ts | auth.api.ts |
236
- | Tests | *.test.ts or *.spec.ts | auth.test.ts |
237
-
238
- ### Code Style
239
-
240
- - Indentation: 2 spaces
241
- - Quotes: single
242
- - Semicolons: required
243
- - Line Width: 100
244
- - Trailing Commas: es5
245
-
246
- ---
247
-
248
- ## Project-Specific Rules
249
-
250
- - Keep changes aligned to the compact spec bundle.
251
- - Prefer defaults when a tradeoff is low risk.
252
- - Surface options only when the decision changes scope, security, or architecture.
253
-
254
- ---
255
-
256
- ## Architecture Patterns
257
-
258
- ### State Management
259
-
260
- - Describe where state lives.
261
- - Keep async work inside the owning module.
262
- - Make loading and error states explicit.
263
-
264
- ### API Layer
265
-
266
- - Keep request and response shapes explicit.
267
- - Normalize errors near the boundary.
268
- - Version breaking contract changes.
269
-
270
- ### Component Structure
271
-
272
- - Keep presentation and side effects separated.
273
- - Put validation near the boundary.
274
- - Make loading and error states visible.
275
-
276
- ---
277
-
278
- ## Testing Standards
279
-
280
- - Unit tests: pure logic and helper functions.
281
- - Integration tests: API calls and service boundaries.
282
- - E2E tests: critical user journeys.
283
- - Quality gate: verify behavior before release.
284
-
285
- ---
286
-
287
- ## Quick Reference
288
-
289
- - Validate templates: node bin/validate-templates.js
290
- - Sync templates: node bin/sync-templates.js
291
- - Entry point: @feature-lead
292
- `;
293
- }
294
-
295
- async function install() {
96
+ function mergeJsonValues(targetValue, sourceValue) {
97
+ if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
98
+ const seen = new Set(targetValue.map((item) => JSON.stringify(item)));
99
+ const merged = [...targetValue];
100
+ for (const item of sourceValue) {
101
+ const key = JSON.stringify(item);
102
+ if (!seen.has(key)) {
103
+ seen.add(key);
104
+ merged.push(item);
105
+ }
106
+ }
107
+ return merged;
108
+ }
109
+
110
+ if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
111
+ const merged = { ...sourceValue };
112
+ for (const [key, value] of Object.entries(targetValue)) {
113
+ if (Object.prototype.hasOwnProperty.call(sourceValue, key)) {
114
+ merged[key] = mergeJsonValues(value, sourceValue[key]);
115
+ } else {
116
+ merged[key] = value;
117
+ }
118
+ }
119
+ return merged;
120
+ }
121
+
122
+ return targetValue;
123
+ }
124
+
125
+ function mergeJsonFile(targetPath, sourceContent) {
126
+ try {
127
+ const sourceJson = JSON.parse(sourceContent);
128
+ const targetJson = JSON.parse(readFileSync(targetPath, 'utf8'));
129
+ const merged = mergeJsonValues(targetJson, sourceJson);
130
+ const nextContent = `${JSON.stringify(merged, null, 2)}\n`;
131
+ if (nextContent === readFileSync(targetPath, 'utf8')) {
132
+ return 'skipped';
133
+ }
134
+ writeFileSync(targetPath, nextContent);
135
+ return 'merged';
136
+ } catch {
137
+ return 'skipped';
138
+ }
139
+ }
140
+
141
+ function mergeDirectory(sourceDir, targetDir, stats) {
142
+ if (!existsSync(targetDir)) {
143
+ mkdirSync(targetDir, { recursive: true });
144
+ }
145
+
146
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
147
+ const sourcePath = join(sourceDir, entry.name);
148
+ const targetPath = join(targetDir, entry.name);
149
+
150
+ if (!shouldCopyPath(sourcePath)) {
151
+ continue;
152
+ }
153
+
154
+ if (entry.isDirectory()) {
155
+ mergeDirectory(sourcePath, targetPath, stats);
156
+ continue;
157
+ }
158
+
159
+ if (!entry.isFile()) {
160
+ continue;
161
+ }
162
+
163
+ if (!existsSync(targetPath)) {
164
+ writeFileSync(targetPath, readFileSync(sourcePath));
165
+ stats.created += 1;
166
+ continue;
167
+ }
168
+
169
+ if (!statSync(targetPath).isFile()) {
170
+ stats.skipped += 1;
171
+ continue;
172
+ }
173
+
174
+ if (isJsonFile(sourcePath)) {
175
+ const result = mergeJsonFile(targetPath, readFileSync(sourcePath, 'utf8'));
176
+ if (result === 'merged') {
177
+ stats.appended += 1;
178
+ } else {
179
+ stats.skipped += 1;
180
+ }
181
+ continue;
182
+ }
183
+
184
+ if (!isAppendableTextFile(sourcePath)) {
185
+ stats.skipped += 1;
186
+ continue;
187
+ }
188
+
189
+ const result = appendUniqueContent(targetPath, readFileSync(sourcePath, 'utf8'));
190
+ if (result === 'appended') {
191
+ stats.appended += 1;
192
+ } else {
193
+ stats.skipped += 1;
194
+ }
195
+ }
196
+ }
197
+
198
+ function mergeProjectConfig(templatesDir, projectRoot, stats) {
199
+ const sourceConfig = join(templatesDir, 'opencode.json');
200
+ if (!existsSync(sourceConfig) || !statSync(sourceConfig).isFile()) {
201
+ return;
202
+ }
203
+
204
+ const targetJson = join(projectRoot, 'opencode.json');
205
+ const targetJsonc = join(projectRoot, 'opencode.jsonc');
206
+ if (!existsSync(targetJson) && existsSync(targetJsonc)) {
207
+ // Avoid generating a second competing config file in repos that already
208
+ // use JSONC. Ask the user to merge manually.
209
+ stats.skipped += 1;
210
+ log.warn('Found opencode.jsonc. Skipping opencode.json install; merge templates/opencode.json manually if desired.');
211
+ return;
212
+ }
213
+
214
+ const sourceContent = readFileSync(sourceConfig, 'utf8');
215
+ if (!existsSync(targetJson)) {
216
+ writeFileSync(targetJson, sourceContent);
217
+ stats.created += 1;
218
+ return;
219
+ }
220
+
221
+ const result = mergeJsonFile(targetJson, sourceContent);
222
+ if (result === 'merged') {
223
+ stats.appended += 1;
224
+ } else {
225
+ stats.skipped += 1;
226
+ }
227
+ }
228
+
229
+ async function install() {
296
230
  console.log(banner);
297
231
  log.title('OpenCode Agile Agent Installer');
298
232
 
@@ -303,9 +237,6 @@ async function install() {
303
237
  return;
304
238
  }
305
239
 
306
- const context = detectProjectContext();
307
- log.info(`Detected project: ${context.projectName} (${context.framework}, ${context.language})`);
308
-
309
240
  const source = join(templatesDir, '.opencode');
310
241
  const target = join(projectRoot, '.opencode');
311
242
 
@@ -316,23 +247,18 @@ async function install() {
316
247
  return;
317
248
  }
318
249
 
319
- if (!existsSync(target)) {
320
- mkdirSync(target, { recursive: true });
321
- } else {
322
- rmSync(target, { recursive: true, force: true });
323
- mkdirSync(target, { recursive: true });
324
- }
325
-
326
- cpSync(source, target, {
327
- recursive: true,
328
- overwrite: true,
329
- filter: shouldCopyPath,
330
- });
331
- writeFileSync(join(projectRoot, 'AGENTS.md'), generateAgentsMd(context), 'utf-8');
332
-
333
- log.success('Installed .opencode and generated AGENTS.md.');
334
- log.title('Done');
335
- console.log('Start with @feature-lead and review .opencode/README.md for the flow.');
250
+ if (!existsSync(target)) {
251
+ mkdirSync(target, { recursive: true });
252
+ }
253
+
254
+ const stats = { created: 0, appended: 0, skipped: 0 };
255
+ mergeDirectory(source, target, stats);
256
+ mergeProjectConfig(templatesDir, projectRoot, stats);
257
+
258
+ log.success('Installed .opencode in merge mode.');
259
+ log.info(`Created ${stats.created} files, appended ${stats.appended} text files, skipped ${stats.skipped} existing files.`);
260
+ log.title('Done');
261
+ console.log('Start with @feature-lead and add or update AGENTS.md manually if your repo needs one.');
336
262
 
337
263
  rl.close();
338
264
  }
@@ -9,8 +9,6 @@ const __dirname = dirname(__filename);
9
9
  const rootDir = join(__dirname, '..');
10
10
  const sourceOpencode = join(rootDir, '.opencode');
11
11
  const targetTemplates = join(rootDir, 'templates');
12
- const sourceTemplate = join(rootDir, 'AGENTS.template.md');
13
- const targetTemplate = join(targetTemplates, 'AGENTS.template.md');
14
12
 
15
13
  function skipNodeModules(path) {
16
14
  return !path.includes(`${sep}node_modules${sep}`) && !path.endsWith(`${sep}node_modules`);
@@ -38,8 +36,4 @@ cpSync(sourceOpencode, join(targetTemplates, '.opencode'), {
38
36
  filter: skipNodeModules,
39
37
  });
40
38
 
41
- if (existsSync(sourceTemplate)) {
42
- cpSync(sourceTemplate, targetTemplate, { force: true });
43
- }
44
-
45
- console.log('Synced .opencode and AGENTS.template.md to templates/');
39
+ console.log('Synced .opencode to templates/');