opencode-auto-agent 1.0.0 → 1.2.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.
- package/README.md +24 -6
- package/bin/cli.js +275 -51
- package/package.json +1 -1
- package/src/commands/doctor.js +337 -54
- package/src/commands/init.js +337 -114
- package/src/commands/run.js +182 -61
- package/src/commands/setup.js +321 -21
- package/src/lib/constants.js +91 -15
- package/src/lib/models.js +172 -0
- package/src/lib/prerequisites.js +151 -0
- package/src/lib/ui.js +446 -0
- package/templates/agents/developer.md +1 -1
- package/templates/agents/docs.md +1 -1
- package/templates/agents/orchestrator.md +4 -4
- package/templates/agents/planner.md +1 -1
- package/templates/agents/qa.md +1 -1
- package/templates/agents/reviewer.md +1 -1
- package/templates/sample-config.json +47 -8
package/src/commands/init.js
CHANGED
|
@@ -1,80 +1,187 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* init command —
|
|
2
|
+
* init command — model-aware, interactive scaffolding of the OpenCode agent team.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Validate prerequisites (opencode, ralphy, node)
|
|
6
|
+
* 2. Discover available models via `opencode models`
|
|
7
|
+
* 3. Let the user choose a preset
|
|
8
|
+
* 4. Let the user assign models to each agent role
|
|
9
|
+
* 5. Scaffold .opencode/ directory with agents, rules, presets, context
|
|
10
|
+
* 6. Write config.json with full agent-model mapping
|
|
11
|
+
* 7. Generate opencode.json at project root (with model assignments)
|
|
12
|
+
* 8. Generate .opencode/agents/*.md from templates (with model frontmatter)
|
|
13
|
+
* 9. Generate .ralphy/config.yaml stub if missing
|
|
14
|
+
* 10. Print summary and next steps
|
|
3
15
|
*
|
|
4
16
|
* Rules:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* 4. Create empty context/ folder for runtime context assembly.
|
|
17
|
+
* - Never overwrite files that already exist (safe re-run) unless --force
|
|
18
|
+
* - Abort if prerequisites fail
|
|
19
|
+
* - Do not auto-install anything
|
|
9
20
|
*/
|
|
10
21
|
|
|
11
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
22
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
12
23
|
import { join, resolve } from "node:path";
|
|
13
24
|
import { fileURLToPath } from "node:url";
|
|
14
25
|
|
|
15
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
SCAFFOLD_DIR, CONFIG_FILE, PRESETS, AGENTS, AGENT_ROLES,
|
|
28
|
+
DIRS, SCHEMA_VERSION, DEFAULT_MODEL_MAP, WORKFLOW_STEPS,
|
|
29
|
+
} from "../lib/constants.js";
|
|
16
30
|
import { copyDirSync, readJsonSync, writeJsonSync, writeTextSync } from "../lib/fs-utils.js";
|
|
31
|
+
import { validateAndReport } from "../lib/prerequisites.js";
|
|
32
|
+
import { discoverModelsInteractive, findModel, groupByProvider } from "../lib/models.js";
|
|
33
|
+
import {
|
|
34
|
+
banner, section, status, kv, success, error, c, boxList,
|
|
35
|
+
select, confirm, table,
|
|
36
|
+
} from "../lib/ui.js";
|
|
17
37
|
|
|
18
38
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
19
39
|
const TEMPLATES_ROOT = resolve(__dirname, "..", "..", "templates");
|
|
20
40
|
|
|
21
41
|
/**
|
|
22
|
-
*
|
|
42
|
+
* Main init entry point.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} targetDir - Project root directory
|
|
45
|
+
* @param {Object} opts
|
|
46
|
+
* @param {string} [opts.preset] - Preset name (skips interactive selection)
|
|
47
|
+
* @param {boolean} [opts.force] - Overwrite existing config
|
|
48
|
+
* @param {boolean} [opts.nonInteractive] - Skip all prompts, use defaults
|
|
23
49
|
*/
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
docsPath: "./Docs",
|
|
30
|
-
tasksPath: "./PRD.md",
|
|
31
|
-
outputPath: "./",
|
|
32
|
-
engine: "opencode",
|
|
33
|
-
agents: AGENTS.reduce((acc, name) => {
|
|
34
|
-
acc[name] = { enabled: true };
|
|
35
|
-
return acc;
|
|
36
|
-
}, {}),
|
|
37
|
-
orchestrator: {
|
|
38
|
-
workflowSteps: ["plan", "implement", "test", "verify"],
|
|
39
|
-
requirePlanApproval: false,
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
50
|
+
export async function init(targetDir, { preset, force, nonInteractive } = {}) {
|
|
51
|
+
banner(
|
|
52
|
+
"OpenCode Auto-Agent Initializer",
|
|
53
|
+
`Scaffolding an AI agent team into ${targetDir}`
|
|
54
|
+
);
|
|
43
55
|
|
|
44
|
-
|
|
56
|
+
// ── Step 1: Prerequisites ────────────────────────────────────────────────
|
|
57
|
+
section("Prerequisites");
|
|
58
|
+
validateAndReport({ requireRalphy: false, abortOnFail: true });
|
|
59
|
+
console.log();
|
|
60
|
+
|
|
61
|
+
// ── Step 2: Discover models ──────────────────────────────────────────────
|
|
62
|
+
section("Model Discovery");
|
|
63
|
+
const models = discoverModelsInteractive();
|
|
64
|
+
|
|
65
|
+
if (models.length === 0) {
|
|
66
|
+
error(
|
|
67
|
+
"No models available from OpenCode.",
|
|
68
|
+
"Configure at least one provider: opencode auth login"
|
|
69
|
+
);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Show provider summary
|
|
74
|
+
const groups = groupByProvider(models);
|
|
75
|
+
const providerList = [...groups.entries()].map(
|
|
76
|
+
([p, ms]) => `${c.bold(p)} ${c.gray(`(${ms.length} models)`)}`
|
|
77
|
+
);
|
|
78
|
+
boxList("Available Providers", providerList);
|
|
79
|
+
|
|
80
|
+
// ── Step 3: Choose preset ────────────────────────────────────────────────
|
|
81
|
+
section("Preset Selection");
|
|
82
|
+
let chosenPreset = preset;
|
|
83
|
+
|
|
84
|
+
if (!chosenPreset && !nonInteractive) {
|
|
85
|
+
const presetItems = PRESETS.map((p) => ({
|
|
86
|
+
label: p,
|
|
87
|
+
value: p,
|
|
88
|
+
hint: presetDescription(p),
|
|
89
|
+
}));
|
|
90
|
+
const result = await select("Choose a project preset", presetItems);
|
|
91
|
+
chosenPreset = result.value;
|
|
92
|
+
}
|
|
93
|
+
chosenPreset = chosenPreset && PRESETS.includes(chosenPreset) ? chosenPreset : "java";
|
|
94
|
+
status("pass", `Preset: ${c.bold(chosenPreset)}`, presetDescription(chosenPreset));
|
|
95
|
+
|
|
96
|
+
// ── Step 4: Assign models to agent roles ─────────────────────────────────
|
|
97
|
+
section("Agent Model Assignment");
|
|
98
|
+
console.log(` ${c.gray("Assign a model to each agent role. Defaults are shown.")}`);
|
|
99
|
+
console.log();
|
|
100
|
+
|
|
101
|
+
const modelMapping = {};
|
|
102
|
+
|
|
103
|
+
for (const role of AGENTS) {
|
|
104
|
+
const roleDef = AGENT_ROLES[role];
|
|
105
|
+
const defaultId = DEFAULT_MODEL_MAP[role];
|
|
106
|
+
const defaultModel = findModel(models, defaultId);
|
|
107
|
+
|
|
108
|
+
if (nonInteractive) {
|
|
109
|
+
// Use default or first available
|
|
110
|
+
modelMapping[role] = defaultModel ? defaultModel.id : models[0].id;
|
|
111
|
+
status("dot", `${padRole(role)} ${c.cyan(modelMapping[role])}`, "(default)");
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Build selection list: default first, then all others
|
|
116
|
+
const items = [];
|
|
117
|
+
if (defaultModel) {
|
|
118
|
+
items.push({
|
|
119
|
+
label: defaultModel.id,
|
|
120
|
+
value: defaultModel.id,
|
|
121
|
+
hint: "(recommended)",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
for (const m of models) {
|
|
125
|
+
if (m.id !== defaultModel?.id) {
|
|
126
|
+
items.push({ label: m.id, value: m.id });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const result = await select(
|
|
131
|
+
`Model for ${c.bold(role)} ${c.gray(`\u2014 ${roleDef.description}`)}`,
|
|
132
|
+
items
|
|
133
|
+
);
|
|
134
|
+
modelMapping[role] = result.value;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Show summary table
|
|
138
|
+
console.log();
|
|
139
|
+
table(
|
|
140
|
+
["Agent Role", "Model", "Mode"],
|
|
141
|
+
AGENTS.map((role) => [
|
|
142
|
+
c.bold(role),
|
|
143
|
+
c.cyan(modelMapping[role]),
|
|
144
|
+
c.gray(AGENT_ROLES[role].mode),
|
|
145
|
+
])
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// ── Step 5: Confirm ──────────────────────────────────────────────────────
|
|
149
|
+
if (!nonInteractive) {
|
|
150
|
+
console.log();
|
|
151
|
+
const proceed = await confirm("Proceed with this configuration?", true);
|
|
152
|
+
if (!proceed) {
|
|
153
|
+
console.log(c.gray("\n Aborted.\n"));
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Step 6: Scaffold directories ─────────────────────────────────────────
|
|
159
|
+
section("Scaffolding");
|
|
45
160
|
const scaffoldDir = join(targetDir, SCAFFOLD_DIR);
|
|
46
161
|
const isUpgrade = existsSync(scaffoldDir);
|
|
47
162
|
|
|
48
163
|
if (isUpgrade) {
|
|
49
|
-
|
|
50
|
-
} else {
|
|
51
|
-
console.log(`[init] Scaffolding ${SCAFFOLD_DIR}/ into ${targetDir}`);
|
|
164
|
+
status("info", `Existing ${SCAFFOLD_DIR}/ detected \u2014 upgrading (no overwrites)`);
|
|
52
165
|
}
|
|
53
166
|
|
|
54
|
-
// 1. Create directory skeleton
|
|
55
167
|
for (const sub of Object.values(DIRS)) {
|
|
56
168
|
mkdirSync(join(scaffoldDir, sub), { recursive: true });
|
|
57
169
|
}
|
|
58
170
|
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const presetsDest = join(scaffoldDir, DIRS.presets);
|
|
74
|
-
copyDirSync(presetsSrc, presetsDest, { overwrite: false });
|
|
75
|
-
console.log(`[init] Presets written to ${DIRS.presets}/`);
|
|
76
|
-
|
|
77
|
-
// 5. Seed empty context readme
|
|
171
|
+
// Copy template agents (skip existing unless --force)
|
|
172
|
+
const overwrite = !!force;
|
|
173
|
+
copyDirSync(join(TEMPLATES_ROOT, "agents"), join(scaffoldDir, DIRS.agents), { overwrite });
|
|
174
|
+
status("pass", "Agent templates", `${DIRS.agents}/`);
|
|
175
|
+
|
|
176
|
+
// Copy rules
|
|
177
|
+
copyDirSync(join(TEMPLATES_ROOT, "rules"), join(scaffoldDir, DIRS.rules), { overwrite });
|
|
178
|
+
status("pass", "Rules", `${DIRS.rules}/`);
|
|
179
|
+
|
|
180
|
+
// Copy presets
|
|
181
|
+
copyDirSync(join(TEMPLATES_ROOT, "presets"), join(scaffoldDir, DIRS.presets), { overwrite });
|
|
182
|
+
status("pass", "Presets", `${DIRS.presets}/`);
|
|
183
|
+
|
|
184
|
+
// Seed context readme
|
|
78
185
|
const contextReadme = join(scaffoldDir, DIRS.context, "README.md");
|
|
79
186
|
if (!existsSync(contextReadme)) {
|
|
80
187
|
writeTextSync(
|
|
@@ -82,95 +189,176 @@ export async function init(targetDir, { preset } = {}) {
|
|
|
82
189
|
"# Runtime Context\n\nThis folder is populated at runtime by the orchestrator.\nDo not manually edit files here; they are regenerated on each `run`.\n"
|
|
83
190
|
);
|
|
84
191
|
}
|
|
192
|
+
status("pass", "Context directory", `${DIRS.context}/`);
|
|
85
193
|
|
|
86
|
-
//
|
|
194
|
+
// ── Step 7: Write config.json ────────────────────────────────────────────
|
|
87
195
|
const configPath = join(scaffoldDir, CONFIG_FILE);
|
|
88
|
-
if (!existsSync(configPath)) {
|
|
89
|
-
const
|
|
90
|
-
writeJsonSync(configPath,
|
|
91
|
-
|
|
196
|
+
if (!existsSync(configPath) || force) {
|
|
197
|
+
const config = buildProjectConfig(chosenPreset, modelMapping);
|
|
198
|
+
writeJsonSync(configPath, config);
|
|
199
|
+
status("pass", "Project config", CONFIG_FILE);
|
|
92
200
|
} else {
|
|
93
|
-
|
|
201
|
+
status("info", "Project config already exists \u2014 skipped", CONFIG_FILE);
|
|
94
202
|
}
|
|
95
203
|
|
|
96
|
-
//
|
|
204
|
+
// ── Step 8: Generate opencode.json ───────────────────────────────────────
|
|
97
205
|
const opencodeConfig = join(targetDir, "opencode.json");
|
|
98
|
-
if (!existsSync(opencodeConfig)) {
|
|
99
|
-
writeJsonSync(opencodeConfig,
|
|
100
|
-
|
|
206
|
+
if (!existsSync(opencodeConfig) || force) {
|
|
207
|
+
writeJsonSync(opencodeConfig, buildOpencodeJson(modelMapping, chosenPreset));
|
|
208
|
+
status("pass", "OpenCode config", "opencode.json");
|
|
209
|
+
} else {
|
|
210
|
+
status("info", "opencode.json already exists \u2014 skipped");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ── Step 9: Generate agent markdown files with model frontmatter ─────────
|
|
214
|
+
for (const role of AGENTS) {
|
|
215
|
+
const agentFile = join(scaffoldDir, DIRS.agents, `${role}.md`);
|
|
216
|
+
const templateFile = join(TEMPLATES_ROOT, "agents", `${role}.md`);
|
|
217
|
+
if (existsSync(templateFile) && (!existsSync(agentFile) || force)) {
|
|
218
|
+
const template = readFileSync(templateFile, "utf8");
|
|
219
|
+
const patched = patchAgentModelFrontmatter(template, modelMapping[role]);
|
|
220
|
+
writeTextSync(agentFile, patched);
|
|
221
|
+
}
|
|
101
222
|
}
|
|
223
|
+
status("pass", "Agent markdown files updated with model assignments");
|
|
102
224
|
|
|
103
|
-
//
|
|
225
|
+
// ── Step 10: .ralphy/config.yaml ─────────────────────────────────────────
|
|
104
226
|
const ralphyDir = join(targetDir, ".ralphy");
|
|
105
227
|
const ralphyConfig = join(ralphyDir, "config.yaml");
|
|
106
228
|
if (!existsSync(ralphyConfig)) {
|
|
107
229
|
mkdirSync(ralphyDir, { recursive: true });
|
|
108
230
|
writeTextSync(ralphyConfig, buildRalphyConfig());
|
|
109
|
-
|
|
231
|
+
status("pass", "Ralphy config", ".ralphy/config.yaml");
|
|
232
|
+
} else {
|
|
233
|
+
status("info", "Ralphy config already exists \u2014 skipped");
|
|
110
234
|
}
|
|
111
235
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
console.log(`
|
|
236
|
+
// ── Done ─────────────────────────────────────────────────────────────────
|
|
237
|
+
success("Initialization complete!");
|
|
238
|
+
|
|
239
|
+
console.log(` ${c.bold("Next steps:")}`);
|
|
240
|
+
console.log(` ${c.gray("1.")} Edit ${c.cyan(`${SCAFFOLD_DIR}/${CONFIG_FILE}`)} to fine-tune agents and paths`);
|
|
241
|
+
console.log(` ${c.gray("2.")} Run ${c.cyan("npx opencode-auto-agent doctor")} to validate`);
|
|
242
|
+
console.log(` ${c.gray("3.")} Run ${c.cyan("npx opencode-auto-agent run")} to start the orchestrator`);
|
|
243
|
+
console.log();
|
|
116
244
|
}
|
|
117
245
|
|
|
118
|
-
// ──
|
|
246
|
+
// ── Config builders ────────────────────────────────────────────────────────
|
|
119
247
|
|
|
120
|
-
function
|
|
248
|
+
function buildProjectConfig(preset, modelMapping) {
|
|
121
249
|
return {
|
|
122
|
-
$schema: "https://
|
|
250
|
+
$schema: "https://github.com/opencode-auto-agent/config-schema",
|
|
251
|
+
version: SCHEMA_VERSION,
|
|
252
|
+
preset,
|
|
253
|
+
docsPath: "./Docs",
|
|
254
|
+
tasksPath: "./PRD.md",
|
|
255
|
+
outputPath: "./",
|
|
256
|
+
engine: "opencode",
|
|
257
|
+
models: { ...modelMapping },
|
|
258
|
+
agents: Object.fromEntries(
|
|
259
|
+
AGENTS.map((name) => [
|
|
260
|
+
name,
|
|
261
|
+
{
|
|
262
|
+
enabled: true,
|
|
263
|
+
model: modelMapping[name],
|
|
264
|
+
mode: AGENT_ROLES[name].mode,
|
|
265
|
+
},
|
|
266
|
+
])
|
|
267
|
+
),
|
|
268
|
+
rules: [
|
|
269
|
+
`.opencode/rules/universal.md`,
|
|
270
|
+
],
|
|
123
271
|
instructions: [
|
|
124
|
-
".
|
|
125
|
-
|
|
272
|
+
"AGENTS.md",
|
|
273
|
+
`.opencode/rules/universal.md`,
|
|
126
274
|
],
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
mode: "primary",
|
|
131
|
-
prompt: "You are the orchestrator. Read .ai-starter-kit/context/ for active context. Follow the workflow: plan -> implement -> test -> verify.",
|
|
132
|
-
tools: { "*": true },
|
|
133
|
-
permission: {
|
|
134
|
-
task: { "*": "allow" },
|
|
135
|
-
},
|
|
136
|
-
},
|
|
137
|
-
planner: {
|
|
138
|
-
description: "Architect & planner — breaks objectives into steps",
|
|
139
|
-
mode: "subagent",
|
|
140
|
-
prompt: "You are the planner agent. Produce step-by-step implementation plans. Do not write code. Output plans as numbered markdown lists.",
|
|
141
|
-
tools: { write: false, edit: false, bash: false },
|
|
142
|
-
},
|
|
143
|
-
developer: {
|
|
144
|
-
description: "Developer — writes and edits production code",
|
|
145
|
-
mode: "subagent",
|
|
146
|
-
prompt: "You are the developer agent. Implement code changes per the plan. Follow project conventions. Write clean, tested code.",
|
|
147
|
-
tools: { "*": true },
|
|
148
|
-
},
|
|
149
|
-
qa: {
|
|
150
|
-
description: "QA / Tester — runs tests, verifies correctness",
|
|
151
|
-
mode: "subagent",
|
|
152
|
-
prompt: "You are the QA agent. Run tests, check for regressions, verify acceptance criteria. Report pass/fail status clearly.",
|
|
153
|
-
tools: { write: false },
|
|
154
|
-
permission: { bash: "allow" },
|
|
155
|
-
},
|
|
156
|
-
reviewer: {
|
|
157
|
-
description: "Code reviewer — reviews for quality, security, performance",
|
|
158
|
-
mode: "subagent",
|
|
159
|
-
prompt: "You are the reviewer agent. Review code for correctness, security issues, performance problems, and style violations. Do not make changes.",
|
|
160
|
-
tools: { write: false, edit: false, bash: false },
|
|
161
|
-
},
|
|
162
|
-
docs: {
|
|
163
|
-
description: "Documentation — keeps docs aligned with code changes",
|
|
164
|
-
mode: "subagent",
|
|
165
|
-
prompt: "You are the docs agent. Update documentation to reflect code changes. Keep README, API docs, and inline comments accurate.",
|
|
166
|
-
tools: { bash: false },
|
|
167
|
-
},
|
|
275
|
+
orchestrator: {
|
|
276
|
+
workflowSteps: WORKFLOW_STEPS,
|
|
277
|
+
requirePlanApproval: false,
|
|
168
278
|
},
|
|
169
279
|
};
|
|
170
280
|
}
|
|
171
281
|
|
|
282
|
+
function buildOpencodeJson(modelMapping, preset) {
|
|
283
|
+
const agents = {};
|
|
284
|
+
for (const role of AGENTS) {
|
|
285
|
+
const def = AGENT_ROLES[role];
|
|
286
|
+
agents[role] = {
|
|
287
|
+
description: def.description,
|
|
288
|
+
mode: def.mode,
|
|
289
|
+
model: modelMapping[role],
|
|
290
|
+
temperature: def.temperature,
|
|
291
|
+
prompt: buildAgentPrompt(role, preset),
|
|
292
|
+
tools: def.tools,
|
|
293
|
+
};
|
|
294
|
+
if (Object.keys(def.permission).length > 0) {
|
|
295
|
+
agents[role].permission = def.permission;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
$schema: "https://opencode.ai/config.json",
|
|
301
|
+
model: modelMapping.orchestrator,
|
|
302
|
+
instructions: [
|
|
303
|
+
"AGENTS.md",
|
|
304
|
+
`.opencode/rules/universal.md`,
|
|
305
|
+
],
|
|
306
|
+
agent: agents,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function buildAgentPrompt(role, preset) {
|
|
311
|
+
const prompts = {
|
|
312
|
+
orchestrator:
|
|
313
|
+
`You are the orchestrator. Read .opencode/context/ for active context. Follow the workflow: plan -> implement -> test -> verify. Preset: ${preset}.`,
|
|
314
|
+
planner:
|
|
315
|
+
"You are the planner agent. Produce step-by-step implementation plans. Do not write code. Output plans as numbered markdown lists.",
|
|
316
|
+
developer:
|
|
317
|
+
"You are the developer agent. Implement code changes per the plan. Follow project conventions. Write clean, tested code.",
|
|
318
|
+
qa:
|
|
319
|
+
"You are the QA agent. Run tests, check for regressions, verify acceptance criteria. Report pass/fail status clearly.",
|
|
320
|
+
reviewer:
|
|
321
|
+
"You are the reviewer agent. Review code for correctness, security issues, performance problems, and style violations. Do not make changes.",
|
|
322
|
+
docs:
|
|
323
|
+
"You are the docs agent. Update documentation to reflect code changes. Keep README, API docs, and inline comments accurate.",
|
|
324
|
+
};
|
|
325
|
+
return prompts[role] || `You are the ${role} agent.`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Patch the `model:` field in a markdown agent file's YAML frontmatter.
|
|
330
|
+
* If no model field exists, add one.
|
|
331
|
+
*/
|
|
332
|
+
function patchAgentModelFrontmatter(content, modelId) {
|
|
333
|
+
// Check if content has YAML frontmatter
|
|
334
|
+
if (!content.startsWith("---")) return content;
|
|
335
|
+
|
|
336
|
+
const endIdx = content.indexOf("---", 3);
|
|
337
|
+
if (endIdx === -1) return content;
|
|
338
|
+
|
|
339
|
+
let frontmatter = content.slice(3, endIdx);
|
|
340
|
+
const body = content.slice(endIdx);
|
|
341
|
+
|
|
342
|
+
if (/^model:/m.test(frontmatter)) {
|
|
343
|
+
// Replace existing model line
|
|
344
|
+
frontmatter = frontmatter.replace(/^model:.*$/m, `model: ${modelId}`);
|
|
345
|
+
} else {
|
|
346
|
+
// Add model field after description or at the end
|
|
347
|
+
const lines = frontmatter.split("\n");
|
|
348
|
+
const descIdx = lines.findIndex((l) => l.startsWith("description:"));
|
|
349
|
+
if (descIdx >= 0) {
|
|
350
|
+
lines.splice(descIdx + 1, 0, `model: ${modelId}`);
|
|
351
|
+
} else {
|
|
352
|
+
lines.push(`model: ${modelId}`);
|
|
353
|
+
}
|
|
354
|
+
frontmatter = lines.join("\n");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return `---${frontmatter}${body}`;
|
|
358
|
+
}
|
|
359
|
+
|
|
172
360
|
function buildRalphyConfig() {
|
|
173
|
-
return `# Ralphy configuration
|
|
361
|
+
return `# Ralphy configuration \u2014 generated by opencode-auto-agent
|
|
174
362
|
project:
|
|
175
363
|
name: ""
|
|
176
364
|
language: ""
|
|
@@ -186,5 +374,40 @@ rules:
|
|
|
186
374
|
- "Follow the plan produced by the planner agent before implementing."
|
|
187
375
|
- "Run tests after every implementation step."
|
|
188
376
|
- "Do not modify files outside the project source tree."
|
|
377
|
+
|
|
378
|
+
boundaries:
|
|
379
|
+
never_touch:
|
|
380
|
+
- ".env"
|
|
381
|
+
- ".env.*"
|
|
382
|
+
- "secrets/**"
|
|
383
|
+
- "**/*.pem"
|
|
384
|
+
- "**/*.key"
|
|
385
|
+
- "node_modules/**"
|
|
386
|
+
- ".git/**"
|
|
387
|
+
- "dist/**"
|
|
388
|
+
- "build/**"
|
|
389
|
+
|
|
390
|
+
capabilities:
|
|
391
|
+
browser: "auto"
|
|
392
|
+
|
|
393
|
+
notifications:
|
|
394
|
+
discord_webhook: ""
|
|
395
|
+
slack_webhook: ""
|
|
396
|
+
custom_webhook: ""
|
|
189
397
|
`;
|
|
190
398
|
}
|
|
399
|
+
|
|
400
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
401
|
+
|
|
402
|
+
function presetDescription(name) {
|
|
403
|
+
const desc = {
|
|
404
|
+
java: "General Java project (Maven/Gradle)",
|
|
405
|
+
springboot: "Spring Boot 3.x web application",
|
|
406
|
+
nextjs: "Next.js 14+ with App Router",
|
|
407
|
+
};
|
|
408
|
+
return desc[name] || "";
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function padRole(role) {
|
|
412
|
+
return role.padEnd(14);
|
|
413
|
+
}
|