@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
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# gc-content-check.sh — the red proof for lib/gc-finished.sh.
|
|
3
|
+
#
|
|
4
|
+
# manifest: needs-env
|
|
5
|
+
# (portable by behaviour — its own sandbox — but it must spell one.ie/web/.astro
|
|
6
|
+
# to exercise GC_EPHEMERAL. See factory-repo.sh's manifest block comment.)
|
|
7
|
+
#
|
|
8
|
+
# Builds a sandbox repo carrying the branch shapes the sweep meets, drives the
|
|
9
|
+
# REAL predicate (sourced, never re-implemented), and then proves the OLD
|
|
10
|
+
# commit-count predicate gets the important one wrong. A checker that only shows
|
|
11
|
+
# the new code passing would not tell you the change was worth making.
|
|
12
|
+
#
|
|
13
|
+
# NOT reproduced here: `git cherry`'s blind spot on a merge commit. That was
|
|
14
|
+
# measured on this repo (merge/trunk-reconcile, 2026-09-07: cherry reported
|
|
15
|
+
# 0-new for a branch holding 24 files and 3416 lines dev had never seen) and the
|
|
16
|
+
# reasoning lives in lib/gc-finished.sh. Faking a multi-merge-base history to
|
|
17
|
+
# re-stage it would be asserting a claim this sandbox does not actually show —
|
|
18
|
+
# the fourth case below covers the same risk directly: a merge carrying unique
|
|
19
|
+
# content must read NOT finished.
|
|
20
|
+
#
|
|
21
|
+
# bash .claude/scripts/gc-content-check.sh # exits non-zero on failure
|
|
22
|
+
set -uo pipefail
|
|
23
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
+
. "$HERE/lib/gc-finished.sh"
|
|
25
|
+
|
|
26
|
+
SANDBOX="$(mktemp -d)"; trap 'rm -rf "$SANDBOX"' EXIT
|
|
27
|
+
fails=0
|
|
28
|
+
EXPECT_RED=0 # set while driving the neutered predicate, so an INTENDED failure
|
|
29
|
+
# does not print the word FAIL and read as a broken checker.
|
|
30
|
+
ok() { printf ' ok %s\n' "$1"; }
|
|
31
|
+
bad() {
|
|
32
|
+
if [ "$EXPECT_RED" = 1 ]; then printf ' red %s — as intended\n' "$1"
|
|
33
|
+
else printf ' FAIL %s\n' "$1"; fi
|
|
34
|
+
fails=$((fails+1))
|
|
35
|
+
}
|
|
36
|
+
want() { # <expected 0|1> <branch> <what>
|
|
37
|
+
gc_finished trunk "$2"; local got=$?
|
|
38
|
+
[ "$got" -eq "$1" ] && ok "$3" || bad "$3 (wanted finished=$1, got $got)"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
cd "$SANDBOX"
|
|
42
|
+
git init -q -b trunk .
|
|
43
|
+
git config user.email t@e.st; git config user.name t
|
|
44
|
+
git config commit.gpgsign false
|
|
45
|
+
echo base > a.txt; git add a.txt; git commit -qm base
|
|
46
|
+
echo one > c.txt; git add c.txt; git commit -qm second
|
|
47
|
+
B1="$(git rev-parse HEAD)"
|
|
48
|
+
|
|
49
|
+
# 1 — REBASED DUPLICATE: the branch's patch reaches trunk under a DIFFERENT sha.
|
|
50
|
+
# This is the case the commit count gets wrong, and the reason for this file.
|
|
51
|
+
git checkout -q -b dup "$B1"
|
|
52
|
+
echo fixed > a.txt; git commit -qam "fix a"
|
|
53
|
+
git checkout -q trunk
|
|
54
|
+
git cherry-pick dup >/dev/null 2>&1 || { bad "sandbox: cherry-pick failed"; exit 1; }
|
|
55
|
+
# Re-message it. A cherry-pick onto the same parent, in the same second, with the
|
|
56
|
+
# same author and message, produces a byte-identical commit OBJECT — trunk then
|
|
57
|
+
# fast-forwards onto the branch and there is no divergence left to detect. That
|
|
58
|
+
# is a sandbox artifact, not the real world, where a re-landed patch always
|
|
59
|
+
# arrives with a different sha. The amend forces the sha apart while leaving the
|
|
60
|
+
# patch identical, which is precisely the case under test.
|
|
61
|
+
git commit -q --amend -m "fix a (relanded under a different sha)"
|
|
62
|
+
|
|
63
|
+
# 2 — ORDINARY UNLANDED BRANCH.
|
|
64
|
+
git checkout -q -b ahead trunk
|
|
65
|
+
echo new > b.txt; git add b.txt; git commit -qm "adds b"
|
|
66
|
+
|
|
67
|
+
# 3 — FULLY CONTAINED: tip is an ancestor of trunk.
|
|
68
|
+
git checkout -q -b behind "$B1"
|
|
69
|
+
|
|
70
|
+
# 4 — MERGE COMMIT CARRYING UNIQUE CONTENT. The shape that makes a
|
|
71
|
+
# patch-counting test (git cherry) unsafe on its own.
|
|
72
|
+
git checkout -q -b side "$B1"
|
|
73
|
+
echo unique > only-here.txt; git add only-here.txt; git commit -qm "unique file"
|
|
74
|
+
git checkout -q -b mergey trunk
|
|
75
|
+
git merge --no-edit side >/dev/null 2>&1 || { bad "sandbox: merge failed"; exit 1; }
|
|
76
|
+
|
|
77
|
+
echo "== the predicate the sweep runs"
|
|
78
|
+
want 0 dup "a rebased duplicate reads FINISHED — its content is already on the platform"
|
|
79
|
+
want 1 ahead "an ordinary unlanded branch reads NOT finished"
|
|
80
|
+
want 0 behind "a branch fully contained in the base reads FINISHED"
|
|
81
|
+
want 1 mergey "a merge carrying a file the base lacks reads NOT finished"
|
|
82
|
+
|
|
83
|
+
echo "== what the OLD commit-count predicate would have said"
|
|
84
|
+
old_finished() { [ "$(git rev-list --count "trunk..$1" 2>/dev/null || echo 1)" -eq 0 ]; }
|
|
85
|
+
if old_finished dup
|
|
86
|
+
then bad "the count predicate should MISS the duplicate — this proof is broken"
|
|
87
|
+
else ok "the count predicate hoards the duplicate for ever — the ~120MB this fixes"; fi
|
|
88
|
+
if old_finished ahead
|
|
89
|
+
then bad "the count predicate wrongly sweeps an unlanded branch"
|
|
90
|
+
else ok "the count predicate keeps an unlanded branch (both agree here)"; fi
|
|
91
|
+
|
|
92
|
+
echo "== the clean-worktree path survives set -e"
|
|
93
|
+
# The sweep runs under `set -euo pipefail`, and `grep -v` exits 1 when it filters
|
|
94
|
+
# EVERYTHING out — the clean-worktree case. Inline, that killed the sweep at the
|
|
95
|
+
# first worktree it was meant to remove: four keep lines, exit 1, no summary, and
|
|
96
|
+
# invisible behind a `| cat`. Drive the real function in a real subshell under
|
|
97
|
+
# the real flags, because that is the only way this comes back red.
|
|
98
|
+
git checkout -q trunk
|
|
99
|
+
if out="$(bash -c '
|
|
100
|
+
set -euo pipefail
|
|
101
|
+
. "'"$HERE"'/lib/gc-finished.sh"
|
|
102
|
+
d="$(gc_real_dirt "'"$SANDBOX"'")"
|
|
103
|
+
[ -z "$d" ] && echo CLEAN-AND-ALIVE
|
|
104
|
+
' 2>&1)" && [ "$out" = "CLEAN-AND-ALIVE" ]; then
|
|
105
|
+
ok "a clean worktree reports no dirt AND the shell survives it"
|
|
106
|
+
else
|
|
107
|
+
bad "the clean-tree path died under set -e (got: ${out:-<nothing>})"
|
|
108
|
+
fi
|
|
109
|
+
# And it still SEES real work, or the fix would be a mute button.
|
|
110
|
+
echo scratch > "$SANDBOX/uncommitted.txt"
|
|
111
|
+
if [ -n "$(gc_real_dirt "$SANDBOX")" ]
|
|
112
|
+
then ok "uncommitted work is still reported — the guard did not silence it"
|
|
113
|
+
else bad "gc_real_dirt missed an uncommitted file"; fi
|
|
114
|
+
rm -f "$SANDBOX/uncommitted.txt"
|
|
115
|
+
# Ephemera alone must NOT read as work, or nothing is ever swept.
|
|
116
|
+
# The tracked file is required, not decoration: git COLLAPSES a wholly-untracked
|
|
117
|
+
# directory to its top level, so an unrepresentative sandbox reports `?? one.ie/`
|
|
118
|
+
# and no path pattern can match. A real worktree has one.ie/web/ tracked, so git
|
|
119
|
+
# descends and reports `?? one.ie/web/.astro` — which is what the filter is
|
|
120
|
+
# written against. Committing one file reproduces that.
|
|
121
|
+
mkdir -p "$SANDBOX/one.ie/web"; echo keep > "$SANDBOX/one.ie/web/keep.txt"
|
|
122
|
+
git add one.ie/web/keep.txt; git commit -qm "tracked file under one.ie/web"
|
|
123
|
+
mkdir -p "$SANDBOX/one.ie/web/.astro"; echo x > "$SANDBOX/one.ie/web/.astro/gen.d.ts"
|
|
124
|
+
if [ -z "$(gc_real_dirt "$SANDBOX")" ]
|
|
125
|
+
then ok "build output alone does not count as work"
|
|
126
|
+
else bad "ephemeral dirt was reported as work — nothing would ever be swept"; fi
|
|
127
|
+
rm -rf "$SANDBOX/one.ie/web/.astro"
|
|
128
|
+
|
|
129
|
+
echo "== red proof: neuter the predicate and the checks must fail"
|
|
130
|
+
gc_finished() { return 0; } # claim every branch is finished
|
|
131
|
+
before=$fails; EXPECT_RED=1
|
|
132
|
+
want 1 ahead "(neutered) an unlanded branch must NOT read finished"
|
|
133
|
+
want 1 mergey "(neutered) a unique-content merge must NOT read finished"
|
|
134
|
+
if [ "$fails" -eq $((before+2)) ]; then
|
|
135
|
+
fails=$before; EXPECT_RED=0; ok "both checks bite when the predicate is gutted"
|
|
136
|
+
else
|
|
137
|
+
fails=$((before+1)); EXPECT_RED=0; printf ' FAIL a gutted predicate still passed — this checker proves nothing\n'
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
echo
|
|
141
|
+
[ "$fails" -eq 0 ] && { echo "gc-content-check: PASS"; exit 0; }
|
|
142
|
+
echo "gc-content-check: FAIL ($fails)"; exit 1
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# gh-traffic-capture.sh — keep GitHub traffic before GitHub throws it away.
|
|
3
|
+
#
|
|
4
|
+
# bash .claude/scripts/gh-traffic-capture.sh # capture, default repos
|
|
5
|
+
# bash .claude/scripts/gh-traffic-capture.sh --repo o/r # one repo
|
|
6
|
+
# bash .claude/scripts/gh-traffic-capture.sh --report # read the ledger
|
|
7
|
+
# bash .claude/scripts/gh-traffic-capture.sh --install-cron # daily at 07:15
|
|
8
|
+
# bash .claude/scripts/gh-traffic-capture.sh --self-test
|
|
9
|
+
#
|
|
10
|
+
# manifest: needs-env — (gh CLI + network; no repo state beyond its own ledger)
|
|
11
|
+
#
|
|
12
|
+
# WHY: /traffic/clones and /traffic/views return THE LAST 14 DAYS AND NOTHING
|
|
13
|
+
# ELSE. There is no backfill, no paging, no "all time" — a day that scrolls out
|
|
14
|
+
# of that window is gone from the API permanently. Measured 2026-09-14 on
|
|
15
|
+
# one-ie/one: the repo has existed since 2024-11-12 and the API could account
|
|
16
|
+
# for 14 days of it. Every day this does not run, a day is lost for good.
|
|
17
|
+
#
|
|
18
|
+
# THE MERGE IS THE WHOLE JOB, and it has one rule that is easy to get wrong:
|
|
19
|
+
# TODAY'S ROW IS PARTIAL. Capture at 07:15 and the day is a few hours old; the
|
|
20
|
+
# same date re-fetched tomorrow is larger and correct. So a merge must take the
|
|
21
|
+
# GREATER of stored and incoming per date — never "skip if the date exists"
|
|
22
|
+
# (freezes every day at its morning value) and never "last write wins" (a later
|
|
23
|
+
# call that drops a day to 0 would erase a real number). max() is the only
|
|
24
|
+
# rule that is right on the first write, the same-day rewrite, and the replay.
|
|
25
|
+
#
|
|
26
|
+
# Idempotent by construction: re-running an hour later, or ten times, converges.
|
|
27
|
+
set -uo pipefail
|
|
28
|
+
|
|
29
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
30
|
+
DATA="${GH_TRAFFIC_DIR:-$ROOT/.claude/data/gh-traffic}"
|
|
31
|
+
REPOS=(); MODE=capture
|
|
32
|
+
|
|
33
|
+
while [[ $# -gt 0 ]]; do
|
|
34
|
+
case "$1" in
|
|
35
|
+
--repo) REPOS+=("$2"); shift ;;
|
|
36
|
+
--report) MODE=report ;;
|
|
37
|
+
--install-cron) MODE=cron ;;
|
|
38
|
+
--self-test) MODE=selftest ;;
|
|
39
|
+
-h|--help) sed -n '2,9p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
40
|
+
*) echo "gh-traffic-capture.sh: unknown flag '$1'" >&2; exit 2 ;;
|
|
41
|
+
esac
|
|
42
|
+
shift
|
|
43
|
+
done
|
|
44
|
+
# ENUMERATE THE ORG, never a hardcoded list. The first version of this script
|
|
45
|
+
# listed the seven subtree-mirror repos from the root CLAUDE.md table — and so
|
|
46
|
+
# it silently missed `ontology` (53 clones) and `astro-shadcn` (18 clones, 17
|
|
47
|
+
# uniques, 26 views), the org's second- and third-busiest repos. A capture that
|
|
48
|
+
# is blind to a repo loses that repo's history permanently, which is the one
|
|
49
|
+
# failure this script exists to prevent. A new repo joins the ledger by
|
|
50
|
+
# existing, not by someone remembering to add it here.
|
|
51
|
+
# GH_TRAFFIC_ORG overrides; --repo still pins an explicit set.
|
|
52
|
+
if (( ! ${#REPOS[@]} )); then
|
|
53
|
+
while IFS= read -r r; do [[ -n "$r" ]] && REPOS+=("$r"); done < <(
|
|
54
|
+
gh api "orgs/${GH_TRAFFIC_ORG:-one-ie}/repos?per_page=100&type=public" --jq '.[].full_name' 2>/dev/null
|
|
55
|
+
)
|
|
56
|
+
(( ${#REPOS[@]} )) || { echo "gh-traffic-capture.sh: could not enumerate org repos" >&2; exit 1; }
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
command -v jq >/dev/null || { echo "gh-traffic-capture.sh: needs jq" >&2; exit 2; }
|
|
60
|
+
command -v gh >/dev/null || { echo "gh-traffic-capture.sh: needs the gh CLI" >&2; exit 2; }
|
|
61
|
+
|
|
62
|
+
mkdir -p "$DATA"
|
|
63
|
+
|
|
64
|
+
# merge_series <file> <key> <json-array of {timestamp,count,uniques}>
|
|
65
|
+
# max() per date, per the rule above.
|
|
66
|
+
merge_series() {
|
|
67
|
+
local f="$1" key="$2" incoming="$3" tmp
|
|
68
|
+
tmp="$(mktemp)"
|
|
69
|
+
[[ -s "$f" ]] || printf '{"repo":"","clones":{},"views":{},"captured":[]}' > "$f"
|
|
70
|
+
jq --arg k "$key" --argjson inc "$incoming" '
|
|
71
|
+
.[$k] = ( (.[$k] // {}) as $have
|
|
72
|
+
| reduce $inc[] as $d ($have;
|
|
73
|
+
($d.timestamp[0:10]) as $day
|
|
74
|
+
| .[$day] = {
|
|
75
|
+
count: ([ (.[$day].count // 0), ($d.count // 0) ] | max),
|
|
76
|
+
uniques: ([ (.[$day].uniques // 0), ($d.uniques // 0) ] | max)
|
|
77
|
+
}
|
|
78
|
+
) )
|
|
79
|
+
' "$f" > "$tmp" && mv "$tmp" "$f"
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
capture_one() {
|
|
83
|
+
# TWO statements, deliberately. `local repo="$1" f="$DATA/${repo//\//__}.json"`
|
|
84
|
+
# expands EVERY word before it performs ANY assignment, so ${repo} there is
|
|
85
|
+
# still unset: f became "$DATA/.json" and all seven repos shared one ledger.
|
|
86
|
+
# The tell was subtle — the merge is max(), so the first repo's 163 clones
|
|
87
|
+
# survived every later repo's zeros and each one reported 163.
|
|
88
|
+
local repo="$1"
|
|
89
|
+
local f="$DATA/${repo//\//__}.json"
|
|
90
|
+
local c v
|
|
91
|
+
c="$(gh api "repos/$repo/traffic/clones" --jq '.clones' 2>/dev/null)"
|
|
92
|
+
v="$(gh api "repos/$repo/traffic/views" --jq '.views' 2>/dev/null)"
|
|
93
|
+
if [[ -z "$c" && -z "$v" ]]; then
|
|
94
|
+
echo " ! $repo — no traffic data (needs push access on that repo)" >&2
|
|
95
|
+
return 1
|
|
96
|
+
fi
|
|
97
|
+
[[ -s "$f" ]] || printf '{"repo":"%s","clones":{},"views":{},"captured":[]}' "$repo" > "$f"
|
|
98
|
+
[[ -n "$c" && "$c" != "null" ]] && merge_series "$f" clones "$c"
|
|
99
|
+
[[ -n "$v" && "$v" != "null" ]] && merge_series "$f" views "$v"
|
|
100
|
+
local tmp; tmp="$(mktemp)"
|
|
101
|
+
jq --arg r "$repo" --arg t "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
102
|
+
'.repo=$r | .captured = ((.captured // []) + [$t] | .[-200:])' "$f" > "$tmp" && mv "$tmp" "$f"
|
|
103
|
+
local days tot
|
|
104
|
+
days="$(jq '(.clones|length)' "$f")"; tot="$(jq '[.clones[].count]|add // 0' "$f")"
|
|
105
|
+
printf ' ✓ %-22s %s day(s) kept · %s clones total\n' "$repo" "$days" "$tot"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
case "$MODE" in
|
|
109
|
+
capture)
|
|
110
|
+
echo "── capturing GitHub traffic → $DATA"
|
|
111
|
+
rc=0; for r in "${REPOS[@]}"; do capture_one "$r" || rc=1; done
|
|
112
|
+
exit $rc ;;
|
|
113
|
+
|
|
114
|
+
report)
|
|
115
|
+
printf '\n %-24s %8s %9s %8s %9s %s\n' "REPO" "CLONES" "UNIQ" "VIEWS" "UNIQ" "DAYS KEPT"
|
|
116
|
+
printf ' %s\n' "$(printf '─%.0s' {1..76})"
|
|
117
|
+
for f in "$DATA"/*.json; do
|
|
118
|
+
[[ -e "$f" ]] || { echo " (ledger empty — run a capture first)"; break; }
|
|
119
|
+
jq -r '" \(.repo[0:24] + " "|.[0:24]) \([.clones[].count]|add // 0|tostring|((" "+.)|.[-8:])) \([.clones[].uniques]|add // 0|tostring|((" "+.)|.[-9:])) \([.views[].count]|add // 0|tostring|((" "+.)|.[-8:])) \([.views[].uniques]|add // 0|tostring|((" "+.)|.[-9:])) \(.clones|length)"' "$f"
|
|
120
|
+
done
|
|
121
|
+
printf '\n NOTE: totals are what THIS LEDGER has kept, not GitHub all-time —\n'
|
|
122
|
+
printf ' anything before the first capture was already unrecoverable.\n\n' ;;
|
|
123
|
+
|
|
124
|
+
cron)
|
|
125
|
+
line="15 7 * * * cd $ROOT && /bin/bash .claude/scripts/gh-traffic-capture.sh >> $DATA/cron.log 2>&1"
|
|
126
|
+
if crontab -l 2>/dev/null | grep -qF 'gh-traffic-capture.sh'; then
|
|
127
|
+
echo " already installed:"; crontab -l 2>/dev/null | grep -F 'gh-traffic-capture.sh' | sed 's/^/ /'
|
|
128
|
+
else
|
|
129
|
+
( crontab -l 2>/dev/null; echo "$line" ) | crontab - \
|
|
130
|
+
&& echo " ✓ installed — daily 07:15" && crontab -l | grep -F 'gh-traffic' | sed 's/^/ /'
|
|
131
|
+
fi
|
|
132
|
+
echo " NOTE: cron only fires while this Mac is awake. A missed day is still"
|
|
133
|
+
echo " recoverable for 14 days — that is the window, and the reason for daily." ;;
|
|
134
|
+
|
|
135
|
+
selftest)
|
|
136
|
+
# The merge is the only logic worth proving, and the property is max().
|
|
137
|
+
d="$(mktemp -d)"; f="$d/t.json"; fails=0
|
|
138
|
+
printf '{"repo":"x","clones":{},"views":{},"captured":[]}' > "$f"
|
|
139
|
+
merge_series "$f" clones '[{"timestamp":"2026-09-01T00:00:00Z","count":5,"uniques":2}]'
|
|
140
|
+
[[ "$(jq -r '.clones["2026-09-01"].count' "$f")" == "5" ]] || { echo " FAIL: first write"; fails=1; }
|
|
141
|
+
# same day, larger (the partial-today case) -> must grow
|
|
142
|
+
merge_series "$f" clones '[{"timestamp":"2026-09-01T00:00:00Z","count":9,"uniques":4}]'
|
|
143
|
+
[[ "$(jq -r '.clones["2026-09-01"].count' "$f")" == "9" ]] || { echo " FAIL: partial day did not grow"; fails=1; }
|
|
144
|
+
# same day, smaller (a late/rescoped answer) -> must NOT shrink
|
|
145
|
+
merge_series "$f" clones '[{"timestamp":"2026-09-01T00:00:00Z","count":1,"uniques":1}]'
|
|
146
|
+
[[ "$(jq -r '.clones["2026-09-01"].count' "$f")" == "9" ]] || { echo " FAIL: a smaller number overwrote a larger one"; fails=1; }
|
|
147
|
+
# a day older than the 14d window must survive a capture that omits it
|
|
148
|
+
merge_series "$f" clones '[{"timestamp":"2026-09-02T00:00:00Z","count":3,"uniques":1}]'
|
|
149
|
+
[[ "$(jq -r '.clones|length' "$f")" == "2" ]] || { echo " FAIL: an out-of-window day was dropped"; fails=1; }
|
|
150
|
+
rm -rf "$d"
|
|
151
|
+
(( fails )) && { echo "gh-traffic-capture.sh: self-test FAILED"; exit 1; }
|
|
152
|
+
echo "gh-traffic-capture.sh: self-test PASS (grows, never shrinks, never forgets)" ;;
|
|
153
|
+
esac
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# govern-order-check.sh — the governor's LOCK ORDER, proved by deadlocking it.
|
|
3
|
+
#
|
|
4
|
+
# manifest: needs-env
|
|
5
|
+
#
|
|
6
|
+
# THE DEFECT (measured 2026-09-13, three occurrences in ~35 minutes, each
|
|
7
|
+
# stalling every session on the box for 8-28 minutes at 0% CPU — one of them a
|
|
8
|
+
# production release gate):
|
|
9
|
+
#
|
|
10
|
+
# tsc-cached.sh took the per-folder dedupe LOCK and then, still holding it,
|
|
11
|
+
# queued for a governor SLOT. LOCK -> SLOT
|
|
12
|
+
# Any caller already inside a gate has the opposite order: `bun run verify:fast`
|
|
13
|
+
# IS `gate-run.sh verify-fast -- verify-fast.sh`, so it holds a SLOT, and its
|
|
14
|
+
# child tsc-cached.sh then waits for the LOCK. SLOT -> LOCK
|
|
15
|
+
#
|
|
16
|
+
# A: gate-run(verify-fast) holds slot-1 -> child tsc-cached waits for lock-tsc-one.ie_web
|
|
17
|
+
# B: tsc-cached holds lock-tsc-one.ie_web -> its inner gate-run waits for a slot
|
|
18
|
+
#
|
|
19
|
+
# Two orders, one cycle. Neither owner is DEAD, so nothing reaps it:
|
|
20
|
+
# `gate_lock`/`gate_slot` only reap an owner whose pid is gone (govern.ts
|
|
21
|
+
# `reapLock`), and gate-reaper.sh is the same predicate. Measured owner pairs:
|
|
22
|
+
# 22173/68920, then 15762/97557, then 87579.
|
|
23
|
+
#
|
|
24
|
+
# KILLING THE LOCK HOLDER DOES NOT FIX IT — observed twice. The lock is released
|
|
25
|
+
# and immediately taken by the next waiter, which is itself inside a gate holding
|
|
26
|
+
# a slot, and the cycle re-forms within seconds. Killing the SLOT holder is what
|
|
27
|
+
# breaks it, because that is the resource nobody else can produce.
|
|
28
|
+
#
|
|
29
|
+
# THE FIX this proves: SLOT before LOCK, everywhere. A cache HIT still takes
|
|
30
|
+
# neither.
|
|
31
|
+
#
|
|
32
|
+
# WHAT EACH HALF PROVES. The green half proves tsc-cached.sh is no longer an
|
|
33
|
+
# inverted participant. It does NOT prove the cycle is impossible — a synthetic
|
|
34
|
+
# LOCK -> SLOT holder still deadlocks a fixed tsc-cached, and that is exactly
|
|
35
|
+
# what the red half plants, so this checker is never green for lack of power.
|
|
36
|
+
# Both halves run in their own GOVERN_DIR: nothing here touches the machine's
|
|
37
|
+
# real governor, so a real gate in another session is never delayed or killed.
|
|
38
|
+
set -uo pipefail
|
|
39
|
+
|
|
40
|
+
_HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
41
|
+
ROOT="$(cd "$_HERE/../.." && pwd)"
|
|
42
|
+
GR="$_HERE/gate-run.sh"
|
|
43
|
+
TSC="$_HERE/tsc-cached.sh"
|
|
44
|
+
|
|
45
|
+
# A folder inside ROOT, because tsc_tree_fingerprint cd's to ROOT and resolves
|
|
46
|
+
# the folder relatively. Never one.ie/web: that is a real 2.5GB typecheck.
|
|
47
|
+
REL=".govern-order-probe"
|
|
48
|
+
P="$ROOT/$REL"
|
|
49
|
+
LOCKNAME="tsc-$(printf '%s' "$REL" | tr '/' '_')"
|
|
50
|
+
|
|
51
|
+
# Waits are SHORT and bounded on purpose: a deadlocked pair must expire by
|
|
52
|
+
# itself in under a minute, on a box where other sessions are running real
|
|
53
|
+
# gates. OBSERVE is when the verdict is read — long enough for both roles to
|
|
54
|
+
# have taken their ground, short enough to finish inside a Bash tool call.
|
|
55
|
+
WAIT="${GOVERN_ORDER_WAIT:-20}"
|
|
56
|
+
OBSERVE="${GOVERN_ORDER_OBSERVE:-10}"
|
|
57
|
+
|
|
58
|
+
bad=0
|
|
59
|
+
ok() { echo " ok: $*"; }
|
|
60
|
+
nope() { echo " FAIL: $*"; bad=$((bad + 1)); }
|
|
61
|
+
|
|
62
|
+
probe_up() {
|
|
63
|
+
rm -rf "$P"; mkdir -p "$P/src"
|
|
64
|
+
printf '{ "compilerOptions": { "strict": true, "noEmit": true }, "include": ["src"] }\n' > "$P/tsconfig.json"
|
|
65
|
+
printf 'export const x: number = 1;\n' > "$P/src/a.ts"
|
|
66
|
+
}
|
|
67
|
+
probe_down() { rm -rf "$P"; }
|
|
68
|
+
|
|
69
|
+
# _race <sandbox> <role-b-command...> — start the two roles in the two orders and
|
|
70
|
+
# read the state at OBSERVE seconds.
|
|
71
|
+
#
|
|
72
|
+
# Role A is always the REAL shape a session runs: gate-run holds a slot, and its
|
|
73
|
+
# child tsc-cached asks for the lock. The `sleep 3` is the whole determinism of
|
|
74
|
+
# this repro — it lets role B take its ground first, so the interleaving is
|
|
75
|
+
# fixed rather than raced.
|
|
76
|
+
#
|
|
77
|
+
# Echoes "done" (both roles exited) or "stalled <lock-owner> <slot-owner>".
|
|
78
|
+
_race() {
|
|
79
|
+
local sb="$1"; shift
|
|
80
|
+
local aout="$sb/a.log" bout="$sb/b.log" pa pb t
|
|
81
|
+
|
|
82
|
+
( GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 GOVERN_QUEUE_WAIT="$WAIT" \
|
|
83
|
+
GOVERN_GATE_TIMEOUT=$((WAIT * 4)) TSC_CACHE_WAIT="$WAIT" TSC_CACHE_TIMEOUT="$WAIT" \
|
|
84
|
+
TSC_CACHE_CMD='echo no-errors-here' \
|
|
85
|
+
bash "$GR" role-a -- bash -c "sleep 3; bash '$TSC' '$REL'" ) >"$aout" 2>&1 &
|
|
86
|
+
pa=$!
|
|
87
|
+
sleep 2
|
|
88
|
+
( GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 GOVERN_QUEUE_WAIT="$WAIT" \
|
|
89
|
+
GOVERN_GATE_TIMEOUT=$((WAIT * 4)) TSC_CACHE_WAIT="$WAIT" TSC_CACHE_TIMEOUT="$WAIT" \
|
|
90
|
+
TSC_CACHE_CMD='echo no-errors-here' \
|
|
91
|
+
"$@" ) >"$bout" 2>&1 &
|
|
92
|
+
pb=$!
|
|
93
|
+
|
|
94
|
+
t=0
|
|
95
|
+
while [ "$t" -lt "$OBSERVE" ]; do
|
|
96
|
+
kill -0 "$pa" 2>/dev/null || kill -0 "$pb" 2>/dev/null || break
|
|
97
|
+
sleep 1; t=$((t + 1))
|
|
98
|
+
done
|
|
99
|
+
|
|
100
|
+
# The exit codes go to DISK, not to variables: _race is read through a command
|
|
101
|
+
# substitution, so anything it assigns dies with the subshell.
|
|
102
|
+
if ! kill -0 "$pa" 2>/dev/null && ! kill -0 "$pb" 2>/dev/null; then
|
|
103
|
+
wait "$pa"; echo $? > "$sb/rc.a"; wait "$pb"; echo $? > "$sb/rc.b"
|
|
104
|
+
echo "done"; return 0
|
|
105
|
+
fi
|
|
106
|
+
# Still alive at OBSERVE: name the two owners, then stop them. These are our
|
|
107
|
+
# own children in our own sandbox — never another session's gate.
|
|
108
|
+
local lo so
|
|
109
|
+
lo=$(cat "$sb/lock-$LOCKNAME/pid" 2>/dev/null | tr -d '[:space:]')
|
|
110
|
+
so=$(cat "$sb/lock-slot-1/pid" 2>/dev/null | tr -d '[:space:]')
|
|
111
|
+
kill -TERM "$pa" "$pb" 2>/dev/null
|
|
112
|
+
sleep 1
|
|
113
|
+
kill -KILL "$pa" "$pb" 2>/dev/null
|
|
114
|
+
wait "$pa" 2>/dev/null; wait "$pb" 2>/dev/null
|
|
115
|
+
echo 99 > "$sb/rc.a"; echo 99 > "$sb/rc.b"
|
|
116
|
+
echo "stalled lock=${lo:-none} slot=${so:-none}"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# ── 1. THE REAL PAIR — both roles are the shipped tsc-cached.sh ──────────────
|
|
120
|
+
real_pair() {
|
|
121
|
+
echo "== the real pair — gate-run(slot)->tsc-cached(lock) vs bare tsc-cached"
|
|
122
|
+
local sb r ra rb
|
|
123
|
+
sb="$(mktemp -d)"; probe_up
|
|
124
|
+
r=$(_race "$sb" bash "$TSC" "$REL")
|
|
125
|
+
ra=$(cat "$sb/rc.a" 2>/dev/null); rb=$(cat "$sb/rc.b" 2>/dev/null)
|
|
126
|
+
case "$r" in
|
|
127
|
+
done)
|
|
128
|
+
if [ "${ra:-1}" = "0" ] && [ "${rb:-1}" = "0" ]; then
|
|
129
|
+
ok "both roles completed (rc 0/0) — tsc-cached takes the SLOT before the LOCK"
|
|
130
|
+
else
|
|
131
|
+
nope "both roles exited but not cleanly (a=${ra:-?} b=${rb:-?})
|
|
132
|
+
$(sed 's/^/ A| /' "$sb/a.log"; sed 's/^/ B| /' "$sb/b.log")"
|
|
133
|
+
fi ;;
|
|
134
|
+
stalled*)
|
|
135
|
+
nope "DEADLOCK reproduced against this code — $r
|
|
136
|
+
neither owner is dead, so nothing reaps it. This is the 2026-09-13 stall.
|
|
137
|
+
$(sed 's/^/ A| /' "$sb/a.log"; sed 's/^/ B| /' "$sb/b.log")" ;;
|
|
138
|
+
esac
|
|
139
|
+
probe_down; rm -rf "$sb"
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
# ── 2. RED PROOF — plant the pre-fix order and the SAME harness must stall ───
|
|
143
|
+
# Without this, a green section 1 could mean "the harness cannot see a deadlock".
|
|
144
|
+
# The planted role is tsc-cached.sh's own pre-2026-09-13 body in miniature: take
|
|
145
|
+
# the folder lock, then queue for a slot while holding it.
|
|
146
|
+
red_proof() {
|
|
147
|
+
echo "== red proof — a planted LOCK->SLOT holder must still deadlock the pair"
|
|
148
|
+
local sb r
|
|
149
|
+
sb="$(mktemp -d)"; probe_up
|
|
150
|
+
r=$(_race "$sb" bash -c ". '$_HERE/lib/govern.sh'
|
|
151
|
+
gate_lock '$LOCKNAME' $WAIT || exit 9
|
|
152
|
+
trap 'gate_release_all' EXIT INT TERM
|
|
153
|
+
bash '$GR' planted -- echo computed")
|
|
154
|
+
case "$r" in
|
|
155
|
+
stalled*)
|
|
156
|
+
ok "the inverted order still deadlocks and the harness sees it — $r" ;;
|
|
157
|
+
done)
|
|
158
|
+
nope "a planted LOCK->SLOT holder did NOT stall — this checker has no power,
|
|
159
|
+
so section 1 going green proves nothing.
|
|
160
|
+
$(sed 's/^/ A| /' "$sb/a.log"; sed 's/^/ B| /' "$sb/b.log")" ;;
|
|
161
|
+
esac
|
|
162
|
+
probe_down; rm -rf "$sb"
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# ── 3. A CACHE HIT COSTS NOTHING — no lock, no slot ─────────────────────────
|
|
166
|
+
# The memo lookup is what makes this cache worth having. If a HIT queued for a
|
|
167
|
+
# slot, every cheap verify-fast would join the same queue as a real typecheck —
|
|
168
|
+
# and land.sh's `--probe` (a lookup, never a compute) would be able to hang.
|
|
169
|
+
hit_is_free() {
|
|
170
|
+
echo "== a cache HIT takes neither the lock nor a slot"
|
|
171
|
+
local sb out n
|
|
172
|
+
sb="$(mktemp -d)"; probe_up
|
|
173
|
+
# compute once (this one legitimately takes a slot)
|
|
174
|
+
GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 TSC_CACHE_CMD='echo no-errors-here' \
|
|
175
|
+
bash "$TSC" "$REL" >/dev/null 2>&1
|
|
176
|
+
n=$(ls "$sb" 2>/dev/null | grep -c "^${LOCKNAME}\." | tr -d ' ')
|
|
177
|
+
[ "${n:-0}" -ge 1 ] || nope "nothing was memoised — the rest of this section cannot bind"
|
|
178
|
+
# Now a HIT, with the slots ALL taken. If it needs one, it cannot finish.
|
|
179
|
+
mkdir -p "$sb/lock-slot-1"; echo $$ > "$sb/lock-slot-1/pid"
|
|
180
|
+
out=$(GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 GOVERN_QUEUE_WAIT=6 TSC_CACHE_WAIT=6 \
|
|
181
|
+
TSC_CACHE_CMD='echo no-errors-here' bash "$TSC" "$REL" 2>/dev/null)
|
|
182
|
+
if [ "$(printf '%s' "$out" | tr -d '[:space:]')" = "0" ]; then
|
|
183
|
+
ok "a HIT answers 0 with every slot held (it never queued)"
|
|
184
|
+
else
|
|
185
|
+
nope "a HIT could not answer with the slots held (got '${out:-}') — it is taking a slot"
|
|
186
|
+
fi
|
|
187
|
+
[ -d "$sb/lock-$LOCKNAME" ] && nope "a HIT left the folder lock behind — it took the lock" \
|
|
188
|
+
|| ok "a HIT took no folder lock"
|
|
189
|
+
# --probe, the pure lookup land.sh uses, under the same starvation
|
|
190
|
+
out=$(GOVERN_DIR="$sb" GOVERN_MAX_GATES=1 GOVERN_QUEUE_WAIT=6 \
|
|
191
|
+
bash "$TSC" --probe "$REL" 2>/dev/null)
|
|
192
|
+
[ "$out" = "green" ] && ok "--probe answers green with every slot held" \
|
|
193
|
+
|| nope "--probe could not answer with the slots held (got '${out:-}')"
|
|
194
|
+
probe_down; rm -rf "$sb"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
echo "govern-order-check — SLOT before LOCK, proved by deadlocking it"
|
|
198
|
+
real_pair
|
|
199
|
+
red_proof
|
|
200
|
+
hit_is_free
|
|
201
|
+
[ "$bad" -eq 0 ] && { echo "govern-order: PASS"; exit 0; }
|
|
202
|
+
echo "govern-order: FAILED ($bad)"; exit 1
|
|
@@ -22,9 +22,17 @@
|
|
|
22
22
|
# hop is followed: its own file, not its package.json, holds the two runners.
|
|
23
23
|
#
|
|
24
24
|
# WHAT IS DELIBERATELY NOT A DOOR is in SKIP below, each with its reason. That
|
|
25
|
-
# list is the argument, not an allowlist of convenience
|
|
26
|
-
#
|
|
27
|
-
#
|
|
25
|
+
# list is the argument, not an allowlist of convenience.
|
|
26
|
+
#
|
|
27
|
+
# A LOCK IS NOT A SLOT. Until 2026-09-07 the typecheck skip below read "tsc
|
|
28
|
+
# already has its own lock via tsc-cached.sh", and that reasoning is wrong in a
|
|
29
|
+
# way that cost the machine: gate_lock is keyed by FOLDER and dedupes identical
|
|
30
|
+
# work, while a SLOT caps concurrent heavy work machine-wide. N worktrees are N
|
|
31
|
+
# folders, so N locks are all uncontended while N typecheckers run. Measured that
|
|
32
|
+
# day: three 2.5GB tsc processes at once, load 171, localhost at 8.4s a page, and
|
|
33
|
+
# THIS CHECKER REPORTING "0 open". The "2-second typecheck" half was wrong too --
|
|
34
|
+
# the measured runs were 2.5GB and 7+ minutes. The compute sites now take a slot
|
|
35
|
+
# (see the `compute sites` section below, which is what would have caught it).
|
|
28
36
|
# SHIPS (needs-env). In a generated factory clone this enumerates THAT repo
|
|
29
37
|
# package.json files, while every line in the skip list names a monorepo path
|
|
30
38
|
# that can never match there -- so it may report open doors it has no opinion
|
|
@@ -44,7 +52,7 @@ HEAVY_RE='vitest|tsc[[:space:]]+--noEmit|astro[[:space:]]+check|playwright[[:spa
|
|
|
44
52
|
# <pkg-relpath>::<script> why
|
|
45
53
|
skips() {
|
|
46
54
|
cat <<'SKIP'
|
|
47
|
-
*::typecheck
|
|
55
|
+
*::typecheck routes through tsc-cached.sh, which since 2026-09-07 takes a real SLOT around its compute (gate-run.sh), not merely its dedupe lock -- and since 2026-09-13 takes that slot BEFORE the lock, never while holding it (govern-order-check.sh proves both halves). Enforced by the `compute sites` section, not asserted here
|
|
48
56
|
*::test:watch a watch never exits; a wall-clock bound would kill it
|
|
49
57
|
*::test:raw an escape hatch by definition -- ungoverned is the whole point
|
|
50
58
|
*::verify:raw an escape hatch by definition
|
|
@@ -236,9 +244,82 @@ reentrancy() {
|
|
|
236
244
|
else
|
|
237
245
|
nope "unexpected red-half output. out=[$(cat "$o")] err=[$(cat "$e")]"
|
|
238
246
|
fi
|
|
247
|
+
# RED PROOF for `compute sites`: a script that spawns a heavy compute with no
|
|
248
|
+
# gate-run.sh must turn compute_sites red. This is the exact shape tsc-cached.sh
|
|
249
|
+
# had before 2026-09-07 -- a gate_lock, and a bare bunx tsc inside it.
|
|
250
|
+
local probe_dir probe
|
|
251
|
+
probe_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
252
|
+
# NOT a dotfile: `for f in "$d"/*.sh` does not glob dotfiles, so a probe named
|
|
253
|
+
# .zz-*.sh is invisible to the very check it is supposed to drive red -- the
|
|
254
|
+
# proof would pass by never being seen. Named plainly, and rm'd below.
|
|
255
|
+
probe="$probe_dir/zz-compute-probe.sh"
|
|
256
|
+
cat > "$probe" <<'PROBE'
|
|
257
|
+
#!/usr/bin/env bash
|
|
258
|
+
# a lock, and a heavy compute with no slot -- the 2026-09-07 defect, in miniature
|
|
259
|
+
gate_lock "probe" 10 && bunx tsc --noEmit
|
|
260
|
+
PROBE
|
|
261
|
+
local b0=$bad
|
|
262
|
+
compute_sites >/dev/null 2>&1
|
|
263
|
+
if [ "$bad" -gt "$b0" ]; then
|
|
264
|
+
ok "a lock-without-slot compute site turns the audit RED"
|
|
265
|
+
bad=$b0
|
|
266
|
+
else
|
|
267
|
+
nope "compute_sites stayed green against a bare 'bunx tsc --noEmit' with no gate-run"
|
|
268
|
+
fi
|
|
269
|
+
rm -f "$probe"
|
|
270
|
+
|
|
239
271
|
rm -rf "$sandbox"
|
|
240
272
|
}
|
|
241
273
|
|
|
274
|
+
# ── compute sites — a gate_lock is not a slot ───────────────────────────────
|
|
275
|
+
#
|
|
276
|
+
# The `doors` section above audits PACKAGE SCRIPTS. It cannot see the defect
|
|
277
|
+
# that actually cost the machine on 2026-09-07, because that one lived one layer
|
|
278
|
+
# down: tsc-cached.sh held a per-folder gate_lock and spawned `bunx tsc` with no
|
|
279
|
+
# slot at all. Every door was green, the skip list had a stated reason, and three
|
|
280
|
+
# 2.5GB typecheckers ran concurrently while the governor reported 1 of 2 slots.
|
|
281
|
+
#
|
|
282
|
+
# The rule this enforces: ANY script here that spawns a heavy compute must also
|
|
283
|
+
# reference gate-run.sh. Not "takes a lock" -- a lock dedupes identical work, a
|
|
284
|
+
# slot bounds the machine, and only one of those is a concurrency cap.
|
|
285
|
+
compute_sites() {
|
|
286
|
+
echo "== compute sites — a heavy compute behind a lock still needs a SLOT"
|
|
287
|
+
local d f n_ok=0 hits
|
|
288
|
+
d="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
289
|
+
for f in "$d"/*.sh; do
|
|
290
|
+
case "$(basename "$f")" in
|
|
291
|
+
gate-run.sh|governor-doors-check.sh|governor-escape-check.sh|machine-check.sh) continue ;;
|
|
292
|
+
# FIXTURES — these NAME a heavy command as test data and spawn nothing:
|
|
293
|
+
# load-guard-check.sh passes command STRINGS to the hook ( check 1 "bunx ..." )
|
|
294
|
+
# govern-mem-check.sh embeds them in JSON tool_input payloads
|
|
295
|
+
# deploy-gate-check.sh greps deploy.sh for the command as a PATTERN
|
|
296
|
+
# Each was verified by reading the matching lines, not assumed.
|
|
297
|
+
load-guard-check.sh|govern-mem-check.sh|deploy-gate-check.sh) continue ;;
|
|
298
|
+
esac
|
|
299
|
+
# PRESENCE, not command-position. The first version of this asked "is the
|
|
300
|
+
# compute in command position", blanking quoted regions -- and it could not
|
|
301
|
+
# see the very defect it was written for: tsc-cached.sh spawned its tsc from
|
|
302
|
+
# inside a quoted `bash -c "... ${TSC_CACHE_CMD:-bunx tsc --noEmit} ..."`.
|
|
303
|
+
# Reverting tsc-cached.sh to its pre-fix state left that check GREEN, which
|
|
304
|
+
# is the only test of a checker that matters.
|
|
305
|
+
#
|
|
306
|
+
# So the rule is blunt on purpose: if a heavy token appears outside a comment,
|
|
307
|
+
# this file must reference gate-run.sh somewhere. Scripts that only NAME a
|
|
308
|
+
# command as a test fixture are handled by FIXTURES below, by name and with a
|
|
309
|
+
# reason -- the same "the skip list is the argument" doctrine as the doors.
|
|
310
|
+
hits=$(grep -nE '(bunx|npx|bun x) +(tsc +--noEmit|vitest +run)' "$f" 2>/dev/null \
|
|
311
|
+
| grep -vE '^[0-9]+:[[:space:]]*#' || true)
|
|
312
|
+
[ -n "$hits" ] || continue
|
|
313
|
+
if grep -q 'gate-run\.sh' "$f"; then
|
|
314
|
+
n_ok=$((n_ok+1))
|
|
315
|
+
else
|
|
316
|
+
nope "$(basename "$f") spawns a heavy compute with no gate-run.sh anywhere:
|
|
317
|
+
$(printf '%s' "$hits" | head -3 | sed 's/^/ /')"
|
|
318
|
+
fi
|
|
319
|
+
done
|
|
320
|
+
[ "$bad" -eq 0 ] && ok "$n_ok script(s) spawn a heavy compute, every one routes through gate-run.sh"
|
|
321
|
+
}
|
|
322
|
+
|
|
242
323
|
# ── red proof ───────────────────────────────────────────────────────────────
|
|
243
324
|
self_test() {
|
|
244
325
|
echo "== self-test — plant an open door, the REAL audit must go RED"
|
|
@@ -273,7 +354,7 @@ case "${1:-}" in
|
|
|
273
354
|
--list) enumerate | sort | column -t -s$'\t' 2>/dev/null || enumerate | sort; exit 0 ;;
|
|
274
355
|
--self-test) self_test ;;
|
|
275
356
|
*) if [ -n "${GOV_DOORS_INNER:-}" ]; then audit
|
|
276
|
-
else audit; echo; paths; echo; reentrancy; echo; self_test; fi ;;
|
|
357
|
+
else audit; echo; paths; echo; compute_sites; echo; reentrancy; echo; self_test; fi ;;
|
|
277
358
|
esac
|
|
278
359
|
|
|
279
360
|
echo
|