monomind 2.2.0 → 2.3.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/package.json +2 -2
  2. package/packages/@monomind/cli/.claude/commands/mastermind/createorg.md +24 -16
  3. package/packages/@monomind/cli/.claude/commands/mastermind/master.md +23 -7
  4. package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +3 -1
  5. package/packages/@monomind/cli/.claude/commands/mastermind/stoporg.md +6 -3
  6. package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +6 -1
  7. package/packages/@monomind/cli/.claude/skills/mastermind-skills/code-quality-reviewer-prompt.md +3 -3
  8. package/packages/@monomind/cli/.claude/skills/mastermind-skills/createorg.md +19 -24
  9. package/packages/@monomind/cli/.claude/skills/mastermind-skills/debug.md +49 -4
  10. package/packages/@monomind/cli/.claude/skills/mastermind-skills/design.md +11 -10
  11. package/packages/@monomind/cli/.claude/skills/mastermind-skills/final-reviewer-prompt.md +144 -0
  12. package/packages/@monomind/cli/.claude/skills/mastermind-skills/implementer-prompt.md +9 -4
  13. package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +20 -2
  14. package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgs.md +40 -19
  15. package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgstatus.md +90 -41
  16. package/packages/@monomind/cli/.claude/skills/mastermind-skills/plan.md +19 -0
  17. package/packages/@monomind/cli/.claude/skills/mastermind-skills/skill-builder.md +171 -0
  18. package/packages/@monomind/cli/.claude/skills/mastermind-skills/spec-reviewer-prompt.md +19 -2
  19. package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +7 -5
  20. package/packages/@monomind/cli/.claude/skills/mastermind-skills/taskdev.md +85 -15
  21. package/packages/@monomind/cli/.claude/skills/mastermind-skills/tdd.md +30 -0
  22. package/packages/@monomind/cli/dist/src/autopilot-state.js +6 -4
  23. package/packages/@monomind/cli/dist/src/browser/dashboard/server.js +4 -1
  24. package/packages/@monomind/cli/dist/src/capabilities/manager.js +3 -1
  25. package/packages/@monomind/cli/dist/src/commands/agent-lifecycle.js +26 -0
  26. package/packages/@monomind/cli/dist/src/commands/agent-ops.js +28 -46
  27. package/packages/@monomind/cli/dist/src/commands/cleanup.d.ts +18 -0
  28. package/packages/@monomind/cli/dist/src/commands/cleanup.js +121 -0
  29. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +13 -4
  30. package/packages/@monomind/cli/dist/src/commands/hooks-routing-commands.js +34 -18
  31. package/packages/@monomind/cli/dist/src/commands/hooks-workers.js +8 -2
  32. package/packages/@monomind/cli/dist/src/commands/hooks.js +1 -1
  33. package/packages/@monomind/cli/dist/src/commands/mcp.js +4 -1
  34. package/packages/@monomind/cli/dist/src/commands/memory-admin.js +7 -1
  35. package/packages/@monomind/cli/dist/src/commands/org-observe.d.ts +18 -0
  36. package/packages/@monomind/cli/dist/src/commands/org-observe.js +295 -0
  37. package/packages/@monomind/cli/dist/src/commands/org.d.ts +9 -1
  38. package/packages/@monomind/cli/dist/src/commands/org.js +191 -9
  39. package/packages/@monomind/cli/dist/src/commands/performance.js +4 -1
  40. package/packages/@monomind/cli/dist/src/commands/platforms.js +4 -1
  41. package/packages/@monomind/cli/dist/src/commands/security-scan.js +8 -2
  42. package/packages/@monomind/cli/dist/src/commands/start.js +4 -1
  43. package/packages/@monomind/cli/dist/src/commands/swarm.js +3 -2
  44. package/packages/@monomind/cli/dist/src/index.js +13 -3
  45. package/packages/@monomind/cli/dist/src/init/executor.js +19 -5
  46. package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +3 -2
  47. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.d.ts +1 -0
  48. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +63 -20
  49. package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +3 -1
  50. package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +3 -2
  51. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +3 -2
  52. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +6 -2
  53. package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +35 -10
  54. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +24 -7
  55. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-embedding.js +13 -3
  56. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-intelligence.js +9 -3
  57. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.d.ts +1 -0
  58. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-routing.js +112 -10
  59. package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +2 -1
  60. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +8 -2
  61. package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +3 -2
  62. package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +15 -5
  63. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +4 -1
  64. package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +6 -4
  65. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.d.ts +20 -0
  66. package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +38 -16
  67. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.d.ts +2 -0
  68. package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +41 -24
  69. package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +6 -2
  70. package/packages/@monomind/cli/dist/src/memory/intelligence.js +32 -10
  71. package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +81 -31
  72. package/packages/@monomind/cli/dist/src/memory/memory-crud.js +26 -2
  73. package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +3 -2
  74. package/packages/@monomind/cli/dist/src/memory/memory-migrations.js +2 -0
  75. package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +4 -1
  76. package/packages/@monomind/cli/dist/src/orgrt/bus.js +4 -1
  77. package/packages/@monomind/cli/dist/src/orgrt/daemon.js +74 -19
  78. package/packages/@monomind/cli/dist/src/orgrt/inbox.js +3 -1
  79. package/packages/@monomind/cli/dist/src/orgrt/reporting.d.ts +40 -0
  80. package/packages/@monomind/cli/dist/src/orgrt/reporting.js +127 -0
  81. package/packages/@monomind/cli/dist/src/orgrt/session.d.ts +2 -0
  82. package/packages/@monomind/cli/dist/src/orgrt/session.js +10 -2
  83. package/packages/@monomind/cli/dist/src/orgrt/templates.d.ts +16 -0
  84. package/packages/@monomind/cli/dist/src/orgrt/templates.js +57 -0
  85. package/packages/@monomind/cli/dist/src/services/config-file-manager.js +10 -1
  86. package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +6 -2
  87. package/packages/@monomind/cli/dist/src/ui/orgs.html +32 -0
  88. package/packages/@monomind/cli/dist/src/ui/server.mjs +25 -0
  89. package/packages/@monomind/cli/dist/src/update/executor.js +3 -1
  90. package/packages/@monomind/cli/dist/src/update/rate-limiter.js +3 -1
  91. package/packages/@monomind/cli/package.json +3 -3
@@ -13,6 +13,8 @@ Execute a plan by dispatching a fresh subagent per task, with two-stage review a
13
13
 
14
14
  **Core principle:** Fresh subagent per task + two-stage review (spec then quality) = high quality, fast iteration
15
15
 
16
+ **Narration:** between tool calls, narrate at most one short line — the progress ledger and the tool results carry the record.
17
+
16
18
  **Continuous execution:** Do not pause to check in with your human partner between tasks. Execute all tasks from the plan without stopping. The only reasons to stop are: BLOCKED status you cannot resolve, ambiguity that genuinely prevents progress, or all tasks complete. "Should I continue?" prompts and progress summaries waste their time — they asked you to execute the plan, so execute it.
17
19
 
18
20
  ---
@@ -75,12 +77,12 @@ digraph process {
75
77
  "Mark task complete in TodoWrite" [shape=box];
76
78
  }
77
79
 
78
- "Read plan, extract all tasks with full text, note context, create TodoWrite" [shape=box];
80
+ "Read plan, note context and global constraints, create TodoWrite" [shape=box];
79
81
  "More tasks remain?" [shape=diamond];
80
82
  "Dispatch final code reviewer subagent for entire implementation" [shape=box];
81
83
  "Use mastermind:finish" [shape=box style=filled fillcolor=lightgreen];
82
84
 
83
- "Read plan, extract all tasks with full text, note context, create TodoWrite" -> "Dispatch implementer subagent (./implementer-prompt.md)";
85
+ "Read plan, note context and global constraints, create TodoWrite" -> "Dispatch implementer subagent (./implementer-prompt.md)";
84
86
  "Dispatch implementer subagent (./implementer-prompt.md)" -> "Implementer subagent asks questions?";
85
87
  "Implementer subagent asks questions?" -> "Answer questions, provide context" [label="yes"];
86
88
  "Answer questions, provide context" -> "Dispatch implementer subagent (./implementer-prompt.md)";
@@ -103,6 +105,17 @@ digraph process {
103
105
 
104
106
  ---
105
107
 
108
+ ## Pre-Flight Plan Review
109
+
110
+ Before dispatching Task 1, scan the plan once for conflicts:
111
+
112
+ - tasks that contradict each other or the plan's Global Constraints
113
+ - anything the plan explicitly mandates that the review rubric treats as a defect (a test that asserts nothing, verbatim duplication of a logic block)
114
+
115
+ Present everything you find to your human partner as one batched question — each finding beside the plan text that mandates it, asking which governs — before execution begins, not one interrupt per discovery mid-plan. If the scan is clean, proceed without comment. The review loop remains the net for conflicts that only emerge from implementation. (In auto mode: resolve each conflict yourself, state the resolution explicitly, and log it as a decision with confidence.)
116
+
117
+ ---
118
+
106
119
  ## Model Selection
107
120
 
108
121
  Use the least powerful model that can handle each role to conserve cost and increase speed.
@@ -111,7 +124,13 @@ Use the least powerful model that can handle each role to conserve cost and incr
111
124
 
112
125
  **Integration and judgment tasks** (multi-file coordination, pattern matching, debugging): use a standard model.
113
126
 
114
- **Architecture, design, and review tasks**: use the most capable available model.
127
+ **Architecture and design tasks**: use the most capable available model. The final whole-branch review is one of these — dispatch it on the most capable available model, not the session default.
128
+
129
+ **Review tasks**: choose the model with the same judgment, scaled to the diff's size, complexity, and risk. A small mechanical diff does not need the most capable model; a subtle concurrency change does.
130
+
131
+ **Always specify the model explicitly when dispatching a subagent.** An omitted model inherits your session's model — often the most capable and most expensive — which silently defeats this section.
132
+
133
+ **Turn count beats token price.** Wall-clock and context cost scale with how many turns a subagent takes, and the cheapest models routinely take 2-3× the turns on multi-step work — costing more overall. Use a mid-tier model as the floor for reviewers and for implementers working from prose descriptions. When the task's plan text contains the complete code to write, the implementation is transcription plus testing: use the cheapest tier for that implementer. Single-file mechanical fixes also take the cheapest tier.
115
134
 
116
135
  **Task complexity signals:**
117
136
  - Touches 1-2 files with a complete spec → cheap model
@@ -124,7 +143,7 @@ Use the least powerful model that can handle each role to conserve cost and incr
124
143
 
125
144
  Implementer subagents report one of four statuses. Handle each appropriately:
126
145
 
127
- **DONE:** Proceed to spec compliance review.
146
+ **DONE:** Generate the task's diff file from the BASE you recorded before dispatching the implementer (never `HEAD~1` — it silently drops all but the last commit of a multi-commit task), then proceed to spec compliance review with the brief, report, and diff file paths.
128
147
 
129
148
  **DONE_WITH_CONCERNS:** The implementer completed the work but flagged doubts. Read the concerns before proceeding. If the concerns are about correctness or scope, address them before review. If they're observations (e.g., "this file is getting large"), note them and proceed to review.
130
149
 
@@ -138,6 +157,54 @@ Implementer subagents report one of four statuses. Handle each appropriately:
138
157
 
139
158
  **Never** ignore an escalation or force the same model to retry without changes. If the implementer said it's stuck, something needs to change.
140
159
 
160
+ **Async dispatches:** reviewer and implementer subagents may complete asynchronously even when dispatched synchronously. Wait for each result before acting on it — never proceed to the next gate, and never touch the files a dispatched subagent is working on, while its result is pending.
161
+
162
+ ---
163
+
164
+ ## Handling Reviewer ⚠️ Items
165
+
166
+ A reviewer may report "⚠️ Cannot verify from diff" items — requirements that live in unchanged code or span tasks. These do not block the rest of the review, but you must resolve each one yourself before marking the task complete: you hold the plan and cross-task context the reviewer lacks. If you confirm an item is a real gap, treat it as a failed spec review — send it back to the implementer and re-review.
167
+
168
+ ---
169
+
170
+ ## Constructing Reviewer Prompts
171
+
172
+ Per-task reviews are task-scoped gates. The broad review happens once, at the final whole-branch review. When you fill a reviewer template:
173
+
174
+ - Do not add open-ended directives like "check all uses" or "run race tests if useful" without a concrete, task-specific reason
175
+ - Do not ask a reviewer to re-run tests the implementer already ran on the same code — the implementer's report carries the test evidence
176
+ - Do not pre-judge findings for the reviewer — never instruct a reviewer to ignore or not flag a specific issue. If you believe a finding would be a false positive, let the reviewer raise it and adjudicate it in the review loop. If the prompt you are writing contains "do not flag," "don't treat X as a defect," "at most Minor," or "the plan chose" — stop: you are pre-judging, usually to spare yourself a review loop.
177
+ - The global-constraints block you hand the reviewer is its attention lens. Copy the binding requirements verbatim from the plan's Global Constraints section or the spec: exact values, exact formats, and the stated relationships between components ("same layout as X", "matches Y"). The reviewer's template already carries the process rules — the constraints block is for what THIS project's spec demands.
178
+ - Hand the reviewer its diff as a file: run `git log --oneline BASE..HEAD`, `git diff --stat BASE..HEAD`, and `git diff -U10 BASE..HEAD`, redirected to one uniquely named file (e.g. `.monomind/taskdev/task-N-review.diff`), and pass the reviewer that path. The output never enters your own context, and the reviewer sees the commit list, stat summary, and full diff with context in one Read call. Use the BASE you recorded before dispatching the implementer — never `HEAD~1`, which silently truncates multi-commit tasks.
179
+ - A dispatch prompt describes one task, not the session's history. Do not paste accumulated prior-task summaries ("state after Tasks 1-3") into later dispatches. A fresh subagent needs its task, the interfaces it touches, and the global constraints. Nothing else.
180
+ - Dispatch fix subagents for Critical and Important findings. Record Minor findings in the progress ledger as you go, and point the final whole-branch review at that list so it can triage which must be fixed before merge. A roll-up nobody reads is a silent discard.
181
+ - A finding labeled plan-mandated — or any finding that conflicts with what the plan's text requires — is the human's decision, like any plan contradiction: present the finding and the plan text, ask which governs. Do not dismiss the finding because the plan mandates it, and do not dispatch a fix that contradicts the plan without asking. (In auto mode: resolve it explicitly, log the decision, and prefer the spec over the plan's example code.)
182
+ - The final whole-branch review gets a diff file too: generate it from the merge base (`git merge-base main HEAD`) to HEAD and include the path in the final review dispatch, so the final reviewer reads one file instead of re-deriving the branch diff with git commands.
183
+ - Every fix dispatch carries the implementer contract: the fix subagent re-runs the tests covering its change and reports the results. Name the covering test files in the dispatch — a one-line fix does not need the whole suite. Before re-dispatching the reviewer, confirm the fix report contains the covering tests, the command run, and the output.
184
+ - If the final whole-branch review returns findings, dispatch ONE fix subagent with the complete findings list — not one fixer per finding. Per-finding fixers each rebuild context and re-run suites; a real session's final-review fix wave cost more than all its tasks combined.
185
+
186
+ ---
187
+
188
+ ## File Handoffs
189
+
190
+ Everything you paste into a dispatch prompt — and everything a subagent prints back — stays resident in your context for the rest of the session and is re-read on every later turn. Hand artifacts over as files:
191
+
192
+ - **Task brief:** before dispatching an implementer, extract the task's full text from the plan into a uniquely named file (e.g. `.monomind/taskdev/task-N-brief.md`). Compose the dispatch so the brief stays the single source of requirements. Your dispatch should contain: (1) one line on where this task fits in the project; (2) the brief path, introduced as "read this first — it is your requirements, with the exact values to use verbatim"; (3) interfaces and decisions from earlier tasks that the brief cannot know; (4) your resolution of any ambiguity you noticed in the brief; (5) the report-file path and report contract. Exact values (numbers, magic strings, signatures, test cases) appear only in the brief.
193
+ - **Report file:** name the implementer's report file after the brief (brief `…/task-N-brief.md` → report `…/task-N-report.md`) and put it in the dispatch prompt. The implementer writes the full report there and returns only status, commits, a one-line test summary, and concerns.
194
+ - **Reviewer inputs:** each reviewer gets three paths — the same brief file, the report file, and the diff file — plus the global constraints that bind the task.
195
+ - Fix dispatches append their fix report (with test results) to the same report file and return a short summary; re-reviews read the updated file.
196
+
197
+ ---
198
+
199
+ ## Durable Progress
200
+
201
+ Conversation memory does not survive compaction. Controllers that lost their place have re-dispatched entire completed task sequences — the single most expensive failure observed. Track progress in a ledger file, not only in todos.
202
+
203
+ - At skill start, check for a ledger: `cat "$(git rev-parse --show-toplevel)/.monomind/taskdev/progress.md"`. Tasks listed there as complete are DONE — do not re-dispatch them; resume at the first task not marked complete.
204
+ - When a task's reviews come back clean, append one line to the ledger in the same message as your other bookkeeping: `Task N: complete (commits <base7>..<head7>, spec ✅ quality ✅)`.
205
+ - The ledger is your recovery map: the commits it names exist in git even when your context no longer remembers creating them. After compaction, trust the ledger and `git log` over your own recollection.
206
+ - The ledger is git-ignored scratch; if it's destroyed, recover from `git log`.
207
+
141
208
  ---
142
209
 
143
210
  ## Prompt Templates
@@ -145,6 +212,7 @@ Implementer subagents report one of four statuses. Handle each appropriately:
145
212
  - `./implementer-prompt.md` - Dispatch implementer subagent
146
213
  - `./spec-reviewer-prompt.md` - Dispatch spec compliance reviewer subagent
147
214
  - `./code-quality-reviewer-prompt.md` - Dispatch code quality reviewer subagent
215
+ - `./final-reviewer-prompt.md` - Dispatch the final whole-branch reviewer (most capable model, diff as a file)
148
216
 
149
217
  ---
150
218
 
@@ -154,13 +222,13 @@ Implementer subagents report one of four statuses. Handle each appropriately:
154
222
  You: I'm using mastermind:taskdev to execute this plan.
155
223
 
156
224
  [Read plan file once: docs/mastermind/plans/feature-plan.md]
157
- [Extract all 5 tasks with full text and context]
225
+ [Check ledger .monomind/taskdev/progress.md empty, starting fresh]
158
226
  [Create TodoWrite with all tasks]
159
227
 
160
228
  Task 1: Hook installation script
161
229
 
162
- [Get Task 1 text and context (already extracted)]
163
- [Dispatch implementation subagent with full task text + context]
230
+ [Extract Task 1 brief to .monomind/taskdev/task-1-brief.md; record BASE]
231
+ [Dispatch implementer with brief + report paths + scene-setting context]
164
232
 
165
233
  Implementer: "Before I begin - should the hook be installed at user or system level?"
166
234
 
@@ -173,18 +241,18 @@ Implementer: "Got it. Implementing now..."
173
241
  - Self-review: Found I missed --force flag, added it
174
242
  - Committed
175
243
 
176
- [Dispatch spec compliance reviewer]
244
+ [Generate diff file from recorded BASE, dispatch spec compliance reviewer]
177
245
  Spec reviewer: ✅ Spec compliant - all requirements met, nothing extra
178
246
 
179
- [Get git SHAs, dispatch code quality reviewer]
247
+ [Dispatch code quality reviewer with the same diff file]
180
248
  Code reviewer: Strengths: Good test coverage, clean. Issues: None. Approved.
181
249
 
182
250
  [Mark Task 1 complete]
183
251
 
184
252
  Task 2: Recovery modes
185
253
 
186
- [Get Task 2 text and context (already extracted)]
187
- [Dispatch implementation subagent with full task text + context]
254
+ [Extract Task 2 brief to .monomind/taskdev/task-2-brief.md; record BASE]
255
+ [Dispatch implementer with brief + report paths + Task 1 interfaces]
188
256
 
189
257
  Implementer: [No questions, proceeds]
190
258
  Implementer:
@@ -240,8 +308,7 @@ Done!
240
308
  - Review checkpoints automatic
241
309
 
242
310
  **Efficiency gains:**
243
- - No file reading overhead (controller provides full text)
244
- - Controller curates exactly what context is needed
311
+ - Controller curates exactly what context is needed; bulk artifacts move as files (briefs, reports, diffs), not pasted text
245
312
  - Subagent gets complete information upfront
246
313
  - Questions surfaced before work begins (not after)
247
314
 
@@ -267,14 +334,17 @@ Done!
267
334
  - Skip reviews (spec compliance OR code quality)
268
335
  - Proceed with unfixed issues
269
336
  - Dispatch multiple implementation subagents in parallel (conflicts)
270
- - Make subagent read plan file (provide full text instead)
337
+ - Make a subagent read the whole plan file (hand it its task-brief file instead)
271
338
  - Skip scene-setting context (subagent needs to understand where task fits)
272
339
  - Ignore subagent questions (answer before letting them proceed)
273
340
  - Accept "close enough" on spec compliance (spec reviewer found issues = not done)
274
341
  - Skip review loops (reviewer found issues = implementer fixes = review again)
275
342
  - Let implementer self-review replace actual review (both are needed)
276
343
  - **Start code quality review before spec compliance is ✅** (wrong order)
277
- - Move to next task while either review has open issues
344
+ - Move to next task while either review has open Critical/Important issues
345
+ - Tell a reviewer what not to flag, or pre-rate a finding's severity in the dispatch prompt ("treat it as Minor at most") — the plan's example code is a starting point, not evidence that its weaknesses were chosen
346
+ - Dispatch a reviewer without a diff file — generate it first from the recorded BASE and name the path in the prompt
347
+ - Re-dispatch a task the progress ledger already marks complete — check the ledger (and `git log`) after any compaction or resume
278
348
 
279
349
  **If subagent asks questions:**
280
350
  - Answer clearly and completely
@@ -376,6 +376,36 @@ Can't check all boxes? You skipped TDD. Start over.
376
376
 
377
377
  ---
378
378
 
379
+ ## Testing Anti-Patterns
380
+
381
+ Tests must verify real behavior, not mock behavior. Mocks are a means to isolate, not the thing being tested. **Test what the code does, not what the mocks do.**
382
+
383
+ **The Iron Laws of mocking:**
384
+
385
+ ```
386
+ 1. NEVER test mock behavior
387
+ 2. NEVER add test-only methods to production classes
388
+ 3. NEVER mock without understanding dependencies
389
+ ```
390
+
391
+ | Anti-pattern | The violation | The fix |
392
+ |---|---|---|
393
+ | **Testing mock behavior** | Asserting a `*-mock` element or mocked return exists — the test verifies the mock works, not the component | Test the real component, or don't assert on the mock at all |
394
+ | **Test-only methods in production** | A method (e.g. `destroy()`) that only tests call, living on a production class | Move it to test utilities; production classes carry only production API |
395
+ | **Mocking without understanding** | Mocking a method whose side effect the test depends on ("I'll mock this to be safe") — the test passes for the wrong reason or fails mysteriously | Run the test against the real implementation FIRST, observe what it needs, then mock minimally at the lowest slow/external level |
396
+ | **Incomplete mocks** | Mocking only the fields your immediate test uses — downstream code reads omitted fields and fails silently in integration | Mirror the COMPLETE real data structure; if uncertain, include all documented fields |
397
+ | **Tests as afterthought** | "Implementation complete, ready for testing" | Testing is part of implementation — TDD, tests first |
398
+
399
+ **Gate before any mock:** What side effects does the real method have? Does this test depend on any of them? Do I fully understand what this test needs? If unsure — run against the real implementation first.
400
+
401
+ **When mocks become too complex** (setup longer than test logic, mock missing methods real components have, test breaks when mock changes): ask "do we need a mock here at all?" — integration tests with real components are often simpler than complex mocks.
402
+
403
+ **Red flags:** assertions on `*-mock` IDs · methods only called from test files · mock setup >50% of the test · test fails when you remove the mock · can't explain why the mock is needed · mocking "just to be safe".
404
+
405
+ If TDD reveals you're testing mock behavior, you added mocks without watching the test fail against real code first — test real behavior or question why you're mocking at all.
406
+
407
+ ---
408
+
379
409
  ## Debugging Integration
380
410
 
381
411
  Bug found? Write failing test reproducing it. Follow TDD cycle. Test proves fix and prevents regression.
@@ -109,8 +109,9 @@ export function loadState() {
109
109
  return merged;
110
110
  }
111
111
  }
112
- catch {
113
- // Corrupted state file — return defaults
112
+ catch (e) {
113
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
114
+ console.error('[autopilot-state] failed to load state file, using defaults:', e);
114
115
  }
115
116
  return defaults;
116
117
  }
@@ -214,8 +215,9 @@ export function loadLog() {
214
215
  return out;
215
216
  }
216
217
  }
217
- catch {
218
- // Corrupted log — return empty
218
+ catch (e) {
219
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
220
+ console.error('[autopilot-state] failed to load log file, returning empty:', e);
219
221
  }
220
222
  return [];
221
223
  }
@@ -125,7 +125,10 @@ export function getDashboardServer(port = DEFAULT_PORT) {
125
125
  client.send(body);
126
126
  }
127
127
  }
128
- catch { }
128
+ catch (e) {
129
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
130
+ console.error('[dashboard] /api/mastermind/event received invalid JSON, not broadcast:', e);
131
+ }
129
132
  res.writeHead(200, { 'Content-Type': 'application/json' });
130
133
  res.end('{"ok":true}');
131
134
  });
@@ -45,8 +45,10 @@ export class CapabilityManager {
45
45
  fs.writeFileSync(tmpPath, JSON.stringify({ active: [...this.active.keys()] }, null, 2));
46
46
  fs.renameSync(tmpPath, capsPath);
47
47
  }
48
- catch {
48
+ catch (e) {
49
49
  // best-effort persistence; activation state still holds in-memory
50
+ if (process.env.DEBUG || process.env.MONOMIND_DEBUG)
51
+ console.error('[capabilities] failed to persist capabilities.json:', e);
50
52
  }
51
53
  }
52
54
  isActive(name) {
@@ -131,6 +131,16 @@ export const spawnCommand = {
131
131
  priority: 'normal',
132
132
  metadata: { name: agentName, capabilities: getAgentCapabilities(agentType) },
133
133
  });
134
+ // agent_spawn resolves (doesn't throw) on a tool-level failure like
135
+ // "agent store is unreadable" or "agent already exists" — callMCPTool
136
+ // only throws for registry/infra errors (tool not found/disabled), not
137
+ // for a handler's own {success:false} response. Without this check the
138
+ // CLI silently rendered a "spawned successfully" table full of
139
+ // undefined fields for a spawn that never actually happened.
140
+ if (result.success === false) {
141
+ output.printError(`Failed to spawn agent: ${result.error || 'unknown error'}`);
142
+ return { success: false, exitCode: 1 };
143
+ }
134
144
  output.writeln();
135
145
  output.printTable({
136
146
  columns: [
@@ -230,6 +240,14 @@ export const statusCommand = {
230
240
  }
231
241
  try {
232
242
  const status = await callMCPTool('agent_status', { agentId, includeMetrics: true, includeHistory: false });
243
+ // agent_status resolves (doesn't throw) with {status:'not_found', error}
244
+ // for a nonexistent agent — callMCPTool only throws for registry/infra
245
+ // errors, not a handler's own not-found response. Without this check
246
+ // the CLI reported success:true for an agent that was never found.
247
+ if (status.error) {
248
+ output.printError(`Failed to get agent status: ${status.error}`);
249
+ return { success: false, exitCode: 1 };
250
+ }
233
251
  if (ctx.flags.format === 'json') {
234
252
  output.printJson(status);
235
253
  return { success: true, data: status };
@@ -296,6 +314,14 @@ export const stopCommand = {
296
314
  const result = await callMCPTool('agent_terminate', {
297
315
  agentId, graceful: !force, reason: 'Stopped by user via CLI',
298
316
  });
317
+ // agent_terminate resolves (doesn't throw) with {success:false, error}
318
+ // for a nonexistent agent or an unreadable store — callMCPTool only
319
+ // throws for registry/infra errors. Without this check the CLI printed
320
+ // "stopped successfully" for an agent that was never actually stopped.
321
+ if (result.success === false) {
322
+ output.printError(`Failed to stop agent: ${result.error || 'unknown error'}`);
323
+ return { success: false, exitCode: 1 };
324
+ }
299
325
  if (!force) {
300
326
  output.writeln(output.dim(' Completing current task...'));
301
327
  output.writeln(output.dim(' Saving state...'));
@@ -9,52 +9,36 @@ export const metricsCommand = {
9
9
  name: 'metrics',
10
10
  description: 'Show agent performance metrics',
11
11
  action: async (ctx) => {
12
- const { existsSync, readFileSync, readdirSync, statSync } = await import('fs');
13
- const { join } = await import('path');
12
+ // Previously read from .swarm/agents/*.json (one file per agent) — a
13
+ // directory nothing in the codebase has ever written to. agent_spawn
14
+ // (agent-tools.ts) persists to a single store.json at
15
+ // getMonomindDataRoot()/agents/store.json, keyed by agentId. This
16
+ // command was structurally incapable of reflecting a single real
17
+ // spawned agent; always reported zero regardless of activity. Read the
18
+ // real store instead.
19
+ const { loadAgentStore } = await import('../mcp-tools/agent-tools.js');
20
+ const store = loadAgentStore();
21
+ const agents = Object.values(store.agents);
14
22
  let totalAgents = 0;
15
23
  let activeAgents = 0;
16
24
  let tasksCompleted = 0;
17
25
  const typeCounts = {};
18
- const swarmDir = join(process.cwd(), '.swarm');
19
- const agentsDir = join(swarmDir, 'agents');
20
- if (existsSync(agentsDir)) {
21
- try {
22
- const files = readdirSync(agentsDir).filter(f => f.endsWith('.json'));
23
- for (const file of files) {
24
- try {
25
- const agentFilePath = join(agentsDir, file);
26
- if (statSync(agentFilePath).size > 512 * 1024)
27
- continue;
28
- const data = JSON.parse(readFileSync(agentFilePath, 'utf-8'));
29
- totalAgents++;
30
- const agType = data.type || 'unknown';
31
- if (!typeCounts[agType])
32
- typeCounts[agType] = { count: 0, tasks: 0, success: 0 };
33
- typeCounts[agType].count++;
34
- if (data.status === 'active' || data.status === 'running')
35
- activeAgents++;
36
- if (data.tasksCompleted) {
37
- typeCounts[agType].tasks += data.tasksCompleted;
38
- tasksCompleted += data.tasksCompleted;
39
- }
40
- if (data.successCount)
41
- typeCounts[agType].success += data.successCount;
42
- }
43
- catch { /* skip malformed */ }
44
- }
45
- }
46
- catch { /* no agents dir */ }
47
- }
48
- const activityFile = join(swarmDir, 'swarm-activity.json');
49
- if (existsSync(activityFile) && statSync(activityFile).size <= 10 * 1024 * 1024) {
50
- try {
51
- const activity = JSON.parse(readFileSync(activityFile, 'utf-8'));
52
- if (activity.totalAgents && totalAgents === 0)
53
- totalAgents = activity.totalAgents;
54
- if (activity.activeAgents && activeAgents === 0)
55
- activeAgents = activity.activeAgents;
56
- }
57
- catch { /* ignore */ }
26
+ for (const agent of agents) {
27
+ totalAgents++;
28
+ const agType = agent.agentType || 'unknown';
29
+ if (!typeCounts[agType])
30
+ typeCounts[agType] = { count: 0, tasks: 0 };
31
+ typeCounts[agType].count++;
32
+ if (agent.status !== 'terminated')
33
+ activeAgents++;
34
+ // AgentRecord tracks a single taskCount, not a completed/failed
35
+ // breakdown — this is the best available proxy for "task activity",
36
+ // not literally "completed" tasks. Success-rate data doesn't exist
37
+ // anywhere in the current agent store schema, so it's reported as
38
+ // N/A below rather than fabricated.
39
+ const taskCount = agent.taskCount || 0;
40
+ typeCounts[agType].tasks += taskCount;
41
+ tasksCompleted += taskCount;
58
42
  }
59
43
  let vectorCount = 0;
60
44
  try {
@@ -65,11 +49,9 @@ export const metricsCommand = {
65
49
  catch { /* backend unavailable */ }
66
50
  const byType = Object.entries(typeCounts).map(([type, data]) => ({
67
51
  type, count: data.count, tasks: data.tasks,
68
- successRate: data.tasks > 0 ? `${Math.round((data.success / data.tasks) * 100)}%` : 'N/A',
52
+ successRate: 'N/A', // not tracked in the current agent store schema
69
53
  }));
70
- const avgSuccessRate = tasksCompleted > 0
71
- ? `${Math.round(Object.values(typeCounts).reduce((a, d) => a + d.success, 0) / tasksCompleted * 100)}%`
72
- : 'N/A';
54
+ const avgSuccessRate = 'N/A'; // not tracked in the current agent store schema
73
55
  const metrics = {
74
56
  summary: {
75
57
  totalAgents, activeAgents, tasksCompleted, avgSuccessRate, vectorCount,
@@ -5,6 +5,24 @@
5
5
  * github.com/monoes/monomind
6
6
  */
7
7
  import type { Command } from '../types.js';
8
+ /** A single stale-scratch candidate returned by {@link findStaleScratch}. */
9
+ interface StaleScratchItem {
10
+ path: string;
11
+ description: string;
12
+ size: number;
13
+ }
14
+ /**
15
+ * Find stale mastermind scratch under `.monomind/taskdev/` and `.monomind/loops/`.
16
+ * Exported for tests. Never returns `progress.md` (the taskdev recovery ledger),
17
+ * directories, or loop JSON it cannot parse — deleting the unclassifiable loses data.
18
+ *
19
+ * A loop JSON is only ever classified as abandoned when it has a real, positive,
20
+ * numeric `nextRunAt` timestamp that is more than a day in the past. Live producers
21
+ * write `nextRunAt: 0` (real /do loops) or `nextRunAt: null` (dashboard) — those,
22
+ * along with missing/non-numeric values, are never eligible for deletion regardless
23
+ * of `status`, since a crashed daemon can leave a stale `status: 'running'` behind.
24
+ */
25
+ export declare function findStaleScratch(cwd: string, now: number): StaleScratchItem[];
8
26
  /**
9
27
  * Cleanup command definition
10
28
  */
@@ -29,6 +29,95 @@ const KEEP_CONFIG_PATHS = [
29
29
  'monomind.config.json',
30
30
  join('.claude', 'settings.json'),
31
31
  ];
32
+ /** Scratch pruning (--scratch): taskdev handoff files and loop state. */
33
+ // monolean: manual flag only — upgrade path: invoke from the `cache` background worker so crashed-run scratch is pruned without anyone remembering the flag
34
+ const SCRATCH_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // taskdev scratch older than this is stale
35
+ const LOOP_STALE_GRACE_MS = 24 * 60 * 60 * 1000; // a live loop reschedules every <=1h; overdue by a day = abandoned
36
+ /**
37
+ * Find stale mastermind scratch under `.monomind/taskdev/` and `.monomind/loops/`.
38
+ * Exported for tests. Never returns `progress.md` (the taskdev recovery ledger),
39
+ * directories, or loop JSON it cannot parse — deleting the unclassifiable loses data.
40
+ *
41
+ * A loop JSON is only ever classified as abandoned when it has a real, positive,
42
+ * numeric `nextRunAt` timestamp that is more than a day in the past. Live producers
43
+ * write `nextRunAt: 0` (real /do loops) or `nextRunAt: null` (dashboard) — those,
44
+ * along with missing/non-numeric values, are never eligible for deletion regardless
45
+ * of `status`, since a crashed daemon can leave a stale `status: 'running'` behind.
46
+ */
47
+ export function findStaleScratch(cwd, now) {
48
+ const out = [];
49
+ const taskdevDir = join(cwd, '.monomind', 'taskdev');
50
+ if (existsSync(taskdevDir)) {
51
+ for (const f of readdirSync(taskdevDir)) {
52
+ if (f === 'progress.md')
53
+ continue; // the ledger is the recovery map — never auto-prune
54
+ try {
55
+ const st = lstatSync(join(taskdevDir, f));
56
+ if (st.isFile() && now - st.mtimeMs > SCRATCH_MAX_AGE_MS) {
57
+ out.push({ path: join('.monomind', 'taskdev', f), description: 'stale taskdev scratch', size: st.size });
58
+ }
59
+ }
60
+ catch { /* raced away or unreadable — leave it */ }
61
+ }
62
+ }
63
+ const loopsDir = join(cwd, '.monomind', 'loops');
64
+ if (existsSync(loopsDir)) {
65
+ const entries = readdirSync(loopsDir);
66
+ const jsonStems = new Set(entries.filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, '')));
67
+ for (const f of entries) {
68
+ try {
69
+ const st = lstatSync(join(loopsDir, f));
70
+ if (!st.isFile())
71
+ continue;
72
+ if (f.endsWith('.json')) {
73
+ const parsed = JSON.parse(readFileSync(join(loopsDir, f), 'utf8'));
74
+ const { nextRunAt } = parsed;
75
+ const isStale = typeof nextRunAt === 'number' &&
76
+ Number.isFinite(nextRunAt) &&
77
+ nextRunAt > 0 &&
78
+ now - nextRunAt > LOOP_STALE_GRACE_MS;
79
+ if (isStale) {
80
+ out.push({ path: join('.monomind', 'loops', f), description: 'abandoned loop state', size: st.size });
81
+ }
82
+ }
83
+ else if (f.endsWith('.stop') && !jsonStems.has(f.replace(/\.stop$/, ''))) {
84
+ out.push({ path: join('.monomind', 'loops', f), description: 'orphaned loop stopfile', size: st.size });
85
+ }
86
+ }
87
+ catch { /* unparseable or unreadable — never delete what we cannot classify */ }
88
+ }
89
+ }
90
+ return out;
91
+ }
92
+ /**
93
+ * Report (dry run) or delete a batch of file/dir items, printing the same
94
+ * `[would remove]` / `[removed]` / `[failed]` lines used across cleanup modes.
95
+ * Shared by the scratch branch; the main artifact loop keeps its own copy
96
+ * because of the `--keep-config` `.claude/settings.json` preservation special case.
97
+ */
98
+ function removeOrReport(cwd, items, dryRun) {
99
+ let removed = 0;
100
+ let removedSize = 0;
101
+ for (const item of items) {
102
+ const sizeStr = formatSize(item.size);
103
+ const typeLabel = item.type === 'dir' ? 'dir ' : 'file';
104
+ if (dryRun) {
105
+ output.writeln(output.warning(` [would remove] ${typeLabel} ${item.path} (${sizeStr}) - ${item.description}`));
106
+ }
107
+ else {
108
+ try {
109
+ rmSync(join(cwd, item.path), { recursive: item.type === 'dir', force: true });
110
+ output.writeln(output.success(` [removed] ${typeLabel} ${item.path} (${sizeStr}) - ${item.description}`));
111
+ removed++;
112
+ removedSize += item.size;
113
+ }
114
+ catch (err) {
115
+ output.writeln(output.error(` [failed] ${typeLabel} ${item.path} - ${err instanceof Error ? err.message : String(err)}`));
116
+ }
117
+ }
118
+ }
119
+ return { removed, removedSize };
120
+ }
32
121
  /**
33
122
  * Maximum directory recursion depth for size calculation.
34
123
  * Prevents stack overflow on deeply-nested or circular-symlink trees.
@@ -112,6 +201,13 @@ export const cleanupCommand = {
112
201
  type: 'boolean',
113
202
  default: false,
114
203
  },
204
+ {
205
+ name: 'scratch',
206
+ short: 's',
207
+ description: 'Prune only stale mastermind scratch (.monomind/taskdev, abandoned .monomind/loops state)',
208
+ type: 'boolean',
209
+ default: false,
210
+ },
115
211
  ],
116
212
  examples: [
117
213
  {
@@ -126,12 +222,37 @@ export const cleanupCommand = {
126
222
  command: 'cleanup --force --keep-config',
127
223
  description: 'Remove artifacts but keep configuration files',
128
224
  },
225
+ {
226
+ command: 'cleanup --scratch --force',
227
+ description: 'Delete stale taskdev scratch and abandoned loop state',
228
+ },
129
229
  ],
130
230
  action: async (ctx) => {
131
231
  const force = ctx.flags.force === true;
132
232
  const keepConfig = ctx.flags['keep-config'] === true;
133
233
  const cwd = ctx.cwd;
134
234
  const dryRun = !force;
235
+ if (ctx.flags.scratch === true) {
236
+ const now = Date.now();
237
+ output.writeln();
238
+ output.writeln(output.bold(dryRun ? 'Monomind Scratch Cleanup (dry run)' : 'Monomind Scratch Cleanup'));
239
+ output.writeln();
240
+ const stale = findStaleScratch(cwd, now);
241
+ if (stale.length === 0) {
242
+ output.writeln(output.info('No stale scratch found.'));
243
+ return { success: true, message: 'Nothing to clean' };
244
+ }
245
+ const { removed, removedSize } = removeOrReport(cwd, stale.map(item => ({ ...item, type: 'file' })), dryRun);
246
+ output.writeln();
247
+ if (dryRun) {
248
+ output.writeln(output.dim(` ${stale.length} stale file(s). This was a dry run. Use --force to delete.`));
249
+ output.writeln();
250
+ return { success: true, message: `Dry run: ${stale.length} stale scratch file(s) found`, data: { found: stale, dryRun } };
251
+ }
252
+ output.writeln(` Removed ${removed} file(s) totaling ${formatSize(removedSize)}`);
253
+ output.writeln();
254
+ return { success: true, message: `Removed ${removed} stale scratch file(s)`, data: { found: stale, removedCount: removed, removedSize, dryRun } };
255
+ }
135
256
  output.writeln();
136
257
  output.writeln(output.bold(dryRun
137
258
  ? 'Monomind Cleanup (dry run)'