@monoes/monomindcli 1.9.17 → 1.10.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.
- package/.claude/commands/mastermind/_repeat.md +182 -39
- package/.claude/commands/mastermind/architect.md +17 -11
- package/.claude/commands/mastermind/brain.md +4 -0
- package/.claude/commands/mastermind/build.md +4 -0
- package/.claude/commands/mastermind/content.md +4 -0
- package/.claude/commands/mastermind/createorg.md +5 -3
- package/.claude/commands/mastermind/finance.md +4 -0
- package/.claude/commands/mastermind/idea.md +4 -0
- package/.claude/commands/mastermind/marketing.md +4 -0
- package/.claude/commands/mastermind/master.md +63 -37
- package/.claude/commands/mastermind/ops.md +4 -0
- package/.claude/commands/mastermind/release.md +4 -0
- package/.claude/commands/mastermind/research.md +4 -0
- package/.claude/commands/mastermind/review.md +4 -0
- package/.claude/commands/mastermind/runorg.md +5 -3
- package/.claude/commands/mastermind/sales.md +4 -0
- package/.claude/commands/mastermind/techport.md +9 -0
- package/.claude/commands/monomind/do.md +5 -1
- package/.claude/commands/monomind/idea.md +5 -1
- package/.claude/commands/monomind/improve.md +5 -1
- package/.claude/commands/monomind/repeat.md +85 -29
- package/.claude/commands/monomind/review.md +6 -2
- package/.claude/commands/monomind/understand.md +10 -8
- package/.claude/helpers/extras-registry.json +235 -235
- package/.claude/helpers/graphify-freshen.cjs +13 -1
- package/.claude/helpers/hook-handler.cjs +1 -1
- package/.claude/helpers/router.cjs +4 -1
- package/.claude/skills/mastermind/_protocol.md +28 -21
- package/.claude/skills/mastermind/access.md +236 -0
- package/.claude/skills/mastermind/activity.md +191 -0
- package/.claude/skills/mastermind/adapter-manager.md +259 -0
- package/.claude/skills/mastermind/adapters.md +204 -0
- package/.claude/skills/mastermind/agent-detail.md +242 -0
- package/.claude/skills/mastermind/agents.md +178 -0
- package/.claude/skills/mastermind/approval-detail.md +259 -0
- package/.claude/skills/mastermind/approve.md +181 -0
- package/.claude/skills/mastermind/architect.md +24 -8
- package/.claude/skills/mastermind/backup.md +197 -0
- package/.claude/skills/mastermind/bootstrap.md +190 -0
- package/.claude/skills/mastermind/budgets.md +237 -0
- package/.claude/skills/mastermind/companies.md +256 -0
- package/.claude/skills/mastermind/costs.md +151 -0
- package/.claude/skills/mastermind/createorg.md +23 -5
- package/.claude/skills/mastermind/diagnose.md +249 -0
- package/.claude/skills/mastermind/env.md +198 -0
- package/.claude/skills/mastermind/environments.md +250 -0
- package/.claude/skills/mastermind/export.md +324 -0
- package/.claude/skills/mastermind/goal-detail.md +255 -0
- package/.claude/skills/mastermind/goals.md +149 -0
- package/.claude/skills/mastermind/heartbeat.md +164 -0
- package/.claude/skills/mastermind/idea.md +250 -122
- package/.claude/skills/mastermind/import.md +281 -0
- package/.claude/skills/mastermind/inbox.md +214 -0
- package/.claude/skills/mastermind/instance-settings.md +315 -0
- package/.claude/skills/mastermind/instance.md +231 -0
- package/.claude/skills/mastermind/invite-landing.md +227 -0
- package/.claude/skills/mastermind/invites.md +254 -0
- package/.claude/skills/mastermind/issue-detail.md +291 -0
- package/.claude/skills/mastermind/issues.md +235 -0
- package/.claude/skills/mastermind/join-queue.md +170 -0
- package/.claude/skills/mastermind/liveness.md +392 -0
- package/.claude/skills/mastermind/memory.md +321 -0
- package/.claude/skills/mastermind/my-issues.md +146 -0
- package/.claude/skills/mastermind/new-agent.md +241 -0
- package/.claude/skills/mastermind/org-chart.md +207 -0
- package/.claude/skills/mastermind/org-settings.md +217 -0
- package/.claude/skills/mastermind/plan-to-tasks.md +136 -0
- package/.claude/skills/mastermind/plugin-manager.md +241 -0
- package/.claude/skills/mastermind/plugin-settings.md +273 -0
- package/.claude/skills/mastermind/plugins.md +190 -0
- package/.claude/skills/mastermind/profile.md +187 -0
- package/.claude/skills/mastermind/project-detail.md +249 -0
- package/.claude/skills/mastermind/project-workspace.md +244 -0
- package/.claude/skills/mastermind/projects.md +164 -0
- package/.claude/skills/mastermind/routine-detail.md +253 -0
- package/.claude/skills/mastermind/routines.md +202 -0
- package/.claude/skills/mastermind/runorg.md +74 -9
- package/.claude/skills/mastermind/search.md +186 -0
- package/.claude/skills/mastermind/secrets.md +199 -0
- package/.claude/skills/mastermind/skills.md +156 -0
- package/.claude/skills/mastermind/tasks.md +149 -0
- package/.claude/skills/mastermind/techport.md +5 -5
- package/.claude/skills/mastermind/threads.md +259 -0
- package/.claude/skills/mastermind/tree-control.md +250 -0
- package/.claude/skills/mastermind/wiki.md +314 -0
- package/.claude/skills/mastermind/workspace-detail.md +317 -0
- package/.claude/skills/mastermind/workspaces.md +261 -0
- package/.claude/skills/mastermind/worktree.md +187 -0
- package/dist/src/init/executor.js +8 -8
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/init/statusline-generator.d.ts.map +1 -1
- package/dist/src/init/statusline-generator.js +12 -0
- package/dist/src/init/statusline-generator.js.map +1 -1
- package/dist/src/ui/.monomind/data/ranked-context.json +1 -1
- package/dist/src/ui/.monomind/loops/mastermind-review-1778664132789.json +16 -0
- package/dist/src/ui/.monomind/sessions/current.json +5 -5
- package/dist/src/ui/.monomind/sessions/session-1776778451399.json +15 -0
- package/dist/src/ui/dashboard.html +3030 -181
- package/dist/src/ui/data/mastermind-events.jsonl +8 -0
- package/dist/src/ui/data/mastermind-sessions.json +1 -0
- package/dist/src/ui/server.mjs +738 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/.claude/skills/.monomind/data/ranked-context.json +0 -5
- package/.claude/skills/.monomind/sessions/current.json +0 -13
- package/.claude/skills/.monomind/sessions/session-1777829336455.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777831614725.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777832095857.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777839814183.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777841847131.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777843309463.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777880867159.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777881884593.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777884090471.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777884808221.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777885672155.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777886852818.json +0 -15
- package/.claude/skills/.monomind/sessions/session-1777896532690.json +0 -15
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mastermind-threads
|
|
3
|
+
description: Mastermind threads — list, view, and create conversation threads within an org. Threads are human-or-agent discussions attached to issues, goals, or the org itself. Reads from -threads.jsonl org state files.
|
|
4
|
+
type: domain-skill
|
|
5
|
+
default_mode: auto
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Mastermind Threads
|
|
9
|
+
|
|
10
|
+
This skill is invoked by `mastermind:threads` or directly via `/mastermind:threads`.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
|
|
16
|
+
- `brain_context`: BRAIN CONTEXT block (injected by command, or loaded below if standalone)
|
|
17
|
+
- `org_name`: org to query (required)
|
|
18
|
+
- `action`: list | view | create | reply
|
|
19
|
+
- `thread_id`: thread ID (required for view/reply)
|
|
20
|
+
- `issue_id`: filter by issue ID (optional)
|
|
21
|
+
- `message`: message body (required for create/reply)
|
|
22
|
+
- `author`: author name or agent ID (default: current user)
|
|
23
|
+
- `limit`: max threads to show (default: 20)
|
|
24
|
+
- `caller`: command | master
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Step 0 — Brain Load (standalone only)
|
|
29
|
+
|
|
30
|
+
If `caller` is not "command", load brain context following _protocol.md Brain Load Procedure with namespace: `ops`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Step 1 — Load Threads File
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
orgFile=".monomind/orgs/${org_name}.json"
|
|
38
|
+
[ ! -f "$orgFile" ] && { echo "ERROR: Org '${org_name}' not found."; exit 1; }
|
|
39
|
+
|
|
40
|
+
threadsFile=".monomind/orgs/${org_name}-threads.jsonl"
|
|
41
|
+
limit="${limit:-20}"
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Step 2 — Execute Action
|
|
47
|
+
|
|
48
|
+
### list (default)
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
echo "THREADS — $org_name"
|
|
52
|
+
echo "────────────────────────────────────────────────────────"
|
|
53
|
+
|
|
54
|
+
if [ ! -f "$threadsFile" ] || [ ! -s "$threadsFile" ]; then
|
|
55
|
+
echo " No threads found."
|
|
56
|
+
echo ""
|
|
57
|
+
echo " Create: /mastermind:threads --org $org_name --action create --message 'Hello team'"
|
|
58
|
+
exit 0
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
python3 - "$threadsFile" "${issue_id:-}" "$limit" <<'PYEOF'
|
|
62
|
+
import json, sys
|
|
63
|
+
|
|
64
|
+
path = sys.argv[1]
|
|
65
|
+
issue_f = sys.argv[2]
|
|
66
|
+
limit = int(sys.argv[3])
|
|
67
|
+
|
|
68
|
+
threads = []
|
|
69
|
+
with open(path) as f:
|
|
70
|
+
for line in f:
|
|
71
|
+
line = line.strip()
|
|
72
|
+
if not line: continue
|
|
73
|
+
try:
|
|
74
|
+
t = json.loads(line)
|
|
75
|
+
if t.get("type","thread") == "thread":
|
|
76
|
+
threads.append(t)
|
|
77
|
+
except: pass
|
|
78
|
+
|
|
79
|
+
if issue_f:
|
|
80
|
+
threads = [t for t in threads if t.get("issueId") == issue_f]
|
|
81
|
+
|
|
82
|
+
threads = threads[-limit:]
|
|
83
|
+
|
|
84
|
+
if not threads:
|
|
85
|
+
print(f" (no threads{' filtered by issue=' + issue_f if issue_f else ''})")
|
|
86
|
+
else:
|
|
87
|
+
print(f" {'ID':<28} {'SUBJECT':<32} {'AUTHOR':<20} {'MSGS':<6} CREATED")
|
|
88
|
+
print(" " + "─" * 96)
|
|
89
|
+
for t in threads:
|
|
90
|
+
tid = t.get("id","?")[:28]
|
|
91
|
+
subj = (t.get("subject") or "(no subject)")[:32]
|
|
92
|
+
author = (t.get("authorName") or t.get("authorId") or "—")[:20]
|
|
93
|
+
msgs = len(t.get("messages", []))
|
|
94
|
+
created = (t.get("createdAt") or "-")[:10]
|
|
95
|
+
print(f" {tid:<28} {subj:<32} {author:<20} {msgs:<6} {created}")
|
|
96
|
+
|
|
97
|
+
print(f"\n {len(threads)} thread(s). View: /mastermind:threads --org <org> --action view --thread-id <id>")
|
|
98
|
+
PYEOF
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### view
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
[ -z "$thread_id" ] && { echo "ERROR: --thread-id required."; exit 1; }
|
|
105
|
+
|
|
106
|
+
if [ ! -f "$threadsFile" ]; then
|
|
107
|
+
echo "No threads found for org '$org_name'."
|
|
108
|
+
exit 0
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
python3 - "$threadsFile" "$thread_id" <<'PYEOF'
|
|
112
|
+
import json, sys
|
|
113
|
+
|
|
114
|
+
path, tid = sys.argv[1], sys.argv[2]
|
|
115
|
+
|
|
116
|
+
thread = None
|
|
117
|
+
with open(path) as f:
|
|
118
|
+
for line in f:
|
|
119
|
+
line = line.strip()
|
|
120
|
+
if not line: continue
|
|
121
|
+
try:
|
|
122
|
+
t = json.loads(line)
|
|
123
|
+
if t.get("id") == tid and t.get("type","thread") == "thread":
|
|
124
|
+
thread = t
|
|
125
|
+
except: pass
|
|
126
|
+
|
|
127
|
+
if not thread:
|
|
128
|
+
print(f"ERROR: Thread '{tid}' not found.")
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
|
|
131
|
+
subj = thread.get("subject","(no subject)")
|
|
132
|
+
issue = thread.get("issueId","")
|
|
133
|
+
created = thread.get("createdAt","-")
|
|
134
|
+
|
|
135
|
+
print(f"THREAD: {subj}")
|
|
136
|
+
print(f" ID: {tid}")
|
|
137
|
+
if issue: print(f" Issue: {issue}")
|
|
138
|
+
print(f" Created: {created}")
|
|
139
|
+
print()
|
|
140
|
+
print("MESSAGES")
|
|
141
|
+
print("────────────────────────────────────────────────────────")
|
|
142
|
+
|
|
143
|
+
msgs = thread.get("messages", [])
|
|
144
|
+
if not msgs:
|
|
145
|
+
print(" (no messages)")
|
|
146
|
+
else:
|
|
147
|
+
for m in msgs:
|
|
148
|
+
author = m.get("authorName") or m.get("authorId") or "?"
|
|
149
|
+
ts = (m.get("createdAt") or "?")[:16].replace("T"," ")
|
|
150
|
+
body = m.get("body","")
|
|
151
|
+
print(f" [{ts}] {author}:")
|
|
152
|
+
for line in body.split("\n"):
|
|
153
|
+
print(f" {line}")
|
|
154
|
+
print()
|
|
155
|
+
PYEOF
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### create
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
[ -z "$message" ] && { echo "ERROR: --message required."; exit 1; }
|
|
162
|
+
|
|
163
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
164
|
+
newId="thread-$(python3 -c 'import time; print(int(time.time()*1000))')"
|
|
165
|
+
authorVal="${author:-operator}"
|
|
166
|
+
|
|
167
|
+
python3 - "$threadsFile" "$newId" "${title:-}" "$message" "$authorVal" "${issue_id:-}" "$ts" <<'PYEOF'
|
|
168
|
+
import json, sys
|
|
169
|
+
|
|
170
|
+
path, tid, subj, body, author, issue, ts = sys.argv[1:]
|
|
171
|
+
thread = {
|
|
172
|
+
"id": tid,
|
|
173
|
+
"type": "thread",
|
|
174
|
+
"subject": subj or "(no subject)",
|
|
175
|
+
"issueId": issue or None,
|
|
176
|
+
"authorId": author,
|
|
177
|
+
"authorName": author,
|
|
178
|
+
"createdAt": ts,
|
|
179
|
+
"messages": [{
|
|
180
|
+
"id": f"msg-{tid}",
|
|
181
|
+
"authorId": author,
|
|
182
|
+
"authorName": author,
|
|
183
|
+
"body": body,
|
|
184
|
+
"createdAt": ts,
|
|
185
|
+
}]
|
|
186
|
+
}
|
|
187
|
+
with open(path, "a") as f:
|
|
188
|
+
f.write(json.dumps(thread) + "\n")
|
|
189
|
+
print(f" Created thread: {tid}")
|
|
190
|
+
print(f" Subject: {subj or '(no subject)'}")
|
|
191
|
+
print(f" Message: {body[:80]}{'...' if len(body) > 80 else ''}")
|
|
192
|
+
PYEOF
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### reply
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
[ -z "$thread_id" ] && { echo "ERROR: --thread-id required."; exit 1; }
|
|
199
|
+
[ -z "$message" ] && { echo "ERROR: --message required."; exit 1; }
|
|
200
|
+
|
|
201
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
202
|
+
authorVal="${author:-operator}"
|
|
203
|
+
|
|
204
|
+
# Read all threads, append message to matching thread, rewrite file
|
|
205
|
+
python3 - "$threadsFile" "$thread_id" "$message" "$authorVal" "$ts" <<'PYEOF'
|
|
206
|
+
import json, sys
|
|
207
|
+
|
|
208
|
+
path, tid, body, author, ts = sys.argv[1:]
|
|
209
|
+
threads = []
|
|
210
|
+
found = False
|
|
211
|
+
with open(path) as f:
|
|
212
|
+
for line in f:
|
|
213
|
+
line = line.strip()
|
|
214
|
+
if not line: continue
|
|
215
|
+
try:
|
|
216
|
+
t = json.loads(line)
|
|
217
|
+
if t.get("id") == tid:
|
|
218
|
+
msg_id = f"msg-{tid}-{len(t.get('messages',[]))}"
|
|
219
|
+
t.setdefault("messages",[]).append({
|
|
220
|
+
"id": msg_id,
|
|
221
|
+
"authorId": author,
|
|
222
|
+
"authorName": author,
|
|
223
|
+
"body": body,
|
|
224
|
+
"createdAt": ts,
|
|
225
|
+
})
|
|
226
|
+
found = True
|
|
227
|
+
threads.append(t)
|
|
228
|
+
except: pass
|
|
229
|
+
|
|
230
|
+
if not found:
|
|
231
|
+
print(f"ERROR: Thread '{tid}' not found.")
|
|
232
|
+
sys.exit(1)
|
|
233
|
+
|
|
234
|
+
with open(path, "w") as f:
|
|
235
|
+
for t in threads:
|
|
236
|
+
f.write(json.dumps(t) + "\n")
|
|
237
|
+
|
|
238
|
+
print(f" Reply added to thread: {tid}")
|
|
239
|
+
print(f" Author: {author} | {ts}")
|
|
240
|
+
PYEOF
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Step 3 — Return Output
|
|
246
|
+
|
|
247
|
+
```yaml
|
|
248
|
+
domain: ops
|
|
249
|
+
status: complete
|
|
250
|
+
action: <action>
|
|
251
|
+
org_name: <org_name>
|
|
252
|
+
thread_id: <thread_id or new_id>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Step 4 — Brain Write (standalone only)
|
|
258
|
+
|
|
259
|
+
If `caller` is not "command", follow _protocol.md Brain Write Procedure for domain `ops`.
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mastermind-tree-control
|
|
3
|
+
description: Mastermind tree-control — pause, hold, release, or preview recovery for an issue/task tree in an org. Lets board members stop runaway loops, hold trees during review, and resume work when ready. Mirrors Paperclip's issue-tree-control API.
|
|
4
|
+
type: domain-skill
|
|
5
|
+
default_mode: confirm
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Mastermind Tree Control
|
|
9
|
+
|
|
10
|
+
This skill is invoked by `mastermind:tree-control` or directly via `/mastermind:tree-control`.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Inputs
|
|
15
|
+
|
|
16
|
+
- `brain_context`: BRAIN CONTEXT block (injected by command, or loaded below if standalone)
|
|
17
|
+
- `org_name`: org to operate on (required)
|
|
18
|
+
- `issue_id`: root issue/task ID to control (required)
|
|
19
|
+
- `action`: preview | hold | release | cancel
|
|
20
|
+
- `reason`: reason for the hold/cancel (required for hold/cancel)
|
|
21
|
+
- `caller`: command | master
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Three Invariants (MUST be preserved)
|
|
26
|
+
|
|
27
|
+
All tree-control actions must respect these invariants:
|
|
28
|
+
|
|
29
|
+
1. **Productive work continues.** Only hold what genuinely needs to stop; don't block productive branches.
|
|
30
|
+
2. **Only real blockers stop work.** A hold is a deliberate decision, not a pseudo-stop.
|
|
31
|
+
3. **No infinite loops.** Release must be possible — never hold without a clear release path.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Step 0 — Brain Load (standalone only)
|
|
36
|
+
|
|
37
|
+
If `caller` is not "command", load brain context following _protocol.md Brain Load Procedure with namespace: `ops`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Step 1 — Load Org and Issue
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
orgFile=".monomind/orgs/${org_name}.json"
|
|
45
|
+
[ ! -f "$orgFile" ] && { echo "ERROR: Org '${org_name}' not found."; exit 1; }
|
|
46
|
+
[ -z "$issue_id" ] && { echo "ERROR: --issue-id required."; exit 1; }
|
|
47
|
+
|
|
48
|
+
issuesFile=".monomind/orgs/${org_name}-issues.json"
|
|
49
|
+
[ ! -f "$issuesFile" ] && { echo "ERROR: No issues file found for org '${org_name}'."; exit 1; }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Step 2 — Execute Action
|
|
55
|
+
|
|
56
|
+
### preview (default)
|
|
57
|
+
|
|
58
|
+
Preview what a hold/cancel would affect without making changes:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
echo "TREE CONTROL PREVIEW — $org_name root: $issue_id"
|
|
62
|
+
echo "════════════════════════════════════════════════════════"
|
|
63
|
+
|
|
64
|
+
python3 - "$issuesFile" "$issue_id" <<'PYEOF'
|
|
65
|
+
import json, sys
|
|
66
|
+
|
|
67
|
+
data = json.load(open(sys.argv[1]))
|
|
68
|
+
rootId = sys.argv[2]
|
|
69
|
+
issues = {i["id"]: i for i in data.get("issues", [])}
|
|
70
|
+
|
|
71
|
+
root = issues.get(rootId)
|
|
72
|
+
if not root:
|
|
73
|
+
print(f"ERROR: Issue '{rootId}' not found.")
|
|
74
|
+
sys.exit(1)
|
|
75
|
+
|
|
76
|
+
print(f" Root: {root.get('id')} — {root.get('title','?')} [{root.get('status','?')}]")
|
|
77
|
+
print()
|
|
78
|
+
|
|
79
|
+
# Find subtree
|
|
80
|
+
def subtree(iid, depth=0):
|
|
81
|
+
children = [i for i in issues.values() if i.get("parentId") == iid or iid in (i.get("blockedByIssueIds") or [])]
|
|
82
|
+
for c in children:
|
|
83
|
+
st = c.get("status","?")
|
|
84
|
+
icon = "⚠" if st in ("in_progress","in_review") else "○"
|
|
85
|
+
print(f" {' ' * depth}{icon} {c['id']} — {c.get('title','?')} [{st}]")
|
|
86
|
+
subtree(c["id"], depth + 1)
|
|
87
|
+
|
|
88
|
+
subtree(rootId)
|
|
89
|
+
|
|
90
|
+
active = [i for i in issues.values()
|
|
91
|
+
if i.get("status") in ("in_progress","in_review")
|
|
92
|
+
and (i.get("parentId") == rootId or rootId in (i.get("blockedByIssueIds") or []))]
|
|
93
|
+
print()
|
|
94
|
+
print(f" Active issues in subtree: {len(active)}")
|
|
95
|
+
print(f" Holding root would affect all {len(active)} active issue(s).")
|
|
96
|
+
print()
|
|
97
|
+
print(" To hold: /mastermind:tree-control --org <org> --action hold --issue-id <id> --reason 'Review needed'")
|
|
98
|
+
print(" To cancel: /mastermind:tree-control --org <org> --action cancel --issue-id <id> --reason 'No longer needed'")
|
|
99
|
+
PYEOF
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### hold
|
|
103
|
+
|
|
104
|
+
Place a hold on the issue tree (pauses all active work in the subtree):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
[ -z "$reason" ] && { echo "ERROR: --reason required for hold action."; exit 1; }
|
|
108
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
109
|
+
|
|
110
|
+
python3 - "$issuesFile" "$issue_id" "$reason" "$ts" <<'PYEOF'
|
|
111
|
+
import json, sys
|
|
112
|
+
|
|
113
|
+
path, rootId, reason, ts = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
114
|
+
data = json.load(open(path))
|
|
115
|
+
issues = data.get("issues", [])
|
|
116
|
+
|
|
117
|
+
# Add hold metadata to root issue
|
|
118
|
+
held = 0
|
|
119
|
+
for iss in issues:
|
|
120
|
+
if iss.get("id") == rootId:
|
|
121
|
+
iss.setdefault("holds", []).append({
|
|
122
|
+
"id": f"hold-{int(ts.replace('-','').replace(':','').replace('T','').replace('Z',''))[:14]}",
|
|
123
|
+
"reason": reason,
|
|
124
|
+
"createdAt": ts,
|
|
125
|
+
"status": "active"
|
|
126
|
+
})
|
|
127
|
+
held += 1
|
|
128
|
+
break
|
|
129
|
+
|
|
130
|
+
if not held:
|
|
131
|
+
print(f"ERROR: Issue '{rootId}' not found.")
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
data["issues"] = issues
|
|
135
|
+
with open(path, "w") as f:
|
|
136
|
+
json.dump(data, f, indent=2)
|
|
137
|
+
|
|
138
|
+
print(f" HOLD PLACED on issue tree rooted at: {rootId}")
|
|
139
|
+
print(f" Reason: {reason}")
|
|
140
|
+
print(f" Applied: {ts}")
|
|
141
|
+
print()
|
|
142
|
+
print(" Active agents should detect the hold and pause execution.")
|
|
143
|
+
print(f" To release: /mastermind:tree-control --org <org> --action release --issue-id {rootId}")
|
|
144
|
+
PYEOF
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### release
|
|
148
|
+
|
|
149
|
+
Release a hold from the issue tree:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
153
|
+
|
|
154
|
+
python3 - "$issuesFile" "$issue_id" "$ts" <<'PYEOF'
|
|
155
|
+
import json, sys
|
|
156
|
+
|
|
157
|
+
path, rootId, ts = sys.argv[1], sys.argv[2], sys.argv[3]
|
|
158
|
+
data = json.load(open(path))
|
|
159
|
+
issues = data.get("issues", [])
|
|
160
|
+
|
|
161
|
+
released = 0
|
|
162
|
+
for iss in issues:
|
|
163
|
+
if iss.get("id") == rootId:
|
|
164
|
+
holds = iss.get("holds", [])
|
|
165
|
+
active_holds = [h for h in holds if h.get("status") == "active"]
|
|
166
|
+
for h in active_holds:
|
|
167
|
+
h["status"] = "released"
|
|
168
|
+
h["releasedAt"] = ts
|
|
169
|
+
released += 1
|
|
170
|
+
iss["holds"] = holds
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
if not released:
|
|
174
|
+
print(f" No active holds found on issue '{rootId}'.")
|
|
175
|
+
print(" (Issue may already be running or was never held.)")
|
|
176
|
+
else:
|
|
177
|
+
data["issues"] = issues
|
|
178
|
+
with open(path, "w") as f:
|
|
179
|
+
json.dump(data, f, indent=2)
|
|
180
|
+
print(f" HOLD RELEASED on {rootId} — {released} hold(s) cleared.")
|
|
181
|
+
print(f" Released: {ts}")
|
|
182
|
+
print()
|
|
183
|
+
print(" Agents can now resume work on this issue tree.")
|
|
184
|
+
PYEOF
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### cancel
|
|
188
|
+
|
|
189
|
+
Cancel an issue and its entire subtree:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
[ -z "$reason" ] && { echo "ERROR: --reason required for cancel action."; exit 1; }
|
|
193
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
194
|
+
|
|
195
|
+
echo "WARNING: This will mark '$issue_id' and its subtree as cancelled."
|
|
196
|
+
echo "Reason: $reason"
|
|
197
|
+
echo ""
|
|
198
|
+
|
|
199
|
+
python3 - "$issuesFile" "$issue_id" "$reason" "$ts" <<'PYEOF'
|
|
200
|
+
import json, sys
|
|
201
|
+
|
|
202
|
+
path, rootId, reason, ts = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
203
|
+
data = json.load(open(path))
|
|
204
|
+
issues = data.get("issues", [])
|
|
205
|
+
issues_map = {i["id"]: i for i in issues}
|
|
206
|
+
|
|
207
|
+
def cancel_subtree(iid):
|
|
208
|
+
iss = issues_map.get(iid)
|
|
209
|
+
if not iss:
|
|
210
|
+
return 0
|
|
211
|
+
if iss.get("status") in ("done","cancelled"):
|
|
212
|
+
return 0
|
|
213
|
+
iss["status"] = "cancelled"
|
|
214
|
+
iss["cancelledAt"] = ts
|
|
215
|
+
iss["cancelReason"] = reason
|
|
216
|
+
count = 1
|
|
217
|
+
children = [i for i in issues if i.get("parentId") == iid]
|
|
218
|
+
for c in children:
|
|
219
|
+
count += cancel_subtree(c["id"])
|
|
220
|
+
return count
|
|
221
|
+
|
|
222
|
+
cancelled = cancel_subtree(rootId)
|
|
223
|
+
|
|
224
|
+
data["issues"] = issues
|
|
225
|
+
with open(path, "w") as f:
|
|
226
|
+
json.dump(data, f, indent=2)
|
|
227
|
+
|
|
228
|
+
print(f" CANCELLED: {cancelled} issue(s) in tree rooted at {rootId}")
|
|
229
|
+
print(f" Reason: {reason}")
|
|
230
|
+
print(f" Applied: {ts}")
|
|
231
|
+
PYEOF
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Step 3 — Return Output
|
|
237
|
+
|
|
238
|
+
```yaml
|
|
239
|
+
domain: ops
|
|
240
|
+
status: complete
|
|
241
|
+
action: <action>
|
|
242
|
+
org_name: <org_name>
|
|
243
|
+
issue_id: <issue_id>
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Step 4 — Brain Write (standalone only)
|
|
249
|
+
|
|
250
|
+
If `caller` is not "command", follow _protocol.md Brain Write Procedure for domain `ops`.
|