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