@oneie/claude 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/abm-strategist.md +89 -0
- package/agents/ads-meta.md +97 -0
- package/agents/analyst.md +173 -0
- package/agents/architect.md +280 -0
- package/agents/brand-guardian.md +88 -0
- package/agents/brand-strategist.md +92 -0
- package/agents/campaign-content.md +90 -0
- package/agents/campaign-email.md +88 -0
- package/agents/campaign-sms.md +87 -0
- package/agents/campaign-social.md +87 -0
- package/agents/cco.md +99 -0
- package/agents/ceo.md +106 -0
- package/agents/chairman.md +120 -0
- package/agents/cmo.md +101 -0
- package/agents/community-greeter.md +89 -0
- package/agents/community-moderator.md +92 -0
- package/agents/compliance.md +92 -0
- package/agents/copywriter.md +100 -0
- package/agents/creative-strategist.md +95 -0
- package/agents/cro.md +98 -0
- package/agents/cto.md +267 -0
- package/agents/customer-interviewer.md +93 -0
- package/agents/customer-researcher.md +89 -0
- package/agents/customer-success-manager.md +90 -0
- package/agents/customer-trainer.md +90 -0
- package/agents/cxo.md +100 -0
- package/agents/demand-creator.md +89 -0
- package/agents/demo-mover.md +83 -0
- package/agents/demo-specialist.md +89 -0
- package/agents/demo-thai-family-law.md +87 -0
- package/agents/designer.md +112 -0
- package/agents/discovery-caller.md +98 -0
- package/agents/doctor.md +139 -0
- package/agents/educate-coach.md +100 -0
- package/agents/elevate-tutor.md +83 -0
- package/agents/email-lifecycle-marketer.md +91 -0
- package/agents/engage-specialist.md +99 -0
- package/agents/events-coordinator.md +90 -0
- package/agents/foundation-builder.md +94 -0
- package/agents/funnel-architect.md +89 -0
- package/agents/gift-creator.md +100 -0
- package/agents/google-ads.md +103 -0
- package/agents/guide.md +292 -0
- package/agents/helpdesk-dispatcher.md +88 -0
- package/agents/hook-specialist.md +91 -0
- package/agents/identify-optimizer.md +101 -0
- package/agents/implementer.md +305 -0
- package/agents/incident-commander.md +120 -0
- package/agents/insights-lead.md +93 -0
- package/agents/journey-runner.md +113 -0
- package/agents/linkedin-ads.md +123 -0
- package/agents/live-sales-chat.md +90 -0
- package/agents/market-researcher.md +88 -0
- package/agents/media-buyer.md +96 -0
- package/agents/movers-customer-researcher.md +95 -0
- package/agents/movers-foundation-builder.md +96 -0
- package/agents/movers-market-researcher.md +97 -0
- package/agents/movers-pricing-strategist.md +171 -0
- package/agents/nurture-architect.md +99 -0
- package/agents/offer-architect.md +92 -0
- package/agents/onboarder.md +222 -0
- package/agents/onboarding-specialist.md +92 -0
- package/agents/operations-dashboard.md +98 -0
- package/agents/perf-engineer.md +348 -0
- package/agents/playbook-writer.md +71 -0
- package/agents/plg-strategist.md +91 -0
- package/agents/positioning-architect.md +88 -0
- package/agents/press-officer.md +89 -0
- package/agents/pricing-strategist.md +94 -0
- package/agents/privacy-officer.md +92 -0
- package/agents/referral-manager.md +91 -0
- package/agents/refine-analyst.md +102 -0
- package/agents/release-manager.md +261 -0
- package/agents/renewals-upsell-rep.md +90 -0
- package/agents/review-engineer.md +327 -0
- package/agents/rewards-steward.md +82 -0
- package/agents/sales-call-coach.md +94 -0
- package/agents/sales-closer.md +98 -0
- package/agents/security-auditor.md +313 -0
- package/agents/sell-closer.md +101 -0
- package/agents/share-amplifier.md +99 -0
- package/agents/social-media-manager.md +92 -0
- package/agents/storyteller.md +250 -0
- package/agents/strategist.md +83 -0
- package/agents/strategy-aligner.md +94 -0
- package/agents/support-agent.md +90 -0
- package/agents/tagger.md +245 -0
- package/agents/tech-writer.md +216 -0
- package/agents/test-engineer.md +243 -0
- package/agents/tiktok-ads.md +105 -0
- package/agents/tracking-engineer.md +92 -0
- package/agents/upsell-strategist.md +101 -0
- package/agents/voice.md +85 -0
- package/agents/w4-tools.md +153 -0
- package/agents/w4-verify.md +20 -138
- package/agents/workflow-optimiser.md +84 -0
- package/commands/close.md +814 -160
- package/commands/create.md +2 -2
- package/commands/deploy.md +554 -175
- package/commands/do.md +119 -109
- package/commands/fast.md +67 -0
- package/commands/improve.md +2 -2
- package/commands/one.md +418 -0
- package/commands/see.md +1 -1
- package/commands/sync.md +1 -1
- package/commands/tasks.md +222 -0
- package/commands/vespio.md +110 -0
- package/commands/vespio.remote.md +227 -0
- package/hooks/hooks.json +25 -79
- package/hooks/lib/governor-escape-match.sh +88 -0
- package/hooks/lib/hook.sh +4 -13
- package/hooks/lib/signal.sh +9 -2
- package/hooks/scripts/branch-pin.sh +6 -0
- package/hooks/scripts/config-protect.sh +6 -0
- package/hooks/scripts/do-outcome-gate.sh +19 -2
- package/hooks/scripts/git-add-guard.sh +43 -2
- package/hooks/scripts/governor-escape.sh +96 -0
- package/hooks/scripts/load-guard.sh +127 -0
- package/hooks/scripts/post-edit-check.sh +36 -29
- package/hooks/scripts/session-start.sh +34 -80
- package/hooks/scripts/task-complete-verify.sh +46 -40
- package/package.json +1 -1
- package/rules/documentation.md +9 -0
- package/scripts/ad-copy-lint.sh +656 -0
- package/scripts/agentverse-audit.sh +256 -0
- package/scripts/asi-walk.sh +435 -0
- package/scripts/astro-build-cached.sh +143 -0
- package/scripts/blocks-manifest-cached.sh +100 -0
- package/scripts/blocks-manifest.mjs +497 -0
- package/scripts/blocks-ratchet.sh +116 -0
- package/scripts/blocks-render-probe.mjs +529 -0
- package/scripts/blocks-usage.mjs +334 -0
- package/scripts/chat-context-check.sh +89 -0
- package/scripts/close-metrics.sh +558 -0
- package/scripts/close-owner.sh +268 -0
- package/scripts/db-sync-lock-check.sh +116 -0
- package/scripts/deploy-dev.sh +35 -0
- package/scripts/deploy-emit.sh +298 -0
- package/scripts/deploy-gate-check.sh +128 -0
- package/scripts/deploy-ready.sh +78 -0
- package/scripts/deploy-record.sh +605 -0
- package/scripts/deploy.sh +1273 -0
- package/scripts/do-auto.sh +598 -48
- package/scripts/do-brief.sh +113 -0
- package/scripts/do-close.sh +1137 -0
- package/scripts/do-commit.sh +75 -0
- package/scripts/do-consumer-sweep.sh +18 -1
- package/scripts/do-cycle-shape-check.sh +160 -0
- package/scripts/do-decide.sh +476 -0
- package/scripts/do-derive-check.sh +436 -0
- package/scripts/do-fleet.sh +106 -28
- package/scripts/do-folder.sh +10 -1
- package/scripts/do-next.sh +106 -0
- package/scripts/do-orchestrate.sh +17 -5
- package/scripts/do-plan-json.mjs +201 -0
- package/scripts/do-plan-json.sh +8 -0
- package/scripts/do-preflight.sh +117 -0
- package/scripts/do-project.sh +157 -0
- package/scripts/do-prove-selftest.sh +108 -0
- package/scripts/do-prove.sh +295 -23
- package/scripts/do-rank.py +31 -1
- package/scripts/do-recon-cache.sh +7 -1
- package/scripts/do-recon-pack.sh +196 -0
- package/scripts/do-reconcile.sh +121 -4
- package/scripts/do-signal.sh +280 -23
- package/scripts/do-smoke.sh +18 -1
- package/scripts/do-test-gate.sh +80 -0
- package/scripts/do-tick.sh +102 -0
- package/scripts/do-tier.sh +6 -0
- package/scripts/do-triage.sh +182 -0
- package/scripts/do-ui-gate.sh +1 -1
- package/scripts/do-w4-gates.sh +451 -0
- package/scripts/do-walk.sh +12 -1
- package/scripts/env-sync.sh +173 -0
- package/scripts/factory-brief-check.sh +330 -0
- package/scripts/factory-check.sh +68 -14
- package/scripts/factory-close-check.sh +257 -0
- package/scripts/factory-emit.sh +211 -0
- package/scripts/factory-executor-check.mjs +353 -0
- package/scripts/factory-peak.sh +301 -0
- package/scripts/factory-repo.sh +118 -3
- package/scripts/factory-review-check.mjs +61 -0
- package/scripts/factory-ship.sh +61 -0
- package/scripts/factory-tasks-check.sh +18 -1
- package/scripts/factory-turn.sh +326 -0
- package/scripts/factory-walk.sh +396 -0
- package/scripts/factory-width.sh +57 -0
- package/scripts/fade-toxic.sh +4 -3
- package/scripts/fixtures/factory-brief-real.md +44 -0
- package/scripts/fixtures/triage-dupe.md +5 -0
- package/scripts/fleet-manifest.mjs +108 -0
- package/scripts/fleet-status.sh +110 -0
- package/scripts/full-suite-paths-check.sh +144 -0
- package/scripts/gate-reaper-check.sh +98 -0
- package/scripts/gate-reaper.sh +125 -0
- package/scripts/gate-run.sh +73 -0
- package/scripts/gc-content-check.sh +140 -0
- package/scripts/gen-dev-config.py +20 -0
- package/scripts/govern-bound-check.sh +60 -0
- package/scripts/govern-claims-check.sh +233 -0
- package/scripts/govern-mem-check.sh +290 -0
- package/scripts/governor-doors-check.sh +362 -0
- package/scripts/governor-escape-check.sh +171 -0
- package/scripts/health.sh +413 -0
- package/scripts/id-inventory.mjs +418 -0
- package/scripts/land.sh +551 -0
- package/scripts/lib/gc-finished.sh +77 -0
- package/scripts/lib/govern.sh +361 -0
- package/scripts/lib/govern.ts +756 -0
- package/scripts/lighthouse-run.sh +187 -0
- package/scripts/livekit-live-check.sh +61 -0
- package/scripts/livekit-ratchet.sh +46 -0
- package/scripts/load-guard-check.sh +49 -0
- package/scripts/machine-check.sh +102 -0
- package/scripts/machine-watch.sh +177 -0
- package/scripts/one-agents.mjs +415 -0
- package/scripts/one-resume.sh +78 -0
- package/scripts/orphan-baseline.json +182 -0
- package/scripts/orphan-modules.mjs +179 -0
- package/scripts/pr-body.sh +335 -0
- package/scripts/preview-fd-check.sh +289 -0
- package/scripts/promise-manifest.mjs +24 -1
- package/scripts/release.sh +322 -0
- package/scripts/roles-check.sh +946 -0
- package/scripts/sdk-build-cached.sh +64 -0
- package/scripts/signal-watch.sh +241 -0
- package/scripts/skills-publish.sh +94 -0
- package/scripts/speed-cache-check.sh +173 -0
- package/scripts/speed-check.mjs +907 -0
- package/scripts/speed-parity-check.sh +648 -0
- package/scripts/speed-waterfall-check.sh +355 -0
- package/scripts/substrate-env-parity.mjs +156 -0
- package/scripts/tasks-claim-race.mjs +108 -0
- package/scripts/tasks-loop.sh +185 -0
- package/scripts/test-cached.sh +255 -0
- package/scripts/test-full.sh +87 -0
- package/scripts/test-honesty.mjs +137 -0
- package/scripts/test-lanes.sh +166 -0
- package/scripts/test-speed.sh +94 -0
- package/scripts/triage-shape-check.sh +149 -0
- package/scripts/tsc-cached.sh +179 -0
- package/scripts/typedb-flake-check.sh +197 -0
- package/scripts/urls-lint.sh +15 -0
- package/scripts/verify-fast.sh +445 -0
- package/scripts/vespio-sync.sh +149 -0
- package/scripts/wf-check.mjs +104 -1
- package/scripts/worktree-preview.sh +879 -0
- package/scripts/worktree-up.sh +208 -0
- package/skills/livekit-agents/SKILL.md +285 -0
- package/skills/livekit-agents/references/freshness-rules.md +168 -0
- package/skills/shadcn/SKILL.md +1 -1
- package/skills/signal/SKILL.md +0 -1
- package/skills/voice/SKILL.md +94 -6
- package/skills/voice/corpus-check.sh +87 -0
- package/hooks/scripts/compact-hint.sh +0 -35
- package/hooks/scripts/gate-guard.sh +0 -83
- package/hooks/scripts/read-tracker.sh +0 -26
- package/hooks/scripts/session-end-verify.sh +0 -51
- package/hooks/scripts/stop-reflect.sh +0 -140
- package/hooks/scripts/sync-priority-todo.sh +0 -57
- package/hooks/scripts/sync-todo-docs.sh +0 -46
- package/hooks/scripts/tool-signal.sh +0 -48
- package/scripts/do-tasks-bridge.py +0 -366
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tasks-loop.sh — the task board as a continuously-running loop.
|
|
3
|
+
#
|
|
4
|
+
# Each pass does three things, in this order:
|
|
5
|
+
# 0. REAP sweep stranded `picked` leases back to `open`. This is the half
|
|
6
|
+
# atomicity does NOT fix: a lease whose owner died is invisible to
|
|
7
|
+
# the claimable view and blocks every dependent via ready-tasks.
|
|
8
|
+
# It runs FIRST so this pass can claim what it just freed.
|
|
9
|
+
# 1. ACCEPTS re-run every seed row's `accept:` check. Green ⇒ the task is
|
|
10
|
+
# done in the tree; it is not filed (and is reported as settled).
|
|
11
|
+
# 2. SEED file every still-RED seed row that is not on the board yet,
|
|
12
|
+
# tagged with its BARE-WORD stake.
|
|
13
|
+
# 3. CLAIM lease up to --claims tasks this pass.
|
|
14
|
+
#
|
|
15
|
+
# SAFE-BY-DEFAULT: it prints what it would do and writes NOTHING unless --live.
|
|
16
|
+
# The board lives in PRODUCTION TypeDB (there is no separate task store — dev
|
|
17
|
+
# and prod share it), so a dry run is the honest default.
|
|
18
|
+
#
|
|
19
|
+
# bash .claude/scripts/tasks-loop.sh --accepts # just score the checks
|
|
20
|
+
# bash .claude/scripts/tasks-loop.sh --once # one pass, DRY
|
|
21
|
+
# bash .claude/scripts/tasks-loop.sh --once --live
|
|
22
|
+
# bash .claude/scripts/tasks-loop.sh --live # continuous
|
|
23
|
+
# bash .claude/scripts/tasks-loop.sh --status # read liveness, don't guess
|
|
24
|
+
# bash .claude/scripts/tasks-loop.sh --stop
|
|
25
|
+
#
|
|
26
|
+
# Liveness is a PUBLISHED FILE, never `ps | grep`. --status reads it.
|
|
27
|
+
#
|
|
28
|
+
# ── why this loop is safe to run beside other fleets ────────────────────────
|
|
29
|
+
# `tasks:claim` is ATOMIC as of 2026-08-26: one guarded TypeDB pipeline, one
|
|
30
|
+
# commit, and the loser of a race is refused at commit time with
|
|
31
|
+
# "[STC2] Commit ... failed with isolation conflict: Transaction uses a lock
|
|
32
|
+
# held by a concurrent commit."
|
|
33
|
+
# Proven both ways by `node .claude/scripts/tasks-claim-race.mjs` (which also
|
|
34
|
+
# demonstrates its own red half). So a second claimer here cannot double-lease.
|
|
35
|
+
# This loop is therefore NOT single-claimer-by-construction; it is safe because
|
|
36
|
+
# the seam underneath it was fixed. If that harness ever stops passing, drop
|
|
37
|
+
# CLAIMS_PER_PASS to 0 until it does again.
|
|
38
|
+
set -uo pipefail
|
|
39
|
+
|
|
40
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
41
|
+
SEED="${TASKS_SEED:-$ROOT/.claude/tasks/seed.tsv}"
|
|
42
|
+
STATE_DIR="${TMPDIR:-/tmp}/one-tasks-loop"
|
|
43
|
+
LIVE="$STATE_DIR/live.json"
|
|
44
|
+
LOG="$STATE_DIR/loop.log"
|
|
45
|
+
LOCK="$STATE_DIR/lock"
|
|
46
|
+
STOP="$STATE_DIR/stop"
|
|
47
|
+
FILED="$STATE_DIR/filed" # slugs already filed by this loop
|
|
48
|
+
INTERVAL="${TASKS_LOOP_INTERVAL:-300}"
|
|
49
|
+
CLAIMS_PER_PASS="${TASKS_LOOP_CLAIMS:-1}"
|
|
50
|
+
WS="${CC_TASKS_WORKSPACE:-one}"
|
|
51
|
+
DRY=true
|
|
52
|
+
mkdir -p "$STATE_DIR"; touch "$FILED"
|
|
53
|
+
|
|
54
|
+
now() { date -u +%Y-%m-%dT%H:%M:%SZ; }
|
|
55
|
+
log() { printf '[%s] %s\n' "$(now)" "$*" | tee -a "$LOG"; }
|
|
56
|
+
|
|
57
|
+
publish() { # publish(state, detail, red, green, filed, claimed)
|
|
58
|
+
cat > "$LIVE" <<JSON
|
|
59
|
+
{"state":"$1","pid":$$,"detail":"$2","red":$3,"green":$4,"filed":$5,"claimed":$6,"dry":$DRY,"workspace":"$WS","at":"$(now)","interval":$INTERVAL,"log":"$LOG"}
|
|
60
|
+
JSON
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# rows() — the seed, comments and blanks stripped.
|
|
64
|
+
rows() { grep -v '^#' "$SEED" | grep -v '^[[:space:]]*$'; }
|
|
65
|
+
|
|
66
|
+
# score_accepts — run every accept check. Prints "RED <slug>" / "GREEN <slug>".
|
|
67
|
+
# An accept that exits 0 means the work is already in the tree.
|
|
68
|
+
score_accepts() {
|
|
69
|
+
local red=0 green=0
|
|
70
|
+
while IFS=$'\t' read -r slug title tags accept; do
|
|
71
|
+
[ -z "${slug:-}" ] && continue
|
|
72
|
+
if ( cd "$ROOT" && eval "$accept" ) >/dev/null 2>&1; then
|
|
73
|
+
echo "GREEN $slug"; green=$((green+1))
|
|
74
|
+
else
|
|
75
|
+
echo "RED $slug"; red=$((red+1))
|
|
76
|
+
fi
|
|
77
|
+
done < <(rows)
|
|
78
|
+
echo "# red=$red green=$green"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case "${1:-}" in
|
|
82
|
+
--status)
|
|
83
|
+
if [ ! -f "$LIVE" ]; then echo "no loop has ever published liveness here ($LIVE)"; exit 1; fi
|
|
84
|
+
cat "$LIVE"
|
|
85
|
+
pid=$(sed -n 's/.*"pid":\([0-9]*\).*/\1/p' "$LIVE")
|
|
86
|
+
if kill -0 "$pid" 2>/dev/null; then echo "owner pid $pid is ALIVE"
|
|
87
|
+
else echo "owner pid $pid is GONE — this file is a record, not a running loop"; fi
|
|
88
|
+
exit 0 ;;
|
|
89
|
+
--stop) touch "$STOP"; echo "stop requested — the loop exits after its current pass"; exit 0 ;;
|
|
90
|
+
--accepts) score_accepts; exit 0 ;;
|
|
91
|
+
esac
|
|
92
|
+
|
|
93
|
+
ONCE=false
|
|
94
|
+
for a in "$@"; do
|
|
95
|
+
case "$a" in
|
|
96
|
+
--live) DRY=false ;;
|
|
97
|
+
--once) ONCE=true ;;
|
|
98
|
+
--claims=*) CLAIMS_PER_PASS="${a#*=}" ;;
|
|
99
|
+
*) echo "unknown flag: $a" >&2; exit 2 ;;
|
|
100
|
+
esac
|
|
101
|
+
done
|
|
102
|
+
|
|
103
|
+
# single instance — mkdir(2) is atomic; macOS ships no flock(1)
|
|
104
|
+
if ! mkdir "$LOCK" 2>/dev/null; then
|
|
105
|
+
owner=$(cat "$LOCK/pid" 2>/dev/null || echo "?")
|
|
106
|
+
if [ "$owner" != "?" ] && kill -0 "$owner" 2>/dev/null; then
|
|
107
|
+
echo "[tasks-loop] already running as pid $owner — refusing to double-file"; exit 1
|
|
108
|
+
fi
|
|
109
|
+
echo "[tasks-loop] reaping dead lock (pid $owner)"; rm -rf "$LOCK"; mkdir "$LOCK" || exit 1
|
|
110
|
+
fi
|
|
111
|
+
echo $$ > "$LOCK/pid"
|
|
112
|
+
rm -f "$STOP"
|
|
113
|
+
cleanup() { rm -rf "$LOCK"; publish stopped "loop exited" 0 0 0 0; }
|
|
114
|
+
trap cleanup EXIT INT TERM
|
|
115
|
+
|
|
116
|
+
log "start — seed=$SEED ws=$WS interval=${INTERVAL}s claims/pass=$CLAIMS_PER_PASS dry=$DRY"
|
|
117
|
+
|
|
118
|
+
one_pass() {
|
|
119
|
+
local red=0 green=0 filed=0 claimed=0 reaped=0
|
|
120
|
+
# 0. REAP — before anything else, so a freed lease is claimable this pass.
|
|
121
|
+
if $DRY; then
|
|
122
|
+
log "DRY would reap stranded leases in workspace $WS"
|
|
123
|
+
else
|
|
124
|
+
rout=$(CC_TASKS_WORKSPACE="$WS" bash "$ROOT/.claude/scripts/do-signal.sh" --task-reap 2>&1)
|
|
125
|
+
case "$rout" in
|
|
126
|
+
*'"reaped":[]'*) log "reap: nothing stranded" ;;
|
|
127
|
+
*'"ok":true'*) log "reap: ${rout:0:220}"; reaped=1 ;;
|
|
128
|
+
*) log "reap inconclusive: ${rout:0:220}" ;;
|
|
129
|
+
esac
|
|
130
|
+
fi
|
|
131
|
+
publish scoring "running accept checks" 0 0 0 0
|
|
132
|
+
while IFS=$'\t' read -r slug title tags accept; do
|
|
133
|
+
[ -z "${slug:-}" ] && continue
|
|
134
|
+
if ( cd "$ROOT" && eval "$accept" ) >/dev/null 2>&1; then
|
|
135
|
+
green=$((green+1))
|
|
136
|
+
log "SETTLED $slug — accept is green, not filing"
|
|
137
|
+
continue
|
|
138
|
+
fi
|
|
139
|
+
red=$((red+1))
|
|
140
|
+
grep -qx "$slug" "$FILED" && continue
|
|
141
|
+
if $DRY; then
|
|
142
|
+
log "DRY would file: $slug [$tags] — $title"
|
|
143
|
+
else
|
|
144
|
+
if CC_TASKS_WORKSPACE="$WS" bash "$ROOT/.claude/scripts/do-signal.sh" \
|
|
145
|
+
--task-create "$slug" "$title" "tags=$tags" "notes=accept: $accept" >>"$LOG" 2>&1; then
|
|
146
|
+
echo "$slug" >> "$FILED"; log "FILED $slug [$tags]"
|
|
147
|
+
else
|
|
148
|
+
log "file FAILED $slug — see $LOG"
|
|
149
|
+
fi
|
|
150
|
+
fi
|
|
151
|
+
filed=$((filed+1))
|
|
152
|
+
done < <(rows)
|
|
153
|
+
|
|
154
|
+
# CLAIM — bounded per pass. Atomic at the seam (see header), so a concurrent
|
|
155
|
+
# fleet claiming the same slug is refused, not silently co-granted.
|
|
156
|
+
local n=0
|
|
157
|
+
while IFS=$'\t' read -r slug title tags accept; do
|
|
158
|
+
[ -z "${slug:-}" ] && continue
|
|
159
|
+
[ "$n" -ge "$CLAIMS_PER_PASS" ] && break
|
|
160
|
+
grep -qx "$slug" "$FILED" || $DRY || continue
|
|
161
|
+
if $DRY; then
|
|
162
|
+
log "DRY would claim: $slug"
|
|
163
|
+
else
|
|
164
|
+
out=$(CC_TASKS_WORKSPACE="$WS" bash "$ROOT/.claude/scripts/do-signal.sh" --task-claim "$slug" 2>&1)
|
|
165
|
+
case "$out" in
|
|
166
|
+
*already-claimed*) log "SKIP $slug — already leased (that is the atomic guard working)" ;;
|
|
167
|
+
*'"ok":true'*) log "CLAIMED $slug"; claimed=$((claimed+1)) ;;
|
|
168
|
+
*) log "claim inconclusive $slug: ${out:0:160}" ;;
|
|
169
|
+
esac
|
|
170
|
+
fi
|
|
171
|
+
n=$((n+1))
|
|
172
|
+
done < <(rows)
|
|
173
|
+
|
|
174
|
+
publish idle "pass complete" "$red" "$green" "$filed" "$claimed"
|
|
175
|
+
log "pass: red=$red green=$green filed=$filed claimed=$claimed reaped=$reaped"
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if $ONCE; then one_pass; exit $?; fi
|
|
179
|
+
while true; do
|
|
180
|
+
one_pass || true
|
|
181
|
+
for _ in $(seq 1 "$INTERVAL"); do
|
|
182
|
+
[ -f "$STOP" ] && { log "stop file seen — exiting"; exit 0; }
|
|
183
|
+
sleep 1
|
|
184
|
+
done
|
|
185
|
+
done
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-cached.sh <folder> -- <vitest args...> — run a vitest selection, or reuse
|
|
3
|
+
# the PASS someone already computed for this exact input. Exits 0 on pass/hit,
|
|
4
|
+
# non-zero on fail or on could-not-run.
|
|
5
|
+
#
|
|
6
|
+
# manifest: needs-env
|
|
7
|
+
#
|
|
8
|
+
# WHY. The dev lane re-ran identical work on every cycle. The pinned block alone
|
|
9
|
+
# is 44 files / 420 tests / ~20s, and it runs on EVERY cycle regardless of the
|
|
10
|
+
# diff -- so a plan of six cycles paid it six times for a tree whose pinned
|
|
11
|
+
# surface never moved. Same for `vitest related` when two cycles touch the same
|
|
12
|
+
# file. Measured 2026-09-01: fast lane ~30s/cycle, two thirds of it the pins.
|
|
13
|
+
#
|
|
14
|
+
# The key is the exact INPUT to the run, so a hit is sound by construction:
|
|
15
|
+
# sha( vitest args + content of every selected test file
|
|
16
|
+
# + content of every tracked source file that differs from the base )
|
|
17
|
+
# Nothing else can change the answer. Change a test, change a source file it
|
|
18
|
+
# reads, or change the selection, and the key moves. This is the tsc-cached.sh
|
|
19
|
+
# idiom (one compute per tree-fingerprint, everyone else reuses) narrowed from
|
|
20
|
+
# the whole tree to the actual inputs of one selection.
|
|
21
|
+
#
|
|
22
|
+
# ONLY PASSES ARE CACHED. A red result is never memoised: a failing gate must be
|
|
23
|
+
# re-run every single time until it is green, because the value of a gate is that
|
|
24
|
+
# it keeps saying no. This is the `unrun-gate-is-not-a-pass` rule applied to its
|
|
25
|
+
# mirror image -- a *remembered* pass is only a pass while its inputs are identical.
|
|
26
|
+
#
|
|
27
|
+
# TEST_CACHE_DISABLE=1 forces a real run (and still records the result).
|
|
28
|
+
# --self-test runs the fixtures.
|
|
29
|
+
set -uo pipefail
|
|
30
|
+
|
|
31
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
32
|
+
CACHE_DIR="${TEST_CACHE_DIR:-${TMPDIR:-/tmp}/one-test-cache}"
|
|
33
|
+
MAX_AGE_DAYS="${TEST_CACHE_MAX_AGE_DAYS:-7}"
|
|
34
|
+
|
|
35
|
+
_self_test() {
|
|
36
|
+
local t fails=0
|
|
37
|
+
t="$(mktemp -d)"; export TEST_CACHE_DIR="$t"
|
|
38
|
+
# 1. a PASS is remembered
|
|
39
|
+
bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-pass >/dev/null 2>&1
|
|
40
|
+
local r1=$?
|
|
41
|
+
local out2; out2=$(bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-pass 2>&1)
|
|
42
|
+
if printf '%s' "$out2" | grep -q 'cache HIT'; then echo "ok: pass is remembered"
|
|
43
|
+
else echo "FAIL: identical pass did not hit (r1=$r1): $out2"; fails=$((fails+1)); fi
|
|
44
|
+
# 2. RED PROOF — a FAIL must never be remembered
|
|
45
|
+
bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-fail >/dev/null 2>&1
|
|
46
|
+
local out3; out3=$(bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-fail 2>&1)
|
|
47
|
+
if printf '%s' "$out3" | grep -q 'cache HIT'; then
|
|
48
|
+
echo "FAIL: a RED result was cached — the gate would stop biting"; fails=$((fails+1))
|
|
49
|
+
else echo "ok: a fail is never cached (re-runs every time)"; fi
|
|
50
|
+
# 3. a different selection must not collide with a remembered pass
|
|
51
|
+
local out4; out4=$(bash "$ROOT/.claude/scripts/test-cached.sh" __probe__ -- --probe-pass --extra 2>&1)
|
|
52
|
+
if printf '%s' "$out4" | grep -q 'cache HIT'; then
|
|
53
|
+
echo "FAIL: different args collided with a cached key"; fails=$((fails+1))
|
|
54
|
+
else echo "ok: key discriminates on args"; fi
|
|
55
|
+
# 4. RED PROOF against a REAL vitest -- a genuinely failing run must exit
|
|
56
|
+
# non-zero AND leave no stamp. Checks 1-3 use the __probe__ folder, which never
|
|
57
|
+
# touches the runner; this one drives the actual code path the gate uses.
|
|
58
|
+
local pt; pt="$(mktemp -d)"
|
|
59
|
+
cat > "$pt/red.test.ts" <<'REDEOF'
|
|
60
|
+
import { it, expect } from "vitest";
|
|
61
|
+
it("is deliberately red", () => { expect(1).toBe(2); });
|
|
62
|
+
REDEOF
|
|
63
|
+
local before after
|
|
64
|
+
before=$(ls "$t" 2>/dev/null | wc -l | tr -d ' ')
|
|
65
|
+
if TEST_CACHE_DIR="$t" bash "$ROOT/.claude/scripts/test-cached.sh" \
|
|
66
|
+
one.ie/web -- --reporter=dot --root "$pt" >/dev/null 2>&1; then
|
|
67
|
+
echo "FAIL: a red vitest exited 0"; fails=$((fails+1))
|
|
68
|
+
else echo "ok: a real failing vitest exits non-zero"; fi
|
|
69
|
+
after=$(ls "$t" 2>/dev/null | wc -l | tr -d ' ')
|
|
70
|
+
if [ "$before" = "$after" ]; then echo "ok: a red run recorded no stamp"
|
|
71
|
+
else echo "FAIL: a red run wrote a cache stamp"; fails=$((fails+1)); fi
|
|
72
|
+
|
|
73
|
+
# 5. the pty path, only where it can actually be exercised. `script -qeF` needs
|
|
74
|
+
# a controlling tty on stdin; without one it dies with
|
|
75
|
+
# "tcgetattr/ioctl: Operation not supported on socket" -- which is a NON-ZERO
|
|
76
|
+
# exit, so a naive red proof here passes for the wrong reason and proves
|
|
77
|
+
# nothing about status propagation. Measured 2026-09-01. An unrunnable check
|
|
78
|
+
# reports n/a; it is never reported as ok.
|
|
79
|
+
if [ -t 0 ]; then
|
|
80
|
+
before=$(ls "$t" 2>/dev/null | wc -l | tr -d ' ')
|
|
81
|
+
if TEST_CACHE_PTY=1 TEST_CACHE_DIR="$t" bash "$ROOT/.claude/scripts/test-cached.sh" \
|
|
82
|
+
one.ie/web -- --reporter=dot --root "$pt" >/dev/null 2>&1; then
|
|
83
|
+
echo "FAIL: a red vitest under the pty wrapper exited 0"; fails=$((fails+1))
|
|
84
|
+
else echo "ok: pty wrapper propagates a red exit status"; fi
|
|
85
|
+
after=$(ls "$t" 2>/dev/null | wc -l | tr -d ' ')
|
|
86
|
+
if [ "$before" = "$after" ]; then echo "ok: a red pty run recorded no stamp"
|
|
87
|
+
else echo "FAIL: a red pty run wrote a cache stamp"; fails=$((fails+1)); fi
|
|
88
|
+
else
|
|
89
|
+
echo "n/a: no controlling tty — the pty wrapper cannot be exercised here (NOT a pass)"
|
|
90
|
+
fi
|
|
91
|
+
rm -rf "$pt"
|
|
92
|
+
rm -rf "$t"
|
|
93
|
+
[ "$fails" -eq 0 ] && { echo "test-cached: self-test PASS"; return 0; }
|
|
94
|
+
echo "test-cached: self-test FAILED ($fails)"; return 1
|
|
95
|
+
}
|
|
96
|
+
[ "${1:-}" = "--self-test" ] && { _self_test; exit $?; }
|
|
97
|
+
|
|
98
|
+
folder="${1:-}"; shift || true
|
|
99
|
+
[ "${1:-}" = "--" ] && shift
|
|
100
|
+
[ -n "$folder" ] || { echo "usage: test-cached.sh <folder> -- <vitest args>" >&2; exit 2; }
|
|
101
|
+
ARGS=("$@")
|
|
102
|
+
|
|
103
|
+
# --- key -------------------------------------------------------------------
|
|
104
|
+
# Selected test files: whichever of the args name a real file, plus (for a
|
|
105
|
+
# substring selection like the pins) the files those substrings resolve to.
|
|
106
|
+
_sel=""
|
|
107
|
+
for a in "${ARGS[@]}"; do
|
|
108
|
+
case "$a" in
|
|
109
|
+
--*) continue ;;
|
|
110
|
+
*) _sel="$_sel$a"$'\n' ;;
|
|
111
|
+
esac
|
|
112
|
+
done
|
|
113
|
+
|
|
114
|
+
# <hash> <repo-relative path> — the shape `shasum <file>` prints, minus $ROOT.
|
|
115
|
+
#
|
|
116
|
+
# This is the SECOND memo key of the two the governor owns, and it is now
|
|
117
|
+
# computed by lib/govern.ts (cycle 16) — the same program that computes
|
|
118
|
+
# tsc_tree_fingerprint, so both keys have one implementation and one place to be
|
|
119
|
+
# wrong. The BYTES ARE IDENTICAL to the `shasum -a 256 < file | cut -d' ' -f1`
|
|
120
|
+
# form it replaces, including the empty hash a missing file produced, so every
|
|
121
|
+
# stamp already on disk still hits: verified 2026-09-04 by comparing
|
|
122
|
+
# TEST_CACHE_KEY_ONLY=1 across three selections before and after.
|
|
123
|
+
# GOVERN_BUN unresolvable -> the hash comes back empty for every file, which
|
|
124
|
+
# moves the key rather than freezing it, so a broken toolchain can only cost a
|
|
125
|
+
# re-run and can never replay a stale pass.
|
|
126
|
+
_GOV_LIB="$(dirname "${BASH_SOURCE[0]}")/lib/govern.sh"
|
|
127
|
+
# shellcheck source=lib/govern.sh
|
|
128
|
+
[ -r "$_GOV_LIB" ] && . "$_GOV_LIB"
|
|
129
|
+
if command -v _gov >/dev/null 2>&1 && [ -n "${GOVERN_BUN:-}" ]; then
|
|
130
|
+
_hash_rel() { _gov hash-rel "$ROOT" "$@"; }
|
|
131
|
+
else
|
|
132
|
+
# No governor lib and no bun (a fresh clone, CI). Same bytes, one file per call.
|
|
133
|
+
_hash_rel() {
|
|
134
|
+
local f
|
|
135
|
+
for f in "$@"; do
|
|
136
|
+
printf '%s %s\n' "$(shasum -a 256 < "$ROOT/$f" 2>/dev/null | cut -d' ' -f1)" "$f"
|
|
137
|
+
done
|
|
138
|
+
}
|
|
139
|
+
fi
|
|
140
|
+
|
|
141
|
+
_key_src=$(
|
|
142
|
+
printf '%s\0' "${ARGS[@]}"
|
|
143
|
+
# content of every file the selection names, when it exists
|
|
144
|
+
while IFS= read -r f; do
|
|
145
|
+
[ -n "$f" ] || continue
|
|
146
|
+
# REPO-RELATIVE names, never "$ROOT/...": `shasum <path>` prints the path
|
|
147
|
+
# beside the hash, and an absolute path puts the WORKTREE into the key. The
|
|
148
|
+
# same bytes hashed from two checkouts then produced two keys, so the memo
|
|
149
|
+
# was private per worktree for every selection that named a file — `related`
|
|
150
|
+
# and BOTH full-suite lanes — and a plan that closed in a worktree and shipped
|
|
151
|
+
# from main paid ~122s twice. Measured 2026-09-03 (three-cell probe: the
|
|
152
|
+
# path-free pins shared, anything naming a file did not). Same bytes, same
|
|
153
|
+
# key, any checkout. This is NOT a narrowing: nothing leaves the key.
|
|
154
|
+
[ -f "$ROOT/$folder/$f" ] && _hash_rel "$folder/$f"
|
|
155
|
+
[ -f "$ROOT/$f" ] && _hash_rel "$f"
|
|
156
|
+
done <<< "$_sel"
|
|
157
|
+
# The working delta, BY CONTENT. `git status --porcelain` was the first attempt
|
|
158
|
+
# and it is a trap: it names files, so " M src/lib/authority.ts" is the same
|
|
159
|
+
# string whether that file gained one comment or was rewritten. Caught in the
|
|
160
|
+
# act 2026-09-01 -- appending a line to an already-modified file did not move
|
|
161
|
+
# the key, and the run returned a cached PASS for a tree that had changed. A
|
|
162
|
+
# green gate over a changed tree is the exact failure this cache must not have.
|
|
163
|
+
# `git diff HEAD` hashes the CONTENT of every tracked change; untracked files
|
|
164
|
+
# are hashed directly since no diff covers them.
|
|
165
|
+
git -C "$ROOT" diff HEAD 2>/dev/null
|
|
166
|
+
git -C "$ROOT" ls-files --others --exclude-standard 2>/dev/null \
|
|
167
|
+
| while IFS= read -r u; do _hash_rel "$u"; done
|
|
168
|
+
git -C "$ROOT" rev-parse HEAD 2>/dev/null
|
|
169
|
+
# Inputs git CANNOT see. `.env`/`.env.local` are gitignored, so neither
|
|
170
|
+
# `git diff HEAD` nor `ls-files --others` (which honours .gitignore) covers
|
|
171
|
+
# them -- yet the suite reads them. Without these lines a pass could be
|
|
172
|
+
# replayed across an environment change. Absent hashes as "ABSENT" so that
|
|
173
|
+
# DELETING one still moves the key.
|
|
174
|
+
for _e in "$folder/.env" "$folder/.env.local" "$folder/.env.test"; do
|
|
175
|
+
if [ -f "$ROOT/$_e" ]; then _hash_rel "$_e"
|
|
176
|
+
else echo "ABSENT $_e"; fi
|
|
177
|
+
done
|
|
178
|
+
# for a substring selection the matched SET can change without any arg changing
|
|
179
|
+
( cd "$ROOT/$folder" 2>/dev/null && git ls-files '*.test.*' 2>/dev/null | shasum -a 256 )
|
|
180
|
+
)
|
|
181
|
+
KEY=$(printf '%s' "$_key_src" | shasum -a 256 | cut -d' ' -f1)
|
|
182
|
+
# TEST_CACHE_KEY_ONLY=1 prints the key and runs nothing — how a caller (or a
|
|
183
|
+
# sharing proof) asks "would this selection hit?" without paying for the run.
|
|
184
|
+
if [ "${TEST_CACHE_KEY_ONLY:-0}" = "1" ]; then echo "$KEY"; exit 0; fi
|
|
185
|
+
STAMP="$CACHE_DIR/$folder-${KEY}.pass"
|
|
186
|
+
STAMP="${STAMP//\//_}"; STAMP="$CACHE_DIR/$(basename "$STAMP")"
|
|
187
|
+
|
|
188
|
+
# TEST_CACHE_PROBE=1 answers "would this HIT?" and runs nothing: exit 0 on a
|
|
189
|
+
# hit, 1 on a miss. It sits AFTER the STAMP lines above on purpose -- the
|
|
190
|
+
# probe and the real check must resolve the same path or the probe is a
|
|
191
|
+
# second, drifting definition of a hit. KEY_ONLY prints the key; PROBE
|
|
192
|
+
# answers the question the caller actually has.
|
|
193
|
+
#
|
|
194
|
+
# WHY IT EXISTS. verify-fast.sh took a GOVERNOR SLOT and then asked whether
|
|
195
|
+
# there was anything to run. Measured 2026-09-09 on a saturated box: 855s
|
|
196
|
+
# and 1324s queued for slots whose work was a memo hit costing ~0. A cache
|
|
197
|
+
# hit must never wait behind a real gate -- it needs no memory, no forks and
|
|
198
|
+
# no slot. Probe first, take the slot only on a MISS.
|
|
199
|
+
if [ "${TEST_CACHE_PROBE:-0}" = "1" ]; then
|
|
200
|
+
if [ "${TEST_CACHE_DISABLE:-0}" != "1" ] && [ -f "$STAMP" ]; then
|
|
201
|
+
echo "[test-cached] probe: HIT ${KEY:0:12}"; exit 0
|
|
202
|
+
fi
|
|
203
|
+
echo "[test-cached] probe: MISS ${KEY:0:12}"; exit 1
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
mkdir -p "$CACHE_DIR" 2>/dev/null
|
|
207
|
+
find "$CACHE_DIR" -name '*.pass' -mtime "+${MAX_AGE_DAYS}" -delete 2>/dev/null
|
|
208
|
+
|
|
209
|
+
if [ "${TEST_CACHE_DISABLE:-0}" != "1" ] && [ -f "$STAMP" ]; then
|
|
210
|
+
echo "[test-cached] cache HIT ${KEY:0:12} — identical inputs already PASSED $(cat "$STAMP" 2>/dev/null)"
|
|
211
|
+
exit 0
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# --- run -------------------------------------------------------------------
|
|
215
|
+
# __probe__ is the self-test's fake folder: no vitest, deterministic outcome.
|
|
216
|
+
if [ "$folder" = "__probe__" ]; then
|
|
217
|
+
case " ${ARGS[*]} " in *--probe-fail*) st=1 ;; *) st=0 ;; esac
|
|
218
|
+
else
|
|
219
|
+
# THE SLOT. This script takes no lock, so unlike tsc-cached.sh there is no
|
|
220
|
+
# ordering hazard -- adding a slot here is purely additive.
|
|
221
|
+
#
|
|
222
|
+
# verify-fast.sh already wraps ITS calls to this script in gate-run, and
|
|
223
|
+
# gate-run exports GOVERN_IN_GATE=1, so _gate below collapses to a plain `env`
|
|
224
|
+
# on that path: the outer slot is INHERITED, never doubled (a second slot held
|
|
225
|
+
# by one logical gate is what deadlocks the queue at cap 2).
|
|
226
|
+
#
|
|
227
|
+
# What this closes is the other caller: `bun run test` -> test-lanes.sh -> here
|
|
228
|
+
# reached vitest with no slot at all. Measured 2026-09-07 in one worktree at
|
|
229
|
+
# 256% CPU and ~800MB, with the governor reporting "1 of 2 slots held".
|
|
230
|
+
_gate=( env )
|
|
231
|
+
if [ "${TEST_CACHE_GATE:-1}" = "1" ] && [ "${GOVERN_IN_GATE:-0}" != "1" ] \
|
|
232
|
+
&& [ -f "$ROOT/.claude/scripts/gate-run.sh" ]; then
|
|
233
|
+
_gate=( env "GOVERN_GATE_TIMEOUT=${TEST_CACHE_TIMEOUT:-1800}"
|
|
234
|
+
bash "$ROOT/.claude/scripts/gate-run.sh" "test-cache:$folder" -- )
|
|
235
|
+
fi
|
|
236
|
+
if [ "${TEST_CACHE_PTY:-0}" = "1" ]; then
|
|
237
|
+
# The deploy gate's vitest HANGS when stdout is not a TTY -- a fork is spawned
|
|
238
|
+
# and never given work (see .claude/commands/deploy.md "The vitest gate hangs").
|
|
239
|
+
# `script -qeF /dev/null` allocates a pty and flushes. The -e is load-bearing:
|
|
240
|
+
# it propagates the CHILD's exit status. Without it `script` exits 0 over a red
|
|
241
|
+
# suite and this cache would record a PASS for a failing run.
|
|
242
|
+
( cd "$ROOT/$folder" && "${_gate[@]}" script -qeF /dev/null bunx vitest run "${ARGS[@]}" )
|
|
243
|
+
st=$?
|
|
244
|
+
else
|
|
245
|
+
( cd "$ROOT/$folder" && "${_gate[@]}" bunx vitest run "${ARGS[@]}" )
|
|
246
|
+
st=$?
|
|
247
|
+
fi
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
# ONLY a pass is recorded. A red gate must re-run until it is green.
|
|
251
|
+
if [ "$st" -eq 0 ]; then
|
|
252
|
+
date -u +%Y-%m-%dT%H:%M:%SZ > "$STAMP" 2>/dev/null
|
|
253
|
+
echo "[test-cached] PASS recorded ${KEY:0:12}"
|
|
254
|
+
fi
|
|
255
|
+
exit "$st"
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# test-full.sh — THE full vitest suite. One definition, every caller.
|
|
3
|
+
#
|
|
4
|
+
# manifest: needs-env
|
|
5
|
+
#
|
|
6
|
+
# WHY THIS FILE EXISTS. The full suite had two callers that ran it with
|
|
7
|
+
# DIFFERENT arguments:
|
|
8
|
+
#
|
|
9
|
+
# ./deploy → test-cached.sh one.ie/web -- --testTimeout=15000 … --reporter=dot
|
|
10
|
+
# /close → bun run test → gate-run test -- vitest run (no memo at all)
|
|
11
|
+
#
|
|
12
|
+
# test-cached.sh keys a PASS on the exact input, and the argv is part of that
|
|
13
|
+
# key. So the two callers could not share a stamp even on a byte-identical
|
|
14
|
+
# tree — and the /close path was not memoised in the first place. A warm tree
|
|
15
|
+
# therefore paid the full suite TWICE: once to close the plan, once to ship it,
|
|
16
|
+
# for inputs that had not moved between them.
|
|
17
|
+
#
|
|
18
|
+
# The fix is not a smarter cache. It is removing the second definition: both
|
|
19
|
+
# callers now run THIS script, so the argv they present to test-cached.sh is
|
|
20
|
+
# identical by construction and the second run is a hit.
|
|
21
|
+
#
|
|
22
|
+
# WHAT THIS IS NOT. It is not the fast lane's stamps being reused to shrink the
|
|
23
|
+
# full run. That was the first idea and it is unsound, recorded here so nobody
|
|
24
|
+
# rebuilds it:
|
|
25
|
+
# · `vitest related --run <files>` and `vitest run` are different runs — fork
|
|
26
|
+
# assignment and module-graph order differ. A file that passes in a 2-file
|
|
27
|
+
# selection is NOT thereby proven in the 875-file pool, and this repo has
|
|
28
|
+
# order-dependent shared-TypeDB suites.
|
|
29
|
+
# · On a do/* branch the fast lane DEFERS the pins (.verify-fast-debt), so its
|
|
30
|
+
# stamps systematically exclude the gates the full run exists to pay.
|
|
31
|
+
# · The window is empty anyway: a fast stamp is valid only at tree-state T, and
|
|
32
|
+
# the full gate is only slow when the tree is not T.
|
|
33
|
+
# The inputs to a full-pool run include the pool. Only a whole-suite pass may
|
|
34
|
+
# stand in for a whole-suite run.
|
|
35
|
+
#
|
|
36
|
+
# TEST_CACHE_PTY=1 for a non-TTY stdout (the deploy gate logs to a file, and the
|
|
37
|
+
# runner hangs there — see deploy.md "The vitest gate hangs").
|
|
38
|
+
# --check asserts no caller has grown a second definition.
|
|
39
|
+
set -uo pipefail
|
|
40
|
+
|
|
41
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
42
|
+
FOLDER="${TEST_FULL_FOLDER:-one.ie/web}"
|
|
43
|
+
|
|
44
|
+
# The one definition. Changing a flag here changes it for every caller at once,
|
|
45
|
+
# which is the whole point — a flag added on one side only would split the key
|
|
46
|
+
# again and silently cost a second full suite.
|
|
47
|
+
ARGS=(--testTimeout=15000 --hookTimeout=15000 --teardownTimeout=15000 --reporter=dot)
|
|
48
|
+
|
|
49
|
+
# --- --check: the second definition must not come back ----------------------
|
|
50
|
+
# A caller that runs the full suite by hand gets a different argv, so its PASS
|
|
51
|
+
# cannot be reused by the other caller. That is invisible — everything is green,
|
|
52
|
+
# it just costs an extra ~3 minutes — so it needs a checker, not a comment.
|
|
53
|
+
if [ "${1:-}" = "--check" ]; then
|
|
54
|
+
fails=0
|
|
55
|
+
# deploy.sh must reach the suite through this script
|
|
56
|
+
if grep -qE 'test-cached\.sh[^|]*one\.ie/web[^|]*--[^|]*--reporter' "$ROOT/.claude/scripts/deploy.sh"; then
|
|
57
|
+
echo "FAIL: deploy.sh still spells out its own vitest args — it must call test-full.sh"; fails=$((fails+1))
|
|
58
|
+
else echo "ok: deploy.sh has no second definition"; fi
|
|
59
|
+
# package.json's `test` must not be a bare vitest run (test:raw is the escape)
|
|
60
|
+
_t=$(python3 -c "import json;print(json.load(open('$ROOT/$FOLDER/package.json'))['scripts'].get('test',''))" 2>/dev/null)
|
|
61
|
+
case "$_t" in
|
|
62
|
+
*test-full.sh*) echo "ok: package.json test routes through test-full.sh" ;;
|
|
63
|
+
*) echo "FAIL: package.json test is '$_t' — not routed through test-full.sh"; fails=$((fails+1)) ;;
|
|
64
|
+
esac
|
|
65
|
+
# and the two must present the SAME argv, which is only true if neither adds any
|
|
66
|
+
# A caller may prefix env (TEST_CACHE_PTY=1) but must pass NO vitest args --
|
|
67
|
+
# an arg on one side only splits the key and quietly costs a second full suite.
|
|
68
|
+
_extra=$(grep -n 'test-full\.sh' "$ROOT/.claude/scripts/deploy.sh" | grep -- '--' | grep -v -- '--check' || true)
|
|
69
|
+
if [ -n "$_extra" ]; then
|
|
70
|
+
echo "FAIL: deploy.sh passes args to test-full.sh -- that splits the key:"; echo "$_extra"; fails=$((fails+1))
|
|
71
|
+
else echo "ok: no caller adds arguments"; fi
|
|
72
|
+
[ "$fails" -eq 0 ] && { echo "test-full: check PASS"; exit 0; }
|
|
73
|
+
echo "test-full: check FAILED ($fails)"; exit 1
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
# TWO LANES, not one run. The suite has two binding constraints — CPU for ~1126
|
|
77
|
+
# files, and one shared external TypeDB gateway for 19 — and a single 8-fork pool
|
|
78
|
+
# serves the second one so badly the gate became a coin flip (four runs on an
|
|
79
|
+
# unchanged tree: 8, 12, 13, 8 different failures). test-lanes.sh splits them and
|
|
80
|
+
# runs them concurrently. Measured 2026-09-03, same tree:
|
|
81
|
+
# before 238-282s, RED, rotating victims
|
|
82
|
+
# after 121.9s wall, GREEN — pool 1125 passed / typedb 19 passed
|
|
83
|
+
# It still reaches test-cached.sh (once per lane), so passes are still memoised
|
|
84
|
+
# and a RED is still never cached. ARGS above stays the one definition of the
|
|
85
|
+
# vitest flags; test-lanes.sh consumes it via TEST_FULL_ARGS.
|
|
86
|
+
export TEST_FULL_ARGS="${ARGS[*]}"
|
|
87
|
+
exec bash "$ROOT/.claude/scripts/test-lanes.sh"
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// test-honesty — find tests that CANNOT FAIL.
|
|
3
|
+
//
|
|
4
|
+
// Written 2026-08-18 after an audit found 8 security tests whose bodies were
|
|
5
|
+
// comment-only. They backed a SETTLED contract: text/SECURITY-plan.md names the
|
|
6
|
+
// UC9 and UC26 canaries as its proof observable, and the kill-switch grepped
|
|
7
|
+
// `[1-9][0-9]+ passed` over the same file — satisfied by ~130 unrelated tests in
|
|
8
|
+
// it. An empty body cannot fail, so the contract rested on nothing.
|
|
9
|
+
//
|
|
10
|
+
// It reports two classes, and only ever counts what it can prove from the source:
|
|
11
|
+
//
|
|
12
|
+
// EMPTY a test body containing no assertion at all. Always a defect: the
|
|
13
|
+
// test is scored as a pass and can never go red.
|
|
14
|
+
// SKIP a conditional skip (skipIf / it.skip / runIf / ternary-gated it).
|
|
15
|
+
// Not a defect by itself — but a suite that skips is INDISTINGUISHABLE
|
|
16
|
+
// from one that passes, so the count must be visible. 31 tests in this
|
|
17
|
+
// repo sat behind an env var vitest could not see and had never once
|
|
18
|
+
// executed on any machine.
|
|
19
|
+
//
|
|
20
|
+
// Deliberately NOT a linter. It makes no style judgements and flags nothing it
|
|
21
|
+
// cannot demonstrate from the text of the file.
|
|
22
|
+
//
|
|
23
|
+
// Usage: node .claude/scripts/test-honesty.mjs [--json] [dir ...]
|
|
24
|
+
// Exit: 1 if any EMPTY body is found, else 0.
|
|
25
|
+
|
|
26
|
+
import { readdirSync, statSync, readFileSync } from 'node:fs'
|
|
27
|
+
import path from 'node:path'
|
|
28
|
+
|
|
29
|
+
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..')
|
|
30
|
+
const args = process.argv.slice(2)
|
|
31
|
+
const JSON_OUT = args.includes('--json')
|
|
32
|
+
const dirs = args.filter((a) => !a.startsWith('--'))
|
|
33
|
+
const TARGETS = dirs.length ? dirs : ['one.ie/web/tests', 'channels/test', 'channels/tests']
|
|
34
|
+
|
|
35
|
+
function walk(dir, out = []) {
|
|
36
|
+
let entries
|
|
37
|
+
try { entries = readdirSync(dir) } catch { return out }
|
|
38
|
+
for (const e of entries) {
|
|
39
|
+
const full = path.join(dir, e)
|
|
40
|
+
let st
|
|
41
|
+
try { st = statSync(full) } catch { continue }
|
|
42
|
+
if (st.isDirectory()) walk(full, out)
|
|
43
|
+
else if (/\.(test|spec)\.[tj]sx?$/.test(e)) out.push(full)
|
|
44
|
+
}
|
|
45
|
+
return out
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Walk from the opening brace, tracking strings/comments, and return the body.
|
|
49
|
+
function bodyFrom(src, openIdx) {
|
|
50
|
+
let depth = 0, i = openIdx
|
|
51
|
+
let inS = null, inLine = false, inBlock = false
|
|
52
|
+
for (; i < src.length; i++) {
|
|
53
|
+
const c = src[i], n = src[i + 1]
|
|
54
|
+
if (inLine) { if (c === '\n') inLine = false; continue }
|
|
55
|
+
if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++ } continue }
|
|
56
|
+
if (inS) { if (c === '\\') { i++; continue } if (c === inS) inS = null; continue }
|
|
57
|
+
if (c === '/' && n === '/') { inLine = true; i++; continue }
|
|
58
|
+
if (c === '/' && n === '*') { inBlock = true; i++; continue }
|
|
59
|
+
if (c === '"' || c === "'" || c === '`') { inS = c; continue }
|
|
60
|
+
if (c === '{') depth++
|
|
61
|
+
else if (c === '}') { depth--; if (depth === 0) return src.slice(openIdx + 1, i) }
|
|
62
|
+
}
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function stripInert(s) {
|
|
67
|
+
return s
|
|
68
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
69
|
+
.replace(/(^|[^:])\/\/[^\n]*/g, '$1')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// An assertion, or a call that defers to one. `expect.assertions`, a bare
|
|
73
|
+
// `await somethingThatThrows()`, and custom helpers all count — the point is to
|
|
74
|
+
// catch bodies with NOTHING, not to police assertion style.
|
|
75
|
+
const ASSERTS = /\b(expect|assert|should|chai|\.rejects|\.resolves|toMatchSnapshot|toThrow|fail\()/
|
|
76
|
+
|
|
77
|
+
const findings = { empty: [], declared: [], indirect: [], skips: [] }
|
|
78
|
+
|
|
79
|
+
for (const target of TARGETS) {
|
|
80
|
+
for (const file of walk(path.join(ROOT, target))) {
|
|
81
|
+
const src = readFileSync(file, 'utf8')
|
|
82
|
+
const rel = path.relative(ROOT, file)
|
|
83
|
+
|
|
84
|
+
// conditional skips
|
|
85
|
+
for (const m of src.matchAll(/\b(?:it|test|describe)\.(skipIf|runIf|skip)\b|\b(?:skipWithout\w*|maybeIt|maybeDescribe)\s*\(/g)) {
|
|
86
|
+
const line = src.slice(0, m.index).split('\n').length
|
|
87
|
+
findings.skips.push({ file: rel, line, form: m[0].trim() })
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// assertion-free bodies
|
|
91
|
+
const re = /\b(?:it|test)(\.\w+)?\s*\(\s*(['"`])([\s\S]*?)\2\s*,\s*(?:async\s*)?\(\s*\)\s*=>\s*\{/g
|
|
92
|
+
for (const m of re.exec_all ? [] : [...src.matchAll(re)]) {
|
|
93
|
+
const open = src.indexOf('{', m.index + m[0].length - 1)
|
|
94
|
+
const body = bodyFrom(src, open)
|
|
95
|
+
if (body === null) continue
|
|
96
|
+
const code = stripInert(body).trim()
|
|
97
|
+
// Two classes, deliberately unequal. A body with NO CODE is provably
|
|
98
|
+
// unable to fail — a hard error. A body with code but no visible assertion
|
|
99
|
+
// token is only a SUSPICION: it may delegate to a helper that asserts
|
|
100
|
+
// internally (engine-golden.test.ts does exactly that via goldenCheck()).
|
|
101
|
+
// Reporting the second as a failure would make this checker the very thing
|
|
102
|
+
// it exists to catch — confident, and wrong.
|
|
103
|
+
const rec = {
|
|
104
|
+
file: rel,
|
|
105
|
+
line: src.slice(0, m.index).split('\n').length,
|
|
106
|
+
name: m[3].slice(0, 100),
|
|
107
|
+
}
|
|
108
|
+
// An empty body under .skip/.todo is HONEST — vitest reports it as
|
|
109
|
+
// skipped/todo, never as passed, and it documents intent. The defect is an
|
|
110
|
+
// empty body that WILL BE SCORED: it reports a pass and can never go red.
|
|
111
|
+
// That is exactly the shape of the 8 security tests that backed a settled
|
|
112
|
+
// contract while asserting nothing.
|
|
113
|
+
const declared = /^\.(skip|todo|fails)$/.test(m[1] ?? '')
|
|
114
|
+
if (code.length === 0 && !declared) findings.empty.push({ ...rec, reason: 'empty body that will be SCORED AS A PASS' })
|
|
115
|
+
else if (code.length === 0) findings.declared.push({ ...rec, reason: `honest placeholder (${m[1]})` })
|
|
116
|
+
else if (!ASSERTS.test(code)) findings.indirect.push({ ...rec, reason: 'no assertion token — verify it delegates to one' })
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (JSON_OUT) {
|
|
122
|
+
console.log(JSON.stringify(findings, null, 2))
|
|
123
|
+
} else {
|
|
124
|
+
const bySkipFile = new Map()
|
|
125
|
+
for (const s of findings.skips) bySkipFile.set(s.file, (bySkipFile.get(s.file) ?? 0) + 1)
|
|
126
|
+
console.log(`test-honesty: scanned ${TARGETS.join(', ')}`)
|
|
127
|
+
console.log(`\nSKIP — conditional skips (visible, not failures): ${findings.skips.length} across ${bySkipFile.size} file(s)`)
|
|
128
|
+
for (const [f, n] of [...bySkipFile].sort((a, b) => b[1] - a[1]).slice(0, 15)) console.log(` ${String(n).padStart(3)} ${f}`)
|
|
129
|
+
console.log(`\nDECLARED — empty but honestly marked .skip/.todo: ${findings.declared.length}`)
|
|
130
|
+
console.log(`\nINDIRECT — no assertion token; verify each delegates to one: ${findings.indirect.length}`)
|
|
131
|
+
for (const e of findings.indirect.slice(0, 8)) console.log(` ${e.file}:${e.line} ${e.name}`)
|
|
132
|
+
if (findings.indirect.length > 8) console.log(` … ${findings.indirect.length - 8} more (--json for all)`)
|
|
133
|
+
console.log(`\nEMPTY — tests that CANNOT FAIL: ${findings.empty.length}`)
|
|
134
|
+
for (const e of findings.empty) console.log(` ${e.file}:${e.line} ${e.reason}\n ${e.name}`)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
process.exit(findings.empty.length > 0 ? 1 : 0)
|