@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,448 @@
1
+ #!/usr/bin/env bash
2
+ # health.sh — is this box healthy enough to work on, and is the estate up?
3
+ #
4
+ # manifest: portable
5
+ #
6
+ # WHY (measured 2026-09-05). Answering "why is localhost slow" took a dozen
7
+ # ad-hoc commands: uptime, vm_stat, sysctl vm.swapusage, three greps of ps, a
8
+ # gate-reaper dry run, an ls of the governor dir, and a curl per surface. Every
9
+ # one of those numbers already had a home; nothing put them on one page. The
10
+ # session that prompted this found 19 orphaned workerd processes holding the box
11
+ # at 20.5G of 21.5G swap — visible in ps all along, in a shape nobody was
12
+ # looking at.
13
+ #
14
+ # It reuses rather than restates: machine-check.sh owns the process/memory read,
15
+ # gate-reaper.sh --dry-run owns the orphan shape, govern.sh owns the arithmetic.
16
+ # This adds the VERDICT and the service probes, and nothing else.
17
+ #
18
+ # Usage: health.sh [--box] [--services] [--json] [--watch N] [--self-test]
19
+ # (no flags) both halves, human-readable
20
+ # --box the machine only --services the estate only
21
+ # --json machine-readable, both halves, no colour
22
+ # --watch N re-run every N seconds until interrupted
23
+ # --fix reclaim what nothing is coming back for, then re-read
24
+ # --dry-run with --fix, name every kill instead of making it
25
+ # --signal emit the box verdict into the world, tagged `engineering`, so
26
+ # the doctor sees the Mac in the same graph as the estate
27
+ # --self-test prove the verdict thresholds bite (red proof included)
28
+ #
29
+ # WHAT --fix WILL AND WILL NOT TOUCH. It reclaims three shapes, each one dead by
30
+ # a test that cannot be true of live work:
31
+ # 1. orphaned gates and workerd — delegated whole to gate-reaper.sh, whose own
32
+ # checker proves it spares a live parent and a fresh reparent
33
+ # 2. language servers rooted in a WORKTREE — the editor should not have opened
34
+ # a project there at all (.vscode/settings.json excludes it); the real
35
+ # tsserver for the main tree is never a candidate
36
+ # 3. dev servers in a worktree with NO live claude session — the session that
37
+ # started it is gone, so nobody is watching the port
38
+ # It will NEVER kill a claude session, anything holding a governor slot, or
39
+ # anything running in the main tree. Those are judgement calls a script does not
40
+ # get to make: a session classifier misfired twice on 2026-09-05, which is the
41
+ # reason this list is shapes-with-a-dead-owner and not "looks idle".
42
+ set -uo pipefail
43
+
44
+ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
45
+ # shellcheck source=lib/govern.sh
46
+ . "$HERE/lib/govern.sh" 2>/dev/null || true
47
+
48
+ WANT_BOX=1; WANT_SVC=1; AS_JSON=0; WATCH=0; SELFTEST=0; FIX=0; DRY=0; SIGNAL=0
49
+ while [ $# -gt 0 ]; do
50
+ case "$1" in
51
+ --box) WANT_SVC=0 ;;
52
+ --services) WANT_BOX=0 ;;
53
+ --json) AS_JSON=1 ;;
54
+ --watch) WATCH="${2:-10}"; shift ;;
55
+ --fix) FIX=1 ;;
56
+ --dry-run) DRY=1 ;;
57
+ --signal) SIGNAL=1 ;;
58
+ --self-test) SELFTEST=1 ;;
59
+ -h|--help) sed -n '2,24p' "${BASH_SOURCE[0]}"; exit 0 ;;
60
+ *) echo "health: unknown flag $1" >&2; exit 2 ;;
61
+ esac
62
+ shift
63
+ done
64
+
65
+ C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_BAD=$'\033[31m'; C_DIM=$'\033[2m'; C_OFF=$'\033[0m'
66
+ [ -t 1 ] || { C_OK=""; C_WARN=""; C_BAD=""; C_DIM=""; C_OFF=""; }
67
+ [ "$AS_JSON" = "1" ] && { C_OK=""; C_WARN=""; C_BAD=""; C_DIM=""; C_OFF=""; }
68
+
69
+ # ── the box ───────────────────────────────────────────────────────────────────
70
+ # Every number here has exactly one source. load and swap come from the kernel;
71
+ # headroom comes from govern.sh so the dashboard can never disagree with the
72
+ # governor that actually admits gates.
73
+ cores() { sysctl -n hw.ncpu 2>/dev/null || nproc 2>/dev/null || echo 1; }
74
+ load1() { uptime | sed -E 's/.*load averages?: *([0-9.]+).*/\1/'; }
75
+ swap_used_mb() { sysctl -n vm.swapusage 2>/dev/null | sed -E 's/.*used = ([0-9.]+)M.*/\1/' | cut -d. -f1; }
76
+ swap_free_mb() { sysctl -n vm.swapusage 2>/dev/null | sed -E 's/.*free = ([0-9.]+)M.*/\1/' | cut -d. -f1; }
77
+ orphan_count() {
78
+ [ -x "$HERE/gate-reaper.sh" ] || { echo 0; return; }
79
+ bash "$HERE/gate-reaper.sh" --once --dry-run 2>/dev/null | grep -c 'WOULD reap' || true
80
+ }
81
+ slots_held() { ls -d "${TMPDIR:-/tmp}/one-govern"/lock-slot-* 2>/dev/null | wc -l | tr -d ' '; }
82
+ sessions() { ps -Ao command= 2>/dev/null | grep -cE '^claude( --|$)' || true; }
83
+
84
+ # VERDICT — the whole point of the file. Thresholds are the ones the harness
85
+ # already uses: machine-check calls load healthy under `cores`, and govern.sh
86
+ # refuses to fund a second cycle below one cycle's price. A dashboard that
87
+ # invents its own thresholds would drift from the governor within a week.
88
+ box_verdict() {
89
+ local c load_i head orph swapfree
90
+ c=$(cores); load_i=$(printf '%.0f' "$(load1)"); head=$(gate_headroom 2>/dev/null || echo 99)
91
+ orph=$(orphan_count); swapfree=$(swap_free_mb)
92
+ if [ "$orph" -gt 0 ]; then echo "DEGRADED|${orph} abandoned gate(s)/orphan(s) burning the box — gate-reaper.sh --once"; return; fi
93
+ if [ "$load_i" -gt $(( c * 2 )) ]; then echo "UNHEALTHY|load ${load_i} is over 2x the ${c} cores — gates are queueing, not running"; return; fi
94
+ if [ "${swapfree:-9999}" -lt 512 ]; then echo "UNHEALTHY|swap has ${swapfree}MB free — the box is paging, every gate runs long"; return; fi
95
+ if [ "$load_i" -gt "$c" ]; then echo "DEGRADED|load ${load_i} is over ${c} cores — work is contending"; return; fi
96
+ if [ "${head:-99}" -lt 2 ]; then echo "DEGRADED|memory funds only ${head} concurrent gate — close idle sessions to widen"; return; fi
97
+ echo "HEALTHY|load ${load_i} on ${c} cores, memory funds ${head} gates"
98
+ }
99
+
100
+ print_box() {
101
+ local v state why
102
+ v=$(box_verdict); state="${v%%|*}"; why="${v#*|}"
103
+ local col="$C_OK"; [ "$state" = "DEGRADED" ] && col="$C_WARN"; [ "$state" = "UNHEALTHY" ] && col="$C_BAD"
104
+ printf '%s\n' "── box ─────────────────────────────────────────"
105
+ printf ' %s%s%s %s\n' "$col" "$state" "$C_OFF" "$why"
106
+ printf ' %sload %s · %s cores · swap %sM used / %sM free · gates funded %s · slots held %s · sessions %s · orphans %s%s\n' \
107
+ "$C_DIM" "$(load1)" "$(cores)" "$(swap_used_mb)" "$(swap_free_mb)" \
108
+ "$(gate_headroom 2>/dev/null || echo '?')" "$(slots_held)" "$(sessions)" "$(orphan_count)" "$C_OFF"
109
+ }
110
+
111
+ # ── the estate ────────────────────────────────────────────────────────────────
112
+ # The URL/assert pairs are deploy.sh's, deliberately. If a probe here disagrees
113
+ # with the one that gates a deploy, this file is the one that is wrong.
114
+ SURFACES="one.ie|https://one.ie/api/health|\"status\":\"ok\"
115
+ api|https://api.one.ie/health|
116
+ channels|https://channels.one.ie/health|
117
+ pay|https://pay.one.ie/status|\"ok\""
118
+
119
+ probe() { # name url assert -> "name|state|code|ms|note"
120
+ local name="$1" url="$2" assert="$3" body code ms t0 t1
121
+ t0=$(date +%s%N 2>/dev/null || echo 0)
122
+ body=$(curl -sL --max-time 8 -w $'\n%{http_code}' "$url" 2>/dev/null)
123
+ t1=$(date +%s%N 2>/dev/null || echo 0)
124
+ ms=$(( (t1 - t0) / 1000000 )); [ "$ms" -lt 0 ] && ms=0
125
+ code="${body##*$'\n'}"; body="${body%$'\n'*}"
126
+ if [ "$code" != "200" ]; then echo "${name}|DOWN|${code:-000}|${ms}|no 200"; return; fi
127
+ if [ -n "$assert" ] && ! printf '%s' "$body" | grep -qF "$assert"; then
128
+ echo "${name}|DEGRADED|${code}|${ms}|200 but missing ${assert}"; return
129
+ fi
130
+ echo "${name}|UP|${code}|${ms}|"
131
+ }
132
+
133
+ print_services() {
134
+ printf '%s\n' "── estate ──────────────────────────────────────"
135
+ local line name url assert r state code ms note col
136
+ while IFS='|' read -r name url assert; do
137
+ [ -n "$name" ] || continue
138
+ r=$(probe "$name" "$url" "$assert")
139
+ IFS='|' read -r _ state code ms note <<<"$r"
140
+ col="$C_OK"; [ "$state" = "DEGRADED" ] && col="$C_WARN"; [ "$state" = "DOWN" ] && col="$C_BAD"
141
+ printf ' %s%-9s%s %-4s %5sms %s%s%s\n' "$col" "$state" "$C_OFF" "$code" "$ms" "$C_DIM" "${name} ${note}" "$C_OFF"
142
+ done <<<"$SURFACES"
143
+ printf ' %s%-9s%s — — %ssync · backup — cron-only, no health endpoint exists%s\n' \
144
+ "$C_DIM" "UNKNOWN" "$C_OFF" "$C_DIM" "$C_OFF"
145
+ }
146
+
147
+ print_json() {
148
+ local v state why
149
+ v=$(box_verdict); state="${v%%|*}"; why="${v#*|}"
150
+ printf '{"box":{"verdict":"%s","why":"%s","load":%s,"cores":%s,"swap_used_mb":%s,"swap_free_mb":%s,"gates_funded":%s,"slots_held":%s,"sessions":%s,"orphans":%s},"services":[' \
151
+ "$state" "$why" "$(load1)" "$(cores)" "$(swap_used_mb)" "$(swap_free_mb)" \
152
+ "$(gate_headroom 2>/dev/null || echo 0)" "$(slots_held)" "$(sessions)" "$(orphan_count)"
153
+ local first=1 name url assert r s code ms note
154
+ while IFS='|' read -r name url assert; do
155
+ [ -n "$name" ] || continue
156
+ r=$(probe "$name" "$url" "$assert"); IFS='|' read -r _ s code ms note <<<"$r"
157
+ [ $first = 1 ] || printf ','; first=0
158
+ printf '{"name":"%s","state":"%s","code":"%s","ms":%s}' "$name" "$s" "$code" "$ms"
159
+ done <<<"$SURFACES"
160
+ printf ']}\n'
161
+ }
162
+
163
+ # ── signal ────────────────────────────────────────────────────────────────────
164
+ # The estate's half (health:diagnose) already writes its verdict back as weighted
165
+ # paths. This is the Mac's half of the same graph: same tag vocabulary, so a
166
+ # symptom here ranks beside a symptom there instead of living in a terminal
167
+ # nobody reads. Tagged `engineering` because that is the doctor's bare stake —
168
+ # a namespaced tag would match zero subscribers (root CLAUDE.md § the stake is
169
+ # written as BARE words).
170
+ emit_verdict() {
171
+ local lib="$HERE/../hooks/lib/signal.sh"
172
+ [ -f "$lib" ] || return 0
173
+ local v state why
174
+ v=$(box_verdict); state="${v%%|*}"; why="${v#*|}"
175
+ # shellcheck source=../hooks/lib/signal.sh
176
+ . "$lib" 2>/dev/null || return 0
177
+ command -v emit_world >/dev/null 2>&1 || return 0
178
+ emit_world "health" "$([ "$state" = "HEALTHY" ] && echo fyi || echo attention)" \
179
+ "engineering,health,box" "box is ${state}: ${why}" \
180
+ "verdict=${state}" "load=$(load1)" "cores=$(cores)" \
181
+ "swap_free_mb=$(swap_free_mb)" "gates_funded=$(gate_headroom 2>/dev/null || echo 0)" \
182
+ "orphans=$(orphan_count)" "sessions=$(sessions)" 2>/dev/null || true
183
+ }
184
+
185
+ run_once() {
186
+ if [ "$AS_JSON" = "1" ]; then print_json; return; fi
187
+ [ "$FIX" = "1" ] && run_fix
188
+ [ "$SIGNAL" = "1" ] && emit_verdict
189
+ [ "$WANT_BOX" = "1" ] && print_box
190
+ [ "$WANT_BOX" = "1" ] && [ "$WANT_SVC" = "1" ] && echo
191
+ [ "$WANT_SVC" = "1" ] && print_services
192
+ return 0
193
+ }
194
+
195
+ # ── fix ───────────────────────────────────────────────────────────────────────
196
+ # Every candidate is named with the reason it is dead, so a --dry-run reads as
197
+ # an argument and not as a list of pids.
198
+ REPO_ROOT="$(cd "$HERE/../.." && pwd)"
199
+
200
+ # Walk REAL descendants via ppid — never signal a process GROUP. A pgid can be
201
+ # shared with an ancestor shell/session when a child is spawned without
202
+ # setsid(2) (tsserver commonly is), and `kill -TERM -$pgid` then kills
203
+ # everything sharing that group, session included. Measured 2026-09-15: this
204
+ # exact line took down 3 of 6 live `claude` sessions whose tsserver children
205
+ # happened to share the launching shell's process group. ppid is the only
206
+ # honest boundary — it can never reach outside the tree rooted at $pid.
207
+ _kill_tree() { # pid -> TERM pid and every process/pid rooted at pid, leaves ancestors untouched
208
+ local pid="$1" child
209
+ for child in $(pgrep -P "$pid" 2>/dev/null); do
210
+ _kill_tree "$child"
211
+ done
212
+ kill -TERM "$pid" 2>/dev/null || true
213
+ }
214
+
215
+ _reap() { # pid why
216
+ local pid="$1" why="$2" rss
217
+ rss=$(( $(ps -o rss= -p "$pid" 2>/dev/null | tr -d ' ' || echo 0) / 1024 ))
218
+ if [ "$DRY" = "1" ]; then printf ' %sWOULD%s reclaim pid=%-7s %5sMB %s\n' "$C_WARN" "$C_OFF" "$pid" "$rss" "$why"; return 0; fi
219
+ _kill_tree "$pid"
220
+ printf ' %sreclaimed%s pid=%-7s %5sMB %s\n' "$C_OK" "$C_OFF" "$pid" "$rss" "$why"
221
+ }
222
+
223
+ # A governor slot owner is live work by definition — it is holding the lock the
224
+ # whole box queues behind.
225
+ _holds_slot() {
226
+ local pid="$1" d
227
+ for d in "${TMPDIR:-/tmp}/one-govern"/lock-slot-*; do
228
+ [ -d "$d" ] || continue
229
+ [ "$(cat "$d/pid" 2>/dev/null)" = "$pid" ] && return 0
230
+ done
231
+ return 1
232
+ }
233
+
234
+ # Is any live claude session cwd'd inside this worktree? lsof on the session pid
235
+ # is the only honest test — a worktree path in a command line proves nothing.
236
+ _worktree_has_session() {
237
+ local wt="$1" pid
238
+ for pid in $(ps -Ao pid=,command= 2>/dev/null | awk '$2 ~ /claude$/ || / claude --/ {print $1}'); do
239
+ lsof -a -p "$pid" -d cwd -Fn 2>/dev/null | grep -q "^n${wt}" && return 0
240
+ done
241
+ return 1
242
+ }
243
+
244
+ fix_orphans() { # delegated whole — this file must not own a second reaper
245
+ [ -x "$HERE/gate-reaper.sh" ] || return 0
246
+ local out
247
+ if [ "$DRY" = "1" ]; then out=$(bash "$HERE/gate-reaper.sh" --once --dry-run 2>/dev/null)
248
+ else out=$(bash "$HERE/gate-reaper.sh" --once 2>/dev/null); fi
249
+ [ -n "$out" ] && printf '%s\n' "$out" | sed 's/^/ /'
250
+ return 0
251
+ }
252
+
253
+ fix_worktree_lsp() {
254
+ local pid root rss
255
+ for pid in $(pgrep -f 'tsserver.js|typescript/lib/tsserver' 2>/dev/null); do
256
+ _holds_slot "$pid" && continue
257
+ root=$(lsof -nP -p "$pid" 2>/dev/null | grep -oE "${REPO_ROOT}/\.claude/worktrees/[^/]+" | head -1)
258
+ [ -n "$root" ] || continue
259
+ _reap "$pid" "language server indexing $(basename "$root") — the editor config excludes worktrees"
260
+ done
261
+ }
262
+
263
+ fix_dead_dev_servers() {
264
+ # Resolve each dev server by its CWD, never by string-matching its argv. The
265
+ # first draft grepped pids and worktree paths out of one ps stream and paired
266
+ # them with `paste - -`; when a server's argv carried no worktree path the
267
+ # columns silently shifted and it paired one pid with ANOTHER pid. A kill list
268
+ # built by that is a coin flip, so cwd it is.
269
+ local pid cwd wt
270
+ for pid in $(pgrep -f 'bin/astro dev' 2>/dev/null); do
271
+ _holds_slot "$pid" && continue
272
+ cwd=$(lsof -a -p "$pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | head -1)
273
+ case "$cwd" in
274
+ "$REPO_ROOT"/.claude/worktrees/*) ;;
275
+ *) continue ;;
276
+ esac
277
+ wt="${cwd#"$REPO_ROOT"/.claude/worktrees/}"; wt="$REPO_ROOT/.claude/worktrees/${wt%%/*}"
278
+ _worktree_has_session "$wt" && continue
279
+ _reap "$pid" "dev server in $(basename "$wt") — no live session is cwd'd there"
280
+ done
281
+ }
282
+
283
+ run_fix() {
284
+ printf '%s\n' "── reclaiming ──────────────────────────────────"
285
+ [ "$DRY" = "1" ] && printf ' %sdry run — nothing will be killed%s\n' "$C_DIM" "$C_OFF"
286
+ local before after
287
+ before=$(swap_free_mb)
288
+ fix_orphans
289
+ fix_worktree_lsp
290
+ fix_dead_dev_servers
291
+ sleep 2
292
+ after=$(swap_free_mb)
293
+ printf ' %sswap free %sM -> %sM%s\n\n' "$C_DIM" "$before" "$after" "$C_OFF"
294
+ }
295
+
296
+ # ── self-test ─────────────────────────────────────────────────────────────────
297
+ # Proves the two things that can silently rot: the verdict thresholds, and the
298
+ # probe's willingness to call a 200 that lacks its assert DEGRADED rather than UP.
299
+ self_test() {
300
+ local fails=0
301
+ ok() { printf ' \033[32mPASS\033[0m %s\n' "$1"; }
302
+ bad() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; fails=$((fails + 1)); }
303
+ echo "── health.sh: the verdict bites, and a bad 200 is not UP ──"
304
+
305
+ cores() { echo 10; }; gate_headroom() { echo 4; }; orphan_count() { echo 0; }
306
+ load1() { echo 2.0; }; swap_free_mb() { echo 4000; }
307
+ [ "$(box_verdict)" != "${_x:-}" ] && case "$(box_verdict)" in HEALTHY*) ok "C1 quiet box reads HEALTHY";; *) bad "C1 quiet box: $(box_verdict)";; esac
308
+
309
+ load1() { echo 25.0; }
310
+ case "$(box_verdict)" in UNHEALTHY*) ok "C2 load over 2x cores reads UNHEALTHY";; *) bad "C2 got: $(box_verdict)";; esac
311
+
312
+ load1() { echo 2.0; }; swap_free_mb() { echo 100; }
313
+ case "$(box_verdict)" in UNHEALTHY*) ok "C3 swap under 512MB free reads UNHEALTHY";; *) bad "C3 got: $(box_verdict)";; esac
314
+
315
+ swap_free_mb() { echo 4000; }; orphan_count() { echo 3; }
316
+ case "$(box_verdict)" in DEGRADED*orphan*) ok "C4 orphans read DEGRADED and name the fix";; *) bad "C4 got: $(box_verdict)";; esac
317
+
318
+ orphan_count() { echo 0; }; gate_headroom() { echo 1; }
319
+ case "$(box_verdict)" in DEGRADED*funds*) ok "C5 headroom of 1 reads DEGRADED";; *) bad "C5 got: $(box_verdict)";; esac
320
+
321
+ # A 200 whose body lost its assert is the failure deploy.sh's probe exists to
322
+ # catch — a worker that boots and serves the wrong app.
323
+ curl() { printf 'not the droid\n200'; }
324
+ case "$(probe x http://stub '"status":"ok"')" in *"|DEGRADED|"*) ok "C6 a 200 missing its assert is DEGRADED, not UP";; *) bad "C6 got: $(probe x http://stub '\"status\":\"ok\"')";; esac
325
+ curl() { printf '{"status":"ok"}\n200'; }
326
+ case "$(probe x http://stub '"status":"ok"')" in *"|UP|"*) ok "C7 a 200 carrying its assert is UP";; *) bad "C7 got: $(probe x http://stub)";; esac
327
+
328
+ # ── the --fix rules: each candidate must be dead by a test live work fails ──
329
+ # _reap is replaced by a recorder, so nothing is killed and the LIST is the
330
+ # assertion. That list is the whole safety argument of --fix.
331
+ REAPED=""
332
+ _reap() { REAPED="$REAPED $1"; }
333
+ REPO_ROOT="/repo"
334
+
335
+ pgrep() { echo 4242; }
336
+ lsof() { echo "n/repo/.claude/worktrees/wt-a/app/web"; } # generic on purpose: a portable script must name no monorepo path
337
+ _holds_slot() { return 1; }
338
+ _worktree_has_session() { return 1; }
339
+
340
+ REAPED=""; fix_worktree_lsp
341
+ case "$REAPED" in *4242*) ok "F1 a language server rooted in a worktree is a candidate";; *) bad "F1 missed it: '$REAPED'";; esac
342
+
343
+ _holds_slot() { return 0; }
344
+ REAPED=""; fix_worktree_lsp
345
+ case "$REAPED" in *4242*) bad "F2 killed a language server that holds a governor slot";; *) ok "F2 a slot holder is spared — it is the lock the box queues behind";; esac
346
+ _holds_slot() { return 1; }
347
+
348
+ REAPED=""; fix_dead_dev_servers
349
+ case "$REAPED" in *4242*) ok "F3 a dev server in a session-less worktree is a candidate";; *) bad "F3 missed it: '$REAPED'";; esac
350
+
351
+ _worktree_has_session() { return 0; }
352
+ REAPED=""; fix_dead_dev_servers
353
+ case "$REAPED" in *4242*) bad "F4 killed a dev server whose worktree HAS a live session";; *) ok "F4 a worktree with a live session is spared";; esac
354
+ _worktree_has_session() { return 1; }
355
+
356
+ # A dev server in the MAIN tree is never a candidate — the cwd case only
357
+ # admits paths under .claude/worktrees/.
358
+ lsof() { echo "n/repo/app/web"; }
359
+ REAPED=""; fix_dead_dev_servers
360
+ case "$REAPED" in *4242*) bad "F5 killed a dev server running in the MAIN tree";; *) ok "F5 the main tree's dev server is never a candidate";; esac
361
+
362
+ # --dry-run must not kill. Restore the real _reap and prove its dry path never
363
+ # reaches kill by stubbing kill itself as a recorder.
364
+ unset -f _reap 2>/dev/null || true
365
+ eval "$(sed -n '/^_reap() {/,/^}/p' "${BASH_SOURCE[0]}")"
366
+ KILLED=""; kill() { KILLED="$KILLED $*"; }
367
+ DRY=1; out=$(_reap 4242 "stub");
368
+ case "$out$KILLED" in *WOULD*) [ -z "$KILLED" ] && ok "F6 --dry-run names the kill and never calls it" || bad "F6 dry run called kill: $KILLED";; *) bad "F6 dry run printed: $out";; esac
369
+ unset -f kill 2>/dev/null || true
370
+
371
+ # The red proofs below run the file AGAINST ITSELF. Without this guard the
372
+ # gutted child runs them too, and each child spawns two more — the first
373
+ # version of R2 died on `mktemp: File exists`, which was the recursion, not
374
+ # the template.
375
+ if [ "${HEALTH_SELFTEST_CHILD:-0}" = "1" ]; then
376
+ if [ $fails -eq 0 ]; then echo "ALL GREEN (child)"; return 0; fi
377
+ echo "RED — $fails check(s) failed"; return 1
378
+ fi
379
+
380
+ # RED PROOF — take the REAL file, delete the swap arm, and run its OWN
381
+ # self-test. C3 must fail there. Stubbing box_verdict to a constant would
382
+ # prove nothing about the arm; this proves the assertion is load-bearing.
383
+ local gutted anchor_lines out
384
+ gutted="$(mktemp "${TMPDIR:-/tmp}/health-gutted.XXXXXX")"
385
+ grep -v "swapfree:-""9999" "${BASH_SOURCE[0]}" > "$gutted"
386
+ anchor_lines=$(( $(wc -l < "${BASH_SOURCE[0]}") - $(wc -l < "$gutted") ))
387
+ if [ "$anchor_lines" -ne 1 ]; then
388
+ bad "R1 could not remove the swap arm — anchor moved, this checker is blind"
389
+ else
390
+ out=$(HEALTH_SELFTEST_CHILD=1 bash "$gutted" --self-test 2>&1)
391
+ case "$out" in
392
+ *"FAIL"*"C3"*) ok "R1 red-proof: with the swap arm deleted, C3 goes red" ;;
393
+ *) bad "R1 red-proof did not bite — gutted script still passed C3" ;;
394
+ esac
395
+ fi
396
+ rm -f "$gutted"
397
+
398
+ # RED PROOF for the fix rules — delete the governor-slot guard from the real
399
+ # file and require F2 to go red there. F1..F6 passing against a build that
400
+ # cannot spare live work would be worthless.
401
+ local gutted2 removed out2 guard
402
+ gutted2="$(mktemp "${TMPDIR:-/tmp}/health-gutted2.XXXXXX")"
403
+ # Assembled with printf so this line is not itself the anchor it deletes.
404
+ guard=$(printf '_holds_slot "$%s" && continue' pid)
405
+ grep -vF "$guard" "${BASH_SOURCE[0]}" > "$gutted2"
406
+ removed=$(( $(wc -l < "${BASH_SOURCE[0]}") - $(wc -l < "$gutted2") ))
407
+ if [ "$removed" -ne 2 ]; then
408
+ bad "R2 could not remove the slot guard (removed ${removed} lines, expected 2) — anchor moved, this checker is blind"
409
+ else
410
+ out2=$(HEALTH_SELFTEST_CHILD=1 bash "$gutted2" --self-test 2>&1)
411
+ case "$out2" in
412
+ *"FAIL"*"F2"*) ok "R2 red-proof: with the slot guard deleted, F2 goes red" ;;
413
+ *) bad "R2 red-proof did not bite — gutted --fix still spared a slot holder" ;;
414
+ esac
415
+ fi
416
+ rm -f "$gutted2"
417
+
418
+ # F8 / R3 — _reap must never signal a process GROUP. Measured 2026-09-15: a
419
+ # negative-pid kill of a shared pgid killed 3 live claude sessions whose
420
+ # tsserver children shared the launching shell's pgid. Static proof this
421
+ # pattern can never silently return, since the runtime proof (spawn a real
422
+ # shared-pgid tree, reap the child, assert the parent survives) needs a real
423
+ # fork this harness cannot safely stage inside a self-test. Excludes THIS
424
+ # comment block and itself from the scan.
425
+ if grep -vE '^\s*#' "${BASH_SOURCE[0]}" | grep -qE 'kill[[:space:]]+-TERM[[:space:]]+-"'; then
426
+ bad "F8 _reap (or something) still signals a process GROUP with -\"\$pgid\""
427
+ else
428
+ ok "F8 nothing in this file signals a process group — only _kill_tree's ppid walk"
429
+ fi
430
+
431
+ # _kill_tree must walk descendants (ppid) and never widen beyond them.
432
+ REAPED=""
433
+ pgrep() { case "$1" in -P) case "$2" in 4242) echo 4243 ;; 4243) : ;; esac ;; esac; }
434
+ kill() { REAPED="$REAPED $2"; }
435
+ _kill_tree 4242
436
+ case "$REAPED" in *" 4242"*" 4243"*|*" 4243"*" 4242"*) ok "F9 _kill_tree reaches the target and its real child" ;; *) bad "F9 got: '$REAPED'";; esac
437
+ unset -f pgrep kill 2>/dev/null || true
438
+
439
+ if [ $fails -eq 0 ]; then echo "ALL GREEN — the verdict bites and a bad 200 is not UP"; return 0; fi
440
+ echo "RED — $fails check(s) failed"; return 1
441
+ }
442
+
443
+ [ "$SELFTEST" = "1" ] && { self_test; exit $?; }
444
+
445
+ if [ "${WATCH:-0}" != "0" ]; then
446
+ while :; do clear 2>/dev/null || true; date '+%H:%M:%S'; run_once; sleep "$WATCH"; done
447
+ fi
448
+ run_once