@monoes/monomindcli 1.6.0 → 1.6.3
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/.claude/commands/monomind-createtask.md +340 -0
- package/.claude/commands/monomind-do.md +41 -0
- package/.claude/commands/monomind-repeat.md +55 -1
- package/.claude/helpers/hook-handler.cjs +1 -0
- package/.claude/helpers/router.cjs +165 -4
- package/dist/src/mcp-tools/agent-tools.js +12 -1
- package/dist/src/mcp-tools/hive-mind-tools.js +18 -1
- package/dist/src/mcp-tools/hooks-tools.js +11 -1
- package/dist/src/mcp-tools/swarm-tools.js +11 -1
- package/dist/src/ui/collector.mjs +98 -9
- package/dist/src/ui/dashboard.html +1173 -511
- package/dist/src/ui/server.mjs +168 -3
- package/package.json +1 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: monomind-createtask
|
|
3
|
+
description: "Monomind — Ingest a prompt, file, or folder, deeply understand it, generate a full implementation plan, and create self-contained tasks on monotask that coder agents can pick up"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
If `$ARGUMENTS` is empty, output this and STOP:
|
|
7
|
+
|
|
8
|
+
> **Usage:** `/monomind:createtask <prompt | path-to-file | path-to-folder>`
|
|
9
|
+
>
|
|
10
|
+
> Examples:
|
|
11
|
+
> - `/monomind:createtask Build a webhook delivery system with retries and dead-letter queue`
|
|
12
|
+
> - `/monomind:createtask docs/superpowers/specs/2026-04-27-swarm-tab-redesign-design.md`
|
|
13
|
+
> - `/monomind:createtask docs/superpowers/specs/`
|
|
14
|
+
>
|
|
15
|
+
> This command deeply analyzes your input, generates a full implementation plan, and creates self-contained tasks on monotask that simple coder agents can execute without additional context.
|
|
16
|
+
|
|
17
|
+
Do NOT proceed further if no arguments were provided.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Step 0: Check monotask CLI
|
|
22
|
+
|
|
23
|
+
Run:
|
|
24
|
+
```bash
|
|
25
|
+
command -v monotask
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If `monotask` is NOT found, attempt to install:
|
|
29
|
+
```bash
|
|
30
|
+
command -v cargo && cargo install monotask
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If `cargo` is also missing, output this and STOP:
|
|
34
|
+
> monotask requires Rust. Install Rust first:
|
|
35
|
+
> ```bash
|
|
36
|
+
> curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
|
37
|
+
> source "$HOME/.cargo/env"
|
|
38
|
+
> cargo install monotask
|
|
39
|
+
> ```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Step 1: Classify and Ingest Input
|
|
44
|
+
|
|
45
|
+
Parse `$ARGUMENTS` to determine input type:
|
|
46
|
+
|
|
47
|
+
### 1a: Detect input type
|
|
48
|
+
|
|
49
|
+
- If `$ARGUMENTS` is an existing **file path** (check with `test -f`): `INPUT_TYPE=file`
|
|
50
|
+
- If `$ARGUMENTS` is an existing **directory path** (check with `test -d`): `INPUT_TYPE=folder`
|
|
51
|
+
- Otherwise: `INPUT_TYPE=prompt`
|
|
52
|
+
|
|
53
|
+
### 1b: Collect raw content
|
|
54
|
+
|
|
55
|
+
**If `INPUT_TYPE=prompt`:**
|
|
56
|
+
- Store the text as `RAW_CONTENT`.
|
|
57
|
+
|
|
58
|
+
**If `INPUT_TYPE=file`:**
|
|
59
|
+
- Read the file using the Read tool.
|
|
60
|
+
- Store the full contents as `RAW_CONTENT`.
|
|
61
|
+
- Store filename as `INPUT_LABEL`.
|
|
62
|
+
|
|
63
|
+
**If `INPUT_TYPE=folder`:**
|
|
64
|
+
- List all files in the directory (non-recursive, skip hidden files):
|
|
65
|
+
```bash
|
|
66
|
+
find "$ARGUMENTS" -maxdepth 2 -type f ! -name '.*' | head -30
|
|
67
|
+
```
|
|
68
|
+
- Read each file using the Read tool (up to 30 files, skip binary files).
|
|
69
|
+
- Concatenate all contents with `--- FILE: <path> ---` separators as `RAW_CONTENT`.
|
|
70
|
+
- Store folder path as `INPUT_LABEL`.
|
|
71
|
+
|
|
72
|
+
### 1c: Enrich with knowledge systems
|
|
73
|
+
|
|
74
|
+
Run ALL of the following in parallel (skip any that error):
|
|
75
|
+
|
|
76
|
+
1. **Knowledge graph — suggest**: Call `mcp__monobrain__graphify_suggest` with the first 200 chars of `RAW_CONTENT`.
|
|
77
|
+
2. **Knowledge graph — query**: If any specific module/component names appear in `RAW_CONTENT`, call `mcp__monobrain__graphify_query` for each (up to 5 queries).
|
|
78
|
+
3. **Memory search**: Call `mcp__monobrain__memory_search` with a summary of the input. Use top 5 results.
|
|
79
|
+
4. **README**: Read `README.md` (first 200 lines). Skip if missing.
|
|
80
|
+
5. **Package manifest**: Read whichever exists first: `package.json`, `Cargo.toml`, `pyproject.toml`, `go.mod`.
|
|
81
|
+
6. **Repo name**: Run `git remote get-url origin`, extract last path segment, strip `.git`. Fallback: `basename` of cwd. Store as `REPO_NAME`.
|
|
82
|
+
|
|
83
|
+
Bundle everything into `FULL_CONTEXT` = `RAW_CONTENT` + graph results + memory results + README + manifest.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Step 2: Setup Monotask Space and Task Board
|
|
88
|
+
|
|
89
|
+
### Space
|
|
90
|
+
- Run `monotask space list` and check if a space named `$REPO_NAME` exists.
|
|
91
|
+
- If not, create it: `monotask space create "$REPO_NAME"`.
|
|
92
|
+
- Store `SPACE_ID`.
|
|
93
|
+
|
|
94
|
+
### Task Board
|
|
95
|
+
- List boards via `monotask board list --json`. For each board ID, run `monotask column list <BOARD_ID> --json` to find one whose columns contain `Todo` (the `monomind-task` board).
|
|
96
|
+
- If the `monomind-task` board does not exist:
|
|
97
|
+
1. Create it: `monotask board create "monomind-task" --json` — store `TASK_BOARD_ID`.
|
|
98
|
+
2. Add to space: `monotask space boards add $SPACE_ID $TASK_BOARD_ID`.
|
|
99
|
+
3. Create columns in order:
|
|
100
|
+
- `Backlog`
|
|
101
|
+
- `Todo`
|
|
102
|
+
- `In Progress`
|
|
103
|
+
- `Review`
|
|
104
|
+
- `Human in Loop`
|
|
105
|
+
- `Done`
|
|
106
|
+
- Store all column IDs mapped by name.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Step 3: Deep Analysis — Understand the Document
|
|
111
|
+
|
|
112
|
+
Spawn a `Software Architect` agent via the Agent tool. Provide it with:
|
|
113
|
+
|
|
114
|
+
- The complete `FULL_CONTEXT`
|
|
115
|
+
- The user's original `$ARGUMENTS`
|
|
116
|
+
|
|
117
|
+
The agent MUST produce a structured analysis:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"summary": "2-3 sentence overview of what this document/prompt is about",
|
|
122
|
+
"goals": ["list of high-level goals or features described"],
|
|
123
|
+
"components": [
|
|
124
|
+
{
|
|
125
|
+
"name": "component or module name",
|
|
126
|
+
"description": "what it does",
|
|
127
|
+
"dependencies": ["other components it depends on"],
|
|
128
|
+
"files_likely_affected": ["paths from graphify or educated guesses"]
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
"technical_constraints": ["any constraints, tech stack requirements, or limitations mentioned"],
|
|
132
|
+
"acceptance_criteria": ["testable conditions for when this is done"],
|
|
133
|
+
"risks": ["potential pitfalls, ambiguities, or unknowns"]
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Store as `ANALYSIS`.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Step 4: Generate Implementation Plan
|
|
142
|
+
|
|
143
|
+
Spawn a `planner` agent via the Agent tool. Provide it with:
|
|
144
|
+
|
|
145
|
+
- The `ANALYSIS` from Step 3
|
|
146
|
+
- The `FULL_CONTEXT`
|
|
147
|
+
- The `REPO_NAME` and project info
|
|
148
|
+
|
|
149
|
+
The agent MUST produce an ordered list of implementation tasks. Each task must be **completely self-contained** — a coder agent with NO prior context should be able to execute it by reading only the task card.
|
|
150
|
+
|
|
151
|
+
For each task, produce:
|
|
152
|
+
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"title": "Short action-oriented title (e.g. 'Add webhook retry logic with exponential backoff')",
|
|
156
|
+
"description": "Detailed description: WHAT to build, WHY it's needed, WHERE it fits in the system",
|
|
157
|
+
"context": "All relevant context a coder needs: existing patterns to follow, related files, API shapes, data models, config values. Include specific file paths from graphify results when available.",
|
|
158
|
+
"acceptance_criteria": ["list of testable conditions that prove this task is done"],
|
|
159
|
+
"checklist": ["step-by-step implementation steps the coder should follow"],
|
|
160
|
+
"agent_type": "recommended agent type (e.g. coder, backend-dev, Frontend Developer, Security Engineer)",
|
|
161
|
+
"priority": "critical | high | medium | low",
|
|
162
|
+
"effort": 1-10,
|
|
163
|
+
"dependencies": ["titles of other tasks that must complete first, or empty array"]
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Rules for task generation:**
|
|
168
|
+
- Tasks MUST be ordered so dependencies come first.
|
|
169
|
+
- Each task should take a single agent 5-30 minutes to complete.
|
|
170
|
+
- Tasks that are too large MUST be split.
|
|
171
|
+
- Every task MUST include enough context that the coder doesn't need to read the original document.
|
|
172
|
+
- Include a test-writing step in every task's checklist.
|
|
173
|
+
- Assign agent types from available agents (coder, backend-dev, Frontend Developer, Security Engineer, etc.) based on the task domain.
|
|
174
|
+
|
|
175
|
+
Store as `TASKS` array.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Step 5: Create Tasks on Monotask Board
|
|
180
|
+
|
|
181
|
+
For each task in the `TASKS` array, in dependency order:
|
|
182
|
+
|
|
183
|
+
1. **Create the card** in the `Todo` column (dependency-free tasks) or `Backlog` column (has unfinished dependencies):
|
|
184
|
+
```bash
|
|
185
|
+
monotask card create $TASK_BOARD_ID $COL_TODO "<title>" --json
|
|
186
|
+
```
|
|
187
|
+
Store the returned `CARD_ID`.
|
|
188
|
+
|
|
189
|
+
2. **Set description** with the full context block:
|
|
190
|
+
```bash
|
|
191
|
+
monotask card set-description $TASK_BOARD_ID $CARD_ID "<description>\n\n## Context\n<context>"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
3. **Add agent assignment comment**:
|
|
195
|
+
```bash
|
|
196
|
+
monotask card comment add $TASK_BOARD_ID $CARD_ID "Assigned agent: <agent_type>"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
4. **Add acceptance criteria comment**:
|
|
200
|
+
```bash
|
|
201
|
+
monotask card comment add $TASK_BOARD_ID $CARD_ID "Acceptance criteria:\n- <criterion 1>\n- <criterion 2>\n..."
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
5. **Add dependency comment** (if any):
|
|
205
|
+
```bash
|
|
206
|
+
monotask card comment add $TASK_BOARD_ID $CARD_ID "Dependencies: <task title 1>, <task title 2>"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
6. **Set priority**:
|
|
210
|
+
```bash
|
|
211
|
+
monotask card set-priority $TASK_BOARD_ID $CARD_ID <1-4>
|
|
212
|
+
```
|
|
213
|
+
Map: critical=1, high=2, medium=3, low=4.
|
|
214
|
+
|
|
215
|
+
7. **Create checklist** with implementation steps:
|
|
216
|
+
```bash
|
|
217
|
+
monotask checklist add $TASK_BOARD_ID $CARD_ID "Implementation Steps" --json
|
|
218
|
+
```
|
|
219
|
+
Store `CHECKLIST_ID`, then for each step:
|
|
220
|
+
```bash
|
|
221
|
+
monotask checklist item-add $TASK_BOARD_ID $CARD_ID $CHECKLIST_ID "<step>"
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Batch card creation commands where possible to reduce round-trips.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Step 6: Exploration Session — Suggest Missing Pieces
|
|
229
|
+
|
|
230
|
+
After all tasks are created, spawn a **second** `Software Architect` agent (fresh context) via the Agent tool. Provide it with:
|
|
231
|
+
|
|
232
|
+
- The `ANALYSIS` from Step 3
|
|
233
|
+
- The complete list of `TASKS` already created (titles + descriptions)
|
|
234
|
+
- The `FULL_CONTEXT`
|
|
235
|
+
|
|
236
|
+
The agent must act as a **critical reviewer** and identify:
|
|
237
|
+
|
|
238
|
+
```json
|
|
239
|
+
{
|
|
240
|
+
"missing_pieces": [
|
|
241
|
+
{
|
|
242
|
+
"title": "What's missing",
|
|
243
|
+
"description": "Why this matters and what should be done",
|
|
244
|
+
"category": "testing | documentation | error-handling | monitoring | security | performance | accessibility | deployment | migration"
|
|
245
|
+
}
|
|
246
|
+
],
|
|
247
|
+
"upcoming_plans": [
|
|
248
|
+
{
|
|
249
|
+
"title": "Natural follow-up work",
|
|
250
|
+
"description": "What this would add and why it's worth considering",
|
|
251
|
+
"category": "enhancement | optimization | scale | integration"
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**Areas to explore:**
|
|
258
|
+
- Missing test coverage (unit, integration, e2e)
|
|
259
|
+
- Error handling and edge cases not covered
|
|
260
|
+
- Documentation that should be created or updated
|
|
261
|
+
- Security considerations (input validation, auth, rate limiting)
|
|
262
|
+
- Performance implications (caching, indexing, pagination)
|
|
263
|
+
- Monitoring and observability (logging, metrics, alerts)
|
|
264
|
+
- Migration or backwards compatibility concerns
|
|
265
|
+
- Deployment steps or configuration changes needed
|
|
266
|
+
- Accessibility requirements (for UI tasks)
|
|
267
|
+
|
|
268
|
+
### Present to User
|
|
269
|
+
|
|
270
|
+
Output the suggestions in a clear format:
|
|
271
|
+
|
|
272
|
+
```
|
|
273
|
+
## Missing Pieces
|
|
274
|
+
|
|
275
|
+
| # | Category | Title | Description |
|
|
276
|
+
|---|-------------|------------------------------------|----------------------------|
|
|
277
|
+
| 1 | testing | Add integration tests for webhook | Currently only unit tests |
|
|
278
|
+
| 2 | security | Rate-limit webhook endpoints | Prevent abuse |
|
|
279
|
+
|
|
280
|
+
## Potential Follow-ups
|
|
281
|
+
|
|
282
|
+
| # | Category | Title | Description |
|
|
283
|
+
|---|-------------|------------------------------------|----------------------------|
|
|
284
|
+
| 1 | enhancement | Add webhook analytics dashboard | Track delivery rates |
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Then ask:
|
|
288
|
+
|
|
289
|
+
> **Found N missing pieces and M potential follow-ups.**
|
|
290
|
+
>
|
|
291
|
+
> Reply with the numbers you want to add as tasks (e.g., `1, 3, 5` or `all` or `none`).
|
|
292
|
+
> Missing pieces will be added to **Todo**. Follow-ups will be added to **Backlog**.
|
|
293
|
+
|
|
294
|
+
### Process User Selection
|
|
295
|
+
|
|
296
|
+
If the user selects items:
|
|
297
|
+
1. For each selected **missing piece**: Create a task card in `Todo` with full context (same Step 5 process). Add a comment: `"Source: exploration — missing piece"`.
|
|
298
|
+
2. For each selected **follow-up**: Create a task card in `Backlog` with full context. Add a comment: `"Source: exploration — follow-up suggestion"`.
|
|
299
|
+
|
|
300
|
+
If the user says `none` or declines, skip.
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Step 7: Final Summary
|
|
305
|
+
|
|
306
|
+
Output:
|
|
307
|
+
|
|
308
|
+
```
|
|
309
|
+
## Task Creation Complete
|
|
310
|
+
|
|
311
|
+
**Source:** <prompt text | file path | folder path>
|
|
312
|
+
**Space:** $REPO_NAME (ID: $SPACE_ID)
|
|
313
|
+
**Board:** monomind-task (ID: $TASK_BOARD_ID)
|
|
314
|
+
|
|
315
|
+
### Tasks Created
|
|
316
|
+
|
|
317
|
+
| # | Title | Agent | Priority | Column |
|
|
318
|
+
|---|----------------------------------------|-------------|----------|---------|
|
|
319
|
+
| 1 | <title> | backend-dev | high | Todo |
|
|
320
|
+
| 2 | <title> | coder | medium | Todo |
|
|
321
|
+
| 3 | <title> | Frontend Developer | medium | Backlog |
|
|
322
|
+
|
|
323
|
+
**Total:** N tasks in Todo, M tasks in Backlog
|
|
324
|
+
**Estimated effort:** X points
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Step 8: Offer to Execute
|
|
330
|
+
|
|
331
|
+
If there are tasks in Todo, ask:
|
|
332
|
+
|
|
333
|
+
> **N tasks are ready for execution.** Want me to start `/monomind:do` to process them?
|
|
334
|
+
>
|
|
335
|
+
> It will pick up tasks one by one, execute them with the assigned agent, review for bugs, and loop until the queue is empty.
|
|
336
|
+
|
|
337
|
+
If the user agrees, invoke:
|
|
338
|
+
```
|
|
339
|
+
Skill("monomind-do", "--space $SPACE_ID --board $TASK_BOARD_ID")
|
|
340
|
+
```
|
|
@@ -55,6 +55,35 @@ If `cargo` is also missing, output this and STOP:
|
|
|
55
55
|
|
|
56
56
|
---
|
|
57
57
|
|
|
58
|
+
## Step 1.5: Initialize Loop State
|
|
59
|
+
|
|
60
|
+
Generate a loop ID and write the initial state file so the dashboard can track this run:
|
|
61
|
+
```bash
|
|
62
|
+
mkdir -p .monomind/loops
|
|
63
|
+
export DO_LOOP_ID="do-$(date +%s%3N)"
|
|
64
|
+
cat > ".monomind/loops/${DO_LOOP_ID}.json" << EOF
|
|
65
|
+
{
|
|
66
|
+
"id": "${DO_LOOP_ID}",
|
|
67
|
+
"type": "do",
|
|
68
|
+
"prompt": "/monomind:do $ARGUMENTS",
|
|
69
|
+
"currentTask": "discovering...",
|
|
70
|
+
"spaceId": "${SPACE_ID:-}",
|
|
71
|
+
"boardId": "${TASK_BOARD_ID:-}",
|
|
72
|
+
"filter": "${FILTER:-}",
|
|
73
|
+
"startedAt": $(date +%s%3N),
|
|
74
|
+
"lastRunAt": $(date +%s%3N),
|
|
75
|
+
"nextRunAt": 0,
|
|
76
|
+
"status": "running"
|
|
77
|
+
}
|
|
78
|
+
EOF
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Also check if a stop was requested from a previous cycle:
|
|
82
|
+
```bash
|
|
83
|
+
[ -f ".monomind/loops/${DO_LOOP_ID}.stop" ] && echo "DO_STOP_REQUESTED=true"
|
|
84
|
+
```
|
|
85
|
+
If `DO_STOP_REQUESTED=true`, output `[monomind:do] Stop requested via dashboard. Halting.`, remove state files, and STOP.
|
|
86
|
+
|
|
58
87
|
## Step 2: Find Next Task
|
|
59
88
|
|
|
60
89
|
1. List cards in `Todo` first (prioritized), then `Backlog`:
|
|
@@ -69,6 +98,13 @@ If `cargo` is also missing, output this and STOP:
|
|
|
69
98
|
```
|
|
70
99
|
[monomind:do] No tasks in Todo or Backlog. Checking again in 2 minutes...
|
|
71
100
|
```
|
|
101
|
+
Update loop state before scheduling:
|
|
102
|
+
```bash
|
|
103
|
+
NEXT_AT=$(( $(date +%s%3N) + 120000 ))
|
|
104
|
+
cat > ".monomind/loops/${DO_LOOP_ID}.json" << EOF
|
|
105
|
+
{"id":"${DO_LOOP_ID}","type":"do","prompt":"/monomind:do $ARGUMENTS","currentTask":"queue empty — waiting","spaceId":"${SPACE_ID:-}","boardId":"${TASK_BOARD_ID:-}","filter":"${FILTER:-}","startedAt":$(cat .monomind/loops/${DO_LOOP_ID}.json 2>/dev/null | python3 -c "import sys,json;print(json.load(sys.stdin).get('startedAt',0))" 2>/dev/null || date +%s%3N),"lastRunAt":$(date +%s%3N),"nextRunAt":${NEXT_AT},"status":"waiting"}
|
|
106
|
+
EOF
|
|
107
|
+
```
|
|
72
108
|
Then use `ScheduleWakeup` with `delaySeconds: 120` and prompt `/monomind:do --space $SPACE_ID --board $TASK_BOARD_ID` (plus `--filter` if one was set) to check again. STOP this iteration.
|
|
73
109
|
|
|
74
110
|
4. Store `CURRENT_CARD_ID` and `CURRENT_CARD_TITLE`.
|
|
@@ -273,4 +309,9 @@ If no tasks remain, output:
|
|
|
273
309
|
[monomind:do] All tasks processed. Queue empty.
|
|
274
310
|
```
|
|
275
311
|
|
|
312
|
+
Remove the loop state file:
|
|
313
|
+
```bash
|
|
314
|
+
rm -f ".monomind/loops/${DO_LOOP_ID}.json" ".monomind/loops/${DO_LOOP_ID}.stop"
|
|
315
|
+
```
|
|
316
|
+
|
|
276
317
|
Do NOT schedule another wake-up. STOP.
|
|
@@ -37,6 +37,27 @@ Extract:
|
|
|
37
37
|
- `MAX_REPS` — from `--times` flag, default `10`
|
|
38
38
|
- `PROMPT` — everything remaining after flags are removed
|
|
39
39
|
- `CURRENT_REP` — starts at `1`
|
|
40
|
+
- `LOOP_ID` — generate as `repeat-<unix-timestamp-ms>` (use `date +%s000`)
|
|
41
|
+
|
|
42
|
+
Write the initial loop state file so the dashboard can track this run:
|
|
43
|
+
```bash
|
|
44
|
+
mkdir -p .monomind/loops
|
|
45
|
+
LOOP_ID="repeat-$(date +%s%3N)"
|
|
46
|
+
cat > ".monomind/loops/${LOOP_ID}.json" << EOF
|
|
47
|
+
{
|
|
48
|
+
"id": "${LOOP_ID}",
|
|
49
|
+
"type": "repeat",
|
|
50
|
+
"prompt": "PROMPT",
|
|
51
|
+
"interval": INTERVAL,
|
|
52
|
+
"currentRep": 1,
|
|
53
|
+
"maxReps": MAX_REPS,
|
|
54
|
+
"startedAt": $(date +%s%3N),
|
|
55
|
+
"lastRunAt": $(date +%s%3N),
|
|
56
|
+
"nextRunAt": $(date +%s%3N),
|
|
57
|
+
"status": "running"
|
|
58
|
+
}
|
|
59
|
+
EOF
|
|
60
|
+
```
|
|
40
61
|
|
|
41
62
|
Output:
|
|
42
63
|
```
|
|
@@ -58,6 +79,16 @@ Run the `PROMPT` as if the user typed it directly. This means:
|
|
|
58
79
|
|
|
59
80
|
## Step 3: Report and Schedule Next
|
|
60
81
|
|
|
82
|
+
Before scheduling the next run, check if a stop was requested:
|
|
83
|
+
```bash
|
|
84
|
+
[ -f ".monomind/loops/${LOOP_ID}.stop" ] && echo "STOP_REQUESTED=true"
|
|
85
|
+
```
|
|
86
|
+
If `STOP_REQUESTED=true`, output `[monomind:repeat] Stop requested via dashboard. Halting.` and remove the state files:
|
|
87
|
+
```bash
|
|
88
|
+
rm -f ".monomind/loops/${LOOP_ID}.json" ".monomind/loops/${LOOP_ID}.stop"
|
|
89
|
+
```
|
|
90
|
+
Then STOP.
|
|
91
|
+
|
|
61
92
|
After execution completes, output:
|
|
62
93
|
```
|
|
63
94
|
[monomind:repeat] Run CURRENT_REP/MAX_REPS complete. Next in INTERVAL minutes...
|
|
@@ -69,9 +100,32 @@ If `CURRENT_REP > MAX_REPS`, output:
|
|
|
69
100
|
```
|
|
70
101
|
[monomind:repeat] All MAX_REPS repetitions complete.
|
|
71
102
|
```
|
|
103
|
+
Remove the state file:
|
|
104
|
+
```bash
|
|
105
|
+
rm -f ".monomind/loops/${LOOP_ID}.json"
|
|
106
|
+
```
|
|
72
107
|
STOP. Do NOT schedule another wake-up.
|
|
73
108
|
|
|
74
|
-
Otherwise,
|
|
109
|
+
Otherwise, update the loop state before scheduling:
|
|
110
|
+
```bash
|
|
111
|
+
NEXT_AT=$(( $(date +%s%3N) + INTERVAL * 60 * 1000 ))
|
|
112
|
+
cat > ".monomind/loops/${LOOP_ID}.json" << EOF
|
|
113
|
+
{
|
|
114
|
+
"id": "${LOOP_ID}",
|
|
115
|
+
"type": "repeat",
|
|
116
|
+
"prompt": "PROMPT",
|
|
117
|
+
"interval": INTERVAL,
|
|
118
|
+
"currentRep": CURRENT_REP,
|
|
119
|
+
"maxReps": MAX_REPS,
|
|
120
|
+
"startedAt": STARTED_AT,
|
|
121
|
+
"lastRunAt": $(date +%s%3N),
|
|
122
|
+
"nextRunAt": ${NEXT_AT},
|
|
123
|
+
"status": "running"
|
|
124
|
+
}
|
|
125
|
+
EOF
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Use `ScheduleWakeup` with:
|
|
75
129
|
- `delaySeconds`: `INTERVAL * 60`
|
|
76
130
|
- `prompt`: `/monomind:repeat --every INTERVAL --times MAX_REPS --rep CURRENT_REP PROMPT`
|
|
77
131
|
- `reason`: `"repeat run CURRENT_REP/MAX_REPS of: PROMPT"`
|
|
@@ -433,6 +433,7 @@ const handlers = {
|
|
|
433
433
|
confidence: result.confidence,
|
|
434
434
|
reason: result.reason,
|
|
435
435
|
semanticRouting: result.semanticRouting || false,
|
|
436
|
+
llmRouting: result.llmRouting || false,
|
|
436
437
|
updatedAt: new Date().toISOString(),
|
|
437
438
|
};
|
|
438
439
|
if (result.extrasMatches && result.extrasMatches.length > 0) {
|
|
@@ -167,6 +167,127 @@ function isNonDevTask(taskLower) {
|
|
|
167
167
|
return false;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// ─── Two-Stage LLM Router (for non-dev and ambiguous tasks) ─────────────────
|
|
171
|
+
// Stage 1: LLM picks category from ~9 categories
|
|
172
|
+
// Stage 2: LLM picks specific agent from agents in that category
|
|
173
|
+
// Falls back to keyword scoring if API unavailable
|
|
174
|
+
|
|
175
|
+
function getAnthropicKey() {
|
|
176
|
+
return process.env.ANTHROPIC_API_KEY || '';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function buildCategoryList() {
|
|
180
|
+
const registry = loadExtrasRegistry();
|
|
181
|
+
const cats = {};
|
|
182
|
+
for (const e of registry.extras) {
|
|
183
|
+
if (!cats[e.category]) cats[e.category] = [];
|
|
184
|
+
cats[e.category].push(e.name);
|
|
185
|
+
}
|
|
186
|
+
return Object.entries(cats).map(([name, agents]) => ({
|
|
187
|
+
name,
|
|
188
|
+
count: agents.length,
|
|
189
|
+
examples: agents.slice(0, 4).join(', '),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function getAgentsInCategory(category) {
|
|
194
|
+
const registry = loadExtrasRegistry();
|
|
195
|
+
return registry.extras
|
|
196
|
+
.filter(e => e.category === category)
|
|
197
|
+
.map(e => ({ slug: e.slug, name: e.name, description: (e.description || '').slice(0, 120) }));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function llmPick(systemPrompt, userPrompt) {
|
|
201
|
+
const key = getAnthropicKey();
|
|
202
|
+
if (!key) return null;
|
|
203
|
+
try {
|
|
204
|
+
const ac = new AbortController();
|
|
205
|
+
const timer = setTimeout(() => ac.abort(), 2000);
|
|
206
|
+
const resp = await fetch('https://api.anthropic.com/v1/messages', {
|
|
207
|
+
method: 'POST',
|
|
208
|
+
headers: {
|
|
209
|
+
'Content-Type': 'application/json',
|
|
210
|
+
'x-api-key': key,
|
|
211
|
+
'anthropic-version': '2023-06-01',
|
|
212
|
+
},
|
|
213
|
+
body: JSON.stringify({
|
|
214
|
+
model: 'claude-haiku-4-5-20251001',
|
|
215
|
+
max_tokens: 60,
|
|
216
|
+
system: systemPrompt,
|
|
217
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
218
|
+
}),
|
|
219
|
+
signal: ac.signal,
|
|
220
|
+
});
|
|
221
|
+
clearTimeout(timer);
|
|
222
|
+
if (!resp.ok) return null;
|
|
223
|
+
const data = await resp.json();
|
|
224
|
+
const text = (data.content && data.content[0] && data.content[0].text) || '';
|
|
225
|
+
return text.trim();
|
|
226
|
+
} catch { return null; }
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function routeTaskLLM(task) {
|
|
230
|
+
const categories = buildCategoryList();
|
|
231
|
+
if (!categories.length) return null;
|
|
232
|
+
|
|
233
|
+
// Stage 1: pick category
|
|
234
|
+
const catList = categories.map((c, i) => `${i + 1}. ${c.name} (${c.count} agents, e.g. ${c.examples})`).join('\n');
|
|
235
|
+
const stage1System = 'You route tasks to agent categories. Reply with ONLY the category name, nothing else.';
|
|
236
|
+
const stage1User = `Task: "${task}"\n\nCategories:\n${catList}\n\nWhich category best fits this task? Reply with the category name only.`;
|
|
237
|
+
|
|
238
|
+
const pickedCat = await llmPick(stage1System, stage1User);
|
|
239
|
+
if (!pickedCat) return null;
|
|
240
|
+
|
|
241
|
+
// Normalize — find closest category match
|
|
242
|
+
const catLower = pickedCat.toLowerCase().replace(/[^a-z-]/g, '');
|
|
243
|
+
const matchedCat = categories.find(c => c.name.toLowerCase().replace(/[^a-z-]/g, '') === catLower);
|
|
244
|
+
const categoryName = matchedCat ? matchedCat.name : categories.find(c => catLower.includes(c.name.toLowerCase().replace(/[^a-z-]/g, '')))?.name;
|
|
245
|
+
if (!categoryName) return null;
|
|
246
|
+
|
|
247
|
+
// Stage 2: pick agent within category
|
|
248
|
+
const agents = getAgentsInCategory(categoryName);
|
|
249
|
+
if (!agents.length) return null;
|
|
250
|
+
|
|
251
|
+
const agentList = agents.map((a, i) => `${i + 1}. ${a.name} — ${a.description}`).join('\n');
|
|
252
|
+
const stage2System = 'You pick the best agent for a task. Reply with ONLY the agent name exactly as listed, nothing else.';
|
|
253
|
+
const stage2User = `Task: "${task}"\n\nAgents in ${categoryName}:\n${agentList}\n\nWhich agent is the best fit? Reply with the exact agent name only.`;
|
|
254
|
+
|
|
255
|
+
const pickedAgent = await llmPick(stage2System, stage2User);
|
|
256
|
+
if (!pickedAgent) return null;
|
|
257
|
+
|
|
258
|
+
// Find the agent entry — exact match only, fall back to keyword routing
|
|
259
|
+
const agentLower = pickedAgent.toLowerCase().trim();
|
|
260
|
+
const matched = agents.find(a => a.name.toLowerCase() === agentLower);
|
|
261
|
+
if (!matched) return null;
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
agent: matched.name,
|
|
265
|
+
agentSlug: matched.slug,
|
|
266
|
+
confidence: 0.9,
|
|
267
|
+
reason: `LLM 2-stage: ${categoryName} → ${matched.name}`,
|
|
268
|
+
category: categoryName,
|
|
269
|
+
allInCategory: agents.map(a => ({ slug: a.slug, label: a.name, note: categoryName })),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Route multiple subtasks for swarm agent selection.
|
|
275
|
+
* Each subtask description gets its own 2-stage LLM routing.
|
|
276
|
+
* Returns array of { subtask, agent, agentSlug, confidence, reason }.
|
|
277
|
+
*/
|
|
278
|
+
async function routeSwarmAgents(subtasks) {
|
|
279
|
+
if (!Array.isArray(subtasks) || !subtasks.length) return [];
|
|
280
|
+
const results = await Promise.all(subtasks.map(async (sub) => {
|
|
281
|
+
const desc = typeof sub === 'string' ? sub : sub.description || sub.task || '';
|
|
282
|
+
if (!desc) return { subtask: desc, agent: 'coder', agentSlug: 'coder', confidence: 0.5, reason: 'empty subtask' };
|
|
283
|
+
const llm = await routeTaskLLM(desc);
|
|
284
|
+
if (llm) return { subtask: desc, agent: llm.agent, agentSlug: llm.agentSlug, confidence: llm.confidence, reason: llm.reason };
|
|
285
|
+
const kw = routeTask(desc);
|
|
286
|
+
return { subtask: desc, agent: kw.agent, agentSlug: kw.agentSlug, confidence: kw.confidence, reason: kw.reason };
|
|
287
|
+
}));
|
|
288
|
+
return results;
|
|
289
|
+
}
|
|
290
|
+
|
|
170
291
|
// ─── RouteLayer bridge (GAP-002) ─────────────────────────────────────────────
|
|
171
292
|
// Cache a Promise so concurrent callers all await the same load operation.
|
|
172
293
|
var _routeLayerPromise = null;
|
|
@@ -187,11 +308,33 @@ async function tryLoadRouteLayer() {
|
|
|
187
308
|
}
|
|
188
309
|
|
|
189
310
|
/**
|
|
190
|
-
* Async variant — tries
|
|
191
|
-
*
|
|
311
|
+
* Async variant — tries LLM 2-stage routing for non-dev tasks,
|
|
312
|
+
* RouteLayer semantic routing for dev tasks, falls back to keywords.
|
|
192
313
|
*/
|
|
193
314
|
async function routeTaskSemantic(task) {
|
|
194
315
|
if (typeof task !== 'string' || !task) return routeTask(task);
|
|
316
|
+
const taskLower = task.toLowerCase();
|
|
317
|
+
|
|
318
|
+
// For non-dev tasks or ambiguous defaults, try LLM 2-stage routing first
|
|
319
|
+
if (isNonDevTask(taskLower)) {
|
|
320
|
+
const llmResult = await routeTaskLLM(task);
|
|
321
|
+
if (llmResult) {
|
|
322
|
+
const extrasMatches = matchExtras(task);
|
|
323
|
+
return {
|
|
324
|
+
agent: llmResult.agent,
|
|
325
|
+
agentSlug: llmResult.agentSlug,
|
|
326
|
+
confidence: llmResult.confidence,
|
|
327
|
+
reason: llmResult.reason,
|
|
328
|
+
skillMatches: [],
|
|
329
|
+
extrasMatches,
|
|
330
|
+
specificAgents: llmResult.allInCategory.slice(0, 5),
|
|
331
|
+
llmRouting: true,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
// LLM failed — fall through to keyword-based extras matching
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Dev tasks: try RouteLayer semantic routing
|
|
195
338
|
const rl = await tryLoadRouteLayer();
|
|
196
339
|
if (rl && rl.route) {
|
|
197
340
|
try {
|
|
@@ -216,7 +359,25 @@ async function routeTaskSemantic(task) {
|
|
|
216
359
|
}
|
|
217
360
|
} catch (e) { /* fall through to keyword */ }
|
|
218
361
|
}
|
|
219
|
-
|
|
362
|
+
|
|
363
|
+
// Default keyword fallback — also try LLM if no dev pattern matched
|
|
364
|
+
const keywordResult = routeTask(task);
|
|
365
|
+
if (keywordResult.confidence <= 0.5) {
|
|
366
|
+
const llmResult = await routeTaskLLM(task);
|
|
367
|
+
if (llmResult) {
|
|
368
|
+
return {
|
|
369
|
+
agent: llmResult.agent,
|
|
370
|
+
agentSlug: llmResult.agentSlug,
|
|
371
|
+
confidence: llmResult.confidence,
|
|
372
|
+
reason: llmResult.reason,
|
|
373
|
+
skillMatches: keywordResult.skillMatches,
|
|
374
|
+
extrasMatches: matchExtras(task),
|
|
375
|
+
specificAgents: llmResult.allInCategory.slice(0, 5),
|
|
376
|
+
llmRouting: true,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return keywordResult;
|
|
220
381
|
}
|
|
221
382
|
|
|
222
383
|
// ─── Main routing ─────────────────────────────────────────────────────────────
|
|
@@ -288,7 +449,7 @@ function loadExtrasAgent(slug) {
|
|
|
288
449
|
} catch (e) { return null; }
|
|
289
450
|
}
|
|
290
451
|
|
|
291
|
-
module.exports = { routeTask, routeTaskSemantic, matchSkills, matchExtras, loadExtrasAgent, loadExtrasRegistry, loadSkillRegistry, AGENT_CAPABILITIES, TASK_PATTERNS };
|
|
452
|
+
module.exports = { routeTask, routeTaskSemantic, routeTaskLLM, routeSwarmAgents, matchSkills, matchExtras, loadExtrasAgent, loadExtrasRegistry, loadSkillRegistry, buildCategoryList, getAgentsInCategory, AGENT_CAPABILITIES, TASK_PATTERNS };
|
|
292
453
|
|
|
293
454
|
// CLI
|
|
294
455
|
if (require.main === module) {
|