@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
package/scripts/signal-watch.sh
CHANGED
|
@@ -17,8 +17,31 @@
|
|
|
17
17
|
# hooks and do-signal.sh post to, plus the stream the listeners sit on:
|
|
18
18
|
#
|
|
19
19
|
# health GET /api/health the worker answers
|
|
20
|
-
# world:announce POST a canary tagged [
|
|
21
|
-
#
|
|
20
|
+
# world:announce POST a canary tagged [signal-watch-wire] on the
|
|
21
|
+
# MARKETPLACE board — intake's door.
|
|
22
|
+
# The wire, and only the wire: zero subscribers matched, so
|
|
23
|
+
# the number is the door's own cost with no fan-out in it.
|
|
24
|
+
# It used to be sent on the caller's own board and the header
|
|
25
|
+
# claimed it "matches nobody by design" — it matched `ceo`,
|
|
26
|
+
# which subscribes to EVERY tag by canon (root CLAUDE.md: the
|
|
27
|
+
# CEO is the default receiver, always listening). Measured
|
|
28
|
+
# 2026-09-08 on prod server-timing, same door, one variable:
|
|
29
|
+
# matched=0 -> data 259/332/401 ms; matched=1 -> data
|
|
30
|
+
# 1535..2066 ms. So ~1.3s per matched subscriber was being
|
|
31
|
+
# read as "the wire". The probe now ASSERTS matched=0 and
|
|
32
|
+
# goes UNRUN if it ever matches — a wire probe that fanned
|
|
33
|
+
# out did not measure the wire, and an unrun door is not a
|
|
34
|
+
# pass. The fan-out's own cost is a separate finding
|
|
35
|
+
# (text/announce-fanout-plan.md), not a budget to widen.
|
|
36
|
+
# BOTH halves are load-bearing and both were measured
|
|
37
|
+
# wrong before: the BOARD (own board -> `ceo`, who
|
|
38
|
+
# follows every tag) and the TAG (`cc` on the marketplace
|
|
39
|
+
# board -> the live `cc-proof-listener`, found 2026-09-08
|
|
40
|
+
# by this very assertion on its first run). Hence a
|
|
41
|
+
# RESERVED tag nobody stakes, on the shared board. If
|
|
42
|
+
# someone ever stakes `signal-watch-wire`, the row goes
|
|
43
|
+
# unrun and says so, rather than quietly reporting a
|
|
44
|
+
# fan-out as the wire.
|
|
22
45
|
# tasks:everywhere POST {workspace:factory-do} the board read
|
|
23
46
|
# factory:size POST {paths:[this file]} the size hop
|
|
24
47
|
# stream GET channels.one.ie/stream/space:vespio time to first byte
|
|
@@ -80,9 +103,14 @@ _row() { # lane door verdict wall budget placement phases note
|
|
|
80
103
|
case "$3" in pass) PASS=$((PASS+1));; fail) FAIL=$((FAIL+1));; n/a) NA=$((NA+1));; unrun) UNRUN=$((UNRUN+1));; esac
|
|
81
104
|
}
|
|
82
105
|
|
|
83
|
-
# door <lane> <name> <method> <url> <payload|-> <budget_ms> <key|->
|
|
106
|
+
# door <lane> <name> <method> <url> <payload|-> <budget_ms> <key|-> [expect|-]
|
|
107
|
+
# `expect` is a fixed string the RESPONSE BODY must contain for the number to
|
|
108
|
+
# mean what the door claims to measure. Absent it, a door can keep answering 200
|
|
109
|
+
# fast while measuring something else entirely — which is exactly how the
|
|
110
|
+
# announce canary reported "the wire" while paying a full fan-out. A miss is
|
|
111
|
+
# UNRUN, never fail and never pass: the door ran, but not the door we named.
|
|
84
112
|
door() {
|
|
85
|
-
local lane="$1" name="$2" method="$3" url="$4" payload="$5" budget key="$7" hdr body out code wall placement phases verdict note=""
|
|
113
|
+
local lane="$1" name="$2" method="$3" url="$4" payload="$5" budget key="$7" expect="${8:--}" hdr body out code wall placement phases verdict note=""
|
|
86
114
|
budget=$(_budget "$6")
|
|
87
115
|
hdr=$(mktemp); body=$(mktemp)
|
|
88
116
|
if [ "$method" = POST ] && [ "$key" = "-" ]; then
|
|
@@ -104,6 +132,7 @@ door() {
|
|
|
104
132
|
elif [ "$code" != "200" ]; then verdict=fail; note="http $code $(head -c 80 "$body" | tr -d '\n')"
|
|
105
133
|
elif [ "$method" = POST ] && ! grep -q '"outcome":"result"' "$body"; then verdict=fail; note="refused: $(head -c 100 "$body" | tr -d '\n')"
|
|
106
134
|
elif [ "$method" = POST ] && grep -q '"ok":false' "$body"; then verdict=unrun; note="refused before the work: $(grep -oE '"error":"[^"]{0,80}' "$body" | head -1 | cut -d'"' -f4)"
|
|
135
|
+
elif [ "$expect" != "-" ] && ! grep -qF "$expect" "$body"; then verdict=unrun; note="not the door we named: body lacks $expect — $(head -c 60 "$body" | tr -d '\n')"
|
|
107
136
|
elif [ "$wall" -gt "$budget" ]; then verdict=fail; note="over budget by $((wall-budget)) ms"
|
|
108
137
|
else verdict=pass; fi
|
|
109
138
|
_row "$lane" "$name" "$verdict" "$wall" "$budget" "${placement:-}" "${phases:-}" "$note"
|
|
@@ -131,7 +160,12 @@ listeners() {
|
|
|
131
160
|
|
|
132
161
|
lane() { # prod|local
|
|
133
162
|
local l="$1" base key
|
|
134
|
-
|
|
163
|
+
# The prod credential file honours ONE_ENV_FILE / DO_ENV_FILE, like every other
|
|
164
|
+
# credential read in the harness (factory-repo.sh --check-env-indirection).
|
|
165
|
+
# A relative path is taken from $ROOT; the shell's GATEWAY_API_KEY still wins.
|
|
166
|
+
local env_file="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.env}}"
|
|
167
|
+
case "$env_file" in /*) ;; *) env_file="$ROOT/$env_file" ;; esac
|
|
168
|
+
if [ "$l" = prod ]; then base="$PROD_URL"; key=$(_key "$env_file")
|
|
135
169
|
else
|
|
136
170
|
base="$LOCAL_URL"; key=$(_key "$ROOT/one.ie/web/.dev.vars")
|
|
137
171
|
if ! curl -s -o /dev/null --max-time 3 "$base/api/health"; then
|
|
@@ -143,10 +177,27 @@ lane() { # prod|local
|
|
|
143
177
|
# watched (2026-09-05 — announce 1.3-1.8s, board/size 0.8-1.5s), lowered only
|
|
144
178
|
# after a measured improvement ships. Never raised to make a red row green.
|
|
145
179
|
# announce: 2000 → 800 after the parallel-read ship (2026-09-05, measured 149-379 ms).
|
|
180
|
+
# UNCHANGED 2026-09-08 on purpose. The door it describes is now the WIRE door
|
|
181
|
+
# (board=marketplace, matched=0), whose data phase measures 259-401 ms — the
|
|
182
|
+
# same range 800 was set against. The wall it is compared to also carries the
|
|
183
|
+
# CLIENT's egress, which is not the worker's: `health` does det=0 ms of server
|
|
184
|
+
# work and has walked 134 → 72 → 437 → 463 ms across the ledger purely on where
|
|
185
|
+
# the laptop was sitting. So a marginal announce/health miss can be this box's
|
|
186
|
+
# network, not the door. Whether these budgets should be worker-phase budgets
|
|
187
|
+
# rather than wall budgets is a RECALIBRATION — a human decision, deliberately
|
|
188
|
+
# not taken here. A budget moved to fit an observation stops meaning anything.
|
|
146
189
|
local b1 b2 b3; if [ "$l" = prod ]; then b1=400; b2=1500; b3=800; else b1=150; b2=300; b3=300; fi
|
|
147
190
|
door "$l" health GET "$base/api/health" - "$b1" -
|
|
191
|
+
# SIGNAL_WATCH_WIRE_OWN_BOARD=1 exists for --self-test case 4 ONLY: DROPPING
|
|
192
|
+
# the board key sends the canary to the caller's own board, where `ceo` matches
|
|
193
|
+
# every tag by canon — the durable red half of the assertion below. (Setting
|
|
194
|
+
# `board` to an arbitrary slug does NOT work: the receiver's Zod schema rejects
|
|
195
|
+
# it with a 400, which is a different failure and proves nothing.) Never set
|
|
196
|
+
# this in a real run.
|
|
197
|
+
local wire_board='"board":"marketplace",'
|
|
198
|
+
if [ "${SIGNAL_WATCH_WIRE_OWN_BOARD:-0}" = 1 ]; then wire_board=''; fi
|
|
148
199
|
door "$l" world:announce POST "$base/api/ask/world:announce" \
|
|
149
|
-
"{\"data\":{\"tags\":[\"
|
|
200
|
+
"{\"data\":{\"tags\":[\"signal-watch-wire\"],${wire_board}\"type\":\"signal:canary\",\"text\":\"signal-watch $(date -u +%H:%M:%SZ)\",\"priority\":\"fyi\",\"slug\":\"one\"}}" "$b3" "$key" '"matched":0'
|
|
150
201
|
door "$l" tasks:everywhere POST "$base/api/ask/tasks:everywhere" '{"data":{"workspace":"factory-do","limit":200}}' "$b2" "$key"
|
|
151
202
|
door "$l" factory:size POST "$base/api/ask/factory:size" '{"data":{"paths":[".claude/scripts/signal-watch.sh"]}}' "$b2" "$key"
|
|
152
203
|
}
|
|
@@ -174,6 +225,12 @@ if [ "${SELF_TEST:-0}" = 1 ]; then
|
|
|
174
225
|
out=$(GATEWAY_API_KEY= SIGNAL_WATCH_BUDGET_SCALE=100 bash "$tmp/.claude/scripts/signal-watch.sh" --prod --no-ledger 2>&1); rc=$?
|
|
175
226
|
[ "$rc" -eq 3 ] && grep -q "unrun" <<<"$out" && echo " ok exit 3, unrun rows" || { echo " FAIL rc=$rc"; echo "$out" | tail -4; fails=$((fails+1)); }
|
|
176
227
|
rm -rf "$tmp"
|
|
228
|
+
echo "== 4. a wire probe that MATCHED somebody is unrun (exit 3), not a fast pass"
|
|
229
|
+
# Red half: point the canary back at the caller's own board, where `ceo`
|
|
230
|
+
# subscribes to every tag. The door still answers 200 with outcome=result —
|
|
231
|
+
# only the assertion catches it.
|
|
232
|
+
out=$(SIGNAL_WATCH_WIRE_OWN_BOARD=1 SIGNAL_WATCH_BUDGET_SCALE=100 bash "$0" --prod --no-ledger 2>&1); rc=$?
|
|
233
|
+
[ "$rc" -eq 3 ] && grep -q 'not the door we named' <<<"$out" && echo " ok exit 3, fan-out named as not-the-wire" || { echo " FAIL rc=$rc"; echo "$out" | tail -4; fails=$((fails+1)); }
|
|
177
234
|
echo "signal-watch --self-test: $fails failed"
|
|
178
235
|
exit "$fails"
|
|
179
236
|
fi
|
|
@@ -28,6 +28,16 @@
|
|
|
28
28
|
|
|
29
29
|
set -euo pipefail
|
|
30
30
|
|
|
31
|
+
# _GATE — see governor-doors-check.sh "compute sites". A gate_lock dedupes
|
|
32
|
+
# identical work; only a SLOT bounds N worktrees running N computes at once.
|
|
33
|
+
# Empty inside an existing gate so the outer slot is inherited, never doubled.
|
|
34
|
+
_GATE=()
|
|
35
|
+
if [ "${GOVERN_IN_GATE:-0}" != "1" ]; then
|
|
36
|
+
_GR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
|
|
37
|
+
[ -f "$_GR" ] && _GATE=( bash "$_GR" "compute:$(basename "${BASH_SOURCE[0]}")" -- )
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
|
|
31
41
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
32
42
|
WEB="$ROOT/one.ie/web"
|
|
33
43
|
REL_MEMO="one.ie/web/src/lib/domain-memo.ts"
|
|
@@ -120,7 +130,7 @@ fi
|
|
|
120
130
|
echo "[speed-cache-check] RED run — $RED_SOURCE_DESC"
|
|
121
131
|
cd "$WEB"
|
|
122
132
|
set +e
|
|
123
|
-
RED_OUT="$(bunx vitest run "$TEST_FILE" -t "$COLD_TEST_NAME" 2>&1)"
|
|
133
|
+
RED_OUT="$("${_GATE[@]}" bunx vitest run "$TEST_FILE" -t "$COLD_TEST_NAME" 2>&1)"
|
|
124
134
|
RED_STATUS=$?
|
|
125
135
|
set -e
|
|
126
136
|
cd "$ROOT"
|
|
@@ -147,7 +157,7 @@ TMP_BROKEN=""
|
|
|
147
157
|
echo "[speed-cache-check] GREEN run — working tree $REL_MEMO"
|
|
148
158
|
cd "$WEB"
|
|
149
159
|
set +e
|
|
150
|
-
GREEN_OUT="$(bunx vitest run "$TEST_FILE" 2>&1)"
|
|
160
|
+
GREEN_OUT="$("${_GATE[@]}" bunx vitest run "$TEST_FILE" 2>&1)"
|
|
151
161
|
GREEN_STATUS=$?
|
|
152
162
|
set -e
|
|
153
163
|
cd "$ROOT"
|
package/scripts/sweep.sh
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# sweep.sh — every finished branch into `dev`, one gate, one sha to promote.
|
|
3
|
+
#
|
|
4
|
+
# manifest: monorepo-only
|
|
5
|
+
#
|
|
6
|
+
# THE RULES. Each is true at the line it guards; the story that earned it is one
|
|
7
|
+
# command away and is never summarised here.
|
|
8
|
+
#
|
|
9
|
+
# ONE gate for N branches, never one gate per branch. incident:sweep-n-trees
|
|
10
|
+
# A batched red RESETS dev; re-land with land.sh, which incident:sweep-batched-red-owner
|
|
11
|
+
# still names the owner. land.sh's default is UNCHANGED.
|
|
12
|
+
# gc measures against origin/main, after a fetch. Removal incident:gc-stale-base
|
|
13
|
+
# is do-auto.sh --gc's; this never re-implements it.
|
|
14
|
+
# A dry run starts no gates, so it is never refused for incident:sweep-dry-run-never-refused
|
|
15
|
+
# the box. Reap only when orphans > 0.
|
|
16
|
+
#
|
|
17
|
+
# Accounts: bash .claude/scripts/incident.sh --for .claude/scripts/sweep.sh
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# sweep.sh # doctor · fetch · classify · land · ONE gate · gc · close
|
|
21
|
+
# sweep.sh --dry-run # the whole plan, nothing merged, removed or pushed
|
|
22
|
+
# sweep.sh --inventory # the classification table only (what --self-test drives)
|
|
23
|
+
# sweep.sh --full # force the FULL lane on the integrated tree
|
|
24
|
+
# sweep.sh --pr # also push dev and open/refresh the dev -> main PR
|
|
25
|
+
# sweep.sh --no-doctor # skip the box read (never skip it because it said no)
|
|
26
|
+
# sweep.sh --self-test # mint a sandbox estate and prove the classifier bites
|
|
27
|
+
#
|
|
28
|
+
# It does NOT ship. The last two doors are release-manager's
|
|
29
|
+
# (../CLAUDE.md § The dev -> prod loop); this one ends by naming the sha to promote.
|
|
30
|
+
#
|
|
31
|
+
# Exit codes, distinct and named — "it refused" is not a finding:
|
|
32
|
+
# 0 ok · 2 usage · 3 box refused (doctor) · 4 integrated gate RED (dev reset)
|
|
33
|
+
# 5 a merge conflicted (dev reset) · 6 nothing to sweep and nothing to promote
|
|
34
|
+
set -uo pipefail
|
|
35
|
+
|
|
36
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
37
|
+
# SWEEP_ROOT lets --self-test point the whole classifier at a sandbox estate.
|
|
38
|
+
# HERE always stays the real script dir, so the lib it sources is never the
|
|
39
|
+
# sandbox's (there isn't one) — the sandbox supplies REFS, not code.
|
|
40
|
+
ROOT="${SWEEP_ROOT:-$(cd "$HERE/../.." && pwd)}"
|
|
41
|
+
cd "$ROOT" || exit 2
|
|
42
|
+
|
|
43
|
+
# SWEEP_GATE_CMD is honoured ONLY under SWEEP_ROOT — there is no way to spell
|
|
44
|
+
# "skip the gate" in a real run. incident:sweep-sandbox-seam
|
|
45
|
+
SANDBOX=0; [ -n "${SWEEP_ROOT:-}" ] && SANDBOX=1
|
|
46
|
+
TRUNK="${SWEEP_TRUNK:-dev}"
|
|
47
|
+
GC_BASE="${SWEEP_GC_BASE:-origin/main}"
|
|
48
|
+
KEEP="${SWEEP_KEEP:-main dev release}"
|
|
49
|
+
DRY=0; INVENTORY=0; FULL=0; PR=0; DOCTOR=1; SELF_TEST=0
|
|
50
|
+
|
|
51
|
+
while [ $# -gt 0 ]; do
|
|
52
|
+
case "$1" in
|
|
53
|
+
--dry-run) DRY=1 ;;
|
|
54
|
+
--inventory) INVENTORY=1 ;;
|
|
55
|
+
--full) FULL=1 ;;
|
|
56
|
+
--pr) PR=1 ;;
|
|
57
|
+
--no-doctor) DOCTOR=0 ;;
|
|
58
|
+
--self-test) SELF_TEST=1 ;;
|
|
59
|
+
-h|--help) sed -n '1,60p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
60
|
+
*) echo "sweep: unknown flag $1" >&2; exit 2 ;;
|
|
61
|
+
esac
|
|
62
|
+
shift
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
say() { printf '%s\n' "$*"; }
|
|
66
|
+
ok() { printf ' ok %s\n' "$*"; }
|
|
67
|
+
bad() { printf ' RED %s\n' "$*" >&2; }
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------- classify ---
|
|
70
|
+
# ONE definition of "finished", shared with the sweep that does the removing.
|
|
71
|
+
. "$HERE/lib/gc-finished.sh"
|
|
72
|
+
|
|
73
|
+
# _live <worktree-dir> — is anything running in this tree?
|
|
74
|
+
# The trailing slash is load-bearing; pgrep -f reads argv, not cwd. incident:live-anchor
|
|
75
|
+
_live() {
|
|
76
|
+
local wt="$1"
|
|
77
|
+
[ -n "$(pgrep -f "${wt%/}/" 2>/dev/null | grep -v "^$$\$" || true)" ]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# _worktree_of <branch> — the checkout holding it, empty when there is none.
|
|
81
|
+
_worktree_of() {
|
|
82
|
+
git -C "$ROOT" worktree list --porcelain 2>/dev/null \
|
|
83
|
+
| awk -v b="refs/heads/$1" '
|
|
84
|
+
/^worktree /{wt=substr($0,10)}
|
|
85
|
+
/^branch /{if (substr($0,8)==b) {print wt; exit}}'
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# classify <branch> -> "<class> <detail>"
|
|
89
|
+
# infra one of the branches the loop itself stands on — never touched
|
|
90
|
+
# landed its content is already in the trunk (content identity, not counting)
|
|
91
|
+
# busy ahead, but its worktree is dirty or something is running in it
|
|
92
|
+
# ready ahead, clean, idle — this is the only class that gets merged
|
|
93
|
+
classify() {
|
|
94
|
+
local b="$1" wt ahead dirt
|
|
95
|
+
case " $KEEP " in *" $b "*) echo "infra -"; return ;; esac
|
|
96
|
+
wt="$(_worktree_of "$b")"
|
|
97
|
+
if gc_finished "$TRUNK" "$b"; then echo "landed ${wt:--}"; return; fi
|
|
98
|
+
ahead="$(gc_carries "$TRUNK" "$b")"
|
|
99
|
+
if [ -n "$wt" ]; then
|
|
100
|
+
dirt="$(gc_real_dirt "$wt")"
|
|
101
|
+
[ -n "$dirt" ] && { echo "busy uncommitted-work-in-$wt"; return; }
|
|
102
|
+
_live "$wt" && { echo "busy something-running-in-$wt"; return; }
|
|
103
|
+
fi
|
|
104
|
+
echo "ready carries-${ahead}-path(s)"
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
inventory() {
|
|
108
|
+
local b cls
|
|
109
|
+
while IFS= read -r b; do
|
|
110
|
+
[ -n "$b" ] || continue
|
|
111
|
+
cls="$(classify "$b")"
|
|
112
|
+
printf '%s\t%s\n' "$b" "$cls"
|
|
113
|
+
done < <(git -C "$ROOT" for-each-ref --format='%(refname:short)' refs/heads/)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# --------------------------------------------------------------- self-test ---
|
|
117
|
+
# A green run on the live estate proves only that a no-op is a no-op.
|
|
118
|
+
self_test() {
|
|
119
|
+
local rc=0
|
|
120
|
+
|
|
121
|
+
# _mint <dir> [extra-shell] — a throwaway estate with one branch of every
|
|
122
|
+
# class. `extra` runs last, inside the repo, so a case can add its own shape.
|
|
123
|
+
_mint() {
|
|
124
|
+
local d="$1" extra="${2:-}"
|
|
125
|
+
( set -e
|
|
126
|
+
mkdir -p "$d"; cd "$d"
|
|
127
|
+
git init -q -b dev .
|
|
128
|
+
git config user.email s@s; git config user.name s
|
|
129
|
+
echo base > a.txt
|
|
130
|
+
# one.ie/web must be TRACKED or .astro never surfaces as its own path.
|
|
131
|
+
# incident:porcelain-collapses-untracked-dir
|
|
132
|
+
mkdir -p one.ie/web; echo keep > one.ie/web/keep.txt
|
|
133
|
+
git add a.txt one.ie/web/keep.txt; git commit -qm base
|
|
134
|
+
|
|
135
|
+
# landed: its patch is in dev under a DIFFERENT sha — the rebase/squash
|
|
136
|
+
# case `rev-list --count` gets wrong, which is why the predicate is content.
|
|
137
|
+
git checkout -qb feat/landed; echo one > b.txt; git add b.txt; git commit -qm one
|
|
138
|
+
git checkout -q dev; echo one > b.txt; git add b.txt; git commit -qm "one, landed differently"
|
|
139
|
+
|
|
140
|
+
# ready: ahead, clean, no worktree.
|
|
141
|
+
git checkout -qb feat/ready dev; echo two > c.txt; git add c.txt; git commit -qm two
|
|
142
|
+
|
|
143
|
+
# busy-dirty: ahead, with uncommitted WORK in its checkout.
|
|
144
|
+
git checkout -qb feat/dirty dev; echo three > d.txt; git add d.txt; git commit -qm three
|
|
145
|
+
git checkout -q dev
|
|
146
|
+
git worktree add -q "$d/wt-dirty" feat/dirty
|
|
147
|
+
echo scribble >> "$d/wt-dirty/d.txt"
|
|
148
|
+
|
|
149
|
+
# ephemeral-only dirt must NOT read as busy — build output is not work.
|
|
150
|
+
git checkout -qb feat/ephemeral dev; echo four > e.txt; git add e.txt; git commit -qm four
|
|
151
|
+
git checkout -q dev
|
|
152
|
+
git worktree add -q "$d/wt-eph" feat/ephemeral
|
|
153
|
+
mkdir -p "$d/wt-eph/one.ie/web/.astro"; echo x > "$d/wt-eph/one.ie/web/.astro/x"
|
|
154
|
+
|
|
155
|
+
git checkout -q dev
|
|
156
|
+
[ -n "$extra" ] && eval "$extra"
|
|
157
|
+
true
|
|
158
|
+
) >/dev/null 2>&1
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_run() { # <dir> <gate-cmd> [flags...] -> prints output, returns sweep's exit
|
|
162
|
+
local d="$1" gate="$2"; shift 2
|
|
163
|
+
( cd "$d" && SWEEP_ROOT="$d" SWEEP_GATE_CMD="$gate" SWEEP_TRUNK=dev \
|
|
164
|
+
SWEEP_KEEP="main dev release" bash "$HERE/sweep.sh" --no-doctor "$@" 2>&1 )
|
|
165
|
+
}
|
|
166
|
+
_assert() { # <label> <condition-result 0/1>
|
|
167
|
+
if [ "$2" = 0 ]; then ok "$1"; else bad "$1"; rc=1; fi
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
say "── sweep --self-test ──"
|
|
171
|
+
|
|
172
|
+
# ---- 1. the classifier, one branch of every class ------------------------
|
|
173
|
+
local t1 out
|
|
174
|
+
t1="$(mktemp -d)"; _mint "$t1" || { bad "sandbox 1 would not build"; return 1; }
|
|
175
|
+
out="$( cd "$t1" && SWEEP_ROOT="$t1" SWEEP_TRUNK=dev SWEEP_KEEP="main dev release" \
|
|
176
|
+
bash "$HERE/sweep.sh" --inventory 2>/dev/null )"
|
|
177
|
+
printf '%s\n' "$out" | sed 's/^/ /'
|
|
178
|
+
_expect() { # <branch> <class>
|
|
179
|
+
local got; got="$(printf '%s\n' "$out" | awk -v b="$1" '$1==b{print $2}')"
|
|
180
|
+
if [ "$got" = "$2" ]; then ok "classify $1 -> $2"
|
|
181
|
+
else bad "classify $1 -> ${got:-<absent>} (expected $2)"; rc=1; fi
|
|
182
|
+
}
|
|
183
|
+
_expect dev infra
|
|
184
|
+
_expect feat/landed landed
|
|
185
|
+
_expect feat/ready ready
|
|
186
|
+
_expect feat/dirty busy
|
|
187
|
+
_expect feat/ephemeral ready
|
|
188
|
+
|
|
189
|
+
# ---- 2. liveness, both directions ---------------------------------------
|
|
190
|
+
# `exec -a` puts the path in argv, which is what pgrep -f reads. incident:live-anchor
|
|
191
|
+
if _live "$t1/definitely-not-a-tree"; then bad "_live said yes for an empty path"; rc=1
|
|
192
|
+
else ok "_live is false for an empty path"; fi
|
|
193
|
+
local holder
|
|
194
|
+
( exec -a "node $t1/one.ie/web/dev-server" sleep 20 ) >/dev/null 2>&1 &
|
|
195
|
+
holder=$!; sleep 0.3
|
|
196
|
+
_live "$t1" && ok "_live sees a live process under the tree" || { bad "_live missed it"; rc=1; }
|
|
197
|
+
# The anchor: `pgrep -f worktrees/media` also matches `worktrees/mediawire`.
|
|
198
|
+
if _live "${t1}-neighbour"; then bad "_live matched a PREFIX neighbour"; rc=1
|
|
199
|
+
else ok "_live does not match a prefix neighbour"; fi
|
|
200
|
+
kill "$holder" 2>/dev/null; wait "$holder" 2>/dev/null
|
|
201
|
+
|
|
202
|
+
# ---- 3. the MERGE path: ready branches land, busy ones do not ------------
|
|
203
|
+
# Everything below MOVES REFS, which is why it is tested. incident:sweep-sandbox-seam
|
|
204
|
+
local t2 pre post
|
|
205
|
+
t2="$(mktemp -d)"; _mint "$t2"
|
|
206
|
+
pre="$(git -C "$t2" rev-parse HEAD)"
|
|
207
|
+
out="$(_run "$t2" true)"; local st2=$?
|
|
208
|
+
post="$(git -C "$t2" rev-parse HEAD)"
|
|
209
|
+
_assert "merge: exits 0 with a green gate" $([ $st2 -eq 0 ] && echo 0 || echo 1)
|
|
210
|
+
_assert "merge: dev moved" $([ "$pre" != "$post" ] && echo 0 || echo 1)
|
|
211
|
+
_assert "merge: feat/ready landed (c.txt)" $(git -C "$t2" cat-file -e dev:c.txt 2>/dev/null && echo 0 || echo 1)
|
|
212
|
+
_assert "merge: feat/ephemeral landed (e.txt)" $(git -C "$t2" cat-file -e dev:e.txt 2>/dev/null && echo 0 || echo 1)
|
|
213
|
+
_assert "merge: feat/dirty did NOT land (d.txt absent)" $(git -C "$t2" cat-file -e dev:d.txt 2>/dev/null && echo 1 || echo 0)
|
|
214
|
+
_assert "merge: reports gates=1" $(printf '%s' "$out" | grep -q "gates=1" && echo 0 || echo 1)
|
|
215
|
+
_assert "merge: reports lane=fast" $(printf '%s' "$out" | grep -q "lane=fast" && echo 0 || echo 1)
|
|
216
|
+
|
|
217
|
+
# ---- 4. the lane escalates on schema/, by itself -------------------------
|
|
218
|
+
local t3
|
|
219
|
+
t3="$(mktemp -d)"
|
|
220
|
+
_mint "$t3" 'git checkout -qb feat/schema dev; mkdir -p schema; echo "define x" > schema/one.tql; git add schema; git commit -qm schema; git checkout -q dev'
|
|
221
|
+
out="$(_run "$t3" true)"
|
|
222
|
+
_assert "lane: a schema/ touch selects FULL without being asked" \
|
|
223
|
+
$(printf '%s' "$out" | grep -q "lane=full" && echo 0 || echo 1)
|
|
224
|
+
|
|
225
|
+
# ---- 5. a RED gate resets dev and names the re-land -----------------------
|
|
226
|
+
local t4 pre4 post4
|
|
227
|
+
t4="$(mktemp -d)"; _mint "$t4"
|
|
228
|
+
pre4="$(git -C "$t4" rev-parse HEAD)"
|
|
229
|
+
out="$(_run "$t4" false)"; local st4=$?
|
|
230
|
+
post4="$(git -C "$t4" rev-parse HEAD)"
|
|
231
|
+
_assert "red gate: exit 4" $([ $st4 -eq 4 ] && echo 0 || echo 1)
|
|
232
|
+
_assert "red gate: dev reset to the pre-sweep sha" $([ "$pre4" = "$post4" ] && echo 0 || echo 1)
|
|
233
|
+
_assert "red gate: names land.sh per branch" \
|
|
234
|
+
$(printf '%s' "$out" | grep -q "land.sh feat/ready" && echo 0 || echo 1)
|
|
235
|
+
|
|
236
|
+
# ---- 6. a CONFLICT resets dev too — its own exit code (5, not 4) ---------
|
|
237
|
+
local t5 pre5 post5
|
|
238
|
+
t5="$(mktemp -d)"
|
|
239
|
+
_mint "$t5" 'git checkout -qb feat/clash dev; echo mine > a.txt; git commit -qam mine; git checkout -q dev; echo theirs > a.txt; git commit -qam theirs'
|
|
240
|
+
pre5="$(git -C "$t5" rev-parse HEAD)"
|
|
241
|
+
out="$(_run "$t5" true)"; local st5=$?
|
|
242
|
+
post5="$(git -C "$t5" rev-parse HEAD)"
|
|
243
|
+
_assert "conflict: exit 5" $([ $st5 -eq 5 ] && echo 0 || echo 1)
|
|
244
|
+
_assert "conflict: dev reset to the pre-sweep sha" $([ "$pre5" = "$post5" ] && echo 0 || echo 1)
|
|
245
|
+
|
|
246
|
+
# ---- 7. the stub is unreachable without a sandbox ------------------------
|
|
247
|
+
_assert "SWEEP_GATE_CMD is honoured only under SWEEP_ROOT" \
|
|
248
|
+
$(grep -q 'if (( SANDBOX )) && \[ -n "\${SWEEP_GATE_CMD:-}" \]' "$HERE/sweep.sh" && echo 0 || echo 1)
|
|
249
|
+
|
|
250
|
+
rm -rf "$t1" "$t2" "$t3" "$t4" "$t5"
|
|
251
|
+
say ""
|
|
252
|
+
[ $rc -eq 0 ] && say " self-test PASS" || say " self-test RED"
|
|
253
|
+
return $rc
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
[ $SELF_TEST -eq 1 ] && { self_test; exit $?; }
|
|
257
|
+
[ $INVENTORY -eq 1 ] && { inventory; exit 0; }
|
|
258
|
+
|
|
259
|
+
# ------------------------------------------------------------ phase 0 doctor --
|
|
260
|
+
# The box read comes FIRST because a sweep starts gates, and a saturated box
|
|
261
|
+
# turns a fine tree red for queueing. health.sh --fix never kills a claude
|
|
262
|
+
# session, a governor-slot holder, or anything in the main tree.
|
|
263
|
+
VERDICT="skipped"; ORPHANS="?"; FUNDED="?"; WHY=""
|
|
264
|
+
|
|
265
|
+
# _box — re-read the doctor's instrument. health.sh owns every number; this only
|
|
266
|
+
# reads them. HEALTHY | DEGRADED | UNHEALTHY (health.sh:307-319).
|
|
267
|
+
_box() {
|
|
268
|
+
local j
|
|
269
|
+
j="$( bash "$HERE/health.sh" --box --json 2>/dev/null )"
|
|
270
|
+
VERDICT="$(printf '%s' "$j" | sed -n 's/.*"verdict":"\([A-Z]*\)".*/\1/p')"
|
|
271
|
+
WHY="$(printf '%s' "$j" | sed -n 's/.*"why":"\([^"]*\)".*/\1/p')"
|
|
272
|
+
ORPHANS="$(printf '%s' "$j" | sed -n 's/.*"orphans":\([0-9]*\).*/\1/p')"
|
|
273
|
+
FUNDED="$(printf '%s' "$j" | sed -n 's/.*"gates_funded":\([0-9]*\).*/\1/p')"
|
|
274
|
+
VERDICT="${VERDICT:-unknown}"
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if [ $DOCTOR -eq 1 ]; then
|
|
278
|
+
say "── doctor ──"
|
|
279
|
+
_box
|
|
280
|
+
say " box: $VERDICT — ${WHY:-no reason given}"
|
|
281
|
+
say " orphans=$ORPHANS gates_funded=$FUNDED"
|
|
282
|
+
|
|
283
|
+
# Reap only when there is something to reap. incident:sweep-dry-run-never-refused
|
|
284
|
+
if [ "${ORPHANS:-0}" != "0" ]; then
|
|
285
|
+
bash "$HERE/gate-reaper.sh" --once 2>/dev/null | sed 's/^/ /'
|
|
286
|
+
_box
|
|
287
|
+
say " box after reap: $VERDICT (orphans=$ORPHANS)"
|
|
288
|
+
fi
|
|
289
|
+
|
|
290
|
+
case "$VERDICT" in
|
|
291
|
+
UNHEALTHY)
|
|
292
|
+
# A dry run starts no gates, so it is never refused.
|
|
293
|
+
if (( DRY )); then say " (dry run — reporting the box, refusing nothing)"; else
|
|
294
|
+
bad "box is UNHEALTHY — $WHY"
|
|
295
|
+
say " A sweep STARTS gates. On a paging box the gate runs long and a slow"
|
|
296
|
+
say " gate is not a red one, but it is indistinguishable from one at the"
|
|
297
|
+
say " wall clock. Hand the box to the doctor first:"
|
|
298
|
+
say " Agent({ subagent_type: \"doctor\", model: \"opus\","
|
|
299
|
+
say " prompt: \"health.sh says: $WHY. Reclaim what nothing is coming back for, then report.\" })"
|
|
300
|
+
say " Or override, knowing the gate will be slow: sweep.sh --no-doctor"
|
|
301
|
+
exit 3
|
|
302
|
+
fi ;;
|
|
303
|
+
DEGRADED)
|
|
304
|
+
# Headroom of 1: a reason to run ONE gate, not a reason to refuse.
|
|
305
|
+
say " proceeding: DEGRADED funds $FUNDED concurrent gate(s), and a sweep needs exactly one" ;;
|
|
306
|
+
*)
|
|
307
|
+
ok "box is $VERDICT" ;;
|
|
308
|
+
esac
|
|
309
|
+
fi
|
|
310
|
+
|
|
311
|
+
# --------------------------------------------------------------- phase 1 fetch --
|
|
312
|
+
say "── fetch ──"
|
|
313
|
+
git -C "$ROOT" fetch -q --prune origin 2>/dev/null || say " (offline — measuring against local refs)"
|
|
314
|
+
TRUNK_WT="$(_worktree_of "$TRUNK")"
|
|
315
|
+
[ -n "$TRUNK_WT" ] || { bad "no worktree holds $TRUNK — cut one with worktree-up.sh $TRUNK"; exit 2; }
|
|
316
|
+
ok "$TRUNK is checked out at $TRUNK_WT"
|
|
317
|
+
|
|
318
|
+
# ------------------------------------------------------------ phase 2 classify --
|
|
319
|
+
say "── classify ──"
|
|
320
|
+
READY=(); LANDED=0; BUSY=0; INFRA=0
|
|
321
|
+
while IFS=$'\t' read -r b rest; do
|
|
322
|
+
cls="${rest%% *}"; detail="${rest#* }"
|
|
323
|
+
case "$cls" in
|
|
324
|
+
infra) INFRA=$((INFRA+1)) ;;
|
|
325
|
+
landed) LANDED=$((LANDED+1)); say " landed $b" ;;
|
|
326
|
+
busy) BUSY=$((BUSY+1)); say " busy $b — $detail" ;;
|
|
327
|
+
ready) READY+=("$b"); say " ready $b — $detail" ;;
|
|
328
|
+
esac
|
|
329
|
+
done < <(inventory)
|
|
330
|
+
say " ready=${#READY[@]} landed=$LANDED busy=$BUSY infra=$INFRA"
|
|
331
|
+
|
|
332
|
+
PRE_SWEEP_SHA="$(git -C "$TRUNK_WT" rev-parse HEAD)"
|
|
333
|
+
|
|
334
|
+
# --------------------------------------------------------------- phase 3 land --
|
|
335
|
+
MERGED=0; TOUCHED=""
|
|
336
|
+
if [ ${#READY[@]} -gt 0 ]; then
|
|
337
|
+
say "── land (batched into $TRUNK) ──"
|
|
338
|
+
for b in "${READY[@]}"; do
|
|
339
|
+
probe="$( TSC_CACHE_ROOT="$(_worktree_of "$b")" \
|
|
340
|
+
bash "$ROOT/.claude/scripts/tsc-cached.sh" --probe one.ie/web 2>/dev/null || echo unknown )"
|
|
341
|
+
if (( DRY )); then say " [dry] merge $b into $TRUNK (own typecheck: $probe)"; MERGED=$((MERGED+1)); continue; fi
|
|
342
|
+
say " merging $b (own typecheck: $probe)"
|
|
343
|
+
if ! git -C "$TRUNK_WT" merge --no-edit "$b" >/dev/null 2>&1; then
|
|
344
|
+
git -C "$TRUNK_WT" merge --abort 2>/dev/null
|
|
345
|
+
git -C "$TRUNK_WT" reset --hard "$PRE_SWEEP_SHA" >/dev/null 2>&1
|
|
346
|
+
bad "$b conflicts with $TRUNK — $TRUNK reset to $PRE_SWEEP_SHA, land it alone: land.sh $b"
|
|
347
|
+
exit 5
|
|
348
|
+
fi
|
|
349
|
+
TOUCHED="$TOUCHED$(git -C "$TRUNK_WT" diff --name-only "$PRE_SWEEP_SHA" HEAD)"$'\n'
|
|
350
|
+
MERGED=$((MERGED+1))
|
|
351
|
+
done
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
# --------------------------------------------------------- phase 4 ONE gate ---
|
|
355
|
+
# schema, the SDK and auth take the FULL lane, whatever the default is.
|
|
356
|
+
GATES=0; LANE="none"
|
|
357
|
+
if [ $MERGED -gt 0 ] || [ $FULL -eq 1 ]; then
|
|
358
|
+
if [ $FULL -eq 1 ] || printf '%s' "$TOUCHED" | grep -qE '^(schema/|packages/sdk/|one\.ie/web/src/lib/(authority|role-check))'; then
|
|
359
|
+
LANE="full"
|
|
360
|
+
else
|
|
361
|
+
LANE="fast"
|
|
362
|
+
fi
|
|
363
|
+
say "── gate (ONE, on the integrated tree) ── lane=$LANE"
|
|
364
|
+
if (( DRY )); then
|
|
365
|
+
say " [dry] (cd $TRUNK_WT/one.ie/web && gate-run.sh sweep -- $([ $LANE = full ] && echo 'FULL_VERIFY=1 bun run verify' || echo 'bun run verify:fast'))"
|
|
366
|
+
else
|
|
367
|
+
GATES=1
|
|
368
|
+
# Status captured per branch of the if, never from `$?` after `fi`, and the
|
|
369
|
+
# gate is never piped. incident:gate-status-never-piped
|
|
370
|
+
st=0
|
|
371
|
+
if (( SANDBOX )) && [ -n "${SWEEP_GATE_CMD:-}" ]; then
|
|
372
|
+
eval "$SWEEP_GATE_CMD" || st=$?
|
|
373
|
+
elif [ "$LANE" = "full" ]; then
|
|
374
|
+
( cd "$TRUNK_WT/one.ie/web" && FULL_VERIFY=1 bash "$ROOT/.claude/scripts/gate-run.sh" sweep -- bun run verify ) || st=$?
|
|
375
|
+
else
|
|
376
|
+
( cd "$TRUNK_WT/one.ie/web" && bash "$ROOT/.claude/scripts/gate-run.sh" sweep -- bun run verify:fast ) || st=$?
|
|
377
|
+
fi
|
|
378
|
+
if [ $st -ne 0 ]; then
|
|
379
|
+
git -C "$TRUNK_WT" reset --hard "$PRE_SWEEP_SHA" >/dev/null 2>&1
|
|
380
|
+
bad "integrated gate RED (exit $st) — $TRUNK reset to $PRE_SWEEP_SHA, nothing landed"
|
|
381
|
+
say " the batch cannot name which of ${#READY[@]} owns it. Re-land one at a time,"
|
|
382
|
+
say " where land.sh diagnoses branch / dev / seam / environment:"
|
|
383
|
+
for b in "${READY[@]}"; do say " bash .claude/scripts/land.sh $b"; done
|
|
384
|
+
exit 4
|
|
385
|
+
fi
|
|
386
|
+
ok "integrated $LANE gate green on $(git -C "$TRUNK_WT" rev-parse --short HEAD)"
|
|
387
|
+
fi
|
|
388
|
+
fi
|
|
389
|
+
|
|
390
|
+
# ----------------------------------------------------------------- phase 5 gc --
|
|
391
|
+
say "── gc (base $GC_BASE — a branch is finished when RELEASED, not integrated) ──"
|
|
392
|
+
if (( SANDBOX )); then
|
|
393
|
+
say " (sandbox — the collector is do-auto.sh's and is not re-run here)"
|
|
394
|
+
elif (( DRY )); then
|
|
395
|
+
DO_GC_BASE="$GC_BASE" bash "$ROOT/.claude/scripts/do-auto.sh" --gc --dry-run 2>&1 | sed 's/^/ /'
|
|
396
|
+
else
|
|
397
|
+
DO_GC_BASE="$GC_BASE" bash "$ROOT/.claude/scripts/do-auto.sh" --gc 2>&1 | sed 's/^/ /'
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
# ------------------------------------------------------------- phase 6 ready ---
|
|
401
|
+
say "── ready to deploy ──"
|
|
402
|
+
DEV_SHA="$(git -C "$TRUNK_WT" rev-parse HEAD)"
|
|
403
|
+
AHEAD_OF_MAIN="$(git -C "$ROOT" rev-list --count "origin/main..$DEV_SHA" 2>/dev/null || echo 0)"
|
|
404
|
+
if [ "$AHEAD_OF_MAIN" = "0" ] && [ $MERGED -eq 0 ]; then
|
|
405
|
+
say " $TRUNK carries nothing origin/main does not have — nothing to promote"
|
|
406
|
+
say "ready=0 merged=0 landed=$LANDED busy=$BUSY infra=$INFRA gates=$GATES lane=$LANE box=$VERDICT dev=$(git -C "$TRUNK_WT" rev-parse --short HEAD) ahead_of_main=0"
|
|
407
|
+
exit 6
|
|
408
|
+
fi
|
|
409
|
+
if (( DRY )); then
|
|
410
|
+
say " [dry] git push origin $TRUNK"
|
|
411
|
+
(( PR )) && say " [dry] gh pr create --base main --head $TRUNK"
|
|
412
|
+
else
|
|
413
|
+
if (( SANDBOX )); then say " (sandbox — not pushing)"
|
|
414
|
+
else git -C "$TRUNK_WT" push -q origin "$TRUNK" 2>/dev/null && ok "pushed $TRUNK" || say " (push skipped — offline or nothing new)"; fi
|
|
415
|
+
if (( PR )); then
|
|
416
|
+
gh pr create --base main --head "$TRUNK" --fill 2>/dev/null \
|
|
417
|
+
|| gh pr list --base main --head "$TRUNK" --json url --jq '.[0].url' 2>/dev/null | sed 's/^/ PR: /'
|
|
418
|
+
fi
|
|
419
|
+
fi
|
|
420
|
+
say " $TRUNK is $AHEAD_OF_MAIN commit(s) ahead of origin/main at $(git -C "$TRUNK_WT" rev-parse --short HEAD)"
|
|
421
|
+
say " the ship door is release-manager's, never this script's:"
|
|
422
|
+
say " Agent({ subagent_type: \"release-manager\", model: \"opus\", prompt: \"…promote $DEV_SHA, then ship\" })"
|
|
423
|
+
|
|
424
|
+
say ""
|
|
425
|
+
# THE NUMERIC CLOSE. Every number is counted, not narrated; `gates` is the point.
|
|
426
|
+
say "ready=${#READY[@]} merged=$MERGED landed=$LANDED busy=$BUSY infra=$INFRA gates=$GATES lane=$LANE box=$VERDICT dev=$(git -C "$TRUNK_WT" rev-parse --short HEAD) ahead_of_main=$AHEAD_OF_MAIN"
|