@oneie/claude 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/agents/abm-strategist.md +67 -1
- package/agents/ads-meta.md +67 -1
- package/agents/analyst.md +67 -1
- package/agents/animator.md +108 -0
- package/agents/architect.md +269 -20
- package/agents/brand-guardian.md +67 -1
- package/agents/brand-strategist.md +67 -1
- package/agents/campaign-content.md +67 -1
- package/agents/campaign-email.md +67 -1
- package/agents/campaign-sms.md +67 -1
- package/agents/campaign-social.md +67 -1
- package/agents/cco.md +83 -2
- package/agents/ceo.md +108 -11
- package/agents/chairman.md +197 -0
- package/agents/cmo.md +82 -2
- package/agents/community-greeter.md +67 -1
- package/agents/community-moderator.md +67 -1
- package/agents/compliance.md +67 -1
- package/agents/copywriter.md +67 -1
- package/agents/creative-strategist.md +67 -1
- package/agents/cro.md +81 -1
- package/agents/cto.md +266 -28
- package/agents/customer-interviewer.md +67 -1
- package/agents/customer-researcher.md +67 -1
- package/agents/customer-success-manager.md +67 -1
- package/agents/customer-trainer.md +67 -1
- package/agents/cxo.md +82 -1
- package/agents/demand-creator.md +67 -1
- package/agents/demo-mover.md +67 -1
- package/agents/demo-specialist.md +67 -1
- package/agents/demo-thai-family-law.md +67 -1
- package/agents/designer.md +67 -1
- package/agents/discovery-caller.md +67 -1
- package/agents/doctor.md +269 -0
- package/agents/educate-coach.md +67 -1
- package/agents/elevate-tutor.md +67 -1
- package/agents/email-lifecycle-marketer.md +67 -1
- package/agents/engage-specialist.md +67 -1
- package/agents/events-coordinator.md +67 -1
- package/agents/foundation-builder.md +67 -1
- package/agents/funnel-architect.md +67 -1
- package/agents/gift-creator.md +67 -1
- package/agents/google-ads.md +67 -1
- package/agents/guide.md +67 -1
- package/agents/helpdesk-dispatcher.md +67 -1
- package/agents/hook-specialist.md +67 -1
- package/agents/identify-optimizer.md +67 -1
- package/agents/implementer.md +313 -45
- package/agents/incident-commander.md +67 -1
- package/agents/insights-lead.md +87 -1
- package/agents/journey-runner.md +67 -1
- package/agents/linkedin-ads.md +67 -1
- package/agents/live-sales-chat.md +67 -1
- package/agents/market-researcher.md +67 -1
- package/agents/media-buyer.md +67 -1
- package/agents/memory-keeper.md +195 -0
- package/agents/movers-customer-researcher.md +67 -1
- package/agents/movers-foundation-builder.md +67 -1
- package/agents/movers-market-researcher.md +67 -1
- package/agents/movers-pricing-strategist.md +67 -1
- package/agents/nurture-architect.md +67 -1
- package/agents/offer-architect.md +67 -1
- package/agents/onboarder.md +67 -1
- package/agents/onboarding-specialist.md +67 -1
- package/agents/operations-dashboard.md +87 -1
- package/agents/perf-engineer.md +333 -37
- package/agents/playbook-writer.md +67 -1
- package/agents/plg-strategist.md +67 -1
- package/agents/positioning-architect.md +67 -1
- package/agents/press-officer.md +67 -1
- package/agents/pricing-strategist.md +67 -1
- package/agents/privacy-officer.md +67 -1
- package/agents/referral-manager.md +67 -1
- package/agents/refine-analyst.md +67 -1
- package/agents/release-manager.md +446 -39
- package/agents/renewals-upsell-rep.md +67 -1
- package/agents/review-engineer.md +319 -45
- package/agents/rewards-steward.md +67 -1
- package/agents/sales-call-coach.md +67 -1
- package/agents/sales-closer.md +67 -1
- package/agents/security-auditor.md +343 -48
- package/agents/sell-closer.md +67 -1
- package/agents/share-amplifier.md +67 -1
- package/agents/social-media-manager.md +67 -1
- package/agents/storyteller.md +301 -0
- package/agents/strategist.md +67 -1
- package/agents/strategy-aligner.md +67 -1
- package/agents/support-agent.md +67 -1
- package/agents/tagger.md +327 -0
- package/agents/tech-writer.md +195 -22
- package/agents/test-engineer.md +398 -29
- package/agents/tiktok-ads.md +67 -1
- package/agents/tracking-engineer.md +67 -1
- package/agents/trailkeeper.md +181 -0
- package/agents/upsell-strategist.md +67 -1
- package/agents/voice.md +67 -1
- package/agents/w1-recon.md +1 -1
- package/agents/w2-decide.md +1 -1
- package/agents/w3-edit.md +8 -2
- package/agents/w4-verify.md +13 -0
- package/agents/workflow-optimiser.md +81 -1
- package/commands/close.md +916 -160
- package/commands/deploy.md +102 -724
- package/commands/do.md +58 -2
- package/commands/sweep.md +159 -0
- package/commands/tasks.md +222 -0
- package/hooks/scripts/dev-only.sh +135 -0
- package/hooks/scripts/git-add-guard.sh +37 -2
- package/hooks/scripts/session-start.sh +32 -4
- package/package.json +1 -1
- package/rules/scripts.md +85 -0
- package/scripts/CLAUDE.md +315 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agent-actor-parity.sh +129 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/chrome.mjs +18 -0
- package/scripts/close-metrics.sh +587 -0
- package/scripts/close-owner.sh +326 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-emit.sh +311 -0
- package/scripts/deploy-gate-check.sh +155 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy-schema-check.sh +58 -0
- package/scripts/deploy.sh +393 -243
- package/scripts/do-auto.sh +127 -26
- package/scripts/do-board.sh +429 -0
- package/scripts/do-close.sh +1184 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-fleet.sh +8 -2
- package/scripts/do-plan-json.mjs +110 -12
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +86 -10
- package/scripts/do-rank.py +200 -3
- package/scripts/do-reconcile.sh +73 -12
- package/scripts/do-signal.sh +101 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-w4-gates.sh +11 -1
- package/scripts/do-world-check.sh +153 -0
- package/scripts/download-stats.sh +172 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +18 -1
- 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 +71 -0
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/flywheel-outcome.sh +63 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +9 -0
- package/scripts/gate-watchdog.sh +619 -0
- package/scripts/gc-content-check.sh +142 -0
- package/scripts/gh-traffic-capture.sh +153 -0
- package/scripts/govern-order-check.sh +202 -0
- package/scripts/governor-doors-check.sh +86 -5
- package/scripts/health.sh +448 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/incident.sh +212 -0
- package/scripts/land.sh +755 -45
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/livekit-ratchet.sh +18 -1
- package/scripts/machine-check.sh +1 -1
- package/scripts/memory-index-budget.sh +79 -0
- package/scripts/npm-downloads.sh +109 -0
- package/scripts/one-agents.mjs +204 -8
- package/scripts/one-resume.sh +31 -3
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/redirect-lint.sh +169 -0
- package/scripts/release.sh +40 -6
- package/scripts/resume-lost-sessions.sh +68 -0
- package/scripts/shoot-pages.mjs +140 -0
- package/scripts/signal-meta-backfill.ts +451 -0
- package/scripts/signal-watch.sh +63 -6
- package/scripts/speed-cache-check.sh +12 -2
- package/scripts/sweep.sh +426 -0
- package/scripts/task-titles-dump.ts +101 -0
- package/scripts/test-cached.sh +47 -10
- package/scripts/test-lanes.sh +14 -0
- package/scripts/thread-name-backfill.ts +215 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +155 -8
- package/scripts/typedb-flake-check.sh +3 -1
- package/scripts/urls-lint.sh +8 -0
- package/scripts/verify-board-doors.sh +80 -0
- package/scripts/verify-fast.sh +159 -6
- package/scripts/worktree-up.sh +21 -3
- package/skills/astro/SKILL.md +9 -3
- package/skills/astro/optimize-performance.md +3 -2
- package/skills/cloudflare/SKILL.md +3 -2
- package/skills/cloudflare-security-audit/AI-AND-LLM.md +83 -0
- package/skills/cloudflare-security-audit/ATTACK-CLASSES.md +130 -0
- package/skills/cloudflare-security-audit/CLIENT-SIDE.md +83 -0
- package/skills/cloudflare-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
- package/skills/cloudflare-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
- package/skills/cloudflare-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
- package/skills/cloudflare-security-audit/HUNTING.md +251 -0
- package/skills/cloudflare-security-audit/LICENSE +21 -0
- package/skills/cloudflare-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
- package/skills/cloudflare-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
- package/skills/cloudflare-security-audit/PROVENANCE.md +78 -0
- package/skills/cloudflare-security-audit/RECONNAISSANCE.md +156 -0
- package/skills/cloudflare-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
- package/skills/cloudflare-security-audit/SKILL.md +192 -0
- package/skills/cloudflare-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
- package/skills/cloudflare-security-audit/VALIDATION-AND-REPORTING.md +186 -0
- package/skills/cloudflare-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
- package/skills/cloudflare-security-audit/report-schema.json +461 -0
- package/skills/cloudflare-security-audit/validate-coverage-ledger.cjs +872 -0
- package/skills/cloudflare-security-audit/validate-coverage-ledger.test.cjs +740 -0
- package/skills/cloudflare-security-audit/validate-findings.cjs +773 -0
- package/skills/cloudflare-security-audit/validate-findings.test.cjs +652 -0
- package/skills/deploy/REFERENCE.md +713 -0
- package/skills/deploy/SKILL.md +140 -0
- package/skills/fleet-audit/SKILL.md +58 -0
- package/skills/meeting/SKILL.md +220 -0
- package/skills/planning/SKILL.md +256 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/typedb/SKILL.md +7 -0
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/templates/template-agent.md +7 -1
- package/templates/template-feature.md +9 -0
- package/templates/template-todo.md +29 -0
|
@@ -54,7 +54,18 @@ _is_blanket() {
|
|
|
54
54
|
fi
|
|
55
55
|
return 1
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
# Any commit on the shared main tree — main receives PRs, never development.
|
|
58
|
+
# QUOTE-STRIPPED and POSITION-ANCHORED, so a command that merely NAMES a commit
|
|
59
|
+
# — a grep for "git commit", a commit message quoting it, a doc being written —
|
|
60
|
+
# is never refused. Measured 2026-09-07: the unanchored form denied
|
|
61
|
+
# `grep -rn "git commit" text/`.
|
|
62
|
+
_is_commit() {
|
|
63
|
+
local bare
|
|
64
|
+
bare=$(printf '%s' "$1" | sed -E "s/'[^']*'//g; s/\"[^\"]*\"//g")
|
|
65
|
+
printf '%s' "$bare" | grep -qE '(^|[;&|][[:space:]]*)git[^;&|]*[[:space:]]commit([[:space:]]|$)'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_is_blanket "$CMD" || _is_commit "$CMD" || exit 0
|
|
58
69
|
|
|
59
70
|
# Carve-out: deploy/release publishes the whole tree on purpose.
|
|
60
71
|
printf '%s' "$CMD" | grep -qE '\bgit\b[^|&;]*\bpush\b' && exit 0
|
|
@@ -78,7 +89,31 @@ GITDIR=$(git -C "$TARGET" rev-parse --absolute-git-dir 2>/dev/null)
|
|
|
78
89
|
[[ -z "$GITDIR" ]] && exit 0 # not a git repo — let git handle it
|
|
79
90
|
case "$GITDIR" in */worktrees/*) exit 0 ;; esac # isolated linked worktree — blanket is safe
|
|
80
91
|
|
|
81
|
-
# Main tree → deny.
|
|
92
|
+
# Main tree → deny. A plain commit gets the dev-only rule; a blanket action gets
|
|
93
|
+
# the sweep warning. Both refuse; the reason must name the right one.
|
|
94
|
+
if ! _is_blanket "$CMD"; then
|
|
95
|
+
CMSG="[git-add-guard] Development on the shared main tree is blocked — we develop on dev.
|
|
96
|
+
|
|
97
|
+
main receives a PULL REQUEST from dev and nothing else. It is never edited,
|
|
98
|
+
never committed to directly, and production only ever ships from a sha that
|
|
99
|
+
arrived on main that way.
|
|
100
|
+
|
|
101
|
+
Do this instead:
|
|
102
|
+
bash .claude/scripts/worktree-up.sh <name> --no-dev # cut from dev
|
|
103
|
+
...edit, then...
|
|
104
|
+
bash .claude/scripts/land.sh feat/<name> # lands on DEV
|
|
105
|
+
gh pr create --base main --head dev # the only door to main
|
|
106
|
+
|
|
107
|
+
Why: on 2026-09-07 four commits went straight onto main while seven sessions
|
|
108
|
+
shared this tree, and an hour-cold ad conversion map plus its test sat
|
|
109
|
+
uncommitted in it — invisible to every branch, and one blanket stage away from
|
|
110
|
+
landing in a stranger's commit.
|
|
111
|
+
|
|
112
|
+
Override once, for a deliberate rescue: ECC_DISABLED_HOOKS=hook:git-add-guard"
|
|
113
|
+
jq -nc --arg r "$CMSG" '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":$r}}'
|
|
114
|
+
exit 0
|
|
115
|
+
fi
|
|
116
|
+
|
|
82
117
|
MSG="[git-add-guard] Blanket staging/stashing on the shared main tree is blocked.
|
|
83
118
|
|
|
84
119
|
Multiple windows share this working tree. A blanket add (-A, bare dot, commit -a)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# SESSION-START — one job: reclaim what a dead session left behind,
|
|
3
|
-
# the
|
|
2
|
+
# SESSION-START — one job: reclaim what a dead session left behind — and, since
|
|
3
|
+
# 2026-09-13, break the one jam whose owners are both ALIVE — then tell the world
|
|
4
|
+
# a session began.
|
|
4
5
|
#
|
|
5
6
|
# `_gv_reap` only fires when someone CONTENDS for a lock, so a session that died
|
|
6
7
|
# holding a governor slot would shrink GOVERN_MAX_GATES until the next contender
|
|
@@ -25,11 +26,38 @@ if [ -f "$CLAUDE_PROJECT_DIR/.claude/scripts/lib/govern.sh" ]; then
|
|
|
25
26
|
CLAIMS_SWEPT=$(claim_sweep 2>/dev/null || echo 0)
|
|
26
27
|
fi
|
|
27
28
|
|
|
29
|
+
# An `astro dev` / `wrangler dev` that dies leaves its workerd child reparented
|
|
30
|
+
# to launchd, holding ~40MB until the box reboots. gate-reaper.sh has known that
|
|
31
|
+
# shape since 2026-09-05 but nothing ever RAN it: on 2026-09-05 nineteen of them
|
|
32
|
+
# had accumulated over 13 hours and the box sat at 20.5G of 21.5G swap with
|
|
33
|
+
# 997MB free. Reaping them returned swap to 7.8G used / 8.6G free in three
|
|
34
|
+
# seconds. The reaper is 60ms, silent when there is nothing to do, and gated on
|
|
35
|
+
# ppid==1 + a gate shape + a 15-minute age floor, so it can never touch live work.
|
|
36
|
+
REAPED=0
|
|
37
|
+
if [ -x "$CLAUDE_PROJECT_DIR/.claude/scripts/gate-reaper.sh" ]; then
|
|
38
|
+
REAPED=$(bash "$CLAUDE_PROJECT_DIR/.claude/scripts/gate-reaper.sh" --once 2>/dev/null | grep -c '^\[gate-reaper\] reaped' || true)
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# The reaper reaps a DEAD owner. The 2026-09-13 deadlock has two LIVE owners —
|
|
42
|
+
# a lock holder with no slot, and a slot holder waiting on that lock — so no
|
|
43
|
+
# reaper could ever see it, and it stalled every session on the box for 8-28
|
|
44
|
+
# minutes at 0% CPU, three times in 35 minutes. gate-watchdog is the one thing
|
|
45
|
+
# that cuts it. A healthy box costs 0.12s and prints nothing; it only spends the
|
|
46
|
+
# confirm delay when it has already found a jam. Its predicate is positive
|
|
47
|
+
# evidence of a closed cycle (--self-test drives all 14 checks, both halves), so
|
|
48
|
+
# a slow-but-working gate is never the victim and a slot owner never is either.
|
|
49
|
+
JAMMED=0
|
|
50
|
+
if [ -x "$CLAUDE_PROJECT_DIR/.claude/scripts/gate-watchdog.sh" ]; then
|
|
51
|
+
JAMMED=$(bash "$CLAUDE_PROJECT_DIR/.claude/scripts/gate-watchdog.sh" --once 2>/dev/null | grep -c '^\[gate-watchdog\] BREAK' || true)
|
|
52
|
+
fi
|
|
53
|
+
|
|
28
54
|
OUT=""
|
|
55
|
+
[ "${JAMMED:-0}" -gt 0 ] 2>/dev/null && OUT+="🧹 broke ${JAMMED} governor deadlock(s) — a lock was held with no slot while every slot sat idle\n"
|
|
56
|
+
[ "${REAPED:-0}" -gt 0 ] 2>/dev/null && OUT+="🧹 reaped ${REAPED} abandoned gate(s)/workerd orphan(s) burning the box\n"
|
|
29
57
|
[ "${SWEPT:-0}" -gt 0 ] 2>/dev/null && OUT+="🧹 reclaimed ${SWEPT} stale build slot(s) from a dead session\n"
|
|
30
58
|
[ "${CLAIMS_SWEPT:-0}" -gt 0 ] 2>/dev/null && OUT+="🧹 reclaimed ${CLAIMS_SWEPT} region claim(s) from a dead worker\n"
|
|
31
59
|
[ -n "$OUT" ] && { echo ""; printf "%b" "$OUT"; echo ""; }
|
|
32
60
|
|
|
33
|
-
emit_signal "hook:session-start:ok" 1 "swept=${SWEPT:-0} claims=${CLAIMS_SWEPT:-0}"
|
|
34
|
-
emit_world "session:start" "fyi" "session,start" "session started" "swept=${SWEPT:-0}" "claims=${CLAIMS_SWEPT:-0}"
|
|
61
|
+
emit_signal "hook:session-start:ok" 1 "swept=${SWEPT:-0} claims=${CLAIMS_SWEPT:-0} reaped=${REAPED:-0} jams=${JAMMED:-0}"
|
|
62
|
+
emit_world "session:start" "fyi" "session,start" "session started" "swept=${SWEPT:-0}" "claims=${CLAIMS_SWEPT:-0}" "reaped=${REAPED:-0}" "jams=${JAMMED:-0}"
|
|
35
63
|
exit 0
|
package/package.json
CHANGED
package/rules/scripts.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- ".claude/scripts/**"
|
|
4
|
+
- "packages/claude/scripts/**"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Harness script rules
|
|
8
|
+
|
|
9
|
+
Short on purpose. Every rule here is imperative and true at the line it guards;
|
|
10
|
+
the story that earned it is one command away and is never summarised:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bash .claude/scripts/incident.sh --for <script> # the accounts for this file
|
|
14
|
+
bash .claude/scripts/incident.sh # every family
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Two neighbours, so nothing here restates them: `.claude/scripts/CLAUDE.md` is
|
|
18
|
+
the per-script INVENTORY (what each door does, and the SIGPIPE/`grep -q` and
|
|
19
|
+
governor sections), loaded when you work in that directory;
|
|
20
|
+
`.claude/incidents/` is the ACCOUNTS. This file is only the imperatives.
|
|
21
|
+
|
|
22
|
+
`incident.sh` and `.claude/incidents/` are **monorepo-only** — they hold this
|
|
23
|
+
repo's measured history and do not ship to the factory clone. In a generated
|
|
24
|
+
clone the rules below still apply; the accounts behind them do not travel.
|
|
25
|
+
|
|
26
|
+
## Writing one
|
|
27
|
+
|
|
28
|
+
- **Declare the manifest — in the TABLE.** The authority is the heredoc in
|
|
29
|
+
`factory-repo.sh` (`portable | needs-env | monorepo-only <script>`); the
|
|
30
|
+
`# manifest:` line in the script itself is a convenience copy the check does
|
|
31
|
+
**not** read. `factory-repo.sh` **refuses to build** with an unclassified
|
|
32
|
+
script, and `--check-portability` only sees TRACKED files — so a new script
|
|
33
|
+
passes the check right up until you commit it.
|
|
34
|
+
- **Split the comment.** A block is two things: the RULE (imperative, 1-2 lines,
|
|
35
|
+
stays inline) and the ACCOUNT (dates, shas, counts, the wrong diagnosis that
|
|
36
|
+
came first — moves to `.claude/incidents/<family>.md` under a `## <id>`, and
|
|
37
|
+
the script keeps a bare `incident:<id>` token). **Carry every measurement,
|
|
38
|
+
date, sha, count and error string over unchanged** — a trap loses its authority
|
|
39
|
+
the moment its numbers are summarised away; rewrapping the prose around them is
|
|
40
|
+
fine. `incident.sh --check` proves every pointer resolves and every account is
|
|
41
|
+
reachable, both directions, both copies — but it scans DIRECTORIES, so it
|
|
42
|
+
cannot see a broken `--for`, which is how deploy.sh's pointers resolved for the
|
|
43
|
+
checker and not for the reader for an hour.
|
|
44
|
+
- **Both copies.** `packages/claude/scripts/` is a hand-maintained verbatim
|
|
45
|
+
mirror with no build step, so it rots silently. `sync-claude-mirror.sh` after
|
|
46
|
+
every edit; a proof that covers canon and not the copy that ships is worse than
|
|
47
|
+
no proof.
|
|
48
|
+
- **`set -uo pipefail`, never `-e`.** These scripts read exit codes as answers
|
|
49
|
+
(`git diff --quiet` exits 1 to mean "differs"); `-e` turns an answer into a
|
|
50
|
+
crash.
|
|
51
|
+
|
|
52
|
+
## Gates and exit codes
|
|
53
|
+
|
|
54
|
+
- **Never pipe a gate.** `bun run verify | tail` reports tail's status — measured
|
|
55
|
+
reporting **0** for an exit-1 verify. Redirect to a file, or read
|
|
56
|
+
`PIPESTATUS[0]`. In the Claude Code Bash tool (zsh) `PIPESTATUS` is **empty**:
|
|
57
|
+
use `${pipestatus[1]}` or a file.
|
|
58
|
+
- **An unrun gate is never a pass.** `gate-run.sh` exits **127** when its target
|
|
59
|
+
is not on `PATH`. Invoke by absolute path, and distinguish `pass|fail|unrun|n/a`.
|
|
60
|
+
- **Everything heavy goes through `gate-run.sh`** — a raw `vitest` bypasses the
|
|
61
|
+
governor and `hook:load-guard` blocks it.
|
|
62
|
+
- **Exit codes are distinct and named.** "It refused" is not a finding: give each
|
|
63
|
+
refusal its own code and say which one bit.
|
|
64
|
+
|
|
65
|
+
## Proofs
|
|
66
|
+
|
|
67
|
+
- **A checker must be able to go RED.** Ship a `--self-test` that drives the
|
|
68
|
+
failure, not only the success, and prove it by breaking the code on a copy.
|
|
69
|
+
- **A `.sh` is in no import graph**, so `vitest related` can never select it. A
|
|
70
|
+
shell proof the suite must keep running needs a vitest wrapper **and** a name
|
|
71
|
+
in `VERIFY_FAST_PINS` (`verify-fast.sh`) — otherwise it stops being run the
|
|
72
|
+
moment its author's session ends.
|
|
73
|
+
- **A fixture looser than production certifies the bug.** Build the sandbox to
|
|
74
|
+
match the real tree's shape, then assert.
|
|
75
|
+
|
|
76
|
+
## Doors
|
|
77
|
+
|
|
78
|
+
- **Run door scripts from the MAIN tree's copy.** `land.sh`, `release.sh`,
|
|
79
|
+
`deploy.sh` and `sweep.sh` all derive `ROOT` from `${BASH_SOURCE[0]}/../..`; a
|
|
80
|
+
worktree's copy operates on that worktree.
|
|
81
|
+
- **And check the main tree is CURRENT** — `git rev-list --count HEAD..origin/main`
|
|
82
|
+
is exactly how many commits old the script you are about to run is. `git status`
|
|
83
|
+
clean says nothing about it.
|
|
84
|
+
- **Deploy and ship are `release-manager`'s**, spawned as a subagent; the box is
|
|
85
|
+
`doctor`'s. See `.claude/commands/deploy.md` and `/sweep`.
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# CLAUDE.md — `.claude/scripts/`
|
|
2
|
+
|
|
3
|
+
Moved from `.claude/CLAUDE.md` so it loads only when working here.
|
|
4
|
+
|
|
5
|
+
## Script notes
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
├── scripts/ # 65 entries — `ls .claude/scripts/` is the map, not this comment.
|
|
9
|
+
│ # deploy.sh — the whole /deploy pipeline, deterministic:
|
|
10
|
+
│ # gates (tree, tsc ×5, vitest, build, creds, smoke, approval,
|
|
11
|
+
│ # D1) then parallel deploy + health probes. `./deploy` at the
|
|
12
|
+
│ # repo root execs it. It is the AUTHORITY for the deploy
|
|
13
|
+
│ # steps — .claude/commands/deploy.md carries no second copy.
|
|
14
|
+
│ # Its vitest gate PROBES THE MEMO BEFORE TAKING A SLOT (the
|
|
15
|
+
│ # `>>> vitest-memo-probe` block): measured 2026-09-13, the gate
|
|
16
|
+
│ # queued 300s under gate-run.sh and then reported both lanes as
|
|
17
|
+
│ # cache HITs. The probe asks PER LANE the way release.sh's
|
|
18
|
+
│ # receipt does (TEST_CACHE_KEY_ONLY=1 + a stamp per key) and
|
|
19
|
+
│ # skips only when EVERY lane is already green — never by reading
|
|
20
|
+
│ # test-lanes.sh's exit code, which under TYPEDB_LANE_NONBLOCKING=1
|
|
21
|
+
│ # is the pool lane's alone and would skip 19 typedb suites that
|
|
22
|
+
│ # had never run. A miss or an unanswerable probe takes the slot.
|
|
23
|
+
│ # A skipped gate counts as PASSED, stays OUT of HEAVY_IDX (a
|
|
24
|
+
│ # process-less gate there makes the next heavy gate `wait 0` —
|
|
25
|
+
│ # exit 127 — and overwrite its rc), and reports REUSED with the
|
|
26
|
+
│ # stamp's timestamp, never "all pass". --skip-tests is distinct.
|
|
27
|
+
│ # release.sh — the PROD door since 2026-09-05: `promote <sha>`
|
|
28
|
+
│ # fast-forwards branch `release`, materialises .release/ (deps
|
|
29
|
+
│ # by symlink, never bun install) and refuses unless that exact
|
|
30
|
+
│ # tree carries deploy's own full-suite receipt (test-cached
|
|
31
|
+
│ # stamp) + 5 clean tsc; `ship` runs deploy.sh --changed FROM
|
|
32
|
+
│ # .release/ so the suite gate is a memo HIT reported REUSED —
|
|
33
|
+
│ # no --skip-tests, ever. Nine named exit codes; --self-test is
|
|
34
|
+
│ # the red proof. Run MAIN's copy (from inside .release/ it
|
|
35
|
+
│ # resolves ROOT to .release and tries to add .release/.release).
|
|
36
|
+
│ # Step 9 (Lighthouse) is detached by default — SPEED_SYNC=1
|
|
37
|
+
│ # holds it inline, SPEED_TIMEOUT bounds it, 124 reads as unrun.
|
|
38
|
+
│ # Two of its gates are MEMOISED on a byte-identical tree: the
|
|
39
|
+
│ # full vitest suite via test-cached.sh (only passes; a reused
|
|
40
|
+
│ # pass is reported as reused, never as "all pass") and the
|
|
41
|
+
│ # astro build via astro-build-cached.sh (stamp inside dist/,
|
|
42
|
+
│ # key covers one.ie/web + packages/ + the gitignored .env and
|
|
43
|
+
│ # lockfiles). The typechecks are deliberately NOT cached —
|
|
44
|
+
│ # measured 8s serial, inside vitest's shadow, and tsc-cached
|
|
45
|
+
│ # shares a gate_lock with do-reconcile — both take a SLOT
|
|
46
|
+
│ # BEFORE that lock since 2026-09-13, never while holding it
|
|
47
|
+
│ # (govern-order-check.sh). An unpaid deferred-pin
|
|
48
|
+
│ # debt (verify-fast's ledger) now REFUSES a deploy that did not
|
|
49
|
+
│ # run the suite. Red proofs: deploy-gate-check.sh (extracts the
|
|
50
|
+
│ # debt block verbatim and drives it), plus --self-test on
|
|
51
|
+
│ # tsc-cached / test-cached / astro-build-cached.
|
|
52
|
+
│ # roles-check.sh — the nine authority checks (IC row 1 of
|
|
53
|
+
│ # roles-vantage-collapse); --check-gate plants a vantage-vs-rung
|
|
54
|
+
│ # comparison and proves one.ie/web's astro-check ratchet goes red.
|
|
55
|
+
│ # --check-floors runs the page-floor matrix (five callers per
|
|
56
|
+
│ # private page through the real guardPage) and proves an
|
|
57
|
+
│ # ANONYMOUS private page cannot land — it forces anonymity with
|
|
58
|
+
│ # a `signed_out=1` cookie, because DEV_SLUG otherwise makes a
|
|
59
|
+
│ # session-less localhost request the owner of the `one` subtree.
|
|
60
|
+
│ # --check-decide / --check-503 run the authority matrix
|
|
61
|
+
│ # (one.ie/web/tests/authority/) through the real decide(), then
|
|
62
|
+
│ # mutate the FIXTURE to prove the deny cells bite and that a
|
|
63
|
+
│ # swallowed lookup error cannot pass as a 403. --check-worldkey
|
|
64
|
+
│ # (C6) replays the same suite with a world-key Principal — the
|
|
65
|
+
│ # original bug report — and asserts a denial NAMES the node
|
|
66
|
+
│ # instead of comparing a vantage word to a rung word.
|
|
67
|
+
│ # land.sh — the BRANCH door. Bare: merge main in, fast gate in the
|
|
68
|
+
│ # branch's worktree, then --ff-only main. `--pr`: gate then open/
|
|
69
|
+
│ # update a PR, skipping the trunk merge so a reviewer sees what the
|
|
70
|
+
│ # branch added. `--pr --deploy`: the whole workflow in order —
|
|
71
|
+
│ # gate → ship THAT WORKTREE to dev.one.ie → prove the routes with
|
|
72
|
+
│ # do-prove (both bases pinned to dev; it falls back to PROVE_PROD_URL
|
|
73
|
+
│ # and would otherwise prove PRODUCTION) → open the PR carrying the
|
|
74
|
+
│ # dev URL. Reads the probe's ROUTE COUNT, never its exit code —
|
|
75
|
+
│ # `PROVE: skipped` exits 0. RUN MAIN'S COPY: from a worktree, ROOT is
|
|
76
|
+
│ # that worktree and the ff merges the branch into itself (always
|
|
77
|
+
│ # "already up to date", main untouched, three false "landed" reports
|
|
78
|
+
│ # on 2026-09-05). It now verifies with `merge-base --is-ancestor`.
|
|
79
|
+
│ # A red gate is DIAGNOSED, not blamed (2026-09-12): branch-alone and
|
|
80
|
+
│ # dev-alone typecheck read from the memo (`tsc-cached.sh --probe`,
|
|
81
|
+
│ # `TSC_CACHE_ROOT=<tree>`), a gateway pre-flight, and which side of
|
|
82
|
+
│ # the merge touched each failing file — branch / dev / seam /
|
|
83
|
+
│ # environment. The verdict is the run's --note. `--self-test` drives
|
|
84
|
+
│ # all seven shapes, including transport+assertion ⇒ NOT environment.
|
|
85
|
+
│ # It links node_modules for every tracked package.json rather than a
|
|
86
|
+
│ # list — the list missed channels/, pay/backend/ and one.ie/web/ in
|
|
87
|
+
│ # turn, each time reading as a RED gate on a branch of shell scripts.
|
|
88
|
+
│ # deploy-record.sh — appends a run to one.ie/web/src/data/deploy-runs.json,
|
|
89
|
+
│ # which /deploy renders (the factory.astro shape: data, not a live
|
|
90
|
+
│ # query — a deploy runs on a laptop and one-prod cannot read that disk).
|
|
91
|
+
│ # Called by deploy.sh and land.sh on BOTH exits, `|| true`. --self-test.
|
|
92
|
+
│ # Load-bearing few: do-reconcile.sh (7 canons) · do-auto.sh + do-fleet.sh
|
|
93
|
+
│ # (worktree cycles) · do-tier.sh (spine prune) · do-promise-lint/settle.sh
|
|
94
|
+
│ # · signal-watch.sh — every door the factory's signals cross (prod + local),
|
|
95
|
+
│ # with server-timing phases, cf-placement, ratchet budgets, a JSONL
|
|
96
|
+
│ # ledger and --self-test; the instrument for root CLAUDE.md § The brain
|
|
97
|
+
│ # and the edge (a receiver that queries TypeDB inline reads RED here)
|
|
98
|
+
│ # · chrome.mjs (the browser — chrome-headless-shell, backs /browser and
|
|
99
|
+
│ # every do-prove.sh --route check; browser-check.mjs is a shim over it)
|
|
100
|
+
│ # do-prove enforces a LANDING RULE: a route only counts as proven if
|
|
101
|
+
│ # the run ENDED on the path asked for. A signed-out /u/<slug>/* 302s
|
|
102
|
+
│ # to /signin, which renders 200 with no console errors — that scored
|
|
103
|
+
│ # "ok" until 2026-08-04, so authed promise clauses were green on the
|
|
104
|
+
│ # sign-in page. Every route runs SIGNED-OUT first; only a bounce to
|
|
105
|
+
│ # the login wall retries with a dev session (dev-sign-in.ts, or
|
|
106
|
+
│ # OWNER_EMAIL+DEV_PASSWORD over HTTP, localhost base only) so those
|
|
107
|
+
│ # clauses still run in a bare shell. Lazy on purpose: DEV_SLUG makes
|
|
108
|
+
│ # a signed-out request render /u/one/* as workspace `one`, while the
|
|
109
|
+
│ # dev session is tony — signing in up front turned a genuine pass on
|
|
110
|
+
│ # /u/one/workflows into "landed on /u/tony/workflows".
|
|
111
|
+
│ # PROVE_SESSION_COOKIE overrides; PROVE_NO_SESSION=1 drives the red
|
|
112
|
+
│ # half on demand. A worktree without .wrangler/state cannot seed the
|
|
113
|
+
│ # credential — export the two vars or the cookie there.
|
|
114
|
+
│ # · typedb-env.sh (flip one.ie/web/.env local↔cloud) · factory-repo.sh
|
|
115
|
+
│ # (emits one-ie/factory, see below) · sync-claude-mirror.sh (packages/claude)
|
|
116
|
+
│ # None are on PATH — always invoke as `bash .claude/scripts/<name>`.
|
|
117
|
+
│ # TRAP, measured 2026-08-04: in a `set -o pipefail` script,
|
|
118
|
+
│ # `<producer> | grep -q PAT` returns 141 when it MATCHES if the
|
|
119
|
+
│ # producer is still writing — grep -q exits first and the
|
|
120
|
+
│ # producer takes SIGPIPE. Sharp threshold: curl of 87 bytes is
|
|
121
|
+
│ # fine, curl of a 378 KB page is not; shell builtins never
|
|
122
|
+
│ # misfire. It bit do-walk's expect_text (false RED) and
|
|
123
|
+
│ # factory-repo's cycle gate (false GREEN — the pipe sat under a
|
|
124
|
+
│ # `!`, so failures read as green). Capture the output, then
|
|
125
|
+
│ # match it with a here-string. `printf "$out" | grep -q` puts
|
|
126
|
+
│ # the same race one process to the left.
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## A non-run must never read as a pass
|
|
130
|
+
|
|
131
|
+
The SIGPIPE trap above has a sibling class: a gate that never ran, reported green.
|
|
132
|
+
Two shapes, both measured 2026-09-14.
|
|
133
|
+
|
|
134
|
+
- **`gate-run.sh <label> -- <cmd>` exits 127 when `<cmd>` is a bare name not on
|
|
135
|
+
PATH** — `vitest` lives in `node_modules/.bin`, not on PATH. It ran **zero
|
|
136
|
+
tests twice** and the harness reported the wrapper as "completed exit 0". The
|
|
137
|
+
mechanism is unconfirmed in source (`govern.ts` spawns with no `env:`
|
|
138
|
+
override, so PATH *should* inherit); the behaviour is what is measured.
|
|
139
|
+
**Invoke the target by ABSOLUTE path** — `gate-run.sh test --
|
|
140
|
+
/abs/path/node_modules/.bin/vitest run …` — and treat a 127 as RED, never as a
|
|
141
|
+
clean suite.
|
|
142
|
+
- **Piping a gate through `tail` masks its exit code.**
|
|
143
|
+
`FULL_VERIFY=1 bun run verify … | tail -60` printed `[exited with code 0]`
|
|
144
|
+
while `verify` itself exited **1**. Same lesson as the pipefail trap one
|
|
145
|
+
process to the right: read **`${PIPESTATUS[0]}`**, or capture the output first
|
|
146
|
+
and match it with a here-string.
|
|
147
|
+
|
|
148
|
+
## Machine governor — why N sessions no longer melt the box
|
|
149
|
+
|
|
150
|
+
**Measured 2026-08-18:** several sessions each ran `bun run verify`
|
|
151
|
+
(tsc --noEmit + vitest over 875 test files) concurrently on a 10-core/24GB Mac.
|
|
152
|
+
Six `tsc` and three vitest pools ran at once — load **57**, swap **15.7/16 GB**,
|
|
153
|
+
~26 MB/s of swapins. Starved by paging, each gate took **18 min instead of ~2**,
|
|
154
|
+
so sessions hit their Bash timeout and launched *more*. The box got slower the
|
|
155
|
+
harder it was pushed. Claude Code was the trigger; the amplifier was that
|
|
156
|
+
nothing in this harness bounded a gate's concurrency or its children's lifetime.
|
|
157
|
+
|
|
158
|
+
macOS ships **neither `flock(1)` nor `timeout(1)`** — both are GNU coreutils.
|
|
159
|
+
That absence is the root reason gates were unbounded here. The governor
|
|
160
|
+
rebuilds both from `mkdir(2)` atomicity and a real process group.
|
|
161
|
+
|
|
162
|
+
**Since 2026-09-04 the mechanics are `scripts/lib/govern.ts` (Bun) and
|
|
163
|
+
`scripts/lib/govern.sh` is a shim.** The design did not change; the runtime
|
|
164
|
+
did — bash 3.2 on macOS has no flock, no timeout and no BASHPID, `set -m`
|
|
165
|
+
needs job control, and a watchdog subshell cannot own the `sleep` it forks.
|
|
166
|
+
Every function NAME, ARGUMENT and EXIT CODE is unchanged, so no caller
|
|
167
|
+
changed. Four functions deliberately stay in bash: `_gv_alive` and the three
|
|
168
|
+
`_gv_vm_stat` / `_gv_swapusage` / `_gv_memsize` wrappers are the seams three
|
|
169
|
+
of the six proofs **stub as bash functions**, and `_gv_claim_key` is BSD
|
|
170
|
+
`cksum` (the POSIX CRC, not zlib's) which the claims checker reads off disk.
|
|
171
|
+
A new `_gv_*` seam a checker stubs belongs in the shim, not in the port.
|
|
172
|
+
|
|
173
|
+
| Piece | Does |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `scripts/lib/govern.ts` | the mechanics — lock/slot loops, `run_bounded`, the claim reaper + TTL, the memory arithmetic, and BOTH memo keys (`tsc_tree_fingerprint`; test-cached's per-file `_hash_rel`). Reached through the shim, never called directly |
|
|
176
|
+
| `scripts/lib/govern.sh` | the shim every caller sources. `gate_lock` (mkdir-atomic named lock, reaps dead owners) · `gate_slot` (machine-wide semaphore, `GOVERN_MAX_GATES`, default 2) · `run_bounded` (wall-clock cap that kills the **process group**) · `gate_pressure` · **claims** — see below. The lock/claim owner is the CALLING SHELL's `$$`, never the short-lived bun process |
|
|
177
|
+
| `scripts/gate-run.sh` | `gate-run.sh <label> -- <cmd>` — slot + bound + group-reap. Re-entrant (`verify` calling `test` inherits the slot, never deadlocks). Execs straight through under `CI=1` or `GOVERN_DISABLE=1` |
|
|
178
|
+
| `hooks/load-guard.sh` | PreToolUse(Bash) backstop for heavy commands typed directly, bypassing package.json |
|
|
179
|
+
| `hooks/governor-escape.sh` | PreToolUse(Bash) — the *escape hatches* themselves are refused unconditionally: `verify:raw`, `test:raw`, a bare `vitest`, an inline `GOVERN_DISABLE=1`/`CI=1`. Matcher shared with the checker at `hooks/lib/governor-escape-match.sh` — one definition, no drift |
|
|
180
|
+
| `scripts/governor-doors-check.sh` | the other half — what the TREE OFFERS. `hook:governor-escape` guards what an executor TYPES, but `cd channels && bun run test` was an ungoverned 135-file two-runner suite and nothing about that string looks like an escape: the hole was the package script. Enumerates every tracked `package.json` at run time (never a hardcoded list), follows one hop through `bash <x>.sh`, and fails on any heavy script that is neither governed nor on the reasoned skip list. Also proves the wrapped relative paths resolve and that a nested gate does not deadlock |
|
|
181
|
+
| `scripts/governor-escape-check.sh` | its proof, three parts: the matcher's case table (bites / does not false-positive), the real hook driven with a payload, and a RED PROOF that guts the matcher and neuters the hook and asserts both halves go red |
|
|
182
|
+
| `scripts/machine-check.sh` | `--watch` for the live read: load, swap **direction**, running gates, held slots, orphans |
|
|
183
|
+
| `scripts/gate-watchdog.sh` | the reaper's OPPOSITE — it breaks the jam whose owners are both **alive**, which by definition no reaper can see (2026-09-13: three deadlocks in 35 minutes, 8-28 min each at 0% CPU). Cuts exactly one shape: a lock holder with **no slot** that has a `gate-run.sh` descendant queueing for one — the single process on the wrong side of the ordering. **Never a slot owner.** The clock is the SLOT's directory mtime, not the lock's: during the live jam the lock changed hands every ~9 minutes (each holder killed by its session's 600s Bash ceiling) while the jam ran >20, so a lock-clocked watchdog never fires. `--once` from `hooks/session-start.sh` (0.12s, silent), `--dry-run` to name a victim without cutting, `--self-test` for 14 checks including a real planted deadlock and three red proofs. The code fix that removes the cause is `govern-order-check.sh`'s SLOT-before-LOCK; this stays useful until every worktree has rebased onto it |
|
|
184
|
+
| `scripts/govern-claims-check.sh` | the claim registry's two properties — exclusion and evaporation — plus a red-proof. `bash .claude/scripts/govern-claims-check.sh`, exits non-zero on failure, touches only a sandbox `GOVERN_DIR` |
|
|
185
|
+
| `scripts/govern-order-check.sh` | **the LOCK ORDER**, proved by deadlocking it. Runs the two roles in the two orders — a gate holding a slot whose child wants the lock, vs a bare caller — and asserts they finish. Its red half plants the pre-2026-09-13 `LOCK -> SLOT` holder and asserts the same harness STALLS, so a green run is never green for lack of power; a third section proves a cache HIT and `--probe` still take neither. ~20s, own sandbox `GOVERN_DIR`, kills only its own children |
|
|
186
|
+
|
|
187
|
+
Wired: `one.ie/web` `verify`/`test`/`check`/`demo:*` plus `api`, `channels`,
|
|
188
|
+
`schema`, `pay/backend`, `packages/sdk` and `packages/cli` `test` all route
|
|
189
|
+
through `gate-run.sh` (`verify:raw`/`test:raw` are the ungoverned escapes,
|
|
190
|
+
and `governor-doors-check.sh` is what keeps that list honest); `do-reconcile.sh types`
|
|
191
|
+
takes a per-folder lock and **caches by tree fingerprint** — git HEAD +
|
|
192
|
+
porcelain + source mtimes — so six identical `tsc` runs collapse to one compute
|
|
193
|
+
and five cache hits; `vitest.config.ts` caps the fork pool (`maxWorkers` **8**,
|
|
194
|
+
`VITEST_MAX_FORKS` to override) with a 1GB per-fork heap.
|
|
195
|
+
|
|
196
|
+
That cap was **4** until 2026-09-01, and the reason it could double is the reason
|
|
197
|
+
it existed: each fork used to hold a ~120MB jsdom. `vitest.config.ts` now defaults
|
|
198
|
+
`environment` to `node` (only ~230 of ~1135 files touch a DOM; the rest opt in
|
|
199
|
+
with a `// @vitest-environment jsdom` docblock), so **jsdom is no longer the memory
|
|
200
|
+
driver** and the same box carries twice the fan-out. Full suite, same tree, all
|
|
201
|
+
green: **518s → 324s → 87.3s**. Memory is still the binding constraint the fleet
|
|
202
|
+
prices in — `gate_headroom` is unchanged, so N sessions cannot each take 8.
|
|
203
|
+
|
|
204
|
+
**The fleet prices its own concurrency in memory, not cores.** `do-fleet.sh`
|
|
205
|
+
capped slots at `cores - 2` — on a 10-core/24GB box that authorised **8
|
|
206
|
+
concurrent worktrees**, and 8 cycles is ~16GB of gates before the editors,
|
|
207
|
+
sessions and OS get a byte. Cores were never the binding constraint.
|
|
208
|
+
`gate_headroom` prices a cycle (~2GB: vitest driver + 4 forks + tsc + node
|
|
209
|
+
overhead), subtracts a reserve, and divides what is actually free. It only ever
|
|
210
|
+
*lowers* the count — `--slots` and the core cap still hold — and the fleet now
|
|
211
|
+
names the binding constraint on its ranking line:
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
[do-fleet] ranking… (top 5 candidates, 2 slots — bound by memory;
|
|
215
|
+
cores<=8 mem<=2, DRY-RUN)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Tune with `GOVERN_GB_PER_CYCLE` (default 2) and `GOVERN_RESERVE_GB` (default 2).
|
|
219
|
+
A probe that cannot read memory returns 99 — a broken sensor must never
|
|
220
|
+
silently serialise the fleet.
|
|
221
|
+
|
|
222
|
+
`hooks/session-start.sh` calls `gate_sweep_stale`: `_gv_reap` only fires when
|
|
223
|
+
someone *contends* for a lock, so a session that died holding a slot would
|
|
224
|
+
shrink `GOVERN_MAX_GATES` until the next contender happened along. Silent
|
|
225
|
+
unless it actually reclaims something. It also calls `claim_sweep` (below) for
|
|
226
|
+
the same reason.
|
|
227
|
+
|
|
228
|
+
### SLOT before LOCK — the one ordering rule, and the day it was broken
|
|
229
|
+
|
|
230
|
+
The governor hands out two different things and **the order between them is
|
|
231
|
+
fixed: take the SLOT first, then the LOCK.** A slot bounds the machine; a lock
|
|
232
|
+
dedupes identical work. Take them the other way round anywhere and that site can
|
|
233
|
+
deadlock against every site that takes them correctly.
|
|
234
|
+
|
|
235
|
+
Measured 2026-09-13 — **three occurrences in ~35 minutes**, each stalling every
|
|
236
|
+
session on the box for 8-28 minutes at 0% CPU, one of them a production release
|
|
237
|
+
gate. `tsc-cached.sh` took the per-folder lock and then queued for a slot while
|
|
238
|
+
holding it; `bun run verify:fast` IS `gate-run.sh verify-fast -- …`, so it
|
|
239
|
+
arrives already holding a slot and its child then waits for that lock:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
A: gate-run(verify-fast) holds slot-1 -> its tsc-cached waits for lock-tsc-one.ie_web
|
|
243
|
+
B: tsc-cached holds lock-tsc-one.ie_web -> its inner gate-run waits for a slot
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Owner pairs measured: 22173/68920, 15762/97557, 87579. **Nothing reaps this** —
|
|
247
|
+
`reapLock` and `gate-reaper.sh` only reap an owner whose pid is GONE, and both
|
|
248
|
+
owners are alive and idle. And **killing the lock holder does not fix it**
|
|
249
|
+
(observed twice): the lock passes straight to the next waiter, which is itself
|
|
250
|
+
inside a gate holding a slot, and the cycle re-forms within seconds. Kill the
|
|
251
|
+
**slot** holder — that is the resource nobody else can produce.
|
|
252
|
+
|
|
253
|
+
Both inverted sites are fixed (`tsc-cached.sh` re-enters itself through
|
|
254
|
+
`gate-run.sh` in an internal `--locked-compute` mode; `do-reconcile.sh` takes the
|
|
255
|
+
lock INSIDE its `gate-run.sh` command). A cache HIT and `tsc-cached.sh --probe`
|
|
256
|
+
still take neither — a memo lookup must never join a queue. Proof, both halves:
|
|
257
|
+
`bash .claude/scripts/govern-order-check.sh`.
|
|
258
|
+
|
|
259
|
+
### Region claims — one pheromone map, not nine
|
|
260
|
+
|
|
261
|
+
`do-fleet.sh` kept its conflict registry in `LOCK_FILE="$(mktemp)"` — **private
|
|
262
|
+
to one fleet process**. Nine concurrent fleet invocations therefore held nine
|
|
263
|
+
private maps and could not see each other, so two fleets each read their region
|
|
264
|
+
as free and both took it. That is how two fleets came to audit the same auth
|
|
265
|
+
seam on one box: not a lock that failed, a map nobody else could read.
|
|
266
|
+
|
|
267
|
+
The registry now lives at `$GOVERN_DIR/claims/<region>.<cksum>` — the same
|
|
268
|
+
machine-wide directory the gate locks use, so every session and worktree reads
|
|
269
|
+
one map. `ls` it; it is meant to be legible.
|
|
270
|
+
|
|
271
|
+
| Function | Does |
|
|
272
|
+
|---|---|
|
|
273
|
+
| `claim_take <region> [slug]` | deposit. `mkdir(2)` is the arbiter — N racers, exactly one wins. 0 = yours (re-claiming your own is idempotent), 1 = a live worker holds it |
|
|
274
|
+
| `claim_owner <region>` | `<slug> <pid>` of the LIVE owner, empty if free — **reaps before it answers** |
|
|
275
|
+
| `claim_release` / `claim_release_all` | give ground back; the latter is wired to do-fleet's EXIT trap |
|
|
276
|
+
| `claim_list` / `claim_sweep` | the live map / reap everything dead, echo the count |
|
|
277
|
+
|
|
278
|
+
**It is not a lock and there is no coordinator.** A worker that finds its region
|
|
279
|
+
claimed takes the next candidate — contention costs a re-rank, never a wait, so
|
|
280
|
+
there is no queue and nothing to deadlock.
|
|
281
|
+
|
|
282
|
+
**Evaporation is the whole contract.** A claim expires with no human in the
|
|
283
|
+
loop, two ways: the owner pid is dead (`kill -0`, instant) or the lease expired
|
|
284
|
+
(`GOVERN_CLAIM_TTL_SECS`, default 5400s = 90 min — p100 observed fleet duration
|
|
285
|
+
is 79 min). Both fire on the **read** path, so the next contender reclaims dead
|
|
286
|
+
ground by itself; `claim_sweep` at session start only stops uncontended dirs
|
|
287
|
+
accumulating. Note `gate_sweep_stale` matches `lock-*` at depth 1 and will never
|
|
288
|
+
see a claim — that is what `claim_sweep` is for.
|
|
289
|
+
|
|
290
|
+
Proof, not assertion: `bash .claude/scripts/govern-claims-check.sh` runs 12
|
|
291
|
+
concurrent independent processes at one region (exactly one wins), kills a live
|
|
292
|
+
owner and proves the region becomes claimable again, backdates a lease to fire
|
|
293
|
+
the TTL branch, proves a losing multi-region worker holds **no** ground
|
|
294
|
+
(rollback), and proves do-fleet's own `has_conflict` shape clears on a dead
|
|
295
|
+
owner. It also **stubs `_gv_alive` true and asserts the evaporation checks then
|
|
296
|
+
fail** — a checker that stays green against a gutted reaper proves nothing.
|
|
297
|
+
|
|
298
|
+
The lock dir is `$TMPDIR/one-govern` — **machine-wide**, so sessions coordinate
|
|
299
|
+
across worktrees automatically. A worktree only participates once it has this
|
|
300
|
+
code: `do-fleet.sh` now **warns per stale worktree** at launch, because until it
|
|
301
|
+
rebases onto `main` its gates are the old unbounded ones and the slot cap is the
|
|
302
|
+
only thing protecting the box from it.
|
|
303
|
+
|
|
304
|
+
**Editors are the other half of the bill.** Each `/do` cycle drops a ~115MB
|
|
305
|
+
checkout in `.do-worktrees/`; it is gitignored, but editors index gitignored
|
|
306
|
+
paths (Zed only with `search.include_ignored`, which is on here) and every
|
|
307
|
+
indexed worktree costs its own tsserver — measured 16 language-server processes
|
|
308
|
+
holding 2.3-3.0GB from 3 worktrees, entirely outside the gate governor.
|
|
309
|
+
`.vscode/settings.json` + `.cursorignore` exclude it; Zed's equivalent
|
|
310
|
+
(`file_scan_exclusions`) is user-level config, not in this repo.
|
|
311
|
+
|
|
312
|
+
**Reading swap correctly:** swapins spike during *recovery* too, as freed memory
|
|
313
|
+
lets pages fault back in. Thrash is pages going **out** while swap usage grows
|
|
314
|
+
and free memory is scarce. `machine-check.sh --watch` distinguishes the two;
|
|
315
|
+
don't diagnose from the swapin counter alone.
|