@monoes/monomindcli 1.9.17 → 1.10.0
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 +1 -1
- 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,392 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mastermind-liveness
|
|
3
|
+
description: Mastermind liveness — enforce the non-terminal issue liveness contract for agent-owned work. Checks if every in_progress/blocked/in_review issue has a valid action path (active run, queued wake, explicit blocker, or recovery action). Can checkout an issue to an agent run, release checkout, trigger wakeup decisions, and file explicit recovery actions for stalled issues. Based on Paperclip's execution-semantics.md liveness contract.
|
|
4
|
+
type: domain-skill
|
|
5
|
+
default_mode: auto
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Mastermind Liveness
|
|
9
|
+
|
|
10
|
+
This skill is invoked by `mastermind:liveness` or directly via `/mastermind:liveness`.
|
|
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 check (required)
|
|
18
|
+
- `action`: check | checkout | release | wakeup | recover
|
|
19
|
+
- `issue_id`: specific issue to operate on (required for checkout/release/wakeup/recover)
|
|
20
|
+
- `agent_id`: agent claiming checkout (required for checkout)
|
|
21
|
+
- `run_id`: execution run ID (required for checkout)
|
|
22
|
+
- `reason`: recovery reason (required for recover)
|
|
23
|
+
- `caller`: command | master
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Liveness Contract
|
|
28
|
+
|
|
29
|
+
An issue is **healthy** when the product can answer "what moves this forward next?" without requiring a human to reconstruct intent.
|
|
30
|
+
|
|
31
|
+
An issue is **stalled** when it is non-terminal but has no:
|
|
32
|
+
- active run linked to the issue
|
|
33
|
+
- queued wake or continuation deliverable to the responsible agent
|
|
34
|
+
- explicit execution-policy participant
|
|
35
|
+
- pending interaction waiting on a specific responder
|
|
36
|
+
- one-shot monitor (`nextCheckAt`) that will wake the assignee
|
|
37
|
+
- human owner (`assigneeUserId`)
|
|
38
|
+
- first-class blocker chain whose leaf issues are themselves healthy
|
|
39
|
+
- open explicit recovery action naming owner + next action
|
|
40
|
+
|
|
41
|
+
**Valid non-terminal statuses for agent-owned work:** `todo`, `in_progress`, `blocked`, `in_review`
|
|
42
|
+
|
|
43
|
+
**Status → execution expectation:**
|
|
44
|
+
- `todo`: actionable but not yet claimed — may still need wake path to assignee
|
|
45
|
+
- `in_progress`: must have agent assignee + active execution backing (strict)
|
|
46
|
+
- `blocked`: must have named external dependency (blockedByIssueIds) or explicit human decision needed
|
|
47
|
+
- `in_review`: review participant must be named; next move belongs to reviewer
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Step 0 — Brain Load (standalone only)
|
|
52
|
+
|
|
53
|
+
If `caller` is not "command", load brain context following _protocol.md Brain Load Procedure with namespace: `ops`.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Step 1 — Load Org and Issues
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
orgFile=".monomind/orgs/${org_name}.json"
|
|
61
|
+
[ ! -f "$orgFile" ] && { echo "ERROR: Org '${org_name}' not found."; exit 1; }
|
|
62
|
+
|
|
63
|
+
issuesFile=".monomind/orgs/${org_name}-issues.json"
|
|
64
|
+
stateFile=".monomind/orgs/${org_name}-state.json"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Step 2 — Execute Action
|
|
70
|
+
|
|
71
|
+
### check (default)
|
|
72
|
+
|
|
73
|
+
Audit every non-terminal agent-owned issue for liveness. Flag stalled issues.
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
echo "LIVENESS CHECK — ${org_name}"
|
|
77
|
+
echo "════════════════════════════════════════════════════════"
|
|
78
|
+
|
|
79
|
+
python3 - "$issuesFile" "$stateFile" <<'PYEOF'
|
|
80
|
+
import json, sys, os
|
|
81
|
+
from datetime import datetime, timedelta
|
|
82
|
+
|
|
83
|
+
issues_path = sys.argv[1]
|
|
84
|
+
state_path = sys.argv[2]
|
|
85
|
+
|
|
86
|
+
# Load issues
|
|
87
|
+
if not os.path.exists(issues_path):
|
|
88
|
+
print(" No issues file found. Org has no tracked issues.")
|
|
89
|
+
sys.exit(0)
|
|
90
|
+
|
|
91
|
+
data = json.load(open(issues_path))
|
|
92
|
+
issues = data.get("issues", [])
|
|
93
|
+
|
|
94
|
+
# Load active agent run IDs from state
|
|
95
|
+
active_runs = set()
|
|
96
|
+
if os.path.exists(state_path):
|
|
97
|
+
try:
|
|
98
|
+
state = json.load(open(state_path))
|
|
99
|
+
for role in state.get("roles", []):
|
|
100
|
+
if role.get("currentRunId"): active_runs.add(role["currentRunId"])
|
|
101
|
+
except: pass
|
|
102
|
+
|
|
103
|
+
non_terminal_statuses = {"todo","in_progress","blocked","in_review"}
|
|
104
|
+
terminal_statuses = {"done","cancelled"}
|
|
105
|
+
|
|
106
|
+
healthy, stalled, warnings = [], [], []
|
|
107
|
+
now = datetime.utcnow()
|
|
108
|
+
|
|
109
|
+
for iss in issues:
|
|
110
|
+
status = iss.get("status","")
|
|
111
|
+
if status in terminal_statuses or status not in non_terminal_statuses:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
aId = iss.get("assigneeAgentId") or iss.get("assigneeId")
|
|
115
|
+
uId = iss.get("assigneeUserId")
|
|
116
|
+
iid = iss.get("id","?")
|
|
117
|
+
title = iss.get("title","?")[:50]
|
|
118
|
+
|
|
119
|
+
# User-owned: skip strict execution checks
|
|
120
|
+
if uId and not aId:
|
|
121
|
+
healthy.append((iid, title, status, "user-owned"))
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Evaluate liveness
|
|
125
|
+
paths = []
|
|
126
|
+
|
|
127
|
+
run_id = iss.get("executionRunId") or iss.get("checkoutRunId")
|
|
128
|
+
if run_id and run_id in active_runs:
|
|
129
|
+
paths.append("active-run")
|
|
130
|
+
|
|
131
|
+
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"))
|
|
140
|
+
|
|
141
|
+
if iss.get("executionPolicy", {}).get("monitor", {}).get("nextCheckAt"):
|
|
142
|
+
paths.append("monitor")
|
|
143
|
+
|
|
144
|
+
if iss.get("recoveryActions") and any(
|
|
145
|
+
r.get("status") not in ("resolved","cancelled")
|
|
146
|
+
for r in iss.get("recoveryActions",[])
|
|
147
|
+
):
|
|
148
|
+
paths.append("recovery-action")
|
|
149
|
+
|
|
150
|
+
if iss.get("currentParticipant"):
|
|
151
|
+
paths.append("participant")
|
|
152
|
+
|
|
153
|
+
# Stale heartbeat check (in_progress with no run and no recent update)
|
|
154
|
+
if status == "in_progress" and aId and not paths:
|
|
155
|
+
updated = iss.get("updatedAt","")
|
|
156
|
+
if updated:
|
|
157
|
+
try:
|
|
158
|
+
age = now - datetime.fromisoformat(updated[:19])
|
|
159
|
+
if age > timedelta(hours=2):
|
|
160
|
+
stalled.append((iid, title, status, f"in_progress {int(age.total_seconds()//3600)}h with no active path"))
|
|
161
|
+
continue
|
|
162
|
+
except: pass
|
|
163
|
+
stalled.append((iid, title, status, "in_progress with no active execution path"))
|
|
164
|
+
continue
|
|
165
|
+
|
|
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:
|
|
169
|
+
healthy.append((iid, title, status, " + ".join(paths)))
|
|
170
|
+
else:
|
|
171
|
+
healthy.append((iid, title, status, "no agent assignee"))
|
|
172
|
+
|
|
173
|
+
print(f" ✓ Healthy: {len(healthy)}")
|
|
174
|
+
if healthy:
|
|
175
|
+
for iid, t, s, p in healthy[:5]:
|
|
176
|
+
print(f" {iid}: [{s}] {t} — {p}")
|
|
177
|
+
if len(healthy) > 5: print(f" … {len(healthy)-5} more")
|
|
178
|
+
|
|
179
|
+
print()
|
|
180
|
+
if stalled:
|
|
181
|
+
print(f" ✗ STALLED: {len(stalled)}")
|
|
182
|
+
for iid, t, s, p in stalled:
|
|
183
|
+
print(f" {iid}: [{s}] {t}")
|
|
184
|
+
print(f" → {p}")
|
|
185
|
+
print()
|
|
186
|
+
print(" Fix: /mastermind:liveness --org <org> --action recover --issue-id <id> --reason 'execution path lost'")
|
|
187
|
+
else:
|
|
188
|
+
print(" ✓ No stalled issues.")
|
|
189
|
+
|
|
190
|
+
if warnings:
|
|
191
|
+
print()
|
|
192
|
+
print(f" ⚠ Warnings: {len(warnings)}")
|
|
193
|
+
for iid, t, s, p in warnings:
|
|
194
|
+
print(f" {iid}: [{s}] {t} — {p}")
|
|
195
|
+
PYEOF
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### checkout
|
|
199
|
+
|
|
200
|
+
Claim an issue for execution by an agent run. Sets `checkoutRunId` and `executionRunId`.
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
[ -z "$issue_id" ] && { echo "ERROR: --issue-id required."; exit 1; }
|
|
204
|
+
[ -z "$agent_id" ] && { echo "ERROR: --agent-id required."; exit 1; }
|
|
205
|
+
[ -z "$run_id" ] && { echo "ERROR: --run-id required."; exit 1; }
|
|
206
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
207
|
+
|
|
208
|
+
python3 - "$issuesFile" "$issue_id" "$agent_id" "$run_id" "$ts" <<'PYEOF'
|
|
209
|
+
import json, sys
|
|
210
|
+
|
|
211
|
+
path, iid, agentId, runId, ts = sys.argv[1:]
|
|
212
|
+
data = json.load(open(path))
|
|
213
|
+
issues = data.get("issues", [])
|
|
214
|
+
|
|
215
|
+
found = False
|
|
216
|
+
for iss in issues:
|
|
217
|
+
if iss.get("id") == iid:
|
|
218
|
+
existing = iss.get("checkoutRunId")
|
|
219
|
+
if existing and existing != runId:
|
|
220
|
+
print(f" CONFLICT: Issue already checked out by run {existing}")
|
|
221
|
+
print(f" Release first: /mastermind:liveness --org <org> --action release --issue-id {iid}")
|
|
222
|
+
sys.exit(1)
|
|
223
|
+
iss["checkoutRunId"] = runId
|
|
224
|
+
iss["executionRunId"] = runId
|
|
225
|
+
iss["assigneeId"] = agentId
|
|
226
|
+
iss["assigneeAgentId"]= agentId
|
|
227
|
+
iss["status"] = "in_progress"
|
|
228
|
+
iss["checkedOutAt"] = ts
|
|
229
|
+
iss["updatedAt"] = ts
|
|
230
|
+
found = True
|
|
231
|
+
print(f" CHECKOUT: Issue {iid} → agent {agentId}, run {runId}")
|
|
232
|
+
print(f" Status set to: in_progress")
|
|
233
|
+
break
|
|
234
|
+
|
|
235
|
+
if not found:
|
|
236
|
+
print(f" ERROR: Issue '{iid}' not found.")
|
|
237
|
+
sys.exit(1)
|
|
238
|
+
|
|
239
|
+
data["issues"] = issues
|
|
240
|
+
with open(path, "w") as f:
|
|
241
|
+
json.dump(data, f, indent=2)
|
|
242
|
+
PYEOF
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### release
|
|
246
|
+
|
|
247
|
+
Release the checkout lock on an issue.
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
[ -z "$issue_id" ] && { echo "ERROR: --issue-id required."; exit 1; }
|
|
251
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
252
|
+
|
|
253
|
+
python3 - "$issuesFile" "$issue_id" "$ts" "${run_id:-}" <<'PYEOF'
|
|
254
|
+
import json, sys, os
|
|
255
|
+
|
|
256
|
+
path, iid, ts, run_id = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4]
|
|
257
|
+
data = json.load(open(path))
|
|
258
|
+
issues = data.get("issues", [])
|
|
259
|
+
|
|
260
|
+
for iss in issues:
|
|
261
|
+
if iss.get("id") == iid:
|
|
262
|
+
current = iss.get("checkoutRunId","")
|
|
263
|
+
if run_id and current != run_id:
|
|
264
|
+
print(f" WARNING: Releasing run {run_id} but issue has run {current}. Proceeding.")
|
|
265
|
+
iss.pop("checkoutRunId", None)
|
|
266
|
+
iss.pop("executionRunId", None)
|
|
267
|
+
iss.pop("checkedOutAt", None)
|
|
268
|
+
iss["updatedAt"] = ts
|
|
269
|
+
print(f" RELEASED: Checkout cleared for issue {iid}")
|
|
270
|
+
print(f" Status remains: {iss.get('status','?')} — update separately if needed.")
|
|
271
|
+
data["issues"] = issues
|
|
272
|
+
with open(path, "w") as f:
|
|
273
|
+
json.dump(data, f, indent=2)
|
|
274
|
+
sys.exit(0)
|
|
275
|
+
|
|
276
|
+
print(f" ERROR: Issue '{iid}' not found.")
|
|
277
|
+
sys.exit(1)
|
|
278
|
+
PYEOF
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### wakeup
|
|
282
|
+
|
|
283
|
+
Decide whether the assignee of a `todo` or `blocked` issue should be woken.
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
[ -z "$issue_id" ] && { echo "ERROR: --issue-id required."; exit 1; }
|
|
287
|
+
|
|
288
|
+
python3 - "$issuesFile" "$stateFile" "$issue_id" "${agent_id:-}" <<'PYEOF'
|
|
289
|
+
import json, sys, os
|
|
290
|
+
|
|
291
|
+
issues_path, state_path, iid, actor_agent_id = sys.argv[1:]
|
|
292
|
+
|
|
293
|
+
data = json.load(open(issues_path))
|
|
294
|
+
iss = next((i for i in data.get("issues",[]) if i.get("id") == iid), None)
|
|
295
|
+
if not iss:
|
|
296
|
+
print(f" ERROR: Issue '{iid}' not found.")
|
|
297
|
+
sys.exit(1)
|
|
298
|
+
|
|
299
|
+
checkout_agent = iss.get("assigneeAgentId") or iss.get("assigneeId","")
|
|
300
|
+
checkout_run = iss.get("checkoutRunId","")
|
|
301
|
+
|
|
302
|
+
# Port of Paperclip's shouldWakeAssigneeOnCheckout logic
|
|
303
|
+
actor_is_agent = bool(actor_agent_id)
|
|
304
|
+
actor_differs = actor_agent_id != checkout_agent
|
|
305
|
+
checkout_has_no_run= not checkout_run
|
|
306
|
+
|
|
307
|
+
should_wake = (
|
|
308
|
+
not actor_is_agent # non-agent actor (board/human) → always wake
|
|
309
|
+
or actor_differs # different agent claiming → wake original
|
|
310
|
+
or checkout_has_no_run # no active run → wake to get work started
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
print(f" Issue: {iid} — {iss.get('title','?')[:60]}")
|
|
314
|
+
print(f" Status: {iss.get('status','?')}")
|
|
315
|
+
print(f" Assignee:{checkout_agent or '(none)'}")
|
|
316
|
+
print(f" Run: {checkout_run or '(none)'}")
|
|
317
|
+
print(f" Actor: {actor_agent_id or '(board)'}")
|
|
318
|
+
print()
|
|
319
|
+
if should_wake:
|
|
320
|
+
print(" WAKE: YES — assignee should be notified to pick up this issue.")
|
|
321
|
+
print(" Reasons:")
|
|
322
|
+
if not actor_is_agent: print(" · Non-agent actor (board/human)")
|
|
323
|
+
if actor_is_agent and actor_differs: print(f" · Actor ({actor_agent_id}) != assignee ({checkout_agent})")
|
|
324
|
+
if checkout_has_no_run: print(" · No active execution run")
|
|
325
|
+
else:
|
|
326
|
+
print(" WAKE: NO — assignee already has an active run for this issue.")
|
|
327
|
+
PYEOF
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### recover
|
|
331
|
+
|
|
332
|
+
File an explicit recovery action on a stalled issue with a named owner and next step.
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
[ -z "$issue_id" ] && { echo "ERROR: --issue-id required."; exit 1; }
|
|
336
|
+
[ -z "$reason" ] && { echo "ERROR: --reason required."; exit 1; }
|
|
337
|
+
ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
338
|
+
recoveryId="recovery-$(python3 -c 'import time; print(int(time.time()*1000))')"
|
|
339
|
+
|
|
340
|
+
python3 - "$issuesFile" "$issue_id" "$recoveryId" "${agent_id:-operator}" "$reason" "$ts" <<'PYEOF'
|
|
341
|
+
import json, sys
|
|
342
|
+
|
|
343
|
+
path, iid, rid, owner, cause, ts = sys.argv[1:]
|
|
344
|
+
data = json.load(open(path))
|
|
345
|
+
issues = data.get("issues", [])
|
|
346
|
+
|
|
347
|
+
for iss in issues:
|
|
348
|
+
if iss.get("id") == iid:
|
|
349
|
+
recovery = {
|
|
350
|
+
"id": rid,
|
|
351
|
+
"kind": "restore-liveness",
|
|
352
|
+
"owner": owner,
|
|
353
|
+
"cause": cause,
|
|
354
|
+
"createdAt": ts,
|
|
355
|
+
"status": "open",
|
|
356
|
+
"nextAction": f"Investigate why issue '{iid}' has no active execution path and restore it.",
|
|
357
|
+
}
|
|
358
|
+
iss.setdefault("recoveryActions", []).append(recovery)
|
|
359
|
+
iss["status"] = "blocked"
|
|
360
|
+
iss["updatedAt"] = ts
|
|
361
|
+
data["issues"] = issues
|
|
362
|
+
with open(path, "w") as f:
|
|
363
|
+
json.dump(data, f, indent=2)
|
|
364
|
+
print(f" RECOVERY ACTION FILED: {rid}")
|
|
365
|
+
print(f" Issue {iid} → status: blocked (pending recovery)")
|
|
366
|
+
print(f" Owner: {owner}")
|
|
367
|
+
print(f" Cause: {cause}")
|
|
368
|
+
print(f" Resolve with: /mastermind:liveness --org <org> --action checkout --issue-id {iid} --agent-id <id> --run-id <id>")
|
|
369
|
+
sys.exit(0)
|
|
370
|
+
|
|
371
|
+
print(f" ERROR: Issue '{iid}' not found.")
|
|
372
|
+
sys.exit(1)
|
|
373
|
+
PYEOF
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Step 3 — Return Output
|
|
379
|
+
|
|
380
|
+
```yaml
|
|
381
|
+
domain: ops
|
|
382
|
+
status: complete
|
|
383
|
+
action: <action>
|
|
384
|
+
org_name: <org_name>
|
|
385
|
+
issue_id: <issue_id or all>
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## Step 4 — Brain Write (standalone only)
|
|
391
|
+
|
|
392
|
+
If `caller` is not "command", follow _protocol.md Brain Write Procedure for domain `ops`.
|