@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
@@ -28,7 +28,7 @@ This skill is invoked by `mastermind:issue-detail` or directly via `/mastermind:
28
28
  ## Issue Status Flow
29
29
 
30
30
  ```
31
- open → in_progress → done
31
+ todo → in_progress → in_review → done
32
32
  ↓
33
33
  blocked → in_progress
34
34
  ↓
@@ -52,7 +52,7 @@ orgFile=".monomind/orgs/${org_name}.json"
52
52
  [ ! -f "$orgFile" ] && { echo "ERROR: Org '${org_name}' not found."; exit 1; }
53
53
 
54
54
  issuesFile=".monomind/orgs/${org_name}-issues.json"
55
- [ ! -f "$issuesFile" ] && { echo "ERROR: No issues file for org '$org_name'. Create tasks via /mastermind:tasks."; exit 1; }
55
+ [ ! -f "$issuesFile" ] && { echo "ERROR: No issues file for org '$org_name'. Create issues via /mastermind:issues --action create."; exit 1; }
56
56
 
57
57
  issueDef=$(jq -r --arg id "$issue_id" '(.issues // [])[] | select(.id == $id or .slug == $id)' "$issuesFile")
58
58
  [ -z "$issueDef" ] && { echo "ERROR: Issue '$issue_id' not found in org '$org_name'."; exit 1; }
@@ -64,6 +64,38 @@ days=${days:-14}
64
64
  cutoff=$(date -u -v-${days}d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "${days} days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")
65
65
  ```
66
66
 
67
+ Normalize any legacy records written by a pre-2.10 version of these skills. Idempotent — safe to run on every load.
68
+
69
+ ```bash
70
+ python3 - "$issuesFile" <<'PYEOF'
71
+ import json, sys
72
+ path = sys.argv[1]
73
+ data = json.load(open(path))
74
+ RENAME = {
75
+ "assignee_id": "assigneeId", "assigned_to": "assigneeId",
76
+ "created_at": "createdAt", "updated_at": "updatedAt",
77
+ "closed_at": "closedAt", "project_id": "projectId",
78
+ "parent_id": "parentId", "recovery_status": "recoveryStatus",
79
+ "lastActivityAt": "updatedAt",
80
+ }
81
+ changed = False
82
+ for iss in data.get("issues", []):
83
+ for old, new in RENAME.items():
84
+ if old in iss:
85
+ iss.setdefault(new, iss.pop(old))
86
+ iss.pop(old, None)
87
+ changed = True
88
+ if iss.get("status") == "open":
89
+ iss["status"] = "todo"
90
+ changed = True
91
+ if changed:
92
+ tmp = path + ".tmp"
93
+ with open(tmp, "w") as f:
94
+ json.dump(data, f, indent=2)
95
+ import os; os.replace(tmp, path)
96
+ PYEOF
97
+ ```
98
+
67
99
  ---
68
100
 
69
101
  ## Step 2 — Execute Action
@@ -77,16 +109,17 @@ echo "────────────────────────
77
109
  echo "$issueDef" | jq -r '
78
110
  " ID: \(.id)",
79
111
  " Title: \(.title // "(no title)")",
80
- " Status: \(.status // "open")",
112
+ " Status: \(.status // "todo")",
81
113
  " Priority: \(.priority // "medium")",
82
- " Assignee: \(.assignee_id // "(unassigned)")",
83
- " Project: \(.project_id // "(none)")",
84
- " Created: \(.created_at // "-")",
85
- " Updated: \(.updated_at // "-")"
114
+ " Assignee: \(.assigneeId // "(unassigned)")",
115
+ " Agent: \(.assigneeAgentId // "(none)")",
116
+ " Project: \(.projectId // "(none)")",
117
+ " Created: \(.createdAt // "-")",
118
+ " Updated: \(.updatedAt // "-")"
86
119
  '
87
120
 
88
121
  # Sub-issues
89
- subCount=$(jq --arg pid "$resolvedId" '[(.issues // [])[] | select(.parent_id == $pid)] | length' "$issuesFile" 2>/dev/null || echo 0)
122
+ subCount=$(jq --arg pid "$resolvedId" '[(.issues // [])[] | select(.parentId == $pid)] | length' "$issuesFile" 2>/dev/null || echo 0)
90
123
  echo " Sub-issues: $subCount"
91
124
 
92
125
  # Attachments
@@ -94,7 +127,7 @@ attCount=$(echo "$issueDef" | jq -r '(.attachments // []) | length')
94
127
  echo " Attachments: $attCount"
95
128
 
96
129
  # Recovery
97
- recoveryStatus=$(echo "$issueDef" | jq -r '.recovery_status // "none"')
130
+ recoveryStatus=$(echo "$issueDef" | jq -r '.recoveryStatus // "none"')
98
131
  [ "$recoveryStatus" != "none" ] && echo "" && echo " RECOVERY STATUS: $recoveryStatus"
99
132
 
100
133
  # Description
@@ -169,15 +202,15 @@ printf "%-24s %-12s %-10s %s\n" "ID" "STATUS" "PRIORITY" "TITLE"
169
202
  echo "────────────────────────────────────────────────────────"
170
203
 
171
204
  count=0
172
- jq -r --arg pid "$resolvedId" '(.issues // [])[] | select(.parent_id == $pid) |
173
- [.id, (.status // "open"), (.priority // "medium"), (.title // "(no title)")] | @tsv' \
205
+ jq -r --arg pid "$resolvedId" '(.issues // [])[] | select(.parentId == $pid) |
206
+ [.id, (.status // "todo"), (.priority // "medium"), (.title // "(no title)")] | @tsv' \
174
207
  "$issuesFile" 2>/dev/null | while IFS=$'\t' read -r id st pri title; do
175
208
  printf "%-24s %-12s %-10s %s\n" "$id" "$st" "$pri" "$title"
176
209
  count=$((count + 1))
177
210
  done
178
211
 
179
212
  echo ""
180
- echo "To create a sub-issue: /mastermind:tasks --org $org_name --action create --parent-id $issue_id"
213
+ echo "To create a sub-issue: /mastermind:issues --org $org_name --action create --title '<title>' --parent-id $issue_id"
181
214
  ```
182
215
 
183
216
  ### attachments
@@ -221,10 +254,16 @@ exists=$(jq --arg id "$assignee_id" '[(.roles // [])[] | select(.id == $id)] | l
221
254
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
222
255
  tmp="${issuesFile}.tmp"
223
256
  jq --arg id "$resolvedId" --arg ag "$assignee_id" --arg ts "$ts" \
224
- '.issues = [(.issues // [])[] | if .id == $id then .assignee_id = $ag | .updated_at = $ts else . end]' \
257
+ '.issues = [(.issues // [])[] | if .id == $id then .assigneeId = $ag | .assigneeAgentId = $ag | .assigneeUserId = null | .updatedAt = $ts else . end]' \
225
258
  "$issuesFile" > "$tmp" && mv "$tmp" "$issuesFile"
226
259
 
227
260
  echo "Issue '$issue_id' assigned to '$assignee_id'."
261
+
262
+ activityFile=".monomind/orgs/${org_name}-activity.jsonl"
263
+ currentStatus=$(jq -r --arg id "$resolvedId" '(.issues // [])[] | select(.id == $id) | .status // "todo"' "$issuesFile")
264
+ jq -cn --arg iid "$resolvedId" --arg ts "$ts" --arg st "$currentStatus" --arg ag "$assignee_id" --arg sm "assign $resolvedId" \
265
+ '{issue_id:$iid, ts:$ts, status:$st, tokens:null, agent:$ag, type:"assign", summary:$sm}' \
266
+ >> "$activityFile"
228
267
  ```
229
268
 
230
269
  ### close
@@ -233,9 +272,14 @@ echo "Issue '$issue_id' assigned to '$assignee_id'."
233
272
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
234
273
  tmp="${issuesFile}.tmp"
235
274
  jq --arg id "$resolvedId" --arg ts "$ts" \
236
- '.issues = [(.issues // [])[] | if .id == $id then .status = "done" | .updated_at = $ts | .closed_at = $ts else . end]' \
275
+ '.issues = [(.issues // [])[] | if .id == $id then .status = "done" | .updatedAt = $ts | .closedAt = $ts else . end]' \
237
276
  "$issuesFile" > "$tmp" && mv "$tmp" "$issuesFile"
238
277
  echo "Issue '$issue_id' → done (closed at $ts)."
278
+
279
+ activityFile=".monomind/orgs/${org_name}-activity.jsonl"
280
+ jq -cn --arg iid "$resolvedId" --arg ts "$ts" --arg st "done" --arg ag "operator" --arg sm "close $resolvedId" \
281
+ '{issue_id:$iid, ts:$ts, status:$st, tokens:null, agent:$ag, type:"close", summary:$sm}' \
282
+ >> "$activityFile"
239
283
  ```
240
284
 
241
285
  ### reopen
@@ -244,9 +288,14 @@ echo "Issue '$issue_id' → done (closed at $ts)."
244
288
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
245
289
  tmp="${issuesFile}.tmp"
246
290
  jq --arg id "$resolvedId" --arg ts "$ts" \
247
- '.issues = [(.issues // [])[] | if .id == $id then .status = "open" | .updated_at = $ts | .closed_at = null else . end]' \
291
+ '.issues = [(.issues // [])[] | if .id == $id then .status = "todo" | .updatedAt = $ts | .closedAt = null else . end]' \
248
292
  "$issuesFile" > "$tmp" && mv "$tmp" "$issuesFile"
249
293
  echo "Issue '$issue_id' → reopened."
294
+
295
+ activityFile=".monomind/orgs/${org_name}-activity.jsonl"
296
+ jq -cn --arg iid "$resolvedId" --arg ts "$ts" --arg st "todo" --arg ag "operator" --arg sm "reopen $resolvedId" \
297
+ '{issue_id:$iid, ts:$ts, status:$st, tokens:null, agent:$ag, type:"reopen", summary:$sm}' \
298
+ >> "$activityFile"
250
299
  ```
251
300
 
252
301
  ### recover
@@ -263,12 +312,17 @@ newStatus=$([ "$recovery_action" = "accept" ] && echo "in_progress" || echo "can
263
312
  tmp="${issuesFile}.tmp"
264
313
  jq --arg id "$resolvedId" --arg st "$newStatus" --arg ts "$ts" --arg ra "$recovery_action" \
265
314
  '.issues = [(.issues // [])[] | if .id == $id then
266
- .status = $st | .recovery_status = $ra | .updated_at = $ts
315
+ .status = $st | .recoveryStatus = $ra | .updatedAt = $ts
267
316
  else . end]' \
268
317
  "$issuesFile" > "$tmp" && mv "$tmp" "$issuesFile"
269
318
 
270
319
  echo "Recovery action '$recovery_action' applied to '$issue_id'."
271
320
  echo " New status: $newStatus"
321
+
322
+ activityFile=".monomind/orgs/${org_name}-activity.jsonl"
323
+ jq -cn --arg iid "$resolvedId" --arg ts "$ts" --arg st "$newStatus" --arg ag "operator" --arg sm "recover $resolvedId" \
324
+ '{issue_id:$iid, ts:$ts, status:$st, tokens:null, agent:$ag, type:"recover", summary:$sm}' \
325
+ >> "$activityFile"
272
326
  ```
273
327
 
274
328
  ---
@@ -18,13 +18,14 @@ This skill is invoked by `mastermind:issues` or directly via `/mastermind:issues
18
18
  - `action`: list | create | update | close | search
19
19
  - `issue_id`: issue ID (required for update/close)
20
20
  - `query`: search term (for search)
21
- - `status`: open | in_progress | in_review | done | cancelled (filter)
21
+ - `status`: todo | in_progress | blocked | in_review | done | cancelled (filter)
22
22
  - `assignee`: agent ID filter
23
23
  - `workspace`: workspace ID filter
24
24
  - `title`: issue title (for create)
25
25
  - `description`: issue body (for create)
26
26
  - `priority`: low | medium | high | urgent (for create/update)
27
27
  - `limit`: max results (default: 50)
28
+ - `parent_id`: parent issue id, making the new issue a sub-issue (for create)
28
29
  - `caller`: command | master
29
30
 
30
31
  ---
@@ -47,6 +48,45 @@ issuesFile=".monomind/orgs/${org_name}-issues.json"
47
48
  limit="${limit:-50}"
48
49
  ```
49
50
 
51
+ Normalize any legacy records written by a pre-2.10 version of these skills. Idempotent — safe to run on every load.
52
+
53
+ ```bash
54
+ python3 - "$issuesFile" <<'PYEOF'
55
+ import json, sys
56
+ path = sys.argv[1]
57
+ data = json.load(open(path))
58
+ RENAME = {
59
+ "assignee_id": "assigneeId", "assigned_to": "assigneeId",
60
+ "created_at": "createdAt", "updated_at": "updatedAt",
61
+ "closed_at": "closedAt", "project_id": "projectId",
62
+ "parent_id": "parentId", "recovery_status": "recoveryStatus",
63
+ "lastActivityAt": "updatedAt",
64
+ }
65
+ changed = False
66
+ for iss in data.get("issues", []):
67
+ for old, new in RENAME.items():
68
+ if old in iss:
69
+ iss.setdefault(new, iss.pop(old))
70
+ iss.pop(old, None)
71
+ changed = True
72
+ if iss.get("status") == "open":
73
+ iss["status"] = "todo"
74
+ changed = True
75
+ if changed:
76
+ tmp = path + ".tmp"
77
+ with open(tmp, "w") as f:
78
+ json.dump(data, f, indent=2)
79
+ import os; os.replace(tmp, path)
80
+ PYEOF
81
+ ```
82
+
83
+ > **These issues are not auto-consumed by the org runtime.** `OrgDaemon` builds its `TaskDag`
84
+ > fresh in memory on every start and never reads `<org>-issues.json` (verified: zero references
85
+ > in `packages/@monomind/cli/src/orgrt/`). Issues created here are tracked by the
86
+ > `mastermind-issues` / `mastermind-my-issues` / `mastermind-liveness` skills and shown in the
87
+ > dashboard, but running `monomind org run <org>` will not pick them up. Drive execution from
88
+ > this file with `mastermind-execute`, or pass work explicitly via `org run --task`.
89
+
50
90
  ---
51
91
 
52
92
  ## Step 2 — Execute Action
@@ -82,7 +122,7 @@ else:
82
122
  print(" " + "─" * 102)
83
123
  for iss in issues:
84
124
  iid = iss.get("id","?")[:28]
85
- st = iss.get("status","open")[:14]
125
+ st = iss.get("status","todo")[:14]
86
126
  pri = iss.get("priority","medium")[:8]
87
127
  title = (iss.get("title") or "-")[:38]
88
128
  asgn = (iss.get("assigneeTitle") or iss.get("assigneeId") or "—")[:20]
@@ -137,24 +177,42 @@ PYEOF
137
177
  [ -z "$title" ] && { echo "ERROR: --title required."; exit 1; }
138
178
 
139
179
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
140
- newId="issue-$(python3 -c 'import time; print(int(time.time()*1000))')"
180
+ newId="issue-$(python3 -c 'import time; print(int(time.time()*1000))')-001"
181
+
182
+ python3 - "$issuesFile" "$newId" "$title" "${description:-}" "${priority:-medium}" "${assignee:-}" "${workspace:-}" "${parent_id:-}" "$ts" <<'PYEOF'
183
+ import json, os, sys
184
+ path, iid, title, desc, pri, asgn, ws, parent, ts = sys.argv[1:]
185
+
186
+ VALID_PRIORITY = {"low","medium","high","urgent"}
187
+ if pri not in VALID_PRIORITY:
188
+ print(f"ERROR: priority '{pri}' must be one of {sorted(VALID_PRIORITY)}.")
189
+ sys.exit(1)
141
190
 
142
- python3 - "$issuesFile" "$newId" "$title" "${description:-}" "${priority:-medium}" "${assignee:-}" "${workspace:-}" "$ts" <<'PYEOF'
143
- import json, sys
144
- path, iid, title, desc, pri, asgn, ws, ts = sys.argv[1:]
145
191
  data = json.load(open(path))
192
+ issues = data.setdefault("issues", [])
193
+
194
+ if parent and not any(i.get("id") == parent for i in issues):
195
+ print(f"ERROR: parent issue '{parent}' not found.")
196
+ sys.exit(1)
197
+
146
198
  issue = {
147
199
  "id": iid, "title": title, "description": desc,
148
- "status": "open", "priority": pri,
149
- "assigneeId": asgn or None, "workspaceId": ws or None,
150
- "createdAt": ts, "updatedAt": ts
200
+ "status": "todo", "priority": pri,
201
+ "assigneeId": asgn or None,
202
+ "assigneeAgentId": None, "assigneeUserId": None,
203
+ "parentId": parent or None,
204
+ "projectId": None, "workspaceId": ws or None,
205
+ "blockedByIssueIds": [],
206
+ "createdAt": ts, "updatedAt": ts,
151
207
  }
152
- data.setdefault("issues", []).append(issue)
153
- with open(path, "w") as f:
208
+ issues.append(issue)
209
+ tmp = path + ".tmp"
210
+ with open(tmp, "w") as f:
154
211
  json.dump(data, f, indent=2)
212
+ os.replace(tmp, path)
155
213
  print(f" Created: {iid}")
156
214
  print(f" Title: {title}")
157
- print(f" Status: open | Priority: {pri}")
215
+ print(f" Status: todo | Priority: {pri}" + (f" | Parent: {parent}" if parent else ""))
158
216
  PYEOF
159
217
  ```
160
218
 
@@ -165,8 +223,18 @@ PYEOF
165
223
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
166
224
 
167
225
  python3 - "$issuesFile" "$issue_id" "${status:-}" "${priority:-}" "${title:-}" "$ts" <<'PYEOF'
168
- import json, sys
226
+ import json, os, sys
169
227
  path, iid, new_st, new_pri, new_title, ts = sys.argv[1:]
228
+
229
+ VALID_STATUS = {"todo","in_progress","blocked","in_review","done","cancelled"}
230
+ VALID_PRIORITY = {"low","medium","high","urgent"}
231
+ if new_st and new_st not in VALID_STATUS:
232
+ print(f"ERROR: status '{new_st}' must be one of {sorted(VALID_STATUS)}.")
233
+ sys.exit(1)
234
+ if new_pri and new_pri not in VALID_PRIORITY:
235
+ print(f"ERROR: priority '{new_pri}' must be one of {sorted(VALID_PRIORITY)}.")
236
+ sys.exit(1)
237
+
170
238
  data = json.load(open(path))
171
239
  issues = data.get("issues", [])
172
240
  found = False
@@ -182,8 +250,10 @@ if not found:
182
250
  print(f"ERROR: Issue '{iid}' not found.")
183
251
  sys.exit(1)
184
252
  data["issues"] = issues
185
- with open(path, "w") as f:
253
+ tmp = path + ".tmp"
254
+ with open(tmp, "w") as f:
186
255
  json.dump(data, f, indent=2)
256
+ os.replace(tmp, path)
187
257
  print(f" Updated: {iid} (updatedAt: {ts})")
188
258
  PYEOF
189
259
  ```
@@ -195,7 +265,7 @@ PYEOF
195
265
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
196
266
 
197
267
  python3 - "$issuesFile" "$issue_id" "$ts" <<'PYEOF'
198
- import json, sys
268
+ import json, os, sys
199
269
  path, iid, ts = sys.argv[1], sys.argv[2], sys.argv[3]
200
270
  data = json.load(open(path))
201
271
  issues = data.get("issues", [])
@@ -211,14 +281,39 @@ if not found:
211
281
  print(f"ERROR: Issue '{iid}' not found.")
212
282
  sys.exit(1)
213
283
  data["issues"] = issues
214
- with open(path, "w") as f:
284
+ tmp = path + ".tmp"
285
+ with open(tmp, "w") as f:
215
286
  json.dump(data, f, indent=2)
287
+ os.replace(tmp, path)
216
288
  print(f" Closed: {iid} (closedAt: {ts})")
217
289
  PYEOF
218
290
  ```
219
291
 
220
292
  ---
221
293
 
294
+ ## Step 2.5 — Append Activity Record
295
+
296
+ After any `create`, `update`, or `close` action succeeds, append one line to the org's
297
+ activity log. This is the only writer for `<org>-activity.jsonl`; seven skills read it.
298
+
299
+ ```bash
300
+ activityFile=".monomind/orgs/${org_name}-activity.jsonl"
301
+ jq -cn \
302
+ --arg iid "${affected_issue_id}" \
303
+ --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
304
+ --arg st "${resulting_status}" \
305
+ --arg ag "${assignee:-operator}" \
306
+ --arg act "${action}" \
307
+ --arg sm "${action} ${affected_issue_id}" \
308
+ '{issue_id:$iid, ts:$ts, status:$st, tokens:null, agent:$ag, type:$act, summary:$sm}' \
309
+ >> "$activityFile"
310
+ ```
311
+
312
+ `affected_issue_id` is `$newId` for `create` and `$issue_id` for `update`/`close`;
313
+ `resulting_status` is `todo`, the new status, or `done` respectively.
314
+
315
+ ---
316
+
222
317
  ## Step 3 — Return Output
223
318
 
224
319
  ```yaml
@@ -64,6 +64,38 @@ issuesFile=".monomind/orgs/${org_name}-issues.json"
64
64
  stateFile=".monomind/orgs/${org_name}-state.json"
65
65
  ```
66
66
 
67
+ Normalize any legacy records written by a pre-2.10 version of these skills. Idempotent — safe to run on every load. Guarded because this skill tolerates a missing issues file.
68
+
69
+ ```bash
70
+ [ -f "$issuesFile" ] && python3 - "$issuesFile" <<'PYEOF'
71
+ import json, sys
72
+ path = sys.argv[1]
73
+ data = json.load(open(path))
74
+ RENAME = {
75
+ "assignee_id": "assigneeId", "assigned_to": "assigneeId",
76
+ "created_at": "createdAt", "updated_at": "updatedAt",
77
+ "closed_at": "closedAt", "project_id": "projectId",
78
+ "parent_id": "parentId", "recovery_status": "recoveryStatus",
79
+ "lastActivityAt": "updatedAt",
80
+ }
81
+ changed = False
82
+ for iss in data.get("issues", []):
83
+ for old, new in RENAME.items():
84
+ if old in iss:
85
+ iss.setdefault(new, iss.pop(old))
86
+ iss.pop(old, None)
87
+ changed = True
88
+ if iss.get("status") == "open":
89
+ iss["status"] = "todo"
90
+ changed = True
91
+ if changed:
92
+ tmp = path + ".tmp"
93
+ with open(tmp, "w") as f:
94
+ json.dump(data, f, indent=2)
95
+ import os; os.replace(tmp, path)
96
+ PYEOF
97
+ ```
98
+
67
99
  ---
68
100
 
69
101
  ## Step 2 — Execute Action
@@ -108,35 +140,44 @@ now = datetime.utcnow()
108
140
 
109
141
  for iss in issues:
110
142
  status = iss.get("status","")
111
- if status in terminal_statuses or status not in non_terminal_statuses:
143
+ iid = iss.get("id","?")
144
+ title = (iss.get("title") or "?")[:50]
145
+
146
+ if status in terminal_statuses:
147
+ continue
148
+ if status not in non_terminal_statuses:
149
+ # Silently skipping an unknown status is how issues become invisible.
150
+ warnings.append((iid, title, status, f"unknown status '{status}' — outside the canonical vocabulary"))
112
151
  continue
113
152
 
114
- aId = iss.get("assigneeAgentId") or iss.get("assigneeId")
153
+ aId = iss.get("assigneeAgentId")
115
154
  uId = iss.get("assigneeUserId")
116
- iid = iss.get("id","?")
117
- title = iss.get("title","?")[:50]
118
155
 
119
- # User-owned: skip strict execution checks
156
+ # Human-owned: the next move belongs to a person, not an execution path.
120
157
  if uId and not aId:
121
158
  healthy.append((iid, title, status, "user-owned"))
122
159
  continue
123
160
 
124
- # Evaluate liveness
125
161
  paths = []
126
162
 
127
163
  run_id = iss.get("executionRunId") or iss.get("checkoutRunId")
128
164
  if run_id and run_id in active_runs:
129
165
  paths.append("active-run")
130
166
 
167
+ resolved_ids = {i.get("id") for i in issues if i.get("status") in terminal_statuses}
131
168
  blockers = iss.get("blockedByIssueIds") or []
132
- if status == "blocked" and blockers:
133
- unresolved = [b for b in blockers if b not in
134
- {i["id"] for i in issues if i.get("status") in terminal_statuses}]
135
- if unresolved:
136
- paths.append(f"blocked-by:{','.join(unresolved[:2])}")
137
- else:
138
- # All blockers resolved — should transition
139
- warnings.append((iid, title, status, "all blockers resolved but issue still blocked"))
169
+ if status == "blocked":
170
+ if blockers:
171
+ unresolved = [b for b in blockers if b not in resolved_ids]
172
+ if unresolved:
173
+ paths.append(f"blocked-by:{','.join(unresolved[:2])}")
174
+ else:
175
+ warnings.append((iid, title, status, "all blockers resolved but issue still blocked"))
176
+ elif not iss.get("recoveryActions"):
177
+ # Liveness Contract: blocked requires a named dependency or an
178
+ # explicit human decision. This issue records neither.
179
+ stalled.append((iid, title, status, "blocked with no blockedByIssueIds and no recovery action"))
180
+ continue
140
181
 
141
182
  if iss.get("executionPolicy", {}).get("monitor", {}).get("nextCheckAt"):
142
183
  paths.append("monitor")
@@ -150,7 +191,10 @@ for iss in issues:
150
191
  if iss.get("currentParticipant"):
151
192
  paths.append("participant")
152
193
 
153
- # Stale heartbeat check (in_progress with no run and no recent update)
194
+ if status == "in_review" and not (iss.get("reviewerId") or iss.get("currentParticipant")):
195
+ stalled.append((iid, title, status, "in_review with no named reviewer"))
196
+ continue
197
+
154
198
  if status == "in_progress" and aId and not paths:
155
199
  updated = iss.get("updatedAt","")
156
200
  if updated:
@@ -159,16 +203,19 @@ for iss in issues:
159
203
  if age > timedelta(hours=2):
160
204
  stalled.append((iid, title, status, f"in_progress {int(age.total_seconds()//3600)}h with no active path"))
161
205
  continue
162
- except: pass
206
+ except Exception:
207
+ pass
163
208
  stalled.append((iid, title, status, "in_progress with no active execution path"))
164
209
  continue
165
210
 
166
- if not paths and status == "todo" and aId:
167
- warnings.append((iid, title, status, "todo assigned to agent — may need wakeup"))
168
- elif paths:
211
+ if paths:
169
212
  healthy.append((iid, title, status, " + ".join(paths)))
213
+ elif not aId:
214
+ warnings.append((iid, title, status, "no assignee — nothing will pick this up"))
215
+ elif status == "todo":
216
+ warnings.append((iid, title, status, "todo assigned to agent — may need wakeup"))
170
217
  else:
171
- healthy.append((iid, title, status, "no agent assignee"))
218
+ stalled.append((iid, title, status, f"{status} with an agent assignee but no execution path"))
172
219
 
173
220
  print(f" ✓ Healthy: {len(healthy)}")
174
221
  if healthy:
@@ -206,7 +253,7 @@ Claim an issue for execution by an agent run. Sets `checkoutRunId` and `executio
206
253
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
207
254
 
208
255
  python3 - "$issuesFile" "$issue_id" "$agent_id" "$run_id" "$ts" <<'PYEOF'
209
- import json, sys
256
+ import json, os, sys
210
257
 
211
258
  path, iid, agentId, runId, ts = sys.argv[1:]
212
259
  data = json.load(open(path))
@@ -224,6 +271,7 @@ for iss in issues:
224
271
  iss["executionRunId"] = runId
225
272
  iss["assigneeId"] = agentId
226
273
  iss["assigneeAgentId"]= agentId
274
+ iss["assigneeUserId"] = None
227
275
  iss["status"] = "in_progress"
228
276
  iss["checkedOutAt"] = ts
229
277
  iss["updatedAt"] = ts
@@ -237,9 +285,16 @@ if not found:
237
285
  sys.exit(1)
238
286
 
239
287
  data["issues"] = issues
240
- with open(path, "w") as f:
288
+ tmp = path + ".tmp"
289
+ with open(tmp, "w") as f:
241
290
  json.dump(data, f, indent=2)
291
+ os.replace(tmp, path)
242
292
  PYEOF
293
+
294
+ activityFile=".monomind/orgs/${org_name}-activity.jsonl"
295
+ jq -cn --arg iid "$issue_id" --arg ts "$ts" --arg st "in_progress" --arg ag "$agent_id" --arg sm "checkout $issue_id" \
296
+ '{issue_id:$iid, ts:$ts, status:$st, tokens:null, agent:$ag, type:"checkout", summary:$sm}' \
297
+ >> "$activityFile"
243
298
  ```
244
299
 
245
300
  ### release
@@ -269,13 +324,21 @@ for iss in issues:
269
324
  print(f" RELEASED: Checkout cleared for issue {iid}")
270
325
  print(f" Status remains: {iss.get('status','?')} — update separately if needed.")
271
326
  data["issues"] = issues
272
- with open(path, "w") as f:
327
+ tmp = path + ".tmp"
328
+ with open(tmp, "w") as f:
273
329
  json.dump(data, f, indent=2)
330
+ os.replace(tmp, path)
274
331
  sys.exit(0)
275
332
 
276
333
  print(f" ERROR: Issue '{iid}' not found.")
277
334
  sys.exit(1)
278
335
  PYEOF
336
+
337
+ activityFile=".monomind/orgs/${org_name}-activity.jsonl"
338
+ currentStatus=$(jq -r --arg id "$issue_id" '(.issues // [])[] | select(.id == $id) | .status // "todo"' "$issuesFile")
339
+ jq -cn --arg iid "$issue_id" --arg ts "$ts" --arg st "$currentStatus" --arg ag "${agent_id:-operator}" --arg sm "release $issue_id" \
340
+ '{issue_id:$iid, ts:$ts, status:$st, tokens:null, agent:$ag, type:"release", summary:$sm}' \
341
+ >> "$activityFile"
279
342
  ```
280
343
 
281
344
  ### wakeup
@@ -296,7 +359,7 @@ if not iss:
296
359
  print(f" ERROR: Issue '{iid}' not found.")
297
360
  sys.exit(1)
298
361
 
299
- checkout_agent = iss.get("assigneeAgentId") or iss.get("assigneeId","")
362
+ checkout_agent = iss.get("assigneeAgentId") or ""
300
363
  checkout_run = iss.get("checkoutRunId","")
301
364
 
302
365
  # Port of Paperclip's shouldWakeAssigneeOnCheckout logic
@@ -338,7 +401,7 @@ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
338
401
  recoveryId="recovery-$(python3 -c 'import time; print(int(time.time()*1000))')"
339
402
 
340
403
  python3 - "$issuesFile" "$issue_id" "$recoveryId" "${agent_id:-operator}" "$reason" "$ts" <<'PYEOF'
341
- import json, sys
404
+ import json, os, sys
342
405
 
343
406
  path, iid, rid, owner, cause, ts = sys.argv[1:]
344
407
  data = json.load(open(path))
@@ -359,8 +422,10 @@ for iss in issues:
359
422
  iss["status"] = "blocked"
360
423
  iss["updatedAt"] = ts
361
424
  data["issues"] = issues
362
- with open(path, "w") as f:
425
+ tmp = path + ".tmp"
426
+ with open(tmp, "w") as f:
363
427
  json.dump(data, f, indent=2)
428
+ os.replace(tmp, path)
364
429
  print(f" RECOVERY ACTION FILED: {rid}")
365
430
  print(f" Issue {iid} → status: blocked (pending recovery)")
366
431
  print(f" Owner: {owner}")
@@ -371,6 +436,11 @@ for iss in issues:
371
436
  print(f" ERROR: Issue '{iid}' not found.")
372
437
  sys.exit(1)
373
438
  PYEOF
439
+
440
+ activityFile=".monomind/orgs/${org_name}-activity.jsonl"
441
+ jq -cn --arg iid "$issue_id" --arg ts "$ts" --arg st "blocked" --arg ag "${agent_id:-operator}" --arg sm "recover $issue_id" \
442
+ '{issue_id:$iid, ts:$ts, status:$st, tokens:null, agent:$ag, type:"recover", summary:$sm}' \
443
+ >> "$activityFile"
374
444
  ```
375
445
 
376
446
  ---