panopticon-cli 0.4.32 → 0.4.33

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 (138) hide show
  1. package/dist/{agents-BDFHF4T3.js → agents-VLK4BMVA.js} +10 -7
  2. package/dist/chunk-7SN4L4PH.js +150 -0
  3. package/dist/chunk-7SN4L4PH.js.map +1 -0
  4. package/dist/chunk-7XNJJBH6.js +538 -0
  5. package/dist/chunk-7XNJJBH6.js.map +1 -0
  6. package/dist/chunk-AQXETQHW.js +113 -0
  7. package/dist/chunk-AQXETQHW.js.map +1 -0
  8. package/dist/{chunk-2NIAOCIC.js → chunk-ASY7T35E.js} +170 -64
  9. package/dist/chunk-ASY7T35E.js.map +1 -0
  10. package/dist/chunk-B3PF6JPQ.js +212 -0
  11. package/dist/chunk-B3PF6JPQ.js.map +1 -0
  12. package/dist/{chunk-XP2DXWYP.js → chunk-BKCWRMUX.js} +88 -35
  13. package/dist/chunk-BKCWRMUX.js.map +1 -0
  14. package/dist/chunk-CFCUOV3Q.js +669 -0
  15. package/dist/chunk-CFCUOV3Q.js.map +1 -0
  16. package/dist/chunk-CWELWPWQ.js +32 -0
  17. package/dist/chunk-CWELWPWQ.js.map +1 -0
  18. package/dist/chunk-DI7ABPNQ.js +352 -0
  19. package/dist/chunk-DI7ABPNQ.js.map +1 -0
  20. package/dist/{chunk-VU4FLXV5.js → chunk-FQ66DECN.js} +31 -4
  21. package/dist/chunk-FQ66DECN.js.map +1 -0
  22. package/dist/{review-status-GWQYY77L.js → chunk-GFP3PIPB.js} +14 -7
  23. package/dist/chunk-GFP3PIPB.js.map +1 -0
  24. package/dist/chunk-JQBV3Q2W.js +29 -0
  25. package/dist/chunk-JQBV3Q2W.js.map +1 -0
  26. package/dist/{chunk-BWGFN44T.js → chunk-JT4O4YVM.js} +28 -16
  27. package/dist/chunk-JT4O4YVM.js.map +1 -0
  28. package/dist/{chunk-VIWUCJ4V.js → chunk-KJ2TRXNK.js} +34 -36
  29. package/dist/chunk-KJ2TRXNK.js.map +1 -0
  30. package/dist/{chunk-JY7R7V4G.js → chunk-OMNXYPXC.js} +2 -2
  31. package/dist/chunk-OMNXYPXC.js.map +1 -0
  32. package/dist/chunk-PELXV435.js +215 -0
  33. package/dist/chunk-PELXV435.js.map +1 -0
  34. package/dist/chunk-PI7Y3PSN.js +797 -0
  35. package/dist/chunk-PI7Y3PSN.js.map +1 -0
  36. package/dist/chunk-RBUO57TC.js +154 -0
  37. package/dist/chunk-RBUO57TC.js.map +1 -0
  38. package/dist/chunk-XFR2DLMR.js +600 -0
  39. package/dist/chunk-XFR2DLMR.js.map +1 -0
  40. package/dist/chunk-XKT5MHPT.js +677 -0
  41. package/dist/chunk-XKT5MHPT.js.map +1 -0
  42. package/dist/{chunk-HCTJFIJJ.js → chunk-YLPSQAM2.js} +2 -2
  43. package/dist/{chunk-HCTJFIJJ.js.map → chunk-YLPSQAM2.js.map} +1 -1
  44. package/dist/{chunk-6HXKTOD7.js → chunk-ZTFNYOC7.js} +53 -38
  45. package/dist/chunk-ZTFNYOC7.js.map +1 -0
  46. package/dist/cli/index.js +4362 -2927
  47. package/dist/cli/index.js.map +1 -1
  48. package/dist/{config-BOAMSKTF.js → config-4CJNUE3O.js} +7 -3
  49. package/dist/dashboard/prompts/merge-agent.md +217 -0
  50. package/dist/dashboard/prompts/review-agent.md +409 -0
  51. package/dist/dashboard/prompts/sync-main.md +84 -0
  52. package/dist/dashboard/prompts/test-agent.md +283 -0
  53. package/dist/dashboard/prompts/work-agent.md +247 -0
  54. package/dist/dashboard/public/assets/index-UjZq6ykz.css +32 -0
  55. package/dist/dashboard/public/assets/index-kAJqtLDO.js +708 -0
  56. package/dist/dashboard/public/index.html +2 -2
  57. package/dist/dashboard/server.js +15194 -3160
  58. package/dist/{dns-L3L2BB27.js → dns-7BDJSD3E.js} +4 -2
  59. package/dist/{feedback-writer-AAKF5BTK.js → feedback-writer-LVZ5TFYZ.js} +8 -4
  60. package/dist/feedback-writer-LVZ5TFYZ.js.map +1 -0
  61. package/dist/hume-WMAUBBV2.js +13 -0
  62. package/dist/index.d.ts +153 -40
  63. package/dist/index.js +65 -23
  64. package/dist/index.js.map +1 -1
  65. package/dist/{projects-VXRUCMLM.js → projects-JEIVIYC6.js} +3 -3
  66. package/dist/rally-RKFSWC7E.js +10 -0
  67. package/dist/{remote-agents-Z3R2A5BN.js → remote-agents-TFSMW7GN.js} +2 -2
  68. package/dist/{remote-workspace-2G6V2KNP.js → remote-workspace-AHVHQEES.js} +8 -8
  69. package/dist/review-status-EPFG4XM7.js +19 -0
  70. package/dist/shadow-state-5MDP6YXH.js +30 -0
  71. package/dist/shadow-state-5MDP6YXH.js.map +1 -0
  72. package/dist/{specialist-context-N32QBNNQ.js → specialist-context-T3NBMCIE.js} +8 -7
  73. package/dist/{specialist-context-N32QBNNQ.js.map → specialist-context-T3NBMCIE.js.map} +1 -1
  74. package/dist/{specialist-logs-GF3YV4KL.js → specialist-logs-CVKD3YJ3.js} +7 -6
  75. package/dist/specialist-logs-CVKD3YJ3.js.map +1 -0
  76. package/dist/{specialists-JBIW6MP4.js → specialists-TKAP6T6Z.js} +7 -6
  77. package/dist/specialists-TKAP6T6Z.js.map +1 -0
  78. package/dist/tldr-daemon-T3THOUGT.js +21 -0
  79. package/dist/tldr-daemon-T3THOUGT.js.map +1 -0
  80. package/dist/traefik-QX4ZV4YG.js +19 -0
  81. package/dist/traefik-QX4ZV4YG.js.map +1 -0
  82. package/dist/tunnel-W2GZBLEV.js +13 -0
  83. package/dist/tunnel-W2GZBLEV.js.map +1 -0
  84. package/dist/workspace-manager-KLHUCIZV.js +22 -0
  85. package/dist/workspace-manager-KLHUCIZV.js.map +1 -0
  86. package/package.json +2 -2
  87. package/scripts/heartbeat-hook +37 -10
  88. package/scripts/patches/llm-tldr-tsx-support.py +109 -0
  89. package/scripts/pre-tool-hook +26 -15
  90. package/scripts/record-cost-event.js +177 -43
  91. package/scripts/record-cost-event.ts +87 -3
  92. package/scripts/statusline.sh +169 -0
  93. package/scripts/stop-hook +14 -11
  94. package/scripts/tldr-post-edit +72 -0
  95. package/scripts/tldr-read-enforcer +275 -0
  96. package/skills/check-merged/SKILL.md +143 -0
  97. package/skills/crash-investigation/SKILL.md +301 -0
  98. package/skills/github-cli/SKILL.md +185 -0
  99. package/skills/pan-reopen/SKILL.md +65 -0
  100. package/skills/pan-sync-main/SKILL.md +87 -0
  101. package/skills/pan-tldr/SKILL.md +149 -0
  102. package/skills/react-best-practices/SKILL.md +125 -0
  103. package/skills/spec-readiness/REPORT-TEMPLATE.md +158 -0
  104. package/skills/spec-readiness/SCORING-REFERENCE.md +369 -0
  105. package/skills/spec-readiness/SKILL.md +400 -0
  106. package/skills/spec-readiness-setup/SKILL.md +361 -0
  107. package/skills/workspace-status/SKILL.md +56 -0
  108. package/templates/traefik/dynamic/panopticon.yml.template +0 -5
  109. package/templates/traefik/traefik.yml +0 -8
  110. package/dist/chunk-2NIAOCIC.js.map +0 -1
  111. package/dist/chunk-3XAB4IXF.js +0 -51
  112. package/dist/chunk-3XAB4IXF.js.map +0 -1
  113. package/dist/chunk-6HXKTOD7.js.map +0 -1
  114. package/dist/chunk-BBCUK6N2.js +0 -241
  115. package/dist/chunk-BBCUK6N2.js.map +0 -1
  116. package/dist/chunk-BWGFN44T.js.map +0 -1
  117. package/dist/chunk-ELK6Q7QI.js +0 -545
  118. package/dist/chunk-ELK6Q7QI.js.map +0 -1
  119. package/dist/chunk-JY7R7V4G.js.map +0 -1
  120. package/dist/chunk-LYSBSZYV.js +0 -1523
  121. package/dist/chunk-LYSBSZYV.js.map +0 -1
  122. package/dist/chunk-VIWUCJ4V.js.map +0 -1
  123. package/dist/chunk-VU4FLXV5.js.map +0 -1
  124. package/dist/chunk-XP2DXWYP.js.map +0 -1
  125. package/dist/dashboard/public/assets/index-C7X6LP5Z.css +0 -32
  126. package/dist/dashboard/public/assets/index-ClYqpcAJ.js +0 -645
  127. package/dist/feedback-writer-AAKF5BTK.js.map +0 -1
  128. package/dist/review-status-GWQYY77L.js.map +0 -1
  129. package/dist/traefik-CUJM6K5Z.js +0 -12
  130. /package/dist/{agents-BDFHF4T3.js.map → agents-VLK4BMVA.js.map} +0 -0
  131. /package/dist/{config-BOAMSKTF.js.map → config-4CJNUE3O.js.map} +0 -0
  132. /package/dist/{dns-L3L2BB27.js.map → dns-7BDJSD3E.js.map} +0 -0
  133. /package/dist/{projects-VXRUCMLM.js.map → hume-WMAUBBV2.js.map} +0 -0
  134. /package/dist/{remote-agents-Z3R2A5BN.js.map → projects-JEIVIYC6.js.map} +0 -0
  135. /package/dist/{specialist-logs-GF3YV4KL.js.map → rally-RKFSWC7E.js.map} +0 -0
  136. /package/dist/{specialists-JBIW6MP4.js.map → remote-agents-TFSMW7GN.js.map} +0 -0
  137. /package/dist/{remote-workspace-2G6V2KNP.js.map → remote-workspace-AHVHQEES.js.map} +0 -0
  138. /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,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