@oneie/claude 0.7.0 → 0.9.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 (263) hide show
  1. package/agents/abm-strategist.md +89 -0
  2. package/agents/ads-meta.md +97 -0
  3. package/agents/analyst.md +173 -0
  4. package/agents/architect.md +280 -0
  5. package/agents/brand-guardian.md +88 -0
  6. package/agents/brand-strategist.md +92 -0
  7. package/agents/campaign-content.md +90 -0
  8. package/agents/campaign-email.md +88 -0
  9. package/agents/campaign-sms.md +87 -0
  10. package/agents/campaign-social.md +87 -0
  11. package/agents/cco.md +99 -0
  12. package/agents/ceo.md +106 -0
  13. package/agents/chairman.md +120 -0
  14. package/agents/cmo.md +101 -0
  15. package/agents/community-greeter.md +89 -0
  16. package/agents/community-moderator.md +92 -0
  17. package/agents/compliance.md +92 -0
  18. package/agents/copywriter.md +100 -0
  19. package/agents/creative-strategist.md +95 -0
  20. package/agents/cro.md +98 -0
  21. package/agents/cto.md +267 -0
  22. package/agents/customer-interviewer.md +93 -0
  23. package/agents/customer-researcher.md +89 -0
  24. package/agents/customer-success-manager.md +90 -0
  25. package/agents/customer-trainer.md +90 -0
  26. package/agents/cxo.md +100 -0
  27. package/agents/demand-creator.md +89 -0
  28. package/agents/demo-mover.md +83 -0
  29. package/agents/demo-specialist.md +89 -0
  30. package/agents/demo-thai-family-law.md +87 -0
  31. package/agents/designer.md +112 -0
  32. package/agents/discovery-caller.md +98 -0
  33. package/agents/doctor.md +139 -0
  34. package/agents/educate-coach.md +100 -0
  35. package/agents/elevate-tutor.md +83 -0
  36. package/agents/email-lifecycle-marketer.md +91 -0
  37. package/agents/engage-specialist.md +99 -0
  38. package/agents/events-coordinator.md +90 -0
  39. package/agents/foundation-builder.md +94 -0
  40. package/agents/funnel-architect.md +89 -0
  41. package/agents/gift-creator.md +100 -0
  42. package/agents/google-ads.md +103 -0
  43. package/agents/guide.md +292 -0
  44. package/agents/helpdesk-dispatcher.md +88 -0
  45. package/agents/hook-specialist.md +91 -0
  46. package/agents/identify-optimizer.md +101 -0
  47. package/agents/implementer.md +305 -0
  48. package/agents/incident-commander.md +120 -0
  49. package/agents/insights-lead.md +93 -0
  50. package/agents/journey-runner.md +113 -0
  51. package/agents/linkedin-ads.md +123 -0
  52. package/agents/live-sales-chat.md +90 -0
  53. package/agents/market-researcher.md +88 -0
  54. package/agents/media-buyer.md +96 -0
  55. package/agents/movers-customer-researcher.md +95 -0
  56. package/agents/movers-foundation-builder.md +96 -0
  57. package/agents/movers-market-researcher.md +97 -0
  58. package/agents/movers-pricing-strategist.md +171 -0
  59. package/agents/nurture-architect.md +99 -0
  60. package/agents/offer-architect.md +92 -0
  61. package/agents/onboarder.md +222 -0
  62. package/agents/onboarding-specialist.md +92 -0
  63. package/agents/operations-dashboard.md +98 -0
  64. package/agents/perf-engineer.md +348 -0
  65. package/agents/playbook-writer.md +71 -0
  66. package/agents/plg-strategist.md +91 -0
  67. package/agents/positioning-architect.md +88 -0
  68. package/agents/press-officer.md +89 -0
  69. package/agents/pricing-strategist.md +94 -0
  70. package/agents/privacy-officer.md +92 -0
  71. package/agents/referral-manager.md +91 -0
  72. package/agents/refine-analyst.md +102 -0
  73. package/agents/release-manager.md +261 -0
  74. package/agents/renewals-upsell-rep.md +90 -0
  75. package/agents/review-engineer.md +327 -0
  76. package/agents/rewards-steward.md +82 -0
  77. package/agents/sales-call-coach.md +94 -0
  78. package/agents/sales-closer.md +98 -0
  79. package/agents/security-auditor.md +313 -0
  80. package/agents/sell-closer.md +101 -0
  81. package/agents/share-amplifier.md +99 -0
  82. package/agents/social-media-manager.md +92 -0
  83. package/agents/storyteller.md +250 -0
  84. package/agents/strategist.md +83 -0
  85. package/agents/strategy-aligner.md +94 -0
  86. package/agents/support-agent.md +90 -0
  87. package/agents/tagger.md +245 -0
  88. package/agents/tech-writer.md +216 -0
  89. package/agents/test-engineer.md +243 -0
  90. package/agents/tiktok-ads.md +105 -0
  91. package/agents/tracking-engineer.md +92 -0
  92. package/agents/upsell-strategist.md +101 -0
  93. package/agents/voice.md +85 -0
  94. package/agents/w4-tools.md +153 -0
  95. package/agents/w4-verify.md +20 -138
  96. package/agents/workflow-optimiser.md +84 -0
  97. package/commands/close.md +814 -160
  98. package/commands/create.md +2 -2
  99. package/commands/deploy.md +554 -175
  100. package/commands/do.md +119 -109
  101. package/commands/fast.md +67 -0
  102. package/commands/improve.md +2 -2
  103. package/commands/one.md +418 -0
  104. package/commands/see.md +1 -1
  105. package/commands/sync.md +1 -1
  106. package/commands/tasks.md +222 -0
  107. package/commands/vespio.md +110 -0
  108. package/commands/vespio.remote.md +227 -0
  109. package/hooks/hooks.json +25 -79
  110. package/hooks/lib/governor-escape-match.sh +88 -0
  111. package/hooks/lib/hook.sh +4 -13
  112. package/hooks/lib/signal.sh +9 -2
  113. package/hooks/scripts/branch-pin.sh +6 -0
  114. package/hooks/scripts/config-protect.sh +6 -0
  115. package/hooks/scripts/do-outcome-gate.sh +19 -2
  116. package/hooks/scripts/git-add-guard.sh +43 -2
  117. package/hooks/scripts/governor-escape.sh +96 -0
  118. package/hooks/scripts/load-guard.sh +127 -0
  119. package/hooks/scripts/post-edit-check.sh +36 -29
  120. package/hooks/scripts/session-start.sh +34 -80
  121. package/hooks/scripts/task-complete-verify.sh +46 -40
  122. package/package.json +1 -1
  123. package/rules/documentation.md +9 -0
  124. package/scripts/ad-copy-lint.sh +656 -0
  125. package/scripts/agentverse-audit.sh +256 -0
  126. package/scripts/asi-walk.sh +435 -0
  127. package/scripts/astro-build-cached.sh +143 -0
  128. package/scripts/blocks-manifest-cached.sh +100 -0
  129. package/scripts/blocks-manifest.mjs +497 -0
  130. package/scripts/blocks-ratchet.sh +116 -0
  131. package/scripts/blocks-render-probe.mjs +529 -0
  132. package/scripts/blocks-usage.mjs +334 -0
  133. package/scripts/chat-context-check.sh +89 -0
  134. package/scripts/close-metrics.sh +558 -0
  135. package/scripts/close-owner.sh +268 -0
  136. package/scripts/db-sync-lock-check.sh +116 -0
  137. package/scripts/deploy-dev.sh +35 -0
  138. package/scripts/deploy-emit.sh +298 -0
  139. package/scripts/deploy-gate-check.sh +128 -0
  140. package/scripts/deploy-ready.sh +78 -0
  141. package/scripts/deploy-record.sh +605 -0
  142. package/scripts/deploy.sh +1273 -0
  143. package/scripts/do-auto.sh +598 -48
  144. package/scripts/do-brief.sh +113 -0
  145. package/scripts/do-close.sh +1137 -0
  146. package/scripts/do-commit.sh +75 -0
  147. package/scripts/do-consumer-sweep.sh +18 -1
  148. package/scripts/do-cycle-shape-check.sh +160 -0
  149. package/scripts/do-decide.sh +476 -0
  150. package/scripts/do-derive-check.sh +436 -0
  151. package/scripts/do-fleet.sh +106 -28
  152. package/scripts/do-folder.sh +10 -1
  153. package/scripts/do-next.sh +106 -0
  154. package/scripts/do-orchestrate.sh +17 -5
  155. package/scripts/do-plan-json.mjs +201 -0
  156. package/scripts/do-plan-json.sh +8 -0
  157. package/scripts/do-preflight.sh +117 -0
  158. package/scripts/do-project.sh +157 -0
  159. package/scripts/do-prove-selftest.sh +108 -0
  160. package/scripts/do-prove.sh +295 -23
  161. package/scripts/do-rank.py +31 -1
  162. package/scripts/do-recon-cache.sh +7 -1
  163. package/scripts/do-recon-pack.sh +196 -0
  164. package/scripts/do-reconcile.sh +121 -4
  165. package/scripts/do-signal.sh +280 -23
  166. package/scripts/do-smoke.sh +18 -1
  167. package/scripts/do-test-gate.sh +80 -0
  168. package/scripts/do-tick.sh +102 -0
  169. package/scripts/do-tier.sh +6 -0
  170. package/scripts/do-triage.sh +182 -0
  171. package/scripts/do-ui-gate.sh +1 -1
  172. package/scripts/do-w4-gates.sh +451 -0
  173. package/scripts/do-walk.sh +12 -1
  174. package/scripts/env-sync.sh +173 -0
  175. package/scripts/factory-brief-check.sh +330 -0
  176. package/scripts/factory-check.sh +68 -14
  177. package/scripts/factory-close-check.sh +257 -0
  178. package/scripts/factory-emit.sh +211 -0
  179. package/scripts/factory-executor-check.mjs +353 -0
  180. package/scripts/factory-peak.sh +301 -0
  181. package/scripts/factory-repo.sh +118 -3
  182. package/scripts/factory-review-check.mjs +61 -0
  183. package/scripts/factory-ship.sh +61 -0
  184. package/scripts/factory-tasks-check.sh +18 -1
  185. package/scripts/factory-turn.sh +326 -0
  186. package/scripts/factory-walk.sh +396 -0
  187. package/scripts/factory-width.sh +57 -0
  188. package/scripts/fade-toxic.sh +4 -3
  189. package/scripts/fixtures/factory-brief-real.md +44 -0
  190. package/scripts/fixtures/triage-dupe.md +5 -0
  191. package/scripts/fleet-manifest.mjs +108 -0
  192. package/scripts/fleet-status.sh +110 -0
  193. package/scripts/full-suite-paths-check.sh +144 -0
  194. package/scripts/gate-reaper-check.sh +98 -0
  195. package/scripts/gate-reaper.sh +125 -0
  196. package/scripts/gate-run.sh +73 -0
  197. package/scripts/gc-content-check.sh +140 -0
  198. package/scripts/gen-dev-config.py +20 -0
  199. package/scripts/govern-bound-check.sh +60 -0
  200. package/scripts/govern-claims-check.sh +233 -0
  201. package/scripts/govern-mem-check.sh +290 -0
  202. package/scripts/governor-doors-check.sh +362 -0
  203. package/scripts/governor-escape-check.sh +171 -0
  204. package/scripts/health.sh +413 -0
  205. package/scripts/id-inventory.mjs +418 -0
  206. package/scripts/land.sh +551 -0
  207. package/scripts/lib/gc-finished.sh +77 -0
  208. package/scripts/lib/govern.sh +361 -0
  209. package/scripts/lib/govern.ts +756 -0
  210. package/scripts/lighthouse-run.sh +187 -0
  211. package/scripts/livekit-live-check.sh +61 -0
  212. package/scripts/livekit-ratchet.sh +46 -0
  213. package/scripts/load-guard-check.sh +49 -0
  214. package/scripts/machine-check.sh +102 -0
  215. package/scripts/machine-watch.sh +177 -0
  216. package/scripts/one-agents.mjs +415 -0
  217. package/scripts/one-resume.sh +78 -0
  218. package/scripts/orphan-baseline.json +182 -0
  219. package/scripts/orphan-modules.mjs +179 -0
  220. package/scripts/pr-body.sh +335 -0
  221. package/scripts/preview-fd-check.sh +289 -0
  222. package/scripts/promise-manifest.mjs +24 -1
  223. package/scripts/release.sh +322 -0
  224. package/scripts/roles-check.sh +946 -0
  225. package/scripts/sdk-build-cached.sh +64 -0
  226. package/scripts/signal-watch.sh +241 -0
  227. package/scripts/skills-publish.sh +94 -0
  228. package/scripts/speed-cache-check.sh +173 -0
  229. package/scripts/speed-check.mjs +907 -0
  230. package/scripts/speed-parity-check.sh +648 -0
  231. package/scripts/speed-waterfall-check.sh +355 -0
  232. package/scripts/substrate-env-parity.mjs +156 -0
  233. package/scripts/tasks-claim-race.mjs +108 -0
  234. package/scripts/tasks-loop.sh +185 -0
  235. package/scripts/test-cached.sh +255 -0
  236. package/scripts/test-full.sh +87 -0
  237. package/scripts/test-honesty.mjs +137 -0
  238. package/scripts/test-lanes.sh +166 -0
  239. package/scripts/test-speed.sh +94 -0
  240. package/scripts/triage-shape-check.sh +149 -0
  241. package/scripts/tsc-cached.sh +179 -0
  242. package/scripts/typedb-flake-check.sh +197 -0
  243. package/scripts/urls-lint.sh +15 -0
  244. package/scripts/verify-fast.sh +445 -0
  245. package/scripts/vespio-sync.sh +149 -0
  246. package/scripts/wf-check.mjs +104 -1
  247. package/scripts/worktree-preview.sh +879 -0
  248. package/scripts/worktree-up.sh +208 -0
  249. package/skills/livekit-agents/SKILL.md +285 -0
  250. package/skills/livekit-agents/references/freshness-rules.md +168 -0
  251. package/skills/shadcn/SKILL.md +1 -1
  252. package/skills/signal/SKILL.md +0 -1
  253. package/skills/voice/SKILL.md +94 -6
  254. package/skills/voice/corpus-check.sh +87 -0
  255. package/hooks/scripts/compact-hint.sh +0 -35
  256. package/hooks/scripts/gate-guard.sh +0 -83
  257. package/hooks/scripts/read-tracker.sh +0 -26
  258. package/hooks/scripts/session-end-verify.sh +0 -51
  259. package/hooks/scripts/stop-reflect.sh +0 -140
  260. package/hooks/scripts/sync-priority-todo.sh +0 -57
  261. package/hooks/scripts/sync-todo-docs.sh +0 -46
  262. package/hooks/scripts/tool-signal.sh +0 -48
  263. package/scripts/do-tasks-bridge.py +0 -366
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env bash
2
+ # lighthouse-run.sh — capture-and-print ONE Lighthouse run. No thresholds.
3
+ #
4
+ # WHY IT EXISTS: the two pre-existing runners (one.ie/web/scripts/
5
+ # lighthouse-{dashboard,in}.sh) each hardcode their own pass/fail numbers.
6
+ # That made the thresholds live in two places and neither could be ratcheted.
7
+ # This script does capture only; `speed-check.mjs` owns every comparison.
8
+ # One source of truth for "what is the number", one for "is it good enough".
9
+ #
10
+ # USAGE
11
+ # lighthouse-run.sh <url> [desktop|mobile]
12
+ #
13
+ # PRINTS (one line, stdout, parseable):
14
+ # perf=<0-100> a11y=<0-100> bp=<0-100> seo=<0-100>
15
+ # fcp=<ms> lcp=<ms> tbt=<ms> cls=<0.000> si=<ms> tti=<ms> bytes=<n>
16
+ #
17
+ # EXIT 0 captured · 2 INCONCLUSIVE (no lighthouse, no chrome, url dead,
18
+ # unparseable report). NEVER 1 — this script has no opinion on the
19
+ # numbers, so it can never "fail". An unrun check is not a green check.
20
+ set -uo pipefail
21
+
22
+ URL="${1:?usage: lighthouse-run.sh <url> [desktop|mobile]}"
23
+ MODE="${2:-desktop}"
24
+ OUT="${LIGHTHOUSE_OUT:-$(mktemp -t lhrun)}"
25
+ LH_ERR="$(mktemp -t lherr)"
26
+ # Clean up BOTH temp files on every exit path. ~40 runs a session otherwise
27
+ # leaves ~80 stray files in $TMPDIR.
28
+ trap 'rm -f "$OUT" "$LH_ERR"' EXIT
29
+ REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
30
+
31
+ inconclusive() { echo "INCONCLUSIVE: $*" >&2; exit 2; }
32
+
33
+ # --- locate lighthouse ------------------------------------------------------
34
+ LH=""
35
+ for cand in \
36
+ "$REPO/one.ie/web/node_modules/.bin/lighthouse" \
37
+ "$REPO/node_modules/.bin/lighthouse" \
38
+ "$(command -v lighthouse 2>/dev/null || true)"; do
39
+ [ -n "$cand" ] && [ -x "$cand" ] && { LH="$cand"; break; }
40
+ done
41
+ [ -n "$LH" ] || inconclusive "lighthouse not installed (bun add -D lighthouse in one.ie/web)"
42
+
43
+ # --- locate chrome ----------------------------------------------------------
44
+ # Lighthouse needs full Chrome, not chrome-headless-shell: several audits
45
+ # (and the whole a11y category) depend on APIs the shell build omits.
46
+ if [ -z "${CHROME_PATH:-}" ]; then
47
+ for cand in \
48
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
49
+ "/Applications/Chromium.app/Contents/MacOS/Chromium" \
50
+ "$(command -v google-chrome 2>/dev/null || true)" \
51
+ "$(command -v chromium 2>/dev/null || true)"; do
52
+ [ -n "$cand" ] && [ -x "$cand" ] && { export CHROME_PATH="$cand"; break; }
53
+ done
54
+ fi
55
+ [ -n "${CHROME_PATH:-}" ] || inconclusive "no Chrome found (set CHROME_PATH)"
56
+
57
+ # --- url must actually answer before we spend 30s on Chrome -----------------
58
+ # NOTE: curl's -w prints "000" on connection failure AND exits non-zero, so a
59
+ # `|| echo 000` fallback concatenates to "000000" and silently misses the
60
+ # guard. Test curl's exit status separately instead.
61
+ # Fetch the body ONCE here and reuse it for the asset guard below — a second
62
+ # full download per sample would add latency to every measurement.
63
+ BODY_FILE="$(mktemp -t lhbody)"; trap 'rm -f "$OUT" "$LH_ERR" "$BODY_FILE"' EXIT
64
+ if code="$(curl -s -o "$BODY_FILE" -w '%{http_code}' --max-time 10 -L "$URL" 2>/dev/null)"; then
65
+ case "$code" in
66
+ 000|"") inconclusive "$URL unreachable (no server listening?)" ;;
67
+ 5*) inconclusive "$URL returned $code — measuring an error page is not a measurement" ;;
68
+ esac
69
+ else
70
+ inconclusive "$URL unreachable (curl failed; no server listening?)"
71
+ fi
72
+
73
+ # --- assets must resolve, or we measure a blank page ----------------------
74
+ # A served-200 document whose stylesheets 404 paints NOTHING: Lighthouse
75
+ # returns runtimeError NO_FCP, or (worse) a partial load that scores 100.
76
+ # Measured 2026-08-18: a concurrent `bun run build` replaced dist/ under a
77
+ # running `wrangler dev`, so the HTML referenced new content hashes the
78
+ # server could no longer serve — every /_astro/*.css returned 404 while `/`
79
+ # still returned 200 in 19ms. Checking the document alone cannot see this.
80
+ if [ "${SPEED_SKIP_ASSET_CHECK:-0}" != "1" ]; then
81
+ first_asset="$(grep -oE '/_astro/[^"]+\.(css|js)' "$BODY_FILE" 2>/dev/null | head -1 || true)"
82
+ if [ -n "$first_asset" ]; then
83
+ base="$(printf '%s' "$URL" | sed -E 's#^(https?://[^/]+).*#\1#')"
84
+ acode="$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "${base}${first_asset}" 2>/dev/null || echo 000)"
85
+ case "$acode" in
86
+ 2*) : ;;
87
+ *) inconclusive "assets 404 (${first_asset} -> ${acode}) — the page cannot paint. Stale build? Rebuild and restart the server." ;;
88
+ esac
89
+ fi
90
+ fi
91
+
92
+ # --- run --------------------------------------------------------------------
93
+ # THROTTLING: default is `simulate` (Lighthouse's own default), NOT `provided`.
94
+ # This matters more than anything else in this file. `provided` scores whatever
95
+ # the real network happened to do, so the same unchanged page measured minutes
96
+ # apart scored perf 95 then 63, LCP 1486ms then 4340ms (measured 2026-08-18) —
97
+ # noise that large makes a ratchet worthless and is why nobody trusted the old
98
+ # numbers. `simulate` loads once then applies a fixed synthetic connection, so
99
+ # scores are stable and comparable across machines and runs.
100
+ # Override with SPEED_THROTTLING=provided for a raw real-network reading.
101
+ # FORM FACTOR: use --preset=desktop, NOT --form-factor=desktop. Lighthouse's
102
+ # default screen emulation is mobile, and setting form-factor alone leaves the
103
+ # two inconsistent -> hard runtime error ("Screen emulation mobile setting
104
+ # (true) does not match formFactor setting (desktop)"). The preset sets
105
+ # form-factor, screen emulation and throttling together. Mobile needs no
106
+ # preset; it is already the default config.
107
+ # NOTE: macOS ships bash 3.2, where expanding an EMPTY array under `set -u`
108
+ # ("${PRESET[@]}") is an "unbound variable" error. Use a plain string + word
109
+ # splitting instead of an array — the value is a single fixed flag, so there is
110
+ # nothing to quote-protect.
111
+ PRESET=""
112
+ [ "$MODE" = "desktop" ] && PRESET="--preset=desktop"
113
+
114
+ "$LH" "$URL" \
115
+ --output=json \
116
+ --output-path="$OUT" \
117
+ --chrome-flags="--headless=new --no-sandbox --disable-gpu --disable-dev-shm-usage" \
118
+ --quiet \
119
+ --only-categories=performance,accessibility,best-practices,seo \
120
+ ${PRESET} \
121
+ --throttling-method="${SPEED_THROTTLING:-simulate}" >/dev/null 2>"$LH_ERR" || true
122
+
123
+ # Surface the real reason. Swallowing stderr here turned a config error into a
124
+ # bare "produced no report", which cost a debugging cycle.
125
+ if [ ! -s "$OUT" ]; then
126
+ inconclusive "lighthouse produced no report for $URL: $(head -2 "$LH_ERR" | tr '\n' ' ')"
127
+ fi
128
+
129
+ node -e '
130
+ const fs = require("fs");
131
+ let j;
132
+ try { j = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); }
133
+ catch { process.exit(3); }
134
+ if (j.runtimeError) { console.error("runtimeError: " + j.runtimeError.code); process.exit(3); }
135
+ const cat = (k) => {
136
+ const c = j.categories && j.categories[k];
137
+ return c && typeof c.score === "number" ? Math.round(c.score * 100) : -1;
138
+ };
139
+ const num = (k) => {
140
+ const a = j.audits && j.audits[k];
141
+ return a && typeof a.numericValue === "number" ? Math.round(a.numericValue) : -1;
142
+ };
143
+ const cls = (() => {
144
+ const a = j.audits && j.audits["cumulative-layout-shift"];
145
+ return a && typeof a.numericValue === "number" ? a.numericValue.toFixed(3) : "-1";
146
+ })();
147
+ const bytes = num("total-byte-weight");
148
+ const perf = cat("performance");
149
+ if (perf < 0) { console.error("no performance score in report"); process.exit(3); }
150
+
151
+ // PARTIAL-LOAD GUARD. A page that only half-loaded scores BRILLIANTLY: fewer
152
+ // bytes, earlier LCP, no long tasks. Measured 2026-08-18 under box contention:
153
+ // /chat reported perf=100 lcp=275ms bytes=13043 while the server was serving a
154
+ // healthy 49KB document with ~400KB of subresources. That reading would have
155
+ // been written into the baseline as a record to defend forever.
156
+ // Any real route here is >= 100KB, so a tiny total is a failed load, not a fast
157
+ // page. Tune with SPEED_MIN_BYTES for a genuinely minimal route.
158
+ const MIN_BYTES = Number(process.env.SPEED_MIN_BYTES || 100000);
159
+ if (bytes >= 0 && bytes < MIN_BYTES) {
160
+ console.error(`partial load: total-byte-weight ${bytes} < ${MIN_BYTES} — not a measurement`);
161
+ process.exit(3);
162
+ }
163
+ // Failed requests: judge the MAIN DOCUMENT strictly and the rest by
164
+ // proportion. Real pages routinely 404 a favicon or lose an analytics beacon;
165
+ // that is not a failed measurement. A dead document, or most of the page
166
+ // missing, is.
167
+ const reqs = (j.audits && j.audits["network-requests"] && j.audits["network-requests"].details
168
+ && j.audits["network-requests"].details.items) || [];
169
+ if (reqs.length) {
170
+ const doc = reqs.find((r) => r.resourceType === "Document");
171
+ if (doc && doc.statusCode >= 400) {
172
+ console.error(`main document returned ${doc.statusCode} — not a measurement`);
173
+ process.exit(3);
174
+ }
175
+ const failed = reqs.filter((r) => r.statusCode >= 400).length;
176
+ if (failed / reqs.length > 0.5) {
177
+ console.error(`partial load: ${failed}/${reqs.length} requests failed`);
178
+ process.exit(3);
179
+ }
180
+ }
181
+ console.log(
182
+ `perf=${perf} a11y=${cat("accessibility")} bp=${cat("best-practices")} seo=${cat("seo")} ` +
183
+ `fcp=${num("first-contentful-paint")} lcp=${num("largest-contentful-paint")} ` +
184
+ `tbt=${num("total-blocking-time")} cls=${cls} si=${num("speed-index")} ` +
185
+ `tti=${num("interactive")} bytes=${bytes}`
186
+ );
187
+ ' "$OUT" 2>"$LH_ERR" || inconclusive "$(head -2 "$LH_ERR" | tr '\n' ' ')"
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bash
2
+ # livekit-live-check.sh — does the LIVE LiveKit server accept a token this repo signs?
3
+ #
4
+ # The offline suites prove the token is well FORMED (claims decode correctly).
5
+ # They cannot prove LiveKit ACCEPTS it — that needs a real project. This does.
6
+ #
7
+ # THREE EXIT CODES, because red and cannot-run send a human to opposite places:
8
+ # 0 the live server verified our HMAC signature
9
+ # 1 RED — it rejected us (wrong key/secret, or the signer regressed)
10
+ # 3 CANNOT RUN — no credentials configured. NOT a failure; the promise's
11
+ # offline proof still stands and this check simply has no evidence to read.
12
+ #
13
+ # Credentials are read from one.ie/web/.dev.vars (gitignored) or the environment.
14
+ # Never prints a secret — only the host, the HTTP status, and the verdict.
15
+ set -uo pipefail
16
+ cd "$(dirname "$0")/../.." || exit 3
17
+
18
+ ENV_FILE="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.dev.vars}}"
19
+
20
+ _read() { # _read VARNAME — env wins, then the env file
21
+ local v="${!1:-}"
22
+ [ -n "$v" ] && { printf '%s' "$v"; return; }
23
+ [ -f "$ENV_FILE" ] || return
24
+ sed -n "s/^${1}=//p" "$ENV_FILE" | tail -1 | tr -d '\r'
25
+ }
26
+
27
+ URL=$(_read LIVEKIT_URL); KEY=$(_read LIVEKIT_API_KEY); SECRET=$(_read LIVEKIT_API_SECRET)
28
+
29
+ if [ -z "$URL" ] || [ -z "$KEY" ] || [ -z "$SECRET" ]; then
30
+ echo "livekit-live-check: CANNOT RUN — LIVEKIT_URL/API_KEY/API_SECRET unset (checked ${ENV_FILE})"
31
+ echo " this is not a failure: local mode needs no credentials. See text/livekit-docs.md."
32
+ exit 3
33
+ fi
34
+
35
+ OUT=$(LK_URL="$URL" LK_KEY="$KEY" LK_SECRET="$SECRET" node -e '
36
+ const { createHmac } = require("crypto");
37
+ const b = (o) => Buffer.from(JSON.stringify(o)).toString("base64")
38
+ .replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");
39
+ const now = Math.floor(Date.now()/1000);
40
+ // Same claim shape as one.ie/web/src/lib/livekit.ts, with roomList so the
41
+ // REST API is reachable without joining anything.
42
+ const h = b({ typ:"JWT", alg:"HS256" });
43
+ const p = b({ iss: process.env.LK_KEY, sub:"live-check", jti:"live-check",
44
+ nbf: now, iat: now, exp: now+120, video:{ roomList:true } });
45
+ const s = createHmac("sha256", process.env.LK_SECRET).update(h+"."+p).digest("base64")
46
+ .replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"");
47
+ const base = process.env.LK_URL.replace(/^wss:/,"https:").replace(/^ws:/,"http:");
48
+ fetch(base + "/twirp/livekit.RoomService/ListRooms", { method:"POST",
49
+ headers:{ Authorization:"Bearer " + h+"."+p+"."+s, "Content-Type":"application/json" },
50
+ body:"{}" })
51
+ .then(r => console.log(new URL(base).host + " " + r.status))
52
+ .catch(e => { console.log("unreachable " + String(e.message).slice(0,60)); });
53
+ ' 2>&1)
54
+
55
+ HOST=${OUT%% *}; STATUS=${OUT##* }
56
+
57
+ case "$STATUS" in
58
+ 200) echo "livekit-live-check OK — $HOST verified our signature (http 200)"; exit 0 ;;
59
+ 401|403) echo "livekit-live-check RED — $HOST rejected our token (http $STATUS): key/secret wrong, or the signer regressed" >&2; exit 1 ;;
60
+ *) echo "livekit-live-check CANNOT RUN — $OUT (server unreachable, not a signing failure)"; exit 3 ;;
61
+ esac
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env bash
2
+ # livekit-ratchet.sh — monorepo-only (hard-asserts against one.ie/web).
3
+ #
4
+ # Proves the LiveKit block family stayed collapsed: exactly ONE registration
5
+ # (`LiveRoom`), no second prefab smuggled either into livekit-blocks.tsx or
6
+ # straight into config.tsx's withChrome({...}) merge.
7
+ #
8
+ # Exit 0 = still one block. Exit 1 = the family re-split (or the test vanished).
9
+ # No pipes on purpose: `producer | grep -q` returns 141 on a match here.
10
+
11
+ set -uo pipefail
12
+
13
+ # _GATE — route a heavy compute through the machine governor. A gate_lock only
14
+ # dedupes IDENTICAL work; a SLOT is what bounds N worktrees each running one of
15
+ # these at once (measured 2026-09-07: three concurrent 2.5GB typecheckers, every
16
+ # lock uncontended, load 171). Empty when already inside a gate, so a nested call
17
+ # inherits the outer slot rather than taking a second one.
18
+ #
19
+ # Resolves gate-run.sh from its OWN directory, deliberately: an earlier version
20
+ # keyed off $ROOT and got inserted above the line that sets it, so _GATE was
21
+ # silently empty and every call ran ungoverned -- a fail-OPEN, which is the exact
22
+ # defect this preamble exists to close.
23
+ _GATE=()
24
+ if [ "${GOVERN_IN_GATE:-0}" != "1" ]; then
25
+ _GR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
26
+ [ -f "$_GR" ] && _GATE=( bash "$_GR" "compute:$(basename "${BASH_SOURCE[0]}")" -- )
27
+ fi
28
+
29
+
30
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
31
+ cd "$ROOT" || exit 1
32
+
33
+ TEST_REL="tests/unit/puck/livekit-ratchet.test.ts"
34
+
35
+ # A vitest run whose path filter matches nothing exits 0. Assert the file first.
36
+ if [ ! -f "one.ie/web/$TEST_REL" ]; then
37
+ echo "livekit-ratchet FAIL — missing one.ie/web/$TEST_REL" >&2
38
+ exit 1
39
+ fi
40
+
41
+ (cd one.ie/web && "${_GATE[@]}" bunx vitest run "$TEST_REL") || {
42
+ echo "livekit-ratchet FAIL — more than one LiveKit block is registered" >&2
43
+ exit 1
44
+ }
45
+
46
+ echo "livekit-ratchet OK — exactly one LiveKit block (LiveRoom)"
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ # Test the load-guard's heavy-command matcher. Lives in a file because the cases
3
+ # manifest: portable
4
+ # ARE command strings -- putting them in the command would make the hook match on
5
+ # the test itself, which is what happened twice while writing this.
6
+ POS='(^|[;&|(]|&&|\|\|)[[:space:]]*([A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+)*'
7
+ A="${POS}((bunx|npx|bun|npm)[[:space:]]+)?(tsc[[:space:]]+--noEmit|vitest([[:space:]]+run)?|astro[[:space:]]+check|playwright[[:space:]]+test)"
8
+ B="${POS}(bun|npm)[[:space:]]+run[[:space:]]+(verify|test|typecheck|check)(:[a-z]+)?([[:space:]]|\$)"
9
+
10
+ heavy() {
11
+ local c="$1" h=0 out
12
+ out=$(printf '%s' "$c" | grep -cE "$A" || true); [ "${out:-0}" -gt 0 ] && h=1
13
+ out=$(printf '%s' "$c" | grep -cE "$B" || true); [ "${out:-0}" -gt 0 ] && h=1
14
+ echo "$h"
15
+ }
16
+
17
+ bad=0
18
+ check() { # check <expected 1|0> <command>
19
+ local want="$1" cmd="$2" got
20
+ got=$(heavy "$cmd")
21
+ if [ "$got" = "$want" ]; then
22
+ printf ' ok %-6s %s\n' "$([ "$want" = 1 ] && echo BLOCK || echo allow)" "$cmd"
23
+ else
24
+ printf ' FAIL %-6s %s\n' "$([ "$want" = 1 ] && echo BLOCK || echo allow)" "$cmd"
25
+ bad=$((bad + 1))
26
+ fi
27
+ }
28
+
29
+ echo "--- must be treated as a heavy gate ---"
30
+ V=verify; T=test
31
+ check 1 "bun run $V"
32
+ check 1 "cd one.ie/web && bun run $T"
33
+ check 1 "bunx vitest run"
34
+ check 1 "bunx tsc --noEmit"
35
+ check 1 "VITEST_MAX_FORKS=2 bunx vitest run"
36
+ check 1 "cd x; npx playwright test"
37
+ check 1 "bun run $V:fast"
38
+ check 1 "bun run check:ratchet"
39
+
40
+ echo "--- must NOT be (merely names a gate) ---"
41
+ check 0 "sed -n '1,60p' vitest.config.ts"
42
+ check 0 "git commit -m 'perf: bun run $V is the review gate'"
43
+ check 0 "cat one.ie/web/vitest.config.ts"
44
+ check 0 "grep -rn 'bun run $V' .claude/"
45
+ check 0 "echo the astro check ratchet is a doc phrase"
46
+ check 0 "ps -Ao pcpu=,command= | sort -rn"
47
+
48
+ echo
49
+ [ "$bad" -eq 0 ] && echo "RESULT: all correct" || { echo "RESULT: $bad wrong"; exit 1; }
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env bash
2
+ # machine-check.sh — is this box healthy, and if not, what is holding it?
3
+ # Answers the question "why is my Mac slow" with numbers, not vibes.
4
+ # bash .claude/scripts/machine-check.sh # snapshot
5
+ # bash .claude/scripts/machine-check.sh --watch # live swap-rate probe
6
+ set -uo pipefail
7
+
8
+ cores=$(sysctl -n hw.ncpu); ram=$(( $(sysctl -n hw.memsize) / 1073741824 ))
9
+ load=$(uptime | sed -E 's/.*load averages?: //')
10
+ l1=$(echo "$load" | awk '{print $1}')
11
+ echo "host : ${cores} cores · ${ram} GB RAM"
12
+ echo "load : $load (healthy < ${cores})"
13
+ sysctl -n vm.swapusage | sed 's/^/swap : /'
14
+ # Both numbers, deliberately. memory_pressure's "free percentage" is what every
15
+ # probe in this harness used to believe; it counts file cache, purgeable and
16
+ # already-compressed pages as free, and read 74% on 2026-09-03 while 1.3% of RAM
17
+ # was actually free and 9.4 GB sat in swap. Printing them side by side is how
18
+ # the next person sees the gap instead of rediscovering it.
19
+ # shellcheck source=lib/govern.sh
20
+ . "$(dirname "${BASH_SOURCE[0]}")/lib/govern.sh"
21
+ free_pct=$(memory_pressure 2>/dev/null | awk -F': ' '/free percentage/{print $2}')
22
+ avail_mb=$(gate_mem_avail_mb 2>/dev/null || echo "")
23
+ tot_mb=$(( ram * 1024 ))
24
+ if [ -n "$avail_mb" ] && [ "$tot_mb" -gt 0 ]; then
25
+ echo "free mem : $(( avail_mb / 1024 )) GB available ($(( avail_mb * 100 / tot_mb ))% — free + purgeable + cold cache)"
26
+ echo " memory_pressure claims ${free_pct:-?} free; that figure counts reclaimable memory — never price gates with it"
27
+ else
28
+ echo "free mem : ? (vm_stat unreadable) · memory_pressure claims ${free_pct:-?}"
29
+ fi
30
+ echo "gates : memory funds $(gate_headroom) concurrent cycle(s) at $(gate_price_mb) MB each"
31
+
32
+ if [ "${1:-}" = "--watch" ]; then
33
+ echo -n "swap rate : sampling 10s ... "
34
+ _sw() { vm_stat | awk '/Swapins/{gsub(/\./,"",$2); i=$2} /Swapouts/{gsub(/\./,"",$2); o=$2} END{print i, o}'; }
35
+ _used() { sysctl -n vm.swapusage | sed -E 's/.*used = ([0-9.]+)M.*/\1/' | cut -d. -f1; }
36
+ a=$(_sw); ua=$(_used); sleep 10; b=$(_sw); ub=$(_used)
37
+ din=$(( $(echo "$b"|awk '{print $1}') - $(echo "$a"|awk '{print $1}') ))
38
+ dout=$(( $(echo "$b"|awk '{print $2}') - $(echo "$a"|awk '{print $2}') ))
39
+ echo "in ${din} / out ${dout} pages per 10s · swap used ${ua}M -> ${ub}M"
40
+ # Swapins alone are ambiguous: they spike during RECOVERY too, as freed
41
+ # memory lets pages fault back in. The thrash signal is pages going OUT
42
+ # while swap usage GROWS and free memory is scarce — that is the box
43
+ # evicting live working sets to make room, which is what makes it crawl.
44
+ fp=$(memory_pressure 2>/dev/null | awk -F': ' '/free percentage/{gsub(/%/,"",$2); print int($2)}')
45
+ if [ "$dout" -gt 2000 ] && [ "$ub" -ge "$ua" ] && [ "${fp:-100}" -lt 20 ]; then
46
+ echo " ^^ THRASHING — evicting live memory; reduce concurrent gates"
47
+ elif [ "$din" -gt 1000 ] && [ "$ub" -lt "$ua" ]; then
48
+ echo " (recovering — swap draining, pages faulting back in; this is fine)"
49
+ else
50
+ echo " (paging normal)"
51
+ fi
52
+ fi
53
+
54
+ echo
55
+ echo "── heavy gates running now ──"
56
+ ps -Ao pid=,etime=,rss=,command= \
57
+ | grep -E '[t]sc --noEmit|[v]itest run|[a]stro check|[e]sbuild --service' \
58
+ | awk '{printf " %-7s %-9s %6.0fMB %s\n",$1,$2,$3/1024,substr($0,index($0,$4),60)}' \
59
+ || echo " none"
60
+
61
+ echo
62
+ echo "── language servers (OUTSIDE the governor) ──"
63
+ # Every Claude Code session spawns typescript-language-server, and its tsserver
64
+ # child grows with the project it indexes. Measured 2026-09-04: one tsserver at
65
+ # 1.99 GB after 1h39m, 2.46 GB across the fleet — the single largest ungoverned
66
+ # consumer on the box, and it never shows up in a "heavy gates" grep because it
67
+ # is not a gate. It is priced only indirectly, through what gate_mem_avail_mb
68
+ # sees left over. Listed so the number is legible, not to be killed here.
69
+ ps -Ao pid=,etime=,rss=,command= \
70
+ | grep -E '[t]sserver\.js|[t]ypescript-language-server' \
71
+ | awk '{n++; s+=$3; if ($3/1024 >= 100) printf " %-7s %-9s %6.0fMB tsserver\n",$1,$2,$3/1024}
72
+ END {printf " %d process(es), %.0f MB total\n", n, s/1024}'
73
+
74
+ echo
75
+ echo "── governor ──"
76
+ GOVERN_DIR="${GOVERN_DIR:-${TMPDIR:-/tmp}/one-govern}"
77
+ if [ -d "$GOVERN_DIR" ]; then
78
+ held=0
79
+ for d in "$GOVERN_DIR"/lock-*; do
80
+ [ -d "$d" ] || continue
81
+ pid=$(cat "$d/pid" 2>/dev/null)
82
+ if kill -0 "$pid" 2>/dev/null; then
83
+ echo " HELD $(basename "$d") by pid $pid"; held=$((held+1))
84
+ else
85
+ echo " stale $(basename "$d") (dead pid $pid — will be reaped)"
86
+ fi
87
+ done
88
+ [ "$held" = 0 ] && echo " no slots held (idle)"
89
+ else
90
+ echo " never used yet"
91
+ fi
92
+
93
+ echo
94
+ echo "── orphans (parent died, work still burning CPU) ──"
95
+ ps -Ao pid=,ppid=,etime=,rss=,command= | awk '$2==1' \
96
+ | grep -E 'tsc --noEmit|vitest|bun run (verify|test)|node .*(one-ie|\.do-worktrees)|node_modules.*workerd serve|sleep [0-9]+$' \
97
+ | awk '{printf " %-7s %-9s %6.0fMB %s\n",$1,$3,$4/1024,substr($0,index($0,$5),55)}' \
98
+ || echo " none"
99
+
100
+ echo
101
+ echo "── claude sessions ──"
102
+ echo " $(pgrep -f '^claude' 2>/dev/null | wc -l | tr -d ' ') running · each /do cycle can launch a gate"
@@ -0,0 +1,177 @@
1
+ #!/bin/bash
2
+ # machine-watch.sh — periodic memory check + conservative reclaim.
3
+ #
4
+ # Reports the same picture as machine-check.sh, then reclaims ONLY what is
5
+ # provably safe: leaked children whose parent is gone, and stale governor locks.
6
+ # It never touches a gate owned by a live session and never touches a deploy —
7
+ # those are a human's call. Appends one line per run to the log so trend is
8
+ # readable without watching.
9
+ #
10
+ # Usage: machine-watch.sh [--reclaim] [--quiet]
11
+ # --reclaim actually kill orphans (default: report only)
12
+ set -uo pipefail
13
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
14
+ LOG="${MACHINE_WATCH_LOG:-$ROOT/.machine-watch.log}"
15
+ RECLAIM=0; QUIET=0
16
+ for a in "$@"; do
17
+ [ "$a" = "--reclaim" ] && RECLAIM=1
18
+ [ "$a" = "--quiet" ] && QUIET=1
19
+ done
20
+
21
+ CORES=$(sysctl -n hw.ncpu 2>/dev/null || echo 8)
22
+ LOAD=$(uptime | sed -E 's/.*load averages?: //' | awk '{print $1}')
23
+ LOADI=${LOAD%%.*}
24
+ FREE=$(memory_pressure 2>/dev/null | awk -F': ' '/free percentage/{gsub(/%/,"",$2); print int($2)}')
25
+ FREE=${FREE:-100}
26
+ SWAP_USED=$(sysctl -n vm.swapusage | sed -E 's/.*used = ([0-9.]+)M.*/\1/')
27
+ SWAP_FREE=$(sysctl -n vm.swapusage | sed -E 's/.*free = ([0-9.]+)M.*/\1/')
28
+ GATE_PAT='(\.bin/tsc --noEmit|\.bin/vitest run|astro check)'
29
+
30
+ # Snapshot ps ONCE, then count from the frozen text. Critical: a `ps | grep PAT`
31
+ # pipeline matches grep's OWN command line (the pattern is an argv element), and
32
+ # the awk variant self-matches the same way — both silently inflate every count
33
+ # by one. Freezing the snapshot before any matcher process exists removes the
34
+ # whole class. (Found 2026-08-21: a phantom ungoverned=2 with nothing running.)
35
+ PS_SNAP=$(ps -Ao pid=,ppid=,command= 2>/dev/null)
36
+
37
+ # Real gate drivers: matches the pattern, and is not a shell wrapper / gate-run
38
+ # whose command TEXT merely mentions it.
39
+ GATES=$(printf '%s\n' "$PS_SNAP" \
40
+ | grep -E "$GATE_PAT" \
41
+ | grep -vE '^ *[0-9]+ +[0-9]+ +([^ ]*/)?(zsh|bash|sh|grep|awk|ps)( |$)|gate-run\.sh' \
42
+ | grep -c . || true)
43
+
44
+ # Of those, how many have no gate-run.sh ancestor — the actionable number.
45
+ UNGOV=$(printf '%s\n' "$PS_SNAP" | awk '
46
+ { cmd=""; for(i=3;i<=NF;i++) cmd=cmd" "$i; parent[$1]=$2; command[$1]=cmd }
47
+ END {
48
+ for (p in command) {
49
+ if (command[p] !~ GP) continue
50
+ if (command[p] ~ /^ *([^ ]*\/)?(zsh|bash|sh|grep|awk|ps) /) continue
51
+ if (command[p] ~ /gate-run\.sh/) continue
52
+ q=parent[p]; gov=0
53
+ for (i=0; i<9 && q!="" && q!="1"; i++) {
54
+ if (command[q] ~ /gate-run\.sh/) { gov=1; break }
55
+ q=parent[q]
56
+ }
57
+ if (!gov) n++
58
+ }
59
+ print n+0
60
+ }' GP="$GATE_PAT" || true)
61
+ UNGOV=${UNGOV:-0}
62
+
63
+ # macOS sizes the swap FILE to demand, so small swap_free is NOT thrash. Thrash
64
+ # is pages going OUT while free memory is scarce. Measure the pageout RATE across
65
+ # runs via a state file.
66
+ STATE="${MACHINE_WATCH_STATE:-$ROOT/.machine-watch.state}"
67
+ NOW=$(date +%s)
68
+ PGOUT=$(vm_stat | awk '/Pageouts/{gsub(/\./,"",$NF); print $NF}')
69
+ PGOUT=${PGOUT:-0}
70
+ PO_RATE=0
71
+ if [ -r "$STATE" ]; then
72
+ read -r LAST_T LAST_PO < "$STATE" 2>/dev/null || true
73
+ if [ -n "${LAST_T:-}" ] && [ -n "${LAST_PO:-}" ] && [ "$NOW" -gt "$LAST_T" ]; then
74
+ PO_RATE=$(( (PGOUT - LAST_PO) / (NOW - LAST_T) ))
75
+ [ "$PO_RATE" -lt 0 ] && PO_RATE=0
76
+ fi
77
+ fi
78
+ echo "$NOW $PGOUT" > "$STATE"
79
+
80
+ # Claude sessions are the other half of the bill and they only ever grow: each
81
+ # session's cost is its whole descendant tree (gates, node, language servers),
82
+ # not the one `claude` process. Track count + rolled-up RSS + oldest age so the
83
+ # trend is visible before the box is in trouble.
84
+ SESSIONS=$(printf '%s\n' "$PS_SNAP" | grep -cE '(^| )[0-9]+ +[0-9]+ +([^ ]*/)?claude( |$)|claude --' || true)
85
+ SESS_MB=$(printf '%s\n' "$PS_SNAP" | awk '
86
+ { cmd=""; for(i=3;i<=NF;i++) cmd=cmd" "$i; parent[$1]=$2; command[$1]=cmd }
87
+ END { print 0 }')
88
+ SESS_MB=$(ps -Ao rss=,command= 2>/dev/null | awk '/[c]laude/{s+=$1} END{printf "%d", s/1024}')
89
+ SESSIONS=${SESSIONS:-0}; SESS_MB=${SESS_MB:-0}
90
+
91
+ # Thresholds are env-tunable so the alarm can be exercised in BOTH directions.
92
+ # An alarm only ever seen green is an unproven alarm.
93
+ W_LOAD_FACTOR="${WATCH_LOAD_FACTOR:-2}"
94
+ W_FREE_WARN="${WATCH_FREE_WARN:-20}"
95
+ W_FREE_CRIT="${WATCH_FREE_CRIT:-10}"
96
+ W_GATES="${WATCH_GATES_WARN:-4}"
97
+ W_PO="${WATCH_PAGEOUT_CRIT:-100}"
98
+ W_PO_FREE="${WATCH_PAGEOUT_FREE:-25}"
99
+
100
+ SEV=ok
101
+ [ "$LOADI" -gt $((CORES * W_LOAD_FACTOR)) ] && SEV=warn
102
+ [ "$FREE" -lt "$W_FREE_WARN" ] && SEV=warn
103
+ [ "$GATES" -ge "$W_GATES" ] && SEV=warn
104
+ [ "$UNGOV" -ge "${WATCH_UNGOV_WARN:-2}" ] && SEV=warn
105
+ # Sustained pageouts with little free memory = the box is actually paging.
106
+ [ "$PO_RATE" -gt "$W_PO" ] && [ "$FREE" -lt "$W_PO_FREE" ] && SEV=CRITICAL
107
+ [ "$FREE" -lt "$W_FREE_CRIT" ] && SEV=CRITICAL
108
+
109
+ # ── reclaim: leaked children of a dead parent, inside this repo, > 5 min old ──
110
+ # Shape filter is load-bearing: a backgrounded `astro dev` / `wrangler dev`
111
+ # reparents to launchd the moment its launching shell exits, and is then
112
+ # indistinguishable BY PATH from a leaked esbuild child. Reaping it is what made
113
+ # the dev server "keep stopping" every 10 minutes. Only leaked HELPERS qualify,
114
+ # and only past the 5-minute age the comment above always claimed (etime is $3).
115
+ ORPHANS=$(ps -Ao pid=,ppid=,etime=,command= 2>/dev/null \
116
+ | awk -v root="$ROOT" '
117
+ $2!=1 { next }
118
+ index($0, root"/one.ie/web/node_modules")==0 { next }
119
+ # long-lived servers a human deliberately started — never reap
120
+ /astro dev|vite|wrangler|workerd serve|miniflare|tsserver/ { next }
121
+ {
122
+ # etime: [[dd-]hh:]mm:ss — require > 5 minutes
123
+ e=$3; sub(/^.*-/,"",e); n=split(e,t,":")
124
+ secs = (n==3 ? t[1]*3600+t[2]*60+t[3] : t[1]*60+t[2])
125
+ if (secs > 300) print $1
126
+ }')
127
+ KILLED=0
128
+ for p in $ORPHANS; do
129
+ [ -z "$p" ] && continue
130
+ if [ "$RECLAIM" = "1" ]; then
131
+ kill -TERM "$p" 2>/dev/null; sleep 1; kill -KILL "$p" 2>/dev/null
132
+ KILLED=$((KILLED+1))
133
+ else
134
+ KILLED=$((KILLED+1))
135
+ fi
136
+ done
137
+
138
+ # stale governor locks (owner pid gone) — shrinks the slot cap silently otherwise
139
+ STALE=0
140
+ GDIR="${TMPDIR:-/tmp}one-govern"
141
+ for l in "$GDIR"/lock-slot-* "$GDIR"/lock-tsc-*; do
142
+ [ -d "$l" ] || continue
143
+ own=$(cat "$l/pid" 2>/dev/null)
144
+ [ -z "$own" ] && continue
145
+ if ! kill -0 "$own" 2>/dev/null; then
146
+ [ "$RECLAIM" = "1" ] && rm -rf "$l"
147
+ STALE=$((STALE+1))
148
+ fi
149
+ done
150
+
151
+ # An ungoverned gate is usually short-lived — by the time a human investigates it
152
+ # has exited. Record WHICH command it was at detection time, or the warning is
153
+ # unactionable (observed 2026-08-21 15:21: ungoverned=2, both gone seconds later).
154
+ if [ "${UNGOV:-0}" -gt 0 ]; then
155
+ ps -Ao pid=,etime=,command= 2>/dev/null \
156
+ | grep -E "$GATE_PAT" \
157
+ | grep -vE '^ *[0-9]+ *[0-9:-]+ *[^ ]*/(zsh|bash|sh) |gate-run\.sh' \
158
+ | sed "s|^|$(date '+%Y-%m-%d %H:%M:%S') UNGOVERNED |" >> "${LOG%.log}.ungoverned.log"
159
+ fi
160
+
161
+ STAMP=$(date '+%Y-%m-%d %H:%M:%S')
162
+ VERB=$([ "$RECLAIM" = "1" ] && echo reaped || echo "would-reap")
163
+ LINE="$STAMP sev=$SEV load=$LOAD free=${FREE}% swap_used=${SWAP_USED}M swap_free=${SWAP_FREE}M pageout_rate=${PO_RATE}/s gates=$GATES ungoverned=$UNGOV sessions=$SESSIONS sess_mb=${SESS_MB}M ${VERB}_orphans=$KILLED stale_locks=$STALE"
164
+ echo "$LINE" >> "$LOG"
165
+
166
+ if [ "$QUIET" = "0" ] || [ "$SEV" != "ok" ]; then
167
+ echo "$LINE"
168
+ if [ "$SEV" != "ok" ]; then
169
+ echo
170
+ echo " top consumers:"
171
+ ps -Ao rss=,comm= | awk '{n=$2;for(i=3;i<=NF;i++)n=n" "$i;split(n,a,"/");k=a[length(a)];s[k]+=$1}END{for(i in s)printf " %8.0f MB %s\n",s[i]/1024,i}' | sort -rn | head -5
172
+ fi
173
+ fi
174
+
175
+ [ "$SEV" = "CRITICAL" ] && exit 2
176
+ [ "$SEV" = "warn" ] && exit 1
177
+ exit 0