panopticon-cli 0.4.32 → 0.5.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.
Files changed (142) hide show
  1. package/README.md +96 -210
  2. package/dist/{agents-BDFHF4T3.js → agents-E43Y3HNU.js} +10 -7
  3. package/dist/chunk-7SN4L4PH.js +150 -0
  4. package/dist/chunk-7SN4L4PH.js.map +1 -0
  5. package/dist/{chunk-2NIAOCIC.js → chunk-AAFQANKW.js} +358 -97
  6. package/dist/chunk-AAFQANKW.js.map +1 -0
  7. package/dist/chunk-AQXETQHW.js +113 -0
  8. package/dist/chunk-AQXETQHW.js.map +1 -0
  9. package/dist/chunk-B3PF6JPQ.js +212 -0
  10. package/dist/chunk-B3PF6JPQ.js.map +1 -0
  11. package/dist/chunk-CFCUOV3Q.js +669 -0
  12. package/dist/chunk-CFCUOV3Q.js.map +1 -0
  13. package/dist/chunk-CWELWPWQ.js +32 -0
  14. package/dist/chunk-CWELWPWQ.js.map +1 -0
  15. package/dist/chunk-DI7ABPNQ.js +352 -0
  16. package/dist/chunk-DI7ABPNQ.js.map +1 -0
  17. package/dist/{chunk-VU4FLXV5.js → chunk-FQ66DECN.js} +31 -4
  18. package/dist/chunk-FQ66DECN.js.map +1 -0
  19. package/dist/{chunk-VIWUCJ4V.js → chunk-FTCPTHIJ.js} +57 -432
  20. package/dist/chunk-FTCPTHIJ.js.map +1 -0
  21. package/dist/{review-status-GWQYY77L.js → chunk-GFP3PIPB.js} +14 -7
  22. package/dist/chunk-GFP3PIPB.js.map +1 -0
  23. package/dist/chunk-GR6ZZMCX.js +816 -0
  24. package/dist/chunk-GR6ZZMCX.js.map +1 -0
  25. package/dist/chunk-HJSM6E6U.js +1038 -0
  26. package/dist/chunk-HJSM6E6U.js.map +1 -0
  27. package/dist/{chunk-XP2DXWYP.js → chunk-HZT2AOPN.js} +164 -39
  28. package/dist/chunk-HZT2AOPN.js.map +1 -0
  29. package/dist/chunk-JQBV3Q2W.js +29 -0
  30. package/dist/chunk-JQBV3Q2W.js.map +1 -0
  31. package/dist/{chunk-BWGFN44T.js → chunk-JT4O4YVM.js} +28 -16
  32. package/dist/chunk-JT4O4YVM.js.map +1 -0
  33. package/dist/chunk-NTO3EDB3.js +600 -0
  34. package/dist/chunk-NTO3EDB3.js.map +1 -0
  35. package/dist/{chunk-JY7R7V4G.js → chunk-OMNXYPXC.js} +2 -2
  36. package/dist/chunk-OMNXYPXC.js.map +1 -0
  37. package/dist/chunk-PELXV435.js +215 -0
  38. package/dist/chunk-PELXV435.js.map +1 -0
  39. package/dist/chunk-PPRFKTVC.js +154 -0
  40. package/dist/chunk-PPRFKTVC.js.map +1 -0
  41. package/dist/chunk-WQG2TYCB.js +677 -0
  42. package/dist/chunk-WQG2TYCB.js.map +1 -0
  43. package/dist/{chunk-HCTJFIJJ.js → chunk-YLPSQAM2.js} +2 -2
  44. package/dist/{chunk-HCTJFIJJ.js.map → chunk-YLPSQAM2.js.map} +1 -1
  45. package/dist/{chunk-6HXKTOD7.js → chunk-ZTFNYOC7.js} +53 -38
  46. package/dist/chunk-ZTFNYOC7.js.map +1 -0
  47. package/dist/cli/index.js +5103 -3165
  48. package/dist/cli/index.js.map +1 -1
  49. package/dist/{config-BOAMSKTF.js → config-4CJNUE3O.js} +7 -3
  50. package/dist/dashboard/prompts/merge-agent.md +217 -0
  51. package/dist/dashboard/prompts/review-agent.md +409 -0
  52. package/dist/dashboard/prompts/sync-main.md +84 -0
  53. package/dist/dashboard/prompts/test-agent.md +283 -0
  54. package/dist/dashboard/prompts/work-agent.md +249 -0
  55. package/dist/dashboard/public/assets/index-BxpjweAL.css +32 -0
  56. package/dist/dashboard/public/assets/index-DQHkwvvJ.js +743 -0
  57. package/dist/dashboard/public/index.html +2 -2
  58. package/dist/dashboard/server.js +17619 -4044
  59. package/dist/{dns-L3L2BB27.js → dns-7BDJSD3E.js} +4 -2
  60. package/dist/{feedback-writer-AAKF5BTK.js → feedback-writer-LVZ5TFYZ.js} +8 -4
  61. package/dist/feedback-writer-LVZ5TFYZ.js.map +1 -0
  62. package/dist/hume-WMAUBBV2.js +13 -0
  63. package/dist/index.d.ts +162 -40
  64. package/dist/index.js +67 -23
  65. package/dist/index.js.map +1 -1
  66. package/dist/{projects-VXRUCMLM.js → projects-JEIVIYC6.js} +3 -3
  67. package/dist/rally-RKFSWC7E.js +10 -0
  68. package/dist/{remote-agents-Z3R2A5BN.js → remote-agents-TFSMW7GN.js} +2 -2
  69. package/dist/{remote-workspace-2G6V2KNP.js → remote-workspace-AHVHQEES.js} +8 -8
  70. package/dist/review-status-EPFG4XM7.js +19 -0
  71. package/dist/shadow-state-5MDP6YXH.js +30 -0
  72. package/dist/shadow-state-5MDP6YXH.js.map +1 -0
  73. package/dist/{specialist-context-N32QBNNQ.js → specialist-context-ZC6A4M3I.js} +8 -7
  74. package/dist/{specialist-context-N32QBNNQ.js.map → specialist-context-ZC6A4M3I.js.map} +1 -1
  75. package/dist/{specialist-logs-GF3YV4KL.js → specialist-logs-KLGJCEUL.js} +7 -6
  76. package/dist/specialist-logs-KLGJCEUL.js.map +1 -0
  77. package/dist/{specialists-JBIW6MP4.js → specialists-O4HWDJL5.js} +7 -6
  78. package/dist/specialists-O4HWDJL5.js.map +1 -0
  79. package/dist/tldr-daemon-T3THOUGT.js +21 -0
  80. package/dist/tldr-daemon-T3THOUGT.js.map +1 -0
  81. package/dist/traefik-QN7R5I6V.js +19 -0
  82. package/dist/traefik-QN7R5I6V.js.map +1 -0
  83. package/dist/tunnel-W2GZBLEV.js +13 -0
  84. package/dist/tunnel-W2GZBLEV.js.map +1 -0
  85. package/dist/workspace-manager-IE4JL2JP.js +22 -0
  86. package/dist/workspace-manager-IE4JL2JP.js.map +1 -0
  87. package/package.json +2 -2
  88. package/scripts/heartbeat-hook +37 -10
  89. package/scripts/patches/llm-tldr-tsx-support.py +109 -0
  90. package/scripts/pre-tool-hook +26 -15
  91. package/scripts/record-cost-event.js +177 -43
  92. package/scripts/record-cost-event.ts +87 -3
  93. package/scripts/statusline.sh +169 -0
  94. package/scripts/stop-hook +21 -11
  95. package/scripts/tldr-post-edit +72 -0
  96. package/scripts/tldr-read-enforcer +275 -0
  97. package/scripts/work-agent-stop-hook +137 -0
  98. package/skills/check-merged/SKILL.md +143 -0
  99. package/skills/crash-investigation/SKILL.md +301 -0
  100. package/skills/github-cli/SKILL.md +185 -0
  101. package/skills/myn-standards/SKILL.md +351 -0
  102. package/skills/pan-reopen/SKILL.md +65 -0
  103. package/skills/pan-sync-main/SKILL.md +87 -0
  104. package/skills/pan-tldr/SKILL.md +149 -0
  105. package/skills/react-best-practices/SKILL.md +125 -0
  106. package/skills/spec-readiness/REPORT-TEMPLATE.md +158 -0
  107. package/skills/spec-readiness/SCORING-REFERENCE.md +369 -0
  108. package/skills/spec-readiness/SKILL.md +400 -0
  109. package/skills/spec-readiness-setup/SKILL.md +361 -0
  110. package/skills/workspace-status/SKILL.md +56 -0
  111. package/skills/write-spec/SKILL.md +138 -0
  112. package/templates/traefik/dynamic/panopticon.yml.template +0 -5
  113. package/templates/traefik/traefik.yml +0 -8
  114. package/dist/chunk-2NIAOCIC.js.map +0 -1
  115. package/dist/chunk-3XAB4IXF.js +0 -51
  116. package/dist/chunk-3XAB4IXF.js.map +0 -1
  117. package/dist/chunk-6HXKTOD7.js.map +0 -1
  118. package/dist/chunk-BBCUK6N2.js +0 -241
  119. package/dist/chunk-BBCUK6N2.js.map +0 -1
  120. package/dist/chunk-BWGFN44T.js.map +0 -1
  121. package/dist/chunk-ELK6Q7QI.js +0 -545
  122. package/dist/chunk-ELK6Q7QI.js.map +0 -1
  123. package/dist/chunk-JY7R7V4G.js.map +0 -1
  124. package/dist/chunk-LYSBSZYV.js +0 -1523
  125. package/dist/chunk-LYSBSZYV.js.map +0 -1
  126. package/dist/chunk-VIWUCJ4V.js.map +0 -1
  127. package/dist/chunk-VU4FLXV5.js.map +0 -1
  128. package/dist/chunk-XP2DXWYP.js.map +0 -1
  129. package/dist/dashboard/public/assets/index-C7X6LP5Z.css +0 -32
  130. package/dist/dashboard/public/assets/index-ClYqpcAJ.js +0 -645
  131. package/dist/feedback-writer-AAKF5BTK.js.map +0 -1
  132. package/dist/review-status-GWQYY77L.js.map +0 -1
  133. package/dist/traefik-CUJM6K5Z.js +0 -12
  134. /package/dist/{agents-BDFHF4T3.js.map → agents-E43Y3HNU.js.map} +0 -0
  135. /package/dist/{config-BOAMSKTF.js.map → config-4CJNUE3O.js.map} +0 -0
  136. /package/dist/{dns-L3L2BB27.js.map → dns-7BDJSD3E.js.map} +0 -0
  137. /package/dist/{projects-VXRUCMLM.js.map → hume-WMAUBBV2.js.map} +0 -0
  138. /package/dist/{remote-agents-Z3R2A5BN.js.map → projects-JEIVIYC6.js.map} +0 -0
  139. /package/dist/{specialist-logs-GF3YV4KL.js.map → rally-RKFSWC7E.js.map} +0 -0
  140. /package/dist/{specialists-JBIW6MP4.js.map → remote-agents-TFSMW7GN.js.map} +0 -0
  141. /package/dist/{remote-workspace-2G6V2KNP.js.map → remote-workspace-AHVHQEES.js.map} +0 -0
  142. /package/dist/{traefik-CUJM6K5Z.js.map → review-status-EPFG4XM7.js.map} +0 -0
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+ # ~/.panopticon/bin/tldr-post-edit
3
+ # PostToolUse hook on Edit/Write — notifies TLDR to re-warm after code changes.
4
+ #
5
+ # Tracks dirty files in a lightweight file. When the dirty count exceeds a
6
+ # threshold, triggers a background re-warm so the index stays fresh.
7
+
8
+ # Don't use set -e — never break Claude Code execution
9
+ INPUT=$(cat 2>/dev/null || echo '{}')
10
+
11
+ # Only act on Edit and Write tools
12
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
13
+ if [ "$TOOL_NAME" != "Edit" ] && [ "$TOOL_NAME" != "Write" ]; then
14
+ exit 0
15
+ fi
16
+
17
+ # Extract file path
18
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
19
+ if [ -z "$FILE_PATH" ]; then
20
+ exit 0
21
+ fi
22
+
23
+ # Only track code files
24
+ EXT="${FILE_PATH##*.}"
25
+ EXT_LOWER=$(echo "$EXT" | tr '[:upper:]' '[:lower:]')
26
+ case "$EXT_LOWER" in
27
+ ts|tsx|js|jsx|py|java|go|rs|cpp|c|h|hpp|rb|php|kt|swift|cs|scala|lua|ex|exs)
28
+ ;;
29
+ *)
30
+ exit 0
31
+ ;;
32
+ esac
33
+
34
+ # Find project root (where .venv lives)
35
+ PROJECT_DIR=""
36
+ DIR="$(dirname "$FILE_PATH")"
37
+ while [ "$DIR" != "/" ]; do
38
+ if [ -d "$DIR/.venv" ]; then
39
+ PROJECT_DIR="$DIR"
40
+ break
41
+ fi
42
+ DIR=$(dirname "$DIR")
43
+ done
44
+
45
+ if [ -z "$PROJECT_DIR" ]; then
46
+ exit 0
47
+ fi
48
+
49
+ # Track dirty files
50
+ DIRTY_FILE="$PROJECT_DIR/.tldr/dirty-files"
51
+ mkdir -p "$PROJECT_DIR/.tldr" 2>/dev/null || true
52
+
53
+ # Append the changed file (deduplicated on warm)
54
+ REL_PATH="${FILE_PATH#$PROJECT_DIR/}"
55
+ echo "$REL_PATH" >> "$DIRTY_FILE" 2>/dev/null || true
56
+
57
+ # Count dirty files (fast line count)
58
+ DIRTY_COUNT=$(wc -l < "$DIRTY_FILE" 2>/dev/null || echo 0)
59
+
60
+ # Trigger background re-warm after 10 edits (keeps index reasonably fresh)
61
+ WARM_THRESHOLD=10
62
+ if [ "$DIRTY_COUNT" -ge "$WARM_THRESHOLD" ]; then
63
+ TLDR_BIN="$PROJECT_DIR/.venv/bin/tldr"
64
+ if [ -x "$TLDR_BIN" ]; then
65
+ # Clear dirty tracking before warm
66
+ : > "$DIRTY_FILE" 2>/dev/null || true
67
+ # Background warm — non-blocking
68
+ (cd "$PROJECT_DIR" && "$TLDR_BIN" warm . --background) >/dev/null 2>&1 &
69
+ fi
70
+ fi
71
+
72
+ exit 0
@@ -0,0 +1,275 @@
1
+ #!/bin/bash
2
+ # ~/.panopticon/bin/tldr-read-enforcer
3
+ # PreToolUse hook on Read — intercepts file reads and returns TLDR summaries
4
+ # for large code files, saving 90-95% of context tokens.
5
+ #
6
+ # Bypasses (allows normal read):
7
+ # - Files < 3KB (small enough to read directly)
8
+ # - Reads with offset/limit (targeted reads for editing)
9
+ # - Non-code files (configs, docs, json, etc.)
10
+ # - No .venv available (TLDR not installed)
11
+ # - TLDR command fails (graceful degradation)
12
+ # - Summary too sparse to be useful (< 100 tokens for file > 5KB)
13
+ # - Recently edited files (in .tldr/dirty-files — agent needs to verify changes)
14
+
15
+ # Don't use set -e — never break Claude Code execution
16
+ INPUT=$(cat 2>/dev/null || echo '{}')
17
+
18
+ # Only act on Read tool
19
+ TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""' 2>/dev/null)
20
+ if [ "$TOOL_NAME" != "Read" ]; then
21
+ exit 0
22
+ fi
23
+
24
+ # Extract Read parameters
25
+ FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""' 2>/dev/null)
26
+ OFFSET=$(echo "$INPUT" | jq -r '.tool_input.offset // empty' 2>/dev/null)
27
+ LIMIT=$(echo "$INPUT" | jq -r '.tool_input.limit // empty' 2>/dev/null)
28
+
29
+ # Bypass: targeted reads (offset or limit specified — agent is reading for editing)
30
+ if [ -n "$OFFSET" ] || [ -n "$LIMIT" ]; then
31
+ exit 0
32
+ fi
33
+
34
+ # Bypass: file doesn't exist
35
+ if [ ! -f "$FILE_PATH" ]; then
36
+ exit 0
37
+ fi
38
+
39
+ # Bypass: small files (< 3KB)
40
+ FILE_SIZE=$(stat -c%s "$FILE_PATH" 2>/dev/null || echo 0)
41
+ if [ "$FILE_SIZE" -lt 3072 ]; then
42
+ exit 0
43
+ fi
44
+
45
+ # Bypass: non-code files
46
+ EXT="${FILE_PATH##*.}"
47
+ EXT_LOWER=$(echo "$EXT" | tr '[:upper:]' '[:lower:]')
48
+ case "$EXT_LOWER" in
49
+ ts|tsx|js|jsx|py|java|go|rs|cpp|c|h|hpp|rb|php|kt|swift|cs|scala|lua|ex|exs)
50
+ # Code file — continue to TLDR
51
+ ;;
52
+ *)
53
+ # Non-code — allow normal read
54
+ exit 0
55
+ ;;
56
+ esac
57
+
58
+ # Find the .venv/bin/tldr binary
59
+ # Check workspace first, then project root
60
+ TLDR_BIN=""
61
+ DIR="$(dirname "$FILE_PATH")"
62
+ while [ "$DIR" != "/" ]; do
63
+ if [ -x "$DIR/.venv/bin/tldr" ]; then
64
+ TLDR_BIN="$DIR/.venv/bin/tldr"
65
+ PROJECT_DIR="$DIR"
66
+ break
67
+ fi
68
+ DIR=$(dirname "$DIR")
69
+ done
70
+
71
+ # Bypass: no TLDR binary found
72
+ if [ -z "$TLDR_BIN" ]; then
73
+ exit 0
74
+ fi
75
+
76
+ # Get relative path from project root
77
+ REL_PATH="${FILE_PATH#$PROJECT_DIR/}"
78
+
79
+ # Bypass: recently edited files (agent needs to verify its own changes)
80
+ # The post-edit hook tracks edits in .tldr/dirty-files
81
+ DIRTY_FILE="$PROJECT_DIR/.tldr/dirty-files"
82
+ if [ -f "$DIRTY_FILE" ] && grep -qxF "$REL_PATH" "$DIRTY_FILE" 2>/dev/null; then
83
+ exit 0
84
+ fi
85
+
86
+ # Detect language from extension for tldr --lang flag
87
+ case "$EXT_LOWER" in
88
+ ts|tsx) TLDR_LANG="typescript" ;;
89
+ js|jsx) TLDR_LANG="javascript" ;;
90
+ py) TLDR_LANG="python" ;;
91
+ go) TLDR_LANG="go" ;;
92
+ rs) TLDR_LANG="rust" ;;
93
+ java|kt) TLDR_LANG="java" ;;
94
+ rb) TLDR_LANG="ruby" ;;
95
+ *) TLDR_LANG="python" ;;
96
+ esac
97
+
98
+ # Strip file extension — tldr context expects module paths without extension
99
+ # e.g., "src/lib/agents" not "src/lib/agents.ts"
100
+ MODULE_PATH="${REL_PATH%.*}"
101
+
102
+ # Try to get TLDR context for this file (module path mode)
103
+ TLDR_OUTPUT=$("$TLDR_BIN" context "$MODULE_PATH" --lang "$TLDR_LANG" 2>/dev/null)
104
+ TLDR_EXIT=$?
105
+
106
+ # Quality gate: check if context output is too sparse to be useful.
107
+ # Pattern: "~XX tokens" where XX < 100 means the summary captured almost nothing.
108
+ # This happens with test files (describe/it blocks) and type-only files.
109
+ CONTEXT_SPARSE=false
110
+ if [ $TLDR_EXIT -eq 0 ] && [ -n "$TLDR_OUTPUT" ]; then
111
+ CTX_TOKENS=$(echo "$TLDR_OUTPUT" | grep -oP '\~\K\d+(?= tokens)' || echo "0")
112
+ if [ "$CTX_TOKENS" -lt 100 ] && [ "$FILE_SIZE" -gt 5120 ]; then
113
+ CONTEXT_SPARSE=true
114
+ fi
115
+ fi
116
+
117
+ # Fallback: if context failed, was sparse, or empty — try extract command
118
+ # extract works on actual file paths (including .tsx) and returns structured JSON
119
+ if [ $TLDR_EXIT -ne 0 ] || [ -z "$TLDR_OUTPUT" ] || [ "$CONTEXT_SPARSE" = true ]; then
120
+ EXTRACT_JSON=$("$TLDR_BIN" extract "$REL_PATH" 2>/dev/null)
121
+ EXTRACT_EXIT=$?
122
+ if [ $EXTRACT_EXIT -eq 0 ] && [ -n "$EXTRACT_JSON" ]; then
123
+ # Check if extract found any real content
124
+ EXTRACT_COUNTS=$(echo "$EXTRACT_JSON" | python3 -c "
125
+ import json, sys
126
+ try:
127
+ d = json.load(sys.stdin)
128
+ nf = len(d.get('functions', []))
129
+ nc = len(d.get('classes', []))
130
+ print(f'{nf} {nc}')
131
+ except:
132
+ print('0 0')
133
+ " 2>/dev/null)
134
+ EXTRACT_FUNCS=$(echo "$EXTRACT_COUNTS" | cut -d' ' -f1)
135
+ EXTRACT_CLASSES=$(echo "$EXTRACT_COUNTS" | cut -d' ' -f2)
136
+
137
+ # If extract also found nothing useful, bypass entirely
138
+ if [ "${EXTRACT_FUNCS:-0}" -eq 0 ] && [ "${EXTRACT_CLASSES:-0}" -eq 0 ]; then
139
+ # Neither context nor extract found useful content — let the agent read the file
140
+ exit 0
141
+ fi
142
+
143
+ # Convert extract JSON to a readable summary with language-appropriate syntax
144
+ TLDR_OUTPUT=$(echo "$EXTRACT_JSON" | python3 -c "
145
+ import json, sys
146
+ try:
147
+ data = json.load(sys.stdin)
148
+ lang = '$TLDR_LANG'
149
+ lines = []
150
+ fname = data.get('file_path', '$REL_PATH')
151
+ short = fname.split('/')[-1]
152
+ lines.append(f'## Code Context: {fname}')
153
+ lines.append('')
154
+
155
+ # Language-appropriate function keyword
156
+ fn_kw = {'typescript': 'function', 'javascript': 'function', 'python': 'def',
157
+ 'go': 'func', 'rust': 'fn', 'java': '', 'ruby': 'def'}.get(lang, 'function')
158
+
159
+ for func in data.get('functions', []):
160
+ name = func.get('name', '?')
161
+ params = ', '.join(
162
+ p.get('name','') + (': ' + p.get('type','') if p.get('type') else '')
163
+ for p in func.get('parameters', [])
164
+ )
165
+ ret = func.get('return_type', '')
166
+ doc = (func.get('docstring') or '')[:80]
167
+ line = func.get('start_line') or '?'
168
+ sig = f'{fn_kw} {name}({params})'.strip()
169
+ if ret:
170
+ if lang in ('typescript', 'javascript', 'go', 'rust'):
171
+ sig += f': {ret}'
172
+ else:
173
+ sig += f' -> {ret}'
174
+ lines.append(f'{name} ({short}:{line})')
175
+ lines.append(f' {sig}')
176
+ if doc:
177
+ lines.append(f' // {doc}')
178
+ lines.append('')
179
+
180
+ for cls in data.get('classes', []):
181
+ name = cls.get('name', '?')
182
+ line = cls.get('start_line') or '?'
183
+ lines.append(f'class {name} ({short}:{line})')
184
+ for m in cls.get('methods', []):
185
+ mname = m.get('name', '?')
186
+ lines.append(f' .{mname}()')
187
+ lines.append('')
188
+
189
+ nfunc = len(data.get('functions', []))
190
+ ncls = len(data.get('classes', []))
191
+ lines.append(f'---')
192
+ lines.append(f'{nfunc} functions, {ncls} classes (via extract)')
193
+ print('\n'.join(lines))
194
+ except:
195
+ pass
196
+ " 2>/dev/null)
197
+ fi
198
+ fi
199
+
200
+ # Bypass: both context and extract failed or produced nothing useful
201
+ if [ -z "$TLDR_OUTPUT" ]; then
202
+ exit 0
203
+ fi
204
+
205
+ # Also get the file's imports and format them
206
+ TLDR_IMPORTS_RAW=$("$TLDR_BIN" imports "$REL_PATH" --lang "$TLDR_LANG" 2>/dev/null || true)
207
+ TLDR_IMPORTS=""
208
+ if [ -n "$TLDR_IMPORTS_RAW" ]; then
209
+ TLDR_IMPORTS=$(echo "$TLDR_IMPORTS_RAW" | python3 -c "
210
+ import json, sys
211
+ try:
212
+ data = json.load(sys.stdin)
213
+ lines = []
214
+ for imp in data:
215
+ mod = imp.get('module', '?')
216
+ names = imp.get('names', [])
217
+ default = imp.get('default')
218
+ parts = []
219
+ if default:
220
+ parts.append(default)
221
+ if names:
222
+ parts.append('{ ' + ', '.join(names) + ' }')
223
+ if parts:
224
+ lines.append(f'import {', '.join(parts)} from \"{mod}\"')
225
+ else:
226
+ lines.append(f'import \"{mod}\"')
227
+ print('\n'.join(lines))
228
+ except:
229
+ # If JSON parsing fails, use raw output (may already be formatted)
230
+ sys.stdout.write(sys.stdin.read() if hasattr(sys, '_raw') else '')
231
+ " 2>/dev/null)
232
+ # If Python formatting failed, fall back to raw
233
+ if [ -z "$TLDR_IMPORTS" ]; then
234
+ TLDR_IMPORTS="$TLDR_IMPORTS_RAW"
235
+ fi
236
+ fi
237
+
238
+ # Build the summary that Claude will see instead of the raw file
239
+ SUMMARY="[TLDR Summary — ${FILE_SIZE} bytes saved from context]
240
+
241
+ File: ${FILE_PATH}
242
+
243
+ ## Structure & Exports
244
+ ${TLDR_OUTPUT}"
245
+
246
+ if [ -n "$TLDR_IMPORTS" ]; then
247
+ SUMMARY="${SUMMARY}
248
+
249
+ ## Imports
250
+ ${TLDR_IMPORTS}"
251
+ fi
252
+
253
+ SUMMARY="${SUMMARY}
254
+
255
+ ---
256
+ To read the full file, use Read with offset/limit parameters for the specific section you need to edit."
257
+
258
+ # Deny the read and provide TLDR context instead
259
+ # Use a temp file for the JSON to handle multiline safely
260
+ TEMP_JSON=$(mktemp /tmp/tldr-hook-XXXXXX.json)
261
+ jq -n --arg reason "TLDR summary provided instead of full file read (${FILE_SIZE} bytes → ~1K tokens)" \
262
+ --arg context "$SUMMARY" \
263
+ '{
264
+ hookSpecificOutput: {
265
+ hookEventName: "PreToolUse",
266
+ permissionDecision: "deny",
267
+ permissionDecisionReason: $reason,
268
+ additionalContext: $context
269
+ }
270
+ }' > "$TEMP_JSON" 2>/dev/null
271
+
272
+ cat "$TEMP_JSON"
273
+ rm -f "$TEMP_JSON"
274
+
275
+ exit 0
@@ -0,0 +1,137 @@
1
+ #!/bin/bash
2
+ # ~/.panopticon/bin/work-agent-stop-hook
3
+ # Called when any agent goes idle — detects work agents that forgot "pan work done"
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.
8
+ #
9
+ # The model used is configurable via PANOPTICON_COMPLETION_CHECK_MODEL env var
10
+ # or defaults to the models.overrides.completion-check-hook setting in config.yaml,
11
+ # falling back to claude-haiku-4-5.
12
+
13
+ # Don't use set -e - resilient to failures, never break Claude Code execution
14
+
15
+ # Get agent ID
16
+ if [ -n "$PANOPTICON_AGENT_ID" ]; then
17
+ AGENT_ID="$PANOPTICON_AGENT_ID"
18
+ elif [ -n "$TMUX" ]; then
19
+ AGENT_ID=$(tmux display-message -p '#S' 2>/dev/null)
20
+ else
21
+ exit 0
22
+ fi
23
+
24
+ # Only run for work agents (agent-min-XXX, agent-pan-XXX, etc.)
25
+ case "$AGENT_ID" in
26
+ agent-*)
27
+ # Check it's not a specialist
28
+ case "$AGENT_ID" in
29
+ specialist-*) exit 0 ;;
30
+ esac
31
+ ;;
32
+ *)
33
+ exit 0 # Not a work agent
34
+ ;;
35
+ esac
36
+
37
+ # Extract issue ID from agent ID (e.g., "agent-min-725" -> "MIN-725")
38
+ ISSUE_ID=$(echo "$AGENT_ID" | sed 's/^agent-//' | tr '[:lower:]' '[:upper:]')
39
+
40
+ # Skip if completion marker already exists (agent already called pan work done)
41
+ COMPLETED_FILE="$HOME/.panopticon/agents/$AGENT_ID/completed"
42
+ if [ -f "$COMPLETED_FILE" ]; then
43
+ exit 0
44
+ fi
45
+
46
+ # Cooldown: don't nudge more than once per 10 minutes
47
+ NUDGE_FILE="$HOME/.panopticon/agents/$AGENT_ID/.last-completion-nudge"
48
+ if [ -f "$NUDGE_FILE" ]; then
49
+ LAST_NUDGE=$(cat "$NUDGE_FILE" 2>/dev/null || echo "0")
50
+ NOW=$(date +%s)
51
+ ELAPSED=$(( NOW - LAST_NUDGE ))
52
+ if [ "$ELAPSED" -lt 600 ]; then
53
+ exit 0 # Too soon since last nudge
54
+ fi
55
+ fi
56
+
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
+ exit 0
61
+ fi
62
+
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
66
+ exit 0
67
+ fi
68
+
69
+ # Check if the agent appears to be at an idle prompt (not mid-response)
70
+ if ! echo "$OUTPUT" | tail -10 | grep -qE '(^❯|Worked for)'; then
71
+ exit 0 # Agent doesn't appear to be at an idle prompt
72
+ fi
73
+
74
+ # Determine model to use for completion check
75
+ # Priority: env var > config.yaml override > default
76
+ COMPLETION_MODEL="${PANOPTICON_COMPLETION_CHECK_MODEL:-}"
77
+ if [ -z "$COMPLETION_MODEL" ]; then
78
+ CONFIG_FILE="$HOME/.panopticon/config.yaml"
79
+ if [ -f "$CONFIG_FILE" ] && command -v grep &> /dev/null; then
80
+ COMPLETION_MODEL=$(grep 'completion-check-hook:' "$CONFIG_FILE" 2>/dev/null | awk '{print $2}' | tr -d '"' || echo "")
81
+ fi
82
+ fi
83
+ COMPLETION_MODEL="${COMPLETION_MODEL:-claude-haiku-4-5}"
84
+
85
+ # Build the analysis prompt
86
+ 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
+
88
+ The agent was working on issue $ISSUE_ID. Here is the last 80 lines of its terminal output:
89
+
90
+ <terminal_output>
91
+ $OUTPUT
92
+ </terminal_output>
93
+
94
+ Respond with EXACTLY one of these words (nothing else):
95
+ - FORGOT_COMPLETION — if the agent clearly finished its implementation work (closed beads, committed code, ran tests) but stopped without calling 'pan work done'
96
+ - STILL_WORKING — if the agent appears to have more work to do (mentioned next steps, was mid-task)
97
+ - STOPPED_FOR_INPUT — if the agent stopped because it needs human input or hit a blocker
98
+ - UNCLEAR — if you cannot determine the state"
99
+
100
+ # Run the analysis using claude CLI (headless, no interactive session)
101
+ 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
+ VERDICT=$(echo "$RESULT" | tr -d '[:space:]' | head -c 30)
105
+
106
+ # Log the check
107
+ LOG_DIR="$HOME/.panopticon/logs"
108
+ mkdir -p "$LOG_DIR"
109
+ echo "[$(date -Iseconds)] work-agent-stop-hook: $AGENT_ID ($ISSUE_ID) -> $VERDICT (model: $COMPLETION_MODEL)" \
110
+ >> "$LOG_DIR/hooks.log" 2>/dev/null || true
111
+
112
+ if [ "$VERDICT" = "FORGOT_COMPLETION" ]; then
113
+ # Record nudge timestamp for cooldown
114
+ mkdir -p "$(dirname "$NUDGE_FILE")"
115
+ date +%s > "$NUDGE_FILE" 2>/dev/null || true
116
+
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
+ NUDGE_MSG="You stopped without calling pan work done. If your implementation is complete, you MUST run this command now:
120
+
121
+ pan work done $ISSUE_ID -c \"Implementation complete\"
122
+
123
+ 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."
124
+
125
+ TMPFILE=$(mktemp)
126
+ echo "$NUDGE_MSG" > "$TMPFILE"
127
+ tmux load-buffer "$TMPFILE" 2>/dev/null
128
+ tmux paste-buffer -t "$AGENT_ID" 2>/dev/null
129
+ sleep 0.3
130
+ tmux send-keys -t "$AGENT_ID" C-m 2>/dev/null
131
+ rm -f "$TMPFILE"
132
+
133
+ echo "[$(date -Iseconds)] work-agent-stop-hook: Sent completion nudge to $AGENT_ID" \
134
+ >> "$LOG_DIR/hooks.log" 2>/dev/null || true
135
+ fi
136
+
137
+ exit 0
@@ -0,0 +1,143 @@
1
+ ---
2
+ name: check-merged
3
+ description: >
4
+ Verify whether an issue's feature branch has been merged into main.
5
+ Checks git history, branch existence, and commit presence. Returns
6
+ MERGED, NOT_MERGED, or BRANCH_NOT_FOUND with evidence.
7
+ Designed for cheap models (Haiku) to run quickly.
8
+ tools: Bash(git:*)
9
+ model: haiku
10
+ ---
11
+
12
+ # Check Merged
13
+
14
+ Verify whether a feature branch for an issue has been merged into the main branch.
15
+
16
+ ## Purpose
17
+
18
+ After an issue reaches "Done" on the kanban board, we need to confirm the code was actually merged — not just that the tracker status was updated. This skill checks git evidence to give a definitive answer.
19
+
20
+ ## When to Use
21
+
22
+ - **Close-out ceremony**: Before archiving workspace artifacts
23
+ - **Done column audit**: Batch-verify all Done items are genuinely merged
24
+ - **Stale branch cleanup**: Identify branches that were abandoned vs merged
25
+ - **As a subagent**: Spawn from a parent agent to verify multiple issues in parallel
26
+
27
+ ## Input
28
+
29
+ The skill expects an issue ID (e.g., `PAN-123`, `MIN-456`) and a project path.
30
+
31
+ ## Execution
32
+
33
+ ### Step 1: Resolve Branch Name
34
+
35
+ ```bash
36
+ # Standard naming convention
37
+ BRANCH="feature/${ISSUE_ID_LOWER}"
38
+
39
+ # Also check alternate patterns
40
+ # edwardbecker/${issue-slug} (Linear default)
41
+ # feature/${issue-id}-description
42
+ ```
43
+
44
+ ### Step 2: Check If Branch Exists
45
+
46
+ ```bash
47
+ # Check local branches
48
+ git -C "$PROJECT_PATH" branch --list "$BRANCH" 2>/dev/null
49
+
50
+ # Check remote branches
51
+ git -C "$PROJECT_PATH" ls-remote --heads origin "$BRANCH" 2>/dev/null
52
+
53
+ # Check Linear-style branch names (broader search)
54
+ git -C "$PROJECT_PATH" branch -a --list "*${ISSUE_ID_LOWER}*" 2>/dev/null
55
+ ```
56
+
57
+ ### Step 3: Check Merge Evidence
58
+
59
+ **If branch exists locally:**
60
+ ```bash
61
+ # Check for unmerged commits
62
+ git -C "$PROJECT_PATH" log main.."$BRANCH" --oneline 2>/dev/null
63
+ # Empty output = fully merged
64
+ # Non-empty = has unmerged commits
65
+ ```
66
+
67
+ **If branch exists on remote only:**
68
+ ```bash
69
+ git -C "$PROJECT_PATH" fetch origin "$BRANCH" 2>/dev/null
70
+ git -C "$PROJECT_PATH" log main..origin/"$BRANCH" --oneline 2>/dev/null
71
+ ```
72
+
73
+ **If no branch found (may be squash-merged and deleted):**
74
+ ```bash
75
+ # Check if any commit in main references the issue ID
76
+ git -C "$PROJECT_PATH" log main --oneline --grep="$ISSUE_ID" 2>/dev/null
77
+
78
+ # Check merge commits
79
+ git -C "$PROJECT_PATH" log main --oneline --merges --grep="$ISSUE_ID" 2>/dev/null
80
+
81
+ # Check squash commits (PR title often contains issue ID)
82
+ git -C "$PROJECT_PATH" log main --oneline --grep="$BRANCH" 2>/dev/null
83
+ ```
84
+
85
+ ### Step 4: Check PR Status (if gh available)
86
+
87
+ ```bash
88
+ # Find closed/merged PRs for this branch
89
+ gh pr list --repo OWNER/REPO --state merged --head "$BRANCH" --json number,title,mergedAt 2>/dev/null
90
+
91
+ # Or search by issue reference
92
+ gh pr list --repo OWNER/REPO --state merged --search "$ISSUE_ID" --json number,title,mergedAt 2>/dev/null
93
+ ```
94
+
95
+ ## Output Format
96
+
97
+ Report one of three results:
98
+
99
+ ### MERGED
100
+ ```
101
+ RESULT: MERGED
102
+ ISSUE: PAN-123
103
+ EVIDENCE:
104
+ - Branch: feature/pan-123 (deleted after merge)
105
+ - Commit: abc1234 "PAN-123: Implement feature X" found on main
106
+ - PR: #456 merged at 2026-02-15T10:30:00Z
107
+ ```
108
+
109
+ ### NOT_MERGED
110
+ ```
111
+ RESULT: NOT_MERGED
112
+ ISSUE: PAN-123
113
+ EVIDENCE:
114
+ - Branch: feature/pan-123 exists with 3 unmerged commits
115
+ - Latest commit: def5678 "WIP: partial implementation"
116
+ - No merged PR found
117
+ ```
118
+
119
+ ### BRANCH_NOT_FOUND
120
+ ```
121
+ RESULT: BRANCH_NOT_FOUND
122
+ ISSUE: PAN-123
123
+ EVIDENCE:
124
+ - No branch matching "feature/pan-123" or "*pan-123*" found
125
+ - No commits on main reference "PAN-123"
126
+ - No merged PRs reference "PAN-123"
127
+ NOTE: Issue may have been completed without code changes, or branch name doesn't follow convention
128
+ ```
129
+
130
+ ## Polyrepo Support
131
+
132
+ For polyrepo projects, check each repo in the project:
133
+
134
+ ```bash
135
+ # Get repo list from project config
136
+ # For each repo, run the same checks against that repo's path
137
+ ```
138
+
139
+ ## Error Handling
140
+
141
+ - If `PROJECT_PATH` doesn't exist or isn't a git repo: report error immediately
142
+ - If `git fetch` fails (network): report based on local evidence only, note the fetch failure
143
+ - If `gh` CLI isn't available: skip PR check, report based on git evidence only