@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,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// fleet-manifest.mjs — project the fleet board into one JSON the web app can import.
|
|
3
|
+
//
|
|
4
|
+
// WHY A GENERATOR AND NOT A GLOB: the fleet docs live in text/, which is OUTSIDE
|
|
5
|
+
// the Astro root. `import.meta.glob('../../../../text/*.md')` is denied by vite's
|
|
6
|
+
// server.fs.allow ("Denied ID /Users/toc/Server/one-ie/text/analyst.md?raw",
|
|
7
|
+
// measured 2026-08-26), and text/ holds 1,568 files / 50MB — inlining any part of
|
|
8
|
+
// it fights the 3 MiB gzip CF Worker ceiling astro.config.mjs already stubs
|
|
9
|
+
// packages to stay under. Same precedent as promise-manifest.mjs.
|
|
10
|
+
//
|
|
11
|
+
// This file is the SINGLE SOURCE of the fleet -> doc map. `.claude/scripts/
|
|
12
|
+
// fleet-status.sh` reads the generated JSON, so the CLI board and the web board
|
|
13
|
+
// can never disagree about which fleets exist.
|
|
14
|
+
//
|
|
15
|
+
// Usage: node .claude/scripts/fleet-manifest.mjs [--check]
|
|
16
|
+
// --check exits 3 if the committed output is stale (no write).
|
|
17
|
+
|
|
18
|
+
import { readFileSync, writeFileSync, existsSync, statSync } from 'node:fs'
|
|
19
|
+
import { resolve, dirname } from 'node:path'
|
|
20
|
+
import { fileURLToPath } from 'node:url'
|
|
21
|
+
|
|
22
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
|
|
23
|
+
const OUT = resolve(ROOT, 'one.ie/web/src/data/fleets.json')
|
|
24
|
+
|
|
25
|
+
// fleet -> doc path (null = running, no doc written yet).
|
|
26
|
+
const FLEETS = [
|
|
27
|
+
['security', 'text/auth-enforcement-todo.md'],
|
|
28
|
+
['wallet', 'text/wallet-durability.md'],
|
|
29
|
+
['keys', 'text/keys.md'],
|
|
30
|
+
['skill-engine', 'text/skill-engine.md'],
|
|
31
|
+
['analytics', 'text/journey-analytics.md'],
|
|
32
|
+
['analyst', 'text/analyst.md'],
|
|
33
|
+
['departments', 'text/departments.md'],
|
|
34
|
+
['colony', 'text/colony.md'],
|
|
35
|
+
['dev-env', 'text/dev-environment.md'],
|
|
36
|
+
['marketing', null],
|
|
37
|
+
['sales', null],
|
|
38
|
+
['service', null],
|
|
39
|
+
['engineering', null],
|
|
40
|
+
['tasks', null],
|
|
41
|
+
['vespio', null],
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
// Exactly what `grep -ci 'NOT WIRED\|NOT MEASURED\|DARK —'` counts: MATCHING
|
|
45
|
+
// LINES, not occurrences. Diverging here would make the page a second source.
|
|
46
|
+
const OPEN_RE = /NOT WIRED|NOT MEASURED|DARK —/i
|
|
47
|
+
|
|
48
|
+
const board = FLEETS.map(([name, doc]) => {
|
|
49
|
+
if (!doc) return { name, doc: null, lines: null, open: null, updated: null }
|
|
50
|
+
const abs = resolve(ROOT, doc)
|
|
51
|
+
if (!existsSync(abs)) return { name, doc, lines: null, open: null, updated: null }
|
|
52
|
+
const body = readFileSync(abs, 'utf8')
|
|
53
|
+
const lines = body.split('\n')
|
|
54
|
+
// `wc -l` counts newline terminators; the trailing "" from split is not a line.
|
|
55
|
+
const wcl = body.endsWith('\n') ? lines.length - 1 : lines.length
|
|
56
|
+
return {
|
|
57
|
+
name,
|
|
58
|
+
doc,
|
|
59
|
+
lines: wcl,
|
|
60
|
+
open: lines.filter((l) => OPEN_RE.test(l)).length,
|
|
61
|
+
updated: new Date(statSync(abs).mtimeMs).toISOString(),
|
|
62
|
+
}
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const manifest = { generatedAt: new Date().toISOString(), board }
|
|
66
|
+
|
|
67
|
+
// Compare ignoring generatedAt so --check does not go red on the clock alone.
|
|
68
|
+
const stable = (m) => JSON.stringify(m.board)
|
|
69
|
+
const next = JSON.stringify(manifest, null, 2) + '\n'
|
|
70
|
+
|
|
71
|
+
if (process.argv.includes('--check')) {
|
|
72
|
+
if (!existsSync(OUT)) {
|
|
73
|
+
console.error('fleet-manifest: missing ' + OUT)
|
|
74
|
+
process.exit(3)
|
|
75
|
+
}
|
|
76
|
+
const cur = JSON.parse(readFileSync(OUT, 'utf8'))
|
|
77
|
+
if (stable(cur) !== stable(manifest)) {
|
|
78
|
+
console.error('fleet-manifest: STALE — run `node .claude/scripts/fleet-manifest.mjs`')
|
|
79
|
+
process.exit(3)
|
|
80
|
+
}
|
|
81
|
+
console.log('fleet-manifest: up to date (' + board.length + ' fleets)')
|
|
82
|
+
process.exit(0)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Do not rewrite an unchanged board. fleet-status.sh calls this on every run,
|
|
86
|
+
// and an always-write generator would leave a tracked file dirty in the shared
|
|
87
|
+
// main tree each time somebody looks at the board — the exact sweep hazard
|
|
88
|
+
// .claude/CLAUDE.md warns about with concurrent windows. `generatedAt` alone is
|
|
89
|
+
// not a change.
|
|
90
|
+
if (existsSync(OUT)) {
|
|
91
|
+
try {
|
|
92
|
+
if (stable(JSON.parse(readFileSync(OUT, 'utf8'))) === stable(manifest)) {
|
|
93
|
+
console.log('fleet-manifest: unchanged (' + board.length + ' fleets)')
|
|
94
|
+
process.exit(0)
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
// unreadable/corrupt output — fall through and rewrite it
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
writeFileSync(OUT, next)
|
|
102
|
+
console.log(
|
|
103
|
+
'fleet-manifest: wrote ' +
|
|
104
|
+
board.length +
|
|
105
|
+
' fleets (' +
|
|
106
|
+
board.filter((f) => f.doc).length +
|
|
107
|
+
' with a doc) -> src/data/fleets.json',
|
|
108
|
+
)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# fleet-status.sh — what the board looks like right now.
|
|
3
|
+
# Reads the machine, the worktrees and the fleet docs. No agents, no cost.
|
|
4
|
+
set -uo pipefail
|
|
5
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
6
|
+
cd "$ROOT"
|
|
7
|
+
|
|
8
|
+
C_DIM=$'\033[2m'; C_OK=$'\033[32m'; C_WARN=$'\033[33m'; C_BAD=$'\033[31m'; C_0=$'\033[0m'; C_B=$'\033[1m'
|
|
9
|
+
|
|
10
|
+
printf '%s\n' "${C_B} ██████╗ ███╗ ██╗███████╗${C_0}"
|
|
11
|
+
printf '%s\n' "${C_B} ██╔═══██╗████╗ ██║██╔════╝${C_0}"
|
|
12
|
+
printf '%s\n' "${C_B} ██║ ██║██╔██╗ ██║█████╗ ${C_0} ${C_DIM}probe · fan out · join${C_0}"
|
|
13
|
+
printf '%s\n' "${C_B} ██║ ██║██║╚██╗██║██╔══╝ ${C_0} ${C_DIM}no conductor · no collisions${C_0}"
|
|
14
|
+
printf '%s\n' "${C_B} ╚██████╔╝██║ ╚████║███████╗${C_0}"
|
|
15
|
+
printf '%s\n' "${C_B} ╚═════╝ ╚═╝ ╚═══╝╚══════╝${C_0}"
|
|
16
|
+
echo
|
|
17
|
+
|
|
18
|
+
# ── the box ────────────────────────────────────────────────────────────────
|
|
19
|
+
cores=$(sysctl -n hw.ncpu 2>/dev/null || echo '?')
|
|
20
|
+
memgb=$(( $(sysctl -n hw.memsize 2>/dev/null || echo 0) / 1073741824 ))
|
|
21
|
+
load=$(uptime | sed 's/.*load averages*: *//' | awk '{print $1}')
|
|
22
|
+
# This board had a THIRD memory arithmetic of its own: (free+inactive) over
|
|
23
|
+
# (free+inactive+active+wired). It counted every inactive page as free, dirty
|
|
24
|
+
# anonymous ones included, and ignored the compressor entirely — on 2026-09-03
|
|
25
|
+
# it printed 41% while vm_stat said 1.3% free and 9.4 GB sat in swap. One honest
|
|
26
|
+
# probe now answers for the whole harness.
|
|
27
|
+
# shellcheck source=lib/govern.sh
|
|
28
|
+
. "$ROOT/.claude/scripts/lib/govern.sh"
|
|
29
|
+
availmb=$(gate_mem_avail_mb 2>/dev/null || echo "")
|
|
30
|
+
freepct='?'; [ -n "$availmb" ] && [ "$memgb" -gt 0 ] && freepct=$(( availmb * 100 / (memgb * 1024) ))
|
|
31
|
+
swapused=$(sysctl -n vm.swapusage 2>/dev/null | sed 's/.*used = \([0-9.]*\)M.*/\1/')
|
|
32
|
+
|
|
33
|
+
lc=$(printf '%.0f' "${load:-0}" 2>/dev/null || echo 0)
|
|
34
|
+
if [ "$lc" -lt "$((cores))" ]; then lcol=$C_OK
|
|
35
|
+
elif [ "$lc" -lt "$((cores*2))" ]; then lcol=$C_WARN
|
|
36
|
+
else lcol=$C_BAD; fi
|
|
37
|
+
|
|
38
|
+
printf ' %-14s %s cores · %s GB\n' 'box' "$cores" "$memgb"
|
|
39
|
+
printf ' %-14s %b%s%b %s free mem swap %s M\n' 'load' "$lcol" "${load:-?}" "$C_0" "${freepct:-?}%" "${swapused:-?}"
|
|
40
|
+
|
|
41
|
+
# how many cycles can this box actually afford? Ask the governor rather than
|
|
42
|
+
# keeping a second copy of its arithmetic — that copy is how this line drifted.
|
|
43
|
+
res=${GOVERN_RESERVE_GB:-2}
|
|
44
|
+
permb=$(gate_price_mb)
|
|
45
|
+
slots=$(gate_headroom)
|
|
46
|
+
capc=$(( cores - 2 )); [ "$slots" -gt "$capc" ] && slots=$capc
|
|
47
|
+
bind='memory'; [ "$slots" -eq "$capc" ] && bind='cores'
|
|
48
|
+
printf ' %-14s %s %b(bound by %s · %sMB/cycle · %sGB reserved)%b\n' 'affordable' "$slots" "$C_DIM" "$bind" "$permb" "$res" "$C_0"
|
|
49
|
+
|
|
50
|
+
gates=$(ls -d "${TMPDIR:-/tmp}"/one-govern/lock-slot-* 2>/dev/null | wc -l | tr -d ' ')
|
|
51
|
+
printf ' %-14s %s held / %s max\n' 'gate slots' "$gates" "${GOVERN_MAX_GATES:-2}"
|
|
52
|
+
|
|
53
|
+
orph=$(pgrep -f 'esbuild|vite/node_modules' 2>/dev/null | wc -l | tr -d ' ')
|
|
54
|
+
if [ "$orph" -gt 0 ]; then
|
|
55
|
+
printf ' %-14s %b%s%b %bpkill -f "esbuild|vite/node_modules"%b\n' 'orphans' "$C_WARN" "$orph" "$C_0" "$C_DIM" "$C_0"
|
|
56
|
+
fi
|
|
57
|
+
echo
|
|
58
|
+
|
|
59
|
+
# ── worktrees ──────────────────────────────────────────────────────────────
|
|
60
|
+
wt=$(git worktree list 2>/dev/null | wc -l | tr -d ' ')
|
|
61
|
+
wtsz=$(du -sh .claude/worktrees 2>/dev/null | cut -f1)
|
|
62
|
+
printf ' %-14s %s %s %b(~120MB + one tsserver each)%b\n' 'worktrees' "$wt" "${wtsz:-0}" "$C_DIM" "$C_0"
|
|
63
|
+
echo
|
|
64
|
+
|
|
65
|
+
# ── the board ──────────────────────────────────────────────────────────────
|
|
66
|
+
printf ' %bFLEET%b\n' "$C_B" "$C_0"
|
|
67
|
+
while IFS='|' read -r name doc; do
|
|
68
|
+
[ -z "$name" ] && continue
|
|
69
|
+
if [ -f "$doc" ]; then
|
|
70
|
+
lines=$(wc -l < "$doc" | tr -d ' ')
|
|
71
|
+
open=$(grep -ci 'NOT WIRED\|NOT MEASURED\|DARK —' "$doc" 2>/dev/null | head -1 | tr -dc '0-9')
|
|
72
|
+
open=${open:-0}
|
|
73
|
+
if [ "$open" -gt 0 ]; then
|
|
74
|
+
printf ' %b●%b %-13s %-38s %4s lines %b%s open%b\n' "$C_WARN" "$C_0" "$name" "$doc" "$lines" "$C_WARN" "$open" "$C_0"
|
|
75
|
+
else
|
|
76
|
+
printf ' %b●%b %-13s %-38s %4s lines\n' "$C_OK" "$C_0" "$name" "$doc" "$lines"
|
|
77
|
+
fi
|
|
78
|
+
else
|
|
79
|
+
printf ' %b○%b %-13s %-38s %bnot written%b\n' "$C_DIM" "$C_0" "$name" "$doc" "$C_DIM" "$C_0"
|
|
80
|
+
fi
|
|
81
|
+
done < <(
|
|
82
|
+
# Single source of the fleet -> doc map: .claude/scripts/fleet-manifest.mjs,
|
|
83
|
+
# which also feeds /u/one/engineering/fleets. Regenerated here so the CLI and
|
|
84
|
+
# the page can never disagree about which fleets exist. Emits nothing if the
|
|
85
|
+
# manifest is missing — a visibly empty board beats a stale second copy.
|
|
86
|
+
node .claude/scripts/fleet-manifest.mjs >/dev/null 2>&1
|
|
87
|
+
node -e '
|
|
88
|
+
const fs=require("fs");
|
|
89
|
+
const p="one.ie/web/src/data/fleets.json";
|
|
90
|
+
if(!fs.existsSync(p)) process.exit(0);
|
|
91
|
+
for(const f of JSON.parse(fs.readFileSync(p,"utf8")).board)
|
|
92
|
+
console.log(f.name+"|"+(f.doc||"(no doc written)"));
|
|
93
|
+
' 2>/dev/null
|
|
94
|
+
)
|
|
95
|
+
echo
|
|
96
|
+
|
|
97
|
+
# ── the wall ───────────────────────────────────────────────────────────────
|
|
98
|
+
DB=$(ls one.ie/web/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/*.sqlite 2>/dev/null | head -1)
|
|
99
|
+
if [ -n "$DB" ] && command -v sqlite3 >/dev/null 2>&1; then
|
|
100
|
+
owners=$(sqlite3 "$DB" "SELECT COUNT(*) FROM owners;" 2>/dev/null)
|
|
101
|
+
charge=$(sqlite3 "$DB" "SELECT COUNT(*) FROM owners WHERE charges_enabled=1;" 2>/dev/null)
|
|
102
|
+
addrs=$(sqlite3 "$DB" "SELECT COUNT(*) FROM wallets WHERE sui_address IS NOT NULL AND sui_address!='';" 2>/dev/null)
|
|
103
|
+
sales=$(sqlite3 "$DB" "SELECT COUNT(*) FROM credit_grants WHERE source='credits_product_sale';" 2>/dev/null)
|
|
104
|
+
printf ' %bTHE FUNNEL%b %b(local mirror of prod)%b\n' "$C_B" "$C_0" "$C_DIM" "$C_0"
|
|
105
|
+
printf ' owners %-6s can charge %b%-4s%b wallets w/ address %b%-4s%b sales %b%s%b\n' \
|
|
106
|
+
"${owners:-?}" "$C_BAD" "${charge:-?}" "$C_0" "$C_BAD" "${addrs:-?}" "$C_0" "$C_WARN" "${sales:-?}" "$C_0"
|
|
107
|
+
echo
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
printf ' %bnext%b re-measure (/one §1) · sweep worktrees · then fan out\n' "$C_DIM" "$C_0"
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# full-suite-paths-check.sh — sweep for unaccounted paths to the full test suite
|
|
3
|
+
#
|
|
4
|
+
# The full suite is run in three places: package.json callers (via test-full.sh),
|
|
5
|
+
# hooks, and outcome: strings in text/*-todo.md. This script sweeps all three
|
|
6
|
+
# surfaces and compares against a reviewed allowlist.
|
|
7
|
+
#
|
|
8
|
+
# manifest: portable
|
|
9
|
+
#
|
|
10
|
+
# Exit 0: all paths accounted for.
|
|
11
|
+
# Exit 1: a path found that is not in the allowlist (new caller, new hook, new outcome).
|
|
12
|
+
|
|
13
|
+
set -uo pipefail
|
|
14
|
+
|
|
15
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
16
|
+
|
|
17
|
+
# --- The allowlist — paths we have reviewed and accept -----------------------
|
|
18
|
+
# Format:
|
|
19
|
+
# caller:<file>:<pattern> — a package.json script that runs the full suite
|
|
20
|
+
# hook:<file> — a hook that runs the full suite
|
|
21
|
+
# outcome:<file>:<line> — an outcome: string in a todo file
|
|
22
|
+
#
|
|
23
|
+
# This list is built by examining the sources and is NOT auto-generated.
|
|
24
|
+
ALLOWLIST=(
|
|
25
|
+
# Package.json callers: test-full.sh --check verifies no duplicates here
|
|
26
|
+
# (gates route through test-full.sh, not duplicated across callers)
|
|
27
|
+
|
|
28
|
+
# Hooks: must not run the full suite (but may mention it in comments)
|
|
29
|
+
# task-complete-verify.sh had bun run verify removed (documented in comments)
|
|
30
|
+
|
|
31
|
+
# Outcome strings in text/*-todo.md (these are the 15 known paths that need
|
|
32
|
+
# to be routed through gate-run.sh or otherwise accounted for)
|
|
33
|
+
"outcome:text/best-practices-todo.md:12"
|
|
34
|
+
"outcome:text/blocks-todo.md:17"
|
|
35
|
+
"outcome:text/campaigns-reimagined-todo.md:12"
|
|
36
|
+
"outcome:text/composio-todo.md:13"
|
|
37
|
+
"outcome:text/dashboard-personas-todo.md:10"
|
|
38
|
+
"outcome:text/dashboard-todo.md:10"
|
|
39
|
+
"outcome:text/inbox-simplify-todo.md:10"
|
|
40
|
+
"outcome:text/inbox-ui-simple-todo.md:10"
|
|
41
|
+
"outcome:text/inbox-ui-todo.md:11"
|
|
42
|
+
"outcome:text/lifecycle-loop-todo.md:21"
|
|
43
|
+
"outcome:text/pack-starter-workflows-todo.md:12"
|
|
44
|
+
"outcome:text/payments-integrated-todo.md:29"
|
|
45
|
+
"outcome:text/roles-act-as-switcher-todo.md:11"
|
|
46
|
+
"outcome:text/roles-final-todo.md:10"
|
|
47
|
+
"outcome:text/voice-agents-todo.md:13"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# --- Check package.json callers -----------------------------------------------
|
|
51
|
+
# test-full.sh --check ensures no caller duplicates the vitest argv
|
|
52
|
+
if ! bash "$ROOT/.claude/scripts/test-full.sh" --check; then
|
|
53
|
+
echo "FAIL: test-full.sh --check found a duplicate caller" >&2
|
|
54
|
+
exit 1
|
|
55
|
+
fi
|
|
56
|
+
echo "✓ package.json callers: test-full.sh --check passed"
|
|
57
|
+
|
|
58
|
+
# --- Sweep hooks for full suite invocations ----------------------------------
|
|
59
|
+
# Read all hooks and look for patterns that EXECUTE the full suite.
|
|
60
|
+
# We exclude: comments (#), error message output (echo/cat), code examples in help text.
|
|
61
|
+
# We include: actual shell execution like $(bun run verify) or command substitution.
|
|
62
|
+
found_hooks=()
|
|
63
|
+
for hook in "$ROOT"/.claude/hooks/*.sh; do
|
|
64
|
+
[ -f "$hook" ] || continue
|
|
65
|
+
# Look for ACTUAL EXECUTION patterns:
|
|
66
|
+
# - $(bun run verify) — command substitution (not in echo/cat/string)
|
|
67
|
+
# Ignore: comments (#), plain text mentions in error messages
|
|
68
|
+
if grep -E '^\s*\$\(.*bun run verify' "$hook" >/dev/null 2>&1; then
|
|
69
|
+
found_hooks+=("$(basename "$hook")")
|
|
70
|
+
fi
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
if [ ${#found_hooks[@]} -gt 0 ]; then
|
|
74
|
+
echo "FAIL: hooks running full suite (should be routed through gate-run.sh or removed):" >&2
|
|
75
|
+
for h in "${found_hooks[@]}"; do
|
|
76
|
+
echo " hook:$h" >&2
|
|
77
|
+
done
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
echo "✓ hooks: none running full suite directly"
|
|
81
|
+
|
|
82
|
+
# --- Sweep outcome: strings in todo files ------------------------------------
|
|
83
|
+
# Count each `outcome:` field that contains `bun run verify` and check against allowlist
|
|
84
|
+
found_outcomes=()
|
|
85
|
+
for todofile in "$ROOT"/text/*-todo.md; do
|
|
86
|
+
[ -f "$todofile" ] || continue
|
|
87
|
+
# Use relative path from ROOT (e.g., text/best-practices-todo.md)
|
|
88
|
+
relpath="${todofile#$ROOT/}"
|
|
89
|
+
|
|
90
|
+
# Find all lines with outcome: (frontmatter field or inline)
|
|
91
|
+
while IFS=: read -r linenum line; do
|
|
92
|
+
# Check if this line contains bun run verify (unrouted)
|
|
93
|
+
if echo "$line" | grep -q "bun run verify"; then
|
|
94
|
+
found_outcomes+=("outcome:$relpath:$linenum")
|
|
95
|
+
fi
|
|
96
|
+
done < <(grep -n "^outcome:" "$todofile" 2>/dev/null || true)
|
|
97
|
+
done
|
|
98
|
+
|
|
99
|
+
# --- Compare against allowlist -----------------------------------------------
|
|
100
|
+
failed=0
|
|
101
|
+
|
|
102
|
+
# Check for new outcomes not in allowlist
|
|
103
|
+
if [ ${#found_outcomes[@]:-0} -gt 0 ]; then
|
|
104
|
+
for found in "${found_outcomes[@]}"; do
|
|
105
|
+
in_allowlist=0
|
|
106
|
+
for allowed in "${ALLOWLIST[@]}"; do
|
|
107
|
+
if [ "$found" = "$allowed" ]; then
|
|
108
|
+
in_allowlist=1
|
|
109
|
+
break
|
|
110
|
+
fi
|
|
111
|
+
done
|
|
112
|
+
if [ $in_allowlist -eq 0 ]; then
|
|
113
|
+
echo "FAIL: unaccounted path to full suite: $found" >&2
|
|
114
|
+
failed=1
|
|
115
|
+
fi
|
|
116
|
+
done
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
# Check for allowlist entries that no longer exist (removed paths)
|
|
120
|
+
for allowed in "${ALLOWLIST[@]}"; do
|
|
121
|
+
found_match=0
|
|
122
|
+
if [ ${#found_outcomes[@]:-0} -gt 0 ]; then
|
|
123
|
+
for found in "${found_outcomes[@]}"; do
|
|
124
|
+
if [ "$found" = "$allowed" ]; then
|
|
125
|
+
found_match=1
|
|
126
|
+
break
|
|
127
|
+
fi
|
|
128
|
+
done
|
|
129
|
+
fi
|
|
130
|
+
if [ $found_match -eq 0 ]; then
|
|
131
|
+
# Extract the file:line from the allowlist entry
|
|
132
|
+
if [[ $allowed == outcome:* ]]; then
|
|
133
|
+
echo "INFO: allowlist entry no longer exists (may be fixed): $allowed" >&2
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
done
|
|
137
|
+
|
|
138
|
+
if [ $failed -eq 1 ]; then
|
|
139
|
+
exit 1
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
echo "✓ outcome strings: all ${#found_outcomes[@]} accounted for"
|
|
143
|
+
echo "✓ full-suite-paths-check.sh: all paths verified"
|
|
144
|
+
exit 0
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gate-reaper-check.sh — what gate-reaper.sh reaps, and what it must never touch.
|
|
3
|
+
#
|
|
4
|
+
# manifest: portable
|
|
5
|
+
#
|
|
6
|
+
# WHY THIS EXISTS (measured 2026-09-05). Five `workerd` processes left behind by
|
|
7
|
+
# dead `astro dev` sessions sat at ppid=1 for 33-63 minutes holding ~196MB, while
|
|
8
|
+
# BOTH gate-reaper and machine-check printed "orphans: none". The reaper keys on
|
|
9
|
+
# a gate SHAPE, and `workerd serve` was not one of the shapes, so nothing in the
|
|
10
|
+
# harness could see them. They had to be found and killed by hand.
|
|
11
|
+
#
|
|
12
|
+
# The reaper's danger is symmetric, and this checker holds both ends:
|
|
13
|
+
# C1 an abandoned workerd under node_modules IS reaped
|
|
14
|
+
# C2 a LIVE workerd (parent alive) is NEVER reaped
|
|
15
|
+
# C3 a freshly reparented workerd is spared — the age floor still applies
|
|
16
|
+
# C4 a long-lived ppid=1 daemon that merely NAMES a gate word is spared
|
|
17
|
+
# C5 the pre-existing gate shapes still reap — no regression
|
|
18
|
+
# C6 a workerd outside node_modules is spared — not ours to kill
|
|
19
|
+
# R1 RED-PROOF: drop the workerd arm and C1 must FAIL
|
|
20
|
+
#
|
|
21
|
+
# Drives the REAL script with --once --dry-run against a STUBBED `ps`, so it
|
|
22
|
+
# never reads the live process table and never signals anything.
|
|
23
|
+
#
|
|
24
|
+
# bash .claude/scripts/gate-reaper-check.sh
|
|
25
|
+
set -uo pipefail
|
|
26
|
+
|
|
27
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
28
|
+
REAPER="$ROOT/.claude/scripts/gate-reaper.sh"
|
|
29
|
+
SANDBOX="$(mktemp -d "${TMPDIR:-/tmp}/gate-reaper-check.XXXXXX")"
|
|
30
|
+
trap 'rm -rf "$SANDBOX"' EXIT
|
|
31
|
+
|
|
32
|
+
fails=0
|
|
33
|
+
ok() { printf ' \033[32mPASS\033[0m %s\n' "$1"; }
|
|
34
|
+
bad() { printf ' \033[31mFAIL\033[0m %s\n' "$1"; fails=$((fails + 1)); }
|
|
35
|
+
|
|
36
|
+
WD='node_modules/@cloudflare/workerd-darwin-arm64/bin/workerd serve --binary --experimental'
|
|
37
|
+
|
|
38
|
+
# The fixture process table. Columns match `ps -Ao pid=,ppid=,etime=,command=`.
|
|
39
|
+
cat > "$SANDBOX/table" <<EOF
|
|
40
|
+
1001 1 45:00 /repo/someapp/web/$WD
|
|
41
|
+
1002 29457 45:00 /repo/someapp/web/$WD
|
|
42
|
+
1003 1 01:00 /repo/someapp/web/$WD
|
|
43
|
+
1004 1 45:00 /Users/toc/bin/cc-connect.sh daemon run verify
|
|
44
|
+
1005 1 45:00 /usr/local/bin/workerd serve --binary --experimental
|
|
45
|
+
1006 1 45:00 bash /repo/.claude/scripts/gate-run.sh verify -- bun run verify
|
|
46
|
+
EOF
|
|
47
|
+
|
|
48
|
+
# Stub `ps`: serve the fixture for the sweep's -Ao read, and a pgid for lookups.
|
|
49
|
+
mkdir -p "$SANDBOX/bin"
|
|
50
|
+
cat > "$SANDBOX/bin/ps" <<EOF
|
|
51
|
+
#!/usr/bin/env bash
|
|
52
|
+
for a in "\$@"; do case "\$a" in pgid=) echo " 9999"; exit 0 ;; esac; done
|
|
53
|
+
cat "$SANDBOX/table"
|
|
54
|
+
EOF
|
|
55
|
+
chmod +x "$SANDBOX/bin/ps"
|
|
56
|
+
|
|
57
|
+
run_reaper() { PATH="$SANDBOX/bin:$PATH" bash "$1" --once --dry-run 2>&1; }
|
|
58
|
+
|
|
59
|
+
echo "── gate-reaper: reaps the abandoned, spares the live ──"
|
|
60
|
+
out="$(run_reaper "$REAPER")"
|
|
61
|
+
|
|
62
|
+
reaped() { grep -q "WOULD reap pid=$1 " <<<"$out"; }
|
|
63
|
+
|
|
64
|
+
if reaped 1001; then ok "C1 abandoned workerd under node_modules is reaped"
|
|
65
|
+
else bad "C1 abandoned workerd NOT reaped — the orphan shape is still invisible: $out"; fi
|
|
66
|
+
|
|
67
|
+
if ! reaped 1002; then ok "C2 live workerd (parent alive) spared"
|
|
68
|
+
else bad "C2 LIVE workerd would be reaped — this kills a running dev server"; fi
|
|
69
|
+
|
|
70
|
+
if ! reaped 1003; then ok "C3 freshly reparented workerd spared by the age floor"
|
|
71
|
+
else bad "C3 age floor not applied to workerd"; fi
|
|
72
|
+
|
|
73
|
+
if ! reaped 1004; then ok "C4 ppid=1 daemon naming a gate word is spared"
|
|
74
|
+
else bad "C4 cc-connect daemon would be reaped — exclusions broken"; fi
|
|
75
|
+
|
|
76
|
+
if reaped 1006; then ok "C5 pre-existing gate-run.sh shape still reaps"
|
|
77
|
+
else bad "C5 REGRESSION — gate-run.sh shape no longer reaped: $out"; fi
|
|
78
|
+
|
|
79
|
+
if ! reaped 1005; then ok "C6 workerd outside node_modules spared"
|
|
80
|
+
else bad "C6 would reap a workerd this tree did not spawn"; fi
|
|
81
|
+
|
|
82
|
+
# ── R1 RED-PROOF: without the workerd arm, C1 must fail ─────────────────────
|
|
83
|
+
GUTTED="$SANDBOX/gutted.sh"
|
|
84
|
+
grep -v 'node_modules\*workerd" serve"\*) ;;' "$REAPER" > "$GUTTED"
|
|
85
|
+
if [ "$(wc -l < "$GUTTED")" -eq "$(wc -l < "$REAPER")" ]; then
|
|
86
|
+
bad "R1 could not remove the workerd arm — anchor moved, this checker is blind"
|
|
87
|
+
else
|
|
88
|
+
gout="$(run_reaper "$GUTTED")"
|
|
89
|
+
if grep -q "WOULD reap pid=1001 " <<<"$gout"; then
|
|
90
|
+
bad "R1 gutted reaper STILL reaped 1001 — C1 is not testing the new arm"
|
|
91
|
+
else
|
|
92
|
+
ok "R1 red-proof: without the arm 1001 is missed, so C1 has teeth"
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
echo
|
|
97
|
+
if [ $fails -eq 0 ]; then echo "ALL GREEN — the reaper sees orphaned workerd and still spares live work"; exit 0; fi
|
|
98
|
+
echo "RED — $fails check(s) failed"; exit 1
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gate-reaper.sh — reap ABANDONED gates, so a stalled fleet cannot poison the box.
|
|
3
|
+
#
|
|
4
|
+
# manifest: portable
|
|
5
|
+
#
|
|
6
|
+
# WHY (measured 2026-08-31). Two fleets were running and the box sat at load 138
|
|
7
|
+
# on 10 cores with swap at 4.3G of 5.1G. The cause was not the live work: four
|
|
8
|
+
# processes had been reparented to launchd (ppid=1) when their parent shell died
|
|
9
|
+
# and were still burning CPU — a `bun run verify` abandoned for 21 minutes, and a
|
|
10
|
+
# gate-run.sh abandoned for 9 minutes that was STILL HOLDING governor slot-3. A
|
|
11
|
+
# dead process holding a slot shrinks the effective gate cap until someone
|
|
12
|
+
# notices by hand; nothing in the harness noticed.
|
|
13
|
+
#
|
|
14
|
+
# gate_sweep_stale reclaims the LOCK a dead owner left behind. Nothing reclaimed
|
|
15
|
+
# the PROCESS. This does.
|
|
16
|
+
#
|
|
17
|
+
# Deliberately narrow. It kills a process only when ALL of:
|
|
18
|
+
# 1. ppid == 1 — genuinely reparented; its parent is gone
|
|
19
|
+
# 2. the command names THIS repo, or is a gate-run.sh / bun-run-verify shape
|
|
20
|
+
# 3. it has burned real CPU time — not merely idle and forgotten
|
|
21
|
+
# so a system daemon (WindowServer and coreaudiod are both ppid=1 and busy) can
|
|
22
|
+
# never match, and neither can a live fleet's gate, whose parent is alive.
|
|
23
|
+
#
|
|
24
|
+
# Usage: gate-reaper.sh [--once] [--interval N] [--dry-run]
|
|
25
|
+
# Prints one line per reap; silent when there is nothing to do, so it is safe to
|
|
26
|
+
# run as a Monitor for the length of a session.
|
|
27
|
+
set -uo pipefail
|
|
28
|
+
|
|
29
|
+
INTERVAL=60
|
|
30
|
+
ONCE=0
|
|
31
|
+
DRY=0
|
|
32
|
+
while [ $# -gt 0 ]; do
|
|
33
|
+
case "$1" in
|
|
34
|
+
--once) ONCE=1 ;;
|
|
35
|
+
--dry-run) DRY=1 ;;
|
|
36
|
+
--interval) INTERVAL="${2:-60}"; shift ;;
|
|
37
|
+
*) echo "gate-reaper: unknown flag $1" >&2; exit 2 ;;
|
|
38
|
+
esac
|
|
39
|
+
shift
|
|
40
|
+
done
|
|
41
|
+
|
|
42
|
+
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
43
|
+
# Gate on ELAPSED age, not CPU. An abandoned `bun run verify` parent sits at 0s
|
|
44
|
+
# CPU while the children it spawned burn the box -- gating on the parent's own
|
|
45
|
+
# CPU skipped exactly the processes worth reaping (measured: pids 79192 and
|
|
46
|
+
# 84649, both 0s CPU, both abandoned). ppid=1 plus a gate shape is already
|
|
47
|
+
# conclusive: a live gate always has a live parent. The age floor only avoids
|
|
48
|
+
# racing a process that was reparented moments ago.
|
|
49
|
+
# 90s was too eager. A gate is reparented the moment its launching shell exits,
|
|
50
|
+
# and that happens to LIVE work -- the harness killing a Bash call at its timeout,
|
|
51
|
+
# or a backgrounded launch -- not only to abandoned work. Reaping a 96s-old gate
|
|
52
|
+
# that a fleet is still waiting on slows the plan down, which is the opposite of
|
|
53
|
+
# the point. Every genuinely dead gate found by hand was 7-22 MINUTES old, and
|
|
54
|
+
# the harness Bash ceiling is 600s, so a gate still orphaned past that is one
|
|
55
|
+
# nobody is coming back for.
|
|
56
|
+
MIN_AGE_SECS="${GATE_REAPER_MIN_AGE:-900}"
|
|
57
|
+
|
|
58
|
+
# Elapsed seconds, from `ps -o etime=` (MM:SS | HH:MM:SS | D-HH:MM:SS)
|
|
59
|
+
_age_secs() {
|
|
60
|
+
printf '%s' "$1" | awk -F: '
|
|
61
|
+
{ d=0; s=$0
|
|
62
|
+
if (s ~ /-/) { split(s, a, "-"); d=a[1]; s=a[2]; n=split(s, b, ":") }
|
|
63
|
+
else n=split(s, b, ":")
|
|
64
|
+
if (n==3) print int(d*86400 + b[1]*3600 + b[2]*60 + b[3])
|
|
65
|
+
else if (n==2) print int(d*86400 + b[1]*60 + b[2])
|
|
66
|
+
else print int(0+s) }'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
sweep() {
|
|
70
|
+
local pid ppid age cmd secs pgid n=0
|
|
71
|
+
while read -r pid ppid age cmd; do
|
|
72
|
+
[ "$ppid" = "1" ] || continue
|
|
73
|
+
# Match the GATE SHAPE, never "mentions the repo". The first draft keyed on
|
|
74
|
+
# the repo path and its dry run would have killed cc-connect, the Telegram
|
|
75
|
+
# listener, a `claude daemon run` and Cursor's extension host -- all of them
|
|
76
|
+
# legitimate long-lived daemons that are ppid=1 by design, exactly like an
|
|
77
|
+
# abandoned gate. A reaper that cannot tell those apart is worse than no
|
|
78
|
+
# reaper. Only these shapes are gates, and a gate is always short-lived:
|
|
79
|
+
case "$cmd" in
|
|
80
|
+
*gate-run.sh*) ;;
|
|
81
|
+
*"vitest"*) ;;
|
|
82
|
+
*"tsc --noEmit"*) ;;
|
|
83
|
+
*"astro check"*) ;;
|
|
84
|
+
*"run verify"*|*"run test"*|*"run typecheck"*) ;;
|
|
85
|
+
# An `astro dev` / `wrangler dev` that dies leaves its workerd child behind,
|
|
86
|
+
# reparented and holding ~40MB for as long as the box is up. FIVE of them
|
|
87
|
+
# accumulated in one hour on 2026-09-05 (ages 33-63min, ~196MB) while both
|
|
88
|
+
# this reaper and machine-check reported "orphans: none" -- neither knew the
|
|
89
|
+
# shape, so nothing in the harness could see them. A LIVE workerd always has
|
|
90
|
+
# a live parent (astro dev, or wrangler), so the ppid==1 test above already
|
|
91
|
+
# spares it; the node_modules anchor keeps this to runtimes THIS tree spawned
|
|
92
|
+
# and away from any workerd a user runs by hand.
|
|
93
|
+
*node_modules*workerd" serve"*) ;;
|
|
94
|
+
*) continue ;;
|
|
95
|
+
esac
|
|
96
|
+
# Belt and braces: never a daemon, an editor, or anything system-owned, even
|
|
97
|
+
# if one of the words above appears somewhere in its argv.
|
|
98
|
+
case "$cmd" in
|
|
99
|
+
*/System/*|/usr/sbin/*|/usr/libexec/*|*Applications*) continue ;;
|
|
100
|
+
*cc-connect*|*tg-listen*|*"daemon run"*|*Cursor*|*Code\ Helper*) continue ;;
|
|
101
|
+
*--watch*|*watchexec*|*nodemon*) continue ;;
|
|
102
|
+
esac
|
|
103
|
+
secs=$(_age_secs "$age")
|
|
104
|
+
[ "${secs:-0}" -ge "$MIN_AGE_SECS" ] || continue
|
|
105
|
+
pgid=$(ps -o pgid= -p "$pid" 2>/dev/null | tr -d ' ')
|
|
106
|
+
if [ "$DRY" = "1" ]; then
|
|
107
|
+
echo "[gate-reaper] WOULD reap pid=$pid pgid=${pgid:-?} age=${secs}s ${cmd:0:60}"
|
|
108
|
+
else
|
|
109
|
+
echo "[gate-reaper] reaped abandoned gate pid=$pid pgid=${pgid:-?} age=${secs}s ${cmd:0:60}"
|
|
110
|
+
kill -TERM -"${pgid:-$pid}" 2>/dev/null || kill -TERM "$pid" 2>/dev/null || true
|
|
111
|
+
fi
|
|
112
|
+
n=$((n + 1))
|
|
113
|
+
done < <(ps -Ao pid=,ppid=,etime=,command= 2>/dev/null)
|
|
114
|
+
|
|
115
|
+
# A dead owner's lock shrinks the gate cap until someone contends. Reclaim it.
|
|
116
|
+
if [ "$DRY" != "1" ] && [ -f "$REPO/.claude/scripts/lib/govern.sh" ]; then
|
|
117
|
+
local reclaimed
|
|
118
|
+
reclaimed=$(bash -c ". '$REPO/.claude/scripts/lib/govern.sh'; gate_sweep_stale; claim_sweep" 2>/dev/null | tr '\n' ' ')
|
|
119
|
+
case "$reclaimed" in *[1-9]*) echo "[gate-reaper] reclaimed stale governor locks/claims: $reclaimed" ;; esac
|
|
120
|
+
fi
|
|
121
|
+
return 0
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if [ "$ONCE" = "1" ]; then sweep; exit 0; fi
|
|
125
|
+
while :; do sweep; sleep "$INTERVAL"; done
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gate-run.sh — run a heavy build gate under the machine-wide governor.
|
|
3
|
+
#
|
|
4
|
+
# gate-run.sh <label> -- <command...>
|
|
5
|
+
#
|
|
6
|
+
# Three things it guarantees that a bare `bun run verify` does not:
|
|
7
|
+
# 1. SLOT — at most GOVERN_MAX_GATES heavy gates run machine-wide, across
|
|
8
|
+
# every Claude Code session and every worktree. Session 3+ queues.
|
|
9
|
+
# 2. BOUND — a hard wall-clock cap; macOS ships no timeout(1), so an
|
|
10
|
+
# unbounded gate ran 18 min under load and outlived its session.
|
|
11
|
+
# 3. REAP — kills the whole PROCESS GROUP on timeout/exit. Killing only the
|
|
12
|
+
# shell reparents bun/node/vitest-forks to launchd, which is how
|
|
13
|
+
# orphaned verify trees leaked here.
|
|
14
|
+
#
|
|
15
|
+
# Degrades to a plain exec if the governor lib is missing (CI, fresh clone).
|
|
16
|
+
set -uo pipefail
|
|
17
|
+
|
|
18
|
+
LABEL="${1:-gate}"; shift || true
|
|
19
|
+
[ "${1:-}" = "--" ] && shift
|
|
20
|
+
|
|
21
|
+
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/govern.sh"
|
|
22
|
+
if [ ! -f "$LIB" ]; then exec "$@"; fi
|
|
23
|
+
. "$LIB"
|
|
24
|
+
|
|
25
|
+
# CI and explicit opt-out run at full throughput.
|
|
26
|
+
if [ -n "${CI:-}" ] || [ "${GOVERN_DISABLE:-0}" = "1" ]; then exec "$@"; fi
|
|
27
|
+
|
|
28
|
+
# Re-entrancy: `verify` calls `test`, and both are wrapped. A nested gate must
|
|
29
|
+
# INHERIT the outer slot, never take a second one — two slots held by one
|
|
30
|
+
# logical gate would deadlock the queue at cap 2.
|
|
31
|
+
if [ "${GOVERN_IN_GATE:-0}" = "1" ]; then exec "$@"; fi
|
|
32
|
+
export GOVERN_IN_GATE=1
|
|
33
|
+
|
|
34
|
+
trap 'gate_release_all' EXIT INT TERM
|
|
35
|
+
|
|
36
|
+
# SAY THAT YOU ARE QUEUEING.
|
|
37
|
+
#
|
|
38
|
+
# gate_slot blocks for up to 1800s and this script printed nothing until it had a
|
|
39
|
+
# slot, so a queued gate and a hung gate looked identical to whoever was waiting.
|
|
40
|
+
# Measured 2026-09-01: lifecycle C8's demo gate was reported by its verifier as
|
|
41
|
+
# "timed out (>120s) and did not complete", and the cycle failed. Run directly it
|
|
42
|
+
# passes 6/6 in 1.19 SECONDS. Nothing was broken — the gate was waiting behind
|
|
43
|
+
# other gates in silence, and the verifier gave up and called it a hang.
|
|
44
|
+
#
|
|
45
|
+
# A wait that is announced is a wait. A wait that is silent is read as a failure.
|
|
46
|
+
_held=$(ls -d "$GOVERN_DIR"/lock-slot-* 2>/dev/null | wc -l | tr -d ' ')
|
|
47
|
+
# The EFFECTIVE cap is min(config, what memory can fund) — gate_slot takes that
|
|
48
|
+
# min every retry. Announcing the config alone misreported the wait on exactly
|
|
49
|
+
# the boxes where the memory probe is doing its job.
|
|
50
|
+
_head=$(gate_headroom 2>/dev/null || echo 99)
|
|
51
|
+
_cap="${GOVERN_MAX_GATES:-2}"
|
|
52
|
+
[ "$_head" -lt "$_cap" ] 2>/dev/null && _cap="$_head"
|
|
53
|
+
[ "${_cap:-0}" -lt 1 ] 2>/dev/null && _cap=1
|
|
54
|
+
if [ "${_held:-0}" -ge "$_cap" ]; then
|
|
55
|
+
echo "[gate-run] $LABEL: QUEUEING for a slot (${_held}/${_cap} held; config ${GOVERN_MAX_GATES:-2}, memory funds ${_head} at $(gate_price_mb)MB each). This is a WAIT, not a hang — do not report it as a timeout. Set GOVERN_QUEUE_WAIT to bound it, or GOVERN_DISABLE=1 to bypass." >&2
|
|
56
|
+
fi
|
|
57
|
+
_q0=$(date +%s)
|
|
58
|
+
|
|
59
|
+
if ! gate_slot "${GOVERN_QUEUE_WAIT:-1800}"; then
|
|
60
|
+
echo "[gate-run] $LABEL: no slot after ${GOVERN_QUEUE_WAIT:-1800}s — machine saturated." >&2
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
if ! gate_pressure; then
|
|
65
|
+
echo "[gate-run] $LABEL: WARNING — low free memory; running anyway in slot $GOVERN_SLOT." >&2
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
_qw=$(( $(date +%s) - _q0 ))
|
|
69
|
+
echo "[gate-run] $LABEL: running in $GOVERN_SLOT (cap ${GOVERN_MAX_GATES} machine-wide$([ "$_qw" -gt 2 ] && echo ", queued ${_qw}s"))" >&2
|
|
70
|
+
run_bounded "${GOVERN_GATE_TIMEOUT:-1800}" "$@"
|
|
71
|
+
rc=$?
|
|
72
|
+
gate_release_all; trap - EXIT INT TERM
|
|
73
|
+
exit $rc
|