@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.
- package/agents/abm-strategist.md +89 -0
- package/agents/ads-meta.md +97 -0
- package/agents/analyst.md +173 -0
- package/agents/architect.md +280 -0
- package/agents/brand-guardian.md +88 -0
- package/agents/brand-strategist.md +92 -0
- package/agents/campaign-content.md +90 -0
- package/agents/campaign-email.md +88 -0
- package/agents/campaign-sms.md +87 -0
- package/agents/campaign-social.md +87 -0
- package/agents/cco.md +99 -0
- package/agents/ceo.md +106 -0
- package/agents/chairman.md +120 -0
- package/agents/cmo.md +101 -0
- package/agents/community-greeter.md +89 -0
- package/agents/community-moderator.md +92 -0
- package/agents/compliance.md +92 -0
- package/agents/copywriter.md +100 -0
- package/agents/creative-strategist.md +95 -0
- package/agents/cro.md +98 -0
- package/agents/cto.md +267 -0
- package/agents/customer-interviewer.md +93 -0
- package/agents/customer-researcher.md +89 -0
- package/agents/customer-success-manager.md +90 -0
- package/agents/customer-trainer.md +90 -0
- package/agents/cxo.md +100 -0
- package/agents/demand-creator.md +89 -0
- package/agents/demo-mover.md +83 -0
- package/agents/demo-specialist.md +89 -0
- package/agents/demo-thai-family-law.md +87 -0
- package/agents/designer.md +112 -0
- package/agents/discovery-caller.md +98 -0
- package/agents/doctor.md +139 -0
- package/agents/educate-coach.md +100 -0
- package/agents/elevate-tutor.md +83 -0
- package/agents/email-lifecycle-marketer.md +91 -0
- package/agents/engage-specialist.md +99 -0
- package/agents/events-coordinator.md +90 -0
- package/agents/foundation-builder.md +94 -0
- package/agents/funnel-architect.md +89 -0
- package/agents/gift-creator.md +100 -0
- package/agents/google-ads.md +103 -0
- package/agents/guide.md +292 -0
- package/agents/helpdesk-dispatcher.md +88 -0
- package/agents/hook-specialist.md +91 -0
- package/agents/identify-optimizer.md +101 -0
- package/agents/implementer.md +305 -0
- package/agents/incident-commander.md +120 -0
- package/agents/insights-lead.md +93 -0
- package/agents/journey-runner.md +113 -0
- package/agents/linkedin-ads.md +123 -0
- package/agents/live-sales-chat.md +90 -0
- package/agents/market-researcher.md +88 -0
- package/agents/media-buyer.md +96 -0
- package/agents/movers-customer-researcher.md +95 -0
- package/agents/movers-foundation-builder.md +96 -0
- package/agents/movers-market-researcher.md +97 -0
- package/agents/movers-pricing-strategist.md +171 -0
- package/agents/nurture-architect.md +99 -0
- package/agents/offer-architect.md +92 -0
- package/agents/onboarder.md +222 -0
- package/agents/onboarding-specialist.md +92 -0
- package/agents/operations-dashboard.md +98 -0
- package/agents/perf-engineer.md +348 -0
- package/agents/playbook-writer.md +71 -0
- package/agents/plg-strategist.md +91 -0
- package/agents/positioning-architect.md +88 -0
- package/agents/press-officer.md +89 -0
- package/agents/pricing-strategist.md +94 -0
- package/agents/privacy-officer.md +92 -0
- package/agents/referral-manager.md +91 -0
- package/agents/refine-analyst.md +102 -0
- package/agents/release-manager.md +261 -0
- package/agents/renewals-upsell-rep.md +90 -0
- package/agents/review-engineer.md +327 -0
- package/agents/rewards-steward.md +82 -0
- package/agents/sales-call-coach.md +94 -0
- package/agents/sales-closer.md +98 -0
- package/agents/security-auditor.md +313 -0
- package/agents/sell-closer.md +101 -0
- package/agents/share-amplifier.md +99 -0
- package/agents/social-media-manager.md +92 -0
- package/agents/storyteller.md +250 -0
- package/agents/strategist.md +83 -0
- package/agents/strategy-aligner.md +94 -0
- package/agents/support-agent.md +90 -0
- package/agents/tagger.md +245 -0
- package/agents/tech-writer.md +216 -0
- package/agents/test-engineer.md +243 -0
- package/agents/tiktok-ads.md +105 -0
- package/agents/tracking-engineer.md +92 -0
- package/agents/upsell-strategist.md +101 -0
- package/agents/voice.md +85 -0
- package/agents/w4-tools.md +153 -0
- package/agents/w4-verify.md +20 -138
- package/agents/workflow-optimiser.md +84 -0
- package/commands/close.md +814 -160
- package/commands/create.md +2 -2
- package/commands/deploy.md +554 -175
- package/commands/do.md +119 -109
- package/commands/fast.md +67 -0
- package/commands/improve.md +2 -2
- package/commands/one.md +418 -0
- package/commands/see.md +1 -1
- package/commands/sync.md +1 -1
- package/commands/tasks.md +222 -0
- package/commands/vespio.md +110 -0
- package/commands/vespio.remote.md +227 -0
- package/hooks/hooks.json +25 -79
- package/hooks/lib/governor-escape-match.sh +88 -0
- package/hooks/lib/hook.sh +4 -13
- package/hooks/lib/signal.sh +9 -2
- package/hooks/scripts/branch-pin.sh +6 -0
- package/hooks/scripts/config-protect.sh +6 -0
- package/hooks/scripts/do-outcome-gate.sh +19 -2
- package/hooks/scripts/git-add-guard.sh +43 -2
- package/hooks/scripts/governor-escape.sh +96 -0
- package/hooks/scripts/load-guard.sh +127 -0
- package/hooks/scripts/post-edit-check.sh +36 -29
- package/hooks/scripts/session-start.sh +34 -80
- package/hooks/scripts/task-complete-verify.sh +46 -40
- package/package.json +1 -1
- package/rules/documentation.md +9 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agentverse-audit.sh +256 -0
- package/scripts/asi-walk.sh +435 -0
- package/scripts/astro-build-cached.sh +143 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/blocks-manifest.mjs +497 -0
- package/scripts/blocks-ratchet.sh +116 -0
- package/scripts/blocks-render-probe.mjs +529 -0
- package/scripts/blocks-usage.mjs +334 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/close-metrics.sh +558 -0
- package/scripts/close-owner.sh +268 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-dev.sh +35 -0
- package/scripts/deploy-emit.sh +298 -0
- package/scripts/deploy-gate-check.sh +128 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy.sh +1273 -0
- package/scripts/do-auto.sh +598 -48
- package/scripts/do-brief.sh +113 -0
- package/scripts/do-close.sh +1137 -0
- package/scripts/do-commit.sh +75 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-cycle-shape-check.sh +160 -0
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-derive-check.sh +436 -0
- package/scripts/do-fleet.sh +106 -28
- package/scripts/do-folder.sh +10 -1
- package/scripts/do-next.sh +106 -0
- package/scripts/do-orchestrate.sh +17 -5
- package/scripts/do-plan-json.mjs +201 -0
- package/scripts/do-plan-json.sh +8 -0
- package/scripts/do-preflight.sh +117 -0
- package/scripts/do-project.sh +157 -0
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +295 -23
- package/scripts/do-rank.py +31 -1
- package/scripts/do-recon-cache.sh +7 -1
- package/scripts/do-recon-pack.sh +196 -0
- package/scripts/do-reconcile.sh +121 -4
- package/scripts/do-signal.sh +280 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-test-gate.sh +80 -0
- package/scripts/do-tick.sh +102 -0
- package/scripts/do-tier.sh +6 -0
- package/scripts/do-triage.sh +182 -0
- package/scripts/do-ui-gate.sh +1 -1
- package/scripts/do-w4-gates.sh +451 -0
- package/scripts/do-walk.sh +12 -1
- package/scripts/env-sync.sh +173 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +68 -14
- package/scripts/factory-close-check.sh +257 -0
- package/scripts/factory-emit.sh +211 -0
- package/scripts/factory-executor-check.mjs +353 -0
- package/scripts/factory-peak.sh +301 -0
- package/scripts/factory-repo.sh +118 -3
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-ship.sh +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/factory-turn.sh +326 -0
- package/scripts/factory-walk.sh +396 -0
- package/scripts/factory-width.sh +57 -0
- package/scripts/fade-toxic.sh +4 -3
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/fixtures/triage-dupe.md +5 -0
- package/scripts/fleet-manifest.mjs +108 -0
- package/scripts/fleet-status.sh +110 -0
- package/scripts/full-suite-paths-check.sh +144 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +125 -0
- package/scripts/gate-run.sh +73 -0
- package/scripts/gc-content-check.sh +140 -0
- package/scripts/gen-dev-config.py +20 -0
- package/scripts/govern-bound-check.sh +60 -0
- package/scripts/govern-claims-check.sh +233 -0
- package/scripts/govern-mem-check.sh +290 -0
- package/scripts/governor-doors-check.sh +362 -0
- package/scripts/governor-escape-check.sh +171 -0
- package/scripts/health.sh +413 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/land.sh +551 -0
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/lib/govern.sh +361 -0
- package/scripts/lib/govern.ts +756 -0
- package/scripts/lighthouse-run.sh +187 -0
- package/scripts/livekit-live-check.sh +61 -0
- package/scripts/livekit-ratchet.sh +46 -0
- package/scripts/load-guard-check.sh +49 -0
- package/scripts/machine-check.sh +102 -0
- package/scripts/machine-watch.sh +177 -0
- package/scripts/one-agents.mjs +415 -0
- package/scripts/one-resume.sh +78 -0
- package/scripts/orphan-baseline.json +182 -0
- package/scripts/orphan-modules.mjs +179 -0
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/promise-manifest.mjs +24 -1
- package/scripts/release.sh +322 -0
- package/scripts/roles-check.sh +946 -0
- package/scripts/sdk-build-cached.sh +64 -0
- package/scripts/signal-watch.sh +241 -0
- package/scripts/skills-publish.sh +94 -0
- package/scripts/speed-cache-check.sh +173 -0
- package/scripts/speed-check.mjs +907 -0
- package/scripts/speed-parity-check.sh +648 -0
- package/scripts/speed-waterfall-check.sh +355 -0
- package/scripts/substrate-env-parity.mjs +156 -0
- package/scripts/tasks-claim-race.mjs +108 -0
- package/scripts/tasks-loop.sh +185 -0
- package/scripts/test-cached.sh +255 -0
- package/scripts/test-full.sh +87 -0
- package/scripts/test-honesty.mjs +137 -0
- package/scripts/test-lanes.sh +166 -0
- package/scripts/test-speed.sh +94 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +179 -0
- package/scripts/typedb-flake-check.sh +197 -0
- package/scripts/urls-lint.sh +15 -0
- package/scripts/verify-fast.sh +445 -0
- package/scripts/vespio-sync.sh +149 -0
- package/scripts/wf-check.mjs +104 -1
- package/scripts/worktree-preview.sh +879 -0
- package/scripts/worktree-up.sh +208 -0
- package/skills/livekit-agents/SKILL.md +285 -0
- package/skills/livekit-agents/references/freshness-rules.md +168 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/signal/SKILL.md +0 -1
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/hooks/scripts/compact-hint.sh +0 -35
- package/hooks/scripts/gate-guard.sh +0 -83
- package/hooks/scripts/read-tracker.sh +0 -26
- package/hooks/scripts/session-end-verify.sh +0 -51
- package/hooks/scripts/stop-reflect.sh +0 -140
- package/hooks/scripts/sync-priority-todo.sh +0 -57
- package/hooks/scripts/sync-todo-docs.sh +0 -46
- package/hooks/scripts/tool-signal.sh +0 -48
- 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
|