@monoes/monomindcli 2.10.10 → 2.10.14

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 (243) hide show
  1. package/.claude/commands/mastermind/brain.md +14 -14
  2. package/.claude/commands/mastermind/help.md +2 -2
  3. package/.claude/commands/mastermind/master.md +24 -19
  4. package/.claude/commands/mastermind/memory.md +8 -8
  5. package/.claude/commands/mastermind/monoswarm.md +4 -4
  6. package/.claude/commands/mastermind.md +7 -7
  7. package/.claude/commands/truth/start.md +3 -3
  8. package/.claude/helpers/handlers/gates-handler.cjs +13 -1
  9. package/.claude/skills/mastermind/SKILL.md +7 -16
  10. package/.claude/skills/mastermind-debug/SKILL.md +3 -3
  11. package/.claude/skills/mastermind-design/SKILL.md +2 -0
  12. package/.claude/skills/mastermind-execute/SKILL.md +66 -11
  13. package/.claude/skills/mastermind-idea/SKILL.md +9 -2
  14. package/.claude/skills/mastermind-intake/SKILL.md +31 -7
  15. package/.claude/skills/mastermind-issue-detail/SKILL.md +70 -16
  16. package/.claude/skills/mastermind-issues/SKILL.md +111 -16
  17. package/.claude/skills/mastermind-liveness/SKILL.md +96 -26
  18. package/.claude/skills/mastermind-my-issues/SKILL.md +40 -8
  19. package/.claude/skills/mastermind-org/SKILL.md +2 -0
  20. package/.claude/skills/mastermind-plan/SKILL.md +7 -16
  21. package/.claude/skills/mastermind-plan-to-tasks/SKILL.md +132 -24
  22. package/.claude/skills/mastermind-protocol/SKILL.md +33 -22
  23. package/.claude/skills/mastermind-runorg/SKILL.md +22 -3
  24. package/.claude/skills/mastermind-skill-builder/SKILL.md +1 -1
  25. package/.claude/skills/mastermind-tasks/SKILL.md +5 -0
  26. package/.claude/skills/mastermind-techport/SKILL.md +1 -1
  27. package/.claude/skills/monodesign/scripts/detector/engines/browser/drivers.mjs +56 -15
  28. package/.claude/skills/performance-analysis/SKILL.md +1 -1
  29. package/.claude/skills/verification-quality/SKILL.md +2 -3
  30. package/README.md +2 -2
  31. package/dist/src/commands/doc.js +2 -2
  32. package/dist/src/commands/doc.js.map +1 -1
  33. package/dist/src/commands/doctor-project-checks.d.ts.map +1 -1
  34. package/dist/src/commands/doctor-project-checks.js +45 -2
  35. package/dist/src/commands/doctor-project-checks.js.map +1 -1
  36. package/dist/src/commands/memory-crud.d.ts.map +1 -1
  37. package/dist/src/commands/memory-crud.js +13 -2
  38. package/dist/src/commands/memory-crud.js.map +1 -1
  39. package/dist/src/commands/memory.js +1 -1
  40. package/dist/src/commands/memory.js.map +1 -1
  41. package/dist/src/commands/monograph.d.ts.map +1 -1
  42. package/dist/src/commands/monograph.js +11 -4
  43. package/dist/src/commands/monograph.js.map +1 -1
  44. package/dist/src/commands/org-observe.d.ts.map +1 -1
  45. package/dist/src/commands/org-observe.js +56 -6
  46. package/dist/src/commands/org-observe.js.map +1 -1
  47. package/dist/src/commands/org.d.ts +26 -0
  48. package/dist/src/commands/org.d.ts.map +1 -1
  49. package/dist/src/commands/org.js +144 -28
  50. package/dist/src/commands/org.js.map +1 -1
  51. package/dist/src/init/executor.d.ts.map +1 -1
  52. package/dist/src/init/executor.js +10 -9
  53. package/dist/src/init/executor.js.map +1 -1
  54. package/dist/src/init/settings-generator.d.ts.map +1 -1
  55. package/dist/src/init/settings-generator.js.map +1 -1
  56. package/dist/src/init/upgrade.d.ts.map +1 -1
  57. package/dist/src/init/upgrade.js +29 -0
  58. package/dist/src/init/upgrade.js.map +1 -1
  59. package/dist/src/init/write-codex.d.ts.map +1 -1
  60. package/dist/src/init/write-codex.js.map +1 -1
  61. package/dist/src/knowledge/document-pipeline.d.ts +5 -0
  62. package/dist/src/knowledge/document-pipeline.d.ts.map +1 -1
  63. package/dist/src/knowledge/document-pipeline.js +32 -16
  64. package/dist/src/knowledge/document-pipeline.js.map +1 -1
  65. package/dist/src/mcp-tools/hooks-routing.d.ts +9 -0
  66. package/dist/src/mcp-tools/hooks-routing.d.ts.map +1 -1
  67. package/dist/src/mcp-tools/hooks-routing.js +12 -1
  68. package/dist/src/mcp-tools/hooks-routing.js.map +1 -1
  69. package/dist/src/mcp-tools/knowledge-tools.d.ts.map +1 -1
  70. package/dist/src/mcp-tools/knowledge-tools.js +105 -13
  71. package/dist/src/mcp-tools/knowledge-tools.js.map +1 -1
  72. package/dist/src/mcp-tools/memory-tools.d.ts +13 -0
  73. package/dist/src/mcp-tools/memory-tools.d.ts.map +1 -1
  74. package/dist/src/mcp-tools/memory-tools.js +257 -31
  75. package/dist/src/mcp-tools/memory-tools.js.map +1 -1
  76. package/dist/src/mcp-tools/monograph/health-tools.d.ts.map +1 -1
  77. package/dist/src/mcp-tools/monograph/health-tools.js +99 -42
  78. package/dist/src/mcp-tools/monograph/health-tools.js.map +1 -1
  79. package/dist/src/mcp-tools/monograph/impact-tools.d.ts.map +1 -1
  80. package/dist/src/mcp-tools/monograph/impact-tools.js +123 -51
  81. package/dist/src/mcp-tools/monograph/impact-tools.js.map +1 -1
  82. package/dist/src/mcp-tools/monograph/query-tools.d.ts.map +1 -1
  83. package/dist/src/mcp-tools/monograph/query-tools.js +113 -96
  84. package/dist/src/mcp-tools/monograph/query-tools.js.map +1 -1
  85. package/dist/src/mcp-tools/monograph/shared.d.ts +37 -4
  86. package/dist/src/mcp-tools/monograph/shared.d.ts.map +1 -1
  87. package/dist/src/mcp-tools/monograph/shared.js +75 -56
  88. package/dist/src/mcp-tools/monograph/shared.js.map +1 -1
  89. package/dist/src/memory/memory-bridge.d.ts +60 -1
  90. package/dist/src/memory/memory-bridge.d.ts.map +1 -1
  91. package/dist/src/memory/memory-bridge.js +172 -42
  92. package/dist/src/memory/memory-bridge.js.map +1 -1
  93. package/dist/src/memory/memory-kg.d.ts +495 -28
  94. package/dist/src/memory/memory-kg.d.ts.map +1 -1
  95. package/dist/src/memory/memory-kg.js +2186 -251
  96. package/dist/src/memory/memory-kg.js.map +1 -1
  97. package/dist/src/memory/query-router.d.ts +51 -0
  98. package/dist/src/memory/query-router.d.ts.map +1 -1
  99. package/dist/src/memory/query-router.js +38 -2
  100. package/dist/src/memory/query-router.js.map +1 -1
  101. package/dist/src/orgrt/agent-exec.d.ts.map +1 -1
  102. package/dist/src/orgrt/agent-exec.js +7 -0
  103. package/dist/src/orgrt/agent-exec.js.map +1 -1
  104. package/dist/src/orgrt/agent-runner.d.ts +26 -1
  105. package/dist/src/orgrt/agent-runner.d.ts.map +1 -1
  106. package/dist/src/orgrt/agent-runner.js +73 -22
  107. package/dist/src/orgrt/agent-runner.js.map +1 -1
  108. package/dist/src/orgrt/antigravity-runner.d.ts +1 -1
  109. package/dist/src/orgrt/antigravity-runner.d.ts.map +1 -1
  110. package/dist/src/orgrt/antigravity-runner.js +36 -19
  111. package/dist/src/orgrt/antigravity-runner.js.map +1 -1
  112. package/dist/src/orgrt/approvals.d.ts +26 -2
  113. package/dist/src/orgrt/approvals.d.ts.map +1 -1
  114. package/dist/src/orgrt/approvals.js +67 -7
  115. package/dist/src/orgrt/approvals.js.map +1 -1
  116. package/dist/src/orgrt/broker.d.ts +21 -2
  117. package/dist/src/orgrt/broker.d.ts.map +1 -1
  118. package/dist/src/orgrt/broker.js +56 -8
  119. package/dist/src/orgrt/broker.js.map +1 -1
  120. package/dist/src/orgrt/bus.d.ts +8 -0
  121. package/dist/src/orgrt/bus.d.ts.map +1 -1
  122. package/dist/src/orgrt/bus.js +27 -0
  123. package/dist/src/orgrt/bus.js.map +1 -1
  124. package/dist/src/orgrt/checkpoint-ops.d.ts.map +1 -1
  125. package/dist/src/orgrt/checkpoint-ops.js +15 -5
  126. package/dist/src/orgrt/checkpoint-ops.js.map +1 -1
  127. package/dist/src/orgrt/checkpoint.d.ts +42 -4
  128. package/dist/src/orgrt/checkpoint.d.ts.map +1 -1
  129. package/dist/src/orgrt/checkpoint.js +75 -4
  130. package/dist/src/orgrt/checkpoint.js.map +1 -1
  131. package/dist/src/orgrt/codex-runner.d.ts +1 -1
  132. package/dist/src/orgrt/codex-runner.d.ts.map +1 -1
  133. package/dist/src/orgrt/codex-runner.js +50 -23
  134. package/dist/src/orgrt/codex-runner.js.map +1 -1
  135. package/dist/src/orgrt/copilot-runner.d.ts +24 -2
  136. package/dist/src/orgrt/copilot-runner.d.ts.map +1 -1
  137. package/dist/src/orgrt/copilot-runner.js +257 -157
  138. package/dist/src/orgrt/copilot-runner.js.map +1 -1
  139. package/dist/src/orgrt/cross-org.d.ts +10 -3
  140. package/dist/src/orgrt/cross-org.d.ts.map +1 -1
  141. package/dist/src/orgrt/cross-org.js +225 -10
  142. package/dist/src/orgrt/cross-org.js.map +1 -1
  143. package/dist/src/orgrt/crush-runner.d.ts +22 -2
  144. package/dist/src/orgrt/crush-runner.d.ts.map +1 -1
  145. package/dist/src/orgrt/crush-runner.js +227 -120
  146. package/dist/src/orgrt/crush-runner.js.map +1 -1
  147. package/dist/src/orgrt/daemon.d.ts +77 -7
  148. package/dist/src/orgrt/daemon.d.ts.map +1 -1
  149. package/dist/src/orgrt/daemon.js +989 -385
  150. package/dist/src/orgrt/daemon.js.map +1 -1
  151. package/dist/src/orgrt/decisions.d.ts +2 -2
  152. package/dist/src/orgrt/decisions.d.ts.map +1 -1
  153. package/dist/src/orgrt/decisions.js +90 -18
  154. package/dist/src/orgrt/decisions.js.map +1 -1
  155. package/dist/src/orgrt/grok-runner.d.ts +29 -3
  156. package/dist/src/orgrt/grok-runner.d.ts.map +1 -1
  157. package/dist/src/orgrt/grok-runner.js +286 -150
  158. package/dist/src/orgrt/grok-runner.js.map +1 -1
  159. package/dist/src/orgrt/kimicode-runner.d.ts +5 -5
  160. package/dist/src/orgrt/kimicode-runner.d.ts.map +1 -1
  161. package/dist/src/orgrt/kimicode-runner.js +53 -32
  162. package/dist/src/orgrt/kimicode-runner.js.map +1 -1
  163. package/dist/src/orgrt/mailbox.d.ts +15 -0
  164. package/dist/src/orgrt/mailbox.d.ts.map +1 -1
  165. package/dist/src/orgrt/mailbox.js +29 -1
  166. package/dist/src/orgrt/mailbox.js.map +1 -1
  167. package/dist/src/orgrt/migrate.d.ts.map +1 -1
  168. package/dist/src/orgrt/migrate.js +8 -5
  169. package/dist/src/orgrt/migrate.js.map +1 -1
  170. package/dist/src/orgrt/opencode-runner.d.ts +1 -1
  171. package/dist/src/orgrt/opencode-runner.d.ts.map +1 -1
  172. package/dist/src/orgrt/opencode-runner.js +29 -3
  173. package/dist/src/orgrt/opencode-runner.js.map +1 -1
  174. package/dist/src/orgrt/org-memory.d.ts +19 -3
  175. package/dist/src/orgrt/org-memory.d.ts.map +1 -1
  176. package/dist/src/orgrt/org-memory.js +110 -38
  177. package/dist/src/orgrt/org-memory.js.map +1 -1
  178. package/dist/src/orgrt/pi-rpc-runner.d.ts +3 -1
  179. package/dist/src/orgrt/pi-rpc-runner.d.ts.map +1 -1
  180. package/dist/src/orgrt/pi-rpc-runner.js +35 -3
  181. package/dist/src/orgrt/pi-rpc-runner.js.map +1 -1
  182. package/dist/src/orgrt/pi-runner.d.ts +25 -2
  183. package/dist/src/orgrt/pi-runner.d.ts.map +1 -1
  184. package/dist/src/orgrt/pi-runner.js +271 -152
  185. package/dist/src/orgrt/pi-runner.js.map +1 -1
  186. package/dist/src/orgrt/policy.d.ts +1 -0
  187. package/dist/src/orgrt/policy.d.ts.map +1 -1
  188. package/dist/src/orgrt/policy.js +199 -39
  189. package/dist/src/orgrt/policy.js.map +1 -1
  190. package/dist/src/orgrt/provider.d.ts +4 -0
  191. package/dist/src/orgrt/provider.d.ts.map +1 -1
  192. package/dist/src/orgrt/provider.js +16 -0
  193. package/dist/src/orgrt/provider.js.map +1 -1
  194. package/dist/src/orgrt/qwen-rpc-runner.d.ts +3 -1
  195. package/dist/src/orgrt/qwen-rpc-runner.d.ts.map +1 -1
  196. package/dist/src/orgrt/qwen-rpc-runner.js +35 -3
  197. package/dist/src/orgrt/qwen-rpc-runner.js.map +1 -1
  198. package/dist/src/orgrt/qwen-runner.d.ts +30 -3
  199. package/dist/src/orgrt/qwen-runner.d.ts.map +1 -1
  200. package/dist/src/orgrt/qwen-runner.js +282 -142
  201. package/dist/src/orgrt/qwen-runner.js.map +1 -1
  202. package/dist/src/orgrt/role-slot.d.ts +88 -0
  203. package/dist/src/orgrt/role-slot.d.ts.map +1 -0
  204. package/dist/src/orgrt/role-slot.js +133 -0
  205. package/dist/src/orgrt/role-slot.js.map +1 -0
  206. package/dist/src/orgrt/runtime-options.d.ts +17 -0
  207. package/dist/src/orgrt/runtime-options.d.ts.map +1 -0
  208. package/dist/src/orgrt/runtime-options.js +32 -0
  209. package/dist/src/orgrt/runtime-options.js.map +1 -0
  210. package/dist/src/orgrt/scheduler-integration.d.ts +11 -0
  211. package/dist/src/orgrt/scheduler-integration.d.ts.map +1 -1
  212. package/dist/src/orgrt/scheduler-integration.js +75 -17
  213. package/dist/src/orgrt/scheduler-integration.js.map +1 -1
  214. package/dist/src/orgrt/scheduler.d.ts +4 -0
  215. package/dist/src/orgrt/scheduler.d.ts.map +1 -1
  216. package/dist/src/orgrt/scheduler.js +7 -1
  217. package/dist/src/orgrt/scheduler.js.map +1 -1
  218. package/dist/src/orgrt/server.d.ts +8 -3
  219. package/dist/src/orgrt/server.d.ts.map +1 -1
  220. package/dist/src/orgrt/server.js +48 -13
  221. package/dist/src/orgrt/server.js.map +1 -1
  222. package/dist/src/orgrt/session.d.ts +26 -2
  223. package/dist/src/orgrt/session.d.ts.map +1 -1
  224. package/dist/src/orgrt/session.js +98 -4
  225. package/dist/src/orgrt/session.js.map +1 -1
  226. package/dist/src/orgrt/task-dag.d.ts +5 -0
  227. package/dist/src/orgrt/task-dag.d.ts.map +1 -1
  228. package/dist/src/orgrt/task-dag.js +41 -0
  229. package/dist/src/orgrt/task-dag.js.map +1 -1
  230. package/dist/src/orgrt/test-loop.js +2 -2
  231. package/dist/src/orgrt/test-loop.js.map +1 -1
  232. package/dist/src/orgrt/types.d.ts +14 -2
  233. package/dist/src/orgrt/types.d.ts.map +1 -1
  234. package/dist/src/orgrt/types.js +15 -1
  235. package/dist/src/orgrt/types.js.map +1 -1
  236. package/dist/src/orgrt/vercel-runner.d.ts.map +1 -1
  237. package/dist/src/orgrt/vercel-runner.js +4 -0
  238. package/dist/src/orgrt/vercel-runner.js.map +1 -1
  239. package/dist/src/ui/routes-org.mjs +27 -7
  240. package/dist/src/ui/server.mjs +82 -48
  241. package/dist/tsconfig.tsbuildinfo +1 -1
  242. package/package.json +3 -2
  243. package/dist/src/ui/data/mastermind-sessions.json +0 -1
@@ -18,7 +18,7 @@ This skill is invoked by `mastermind:my-issues` or directly via `/mastermind:my-
18
18
  - `action`: list | assign-self | unassign | close
19
19
  - `assignee_id`: user/agent id to filter by (default: local-operator)
20
20
  - `issue_id`: issue id (required for assign-self / unassign / close)
21
- - `status_filter`: open | in_progress | all (default: open+in_progress)
21
+ - `status_filter`: active | todo | in_progress | blocked | in_review | done | cancelled | all (default: active = todo+in_progress)
22
22
  - `caller`: command | master
23
23
 
24
24
  ---
@@ -41,6 +41,38 @@ issuesFile=".monomind/orgs/${org_name}-issues.json"
41
41
  assigneeFilter="${assignee_id:-local-operator}"
42
42
  ```
43
43
 
44
+ Normalize any legacy records written by a pre-2.10 version of these skills. Idempotent — safe to run on every load.
45
+
46
+ ```bash
47
+ python3 - "$issuesFile" <<'PYEOF'
48
+ import json, sys
49
+ path = sys.argv[1]
50
+ data = json.load(open(path))
51
+ RENAME = {
52
+ "assignee_id": "assigneeId", "assigned_to": "assigneeId",
53
+ "created_at": "createdAt", "updated_at": "updatedAt",
54
+ "closed_at": "closedAt", "project_id": "projectId",
55
+ "parent_id": "parentId", "recovery_status": "recoveryStatus",
56
+ "lastActivityAt": "updatedAt",
57
+ }
58
+ changed = False
59
+ for iss in data.get("issues", []):
60
+ for old, new in RENAME.items():
61
+ if old in iss:
62
+ iss.setdefault(new, iss.pop(old))
63
+ iss.pop(old, None)
64
+ changed = True
65
+ if iss.get("status") == "open":
66
+ iss["status"] = "todo"
67
+ changed = True
68
+ if changed:
69
+ tmp = path + ".tmp"
70
+ with open(tmp, "w") as f:
71
+ json.dump(data, f, indent=2)
72
+ import os; os.replace(tmp, path)
73
+ PYEOF
74
+ ```
75
+
44
76
  ---
45
77
 
46
78
  ## Step 2 — Execute Action
@@ -57,19 +89,19 @@ echo "────────────────────────
57
89
  jq -r --arg uid "$assigneeFilter" --arg sf "$statusFilter" '
58
90
  (.issues // [])[] |
59
91
  select(
60
- (.assigneeId == $uid or .assigned_to == $uid) and
61
- (if $sf == "active" then (.status == "open" or .status == "in_progress")
92
+ (.assigneeId == $uid) and
93
+ (if $sf == "active" then (.status == "todo" or .status == "in_progress")
62
94
  elif $sf == "all" then true
63
95
  else .status == $sf
64
96
  end)
65
97
  ) |
66
- [.id, (.status // "open"), (.priority // "medium"), (.title // "(no title)")] | @tsv
98
+ [.id, (.status // "todo"), (.priority // "medium"), (.title // "(no title)")] | @tsv
67
99
  ' "$issuesFile" | while IFS=$'\t' read -r id st pri title; do
68
100
  printf "%-24s %-12s %-10s %s\n" "$id" "$st" "$pri" "$title"
69
101
  done
70
102
 
71
103
  total=$(jq -r --arg uid "$assigneeFilter" \
72
- '[(.issues // [])[] | select(.assigneeId == $uid or .assigned_to == $uid)] | length' \
104
+ '[(.issues // [])[] | select(.assigneeId == $uid)] | length' \
73
105
  "$issuesFile")
74
106
  echo ""
75
107
  echo "Total assigned: $total"
@@ -88,7 +120,7 @@ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
88
120
  tmp="${issuesFile}.tmp"
89
121
  jq --arg id "$issue_id" --arg uid "$assigneeFilter" --arg ts "$ts" \
90
122
  '.issues = [(.issues // [])[] | if .id == $id then
91
- .assigneeId = $uid | .lastActivityAt = $ts
123
+ .assigneeId = $uid | .assigneeUserId = $uid | .assigneeAgentId = null | .updatedAt = $ts
92
124
  else . end]' \
93
125
  "$issuesFile" > "$tmp" && mv "$tmp" "$issuesFile"
94
126
 
@@ -104,7 +136,7 @@ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
104
136
  tmp="${issuesFile}.tmp"
105
137
  jq --arg id "$issue_id" --arg ts "$ts" \
106
138
  '.issues = [(.issues // [])[] | if .id == $id then
107
- .assigneeId = null | .lastActivityAt = $ts
139
+ .assigneeId = null | .assigneeUserId = null | .assigneeAgentId = null | .updatedAt = $ts
108
140
  else . end]' \
109
141
  "$issuesFile" > "$tmp" && mv "$tmp" "$issuesFile"
110
142
 
@@ -120,7 +152,7 @@ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
120
152
  tmp="${issuesFile}.tmp"
121
153
  jq --arg id "$issue_id" --arg ts "$ts" \
122
154
  '.issues = [(.issues // [])[] | if .id == $id then
123
- .status = "done" | .closedAt = $ts | .lastActivityAt = $ts
155
+ .status = "done" | .closedAt = $ts | .updatedAt = $ts
124
156
  else . end]' \
125
157
  "$issuesFile" > "$tmp" && mv "$tmp" "$issuesFile"
126
158
 
@@ -14,3 +14,5 @@ Use the `monomind org` commands as the portable organization control surface.
14
14
  - Stop a running organization with `monomind org stop <name>`.
15
15
 
16
16
  Before changing organization configuration, inspect its current status and preserve the user's explicit goal, budget, and safety constraints. Use the platform's native task and agent interfaces when available; otherwise keep the CLI as the functional source of truth.
17
+
18
+ The org runtime does not read `<org>-issues.json`. Work is driven by the org definition's roles and goal plus an optional `--task` string.
@@ -40,6 +40,7 @@ If the spec covers multiple independent subsystems, it should have been broken i
40
40
 
41
41
  Before defining tasks, map out which files will be created or modified and what each one is responsible for. This is where decomposition decisions get locked in.
42
42
 
43
+ - Check `brain_context` for relevant prior lessons or patterns for this domain/project — past task-granularity mistakes, file-organization conventions, known gotchas — and apply them to the decomposition below. Note when a decomposition choice is informed by recalled history.
43
44
  - Design units with clear boundaries and well-defined interfaces. Each file should have one clear responsibility.
44
45
  - You reason best about code you can hold in context at once, and your edits are more reliable when files are focused. Prefer smaller, focused files over large ones that do too much.
45
46
  - Files that change together should live together. Split by responsibility, not by technical layer.
@@ -73,7 +74,7 @@ A task is the smallest unit that carries its own test cycle and is worth a fresh
73
74
  ```markdown
74
75
  # [Feature Name] Implementation Plan
75
76
 
76
- > **For agentic workers:** REQUIRED SUB-SKILL: Use `Skill("mastermind-taskdev")` (recommended) or `Skill("mastermind-execute")` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
77
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use `Skill("mastermind-execute")` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
77
78
 
78
79
  **Goal:** [One sentence describing what this builds]
79
80
 
@@ -196,22 +197,12 @@ Wait for the user's response. If they request changes, make them inline and re-r
196
197
 
197
198
  After the plan is approved (or in auto mode, after self-review):
198
199
 
199
- **In confirm mode (default):** Ask the user to choose execution mode:
200
+ **In confirm mode (default):** Ask the user to confirm execution:
200
201
 
201
- **"Plan complete and saved to `docs/mastermind/plans/<filename>.md`. Two execution options:**
202
+ **"Plan complete and saved to `docs/mastermind/plans/<filename>.md`. Execute it now with `mastermind-execute`?"**
202
203
 
203
- **1. Subagent-Driven (recommended)** — Invoke `Skill("mastermind-taskdev")`: dispatches a fresh subagent per task, reviews between tasks, fast iteration.
204
+ **In auto mode:** Skip the question — invoke `Skill("mastermind-execute")` immediately.
204
205
 
205
- **2. Inline Execution** — Invoke `Skill("mastermind-execute")`: batch execution with checkpoints.
206
-
207
- **Which approach?"**
208
-
209
- **In auto mode:** Skip the question. Default to subagent-driven — invoke `Skill("mastermind-taskdev")` immediately.
210
-
211
- **If Subagent-Driven chosen (or auto mode):**
212
- - Invoke `Skill("mastermind-taskdev")`
213
- - Fresh subagent per task + two-stage review
214
-
215
- **If Inline Execution chosen:**
206
+ **On execution:**
216
207
  - Invoke `Skill("mastermind-execute")`
217
- - Batch execution with checkpoints for review
208
+ - It loads the plan, reviews it critically, and works task-by-task with checkpoints for review
@@ -70,15 +70,24 @@ Read the plan carefully and apply these rules:
70
70
 
71
71
  **Decomposition output:**
72
72
 
73
- For each issue extracted from the plan, produce:
74
- ```
75
- ISSUE: <title>
76
- assignee: <agent-id or UNASSIGNED>
77
- priority: low | medium | high | urgent
78
- blockedBy: <issue-title> (or none)
79
- description: <1-2 sentence summary of the deliverable>
73
+ For each issue extracted from the plan, emit one object into a JSON array and write the
74
+ whole array to `.monomind/orgs/<org_name>-plan-decomposition.json` using the Write tool:
75
+
76
+ ```json
77
+ [
78
+ {
79
+ "title": "Short imperative deliverable name",
80
+ "description": "1-2 sentence summary of the deliverable",
81
+ "assignee": "<agent-id from the org roster, or null if unassigned>",
82
+ "priority": "low | medium | high | urgent",
83
+ "blockedBy": ["<exact title of a blocking issue in this same array>"]
84
+ }
85
+ ]
80
86
  ```
81
87
 
88
+ Titles must be unique within the array — `blockedBy` is resolved by exact title match.
89
+ Use `[]` for `blockedBy` when an issue has no blockers.
90
+
82
91
  **Quality checklist before creating:**
83
92
  - [ ] Enough detail that assignees can act without re-asking
84
93
  - [ ] Every concrete deliverable is an issue
@@ -91,31 +100,130 @@ ISSUE: <title>
91
100
 
92
101
  ## Step 4 — Create Issues (unless dry_run=true)
93
102
 
103
+ Reads the decomposition written in Step 3, assigns collision-free ids, validates assignees
104
+ against the org roster, resolves `blockedBy` titles into `blockedByIssueIds`, and appends
105
+ to the issues file atomically. Fails loudly on any unresolved or ambiguous reference —
106
+ an unresolved blocker silently dropped is exactly what produces a `blocked` issue with an
107
+ empty `blockedByIssueIds`, which `mastermind-liveness` reports as stalled.
108
+
94
109
  ```bash
95
110
  issuesFile=".monomind/orgs/${org_name}-issues.json"
111
+ decompFile=".monomind/orgs/${org_name}-plan-decomposition.json"
96
112
  [ ! -f "$issuesFile" ] && echo '{"issues":[]}' > "$issuesFile"
113
+ [ ! -f "$decompFile" ] && { echo "ERROR: $decompFile not found — Step 3 must write it first."; exit 1; }
97
114
 
98
115
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
99
116
 
100
- # Issues are created from the decomposition above
101
- # Each issue gets:
102
- # id: issue-<timestamp>-<N>
103
- # title, description, priority, status: open
104
- # assigneeId (from matched agent)
105
- # projectId, workspaceId (if provided)
106
- # blockedByIssueIds (resolved after all issues are created)
107
- # createdAt, updatedAt
108
-
109
- echo ""
110
- if [ "${dry_run:-false}" = "true" ]; then
111
- echo "DRY RUN — no issues were created."
112
- echo "Remove --dry-run to create these issues."
113
- else
114
- echo "CREATED ISSUES:"
115
- # (issue creation happens inline above as each issue is decomposed)
116
- fi
117
+ python3 - "$issuesFile" "$decompFile" "$orgFile" "${project_id:-}" "${workspace_id:-}" "${dry_run:-false}" "$ts" <<'PYEOF'
118
+ import json, os, sys, time
119
+
120
+ issues_path, decomp_path, org_path, project_id, workspace_id, dry_run, ts = sys.argv[1:]
121
+ dry = dry_run == "true"
122
+
123
+ VALID_PRIORITY = {"low", "medium", "high", "urgent"}
124
+
125
+ decomp = json.load(open(decomp_path))
126
+ if not isinstance(decomp, list) or not decomp:
127
+ print("ERROR: decomposition must be a non-empty JSON array.")
128
+ sys.exit(1)
129
+
130
+ roster = {r.get("id") for r in json.load(open(org_path)).get("roles", [])}
131
+ data = json.load(open(issues_path))
132
+ issues = data.setdefault("issues", [])
133
+
134
+ # --- Validate before mutating anything ---
135
+ errors, titles = [], {}
136
+ for n, item in enumerate(decomp, 1):
137
+ title = (item.get("title") or "").strip()
138
+ if not title:
139
+ errors.append(f"item {n}: missing title")
140
+ continue
141
+ if title in titles:
142
+ errors.append(f"item {n}: duplicate title {title!r} — titles must be unique to resolve blockedBy")
143
+ titles[title] = None
144
+ pri = item.get("priority") or "medium"
145
+ if pri not in VALID_PRIORITY:
146
+ errors.append(f"{title!r}: priority {pri!r} must be one of {sorted(VALID_PRIORITY)}")
147
+ asgn = item.get("assignee")
148
+ if asgn and asgn not in roster:
149
+ errors.append(f"{title!r}: assignee {asgn!r} is not a role in this org (roster: {sorted(roster)})")
150
+
151
+ existing_by_title = {i.get("title"): i.get("id") for i in issues}
152
+ for item in decomp:
153
+ for b in item.get("blockedBy") or []:
154
+ if b not in titles and b not in existing_by_title:
155
+ errors.append(f"{item.get('title')!r}: blockedBy {b!r} matches no issue in this batch or in the existing file")
156
+
157
+ if errors:
158
+ print("ERROR: decomposition did not validate — no issues were created.")
159
+ for e in errors:
160
+ print(f" · {e}")
161
+ sys.exit(1)
162
+
163
+ # --- Assign ids (batch-stable, collision-free) ---
164
+ batch = int(time.time() * 1000)
165
+ for n, item in enumerate(decomp, 1):
166
+ titles[item["title"].strip()] = f"issue-{batch}-{n:03d}"
167
+
168
+ def resolve(t):
169
+ return titles.get(t) or existing_by_title[t]
170
+
171
+ created = []
172
+ for item in decomp:
173
+ title = item["title"].strip()
174
+ created.append({
175
+ "id": titles[title],
176
+ "title": title,
177
+ "description": item.get("description") or "",
178
+ "status": "todo",
179
+ "priority": item.get("priority") or "medium",
180
+ "assigneeId": item.get("assignee") or None,
181
+ "assigneeAgentId": item.get("assignee") or None,
182
+ "assigneeUserId": None,
183
+ "parentId": None,
184
+ "projectId": project_id or None,
185
+ "workspaceId": workspace_id or None,
186
+ "blockedByIssueIds": [resolve(b) for b in (item.get("blockedBy") or [])],
187
+ "createdAt": ts,
188
+ "updatedAt": ts,
189
+ })
190
+
191
+ if dry:
192
+ print("DRY RUN — no issues were created. Would create:")
193
+ for c in created:
194
+ blockers = ", ".join(c["blockedByIssueIds"]) or "none"
195
+ print(f" {c['id']} [{c['priority']:<6}] {c['title']}")
196
+ print(f" assignee: {c['assigneeId'] or 'UNASSIGNED'} blockedBy: {blockers}")
197
+ print(f"\n {len(created)} issue(s) would be created.")
198
+ sys.exit(0)
199
+
200
+ issues.extend(created)
201
+ tmp = issues_path + ".tmp"
202
+ with open(tmp, "w") as f:
203
+ json.dump(data, f, indent=2)
204
+ os.replace(tmp, issues_path)
205
+
206
+ print("CREATED ISSUES:")
207
+ for c in created:
208
+ blockers = ", ".join(c["blockedByIssueIds"]) or "none"
209
+ print(f" {c['id']} [{c['priority']:<6}] {c['title']}")
210
+ print(f" assignee: {c['assigneeId'] or 'UNASSIGNED'} blockedBy: {blockers}")
211
+ print(f"\n {len(created)} issue(s) created in {issues_path}")
212
+
213
+ roots = [c for c in created if not c["blockedByIssueIds"]]
214
+ print(f" {len(roots)} issue(s) are unblocked and can start in parallel.")
215
+ PYEOF
216
+
217
+ rm -f "$decompFile"
117
218
  ```
118
219
 
220
+ > **These issues are not auto-consumed by the org runtime.** `OrgDaemon` builds its `TaskDag`
221
+ > fresh in memory on every start and never reads `<org>-issues.json` (verified: zero references
222
+ > in `packages/@monomind/cli/src/orgrt/`). Issues created here are tracked by the
223
+ > `mastermind-issues` / `mastermind-my-issues` / `mastermind-liveness` skills and shown in the
224
+ > dashboard, but running `monomind org run <org>` will not pick them up. Drive execution from
225
+ > this file with `mastermind-execute`, or pass work explicitly via `org run --task`.
226
+
119
227
  ---
120
228
 
121
229
  ## Step 5 — Return Output
@@ -30,17 +30,19 @@ Execute at the START of every mastermind run (master or standalone domain comman
30
30
 
31
31
  **Step A — Tier 3 core principles (all domains):**
32
32
  Try `mcp__monomind__memory_hierarchical-recall` with query `"mastermind principles"`, topK 20.
33
- If it returns `"LanceDB bridge not available"` or any error, fall back to:
33
+ If it returns an error, fall back to:
34
34
  `mcp__monomind__memory_pattern-search` with query `"mastermind principles"`, namespace `"mastermind:principles"`, limit 20.
35
+ Record the entry ids of whichever call actually returned results — `results[].id` from `memory_hierarchical-recall`, or `patterns[].id` from the `memory_pattern-search` fallback — into a running list: `recalled_entry_ids`.
35
36
 
36
37
  **Step B — Tier 2 weekly summary for this domain:**
37
38
  Try `mcp__monomind__memory_context-synthesize` with query `[current prompt keywords]`, maxEntries 10.
38
39
  If it fails, fall back to:
39
40
  `mcp__monomind__memory_pattern-search` with query `[current prompt keywords]`, namespace `"mastermind:<domain>:weekly"`, limit 10.
41
+ `memory_context-synthesize` returns only a `context` string and a `sources` count — no entry ids — so it cannot add to `recalled_entry_ids`. Only the `memory_pattern-search` fallback contributes ids (`patterns[].id`); append them to `recalled_entry_ids` when that fallback runs.
40
42
 
41
43
  **Step C — Relevant graph nodes:**
42
44
  Call `mcp__monomind__monograph_query` with question `[3-5 keywords extracted from current prompt]`, depth 2.
43
- If the graph is not built yet (error: "No graph found"), skip this tier — continue without graph context.
45
+ If the graph is not built yet (error: "No graph found"), skip this tier — continue without graph context. Graph nodes are not memory entries and never contribute to `recalled_entry_ids`.
44
46
 
45
47
  Combine all results into a **BRAIN CONTEXT** block. Insert this block before any planning, decomposition, or agent spawning step. Format:
46
48
 
@@ -54,19 +56,30 @@ Combine all results into a **BRAIN CONTEXT** block. Insert this block before any
54
56
 
55
57
  If any MCP call fails or returns empty, continue without that tier — do not abort the run.
56
58
 
59
+ **Step D — Visible recall confirmation:**
60
+ Immediately after loading, print one short line as part of the calling skill's normal output — not buried inside the BRAIN CONTEXT block or an internal note the user never sees:
61
+ - If anything was recalled: `Remembered: <one-line gist of the single most relevant recalled item>`
62
+ - If every tier came back empty: `Nothing relevant recalled.`
63
+
64
+ Keep `recalled_entry_ids` in scope for the rest of the run — the Brain Write Procedure's feedback step below needs it. Memory tools are stateless between calls, so the calling skill (not the MCP server) is what carries this list forward.
65
+
57
66
  ---
58
67
 
59
68
  ## Brain Write Procedure
60
69
 
61
70
  Execute at the END of every mastermind run. Always runs even if execution was partial or blocked.
62
71
 
63
- **Step 1 — Score this run:**
64
- ```
65
- score = confidence × (1 / (days_since_run + 1)) × log(uses + 1)
66
- ```
67
- - `confidence`: average of all decision confidence values from the unified output schema
68
- - `days_since_run`: 0 (this is a fresh run)
69
- - `uses`: 1 (first write)
72
+ **Step 1 — Close the feedback loop on recalled memory:**
73
+ If `recalled_entry_ids` from the Brain Load Procedure is non-empty, call `mcp__monomind__memory_feedback`:
74
+ - `taskId`: the `run_id` used in this run's unified output schema (ISO8601 timestamp) — also the idempotency key, so a retried or duplicated call for the same run never double-applies the rating
75
+ - `entryIds`: `recalled_entry_ids`
76
+ - `quality`: average of `decisions[].confidence` (0.0-1.0) from this run's unified output schema; omit if `decisions` is empty
77
+ - `success`: `true` when `status: complete`, or `status: partial` with no `outcome: reverted` decisions and no user rejection/correction of the recalled guidance during the run; otherwise `false`
78
+ - `agent`: `<domain>` (this skill's domain)
79
+
80
+ This applies an EWMA update to `feedback_weight` on each recalled entry (idempotent per `taskId`) and appends a feedback-event record via the same tool call. If `recalled_entry_ids` is empty — e.g. Brain Load only got a hit from `memory_context-synthesize`, which returns no entry ids — skip this call; there is nothing to rate.
81
+
82
+ `score` referenced in Steps 2-3 below is this same value: the average of `decisions[].confidence` from this run's unified output schema. The old day/use decay factors were already constants on a fresh write (day 0, first use), so this is equivalent and simpler.
70
83
 
71
84
  **Step 2 — Append to Tier 1 raw log:**
72
85
  Try `mcp__monomind__memory_hierarchical-store` with:
@@ -74,7 +87,7 @@ Try `mcp__monomind__memory_hierarchical-store` with:
74
87
  - content: [full unified output schema YAML from this run, as a string]
75
88
  - metadata: `{ score, project, run_id, date: ISO8601, domain }`
76
89
 
77
- If LanceDB is unavailable, fall back to `mcp__monomind__memory_pattern-store`:
90
+ If hierarchical storage is unavailable, fall back to `mcp__monomind__memory_pattern-store`:
78
91
  - key: `mastermind:<domain>:run:<run_id>`
79
92
  - value: [JSON-encoded unified output schema]
80
93
  - namespace: `mastermind:<domain>:raw`
@@ -82,7 +95,7 @@ If LanceDB is unavailable, fall back to `mcp__monomind__memory_pattern-store`:
82
95
 
83
96
  **Step 3 — Check weekly compaction trigger:**
84
97
  Try `mcp__monomind__memory_health` on namespace `mastermind:<domain>:raw`.
85
- If unavailable, call `mcp__monomind__memory_stats` and check entry count manually.
98
+ If unavailable, call `mcp__monomind__memory_health` and check entry count manually.
86
99
  If `entry_count >= 20` OR `days_since_last_compaction >= 7`:
87
100
  1. Retrieve all Tier 1 entries since last compaction
88
101
  2. Produce a per-domain weekly summary (use LLM synthesis: "Summarize the key decisions, patterns, and lessons from these run logs in under 300 words")
@@ -94,7 +107,7 @@ Call `mcp__monomind__monograph_community` for nodes matching `mastermind:<domain
94
107
  If 3+ similar memory nodes are detected in a cluster:
95
108
  1. Merge into a single principle via LLM: "Distill these memories into one clear principle in 1-2 sentences"
96
109
  2. Store principle: `mcp__monomind__memory_hierarchical-store` namespace `mastermind:principles`
97
- 3. Add `EXCEPTION` edge in Monograph for any conflicting memory: `mcp__monomind__monograph_add_fact`
110
+ 3. Add `EXCEPTION` edge in Monograph for any conflicting memory: `mcp__monomind__memory_kg_ingest`
98
111
 
99
112
  ---
100
113
 
@@ -126,19 +139,17 @@ Empty fields use `[]`. The `status` field reflects the highest-level result: `co
126
139
 
127
140
  ---
128
141
 
129
- ## Memory Scoring Formula
142
+ ## Memory Feedback Loop
130
143
 
131
- ```
132
- score = confidence × (1 / (days_since_run + 1)) × log(uses + 1)
133
- ```
144
+ Recalled memories are rated for real via `mcp__monomind__memory_feedback` (Brain Write Procedure, Step 1) — not a locally-computed formula. `score` (used by Steps 2-3 for the newly-stored run entry) is the average of `decisions[].confidence` from the unified output schema.
134
145
 
135
- | Variable | Source |
146
+ | Concept | Mechanism |
136
147
  |---|---|
137
- | `confidence` | Average of `decisions[].confidence` from output schema |
138
- | `days_since_run` | 0 on day of run, increases 1 per calendar day |
139
- | `uses` | Incremented each time this memory is returned by brain load |
140
- | Archive threshold | score < 0.1 |
141
- | Reinforcement | Increment `uses` on every brain load hit |
148
+ | `feedback_weight` | Per-entry EWMA, updated by `memory_feedback`'s `quality`/`success` params, blended into future ranking (`bridgeApplyFeedback` in `memory-bridge.ts`) |
149
+ | `frequency_weight` | Incremented automatically on every recall hit, inside the `memory_hierarchical-recall` / `memory_pattern-search` handlers themselves — protects frequently-used entries from GC |
150
+ | Idempotency | `memory_feedback`'s `taskId` is the idempotency key — the same `run_id` never double-applies a rating |
151
+ | Archive threshold | score < 0.1 (Step 3 above) |
152
+ | GC protection | `bridgeConsolidate` never collects entries with `feedback_weight > 0.6` or `frequency_weight >= 3` |
142
153
 
143
154
  ---
144
155
 
@@ -30,14 +30,33 @@ no curl emissions, no delivery gaps.
30
30
  if step 3 ran).
31
31
  - Valid → step 5.
32
32
  - Invalid → surface the validator output and stop.
33
- 5. **Start.**
33
+ 5. **Estimate scope and budget.** Before starting, read the numbers already sitting in
34
+ the org config and print a short summary — this is a read-and-summarize step, not a
35
+ spend prediction, so the user sees scope and the configured ceiling before any agent-hour
36
+ is spent, not only after the run stops on a budget:
37
+ ```bash
38
+ orgFile=".monomind/orgs/<name>.json"
39
+ roleCount=$(jq '.roles | length' "$orgFile")
40
+ maxConcurrent=$(jq -r '.run_config.max_concurrent_agents // 4' "$orgFile")
41
+ orgBudgetTokens=$(jq -r '.run_config.budget_tokens // 1000000' "$orgFile")
42
+ roleBudgets=$(jq -r '.roles[] | " - \(.id): budget_tokens=\(.budget_tokens // "org-default split"), budget_usd=\(.budget_usd // "none")"' "$orgFile")
43
+ echo "About to run ${roleCount} role(s) (max ${maxConcurrent} concurrent)."
44
+ echo "Org-wide token budget ceiling: ${orgBudgetTokens}"
45
+ echo "Per-role overrides:"
46
+ echo "${roleBudgets}"
47
+ ```
48
+ Print this summary unconditionally (auto and confirm mode alike) before step 6.
49
+ 6. **Start.**
34
50
  - One-shot (no `schedule` in config): run in background bash:
35
51
  `monomind org run <name> --task "<optional task from params>"`
36
52
  - Scheduled (`schedule` set): ensure the daemon host is up:
37
53
  `monomind org serve` (background) — it picks up every scheduled org.
38
- 6. **Confirm liveness.** Within ~15 s: `monomind org status <name>` shows
54
+ 7. **Confirm liveness.** Within ~15 s: `monomind org status <name>` shows
39
55
  `running`. Surface the dashboard link (`<CTRL_URL>/orgs`) and
40
56
  `monomind org logs <name> --follow` as the tail command.
41
- 7. **Never** spawn a boss Task agent, create monotask boards, or emit
57
+ 8. **Never** spawn a boss Task agent, create monotask boards, or emit
42
58
  dashboard events manually. If the user explicitly asks for the legacy
43
59
  behavior, direct them to `/mastermind:runorgv1` (deprecated).
60
+
61
+ The org runtime does not read `<org>-issues.json`. Work is driven by the org
62
+ definition's roles and goal plus an optional `--task` string.
@@ -192,7 +192,7 @@ Use words Claude would search for:
192
192
  ```markdown
193
193
  # GOOD: Explicit requirement marker
194
194
  **REQUIRED SUB-SKILL:** Use Skill("mastermind-worktree")
195
- **REQUIRED BACKGROUND:** You MUST understand Skill("mastermind-finish")
195
+ **REQUIRED BACKGROUND:** You MUST understand Skill("mastermind-review")
196
196
 
197
197
  # BAD: Unclear if required
198
198
  See mastermind/worktree.md
@@ -5,6 +5,11 @@ type: domain-skill
5
5
  default_mode: auto
6
6
  ---
7
7
 
8
+ > **LEGACY-ORG-V1 — writes to a monotask board, not to `<org>-issues.json`.** This skill's
9
+ > `parent_id`, `status` (`todo | doing | done`), and card model belong to the pre-v2
10
+ > board-backed runner. For v2 issue tracking — including sub-issues — use
11
+ > `/mastermind:issues --action create --parent-id <id>`.
12
+
8
13
  # Mastermind Tasks
9
14
 
10
15
  This skill is invoked by `mastermind:tasks` or directly via `/mastermind:tasks`.
@@ -54,7 +54,7 @@ IF health.commitsBehind > 0 OR health.status != "fresh":
54
54
  ```
55
55
  Call mcp__monomind__monograph_god_nodes({}) // load-bearing files — porting near them is dangerous
56
56
  Call mcp__monomind__monograph_community({}) // module cluster structure — boundary map
57
- Call mcp__monomind__monograph_bridge({}) // cross-community connectors — architectural seams
57
+ Call mcp__monomind__monograph_surprises({}) // cross-community connectors — architectural seams
58
58
  Call mcp__monomind__monograph_stats({}) // fan-in/out percentile distribution
59
59
  Call mcp__monomind__monograph_query({ query: focus_hint OR "core architecture skills commands hooks agents" })
60
60
  ```
@@ -28,11 +28,20 @@
28
28
  // Selection: MONODESIGN_BROWSER_DRIVER=monobrowse|puppeteer forces one;
29
29
  // otherwise monobrowse is tried first and puppeteer is the fallback.
30
30
 
31
- import { Socket } from 'node:net';
31
+ import { createServer } from 'node:net';
32
+ import { mkdtempSync, rmSync } from 'node:fs';
33
+ import { tmpdir } from 'node:os';
34
+ import { join } from 'node:path';
32
35
 
33
36
  const DRIVER_ENV = 'MONODESIGN_BROWSER_DRIVER';
34
- const CDP_PORT_RELEASE_TIMEOUT_MS = 5_000;
37
+ // Windows CI teardown is the slow case: Chrome acknowledges Browser.close long
38
+ // before the process exits and hands the socket back, and closeBrowser()'s
39
+ // force-kill safety net is itself a deferred 1s timer. 5s is comfortable
40
+ // locally but cuts into a loaded runner's teardown, so give CI more runway —
41
+ // same reasoning as launchTimeoutMs below.
42
+ const CDP_PORT_RELEASE_TIMEOUT_MS = process.env.CI ? 15_000 : 5_000;
35
43
  const CDP_PORT_RELEASE_POLL_MS = 25;
44
+ const PROFILE_CLEANUP_RETRY_MS = 2_000;
36
45
 
37
46
  // Ports we launch headless Chrome on for detection runs. Deliberately away
38
47
  // from 9222 (the default `monomind browse` port): we must never attach to a
@@ -60,25 +69,36 @@ function withTimeout(promise, ms, label) {
60
69
  return Promise.race([promise, timeout]).finally(() => clearTimeout(handle));
61
70
  }
62
71
 
63
- function isPortListening(port) {
72
+ // "Released" has to mean BINDABLE, not merely unreachable. A connect probe
73
+ // stops succeeding as soon as Chrome's listener socket closes, which happens
74
+ // early in its shutdown — while the process is still alive holding both the
75
+ // port and the --user-data-dir singleton lock for this port. On Windows that
76
+ // gap is wide and the socket stays unbindable across it (node does not set
77
+ // SO_REUSEADDR there), so close() reported success and the next launch on the
78
+ // same forced port sat until its timeout: "Chrome failed to start on port N
79
+ // within 30000ms". Binding is the question the next launch actually asks, so
80
+ // ask it directly. A listener that never accepted a connection leaves no
81
+ // TIME_WAIT behind, so probing this way does not itself hold the port.
82
+ function isPortBindable(port) {
64
83
  return new Promise((resolve) => {
65
- const socket = new Socket();
66
- const done = (listening) => {
67
- socket.removeAllListeners();
68
- socket.destroy();
69
- resolve(listening);
70
- };
71
- socket.once('connect', () => done(true));
72
- socket.once('error', () => done(false));
73
- socket.setTimeout(250, () => done(false));
74
- socket.connect(port, '127.0.0.1');
84
+ const server = createServer();
85
+ server.once('error', () => resolve(false));
86
+ server.listen(port, '127.0.0.1', () => server.close(() => resolve(true)));
75
87
  });
76
88
  }
77
89
 
90
+ function removeProfileDir(dir) {
91
+ try {
92
+ rmSync(dir, { recursive: true, force: true });
93
+ } catch {
94
+ /* reclaimed by the OS's temp cleanup instead */
95
+ }
96
+ }
97
+
78
98
  async function waitForCdpPortRelease(port) {
79
99
  const deadline = Date.now() + CDP_PORT_RELEASE_TIMEOUT_MS;
80
100
  while (Date.now() < deadline) {
81
- if (!(await isPortListening(port))) return;
101
+ if (await isPortBindable(port)) return;
82
102
  await new Promise(resolve => setTimeout(resolve, CDP_PORT_RELEASE_POLL_MS));
83
103
  }
84
104
  throw new Error(`Chrome did not release CDP port ${port} within ${CDP_PORT_RELEASE_TIMEOUT_MS}ms`);
@@ -178,7 +198,20 @@ async function launchMonobrowseBrowser(options = {}) {
178
198
  // browser starts fine, just later. Give CI more runway; local dev keeps
179
199
  // the tighter default so a genuinely broken launch still fails fast.
180
200
  const launchTimeoutMs = options.launchTimeoutMs ?? (process.env.CI ? 30000 : undefined);
181
- const launchedPort = await mb.launchBrowser({ port, headless, args: launchArgs, launchTimeoutMs });
201
+ // Throwaway profile per launch. monobrowse defaults --user-data-dir to
202
+ // tmpdir()/monomind-browser-<port>, which is right for `monomind browse`
203
+ // (a later process reattaches to the same session by port) but wrong here:
204
+ // detection launches are stateless, and keying the profile on the port
205
+ // means two launches on the SAME port share one profile. Chrome enforces
206
+ // single-instance-per-profile, so the second launch rendezvouses with the
207
+ // first instead of starting its own — it hands over its command line and
208
+ // exits without ever opening a debugging port, which surfaces as "Chrome
209
+ // failed to start on port N" after the full launch timeout. Windows is
210
+ // where this bites: its singleton lock survives the previous Chrome being
211
+ // force-killed, and outlives the port being released. A unique dir removes
212
+ // the sharing entirely rather than racing the lock's cleanup.
213
+ const userDataDir = mkdtempSync(join(tmpdir(), 'monodesign-cdp-'));
214
+ const launchedPort = await mb.launchBrowser({ port, headless, args: launchArgs, launchTimeoutMs, userDataDir });
182
215
  // Control connection (to the initial about:blank tab) — used for lifecycle
183
216
  // commands; detection pages get their own dedicated connections.
184
217
  const control = await mb.connectToTarget(launchedPort);
@@ -217,6 +250,14 @@ async function launchMonobrowseBrowser(options = {}) {
217
250
  await waitForCdpPortRelease(launchedPort);
218
251
  } finally {
219
252
  control.client.close();
253
+ // Best effort, twice: Chrome is still flushing profile data when the
254
+ // port comes back, so an immediate delete gets a few files recreated
255
+ // under it. The unref'd retry lands after that (same pattern as
256
+ // monobrowse's own post-close force-kill net) and never holds the
257
+ // process open. Whatever still survives is a few KB under tmpdir that
258
+ // nothing reuses, so neither attempt may fail close().
259
+ removeProfileDir(userDataDir);
260
+ setTimeout(() => removeProfileDir(userDataDir), PROFILE_CLEANUP_RETRY_MS).unref?.();
220
261
  }
221
262
  },
222
263
  };
@@ -280,7 +280,7 @@ instance and let Grafana draw the trends.
280
280
  - **`mastermind-debug`** — Phase 1 root-cause methodology underpins this skill
281
281
  - **`swarm-orchestration`** — topology decisions driven by bottleneck findings
282
282
  - **`verification-quality`** — confirm the optimization actually held
283
- - **`mastermind-verify`** — evidence-before-claims for "is it faster?"
283
+ - **`mastermind-review`** — evidence-before-claims for "is it faster?"
284
284
 
285
285
  ## Quick Reference
286
286