@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
|
@@ -101,6 +101,7 @@ function toList(v) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
const promises = []
|
|
104
|
+
const broken = []
|
|
104
105
|
for (const file of readdirSync(TEXT).sort()) {
|
|
105
106
|
if (!file.endsWith('.md') || DERIVED.test(file)) continue
|
|
106
107
|
if (file.startsWith('template-')) continue
|
|
@@ -109,7 +110,18 @@ for (const file of readdirSync(TEXT).sort()) {
|
|
|
109
110
|
if (!split) continue
|
|
110
111
|
|
|
111
112
|
let fm
|
|
112
|
-
try {
|
|
113
|
+
try {
|
|
114
|
+
fm = parseYaml(split.fm)
|
|
115
|
+
} catch (err) {
|
|
116
|
+
// An unparseable head is not a contract — UNLESS it was meant to be one. A file
|
|
117
|
+
// that declares `deliverables:` and then fails to parse is a promise the site
|
|
118
|
+
// would drop on the floor, silently, forever (text/urls.md vanished this way:
|
|
119
|
+
// an unescaped `"` inside a double-quoted accept:). Loud, not skipped.
|
|
120
|
+
if (/^deliverables:/m.test(split.fm)) {
|
|
121
|
+
broken.push({ file, message: String(err.message || err).split('\n')[0] })
|
|
122
|
+
}
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
113
125
|
if (!fm || typeof fm !== 'object') continue
|
|
114
126
|
const rows = Array.isArray(fm.deliverables) ? fm.deliverables : []
|
|
115
127
|
if (rows.length === 0) continue // no schedule = not a promise
|
|
@@ -132,6 +144,17 @@ for (const file of readdirSync(TEXT).sort()) {
|
|
|
132
144
|
})
|
|
133
145
|
}
|
|
134
146
|
|
|
147
|
+
if (broken.length) {
|
|
148
|
+
console.error(
|
|
149
|
+
`[promise-manifest] ${broken.length} promise file(s) declare deliverables: but their\n` +
|
|
150
|
+
`frontmatter does not parse — each would be MISSING from the site, silently:\n` +
|
|
151
|
+
broken.map((b) => ` text/${b.file} — ${b.message}`).join('\n') +
|
|
152
|
+
`\nFix the YAML (usually an unescaped " or \\ inside a double-quoted accept:,\n` +
|
|
153
|
+
`or an unquoted title: containing ": "). Nothing was written.`,
|
|
154
|
+
)
|
|
155
|
+
process.exit(4)
|
|
156
|
+
}
|
|
157
|
+
|
|
135
158
|
// The index carries the claim and the count and nothing else — it is imported by
|
|
136
159
|
// the Astro route, so every field costs worker bundle on every tasks request.
|
|
137
160
|
const index = promises.map((p) => ({
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# release.sh — a deploy worktree of beautifully tested code, shipped without
|
|
3
|
+
# re-running the suite.
|
|
4
|
+
#
|
|
5
|
+
# manifest: monorepo-only
|
|
6
|
+
#
|
|
7
|
+
# THE ASK. "We should have a deploy worktree with beautifully tested code that we
|
|
8
|
+
# can deploy without testing." The second half is the part that needs care: not
|
|
9
|
+
# *without testing* — without RE-RUNNING the tests, because the tree already
|
|
10
|
+
# carries a green full-suite receipt and the receipt is bound to the tree.
|
|
11
|
+
#
|
|
12
|
+
# WHAT THE RECEIPT IS. test-cached.sh memoises a full-suite PASS under a key over
|
|
13
|
+
# the exact inputs: the vitest argv + the content of every selected test file +
|
|
14
|
+
# `git diff HEAD` BY CONTENT + untracked contents + HEAD + the gitignored .env
|
|
15
|
+
# files + the test-file set. Only passes are ever recorded. So a stamp existing
|
|
16
|
+
# for a tree is a proof that this exact tree ran the whole suite green, and it is
|
|
17
|
+
# the same object deploy.sh's own vitest gate looks for. release.sh does not
|
|
18
|
+
# invent a second notion of "tested" — it reads deploy's.
|
|
19
|
+
#
|
|
20
|
+
# promote [<sha>] refuse unless <sha>'s tree carries that receipt; then
|
|
21
|
+
# fast-forward `release` to it and materialise .release/
|
|
22
|
+
# ship run ./deploy FROM .release/, where the suite gate is a
|
|
23
|
+
# memo HIT rather than a 400-second re-run
|
|
24
|
+
# --self-test the red proof (three refusals, sandboxed)
|
|
25
|
+
#
|
|
26
|
+
# WHY .release/ AND NOT .do-worktrees/. do-auto.sh's exit-time --gc sweep deletes
|
|
27
|
+
# worktrees under .do-worktrees/ (project_do_auto_gc_collateral_bug). A release
|
|
28
|
+
# tree that a neighbouring /do cycle can garbage-collect mid-deploy is not a
|
|
29
|
+
# release tree. .release/ is gitignored and swept by nothing.
|
|
30
|
+
#
|
|
31
|
+
# WHY THE KEY IS COMPUTED BY RUNNING THE REAL MACHINERY. The full suite runs as
|
|
32
|
+
# TWO lanes (test-lanes.sh) with different argv, so it has TWO keys — and a third
|
|
33
|
+
# shape if the `real TypeDB` marker ever matches nothing (test-lanes falls back to
|
|
34
|
+
# a single run). Spelling those lanes out here would be a second definition that
|
|
35
|
+
# drifts silently, which is the exact bug test-full.sh was created to kill. So
|
|
36
|
+
# promote runs test-full.sh with TEST_CACHE_KEY_ONLY=1: every lane's test-cached.sh
|
|
37
|
+
# prints its key and exits without running anything. Whatever the lanes are, the
|
|
38
|
+
# keys are theirs.
|
|
39
|
+
#
|
|
40
|
+
# THE DIRTY-MAIN COROLLARY, measured 2026-09-05. The key hashes `git diff HEAD`
|
|
41
|
+
# by content, and main carried 9 uncommitted paths. A receipt minted on main is
|
|
42
|
+
# therefore a receipt for a tree that will never ship. The mint has to happen on
|
|
43
|
+
# a CLEAN tree at the sha — which is what .release/ is. This is also the honest
|
|
44
|
+
# security property: `./deploy --allow-dirty` from main ships uncommitted code;
|
|
45
|
+
# `ship` structurally cannot.
|
|
46
|
+
#
|
|
47
|
+
# Exit codes are distinct and named, because "it refused" is not a finding:
|
|
48
|
+
# 0 ok · 2 usage · 3 no receipt · 4 tsc red · 5 not a fast-forward
|
|
49
|
+
# 6 dirty release tree · 7 tsc could not run (lock/timeout) · 8 deploy failed
|
|
50
|
+
# 9 no receipt key could be computed
|
|
51
|
+
set -uo pipefail
|
|
52
|
+
|
|
53
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
54
|
+
WT="${RELEASE_WORKTREE:-$ROOT/.release}"
|
|
55
|
+
BRANCH="${RELEASE_BRANCH:-release}"
|
|
56
|
+
CACHE_DIR="${TEST_CACHE_DIR:-${TMPDIR:-/tmp}/one-test-cache}"
|
|
57
|
+
# Bounded, unlike tsc-cached's 900s default. deploy.sh deliberately does NOT route
|
|
58
|
+
# its typechecks through tsc-cached because a /do cycle holding the per-folder
|
|
59
|
+
# gate_lock would turn a ship gate red for lock contention rather than for code.
|
|
60
|
+
# promote reads the cache but must never inherit that stall: 120s, then refuse
|
|
61
|
+
# with its own exit code (7), which says "unrun", never "green".
|
|
62
|
+
TSC_WAIT="${RELEASE_TSC_WAIT:-120}"
|
|
63
|
+
TSC_SERVICES=(one.ie/web api sync channels pay/backend)
|
|
64
|
+
|
|
65
|
+
say() { printf '%s\n' "$*"; }
|
|
66
|
+
ok() { printf ' ok %s\n' "$*"; }
|
|
67
|
+
bad() { printf ' RED %s\n' "$*" >&2; }
|
|
68
|
+
|
|
69
|
+
# Everything gitignored that a checkout does not get but the gates read. The
|
|
70
|
+
# node_modules half is do-auto.sh's _link_deps set; the .env half is WIDER than
|
|
71
|
+
# _link_deps on purpose. test-cached.sh's key hashes one.ie/web/.env, .env.local
|
|
72
|
+
# AND .env.test, printing "ABSENT" for a missing one — so a worktree that links
|
|
73
|
+
# .env but not .env.local computes a DIFFERENT key from the main tree for the same
|
|
74
|
+
# commit, and every receipt misses for a reason nothing prints. Measured
|
|
75
|
+
# 2026-09-05: one.ie/web/.env.local exists on this box and _link_deps does not
|
|
76
|
+
# link it.
|
|
77
|
+
_link_deps() {
|
|
78
|
+
local wt="$1" nm
|
|
79
|
+
for nm in node_modules one.ie/web/node_modules packages/node_modules \
|
|
80
|
+
packages/sdk/dist packages/sdk/node_modules \
|
|
81
|
+
pay/backend/node_modules channels/node_modules \
|
|
82
|
+
api/node_modules sync/node_modules \
|
|
83
|
+
one.ie/web/.env one.ie/web/.env.local one.ie/web/.env.test \
|
|
84
|
+
.env.local pay/backend/.env channels/.env; do
|
|
85
|
+
if [ -e "$ROOT/$nm" ] && [ ! -e "$wt/$nm" ]; then
|
|
86
|
+
mkdir -p "$wt/$(dirname "$nm")"
|
|
87
|
+
ln -s "$ROOT/$nm" "$wt/$nm" 2>/dev/null && say " linked $nm"
|
|
88
|
+
fi
|
|
89
|
+
done
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# The receipt keys for the tree standing in $1. Prints one 64-hex key per lane.
|
|
93
|
+
# TEST_CACHE_KEY_ONLY=1 makes every test-cached.sh in the fan-out print its key
|
|
94
|
+
# and exit 0 without running vitest, so this costs ~1s and cannot start a suite.
|
|
95
|
+
_receipt_keys() {
|
|
96
|
+
( cd "$1" && TEST_CACHE_KEY_ONLY=1 bash .claude/scripts/test-full.sh 2>/dev/null ) \
|
|
97
|
+
| grep -Eo '^[0-9a-f]{64}$'
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# The stamp for a key, if one exists. test-cached.sh mangles the whole stamp path
|
|
101
|
+
# into the filename, so match on the key rather than reproducing that mangling —
|
|
102
|
+
# the key is a sha256 and is unique on its own.
|
|
103
|
+
_stamp_for() {
|
|
104
|
+
local k="$1" f
|
|
105
|
+
for f in "$CACHE_DIR"/*"$k".pass; do [ -f "$f" ] && { printf '%s\n' "$f"; return 0; }; done
|
|
106
|
+
return 1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# ── promote ─────────────────────────────────────────────────────────────────
|
|
110
|
+
cmd_promote() {
|
|
111
|
+
local sha="${1:-}"
|
|
112
|
+
sha="$(git -C "$ROOT" rev-parse --verify "${sha:-HEAD}^{commit}" 2>/dev/null)" || {
|
|
113
|
+
bad "not a commit: ${1:-HEAD}"; return 2; }
|
|
114
|
+
|
|
115
|
+
say "release: promote ${sha:0:9}"
|
|
116
|
+
|
|
117
|
+
# 1. fast-forward only. A release branch that can move backwards is not a record.
|
|
118
|
+
local prev="" dist="(first release)"
|
|
119
|
+
if prev="$(git -C "$ROOT" rev-parse --verify "$BRANCH" 2>/dev/null)"; then
|
|
120
|
+
if [ "$prev" != "$sha" ] && ! git -C "$ROOT" merge-base --is-ancestor "$prev" "$sha"; then
|
|
121
|
+
bad "$sha is not a fast-forward of $BRANCH (${prev:0:9}) — refusing to rewind the release record"
|
|
122
|
+
return 5
|
|
123
|
+
fi
|
|
124
|
+
dist="$(git -C "$ROOT" rev-list --count "$prev..$sha") commit(s) since ${prev:0:9}"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# 2. materialise the tree. Idempotent: create, or move an existing worktree.
|
|
128
|
+
#
|
|
129
|
+
# THE DIRTY CHECK COMES FIRST, and the ordering is the whole point. The obvious
|
|
130
|
+
# shape — reset --hard, then check clean — can never report a dirty tree,
|
|
131
|
+
# because the reset already destroyed the evidence, and the operator's work
|
|
132
|
+
# with it. Silently discarding what is standing in the tree is not a refusal;
|
|
133
|
+
# it is data loss that reads as success.
|
|
134
|
+
if [ -d "$WT/.git" ] || [ -f "$WT/.git" ]; then
|
|
135
|
+
local dirty; dirty="$(git -C "$WT" status --porcelain)"
|
|
136
|
+
if [ -n "$dirty" ]; then
|
|
137
|
+
bad "release tree is dirty — the receipt would not describe what ships:"
|
|
138
|
+
printf '%s\n' "$dirty" | sed 's/^/ /' >&2
|
|
139
|
+
return 6
|
|
140
|
+
fi
|
|
141
|
+
# -B moves the branch and HEAD together. A plain `git branch -f` is REFUSED
|
|
142
|
+
# by git for a branch checked out in a worktree ("cannot force update the
|
|
143
|
+
# branch ... checked out at"), so the move is made from inside the worktree.
|
|
144
|
+
git -C "$WT" checkout -q -B "$BRANCH" "$sha" \
|
|
145
|
+
|| { bad "could not move $WT to $sha"; return 5; }
|
|
146
|
+
else
|
|
147
|
+
git -C "$ROOT" worktree add -B "$BRANCH" "$WT" "$sha" >/dev/null 2>&1 \
|
|
148
|
+
|| { bad "could not add worktree $WT on $BRANCH"; return 5; }
|
|
149
|
+
fi
|
|
150
|
+
_link_deps "$WT"
|
|
151
|
+
|
|
152
|
+
# 3. after the move it must still be clean — a checkout that left something
|
|
153
|
+
# behind is not a tree any receipt describes.
|
|
154
|
+
local dirty2; dirty2="$(git -C "$WT" status --porcelain)"
|
|
155
|
+
if [ -n "$dirty2" ]; then
|
|
156
|
+
bad "release tree is dirty after checkout:"
|
|
157
|
+
printf '%s\n' "$dirty2" | sed 's/^/ /' >&2
|
|
158
|
+
return 6
|
|
159
|
+
fi
|
|
160
|
+
ok "release tree clean at $(git -C "$WT" rev-parse --short HEAD)"
|
|
161
|
+
|
|
162
|
+
# 4. the receipt.
|
|
163
|
+
local keys; keys="$(_receipt_keys "$WT")"
|
|
164
|
+
if [ -z "$keys" ]; then
|
|
165
|
+
bad "no receipt key could be computed — test-full.sh printed no lane key"
|
|
166
|
+
return 9
|
|
167
|
+
fi
|
|
168
|
+
local n_keys=0 missing=0 stamps=()
|
|
169
|
+
while IFS= read -r k; do
|
|
170
|
+
[ -n "$k" ] || continue
|
|
171
|
+
n_keys=$((n_keys+1))
|
|
172
|
+
local s
|
|
173
|
+
if s="$(_stamp_for "$k")"; then
|
|
174
|
+
stamps+=("$s")
|
|
175
|
+
ok "receipt ${k:0:12} — full suite PASSED $(cat "$s" 2>/dev/null)"
|
|
176
|
+
else
|
|
177
|
+
bad "receipt ${k:0:12} — NO green full-suite stamp for this tree"
|
|
178
|
+
missing=$((missing+1))
|
|
179
|
+
fi
|
|
180
|
+
done <<< "$keys"
|
|
181
|
+
if (( missing )); then
|
|
182
|
+
say ""
|
|
183
|
+
say " $missing of $n_keys lane(s) have no receipt. Mint one on this exact tree:"
|
|
184
|
+
say " cd $WT && DEPLOY_YES=1 bash .claude/scripts/deploy.sh --gates-only"
|
|
185
|
+
say " (a receipt minted on a DIRTY main describes a tree that never ships —"
|
|
186
|
+
say " the mint has to happen here, on the clean tree at $(git -C "$WT" rev-parse --short HEAD))"
|
|
187
|
+
return 3
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
# 5. tsc, for the same tree. The suite does not typecheck; deploy does it as a
|
|
191
|
+
# separate gate, and a promoted tree should already know the answer.
|
|
192
|
+
if [ "${RELEASE_SKIP_TSC:-0}" != "1" ]; then
|
|
193
|
+
local svc out rc
|
|
194
|
+
for svc in "${TSC_SERVICES[@]}"; do
|
|
195
|
+
out="$( cd "$WT" && TSC_CACHE_WAIT="$TSC_WAIT" bash .claude/scripts/tsc-cached.sh "$svc" 2>/dev/null )"
|
|
196
|
+
rc=$?
|
|
197
|
+
if (( rc != 0 )); then
|
|
198
|
+
bad "tsc $svc — could NOT run (lock or timeout); an unrun check is not a pass"
|
|
199
|
+
return 7
|
|
200
|
+
fi
|
|
201
|
+
if [ "$out" != "0" ]; then bad "tsc $svc — $out error(s)"; return 4; fi
|
|
202
|
+
ok "tsc $svc — 0 errors"
|
|
203
|
+
done
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
say ""
|
|
207
|
+
say " promoted: $sha"
|
|
208
|
+
say " branch: $BRANCH -> $(git -C "$ROOT" rev-parse --short "$BRANCH")"
|
|
209
|
+
say " worktree: $WT"
|
|
210
|
+
say " distance: $dist"
|
|
211
|
+
local s; for s in "${stamps[@]}"; do say " receipt: $s"; done
|
|
212
|
+
say ""
|
|
213
|
+
say " ship it: bash .claude/scripts/release.sh ship"
|
|
214
|
+
return 0
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# ── ship ────────────────────────────────────────────────────────────────────
|
|
218
|
+
cmd_ship() {
|
|
219
|
+
[ -d "$WT" ] || { bad "no release worktree at $WT — run promote first"; return 2; }
|
|
220
|
+
local dirty; dirty="$(git -C "$WT" status --porcelain)"
|
|
221
|
+
[ -z "$dirty" ] || { bad "release tree is dirty — refusing to ship an unproven tree"; return 6; }
|
|
222
|
+
local sha; sha="$(git -C "$WT" rev-parse HEAD)"
|
|
223
|
+
say "release: ship ${sha:0:9} from $WT"
|
|
224
|
+
|
|
225
|
+
# Seconds, ships nothing, and catches a credential ladder that cannot resolve
|
|
226
|
+
# from a worktree BEFORE the seven-minute pipeline dies at the same place.
|
|
227
|
+
say ""; say "── credential pre-flight ──"
|
|
228
|
+
( cd "$WT" && bash .claude/scripts/deploy.sh --check-creds ) || {
|
|
229
|
+
bad "credential ladder did not verify from $WT"; return 8; }
|
|
230
|
+
|
|
231
|
+
say ""; say "── deploy ──"
|
|
232
|
+
local t0=$SECONDS
|
|
233
|
+
# NO --skip-tests. The whole point is that the suite gate hits the memo and
|
|
234
|
+
# reports "REUSED" — forcing the skip would throw away the proof and ship on a
|
|
235
|
+
# promise instead of a receipt.
|
|
236
|
+
#
|
|
237
|
+
# --changed BY DEFAULT. A service with no commit under its svc_paths since its
|
|
238
|
+
# last clean deploy has nothing to ship; deploy.sh's per-service markers live
|
|
239
|
+
# under $WT/.deploy-logs, so they describe THIS tree's ships, never main's.
|
|
240
|
+
# svc_paths already folds packages/ into astro and channels (an SDK-only
|
|
241
|
+
# commit re-ships both), which is what makes the skip safe. First ship from a
|
|
242
|
+
# fresh .release/ has no markers and ships all five. RELEASE_ALL=1 forces it.
|
|
243
|
+
local changed=(--changed); [ "${RELEASE_ALL:-0}" = "1" ] && changed=(--all)
|
|
244
|
+
( cd "$WT" && DEPLOY_YES=1 bash .claude/scripts/deploy.sh "${changed[@]}" )
|
|
245
|
+
local rc=$?
|
|
246
|
+
say ""
|
|
247
|
+
say " ship wall-clock: $((SECONDS - t0))s"
|
|
248
|
+
(( rc == 0 )) || { bad "deploy exited $rc"; return 8; }
|
|
249
|
+
return 0
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
# ── --self-test — the red proof ─────────────────────────────────────────────
|
|
253
|
+
# Three refusals, each asserted on its OWN exit code: three refusals that all
|
|
254
|
+
# exit 1 would pass a checker that only asks "did it refuse?".
|
|
255
|
+
#
|
|
256
|
+
# Sandboxed on every axis that could touch the real thing: its own TEST_CACHE_DIR
|
|
257
|
+
# (so a planted stamp can never be replayed by a future deploy), its own worktree
|
|
258
|
+
# path, its own throwaway branch. The last assertion is that the REAL cache gained
|
|
259
|
+
# zero files — a fake receipt leaking into it is the single worst outcome this
|
|
260
|
+
# script could have.
|
|
261
|
+
_self_test() {
|
|
262
|
+
local fails=0
|
|
263
|
+
local sbcache sbwt sbbranch base
|
|
264
|
+
sbcache="$(mktemp -d)"; sbwt="$(mktemp -d)/rel"; sbbranch="release-selftest-$$"
|
|
265
|
+
base="$(git -C "$ROOT" rev-parse --verify HEAD)"
|
|
266
|
+
local real_before real_after
|
|
267
|
+
real_before=$(ls "${TMPDIR:-/tmp}/one-test-cache" 2>/dev/null | wc -l | tr -d ' ')
|
|
268
|
+
|
|
269
|
+
_run() { TEST_CACHE_DIR="$sbcache" RELEASE_WORKTREE="$sbwt" RELEASE_BRANCH="$sbbranch" \
|
|
270
|
+
RELEASE_SKIP_TSC=1 bash "${BASH_SOURCE[0]}" promote "$@" >/dev/null 2>&1; }
|
|
271
|
+
_assert() { # <expected-rc> <actual-rc> <label>
|
|
272
|
+
if [ "$1" = "$2" ]; then echo " ok $3 (exit $2)"
|
|
273
|
+
else echo " FAIL $3 — expected exit $1, got $2"; fails=$((fails+1)); fi
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
# 1. a tree with no receipt is REFUSED (3)
|
|
277
|
+
_run "$base"; _assert 3 $? "no receipt refuses"
|
|
278
|
+
|
|
279
|
+
# 2. plant a PASS stamp for every lane key of that tree — promotion succeeds
|
|
280
|
+
local k
|
|
281
|
+
while IFS= read -r k; do
|
|
282
|
+
[ -n "$k" ] && date -u +%Y-%m-%dT%H:%M:%SZ > "$sbcache/selftest-$k.pass"
|
|
283
|
+
done < <(TEST_CACHE_KEY_ONLY=1 bash -c "cd '$sbwt' && bash .claude/scripts/test-full.sh" 2>/dev/null \
|
|
284
|
+
| grep -Eo '^[0-9a-f]{64}$')
|
|
285
|
+
_run "$base"; _assert 0 $? "a planted receipt promotes"
|
|
286
|
+
|
|
287
|
+
# 3. the receipt is bound to the TREE. Append a byte (never `touch` — the key is
|
|
288
|
+
# content, never mtime, so a touch would move nothing and look like a bug),
|
|
289
|
+
# commit it so the tree is clean again, and the same stamps must no longer
|
|
290
|
+
# answer for the new tree.
|
|
291
|
+
local probe="$sbwt/text/dictionary.md"
|
|
292
|
+
[ -f "$probe" ] || probe="$(git -C "$sbwt" ls-files | head -1 | sed "s|^|$sbwt/|")"
|
|
293
|
+
printf '\n<!-- release.sh self-test -->\n' >> "$probe"
|
|
294
|
+
git -C "$sbwt" add -- "${probe#$sbwt/}" >/dev/null 2>&1
|
|
295
|
+
git -C "$sbwt" -c user.email=selftest@one.ie -c user.name=selftest \
|
|
296
|
+
commit -q -m "release.sh self-test: move the tree" >/dev/null 2>&1
|
|
297
|
+
local moved; moved="$(git -C "$sbwt" rev-parse HEAD)"
|
|
298
|
+
_run "$moved"; _assert 3 $? "a moved tree loses the receipt (key follows content)"
|
|
299
|
+
|
|
300
|
+
# 4. and a DIRTY tree is refused before any key is computed
|
|
301
|
+
printf 'dirty\n' >> "$probe"
|
|
302
|
+
_run "$moved"; _assert 6 $? "a dirty release tree refuses"
|
|
303
|
+
|
|
304
|
+
# 5. nothing leaked into the real cache
|
|
305
|
+
real_after=$(ls "${TMPDIR:-/tmp}/one-test-cache" 2>/dev/null | wc -l | tr -d ' ')
|
|
306
|
+
if [ "$real_before" = "$real_after" ]; then ok "no stamp leaked into the real cache ($real_after files)"
|
|
307
|
+
else echo " FAIL self-test wrote to the REAL cache ($real_before -> $real_after)"; fails=$((fails+1)); fi
|
|
308
|
+
|
|
309
|
+
git -C "$ROOT" worktree remove --force "$sbwt" >/dev/null 2>&1
|
|
310
|
+
git -C "$ROOT" branch -D "$sbbranch" >/dev/null 2>&1
|
|
311
|
+
rm -rf "$sbcache"
|
|
312
|
+
[ "$fails" -eq 0 ] && { echo "release: self-test PASS"; return 0; }
|
|
313
|
+
echo "release: self-test FAILED ($fails)"; return 1
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case "${1:-}" in
|
|
317
|
+
promote) shift; cmd_promote "${1:-}"; exit $? ;;
|
|
318
|
+
ship) cmd_ship; exit $? ;;
|
|
319
|
+
--self-test) _self_test; exit $? ;;
|
|
320
|
+
-h|--help|"") sed -n '2,30p' "${BASH_SOURCE[0]}"; exit 0 ;;
|
|
321
|
+
*) echo "release.sh: unknown verb '${1}' (try --help)" >&2; exit 2 ;;
|
|
322
|
+
esac
|