opencode-auto-agent 1.0.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 +84 -0
- package/bin/cli.js +104 -0
- package/package.json +54 -0
- package/src/commands/doctor.js +91 -0
- package/src/commands/init.js +190 -0
- package/src/commands/run.js +147 -0
- package/src/commands/setup.js +40 -0
- package/src/lib/constants.js +33 -0
- package/src/lib/fs-utils.js +63 -0
- package/templates/agents/developer.md +58 -0
- package/templates/agents/docs.md +47 -0
- package/templates/agents/orchestrator.md +67 -0
- package/templates/agents/planner.md +55 -0
- package/templates/agents/qa.md +66 -0
- package/templates/agents/reviewer.md +58 -0
- package/templates/context/README.md +4 -0
- package/templates/presets/java.md +55 -0
- package/templates/presets/nextjs.md +80 -0
- package/templates/presets/springboot.md +69 -0
- package/templates/rules/universal.md +31 -0
- package/templates/sample-config.json +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# opencode-auto-agent
|
|
2
|
+
|
|
3
|
+
`opencode-auto-agent` is a lightweight starter template for running a reusable AI agent team with **Ralphy + OpenCode**.
|
|
4
|
+
|
|
5
|
+
It scaffolds a standard project integration folder, ships language-agnostic agent definitions, and supports stack presets like Java, Spring Boot, and Next.js.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- One-command project setup
|
|
10
|
+
- Reusable agent team (orchestrator, planner, developer, qa, reviewer, docs)
|
|
11
|
+
- Preset-based context (Java, Spring Boot, Next.js)
|
|
12
|
+
- Orchestrated workflow: plan -> implement -> test -> verify
|
|
13
|
+
- Auto-release pipeline with semantic-release (GitHub Actions)
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Run directly with npx:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx opencode-auto-agent init
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or install globally:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g opencode-auto-agent
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## CLI
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
opencode-auto-agent init [--preset=<name>]
|
|
33
|
+
opencode-auto-agent run [--engine=<name>]
|
|
34
|
+
opencode-auto-agent setup <preset>
|
|
35
|
+
opencode-auto-agent doctor
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Presets
|
|
39
|
+
|
|
40
|
+
- `java`
|
|
41
|
+
- `springboot`
|
|
42
|
+
- `nextjs`
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npx opencode-auto-agent init --preset=nextjs
|
|
48
|
+
npx opencode-auto-agent doctor
|
|
49
|
+
npx opencode-auto-agent run
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Generated Structure
|
|
53
|
+
|
|
54
|
+
The `init` command creates:
|
|
55
|
+
|
|
56
|
+
```text
|
|
57
|
+
.ai-starter-kit/
|
|
58
|
+
agents/
|
|
59
|
+
presets/
|
|
60
|
+
context/
|
|
61
|
+
rules/
|
|
62
|
+
config.json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Plus helper config files:
|
|
66
|
+
|
|
67
|
+
- `opencode.json`
|
|
68
|
+
- `.ralphy/config.yaml`
|
|
69
|
+
|
|
70
|
+
## Requirements
|
|
71
|
+
|
|
72
|
+
- Node.js 18+
|
|
73
|
+
- OpenCode CLI installed and configured
|
|
74
|
+
- Ralphy CLI installed
|
|
75
|
+
|
|
76
|
+
## Releasing
|
|
77
|
+
|
|
78
|
+
Releases are fully automated via semantic-release on pushes to `main`.
|
|
79
|
+
|
|
80
|
+
See `Docs/releasing.md` for details.
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* opencode-auto-agent CLI
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* init Scaffold .ai-starter-kit/ into the current repo
|
|
8
|
+
* run Start the orchestrator (Ralphy + OpenCode agents)
|
|
9
|
+
* setup <preset> Apply or switch a preset (java, springboot, nextjs, ...)
|
|
10
|
+
* doctor Validate configuration and tool availability
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { resolve, basename } from "node:path";
|
|
14
|
+
import { argv, exit, cwd } from "node:process";
|
|
15
|
+
|
|
16
|
+
// ── arg parsing (zero-dep) ─────────────────────────────────────────────────
|
|
17
|
+
const args = argv.slice(2);
|
|
18
|
+
const command = args[0] || "help";
|
|
19
|
+
const positional = args.slice(1).filter((a) => !a.startsWith("--"));
|
|
20
|
+
const flags = Object.fromEntries(
|
|
21
|
+
args
|
|
22
|
+
.filter((a) => a.startsWith("--"))
|
|
23
|
+
.map((a) => {
|
|
24
|
+
const [k, v] = a.replace(/^--/, "").split("=");
|
|
25
|
+
return [k, v ?? true];
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const TARGET_DIR = resolve(flags.dir || cwd());
|
|
30
|
+
|
|
31
|
+
// ── dynamic import of command modules ──────────────────────────────────────
|
|
32
|
+
async function main() {
|
|
33
|
+
switch (command) {
|
|
34
|
+
case "init": {
|
|
35
|
+
const { init } = await import("../src/commands/init.js");
|
|
36
|
+
await init(TARGET_DIR, { preset: positional[0] || flags.preset });
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
case "run": {
|
|
40
|
+
const { run } = await import("../src/commands/run.js");
|
|
41
|
+
await run(TARGET_DIR, flags);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
case "setup": {
|
|
45
|
+
const { setup } = await import("../src/commands/setup.js");
|
|
46
|
+
const preset = positional[0] || flags.preset;
|
|
47
|
+
if (!preset) {
|
|
48
|
+
console.error("Usage: opencode-auto-agent setup <preset>");
|
|
49
|
+
console.error("Available presets: java, springboot, nextjs");
|
|
50
|
+
exit(1);
|
|
51
|
+
}
|
|
52
|
+
await setup(TARGET_DIR, preset);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case "doctor": {
|
|
56
|
+
const { doctor } = await import("../src/commands/doctor.js");
|
|
57
|
+
await doctor(TARGET_DIR);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
case "help":
|
|
61
|
+
case "--help":
|
|
62
|
+
case "-h":
|
|
63
|
+
default:
|
|
64
|
+
printHelp();
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function printHelp() {
|
|
70
|
+
const name = "opencode-auto-agent";
|
|
71
|
+
console.log(`
|
|
72
|
+
${name} v0.1.0 — AI agent team starter kit (Ralphy + OpenCode)
|
|
73
|
+
|
|
74
|
+
USAGE
|
|
75
|
+
npx ${name} <command> [options]
|
|
76
|
+
|
|
77
|
+
COMMANDS
|
|
78
|
+
init [--preset=<name>] Scaffold .ai-starter-kit/ into the current project
|
|
79
|
+
run [--engine=<name>] Start the orchestrator (Ralphy task loop)
|
|
80
|
+
setup <preset> Apply or switch a preset (java | springboot | nextjs)
|
|
81
|
+
doctor Validate config, tools, and agent definitions
|
|
82
|
+
|
|
83
|
+
OPTIONS
|
|
84
|
+
--dir=<path> Target directory (default: cwd)
|
|
85
|
+
--preset=<name> Preset to apply during init
|
|
86
|
+
--engine=<name> Ralphy engine (opencode | claude | cursor, default: opencode)
|
|
87
|
+
|
|
88
|
+
PRESETS
|
|
89
|
+
java General Java project (Maven/Gradle)
|
|
90
|
+
springboot Spring Boot web application
|
|
91
|
+
nextjs Next.js (React) application
|
|
92
|
+
|
|
93
|
+
EXAMPLES
|
|
94
|
+
npx ${name} init --preset=nextjs
|
|
95
|
+
npx ${name} run
|
|
96
|
+
npx ${name} setup springboot
|
|
97
|
+
npx ${name} doctor
|
|
98
|
+
`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
main().catch((err) => {
|
|
102
|
+
console.error("Fatal:", err.message || err);
|
|
103
|
+
exit(1);
|
|
104
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-auto-agent",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffold and run an AI agent team powered by Ralphy + OpenCode. Presets for Java, Spring Boot, Next.js, and more.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"opencode-auto-agent": "./bin/cli.js",
|
|
9
|
+
"aisk": "./bin/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"src/",
|
|
14
|
+
"templates/"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "node bin/cli.js",
|
|
18
|
+
"test": "node --test src/**/*.test.js 2>/dev/null || true",
|
|
19
|
+
"verify": "node bin/cli.js help"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"ai",
|
|
23
|
+
"agents",
|
|
24
|
+
"opencode",
|
|
25
|
+
"ralphy",
|
|
26
|
+
"scaffolding",
|
|
27
|
+
"cli",
|
|
28
|
+
"orchestrator",
|
|
29
|
+
"starter-kit",
|
|
30
|
+
"code-generation"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/maystudios/opencode-auto-agent.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/maystudios/opencode-auto-agent#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/maystudios/opencode-auto-agent/issues"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"semantic-release": "^24.2.1",
|
|
46
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
47
|
+
"@semantic-release/git": "^10.0.1",
|
|
48
|
+
"@semantic-release/github": "^11.0.1",
|
|
49
|
+
"@semantic-release/npm": "^12.0.1",
|
|
50
|
+
"@semantic-release/commit-analyzer": "^13.0.1",
|
|
51
|
+
"@semantic-release/release-notes-generator": "^14.0.3",
|
|
52
|
+
"conventional-changelog-conventionalcommits": "^8.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* doctor command — validate configuration and tool availability.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { execSync } from "node:child_process";
|
|
8
|
+
|
|
9
|
+
import { SCAFFOLD_DIR, CONFIG_FILE, DIRS, AGENTS, PRESETS } from "../lib/constants.js";
|
|
10
|
+
import { readJsonSync } from "../lib/fs-utils.js";
|
|
11
|
+
|
|
12
|
+
export async function doctor(targetDir) {
|
|
13
|
+
const scaffoldDir = join(targetDir, SCAFFOLD_DIR);
|
|
14
|
+
let errors = 0;
|
|
15
|
+
let warnings = 0;
|
|
16
|
+
|
|
17
|
+
console.log("[doctor] Checking opencode-auto-agent configuration...\n");
|
|
18
|
+
|
|
19
|
+
// 1. Scaffold directory exists
|
|
20
|
+
check("Scaffold directory", existsSync(scaffoldDir), `${SCAFFOLD_DIR}/ not found. Run 'opencode-auto-agent init'.`);
|
|
21
|
+
|
|
22
|
+
// 2. Config file
|
|
23
|
+
const configPath = join(scaffoldDir, CONFIG_FILE);
|
|
24
|
+
const config = readJsonSync(configPath);
|
|
25
|
+
check("Config file", !!config, `${CONFIG_FILE} missing or invalid JSON.`);
|
|
26
|
+
|
|
27
|
+
if (config) {
|
|
28
|
+
// 3. Preset is valid
|
|
29
|
+
const presetFile = join(scaffoldDir, DIRS.presets, `${config.preset}.md`);
|
|
30
|
+
check(`Preset "${config.preset}"`, existsSync(presetFile), `Preset file not found: presets/${config.preset}.md`);
|
|
31
|
+
|
|
32
|
+
// 4. Task file exists
|
|
33
|
+
const tasksPath = join(targetDir, config.tasksPath || "PRD.md");
|
|
34
|
+
warn(`Tasks file (${config.tasksPath || "PRD.md"})`, existsSync(tasksPath), "Create a PRD.md or update tasksPath in config.");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 5. Agent markdowns
|
|
38
|
+
for (const agent of AGENTS) {
|
|
39
|
+
const agentFile = join(scaffoldDir, DIRS.agents, `${agent}.md`);
|
|
40
|
+
check(`Agent: ${agent}`, existsSync(agentFile), `Missing agent definition: agents/${agent}.md`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 6. Rules
|
|
44
|
+
const rulesDir = join(scaffoldDir, DIRS.rules, "universal.md");
|
|
45
|
+
check("Universal rules", existsSync(rulesDir), "Missing rules/universal.md");
|
|
46
|
+
|
|
47
|
+
// 7. External tools
|
|
48
|
+
checkTool("ralphy", "npm install -g ralphy-cli");
|
|
49
|
+
checkTool("opencode", "npm install -g opencode-ai");
|
|
50
|
+
|
|
51
|
+
// 8. OpenCode config
|
|
52
|
+
const ocConfig = join(targetDir, "opencode.json");
|
|
53
|
+
warn("opencode.json", existsSync(ocConfig), "Run 'opencode-auto-agent init' to generate it.");
|
|
54
|
+
|
|
55
|
+
console.log("\n[doctor] ─────────────────────────────────────────");
|
|
56
|
+
if (errors === 0 && warnings === 0) {
|
|
57
|
+
console.log("[doctor] All checks passed.");
|
|
58
|
+
} else {
|
|
59
|
+
console.log(`[doctor] ${errors} error(s), ${warnings} warning(s).`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── helpers ──────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function check(label, ok, hint) {
|
|
65
|
+
if (ok) {
|
|
66
|
+
console.log(` [pass] ${label}`);
|
|
67
|
+
} else {
|
|
68
|
+
console.log(` [FAIL] ${label} — ${hint}`);
|
|
69
|
+
errors++;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function warn(label, ok, hint) {
|
|
74
|
+
if (ok) {
|
|
75
|
+
console.log(` [pass] ${label}`);
|
|
76
|
+
} else {
|
|
77
|
+
console.log(` [warn] ${label} — ${hint}`);
|
|
78
|
+
warnings++;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function checkTool(name, installHint) {
|
|
83
|
+
try {
|
|
84
|
+
execSync(`${name} --version`, { stdio: "pipe" });
|
|
85
|
+
console.log(` [pass] CLI: ${name}`);
|
|
86
|
+
} catch {
|
|
87
|
+
console.log(` [warn] CLI: ${name} not found. Install: ${installHint}`);
|
|
88
|
+
warnings++;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* init command — scaffold .ai-starter-kit/ into the target repository.
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* 1. Never overwrite files that already exist (safe re-run).
|
|
6
|
+
* 2. Write config.json with schema version for future upgrades.
|
|
7
|
+
* 3. Copy agent markdowns, rules, and selected preset.
|
|
8
|
+
* 4. Create empty context/ folder for runtime context assembly.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join, resolve } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
import { SCAFFOLD_DIR, CONFIG_FILE, PRESETS, AGENTS, DIRS, SCHEMA_VERSION } from "../lib/constants.js";
|
|
16
|
+
import { copyDirSync, readJsonSync, writeJsonSync, writeTextSync } from "../lib/fs-utils.js";
|
|
17
|
+
|
|
18
|
+
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
19
|
+
const TEMPLATES_ROOT = resolve(__dirname, "..", "..", "templates");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default config written on first init.
|
|
23
|
+
*/
|
|
24
|
+
function makeDefaultConfig(preset) {
|
|
25
|
+
return {
|
|
26
|
+
$schema: "https://github.com/opencode-auto-agent/config-schema",
|
|
27
|
+
version: SCHEMA_VERSION,
|
|
28
|
+
preset: preset || "java",
|
|
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
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function init(targetDir, { preset } = {}) {
|
|
45
|
+
const scaffoldDir = join(targetDir, SCAFFOLD_DIR);
|
|
46
|
+
const isUpgrade = existsSync(scaffoldDir);
|
|
47
|
+
|
|
48
|
+
if (isUpgrade) {
|
|
49
|
+
console.log(`[init] Existing ${SCAFFOLD_DIR}/ detected — upgrading (no overwrites).`);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(`[init] Scaffolding ${SCAFFOLD_DIR}/ into ${targetDir}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 1. Create directory skeleton
|
|
55
|
+
for (const sub of Object.values(DIRS)) {
|
|
56
|
+
mkdirSync(join(scaffoldDir, sub), { recursive: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 2. Copy agent markdowns (skip existing)
|
|
60
|
+
const agentsSrc = join(TEMPLATES_ROOT, "agents");
|
|
61
|
+
const agentsDest = join(scaffoldDir, DIRS.agents);
|
|
62
|
+
copyDirSync(agentsSrc, agentsDest, { overwrite: false });
|
|
63
|
+
console.log(`[init] Agents written to ${DIRS.agents}/`);
|
|
64
|
+
|
|
65
|
+
// 3. Copy rules (skip existing)
|
|
66
|
+
const rulesSrc = join(TEMPLATES_ROOT, "rules");
|
|
67
|
+
const rulesDest = join(scaffoldDir, DIRS.rules);
|
|
68
|
+
copyDirSync(rulesSrc, rulesDest, { overwrite: false });
|
|
69
|
+
console.log(`[init] Rules written to ${DIRS.rules}/`);
|
|
70
|
+
|
|
71
|
+
// 4. Copy selected preset (or all presets)
|
|
72
|
+
const presetsSrc = join(TEMPLATES_ROOT, "presets");
|
|
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
|
|
78
|
+
const contextReadme = join(scaffoldDir, DIRS.context, "README.md");
|
|
79
|
+
if (!existsSync(contextReadme)) {
|
|
80
|
+
writeTextSync(
|
|
81
|
+
contextReadme,
|
|
82
|
+
"# 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
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 6. Write config.json (only if missing)
|
|
87
|
+
const configPath = join(scaffoldDir, CONFIG_FILE);
|
|
88
|
+
if (!existsSync(configPath)) {
|
|
89
|
+
const chosenPreset = preset && PRESETS.includes(preset) ? preset : undefined;
|
|
90
|
+
writeJsonSync(configPath, makeDefaultConfig(chosenPreset));
|
|
91
|
+
console.log(`[init] Config written to ${CONFIG_FILE} (preset: ${chosenPreset || "java"})`);
|
|
92
|
+
} else {
|
|
93
|
+
console.log(`[init] Config already exists — skipped.`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 7. Generate opencode.json at project root if missing
|
|
97
|
+
const opencodeConfig = join(targetDir, "opencode.json");
|
|
98
|
+
if (!existsSync(opencodeConfig)) {
|
|
99
|
+
writeJsonSync(opencodeConfig, buildOpencodeConfig());
|
|
100
|
+
console.log(`[init] Generated opencode.json at project root.`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 8. Generate .ralphy/config.yaml stub if missing
|
|
104
|
+
const ralphyDir = join(targetDir, ".ralphy");
|
|
105
|
+
const ralphyConfig = join(ralphyDir, "config.yaml");
|
|
106
|
+
if (!existsSync(ralphyConfig)) {
|
|
107
|
+
mkdirSync(ralphyDir, { recursive: true });
|
|
108
|
+
writeTextSync(ralphyConfig, buildRalphyConfig());
|
|
109
|
+
console.log(`[init] Generated .ralphy/config.yaml`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
console.log(`\n[init] Done. Next steps:`);
|
|
113
|
+
console.log(` 1. Edit ${SCAFFOLD_DIR}/${CONFIG_FILE} to set your preset and paths.`);
|
|
114
|
+
console.log(` 2. Run: npx opencode-auto-agent doctor`);
|
|
115
|
+
console.log(` 3. Run: npx opencode-auto-agent run`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ── helpers ────────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
function buildOpencodeConfig() {
|
|
121
|
+
return {
|
|
122
|
+
$schema: "https://opencode.ai/config.json",
|
|
123
|
+
instructions: [
|
|
124
|
+
".ai-starter-kit/agents/orchestrator.md",
|
|
125
|
+
".ai-starter-kit/rules/universal.md",
|
|
126
|
+
],
|
|
127
|
+
agent: {
|
|
128
|
+
orchestrator: {
|
|
129
|
+
description: "Primary orchestrator — routes tasks to specialist sub-agents",
|
|
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
|
+
},
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function buildRalphyConfig() {
|
|
173
|
+
return `# Ralphy configuration — generated by opencode-auto-agent
|
|
174
|
+
project:
|
|
175
|
+
name: ""
|
|
176
|
+
language: ""
|
|
177
|
+
framework: ""
|
|
178
|
+
description: ""
|
|
179
|
+
|
|
180
|
+
commands:
|
|
181
|
+
test: ""
|
|
182
|
+
lint: ""
|
|
183
|
+
build: ""
|
|
184
|
+
|
|
185
|
+
rules:
|
|
186
|
+
- "Follow the plan produced by the planner agent before implementing."
|
|
187
|
+
- "Run tests after every implementation step."
|
|
188
|
+
- "Do not modify files outside the project source tree."
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* run command — start the orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. Load config.json
|
|
6
|
+
* 2. Assemble runtime context (preset rules + base rules + docs)
|
|
7
|
+
* 3. Write assembled context to .ai-starter-kit/context/
|
|
8
|
+
* 4. Invoke Ralphy with --opencode engine against the PRD/task file
|
|
9
|
+
* 5. Ralphy drives OpenCode, which loads the orchestrator agent and delegates
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { execSync, spawn } from "node:child_process";
|
|
15
|
+
|
|
16
|
+
import { SCAFFOLD_DIR, CONFIG_FILE, DIRS } from "../lib/constants.js";
|
|
17
|
+
import { readJsonSync, readTextSync, writeTextSync } from "../lib/fs-utils.js";
|
|
18
|
+
|
|
19
|
+
export async function run(targetDir, flags = {}) {
|
|
20
|
+
const scaffoldDir = join(targetDir, SCAFFOLD_DIR);
|
|
21
|
+
const configPath = join(scaffoldDir, CONFIG_FILE);
|
|
22
|
+
|
|
23
|
+
// 1. Load config
|
|
24
|
+
const config = readJsonSync(configPath);
|
|
25
|
+
if (!config) {
|
|
26
|
+
console.error(`[run] No config found at ${configPath}. Run 'opencode-auto-agent init' first.`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`[run] Preset: ${config.preset}`);
|
|
31
|
+
console.log(`[run] Engine: ${flags.engine || config.engine || "opencode"}`);
|
|
32
|
+
|
|
33
|
+
// 2. Assemble runtime context
|
|
34
|
+
console.log("[run] Assembling runtime context...");
|
|
35
|
+
const context = assembleContext(targetDir, config);
|
|
36
|
+
const contextFile = join(scaffoldDir, DIRS.context, "runtime-context.md");
|
|
37
|
+
writeTextSync(contextFile, context);
|
|
38
|
+
console.log(`[run] Context written to ${DIRS.context}/runtime-context.md`);
|
|
39
|
+
|
|
40
|
+
// 3. Determine task file
|
|
41
|
+
const tasksPath = join(targetDir, config.tasksPath || "PRD.md");
|
|
42
|
+
if (!existsSync(tasksPath)) {
|
|
43
|
+
console.error(`[run] Task file not found: ${tasksPath}`);
|
|
44
|
+
console.error(`[run] Create a PRD.md or update tasksPath in config.json.`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 4. Invoke Ralphy
|
|
49
|
+
const engine = flags.engine || config.engine || "opencode";
|
|
50
|
+
const ralphyArgs = [
|
|
51
|
+
"--prd", tasksPath,
|
|
52
|
+
`--${engine}`,
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
console.log(`[run] Starting Ralphy: ralphy ${ralphyArgs.join(" ")}`);
|
|
56
|
+
console.log("[run] ─────────────────────────────────────────────");
|
|
57
|
+
|
|
58
|
+
const child = spawn("ralphy", ralphyArgs, {
|
|
59
|
+
cwd: targetDir,
|
|
60
|
+
stdio: "inherit",
|
|
61
|
+
shell: true,
|
|
62
|
+
env: {
|
|
63
|
+
...process.env,
|
|
64
|
+
// Pass context location to OpenCode via env
|
|
65
|
+
AISK_CONTEXT_DIR: join(scaffoldDir, DIRS.context),
|
|
66
|
+
AISK_CONFIG_PATH: configPath,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
child.on("close", (code) => {
|
|
71
|
+
if (code === 0) {
|
|
72
|
+
console.log("\n[run] Orchestrator completed successfully.");
|
|
73
|
+
} else {
|
|
74
|
+
console.error(`\n[run] Orchestrator exited with code ${code}.`);
|
|
75
|
+
process.exit(code);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
child.on("error", (err) => {
|
|
80
|
+
if (err.code === "ENOENT") {
|
|
81
|
+
console.error("[run] 'ralphy' CLI not found. Install it:");
|
|
82
|
+
console.error(" npm install -g ralphy-cli");
|
|
83
|
+
} else {
|
|
84
|
+
console.error("[run] Failed to start Ralphy:", err.message);
|
|
85
|
+
}
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── context assembly ───────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
function assembleContext(targetDir, config) {
|
|
93
|
+
const scaffoldDir = join(targetDir, SCAFFOLD_DIR);
|
|
94
|
+
const sections = [];
|
|
95
|
+
|
|
96
|
+
sections.push("# Runtime Context (auto-generated)\n");
|
|
97
|
+
sections.push(`Preset: **${config.preset}**`);
|
|
98
|
+
sections.push(`Workflow: ${(config.orchestrator?.workflowSteps || []).join(" → ")}\n`);
|
|
99
|
+
|
|
100
|
+
// Base rules
|
|
101
|
+
const rulesDir = join(scaffoldDir, DIRS.rules);
|
|
102
|
+
if (existsSync(rulesDir)) {
|
|
103
|
+
for (const file of readdirSync(rulesDir).filter((f) => f.endsWith(".md"))) {
|
|
104
|
+
const content = readTextSync(join(rulesDir, file));
|
|
105
|
+
if (content) {
|
|
106
|
+
sections.push(`## Rules: ${file}\n${content}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Preset rules
|
|
112
|
+
const presetFile = join(scaffoldDir, DIRS.presets, `${config.preset}.md`);
|
|
113
|
+
const presetContent = readTextSync(presetFile);
|
|
114
|
+
if (presetContent) {
|
|
115
|
+
sections.push(`## Preset: ${config.preset}\n${presetContent}`);
|
|
116
|
+
} else {
|
|
117
|
+
sections.push(`## Preset: ${config.preset}\n(No preset file found at ${presetFile})`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Repo docs (if configured and present)
|
|
121
|
+
const docsPath = join(targetDir, config.docsPath || "Docs");
|
|
122
|
+
if (existsSync(docsPath)) {
|
|
123
|
+
sections.push(`## Project Documentation\nDocs location: ${docsPath}`);
|
|
124
|
+
try {
|
|
125
|
+
const docFiles = readdirSync(docsPath).filter((f) => f.endsWith(".md"));
|
|
126
|
+
for (const file of docFiles.slice(0, 10)) {
|
|
127
|
+
const content = readTextSync(join(docsPath, file));
|
|
128
|
+
if (content) {
|
|
129
|
+
sections.push(`### ${file}\n${content.slice(0, 2000)}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
// non-fatal
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Agent manifest
|
|
138
|
+
sections.push("## Agent Team");
|
|
139
|
+
const agentsDir = join(scaffoldDir, DIRS.agents);
|
|
140
|
+
if (existsSync(agentsDir)) {
|
|
141
|
+
for (const file of readdirSync(agentsDir).filter((f) => f.endsWith(".md")).sort()) {
|
|
142
|
+
sections.push(`- ${file.replace(".md", "")}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return sections.join("\n\n");
|
|
147
|
+
}
|