@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.
Files changed (263) hide show
  1. package/agents/abm-strategist.md +89 -0
  2. package/agents/ads-meta.md +97 -0
  3. package/agents/analyst.md +173 -0
  4. package/agents/architect.md +280 -0
  5. package/agents/brand-guardian.md +88 -0
  6. package/agents/brand-strategist.md +92 -0
  7. package/agents/campaign-content.md +90 -0
  8. package/agents/campaign-email.md +88 -0
  9. package/agents/campaign-sms.md +87 -0
  10. package/agents/campaign-social.md +87 -0
  11. package/agents/cco.md +99 -0
  12. package/agents/ceo.md +106 -0
  13. package/agents/chairman.md +120 -0
  14. package/agents/cmo.md +101 -0
  15. package/agents/community-greeter.md +89 -0
  16. package/agents/community-moderator.md +92 -0
  17. package/agents/compliance.md +92 -0
  18. package/agents/copywriter.md +100 -0
  19. package/agents/creative-strategist.md +95 -0
  20. package/agents/cro.md +98 -0
  21. package/agents/cto.md +267 -0
  22. package/agents/customer-interviewer.md +93 -0
  23. package/agents/customer-researcher.md +89 -0
  24. package/agents/customer-success-manager.md +90 -0
  25. package/agents/customer-trainer.md +90 -0
  26. package/agents/cxo.md +100 -0
  27. package/agents/demand-creator.md +89 -0
  28. package/agents/demo-mover.md +83 -0
  29. package/agents/demo-specialist.md +89 -0
  30. package/agents/demo-thai-family-law.md +87 -0
  31. package/agents/designer.md +112 -0
  32. package/agents/discovery-caller.md +98 -0
  33. package/agents/doctor.md +139 -0
  34. package/agents/educate-coach.md +100 -0
  35. package/agents/elevate-tutor.md +83 -0
  36. package/agents/email-lifecycle-marketer.md +91 -0
  37. package/agents/engage-specialist.md +99 -0
  38. package/agents/events-coordinator.md +90 -0
  39. package/agents/foundation-builder.md +94 -0
  40. package/agents/funnel-architect.md +89 -0
  41. package/agents/gift-creator.md +100 -0
  42. package/agents/google-ads.md +103 -0
  43. package/agents/guide.md +292 -0
  44. package/agents/helpdesk-dispatcher.md +88 -0
  45. package/agents/hook-specialist.md +91 -0
  46. package/agents/identify-optimizer.md +101 -0
  47. package/agents/implementer.md +305 -0
  48. package/agents/incident-commander.md +120 -0
  49. package/agents/insights-lead.md +93 -0
  50. package/agents/journey-runner.md +113 -0
  51. package/agents/linkedin-ads.md +123 -0
  52. package/agents/live-sales-chat.md +90 -0
  53. package/agents/market-researcher.md +88 -0
  54. package/agents/media-buyer.md +96 -0
  55. package/agents/movers-customer-researcher.md +95 -0
  56. package/agents/movers-foundation-builder.md +96 -0
  57. package/agents/movers-market-researcher.md +97 -0
  58. package/agents/movers-pricing-strategist.md +171 -0
  59. package/agents/nurture-architect.md +99 -0
  60. package/agents/offer-architect.md +92 -0
  61. package/agents/onboarder.md +222 -0
  62. package/agents/onboarding-specialist.md +92 -0
  63. package/agents/operations-dashboard.md +98 -0
  64. package/agents/perf-engineer.md +348 -0
  65. package/agents/playbook-writer.md +71 -0
  66. package/agents/plg-strategist.md +91 -0
  67. package/agents/positioning-architect.md +88 -0
  68. package/agents/press-officer.md +89 -0
  69. package/agents/pricing-strategist.md +94 -0
  70. package/agents/privacy-officer.md +92 -0
  71. package/agents/referral-manager.md +91 -0
  72. package/agents/refine-analyst.md +102 -0
  73. package/agents/release-manager.md +261 -0
  74. package/agents/renewals-upsell-rep.md +90 -0
  75. package/agents/review-engineer.md +327 -0
  76. package/agents/rewards-steward.md +82 -0
  77. package/agents/sales-call-coach.md +94 -0
  78. package/agents/sales-closer.md +98 -0
  79. package/agents/security-auditor.md +313 -0
  80. package/agents/sell-closer.md +101 -0
  81. package/agents/share-amplifier.md +99 -0
  82. package/agents/social-media-manager.md +92 -0
  83. package/agents/storyteller.md +250 -0
  84. package/agents/strategist.md +83 -0
  85. package/agents/strategy-aligner.md +94 -0
  86. package/agents/support-agent.md +90 -0
  87. package/agents/tagger.md +245 -0
  88. package/agents/tech-writer.md +216 -0
  89. package/agents/test-engineer.md +243 -0
  90. package/agents/tiktok-ads.md +105 -0
  91. package/agents/tracking-engineer.md +92 -0
  92. package/agents/upsell-strategist.md +101 -0
  93. package/agents/voice.md +85 -0
  94. package/agents/w4-tools.md +153 -0
  95. package/agents/w4-verify.md +20 -138
  96. package/agents/workflow-optimiser.md +84 -0
  97. package/commands/close.md +814 -160
  98. package/commands/create.md +2 -2
  99. package/commands/deploy.md +554 -175
  100. package/commands/do.md +119 -109
  101. package/commands/fast.md +67 -0
  102. package/commands/improve.md +2 -2
  103. package/commands/one.md +418 -0
  104. package/commands/see.md +1 -1
  105. package/commands/sync.md +1 -1
  106. package/commands/tasks.md +222 -0
  107. package/commands/vespio.md +110 -0
  108. package/commands/vespio.remote.md +227 -0
  109. package/hooks/hooks.json +25 -79
  110. package/hooks/lib/governor-escape-match.sh +88 -0
  111. package/hooks/lib/hook.sh +4 -13
  112. package/hooks/lib/signal.sh +9 -2
  113. package/hooks/scripts/branch-pin.sh +6 -0
  114. package/hooks/scripts/config-protect.sh +6 -0
  115. package/hooks/scripts/do-outcome-gate.sh +19 -2
  116. package/hooks/scripts/git-add-guard.sh +43 -2
  117. package/hooks/scripts/governor-escape.sh +96 -0
  118. package/hooks/scripts/load-guard.sh +127 -0
  119. package/hooks/scripts/post-edit-check.sh +36 -29
  120. package/hooks/scripts/session-start.sh +34 -80
  121. package/hooks/scripts/task-complete-verify.sh +46 -40
  122. package/package.json +1 -1
  123. package/rules/documentation.md +9 -0
  124. package/scripts/ad-copy-lint.sh +656 -0
  125. package/scripts/agentverse-audit.sh +256 -0
  126. package/scripts/asi-walk.sh +435 -0
  127. package/scripts/astro-build-cached.sh +143 -0
  128. package/scripts/blocks-manifest-cached.sh +100 -0
  129. package/scripts/blocks-manifest.mjs +497 -0
  130. package/scripts/blocks-ratchet.sh +116 -0
  131. package/scripts/blocks-render-probe.mjs +529 -0
  132. package/scripts/blocks-usage.mjs +334 -0
  133. package/scripts/chat-context-check.sh +89 -0
  134. package/scripts/close-metrics.sh +558 -0
  135. package/scripts/close-owner.sh +268 -0
  136. package/scripts/db-sync-lock-check.sh +116 -0
  137. package/scripts/deploy-dev.sh +35 -0
  138. package/scripts/deploy-emit.sh +298 -0
  139. package/scripts/deploy-gate-check.sh +128 -0
  140. package/scripts/deploy-ready.sh +78 -0
  141. package/scripts/deploy-record.sh +605 -0
  142. package/scripts/deploy.sh +1273 -0
  143. package/scripts/do-auto.sh +598 -48
  144. package/scripts/do-brief.sh +113 -0
  145. package/scripts/do-close.sh +1137 -0
  146. package/scripts/do-commit.sh +75 -0
  147. package/scripts/do-consumer-sweep.sh +18 -1
  148. package/scripts/do-cycle-shape-check.sh +160 -0
  149. package/scripts/do-decide.sh +476 -0
  150. package/scripts/do-derive-check.sh +436 -0
  151. package/scripts/do-fleet.sh +106 -28
  152. package/scripts/do-folder.sh +10 -1
  153. package/scripts/do-next.sh +106 -0
  154. package/scripts/do-orchestrate.sh +17 -5
  155. package/scripts/do-plan-json.mjs +201 -0
  156. package/scripts/do-plan-json.sh +8 -0
  157. package/scripts/do-preflight.sh +117 -0
  158. package/scripts/do-project.sh +157 -0
  159. package/scripts/do-prove-selftest.sh +108 -0
  160. package/scripts/do-prove.sh +295 -23
  161. package/scripts/do-rank.py +31 -1
  162. package/scripts/do-recon-cache.sh +7 -1
  163. package/scripts/do-recon-pack.sh +196 -0
  164. package/scripts/do-reconcile.sh +121 -4
  165. package/scripts/do-signal.sh +280 -23
  166. package/scripts/do-smoke.sh +18 -1
  167. package/scripts/do-test-gate.sh +80 -0
  168. package/scripts/do-tick.sh +102 -0
  169. package/scripts/do-tier.sh +6 -0
  170. package/scripts/do-triage.sh +182 -0
  171. package/scripts/do-ui-gate.sh +1 -1
  172. package/scripts/do-w4-gates.sh +451 -0
  173. package/scripts/do-walk.sh +12 -1
  174. package/scripts/env-sync.sh +173 -0
  175. package/scripts/factory-brief-check.sh +330 -0
  176. package/scripts/factory-check.sh +68 -14
  177. package/scripts/factory-close-check.sh +257 -0
  178. package/scripts/factory-emit.sh +211 -0
  179. package/scripts/factory-executor-check.mjs +353 -0
  180. package/scripts/factory-peak.sh +301 -0
  181. package/scripts/factory-repo.sh +118 -3
  182. package/scripts/factory-review-check.mjs +61 -0
  183. package/scripts/factory-ship.sh +61 -0
  184. package/scripts/factory-tasks-check.sh +18 -1
  185. package/scripts/factory-turn.sh +326 -0
  186. package/scripts/factory-walk.sh +396 -0
  187. package/scripts/factory-width.sh +57 -0
  188. package/scripts/fade-toxic.sh +4 -3
  189. package/scripts/fixtures/factory-brief-real.md +44 -0
  190. package/scripts/fixtures/triage-dupe.md +5 -0
  191. package/scripts/fleet-manifest.mjs +108 -0
  192. package/scripts/fleet-status.sh +110 -0
  193. package/scripts/full-suite-paths-check.sh +144 -0
  194. package/scripts/gate-reaper-check.sh +98 -0
  195. package/scripts/gate-reaper.sh +125 -0
  196. package/scripts/gate-run.sh +73 -0
  197. package/scripts/gc-content-check.sh +140 -0
  198. package/scripts/gen-dev-config.py +20 -0
  199. package/scripts/govern-bound-check.sh +60 -0
  200. package/scripts/govern-claims-check.sh +233 -0
  201. package/scripts/govern-mem-check.sh +290 -0
  202. package/scripts/governor-doors-check.sh +362 -0
  203. package/scripts/governor-escape-check.sh +171 -0
  204. package/scripts/health.sh +413 -0
  205. package/scripts/id-inventory.mjs +418 -0
  206. package/scripts/land.sh +551 -0
  207. package/scripts/lib/gc-finished.sh +77 -0
  208. package/scripts/lib/govern.sh +361 -0
  209. package/scripts/lib/govern.ts +756 -0
  210. package/scripts/lighthouse-run.sh +187 -0
  211. package/scripts/livekit-live-check.sh +61 -0
  212. package/scripts/livekit-ratchet.sh +46 -0
  213. package/scripts/load-guard-check.sh +49 -0
  214. package/scripts/machine-check.sh +102 -0
  215. package/scripts/machine-watch.sh +177 -0
  216. package/scripts/one-agents.mjs +415 -0
  217. package/scripts/one-resume.sh +78 -0
  218. package/scripts/orphan-baseline.json +182 -0
  219. package/scripts/orphan-modules.mjs +179 -0
  220. package/scripts/pr-body.sh +335 -0
  221. package/scripts/preview-fd-check.sh +289 -0
  222. package/scripts/promise-manifest.mjs +24 -1
  223. package/scripts/release.sh +322 -0
  224. package/scripts/roles-check.sh +946 -0
  225. package/scripts/sdk-build-cached.sh +64 -0
  226. package/scripts/signal-watch.sh +241 -0
  227. package/scripts/skills-publish.sh +94 -0
  228. package/scripts/speed-cache-check.sh +173 -0
  229. package/scripts/speed-check.mjs +907 -0
  230. package/scripts/speed-parity-check.sh +648 -0
  231. package/scripts/speed-waterfall-check.sh +355 -0
  232. package/scripts/substrate-env-parity.mjs +156 -0
  233. package/scripts/tasks-claim-race.mjs +108 -0
  234. package/scripts/tasks-loop.sh +185 -0
  235. package/scripts/test-cached.sh +255 -0
  236. package/scripts/test-full.sh +87 -0
  237. package/scripts/test-honesty.mjs +137 -0
  238. package/scripts/test-lanes.sh +166 -0
  239. package/scripts/test-speed.sh +94 -0
  240. package/scripts/triage-shape-check.sh +149 -0
  241. package/scripts/tsc-cached.sh +179 -0
  242. package/scripts/typedb-flake-check.sh +197 -0
  243. package/scripts/urls-lint.sh +15 -0
  244. package/scripts/verify-fast.sh +445 -0
  245. package/scripts/vespio-sync.sh +149 -0
  246. package/scripts/wf-check.mjs +104 -1
  247. package/scripts/worktree-preview.sh +879 -0
  248. package/scripts/worktree-up.sh +208 -0
  249. package/skills/livekit-agents/SKILL.md +285 -0
  250. package/skills/livekit-agents/references/freshness-rules.md +168 -0
  251. package/skills/shadcn/SKILL.md +1 -1
  252. package/skills/signal/SKILL.md +0 -1
  253. package/skills/voice/SKILL.md +94 -6
  254. package/skills/voice/corpus-check.sh +87 -0
  255. package/hooks/scripts/compact-hint.sh +0 -35
  256. package/hooks/scripts/gate-guard.sh +0 -83
  257. package/hooks/scripts/read-tracker.sh +0 -26
  258. package/hooks/scripts/session-end-verify.sh +0 -51
  259. package/hooks/scripts/stop-reflect.sh +0 -140
  260. package/hooks/scripts/sync-priority-todo.sh +0 -57
  261. package/hooks/scripts/sync-todo-docs.sh +0 -46
  262. package/hooks/scripts/tool-signal.sh +0 -48
  263. package/scripts/do-tasks-bridge.py +0 -366
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env bash
2
+ # asi-walk.sh — the deterministic walk that brings an agent along the ASI
3
+ # (Fetch.ai / Agentverse) lifecycle. Zero LLM. Numeric receipts. Closed loop.
4
+ #
5
+ # The lifecycle has two halves and they are NOT the same kind of check:
6
+ #
7
+ # LOCAL (default, no credentials) authored → parses → compiles →
8
+ # imports resolve → protocols emitted → stamped
9
+ # REMOTE (--remote, needs a key) create → upload → secrets → start →
10
+ # poll → reachable
11
+ #
12
+ # The local half is the half that goes RED TODAY if a compiler regression
13
+ # lands. It is the default for that reason. The remote half touches a third
14
+ # party, so it is opt-in by flag AND gated on the key being present — it is
15
+ # never attempted by default and never by --self-test.
16
+ #
17
+ # ── The three exit states ────────────────────────────────────────────────
18
+ # A station that could not run is NOT a station that passed. Precedent:
19
+ # pay/backend/src/chains/lightning.ts reports "unconfigured" rather than
20
+ # fabricating a pass when LIGHTNING_API_KEY is unset. Same rule here.
21
+ #
22
+ # 0 every station that ran, passed
23
+ # 1 a station RAN and FAILED
24
+ # 3 a station COULD NOT RUN (no key, no network) — only with --strict,
25
+ # or always for --remote, because an unrun remote station aggregating
26
+ # into green is the exact lie this script exists to prevent.
27
+ #
28
+ # Usage:
29
+ # asi-walk.sh <agent.md|agent-dir> # local stations
30
+ # asi-walk.sh <agent.md> --skills <dir> # resolve skills from dir
31
+ # asi-walk.sh <agent.md> --remote # + remote READ stations (needs key)
32
+ # asi-walk.sh <agent.md> --publish # actually DEPLOY to Agentverse
33
+ #
34
+ # --remote is read-only: it proves the key is accepted and the API answers.
35
+ # --publish is the only flag that mutates a third party, and it drives the real
36
+ # `oneie agent publish --target agentverse` path rather than a reimplementation.
37
+ #
38
+ # AGENTVERSE_API_KEY is NOT the ASI:One key. Measured 2026-08-24: an ASI:One key
39
+ # (api.asi1.ai) gets 401 "Could not validate credentials" from
40
+ # agentverse.ai/v1/hosting. Two products, two credentials. Get an Agentverse key
41
+ # from agentverse.ai → Profile → API Keys.
42
+ # asi-walk.sh <agent.md> --strict # unrun local station ⇒ exit 3
43
+ # asi-walk.sh <agent.md> --json # machine-readable receipt
44
+ # asi-walk.sh --list # print stations, run nothing
45
+ # asi-walk.sh --self-test # prove each station can go RED
46
+ #
47
+ # Env:
48
+ # AGENTVERSE_API_KEY required by --remote. Absent ⇒ every remote station
49
+ # reports unconfigured (exit 3), never a pass.
50
+ # ASI_WALK_BASE Agentverse API base (default https://agentverse.ai/v1)
51
+ # ONE_API_KEY substrate key injected into a published agent
52
+ # (legacy ONEIE_API_KEY also read, per resolveKey order)
53
+ # ANTHROPIC_API_KEY the deployed agent's model credential. Absent is
54
+ # reported, not invented — a published agent without it
55
+ # dies on its first handled message.
56
+ set -uo pipefail
57
+
58
+ REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
59
+ ASI_WALK_BASE="${ASI_WALK_BASE:-https://agentverse.ai/v1}"
60
+
61
+ AGENT_PATH=""; SKILLS_DIR=""; DO_REMOTE=0; DO_PUBLISH=0; STRICT=0; JSON=0; LIST=0; SELFTEST=0
62
+ while [[ $# -gt 0 ]]; do
63
+ case "$1" in
64
+ --skills) SKILLS_DIR="${2:-}"; shift 2 ;;
65
+ --remote) DO_REMOTE=1; shift ;;
66
+ --publish) DO_REMOTE=1; DO_PUBLISH=1; shift ;;
67
+ --strict) STRICT=1; shift ;;
68
+ --json) JSON=1; shift ;;
69
+ --list) LIST=1; shift ;;
70
+ --self-test) SELFTEST=1; shift ;;
71
+ -h|--help) sed -n '2,45p' "${BASH_SOURCE[0]}"; exit 0 ;;
72
+ -*) echo "asi-walk: unknown flag $1" >&2; exit 2 ;;
73
+ *) AGENT_PATH="$1"; shift ;;
74
+ esac
75
+ done
76
+
77
+ PASS=0; FAIL=0; UNRUN=0
78
+ declare -a RECEIPTS=()
79
+
80
+ # station <id> <title> — then the body runs; use ok/bad/unrun to close it.
81
+ _station_id=""; _station_title=""
82
+ station() { _station_id="$1"; _station_title="$2"; }
83
+ ok() { PASS=$((PASS+1)); RECEIPTS+=("$_station_id|pass|$_station_title|${1:-}"); printf ' \033[32mPASS\033[0m %-22s %s\n' "$_station_id" "${1:-$_station_title}"; }
84
+ bad() { FAIL=$((FAIL+1)); RECEIPTS+=("$_station_id|fail|$_station_title|${1:-}"); printf ' \033[31mFAIL\033[0m %-22s %s\n' "$_station_id" "${1:-$_station_title}"; }
85
+ unrun() { UNRUN=$((UNRUN+1)); RECEIPTS+=("$_station_id|unrun|$_station_title|${1:-}"); printf ' \033[33mUNRUN\033[0m %-22s %s\n' "$_station_id" "${1:-$_station_title}"; }
86
+
87
+ STATIONS_LOCAL=(
88
+ "parses|agent.md exists and frontmatter parses"
89
+ "name|frontmatter declares a name"
90
+ "compiles|compileAgent(target=uagents) emits Python"
91
+ "import-root|Model imported from the uagents ROOT, not uagents.models"
92
+ "agent-ctor|emitted Python constructs Agent(...)"
93
+ "protocols|skills resolve to Protocol/on_message handlers"
94
+ "stamp|Generated by ONE stamp present"
95
+ "model-cred|ANTHROPIC_API_KEY available for the deployed agent"
96
+ "substrate-cred|ONE/ONEIE key available to inject"
97
+ )
98
+ STATIONS_REMOTE=(
99
+ "av-key|AGENTVERSE_API_KEY present"
100
+ "av-reach|Agentverse API reachable AND the key is accepted"
101
+ "av-publish|oneie agent publish --target agentverse (needs --publish)"
102
+ "av-poll|GET /hosting/agents/{addr} reports compiled"
103
+ )
104
+
105
+ if [[ $LIST -eq 1 ]]; then
106
+ echo "LOCAL stations (default):"
107
+ for s in "${STATIONS_LOCAL[@]}"; do printf ' %-16s %s\n' "${s%%|*}" "${s#*|}"; done
108
+ echo "REMOTE stations (--remote, needs AGENTVERSE_API_KEY):"
109
+ for s in "${STATIONS_REMOTE[@]}"; do printf ' %-16s %s\n' "${s%%|*}" "${s#*|}"; done
110
+ exit 0
111
+ fi
112
+
113
+ # ── resolve the agent file ───────────────────────────────────────────────
114
+ resolve_agent() {
115
+ local p="$1"
116
+ [[ -z "$p" ]] && return 1
117
+ if [[ -d "$p" ]]; then
118
+ for cand in "$p/agent.md" "$p/$(basename "$p").md"; do
119
+ [[ -f "$cand" ]] && { echo "$cand"; return 0; }
120
+ done
121
+ local first; first="$(find "$p" -maxdepth 1 -name '*.md' | head -1)"
122
+ [[ -n "$first" ]] && { echo "$first"; return 0; }
123
+ return 1
124
+ fi
125
+ [[ -f "$p" ]] && { echo "$p"; return 0; }
126
+ return 1
127
+ }
128
+
129
+ # compile_out <agent.md> <skills-dir-or-empty> — prints emitted Python, or
130
+ # prints nothing and returns non-zero. Uses the REAL SDK compiler, never a
131
+ # reimplementation: this station must break when the compiler breaks.
132
+ #
133
+ # ── why this retries, and why stderr is kept ─────────────────────────────
134
+ # This swallowed stderr with 2>/dev/null. A transient compiler failure (cold
135
+ # module cache, a saturated box) then looked BYTE-IDENTICAL to "this agent has
136
+ # nothing to compile": empty stdout either way. Measured: the first run of an
137
+ # agent reported pass=6 unrun=3 where five consecutive warm runs reported
138
+ # pass=7 unrun=2 — the same agent, two different verdicts, cache warmth the only
139
+ # variable. A walk whose answer depends on cache warmth is not deterministic,
140
+ # which is the one property this script exists to have.
141
+ # Retry once on empty output, and keep stderr so a real failure can be NAMED
142
+ # instead of silently degrading into an unrun station.
143
+ COMPILE_ERR=""
144
+ compile_out() {
145
+ local md="$1" skills="$2" out="" err_file
146
+ md="$(cd "$(dirname "$md")" && pwd)/$(basename "$md")"
147
+ [[ -n "$skills" && -d "$skills" ]] && skills="$(cd "$skills" && pwd)"
148
+ err_file="$(mktemp)"
149
+ for attempt in 1 2; do
150
+ out="$(_compile_once "$md" "$skills" 2>"$err_file")"
151
+ [[ -n "$out" ]] && break
152
+ done
153
+ COMPILE_ERR="$(head -c 400 "$err_file" | tr '\n' ' ')"
154
+ rm -f "$err_file"
155
+ printf '%s' "$out"
156
+ [[ -n "$out" ]]
157
+ }
158
+
159
+ _compile_once() {
160
+ local md="$1" skills="$2"
161
+ # ABSOLUTE paths: `bun --cwd` moves the working directory to packages/sdk, so a
162
+ # relative agent path resolves against the wrong root and reads ENOENT — which
163
+ # the caller would otherwise report as the compiler emitting nothing.
164
+ ONE_ASI_MD="$md" ONE_ASI_SKILLS="$skills" bun --cwd "$REPO_ROOT/packages/sdk" - <<'TS'
165
+ import { readFileSync, existsSync, readdirSync } from 'node:fs'
166
+ import { join, basename } from 'node:path'
167
+ import { compileAgent } from './src/compile'
168
+ const md = readFileSync(process.env.ONE_ASI_MD!, 'utf8')
169
+ const dir = process.env.ONE_ASI_SKILLS || ''
170
+ const skills: Record<string, string> = {}
171
+ // Two shapes in this tree: a flat `<dir>/<name>.md`, and the shared pool's
172
+ // `<dir>/<name>/SKILL.md` (one.ie/ai/skills and .claude/skills both use the
173
+ // second). Reading only the flat form silently resolves ZERO skills against the
174
+ // real pool, which then reads as "skills never reached the compiler".
175
+ // Never branch on the dirent TYPE. `withFileTypes` reports a symlinked
176
+ // directory as isSymbolicLink(), not isDirectory(), so an --skills dir built
177
+ // from symlinks (the only way to span both pools) resolved ZERO skills and every
178
+ // agent read as "skills never reached the compiler". Probe the PATHS instead —
179
+ // existsSync follows links, so all three shapes resolve the same way.
180
+ if (dir && existsSync(dir)) {
181
+ for (const name of readdirSync(dir)) {
182
+ if (name.endsWith('.md')) {
183
+ const flat = join(dir, name)
184
+ if (existsSync(flat)) skills[basename(name, '.md')] = readFileSync(flat, 'utf8')
185
+ continue
186
+ }
187
+ for (const inner of ['SKILL.md', `${name}.md`]) {
188
+ const p = join(dir, name, inner)
189
+ if (existsSync(p)) { skills[name] = readFileSync(p, 'utf8'); break }
190
+ }
191
+ }
192
+ }
193
+ process.stdout.write(compileAgent(md, { target: 'uagents', skills }))
194
+ TS
195
+ }
196
+
197
+ run_local() {
198
+ local md="$1" skills="$2"
199
+
200
+ station parses "agent.md exists and frontmatter parses"
201
+ if [[ ! -f "$md" ]]; then bad "no such file: $md"; return; fi
202
+ if ! head -1 "$md" | grep -q '^---'; then bad "no YAML frontmatter fence at line 1"; else ok "$md"; fi
203
+
204
+ station name "frontmatter declares a name"
205
+ local nm; nm="$(sed -n '2,40p' "$md" | grep -m1 '^name:' | sed 's/^name:[[:space:]]*//')"
206
+ if [[ -n "$nm" ]]; then ok "name=$nm"; else bad "no name: in frontmatter"; fi
207
+
208
+ station compiles "compileAgent(target=uagents) emits Python"
209
+ local out; out="$(compile_out "$md" "$skills")"
210
+ if [[ -z "$out" ]]; then
211
+ # Name the cause. "produced nothing" with the reason hidden is what let a
212
+ # transient failure masquerade as an agent with nothing to compile.
213
+ bad "compiler produced nothing after 2 attempts${COMPILE_ERR:+ — stderr: $COMPILE_ERR}";
214
+ for s in import-root agent-ctor protocols stamp; do
215
+ station "$s" "skipped — nothing compiled"; unrun "no compiler output to assert on"
216
+ done
217
+ else
218
+ ok "$(wc -l <<<"$out" | tr -d ' ') lines of Python"
219
+
220
+ # The D10 regression guard. uagents/models.py does not exist upstream
221
+ # (404); Model is re-exported from the package root. A wrong path here is
222
+ # an ImportError at agent startup, which no local typecheck can see.
223
+ station import-root "Model imported from the uagents ROOT"
224
+ if grep -q 'from uagents\.models import' <<<"$out"; then
225
+ bad "emits 'from uagents.models import' — that module does not exist upstream"
226
+ elif grep -qE '^from uagents import .*\bModel\b' <<<"$out"; then
227
+ ok "from uagents import ... Model"
228
+ # Match Model as a PYTHON SYMBOL only — `class X(Model)` / `: Model`. The
229
+ # emitted file embeds the agent's markdown prompt as a string, and that prose
230
+ # says things like "## Model · Effort dial"; a bare \bModel\b grep matches the
231
+ # documentation and reports a NameError that does not exist.
232
+ elif grep -qE '\(Model\)|:[[:space:]]*Model\b' <<<"$out"; then
233
+ bad "uses Model as a symbol but never imports it from the root (NameError at runtime)"
234
+ else
235
+ unrun "this agent emits no Model symbol (no skills, no endpoints)"
236
+ fi
237
+
238
+ station agent-ctor "emitted Python constructs Agent(...)"
239
+ if grep -qE '(^|[^A-Za-z_])Agent\(' <<<"$out"; then ok; else bad "no Agent( constructor emitted"; fi
240
+
241
+ # D8 guard: with no skills map, hasSkills is false and NO protocol handlers
242
+ # are emitted — every deploy shipped a shell.
243
+ #
244
+ # Read the AGENT'S OWN declaration first. An agent that declares no skills
245
+ # (absent, or `skills: []` — 28 of 116 in this tree) correctly emits no
246
+ # handler, and calling that a failure blamed 28 healthy agents for a
247
+ # property they never claimed. Only a DECLARED-but-unresolved skill is a
248
+ # defect: that is a dangling ref, and it publishes a silent shell.
249
+ station protocols "declared skills resolve to Protocol/on_message handlers"
250
+ local decl; decl="$(sed -n '2,40p' "$md" | grep -m1 '^skills:' | sed 's/^skills:[[:space:]]*//')"
251
+ local n_handlers; n_handlers="$(grep -c 'on_message' <<<"$out")"
252
+ if [[ -z "$decl" || "$decl" == "[]" ]]; then
253
+ unrun "agent declares no skills — no protocol expected"
254
+ elif [[ -z "$skills" ]]; then
255
+ unrun "declares skills ($decl) but no --skills dir given; cannot resolve"
256
+ elif [[ "$n_handlers" -gt 0 ]]; then
257
+ ok "$n_handlers on_message handler(s) from: $decl"
258
+ else
259
+ bad "declares skills ($decl) but NONE resolved — dangling ref, would publish a handler-less agent"
260
+ fi
261
+
262
+ station stamp "Generated by ONE stamp present"
263
+ if grep -q 'Generated by ONE' <<<"$out"; then ok; else bad "no ONE attribution in emitted code"; fi
264
+ fi
265
+
266
+ # Credentials the PUBLISHED agent needs. Reported, never invented. An agent
267
+ # that deploys without a model credential dies on its first handled message.
268
+ station model-cred "ANTHROPIC_API_KEY available for the deployed agent"
269
+ if [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then ok "present in env (value never printed)"
270
+ else unrun "unset — a published agent would fail on its first message"; fi
271
+
272
+ station substrate-cred "ONE/ONEIE key available to inject"
273
+ if [[ -n "${ONE_API_KEY:-}" ]]; then ok "ONE_API_KEY present"
274
+ elif [[ -n "${ONEIE_API_KEY:-}" ]]; then ok "ONEIE_API_KEY (legacy) present"
275
+ else unrun "neither ONE_API_KEY nor ONEIE_API_KEY set"; fi
276
+ }
277
+
278
+ run_remote() {
279
+ station av-key "AGENTVERSE_API_KEY present"
280
+ if [[ -z "${AGENTVERSE_API_KEY:-}" ]]; then
281
+ unrun "unset — refusing to guess; remote stations cannot run. NOTE: this is NOT the ASI:One key; get one from agentverse.ai → Profile → API Keys"
282
+ for s in av-reach av-publish av-poll; do
283
+ station "$s" "gated on AGENTVERSE_API_KEY"; unrun "no credential"
284
+ done
285
+ return
286
+ fi
287
+ ok "present (value never printed)"
288
+
289
+ station av-reach "Agentverse API reachable"
290
+ local code
291
+ code="$(curl -s -o /dev/null -w '%{http_code}' -m 15 \
292
+ -H "Authorization: Bearer ${AGENTVERSE_API_KEY}" \
293
+ "$ASI_WALK_BASE/hosting/agents" 2>/dev/null)"
294
+ if [[ "$code" == "200" ]]; then ok "GET /hosting/agents → 200"
295
+ elif [[ -z "$code" || "$code" == "000" ]]; then
296
+ unrun "no network / no response from $ASI_WALK_BASE"
297
+ for s in av-publish av-poll; do
298
+ station "$s" "unreachable"; unrun "API did not respond"
299
+ done
300
+ return
301
+ elif [[ "$code" == "401" || "$code" == "403" ]]; then
302
+ # A key that is PRESENT but REJECTED is the failure the av-key station
303
+ # cannot see. Measured 2026-08-24: the ASI:One key (api.asi1.ai) returns
304
+ # 401 "Could not validate credentials" here — Agentverse hosting is a
305
+ # SEPARATE credential from the ASI:One LLM key. Distinguish the two 401s:
306
+ # no credential answers "Not authenticated"; a rejected Bearer answers
307
+ # "Could not validate credentials".
308
+ bad "GET /hosting/agents → $code (key present but REJECTED — an ASI:One key is not an Agentverse key)"
309
+ for s in av-publish av-poll; do station "$s" "gated on a valid credential"; unrun "credential rejected"; done
310
+ return
311
+ else bad "GET /hosting/agents → $code"; fi
312
+
313
+ # ── the write half ──────────────────────────────────────────────────────
314
+ # This CREATES a live hosted agent on a third party, so it needs its own
315
+ # opt-in: --remote alone stays read-only (key + reachability). --publish arms
316
+ # it. Read-only at every level by default.
317
+ #
318
+ # It invokes the REAL CLI publish path, never a reimplementation. A walk that
319
+ # POSTs its own create/upload/start sequence would prove a code path nobody
320
+ # ships; `oneie agent publish --target agentverse` is the door production
321
+ # uses, so that is the door this station opens.
322
+ station av-publish "oneie agent publish --target agentverse"
323
+ if [[ $DO_PUBLISH -ne 1 ]]; then
324
+ unrun "not armed — pass --publish to actually deploy (it creates a live agent)"
325
+ station av-poll "GET /hosting/agents/{addr} reports compiled"; unrun "nothing published"
326
+ return
327
+ fi
328
+
329
+ # Run the LOCAL CLI source, never `bunx oneie` — bunx resolves the PUBLISHED
330
+ # npm package, which lags this tree. Measured: it errored `unknown option
331
+ # '--skills'` because that flag exists only locally, so the walk was testing a
332
+ # different CLI than the one being developed. `bun run <src>` pins it to HEAD.
333
+ local pub rc addr cli="$REPO_ROOT/packages/cli/src/index.ts"
334
+ if [[ ! -f "$cli" ]]; then
335
+ bad "local CLI source not found at $cli"
336
+ station av-poll "GET /hosting/agents/{addr} reports compiled"; unrun "no CLI to run"
337
+ return
338
+ fi
339
+ pub="$(cd "$REPO_ROOT" && AGENTVERSE_API_KEY="$AGENTVERSE_API_KEY" \
340
+ bun "$cli" agent publish "$MD" --target agentverse \
341
+ ${SKILLS_DIR:+--skills "$SKILLS_DIR"} 2>&1)"; rc=$?
342
+ addr="$(grep -oE 'agent1[a-z0-9]{20,}' <<<"$pub" | head -1)"
343
+ if [[ $rc -ne 0 ]]; then
344
+ bad "publish exited $rc: $(head -c 300 <<<"$pub" | tr '\n' ' ')"
345
+ station av-poll "GET /hosting/agents/{addr} reports compiled"; unrun "publish failed"
346
+ return
347
+ fi
348
+ if [[ -z "$addr" ]]; then
349
+ bad "publish reported ok but returned no agent address: $(head -c 200 <<<"$pub" | tr '\n' ' ')"
350
+ station av-poll "GET /hosting/agents/{addr} reports compiled"; unrun "no address to poll"
351
+ return
352
+ fi
353
+ ok "published → $addr"
354
+
355
+ station av-poll "GET /hosting/agents/{addr} reports compiled"
356
+ local body compiled
357
+ body="$(curl -s -m 20 -H "Authorization: Bearer ${AGENTVERSE_API_KEY}" \
358
+ "$ASI_WALK_BASE/hosting/agents/$addr" 2>/dev/null)"
359
+ compiled="$(grep -o '"compiled":[a-z]*' <<<"$body" | cut -d: -f2)"
360
+ if [[ "$compiled" == "true" ]]; then ok "compiled=true, running on Agentverse"
361
+ elif [[ "$compiled" == "false" ]]; then bad "compiled=false — deployed but will not run"
362
+ else unrun "could not read compiled from the status body"; fi
363
+ }
364
+
365
+ self_test() {
366
+ echo "asi-walk --self-test: proving each local station can go RED"
367
+ local tmp; tmp="$(mktemp -d)"; trap 'rm -rf "$tmp"' RETURN
368
+ local rc red=0 total=0
369
+
370
+ # 1. missing file must fail
371
+ total=$((total+1))
372
+ "${BASH_SOURCE[0]}" "$tmp/nope.md" >/dev/null 2>&1; rc=$?
373
+ if [[ $rc -ne 0 ]]; then red=$((red+1)); echo " ok missing-file reds (exit $rc)"; else echo " BAD missing-file passed"; fi
374
+
375
+ # 2. no frontmatter must fail
376
+ total=$((total+1))
377
+ printf 'no frontmatter here\n' > "$tmp/bare.md"
378
+ "${BASH_SOURCE[0]}" "$tmp/bare.md" >/dev/null 2>&1; rc=$?
379
+ if [[ $rc -ne 0 ]]; then red=$((red+1)); echo " ok no-frontmatter reds (exit $rc)"; else echo " BAD no-frontmatter passed"; fi
380
+
381
+ # 3. frontmatter without a name must fail
382
+ total=$((total+1))
383
+ printf -- '---\ndescription: x\n---\nbody\n' > "$tmp/noname.md"
384
+ "${BASH_SOURCE[0]}" "$tmp/noname.md" >/dev/null 2>&1; rc=$?
385
+ if [[ $rc -ne 0 ]]; then red=$((red+1)); echo " ok no-name reds (exit $rc)"; else echo " BAD no-name passed"; fi
386
+
387
+ # 4. --remote with no key must NOT report green (exit 3, never 0)
388
+ total=$((total+1))
389
+ printf -- '---\nname: t\n---\nbody\n' > "$tmp/ok.md"
390
+ ( unset AGENTVERSE_API_KEY; "${BASH_SOURCE[0]}" "$tmp/ok.md" --remote >/dev/null 2>&1 ); rc=$?
391
+ if [[ $rc -eq 3 ]]; then red=$((red+1)); echo " ok --remote with no key exits 3 (unconfigured, not green)"
392
+ else echo " BAD --remote with no key exited $rc (expected 3)"; fi
393
+
394
+ echo "self-test: $red/$total station guards proved falsifiable"
395
+ [[ $red -eq $total ]] || return 1
396
+ return 0
397
+ }
398
+
399
+ # ── main ─────────────────────────────────────────────────────────────────
400
+ if [[ $SELFTEST -eq 1 ]]; then self_test; exit $?; fi
401
+
402
+ MD="$(resolve_agent "$AGENT_PATH")" || { echo "asi-walk: no agent .md at '${AGENT_PATH:-<missing>}'" >&2; exit 2; }
403
+
404
+ # A sibling skills/ dir next to the agent is the convention resolveSkillMds
405
+ # uses; adopt it so the protocols station has something real to assert on.
406
+ if [[ -z "$SKILLS_DIR" && -d "$(dirname "$MD")/skills" ]]; then SKILLS_DIR="$(dirname "$MD")/skills"; fi
407
+
408
+ echo "asi-walk — ASI (Fetch.ai/Agentverse) lifecycle"
409
+ echo " agent: $MD"
410
+ echo " skills: ${SKILLS_DIR:-<none>}"
411
+ echo " remote: $([[ $DO_REMOTE -eq 1 ]] && echo yes || echo 'no (local stations only)')"
412
+ echo
413
+ echo "LOCAL"
414
+ run_local "$MD" "$SKILLS_DIR"
415
+ if [[ $DO_REMOTE -eq 1 ]]; then echo; echo "REMOTE"; run_remote; fi
416
+
417
+ echo
418
+ echo "asi-walk: pass=$PASS fail=$FAIL unrun=$UNRUN"
419
+
420
+ if [[ $JSON -eq 1 ]]; then
421
+ printf '{"pass":%d,"fail":%d,"unrun":%d,"stations":[' "$PASS" "$FAIL" "$UNRUN"
422
+ for i in "${!RECEIPTS[@]}"; do
423
+ IFS='|' read -r id st title note <<<"${RECEIPTS[$i]}"
424
+ [[ $i -gt 0 ]] && printf ','
425
+ printf '{"id":"%s","state":"%s","title":"%s","note":"%s"}' "$id" "$st" "$title" "${note//\"/\\\"}"
426
+ done
427
+ printf ']}\n'
428
+ fi
429
+
430
+ # A failure always wins. An unrun station is exit 3 under --strict, and ALWAYS
431
+ # exit 3 when --remote was asked for: "I could not check" must never be read as
432
+ # "it works".
433
+ [[ $FAIL -gt 0 ]] && exit 1
434
+ if [[ $UNRUN -gt 0 ]] && { [[ $STRICT -eq 1 ]] || [[ $DO_REMOTE -eq 1 ]]; }; then exit 3; fi
435
+ exit 0
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env bash
2
+ # astro-build-cached.sh — build one.ie/web, or skip when nothing it reads has moved.
3
+ #
4
+ # manifest: needs-env
5
+ #
6
+ # WHY. The production astro build is one of the two heavy deploy gates: ~174s with
7
+ # --max-old-space-size=8192. It is a pure function of its sources, so re-running it
8
+ # over a tree that has not moved since the last successful build reproduces bytes
9
+ # that are already on disk. Same idiom as sdk-build-cached.sh, widened to cover
10
+ # everything the site build actually reads.
11
+ #
12
+ # SCOPE IS THE WHOLE SAFETY ARGUMENT. Under-scope the fingerprint and this ships a
13
+ # STALE dist -- unproven code in production, the one failure the deploy gate exists
14
+ # to prevent. So the key covers one.ie/web in full (src, public, every config) AND
15
+ # packages/, because the site imports @oneie/sdk, @oneie/design, @oneie/frontend and
16
+ # ten plugins by path. It is deliberately over-broad: an unnecessary rebuild costs
17
+ # three minutes, a missed one costs a bad deploy.
18
+ #
19
+ # The key is CONTENT, never mtimes and never `git status --porcelain` (which names
20
+ # files, so an edit that keeps a file "modified" does not move it -- the trap that
21
+ # was caught red-handed in test-cached.sh).
22
+ #
23
+ # SAFETY. The stamp lives INSIDE dist/. Delete, clean, or never build dist and the
24
+ # stamp goes with it, so a missing dist can never read as cached. The stamp is
25
+ # written only AFTER the build exits 0 and dist/server/ exists -- a failed or
26
+ # half-written build leaves no stamp and the next run rebuilds.
27
+ #
28
+ # ASTRO_BUILD_FORCE=1 rebuilds unconditionally. --self-test runs the fixtures.
29
+ set -uo pipefail
30
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
31
+ WEB="$ROOT/one.ie/web"
32
+ STAMP="$WEB/dist/.astro-build-fingerprint"
33
+
34
+ # Files the build reads that git CANNOT see. `.env`/`.env.local` are gitignored,
35
+ # so neither ls-files nor diff nor even ls-files --others (which honours
36
+ # .gitignore) covers them -- and astro inlines PUBLIC_* values from them into the
37
+ # output. A key blind to them would hand back a cache hit for a build that would
38
+ # now produce different bytes. Lockfiles are listed explicitly for the same
39
+ # reason: a dependency bump can move only a lockfile.
40
+ # The credential file is part of the key (BETTER_AUTH_SECRET et al. are read at
41
+ # build time), and it honours ONE_ENV_FILE / DO_ENV_FILE like every credential
42
+ # read in the harness — factory-repo.sh --check-env-indirection is the gate.
43
+ _ENV_FILE="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.env}}"
44
+ _UNTRACKED_INPUTS="$_ENV_FILE one.ie/web/.env.local one.ie/web/bun.lock one.ie/web/package-lock.json packages/bun.lock bun.lock"
45
+
46
+ _fp() {
47
+ { git -C "$ROOT" ls-files -s one.ie/web packages 2>/dev/null
48
+ git -C "$ROOT" diff -- one.ie/web packages 2>/dev/null
49
+ git -C "$ROOT" ls-files --others --exclude-standard one.ie/web packages 2>/dev/null \
50
+ | grep -v '^one.ie/web/dist/' \
51
+ | while IFS= read -r u; do shasum -a 256 "$ROOT/$u" 2>/dev/null; done
52
+ for f in $_UNTRACKED_INPUTS; do
53
+ # An input that is absent must hash as "absent", not as nothing -- otherwise
54
+ # deleting .env would leave the key unmoved.
55
+ if [ -f "$ROOT/$f" ]; then shasum -a 256 "$ROOT/$f" 2>/dev/null
56
+ else echo "ABSENT $f"; fi
57
+ done
58
+ } | shasum -a 256 | cut -d' ' -f1
59
+ }
60
+
61
+ if [ "${1:-}" = "--self-test" ]; then
62
+ fails=0
63
+ if [ ! -f "$STAMP" ]; then
64
+ echo "n/a: no prior build stamp — run a real build first, then re-run --self-test" >&2
65
+ echo " (an absent stamp is 'not measured', never 'ok')" >&2
66
+ exit 2
67
+ fi
68
+ # 1. an unchanged tree is cached
69
+ o1=$(bash "$0" --check-only)
70
+ case "$o1" in *cached*) echo "ok: unchanged tree reads as cached";;
71
+ *) echo "FAIL: unchanged tree did not read as cached: $o1"; fails=$((fails+1));; esac
72
+ # RED PROOF 1 — a source edit must invalidate
73
+ probe="$WEB/src/.__build_cache_probe.ts"; echo 'export const __p = 1' > "$probe"
74
+ o2=$(bash "$0" --check-only)
75
+ case "$o2" in *cached*) echo "FAIL: an src edit did NOT invalidate the build cache"; fails=$((fails+1));;
76
+ *) echo "ok: an src edit invalidates";; esac
77
+ rm -f "$probe"
78
+ # RED PROOF 2 — an edit in packages/ must invalidate (the cross-package trap)
79
+ probe2="$ROOT/packages/design/.__build_cache_probe.ts"; echo 'export const __p = 1' > "$probe2"
80
+ o3=$(bash "$0" --check-only)
81
+ case "$o3" in *cached*) echo "FAIL: a packages/ edit did NOT invalidate"; fails=$((fails+1));;
82
+ *) echo "ok: a packages/ edit invalidates";; esac
83
+ rm -f "$probe2"
84
+ # RED PROOF 3 — a gitignored .env edit must invalidate. git cannot see this file
85
+ # at all, so a key built only from ls-files/diff stays put while the build's
86
+ # inlined PUBLIC_* values change underneath it.
87
+ envf="$WEB/.env"
88
+ if [ -f "$envf" ]; then
89
+ cp "$envf" "$envf.__probe.bak"
90
+ echo '# __build_cache_probe' >> "$envf"
91
+ o3b=$(bash "$0" --check-only)
92
+ case "$o3b" in *cached*) echo "FAIL: a .env edit did NOT invalidate (git cannot see it)"; fails=$((fails+1));;
93
+ *) echo "ok: a gitignored .env edit invalidates";; esac
94
+ mv "$envf.__probe.bak" "$envf"
95
+ else
96
+ echo "n/a: no $_ENV_FILE present, cannot run the gitignored-input proof" >&2
97
+ fi
98
+ # RED PROOF 4 — a missing stamp must never read as cached
99
+ mv "$STAMP" "$STAMP.bak"
100
+ o4=$(bash "$0" --check-only)
101
+ case "$o4" in *cached*) echo "FAIL: missing stamp read as cached"; fails=$((fails+1));;
102
+ *) echo "ok: a missing stamp forces a rebuild";; esac
103
+ mv "$STAMP.bak" "$STAMP"
104
+ # RED PROOF 5 — a stamp whose dist/server is gone must never read as cached
105
+ if [ -d "$WEB/dist/server" ]; then
106
+ mv "$WEB/dist/server" "$WEB/dist/.server.bak"
107
+ o5=$(bash "$0" --check-only)
108
+ case "$o5" in *cached*) echo "FAIL: a stamp with no dist/server read as cached"; fails=$((fails+1));;
109
+ *) echo "ok: a missing dist/server forces a rebuild";; esac
110
+ mv "$WEB/dist/.server.bak" "$WEB/dist/server"
111
+ else
112
+ echo "n/a: dist/server absent, cannot run the missing-output proof" >&2
113
+ fi
114
+ [ "$fails" -eq 0 ] && { echo "astro-build-cached: self-test PASS"; exit 0; }
115
+ echo "astro-build-cached: self-test FAILED ($fails)"; exit 1
116
+ fi
117
+
118
+ CHECK_ONLY=0
119
+ [ "${1:-}" = "--check-only" ] && CHECK_ONLY=1
120
+
121
+ want="$(_fp)"
122
+ # THREE conditions, all required. dist/server is checked as well as the stamp
123
+ # because the stamp records "these sources built cleanly", not "the output is
124
+ # still here" -- a `rm -rf dist/server` with the stamp left behind must rebuild.
125
+ if [ "${ASTRO_BUILD_FORCE:-0}" != "1" ] \
126
+ && [ -f "$STAMP" ] && [ "$(cat "$STAMP" 2>/dev/null)" = "$want" ] \
127
+ && [ -d "$WEB/dist/server" ]; then
128
+ echo "[astro] cached — one.ie/web + packages unchanged (${want:0:12})"
129
+ exit 0
130
+ fi
131
+
132
+ if (( CHECK_ONLY )); then
133
+ echo "[astro] would REBUILD (${want:0:12})"
134
+ exit 0
135
+ fi
136
+
137
+ ( cd "$WEB" && NODE_ENV=production bun run build ) || { echo "[astro] BUILD FAILED" >&2; exit 1; }
138
+ # Never stamp an output that is not there. A build that exits 0 without producing
139
+ # dist/server has not proven anything, and stamping it would hand the next deploy
140
+ # a cache hit over a missing artifact.
141
+ [ -d "$WEB/dist/server" ] || { echo "[astro] build exited 0 but dist/server is missing — NOT cached" >&2; exit 1; }
142
+ printf '%s' "$want" > "$STAMP"
143
+ echo "[astro] built (${want:0:12})"