@nbardy/oompa 0.1.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 +197 -0
- package/agentnet/src/agentnet/agent.clj +293 -0
- package/agentnet/src/agentnet/cli.clj +436 -0
- package/agentnet/src/agentnet/core.clj +211 -0
- package/agentnet/src/agentnet/merge.clj +355 -0
- package/agentnet/src/agentnet/notes.clj +123 -0
- package/agentnet/src/agentnet/orchestrator.clj +367 -0
- package/agentnet/src/agentnet/review.clj +263 -0
- package/agentnet/src/agentnet/schema.clj +141 -0
- package/agentnet/src/agentnet/tasks.clj +198 -0
- package/agentnet/src/agentnet/worker.clj +427 -0
- package/agentnet/src/agentnet/worktree.clj +342 -0
- package/bin/oompa.js +37 -0
- package/config/prompts/cto.md +45 -0
- package/config/prompts/engineer.md +44 -0
- package/config/prompts/executor.md +32 -0
- package/config/prompts/planner.md +35 -0
- package/config/prompts/reviewer.md +49 -0
- package/config/prompts/worker.md +31 -0
- package/oompa.example.json +18 -0
- package/package.json +45 -0
- package/swarm.bb +18 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
(ns agentnet.worktree
|
|
2
|
+
"Git worktree management for isolated agent workspaces.
|
|
3
|
+
|
|
4
|
+
Each agent gets its own worktree (directory + branch) so they can
|
|
5
|
+
work in parallel without stepping on each other.
|
|
6
|
+
|
|
7
|
+
Lifecycle:
|
|
8
|
+
1. init-pool! - Create N worktrees at startup
|
|
9
|
+
2. acquire! - Claim a worktree for a task
|
|
10
|
+
3. (agent works) - Agent modifies files, commits
|
|
11
|
+
4. release! - Mark worktree available, optionally reset
|
|
12
|
+
5. cleanup-pool! - Remove all worktrees at shutdown
|
|
13
|
+
|
|
14
|
+
Design:
|
|
15
|
+
- Worktrees live in .workers/ directory
|
|
16
|
+
- Each worktree has its own branch: work/<worktree-id>
|
|
17
|
+
- State tracked in .workers/state.edn
|
|
18
|
+
- All git operations are idempotent"
|
|
19
|
+
(:require [agentnet.schema :as schema]
|
|
20
|
+
[babashka.process :as process]
|
|
21
|
+
[clojure.java.io :as io]
|
|
22
|
+
[clojure.edn :as edn]
|
|
23
|
+
[clojure.string :as str]))
|
|
24
|
+
|
|
25
|
+
;; =============================================================================
|
|
26
|
+
;; Function Specs (Hickey-style: data in, data out)
|
|
27
|
+
;; =============================================================================
|
|
28
|
+
|
|
29
|
+
;; init-pool! : OrchestratorConfig -> WorktreePool
|
|
30
|
+
;; Creates N worktrees in .workers/ directory, returns pool state
|
|
31
|
+
|
|
32
|
+
;; acquire! : WorktreePool, TaskId -> [WorktreePool, Worktree | nil]
|
|
33
|
+
;; Claims first available worktree for task, returns updated pool + worktree
|
|
34
|
+
|
|
35
|
+
;; release! : WorktreePool, WorktreeId, {:reset? bool} -> WorktreePool
|
|
36
|
+
;; Releases worktree back to pool, optionally resets to main
|
|
37
|
+
|
|
38
|
+
;; sync-to-main! : Worktree -> Worktree
|
|
39
|
+
;; Rebases worktree branch onto main, returns updated worktree
|
|
40
|
+
|
|
41
|
+
;; status : WorktreeId -> Worktree
|
|
42
|
+
;; Gets current state of worktree
|
|
43
|
+
|
|
44
|
+
;; cleanup-pool! : WorktreePool -> nil
|
|
45
|
+
;; Removes all worktrees and their branches
|
|
46
|
+
|
|
47
|
+
;; =============================================================================
|
|
48
|
+
;; Constants
|
|
49
|
+
;; =============================================================================
|
|
50
|
+
|
|
51
|
+
(def ^:const WORKERS_DIR ".workers")
|
|
52
|
+
(def ^:const STATE_FILE ".workers/state.edn")
|
|
53
|
+
(def ^:const BRANCH_PREFIX "work/")
|
|
54
|
+
|
|
55
|
+
;; =============================================================================
|
|
56
|
+
;; Git Helpers (pure where possible)
|
|
57
|
+
;; =============================================================================
|
|
58
|
+
|
|
59
|
+
(defn- git
|
|
60
|
+
"Run git command, return {:exit :out :err}"
|
|
61
|
+
[& args]
|
|
62
|
+
(let [cmd (into ["git"] args)
|
|
63
|
+
{:keys [exit out err]} (process/sh cmd {:out :string :err :string})]
|
|
64
|
+
{:exit exit
|
|
65
|
+
:out (str/trim (or out ""))
|
|
66
|
+
:err (str/trim (or err ""))}))
|
|
67
|
+
|
|
68
|
+
(defn- git!
|
|
69
|
+
"Run git command, throw on failure"
|
|
70
|
+
[& args]
|
|
71
|
+
(let [{:keys [exit out err]} (apply git args)]
|
|
72
|
+
(if (zero? exit)
|
|
73
|
+
out
|
|
74
|
+
(throw (ex-info (str "git failed: " err)
|
|
75
|
+
{:command args :exit exit :err err})))))
|
|
76
|
+
|
|
77
|
+
(defn- git-in
|
|
78
|
+
"Run git command in specific directory"
|
|
79
|
+
[dir & args]
|
|
80
|
+
(apply git "-C" dir args))
|
|
81
|
+
|
|
82
|
+
(defn- git-in!
|
|
83
|
+
"Run git command in specific directory, throw on failure"
|
|
84
|
+
[dir & args]
|
|
85
|
+
(apply git! "-C" dir args))
|
|
86
|
+
|
|
87
|
+
(defn- current-branch
|
|
88
|
+
"Get current branch name"
|
|
89
|
+
([] (git! "rev-parse" "--abbrev-ref" "HEAD"))
|
|
90
|
+
([dir] (git-in! dir "rev-parse" "--abbrev-ref" "HEAD")))
|
|
91
|
+
|
|
92
|
+
(defn- current-sha
|
|
93
|
+
"Get current commit SHA"
|
|
94
|
+
([] (git! "rev-parse" "HEAD"))
|
|
95
|
+
([dir] (git-in! dir "rev-parse" "HEAD")))
|
|
96
|
+
|
|
97
|
+
(defn- branch-exists?
|
|
98
|
+
"Check if branch exists"
|
|
99
|
+
[branch]
|
|
100
|
+
(zero? (:exit (git "rev-parse" "--verify" branch))))
|
|
101
|
+
|
|
102
|
+
(defn- worktree-exists?
|
|
103
|
+
"Check if worktree path exists"
|
|
104
|
+
[path]
|
|
105
|
+
(let [{:keys [out]} (git "worktree" "list" "--porcelain")]
|
|
106
|
+
(str/includes? out path)))
|
|
107
|
+
|
|
108
|
+
;; =============================================================================
|
|
109
|
+
;; State Persistence
|
|
110
|
+
;; =============================================================================
|
|
111
|
+
|
|
112
|
+
(defn- now-ms []
|
|
113
|
+
(System/currentTimeMillis))
|
|
114
|
+
|
|
115
|
+
(defn- ensure-workers-dir! []
|
|
116
|
+
(.mkdirs (io/file WORKERS_DIR)))
|
|
117
|
+
|
|
118
|
+
(defn- save-state! [pool]
|
|
119
|
+
(ensure-workers-dir!)
|
|
120
|
+
(spit STATE_FILE (pr-str pool))
|
|
121
|
+
pool)
|
|
122
|
+
|
|
123
|
+
(defn- load-state []
|
|
124
|
+
(let [f (io/file STATE_FILE)]
|
|
125
|
+
(when (.exists f)
|
|
126
|
+
(edn/read-string (slurp f)))))
|
|
127
|
+
|
|
128
|
+
;; =============================================================================
|
|
129
|
+
;; Worktree CRUD
|
|
130
|
+
;; =============================================================================
|
|
131
|
+
|
|
132
|
+
(defn- worktree-path [worktree-id]
|
|
133
|
+
(str WORKERS_DIR "/" worktree-id))
|
|
134
|
+
|
|
135
|
+
(defn- worktree-branch [worktree-id]
|
|
136
|
+
(str BRANCH_PREFIX worktree-id))
|
|
137
|
+
|
|
138
|
+
(defn- create-worktree!
|
|
139
|
+
"Create a single worktree with its branch"
|
|
140
|
+
[worktree-id]
|
|
141
|
+
(let [path (worktree-path worktree-id)
|
|
142
|
+
branch (worktree-branch worktree-id)]
|
|
143
|
+
(ensure-workers-dir!)
|
|
144
|
+
;; Remove stale worktree if exists
|
|
145
|
+
(when (worktree-exists? path)
|
|
146
|
+
(git! "worktree" "remove" "--force" path))
|
|
147
|
+
;; Remove stale branch if exists
|
|
148
|
+
(when (branch-exists? branch)
|
|
149
|
+
(git! "branch" "-D" branch))
|
|
150
|
+
;; Create fresh worktree with new branch from HEAD
|
|
151
|
+
(git! "worktree" "add" "-b" branch path "HEAD")
|
|
152
|
+
{:id worktree-id
|
|
153
|
+
:path path
|
|
154
|
+
:branch branch
|
|
155
|
+
:status :available
|
|
156
|
+
:current-task nil
|
|
157
|
+
:created-at (now-ms)
|
|
158
|
+
:last-used nil}))
|
|
159
|
+
|
|
160
|
+
(defn- remove-worktree!
|
|
161
|
+
"Remove a single worktree and its branch"
|
|
162
|
+
[worktree]
|
|
163
|
+
(let [{:keys [path branch]} worktree]
|
|
164
|
+
(when (worktree-exists? path)
|
|
165
|
+
(git "worktree" "remove" "--force" path))
|
|
166
|
+
(when (branch-exists? branch)
|
|
167
|
+
(git "branch" "-D" branch))
|
|
168
|
+
nil))
|
|
169
|
+
|
|
170
|
+
(defn- reset-worktree!
|
|
171
|
+
"Reset worktree to match main branch"
|
|
172
|
+
[worktree]
|
|
173
|
+
(let [{:keys [path branch]} worktree
|
|
174
|
+
main-branch (current-branch)]
|
|
175
|
+
;; Fetch latest main
|
|
176
|
+
(git-in! path "fetch" "." (str main-branch ":" main-branch))
|
|
177
|
+
;; Hard reset to main
|
|
178
|
+
(git-in! path "reset" "--hard" main-branch)
|
|
179
|
+
;; Clean untracked files
|
|
180
|
+
(git-in! path "clean" "-fd")
|
|
181
|
+
(assoc worktree
|
|
182
|
+
:status :available
|
|
183
|
+
:current-task nil
|
|
184
|
+
:last-used (now-ms))))
|
|
185
|
+
|
|
186
|
+
(defn- worktree-dirty?
|
|
187
|
+
"Check if worktree has uncommitted changes"
|
|
188
|
+
[worktree]
|
|
189
|
+
(let [{:keys [path]} worktree
|
|
190
|
+
{:keys [out]} (git-in path "status" "--porcelain")]
|
|
191
|
+
(not (str/blank? out))))
|
|
192
|
+
|
|
193
|
+
(defn- refresh-worktree-status
|
|
194
|
+
"Update worktree status based on git state"
|
|
195
|
+
[worktree]
|
|
196
|
+
(let [{:keys [path]} worktree]
|
|
197
|
+
(cond
|
|
198
|
+
(not (.exists (io/file path)))
|
|
199
|
+
(assoc worktree :status :stale)
|
|
200
|
+
|
|
201
|
+
(worktree-dirty? worktree)
|
|
202
|
+
(assoc worktree :status :dirty)
|
|
203
|
+
|
|
204
|
+
(:current-task worktree)
|
|
205
|
+
(assoc worktree :status :busy)
|
|
206
|
+
|
|
207
|
+
:else
|
|
208
|
+
(assoc worktree :status :available))))
|
|
209
|
+
|
|
210
|
+
;; =============================================================================
|
|
211
|
+
;; Pool Management
|
|
212
|
+
;; =============================================================================
|
|
213
|
+
|
|
214
|
+
(defn init-pool!
|
|
215
|
+
"Create a pool of N worktrees. Idempotent - reuses existing if clean."
|
|
216
|
+
[{:keys [worker-count worktree-root] :as config}]
|
|
217
|
+
(schema/assert-valid schema/valid-orchestrator-config? config "OrchestratorConfig")
|
|
218
|
+
(ensure-workers-dir!)
|
|
219
|
+
(let [existing (or (load-state) [])
|
|
220
|
+
existing-ids (set (map :id existing))
|
|
221
|
+
needed-ids (map #(str "worker-" %) (range worker-count))
|
|
222
|
+
;; Create missing worktrees
|
|
223
|
+
pool (mapv (fn [id]
|
|
224
|
+
(if (contains? existing-ids id)
|
|
225
|
+
(-> (first (filter #(= (:id %) id) existing))
|
|
226
|
+
refresh-worktree-status)
|
|
227
|
+
(create-worktree! id)))
|
|
228
|
+
needed-ids)]
|
|
229
|
+
(save-state! pool)))
|
|
230
|
+
|
|
231
|
+
(defn acquire!
|
|
232
|
+
"Claim an available worktree for a task. Returns [pool worktree] or [pool nil]."
|
|
233
|
+
[pool task-id]
|
|
234
|
+
(schema/assert-valid schema/non-blank-string? task-id "TaskId")
|
|
235
|
+
(if-let [idx (first (keep-indexed
|
|
236
|
+
(fn [i wt]
|
|
237
|
+
(when (= :available (:status wt)) i))
|
|
238
|
+
pool))]
|
|
239
|
+
(let [worktree (-> (get pool idx)
|
|
240
|
+
(assoc :status :busy
|
|
241
|
+
:current-task task-id
|
|
242
|
+
:last-used (now-ms)))
|
|
243
|
+
new-pool (assoc pool idx worktree)]
|
|
244
|
+
(save-state! new-pool)
|
|
245
|
+
[new-pool worktree])
|
|
246
|
+
[pool nil]))
|
|
247
|
+
|
|
248
|
+
(defn release!
|
|
249
|
+
"Release a worktree back to the pool."
|
|
250
|
+
[pool worktree-id {:keys [reset?] :or {reset? true}}]
|
|
251
|
+
(if-let [idx (first (keep-indexed
|
|
252
|
+
(fn [i wt]
|
|
253
|
+
(when (= (:id wt) worktree-id) i))
|
|
254
|
+
pool))]
|
|
255
|
+
(let [worktree (get pool idx)
|
|
256
|
+
updated (if reset?
|
|
257
|
+
(reset-worktree! worktree)
|
|
258
|
+
(assoc worktree
|
|
259
|
+
:status :available
|
|
260
|
+
:current-task nil))
|
|
261
|
+
new-pool (assoc pool idx updated)]
|
|
262
|
+
(save-state! new-pool))
|
|
263
|
+
pool))
|
|
264
|
+
|
|
265
|
+
(defn sync-to-main!
|
|
266
|
+
"Rebase worktree branch onto current main. Returns updated worktree."
|
|
267
|
+
[worktree]
|
|
268
|
+
(let [{:keys [path branch]} worktree
|
|
269
|
+
main (current-branch)]
|
|
270
|
+
;; Fetch main into worktree
|
|
271
|
+
(git-in! path "fetch" "origin" main)
|
|
272
|
+
;; Rebase onto main
|
|
273
|
+
(let [{:keys [exit err]} (git-in path "rebase" (str "origin/" main))]
|
|
274
|
+
(if (zero? exit)
|
|
275
|
+
(assoc worktree :status :available)
|
|
276
|
+
;; Abort failed rebase
|
|
277
|
+
(do
|
|
278
|
+
(git-in path "rebase" "--abort")
|
|
279
|
+
(throw (ex-info "Rebase failed" {:worktree worktree :error err})))))))
|
|
280
|
+
|
|
281
|
+
(defn status
|
|
282
|
+
"Get current status of a worktree by ID"
|
|
283
|
+
[pool worktree-id]
|
|
284
|
+
(when-let [worktree (first (filter #(= (:id %) worktree-id) pool))]
|
|
285
|
+
(refresh-worktree-status worktree)))
|
|
286
|
+
|
|
287
|
+
(defn cleanup-pool!
|
|
288
|
+
"Remove all worktrees and clean up state"
|
|
289
|
+
[pool]
|
|
290
|
+
(doseq [worktree pool]
|
|
291
|
+
(remove-worktree! worktree))
|
|
292
|
+
(when (.exists (io/file STATE_FILE))
|
|
293
|
+
(io/delete-file STATE_FILE))
|
|
294
|
+
nil)
|
|
295
|
+
|
|
296
|
+
(defn list-worktrees
|
|
297
|
+
"List all worktrees with current status"
|
|
298
|
+
[pool]
|
|
299
|
+
(mapv refresh-worktree-status pool))
|
|
300
|
+
|
|
301
|
+
;; =============================================================================
|
|
302
|
+
;; Convenience Functions
|
|
303
|
+
;; =============================================================================
|
|
304
|
+
|
|
305
|
+
(defn with-worktree
|
|
306
|
+
"Execute function with an acquired worktree, auto-release on completion.
|
|
307
|
+
|
|
308
|
+
Usage:
|
|
309
|
+
(with-worktree pool task-id
|
|
310
|
+
(fn [worktree]
|
|
311
|
+
(do-work-in (:path worktree))))"
|
|
312
|
+
[pool task-id f]
|
|
313
|
+
(let [[pool' worktree] (acquire! pool task-id)]
|
|
314
|
+
(if worktree
|
|
315
|
+
(try
|
|
316
|
+
(let [result (f worktree)]
|
|
317
|
+
[(release! pool' (:id worktree) {:reset? false}) result])
|
|
318
|
+
(catch Exception e
|
|
319
|
+
[(release! pool' (:id worktree) {:reset? true}) nil]))
|
|
320
|
+
(throw (ex-info "No available worktrees" {:pool pool :task-id task-id})))))
|
|
321
|
+
|
|
322
|
+
(defn commit-in-worktree!
|
|
323
|
+
"Create a commit in the worktree"
|
|
324
|
+
[worktree message]
|
|
325
|
+
(let [{:keys [path]} worktree]
|
|
326
|
+
(git-in! path "add" "-A")
|
|
327
|
+
(let [{:keys [exit]} (git-in path "diff" "--cached" "--quiet")]
|
|
328
|
+
(when-not (zero? exit) ; there are staged changes
|
|
329
|
+
(git-in! path "commit" "-m" message)))))
|
|
330
|
+
|
|
331
|
+
(defn diff-in-worktree
|
|
332
|
+
"Get the diff of uncommitted changes in worktree"
|
|
333
|
+
[worktree]
|
|
334
|
+
(let [{:keys [path]} worktree]
|
|
335
|
+
(:out (git-in path "diff"))))
|
|
336
|
+
|
|
337
|
+
(defn log-in-worktree
|
|
338
|
+
"Get commit log for worktree branch (since divergence from main)"
|
|
339
|
+
[worktree]
|
|
340
|
+
(let [{:keys [path branch]} worktree
|
|
341
|
+
main (current-branch)]
|
|
342
|
+
(:out (git-in path "log" "--oneline" (str main ".." branch)))))
|
package/bin/oompa.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { spawnSync } = require("node:child_process");
|
|
7
|
+
|
|
8
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
9
|
+
const swarmScript = path.join(packageRoot, "swarm.bb");
|
|
10
|
+
const classpath = path.join(packageRoot, "agentnet", "src");
|
|
11
|
+
const argv = process.argv.slice(2);
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(swarmScript) || !fs.existsSync(classpath)) {
|
|
14
|
+
console.error("oompa package installation is incomplete.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const result = spawnSync("bb", ["--classpath", classpath, swarmScript, ...argv], {
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
cwd: process.cwd(),
|
|
21
|
+
env: process.env
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (result.error) {
|
|
25
|
+
if (result.error.code === "ENOENT") {
|
|
26
|
+
console.error("Babashka (bb) is required. Install: https://github.com/babashka/babashka");
|
|
27
|
+
} else {
|
|
28
|
+
console.error(`Failed to run bb: ${result.error.message}`);
|
|
29
|
+
}
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof result.status === "number") {
|
|
34
|
+
process.exit(result.status);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
process.exit(1);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{context_header}
|
|
2
|
+
|
|
3
|
+
<rules>
|
|
4
|
+
<prompt_metadata>
|
|
5
|
+
Type: Codebase Consolidation & Review
|
|
6
|
+
Purpose: Unify style, simplify interfaces, clarify data model invariants
|
|
7
|
+
Paradigm: Metamorphic Refactoring with Semantic Invariants
|
|
8
|
+
Constraints: Patch-only output; allowed paths = {targets}; sandboxed FS
|
|
9
|
+
Objective: {mode_hint} with emphasis on hotspots last {recent_sec}s
|
|
10
|
+
</prompt_metadata>
|
|
11
|
+
|
|
12
|
+
<think privacy="internal">?(trace types, data flows, invariants) → !(minimal behavior-preserving refactor)</think>
|
|
13
|
+
|
|
14
|
+
<context>
|
|
15
|
+
Queue:
|
|
16
|
+
{queue_md}
|
|
17
|
+
|
|
18
|
+
Hotspots:
|
|
19
|
+
{recent_files_md}
|
|
20
|
+
|
|
21
|
+
Diffstat:
|
|
22
|
+
{diffstat_md}
|
|
23
|
+
|
|
24
|
+
Next work:
|
|
25
|
+
{next_work_md}
|
|
26
|
+
</context>
|
|
27
|
+
|
|
28
|
+
<answer_operator>
|
|
29
|
+
If {mode_hint} == "review":
|
|
30
|
+
- Inspect `.agent/patch.diff` (or repo deltas if empty); decide approve vs change.
|
|
31
|
+
- If acceptable, create empty `.agent/{approval_token}`.
|
|
32
|
+
- Else, write concise bullets to `.agent/review.txt` (no chain-of-thought).
|
|
33
|
+
Else:
|
|
34
|
+
- Identify the highest leverage consolidation from context.
|
|
35
|
+
- Emit unified diff to `.agent/patch.diff` (no prose).
|
|
36
|
+
- Emit `.agent/meta.json` with touched files and summary.
|
|
37
|
+
</answer_operator>
|
|
38
|
+
|
|
39
|
+
<checklist>
|
|
40
|
+
- Respect allowed paths only: {targets}
|
|
41
|
+
- Prefer deletions/simplifications over additions.
|
|
42
|
+
- Preserve behaviour unless requirement allows otherwise.
|
|
43
|
+
- Update docs/tests only if the change requires it.
|
|
44
|
+
</checklist>
|
|
45
|
+
</rules>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{context_header}
|
|
2
|
+
|
|
3
|
+
<rules>
|
|
4
|
+
<prompt_metadata>
|
|
5
|
+
Type: Implementation & Local Refactor
|
|
6
|
+
Purpose: Deliver focused patches; reduce local entropy
|
|
7
|
+
Paradigm: Small, Composable Transformations
|
|
8
|
+
Constraints: Patch-only; allowed paths = {targets}
|
|
9
|
+
Objective: complete assigned task or propose safe maintenance
|
|
10
|
+
</prompt_metadata>
|
|
11
|
+
|
|
12
|
+
<think privacy="internal">?(spec → invariants → tests → diff) → !(smallest-correct-change)</think>
|
|
13
|
+
|
|
14
|
+
<context>
|
|
15
|
+
Queue:
|
|
16
|
+
{queue_md}
|
|
17
|
+
|
|
18
|
+
Hotspots:
|
|
19
|
+
{recent_files_md}
|
|
20
|
+
|
|
21
|
+
Diffstat:
|
|
22
|
+
{diffstat_md}
|
|
23
|
+
|
|
24
|
+
Next work:
|
|
25
|
+
{next_work_md}
|
|
26
|
+
</context>
|
|
27
|
+
|
|
28
|
+
<answer_operator>
|
|
29
|
+
If task provided:
|
|
30
|
+
- Implement the requirement with minimal surface area.
|
|
31
|
+
- Update tests/docs only if necessary for the change.
|
|
32
|
+
- Emit `.agent/patch.diff` and `.agent/meta.json`.
|
|
33
|
+
Else:
|
|
34
|
+
- Choose one maintenance action tied to recent hotspots.
|
|
35
|
+
- Keep the diff minimal; respect policy constraints.
|
|
36
|
+
</answer_operator>
|
|
37
|
+
|
|
38
|
+
<checklist>
|
|
39
|
+
- Allowed paths only: {targets}
|
|
40
|
+
- No direct file edits outside `.agent/*`.
|
|
41
|
+
- Capture patch via unified diff; no prose in diff.
|
|
42
|
+
- Ensure new names/types align with existing style.
|
|
43
|
+
</checklist>
|
|
44
|
+
</rules>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
Goal: Execute tasks from tasks/pending/
|
|
2
|
+
Process: Claim task (mv pending/ → current/), execute, complete (mv current/ → complete/)
|
|
3
|
+
Method: Isolate changes to your worktree, commit and merge when complete
|
|
4
|
+
|
|
5
|
+
## Your Role
|
|
6
|
+
|
|
7
|
+
You are an executor. You:
|
|
8
|
+
- Claim and execute tasks
|
|
9
|
+
- Do NOT create new tasks
|
|
10
|
+
- Do NOT plan or design
|
|
11
|
+
- Just execute what's assigned
|
|
12
|
+
|
|
13
|
+
## Workflow
|
|
14
|
+
|
|
15
|
+
1. Pick a task from tasks/pending/
|
|
16
|
+
2. Move it to tasks/current/
|
|
17
|
+
3. Execute the task in your worktree
|
|
18
|
+
4. Commit your changes
|
|
19
|
+
5. Move task to tasks/complete/
|
|
20
|
+
6. Merge your worktree to main
|
|
21
|
+
|
|
22
|
+
## Guidelines
|
|
23
|
+
|
|
24
|
+
- Focus on one task at a time
|
|
25
|
+
- Follow the task description exactly
|
|
26
|
+
- If a task is unclear, skip it (leave in pending/)
|
|
27
|
+
- Keep changes minimal and focused
|
|
28
|
+
|
|
29
|
+
## Exit Condition
|
|
30
|
+
|
|
31
|
+
When tasks/pending/ is empty:
|
|
32
|
+
Output: __DONE__
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
Goal: Break spec.md into executable tasks
|
|
2
|
+
Process: Create tasks in tasks/pending/*.edn
|
|
3
|
+
Method: Do NOT write code. Only create and refine tasks.
|
|
4
|
+
|
|
5
|
+
## Your Role
|
|
6
|
+
|
|
7
|
+
You are a planner. You:
|
|
8
|
+
- Read spec.md to understand the goal
|
|
9
|
+
- Create small, focused tasks in tasks/pending/
|
|
10
|
+
- Monitor progress by checking tasks/complete/
|
|
11
|
+
- Refine or split tasks that are too large
|
|
12
|
+
- Do NOT execute tasks yourself
|
|
13
|
+
|
|
14
|
+
## Task Creation
|
|
15
|
+
|
|
16
|
+
Write .edn files to tasks/pending/:
|
|
17
|
+
```edn
|
|
18
|
+
{:id "task-001"
|
|
19
|
+
:summary "Add user authentication"
|
|
20
|
+
:description "Implement JWT-based auth for the API"
|
|
21
|
+
:files ["src/auth.py" "tests/test_auth.py"]
|
|
22
|
+
:acceptance ["Login endpoint returns token" "Tests pass"]}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Guidelines
|
|
26
|
+
|
|
27
|
+
- Keep tasks small (1-2 files max)
|
|
28
|
+
- Be specific about acceptance criteria
|
|
29
|
+
- Consider dependencies between tasks
|
|
30
|
+
- Check what's already complete before creating duplicates
|
|
31
|
+
|
|
32
|
+
## Exit Condition
|
|
33
|
+
|
|
34
|
+
When spec.md is fully covered by tasks and all are complete:
|
|
35
|
+
Output: __DONE__
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Goal: Review changes for correctness before merge
|
|
2
|
+
Process: Check diff against task requirements
|
|
3
|
+
Method: Approve, request changes, or reject
|
|
4
|
+
|
|
5
|
+
## Your Role
|
|
6
|
+
|
|
7
|
+
You are a reviewer. You:
|
|
8
|
+
- Check code changes for correctness
|
|
9
|
+
- Verify changes match the intended task
|
|
10
|
+
- Look for obvious bugs or issues
|
|
11
|
+
- Do NOT write code yourself
|
|
12
|
+
|
|
13
|
+
## Review Criteria
|
|
14
|
+
|
|
15
|
+
1. **Correctness**: Does the code work as intended?
|
|
16
|
+
2. **Completeness**: Does it fully address the task?
|
|
17
|
+
3. **Quality**: No obvious bugs, security issues, or regressions?
|
|
18
|
+
4. **Focus**: Changes are minimal and on-target?
|
|
19
|
+
|
|
20
|
+
## Response Format
|
|
21
|
+
|
|
22
|
+
Respond with ONE of:
|
|
23
|
+
|
|
24
|
+
**APPROVED** - Changes are good to merge
|
|
25
|
+
```
|
|
26
|
+
APPROVED
|
|
27
|
+
- Clean implementation
|
|
28
|
+
- Tests pass
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**NEEDS_CHANGES** - Fixable issues found
|
|
32
|
+
```
|
|
33
|
+
NEEDS_CHANGES
|
|
34
|
+
- Issue 1: description
|
|
35
|
+
- Issue 2: description
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**REJECTED** - Fundamentally wrong approach
|
|
39
|
+
```
|
|
40
|
+
REJECTED
|
|
41
|
+
- Reason: wrong approach, should use X instead
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Guidelines
|
|
45
|
+
|
|
46
|
+
- Be concise and specific
|
|
47
|
+
- List actionable items for NEEDS_CHANGES
|
|
48
|
+
- Err on the side of APPROVED for minor style issues
|
|
49
|
+
- REJECTED is for wrong direction, not small bugs
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Goal: Match spec.md
|
|
2
|
+
Process: Create/claim tasks in tasks/{pending,current,complete}/*.edn
|
|
3
|
+
Method: Isolate changes to your worktree, commit and merge when complete
|
|
4
|
+
|
|
5
|
+
## Task Management
|
|
6
|
+
|
|
7
|
+
Check tasks/pending/ for available work:
|
|
8
|
+
- To claim: move file from pending/ to current/
|
|
9
|
+
- To complete: move file from current/ to complete/
|
|
10
|
+
- To create: write new .edn file to pending/
|
|
11
|
+
|
|
12
|
+
Task file format:
|
|
13
|
+
```edn
|
|
14
|
+
{:id "task-001"
|
|
15
|
+
:summary "Short description"
|
|
16
|
+
:description "Detailed description"
|
|
17
|
+
:files ["src/foo.py"]}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Workflow
|
|
21
|
+
|
|
22
|
+
1. Check if tasks/pending/ has tasks
|
|
23
|
+
2. If yes: claim one (mv to current/), execute it, complete it (mv to complete/)
|
|
24
|
+
3. If no: check spec.md for gaps, create new tasks if needed
|
|
25
|
+
4. Commit changes to your worktree
|
|
26
|
+
5. Merge your worktree branch to main
|
|
27
|
+
|
|
28
|
+
## Exit Condition
|
|
29
|
+
|
|
30
|
+
When spec.md is fully satisfied and no more tasks are needed:
|
|
31
|
+
Output: __DONE__
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"review_model": "codex:codex-5.2",
|
|
3
|
+
|
|
4
|
+
"workers": [
|
|
5
|
+
{
|
|
6
|
+
"model": "claude:opus-4.5",
|
|
7
|
+
"iterations": 5,
|
|
8
|
+
"count": 1,
|
|
9
|
+
"prompt": "config/prompts/planner.md"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"model": "codex:codex-5.2-mini",
|
|
13
|
+
"iterations": 10,
|
|
14
|
+
"count": 3,
|
|
15
|
+
"prompt": "config/prompts/executor.md"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nbardy/oompa",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Git-worktree multi-agent swarm orchestrator for Codex and Claude",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"oompa": "bin/oompa.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"swarm.bb",
|
|
13
|
+
"agentnet/src/",
|
|
14
|
+
"config/prompts/",
|
|
15
|
+
"oompa.example.json",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"check": "node bin/oompa.js check",
|
|
20
|
+
"help": "node bin/oompa.js help",
|
|
21
|
+
"pack:dry": "npm pack --dry-run"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"agents",
|
|
28
|
+
"swarm",
|
|
29
|
+
"worktree",
|
|
30
|
+
"codex",
|
|
31
|
+
"claude",
|
|
32
|
+
"babashka"
|
|
33
|
+
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/nbardy/oompa.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/nbardy/oompa/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/nbardy/oompa#readme",
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
}
|
|
45
|
+
}
|