opencode-kolchoz-loop 1.0.5 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -122,19 +122,25 @@ The plugin registers 7 tools:
122
122
 
123
123
  ## Model configuration
124
124
 
125
- Agents use `claude-sonnet-4` by default. Override in `opencode.json`:
125
+ You can override agent models in `opencode.json`:
126
126
 
127
127
  ```json
128
128
  {
129
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" }
130
+ "januszek": { "model": "<provider>/<model-name>" },
131
+ "areczek": { "model": "<provider>/<model-name>" },
132
+ "anetka": { "model": "<provider>/<model-name>" }
133
133
  }
134
134
  }
135
135
  ```
136
136
 
137
- Recommendation: Anetka and Areczek should use different models. Cross-model review reduces shared blind spots.
137
+ ## Commit gate
138
+
139
+ Before Areczek can submit a story for review, `kolchoz_submit_for_review` validates Git:
140
+ - no uncommitted changes in the working tree
141
+ - latest commit message starts with `feat(<storyId>):`
142
+
143
+ If either check fails, submission is blocked until Areczek creates the correct commit.
138
144
 
139
145
  ## Compound engineering
140
146
 
@@ -144,6 +150,28 @@ The system builds knowledge in `AGENTS.md` automatically:
144
150
  2. Anetka discovers a gotcha -> `kolchoz_learn("gotcha", "...")`
145
151
  3. Future sessions read AGENTS.md -> better context -> better outcomes
146
152
 
153
+ ## Release automation
154
+
155
+ This repository includes GitHub Actions workflows for a release cycle:
156
+
157
+ 1. Run `Prepare Release Draft` manually from GitHub Actions on `main` or `master`.
158
+ 2. Choose `RELEASE_TYPE`: `major`, `minor`, or `patch`.
159
+ 3. Workflow bumps `package.json` version, commits it, creates a tag, and opens a draft release.
160
+ 4. Publish the draft release in GitHub Releases.
161
+ 5. Publishing the release triggers npm publishing from the release tag.
162
+
163
+ Workflow files:
164
+ - `.github/workflows/prepare-release.yml`
165
+ - `.github/workflows/publish-on-release.yml`
166
+
167
+ Required npm setup:
168
+ - Configure npm Trusted Publisher for this GitHub repository and workflow file `publish-on-release.yml`.
169
+ - Trusted publishing uses GitHub OIDC, so `NPM_TOKEN` is not required for package publishing.
170
+
171
+ Important:
172
+ - `Prepare Release Draft` guarantees matching `tag` and `package.json` version.
173
+ - If branch protection blocks direct pushes by Actions, allow `GITHUB_TOKEN` pushes for this workflow.
174
+
147
175
  ## License
148
176
 
149
177
  MIT
@@ -41,6 +41,9 @@ Use these capabilities consistently:
41
41
  - Commit format: `feat(story-id): short description`
42
42
  - Always inspect `git diff` before committing
43
43
  - If something goes wrong: `git stash` or `git reset`
44
+ - `kolchoz_submit_for_review` is hard-gated by Git checks:
45
+ - working tree must be clean (no pending changes)
46
+ - latest commit message must match current story: `feat(<storyId>): ...`
44
47
 
45
48
  ### Tests
46
49
  - Run existing tests after changes: `npm test` / `bun test` / equivalent
@@ -10,6 +10,7 @@ tools:
10
10
  read: true
11
11
  glob: true
12
12
  todo: true
13
+ question: true
13
14
  ---
14
15
 
15
16
  # Januszek - Lead Orchestrator
@@ -29,11 +30,12 @@ You are responsible for:
29
30
  When a user submits a task:
30
31
 
31
32
  1. Analyze the request - is it specific enough?
32
- 2. If not, delegate to `@grazynka` with what needs clarification
33
- 3. Once Grazynka returns a PRD (`.opencode/state/prd.json`), instruct `@areczek` to fetch a task (`kolchoz_next_task`)
34
- 4. After Areczek implements, `@anetka` performs review
35
- 5. If Anetka gives PASS -> next story. If FAIL -> back to Areczek.
36
- 6. When all stories are marked "done", report results to the user
33
+ 2. If requirements are unclear or ambiguous, use the `question` tool to ask the user clarifying questions **before** delegating to Grazynka. Ask about scope, priorities, constraints, and success criteria. Keep questions concise — group related ones together and offer predefined options where possible.
34
+ 3. Delegate to `@grazynka`, passing the user's task **together with all answers** gathered from the `question` tool
35
+ 4. Once Grazynka returns a PRD (`.opencode/state/prd.json`), instruct `@areczek` to fetch a task (`kolchoz_next_task`)
36
+ 5. After Areczek implements, `@anetka` performs review
37
+ 6. If Anetka gives PASS -> next story. If FAIL -> back to Areczek.
38
+ 7. When all stories are marked "done", report results to the user
37
39
 
38
40
  ## Rules
39
41
 
@@ -41,6 +41,9 @@ Use these capabilities consistently:
41
41
  - Commit format: `feat(story-id): short description`
42
42
  - Always inspect `git diff` before committing
43
43
  - If something goes wrong: `git stash` or `git reset`
44
+ - `kolchoz_submit_for_review` is hard-gated by Git checks:
45
+ - working tree must be clean (no pending changes)
46
+ - latest commit message must match current story: `feat(<storyId>): ...`
44
47
 
45
48
  ### Tests
46
49
  - Run existing tests after changes: `npm test` / `bun test` / equivalent
@@ -10,6 +10,7 @@ tools:
10
10
  read: true
11
11
  glob: true
12
12
  todo: true
13
+ question: true
13
14
  ---
14
15
 
15
16
  # Januszek - Lead Orchestrator
@@ -29,11 +30,12 @@ You are responsible for:
29
30
  When a user submits a task:
30
31
 
31
32
  1. Analyze the request - is it specific enough?
32
- 2. If not, delegate to `@grazynka` with what needs clarification
33
- 3. Once Grazynka returns a PRD (`.opencode/state/prd.json`), instruct `@areczek` to fetch a task (`kolchoz_next_task`)
34
- 4. After Areczek implements, `@anetka` performs review
35
- 5. If Anetka gives PASS -> next story. If FAIL -> back to Areczek.
36
- 6. When all stories are marked "done", report results to the user
33
+ 2. If requirements are unclear or ambiguous, use the `question` tool to ask the user clarifying questions **before** delegating to Grazynka. Ask about scope, priorities, constraints, and success criteria. Keep questions concise — group related ones together and offer predefined options where possible.
34
+ 3. Delegate to `@grazynka`, passing the user's task **together with all answers** gathered from the `question` tool
35
+ 4. Once Grazynka returns a PRD (`.opencode/state/prd.json`), instruct `@areczek` to fetch a task (`kolchoz_next_task`)
36
+ 5. After Areczek implements, `@anetka` performs review
37
+ 6. If Anetka gives PASS -> next story. If FAIL -> back to Areczek.
38
+ 7. When all stories are marked "done", report results to the user
37
39
 
38
40
  ## Rules
39
41
 
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@ import { tool } from "@opencode-ai/plugin/tool";
2
2
  import { readFile, writeFile, mkdir, readdir, copyFile, stat } from "fs/promises";
3
3
  import { join, dirname } from "path";
4
4
  import { fileURLToPath } from "url";
5
+ import { execFile } from "child_process";
6
+ import { promisify } from "util";
7
+ const execFileAsync = promisify(execFile);
5
8
  // ── Filesystem helpers ──
6
9
  async function ensureDir(dir) {
7
10
  await mkdir(dir, { recursive: true });
@@ -41,6 +44,30 @@ async function appendProgress(dir, message) {
41
44
  await writeFile(file, line, "utf-8");
42
45
  }
43
46
  }
47
+ async function runGit(projectRoot, args) {
48
+ const { stdout } = await execFileAsync("git", args, { cwd: projectRoot });
49
+ return stdout.trim();
50
+ }
51
+ async function validateStoryCommit(projectRoot, storyId) {
52
+ try {
53
+ await runGit(projectRoot, ["rev-parse", "--is-inside-work-tree"]);
54
+ }
55
+ catch {
56
+ return "ERROR: Git repository not found. Commit is required before review.";
57
+ }
58
+ const status = await runGit(projectRoot, ["status", "--porcelain"]);
59
+ if (status.length > 0) {
60
+ return (`ERROR: Uncommitted changes detected. Commit all changes for ${storyId} before review.\n` +
61
+ `Hint: git add -A && git commit -m "feat(${storyId}): short description"`);
62
+ }
63
+ const lastCommitMessage = await runGit(projectRoot, ["log", "-1", "--pretty=%s"]);
64
+ const requiredPrefix = `feat(${storyId}):`;
65
+ if (!lastCommitMessage.startsWith(requiredPrefix)) {
66
+ return (`ERROR: Last commit message must start with "${requiredPrefix}".\n` +
67
+ `Current: "${lastCommitMessage}"`);
68
+ }
69
+ return null;
70
+ }
44
71
  // ── Agent provisioning ──
45
72
  // Copies bundled .md agent files to .opencode/agents/
46
73
  // so OpenCode discovers them as real agents.
@@ -89,6 +116,16 @@ async function ensureGitignore(projectRoot) {
89
116
  // ── Main Plugin Export ──
90
117
  export const KolchozLoop = async ({ project, client, $, directory, worktree }) => {
91
118
  const projectRoot = worktree || directory;
119
+ if (!projectRoot || projectRoot === "/") {
120
+ await client.app.log({
121
+ body: {
122
+ service: "kolchoz-loop",
123
+ level: "warn",
124
+ message: `Kolchoz Loop: no valid project directory (worktree="${worktree}", directory="${directory}"). Plugin inactive.`,
125
+ },
126
+ });
127
+ return { tool: {}, event: async () => { } };
128
+ }
92
129
  const stateDir = join(projectRoot, ".opencode", "state");
93
130
  // ── Auto-provision on first run ──
94
131
  await ensureDir(stateDir);
@@ -121,16 +158,29 @@ export const KolchozLoop = async ({ project, client, $, directory, worktree }) =
121
158
  stories: tool.schema.string(), // JSON array
122
159
  },
123
160
  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
- }));
161
+ let parsed;
162
+ try {
163
+ const raw = JSON.parse(args.stories);
164
+ if (!Array.isArray(raw))
165
+ throw new Error("stories must be a JSON array");
166
+ parsed = raw;
167
+ }
168
+ catch (err) {
169
+ return `ERROR: Invalid stories JSON — ${err instanceof Error ? err.message : String(err)}`;
170
+ }
171
+ const stories = parsed.map((value, i) => {
172
+ const s = value;
173
+ return {
174
+ id: s.id || `story-${i + 1}`,
175
+ title: s.title || `Story ${i + 1}`,
176
+ description: s.description || "",
177
+ acceptanceCriteria: s.acceptanceCriteria || [],
178
+ priority: s.priority || "medium",
179
+ status: "pending",
180
+ assignedTo: "areczek",
181
+ iteration: 0,
182
+ };
183
+ });
134
184
  const prd = {
135
185
  title: args.title,
136
186
  createdBy: "grazynka",
@@ -202,6 +252,11 @@ export const KolchozLoop = async ({ project, client, $, directory, worktree }) =
202
252
  const story = prd.userStories.find((s) => s.id === args.storyId);
203
253
  if (!story)
204
254
  return `ERROR: Story ${args.storyId} not found.`;
255
+ const commitValidationError = await validateStoryCommit(projectRoot, args.storyId);
256
+ if (commitValidationError) {
257
+ await appendProgress(stateDir, `[Areczek] Submission blocked for ${args.storyId}: missing/invalid commit`);
258
+ return commitValidationError;
259
+ }
205
260
  story.status = "review";
206
261
  await writeJson(stateDir, "prd.json", prd);
207
262
  const state = await readJson(stateDir, "loop-state.json", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-kolchoz-loop",
3
- "version": "1.0.5",
3
+ "version": "1.3.1",
4
4
  "description": "Multi-agent Ralph Loop plugin for OpenCode - Januszek, Grazynka, Areczek, Anetka",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -27,11 +27,11 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@opencode-ai/plugin": "latest"
30
+ "@opencode-ai/plugin": "^1.2.7"
31
31
  },
32
32
  "devDependencies": {
33
- "@types/node": "latest",
34
- "typescript": "latest"
33
+ "@types/node": "^25.3.0",
34
+ "typescript": "^5.9.3"
35
35
  },
36
36
  "files": [
37
37
  "dist/**/*",
@@ -41,6 +41,9 @@ Use these capabilities consistently:
41
41
  - Commit format: `feat(story-id): short description`
42
42
  - Always inspect `git diff` before committing
43
43
  - If something goes wrong: `git stash` or `git reset`
44
+ - `kolchoz_submit_for_review` is hard-gated by Git checks:
45
+ - working tree must be clean (no pending changes)
46
+ - latest commit message must match current story: `feat(<storyId>): ...`
44
47
 
45
48
  ### Tests
46
49
  - Run existing tests after changes: `npm test` / `bun test` / equivalent
@@ -10,6 +10,7 @@ tools:
10
10
  read: true
11
11
  glob: true
12
12
  todo: true
13
+ question: true
13
14
  ---
14
15
 
15
16
  # Januszek - Lead Orchestrator
@@ -29,11 +30,12 @@ You are responsible for:
29
30
  When a user submits a task:
30
31
 
31
32
  1. Analyze the request - is it specific enough?
32
- 2. If not, delegate to `@grazynka` with what needs clarification
33
- 3. Once Grazynka returns a PRD (`.opencode/state/prd.json`), instruct `@areczek` to fetch a task (`kolchoz_next_task`)
34
- 4. After Areczek implements, `@anetka` performs review
35
- 5. If Anetka gives PASS -> next story. If FAIL -> back to Areczek.
36
- 6. When all stories are marked "done", report results to the user
33
+ 2. If requirements are unclear or ambiguous, use the `question` tool to ask the user clarifying questions **before** delegating to Grazynka. Ask about scope, priorities, constraints, and success criteria. Keep questions concise — group related ones together and offer predefined options where possible.
34
+ 3. Delegate to `@grazynka`, passing the user's task **together with all answers** gathered from the `question` tool
35
+ 4. Once Grazynka returns a PRD (`.opencode/state/prd.json`), instruct `@areczek` to fetch a task (`kolchoz_next_task`)
36
+ 5. After Areczek implements, `@anetka` performs review
37
+ 6. If Anetka gives PASS -> next story. If FAIL -> back to Areczek.
38
+ 7. When all stories are marked "done", report results to the user
37
39
 
38
40
  ## Rules
39
41