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.
- package/README.md +78 -14
- package/bin/cli.js +180 -254
- package/bin/sync-templates.js +1 -7
- package/bin/validate-templates.js +17 -19
- package/package.json +1 -1
- package/templates/.opencode/ARCHITECTURE.md +94 -64
- package/templates/.opencode/README.md +115 -63
- package/templates/.opencode/agents/archiver.md +45 -0
- package/templates/.opencode/agents/backend-specialist.md +43 -46
- package/templates/.opencode/agents/context-gatherer.md +26 -26
- package/templates/.opencode/agents/debugger.md +45 -45
- package/templates/.opencode/agents/developer.md +54 -45
- package/templates/.opencode/agents/devops-engineer.md +42 -45
- package/templates/.opencode/agents/feature-lead.md +81 -50
- package/templates/.opencode/agents/frontend-specialist.md +44 -46
- package/templates/.opencode/agents/performance-optimizer.md +45 -45
- package/templates/.opencode/agents/pr-reviewer.md +46 -45
- package/templates/.opencode/agents/project-planner.md +41 -45
- package/templates/.opencode/agents/retrospective-writer.md +48 -0
- package/templates/.opencode/agents/security-auditor.md +39 -45
- package/templates/.opencode/agents/system-analyst.md +43 -43
- package/templates/.opencode/agents/test-engineer.md +44 -44
- package/templates/.opencode/bun.lock +18 -0
- package/templates/.opencode/commands/archive.md +15 -0
- package/templates/.opencode/commands/assign-models.md +39 -0
- package/templates/.opencode/commands/brainstorm.md +5 -2
- package/templates/.opencode/commands/check-progress.md +21 -0
- package/templates/.opencode/commands/create.md +8 -3
- package/templates/.opencode/commands/plan.md +7 -2
- package/templates/.opencode/commands/reframe.md +17 -0
- package/templates/.opencode/commands/review.md +9 -3
- package/templates/.opencode/commands/rubber-duck.md +14 -0
- package/templates/.opencode/commands/status.md +3 -0
- package/templates/.opencode/commands/test.md +8 -3
- package/templates/.opencode/config.template.json +160 -20
- package/templates/.opencode/package-lock.json +115 -0
- package/templates/.opencode/package.json +6 -0
- package/templates/.opencode/plugins/session-artifacts.ts +611 -0
- package/templates/.opencode/skills/archive-writing/SKILL.md +36 -0
- package/templates/.opencode/skills/artifact-discipline/SKILL.md +30 -0
- package/templates/.opencode/skills/clarify-first/SKILL.md +34 -0
- package/templates/.opencode/skills/context-archive/SKILL.md +10 -26
- package/templates/.opencode/skills/context-gathering/SKILL.md +2 -0
- package/templates/.opencode/skills/intelligent-routing/SKILL.md +10 -2
- package/templates/.opencode/skills/plan-writing/SKILL.md +5 -5
- package/templates/.opencode/templates/brief.template.md +25 -0
- package/templates/.opencode/templates/notes.template.md +13 -0
- package/templates/.opencode/templates/review-summary.template.md +6 -0
- package/templates/.opencode/templates/session-summary.template.md +7 -0
- package/templates/.opencode/templates/spec.template.md +17 -0
- package/templates/.opencode/templates/status.template.yaml +14 -0
- package/templates/.opencode/templates/task.template.md +5 -0
- package/templates/opencode.json +12 -0
- package/templates/.opencode/agents/api-designer.md +0 -45
- package/templates/.opencode/agents/code-archaeologist.md +0 -45
- package/templates/.opencode/agents/database-architect.md +0 -45
- package/templates/.opencode/agents/documentation-writer.md +0 -45
- package/templates/.opencode/agents/explorer-agent.md +0 -55
- package/templates/.opencode/agents/game-developer.md +0 -45
- package/templates/.opencode/agents/mobile-developer.md +0 -45
- package/templates/.opencode/agents/orchestrator.md +0 -48
- package/templates/.opencode/agents/penetration-tester.md +0 -46
- package/templates/.opencode/agents/product-manager.md +0 -46
- package/templates/.opencode/agents/qa-automation-engineer.md +0 -46
- package/templates/.opencode/agents/seo-specialist.md +0 -45
- package/templates/.opencode/archive/README.md +0 -24
- package/templates/.opencode/commands/debug.md +0 -10
- package/templates/.opencode/skills/parallel-agents/SKILL.md +0 -38
- package/templates/.opencode/skills/redteam-validation/SKILL.md +0 -33
- 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
|
|
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
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
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`, `
|
|
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
|
-
- `
|
|
34
|
-
- `
|
|
35
|
-
- `
|
|
36
|
-
- `
|
|
37
|
-
-
|
|
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
|
-
-
|
|
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 `
|
|
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`, `@
|
|
47
|
-
-
|
|
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,
|
|
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
|
-
-
|
|
59
|
-
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
76
|
+
function isJsonFile(path) {
|
|
77
|
+
return extname(path) === '.json';
|
|
78
|
+
}
|
|
204
79
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
87
|
+
const nextContent = `${targetContent.trimEnd()}\n\n${trimmedSource}\n`;
|
|
88
|
+
writeFileSync(targetPath, nextContent);
|
|
89
|
+
return 'appended';
|
|
90
|
+
}
|
|
212
91
|
|
|
213
|
-
|
|
92
|
+
function isPlainObject(value) {
|
|
93
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
94
|
+
}
|
|
214
95
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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
|
-
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
}
|
package/bin/sync-templates.js
CHANGED
|
@@ -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
|
-
|
|
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/');
|