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.
Files changed (117) hide show
  1. package/dist/{agents-E43Y3HNU.js → agents-DMPT32H7.js} +7 -5
  2. package/dist/{chunk-AAFQANKW.js → chunk-2V2DQ3IX.js} +387 -110
  3. package/dist/chunk-2V2DQ3IX.js.map +1 -0
  4. package/dist/{chunk-GR6ZZMCX.js → chunk-4HST45MO.js} +15 -12
  5. package/dist/chunk-4HST45MO.js.map +1 -0
  6. package/dist/{chunk-YLPSQAM2.js → chunk-565HZ6VV.js} +2 -2
  7. package/dist/chunk-6N2KBSJA.js +452 -0
  8. package/dist/chunk-6N2KBSJA.js.map +1 -0
  9. package/dist/{chunk-HZT2AOPN.js → chunk-D67AQTHF.js} +44 -19
  10. package/dist/chunk-D67AQTHF.js.map +1 -0
  11. package/dist/{chunk-WQG2TYCB.js → chunk-DFNVHK3N.js} +2 -2
  12. package/dist/{chunk-7SN4L4PH.js → chunk-HOGYHJ2G.js} +2 -2
  13. package/dist/{chunk-FTCPTHIJ.js → chunk-HRU7S4TA.js} +24 -7
  14. package/dist/chunk-HRU7S4TA.js.map +1 -0
  15. package/dist/{chunk-PPRFKTVC.js → chunk-KBHRXV5T.js} +3 -3
  16. package/dist/chunk-KBHRXV5T.js.map +1 -0
  17. package/dist/{chunk-CWELWPWQ.js → chunk-MOPGR3CL.js} +1 -1
  18. package/dist/chunk-MOPGR3CL.js.map +1 -0
  19. package/dist/{chunk-OMNXYPXC.js → chunk-RLZQB7HS.js} +15 -2
  20. package/dist/chunk-RLZQB7HS.js.map +1 -0
  21. package/dist/{chunk-NTO3EDB3.js → chunk-T7BBPDEJ.js} +56 -13
  22. package/dist/chunk-T7BBPDEJ.js.map +1 -0
  23. package/dist/chunk-USYP2SBE.js +317 -0
  24. package/dist/chunk-USYP2SBE.js.map +1 -0
  25. package/dist/{chunk-JM6V62LT.js → chunk-ZDNQFWR5.js} +2 -2
  26. package/dist/{chunk-JM6V62LT.js.map → chunk-ZDNQFWR5.js.map} +1 -1
  27. package/dist/{chunk-HJSM6E6U.js → chunk-ZP6EWSZV.js} +29 -322
  28. package/dist/chunk-ZP6EWSZV.js.map +1 -0
  29. package/dist/{chunk-PELXV435.js → chunk-ZTYHZMEC.js} +2 -2
  30. package/dist/chunk-ZTYHZMEC.js.map +1 -0
  31. package/dist/cli/index.js +1612 -925
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/config-yaml-OVZLKFMA.js +18 -0
  34. package/dist/dashboard/prompts/merge-agent.md +7 -5
  35. package/dist/dashboard/prompts/review-agent.md +12 -1
  36. package/dist/dashboard/prompts/test-agent.md +3 -1
  37. package/dist/dashboard/public/assets/index-BJKEp64j.css +32 -0
  38. package/dist/dashboard/public/assets/index-CgJjqjAV.js +767 -0
  39. package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  40. package/dist/dashboard/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  41. package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  42. package/dist/dashboard/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  43. package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  44. package/dist/dashboard/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  45. package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  46. package/dist/dashboard/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  47. package/dist/dashboard/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  48. package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-BnQMeOim.woff +0 -0
  49. package/dist/dashboard/public/assets/space-grotesk-latin-400-normal-CJ-V5oYT.woff2 +0 -0
  50. package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-BflQw4A9.woff +0 -0
  51. package/dist/dashboard/public/assets/space-grotesk-latin-600-normal-DjKNqYRj.woff2 +0 -0
  52. package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-CwsQ-cCU.woff +0 -0
  53. package/dist/dashboard/public/assets/space-grotesk-latin-700-normal-RjhwGPKo.woff2 +0 -0
  54. package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-CfP_5XZW.woff2 +0 -0
  55. package/dist/dashboard/public/assets/space-grotesk-latin-ext-400-normal-DRPE3kg4.woff +0 -0
  56. package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-DxxdqCpr.woff2 +0 -0
  57. package/dist/dashboard/public/assets/space-grotesk-latin-ext-600-normal-VcznFIpX.woff +0 -0
  58. package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-BQnZhY3m.woff2 +0 -0
  59. package/dist/dashboard/public/assets/space-grotesk-latin-ext-700-normal-HVCqSBdx.woff +0 -0
  60. package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-B7xT_GF5.woff2 +0 -0
  61. package/dist/dashboard/public/assets/space-grotesk-vietnamese-400-normal-BIWiOVfw.woff +0 -0
  62. package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-D6zpsUhD.woff +0 -0
  63. package/dist/dashboard/public/assets/space-grotesk-vietnamese-600-normal-DUi7WF5p.woff2 +0 -0
  64. package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-DMty7AZE.woff2 +0 -0
  65. package/dist/dashboard/public/assets/space-grotesk-vietnamese-700-normal-Duxec5Rn.woff +0 -0
  66. package/dist/dashboard/public/index.html +5 -3
  67. package/dist/dashboard/server.js +5196 -2859
  68. package/dist/{feedback-writer-LVZ5TFYZ.js → feedback-writer-T43PI5S2.js} +2 -2
  69. package/dist/{hume-WMAUBBV2.js → hume-CKJJ3OUU.js} +3 -3
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +4 -3
  72. package/dist/index.js.map +1 -1
  73. package/dist/{projects-JEIVIYC6.js → projects-KVM3MN3Y.js} +4 -2
  74. package/dist/{remote-agents-TFSMW7GN.js → remote-agents-ULPD6C5U.js} +3 -3
  75. package/dist/{remote-workspace-AHVHQEES.js → remote-workspace-XX6ARE6I.js} +3 -3
  76. package/dist/{review-status-EPFG4XM7.js → review-status-XKUKZF6J.js} +3 -2
  77. package/dist/{specialist-context-ZC6A4M3I.js → specialist-context-53AWO6AE.js} +6 -5
  78. package/dist/{specialist-context-ZC6A4M3I.js.map → specialist-context-53AWO6AE.js.map} +1 -1
  79. package/dist/{specialist-logs-KLGJCEUL.js → specialist-logs-QREUJ4HN.js} +6 -5
  80. package/dist/{specialists-O4HWDJL5.js → specialists-2DBBXRCK.js} +6 -5
  81. package/dist/{traefik-QN7R5I6V.js → traefik-5GL3Q7DJ.js} +3 -3
  82. package/dist/{tunnel-W2GZBLEV.js → tunnel-BKC7KLBX.js} +3 -3
  83. package/dist/{workspace-manager-IE4JL2JP.js → workspace-manager-ALBR62AS.js} +5 -5
  84. package/dist/workspace-manager-ALBR62AS.js.map +1 -0
  85. package/package.json +1 -1
  86. package/scripts/record-cost-event.js +8429 -47
  87. package/scripts/work-agent-stop-hook +221 -24
  88. package/skills/pan-new-project/SKILL.md +304 -0
  89. package/dist/chunk-AAFQANKW.js.map +0 -1
  90. package/dist/chunk-CWELWPWQ.js.map +0 -1
  91. package/dist/chunk-FTCPTHIJ.js.map +0 -1
  92. package/dist/chunk-GFP3PIPB.js +0 -96
  93. package/dist/chunk-GFP3PIPB.js.map +0 -1
  94. package/dist/chunk-GR6ZZMCX.js.map +0 -1
  95. package/dist/chunk-HJSM6E6U.js.map +0 -1
  96. package/dist/chunk-HZT2AOPN.js.map +0 -1
  97. package/dist/chunk-NTO3EDB3.js.map +0 -1
  98. package/dist/chunk-OMNXYPXC.js.map +0 -1
  99. package/dist/chunk-PELXV435.js.map +0 -1
  100. package/dist/chunk-PPRFKTVC.js.map +0 -1
  101. package/dist/dashboard/public/assets/index-BxpjweAL.css +0 -32
  102. package/dist/dashboard/public/assets/index-DQHkwvvJ.js +0 -743
  103. /package/dist/{agents-E43Y3HNU.js.map → agents-DMPT32H7.js.map} +0 -0
  104. /package/dist/{chunk-YLPSQAM2.js.map → chunk-565HZ6VV.js.map} +0 -0
  105. /package/dist/{chunk-WQG2TYCB.js.map → chunk-DFNVHK3N.js.map} +0 -0
  106. /package/dist/{chunk-7SN4L4PH.js.map → chunk-HOGYHJ2G.js.map} +0 -0
  107. /package/dist/{hume-WMAUBBV2.js.map → config-yaml-OVZLKFMA.js.map} +0 -0
  108. /package/dist/{feedback-writer-LVZ5TFYZ.js.map → feedback-writer-T43PI5S2.js.map} +0 -0
  109. /package/dist/{projects-JEIVIYC6.js.map → hume-CKJJ3OUU.js.map} +0 -0
  110. /package/dist/{remote-agents-TFSMW7GN.js.map → projects-KVM3MN3Y.js.map} +0 -0
  111. /package/dist/{review-status-EPFG4XM7.js.map → remote-agents-ULPD6C5U.js.map} +0 -0
  112. /package/dist/{remote-workspace-AHVHQEES.js.map → remote-workspace-XX6ARE6I.js.map} +0 -0
  113. /package/dist/{specialist-logs-KLGJCEUL.js.map → review-status-XKUKZF6J.js.map} +0 -0
  114. /package/dist/{specialists-O4HWDJL5.js.map → specialist-logs-QREUJ4HN.js.map} +0 -0
  115. /package/dist/{traefik-QN7R5I6V.js.map → specialists-2DBBXRCK.js.map} +0 -0
  116. /package/dist/{tunnel-W2GZBLEV.js.map → traefik-5GL3Q7DJ.js.map} +0 -0
  117. /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 a lightweight AI model to analyze the last N lines of terminal output
6
- # and determine if the agent completed its work but failed to signal completion.
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="$HOME/.panopticon/agents/$AGENT_ID/completed"
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="$HOME/.panopticon/agents/$AGENT_ID/.last-completion-nudge"
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
- # Capture the last 80 lines of terminal output
58
- OUTPUT=$(tmux capture-pane -t "$AGENT_ID" -p -S -80 2>/dev/null || echo "")
59
- if [ -z "$OUTPUT" ]; then
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
- # Quick heuristic pre-check: skip the expensive AI call if the output clearly
64
- # shows the agent is mid-work (e.g., running tests, editing files, reading)
65
- if echo "$OUTPUT" | grep -qE '(Reading|Editing|Searching|Running|Compiling|Building|Installing|●.*Bash|●.*Read|●.*Edit|●.*Grep|●.*Write|●.*Glob)' | tail -5 | grep -qE '●'; then
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
- # Determine model to use for completion check
75
- # Priority: env var > config.yaml override > default
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
- # Build the analysis prompt
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 "$(dirname "$NUDGE_FILE")"
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 completion nudge to $AGENT_ID" \
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.