panopticon-cli 0.5.0 → 0.5.3
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/dist/{agents-E43Y3HNU.js → agents-DMPT32H7.js} +7 -5
- package/dist/{chunk-AAFQANKW.js → chunk-2V2DQ3IX.js} +387 -110
- package/dist/chunk-2V2DQ3IX.js.map +1 -0
- package/dist/{chunk-GR6ZZMCX.js → chunk-4HST45MO.js} +15 -12
- package/dist/chunk-4HST45MO.js.map +1 -0
- package/dist/{chunk-YLPSQAM2.js → chunk-565HZ6VV.js} +2 -2
- package/dist/chunk-6N2KBSJA.js +452 -0
- package/dist/chunk-6N2KBSJA.js.map +1 -0
- package/dist/{chunk-HZT2AOPN.js → chunk-D67AQTHF.js} +44 -19
- package/dist/chunk-D67AQTHF.js.map +1 -0
- package/dist/{chunk-WQG2TYCB.js → chunk-DFNVHK3N.js} +2 -2
- package/dist/{chunk-7SN4L4PH.js → chunk-HOGYHJ2G.js} +2 -2
- package/dist/{chunk-FTCPTHIJ.js → chunk-HRU7S4TA.js} +24 -7
- package/dist/chunk-HRU7S4TA.js.map +1 -0
- package/dist/{chunk-PPRFKTVC.js → chunk-KBHRXV5T.js} +3 -3
- package/dist/chunk-KBHRXV5T.js.map +1 -0
- package/dist/{chunk-CWELWPWQ.js → chunk-MOPGR3CL.js} +1 -1
- package/dist/chunk-MOPGR3CL.js.map +1 -0
- package/dist/{chunk-OMNXYPXC.js → chunk-RLZQB7HS.js} +15 -2
- package/dist/chunk-RLZQB7HS.js.map +1 -0
- package/dist/{chunk-NTO3EDB3.js → chunk-T7BBPDEJ.js} +56 -13
- package/dist/chunk-T7BBPDEJ.js.map +1 -0
- package/dist/chunk-USYP2SBE.js +317 -0
- package/dist/chunk-USYP2SBE.js.map +1 -0
- package/dist/{chunk-JM6V62LT.js → chunk-ZDNQFWR5.js} +2 -2
- package/dist/{chunk-JM6V62LT.js.map → chunk-ZDNQFWR5.js.map} +1 -1
- package/dist/{chunk-HJSM6E6U.js → chunk-ZP6EWSZV.js} +29 -322
- package/dist/chunk-ZP6EWSZV.js.map +1 -0
- package/dist/{chunk-PELXV435.js → chunk-ZTYHZMEC.js} +2 -2
- package/dist/chunk-ZTYHZMEC.js.map +1 -0
- package/dist/cli/index.js +1612 -925
- package/dist/cli/index.js.map +1 -1
- package/dist/config-yaml-OVZLKFMA.js +18 -0
- package/dist/dashboard/prompts/merge-agent.md +7 -5
- package/dist/dashboard/prompts/review-agent.md +12 -1
- package/dist/dashboard/prompts/test-agent.md +3 -1
- package/dist/dashboard/public/assets/index-BJKEp64j.css +32 -0
- package/dist/dashboard/public/assets/index-CgJjqjAV.js +767 -0
- package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/dashboard/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-BflQw4A9.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-DxxdqCpr.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-VcznFIpX.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-D6zpsUhD.woff +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-DUi7WF5p.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
- package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
- package/dist/dashboard/public/index.html +5 -3
- package/dist/dashboard/server.js +5196 -2859
- package/dist/{feedback-writer-LVZ5TFYZ.js → feedback-writer-T43PI5S2.js} +2 -2
- package/dist/{hume-WMAUBBV2.js → hume-CKJJ3OUU.js} +3 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/{projects-JEIVIYC6.js → projects-KVM3MN3Y.js} +4 -2
- package/dist/{remote-agents-TFSMW7GN.js → remote-agents-ULPD6C5U.js} +3 -3
- package/dist/{remote-workspace-AHVHQEES.js → remote-workspace-XX6ARE6I.js} +3 -3
- package/dist/{review-status-EPFG4XM7.js → review-status-XKUKZF6J.js} +3 -2
- package/dist/{specialist-context-ZC6A4M3I.js → specialist-context-53AWO6AE.js} +6 -5
- package/dist/{specialist-context-ZC6A4M3I.js.map → specialist-context-53AWO6AE.js.map} +1 -1
- package/dist/{specialist-logs-KLGJCEUL.js → specialist-logs-QREUJ4HN.js} +6 -5
- package/dist/{specialists-O4HWDJL5.js → specialists-2DBBXRCK.js} +6 -5
- package/dist/{traefik-QN7R5I6V.js → traefik-5GL3Q7DJ.js} +3 -3
- package/dist/{tunnel-W2GZBLEV.js → tunnel-BKC7KLBX.js} +3 -3
- package/dist/{workspace-manager-IE4JL2JP.js → workspace-manager-ALBR62AS.js} +5 -5
- package/dist/workspace-manager-ALBR62AS.js.map +1 -0
- package/package.json +1 -1
- package/scripts/record-cost-event.js +8429 -47
- package/scripts/work-agent-stop-hook +221 -24
- package/skills/pan-new-project/SKILL.md +304 -0
- package/dist/chunk-AAFQANKW.js.map +0 -1
- package/dist/chunk-CWELWPWQ.js.map +0 -1
- package/dist/chunk-FTCPTHIJ.js.map +0 -1
- package/dist/chunk-GFP3PIPB.js +0 -96
- package/dist/chunk-GFP3PIPB.js.map +0 -1
- package/dist/chunk-GR6ZZMCX.js.map +0 -1
- package/dist/chunk-HJSM6E6U.js.map +0 -1
- package/dist/chunk-HZT2AOPN.js.map +0 -1
- package/dist/chunk-NTO3EDB3.js.map +0 -1
- package/dist/chunk-OMNXYPXC.js.map +0 -1
- package/dist/chunk-PELXV435.js.map +0 -1
- package/dist/chunk-PPRFKTVC.js.map +0 -1
- package/dist/dashboard/public/assets/index-BxpjweAL.css +0 -32
- package/dist/dashboard/public/assets/index-DQHkwvvJ.js +0 -743
- /package/dist/{agents-E43Y3HNU.js.map → agents-DMPT32H7.js.map} +0 -0
- /package/dist/{chunk-YLPSQAM2.js.map → chunk-565HZ6VV.js.map} +0 -0
- /package/dist/{chunk-WQG2TYCB.js.map → chunk-DFNVHK3N.js.map} +0 -0
- /package/dist/{chunk-7SN4L4PH.js.map → chunk-HOGYHJ2G.js.map} +0 -0
- /package/dist/{hume-WMAUBBV2.js.map → config-yaml-OVZLKFMA.js.map} +0 -0
- /package/dist/{feedback-writer-LVZ5TFYZ.js.map → feedback-writer-T43PI5S2.js.map} +0 -0
- /package/dist/{projects-JEIVIYC6.js.map → hume-CKJJ3OUU.js.map} +0 -0
- /package/dist/{remote-agents-TFSMW7GN.js.map → projects-KVM3MN3Y.js.map} +0 -0
- /package/dist/{review-status-EPFG4XM7.js.map → remote-agents-ULPD6C5U.js.map} +0 -0
- /package/dist/{remote-workspace-AHVHQEES.js.map → remote-workspace-XX6ARE6I.js.map} +0 -0
- /package/dist/{specialist-logs-KLGJCEUL.js.map → review-status-XKUKZF6J.js.map} +0 -0
- /package/dist/{specialists-O4HWDJL5.js.map → specialist-logs-QREUJ4HN.js.map} +0 -0
- /package/dist/{traefik-QN7R5I6V.js.map → specialists-2DBBXRCK.js.map} +0 -0
- /package/dist/{tunnel-W2GZBLEV.js.map → traefik-5GL3Q7DJ.js.map} +0 -0
- /package/dist/{workspace-manager-IE4JL2JP.js.map → tunnel-BKC7KLBX.js.map} +0 -0
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
# ~/.panopticon/bin/work-agent-stop-hook
|
|
3
3
|
# Called when any agent goes idle — detects work agents that forgot "pan work done"
|
|
4
4
|
#
|
|
5
|
-
# Uses
|
|
6
|
-
#
|
|
7
|
-
# If so, sends a nudge message to the agent via tmux.
|
|
5
|
+
# Uses evidence-based detection first (STATE.md, git, beads), then falls back to
|
|
6
|
+
# a lightweight AI model for ambiguous cases. Writes resolution to runtime.json.
|
|
8
7
|
#
|
|
9
8
|
# The model used is configurable via PANOPTICON_COMPLETION_CHECK_MODEL env var
|
|
10
9
|
# or defaults to the models.overrides.completion-check-hook setting in config.yaml,
|
|
@@ -37,14 +36,18 @@ esac
|
|
|
37
36
|
# Extract issue ID from agent ID (e.g., "agent-min-725" -> "MIN-725")
|
|
38
37
|
ISSUE_ID=$(echo "$AGENT_ID" | sed 's/^agent-//' | tr '[:lower:]' '[:upper:]')
|
|
39
38
|
|
|
39
|
+
AGENT_STATE_DIR="$HOME/.panopticon/agents/$AGENT_ID"
|
|
40
|
+
LOG_DIR="$HOME/.panopticon/logs"
|
|
41
|
+
mkdir -p "$LOG_DIR"
|
|
42
|
+
|
|
40
43
|
# Skip if completion marker already exists (agent already called pan work done)
|
|
41
|
-
COMPLETED_FILE="$
|
|
44
|
+
COMPLETED_FILE="$AGENT_STATE_DIR/completed"
|
|
42
45
|
if [ -f "$COMPLETED_FILE" ]; then
|
|
43
46
|
exit 0
|
|
44
47
|
fi
|
|
45
48
|
|
|
46
49
|
# Cooldown: don't nudge more than once per 10 minutes
|
|
47
|
-
NUDGE_FILE="$
|
|
50
|
+
NUDGE_FILE="$AGENT_STATE_DIR/.last-completion-nudge"
|
|
48
51
|
if [ -f "$NUDGE_FILE" ]; then
|
|
49
52
|
LAST_NUDGE=$(cat "$NUDGE_FILE" 2>/dev/null || echo "0")
|
|
50
53
|
NOW=$(date +%s)
|
|
@@ -54,15 +57,151 @@ if [ -f "$NUDGE_FILE" ]; then
|
|
|
54
57
|
fi
|
|
55
58
|
fi
|
|
56
59
|
|
|
57
|
-
#
|
|
58
|
-
|
|
59
|
-
|
|
60
|
+
# ============================================================================
|
|
61
|
+
# Helper: write resolution to runtime.json
|
|
62
|
+
# ============================================================================
|
|
63
|
+
write_resolution() {
|
|
64
|
+
local resolution="$1"
|
|
65
|
+
local runtime_file="$AGENT_STATE_DIR/runtime.json"
|
|
66
|
+
|
|
67
|
+
# Read existing resolution count
|
|
68
|
+
local existing_count=0
|
|
69
|
+
local existing_resolution=""
|
|
70
|
+
if [ -f "$runtime_file" ]; then
|
|
71
|
+
existing_resolution=$(python3 -c "import json,sys; d=json.load(open('$runtime_file')); print(d.get('resolution',''))" 2>/dev/null || echo "")
|
|
72
|
+
existing_count=$(python3 -c "import json,sys; d=json.load(open('$runtime_file')); print(d.get('resolutionCount',0))" 2>/dev/null || echo "0")
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# Increment count if same resolution, reset if changed
|
|
76
|
+
local new_count
|
|
77
|
+
if [ "$existing_resolution" = "$resolution" ]; then
|
|
78
|
+
new_count=$(( existing_count + 1 ))
|
|
79
|
+
else
|
|
80
|
+
new_count=1
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
local now_iso
|
|
84
|
+
now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
85
|
+
|
|
86
|
+
# Merge into runtime.json using python3 (jq not always available)
|
|
87
|
+
python3 - <<PYEOF 2>/dev/null || true
|
|
88
|
+
import json, os
|
|
89
|
+
|
|
90
|
+
runtime_file = "$runtime_file"
|
|
91
|
+
resolution = "$resolution"
|
|
92
|
+
new_count = $new_count
|
|
93
|
+
now_iso = "$now_iso"
|
|
94
|
+
|
|
95
|
+
# Read existing
|
|
96
|
+
data = {}
|
|
97
|
+
if os.path.exists(runtime_file):
|
|
98
|
+
try:
|
|
99
|
+
with open(runtime_file) as f:
|
|
100
|
+
data = json.load(f)
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# Update resolution fields
|
|
105
|
+
data['resolution'] = resolution
|
|
106
|
+
data['resolutionCount'] = new_count
|
|
107
|
+
data['resolutionUpdatedAt'] = now_iso
|
|
108
|
+
|
|
109
|
+
# Write back
|
|
110
|
+
with open(runtime_file, 'w') as f:
|
|
111
|
+
json.dump(data, f, indent=2)
|
|
112
|
+
PYEOF
|
|
113
|
+
|
|
114
|
+
echo "[$(date -Iseconds)] work-agent-stop-hook: $AGENT_ID resolution=$resolution count=$new_count" \
|
|
115
|
+
>> "$LOG_DIR/hooks.log" 2>/dev/null || true
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# ============================================================================
|
|
119
|
+
# Phase 1: Evidence-Based Completion Detection
|
|
120
|
+
# ============================================================================
|
|
121
|
+
|
|
122
|
+
# Read workspace path from state.json
|
|
123
|
+
WORKSPACE=""
|
|
124
|
+
STATE_FILE="$AGENT_STATE_DIR/state.json"
|
|
125
|
+
if [ -f "$STATE_FILE" ]; then
|
|
126
|
+
WORKSPACE=$(python3 -c "import json; d=json.load(open('$STATE_FILE')); print(d.get('workspace',''))" 2>/dev/null || echo "")
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
STATE_MD_COMPLETE=false
|
|
130
|
+
BRANCH_PUSHED=false
|
|
131
|
+
BEADS_CLOSED=false
|
|
132
|
+
|
|
133
|
+
# Check 1: STATE.md completion markers
|
|
134
|
+
if [ -n "$WORKSPACE" ] && [ -f "$WORKSPACE/.planning/STATE.md" ]; then
|
|
135
|
+
if grep -qiE '(^##?\s*(Status|Current Status).*:.*\b(COMPLETE|DONE|FINISHED)\b|^Status:\s*(COMPLETE|DONE|FINISHED)|implementation[[:space:]]+complete|all[[:space:]]+tasks[[:space:]]+complete)' "$WORKSPACE/.planning/STATE.md" 2>/dev/null; then
|
|
136
|
+
STATE_MD_COMPLETE=true
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
# Check 2: Branch pushed to remote (git log origin/<branch>..HEAD is empty)
|
|
141
|
+
if [ -n "$WORKSPACE" ] && [ -d "$WORKSPACE/.git" ] || ([ -n "$WORKSPACE" ] && git -C "$WORKSPACE" rev-parse --git-dir >/dev/null 2>&1); then
|
|
142
|
+
BRANCH=$(git -C "$WORKSPACE" branch --show-current 2>/dev/null || echo "")
|
|
143
|
+
if [ -n "$BRANCH" ]; then
|
|
144
|
+
UNPUSHED=$(git -C "$WORKSPACE" log "origin/$BRANCH..HEAD" --oneline 2>/dev/null || echo "no-remote")
|
|
145
|
+
if [ "$UNPUSHED" = "" ]; then
|
|
146
|
+
BRANCH_PUSHED=true
|
|
147
|
+
fi
|
|
148
|
+
fi
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Check 3: Beads closed (all tasks done)
|
|
152
|
+
if [ -n "$WORKSPACE" ] && [ -d "$WORKSPACE/.planning/beads" ]; then
|
|
153
|
+
OPEN_BEADS=$(bd list --status open 2>/dev/null | grep -c '.' || echo "0")
|
|
154
|
+
if [ "$OPEN_BEADS" = "0" ]; then
|
|
155
|
+
BEADS_CLOSED=true
|
|
156
|
+
fi
|
|
157
|
+
elif [ -z "$WORKSPACE" ] || [ ! -d "$WORKSPACE/.planning/beads" ]; then
|
|
158
|
+
# No beads directory = treat as N/A (closed)
|
|
159
|
+
BEADS_CLOSED=true
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# Apply decision matrix from spec
|
|
163
|
+
if [ "$STATE_MD_COMPLETE" = "true" ] && [ "$BRANCH_PUSHED" = "true" ]; then
|
|
164
|
+
# Strong evidence: agent completed but forgot pan work done
|
|
165
|
+
echo "[$(date -Iseconds)] work-agent-stop-hook: $AGENT_ID evidence=FORGOT_COMPLETION (STATE.md=complete, branch=pushed, beads=$BEADS_CLOSED)" \
|
|
166
|
+
>> "$LOG_DIR/hooks.log" 2>/dev/null || true
|
|
167
|
+
|
|
168
|
+
write_resolution "done"
|
|
169
|
+
|
|
170
|
+
# Record nudge timestamp for cooldown
|
|
171
|
+
mkdir -p "$AGENT_STATE_DIR"
|
|
172
|
+
date +%s > "$NUDGE_FILE" 2>/dev/null || true
|
|
173
|
+
|
|
174
|
+
NUDGE_MSG="Your STATE.md indicates work is complete and your branch is pushed. You MUST run this command now:
|
|
175
|
+
|
|
176
|
+
pan work done $ISSUE_ID -c \"Implementation complete\"
|
|
177
|
+
|
|
178
|
+
If you still have remaining tasks, continue working on them. Do NOT stop until all work is done AND you have called pan work done."
|
|
179
|
+
|
|
180
|
+
TMPFILE=$(mktemp)
|
|
181
|
+
echo "$NUDGE_MSG" > "$TMPFILE"
|
|
182
|
+
tmux load-buffer "$TMPFILE" 2>/dev/null
|
|
183
|
+
tmux paste-buffer -t "$AGENT_ID" 2>/dev/null
|
|
184
|
+
sleep 0.3
|
|
185
|
+
tmux send-keys -t "$AGENT_ID" C-m 2>/dev/null
|
|
186
|
+
rm -f "$TMPFILE"
|
|
187
|
+
|
|
188
|
+
echo "[$(date -Iseconds)] work-agent-stop-hook: Sent FORGOT_COMPLETION nudge to $AGENT_ID" \
|
|
189
|
+
>> "$LOG_DIR/hooks.log" 2>/dev/null || true
|
|
190
|
+
|
|
191
|
+
exit 0
|
|
192
|
+
|
|
193
|
+
elif [ "$STATE_MD_COMPLETE" = "true" ] && [ "$BRANCH_PUSHED" = "false" ]; then
|
|
194
|
+
# STATE.md says done but branch not yet pushed — agent is still working (pushing)
|
|
195
|
+
echo "[$(date -Iseconds)] work-agent-stop-hook: $AGENT_ID evidence=STILL_WORKING (STATE.md=complete, branch=not-pushed)" \
|
|
196
|
+
>> "$LOG_DIR/hooks.log" 2>/dev/null || true
|
|
60
197
|
exit 0
|
|
61
198
|
fi
|
|
62
199
|
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
|
|
200
|
+
# ============================================================================
|
|
201
|
+
# Heuristic pre-check: skip expensive AI call if clearly mid-work
|
|
202
|
+
# ============================================================================
|
|
203
|
+
OUTPUT=$(tmux capture-pane -t "$AGENT_ID" -p -S -80 2>/dev/null || echo "")
|
|
204
|
+
if [ -z "$OUTPUT" ]; then
|
|
66
205
|
exit 0
|
|
67
206
|
fi
|
|
68
207
|
|
|
@@ -71,8 +210,9 @@ if ! echo "$OUTPUT" | tail -10 | grep -qE '(^❯|Worked for)'; then
|
|
|
71
210
|
exit 0 # Agent doesn't appear to be at an idle prompt
|
|
72
211
|
fi
|
|
73
212
|
|
|
74
|
-
#
|
|
75
|
-
#
|
|
213
|
+
# ============================================================================
|
|
214
|
+
# LLM fallback for ambiguous cases
|
|
215
|
+
# ============================================================================
|
|
76
216
|
COMPLETION_MODEL="${PANOPTICON_COMPLETION_CHECK_MODEL:-}"
|
|
77
217
|
if [ -z "$COMPLETION_MODEL" ]; then
|
|
78
218
|
CONFIG_FILE="$HOME/.panopticon/config.yaml"
|
|
@@ -82,7 +222,33 @@ if [ -z "$COMPLETION_MODEL" ]; then
|
|
|
82
222
|
fi
|
|
83
223
|
COMPLETION_MODEL="${COMPLETION_MODEL:-claude-haiku-4-5}"
|
|
84
224
|
|
|
85
|
-
#
|
|
225
|
+
# Read current resolution count from runtime.json for escalation logic
|
|
226
|
+
CURRENT_RESOLUTION=$(python3 -c "
|
|
227
|
+
import json, os
|
|
228
|
+
f = '$AGENT_STATE_DIR/runtime.json'
|
|
229
|
+
if os.path.exists(f):
|
|
230
|
+
try:
|
|
231
|
+
d = json.load(open(f))
|
|
232
|
+
print(d.get('resolution', ''))
|
|
233
|
+
except Exception:
|
|
234
|
+
print('')
|
|
235
|
+
else:
|
|
236
|
+
print('')
|
|
237
|
+
" 2>/dev/null || echo "")
|
|
238
|
+
|
|
239
|
+
CURRENT_COUNT=$(python3 -c "
|
|
240
|
+
import json, os
|
|
241
|
+
f = '$AGENT_STATE_DIR/runtime.json'
|
|
242
|
+
if os.path.exists(f):
|
|
243
|
+
try:
|
|
244
|
+
d = json.load(open(f))
|
|
245
|
+
print(d.get('resolutionCount', 0))
|
|
246
|
+
except Exception:
|
|
247
|
+
print(0)
|
|
248
|
+
else:
|
|
249
|
+
print(0)
|
|
250
|
+
" 2>/dev/null || echo "0")
|
|
251
|
+
|
|
86
252
|
ANALYSIS_PROMPT="You are analyzing a work agent's terminal output to determine if it finished its work but forgot to call 'pan work done'.
|
|
87
253
|
|
|
88
254
|
The agent was working on issue $ISSUE_ID. Here is the last 80 lines of its terminal output:
|
|
@@ -97,25 +263,19 @@ Respond with EXACTLY one of these words (nothing else):
|
|
|
97
263
|
- STOPPED_FOR_INPUT — if the agent stopped because it needs human input or hit a blocker
|
|
98
264
|
- UNCLEAR — if you cannot determine the state"
|
|
99
265
|
|
|
100
|
-
# Run the analysis using claude CLI (headless, no interactive session)
|
|
101
266
|
RESULT=$(echo "$ANALYSIS_PROMPT" | claude -p --model "$COMPLETION_MODEL" --max-tokens 20 2>/dev/null || echo "UNCLEAR")
|
|
102
|
-
|
|
103
|
-
# Extract just the verdict (first word of output, strip whitespace)
|
|
104
267
|
VERDICT=$(echo "$RESULT" | tr -d '[:space:]' | head -c 30)
|
|
105
268
|
|
|
106
|
-
# Log the check
|
|
107
|
-
LOG_DIR="$HOME/.panopticon/logs"
|
|
108
|
-
mkdir -p "$LOG_DIR"
|
|
109
269
|
echo "[$(date -Iseconds)] work-agent-stop-hook: $AGENT_ID ($ISSUE_ID) -> $VERDICT (model: $COMPLETION_MODEL)" \
|
|
110
270
|
>> "$LOG_DIR/hooks.log" 2>/dev/null || true
|
|
111
271
|
|
|
112
272
|
if [ "$VERDICT" = "FORGOT_COMPLETION" ]; then
|
|
273
|
+
write_resolution "done"
|
|
274
|
+
|
|
113
275
|
# Record nudge timestamp for cooldown
|
|
114
|
-
mkdir -p "$
|
|
276
|
+
mkdir -p "$AGENT_STATE_DIR"
|
|
115
277
|
date +%s > "$NUDGE_FILE" 2>/dev/null || true
|
|
116
278
|
|
|
117
|
-
# Write the nudge message to a temp file and use load-buffer + paste-buffer
|
|
118
|
-
# (the reliable tmux message delivery pattern from CLAUDE.md)
|
|
119
279
|
NUDGE_MSG="You stopped without calling pan work done. If your implementation is complete, you MUST run this command now:
|
|
120
280
|
|
|
121
281
|
pan work done $ISSUE_ID -c \"Implementation complete\"
|
|
@@ -130,8 +290,45 @@ If you still have remaining tasks, continue working on them. Do NOT stop until a
|
|
|
130
290
|
tmux send-keys -t "$AGENT_ID" C-m 2>/dev/null
|
|
131
291
|
rm -f "$TMPFILE"
|
|
132
292
|
|
|
133
|
-
echo "[$(date -Iseconds)] work-agent-stop-hook: Sent
|
|
293
|
+
echo "[$(date -Iseconds)] work-agent-stop-hook: Sent FORGOT_COMPLETION nudge to $AGENT_ID" \
|
|
134
294
|
>> "$LOG_DIR/hooks.log" 2>/dev/null || true
|
|
295
|
+
|
|
296
|
+
elif [ "$VERDICT" = "STOPPED_FOR_INPUT" ]; then
|
|
297
|
+
write_resolution "needs_input"
|
|
298
|
+
|
|
299
|
+
elif [ "$VERDICT" = "UNCLEAR" ]; then
|
|
300
|
+
# Escalate to stuck after 2+ UNCLEAR results
|
|
301
|
+
if [ "$CURRENT_RESOLUTION" = "unclear" ] || [ "$CURRENT_COUNT" -ge 2 ]; then
|
|
302
|
+
write_resolution "stuck"
|
|
303
|
+
echo "[$(date -Iseconds)] work-agent-stop-hook: $AGENT_ID escalated to stuck after $CURRENT_COUNT UNCLEAR results" \
|
|
304
|
+
>> "$LOG_DIR/hooks.log" 2>/dev/null || true
|
|
305
|
+
else
|
|
306
|
+
# First UNCLEAR — track it but don't escalate yet
|
|
307
|
+
python3 - <<PYEOF 2>/dev/null || true
|
|
308
|
+
import json, os
|
|
309
|
+
|
|
310
|
+
runtime_file = "$AGENT_STATE_DIR/runtime.json"
|
|
311
|
+
data = {}
|
|
312
|
+
if os.path.exists(runtime_file):
|
|
313
|
+
try:
|
|
314
|
+
with open(runtime_file) as f:
|
|
315
|
+
data = json.load(f)
|
|
316
|
+
except Exception:
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
existing = data.get('resolution', '')
|
|
320
|
+
count = data.get('resolutionCount', 0)
|
|
321
|
+
if existing == 'unclear':
|
|
322
|
+
data['resolutionCount'] = count + 1
|
|
323
|
+
else:
|
|
324
|
+
data['resolution'] = 'unclear'
|
|
325
|
+
data['resolutionCount'] = 1
|
|
326
|
+
data['resolutionUpdatedAt'] = "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
327
|
+
|
|
328
|
+
with open(runtime_file, 'w') as f:
|
|
329
|
+
json.dump(data, f, indent=2)
|
|
330
|
+
PYEOF
|
|
331
|
+
fi
|
|
135
332
|
fi
|
|
136
333
|
|
|
137
334
|
exit 0
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pan-new-project
|
|
3
|
+
description: >
|
|
4
|
+
Complete setup for registering a new project with Panopticon. Handles
|
|
5
|
+
project registration, issue prefix, workspace config, trust setup,
|
|
6
|
+
beads init, tracker config, and validates against working projects.
|
|
7
|
+
triggers:
|
|
8
|
+
- new project
|
|
9
|
+
- add new project
|
|
10
|
+
- register new project
|
|
11
|
+
- setup new project
|
|
12
|
+
- onboard project
|
|
13
|
+
- pan new project
|
|
14
|
+
allowed-tools:
|
|
15
|
+
- Bash
|
|
16
|
+
- Read
|
|
17
|
+
- Edit
|
|
18
|
+
- Write
|
|
19
|
+
- Glob
|
|
20
|
+
- Grep
|
|
21
|
+
- AskUserQuestion
|
|
22
|
+
version: "2.0.0"
|
|
23
|
+
author: "Ed Becker"
|
|
24
|
+
license: "MIT"
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# New Project Setup
|
|
28
|
+
|
|
29
|
+
**Trigger:** `/pan-new-project`
|
|
30
|
+
|
|
31
|
+
Sets up a new project for Panopticon management. This is the ONLY correct
|
|
32
|
+
way to add a new project. Do NOT just run `pan project add` alone — it
|
|
33
|
+
creates a skeleton entry that breaks planning agents, workspace creation,
|
|
34
|
+
issue routing, and beads.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## WHY THIS SKILL EXISTS
|
|
39
|
+
|
|
40
|
+
Running `pan project add /path --name foo` alone causes these failures:
|
|
41
|
+
|
|
42
|
+
| Missing config | Symptom |
|
|
43
|
+
|----------------|---------|
|
|
44
|
+
| `linear_team` (issue prefix) | Planning agents start in `$HOME`, not the project root |
|
|
45
|
+
| Trust entry in `~/.claude.json` | Claude Code shows trust dialog, blocking autonomous agents |
|
|
46
|
+
| `GITHUB_REPOS` entry | Issues don't appear on the dashboard kanban board |
|
|
47
|
+
| `beads.role` in git config | Every `bd` command prints "beads.role not configured" warnings |
|
|
48
|
+
| `workspaces/` directory | Git worktree creation fails |
|
|
49
|
+
| `.gitignore` entry | `workspaces/` gets committed accidentally |
|
|
50
|
+
| Test config | Specialist test agents can't run tests |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## EXECUTION STEPS
|
|
55
|
+
|
|
56
|
+
### Step 1: Gather Project Information
|
|
57
|
+
|
|
58
|
+
Ask the user for (or auto-detect from the filesystem):
|
|
59
|
+
|
|
60
|
+
| Field | Required | Example | Notes |
|
|
61
|
+
|-------|----------|---------|-------|
|
|
62
|
+
| Path | Yes | `/home/eltmon/Projects/myapp` | Must exist, must have `.git/` |
|
|
63
|
+
| Name | Yes | `myapp` | Short lowercase key for projects.yaml |
|
|
64
|
+
| Issue prefix | Yes | `APP` | Maps `APP-123` → this project. Goes in `linear_team` field |
|
|
65
|
+
| Tracker | Yes | `github` / `linear` / `gitlab` | Where issues live |
|
|
66
|
+
| Repo slug | Yes | `owner/repo` | `github_repo` or `gitlab_repo` |
|
|
67
|
+
| Workspace type | Yes | `standalone` / `monorepo` / `polyrepo` | How git worktrees work |
|
|
68
|
+
|
|
69
|
+
**Auto-detection:**
|
|
70
|
+
- `go.mod` → Go, test: `make test` or `go test ./...`
|
|
71
|
+
- `package.json` → Node/TS, test: `npm test` or `pnpm test`
|
|
72
|
+
- `pom.xml` / `mvnw` → Java/Maven, test: `./mvnw test`
|
|
73
|
+
- `Cargo.toml` → Rust, test: `cargo test`
|
|
74
|
+
- `pyproject.toml` → Python, test: `pytest`
|
|
75
|
+
|
|
76
|
+
### Step 2: Register Project
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pan project add <path> --name <name>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This creates a minimal entry AND pre-trusts the directory in `~/.claude.json`
|
|
83
|
+
(the `projectAddCommand` calls `preTrustDirectory` automatically).
|
|
84
|
+
|
|
85
|
+
### Step 3: Configure projects.yaml
|
|
86
|
+
|
|
87
|
+
Edit `~/.panopticon/projects.yaml` to add the FULL configuration.
|
|
88
|
+
|
|
89
|
+
**Minimum viable config:**
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
<project-key>:
|
|
93
|
+
name: <name>
|
|
94
|
+
path: <absolute-path>
|
|
95
|
+
linear_team: <PREFIX> # CRITICAL: issue prefix for routing
|
|
96
|
+
github_repo: <owner/repo> # or gitlab_repo
|
|
97
|
+
workspace:
|
|
98
|
+
type: <standalone|monorepo|polyrepo>
|
|
99
|
+
workspaces_dir: workspaces
|
|
100
|
+
default_branch: main
|
|
101
|
+
tests:
|
|
102
|
+
unit:
|
|
103
|
+
type: <go|vitest|maven|pytest|cargo>
|
|
104
|
+
path: .
|
|
105
|
+
command: <test command>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Full config** (for projects with services, Docker, DNS):
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
<project-key>:
|
|
112
|
+
name: <name>
|
|
113
|
+
path: <absolute-path>
|
|
114
|
+
linear_team: <PREFIX>
|
|
115
|
+
github_repo: <owner/repo>
|
|
116
|
+
workspace:
|
|
117
|
+
type: <type>
|
|
118
|
+
workspaces_dir: workspaces
|
|
119
|
+
default_branch: main
|
|
120
|
+
dns:
|
|
121
|
+
domain: <name>.localhost
|
|
122
|
+
entries:
|
|
123
|
+
- "{{FEATURE_FOLDER}}.{{DOMAIN}}"
|
|
124
|
+
sync_method: hosts_file
|
|
125
|
+
docker:
|
|
126
|
+
traefik: templates/traefik
|
|
127
|
+
compose_template: infra/.devcontainer-template
|
|
128
|
+
agent:
|
|
129
|
+
template_dir: infra/.agent-template
|
|
130
|
+
copy_dirs:
|
|
131
|
+
- .claude/commands
|
|
132
|
+
- .claude/skills
|
|
133
|
+
services:
|
|
134
|
+
- name: <service>
|
|
135
|
+
path: .
|
|
136
|
+
start_command: <cmd>
|
|
137
|
+
health_url: <url>
|
|
138
|
+
port: <port>
|
|
139
|
+
env:
|
|
140
|
+
secrets_file: ~/.myapp/.env
|
|
141
|
+
tests:
|
|
142
|
+
unit:
|
|
143
|
+
type: <type>
|
|
144
|
+
path: .
|
|
145
|
+
command: <cmd>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Step 4: Add to Dashboard Tracker Config
|
|
149
|
+
|
|
150
|
+
For **GitHub** projects, add to `GITHUB_REPOS` in `~/.panopticon.env`:
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Format: owner/repo:PREFIX (comma-separated)
|
|
154
|
+
# Example: current value might be:
|
|
155
|
+
# GITHUB_REPOS=eltmon/panopticon-cli:PAN
|
|
156
|
+
# Append the new project:
|
|
157
|
+
# GITHUB_REPOS=eltmon/panopticon-cli:PAN,owner/newrepo:APP
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Read current value, append new repo, write back. The dashboard polls this
|
|
161
|
+
to fetch issues from GitHub.
|
|
162
|
+
|
|
163
|
+
For **Linear** projects, issues are fetched automatically by team — no
|
|
164
|
+
extra config needed beyond `linear_team` in projects.yaml.
|
|
165
|
+
|
|
166
|
+
For **GitLab** projects, TBD — not yet supported in dashboard polling.
|
|
167
|
+
|
|
168
|
+
### Step 5: Initialize Beads
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
cd <project-path>
|
|
172
|
+
git config beads.role agent
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
This prevents the `"beads.role not configured"` warning on every `bd` command.
|
|
176
|
+
New worktrees inherit this automatically since Panopticon now sets it during
|
|
177
|
+
workspace creation (`workspace-manager.ts` and `worktree.ts`).
|
|
178
|
+
|
|
179
|
+
### Step 6: Create workspaces/ Directory
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
mkdir -p <project-path>/workspaces
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Check `.gitignore` — add `workspaces/` if not already there:
|
|
186
|
+
```bash
|
|
187
|
+
grep -q '^workspaces/' <project-path>/.gitignore 2>/dev/null || \
|
|
188
|
+
echo 'workspaces/' >> <project-path>/.gitignore
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Step 7: Create CLAUDE.md (if missing)
|
|
192
|
+
|
|
193
|
+
Check if the project has a `CLAUDE.md`. If not, create a minimal one:
|
|
194
|
+
|
|
195
|
+
```markdown
|
|
196
|
+
# <Project Name>
|
|
197
|
+
|
|
198
|
+
## Project Overview
|
|
199
|
+
<Brief description>
|
|
200
|
+
|
|
201
|
+
## Stack
|
|
202
|
+
<Language, framework, key dependencies>
|
|
203
|
+
|
|
204
|
+
## Development
|
|
205
|
+
<How to build, run, test>
|
|
206
|
+
|
|
207
|
+
## Testing
|
|
208
|
+
<Test commands, coverage requirements>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Step 8: Validate Configuration
|
|
212
|
+
|
|
213
|
+
Run ALL of these checks and report pass/fail:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# 1. Project registered
|
|
217
|
+
pan project list | grep <name>
|
|
218
|
+
|
|
219
|
+
# 2. Issue prefix resolves (won't crash)
|
|
220
|
+
# Check projects.yaml has linear_team: <PREFIX>
|
|
221
|
+
|
|
222
|
+
# 3. Trust is set in ~/.claude.json
|
|
223
|
+
node -e "
|
|
224
|
+
const d=JSON.parse(require('fs').readFileSync(
|
|
225
|
+
require('os').homedir()+'/.claude.json','utf8'));
|
|
226
|
+
console.log(d.projects?.['<path>']?.hasTrustDialogAccepted
|
|
227
|
+
? 'PASS: trusted' : 'FAIL: not trusted');
|
|
228
|
+
"
|
|
229
|
+
|
|
230
|
+
# 4. Dashboard can see issues (GitHub only)
|
|
231
|
+
grep 'GITHUB_REPOS' ~/.panopticon.env | grep -q '<PREFIX>' && \
|
|
232
|
+
echo "PASS: in GITHUB_REPOS" || echo "FAIL: not in GITHUB_REPOS"
|
|
233
|
+
|
|
234
|
+
# 5. Beads configured
|
|
235
|
+
cd <path> && git config beads.role && echo "PASS" || echo "FAIL: beads.role not set"
|
|
236
|
+
|
|
237
|
+
# 6. workspaces/ exists
|
|
238
|
+
test -d <path>/workspaces && echo "PASS" || echo "FAIL: no workspaces/"
|
|
239
|
+
|
|
240
|
+
# 7. workspaces/ in .gitignore
|
|
241
|
+
grep -q 'workspaces' <path>/.gitignore 2>/dev/null && \
|
|
242
|
+
echo "PASS" || echo "FAIL: workspaces/ not in .gitignore"
|
|
243
|
+
|
|
244
|
+
# 8. CLAUDE.md exists
|
|
245
|
+
test -f <path>/CLAUDE.md && echo "PASS" || echo "WARN: no CLAUDE.md"
|
|
246
|
+
|
|
247
|
+
# 9. Git clean
|
|
248
|
+
cd <path> && git status --short | head -5
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Step 9: Summary
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
## New Project Setup Complete: <NAME>
|
|
255
|
+
|
|
256
|
+
Path: <path>
|
|
257
|
+
Issue prefix: <PREFIX> (e.g., <PREFIX>-1, <PREFIX>-42)
|
|
258
|
+
Tracker: GitHub (<owner/repo>)
|
|
259
|
+
Workspace type: <type>
|
|
260
|
+
Tests: <command>
|
|
261
|
+
Trusted: Yes
|
|
262
|
+
Beads: Configured
|
|
263
|
+
Dashboard: Issues visible
|
|
264
|
+
|
|
265
|
+
Validation: 8/8 checks passed
|
|
266
|
+
|
|
267
|
+
Next steps:
|
|
268
|
+
1. Create issues on <tracker>
|
|
269
|
+
2. Run: pan opus-plan <PREFIX>-<N> (plan with Opus)
|
|
270
|
+
3. Run: pan work issue <PREFIX>-<N> (spawn implementation agent)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## REFERENCE: Working Project Configs
|
|
276
|
+
|
|
277
|
+
### panopticon-cli (monorepo, GitHub)
|
|
278
|
+
- `linear_team: PAN`, `github_repo: eltmon/panopticon-cli`
|
|
279
|
+
- `workspace.type: monorepo`
|
|
280
|
+
- Has: dns, docker, agent, services, env, tests
|
|
281
|
+
|
|
282
|
+
### mind-your-now (polyrepo, Linear/GitLab)
|
|
283
|
+
- `linear_team: MIN`, `gitlab_repo: eltmon/mind-your-now`
|
|
284
|
+
- `workspace.type: polyrepo` with 6 sub-repos
|
|
285
|
+
- Has: dns, docker, database, agent, services, tunnel, hume, env, tests
|
|
286
|
+
|
|
287
|
+
### myn-cli (standalone, GitHub)
|
|
288
|
+
- `linear_team: CLI`, `github_repo: mindyournow/myn-cli`
|
|
289
|
+
- `workspace.type: standalone`
|
|
290
|
+
- Has: tests
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## COMMON MISTAKES
|
|
295
|
+
|
|
296
|
+
1. **Missing `linear_team`** — The #1 cause of "planning agent starts in $HOME."
|
|
297
|
+
Despite the name, this field is the issue PREFIX for ALL trackers, not just Linear.
|
|
298
|
+
2. **Not in `GITHUB_REPOS`** — Issues don't appear on dashboard kanban board.
|
|
299
|
+
3. **No `beads.role`** — Every `bd` command prints warning noise in agent output.
|
|
300
|
+
4. **Not pre-trusting the directory** — Agent gets stuck on trust dialog.
|
|
301
|
+
5. **Wrong `workspace.type`** — `standalone` = single repo, `monorepo` = one repo with
|
|
302
|
+
worktrees, `polyrepo` = multiple repos under one parent dir.
|
|
303
|
+
6. **Missing `workspaces/` directory** — Git worktree creation fails.
|
|
304
|
+
7. **Missing `.gitignore` entry** — `workspaces/` gets committed accidentally.
|