monomind 1.17.0 → 1.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.claude/agents/engineering/engineering-security-engineer.md +1 -1
  2. package/.claude/commands/mastermind/_repeat.md +4 -0
  3. package/.claude/commands/mastermind/master.md +52 -1
  4. package/.claude/scheduled_tasks.lock +1 -1
  5. package/.claude/skills/mastermind/_repeat.md +2 -0
  6. package/package.json +1 -1
  7. package/packages/@monomind/cli/.claude/agents/engineering/engineering-security-engineer.md +1 -1
  8. package/packages/@monomind/cli/.claude/commands/mastermind/_repeat.md +4 -0
  9. package/packages/@monomind/cli/.claude/commands/mastermind/master.md +52 -1
  10. package/packages/@monomind/cli/.claude/skills/mastermind/_repeat.md +2 -0
  11. package/packages/@monomind/cli/dist/src/__tests__/browse-analyzer.test.js +42 -59
  12. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +18 -0
  13. package/packages/@monomind/cli/dist/src/browser/dashboard/ui.html +37 -125
  14. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.d.ts +17 -0
  15. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +320 -0
  16. package/packages/@monomind/cli/dist/src/commands/agent-ops.d.ts +9 -0
  17. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +329 -0
  18. package/packages/@monomind/cli/dist/src/commands/agent.js +5 -907
  19. package/packages/@monomind/cli/dist/src/commands/analyze-ast.d.ts +26 -0
  20. package/packages/@monomind/cli/dist/src/commands/analyze-ast.js +284 -0
  21. package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.d.ts +14 -0
  22. package/packages/@monomind/cli/dist/src/commands/analyze-boundaries.js +295 -0
  23. package/packages/@monomind/cli/dist/src/commands/analyze-diff.d.ts +8 -0
  24. package/packages/@monomind/cli/dist/src/commands/analyze-diff.js +395 -0
  25. package/packages/@monomind/cli/dist/src/commands/analyze-graph.d.ts +14 -0
  26. package/packages/@monomind/cli/dist/src/commands/analyze-graph.js +304 -0
  27. package/packages/@monomind/cli/dist/src/commands/analyze-imports.d.ts +11 -0
  28. package/packages/@monomind/cli/dist/src/commands/analyze-imports.js +287 -0
  29. package/packages/@monomind/cli/dist/src/commands/analyze-symbols.d.ts +14 -0
  30. package/packages/@monomind/cli/dist/src/commands/analyze-symbols.js +302 -0
  31. package/packages/@monomind/cli/dist/src/commands/analyze.d.ts +38 -0
  32. package/packages/@monomind/cli/dist/src/commands/analyze.js +12 -1827
  33. package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.d.ts +26 -0
  34. package/packages/@monomind/cli/dist/src/commands/doctor-env-checks.js +189 -0
  35. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.d.ts +19 -0
  36. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +388 -0
  37. package/packages/@monomind/cli/dist/src/commands/doctor.js +51 -942
  38. package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.d.ts +11 -0
  39. package/packages/@monomind/cli/dist/src/commands/hive-mind-comms.js +242 -0
  40. package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.d.ts +35 -0
  41. package/packages/@monomind/cli/dist/src/commands/hive-mind-helpers.js +203 -0
  42. package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.d.ts +8 -0
  43. package/packages/@monomind/cli/dist/src/commands/hive-mind-ops.js +233 -0
  44. package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.d.ts +12 -0
  45. package/packages/@monomind/cli/dist/src/commands/hive-mind-spawn.js +274 -0
  46. package/packages/@monomind/cli/dist/src/commands/hive-mind.js +10 -1129
  47. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.d.ts +4 -4
  48. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-commands.js +19 -819
  49. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.d.ts +7 -0
  50. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-gaps.js +334 -0
  51. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.d.ts +7 -0
  52. package/packages/@monomind/cli/dist/src/commands/hooks-coverage-routing.js +399 -0
  53. package/packages/@monomind/cli/dist/src/commands/init-subcommands.d.ts +8 -0
  54. package/packages/@monomind/cli/dist/src/commands/init-subcommands.js +156 -0
  55. package/packages/@monomind/cli/dist/src/commands/init-upgrade.d.ts +6 -0
  56. package/packages/@monomind/cli/dist/src/commands/init-upgrade.js +203 -0
  57. package/packages/@monomind/cli/dist/src/commands/init-wizard.d.ts +6 -0
  58. package/packages/@monomind/cli/dist/src/commands/init-wizard.js +246 -0
  59. package/packages/@monomind/cli/dist/src/commands/init.js +6 -623
  60. package/packages/@monomind/cli/dist/src/commands/memory-admin.d.ts +10 -0
  61. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +433 -0
  62. package/packages/@monomind/cli/dist/src/commands/memory-crud.d.ts +9 -0
  63. package/packages/@monomind/cli/dist/src/commands/memory-crud.js +342 -0
  64. package/packages/@monomind/cli/dist/src/commands/memory-list.d.ts +10 -0
  65. package/packages/@monomind/cli/dist/src/commands/memory-list.js +321 -0
  66. package/packages/@monomind/cli/dist/src/commands/memory-transfer.d.ts +9 -0
  67. package/packages/@monomind/cli/dist/src/commands/memory-transfer.js +372 -0
  68. package/packages/@monomind/cli/dist/src/commands/memory.d.ts +6 -0
  69. package/packages/@monomind/cli/dist/src/commands/memory.js +10 -1441
  70. package/packages/@monomind/cli/dist/src/commands/neural-core.d.ts +8 -0
  71. package/packages/@monomind/cli/dist/src/commands/neural-core.js +274 -0
  72. package/packages/@monomind/cli/dist/src/commands/neural-optimize.d.ts +7 -0
  73. package/packages/@monomind/cli/dist/src/commands/neural-optimize.js +332 -0
  74. package/packages/@monomind/cli/dist/src/commands/neural-registry.d.ts +7 -0
  75. package/packages/@monomind/cli/dist/src/commands/neural-registry.js +290 -0
  76. package/packages/@monomind/cli/dist/src/commands/neural.js +3 -974
  77. package/packages/@monomind/cli/dist/src/commands/platforms.js +327 -7
  78. package/packages/@monomind/cli/dist/src/commands/security-cve.d.ts +6 -0
  79. package/packages/@monomind/cli/dist/src/commands/security-cve.js +310 -0
  80. package/packages/@monomind/cli/dist/src/commands/security-misc.d.ts +9 -0
  81. package/packages/@monomind/cli/dist/src/commands/security-misc.js +293 -0
  82. package/packages/@monomind/cli/dist/src/commands/security-scan.d.ts +18 -0
  83. package/packages/@monomind/cli/dist/src/commands/security-scan.js +328 -0
  84. package/packages/@monomind/cli/dist/src/commands/security.js +3 -958
  85. package/packages/@monomind/cli/dist/src/commands/session.js +1 -1
  86. package/packages/@monomind/cli/dist/src/commands/swarm.js +23 -17
  87. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +77 -0
  88. package/packages/@monomind/cli/dist/src/parser.js +11 -6
  89. package/packages/@monomind/cli/dist/src/routing/llm-caller.js +1 -2
  90. package/packages/@monomind/cli/package.json +2 -3
  91. package/packages/@monomind/cli/scripts/understand-analyze.mjs +1 -1
@@ -24,7 +24,7 @@ capability:
24
24
  model_preference: opus
25
25
  termination: All identified vulnerabilities have remediation steps documented
26
26
  triggers:
27
- - pattern: "\\b(auth|jwt|session|oauth|saml|oidc|csrf|xss|sql.injection|ssrf|rce|lfi)\\b"
27
+ - pattern: "\\b(jwt|oauth|saml|oidc|csrf|xss|sql.injection|ssrf|rce|lfi|session\\W{0,3}(hijack|fixation|token\\W{0,3}theft)\\w*|auth\\w*\\W{0,3}(bypass|bug|vuln|flaw|exploit)\\w*)\\b"
28
28
  mode: "inject"
29
29
  - pattern: "CVE-\\d{4}-\\d+"
30
30
  mode: "inject"
@@ -325,6 +325,8 @@ RECENT_HIL=$(find . -maxdepth 3 \( -name "humaninloop*.md" -o -name "humaninloop
325
325
 
326
326
  **Skip this section if `tillend_mode` is not true.** Proceed to section 5.
327
327
 
328
+ **`LOOP_ASYNC_PENDING` escape hatch:** If the calling skill set `LOOP_ASYNC_PENDING=true` before invoking the postamble (because it spawned background agents whose results have not yet arrived in this turn), skip the empty-round check entirely — treat this round as non-empty and go directly to section 6 to schedule the next run. The next scheduled run will evaluate results from the agents that are currently in-flight. Output: `[tillend] Async work in flight — deferring empty-round check to run <next_rep>.`
329
+
328
330
  After each run in tillend mode, evaluate whether this run produced **zero findings and zero actions**. The loop stops only when a complete round finds nothing new and makes no changes — not when the AI predicts there is nothing left.
329
331
 
330
332
  **You (the AI running the loop) must now assess your own output from this run.** Answer these two questions:
@@ -459,6 +461,8 @@ curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
459
461
 
460
462
  ## Notes for Calling Commands
461
463
 
464
+ - **Async agents in loop mode**: In `--repeat` or `--tillend` mode, all subagents MUST be synchronous (`run_in_background: false` or omitted). The postamble's empty-round check runs in the same turn as the command — background agents complete in a later turn and their output is invisible to the check. If a skill must go async (e.g. it needs to spawn a long-running agent), set `LOOP_ASYNC_PENDING=true` before invoking the postamble; section 4 will skip the empty-round check and schedule the next run unconditionally. The next run will then evaluate the results.
465
+ - **Loop state is durable across turns**: The counter and all loop parameters survive across `ScheduleWakeup` fires via two mechanisms: (1) `--rep <N> --loop <ID>` is re-encoded in every ScheduleWakeup prompt, and (2) the `.monomind/loops/<ID>.json` file stores current state persistently. There is no in-memory counter — the counter is always re-read from the prompt flags or the file.
462
466
  - **`<command_slug>`**: lowercase command name without namespace (`build` for `/mastermind:build`, `mastermind-idea` for `/mastermind:idea`)
463
467
  - **Dashboard**: the monomind panel reads `.monomind/loops/*.json`; `type` field is `"repeat"` or `"tillend"`; HIL status shows as `"hil:pending"`
464
468
  - **Stopping a loop**: create `.monomind/loops/${LOOP_ID}.stop` or delete the `.json` file; the next wake-up detects it
@@ -347,6 +347,21 @@ curl -s -o /dev/null -X POST "${CTRL_URL}/api/mastermind/event" \
347
347
  '{type:"session:start",session:$sid,prompt:$prompt,mode:$mode,project:$proj,ts:(now*1000|floor)}')" || true
348
348
  ```
349
349
 
350
+ ### Step 3.5 — Design Gate
351
+
352
+ <HARD-GATE>
353
+ STOP before Step 4. If `domains_needed` includes `build` (or any code/feature/fix work):
354
+
355
+ 1. Invoke `Skill("mastermind:design")` NOW — pass `resolved_prompt` as the task description
356
+ 2. Do NOT write any code, do NOT spawn any domain managers, do NOT proceed to Step 4 until the design skill returns with an approved spec
357
+ 3. When design returns: save the approved spec as `build_spec` in `current.json`
358
+ 4. Only then continue to Step 4
359
+
360
+ If `domains_needed` contains ONLY non-build domains (marketing, sales, research, content, ops, finance, idea): skip this gate and go directly to Step 4.
361
+
362
+ Rationale: without a design gate, master spawns agents before the user has confirmed what to build. The design gate replicates the superpowers workflow: questions → approaches → spec approval → plan → execute.
363
+ </HARD-GATE>
364
+
350
365
  ### Step 4 — Decompose
351
366
 
352
367
  For each domain in `domains_needed`, assess complexity:
@@ -423,7 +438,39 @@ If mode = confirm: show plan and wait for user response. Valid responses:
423
438
  - Any modification (e.g. "add sales domain", "remove marketing") — apply the change, re-show the plan, wait again
424
439
  - "cancel" or "stop" — emit `session:complete` with `status: blocked`, reason "cancelled by user", then STOP
425
440
 
426
- If mode = auto: proceed immediately.
441
+ After the user says "go" (and `build` is in `domains_needed`), ask once:
442
+ > "For the build work: **subagents** (recommended — fresh agent per task with 2-stage review, like mastermind:taskdev) or **inline** (direct execution, mastermind:execute)?"
443
+
444
+ - "subagents" → `build_exec_mode = "taskdev"`
445
+ - "inline" → `build_exec_mode = "execute"`
446
+ - No answer / skipped → `build_exec_mode = "taskdev"` (default)
447
+
448
+ If mode = auto: `build_exec_mode = "taskdev"` (default).
449
+
450
+ **Persist `build_exec_mode` to `current.json`** (required — Phase C reads it from there):
451
+
452
+ ```bash
453
+ REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
454
+ _get_mono_dir() {
455
+ local w="${1:-$(pwd)}"
456
+ if [ -d "$w/.git" ]; then echo "$w/.git/monomind"; return; fi
457
+ if [ -f "$w/.git" ]; then
458
+ local m; m=$(grep '^gitdir:' "$w/.git" | sed 's/gitdir: *//')
459
+ [ -z "$m" ] && { echo "$w/.monomind"; return; }
460
+ [[ "$m" != /* ]] && m="$w/$m"
461
+ echo "$(dirname "$(dirname "$m")")/monomind"; return
462
+ fi
463
+ echo "$w/.monomind"
464
+ }
465
+ MONO_DIR=$(_get_mono_dir "$REPO_ROOT")
466
+ SESSION_STATE="$MONO_DIR/sessions/current.json"
467
+ # LLM: replace EXEC_MODE_VALUE with the resolved value: taskdev or execute
468
+ build_exec_mode="EXEC_MODE_VALUE"
469
+ [ "$build_exec_mode" = "EXEC_MODE_VALUE" ] && build_exec_mode="taskdev"
470
+ jq --arg m "$build_exec_mode" '. + {build_exec_mode: $m}' \
471
+ "$SESSION_STATE" > "$SESSION_STATE.tmp" && mv "$SESSION_STATE.tmp" "$SESSION_STATE"
472
+ echo "build_exec_mode=$build_exec_mode written to current.json"
473
+ ```
427
474
 
428
475
  ### Step 6 — Monotask Setup
429
476
 
@@ -678,12 +725,15 @@ for domain in $(jq -r '.domains_needed[]? // empty' "$SESSION_STATE" | grep -v '
678
725
  if [ -z "$board_id" ]; then
679
726
  echo "WARN: DOMAIN=$domain has no board_id — Step 6 may not have run or monotask is missing. Task agent will run without board tracking."
680
727
  fi
728
+ exec_mode=""
729
+ [ "$domain" = "build" ] && exec_mode=$(jq -r '.build_exec_mode // "taskdev"' "$SESSION_STATE")
681
730
  echo "DOMAIN=$domain \
682
731
  MANAGER=$(jq -r --arg d "$domain" '.domain_managers[$d] // "coordinator"' "$SESSION_STATE") \
683
732
  BOARD=$board_id \
684
733
  TODO=$(jq -r --arg d "$domain" '.todo_cols[$d] // ""' "$SESSION_STATE") \
685
734
  DOING=$(jq -r --arg d "$domain" '.doing_cols[$d] // ""' "$SESSION_STATE") \
686
735
  DONE=$(jq -r --arg d "$domain" '.done_cols[$d] // ""' "$SESSION_STATE") \
736
+ EXEC_MODE=$exec_mode \
687
737
  GOAL=$(jq -r --arg d "$domain" '.domain_goals[$d] // .prompt' "$SESSION_STATE" | tr -d '\n')"
688
738
  done
689
739
  ```
@@ -698,6 +748,7 @@ Each Task call must include a complete briefing following the Monotask Task Brie
698
748
  - Instruction to create monotask cards directly using `monotask card create $BOARD_ID $COL_TODO_ID "<title>" --json` for all sub-tasks
699
749
  - Instruction to use `Skill("mastermind:do")` to execute tasks (Task agents have Skill tool access — do NOT use slash command syntax)
700
750
  - Instruction to spawn specialized agents using the domain-appropriate swarm topology
751
+ - **For the `build` domain only:** include `build_exec_mode` (value: `"taskdev"` or `"execute"`) and instruct the manager: "Use `Skill("mastermind:taskdev")` if build_exec_mode is `taskdev`, or `Skill("mastermind:execute")` if `execute`. This was chosen by the user in Step 5."
701
752
  - Instruction to return the unified output schema when done
702
753
 
703
754
  Example Task call for Development Manager. Substitute all **pre-known** `<…>` placeholders (project_name, SESSION_ID, board/col IDs, goals, manager name) before calling Task. Placeholders like `<status>`, `<path1>`, `<action1>` are filled at runtime by the spawned agent — do not attempt to substitute them. `subagent_type` is the **string value** of `$domain_manager_build` (e.g. `"Backend Architect"`), not a variable reference.
@@ -1 +1 @@
1
- {"sessionId":"db24c21a-e929-4aba-911b-4cdb7fc5c3d5","pid":2346,"procStart":"Wed Jun 24 16:31:36 2026","acquiredAt":1782380072591}
1
+ {"sessionId":"361cc64c-bfbe-4774-a739-e3b982e26b21","pid":85888,"procStart":"Thu Jul 2 11:48:13 2026","acquiredAt":1782999721096}
@@ -27,6 +27,8 @@ If the stop file exists:
27
27
 
28
28
  ## Step 2 — Tillend termination check (tillend mode only — skip for fixed-count)
29
29
 
30
+ **`LOOP_ASYNC_PENDING` escape hatch:** If the calling skill set `LOOP_ASYNC_PENDING=true` before invoking this postamble (because it spawned background agents whose results have not yet arrived), skip the empty-round check and go directly to Step 4 to schedule the next run. Output: `[tillend] Async work in flight — deferring empty-round check to run <next_rep>.`
31
+
30
32
  Evaluate now whether this run produced **zero findings AND zero actions**:
31
33
 
32
34
  - **Findings**: issues found, problems detected, items flagged, security vulnerabilities, tasks discovered, errors reported
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "monomind",
3
- "version": "1.17.0",
3
+ "version": "1.17.1",
4
4
  "description": "Monomind - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -24,7 +24,7 @@ capability:
24
24
  model_preference: opus
25
25
  termination: All identified vulnerabilities have remediation steps documented
26
26
  triggers:
27
- - pattern: "\\b(auth|jwt|session|oauth|saml|oidc|csrf|xss|sql.injection|ssrf|rce|lfi)\\b"
27
+ - pattern: "\\b(jwt|oauth|saml|oidc|csrf|xss|sql.injection|ssrf|rce|lfi|session\\W{0,3}(hijack|fixation|token\\W{0,3}theft)\\w*|auth\\w*\\W{0,3}(bypass|bug|vuln|flaw|exploit)\\w*)\\b"
28
28
  mode: "inject"
29
29
  - pattern: "CVE-\\d{4}-\\d+"
30
30
  mode: "inject"
@@ -325,6 +325,8 @@ RECENT_HIL=$(find . -maxdepth 3 \( -name "humaninloop*.md" -o -name "humaninloop
325
325
 
326
326
  **Skip this section if `tillend_mode` is not true.** Proceed to section 5.
327
327
 
328
+ **`LOOP_ASYNC_PENDING` escape hatch:** If the calling skill set `LOOP_ASYNC_PENDING=true` before invoking the postamble (because it spawned background agents whose results have not yet arrived in this turn), skip the empty-round check entirely — treat this round as non-empty and go directly to section 6 to schedule the next run. The next scheduled run will evaluate results from the agents that are currently in-flight. Output: `[tillend] Async work in flight — deferring empty-round check to run <next_rep>.`
329
+
328
330
  After each run in tillend mode, evaluate whether this run produced **zero findings and zero actions**. The loop stops only when a complete round finds nothing new and makes no changes — not when the AI predicts there is nothing left.
329
331
 
330
332
  **You (the AI running the loop) must now assess your own output from this run.** Answer these two questions:
@@ -459,6 +461,8 @@ curl -s -X POST "${CTRL_URL}/api/mastermind/event" \
459
461
 
460
462
  ## Notes for Calling Commands
461
463
 
464
+ - **Async agents in loop mode**: In `--repeat` or `--tillend` mode, all subagents MUST be synchronous (`run_in_background: false` or omitted). The postamble's empty-round check runs in the same turn as the command — background agents complete in a later turn and their output is invisible to the check. If a skill must go async (e.g. it needs to spawn a long-running agent), set `LOOP_ASYNC_PENDING=true` before invoking the postamble; section 4 will skip the empty-round check and schedule the next run unconditionally. The next run will then evaluate the results.
465
+ - **Loop state is durable across turns**: The counter and all loop parameters survive across `ScheduleWakeup` fires via two mechanisms: (1) `--rep <N> --loop <ID>` is re-encoded in every ScheduleWakeup prompt, and (2) the `.monomind/loops/<ID>.json` file stores current state persistently. There is no in-memory counter — the counter is always re-read from the prompt flags or the file.
462
466
  - **`<command_slug>`**: lowercase command name without namespace (`build` for `/mastermind:build`, `mastermind-idea` for `/mastermind:idea`)
463
467
  - **Dashboard**: the monomind panel reads `.monomind/loops/*.json`; `type` field is `"repeat"` or `"tillend"`; HIL status shows as `"hil:pending"`
464
468
  - **Stopping a loop**: create `.monomind/loops/${LOOP_ID}.stop` or delete the `.json` file; the next wake-up detects it
@@ -347,6 +347,21 @@ curl -s -o /dev/null -X POST "${CTRL_URL}/api/mastermind/event" \
347
347
  '{type:"session:start",session:$sid,prompt:$prompt,mode:$mode,project:$proj,ts:(now*1000|floor)}')" || true
348
348
  ```
349
349
 
350
+ ### Step 3.5 — Design Gate
351
+
352
+ <HARD-GATE>
353
+ STOP before Step 4. If `domains_needed` includes `build` (or any code/feature/fix work):
354
+
355
+ 1. Invoke `Skill("mastermind:design")` NOW — pass `resolved_prompt` as the task description
356
+ 2. Do NOT write any code, do NOT spawn any domain managers, do NOT proceed to Step 4 until the design skill returns with an approved spec
357
+ 3. When design returns: save the approved spec as `build_spec` in `current.json`
358
+ 4. Only then continue to Step 4
359
+
360
+ If `domains_needed` contains ONLY non-build domains (marketing, sales, research, content, ops, finance, idea): skip this gate and go directly to Step 4.
361
+
362
+ Rationale: without a design gate, master spawns agents before the user has confirmed what to build. The design gate replicates the superpowers workflow: questions → approaches → spec approval → plan → execute.
363
+ </HARD-GATE>
364
+
350
365
  ### Step 4 — Decompose
351
366
 
352
367
  For each domain in `domains_needed`, assess complexity:
@@ -423,7 +438,39 @@ If mode = confirm: show plan and wait for user response. Valid responses:
423
438
  - Any modification (e.g. "add sales domain", "remove marketing") — apply the change, re-show the plan, wait again
424
439
  - "cancel" or "stop" — emit `session:complete` with `status: blocked`, reason "cancelled by user", then STOP
425
440
 
426
- If mode = auto: proceed immediately.
441
+ After the user says "go" (and `build` is in `domains_needed`), ask once:
442
+ > "For the build work: **subagents** (recommended — fresh agent per task with 2-stage review, like mastermind:taskdev) or **inline** (direct execution, mastermind:execute)?"
443
+
444
+ - "subagents" → `build_exec_mode = "taskdev"`
445
+ - "inline" → `build_exec_mode = "execute"`
446
+ - No answer / skipped → `build_exec_mode = "taskdev"` (default)
447
+
448
+ If mode = auto: `build_exec_mode = "taskdev"` (default).
449
+
450
+ **Persist `build_exec_mode` to `current.json`** (required — Phase C reads it from there):
451
+
452
+ ```bash
453
+ REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
454
+ _get_mono_dir() {
455
+ local w="${1:-$(pwd)}"
456
+ if [ -d "$w/.git" ]; then echo "$w/.git/monomind"; return; fi
457
+ if [ -f "$w/.git" ]; then
458
+ local m; m=$(grep '^gitdir:' "$w/.git" | sed 's/gitdir: *//')
459
+ [ -z "$m" ] && { echo "$w/.monomind"; return; }
460
+ [[ "$m" != /* ]] && m="$w/$m"
461
+ echo "$(dirname "$(dirname "$m")")/monomind"; return
462
+ fi
463
+ echo "$w/.monomind"
464
+ }
465
+ MONO_DIR=$(_get_mono_dir "$REPO_ROOT")
466
+ SESSION_STATE="$MONO_DIR/sessions/current.json"
467
+ # LLM: replace EXEC_MODE_VALUE with the resolved value: taskdev or execute
468
+ build_exec_mode="EXEC_MODE_VALUE"
469
+ [ "$build_exec_mode" = "EXEC_MODE_VALUE" ] && build_exec_mode="taskdev"
470
+ jq --arg m "$build_exec_mode" '. + {build_exec_mode: $m}' \
471
+ "$SESSION_STATE" > "$SESSION_STATE.tmp" && mv "$SESSION_STATE.tmp" "$SESSION_STATE"
472
+ echo "build_exec_mode=$build_exec_mode written to current.json"
473
+ ```
427
474
 
428
475
  ### Step 6 — Monotask Setup
429
476
 
@@ -678,12 +725,15 @@ for domain in $(jq -r '.domains_needed[]? // empty' "$SESSION_STATE" | grep -v '
678
725
  if [ -z "$board_id" ]; then
679
726
  echo "WARN: DOMAIN=$domain has no board_id — Step 6 may not have run or monotask is missing. Task agent will run without board tracking."
680
727
  fi
728
+ exec_mode=""
729
+ [ "$domain" = "build" ] && exec_mode=$(jq -r '.build_exec_mode // "taskdev"' "$SESSION_STATE")
681
730
  echo "DOMAIN=$domain \
682
731
  MANAGER=$(jq -r --arg d "$domain" '.domain_managers[$d] // "coordinator"' "$SESSION_STATE") \
683
732
  BOARD=$board_id \
684
733
  TODO=$(jq -r --arg d "$domain" '.todo_cols[$d] // ""' "$SESSION_STATE") \
685
734
  DOING=$(jq -r --arg d "$domain" '.doing_cols[$d] // ""' "$SESSION_STATE") \
686
735
  DONE=$(jq -r --arg d "$domain" '.done_cols[$d] // ""' "$SESSION_STATE") \
736
+ EXEC_MODE=$exec_mode \
687
737
  GOAL=$(jq -r --arg d "$domain" '.domain_goals[$d] // .prompt' "$SESSION_STATE" | tr -d '\n')"
688
738
  done
689
739
  ```
@@ -698,6 +748,7 @@ Each Task call must include a complete briefing following the Monotask Task Brie
698
748
  - Instruction to create monotask cards directly using `monotask card create $BOARD_ID $COL_TODO_ID "<title>" --json` for all sub-tasks
699
749
  - Instruction to use `Skill("mastermind:do")` to execute tasks (Task agents have Skill tool access — do NOT use slash command syntax)
700
750
  - Instruction to spawn specialized agents using the domain-appropriate swarm topology
751
+ - **For the `build` domain only:** include `build_exec_mode` (value: `"taskdev"` or `"execute"`) and instruct the manager: "Use `Skill("mastermind:taskdev")` if build_exec_mode is `taskdev`, or `Skill("mastermind:execute")` if `execute`. This was chosen by the user in Step 5."
701
752
  - Instruction to return the unified output schema when done
702
753
 
703
754
  Example Task call for Development Manager. Substitute all **pre-known** `<…>` placeholders (project_name, SESSION_ID, board/col IDs, goals, manager name) before calling Task. Placeholders like `<status>`, `<path1>`, `<action1>` are filled at runtime by the spawned agent — do not attempt to substitute them. `subagent_type` is the **string value** of `$domain_manager_build` (e.g. `"Backend Architect"`), not a variable reference.
@@ -27,6 +27,8 @@ If the stop file exists:
27
27
 
28
28
  ## Step 2 — Tillend termination check (tillend mode only — skip for fixed-count)
29
29
 
30
+ **`LOOP_ASYNC_PENDING` escape hatch:** If the calling skill set `LOOP_ASYNC_PENDING=true` before invoking this postamble (because it spawned background agents whose results have not yet arrived), skip the empty-round check and go directly to Step 4 to schedule the next run. Output: `[tillend] Async work in flight — deferring empty-round check to run <next_rep>.`
31
+
30
32
  Evaluate now whether this run produced **zero findings AND zero actions**:
31
33
 
32
34
  - **Findings**: issues found, problems detected, items flagged, security vulnerabilities, tasks discovered, errors reported
@@ -1,85 +1,68 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- // Shared mock instance so tests can override `create` per-test
3
- const mockCreate = vi.fn().mockResolvedValue({
4
- content: [{
5
- type: 'text',
6
- text: JSON.stringify({
7
- id: 'linkedin:comment_post',
8
- platform: 'linkedin',
9
- name: 'Comment on Post',
10
- params: ['post_url', 'text'],
11
- steps: [
12
- { type: 'navigate', url: '{{params.post_url}}' },
13
- { type: 'find', selectors: ['.comment-box'], as: 'box' },
14
- { type: 'click', target: '{{box}}' },
15
- { type: 'type', target: '{{box}}', text: '{{params.text}}', humanDelay: true },
16
- { type: 'wait', condition: 'network_idle', timeout: 3000 },
17
- ],
18
- }),
19
- }],
2
+ import { EventEmitter } from 'events';
3
+ // Mock child_process.spawn so tests don't invoke the real claude CLI
4
+ vi.mock('child_process', async (importOriginal) => {
5
+ const actual = await importOriginal();
6
+ return { ...actual, spawn: vi.fn() };
20
7
  });
21
- // Mock Anthropic SDK before importing analyzer
22
- vi.mock('@anthropic-ai/sdk', () => {
23
- const MockAnthropic = vi.fn(function () {
24
- this.messages = { create: mockCreate };
8
+ async function makeSpawnMock(stdout, exitCode = 0) {
9
+ const { spawn } = vi.mocked(await import('child_process'));
10
+ spawn.mockImplementationOnce(() => {
11
+ const proc = new EventEmitter();
12
+ proc.stdout = new EventEmitter();
13
+ proc.stderr = new EventEmitter();
14
+ proc.kill = vi.fn();
15
+ setTimeout(() => {
16
+ proc.stdout.emit('data', Buffer.from(stdout));
17
+ proc.emit('close', exitCode);
18
+ }, 0);
19
+ return proc;
25
20
  });
26
- return { default: MockAnthropic };
27
- });
21
+ }
22
+ const VALID_ACTION_DEF = {
23
+ id: 'linkedin:comment_post',
24
+ platform: 'linkedin',
25
+ name: 'Comment on Post',
26
+ params: ['post_url', 'text'],
27
+ steps: [
28
+ { type: 'navigate', url: '{{params.post_url}}' },
29
+ { type: 'find', selectors: ['.comment-box'], as: 'box' },
30
+ { type: 'click', target: '{{box}}' },
31
+ { type: 'type', target: '{{box}}', text: '{{params.text}}', humanDelay: true },
32
+ { type: 'wait', condition: 'network_idle', timeout: 3000 },
33
+ ],
34
+ };
28
35
  function mockPage(url = 'https://linkedin.com/feed', title = 'LinkedIn') {
29
36
  return {
30
37
  url: vi.fn().mockResolvedValue(url),
31
38
  evaluate: vi.fn().mockImplementation((expr) => {
32
39
  if (expr === 'document.title')
33
40
  return Promise.resolve(title);
34
- return Promise.resolve('[]'); // empty elements for DOM capture
41
+ return Promise.resolve('[]');
35
42
  }),
36
43
  };
37
44
  }
38
45
  describe('analyzePageForAction', () => {
39
46
  let analyzePageForAction;
40
47
  beforeEach(async () => {
41
- mockCreate.mockResolvedValue({
42
- content: [{
43
- type: 'text',
44
- text: JSON.stringify({
45
- id: 'linkedin:comment_post',
46
- platform: 'linkedin',
47
- name: 'Comment on Post',
48
- params: ['post_url', 'text'],
49
- steps: [
50
- { type: 'navigate', url: '{{params.post_url}}' },
51
- { type: 'find', selectors: ['.comment-box'], as: 'box' },
52
- { type: 'click', target: '{{box}}' },
53
- { type: 'type', target: '{{box}}', text: '{{params.text}}', humanDelay: true },
54
- { type: 'wait', condition: 'network_idle', timeout: 3000 },
55
- ],
56
- }),
57
- }],
58
- });
48
+ vi.resetModules();
59
49
  const mod = await import('@monoes/monobrowse');
60
50
  analyzePageForAction = mod.analyzePageForAction;
61
- });
62
- it('returns a valid ActionDef from mocked Claude response', async () => {
63
- const page = mockPage();
64
- // Pass a dummy key via options (Anthropic SDK is mocked no real call is made)
65
- const result = await analyzePageForAction(page, 'comment on a LinkedIn post', { apiKey: 'sk-test' });
51
+ }, 20000); // monolean: re-importing the workspace package can exceed the 10s default under full-suite parallel load
52
+ it('returns a valid ActionDef from mocked claude --print response', async () => {
53
+ await makeSpawnMock(JSON.stringify(VALID_ACTION_DEF));
54
+ const result = await analyzePageForAction(mockPage(), 'comment on a LinkedIn post');
66
55
  expect(result.id).toBe('linkedin:comment_post');
67
56
  expect(result.steps).toHaveLength(5);
68
57
  expect(result.params).toContain('text');
69
58
  });
70
- it('throws on invalid JSON from Claude', async () => {
71
- mockCreate.mockResolvedValueOnce({
72
- content: [{ type: 'text', text: 'not json at all' }],
73
- });
74
- const page = mockPage();
75
- await expect(analyzePageForAction(page, 'test', { apiKey: 'sk-test' })).rejects.toThrow('invalid JSON');
59
+ it('throws on invalid JSON from claude', async () => {
60
+ await makeSpawnMock('not json at all');
61
+ await expect(analyzePageForAction(mockPage(), 'test')).rejects.toThrow('invalid JSON');
76
62
  });
77
63
  it('throws when ActionDef is missing id', async () => {
78
- mockCreate.mockResolvedValueOnce({
79
- content: [{ type: 'text', text: JSON.stringify({ steps: [] }) }],
80
- });
81
- const page = mockPage();
82
- await expect(analyzePageForAction(page, 'test', { apiKey: 'sk-test' })).rejects.toThrow('invalid ActionDef');
64
+ await makeSpawnMock(JSON.stringify({ steps: [] }));
65
+ await expect(analyzePageForAction(mockPage(), 'test')).rejects.toThrow('invalid ActionDef');
83
66
  });
84
67
  });
85
68
  //# sourceMappingURL=browse-analyzer.test.js.map
@@ -30,6 +30,24 @@ export function getDashboardServer(port = DEFAULT_PORT) {
30
30
  res.end(JSON.stringify(recentRuns));
31
31
  return;
32
32
  }
33
+ if (req.method === 'POST' && req.url === '/api/mastermind/event') {
34
+ const chunks = [];
35
+ req.on('data', (chunk) => chunks.push(chunk));
36
+ req.on('end', () => {
37
+ try {
38
+ const body = Buffer.concat(chunks).toString('utf8');
39
+ JSON.parse(body); // validate before broadcast
40
+ for (const client of clients) {
41
+ if (client.readyState === client.OPEN)
42
+ client.send(body);
43
+ }
44
+ }
45
+ catch { }
46
+ res.writeHead(200, { 'Content-Type': 'application/json' });
47
+ res.end('{"ok":true}');
48
+ });
49
+ return;
50
+ }
33
51
  res.writeHead(404);
34
52
  res.end();
35
53
  });