@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,289 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# preview-fd-check.sh — is the preview tax a PIPE that outlives the script?
|
|
3
|
+
#
|
|
4
|
+
# manifest: needs-env
|
|
5
|
+
# Mode A (default) is self-contained: a scratch sandbox, one node mimic, no
|
|
6
|
+
# ports, no worktree, no astro, no credential. Mode B (--real) drives
|
|
7
|
+
# worktree-preview.sh against one.ie/web and costs a worktree and a port.
|
|
8
|
+
#
|
|
9
|
+
# NOT A FIX. A MEASUREMENT THAT IS ALLOWED TO COME BACK NEGATIVE.
|
|
10
|
+
#
|
|
11
|
+
# THE OBSERVATION. The chairman measured ~7 min of Opus per UI task blocked in
|
|
12
|
+
# worktree-preview.sh up <slug> --route / | tail -30
|
|
13
|
+
# with the server green in under a minute and the shell parked in __wait4 at
|
|
14
|
+
# 8m42s. Two mechanisms fit that sample and only one of them is fixable at the
|
|
15
|
+
# launch line:
|
|
16
|
+
#
|
|
17
|
+
# H1 THE PIPE OUTLIVES THE SCRIPT. cmd_up prints GREEN and returns, but the
|
|
18
|
+
# reader never sees EOF because a descendant of the launch still holds the
|
|
19
|
+
# script's stdout. The tool call is waiting on `tail`, not on the server.
|
|
20
|
+
# H2 THE SCRIPT REALLY IS STILL RUNNING. BOOT_LOCK_WAIT 240 + BOOT_TIMEOUT 60
|
|
21
|
+
# + WARM_TIMEOUT 180 = 480s against an observed 522s. A `sleep 1` poll loop
|
|
22
|
+
# samples as __wait4 exactly like waiting on a pipe.
|
|
23
|
+
#
|
|
24
|
+
# Mode A can only support or refute H1. It CANNOT exclude H2 — only --real can,
|
|
25
|
+
# by asking whether the driver process is already gone at the moment the server
|
|
26
|
+
# answers 200. So a green Mode A is not licence to touch worktree-preview.sh.
|
|
27
|
+
#
|
|
28
|
+
# THE LAUNCH THIS REPRODUCES, quoted from worktree-preview.sh (the perl branch;
|
|
29
|
+
# the nohup branch is its twin):
|
|
30
|
+
# ( cd "$app" && perl -e 'setsid; exec @ARGV' -- sh -c "exec $astro dev ..." >>"$log" 2>&1 & echo $! > "$state/pid" )
|
|
31
|
+
#
|
|
32
|
+
# VARIANTS, each timed from the DRIVER'S OWN EXIT to the reader's EOF:
|
|
33
|
+
# V1 driver | cat the shape the operator runs
|
|
34
|
+
# V2 driver > file no pipe at all
|
|
35
|
+
# V3 driver | cat, launch gets </dev/null is stdin the holder?
|
|
36
|
+
# V4 driver | cat, subshell does `exec >>log 2>&1 </dev/null` BEFORE the fork
|
|
37
|
+
#
|
|
38
|
+
# VERDICT DISCIPLINE. REPRODUCED requires ALL THREE:
|
|
39
|
+
# V1 hold >= 60% of the mimic's lifetime AND V2 hold <= 2s AND a NAMED
|
|
40
|
+
# process holding the same pipe as the reader's fd 0.
|
|
41
|
+
# A timing alone is a correlation. Anything short of all three prints
|
|
42
|
+
# NOT REPRODUCED with every number and exits 3, and no line of
|
|
43
|
+
# worktree-preview.sh is touched.
|
|
44
|
+
#
|
|
45
|
+
# WHAT MODE A FOUND, 2026-09-06 (hold 12s):
|
|
46
|
+
# V1(pipe)=12.2s V2(file)=0.0s V3(stdin</dev/null)=12.2s V4(exec-before-fork)=0.0s
|
|
47
|
+
# holder = <pid> bash / fd 1 / the exact peer of the reader's fd 0.
|
|
48
|
+
# The holder is a BASH SUBSHELL, not node. `cmd >>log 2>&1 &` backgrounds the
|
|
49
|
+
# whole `cd "$app" && perl …` AND-LIST: bash forks a subshell to run it, and the
|
|
50
|
+
# `>>"$log" 2>&1` binds to the `perl` simple command INSIDE it. So the subshell
|
|
51
|
+
# keeps the caller's stdout on its own fd 1 for as long as the server lives, and
|
|
52
|
+
# `$!` — the pid worktree-preview.sh stores and polls — IS that subshell.
|
|
53
|
+
# V3 shows stdin is not the holder; V4 shows giving the subshell its own stdio
|
|
54
|
+
# before the fork closes it. That is H1, named.
|
|
55
|
+
#
|
|
56
|
+
# H2 IS STILL LIVE for the 522s the chairman observed (BOOT_LOCK_WAIT 240 +
|
|
57
|
+
# BOOT_TIMEOUT 60 + WARM_TIMEOUT 180 = 480s), and Mode A cannot separate them.
|
|
58
|
+
# NOTHING IN worktree-preview.sh HAS BEEN CHANGED on the strength of this run.
|
|
59
|
+
#
|
|
60
|
+
# USAGE preview-fd-check.sh [--sandbox] [--hold N] Mode A, default
|
|
61
|
+
# preview-fd-check.sh --real <slug> Mode B, opt-in
|
|
62
|
+
# preview-fd-check.sh --self-test the harness's own guards
|
|
63
|
+
# EXITS 0 REPRODUCED · 3 NOT REPRODUCED · 4 bad arguments / missing tool /
|
|
64
|
+
# UNMEASURED (a variant produced no timing — never reported as a verdict)
|
|
65
|
+
set -uo pipefail
|
|
66
|
+
|
|
67
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
68
|
+
HOLD="${PREVIEW_FD_HOLD:-12}"
|
|
69
|
+
MODE=sandbox
|
|
70
|
+
SLUG=""
|
|
71
|
+
|
|
72
|
+
now() { perl -MTime::HiRes=time -e 'printf "%.3f\n", time'; }
|
|
73
|
+
el() { perl -e 'printf "%.1f\n", $ARGV[1] - $ARGV[0]' "$1" "$2"; }
|
|
74
|
+
|
|
75
|
+
while [ $# -gt 0 ]; do
|
|
76
|
+
case "$1" in
|
|
77
|
+
--sandbox) MODE=sandbox; shift ;;
|
|
78
|
+
--real) MODE=real; SLUG="${2:-}"; shift 2 ;;
|
|
79
|
+
--hold) HOLD="${2:-12}"; shift 2 ;;
|
|
80
|
+
--self-test) MODE=selftest; shift ;;
|
|
81
|
+
-h|--help) sed -n '2,50p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
82
|
+
*) echo "preview-fd-check: unknown argument: $1" >&2; exit 4 ;;
|
|
83
|
+
esac
|
|
84
|
+
done
|
|
85
|
+
|
|
86
|
+
command -v perl >/dev/null 2>&1 || { echo "preview-fd-check: perl not found — it is both the timer and the launch shape" >&2; exit 4; }
|
|
87
|
+
command -v node >/dev/null 2>&1 || { echo "preview-fd-check: node not found — the mimic is a node process on purpose" >&2; exit 4; }
|
|
88
|
+
|
|
89
|
+
SB="${TMPDIR:-/tmp}/preview-fd-$$"
|
|
90
|
+
mkdir -p "$SB"
|
|
91
|
+
cleanup() {
|
|
92
|
+
local p
|
|
93
|
+
for p in "$SB"/*/pid; do [ -f "$p" ] || continue; kill -- -"$(cat "$p")" 2>/dev/null || kill "$(cat "$p")" 2>/dev/null; done
|
|
94
|
+
rm -rf "$SB"
|
|
95
|
+
}
|
|
96
|
+
trap cleanup EXIT INT TERM
|
|
97
|
+
|
|
98
|
+
# The astro-mimic: a node process that (a) runs a worker thread, (b) spawns a
|
|
99
|
+
# grandchild with inherited stdio, (c) holds the event loop. Those are the three
|
|
100
|
+
# things astro dev's process tree does that a bare `sleep` child does not — and
|
|
101
|
+
# a bare `sleep` child is exactly the harness that already failed to reproduce.
|
|
102
|
+
cat > "$SB/mimic.js" <<'MIMIC'
|
|
103
|
+
const { Worker } = require('worker_threads')
|
|
104
|
+
const { spawn } = require('child_process')
|
|
105
|
+
const secs = Number(process.argv[2] || 12)
|
|
106
|
+
const ms = secs * 1000
|
|
107
|
+
new Worker(`setTimeout(() => {}, ${ms})`, { eval: true })
|
|
108
|
+
spawn('sh', ['-c', `sleep ${secs}`], { stdio: 'inherit' })
|
|
109
|
+
console.log('mimic up')
|
|
110
|
+
setTimeout(() => process.exit(0), ms)
|
|
111
|
+
MIMIC
|
|
112
|
+
|
|
113
|
+
# The driver: worktree-preview.sh's cmd_up, reduced to its launch and its GREEN
|
|
114
|
+
# line. $1 = variant, $2 = state dir, $3 = hold seconds.
|
|
115
|
+
cat > "$SB/driver.sh" <<'DRIVER'
|
|
116
|
+
#!/usr/bin/env bash
|
|
117
|
+
set -uo pipefail
|
|
118
|
+
variant="$1"; state="$2"; hold="$3"
|
|
119
|
+
log="$state/mimic.log"; : > "$log"
|
|
120
|
+
mimic="$state/../mimic.js"
|
|
121
|
+
case "$variant" in
|
|
122
|
+
plain)
|
|
123
|
+
( cd "$state" && perl -e 'setsid; exec @ARGV' -- sh -c "exec node $mimic $hold" >>"$log" 2>&1 & echo $! > "$state/pid" ) ;;
|
|
124
|
+
stdin)
|
|
125
|
+
( cd "$state" && perl -e 'setsid; exec @ARGV' -- sh -c "exec node $mimic $hold" >>"$log" 2>&1 </dev/null & echo $! > "$state/pid" ) ;;
|
|
126
|
+
execfd)
|
|
127
|
+
( cd "$state" && exec >>"$log" 2>&1 </dev/null; perl -e 'setsid; exec @ARGV' -- sh -c "exec node $mimic $hold" & echo $! > "$state/pid" ) ;;
|
|
128
|
+
*) echo "driver: unknown variant $variant" >&2; exit 4 ;;
|
|
129
|
+
esac
|
|
130
|
+
perl -MTime::HiRes=time -e 'printf "%.3f\n", time' > "$state/driver_exit"
|
|
131
|
+
echo "GREEN mimic up (driver returning, exactly as cmd_up does)"
|
|
132
|
+
DRIVER
|
|
133
|
+
chmod +x "$SB/driver.sh"
|
|
134
|
+
|
|
135
|
+
# One variant, measured. $1 = name, $2 = driver variant, $3 = pipe|file
|
|
136
|
+
run_variant() {
|
|
137
|
+
# NOT one `local` line: bash expands every word of the builtin BEFORE any of
|
|
138
|
+
# the assignments take effect, so `st="$SB/$name"` reads an unset `name` and
|
|
139
|
+
# dies under `set -u`. Measured here 2026-09-06 — it produced four empty
|
|
140
|
+
# timings that the verdict then reported as a confident NOT REPRODUCED.
|
|
141
|
+
local name="$1" variant="$2" sink="$3" t0 t_eof t_drv
|
|
142
|
+
local st="$SB/$name"
|
|
143
|
+
mkdir -p "$st"
|
|
144
|
+
t0="$(now)"
|
|
145
|
+
if [ "$sink" = pipe ]; then
|
|
146
|
+
( bash "$SB/driver.sh" "$variant" "$st" "$HOLD" | cat > "$st/out.txt"
|
|
147
|
+
perl -MTime::HiRes=time -e 'printf "%.3f\n", time' > "$st/eof" ) &
|
|
148
|
+
else
|
|
149
|
+
( bash "$SB/driver.sh" "$variant" "$st" "$HOLD" > "$st/out.txt"
|
|
150
|
+
perl -MTime::HiRes=time -e 'printf "%.3f\n", time' > "$st/eof" ) &
|
|
151
|
+
fi
|
|
152
|
+
local job=$!
|
|
153
|
+
# While the reader may still be blocked, name the holder. A timing without a
|
|
154
|
+
# named process is a correlation, and the verdict refuses it.
|
|
155
|
+
if [ "$sink" = pipe ] && [ "$name" = V1 ]; then sleep 3; name_holder "$st"; fi
|
|
156
|
+
wait "$job" 2>/dev/null
|
|
157
|
+
t_eof="$(cat "$st/eof" 2>/dev/null)"; t_drv="$(cat "$st/driver_exit" 2>/dev/null)"
|
|
158
|
+
if [ -z "$t_eof" ] || [ -z "$t_drv" ]; then echo "unmeasured"; return; fi
|
|
159
|
+
el "$t_drv" "$t_eof"
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
# Does any process in the launched tree hold the SAME pipe the reader is blocked
|
|
163
|
+
# on? macOS lsof prints a pipe as `PIPE 0x<dev> ... ->0x<peer>`; the reader's fd
|
|
164
|
+
# 0 and a writer's fd share those addresses.
|
|
165
|
+
HOLDER="none"
|
|
166
|
+
name_holder() {
|
|
167
|
+
local st="$1" mpid rp toks t
|
|
168
|
+
mpid="$(cat "$st/pid" 2>/dev/null)"
|
|
169
|
+
command -v lsof >/dev/null 2>&1 || { HOLDER="lsof-missing"; return; }
|
|
170
|
+
# The reader is the `cat` whose fd 1 is out.txt; find its pipe tokens on fd 0.
|
|
171
|
+
rp="$(lsof -n -t -- "$st/out.txt" 2>/dev/null | head -1)"
|
|
172
|
+
[ -n "$rp" ] || { HOLDER="reader-not-found"; return; }
|
|
173
|
+
toks="$(lsof -n -p "$rp" 2>/dev/null | awk '$5 == "PIPE" {print $6; if ($NF ~ /^->0x/) print substr($NF,3)}' | sort -u)"
|
|
174
|
+
[ -n "$toks" ] || { HOLDER="reader-holds-no-pipe"; return; }
|
|
175
|
+
local cand="" line
|
|
176
|
+
# Every descendant of the launched pid, plus the pid itself.
|
|
177
|
+
local pids; pids="$(ps -A -o pid=,ppid=,comm= 2>/dev/null)"
|
|
178
|
+
local tree; tree="$(printf '%s\n' "$mpid"; printf '%s\n' "$pids" | awk -v m="$mpid" '$2 == m {print $1}')"
|
|
179
|
+
for t in $tree; do
|
|
180
|
+
[ -n "$t" ] || continue
|
|
181
|
+
line="$(lsof -n -p "$t" 2>/dev/null | awk '$5 == "PIPE" {print $6; if ($NF ~ /^->0x/) print substr($NF,3)}' | sort -u)"
|
|
182
|
+
[ -n "$line" ] || continue
|
|
183
|
+
local shared; shared="$(comm -12 <(printf '%s\n' "$toks") <(printf '%s\n' "$line") 2>/dev/null | head -1)"
|
|
184
|
+
if [ -n "$shared" ]; then
|
|
185
|
+
# Name the process AND the pipe, with the fd it holds it on. A pid alone
|
|
186
|
+
# is a coincidence; a pid whose fd 1 is the peer of the reader's fd 0 is
|
|
187
|
+
# the mechanism.
|
|
188
|
+
local fd; fd="$(lsof -n -p "$t" 2>/dev/null | awk -v s="$shared" '$5 == "PIPE" && ($6 == s || substr($NF,3) == s) {print $4; exit}')"
|
|
189
|
+
cand="$(ps -o pid=,comm= -p "$t" 2>/dev/null | tr -s ' ' | sed 's/^ *//;s/ *$//')/fd${fd:-?}/pipe${shared}"
|
|
190
|
+
break
|
|
191
|
+
fi
|
|
192
|
+
done
|
|
193
|
+
HOLDER="${cand:-no-shared-pipe-in-launch-tree}"
|
|
194
|
+
# run_variant is called in a command substitution, so this assignment lives in
|
|
195
|
+
# a subshell and would evaporate. The answer travels by file, or the verdict
|
|
196
|
+
# would silently read `none` and refuse a real reproduction.
|
|
197
|
+
printf '%s' "$HOLDER" > "$SB/holder"
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
self_test() {
|
|
201
|
+
local fails=0 rc
|
|
202
|
+
printf '== preview-fd-check --self-test\n'
|
|
203
|
+
# The harness must refuse to call a hang a reproduction without a named holder.
|
|
204
|
+
if ( HOLDER="no-shared-pipe-in-launch-tree"; verdict 30 0 30 0 >/dev/null 2>&1 ); then
|
|
205
|
+
printf ' FAIL %-52s\n' 'a hang with NO named holder read as REPRODUCED' >&2; fails=$((fails+1))
|
|
206
|
+
else
|
|
207
|
+
printf ' ok %-52s\n' 'no named holder => NOT REPRODUCED, whatever the timings'
|
|
208
|
+
fi
|
|
209
|
+
if ( HOLDER="1234 node"; verdict 0 0 0 0 >/dev/null 2>&1 ); then
|
|
210
|
+
printf ' FAIL %-52s\n' 'zero hold everywhere read as REPRODUCED' >&2; fails=$((fails+1))
|
|
211
|
+
else
|
|
212
|
+
printf ' ok %-52s\n' 'no hold => NOT REPRODUCED even with a holder'
|
|
213
|
+
fi
|
|
214
|
+
if ( HOLDER="1234 node"; verdict "$HOLD" 0 "$HOLD" 0 >/dev/null 2>&1 ); then
|
|
215
|
+
printf ' ok %-52s\n' 'all three conditions => REPRODUCED'
|
|
216
|
+
else
|
|
217
|
+
printf ' FAIL %-52s\n' 'the green half is unreachable' >&2; fails=$((fails+1))
|
|
218
|
+
fi
|
|
219
|
+
if ( HOLDER="1234 node"; verdict "$HOLD" "$HOLD" "$HOLD" "$HOLD" >/dev/null 2>&1 ); then
|
|
220
|
+
printf ' FAIL %-52s\n' 'V2 hanging too (so: not the pipe) read as REPRODUCED' >&2; fails=$((fails+1))
|
|
221
|
+
else
|
|
222
|
+
printf ' ok %-52s\n' 'V2 hanging too => NOT REPRODUCED (not a pipe effect)'
|
|
223
|
+
fi
|
|
224
|
+
# THE HOUSE BUG, in this harness's own shape: an empty timing must not read as
|
|
225
|
+
# a benign "not reproduced". Driven for real — this exact defect shipped in the
|
|
226
|
+
# first draft and was caught by running it.
|
|
227
|
+
( HOLDER="1234 node"; verdict "" 0 0 0 >/dev/null 2>&1 ); rc=$?
|
|
228
|
+
if [ "$rc" = 4 ]; then printf ' ok %-52s exit 4\n' 'an unmeasured variant is UNMEASURED, not a verdict'
|
|
229
|
+
else printf ' FAIL %-52s exit %s\n' 'an empty timing produced a verdict' "$rc" >&2; fails=$((fails+1)); fi
|
|
230
|
+
( HOLDER="1234 node"; verdict "$HOLD" unmeasured 0 0 >/dev/null 2>&1 ); rc=$?
|
|
231
|
+
if [ "$rc" = 4 ]; then printf ' ok %-52s exit 4\n' 'a word where a number belongs is UNMEASURED'
|
|
232
|
+
else printf ' FAIL %-52s exit %s\n' 'a non-numeric timing produced a verdict' "$rc" >&2; fails=$((fails+1)); fi
|
|
233
|
+
|
|
234
|
+
[ "$fails" -eq 0 ] && { printf 'preview-fd-check --self-test: PASS (6 checks)\n'; return 0; }
|
|
235
|
+
printf 'preview-fd-check --self-test: RED — %s failed\n' "$fails" >&2; return 1
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
# verdict <v1> <v2> <v3> <v4> — the three conditions, judged together.
|
|
239
|
+
verdict() {
|
|
240
|
+
local v1="$1" v2="$2" v3="$3" v4="$4" floor
|
|
241
|
+
floor="$(perl -e 'printf "%.1f", $ARGV[0] * 0.6' "$HOLD")"
|
|
242
|
+
printf 'PREVIEW-FD hold=%ss floor=%ss V1(pipe)=%ss V2(file)=%ss V3(stdin</dev/null)=%ss V4(exec-before-fork)=%ss holder=%s\n' \
|
|
243
|
+
"$HOLD" "$floor" "$v1" "$v2" "$v3" "$v4" "$HOLDER"
|
|
244
|
+
local n
|
|
245
|
+
for n in "$v1" "$v2" "$v3" "$v4"; do
|
|
246
|
+
case "$n" in
|
|
247
|
+
''|*[!0-9.]*)
|
|
248
|
+
printf 'PREVIEW-FD: UNMEASURED — a variant produced no timing (%s). This is NOT "not reproduced";\n' "$n"
|
|
249
|
+
printf ' the harness failed to measure and says so. worktree-preview.sh unchanged.\n'
|
|
250
|
+
return 4 ;;
|
|
251
|
+
esac
|
|
252
|
+
done
|
|
253
|
+
local ok1 ok2 ok3
|
|
254
|
+
ok1="$(perl -e 'print(($ARGV[0] =~ /^[0-9.]+$/ && $ARGV[0] >= $ARGV[1]) ? 1 : 0)' "$v1" "$floor")"
|
|
255
|
+
ok2="$(perl -e 'print(($ARGV[0] =~ /^[0-9.]+$/ && $ARGV[0] <= 2.0) ? 1 : 0)' "$v2")"
|
|
256
|
+
case "$HOLDER" in none|lsof-missing|reader-not-found|reader-holds-no-pipe|no-shared-pipe-in-launch-tree|'') ok3=0 ;; *) ok3=1 ;; esac
|
|
257
|
+
if [ "$ok1" = 1 ] && [ "$ok2" = 1 ] && [ "$ok3" = 1 ]; then
|
|
258
|
+
printf 'PREVIEW-FD: REPRODUCED (H1 candidate) — a named process in the launch tree holds the reader'"'"'s pipe.\n'
|
|
259
|
+
printf 'PREVIEW-FD: H2 IS NOT EXCLUDED — Mode A cannot separate them. --real is unrun.\n'
|
|
260
|
+
return 0
|
|
261
|
+
fi
|
|
262
|
+
printf 'PREVIEW-FD: NOT REPRODUCED — '
|
|
263
|
+
[ "$ok1" = 1 ] || printf 'V1 did not hold past the floor. '
|
|
264
|
+
[ "$ok2" = 1 ] || printf 'V2 held too (so the pipe is not the discriminator). '
|
|
265
|
+
[ "$ok3" = 1 ] || printf 'no named fd holder (%s). ' "$HOLDER"
|
|
266
|
+
printf 'worktree-preview.sh unchanged.\n'
|
|
267
|
+
return 3
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
case "$MODE" in
|
|
271
|
+
selftest) self_test; exit $? ;;
|
|
272
|
+
real)
|
|
273
|
+
[ -n "$SLUG" ] || { echo "preview-fd-check: --real needs a slug" >&2; exit 4; }
|
|
274
|
+
echo "preview-fd-check: --real is opt-in and UNRUN here — it costs a worktree, a port and ~2.3GB," >&2
|
|
275
|
+
echo " it takes no boot lock, and this box is memory-bound. Run it alone, never in a fleet:" >&2
|
|
276
|
+
echo " PREVIEW_FD_REAL_YES=1 bash .claude/scripts/preview-fd-check.sh --real $SLUG" >&2
|
|
277
|
+
[ "${PREVIEW_FD_REAL_YES:-0}" = 1 ] || exit 4
|
|
278
|
+
echo "preview-fd-check: --real not implemented in this cycle; Mode A is the deliverable." >&2
|
|
279
|
+
exit 4 ;;
|
|
280
|
+
esac
|
|
281
|
+
|
|
282
|
+
printf '== preview-fd-check --sandbox (mimic lifetime %ss)\n' "$HOLD"
|
|
283
|
+
V1="$(run_variant V1 plain pipe)"
|
|
284
|
+
HOLDER="$(cat "$SB/holder" 2>/dev/null)"; HOLDER="${HOLDER:-none}"
|
|
285
|
+
V2="$(run_variant V2 plain file)"
|
|
286
|
+
V3="$(run_variant V3 stdin pipe)"
|
|
287
|
+
V4="$(run_variant V4 execfd pipe)"
|
|
288
|
+
verdict "$V1" "$V2" "$V3" "$V4"
|
|
289
|
+
exit $?
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# redirect-lint.sh — `Response.redirect()` is banned under one.ie/web/src/pages/api/.
|
|
3
|
+
#
|
|
4
|
+
# WHY. `Response.redirect()` returns a Response whose headers are IMMUTABLE.
|
|
5
|
+
# The Astro Cloudflare adapter / middleware then writes `Server-Timing` (or the
|
|
6
|
+
# queued `vid` cookie) onto it and the worker throws "Can't modify immutable
|
|
7
|
+
# headers" — a 500 with Cloudflare error code 1101, on the success path only.
|
|
8
|
+
# Measured on production 2026-09-11: /api/composio/redirect and
|
|
9
|
+
# /api/composio/callback both 500.
|
|
10
|
+
#
|
|
11
|
+
# This repo has diagnosed and fixed the same mechanism three separate times
|
|
12
|
+
# before this lint existed:
|
|
13
|
+
# one.ie/web/src/pages/api/og/screenshot.png.ts:33 (the comment + the fix)
|
|
14
|
+
# one.ie/web/src/middleware.ts:746 (the comment + the fix)
|
|
15
|
+
# one.ie/web/src/lib/page-cache-middleware.ts:45 (swallows the throw)
|
|
16
|
+
# The lint is what stops a fourth time.
|
|
17
|
+
#
|
|
18
|
+
# THE FIX, every time:
|
|
19
|
+
# return new Response(null, { status: 302, headers: { Location: <absolute url> } })
|
|
20
|
+
#
|
|
21
|
+
# SCOPE. API routes only — those are the handlers whose response the adapter
|
|
22
|
+
# decorates. Comment lines are exempt: the three files above NAME the pattern in
|
|
23
|
+
# prose, and a lint that is red on a clean tree gets deleted within a week.
|
|
24
|
+
#
|
|
25
|
+
# USAGE
|
|
26
|
+
# bash .claude/scripts/redirect-lint.sh # lint the repo, exit 1 on any hit
|
|
27
|
+
# bash .claude/scripts/redirect-lint.sh --self-test # drive it RED, then GREEN, then
|
|
28
|
+
# # prove a comment stays GREEN
|
|
29
|
+
# EXITS
|
|
30
|
+
# 0 clean 1 violations found 2 usage / self-test failed
|
|
31
|
+
set -euo pipefail
|
|
32
|
+
|
|
33
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
34
|
+
API_DIR="one.ie/web/src/pages/api"
|
|
35
|
+
PATTERN='Response\.redirect('
|
|
36
|
+
|
|
37
|
+
# scan <dir> — every CODE occurrence as `file:line:content`, comments dropped.
|
|
38
|
+
# Empty output means clean. The grep output is CAPTURED first and matched from a
|
|
39
|
+
# here-string: under `set -o pipefail`, `<producer> | grep -q` returns 141 when
|
|
40
|
+
# it matches while the producer is still writing.
|
|
41
|
+
scan() {
|
|
42
|
+
local dir="$1" raw line content trimmed
|
|
43
|
+
raw="$(grep -rn "$PATTERN" "$dir" 2>/dev/null || true)"
|
|
44
|
+
[ -z "$raw" ] && return 0
|
|
45
|
+
while IFS= read -r line; do
|
|
46
|
+
[ -z "$line" ] && continue
|
|
47
|
+
content="${line#*:}" # strip `file:`
|
|
48
|
+
content="${content#*:}" # strip `line:`
|
|
49
|
+
trimmed="$(printf '%s' "$content" | sed 's/^[[:space:]]*//')"
|
|
50
|
+
case "$trimmed" in
|
|
51
|
+
'//'*|'*'*|'/*'*) continue ;; # a comment naming the pattern is not a call
|
|
52
|
+
esac
|
|
53
|
+
printf '%s\n' "$line"
|
|
54
|
+
done <<< "$raw"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# lint <dir> [label] — report and set the exit code. The whole main path, so the
|
|
58
|
+
# self-test drives the real thing rather than a re-implementation of it.
|
|
59
|
+
lint() {
|
|
60
|
+
local dir="$1" label="${2:-$dir}" hits count
|
|
61
|
+
if [ ! -d "$dir" ]; then
|
|
62
|
+
echo "[redirect-lint] RED — directory not found: $dir" >&2
|
|
63
|
+
return 2
|
|
64
|
+
fi
|
|
65
|
+
hits="$(scan "$dir")"
|
|
66
|
+
if [ -z "$hits" ]; then
|
|
67
|
+
echo "[redirect-lint] ok — no Response.redirect() under $label"
|
|
68
|
+
return 0
|
|
69
|
+
fi
|
|
70
|
+
count="$(printf '%s\n' "$hits" | wc -l | tr -d ' ')"
|
|
71
|
+
echo "[redirect-lint] RED — $count call site(s) of Response.redirect() under $label:" >&2
|
|
72
|
+
printf '%s\n' "$hits" | sed "s#^$ROOT/##" >&2
|
|
73
|
+
cat >&2 <<'HINT'
|
|
74
|
+
|
|
75
|
+
Response.redirect() headers are immutable; the adapter's Server-Timing write
|
|
76
|
+
then throws "Can't modify immutable headers" (500, cf error 1101). Use:
|
|
77
|
+
|
|
78
|
+
return new Response(null, { status: 302, headers: { Location: <absolute url> } })
|
|
79
|
+
HINT
|
|
80
|
+
return 1
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Global on purpose: the EXIT trap body is evaluated after self_test has
|
|
84
|
+
# returned, so a `local` sandbox is already out of scope by then — under
|
|
85
|
+
# `set -u` that made a fully passing self-test exit 1 on its own cleanup.
|
|
86
|
+
sandbox=""
|
|
87
|
+
|
|
88
|
+
self_test() {
|
|
89
|
+
local rc out fails=0
|
|
90
|
+
sandbox="$(mktemp -d "${TMPDIR:-/tmp}/redirect-lint-selftest.XXXXXX")"
|
|
91
|
+
trap 'rm -rf "${sandbox:-}"' EXIT
|
|
92
|
+
mkdir -p "$sandbox/api"
|
|
93
|
+
|
|
94
|
+
# A file that is already correct — present in all three phases, so a green
|
|
95
|
+
# phase proves the scan ran over real content rather than over nothing.
|
|
96
|
+
cat > "$sandbox/api/ok.ts" <<'TS'
|
|
97
|
+
export const GET = ({ url }) =>
|
|
98
|
+
new Response(null, { status: 302, headers: { Location: `${url.origin}/next` } })
|
|
99
|
+
TS
|
|
100
|
+
|
|
101
|
+
# 1 — a real call site must go RED.
|
|
102
|
+
cat > "$sandbox/api/bad.ts" <<'TS'
|
|
103
|
+
export const GET = ({ url }) => Response.redirect(`${url.origin}/next`, 302)
|
|
104
|
+
TS
|
|
105
|
+
set +e
|
|
106
|
+
out="$(lint "$sandbox" "sandbox" 2>&1)"; rc=$?
|
|
107
|
+
set -e
|
|
108
|
+
if [ "$rc" -eq 1 ]; then
|
|
109
|
+
echo " [1/3] planted call site → RED (exit 1) ✓"
|
|
110
|
+
else
|
|
111
|
+
echo " [1/3] planted call site → expected exit 1, got $rc" >&2
|
|
112
|
+
printf '%s\n' "$out" >&2
|
|
113
|
+
fails=$((fails + 1))
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# 2 — removing it must go GREEN.
|
|
117
|
+
rm -f "$sandbox/api/bad.ts"
|
|
118
|
+
set +e
|
|
119
|
+
out="$(lint "$sandbox" "sandbox" 2>&1)"; rc=$?
|
|
120
|
+
set -e
|
|
121
|
+
if [ "$rc" -eq 0 ]; then
|
|
122
|
+
echo " [2/3] violation removed → GREEN (exit 0) ✓"
|
|
123
|
+
else
|
|
124
|
+
echo " [2/3] violation removed → expected exit 0, got $rc" >&2
|
|
125
|
+
printf '%s\n' "$out" >&2
|
|
126
|
+
fails=$((fails + 1))
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
# 3 — a COMMENT naming the pattern must stay GREEN. This is the exclusion the
|
|
130
|
+
# three prior fixes depend on; untested, someone "simplifies" it back into a
|
|
131
|
+
# false positive and the lint starts firing on its own documentation.
|
|
132
|
+
cat > "$sandbox/api/documented.ts" <<'TS'
|
|
133
|
+
export const GET = ({ url }) => {
|
|
134
|
+
// Response.redirect() returns an immutable Response — the adapter's
|
|
135
|
+
// Server-Timing header write then throws "Can't modify immutable headers".
|
|
136
|
+
/* Response.redirect( in a block comment too */
|
|
137
|
+
return new Response(null, { status: 302, headers: { Location: `${url.origin}/next` } })
|
|
138
|
+
}
|
|
139
|
+
TS
|
|
140
|
+
set +e
|
|
141
|
+
out="$(lint "$sandbox" "sandbox" 2>&1)"; rc=$?
|
|
142
|
+
set -e
|
|
143
|
+
if [ "$rc" -eq 0 ]; then
|
|
144
|
+
echo " [3/3] comment naming the pattern → GREEN (exit 0) ✓"
|
|
145
|
+
else
|
|
146
|
+
echo " [3/3] comment naming the pattern → expected exit 0, got $rc" >&2
|
|
147
|
+
printf '%s\n' "$out" >&2
|
|
148
|
+
fails=$((fails + 1))
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
if [ "$fails" -ne 0 ]; then
|
|
152
|
+
echo "[redirect-lint] --self-test FAILED ($fails/3)" >&2
|
|
153
|
+
return 2
|
|
154
|
+
fi
|
|
155
|
+
echo "[redirect-lint] --self-test ok (3/3)"
|
|
156
|
+
return 0
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
case "${1:-}" in
|
|
160
|
+
--self-test) self_test ;;
|
|
161
|
+
--scan-dir) lint "${2:?--scan-dir needs a directory}" ;;
|
|
162
|
+
-h|--help)
|
|
163
|
+
sed -n '1,32p' "${BASH_SOURCE[0]}"
|
|
164
|
+
exit 2 ;;
|
|
165
|
+
'') lint "$ROOT/$API_DIR" "$API_DIR" ;;
|
|
166
|
+
*)
|
|
167
|
+
echo "usage: redirect-lint.sh [--self-test|--scan-dir <dir>]" >&2
|
|
168
|
+
exit 2 ;;
|
|
169
|
+
esac
|
package/scripts/release.sh
CHANGED
|
@@ -92,9 +92,14 @@ _link_deps() {
|
|
|
92
92
|
# The receipt keys for the tree standing in $1. Prints one 64-hex key per lane.
|
|
93
93
|
# TEST_CACHE_KEY_ONLY=1 makes every test-cached.sh in the fan-out print its key
|
|
94
94
|
# and exit 0 without running vitest, so this costs ~1s and cannot start a suite.
|
|
95
|
+
#
|
|
96
|
+
# Each line is "<lane> <key>". The lane is read off test-lanes.sh's own
|
|
97
|
+
# `───── <lane> lane (rc=N) ─────` header that precedes the key, so a new lane
|
|
98
|
+
# names itself; a key with no header (the single-run fallback) is lane `suite`.
|
|
95
99
|
_receipt_keys() {
|
|
96
100
|
( cd "$1" && TEST_CACHE_KEY_ONLY=1 bash .claude/scripts/test-full.sh 2>/dev/null ) \
|
|
97
|
-
|
|
|
101
|
+
| awk '/ lane \(rc=/ { for (i = 1; i <= NF; i++) if ($i == "lane") lane = $(i-1); next }
|
|
102
|
+
/^[0-9a-f]{64}$/ { print (lane == "" ? "suite" : lane), $0 }'
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
# The stamp for a key, if one exists. test-cached.sh mangles the whole stamp path
|
|
@@ -165,16 +170,27 @@ cmd_promote() {
|
|
|
165
170
|
bad "no receipt key could be computed — test-full.sh printed no lane key"
|
|
166
171
|
return 9
|
|
167
172
|
fi
|
|
168
|
-
|
|
169
|
-
|
|
173
|
+
# THE TYPEDB LANE DOES NOT GATE PRODUCTION (operator ruling 2026-09-13). The
|
|
174
|
+
# request path reads the edge snapshot, never TypeDB (CLAUDE.md § The brain and
|
|
175
|
+
# the edge), and the lane's reds were the shared cluster timing out, not code:
|
|
176
|
+
# three promotes in a row held a green pool lane behind `upstream 500: aborted
|
|
177
|
+
# due to timeout`. deploy.sh already runs it non-blocking
|
|
178
|
+
# (TYPEDB_LANE_NONBLOCKING=1); promote now weighs it the same way, so the two
|
|
179
|
+
# doors can no longer disagree. The lane is still READ and printed — a missing
|
|
180
|
+
# stamp is a named warning, never silence. RELEASE_TYPEDB_BLOCKING=1 restores
|
|
181
|
+
# the hard gate.
|
|
182
|
+
local n_keys=0 missing=0 stamps=() lane k
|
|
183
|
+
while read -r lane k; do
|
|
170
184
|
[ -n "$k" ] || continue
|
|
171
185
|
n_keys=$((n_keys+1))
|
|
172
186
|
local s
|
|
173
187
|
if s="$(_stamp_for "$k")"; then
|
|
174
188
|
stamps+=("$s")
|
|
175
|
-
ok "receipt ${k:0:12} — full suite PASSED $(cat "$s" 2>/dev/null)"
|
|
189
|
+
ok "receipt ${k:0:12} ($lane lane) — full suite PASSED $(cat "$s" 2>/dev/null)"
|
|
190
|
+
elif [ "$lane" = "typedb" ] && [ "${RELEASE_TYPEDB_BLOCKING:-0}" != "1" ]; then
|
|
191
|
+
say " warn receipt ${k:0:12} (typedb lane) — no green stamp; non-blocking for production"
|
|
176
192
|
else
|
|
177
|
-
bad "receipt ${k:0:12} — NO green full-suite stamp for this tree"
|
|
193
|
+
bad "receipt ${k:0:12} ($lane lane) — NO green full-suite stamp for this tree"
|
|
178
194
|
missing=$((missing+1))
|
|
179
195
|
fi
|
|
180
196
|
done <<< "$keys"
|
|
@@ -276,8 +292,26 @@ _self_test() {
|
|
|
276
292
|
# 1. a tree with no receipt is REFUSED (3)
|
|
277
293
|
_run "$base"; _assert 3 $? "no receipt refuses"
|
|
278
294
|
|
|
295
|
+
# 2a. the typedb lane does not gate production: stamp every OTHER lane and the
|
|
296
|
+
# promote succeeds; RELEASE_TYPEDB_BLOCKING=1 restores the refusal. Red half
|
|
297
|
+
# first — a non-typedb lane left unstamped must still refuse.
|
|
298
|
+
local lane k
|
|
299
|
+
while read -r lane k; do
|
|
300
|
+
[ -n "$k" ] && [ "$lane" = "typedb" ] && date -u +%Y-%m-%dT%H:%M:%SZ > "$sbcache/selftest-$k.pass"
|
|
301
|
+
done < <(_receipt_keys "$sbwt")
|
|
302
|
+
_run "$base"; _assert 3 $? "a green typedb lane cannot carry an unstamped pool lane"
|
|
303
|
+
rm -f "$sbcache"/selftest-*.pass
|
|
304
|
+
while read -r lane k; do
|
|
305
|
+
[ -n "$k" ] && [ "$lane" != "typedb" ] && date -u +%Y-%m-%dT%H:%M:%SZ > "$sbcache/selftest-$k.pass"
|
|
306
|
+
done < <(_receipt_keys "$sbwt")
|
|
307
|
+
if _receipt_keys "$sbwt" | grep -q '^typedb '; then
|
|
308
|
+
RELEASE_TYPEDB_BLOCKING=1 _run "$base"; _assert 3 $? "RELEASE_TYPEDB_BLOCKING=1 refuses an unstamped typedb lane"
|
|
309
|
+
_run "$base"; _assert 0 $? "an unstamped typedb lane does not gate production"
|
|
310
|
+
else
|
|
311
|
+
echo " FAIL no typedb lane key found — the non-blocking branch went unproven"; fails=$((fails+1))
|
|
312
|
+
fi
|
|
313
|
+
|
|
279
314
|
# 2. plant a PASS stamp for every lane key of that tree — promotion succeeds
|
|
280
|
-
local k
|
|
281
315
|
while IFS= read -r k; do
|
|
282
316
|
[ -n "$k" ] && date -u +%Y-%m-%dT%H:%M:%SZ > "$sbcache/selftest-$k.pass"
|
|
283
317
|
done < <(TEST_CACHE_KEY_ONLY=1 bash -c "cd '$sbwt' && bash .claude/scripts/test-full.sh" 2>/dev/null \
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# resume-lost-sessions.sh — reopen the Claude Code sessions killed by the
|
|
3
|
+
# 2026-09-16 12:14 reboot, one Ghostty tab each.
|
|
4
|
+
#
|
|
5
|
+
# manifest: monorepo-only
|
|
6
|
+
#
|
|
7
|
+
# WHY THIS EXISTS. The box rebooted at 12:14 and took 8 live sessions with it.
|
|
8
|
+
# Every session's COMMITTED work survived — all of it is in production at
|
|
9
|
+
# c716295ac (verified by probe: /buttons 200, /ambient 404). What died was the
|
|
10
|
+
# conversation: the half-finished next step each one was holding in context.
|
|
11
|
+
# `--resume <id>` restores that context; the transcripts are on disk under
|
|
12
|
+
# ~/.claude/projects/-Users-toc-Server-one-ie/.
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# bash .claude/scripts/resume-lost-sessions.sh --list # names + status, no side effects
|
|
16
|
+
# bash .claude/scripts/resume-lost-sessions.sh <key>... # open those tabs
|
|
17
|
+
# bash .claude/scripts/resume-lost-sessions.sh --unfinished
|
|
18
|
+
set -uo pipefail
|
|
19
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
20
|
+
|
|
21
|
+
# key|session-id|status|what it was doing
|
|
22
|
+
ROWS=(
|
|
23
|
+
"media|baa8d065-7e0a-44a5-be4a-0b65ea39252f|MID-CYCLE|@chairman team: pages/media — view, upload, generate media and insert into pages. Was at /close when it died."
|
|
24
|
+
"blocks|3aa1bc43-0029-4ddd-bd4e-bc76695dd74c|LANDED|One block-insert component shared by page editor and documents. Shipped ca89b9c58 + 5b74e7f4d. Was at /close."
|
|
25
|
+
"components|a82ef0c3-3863-40ed-84b8-49502e42a42e|MID-CYCLE|Parallel agents enhancing the hundreds of page-editor components. 9 turns in, feat/components merged — more was planned."
|
|
26
|
+
"pages-links|bd004874-cd1e-4298-bd8d-a733721c8af0|OPEN Q|Feature pages (pages/analytics etc.) — wanted links to them, 'on prod'. A question, no commit."
|
|
27
|
+
"models|95bead26-d470-4c4a-84d7-41dabbf54144|LANDED|Rank models by intelligence and speed in the + picker. Shipped: src/lib/chat/model-rank.ts."
|
|
28
|
+
"ticker|d8b1424b-3bfe-4fa5-92bc-fa4fc197ed4e|LANDED|Real streamed 'thinking' text instead of pretend. Shipped a99651b48. Last words: 'ship it all'."
|
|
29
|
+
"chat-url|79bc3b97-dab2-4f3d-a4c1-81b163917e4f|LANDED|Can chat change the page URL the user is on. Shipped d6a41f2f5 + text/chat-navigation-docs.md."
|
|
30
|
+
"health|4c2ff180-a2d9-4b37-9cef-9ce9740c5fc9|ONE-SHOT|'health' — the doctor pass. Nothing to resume unless you want another."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_row() { local k=$1; for r in "${ROWS[@]}"; do [ "${r%%|*}" = "$k" ] && { echo "$r"; return 0; }; done; return 1; }
|
|
34
|
+
|
|
35
|
+
if [ $# -eq 0 ] || [ "${1:-}" = "--list" ]; then
|
|
36
|
+
printf '\n %-13s %-10s %s\n' KEY STATUS "WHAT IT WAS DOING"
|
|
37
|
+
for r in "${ROWS[@]}"; do
|
|
38
|
+
IFS='|' read -r k id st what <<<"$r"
|
|
39
|
+
printf ' %-13s %-10s %s\n' "$k" "$st" "${what:0:88}"
|
|
40
|
+
done
|
|
41
|
+
printf '\n open one: bash %s <key>\n' "${BASH_SOURCE[0]#"$ROOT/"}"
|
|
42
|
+
printf ' unfinished only: bash %s --unfinished\n\n' "${BASH_SOURCE[0]#"$ROOT/"}"
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
KEYS=("$@")
|
|
47
|
+
if [ "${1:-}" = "--unfinished" ]; then
|
|
48
|
+
KEYS=()
|
|
49
|
+
for r in "${ROWS[@]}"; do
|
|
50
|
+
IFS='|' read -r k id st what <<<"$r"
|
|
51
|
+
case "$st" in MID-CYCLE|"OPEN Q") KEYS+=("$k");; esac
|
|
52
|
+
done
|
|
53
|
+
echo " unfinished: ${KEYS[*]}"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
for k in "${KEYS[@]}"; do
|
|
57
|
+
r="$(_row "$k")" || { echo " ✗ unknown key: $k (try --list)" >&2; continue; }
|
|
58
|
+
IFS='|' read -r _ id st what <<<"$r"
|
|
59
|
+
echo " → $k ($st) $id"
|
|
60
|
+
osascript <<OSA >/dev/null 2>&1 || echo " (could not open a tab — run by hand: cd $ROOT && claude --resume $id)"
|
|
61
|
+
tell application "Ghostty" to activate
|
|
62
|
+
tell application "System Events" to keystroke "t" using command down
|
|
63
|
+
delay 0.7
|
|
64
|
+
tell application "System Events" to keystroke "cd $ROOT && claude --resume $id"
|
|
65
|
+
tell application "System Events" to key code 36
|
|
66
|
+
OSA
|
|
67
|
+
sleep 1
|
|
68
|
+
done
|