@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
package/scripts/do-signal.sh
CHANGED
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
# do-signal.sh [--print] <type> <slug> <tier> <cycle> [key=value ...]
|
|
10
10
|
# do-signal.sh [--print] --task-create <slug> <title> [tags=a,b,c] [notes=...]
|
|
11
11
|
# do-signal.sh [--print] --task-claim <slug>
|
|
12
|
+
# do-signal.sh [--print] --task-plan <slug> # one task per cycle
|
|
13
|
+
# do-signal.sh [--print] --task-reap
|
|
12
14
|
# do-signal.sh [--print] --task-link <slug> [docs=a,b,c]
|
|
15
|
+
# do-signal.sh [--print] --task-heartbeat <slug> <body>
|
|
16
|
+
# do-signal.sh [--print] --outcome <slug> tags=a,b actor=<aid> success=<bool> [weight=<n>]
|
|
13
17
|
#
|
|
14
18
|
# --print: write JSON to stdout without POSTing (tests + dry runs).
|
|
15
19
|
# Extra key=value pairs are merged into the world:do-event payload.
|
|
@@ -23,7 +27,9 @@
|
|
|
23
27
|
# ONE_ENV_FILE default: one.ie/web/.env (read SERVER_SECRET for auth)
|
|
24
28
|
# DO_ENV_FILE accepted as an alias for ONE_ENV_FILE (kept for callers)
|
|
25
29
|
#
|
|
26
|
-
#
|
|
30
|
+
# world:do-event failure never blocks the build loop — callers wrap with `|| true`.
|
|
31
|
+
# tasks:* (create/claim/link/heartbeat) propagate failure so callers can log it;
|
|
32
|
+
# they must still not abort the build (capture the exit, continue).
|
|
27
33
|
set -euo pipefail
|
|
28
34
|
|
|
29
35
|
PRINT=false
|
|
@@ -45,6 +51,10 @@ _server_secret() {
|
|
|
45
51
|
local _f _v
|
|
46
52
|
for _f in "$ONE_ENV_FILE" "$_ROOT/.env" $_MONOREPO_ENV_CANDIDATES; do
|
|
47
53
|
[ -f "$_f" ] || continue
|
|
54
|
+
# DO_SIGNAL_SKIP_FALLBACKS=1 → ONE_ENV_FILE is the WHOLE ladder. Without it the
|
|
55
|
+
# no-secret state is unreachable from inside the monorepo (_ROOT resolves to a
|
|
56
|
+
# tree that carries a real .env), so the dark-cycle guard could not be proven.
|
|
57
|
+
if [ "${DO_SIGNAL_SKIP_FALLBACKS:-0}" = "1" ] && [ "$_f" != "$ONE_ENV_FILE" ]; then continue; fi
|
|
48
58
|
_v=$(grep -E '^SERVER_SECRET=' "$_f" 2>/dev/null | head -1 | sed 's/^[^=]*=//;s/^"//;s/"$//' || true)
|
|
49
59
|
[ -n "$_v" ] && { printf '%s' "$_v"; return 0; }
|
|
50
60
|
done
|
|
@@ -66,24 +76,39 @@ _gateway_key() {
|
|
|
66
76
|
printf ''
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
# POST a JSON payload to /api/ask/<receiver> with bearer auth.
|
|
70
|
-
#
|
|
71
|
-
#
|
|
79
|
+
# POST a JSON payload to /api/ask/<receiver> with bearer auth. Board writes
|
|
80
|
+
# (tasks:*) must not disguise failure as ok: curl errors and a body without
|
|
81
|
+
# `"ok":true` (HTTP 200 wrapping {error:...} is the TypeDB-down case) exit
|
|
82
|
+
# non-zero. Prints the response body. Callers log and continue.
|
|
72
83
|
_post_ask() {
|
|
73
|
-
local _receiver="$1" _payload="$2" _secret _authcfg _url _resp
|
|
84
|
+
local _receiver="$1" _payload="$2" _secret _authcfg _url _resp _rc=0
|
|
74
85
|
_secret="$(_gateway_key)"
|
|
75
86
|
_url="${DO_SIGNAL_URL}/api/ask/${_receiver}"
|
|
76
87
|
if [ -n "$_secret" ]; then
|
|
77
88
|
_authcfg="$(mktemp)"; chmod 600 "$_authcfg"
|
|
78
89
|
printf 'header = "Authorization: Bearer %s"\n' "$_secret" > "$_authcfg"
|
|
79
|
-
_resp=$(curl -sf -X POST "$_url" -H "Content-Type: application/json" --config "$_authcfg" -d "$_payload" 2>/dev/null ||
|
|
90
|
+
_resp=$(curl -sf -X POST "$_url" -H "Content-Type: application/json" --config "$_authcfg" -d "$_payload" 2>/dev/null) || _rc=$?
|
|
80
91
|
rm -f "$_authcfg"
|
|
81
92
|
else
|
|
82
|
-
_resp=$(curl -sf -X POST "$_url" -H "Content-Type: application/json" -d "$_payload" 2>/dev/null ||
|
|
93
|
+
_resp=$(curl -sf -X POST "$_url" -H "Content-Type: application/json" -d "$_payload" 2>/dev/null) || _rc=$?
|
|
83
94
|
fi
|
|
84
95
|
printf '%s' "$_resp"
|
|
96
|
+
if [ "$_rc" -eq 0 ]; then
|
|
97
|
+
case "$_resp" in
|
|
98
|
+
*'"ok":true'*) ;;
|
|
99
|
+
*) _rc=1 ;;
|
|
100
|
+
esac
|
|
101
|
+
fi
|
|
102
|
+
return "$_rc"
|
|
85
103
|
}
|
|
86
104
|
|
|
105
|
+
# _jsonesc — JSON-escape a value before it is interpolated into a payload
|
|
106
|
+
# string. EVERY free-text field must go through this. Measured 2026-08-26: a
|
|
107
|
+
# task filed with `notes=accept: ! grep -q "x" f.ts` produced
|
|
108
|
+
# "notes":"accept: ! grep -q "x" f.ts"
|
|
109
|
+
# — malformed JSON, and the create failed. Titles carry quotes just as often.
|
|
110
|
+
_jsonesc() { printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/\t/\\t/g' | tr -d '\n\r'; }
|
|
111
|
+
|
|
87
112
|
# Every task this harness files lands in one workspace — /u/one/tasks — so a
|
|
88
113
|
# human never has to hunt across slugs for Claude Code's own work. Overridable
|
|
89
114
|
# for a differently-branded harness fork; unset means "one" (same convention
|
|
@@ -93,6 +118,23 @@ _post_ask() {
|
|
|
93
118
|
# session happened to be attested.
|
|
94
119
|
CC_TASKS_WORKSPACE="${CC_TASKS_WORKSPACE:-one}"
|
|
95
120
|
|
|
121
|
+
# CC_TASKS_AUTHOR — WHO the comment is from, when it is not simply "the
|
|
122
|
+
# workspace". tasks:comment records an author on the message and the inbox draws
|
|
123
|
+
# it (three directors on one task read as three speakers, not one anonymous
|
|
124
|
+
# run), but a field no caller supplies is a field production never sets: this
|
|
125
|
+
# script and the MCP door are the two writers, and both built their payload with
|
|
126
|
+
# tid/workspace/body only, so every /do close and every wave heartbeat landed
|
|
127
|
+
# `authorKind:"staff"`. The receiver honours the field only for an ATTESTED
|
|
128
|
+
# staff caller — which is exactly what GATEWAY_API_KEY auth above makes this
|
|
129
|
+
# script — and validates the slug shape; a caller that cannot be attested names
|
|
130
|
+
# nobody. UNSET is the default and emits the byte-identical payload this script
|
|
131
|
+
# has always sent, so nothing changes for a harness that does not set it.
|
|
132
|
+
CC_TASKS_AUTHOR="${CC_TASKS_AUTHOR:-}"
|
|
133
|
+
_author_json() { # emits `,"author":"<slug>"` or nothing
|
|
134
|
+
[ -n "$CC_TASKS_AUTHOR" ] || return 0
|
|
135
|
+
printf ',"author":"%s"' "$CC_TASKS_AUTHOR"
|
|
136
|
+
}
|
|
137
|
+
|
|
96
138
|
# signal_tasks_create — plan arm: files an open task tagged slug:<slug>, so the
|
|
97
139
|
# plan appears in tasks:everywhere the moment it's armed (C1/C2 interface contract).
|
|
98
140
|
signal_tasks_create() { # signal_tasks_create <slug> <title> [tags=a,b,c] [notes=...]
|
|
@@ -105,26 +147,184 @@ signal_tasks_create() { # signal_tasks_create <slug> <title> [tags=a,b,c] [note
|
|
|
105
147
|
esac
|
|
106
148
|
done
|
|
107
149
|
local tags_json; tags_json=$(printf '%s' "$tags" | awk -F',' '{for(i=1;i<=NF;i++){printf "%s\"%s\"", (i>1?",":""), $i}}')
|
|
108
|
-
local
|
|
150
|
+
local title_esc notes_esc
|
|
151
|
+
title_esc="$(_jsonesc "$title")"; notes_esc="$(_jsonesc "$notes")"
|
|
152
|
+
local payload="{\"data\":{\"title\":\"${title_esc}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"tags\":[${tags_json}]${notes:+,\"notes\":\"${notes_esc}\"}}}"
|
|
109
153
|
if $PRINT; then printf '%s\n' "$payload"; return 0; fi
|
|
110
154
|
_post_ask "tasks:create" "$payload"
|
|
111
155
|
}
|
|
112
156
|
|
|
157
|
+
# signal_tasks_plan — file EVERY cycle of a plan as its own task, once.
|
|
158
|
+
#
|
|
159
|
+
# WHY. The loop filed exactly ONE task per plan (do-auto.sh's `.task-created`
|
|
160
|
+
# sentinel) and nothing else. Measured 2026-09-01: text/lifecycle-todo.md carries
|
|
161
|
+
# 10 cycles and its promise 13 deliverables -- 1 task reached the substrate.
|
|
162
|
+
# `signal_tasks_cycle` (the per-cycle status writer, and its --task-cycle flag)
|
|
163
|
+
# was already written and had ZERO callers, because the tasks it sets status on
|
|
164
|
+
# were never created: _tid_for_slug found nothing and it returned 1 every time.
|
|
165
|
+
#
|
|
166
|
+
# So the substrate could not answer "which cycles are ready" -- and that is
|
|
167
|
+
# exactly why do-next.sh re-derives readiness by PARSING MARKDOWN CHECKBOXES, a
|
|
168
|
+
# second source of truth whose divergences are most of this repo's scars.
|
|
169
|
+
#
|
|
170
|
+
# The cycle list is read from do-plan-json.sh, which is a parser, not a model.
|
|
171
|
+
# tasks:create dedupes on the slug tag, so re-running a plan is idempotent.
|
|
172
|
+
signal_tasks_plan() { # signal_tasks_plan <slug>
|
|
173
|
+
local slug="$1" plan cid title n=0
|
|
174
|
+
plan="$(bash "$(dirname "${BASH_SOURCE[0]}")/do-plan-json.sh" "$slug" 2>/dev/null)" || {
|
|
175
|
+
printf 'do-signal: cannot parse plan %s — no cycle tasks filed\n' "$slug" >&2; return 1; }
|
|
176
|
+
# one task per cycle, tagged so the router can fan on any of them
|
|
177
|
+
while IFS=$'\t' read -r cid title; do
|
|
178
|
+
[ -n "$cid" ] && [ "$cid" != "null" ] || continue
|
|
179
|
+
if $PRINT; then
|
|
180
|
+
signal_tasks_create "${slug}-${cid}" "${title:-$slug $cid}" "tags=do:cycle,cycle:${cid#C},slug:${slug}"
|
|
181
|
+
else
|
|
182
|
+
signal_tasks_create "${slug}-${cid}" "${title:-$slug $cid}" "tags=do:cycle,cycle:${cid#C},slug:${slug}" >/dev/null || true
|
|
183
|
+
fi
|
|
184
|
+
n=$((n+1))
|
|
185
|
+
# `.cycles` is an OBJECT KEYED BY CYCLE ID, not an array. The first draft
|
|
186
|
+
# used `.cycles[]? | .id` and got null for every id -- it still reported
|
|
187
|
+
# "filed 2 cycle task(s)", so the count looked right while every task was
|
|
188
|
+
# named `<slug>-` and tagged `cycle:`. A count is not a check.
|
|
189
|
+
done < <(printf '%s' "$plan" | jq -r '.cycles | to_entries[] | "\(.key)\t\(.value.title // .key)"' 2>/dev/null)
|
|
190
|
+
[ "$n" -gt 0 ] || { printf 'do-signal: %s parsed but yielded ZERO cycles — refusing to report success\n' "$slug" >&2; return 1; }
|
|
191
|
+
printf 'do-signal: filed %d cycle task(s) for %s\n' "$n" "$slug"
|
|
192
|
+
}
|
|
193
|
+
|
|
113
194
|
# signal_tasks_claim — cycle open: leases the plan's substrate task so a fleet
|
|
114
195
|
# run (or a human) never double-builds the same slug (do-fleet.sh mirrors this).
|
|
196
|
+
# _tid_for_slug <slug> [title] — resolve a plan slug to its task tid, and cache it.
|
|
197
|
+
#
|
|
198
|
+
# The SERVER's slug→tid resolver is broken in prod: measured 2026-08-31, every
|
|
199
|
+
# tasks:claim/link/heartbeat addressed by `slug` returns not_found, while the
|
|
200
|
+
# same task claimed by `tid` succeeds. That cost a whole campaign's board
|
|
201
|
+
# visibility — the /do engine's claim, six heartbeats and link all failed exit 1
|
|
202
|
+
# on every run, silently, because nothing treats a board write as load-bearing.
|
|
203
|
+
#
|
|
204
|
+
# `tasks:create` is idempotent — it dedupes on the `slug:` tag and returns the
|
|
205
|
+
# EXISTING tid — so create IS a working slug→tid resolver. One call adds the task
|
|
206
|
+
# if missing and resolves it if present, which is also the simplest thing for a
|
|
207
|
+
# human: there is no separate "add" step to forget.
|
|
208
|
+
#
|
|
209
|
+
# Cached under .claude/.task-tids/<slug> so the steady state is zero round-trips.
|
|
210
|
+
_tid_for_slug() {
|
|
211
|
+
local slug="$1" title="${2:-}" dir="$_ROOT/.claude/.task-tids" f tid resp
|
|
212
|
+
f="$dir/$slug"
|
|
213
|
+
if [ -f "$f" ]; then tid=$(cat "$f" 2>/dev/null); [ -n "$tid" ] && { printf '%s' "$tid"; return 0; }; fi
|
|
214
|
+
[ -n "$title" ] || title="$slug — /do plan"
|
|
215
|
+
resp=$(signal_tasks_create "$slug" "$title" 2>/dev/null) || true
|
|
216
|
+
tid=$(printf '%s' "$resp" | sed -n 's/.*"tid":"\([^"]*\)".*/\1/p')
|
|
217
|
+
[ -n "$tid" ] || { printf ''; return 1; }
|
|
218
|
+
mkdir -p "$dir" && printf '%s' "$tid" > "$f"
|
|
219
|
+
printf '%s' "$tid"
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# signal_tasks_cycle <slug> <cid> <status> — the per-CYCLE board row.
|
|
223
|
+
#
|
|
224
|
+
# The plan-level task says "lifecycle is being worked on". It cannot show the
|
|
225
|
+
# thing an operator actually wants to watch: which cycle is in flight and which
|
|
226
|
+
# ones are finished. This files one row per cycle (slug `<plan>-<cid>`, tagged
|
|
227
|
+
# with the plan) and moves it open -> picked -> done as the engine runs.
|
|
228
|
+
#
|
|
229
|
+
# create is idempotent (dedupes on the slug: tag), so calling this on every cycle
|
|
230
|
+
# open is safe and needs no separate "add" step. Status is addressed by tid,
|
|
231
|
+
# because prod's slug resolver returns not_found (see _tid_for_slug).
|
|
232
|
+
signal_tasks_cycle() { # signal_tasks_cycle <slug> <cid> <status>
|
|
233
|
+
local slug="$1" cid="$2" st="${3:-picked}" cslug tid
|
|
234
|
+
cslug="$(printf '%s-%s' "$slug" "$cid" | tr '[:upper:]' '[:lower:]')"
|
|
235
|
+
tid=$(_tid_for_slug "$cslug" "${slug} ${cid}") || true
|
|
236
|
+
[ -n "$tid" ] || return 1
|
|
237
|
+
_post_ask "tasks:status" "{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"status\":\"${st}\"}}"
|
|
238
|
+
}
|
|
239
|
+
|
|
115
240
|
signal_tasks_claim() { # signal_tasks_claim <slug>
|
|
116
241
|
# Address the lease by the plan SLUG, not a guessed tid: create returns a random
|
|
117
242
|
# task:<traceId>, so "task:<slug>" never matched — claim silently 404'd for every /do
|
|
118
243
|
# run until this. The server resolves slug → tid (resolvers/tasks.ts resolveTaskBySlug).
|
|
119
|
-
local slug="$1"
|
|
120
|
-
|
|
244
|
+
local slug="$1" tid
|
|
245
|
+
tid=$(_tid_for_slug "$slug") || true
|
|
246
|
+
local payload
|
|
247
|
+
if [ -n "$tid" ]; then
|
|
248
|
+
payload="{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\"}}"
|
|
249
|
+
else
|
|
250
|
+
payload="{\"data\":{\"slug\":\"${slug}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\"}}"
|
|
251
|
+
fi
|
|
121
252
|
if $PRINT; then printf '%s\n' "$payload"; return 0; fi
|
|
122
253
|
_post_ask "tasks:claim" "$payload"
|
|
123
254
|
}
|
|
124
255
|
|
|
256
|
+
# signal_tasks_reap — the stranded-lease sweep. `tasks:claim` is atomic, so no
|
|
257
|
+
# two workers double-lease; this is the OTHER half — a lease whose owner died is
|
|
258
|
+
# `picked` forever, invisible to the claimable view and blocking every dependent
|
|
259
|
+
# via ready-tasks. Nothing else in the system goes looking for those rows.
|
|
260
|
+
signal_tasks_reap() { # signal_tasks_reap
|
|
261
|
+
local payload="{\"data\":{\"workspace\":\"${CC_TASKS_WORKSPACE}\"}}"
|
|
262
|
+
if $PRINT; then printf '%s\n' "$payload"; return 0; fi
|
|
263
|
+
_post_ask "tasks:reap" "$payload"
|
|
264
|
+
}
|
|
265
|
+
|
|
125
266
|
# signal_tasks_link — cycle close: records the handoff-completion back on the
|
|
126
267
|
# origin task (provenance line + task-status:done), same shape lifecycle.ts's
|
|
127
268
|
# tasks_link MCP tool posts.
|
|
269
|
+
# signal_outcome — /close's loop-closer, and the reason it is world:outcome.
|
|
270
|
+
#
|
|
271
|
+
# THE NAMING GAP, measured. `/api/receivers/signals` ranks every emitted signal
|
|
272
|
+
# name as declared | candidate | unhomed against packages/sdk/src/receivers.ts,
|
|
273
|
+
# because 35 distinct event names were emitted in 90 days and exactly ONE
|
|
274
|
+
# (`world:in`) was a declared receiver. /close was a top contributor: measured
|
|
275
|
+
# 2026-09-09, `do:close` 0, `loop:feedback` 0, `world:do-event` 0 declarations —
|
|
276
|
+
# three of the four names it emitted could not be answered by anything, and it
|
|
277
|
+
# reported success anyway.
|
|
278
|
+
#
|
|
279
|
+
# `world:outcome` is not a new contract written for this. It already says exactly
|
|
280
|
+
# what /close does: "whoever consumed a route reports success/failure, moving the
|
|
281
|
+
# same tag->receiver paths the router reads - success marks, failure warns."
|
|
282
|
+
# So the fix converges four names to one and designs nothing.
|
|
283
|
+
#
|
|
284
|
+
# THE VERB IS READ BACK, NEVER ASSUMED. The receiver answers
|
|
285
|
+
# {ok, outcome:"marked"|"warned", ...}; a caller that prints "marked" because it
|
|
286
|
+
# sent success:true is reporting its own intent, which is the class of defect the
|
|
287
|
+
# whole four-outcome grammar exists to stop.
|
|
288
|
+
#
|
|
289
|
+
# TAGS ARE BARE WORDS. `marketing`, never `lifecycle:marketing` - a namespaced
|
|
290
|
+
# tag matches zero subscribers. `slug:`/`cycle:` are provenance, not stakes, and
|
|
291
|
+
# are appended separately so they can never be mistaken for a routable tag.
|
|
292
|
+
signal_outcome() { # signal_outcome <slug> tags=a,b actor=<aid> success=<bool> weight=<n> [workspace=<ws>]
|
|
293
|
+
local slug="$1"; shift
|
|
294
|
+
local tags="" actor="" success="true" weight="" ws="${CC_TASKS_WORKSPACE}"
|
|
295
|
+
local kv
|
|
296
|
+
for kv in "$@"; do
|
|
297
|
+
case "$kv" in
|
|
298
|
+
tags=*) tags="${kv#*=}" ;;
|
|
299
|
+
actor=*) actor="${kv#*=}" ;;
|
|
300
|
+
success=*) success="${kv#*=}" ;;
|
|
301
|
+
weight=*) weight="${kv#*=}" ;;
|
|
302
|
+
workspace=*) ws="${kv#*=}" ;;
|
|
303
|
+
esac
|
|
304
|
+
done
|
|
305
|
+
[ -n "$slug" ] || { echo "signal_outcome: slug required" >&2; return 2; }
|
|
306
|
+
[ -n "$actor" ] || { echo "signal_outcome: actor= required (world:outcome moves that actor's paths)" >&2; return 2; }
|
|
307
|
+
case "$success" in true|false) ;; *) echo "signal_outcome: success must be true|false" >&2; return 2 ;; esac
|
|
308
|
+
|
|
309
|
+
# A namespaced word here would route to nobody. Refuse it loudly rather than
|
|
310
|
+
# emit a signal that matches zero subscribers and still exits 0.
|
|
311
|
+
local t
|
|
312
|
+
for t in ${tags//,/ }; do
|
|
313
|
+
case "$t" in
|
|
314
|
+
slug:*|cycle:*) ;;
|
|
315
|
+
*:*) echo "signal_outcome: '$t' is namespaced - stake tags are BARE words (see subscribe-tags-parity.test.ts)" >&2; return 2 ;;
|
|
316
|
+
esac
|
|
317
|
+
done
|
|
318
|
+
|
|
319
|
+
tags="${tags:+${tags},}slug:${slug}"
|
|
320
|
+
local tags_json
|
|
321
|
+
tags_json=$(printf '%s' "$tags" | awk -F',' '{for(i=1;i<=NF;i++){if($i!=""){printf "%s\"%s\"", (n++?",":""), $i}}}')
|
|
322
|
+
|
|
323
|
+
local payload="{\"data\":{\"tags\":[${tags_json}],\"actor\":\"$(_jsonesc "$actor")\",\"success\":${success}${weight:+,\"weight\":${weight}},\"slug\":\"$(_jsonesc "$slug")\",\"workspace\":\"$(_jsonesc "$ws")\"}}"
|
|
324
|
+
if $PRINT; then printf '%s\n' "$payload"; return 0; fi
|
|
325
|
+
_post_ask "world:outcome" "$payload"
|
|
326
|
+
}
|
|
327
|
+
|
|
128
328
|
signal_tasks_link() { # signal_tasks_link <slug> [docs=a,b,c]
|
|
129
329
|
# Slug-addressed like claim — the server resolves slug → the origin tid create wrote.
|
|
130
330
|
local slug="$1" docs=""
|
|
@@ -136,9 +336,38 @@ signal_tasks_link() { # signal_tasks_link <slug> [docs=a,b,c]
|
|
|
136
336
|
done
|
|
137
337
|
local docs_json=""
|
|
138
338
|
[ -n "$docs" ] && docs_json=$(printf '%s' "$docs" | awk -F',' '{for(i=1;i<=NF;i++){printf "%s\"%s\"", (i>1?",":""), $i}}')
|
|
139
|
-
local
|
|
339
|
+
local tid; tid=$(_tid_for_slug "$slug") || true
|
|
340
|
+
local payload="{\"data\":{\"slug\":\"${slug}\"${tid:+,\"tid\":\"${tid}\"},\"workspace\":\"${CC_TASKS_WORKSPACE}\"${docs_json:+,\"docs\":[${docs_json}]}}}"
|
|
140
341
|
if $PRINT; then printf '%s\n' "$payload"; return 0; fi
|
|
141
|
-
|
|
342
|
+
# Prod's tasks:link still resolves by slug and ignores an explicit tid, so it
|
|
343
|
+
# returns not_found for every /do close (measured 2026-08-31). Fall back to the
|
|
344
|
+
# two tid-addressed verbs that DO work, so a close is never silently lost:
|
|
345
|
+
# status:done is the state the board renders, the comment is the provenance line.
|
|
346
|
+
if _post_ask "tasks:link" "$payload"; then return 0; fi
|
|
347
|
+
[ -n "$tid" ] || return 1
|
|
348
|
+
_post_ask "tasks:status" "{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"status\":\"done\"}}" >/dev/null || return 1
|
|
349
|
+
_post_ask "tasks:comment" "{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"/do close — ${slug}${docs:+ (docs: ${docs})}\"$(_author_json)}}"
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
# signal_tasks_heartbeat — batch/wave close: posts tasks:comment so the lease
|
|
353
|
+
# stays live (the comment resolver bumps updated-at-ms). Slug-addressed like
|
|
354
|
+
# claim/link; body is the wave-close note (e.g. "wave 2 close").
|
|
355
|
+
signal_tasks_heartbeat() { # signal_tasks_heartbeat <slug> <body>
|
|
356
|
+
local slug="$1"; shift
|
|
357
|
+
local body="$*" body_esc
|
|
358
|
+
body_esc="${body//\\/\\\\}"
|
|
359
|
+
body_esc="${body_esc//\"/\\\"}"
|
|
360
|
+
local tid; tid=$(_tid_for_slug "$slug") || true
|
|
361
|
+
# tasks:comment lets `slug` OVERRIDE `tid`, so the slug must be omitted, not merely
|
|
362
|
+
# accompanied, when the tid is known — otherwise the broken resolver runs anyway.
|
|
363
|
+
local payload
|
|
364
|
+
if [ -n "$tid" ]; then
|
|
365
|
+
payload="{\"data\":{\"tid\":\"${tid}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"${body_esc}\"$(_author_json)}}"
|
|
366
|
+
else
|
|
367
|
+
payload="{\"data\":{\"slug\":\"${slug}\",\"workspace\":\"${CC_TASKS_WORKSPACE}\",\"body\":\"${body_esc}\"$(_author_json)}}"
|
|
368
|
+
fi
|
|
369
|
+
if $PRINT; then printf '%s\n' "$payload"; return 0; fi
|
|
370
|
+
_post_ask "tasks:comment" "$payload"
|
|
142
371
|
}
|
|
143
372
|
|
|
144
373
|
while [ "$#" -gt 0 ]; do
|
|
@@ -156,16 +385,22 @@ while [ "$#" -gt 0 ]; do
|
|
|
156
385
|
done
|
|
157
386
|
printf 'resolved-from: %s\n' "${_which:-<none>}"
|
|
158
387
|
exit 0 ;;
|
|
159
|
-
--task-create)
|
|
160
|
-
--task-claim)
|
|
161
|
-
--task-
|
|
388
|
+
--task-create) shift; signal_tasks_create "$@"; exit $? ;;
|
|
389
|
+
--task-claim) shift; signal_tasks_claim "$@"; exit $? ;;
|
|
390
|
+
--task-cycle) shift; signal_tasks_cycle "$@"; exit $? ;;
|
|
391
|
+
--task-plan) shift; signal_tasks_plan "$@"; exit $? ;;
|
|
392
|
+
--task-reap) shift; signal_tasks_reap "$@"; exit $? ;;
|
|
393
|
+
--task-link) shift; signal_tasks_link "$@"; exit $? ;;
|
|
394
|
+
--task-heartbeat) shift; signal_tasks_heartbeat "$@"; exit $? ;;
|
|
395
|
+
--outcome) shift; signal_outcome "$@"; exit $? ;;
|
|
162
396
|
*) break ;;
|
|
163
397
|
esac
|
|
164
398
|
done
|
|
165
399
|
|
|
166
400
|
if [ "$#" -lt 4 ]; then
|
|
167
401
|
echo "usage: do-signal.sh [--print] <type> <slug> <tier> <cycle> [key=value ...]" >&2
|
|
168
|
-
echo " do-signal.sh [--print] --task-create|--task-claim|--task-link <slug> ..." >&2
|
|
402
|
+
echo " do-signal.sh [--print] --task-create|--task-claim|--task-link|--task-heartbeat <slug> ..." >&2
|
|
403
|
+
echo " do-signal.sh [--print] --outcome <slug> tags=a,b actor=<aid> success=<bool> [weight=<n>]" >&2
|
|
169
404
|
exit 1
|
|
170
405
|
fi
|
|
171
406
|
|
|
@@ -218,20 +453,42 @@ for kv in "$@"; do
|
|
|
218
453
|
esac
|
|
219
454
|
done
|
|
220
455
|
|
|
456
|
+
# is_test discriminator. An emission aimed anywhere but production is a test run by
|
|
457
|
+
# construction, so it defaults to dev; a real prod cycle is a real run and stays
|
|
458
|
+
# is_test=0. There is no automatic way to tell a developer's experimental prod cycle
|
|
459
|
+
# from a genuine one, so that case is the operator's explicit DO_SIGNAL_ENV=dev —
|
|
460
|
+
# which this respects rather than overrides.
|
|
461
|
+
case "$DO_SIGNAL_URL" in
|
|
462
|
+
https://one.ie|https://one.ie/) : ;;
|
|
463
|
+
*) DO_SIGNAL_ENV="${DO_SIGNAL_ENV:-dev}" ;;
|
|
464
|
+
esac
|
|
465
|
+
# DO_SIGNAL_ENV rides in the payload so the receiver can flag the projected run
|
|
466
|
+
# is_test — those cycles are then filterable instead of polluting the operator's list.
|
|
467
|
+
[ -n "${DO_SIGNAL_ENV:-}" ] && EXTRA_JSON="${EXTRA_JSON},\"env\":\"${DO_SIGNAL_ENV}\""
|
|
468
|
+
|
|
221
469
|
PAYLOAD="{\"type\":\"${TYPE}\",\"slug\":\"${SLUG}\",\"tier\":\"${TIER}\",\"cycle\":${CYCLE},\"tags\":[${TAGS}]${EXTRA_JSON}}"
|
|
222
470
|
|
|
471
|
+
# SERVER_SECRET for the Authorization header — _server_secret() (defined above) is the
|
|
472
|
+
# one lookup, shared with the tasks:* verbs. .env historically lacks it;
|
|
473
|
+
# .secrets.generated.local is what push-prod-secrets.sh pushed to the worker, so it
|
|
474
|
+
# matches prod. Without a matching secret the POST is silently 401/403-dropped at the
|
|
475
|
+
# gateway (verified 2026-07-04 — zero do-* threads had ever landed in prod D1).
|
|
476
|
+
SERVER_SECRET="$(_server_secret)"
|
|
477
|
+
|
|
478
|
+
# Dark-cycle guard — resolved BEFORE the --print exit so a dry run reports the same
|
|
479
|
+
# state a real emit would hit. A worktree with no secret emits nothing, and the
|
|
480
|
+
# resulting gap in the run trace reads as "no cycles ran" rather than "the wire was
|
|
481
|
+
# down". One loud stderr line; stdout is untouched, so every existing --print
|
|
482
|
+
# consumer is unaffected.
|
|
483
|
+
if [ -z "$SERVER_SECRET" ]; then
|
|
484
|
+
echo "[do-signal] dark: no secret (SERVER_SECRET unresolved from ${ONE_ENV_FILE}) — this cycle emits nothing" >&2
|
|
485
|
+
fi
|
|
486
|
+
|
|
223
487
|
if $PRINT; then
|
|
224
488
|
printf '%s\n' "$PAYLOAD"
|
|
225
489
|
exit 0
|
|
226
490
|
fi
|
|
227
491
|
|
|
228
|
-
# SERVER_SECRET for the Authorization header — _server_secret() (defined above)
|
|
229
|
-
# is the one lookup, shared with the tasks:* verbs. .env historically lacks it;
|
|
230
|
-
# .secrets.generated.local is what push-prod-secrets.sh pushed to the worker, so
|
|
231
|
-
# it matches prod. Without a matching secret the POST is silently 401/403-dropped
|
|
232
|
-
# at the gateway (verified 2026-07-04 — zero do-* threads had ever landed in prod D1).
|
|
233
|
-
SERVER_SECRET="$(_server_secret)"
|
|
234
|
-
|
|
235
492
|
# POST — fire-and-forget; failures are silent so the build loop is never blocked.
|
|
236
493
|
_url="${DO_SIGNAL_URL}/api/signal/world:do-event"
|
|
237
494
|
if [ -n "$SERVER_SECRET" ]; then
|
package/scripts/do-smoke.sh
CHANGED
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
# the seven-canon predicate (do-reconcile.sh) + navigation reachable/orphan fixtures.
|
|
5
5
|
# Exits 0 only if ALL pass.
|
|
6
6
|
set -uo pipefail
|
|
7
|
+
|
|
8
|
+
# _GATE — route a heavy compute through the machine governor. A gate_lock only
|
|
9
|
+
# dedupes IDENTICAL work; a SLOT is what bounds N worktrees each running one of
|
|
10
|
+
# these at once (measured 2026-09-07: three concurrent 2.5GB typecheckers, every
|
|
11
|
+
# lock uncontended, load 171). Empty when already inside a gate, so a nested call
|
|
12
|
+
# inherits the outer slot rather than taking a second one.
|
|
13
|
+
#
|
|
14
|
+
# Resolves gate-run.sh from its OWN directory, deliberately: an earlier version
|
|
15
|
+
# keyed off $ROOT and got inserted above the line that sets it, so _GATE was
|
|
16
|
+
# silently empty and every call ran ungoverned -- a fail-OPEN, which is the exact
|
|
17
|
+
# defect this preamble exists to close.
|
|
18
|
+
_GATE=()
|
|
19
|
+
if [ "${GOVERN_IN_GATE:-0}" != "1" ]; then
|
|
20
|
+
_GR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
|
|
21
|
+
[ -f "$_GR" ] && _GATE=( bash "$_GR" "compute:$(basename "${BASH_SOURCE[0]}")" -- )
|
|
22
|
+
fi
|
|
23
|
+
|
|
7
24
|
cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit 2
|
|
8
25
|
S=.claude/scripts
|
|
9
26
|
fail=0
|
|
@@ -61,7 +78,7 @@ echo "== C2 do-reconcile — types canon =="
|
|
|
61
78
|
# Use a temp file to capture grep output — avoids pipefail swallowing the value when tsc
|
|
62
79
|
# exits non-zero (which it does whenever there are any type errors).
|
|
63
80
|
_tsc_tmp=$(mktemp)
|
|
64
|
-
( cd one.ie/web 2>/dev/null && bunx tsc --noEmit 2>&1 | grep -c 'error TS' > "$_tsc_tmp" ) 2>/dev/null || true
|
|
81
|
+
( cd one.ie/web 2>/dev/null && "${_GATE[@]}" bunx tsc --noEmit 2>&1 | grep -c 'error TS' > "$_tsc_tmp" ) 2>/dev/null || true
|
|
65
82
|
_tsc_now=$(cat "$_tsc_tmp" 2>/dev/null | tr -d '[:space:]'); rm -f "$_tsc_tmp"
|
|
66
83
|
echo "{\"tscErrors\": ${_tsc_now:-0}}" > .w0-baseline.json
|
|
67
84
|
"$S/do-reconcile.sh" types >/dev/null 2>&1 && ok "types delta=0 → exit 0" || no "types should pass when no delta"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-test-gate.sh — a vitest accept clause that cannot pass vacuously.
|
|
3
|
+
#
|
|
4
|
+
# WHY THIS EXISTS. A promise `accept:` of the form
|
|
5
|
+
# ( cd <workdir> && bunx vitest run tests/unit/<slug>.test.ts )
|
|
6
|
+
# is not proof. Measured on vitest 4.1.7 in this repo (2026-08-18):
|
|
7
|
+
# · a test file full of `it.skip(...)` → exit 0
|
|
8
|
+
# · `-t "name-that-does-not-exist"` → exit 0, "10 skipped"
|
|
9
|
+
# · a file containing only `it('x', () => {})` → exit 0
|
|
10
|
+
# The only shape that DOES go red is a missing file ("No test files found",
|
|
11
|
+
# exit 1). So the clause proves the file exists — the same class of claim as a
|
|
12
|
+
# presence grep, and the same trap: the cycle that must pass the check is the
|
|
13
|
+
# cycle that authors the file.
|
|
14
|
+
#
|
|
15
|
+
# This gate asserts the run actually EXERCISED something: at least <min> tests
|
|
16
|
+
# passed, zero failed, and zero were pending/skipped. A skipped test is a hole,
|
|
17
|
+
# not a pass — `it.skip` in a security accept is the failure mode this closes.
|
|
18
|
+
#
|
|
19
|
+
# Usage: do-test-gate.sh <workdir> <min-passed> <test-file> [<test-file>...]
|
|
20
|
+
# Example (inside a promise accept:):
|
|
21
|
+
# bash .claude/scripts/do-test-gate.sh <workdir> 4 tests/unit/<slug>-persona.test.ts
|
|
22
|
+
#
|
|
23
|
+
# Exit 0 only when: numFailedTests == 0 AND numPendingTests == 0 AND
|
|
24
|
+
# numPassedTests >= min.
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
28
|
+
workdir="${1:?usage: do-test-gate.sh <workdir> <min-passed> <test-file>...}"
|
|
29
|
+
min="${2:?usage: do-test-gate.sh <workdir> <min-passed> <test-file>...}"
|
|
30
|
+
shift 2
|
|
31
|
+
[ "$#" -gt 0 ] || { echo "[test-gate] no test files given" >&2; exit 2; }
|
|
32
|
+
|
|
33
|
+
case "$min" in ''|*[!0-9]*) echo "[test-gate] min-passed must be an integer, got '$min'" >&2; exit 2 ;; esac
|
|
34
|
+
|
|
35
|
+
out="$(mktemp -t do-test-gate.XXXXXX)"
|
|
36
|
+
trap 'rm -f "$out"' EXIT
|
|
37
|
+
|
|
38
|
+
# Missing files must fail here rather than inside vitest's include-glob, whose
|
|
39
|
+
# "No test files found" message is easy to mistake for a red test.
|
|
40
|
+
for f in "$@"; do
|
|
41
|
+
[ -f "$ROOT/$workdir/$f" ] || { echo "[test-gate] FAIL missing test file: $workdir/$f" >&2; exit 1; }
|
|
42
|
+
done
|
|
43
|
+
|
|
44
|
+
# vitest's own exit code is deliberately IGNORED — it is 0 for the vacuous
|
|
45
|
+
# shapes above. The json report is the verdict.
|
|
46
|
+
#
|
|
47
|
+
# GOVERNED. This gate is written to be called from many parallel /do cycles at
|
|
48
|
+
# once, and a bare `bunx vitest` is exactly the ungoverned heavy gate that melted
|
|
49
|
+
# a 10-core box (see .claude/CLAUDE.md § Machine governor): no slot, no wall
|
|
50
|
+
# clock, no process-group reap. Routing through gate-run.sh means N parallel
|
|
51
|
+
# cycles QUEUE on GOVERN_MAX_GATES instead of thrashing, and it also satisfies
|
|
52
|
+
# hook:load-guard, which would otherwise refuse a direct `vitest run` on a
|
|
53
|
+
# saturated box and fail the accept for a reason that has nothing to do with the
|
|
54
|
+
# code. Degrades to a plain exec when the governor lib is absent (CI, fresh clone).
|
|
55
|
+
GATE_RUN="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/gate-run.sh"
|
|
56
|
+
runner=( bash "$GATE_RUN" "test-gate:$workdir" -- )
|
|
57
|
+
[ -x "$GATE_RUN" ] || runner=()
|
|
58
|
+
( cd "$ROOT/$workdir" && "${runner[@]}" bunx vitest run "$@" --reporter=json --outputFile="$out" ) >/dev/null 2>&1 || true
|
|
59
|
+
|
|
60
|
+
python3 - "$out" "$min" "$workdir" "$@" <<'PY'
|
|
61
|
+
import json, sys
|
|
62
|
+
out, min_passed, workdir = sys.argv[1], int(sys.argv[2]), sys.argv[3]
|
|
63
|
+
files = sys.argv[4:]
|
|
64
|
+
try:
|
|
65
|
+
d = json.load(open(out))
|
|
66
|
+
except Exception as e:
|
|
67
|
+
print(f"[test-gate] FAIL no json report ({e}) — the run did not start", file=sys.stderr)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
passed = int(d.get("numPassedTests", 0))
|
|
70
|
+
failed = int(d.get("numFailedTests", 0))
|
|
71
|
+
pending = int(d.get("numPendingTests", 0))
|
|
72
|
+
label = " ".join(files)
|
|
73
|
+
if failed:
|
|
74
|
+
print(f"[test-gate] FAIL {label}: {failed} failing", file=sys.stderr); sys.exit(1)
|
|
75
|
+
if pending:
|
|
76
|
+
print(f"[test-gate] FAIL {label}: {pending} skipped — a skipped assertion is a hole, not a pass", file=sys.stderr); sys.exit(1)
|
|
77
|
+
if passed < min_passed:
|
|
78
|
+
print(f"[test-gate] FAIL {label}: {passed} passed, expected >= {min_passed}", file=sys.stderr); sys.exit(1)
|
|
79
|
+
print(f"[test-gate] OK {workdir}/{label} — {passed} passed, 0 skipped, 0 failed (floor {min_passed})")
|
|
80
|
+
PY
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-tick.sh <slug> <C1> [C2 ...] — tick a cycle's Status-kanban line after a
|
|
3
|
+
# green W4. Run from the plan worktree (or pass DO_TICK_ROOT).
|
|
4
|
+
#
|
|
5
|
+
# manifest: portable
|
|
6
|
+
#
|
|
7
|
+
# WHY (2026-08-31). do-engine had no writer for the tick, so a cycle that PASSED
|
|
8
|
+
# stayed `- [ ]` forever. openCyclesFromTodo/_remaining then reported it open on
|
|
9
|
+
# the next launch, the batch rebuilt work that was already verified green, and
|
|
10
|
+
# because a rebuilt batch never got further the run reported stoppedAt:1 every
|
|
11
|
+
# time. Measured: lifecycle-money C2 was verified green at 15:43 and was being
|
|
12
|
+
# rebuilt from scratch at 16:02. Two W2 agents diagnosed it independently and
|
|
13
|
+
# named it ROOT CAUSE in the census.
|
|
14
|
+
#
|
|
15
|
+
# Deterministic on purpose: ticking is a two-character edit on a line whose exact
|
|
16
|
+
# shape is already the contract. Nothing about it needs a model.
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
SLUG="${1:-}"; shift || true
|
|
19
|
+
[ -n "$SLUG" ] && [ "$#" -gt 0 ] || { echo "usage: do-tick.sh <slug> <C1> [C2 ...]" >&2; exit 2; }
|
|
20
|
+
ROOT="${DO_TICK_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
21
|
+
TODO="$ROOT/text/${SLUG}-todo.md"
|
|
22
|
+
[ -f "$TODO" ] || { echo "do-tick: no such plan: $TODO" >&2; exit 3; }
|
|
23
|
+
|
|
24
|
+
# _is_open <cid> — the ONE reader in this script. Same shape do-auto.sh's
|
|
25
|
+
# _remaining() and do-engine's openCyclesFromTodo use; parity across all four
|
|
26
|
+
# is proven behaviourally by do-cycle-shape-check.sh, not by eyeballing them.
|
|
27
|
+
_is_open() { grep -qE "^[[:space:]]*- \[[ ~]\] \*{0,2}${1}\*{0,2} —" "$TODO"; }
|
|
28
|
+
|
|
29
|
+
# ── the OTHER half of closing a cycle ────────────────────────────────────────
|
|
30
|
+
# This script was the single writer of the markdown tick and touched the
|
|
31
|
+
# substrate ZERO times, so a cycle went `[x]` in a file while the platform task
|
|
32
|
+
# stayed open forever. Two records of the same fact, one of them always stale --
|
|
33
|
+
# and the stale one is what a human or an agent reads on the board.
|
|
34
|
+
#
|
|
35
|
+
# So the tick writes BOTH, here, in the one place a cycle is declared complete.
|
|
36
|
+
#
|
|
37
|
+
# It must not fail the tick. The markdown tick is the ratchet the engine reads to
|
|
38
|
+
# avoid rebuilding proven work; losing it because a gateway was unreachable would
|
|
39
|
+
# trade a stale board for a rebuilt cycle, which is far worse. Instead an
|
|
40
|
+
# unreachable substrate PARKS the update in a machine-keyed pending file and every
|
|
41
|
+
# later tick flushes it first -- so "as soon as complete" degrades to "at the next
|
|
42
|
+
# tick", never to "never". Same debt-ledger shape as the deferred test pins.
|
|
43
|
+
PENDING="${DO_TICK_PENDING:-${TMPDIR:-/tmp}/one-do-tick-pending}"
|
|
44
|
+
_sig() { bash "$(dirname "${BASH_SOURCE[0]}")/do-signal.sh" --task-cycle "$1" "$2" done >/dev/null 2>&1; }
|
|
45
|
+
|
|
46
|
+
_flush_pending() {
|
|
47
|
+
[ -f "$PENDING" ] || return 0
|
|
48
|
+
local left; left="$(mktemp)"
|
|
49
|
+
while IFS=$'\t' read -r pslug pcid; do
|
|
50
|
+
[ -n "$pcid" ] || continue
|
|
51
|
+
_sig "$pslug" "$pcid" || printf '%s\t%s\n' "$pslug" "$pcid" >> "$left"
|
|
52
|
+
done < "$PENDING"
|
|
53
|
+
if [ -s "$left" ]; then mv "$left" "$PENDING"
|
|
54
|
+
echo "do-tick: $(wc -l < "$PENDING" | tr -d ' ') substrate update(s) still pending"
|
|
55
|
+
else rm -f "$left" "$PENDING"; fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
_mark_done_in_substrate() {
|
|
59
|
+
local cid="$1"
|
|
60
|
+
if _sig "$SLUG" "$cid"; then
|
|
61
|
+
echo "do-tick: ${SLUG} ${cid} -> task done (substrate)"
|
|
62
|
+
else
|
|
63
|
+
printf '%s\t%s\n' "$SLUG" "$cid" >> "$PENDING"
|
|
64
|
+
echo "do-tick: substrate unreachable — ${SLUG} ${cid} PARKED in $PENDING (flushed on the next tick)" >&2
|
|
65
|
+
fi
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_flush_pending
|
|
69
|
+
|
|
70
|
+
ticked=0; failed=0
|
|
71
|
+
for cid in "$@"; do
|
|
72
|
+
case "$cid" in C[0-9]|C[0-9][0-9]) ;; *) echo "do-tick: skip bad cycle id '$cid'" >&2; continue ;; esac
|
|
73
|
+
# Only ever [ ] or [~] -> [x]. Never the reverse: the ratchet cannot regress.
|
|
74
|
+
if _is_open "$cid"; then
|
|
75
|
+
# SUBSTRATE FIRST, checkbox second. The task is the record; the checkbox is a
|
|
76
|
+
# projection of it. Writing the file first made these two peers that could
|
|
77
|
+
# disagree -- a dual write, which is the same two-sources-of-truth shape that
|
|
78
|
+
# produced every engine scar here. Written in this order the box can only
|
|
79
|
+
# reach [x] downstream of a task that is already `done`.
|
|
80
|
+
_mark_done_in_substrate "$cid"
|
|
81
|
+
perl -i -pe "s/^(\s*- )\[[ ~]\](\s\*{0,2}${cid}\*{0,2}\s—)/\${1}[x]\${2}/" "$TODO"
|
|
82
|
+
# POST-CONDITION. A fire-and-forget write is how the original bug survived:
|
|
83
|
+
# the engine called a ticker, nobody checked the box actually moved, and the
|
|
84
|
+
# next launch rebuilt proven work. Asserting the write LANDED — with this
|
|
85
|
+
# script's own reader — is the guard that works even when do-engine runs from
|
|
86
|
+
# a cached snapshot, because it runs inside whatever code actually executed.
|
|
87
|
+
if _is_open "$cid"; then
|
|
88
|
+
echo "do-tick: FAILED — ${SLUG} ${cid} is STILL open after the write. The plan will rebuild proven work. Line shape in $TODO does not match the writer." >&2
|
|
89
|
+
failed=$((failed+1))
|
|
90
|
+
else
|
|
91
|
+
echo "do-tick: ${SLUG} ${cid} -> [x]"; ticked=$((ticked+1))
|
|
92
|
+
fi
|
|
93
|
+
else
|
|
94
|
+
echo "do-tick: ${SLUG} ${cid} already closed or absent — no change"
|
|
95
|
+
fi
|
|
96
|
+
done
|
|
97
|
+
|
|
98
|
+
# Report what the ENGINE's next launch will see. A silent success that leaves the
|
|
99
|
+
# same count open is the failure mode this whole file exists to end.
|
|
100
|
+
open_now=$(grep -cE '^[[:space:]]*- \[[ ~]\] \*{0,2}C[0-9]+\*{0,2} —' "$TODO" 2>/dev/null || true)
|
|
101
|
+
echo "do-tick: ${SLUG} ticked=${ticked} open=${open_now:-0}"
|
|
102
|
+
[ "$failed" -eq 0 ] || exit 4
|
package/scripts/do-tier.sh
CHANGED
|
@@ -24,6 +24,8 @@ if [ "${1:-}" = "--self-test" ]; then
|
|
|
24
24
|
chk SCHEMA channels/migrations/0001_init.sql
|
|
25
25
|
chk SCHEMA pay/contracts/sui/sources/one_token.move
|
|
26
26
|
chk FIX pay/contracts/sui/tests/gateway_tests.move
|
|
27
|
+
chk FIX .claude/scripts/x.sh
|
|
28
|
+
chk FIX .claude/workflows/x.js
|
|
27
29
|
chk FEATURE --intent "add billing" one.ie/web/src/api/b.ts
|
|
28
30
|
# Absence of recon must not read as simplicity — no paths → UNSIZED, never PATCH.
|
|
29
31
|
# Intent is ignored by design: "add a null check" and "add a settings page" share a verb.
|
|
@@ -82,6 +84,10 @@ for p in "${paths[@]}"; do
|
|
|
82
84
|
*/migrations/*.sql|migrations/*.sql) schema=true; code=true; doconly=false ;;
|
|
83
85
|
*/tests/*.move) code=true; doconly=false ;; # contract tests are code, not shape
|
|
84
86
|
*.move|*.sol) schema=true; code=true; doconly=false ;;
|
|
87
|
+
# .claude/scripts/*.sh and .claude/workflows/*.js are code that runs gates and
|
|
88
|
+
# fleets, not .claude/'s usual prose arm — must precede the .claude/* PATCH
|
|
89
|
+
# fallback below or they fall through as doc-only.
|
|
90
|
+
.claude/scripts/*.sh|.claude/workflows/*.js) code=true; doconly=false ;;
|
|
85
91
|
*.md|.claude/*|text/*|docs/*) ;;
|
|
86
92
|
"") ;;
|
|
87
93
|
*) code=true; doconly=false ;;
|