opencode-kolchoz-loop 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,149 @@
1
+ # opencode-kolchoz-loop
2
+
3
+ Multi-agent Ralph Loop plugin for [OpenCode](https://opencode.ai).
4
+
5
+ Four agents work in a Ralph Loop workflow and autonomously execute tasks from requirements analysis to code review.
6
+
7
+ ## Agents
8
+
9
+ | Agent | Role | Mode | Color |
10
+ |-------|------|------|-------|
11
+ | **Januszek** 👔 | Orchestrator - talks to the user, delegates work | primary | orange |
12
+ | **Grazynka** 📋 | Requirements analyst - creates PRDs with user stories | subagent | purple |
13
+ | **Areczek** 🔧 | Builder - implements code, tests, commits | subagent | cyan |
14
+ | **Anetka** 🔍 | Reviewer - quality gate: tests, lint, typecheck, diff | subagent | pink |
15
+
16
+ ## Installation
17
+
18
+ Add the package to `opencode.json` in your project:
19
+
20
+ ```json
21
+ {
22
+ "$schema": "https://opencode.ai/config.json",
23
+ "plugin": ["opencode-kolchoz-loop"]
24
+ }
25
+ ```
26
+
27
+ On the next OpenCode start it will automatically:
28
+ 1. Install the package via Bun
29
+ 2. Copy agent definitions into `.opencode/agents/`
30
+ 3. Create `.opencode/state/` for runtime state files
31
+ 4. Add `.opencode/state/` to `.gitignore`
32
+
33
+ ### From a private npm registry (internal)
34
+
35
+ ```json
36
+ {
37
+ "plugin": ["@your-company/opencode-kolchoz-loop"]
38
+ }
39
+ ```
40
+
41
+ ### From a local folder (testing)
42
+
43
+ Copy the full `src/` directory into `.opencode/plugins/` in your project:
44
+
45
+ ```bash
46
+ cp -r src/ /path/to/project/.opencode/plugins/kolchoz-loop/
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ### Interactive mode
52
+
53
+ After OpenCode starts, Januszek is the default agent. Describe the task:
54
+
55
+ ```
56
+ Implement an authentication system with OAuth2 and 2FA
57
+ ```
58
+
59
+ Januszek then:
60
+ 1. Delegates requirement clarification to `@grazynka`
61
+ 2. Grazynka creates a PRD with user stories
62
+ 3. Januszek delegates implementation to `@areczek`
63
+ 4. Areczek codes, tests, and commits
64
+ 5. `@anetka` performs code review
65
+ 6. If FAIL -> Areczek fixes. If PASS -> next story.
66
+ 7. After PRD completion -> Januszek reports back.
67
+
68
+ ### Manual agent calls
69
+
70
+ ```
71
+ @grazynka Analyze requirements for the payments module
72
+ @areczek Fetch the next task and implement it
73
+ @anetka Review the latest changes
74
+ ```
75
+
76
+ ### Check status
77
+
78
+ Januszek uses `kolchoz_status`, but you can also inspect state manually:
79
+
80
+ ```bash
81
+ cat .opencode/state/prd.json | jq '.userStories[] | {id, title, status}'
82
+ tail -20 .opencode/state/progress.txt
83
+ ```
84
+
85
+ ### Reset (new task)
86
+
87
+ `kolchoz_reset` clears loop state while preserving accumulated knowledge in `AGENTS.md`.
88
+
89
+ ## File structure
90
+
91
+ After installation this appears in your project:
92
+
93
+ ```
94
+ your-project/
95
+ ├── .opencode/
96
+ │ ├── agents/ ← copied automatically from the package
97
+ │ │ ├── januszek.md
98
+ │ │ ├── grazynka.md
99
+ │ │ ├── areczek.md
100
+ │ │ └── anetka.md
101
+ │ └── state/ ← gitignored, ephemeral
102
+ │ ├── prd.json
103
+ │ ├── progress.txt
104
+ │ └── loop-state.json
105
+ ├── AGENTS.md ← accumulated knowledge, COMMITTED
106
+ └── .gitignore ← auto-updated: .opencode/state/
107
+ ```
108
+
109
+ ## Tools (custom tools)
110
+
111
+ The plugin registers 7 tools:
112
+
113
+ | Tool | Used by | Description |
114
+ |------|---------|-------------|
115
+ | `kolchoz_create_prd` | Grazynka | Creates PRD with user stories |
116
+ | `kolchoz_next_task` | Areczek | Fetches next task |
117
+ | `kolchoz_submit_for_review` | Areczek | Submits work for review |
118
+ | `kolchoz_review_verdict` | Anetka | Pass/fail verdict |
119
+ | `kolchoz_status` | Everyone | Loop status |
120
+ | `kolchoz_learn` | Everyone | Writes knowledge to AGENTS.md |
121
+ | `kolchoz_reset` | Januszek | Clears state, keeps knowledge |
122
+
123
+ ## Model configuration
124
+
125
+ Agents use `claude-sonnet-4` by default. Override in `opencode.json`:
126
+
127
+ ```json
128
+ {
129
+ "agent": {
130
+ "januszek": { "model": "anthropic/claude-opus-4-20250514" },
131
+ "areczek": { "model": "openai/gpt-4.1" },
132
+ "anetka": { "model": "google/gemini-2.5-pro" }
133
+ }
134
+ }
135
+ ```
136
+
137
+ Recommendation: Anetka and Areczek should use different models. Cross-model review reduces shared blind spots.
138
+
139
+ ## Compound engineering
140
+
141
+ The system builds knowledge in `AGENTS.md` automatically:
142
+
143
+ 1. Areczek discovers a pattern -> `kolchoz_learn("pattern", "...")`
144
+ 2. Anetka discovers a gotcha -> `kolchoz_learn("gotcha", "...")`
145
+ 3. Future sessions read AGENTS.md -> better context -> better outcomes
146
+
147
+ ## License
148
+
149
+ MIT
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ export declare const KolchozLoop: Plugin;
3
+ export default KolchozLoop;
package/dist/index.js ADDED
@@ -0,0 +1,409 @@
1
+ import { tool } from "@opencode-ai/plugin/tool";
2
+ import { readFile, writeFile, mkdir, readdir, copyFile, stat } from "fs/promises";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ // ── Filesystem helpers ──
6
+ async function ensureDir(dir) {
7
+ await mkdir(dir, { recursive: true });
8
+ }
9
+ async function exists(path) {
10
+ try {
11
+ await stat(path);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ async function readJson(dir, filename, fallback) {
19
+ try {
20
+ const raw = await readFile(join(dir, filename), "utf-8");
21
+ return JSON.parse(raw);
22
+ }
23
+ catch {
24
+ return fallback;
25
+ }
26
+ }
27
+ async function writeJson(dir, filename, data) {
28
+ await ensureDir(dir);
29
+ await writeFile(join(dir, filename), JSON.stringify(data, null, 2), "utf-8");
30
+ }
31
+ async function appendProgress(dir, message) {
32
+ await ensureDir(dir);
33
+ const file = join(dir, "progress.txt");
34
+ const timestamp = new Date().toISOString();
35
+ const line = `[${timestamp}] ${message}\n`;
36
+ try {
37
+ const existing = await readFile(file, "utf-8");
38
+ await writeFile(file, existing + line, "utf-8");
39
+ }
40
+ catch {
41
+ await writeFile(file, line, "utf-8");
42
+ }
43
+ }
44
+ // ── Agent provisioning ──
45
+ // Copies bundled .md agent files to .opencode/agents/
46
+ // so OpenCode discovers them as real agents.
47
+ async function provisionAgents(projectRoot, log) {
48
+ const targetDir = join(projectRoot, ".opencode", "agents");
49
+ await ensureDir(targetDir);
50
+ // Resolve the agents directory bundled inside this package
51
+ const packageAgentsDir = join(dirname(fileURLToPath(import.meta.url)), "agents");
52
+ let agentFiles;
53
+ try {
54
+ agentFiles = (await readdir(packageAgentsDir)).filter((f) => f.endsWith(".md"));
55
+ }
56
+ catch {
57
+ log("WARN: Could not read bundled agents directory. Agents must be provisioned manually.");
58
+ return;
59
+ }
60
+ for (const file of agentFiles) {
61
+ const target = join(targetDir, file);
62
+ if (await exists(target)) {
63
+ // Don't overwrite — user may have customized
64
+ continue;
65
+ }
66
+ await copyFile(join(packageAgentsDir, file), target);
67
+ log(`Provisioned agent: ${file} → .opencode/agents/${file}`);
68
+ }
69
+ }
70
+ // ── Gitignore helper ──
71
+ // Ensures .opencode/state/ is in .gitignore
72
+ async function ensureGitignore(projectRoot) {
73
+ const gitignorePath = join(projectRoot, ".gitignore");
74
+ const entry = ".opencode/state/";
75
+ let content = "";
76
+ try {
77
+ content = await readFile(gitignorePath, "utf-8");
78
+ }
79
+ catch {
80
+ // No .gitignore — create one
81
+ }
82
+ if (!content.includes(entry)) {
83
+ const addition = content.length > 0 && !content.endsWith("\n")
84
+ ? `\n\n# Kolkhoz Loop state (ephemeral)\n${entry}\n`
85
+ : `\n# Kolkhoz Loop state (ephemeral)\n${entry}\n`;
86
+ await writeFile(gitignorePath, content + addition, "utf-8");
87
+ }
88
+ }
89
+ // ── Main Plugin Export ──
90
+ export const KolchozLoop = async ({ project, client, $, directory, worktree }) => {
91
+ const projectRoot = worktree || directory;
92
+ const stateDir = join(projectRoot, ".opencode", "state");
93
+ // ── Auto-provision on first run ──
94
+ await ensureDir(stateDir);
95
+ await provisionAgents(projectRoot, (msg) => {
96
+ client.app.log({
97
+ body: { service: "kolchoz-loop", level: "info", message: msg },
98
+ });
99
+ });
100
+ await ensureGitignore(projectRoot);
101
+ await client.app.log({
102
+ body: {
103
+ service: "kolchoz-loop",
104
+ level: "info",
105
+ message: `Kolkhoz Loop initialized. Project: ${projectRoot}, State: ${stateDir}`,
106
+ },
107
+ });
108
+ return {
109
+ // ══════════════════════════════════
110
+ // Custom Tools
111
+ // ══════════════════════════════════
112
+ tool: {
113
+ // ── Grazynka: create/update PRD ──
114
+ kolchoz_create_prd: tool({
115
+ description: "[Grazynka] Create or update a structured PRD with user stories " +
116
+ "and acceptance criteria. Writes to .opencode/state/prd.json.",
117
+ args: {
118
+ title: tool.schema.string(),
119
+ context: tool.schema.string(),
120
+ constraints: tool.schema.string(),
121
+ stories: tool.schema.string(), // JSON array
122
+ },
123
+ async execute(args) {
124
+ const stories = JSON.parse(args.stories).map((s, i) => ({
125
+ id: s.id || `story-${i + 1}`,
126
+ title: s.title || `Story ${i + 1}`,
127
+ description: s.description || "",
128
+ acceptanceCriteria: s.acceptanceCriteria || [],
129
+ priority: s.priority || "medium",
130
+ status: "pending",
131
+ assignedTo: "areczek",
132
+ iteration: 0,
133
+ }));
134
+ const prd = {
135
+ title: args.title,
136
+ createdBy: "grazynka",
137
+ createdAt: new Date().toISOString(),
138
+ userStories: stories,
139
+ context: args.context,
140
+ constraints: args.constraints.split(",").map((c) => c.trim()),
141
+ };
142
+ await writeJson(stateDir, "prd.json", prd);
143
+ await appendProgress(stateDir, `[Grazynka] PRD created: "${args.title}" with ${stories.length} stories`);
144
+ return `PRD created with ${stories.length} stories. Saved to .opencode/state/prd.json`;
145
+ },
146
+ }),
147
+ // ── Areczek: get next task ──
148
+ kolchoz_next_task: tool({
149
+ description: "[Areczek] Get the next pending or failed story from the PRD. " +
150
+ "Returns story details or signals completion.",
151
+ args: {},
152
+ async execute() {
153
+ const prd = await readJson(stateDir, "prd.json", null);
154
+ if (!prd)
155
+ return "ERROR: No PRD found. Ask Januszek to start the process.";
156
+ const next = prd.userStories.find((s) => s.status === "pending" || s.status === "failed");
157
+ if (!next) {
158
+ const allDone = prd.userStories.every((s) => s.status === "done");
159
+ if (allDone)
160
+ return "<promise>COMPLETE</promise> — All stories are done!";
161
+ return "No actionable stories. Some may be in review.";
162
+ }
163
+ next.status = "in_progress";
164
+ next.iteration += 1;
165
+ await writeJson(stateDir, "prd.json", prd);
166
+ const state = await readJson(stateDir, "loop-state.json", {
167
+ currentIteration: 0,
168
+ maxIterations: 10,
169
+ currentStoryId: null,
170
+ phase: "idle",
171
+ startedAt: new Date().toISOString(),
172
+ history: [],
173
+ });
174
+ state.phase = "building";
175
+ state.currentStoryId = next.id;
176
+ state.currentIteration += 1;
177
+ await writeJson(stateDir, "loop-state.json", state);
178
+ await appendProgress(stateDir, `[Areczek] Starting: ${next.id} "${next.title}" (iteration ${next.iteration})`);
179
+ return JSON.stringify({
180
+ storyId: next.id,
181
+ title: next.title,
182
+ description: next.description,
183
+ acceptanceCriteria: next.acceptanceCriteria,
184
+ priority: next.priority,
185
+ iteration: next.iteration,
186
+ feedback: next.feedback || null,
187
+ }, null, 2);
188
+ },
189
+ }),
190
+ // ── Areczek: submit for review ──
191
+ kolchoz_submit_for_review: tool({
192
+ description: "[Areczek] Submit current story for review by Anetka.",
193
+ args: {
194
+ storyId: tool.schema.string(),
195
+ summary: tool.schema.string(),
196
+ filesChanged: tool.schema.string(),
197
+ },
198
+ async execute(args) {
199
+ const prd = await readJson(stateDir, "prd.json", null);
200
+ if (!prd)
201
+ return "ERROR: No PRD found.";
202
+ const story = prd.userStories.find((s) => s.id === args.storyId);
203
+ if (!story)
204
+ return `ERROR: Story ${args.storyId} not found.`;
205
+ story.status = "review";
206
+ await writeJson(stateDir, "prd.json", prd);
207
+ const state = await readJson(stateDir, "loop-state.json", {
208
+ currentIteration: 0, maxIterations: 10, currentStoryId: null,
209
+ phase: "idle", startedAt: new Date().toISOString(), history: [],
210
+ });
211
+ state.phase = "reviewing";
212
+ await writeJson(stateDir, "loop-state.json", state);
213
+ await appendProgress(stateDir, `[Areczek → Anetka] Story ${args.storyId} submitted. Files: ${args.filesChanged}`);
214
+ return `Story ${args.storyId} submitted for review. @anetka will verify.`;
215
+ },
216
+ }),
217
+ // ── Anetka: review verdict ──
218
+ kolchoz_review_verdict: tool({
219
+ description: "[Anetka] Submit review verdict. Pass = done. Fail = back to Areczek.",
220
+ args: {
221
+ storyId: tool.schema.string(),
222
+ verdict: tool.schema.enum(["pass", "fail"]),
223
+ feedback: tool.schema.string(),
224
+ testsRun: tool.schema.string(),
225
+ issuesFound: tool.schema.string(),
226
+ },
227
+ async execute(args) {
228
+ const prd = await readJson(stateDir, "prd.json", null);
229
+ if (!prd)
230
+ return "ERROR: No PRD found.";
231
+ const story = prd.userStories.find((s) => s.id === args.storyId);
232
+ if (!story)
233
+ return `ERROR: Story ${args.storyId} not found.`;
234
+ const state = await readJson(stateDir, "loop-state.json", {
235
+ currentIteration: 0, maxIterations: 10, currentStoryId: null,
236
+ phase: "idle", startedAt: new Date().toISOString(), history: [],
237
+ });
238
+ if (args.verdict === "pass") {
239
+ story.status = "done";
240
+ story.feedback = undefined;
241
+ state.phase = "idle";
242
+ await appendProgress(stateDir, `[Anetka ✅] Story ${args.storyId} PASSED. ${args.feedback}`);
243
+ }
244
+ else {
245
+ story.status = "failed";
246
+ story.feedback = args.feedback;
247
+ state.phase = "building";
248
+ await appendProgress(stateDir, `[Anetka ❌] Story ${args.storyId} FAILED. ${args.feedback}`);
249
+ }
250
+ state.history.push({
251
+ iteration: state.currentIteration,
252
+ storyId: args.storyId,
253
+ phase: "review",
254
+ result: args.verdict,
255
+ timestamp: new Date().toISOString(),
256
+ summary: `${args.verdict.toUpperCase()}: ${args.feedback}`,
257
+ });
258
+ await writeJson(stateDir, "prd.json", prd);
259
+ await writeJson(stateDir, "loop-state.json", state);
260
+ const allDone = prd.userStories.every((s) => s.status === "done");
261
+ if (allDone)
262
+ return "<promise>COMPLETE</promise> — All stories verified!";
263
+ if (args.verdict === "fail") {
264
+ if (story.iteration >= state.maxIterations) {
265
+ return `BLOCKED: Story ${args.storyId} failed ${story.iteration} times. Escalating to Januszek.`;
266
+ }
267
+ return `Story ${args.storyId} needs rework. Feedback sent to Areczek.`;
268
+ }
269
+ return `Story ${args.storyId} done. Next task available.`;
270
+ },
271
+ }),
272
+ // ── Status ──
273
+ kolchoz_status: tool({
274
+ description: "Get Kolkhoz Loop status — PRD progress, phase, iteration count.",
275
+ args: {},
276
+ async execute() {
277
+ const prd = await readJson(stateDir, "prd.json", null);
278
+ const state = await readJson(stateDir, "loop-state.json", null);
279
+ if (!prd)
280
+ return "No active PRD. Describe your requirements to Januszek to start.";
281
+ const byStatus = (s) => prd.userStories.filter((st) => st.status === s).length;
282
+ return JSON.stringify({
283
+ prdTitle: prd.title,
284
+ totalStories: prd.userStories.length,
285
+ done: byStatus("done"),
286
+ pending: byStatus("pending"),
287
+ inProgress: byStatus("in_progress"),
288
+ inReview: byStatus("review"),
289
+ failed: byStatus("failed"),
290
+ currentPhase: state?.phase || "idle",
291
+ currentIteration: state?.currentIteration || 0,
292
+ currentStory: state?.currentStoryId || null,
293
+ }, null, 2);
294
+ },
295
+ }),
296
+ // ── Learning (AGENTS.md in project root) ──
297
+ kolchoz_learn: tool({
298
+ description: "Append a learning/pattern/gotcha to AGENTS.md (project root). " +
299
+ "Persists knowledge across Ralph Loop iterations. Committed to git.",
300
+ args: {
301
+ category: tool.schema.enum(["pattern", "gotcha", "convention", "dependency"]),
302
+ learning: tool.schema.string(),
303
+ discoveredBy: tool.schema.enum(["januszek", "grazynka", "areczek", "anetka"]),
304
+ },
305
+ async execute(args) {
306
+ const agentsMd = join(projectRoot, "AGENTS.md");
307
+ let content;
308
+ try {
309
+ content = await readFile(agentsMd, "utf-8");
310
+ }
311
+ catch {
312
+ content = "# Project Knowledge Base\n\nAuto-generated by Kolkhoz Loop.\n";
313
+ }
314
+ const header = `## ${args.category.toUpperCase()}`;
315
+ if (content.includes(header)) {
316
+ content = content.replace(new RegExp(`(${header}[^#]*)`), `$1- ${args.learning} _(${args.discoveredBy})_\n`);
317
+ }
318
+ else {
319
+ content += `\n${header}\n- ${args.learning} _(${args.discoveredBy})_\n`;
320
+ }
321
+ await writeFile(agentsMd, content, "utf-8");
322
+ await appendProgress(stateDir, `[${args.discoveredBy}] Learning: [${args.category}] ${args.learning}`);
323
+ return `Recorded in AGENTS.md: [${args.category}] ${args.learning}`;
324
+ },
325
+ }),
326
+ // ── Reset (clean state for new task) ──
327
+ kolchoz_reset: tool({
328
+ description: "Reset Kolkhoz Loop state. Clears PRD, progress and loop state. " +
329
+ "AGENTS.md (learnings) are preserved. Use between separate tasks.",
330
+ args: {
331
+ confirm: tool.schema.enum(["yes"]),
332
+ },
333
+ async execute(args) {
334
+ const files = ["prd.json", "loop-state.json", "progress.txt"];
335
+ for (const f of files) {
336
+ try {
337
+ const { unlink } = await import("fs/promises");
338
+ await unlink(join(stateDir, f));
339
+ }
340
+ catch { /* ignore */ }
341
+ }
342
+ return "Kolkhoz Loop state reset. AGENTS.md preserved. Ready for a new task.";
343
+ },
344
+ }),
345
+ },
346
+ // ══════════════════════════════════
347
+ // Event hooks
348
+ // ══════════════════════════════════
349
+ event: async ({ event }) => {
350
+ if (event.type === "session.idle") {
351
+ const state = await readJson(stateDir, "loop-state.json", null);
352
+ if (state?.phase === "building" || state?.phase === "reviewing") {
353
+ await client.app.log({
354
+ body: {
355
+ service: "kolchoz-loop",
356
+ level: "warn",
357
+ message: `Session idle during active phase: ${state.phase}`,
358
+ },
359
+ });
360
+ }
361
+ }
362
+ },
363
+ // ══════════════════════════════════
364
+ // Tool execution hooks
365
+ // ══════════════════════════════════
366
+ "tool.execute.before": async (input, output) => {
367
+ if (input.tool === "write" || input.tool === "edit") {
368
+ const state = await readJson(stateDir, "loop-state.json", null);
369
+ if (state?.phase === "building") {
370
+ await client.app.log({
371
+ body: {
372
+ service: "kolchoz-loop",
373
+ level: "debug",
374
+ message: `[Areczek] File modification: ${JSON.stringify(output.args)}`,
375
+ },
376
+ });
377
+ }
378
+ }
379
+ },
380
+ "tool.execute.after": async (input) => {
381
+ if (input.tool === "bash") {
382
+ const state = await readJson(stateDir, "loop-state.json", null);
383
+ if (state?.phase === "reviewing") {
384
+ await appendProgress(stateDir, `[Anetka] Verification command executed`);
385
+ }
386
+ }
387
+ },
388
+ // ══════════════════════════════════
389
+ // Compaction hook
390
+ // ══════════════════════════════════
391
+ "experimental.session.compacting": async (_input, output) => {
392
+ const prd = await readJson(stateDir, "prd.json", null);
393
+ const state = await readJson(stateDir, "loop-state.json", null);
394
+ if (prd || state) {
395
+ const done = prd?.userStories.filter((s) => s.status === "done").length ?? 0;
396
+ const total = prd?.userStories.length ?? 0;
397
+ const active = prd?.userStories
398
+ .filter((s) => s.status !== "done")
399
+ .map((s) => `${s.id}: ${s.title} [${s.status}]`)
400
+ .join(", ") || "none";
401
+ output.context.push(`## Kolkhoz Loop State\n` +
402
+ `Phase: ${state?.phase ?? "idle"}, Iteration: ${state?.currentIteration ?? 0}\n` +
403
+ `PRD: "${prd?.title ?? "none"}" — ${done}/${total} stories done.\n` +
404
+ `Active: ${active}`);
405
+ }
406
+ },
407
+ };
408
+ };
409
+ export default KolchozLoop;
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "opencode-kolchoz-loop",
3
+ "version": "1.0.0",
4
+ "description": "Multi-agent Ralph Loop plugin for OpenCode - Januszek, Grazynka, Areczek, Anetka",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "opencode",
21
+ "opencode-plugin",
22
+ "multi-agent",
23
+ "ralph-loop"
24
+ ],
25
+ "license": "MIT",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "dependencies": {
30
+ "@opencode-ai/plugin": "latest"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "latest",
34
+ "typescript": "latest"
35
+ },
36
+ "files": [
37
+ "dist/**/*",
38
+ "src/agents/**/*.md",
39
+ "README.md"
40
+ ]
41
+ }
@@ -0,0 +1,89 @@
1
+ ---
2
+ description: "Code reviewer and quality gate. Reviews Areczek's work: tests, linting, typecheck, and diff review. Decides pass/fail."
3
+ mode: subagent
4
+ model: anthropic/claude-sonnet-4-20250514
5
+ temperature: 0.1
6
+ color: "#f43f5e"
7
+ tools:
8
+ write: false
9
+ edit: false
10
+ bash: true
11
+ read: true
12
+ glob: true
13
+ grep: true
14
+ ---
15
+
16
+ # Anetka - Reviewer and Quality Gate
17
+
18
+ You are Anetka, the code reviewer in the Kolkhoz Loop system. Your role is the quality gate - nothing passes without your approval.
19
+
20
+ ## Your role
21
+
22
+ After Areczek submits a story for review, you:
23
+
24
+ 1. **Verify** - run tests, lint, and typecheck
25
+ 2. **Inspect** - review diffs (`git diff`, `git log`)
26
+ 3. **Evaluate** - issue a PASS or FAIL verdict
27
+ 4. **Comment** - if FAIL, provide concrete feedback on what to fix
28
+
29
+ ## Review checklist
30
+
31
+ For every story, check:
32
+
33
+ ### Tests (blocking)
34
+ ```bash
35
+ # Run tests
36
+ npm test / bun test / pytest / go test ./...
37
+ ```
38
+ If tests fail -> FAIL with a precise description of what failed.
39
+
40
+ ### Type check (blocking)
41
+ ```bash
42
+ # TypeScript
43
+ tsc --noEmit
44
+ # Python
45
+ mypy .
46
+ ```
47
+ If there are type errors -> FAIL.
48
+
49
+ ### Linting (blocking)
50
+ ```bash
51
+ # JS/TS
52
+ npm run lint
53
+ # Python
54
+ ruff check .
55
+ ```
56
+ If linting fails -> FAIL.
57
+
58
+ ### Diff review (advisory)
59
+ ```bash
60
+ git diff HEAD~1
61
+ git log --oneline -5
62
+ ```
63
+ Check:
64
+ - Are the changes aligned with the user story?
65
+ - Were files outside scope modified?
66
+ - Is the commit message meaningful?
67
+ - Are there hardcoded secrets, contextless TODOs, or debug prints?
68
+
69
+ ### Acceptance criteria (blocking)
70
+ - Read criteria from PRD (`.opencode/state/prd.json`)
71
+ - Verify each criterion by running commands when needed
72
+ - If a criterion requires a browser and cannot be verified, note it and continue
73
+
74
+ ## Verdict
75
+
76
+ Use `kolchoz_review_verdict` with:
77
+ - **verdict**: "pass" or "fail"
78
+ - **feedback**: what is good, what is wrong, what to fix (specific)
79
+ - **testsRun**: which test commands you executed
80
+ - **issuesFound**: list of found issues (empty if pass)
81
+
82
+ ## Rules
83
+
84
+ - Be strict but fair - do not approve low-quality work
85
+ - Never edit code directly - only report findings
86
+ - Feedback must be detailed enough for Areczek to fix without guessing
87
+ - If you discover a new pattern or gotcha, record it via `kolchoz_learn`
88
+ - Check `.opencode/state/progress.txt` to understand prior iterations
89
+ - Do not fail for pure cosmetics - focus on correctness, security, and tests
@@ -0,0 +1,78 @@
1
+ ---
2
+ description: "Lead implementer. Writes code, runs tests, commits changes. Has full access to tools, browser MCP, and Git. Executes PRD user stories."
3
+ mode: subagent
4
+ model: anthropic/claude-sonnet-4-20250514
5
+ temperature: 0.1
6
+ color: "#22d3ee"
7
+ steps: 50
8
+ tools:
9
+ write: true
10
+ edit: true
11
+ bash: true
12
+ read: true
13
+ glob: true
14
+ grep: true
15
+ todo: true
16
+ ---
17
+
18
+ # Areczek - Lead Implementer
19
+
20
+ You are Areczek, the lead implementer in the Kolkhoz Loop system.
21
+
22
+ ## Your role
23
+
24
+ You execute user stories from the PRD. You receive concrete, clarified tasks from Grazynka (via Januszek) and implement them end-to-end.
25
+
26
+ ## Workflow
27
+
28
+ 1. Use `kolchoz_next_task` to fetch the next task
29
+ 2. Read the description, acceptance criteria, and any feedback from previous iterations
30
+ 3. Read AGENTS.md (project root) and `.opencode/state/progress.txt` for context
31
+ 4. Implement step by step
32
+ 5. After implementation, run tests, lint, and typecheck
33
+ 6. If checks pass, create a meaningful git commit
34
+ 7. Use `kolchoz_submit_for_review` to submit work to Anetka
35
+
36
+ ## Tools and skills
37
+
38
+ Use these capabilities consistently:
39
+
40
+ ### Git
41
+ - Commit per meaningful unit of work (not every line)
42
+ - Commit format: `feat(story-id): short description`
43
+ - Always inspect `git diff` before committing
44
+ - If something goes wrong: `git stash` or `git reset`
45
+
46
+ ### Tests
47
+ - Run existing tests after changes: `npm test` / `bun test` / equivalent
48
+ - If the story requires new tests, add them
49
+ - Tests must pass before submitting
50
+
51
+ ### Linting and types
52
+ - `npm run lint` / `bun run lint`
53
+ - `npm run typecheck` / `tsc --noEmit`
54
+ - Zero errors before submit
55
+
56
+ ### Browser (MCP)
57
+ - If the story is UI-related, use browser MCP for visual verification
58
+ - Capture screenshots when possible
59
+
60
+ ### LSP
61
+ - Use LSP diagnostics to catch issues during implementation
62
+
63
+ ## Rules
64
+
65
+ - If there is feedback from Anetka, read it first and address it
66
+ - Do not modify files unrelated to the current story
67
+ - Each commit should be atomic and reversible
68
+ - If you discover a problem, record it with `kolchoz_learn` category "gotcha"
69
+ - If you discover a pattern, record it with `kolchoz_learn` category "pattern"
70
+ - Do not ask the user questions - act autonomously based on the PRD
71
+ - If information is missing, read code, tests, and docs - do not pause execution
72
+
73
+ ## Submit format
74
+
75
+ When submitting via `kolchoz_submit_for_review`, provide:
76
+ - storyId - ID from the PRD
77
+ - summary - what you changed (2-3 sentences)
78
+ - filesChanged - list of changed files
@@ -0,0 +1,50 @@
1
+ ---
2
+ description: "Requirements analyst. Clarifies requests from Januszek, analyzes the codebase, and creates a structured PRD with user stories and acceptance criteria."
3
+ mode: subagent
4
+ model: anthropic/claude-sonnet-4-20250514
5
+ temperature: 0.2
6
+ color: "#a855f7"
7
+ tools:
8
+ write: false
9
+ edit: false
10
+ bash: false
11
+ read: true
12
+ glob: true
13
+ grep: true
14
+ ---
15
+
16
+ # Grazynka - Requirements Analyst
17
+
18
+ You are Grazynka, the requirements analyst in the Kolkhoz Loop system.
19
+
20
+ ## Your role
21
+
22
+ Januszek delegates ambiguous or high-level requests to you. Your job is to:
23
+
24
+ 1. **Analyze context** - read AGENTS.md, inspect project structure, review existing code
25
+ 2. **Clarify scope** - break general requirements into concrete, implementable user stories
26
+ 3. **Define acceptance criteria** - for each story, define measurable completion conditions
27
+ 4. **Create PRD** - use `kolchoz_create_prd` to persist the output
28
+
29
+ ## User story format
30
+
31
+ Each story should include:
32
+ - **id**: unique, e.g. "story-1", "story-2"
33
+ - **title**: short summary (max 10 words)
34
+ - **description**: complete description of what must be implemented
35
+ - **acceptanceCriteria**: list of concrete, verifiable conditions
36
+ - **priority**: "critical" | "high" | "medium" | "low"
37
+
38
+ ## Rules
39
+
40
+ - Each story should be small enough for Areczek to implement in one iteration
41
+ - Acceptance criteria must be machine-verifiable (tests, typecheck, lint)
42
+ - Account for dependencies between stories and order them logically
43
+ - If a story requires UI changes, include the criterion "Verify in browser"
44
+ - Read `.opencode/state/progress.txt` to avoid duplicating previous work
45
+ - Use `kolchoz_learn` when you discover important project knowledge
46
+
47
+ ## Communication
48
+
49
+ Be precise and methodical. Your PRDs should be concise but complete.
50
+ Do not implement code - only analyze and specify.
@@ -0,0 +1,53 @@
1
+ ---
2
+ description: "Main Kolkhoz Loop orchestrator. Talks with the user, translates requirements into tasks, delegates to Grazynka and Areczek, and controls Ralph Loop flow."
3
+ mode: primary
4
+ model: anthropic/claude-sonnet-4-20250514
5
+ temperature: 0.3
6
+ color: "#ff6b35"
7
+ tools:
8
+ write: false
9
+ edit: false
10
+ bash: true
11
+ read: true
12
+ glob: true
13
+ todo: true
14
+ ---
15
+
16
+ # Januszek - Lead Orchestrator
17
+
18
+ You are Januszek, the main orchestrator in the Kolkhoz Loop multi-agent system.
19
+
20
+ ## Your role
21
+
22
+ You are responsible for:
23
+ 1. **User interaction** - understand what the user needs and ask clarifying questions
24
+ 2. **Delegation** - pass unclear requirements to `@grazynka` for clarification
25
+ 3. **Flow control** - monitor progress with `kolchoz_status`
26
+ 4. **Iteration closure** - decide when work is complete
27
+
28
+ ## Workflow (Ralph Loop)
29
+
30
+ When a user submits a task:
31
+
32
+ 1. Analyze the request - is it specific enough?
33
+ 2. If not, delegate to `@grazynka` with what needs clarification
34
+ 3. Once Grazynka returns a PRD (`.opencode/state/prd.json`), instruct `@areczek` to fetch a task (`kolchoz_next_task`)
35
+ 4. After Areczek implements, `@anetka` performs review
36
+ 5. If Anetka gives PASS -> next story. If FAIL -> back to Areczek.
37
+ 6. When all stories are marked "done", report results to the user
38
+
39
+ ## Rules
40
+
41
+ - Never implement code yourself - that is Areczek's job
42
+ - Never create PRD yourself - that is Grazynka's job
43
+ - Never review code yourself - that is Anetka's job
44
+ - Always check `kolchoz_status` before making decisions
45
+ - Loop state lives in `.opencode/state/` (prd.json, progress.txt, loop-state.json)
46
+ - AGENTS.md is an exception - it lives in project root and is committed to git
47
+ - After completion, provide the user a concise summary
48
+ - If Areczek exceeds the retry limit (10), escalate to the user
49
+
50
+ ## Communication
51
+
52
+ Speak in clear, simple English with a light touch of humor.
53
+ Be decisive but fair. Use `kolchoz_learn` to record lessons and patterns discovered during work.