@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,141 @@
|
|
|
1
|
+
(ns agentnet.schema
|
|
2
|
+
"Data schemas for AgentNet using simple predicates.
|
|
3
|
+
|
|
4
|
+
Design Philosophy (Hickey-style):
|
|
5
|
+
- Data is the interface
|
|
6
|
+
- Schemas are documentation
|
|
7
|
+
- Validate at boundaries, trust inside
|
|
8
|
+
- Prefer maps over positional args
|
|
9
|
+
|
|
10
|
+
Note: Using simple predicates instead of Malli to avoid
|
|
11
|
+
external dependencies (Malli needs Java for deps.clj)."
|
|
12
|
+
(:require [clojure.string :as str]))
|
|
13
|
+
|
|
14
|
+
;; =============================================================================
|
|
15
|
+
;; Primitive Validators
|
|
16
|
+
;; =============================================================================
|
|
17
|
+
|
|
18
|
+
(defn non-blank-string? [x]
|
|
19
|
+
(and (string? x) (not (str/blank? x))))
|
|
20
|
+
|
|
21
|
+
(defn pos-int? [x]
|
|
22
|
+
(and (int? x) (pos? x)))
|
|
23
|
+
|
|
24
|
+
(defn nat-int? [x]
|
|
25
|
+
(and (int? x) (>= x 0)))
|
|
26
|
+
|
|
27
|
+
(defn timestamp? [x]
|
|
28
|
+
(nat-int? x))
|
|
29
|
+
|
|
30
|
+
(defn file-path? [x]
|
|
31
|
+
(non-blank-string? x))
|
|
32
|
+
|
|
33
|
+
(defn git-branch? [x]
|
|
34
|
+
(non-blank-string? x))
|
|
35
|
+
|
|
36
|
+
(defn git-sha? [x]
|
|
37
|
+
(and (string? x) (re-matches #"^[a-f0-9]{7,40}$" x)))
|
|
38
|
+
|
|
39
|
+
;; =============================================================================
|
|
40
|
+
;; Enum Validators
|
|
41
|
+
;; =============================================================================
|
|
42
|
+
|
|
43
|
+
(def agent-types #{:codex :claude})
|
|
44
|
+
(def agent-roles #{:proposer :reviewer :cto})
|
|
45
|
+
(def task-statuses #{:pending :in-progress :review :approved :merged :failed :blocked})
|
|
46
|
+
(def worktree-statuses #{:available :busy :dirty :stale})
|
|
47
|
+
(def review-verdicts #{:approved :needs-changes :rejected})
|
|
48
|
+
(def merge-strategies #{:fast-forward :no-ff :squash :rebase})
|
|
49
|
+
(def conflict-resolutions #{:ours :theirs :manual :abort})
|
|
50
|
+
|
|
51
|
+
(defn agent-type? [x] (contains? agent-types x))
|
|
52
|
+
(defn agent-role? [x] (contains? agent-roles x))
|
|
53
|
+
(defn task-status? [x] (contains? task-statuses x))
|
|
54
|
+
(defn worktree-status? [x] (contains? worktree-statuses x))
|
|
55
|
+
(defn review-verdict? [x] (contains? review-verdicts x))
|
|
56
|
+
(defn merge-strategy? [x] (contains? merge-strategies x))
|
|
57
|
+
(defn conflict-resolution? [x] (contains? conflict-resolutions x))
|
|
58
|
+
|
|
59
|
+
;; =============================================================================
|
|
60
|
+
;; Map Validators
|
|
61
|
+
;; =============================================================================
|
|
62
|
+
|
|
63
|
+
(defn has-keys? [m required-keys]
|
|
64
|
+
(every? #(contains? m %) required-keys))
|
|
65
|
+
|
|
66
|
+
(defn valid-task? [x]
|
|
67
|
+
(and (map? x)
|
|
68
|
+
(has-keys? x [:id :summary])
|
|
69
|
+
(non-blank-string? (:id x))
|
|
70
|
+
(non-blank-string? (:summary x))))
|
|
71
|
+
|
|
72
|
+
(defn valid-worktree? [x]
|
|
73
|
+
(and (map? x)
|
|
74
|
+
(has-keys? x [:id :path :branch :status])
|
|
75
|
+
(non-blank-string? (:id x))
|
|
76
|
+
(file-path? (:path x))
|
|
77
|
+
(git-branch? (:branch x))
|
|
78
|
+
(worktree-status? (:status x))))
|
|
79
|
+
|
|
80
|
+
(defn valid-agent-config? [x]
|
|
81
|
+
(and (map? x)
|
|
82
|
+
(has-keys? x [:type])
|
|
83
|
+
(agent-type? (:type x))))
|
|
84
|
+
|
|
85
|
+
(defn valid-review-feedback? [x]
|
|
86
|
+
(and (map? x)
|
|
87
|
+
(has-keys? x [:verdict])
|
|
88
|
+
(review-verdict? (:verdict x))))
|
|
89
|
+
|
|
90
|
+
(defn valid-merge-result? [x]
|
|
91
|
+
(and (map? x)
|
|
92
|
+
(has-keys? x [:status :source-branch :target-branch])
|
|
93
|
+
(contains? #{:merged :conflict :failed :skipped} (:status x))))
|
|
94
|
+
|
|
95
|
+
(defn valid-orchestrator-config? [x]
|
|
96
|
+
(and (map? x)
|
|
97
|
+
(has-keys? x [:worker-count :harness])
|
|
98
|
+
(pos-int? (:worker-count x))
|
|
99
|
+
(agent-type? (:harness x))))
|
|
100
|
+
|
|
101
|
+
;; =============================================================================
|
|
102
|
+
;; Validation Helpers
|
|
103
|
+
;; =============================================================================
|
|
104
|
+
|
|
105
|
+
(defn validate
|
|
106
|
+
"Validate data against a predicate, return [valid? errors]"
|
|
107
|
+
[pred data context]
|
|
108
|
+
(if (pred data)
|
|
109
|
+
[true nil]
|
|
110
|
+
[false {:context context :data data :error "Validation failed"}]))
|
|
111
|
+
|
|
112
|
+
(defn assert-valid
|
|
113
|
+
"Throw if data doesn't match predicate"
|
|
114
|
+
[pred data context]
|
|
115
|
+
(when-not (pred data)
|
|
116
|
+
(throw (ex-info (str "Invalid " context)
|
|
117
|
+
{:context context
|
|
118
|
+
:data data
|
|
119
|
+
:type (type data)}))))
|
|
120
|
+
|
|
121
|
+
;; =============================================================================
|
|
122
|
+
;; Schema Reference (for documentation)
|
|
123
|
+
;; =============================================================================
|
|
124
|
+
|
|
125
|
+
;; TaskId <- non-blank-string?
|
|
126
|
+
;; AgentType <- agent-type? (:codex, :claude)
|
|
127
|
+
;; AgentRole <- agent-role? (:proposer, :reviewer, :cto)
|
|
128
|
+
;; TaskStatus <- task-status?
|
|
129
|
+
;; WorktreeStatus <- worktree-status?
|
|
130
|
+
;; ReviewVerdict <- review-verdict?
|
|
131
|
+
;; MergeStrategy <- merge-strategy?
|
|
132
|
+
;; ConflictResolution <- conflict-resolution?
|
|
133
|
+
;; GitBranch <- git-branch?
|
|
134
|
+
;; GitSha <- git-sha?
|
|
135
|
+
;;
|
|
136
|
+
;; Task <- {:id string, :summary string, :targets [string], ...}
|
|
137
|
+
;; Worktree <- {:id string, :path string, :branch string, :status keyword}
|
|
138
|
+
;; AgentConfig <- {:type :codex|:claude, :model string, :sandbox keyword}
|
|
139
|
+
;; ReviewFeedback <- {:verdict :approved|:needs-changes|:rejected, :comments [string]}
|
|
140
|
+
;; MergeResult <- {:status :merged|:conflict|:failed, :source-branch string, ...}
|
|
141
|
+
;; OrchestratorConfig <- {:worker-count int, :harness :codex|:claude, :model string, :dry-run bool}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
(ns agentnet.tasks
|
|
2
|
+
"Folder-based task management.
|
|
3
|
+
|
|
4
|
+
Tasks live in tasks/{pending,current,complete}/*.edn
|
|
5
|
+
|
|
6
|
+
Workers:
|
|
7
|
+
- Claim tasks: mv pending/foo.edn → current/foo.edn
|
|
8
|
+
- Complete tasks: mv current/foo.edn → complete/foo.edn
|
|
9
|
+
- Create tasks: write new .edn to pending/
|
|
10
|
+
|
|
11
|
+
Task format:
|
|
12
|
+
{:id \"task-001\"
|
|
13
|
+
:summary \"Add authentication\"
|
|
14
|
+
:description \"Implement JWT auth...\"
|
|
15
|
+
:files [\"src/auth.py\"]
|
|
16
|
+
:acceptance [\"Login works\" \"Tests pass\"]}"
|
|
17
|
+
(:require [clojure.edn :as edn]
|
|
18
|
+
[clojure.java.io :as io]
|
|
19
|
+
[clojure.string :as str]))
|
|
20
|
+
|
|
21
|
+
;; =============================================================================
|
|
22
|
+
;; Paths
|
|
23
|
+
;; =============================================================================
|
|
24
|
+
|
|
25
|
+
(def ^:const TASKS_ROOT "tasks")
|
|
26
|
+
(def ^:const PENDING_DIR (str TASKS_ROOT "/pending"))
|
|
27
|
+
(def ^:const CURRENT_DIR (str TASKS_ROOT "/current"))
|
|
28
|
+
(def ^:const COMPLETE_DIR (str TASKS_ROOT "/complete"))
|
|
29
|
+
|
|
30
|
+
(defn ensure-dirs!
|
|
31
|
+
"Create task directories if they don't exist"
|
|
32
|
+
[]
|
|
33
|
+
(doseq [dir [PENDING_DIR CURRENT_DIR COMPLETE_DIR]]
|
|
34
|
+
(.mkdirs (io/file dir))))
|
|
35
|
+
|
|
36
|
+
;; =============================================================================
|
|
37
|
+
;; Task I/O
|
|
38
|
+
;; =============================================================================
|
|
39
|
+
|
|
40
|
+
(defn- read-task-file
|
|
41
|
+
"Read a single task .edn file"
|
|
42
|
+
[^java.io.File f]
|
|
43
|
+
(when (.exists f)
|
|
44
|
+
(try
|
|
45
|
+
(with-open [r (java.io.PushbackReader. (io/reader f))]
|
|
46
|
+
(let [task (edn/read {:eof nil} r)]
|
|
47
|
+
(assoc task :_file (.getPath f))))
|
|
48
|
+
(catch Exception e
|
|
49
|
+
(println (format "[warn] Failed to read task %s: %s" (.getName f) (.getMessage e)))
|
|
50
|
+
nil))))
|
|
51
|
+
|
|
52
|
+
(defn- list-task-files
|
|
53
|
+
"List all .edn files in a directory"
|
|
54
|
+
[dir]
|
|
55
|
+
(let [d (io/file dir)]
|
|
56
|
+
(when (.exists d)
|
|
57
|
+
(->> (.listFiles d)
|
|
58
|
+
(filter #(str/ends-with? (.getName %) ".edn"))
|
|
59
|
+
(sort-by #(.getName %))))))
|
|
60
|
+
|
|
61
|
+
(defn list-pending
|
|
62
|
+
"List all pending tasks"
|
|
63
|
+
[]
|
|
64
|
+
(->> (list-task-files PENDING_DIR)
|
|
65
|
+
(keep read-task-file)
|
|
66
|
+
vec))
|
|
67
|
+
|
|
68
|
+
(defn list-current
|
|
69
|
+
"List all in-progress tasks"
|
|
70
|
+
[]
|
|
71
|
+
(->> (list-task-files CURRENT_DIR)
|
|
72
|
+
(keep read-task-file)
|
|
73
|
+
vec))
|
|
74
|
+
|
|
75
|
+
(defn list-complete
|
|
76
|
+
"List all completed tasks"
|
|
77
|
+
[]
|
|
78
|
+
(->> (list-task-files COMPLETE_DIR)
|
|
79
|
+
(keep read-task-file)
|
|
80
|
+
vec))
|
|
81
|
+
|
|
82
|
+
(defn list-all
|
|
83
|
+
"List all tasks with their status"
|
|
84
|
+
[]
|
|
85
|
+
(concat
|
|
86
|
+
(map #(assoc % :status :pending) (list-pending))
|
|
87
|
+
(map #(assoc % :status :current) (list-current))
|
|
88
|
+
(map #(assoc % :status :complete) (list-complete))))
|
|
89
|
+
|
|
90
|
+
;; =============================================================================
|
|
91
|
+
;; Task Operations
|
|
92
|
+
;; =============================================================================
|
|
93
|
+
|
|
94
|
+
(defn- move-task!
|
|
95
|
+
"Move a task file from one dir to another"
|
|
96
|
+
[task from-dir to-dir]
|
|
97
|
+
(let [filename (-> (:_file task) io/file .getName)
|
|
98
|
+
from-path (io/file from-dir filename)
|
|
99
|
+
to-path (io/file to-dir filename)]
|
|
100
|
+
(when (.exists from-path)
|
|
101
|
+
(.renameTo from-path to-path)
|
|
102
|
+
(assoc task :_file (.getPath to-path)))))
|
|
103
|
+
|
|
104
|
+
(defn claim-task!
|
|
105
|
+
"Claim a pending task (mv pending → current). Returns task or nil."
|
|
106
|
+
[task]
|
|
107
|
+
(move-task! task PENDING_DIR CURRENT_DIR))
|
|
108
|
+
|
|
109
|
+
(defn complete-task!
|
|
110
|
+
"Mark a task complete (mv current → complete). Returns task or nil."
|
|
111
|
+
[task]
|
|
112
|
+
(move-task! task CURRENT_DIR COMPLETE_DIR))
|
|
113
|
+
|
|
114
|
+
(defn unclaim-task!
|
|
115
|
+
"Return a task to pending (mv current → pending). Returns task or nil."
|
|
116
|
+
[task]
|
|
117
|
+
(move-task! task CURRENT_DIR PENDING_DIR))
|
|
118
|
+
|
|
119
|
+
(defn claim-next!
|
|
120
|
+
"Claim the next available pending task. Returns task or nil."
|
|
121
|
+
[]
|
|
122
|
+
(when-let [task (first (list-pending))]
|
|
123
|
+
(claim-task! task)))
|
|
124
|
+
|
|
125
|
+
;; =============================================================================
|
|
126
|
+
;; Task Creation
|
|
127
|
+
;; =============================================================================
|
|
128
|
+
|
|
129
|
+
(defn- generate-task-id
|
|
130
|
+
"Generate a unique task ID"
|
|
131
|
+
[]
|
|
132
|
+
(format "task-%d" (System/currentTimeMillis)))
|
|
133
|
+
|
|
134
|
+
(defn- task->filename
|
|
135
|
+
"Convert task to filename"
|
|
136
|
+
[task]
|
|
137
|
+
(let [id (or (:id task) (generate-task-id))
|
|
138
|
+
safe-id (str/replace id #"[^a-zA-Z0-9-_]" "-")]
|
|
139
|
+
(str safe-id ".edn")))
|
|
140
|
+
|
|
141
|
+
(defn create-task!
|
|
142
|
+
"Create a new task in pending/. Returns the task with :_file set."
|
|
143
|
+
[{:keys [id summary] :as task}]
|
|
144
|
+
(ensure-dirs!)
|
|
145
|
+
(let [task-id (or id (generate-task-id))
|
|
146
|
+
task (assoc task :id task-id)
|
|
147
|
+
filename (task->filename task)
|
|
148
|
+
path (io/file PENDING_DIR filename)]
|
|
149
|
+
(spit path (pr-str task))
|
|
150
|
+
(assoc task :_file (.getPath path))))
|
|
151
|
+
|
|
152
|
+
(defn create-tasks!
|
|
153
|
+
"Create multiple tasks. Returns list of created tasks."
|
|
154
|
+
[tasks]
|
|
155
|
+
(mapv create-task! tasks))
|
|
156
|
+
|
|
157
|
+
;; =============================================================================
|
|
158
|
+
;; Status Checks
|
|
159
|
+
;; =============================================================================
|
|
160
|
+
|
|
161
|
+
(defn pending-count [] (count (list-task-files PENDING_DIR)))
|
|
162
|
+
(defn current-count [] (count (list-task-files CURRENT_DIR)))
|
|
163
|
+
(defn complete-count [] (count (list-task-files COMPLETE_DIR)))
|
|
164
|
+
|
|
165
|
+
(defn all-complete?
|
|
166
|
+
"True if no pending or current tasks"
|
|
167
|
+
[]
|
|
168
|
+
(and (zero? (pending-count))
|
|
169
|
+
(zero? (current-count))))
|
|
170
|
+
|
|
171
|
+
(defn has-pending?
|
|
172
|
+
"True if there are pending tasks"
|
|
173
|
+
[]
|
|
174
|
+
(pos? (pending-count)))
|
|
175
|
+
|
|
176
|
+
(defn status-summary
|
|
177
|
+
"Return status counts"
|
|
178
|
+
[]
|
|
179
|
+
{:pending (pending-count)
|
|
180
|
+
:current (current-count)
|
|
181
|
+
:complete (complete-count)})
|
|
182
|
+
|
|
183
|
+
;; =============================================================================
|
|
184
|
+
;; Migration from old format
|
|
185
|
+
;; =============================================================================
|
|
186
|
+
|
|
187
|
+
(defn migrate-from-tasks-edn!
|
|
188
|
+
"Migrate from config/tasks.edn to folder structure"
|
|
189
|
+
[]
|
|
190
|
+
(let [old-file (io/file "config/tasks.edn")]
|
|
191
|
+
(when (.exists old-file)
|
|
192
|
+
(println "Migrating from config/tasks.edn...")
|
|
193
|
+
(ensure-dirs!)
|
|
194
|
+
(let [tasks (with-open [r (java.io.PushbackReader. (io/reader old-file))]
|
|
195
|
+
(edn/read {:eof nil} r))]
|
|
196
|
+
(doseq [task tasks]
|
|
197
|
+
(create-task! task))
|
|
198
|
+
(println (format "Migrated %d tasks to tasks/pending/" (count tasks)))))))
|