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 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
+ }