@oneie/claude 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/agents/abm-strategist.md +67 -1
  3. package/agents/ads-meta.md +67 -1
  4. package/agents/analyst.md +67 -1
  5. package/agents/animator.md +108 -0
  6. package/agents/architect.md +269 -20
  7. package/agents/brand-guardian.md +67 -1
  8. package/agents/brand-strategist.md +67 -1
  9. package/agents/campaign-content.md +67 -1
  10. package/agents/campaign-email.md +67 -1
  11. package/agents/campaign-sms.md +67 -1
  12. package/agents/campaign-social.md +67 -1
  13. package/agents/cco.md +83 -2
  14. package/agents/ceo.md +108 -11
  15. package/agents/chairman.md +197 -0
  16. package/agents/cmo.md +82 -2
  17. package/agents/community-greeter.md +67 -1
  18. package/agents/community-moderator.md +67 -1
  19. package/agents/compliance.md +67 -1
  20. package/agents/copywriter.md +67 -1
  21. package/agents/creative-strategist.md +67 -1
  22. package/agents/cro.md +81 -1
  23. package/agents/cto.md +266 -28
  24. package/agents/customer-interviewer.md +67 -1
  25. package/agents/customer-researcher.md +67 -1
  26. package/agents/customer-success-manager.md +67 -1
  27. package/agents/customer-trainer.md +67 -1
  28. package/agents/cxo.md +82 -1
  29. package/agents/demand-creator.md +67 -1
  30. package/agents/demo-mover.md +67 -1
  31. package/agents/demo-specialist.md +67 -1
  32. package/agents/demo-thai-family-law.md +67 -1
  33. package/agents/designer.md +67 -1
  34. package/agents/discovery-caller.md +67 -1
  35. package/agents/doctor.md +269 -0
  36. package/agents/educate-coach.md +67 -1
  37. package/agents/elevate-tutor.md +67 -1
  38. package/agents/email-lifecycle-marketer.md +67 -1
  39. package/agents/engage-specialist.md +67 -1
  40. package/agents/events-coordinator.md +67 -1
  41. package/agents/foundation-builder.md +67 -1
  42. package/agents/funnel-architect.md +67 -1
  43. package/agents/gift-creator.md +67 -1
  44. package/agents/google-ads.md +67 -1
  45. package/agents/guide.md +67 -1
  46. package/agents/helpdesk-dispatcher.md +67 -1
  47. package/agents/hook-specialist.md +67 -1
  48. package/agents/identify-optimizer.md +67 -1
  49. package/agents/implementer.md +313 -45
  50. package/agents/incident-commander.md +67 -1
  51. package/agents/insights-lead.md +87 -1
  52. package/agents/journey-runner.md +67 -1
  53. package/agents/linkedin-ads.md +67 -1
  54. package/agents/live-sales-chat.md +67 -1
  55. package/agents/market-researcher.md +67 -1
  56. package/agents/media-buyer.md +67 -1
  57. package/agents/memory-keeper.md +195 -0
  58. package/agents/movers-customer-researcher.md +67 -1
  59. package/agents/movers-foundation-builder.md +67 -1
  60. package/agents/movers-market-researcher.md +67 -1
  61. package/agents/movers-pricing-strategist.md +67 -1
  62. package/agents/nurture-architect.md +67 -1
  63. package/agents/offer-architect.md +67 -1
  64. package/agents/onboarder.md +67 -1
  65. package/agents/onboarding-specialist.md +67 -1
  66. package/agents/operations-dashboard.md +87 -1
  67. package/agents/perf-engineer.md +333 -37
  68. package/agents/playbook-writer.md +67 -1
  69. package/agents/plg-strategist.md +67 -1
  70. package/agents/positioning-architect.md +67 -1
  71. package/agents/press-officer.md +67 -1
  72. package/agents/pricing-strategist.md +67 -1
  73. package/agents/privacy-officer.md +67 -1
  74. package/agents/referral-manager.md +67 -1
  75. package/agents/refine-analyst.md +67 -1
  76. package/agents/release-manager.md +446 -39
  77. package/agents/renewals-upsell-rep.md +67 -1
  78. package/agents/review-engineer.md +319 -45
  79. package/agents/rewards-steward.md +67 -1
  80. package/agents/sales-call-coach.md +67 -1
  81. package/agents/sales-closer.md +67 -1
  82. package/agents/security-auditor.md +343 -48
  83. package/agents/sell-closer.md +67 -1
  84. package/agents/share-amplifier.md +67 -1
  85. package/agents/social-media-manager.md +67 -1
  86. package/agents/storyteller.md +301 -0
  87. package/agents/strategist.md +67 -1
  88. package/agents/strategy-aligner.md +67 -1
  89. package/agents/support-agent.md +67 -1
  90. package/agents/tagger.md +327 -0
  91. package/agents/tech-writer.md +195 -22
  92. package/agents/test-engineer.md +398 -29
  93. package/agents/tiktok-ads.md +67 -1
  94. package/agents/tracking-engineer.md +67 -1
  95. package/agents/trailkeeper.md +181 -0
  96. package/agents/upsell-strategist.md +67 -1
  97. package/agents/voice.md +67 -1
  98. package/agents/w1-recon.md +1 -1
  99. package/agents/w2-decide.md +1 -1
  100. package/agents/w3-edit.md +8 -2
  101. package/agents/w4-verify.md +13 -0
  102. package/agents/workflow-optimiser.md +81 -1
  103. package/commands/close.md +916 -160
  104. package/commands/deploy.md +102 -724
  105. package/commands/do.md +58 -2
  106. package/commands/sweep.md +159 -0
  107. package/commands/tasks.md +222 -0
  108. package/hooks/scripts/dev-only.sh +135 -0
  109. package/hooks/scripts/git-add-guard.sh +37 -2
  110. package/hooks/scripts/session-start.sh +32 -4
  111. package/package.json +1 -1
  112. package/rules/scripts.md +85 -0
  113. package/scripts/CLAUDE.md +315 -0
  114. package/scripts/ad-copy-lint.sh +656 -0
  115. package/scripts/agent-actor-parity.sh +129 -0
  116. package/scripts/blocks-manifest-cached.sh +100 -0
  117. package/scripts/chat-context-check.sh +89 -0
  118. package/scripts/chrome.mjs +18 -0
  119. package/scripts/close-metrics.sh +587 -0
  120. package/scripts/close-owner.sh +326 -0
  121. package/scripts/db-sync-lock-check.sh +116 -0
  122. package/scripts/deploy-emit.sh +311 -0
  123. package/scripts/deploy-gate-check.sh +155 -0
  124. package/scripts/deploy-ready.sh +78 -0
  125. package/scripts/deploy-record.sh +605 -0
  126. package/scripts/deploy-schema-check.sh +58 -0
  127. package/scripts/deploy.sh +393 -243
  128. package/scripts/do-auto.sh +127 -26
  129. package/scripts/do-board.sh +429 -0
  130. package/scripts/do-close.sh +1184 -0
  131. package/scripts/do-consumer-sweep.sh +18 -1
  132. package/scripts/do-decide.sh +476 -0
  133. package/scripts/do-fleet.sh +8 -2
  134. package/scripts/do-plan-json.mjs +110 -12
  135. package/scripts/do-prove-selftest.sh +108 -0
  136. package/scripts/do-prove.sh +86 -10
  137. package/scripts/do-rank.py +200 -3
  138. package/scripts/do-reconcile.sh +73 -12
  139. package/scripts/do-signal.sh +101 -23
  140. package/scripts/do-smoke.sh +18 -1
  141. package/scripts/do-w4-gates.sh +11 -1
  142. package/scripts/do-world-check.sh +153 -0
  143. package/scripts/download-stats.sh +172 -0
  144. package/scripts/factory-brief-check.sh +330 -0
  145. package/scripts/factory-check.sh +18 -1
  146. package/scripts/factory-close-check.sh +257 -0
  147. package/scripts/factory-emit.sh +211 -0
  148. package/scripts/factory-executor-check.mjs +353 -0
  149. package/scripts/factory-peak.sh +301 -0
  150. package/scripts/factory-repo.sh +71 -0
  151. package/scripts/factory-review-check.mjs +61 -0
  152. package/scripts/factory-tasks-check.sh +18 -1
  153. package/scripts/fixtures/factory-brief-real.md +44 -0
  154. package/scripts/flywheel-outcome.sh +63 -0
  155. package/scripts/gate-reaper-check.sh +98 -0
  156. package/scripts/gate-reaper.sh +9 -0
  157. package/scripts/gate-watchdog.sh +619 -0
  158. package/scripts/gc-content-check.sh +142 -0
  159. package/scripts/gh-traffic-capture.sh +153 -0
  160. package/scripts/govern-order-check.sh +202 -0
  161. package/scripts/governor-doors-check.sh +86 -5
  162. package/scripts/health.sh +448 -0
  163. package/scripts/id-inventory.mjs +418 -0
  164. package/scripts/incident.sh +212 -0
  165. package/scripts/land.sh +755 -45
  166. package/scripts/lib/gc-finished.sh +77 -0
  167. package/scripts/livekit-ratchet.sh +18 -1
  168. package/scripts/machine-check.sh +1 -1
  169. package/scripts/memory-index-budget.sh +79 -0
  170. package/scripts/npm-downloads.sh +109 -0
  171. package/scripts/one-agents.mjs +204 -8
  172. package/scripts/one-resume.sh +31 -3
  173. package/scripts/pr-body.sh +335 -0
  174. package/scripts/preview-fd-check.sh +289 -0
  175. package/scripts/redirect-lint.sh +169 -0
  176. package/scripts/release.sh +40 -6
  177. package/scripts/resume-lost-sessions.sh +68 -0
  178. package/scripts/shoot-pages.mjs +140 -0
  179. package/scripts/signal-meta-backfill.ts +451 -0
  180. package/scripts/signal-watch.sh +63 -6
  181. package/scripts/speed-cache-check.sh +12 -2
  182. package/scripts/sweep.sh +426 -0
  183. package/scripts/task-titles-dump.ts +101 -0
  184. package/scripts/test-cached.sh +47 -10
  185. package/scripts/test-lanes.sh +14 -0
  186. package/scripts/thread-name-backfill.ts +215 -0
  187. package/scripts/triage-shape-check.sh +149 -0
  188. package/scripts/tsc-cached.sh +155 -8
  189. package/scripts/typedb-flake-check.sh +3 -1
  190. package/scripts/urls-lint.sh +8 -0
  191. package/scripts/verify-board-doors.sh +80 -0
  192. package/scripts/verify-fast.sh +159 -6
  193. package/scripts/worktree-up.sh +21 -3
  194. package/skills/astro/SKILL.md +9 -3
  195. package/skills/astro/optimize-performance.md +3 -2
  196. package/skills/cloudflare/SKILL.md +3 -2
  197. package/skills/cloudflare-security-audit/AI-AND-LLM.md +83 -0
  198. package/skills/cloudflare-security-audit/ATTACK-CLASSES.md +130 -0
  199. package/skills/cloudflare-security-audit/CLIENT-SIDE.md +83 -0
  200. package/skills/cloudflare-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
  201. package/skills/cloudflare-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
  202. package/skills/cloudflare-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
  203. package/skills/cloudflare-security-audit/HUNTING.md +251 -0
  204. package/skills/cloudflare-security-audit/LICENSE +21 -0
  205. package/skills/cloudflare-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
  206. package/skills/cloudflare-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
  207. package/skills/cloudflare-security-audit/PROVENANCE.md +78 -0
  208. package/skills/cloudflare-security-audit/RECONNAISSANCE.md +156 -0
  209. package/skills/cloudflare-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
  210. package/skills/cloudflare-security-audit/SKILL.md +192 -0
  211. package/skills/cloudflare-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
  212. package/skills/cloudflare-security-audit/VALIDATION-AND-REPORTING.md +186 -0
  213. package/skills/cloudflare-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
  214. package/skills/cloudflare-security-audit/report-schema.json +461 -0
  215. package/skills/cloudflare-security-audit/validate-coverage-ledger.cjs +872 -0
  216. package/skills/cloudflare-security-audit/validate-coverage-ledger.test.cjs +740 -0
  217. package/skills/cloudflare-security-audit/validate-findings.cjs +773 -0
  218. package/skills/cloudflare-security-audit/validate-findings.test.cjs +652 -0
  219. package/skills/deploy/REFERENCE.md +713 -0
  220. package/skills/deploy/SKILL.md +140 -0
  221. package/skills/fleet-audit/SKILL.md +58 -0
  222. package/skills/meeting/SKILL.md +220 -0
  223. package/skills/planning/SKILL.md +256 -0
  224. package/skills/shadcn/SKILL.md +1 -1
  225. package/skills/typedb/SKILL.md +7 -0
  226. package/skills/voice/SKILL.md +94 -6
  227. package/skills/voice/corpus-check.sh +87 -0
  228. package/templates/template-agent.md +7 -1
  229. package/templates/template-feature.md +9 -0
  230. package/templates/template-todo.md +29 -0
@@ -0,0 +1,289 @@
1
+ #!/usr/bin/env bash
2
+ # preview-fd-check.sh — is the preview tax a PIPE that outlives the script?
3
+ #
4
+ # manifest: needs-env
5
+ # Mode A (default) is self-contained: a scratch sandbox, one node mimic, no
6
+ # ports, no worktree, no astro, no credential. Mode B (--real) drives
7
+ # worktree-preview.sh against one.ie/web and costs a worktree and a port.
8
+ #
9
+ # NOT A FIX. A MEASUREMENT THAT IS ALLOWED TO COME BACK NEGATIVE.
10
+ #
11
+ # THE OBSERVATION. The chairman measured ~7 min of Opus per UI task blocked in
12
+ # worktree-preview.sh up <slug> --route / | tail -30
13
+ # with the server green in under a minute and the shell parked in __wait4 at
14
+ # 8m42s. Two mechanisms fit that sample and only one of them is fixable at the
15
+ # launch line:
16
+ #
17
+ # H1 THE PIPE OUTLIVES THE SCRIPT. cmd_up prints GREEN and returns, but the
18
+ # reader never sees EOF because a descendant of the launch still holds the
19
+ # script's stdout. The tool call is waiting on `tail`, not on the server.
20
+ # H2 THE SCRIPT REALLY IS STILL RUNNING. BOOT_LOCK_WAIT 240 + BOOT_TIMEOUT 60
21
+ # + WARM_TIMEOUT 180 = 480s against an observed 522s. A `sleep 1` poll loop
22
+ # samples as __wait4 exactly like waiting on a pipe.
23
+ #
24
+ # Mode A can only support or refute H1. It CANNOT exclude H2 — only --real can,
25
+ # by asking whether the driver process is already gone at the moment the server
26
+ # answers 200. So a green Mode A is not licence to touch worktree-preview.sh.
27
+ #
28
+ # THE LAUNCH THIS REPRODUCES, quoted from worktree-preview.sh (the perl branch;
29
+ # the nohup branch is its twin):
30
+ # ( cd "$app" && perl -e 'setsid; exec @ARGV' -- sh -c "exec $astro dev ..." >>"$log" 2>&1 & echo $! > "$state/pid" )
31
+ #
32
+ # VARIANTS, each timed from the DRIVER'S OWN EXIT to the reader's EOF:
33
+ # V1 driver | cat the shape the operator runs
34
+ # V2 driver > file no pipe at all
35
+ # V3 driver | cat, launch gets </dev/null is stdin the holder?
36
+ # V4 driver | cat, subshell does `exec >>log 2>&1 </dev/null` BEFORE the fork
37
+ #
38
+ # VERDICT DISCIPLINE. REPRODUCED requires ALL THREE:
39
+ # V1 hold >= 60% of the mimic's lifetime AND V2 hold <= 2s AND a NAMED
40
+ # process holding the same pipe as the reader's fd 0.
41
+ # A timing alone is a correlation. Anything short of all three prints
42
+ # NOT REPRODUCED with every number and exits 3, and no line of
43
+ # worktree-preview.sh is touched.
44
+ #
45
+ # WHAT MODE A FOUND, 2026-09-06 (hold 12s):
46
+ # V1(pipe)=12.2s V2(file)=0.0s V3(stdin</dev/null)=12.2s V4(exec-before-fork)=0.0s
47
+ # holder = <pid> bash / fd 1 / the exact peer of the reader's fd 0.
48
+ # The holder is a BASH SUBSHELL, not node. `cmd >>log 2>&1 &` backgrounds the
49
+ # whole `cd "$app" && perl …` AND-LIST: bash forks a subshell to run it, and the
50
+ # `>>"$log" 2>&1` binds to the `perl` simple command INSIDE it. So the subshell
51
+ # keeps the caller's stdout on its own fd 1 for as long as the server lives, and
52
+ # `$!` — the pid worktree-preview.sh stores and polls — IS that subshell.
53
+ # V3 shows stdin is not the holder; V4 shows giving the subshell its own stdio
54
+ # before the fork closes it. That is H1, named.
55
+ #
56
+ # H2 IS STILL LIVE for the 522s the chairman observed (BOOT_LOCK_WAIT 240 +
57
+ # BOOT_TIMEOUT 60 + WARM_TIMEOUT 180 = 480s), and Mode A cannot separate them.
58
+ # NOTHING IN worktree-preview.sh HAS BEEN CHANGED on the strength of this run.
59
+ #
60
+ # USAGE preview-fd-check.sh [--sandbox] [--hold N] Mode A, default
61
+ # preview-fd-check.sh --real <slug> Mode B, opt-in
62
+ # preview-fd-check.sh --self-test the harness's own guards
63
+ # EXITS 0 REPRODUCED · 3 NOT REPRODUCED · 4 bad arguments / missing tool /
64
+ # UNMEASURED (a variant produced no timing — never reported as a verdict)
65
+ set -uo pipefail
66
+
67
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
68
+ HOLD="${PREVIEW_FD_HOLD:-12}"
69
+ MODE=sandbox
70
+ SLUG=""
71
+
72
+ now() { perl -MTime::HiRes=time -e 'printf "%.3f\n", time'; }
73
+ el() { perl -e 'printf "%.1f\n", $ARGV[1] - $ARGV[0]' "$1" "$2"; }
74
+
75
+ while [ $# -gt 0 ]; do
76
+ case "$1" in
77
+ --sandbox) MODE=sandbox; shift ;;
78
+ --real) MODE=real; SLUG="${2:-}"; shift 2 ;;
79
+ --hold) HOLD="${2:-12}"; shift 2 ;;
80
+ --self-test) MODE=selftest; shift ;;
81
+ -h|--help) sed -n '2,50p' "${BASH_SOURCE[0]}"; exit 0 ;;
82
+ *) echo "preview-fd-check: unknown argument: $1" >&2; exit 4 ;;
83
+ esac
84
+ done
85
+
86
+ command -v perl >/dev/null 2>&1 || { echo "preview-fd-check: perl not found — it is both the timer and the launch shape" >&2; exit 4; }
87
+ command -v node >/dev/null 2>&1 || { echo "preview-fd-check: node not found — the mimic is a node process on purpose" >&2; exit 4; }
88
+
89
+ SB="${TMPDIR:-/tmp}/preview-fd-$$"
90
+ mkdir -p "$SB"
91
+ cleanup() {
92
+ local p
93
+ for p in "$SB"/*/pid; do [ -f "$p" ] || continue; kill -- -"$(cat "$p")" 2>/dev/null || kill "$(cat "$p")" 2>/dev/null; done
94
+ rm -rf "$SB"
95
+ }
96
+ trap cleanup EXIT INT TERM
97
+
98
+ # The astro-mimic: a node process that (a) runs a worker thread, (b) spawns a
99
+ # grandchild with inherited stdio, (c) holds the event loop. Those are the three
100
+ # things astro dev's process tree does that a bare `sleep` child does not — and
101
+ # a bare `sleep` child is exactly the harness that already failed to reproduce.
102
+ cat > "$SB/mimic.js" <<'MIMIC'
103
+ const { Worker } = require('worker_threads')
104
+ const { spawn } = require('child_process')
105
+ const secs = Number(process.argv[2] || 12)
106
+ const ms = secs * 1000
107
+ new Worker(`setTimeout(() => {}, ${ms})`, { eval: true })
108
+ spawn('sh', ['-c', `sleep ${secs}`], { stdio: 'inherit' })
109
+ console.log('mimic up')
110
+ setTimeout(() => process.exit(0), ms)
111
+ MIMIC
112
+
113
+ # The driver: worktree-preview.sh's cmd_up, reduced to its launch and its GREEN
114
+ # line. $1 = variant, $2 = state dir, $3 = hold seconds.
115
+ cat > "$SB/driver.sh" <<'DRIVER'
116
+ #!/usr/bin/env bash
117
+ set -uo pipefail
118
+ variant="$1"; state="$2"; hold="$3"
119
+ log="$state/mimic.log"; : > "$log"
120
+ mimic="$state/../mimic.js"
121
+ case "$variant" in
122
+ plain)
123
+ ( cd "$state" && perl -e 'setsid; exec @ARGV' -- sh -c "exec node $mimic $hold" >>"$log" 2>&1 & echo $! > "$state/pid" ) ;;
124
+ stdin)
125
+ ( cd "$state" && perl -e 'setsid; exec @ARGV' -- sh -c "exec node $mimic $hold" >>"$log" 2>&1 </dev/null & echo $! > "$state/pid" ) ;;
126
+ execfd)
127
+ ( cd "$state" && exec >>"$log" 2>&1 </dev/null; perl -e 'setsid; exec @ARGV' -- sh -c "exec node $mimic $hold" & echo $! > "$state/pid" ) ;;
128
+ *) echo "driver: unknown variant $variant" >&2; exit 4 ;;
129
+ esac
130
+ perl -MTime::HiRes=time -e 'printf "%.3f\n", time' > "$state/driver_exit"
131
+ echo "GREEN mimic up (driver returning, exactly as cmd_up does)"
132
+ DRIVER
133
+ chmod +x "$SB/driver.sh"
134
+
135
+ # One variant, measured. $1 = name, $2 = driver variant, $3 = pipe|file
136
+ run_variant() {
137
+ # NOT one `local` line: bash expands every word of the builtin BEFORE any of
138
+ # the assignments take effect, so `st="$SB/$name"` reads an unset `name` and
139
+ # dies under `set -u`. Measured here 2026-09-06 — it produced four empty
140
+ # timings that the verdict then reported as a confident NOT REPRODUCED.
141
+ local name="$1" variant="$2" sink="$3" t0 t_eof t_drv
142
+ local st="$SB/$name"
143
+ mkdir -p "$st"
144
+ t0="$(now)"
145
+ if [ "$sink" = pipe ]; then
146
+ ( bash "$SB/driver.sh" "$variant" "$st" "$HOLD" | cat > "$st/out.txt"
147
+ perl -MTime::HiRes=time -e 'printf "%.3f\n", time' > "$st/eof" ) &
148
+ else
149
+ ( bash "$SB/driver.sh" "$variant" "$st" "$HOLD" > "$st/out.txt"
150
+ perl -MTime::HiRes=time -e 'printf "%.3f\n", time' > "$st/eof" ) &
151
+ fi
152
+ local job=$!
153
+ # While the reader may still be blocked, name the holder. A timing without a
154
+ # named process is a correlation, and the verdict refuses it.
155
+ if [ "$sink" = pipe ] && [ "$name" = V1 ]; then sleep 3; name_holder "$st"; fi
156
+ wait "$job" 2>/dev/null
157
+ t_eof="$(cat "$st/eof" 2>/dev/null)"; t_drv="$(cat "$st/driver_exit" 2>/dev/null)"
158
+ if [ -z "$t_eof" ] || [ -z "$t_drv" ]; then echo "unmeasured"; return; fi
159
+ el "$t_drv" "$t_eof"
160
+ }
161
+
162
+ # Does any process in the launched tree hold the SAME pipe the reader is blocked
163
+ # on? macOS lsof prints a pipe as `PIPE 0x<dev> ... ->0x<peer>`; the reader's fd
164
+ # 0 and a writer's fd share those addresses.
165
+ HOLDER="none"
166
+ name_holder() {
167
+ local st="$1" mpid rp toks t
168
+ mpid="$(cat "$st/pid" 2>/dev/null)"
169
+ command -v lsof >/dev/null 2>&1 || { HOLDER="lsof-missing"; return; }
170
+ # The reader is the `cat` whose fd 1 is out.txt; find its pipe tokens on fd 0.
171
+ rp="$(lsof -n -t -- "$st/out.txt" 2>/dev/null | head -1)"
172
+ [ -n "$rp" ] || { HOLDER="reader-not-found"; return; }
173
+ toks="$(lsof -n -p "$rp" 2>/dev/null | awk '$5 == "PIPE" {print $6; if ($NF ~ /^->0x/) print substr($NF,3)}' | sort -u)"
174
+ [ -n "$toks" ] || { HOLDER="reader-holds-no-pipe"; return; }
175
+ local cand="" line
176
+ # Every descendant of the launched pid, plus the pid itself.
177
+ local pids; pids="$(ps -A -o pid=,ppid=,comm= 2>/dev/null)"
178
+ local tree; tree="$(printf '%s\n' "$mpid"; printf '%s\n' "$pids" | awk -v m="$mpid" '$2 == m {print $1}')"
179
+ for t in $tree; do
180
+ [ -n "$t" ] || continue
181
+ line="$(lsof -n -p "$t" 2>/dev/null | awk '$5 == "PIPE" {print $6; if ($NF ~ /^->0x/) print substr($NF,3)}' | sort -u)"
182
+ [ -n "$line" ] || continue
183
+ local shared; shared="$(comm -12 <(printf '%s\n' "$toks") <(printf '%s\n' "$line") 2>/dev/null | head -1)"
184
+ if [ -n "$shared" ]; then
185
+ # Name the process AND the pipe, with the fd it holds it on. A pid alone
186
+ # is a coincidence; a pid whose fd 1 is the peer of the reader's fd 0 is
187
+ # the mechanism.
188
+ local fd; fd="$(lsof -n -p "$t" 2>/dev/null | awk -v s="$shared" '$5 == "PIPE" && ($6 == s || substr($NF,3) == s) {print $4; exit}')"
189
+ cand="$(ps -o pid=,comm= -p "$t" 2>/dev/null | tr -s ' ' | sed 's/^ *//;s/ *$//')/fd${fd:-?}/pipe${shared}"
190
+ break
191
+ fi
192
+ done
193
+ HOLDER="${cand:-no-shared-pipe-in-launch-tree}"
194
+ # run_variant is called in a command substitution, so this assignment lives in
195
+ # a subshell and would evaporate. The answer travels by file, or the verdict
196
+ # would silently read `none` and refuse a real reproduction.
197
+ printf '%s' "$HOLDER" > "$SB/holder"
198
+ }
199
+
200
+ self_test() {
201
+ local fails=0 rc
202
+ printf '== preview-fd-check --self-test\n'
203
+ # The harness must refuse to call a hang a reproduction without a named holder.
204
+ if ( HOLDER="no-shared-pipe-in-launch-tree"; verdict 30 0 30 0 >/dev/null 2>&1 ); then
205
+ printf ' FAIL %-52s\n' 'a hang with NO named holder read as REPRODUCED' >&2; fails=$((fails+1))
206
+ else
207
+ printf ' ok %-52s\n' 'no named holder => NOT REPRODUCED, whatever the timings'
208
+ fi
209
+ if ( HOLDER="1234 node"; verdict 0 0 0 0 >/dev/null 2>&1 ); then
210
+ printf ' FAIL %-52s\n' 'zero hold everywhere read as REPRODUCED' >&2; fails=$((fails+1))
211
+ else
212
+ printf ' ok %-52s\n' 'no hold => NOT REPRODUCED even with a holder'
213
+ fi
214
+ if ( HOLDER="1234 node"; verdict "$HOLD" 0 "$HOLD" 0 >/dev/null 2>&1 ); then
215
+ printf ' ok %-52s\n' 'all three conditions => REPRODUCED'
216
+ else
217
+ printf ' FAIL %-52s\n' 'the green half is unreachable' >&2; fails=$((fails+1))
218
+ fi
219
+ if ( HOLDER="1234 node"; verdict "$HOLD" "$HOLD" "$HOLD" "$HOLD" >/dev/null 2>&1 ); then
220
+ printf ' FAIL %-52s\n' 'V2 hanging too (so: not the pipe) read as REPRODUCED' >&2; fails=$((fails+1))
221
+ else
222
+ printf ' ok %-52s\n' 'V2 hanging too => NOT REPRODUCED (not a pipe effect)'
223
+ fi
224
+ # THE HOUSE BUG, in this harness's own shape: an empty timing must not read as
225
+ # a benign "not reproduced". Driven for real — this exact defect shipped in the
226
+ # first draft and was caught by running it.
227
+ ( HOLDER="1234 node"; verdict "" 0 0 0 >/dev/null 2>&1 ); rc=$?
228
+ if [ "$rc" = 4 ]; then printf ' ok %-52s exit 4\n' 'an unmeasured variant is UNMEASURED, not a verdict'
229
+ else printf ' FAIL %-52s exit %s\n' 'an empty timing produced a verdict' "$rc" >&2; fails=$((fails+1)); fi
230
+ ( HOLDER="1234 node"; verdict "$HOLD" unmeasured 0 0 >/dev/null 2>&1 ); rc=$?
231
+ if [ "$rc" = 4 ]; then printf ' ok %-52s exit 4\n' 'a word where a number belongs is UNMEASURED'
232
+ else printf ' FAIL %-52s exit %s\n' 'a non-numeric timing produced a verdict' "$rc" >&2; fails=$((fails+1)); fi
233
+
234
+ [ "$fails" -eq 0 ] && { printf 'preview-fd-check --self-test: PASS (6 checks)\n'; return 0; }
235
+ printf 'preview-fd-check --self-test: RED — %s failed\n' "$fails" >&2; return 1
236
+ }
237
+
238
+ # verdict <v1> <v2> <v3> <v4> — the three conditions, judged together.
239
+ verdict() {
240
+ local v1="$1" v2="$2" v3="$3" v4="$4" floor
241
+ floor="$(perl -e 'printf "%.1f", $ARGV[0] * 0.6' "$HOLD")"
242
+ printf 'PREVIEW-FD hold=%ss floor=%ss V1(pipe)=%ss V2(file)=%ss V3(stdin</dev/null)=%ss V4(exec-before-fork)=%ss holder=%s\n' \
243
+ "$HOLD" "$floor" "$v1" "$v2" "$v3" "$v4" "$HOLDER"
244
+ local n
245
+ for n in "$v1" "$v2" "$v3" "$v4"; do
246
+ case "$n" in
247
+ ''|*[!0-9.]*)
248
+ printf 'PREVIEW-FD: UNMEASURED — a variant produced no timing (%s). This is NOT "not reproduced";\n' "$n"
249
+ printf ' the harness failed to measure and says so. worktree-preview.sh unchanged.\n'
250
+ return 4 ;;
251
+ esac
252
+ done
253
+ local ok1 ok2 ok3
254
+ ok1="$(perl -e 'print(($ARGV[0] =~ /^[0-9.]+$/ && $ARGV[0] >= $ARGV[1]) ? 1 : 0)' "$v1" "$floor")"
255
+ ok2="$(perl -e 'print(($ARGV[0] =~ /^[0-9.]+$/ && $ARGV[0] <= 2.0) ? 1 : 0)' "$v2")"
256
+ case "$HOLDER" in none|lsof-missing|reader-not-found|reader-holds-no-pipe|no-shared-pipe-in-launch-tree|'') ok3=0 ;; *) ok3=1 ;; esac
257
+ if [ "$ok1" = 1 ] && [ "$ok2" = 1 ] && [ "$ok3" = 1 ]; then
258
+ printf 'PREVIEW-FD: REPRODUCED (H1 candidate) — a named process in the launch tree holds the reader'"'"'s pipe.\n'
259
+ printf 'PREVIEW-FD: H2 IS NOT EXCLUDED — Mode A cannot separate them. --real is unrun.\n'
260
+ return 0
261
+ fi
262
+ printf 'PREVIEW-FD: NOT REPRODUCED — '
263
+ [ "$ok1" = 1 ] || printf 'V1 did not hold past the floor. '
264
+ [ "$ok2" = 1 ] || printf 'V2 held too (so the pipe is not the discriminator). '
265
+ [ "$ok3" = 1 ] || printf 'no named fd holder (%s). ' "$HOLDER"
266
+ printf 'worktree-preview.sh unchanged.\n'
267
+ return 3
268
+ }
269
+
270
+ case "$MODE" in
271
+ selftest) self_test; exit $? ;;
272
+ real)
273
+ [ -n "$SLUG" ] || { echo "preview-fd-check: --real needs a slug" >&2; exit 4; }
274
+ echo "preview-fd-check: --real is opt-in and UNRUN here — it costs a worktree, a port and ~2.3GB," >&2
275
+ echo " it takes no boot lock, and this box is memory-bound. Run it alone, never in a fleet:" >&2
276
+ echo " PREVIEW_FD_REAL_YES=1 bash .claude/scripts/preview-fd-check.sh --real $SLUG" >&2
277
+ [ "${PREVIEW_FD_REAL_YES:-0}" = 1 ] || exit 4
278
+ echo "preview-fd-check: --real not implemented in this cycle; Mode A is the deliverable." >&2
279
+ exit 4 ;;
280
+ esac
281
+
282
+ printf '== preview-fd-check --sandbox (mimic lifetime %ss)\n' "$HOLD"
283
+ V1="$(run_variant V1 plain pipe)"
284
+ HOLDER="$(cat "$SB/holder" 2>/dev/null)"; HOLDER="${HOLDER:-none}"
285
+ V2="$(run_variant V2 plain file)"
286
+ V3="$(run_variant V3 stdin pipe)"
287
+ V4="$(run_variant V4 execfd pipe)"
288
+ verdict "$V1" "$V2" "$V3" "$V4"
289
+ exit $?
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env bash
2
+ # redirect-lint.sh — `Response.redirect()` is banned under one.ie/web/src/pages/api/.
3
+ #
4
+ # WHY. `Response.redirect()` returns a Response whose headers are IMMUTABLE.
5
+ # The Astro Cloudflare adapter / middleware then writes `Server-Timing` (or the
6
+ # queued `vid` cookie) onto it and the worker throws "Can't modify immutable
7
+ # headers" — a 500 with Cloudflare error code 1101, on the success path only.
8
+ # Measured on production 2026-09-11: /api/composio/redirect and
9
+ # /api/composio/callback both 500.
10
+ #
11
+ # This repo has diagnosed and fixed the same mechanism three separate times
12
+ # before this lint existed:
13
+ # one.ie/web/src/pages/api/og/screenshot.png.ts:33 (the comment + the fix)
14
+ # one.ie/web/src/middleware.ts:746 (the comment + the fix)
15
+ # one.ie/web/src/lib/page-cache-middleware.ts:45 (swallows the throw)
16
+ # The lint is what stops a fourth time.
17
+ #
18
+ # THE FIX, every time:
19
+ # return new Response(null, { status: 302, headers: { Location: <absolute url> } })
20
+ #
21
+ # SCOPE. API routes only — those are the handlers whose response the adapter
22
+ # decorates. Comment lines are exempt: the three files above NAME the pattern in
23
+ # prose, and a lint that is red on a clean tree gets deleted within a week.
24
+ #
25
+ # USAGE
26
+ # bash .claude/scripts/redirect-lint.sh # lint the repo, exit 1 on any hit
27
+ # bash .claude/scripts/redirect-lint.sh --self-test # drive it RED, then GREEN, then
28
+ # # prove a comment stays GREEN
29
+ # EXITS
30
+ # 0 clean 1 violations found 2 usage / self-test failed
31
+ set -euo pipefail
32
+
33
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
34
+ API_DIR="one.ie/web/src/pages/api"
35
+ PATTERN='Response\.redirect('
36
+
37
+ # scan <dir> — every CODE occurrence as `file:line:content`, comments dropped.
38
+ # Empty output means clean. The grep output is CAPTURED first and matched from a
39
+ # here-string: under `set -o pipefail`, `<producer> | grep -q` returns 141 when
40
+ # it matches while the producer is still writing.
41
+ scan() {
42
+ local dir="$1" raw line content trimmed
43
+ raw="$(grep -rn "$PATTERN" "$dir" 2>/dev/null || true)"
44
+ [ -z "$raw" ] && return 0
45
+ while IFS= read -r line; do
46
+ [ -z "$line" ] && continue
47
+ content="${line#*:}" # strip `file:`
48
+ content="${content#*:}" # strip `line:`
49
+ trimmed="$(printf '%s' "$content" | sed 's/^[[:space:]]*//')"
50
+ case "$trimmed" in
51
+ '//'*|'*'*|'/*'*) continue ;; # a comment naming the pattern is not a call
52
+ esac
53
+ printf '%s\n' "$line"
54
+ done <<< "$raw"
55
+ }
56
+
57
+ # lint <dir> [label] — report and set the exit code. The whole main path, so the
58
+ # self-test drives the real thing rather than a re-implementation of it.
59
+ lint() {
60
+ local dir="$1" label="${2:-$dir}" hits count
61
+ if [ ! -d "$dir" ]; then
62
+ echo "[redirect-lint] RED — directory not found: $dir" >&2
63
+ return 2
64
+ fi
65
+ hits="$(scan "$dir")"
66
+ if [ -z "$hits" ]; then
67
+ echo "[redirect-lint] ok — no Response.redirect() under $label"
68
+ return 0
69
+ fi
70
+ count="$(printf '%s\n' "$hits" | wc -l | tr -d ' ')"
71
+ echo "[redirect-lint] RED — $count call site(s) of Response.redirect() under $label:" >&2
72
+ printf '%s\n' "$hits" | sed "s#^$ROOT/##" >&2
73
+ cat >&2 <<'HINT'
74
+
75
+ Response.redirect() headers are immutable; the adapter's Server-Timing write
76
+ then throws "Can't modify immutable headers" (500, cf error 1101). Use:
77
+
78
+ return new Response(null, { status: 302, headers: { Location: <absolute url> } })
79
+ HINT
80
+ return 1
81
+ }
82
+
83
+ # Global on purpose: the EXIT trap body is evaluated after self_test has
84
+ # returned, so a `local` sandbox is already out of scope by then — under
85
+ # `set -u` that made a fully passing self-test exit 1 on its own cleanup.
86
+ sandbox=""
87
+
88
+ self_test() {
89
+ local rc out fails=0
90
+ sandbox="$(mktemp -d "${TMPDIR:-/tmp}/redirect-lint-selftest.XXXXXX")"
91
+ trap 'rm -rf "${sandbox:-}"' EXIT
92
+ mkdir -p "$sandbox/api"
93
+
94
+ # A file that is already correct — present in all three phases, so a green
95
+ # phase proves the scan ran over real content rather than over nothing.
96
+ cat > "$sandbox/api/ok.ts" <<'TS'
97
+ export const GET = ({ url }) =>
98
+ new Response(null, { status: 302, headers: { Location: `${url.origin}/next` } })
99
+ TS
100
+
101
+ # 1 — a real call site must go RED.
102
+ cat > "$sandbox/api/bad.ts" <<'TS'
103
+ export const GET = ({ url }) => Response.redirect(`${url.origin}/next`, 302)
104
+ TS
105
+ set +e
106
+ out="$(lint "$sandbox" "sandbox" 2>&1)"; rc=$?
107
+ set -e
108
+ if [ "$rc" -eq 1 ]; then
109
+ echo " [1/3] planted call site → RED (exit 1) ✓"
110
+ else
111
+ echo " [1/3] planted call site → expected exit 1, got $rc" >&2
112
+ printf '%s\n' "$out" >&2
113
+ fails=$((fails + 1))
114
+ fi
115
+
116
+ # 2 — removing it must go GREEN.
117
+ rm -f "$sandbox/api/bad.ts"
118
+ set +e
119
+ out="$(lint "$sandbox" "sandbox" 2>&1)"; rc=$?
120
+ set -e
121
+ if [ "$rc" -eq 0 ]; then
122
+ echo " [2/3] violation removed → GREEN (exit 0) ✓"
123
+ else
124
+ echo " [2/3] violation removed → expected exit 0, got $rc" >&2
125
+ printf '%s\n' "$out" >&2
126
+ fails=$((fails + 1))
127
+ fi
128
+
129
+ # 3 — a COMMENT naming the pattern must stay GREEN. This is the exclusion the
130
+ # three prior fixes depend on; untested, someone "simplifies" it back into a
131
+ # false positive and the lint starts firing on its own documentation.
132
+ cat > "$sandbox/api/documented.ts" <<'TS'
133
+ export const GET = ({ url }) => {
134
+ // Response.redirect() returns an immutable Response — the adapter's
135
+ // Server-Timing header write then throws "Can't modify immutable headers".
136
+ /* Response.redirect( in a block comment too */
137
+ return new Response(null, { status: 302, headers: { Location: `${url.origin}/next` } })
138
+ }
139
+ TS
140
+ set +e
141
+ out="$(lint "$sandbox" "sandbox" 2>&1)"; rc=$?
142
+ set -e
143
+ if [ "$rc" -eq 0 ]; then
144
+ echo " [3/3] comment naming the pattern → GREEN (exit 0) ✓"
145
+ else
146
+ echo " [3/3] comment naming the pattern → expected exit 0, got $rc" >&2
147
+ printf '%s\n' "$out" >&2
148
+ fails=$((fails + 1))
149
+ fi
150
+
151
+ if [ "$fails" -ne 0 ]; then
152
+ echo "[redirect-lint] --self-test FAILED ($fails/3)" >&2
153
+ return 2
154
+ fi
155
+ echo "[redirect-lint] --self-test ok (3/3)"
156
+ return 0
157
+ }
158
+
159
+ case "${1:-}" in
160
+ --self-test) self_test ;;
161
+ --scan-dir) lint "${2:?--scan-dir needs a directory}" ;;
162
+ -h|--help)
163
+ sed -n '1,32p' "${BASH_SOURCE[0]}"
164
+ exit 2 ;;
165
+ '') lint "$ROOT/$API_DIR" "$API_DIR" ;;
166
+ *)
167
+ echo "usage: redirect-lint.sh [--self-test|--scan-dir <dir>]" >&2
168
+ exit 2 ;;
169
+ esac
@@ -92,9 +92,14 @@ _link_deps() {
92
92
  # The receipt keys for the tree standing in $1. Prints one 64-hex key per lane.
93
93
  # TEST_CACHE_KEY_ONLY=1 makes every test-cached.sh in the fan-out print its key
94
94
  # and exit 0 without running vitest, so this costs ~1s and cannot start a suite.
95
+ #
96
+ # Each line is "<lane> <key>". The lane is read off test-lanes.sh's own
97
+ # `───── <lane> lane (rc=N) ─────` header that precedes the key, so a new lane
98
+ # names itself; a key with no header (the single-run fallback) is lane `suite`.
95
99
  _receipt_keys() {
96
100
  ( cd "$1" && TEST_CACHE_KEY_ONLY=1 bash .claude/scripts/test-full.sh 2>/dev/null ) \
97
- | grep -Eo '^[0-9a-f]{64}$'
101
+ | awk '/ lane \(rc=/ { for (i = 1; i <= NF; i++) if ($i == "lane") lane = $(i-1); next }
102
+ /^[0-9a-f]{64}$/ { print (lane == "" ? "suite" : lane), $0 }'
98
103
  }
99
104
 
100
105
  # The stamp for a key, if one exists. test-cached.sh mangles the whole stamp path
@@ -165,16 +170,27 @@ cmd_promote() {
165
170
  bad "no receipt key could be computed — test-full.sh printed no lane key"
166
171
  return 9
167
172
  fi
168
- local n_keys=0 missing=0 stamps=()
169
- while IFS= read -r k; do
173
+ # THE TYPEDB LANE DOES NOT GATE PRODUCTION (operator ruling 2026-09-13). The
174
+ # request path reads the edge snapshot, never TypeDB (CLAUDE.md § The brain and
175
+ # the edge), and the lane's reds were the shared cluster timing out, not code:
176
+ # three promotes in a row held a green pool lane behind `upstream 500: aborted
177
+ # due to timeout`. deploy.sh already runs it non-blocking
178
+ # (TYPEDB_LANE_NONBLOCKING=1); promote now weighs it the same way, so the two
179
+ # doors can no longer disagree. The lane is still READ and printed — a missing
180
+ # stamp is a named warning, never silence. RELEASE_TYPEDB_BLOCKING=1 restores
181
+ # the hard gate.
182
+ local n_keys=0 missing=0 stamps=() lane k
183
+ while read -r lane k; do
170
184
  [ -n "$k" ] || continue
171
185
  n_keys=$((n_keys+1))
172
186
  local s
173
187
  if s="$(_stamp_for "$k")"; then
174
188
  stamps+=("$s")
175
- ok "receipt ${k:0:12} — full suite PASSED $(cat "$s" 2>/dev/null)"
189
+ ok "receipt ${k:0:12} ($lane lane) — full suite PASSED $(cat "$s" 2>/dev/null)"
190
+ elif [ "$lane" = "typedb" ] && [ "${RELEASE_TYPEDB_BLOCKING:-0}" != "1" ]; then
191
+ say " warn receipt ${k:0:12} (typedb lane) — no green stamp; non-blocking for production"
176
192
  else
177
- bad "receipt ${k:0:12} — NO green full-suite stamp for this tree"
193
+ bad "receipt ${k:0:12} ($lane lane) — NO green full-suite stamp for this tree"
178
194
  missing=$((missing+1))
179
195
  fi
180
196
  done <<< "$keys"
@@ -276,8 +292,26 @@ _self_test() {
276
292
  # 1. a tree with no receipt is REFUSED (3)
277
293
  _run "$base"; _assert 3 $? "no receipt refuses"
278
294
 
295
+ # 2a. the typedb lane does not gate production: stamp every OTHER lane and the
296
+ # promote succeeds; RELEASE_TYPEDB_BLOCKING=1 restores the refusal. Red half
297
+ # first — a non-typedb lane left unstamped must still refuse.
298
+ local lane k
299
+ while read -r lane k; do
300
+ [ -n "$k" ] && [ "$lane" = "typedb" ] && date -u +%Y-%m-%dT%H:%M:%SZ > "$sbcache/selftest-$k.pass"
301
+ done < <(_receipt_keys "$sbwt")
302
+ _run "$base"; _assert 3 $? "a green typedb lane cannot carry an unstamped pool lane"
303
+ rm -f "$sbcache"/selftest-*.pass
304
+ while read -r lane k; do
305
+ [ -n "$k" ] && [ "$lane" != "typedb" ] && date -u +%Y-%m-%dT%H:%M:%SZ > "$sbcache/selftest-$k.pass"
306
+ done < <(_receipt_keys "$sbwt")
307
+ if _receipt_keys "$sbwt" | grep -q '^typedb '; then
308
+ RELEASE_TYPEDB_BLOCKING=1 _run "$base"; _assert 3 $? "RELEASE_TYPEDB_BLOCKING=1 refuses an unstamped typedb lane"
309
+ _run "$base"; _assert 0 $? "an unstamped typedb lane does not gate production"
310
+ else
311
+ echo " FAIL no typedb lane key found — the non-blocking branch went unproven"; fails=$((fails+1))
312
+ fi
313
+
279
314
  # 2. plant a PASS stamp for every lane key of that tree — promotion succeeds
280
- local k
281
315
  while IFS= read -r k; do
282
316
  [ -n "$k" ] && date -u +%Y-%m-%dT%H:%M:%SZ > "$sbcache/selftest-$k.pass"
283
317
  done < <(TEST_CACHE_KEY_ONLY=1 bash -c "cd '$sbwt' && bash .claude/scripts/test-full.sh" 2>/dev/null \
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ # resume-lost-sessions.sh — reopen the Claude Code sessions killed by the
3
+ # 2026-09-16 12:14 reboot, one Ghostty tab each.
4
+ #
5
+ # manifest: monorepo-only
6
+ #
7
+ # WHY THIS EXISTS. The box rebooted at 12:14 and took 8 live sessions with it.
8
+ # Every session's COMMITTED work survived — all of it is in production at
9
+ # c716295ac (verified by probe: /buttons 200, /ambient 404). What died was the
10
+ # conversation: the half-finished next step each one was holding in context.
11
+ # `--resume <id>` restores that context; the transcripts are on disk under
12
+ # ~/.claude/projects/-Users-toc-Server-one-ie/.
13
+ #
14
+ # Usage:
15
+ # bash .claude/scripts/resume-lost-sessions.sh --list # names + status, no side effects
16
+ # bash .claude/scripts/resume-lost-sessions.sh <key>... # open those tabs
17
+ # bash .claude/scripts/resume-lost-sessions.sh --unfinished
18
+ set -uo pipefail
19
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
20
+
21
+ # key|session-id|status|what it was doing
22
+ ROWS=(
23
+ "media|baa8d065-7e0a-44a5-be4a-0b65ea39252f|MID-CYCLE|@chairman team: pages/media — view, upload, generate media and insert into pages. Was at /close when it died."
24
+ "blocks|3aa1bc43-0029-4ddd-bd4e-bc76695dd74c|LANDED|One block-insert component shared by page editor and documents. Shipped ca89b9c58 + 5b74e7f4d. Was at /close."
25
+ "components|a82ef0c3-3863-40ed-84b8-49502e42a42e|MID-CYCLE|Parallel agents enhancing the hundreds of page-editor components. 9 turns in, feat/components merged — more was planned."
26
+ "pages-links|bd004874-cd1e-4298-bd8d-a733721c8af0|OPEN Q|Feature pages (pages/analytics etc.) — wanted links to them, 'on prod'. A question, no commit."
27
+ "models|95bead26-d470-4c4a-84d7-41dabbf54144|LANDED|Rank models by intelligence and speed in the + picker. Shipped: src/lib/chat/model-rank.ts."
28
+ "ticker|d8b1424b-3bfe-4fa5-92bc-fa4fc197ed4e|LANDED|Real streamed 'thinking' text instead of pretend. Shipped a99651b48. Last words: 'ship it all'."
29
+ "chat-url|79bc3b97-dab2-4f3d-a4c1-81b163917e4f|LANDED|Can chat change the page URL the user is on. Shipped d6a41f2f5 + text/chat-navigation-docs.md."
30
+ "health|4c2ff180-a2d9-4b37-9cef-9ce9740c5fc9|ONE-SHOT|'health' — the doctor pass. Nothing to resume unless you want another."
31
+ )
32
+
33
+ _row() { local k=$1; for r in "${ROWS[@]}"; do [ "${r%%|*}" = "$k" ] && { echo "$r"; return 0; }; done; return 1; }
34
+
35
+ if [ $# -eq 0 ] || [ "${1:-}" = "--list" ]; then
36
+ printf '\n %-13s %-10s %s\n' KEY STATUS "WHAT IT WAS DOING"
37
+ for r in "${ROWS[@]}"; do
38
+ IFS='|' read -r k id st what <<<"$r"
39
+ printf ' %-13s %-10s %s\n' "$k" "$st" "${what:0:88}"
40
+ done
41
+ printf '\n open one: bash %s <key>\n' "${BASH_SOURCE[0]#"$ROOT/"}"
42
+ printf ' unfinished only: bash %s --unfinished\n\n' "${BASH_SOURCE[0]#"$ROOT/"}"
43
+ exit 0
44
+ fi
45
+
46
+ KEYS=("$@")
47
+ if [ "${1:-}" = "--unfinished" ]; then
48
+ KEYS=()
49
+ for r in "${ROWS[@]}"; do
50
+ IFS='|' read -r k id st what <<<"$r"
51
+ case "$st" in MID-CYCLE|"OPEN Q") KEYS+=("$k");; esac
52
+ done
53
+ echo " unfinished: ${KEYS[*]}"
54
+ fi
55
+
56
+ for k in "${KEYS[@]}"; do
57
+ r="$(_row "$k")" || { echo " ✗ unknown key: $k (try --list)" >&2; continue; }
58
+ IFS='|' read -r _ id st what <<<"$r"
59
+ echo " → $k ($st) $id"
60
+ osascript <<OSA >/dev/null 2>&1 || echo " (could not open a tab — run by hand: cd $ROOT && claude --resume $id)"
61
+ tell application "Ghostty" to activate
62
+ tell application "System Events" to keystroke "t" using command down
63
+ delay 0.7
64
+ tell application "System Events" to keystroke "cd $ROOT && claude --resume $id"
65
+ tell application "System Events" to key code 36
66
+ OSA
67
+ sleep 1
68
+ done