@oneie/claude 0.6.0 → 0.7.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/w1-recon.md +9 -4
- package/agents/w2-decide.md +51 -28
- package/agents/w3-edit.md +70 -78
- package/agents/w4-verify.md +164 -69
- package/commands/browser.md +98 -35
- package/commands/cc-connect.md +6 -0
- package/commands/chat.md +13 -1
- package/commands/close.md +45 -19
- package/commands/create.md +20 -10
- package/commands/db-sync.md +5 -1
- package/commands/deploy.md +174 -87
- package/commands/do-autonomous.md +31 -20
- package/commands/do-improve.md +4 -4
- package/commands/do-show.md +4 -3
- package/commands/do.md +53 -13
- package/commands/go.md +75 -0
- package/commands/improve.md +4 -2
- package/commands/kill.md +6 -8
- package/commands/notify.md +15 -2
- package/commands/oo-push.md +24 -9
- package/commands/rag.md +6 -0
- package/commands/release.md +46 -25
- package/commands/restart.md +5 -4
- package/commands/see.md +45 -35
- package/commands/skill-create.md +38 -5
- package/commands/sync.md +21 -11
- package/hooks/scripts/auto-continue.sh +70 -0
- package/hooks/scripts/branch-pin.sh +85 -0
- package/hooks/scripts/git-add-guard.sh +16 -5
- package/hooks/scripts/post-edit-check.sh +16 -0
- package/hooks/scripts/session-start.sh +13 -8
- package/hooks/scripts/sync-priority-todo.sh +5 -0
- package/package.json +1 -1
- package/rules/api.md +9 -0
- package/rules/design.md +13 -3
- package/rules/documentation.md +30 -71
- package/rules/engine.md +8 -1
- package/rules/ui.md +5 -1
- package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
- package/scripts/browser-check.mjs +44 -103
- package/scripts/cc-events-proof.sh +13 -12
- package/scripts/chrome.mjs +381 -0
- package/scripts/do-analyze.sh +109 -4
- package/scripts/do-auto.sh +350 -43
- package/scripts/do-consumer-sweep.sh +72 -0
- package/scripts/do-derives-check.sh +88 -0
- package/scripts/do-fleet.sh +50 -1
- package/scripts/do-killswitch-audit.py +33 -14
- package/scripts/do-promise-lint.sh +150 -4
- package/scripts/do-promise-settle.sh +131 -9
- package/scripts/do-prove.sh +197 -17
- package/scripts/do-rank.py +606 -20
- package/scripts/do-rubric.py +21 -2
- package/scripts/do-signal.sh +157 -17
- package/scripts/do-smoke.sh +9 -0
- package/scripts/do-substrate-check.sh +49 -0
- package/scripts/do-tasks-wire-check.sh +81 -0
- package/scripts/do-tier.sh +90 -5
- package/scripts/do-ui-gate.sh +332 -0
- package/scripts/do-untracked-gate.sh +231 -0
- package/scripts/do-walk.sh +344 -0
- package/scripts/factory-check.sh +747 -0
- package/scripts/factory-repo.sh +1361 -0
- package/scripts/factory-tasks-check.sh +85 -0
- package/scripts/outcome-pull.ts +279 -0
- package/scripts/promise-manifest.mjs +167 -0
- package/scripts/reconcile-allow.txt +5 -0
- package/scripts/rubric-weights.json +64 -0
- package/scripts/spine-canary.sh +20 -0
- package/scripts/typedb-cluster-status.sh +128 -0
- package/scripts/typedb-env.sh +182 -0
- package/scripts/typedb-flap-recorder.sh +85 -0
- package/scripts/typedb-probes/containment-probe.py +88 -0
- package/scripts/typedb-probes/panic-probe-version.py +92 -0
- package/scripts/typedb-probes/panic-probe.py +77 -0
- package/scripts/typedb-scratch.sh +73 -0
- package/scripts/urls-lint.sh +58 -0
- package/scripts/w4-rubric.ts +96 -12
- package/scripts/wf-check.mjs +75 -0
- package/skills/ai-ui/SKILL.md +1 -1
- package/skills/astro/SKILL.md +177 -132
- package/skills/astro/add-content-collection.md +29 -13
- package/skills/astro/check-build.md +19 -13
- package/skills/astro/create-component.md +36 -9
- package/skills/astro/create-page.md +39 -10
- package/skills/astro/optimize-performance.md +36 -8
- package/skills/cli/SKILL.md +254 -220
- package/skills/dev/SKILL.md +1 -1
- package/skills/directory-autofill/SKILL.md +68 -0
- package/skills/docs/SKILL.md +4 -4
- package/skills/mcp/SKILL.md +186 -156
- package/skills/promise-make/SKILL.md +2 -2
- package/skills/puck/SKILL.md +465 -250
- package/skills/react19/SKILL.md +196 -296
- package/skills/reactflow/SKILL.md +387 -412
- package/skills/sdk/SKILL.md +167 -106
- package/skills/shadcn/SKILL.md +245 -201
- package/skills/signal/SKILL.md +5 -0
- package/skills/sui/SKILL.md +6 -1
- package/skills/tutorial/SKILL.md +15 -0
- package/skills/typedb/SKILL.md +427 -217
- package/skills/typedb/examples/python-patterns.py +11 -4
- package/skills/typedb/examples/query-patterns.tql +8 -3
- package/skills/typedb/examples/schema-patterns.tql +4 -2
- package/skills/typedb/reference/migration-2x-3x.md +19 -1
- package/skills/typedb/reference/python-driver.md +17 -1
- package/skills/voice/SKILL.md +117 -0
- package/skills/writer/SKILL.md +29 -12
- package/templates/template-feature.md +26 -1
- package/templates/template-tests.md +4 -0
- package/templates/template-todo.md +65 -10
package/scripts/do-auto.sh
CHANGED
|
@@ -20,23 +20,30 @@ MAX_CYCLES=30
|
|
|
20
20
|
DRY_RUN=false
|
|
21
21
|
GC_ONLY=false
|
|
22
22
|
SETUP_ONLY=false
|
|
23
|
+
SELF_TEST_LIFECYCLE=false
|
|
24
|
+
SELF_TEST_KANBAN=false
|
|
23
25
|
|
|
24
26
|
while [ "$#" -gt 0 ]; do
|
|
25
27
|
case "$1" in
|
|
26
|
-
--setup-only)
|
|
27
|
-
--max-cycles)
|
|
28
|
-
--dry-run)
|
|
29
|
-
--gc)
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
--setup-only) SETUP_ONLY=true; shift ;;
|
|
29
|
+
--max-cycles) MAX_CYCLES="$2"; shift 2 ;;
|
|
30
|
+
--dry-run) DRY_RUN=true; shift ;;
|
|
31
|
+
--gc) GC_ONLY=true; shift ;;
|
|
32
|
+
--self-test-lifecycle) SELF_TEST_LIFECYCLE=true; shift ;;
|
|
33
|
+
--self-test-kanban) SELF_TEST_KANBAN=true; shift ;;
|
|
34
|
+
-*) echo "unknown flag: $1" >&2; exit 1 ;;
|
|
35
|
+
*) SLUG="$1"; shift ;;
|
|
32
36
|
esac
|
|
33
37
|
done
|
|
34
38
|
|
|
35
39
|
# Standalone GC: sweep all worktrees whose do/<slug> branch is fully merged into
|
|
36
|
-
#
|
|
37
|
-
# refuses unmerged. Unmerged/dirty worktrees are listed,
|
|
40
|
+
# trunk (main; DO_BASE_REF overrides). Safe-only: `worktree remove` refuses
|
|
41
|
+
# dirty, `branch -d` refuses unmerged. Unmerged/dirty worktrees are listed,
|
|
42
|
+
# never touched.
|
|
38
43
|
if $GC_ONLY; then
|
|
39
|
-
BASE="$
|
|
44
|
+
BASE="${DO_BASE_REF:-main}"
|
|
45
|
+
git show-ref --verify --quiet "refs/heads/$BASE" \
|
|
46
|
+
|| BASE="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
|
|
40
47
|
removed=0; kept=0
|
|
41
48
|
for wt in .do-worktrees/*/; do
|
|
42
49
|
[ -d "$wt" ] || continue
|
|
@@ -56,6 +63,195 @@ if $GC_ONLY; then
|
|
|
56
63
|
exit 0
|
|
57
64
|
fi
|
|
58
65
|
|
|
66
|
+
# _remaining/_reap_check/_reap_watcher are defined here (ahead of the SLUG
|
|
67
|
+
# requirement) so --self-test-lifecycle can exercise them without a plan.
|
|
68
|
+
_remaining() {
|
|
69
|
+
# Count cycles in the Status section that are open ([ ]) or in-flight ([~]) — i.e. not [x].
|
|
70
|
+
# Pattern starts with '\[' (not '-') so grep never mistakes it for an option flag.
|
|
71
|
+
# grep -c already prints a count (0 on no match) but exits 1 then — capture it so
|
|
72
|
+
# the `|| true` swallows the exit without appending a second "0" to the output.
|
|
73
|
+
# IC3 hardened: requires the em-dash after `C<n> ` so a prose bullet like
|
|
74
|
+
# "C2's collapsed into C1" is never mistaken for a real Status-block line.
|
|
75
|
+
# `^[[:space:]]*` (zero-or-more, not one-or-more) so the --self-test-lifecycle
|
|
76
|
+
# fixture (no leading indentation) still matches.
|
|
77
|
+
local n; n=$(grep -cE '^[[:space:]]*- \[[ ~]\] C[0-9]+ —' "$TODO" 2>/dev/null) || true
|
|
78
|
+
echo "${n:-0}"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# IC3: keep the Status-block checkbox in sync with each cycle's own wave checkboxes —
|
|
82
|
+
# a cycle is done iff its `## C<n>` section has zero remaining `- [ ] W` lines.
|
|
83
|
+
# POSIX-portable (no gawk-only 3-arg match()) — works with BSD awk/sed (macOS) and GNU.
|
|
84
|
+
_sync_status() {
|
|
85
|
+
local todo="$1"
|
|
86
|
+
[ -f "$todo" ] || return 0
|
|
87
|
+
local total_lines
|
|
88
|
+
total_lines=$(wc -l < "$todo" 2>/dev/null | tr -d ' ') || return 0
|
|
89
|
+
# Line numbers of every `## C<n>` header (cycle-section starts) and every
|
|
90
|
+
# `## ` header (section starts in general, used to find where a cycle ends).
|
|
91
|
+
local cid_lines
|
|
92
|
+
cid_lines=$(grep -nE '^## C[0-9]+' "$todo" 2>/dev/null) || true
|
|
93
|
+
[ -z "$cid_lines" ] && return 0
|
|
94
|
+
local all_hdr_lines
|
|
95
|
+
all_hdr_lines=$(grep -nE '^## ' "$todo" 2>/dev/null) || true
|
|
96
|
+
local entry cid start_line end_line section
|
|
97
|
+
while IFS= read -r entry; do
|
|
98
|
+
[ -z "$entry" ] && continue
|
|
99
|
+
start_line="${entry%%:*}"
|
|
100
|
+
cid=$(sed -E 's/^[0-9]+:## (C[0-9]+).*/\1/' <<<"$entry")
|
|
101
|
+
# End line = line number of the next header strictly after start_line, minus 1;
|
|
102
|
+
# if none, end of file.
|
|
103
|
+
end_line=$(awk -F: -v s="$start_line" '$1 > s {print $1; exit}' <<<"$all_hdr_lines")
|
|
104
|
+
if [ -z "$end_line" ]; then
|
|
105
|
+
end_line="$total_lines"
|
|
106
|
+
else
|
|
107
|
+
end_line=$((end_line - 1))
|
|
108
|
+
fi
|
|
109
|
+
section=$(sed -n "$((start_line + 1)),${end_line}p" "$todo" 2>/dev/null)
|
|
110
|
+
if ! printf '%s\n' "$section" | grep -qE '^[[:space:]]*- \[ \] W'; then
|
|
111
|
+
# No open wave line left in this cycle's section — flip the Status-block
|
|
112
|
+
# line for this cid from [ ]/[~] to [x] if not already ticked.
|
|
113
|
+
sed -i.bak -E "s/^([[:space:]]*- \\[)[ ~](\\] ${cid} —)/\\1x\\2/" "$todo" 2>/dev/null || true
|
|
114
|
+
rm -f "${todo}.bak"
|
|
115
|
+
fi
|
|
116
|
+
done <<<"$cid_lines"
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# IC2: reap a lingering conductor whose close-commit already landed. Split into
|
|
120
|
+
# a pure decision (_reap_check, unit-testable) and the polling loop (_reap_watcher).
|
|
121
|
+
_reap_check() {
|
|
122
|
+
local cur_sha="$1" iter_sha="$2" cur_remaining="$3" iter_remaining="$4"
|
|
123
|
+
[ -n "$cur_sha" ] && [ "$cur_sha" != "$iter_sha" ] && [ "$cur_remaining" -lt "$iter_remaining" ]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# interval/grace default to 60s/120s in production; --self-test-lifecycle passes
|
|
127
|
+
# 1s/1s so the fixture doesn't block on real wall-clock time.
|
|
128
|
+
_reap_watcher() {
|
|
129
|
+
local wt="$1" cond_pid="$2" iter_sha="$3" iter_remaining="$4"
|
|
130
|
+
local interval="${5:-60}" grace="${6:-120}"
|
|
131
|
+
# Iteration cap (backstop): the CONDUCTOR_TIMEOUT watchdog is what actually
|
|
132
|
+
# bounds this loop in production (it kills cond_pid, so `kill -0` fails and
|
|
133
|
+
# the while exits) — this cap only guards against a logic regression turning
|
|
134
|
+
# the loop infinite, at production defaults that's ~24h, harmless.
|
|
135
|
+
local max_iters=1440 n=0
|
|
136
|
+
while kill -0 "$cond_pid" 2>/dev/null; do
|
|
137
|
+
n=$((n + 1)); [ "$n" -gt "$max_iters" ] && return 1
|
|
138
|
+
sleep "$interval"
|
|
139
|
+
kill -0 "$cond_pid" 2>/dev/null || break
|
|
140
|
+
local cur_sha cur_remaining
|
|
141
|
+
cur_sha=$(git -C "$wt" rev-parse HEAD 2>/dev/null || echo "")
|
|
142
|
+
cur_remaining=$(_remaining)
|
|
143
|
+
if _reap_check "$cur_sha" "$iter_sha" "$cur_remaining" "$iter_remaining"; then
|
|
144
|
+
echo "[do-auto] reap watcher: close-commit detected (HEAD moved, remaining ${iter_remaining}→${cur_remaining}) — grace ${grace}s" >&2
|
|
145
|
+
sleep "$grace"
|
|
146
|
+
if kill -0 "$cond_pid" 2>/dev/null; then
|
|
147
|
+
kill -TERM "$cond_pid" 2>/dev/null || true
|
|
148
|
+
echo "[do-auto] reaped conductor — close-commit detected" >&2
|
|
149
|
+
fi
|
|
150
|
+
return 0
|
|
151
|
+
fi
|
|
152
|
+
done
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
# IC1: JSON sentinel — {"pid":N,"port":N,"iteration":N,"started_at":"<iso>"}.
|
|
156
|
+
# Port is derived deterministically from the slug so a self-started dev server
|
|
157
|
+
# (do-ui-gate.sh, C5) and the sentinel agree without a coordination file.
|
|
158
|
+
_sentinel_port() {
|
|
159
|
+
echo $(( 4400 + $(cksum <<<"$1" | cut -d' ' -f1) % 100 ))
|
|
160
|
+
}
|
|
161
|
+
_write_sentinel() {
|
|
162
|
+
local path="$1" pid="$2" iteration="$3" slug="$4"
|
|
163
|
+
printf '{"pid":%d,"port":%d,"iteration":%d,"started_at":"%s"}\n' \
|
|
164
|
+
"$pid" "$(_sentinel_port "$slug")" "$iteration" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$path"
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if $SELF_TEST_LIFECYCLE; then
|
|
168
|
+
rc=0
|
|
169
|
+
tmp=$(mktemp -d)
|
|
170
|
+
echo "[self-test-lifecycle] stale sentinel sweep..."
|
|
171
|
+
mkdir -p "$tmp/.do-worktrees/fake-slug"
|
|
172
|
+
sfile="$tmp/.do-worktrees/fake-slug/.do-loop-running"
|
|
173
|
+
_write_sentinel "$sfile" 99999999 1 "fake-slug"
|
|
174
|
+
spid=$(jq -r '.pid // empty' "$sfile" 2>/dev/null || true)
|
|
175
|
+
if [ -n "$spid" ] && kill -0 "$spid" 2>/dev/null; then
|
|
176
|
+
echo "[self-test-lifecycle] FAIL: expected dead pid $spid to fail kill -0" >&2; rc=1
|
|
177
|
+
else
|
|
178
|
+
rm -f "$sfile"
|
|
179
|
+
if [ -f "$sfile" ]; then echo "[self-test-lifecycle] FAIL: sentinel not swept" >&2; rc=1
|
|
180
|
+
else echo "[self-test-lifecycle] PASS: stale sentinel swept"; fi
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
echo "[self-test-lifecycle] reap watcher..."
|
|
184
|
+
grepo=$(mktemp -d)
|
|
185
|
+
( cd "$grepo" && git init -q && git commit --allow-empty -q -m init )
|
|
186
|
+
iter_sha=$(git -C "$grepo" rev-parse HEAD)
|
|
187
|
+
TODO="$tmp/fake-todo.md"
|
|
188
|
+
printf -- '- [x] C1 — reap + sentinel\n- [ ] C2 — kanban sync\n' > "$TODO"
|
|
189
|
+
iter_remaining=$(_remaining)
|
|
190
|
+
sleep 300 & fake_cond=$!
|
|
191
|
+
( cd "$grepo" && git commit --allow-empty -q -m close )
|
|
192
|
+
printf -- '- [x] C1 — reap + sentinel\n- [x] C2 — kanban sync\n' > "$TODO"
|
|
193
|
+
cur_sha=$(git -C "$grepo" rev-parse HEAD); cur_remaining=$(_remaining)
|
|
194
|
+
if _reap_check "$cur_sha" "$iter_sha" "$cur_remaining" "$iter_remaining"; then
|
|
195
|
+
echo "[self-test-lifecycle] PASS: reap check fires on close-commit"
|
|
196
|
+
else
|
|
197
|
+
echo "[self-test-lifecycle] FAIL: reap check should fire" >&2; rc=1
|
|
198
|
+
fi
|
|
199
|
+
_reap_watcher "$grepo" "$fake_cond" "$iter_sha" "$iter_remaining" 1 1
|
|
200
|
+
sleep 1
|
|
201
|
+
if kill -0 "$fake_cond" 2>/dev/null; then
|
|
202
|
+
echo "[self-test-lifecycle] FAIL: fake conductor not reaped" >&2; rc=1; kill -KILL "$fake_cond" 2>/dev/null || true
|
|
203
|
+
else
|
|
204
|
+
echo "[self-test-lifecycle] PASS: fake conductor reaped"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
rm -rf "$tmp" "$grepo"
|
|
208
|
+
[ "$rc" -eq 0 ] && echo "[self-test-lifecycle] all checks passed"
|
|
209
|
+
exit "$rc"
|
|
210
|
+
fi
|
|
211
|
+
|
|
212
|
+
if $SELF_TEST_KANBAN; then
|
|
213
|
+
rc=0
|
|
214
|
+
tmp=$(mktemp -d)
|
|
215
|
+
TODO="$tmp/fake-todo.md"
|
|
216
|
+
cat > "$TODO" <<'EOF'
|
|
217
|
+
## Status
|
|
218
|
+
|
|
219
|
+
Batch 1
|
|
220
|
+
- [x] C1 — reap + sentinel (do-auto lifecycle) state: done
|
|
221
|
+
- [x] W1 · W2 · W3 · W4
|
|
222
|
+
|
|
223
|
+
Batch 2
|
|
224
|
+
- [ ] C2 — kanban sync + do-engine pick state: blocked-on-C1
|
|
225
|
+
- [ ] W1 · W2 · W3 · W4
|
|
226
|
+
|
|
227
|
+
Prose note: C2's collapsed into C1 because both touch the same file — not a real Status line.
|
|
228
|
+
|
|
229
|
+
## C1 — reap + sentinel
|
|
230
|
+
some text, no open wave boxes here
|
|
231
|
+
|
|
232
|
+
## C2 — kanban sync + do-engine pick
|
|
233
|
+
- [ ] W1 · W2 · W3 · W4
|
|
234
|
+
EOF
|
|
235
|
+
before=$(_remaining)
|
|
236
|
+
# Now tick C2's wave line to simulate it finishing, then sync should flip Status too
|
|
237
|
+
sed -i.bak 's/^- \[ \] W1/- [x] W1/' "$TODO" && rm -f "$TODO.bak"
|
|
238
|
+
_sync_status "$TODO"
|
|
239
|
+
after=$(_remaining)
|
|
240
|
+
if grep -qE '^\s*- \[x\] C2 —' "$TODO"; then
|
|
241
|
+
echo "[self-test-kanban] PASS: Status line auto-ticked for C2"
|
|
242
|
+
else
|
|
243
|
+
echo "[self-test-kanban] FAIL: C2 Status line not ticked" >&2; rc=1
|
|
244
|
+
fi
|
|
245
|
+
if [ "$before" -eq 1 ] && [ "$after" -eq 0 ]; then
|
|
246
|
+
echo "[self-test-kanban] PASS: count correct before=$before after=$after"
|
|
247
|
+
else
|
|
248
|
+
echo "[self-test-kanban] FAIL: count wrong before=$before after=$after (expected 1,0)" >&2; rc=1
|
|
249
|
+
fi
|
|
250
|
+
rm -rf "$tmp"
|
|
251
|
+
[ "$rc" -eq 0 ] && echo "[self-test-kanban] all checks passed"
|
|
252
|
+
exit "$rc"
|
|
253
|
+
fi
|
|
254
|
+
|
|
59
255
|
[ -z "$SLUG" ] && { echo "usage: do-auto.sh <slug> [--max-cycles N] [--dry-run] | do-auto.sh --gc" >&2; exit 1; }
|
|
60
256
|
|
|
61
257
|
# Validate slug is safe (kebab-case only) before it touches any shell string.
|
|
@@ -66,11 +262,20 @@ fi
|
|
|
66
262
|
|
|
67
263
|
# Recursion guard: the conductor subprocess runs `/do <slug> --next-cycle` inside
|
|
68
264
|
# the worktree. A buggy conductor may call do-auto.sh again despite the --next-cycle
|
|
69
|
-
# flag. If our sentinel
|
|
265
|
+
# flag. If our JSON sentinel exists AND its pid is alive, we're already inside a
|
|
266
|
+
# parent loop — exit cleanly. A dead pid means a prior run crashed without
|
|
267
|
+
# cleanup (kill -9, machine sleep) — self-heal by sweeping it instead of wedging
|
|
268
|
+
# every future invocation forever.
|
|
70
269
|
_sentinel=".do-worktrees/${SLUG}/.do-loop-running"
|
|
71
270
|
if [ -f "$_sentinel" ]; then
|
|
72
|
-
|
|
73
|
-
|
|
271
|
+
_sentinel_pid=$(jq -r '.pid // empty' "$_sentinel" 2>/dev/null || true)
|
|
272
|
+
if [ -n "$_sentinel_pid" ] && kill -0 "$_sentinel_pid" 2>/dev/null; then
|
|
273
|
+
echo "[do-auto] recursion guard: parent loop already running for $SLUG (pid $_sentinel_pid) — exiting" >&2
|
|
274
|
+
exit 0
|
|
275
|
+
else
|
|
276
|
+
echo "[do-auto] swept stale sentinel (pid ${_sentinel_pid:-unknown} dead)" >&2
|
|
277
|
+
rm -f "$_sentinel"
|
|
278
|
+
fi
|
|
74
279
|
fi
|
|
75
280
|
|
|
76
281
|
# Trust boundary: this script runs in the developer's own workspace, invoked by /do
|
|
@@ -99,13 +304,28 @@ CONDUCTOR_EFFORT="${DO_CONDUCTOR_EFFORT:-medium}"
|
|
|
99
304
|
|
|
100
305
|
# Per-iteration hang guard: `claude -p` has no built-in wall-clock limit, and the
|
|
101
306
|
# stall guard only fires BETWEEN iterations — a conductor that truly hangs would
|
|
102
|
-
# block the loop forever.
|
|
103
|
-
#
|
|
104
|
-
#
|
|
307
|
+
# block the loop forever.
|
|
308
|
+
#
|
|
309
|
+
# F11 (2026-07-23): the guard is PROGRESS-BASED, not wall-clock. The old fixed
|
|
310
|
+
# `sleep CONDUCTOR_TIMEOUT` killed live-but-slow COMPLEX/UI cycles mid-edit (done-ui:
|
|
311
|
+
# a healthy cycle was murdered at 900s while actively editing). Now the watchdog
|
|
312
|
+
# polls the worktree for SILENCE — it resets whenever the conductor writes anything
|
|
313
|
+
# (new/edited files, growing diffs, a commit) and kills only after DO_IDLE_SECS of
|
|
314
|
+
# NO change, with an absolute DO_HARD_CAP backstop against a pathological write-loop.
|
|
315
|
+
# DO_CONDUCTOR_TIMEOUT is retained as the default hard-cap for back-compat.
|
|
105
316
|
CONDUCTOR_TIMEOUT="${DO_CONDUCTOR_TIMEOUT:-2400}"
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
317
|
+
IDLE_SECS="${DO_IDLE_SECS:-600}" # 10 min of total silence = hung
|
|
318
|
+
HARD_CAP="${DO_HARD_CAP:-$(( CONDUCTOR_TIMEOUT > 0 ? CONDUCTOR_TIMEOUT * 3 : 7200 ))}" # absolute backstop
|
|
319
|
+
POLL_SECS="${DO_POLL_SECS:-30}"
|
|
320
|
+
|
|
321
|
+
# W1/W4 scripts (w1-recon.ts, w4-rubric.ts) exit 2 and defer to agent spawns
|
|
322
|
+
# through the Claude Code session (Claude Max subscription path) ONLY when no
|
|
323
|
+
# OPENROUTER_API_KEY resolves. With a key present — and one is present in
|
|
324
|
+
# one.ie/web/.env, which both scripts fall back to — they call OpenRouter and
|
|
325
|
+
# exit 0/1 with a REAL verdict. "Always defers" was wrong and hid that
|
|
326
|
+
# w4-rubric.ts gates for real: its exit 1 fails a wave without any agent
|
|
327
|
+
# adjudicating. Note its number is the TASK rubric (no goal-fit); the cycle gate
|
|
328
|
+
# is W4's own, per .claude/scripts/rubric-weights.json.
|
|
109
329
|
|
|
110
330
|
# ── Worktree isolation (one per plan) ────────────────────────────────────────
|
|
111
331
|
# The loop's existence IS the trigger: do-auto only runs for multi-cycle plans
|
|
@@ -121,7 +341,14 @@ CONDUCTOR_TIMEOUT="${DO_CONDUCTOR_TIMEOUT:-2400}"
|
|
|
121
341
|
# └─ do/<slug> (worktree .do-worktrees/<slug>) ── every cycle commits here
|
|
122
342
|
# plan complete → report `git merge do/<slug>` (human lands it — never auto)
|
|
123
343
|
# halt → worktree persists → re-run /do <slug> resumes in place
|
|
124
|
-
|
|
344
|
+
#
|
|
345
|
+
# BASE is pinned to main, not HEAD: a shared tree parked on someone's feature
|
|
346
|
+
# branch would otherwise seed every new do/<slug> with that branch's commits
|
|
347
|
+
# (plans cross-contaminating across concurrent sessions, 2026-07-21).
|
|
348
|
+
# DO_BASE_REF overrides for a deliberate stack on another branch.
|
|
349
|
+
BASE="${DO_BASE_REF:-main}"
|
|
350
|
+
git show-ref --verify --quiet "refs/heads/$BASE" \
|
|
351
|
+
|| BASE="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
|
|
125
352
|
BR="do/${SLUG}"
|
|
126
353
|
WT=".do-worktrees/${SLUG}"
|
|
127
354
|
|
|
@@ -152,7 +379,7 @@ _setup_worktree() {
|
|
|
152
379
|
git worktree add "$WT" "$BR" >/dev/null
|
|
153
380
|
else
|
|
154
381
|
echo "[do-auto] creating worktree $WT on new branch $BR (from $BASE)"
|
|
155
|
-
git worktree add -b "$BR" "$WT"
|
|
382
|
+
git worktree add -b "$BR" "$WT" "$BASE" >/dev/null
|
|
156
383
|
fi
|
|
157
384
|
_link_deps "$WT"
|
|
158
385
|
|
|
@@ -263,19 +490,20 @@ if ! $DRY_RUN; then
|
|
|
263
490
|
# All loop state now lives in the worktree — read the boxes the subprocess ticks.
|
|
264
491
|
TODO="$WT/text/${SLUG}-todo.md"
|
|
265
492
|
TRUST="$WT/.do-trust.json"
|
|
493
|
+
# Plan arm (tasks-do C1): file the origin substrate task once per plan — a
|
|
494
|
+
# worktree-local sentinel (persists across a resumed loop on the same branch)
|
|
495
|
+
# keeps a re-run from re-creating it.
|
|
496
|
+
if [ ! -f "$WT/.task-created" ]; then
|
|
497
|
+
_title=$(grep -m1 '^title:' "$TODO" 2>/dev/null | sed 's/^title:[[:space:]]*//' | tr -d '"' || true)
|
|
498
|
+
bash "$(dirname "$0")/do-signal.sh" --task-create "$SLUG" "${_title:-$SLUG}" "tags=do:cycle" >/dev/null 2>&1 || true
|
|
499
|
+
touch "$WT/.task-created"
|
|
500
|
+
fi
|
|
266
501
|
else
|
|
267
502
|
TODO="text/${SLUG}-todo.md"
|
|
268
503
|
TRUST=".do-trust.json"
|
|
269
504
|
fi
|
|
270
505
|
|
|
271
|
-
_remaining()
|
|
272
|
-
# Count cycles in the Status section that are open ([ ]) or in-flight ([~]) — i.e. not [x].
|
|
273
|
-
# Pattern starts with '\[' (not '-') so grep never mistakes it for an option flag.
|
|
274
|
-
# grep -c already prints a count (0 on no match) but exits 1 then — capture it so
|
|
275
|
-
# the `|| true` swallows the exit without appending a second "0" to the output.
|
|
276
|
-
local n; n=$(grep -cE '\[[ ~]\] C[0-9]+' "$TODO" 2>/dev/null) || true
|
|
277
|
-
echo "${n:-0}"
|
|
278
|
-
}
|
|
506
|
+
# _remaining() is defined earlier (ahead of the SLUG check, for --self-test-lifecycle).
|
|
279
507
|
|
|
280
508
|
_trust() {
|
|
281
509
|
jq -r '.level // "standard"' "$TRUST" 2>/dev/null || echo "standard"
|
|
@@ -355,7 +583,7 @@ _merge_digest() {
|
|
|
355
583
|
# we threw away). Pure git + jq: cycles closed, diffstat, gate verdict, open items,
|
|
356
584
|
# cycle commits. Written to trunk's root (.do-digest.md) where the human stands.
|
|
357
585
|
local digest=".do-digest.md"
|
|
358
|
-
local cycles shortstat trust composite open mb behind
|
|
586
|
+
local cycles shortstat trust composite open mb behind agents
|
|
359
587
|
cycles=$(grep -cE '\[[xX]\] C[0-9]+' "$TODO" 2>/dev/null || true); cycles=${cycles:-0}
|
|
360
588
|
# Diff from the MERGE-BASE, not BASE..BR. A two-dot BASE..BR diff conflates
|
|
361
589
|
# "what this branch added" with "what trunk advanced while we built" (the
|
|
@@ -368,6 +596,11 @@ _merge_digest() {
|
|
|
368
596
|
composite=$(jq -r '.composite // "?"' "$TRUST" 2>/dev/null || echo "?")
|
|
369
597
|
open=0
|
|
370
598
|
[ -f "$WT/.w4-improvements.json" ] && open=$(jq -r 'if type=="array" then length elif type=="object" then ([.[]?]|length) else 0 end' "$WT/.w4-improvements.json" 2>/dev/null || echo 0)
|
|
599
|
+
# IC7: exact agent count, not a guess — every wave agent appends a census
|
|
600
|
+
# line to $WT/.do-census.jsonl as it settles (do.md wave instructions,
|
|
601
|
+
# do-engine.js agent() wrapper); the digest just counts lines.
|
|
602
|
+
agents=0
|
|
603
|
+
[ -f "$WT/.do-census.jsonl" ] && agents=$(wc -l < "$WT/.do-census.jsonl" 2>/dev/null | tr -d ' ')
|
|
371
604
|
{
|
|
372
605
|
echo "# Merge digest — ${SLUG}"
|
|
373
606
|
echo
|
|
@@ -378,6 +611,7 @@ _merge_digest() {
|
|
|
378
611
|
[ "${behind:-0}" -gt 0 ] && echo "- **Trunk advanced during build:** ${behind} commit(s) — run \`git merge-tree ${BASE} ${BR}\` to confirm a clean merge before landing"
|
|
379
612
|
echo "- **Gate verdict:** trust=${trust} · composite=${composite}"
|
|
380
613
|
echo "- **Open improvements carried:** ${open}"
|
|
614
|
+
echo "- **Agents:** ${agents:-0}"
|
|
381
615
|
echo
|
|
382
616
|
echo "## Files changed (this branch only)"
|
|
383
617
|
git -C "$WT" diff --stat "$mb..$BR" 2>/dev/null | sed 's/^/ /' || true
|
|
@@ -387,6 +621,34 @@ _merge_digest() {
|
|
|
387
621
|
} > "$digest" 2>/dev/null || true
|
|
388
622
|
}
|
|
389
623
|
|
|
624
|
+
# --- Nested-CLI probe (fail-fast) --------------------------------------------
|
|
625
|
+
# do-auto drives every cycle by spawning `claude -p` in a fresh session (line ~729).
|
|
626
|
+
# In environments without a headless claude CLI / auth, those spawns die instantly
|
|
627
|
+
# ("Execution error") and the loop grinds out ZERO work while LOOKING like a stall
|
|
628
|
+
# (verified done-ui 2026-07-23: 3 launches, 0 cycles built, ~45min wasted). Probe
|
|
629
|
+
# once, up front, and fail LOUDLY with the real cause + the Path-2 fallback instead
|
|
630
|
+
# of two silent no-progress iterations. Override with DO_SKIP_CLI_PROBE=1.
|
|
631
|
+
if [ "${DO_SKIP_CLI_PROBE:-0}" != "1" ] && ! $DRY_RUN; then
|
|
632
|
+
( cd "$WT" && unset CLAUDECODE CLAUDE_CODE_CHILD_SESSION CLAUDE_CODE_SESSION_ID AI_AGENT && \
|
|
633
|
+
claude --model "$CONDUCTOR_MODEL" --dangerously-skip-permissions -p "Reply with exactly: PROBE_OK" ) >"$WT/.do-probe.out" 2>&1 &
|
|
634
|
+
_probe_pid=$!
|
|
635
|
+
( sleep 45; kill -0 "$_probe_pid" 2>/dev/null && { pkill -f "PROBE_OK" 2>/dev/null || true; kill -TERM "$_probe_pid" 2>/dev/null || true; } ) &
|
|
636
|
+
_probe_wd=$!
|
|
637
|
+
wait "$_probe_pid" 2>/dev/null || true
|
|
638
|
+
kill "$_probe_wd" 2>/dev/null || true
|
|
639
|
+
if ! grep -q "PROBE_OK" "$WT/.do-probe.out" 2>/dev/null; then
|
|
640
|
+
echo "[do-auto] FATAL: nested \`claude -p\` cannot run in this environment — the conductor would build zero work." >&2
|
|
641
|
+
echo "[do-auto] probe said: $(head -c 200 "$WT/.do-probe.out" 2>/dev/null | tr '\n' ' ')" >&2
|
|
642
|
+
echo "[do-auto] Run Path 2 instead (in-process agents, no nested CLI):" >&2
|
|
643
|
+
echo "[do-auto] Workflow({scriptPath: \".claude/workflows/do-engine.js\", args:{slug:\"$SLUG\"}})" >&2
|
|
644
|
+
rm -f "$WT/.do-probe.out"
|
|
645
|
+
_signal halt "$SLUG" "$(_tier)" 0 "reason=nested-cli-unavailable" 2>/dev/null || true
|
|
646
|
+
exit 3
|
|
647
|
+
fi
|
|
648
|
+
rm -f "$WT/.do-probe.out"
|
|
649
|
+
echo "[do-auto] nested-CLI probe: ok"
|
|
650
|
+
fi
|
|
651
|
+
|
|
390
652
|
i=0
|
|
391
653
|
prev_remaining=-1
|
|
392
654
|
stall=0
|
|
@@ -396,6 +658,7 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
396
658
|
break
|
|
397
659
|
fi
|
|
398
660
|
|
|
661
|
+
_sync_status "$TODO"
|
|
399
662
|
remaining=$(_remaining)
|
|
400
663
|
if [ "$remaining" -eq 0 ]; then
|
|
401
664
|
echo "[do-auto] no open cycles — plan complete"
|
|
@@ -458,6 +721,10 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
458
721
|
|
|
459
722
|
# Emit cycle-open signal (world:do-event type:aim) before the conductor starts.
|
|
460
723
|
_signal aim "$SLUG" "$(_tier)" "$i"
|
|
724
|
+
# Lease the plan's substrate task (tasks-do C1/C5 — claim = lease, same verb
|
|
725
|
+
# do-fleet.sh checks before spawning a worktree). Fire-and-forget: a missing
|
|
726
|
+
# task (never seeded) or a claim race never blocks the loop.
|
|
727
|
+
bash "$(dirname "$0")/do-signal.sh" --task-claim "$SLUG" >/dev/null 2>&1 || true
|
|
461
728
|
|
|
462
729
|
# C6: Trail confidence hint — query KV trail before spawning the conductor.
|
|
463
730
|
# If confidence ≥ 0.8, write .do-trail-hint.json in the worktree so W1 can skip W2.
|
|
@@ -488,32 +755,65 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
488
755
|
# Fresh context, isolated tree: the subprocess runs INSIDE the worktree, so
|
|
489
756
|
# every edit/box-tick/state-write lands on branch $BR — trunk is never touched.
|
|
490
757
|
# Sentinel prevents the conductor from spawning a recursive do-auto.sh loop.
|
|
491
|
-
touch "$WT/.do-loop-running"
|
|
492
758
|
# Pick the conductor model — fallback to sonnet (world:pick-model not yet wired).
|
|
493
759
|
_shape_pick="do:$(_tier)"
|
|
494
760
|
if [ -n "${_axis:-}" ]; then _shape_pick="${_shape_pick}·${_axis}"; fi
|
|
495
761
|
|
|
496
|
-
#
|
|
497
|
-
#
|
|
498
|
-
|
|
762
|
+
# HEAD baseline + open-cycle baseline captured BEFORE the conductor runs — the
|
|
763
|
+
# reap watcher (IC2) compares against these to detect a close-commit.
|
|
764
|
+
_iter_sha=$(git -C "$WT" rev-parse HEAD 2>/dev/null || echo "")
|
|
765
|
+
_iter_remaining="$remaining"
|
|
766
|
+
|
|
767
|
+
# Run the conductor in the background with a bash-native, PROGRESS-BASED watchdog
|
|
768
|
+
# (macOS has no `timeout`). It kills the conductor only after IDLE_SECS of worktree
|
|
769
|
+
# SILENCE (or the HARD_CAP absolute backstop) — never mid-edit on a slow-but-live
|
|
770
|
+
# cycle. Matches the claude process by its --next-cycle command line.
|
|
499
771
|
# Unset Claude Code session vars so the subprocess starts a fresh session instead
|
|
500
772
|
# of being blocked by the anti-nesting guard (CLAUDECODE/CLAUDE_CODE_CHILD_SESSION).
|
|
501
773
|
( cd "$WT" && unset CLAUDECODE CLAUDE_CODE_CHILD_SESSION CLAUDE_CODE_SESSION_ID AI_AGENT && claude --model "$CONDUCTOR_MODEL" --effort "$CONDUCTOR_EFFORT" --dangerously-skip-permissions -p "/do $SLUG --next-cycle${AUTO_FLAG}" ) &
|
|
502
774
|
cond_pid=$!
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
775
|
+
_write_sentinel "$WT/.do-loop-running" "$cond_pid" "$i" "$SLUG"
|
|
776
|
+
# Progress signal (F11): ANY file written under the worktree since the last poll —
|
|
777
|
+
# code edits, new files, commits, AND gitignored state (.w2-spec.json, .w1-cache/,
|
|
778
|
+
# .do-census.jsonl) that a git-only signal would miss during a long W1/W2 phase that
|
|
779
|
+
# writes no tracked code. `find -newer <marker> | head -1` is POSIX-portable (BSD +
|
|
780
|
+
# GNU). Marker lives OUTSIDE the worktree so it never lands in a commit; excludes
|
|
781
|
+
# .git + node_modules (the linked heavy tree).
|
|
782
|
+
_wd_marker="${TMPDIR:-/tmp}/.do-wd-${SLUG}-$$"; : > "$_wd_marker"
|
|
783
|
+
( _t0=$(date +%s); _last_change=$_t0
|
|
784
|
+
while kill -0 "$cond_pid" 2>/dev/null; do
|
|
785
|
+
sleep "$POLL_SECS"
|
|
786
|
+
_now=$(date +%s)
|
|
787
|
+
if [ -n "$(find "$WT" -type f -not -path '*/.git/*' -not -path '*/node_modules/*' -newer "$_wd_marker" 2>/dev/null | head -1)" ]; then
|
|
788
|
+
: > "$_wd_marker"; _last_change=$_now
|
|
789
|
+
fi
|
|
790
|
+
_idle=$(( _now - _last_change )); _elapsed=$(( _now - _t0 ))
|
|
791
|
+
if [ "$_idle" -ge "$IDLE_SECS" ]; then
|
|
792
|
+
echo "[do-auto] conductor idle ${_idle}s (no worktree write ≥ ${IDLE_SECS}s) — terminating (progress guard); stall guard halts if no box ticked." >&2
|
|
793
|
+
pkill -f "do ${SLUG} --next-cycle" 2>/dev/null || true
|
|
794
|
+
kill -TERM "$cond_pid" 2>/dev/null || true; sleep 5; kill -KILL "$cond_pid" 2>/dev/null || true; break
|
|
795
|
+
fi
|
|
796
|
+
if [ "$_elapsed" -ge "$HARD_CAP" ]; then
|
|
797
|
+
echo "[do-auto] conductor exceeded hard cap ${HARD_CAP}s (writing but never closing) — terminating (backstop)." >&2
|
|
798
|
+
pkill -f "do ${SLUG} --next-cycle" 2>/dev/null || true
|
|
799
|
+
kill -TERM "$cond_pid" 2>/dev/null || true; sleep 5; kill -KILL "$cond_pid" 2>/dev/null || true; break
|
|
800
|
+
fi
|
|
801
|
+
done
|
|
802
|
+
rm -f "$_wd_marker" ) &
|
|
510
803
|
wd_pid=$!
|
|
804
|
+
# IC2: reap a conductor that's already committed its close but is still idling
|
|
805
|
+
# (e.g. waiting on a tool call that never returns) — the 3600s+ hang guard above
|
|
806
|
+
# stays as the backstop for a truly stuck conductor; this catches the faster,
|
|
807
|
+
# more common case of "done but not exiting."
|
|
808
|
+
TODO="$WT/text/${SLUG}-todo.md" _reap_watcher "$WT" "$cond_pid" "$_iter_sha" "$_iter_remaining" &
|
|
809
|
+
reap_pid=$!
|
|
511
810
|
wait "$cond_pid" 2>/dev/null || true
|
|
512
811
|
kill "$wd_pid" 2>/dev/null || true # conductor finished first — cancel the watchdog
|
|
812
|
+
kill "$reap_pid" 2>/dev/null || true # conductor finished first — cancel the reap watcher
|
|
513
813
|
# Kill any child do-auto.sh the conductor may have spawned despite --next-cycle.
|
|
514
814
|
pkill -f "do-auto.sh ${SLUG}" 2>/dev/null || true
|
|
515
815
|
sleep 1
|
|
516
|
-
rm -f "$WT/.do-loop-running"
|
|
816
|
+
rm -f "$WT/.do-loop-running" "${_wd_marker:-}" # marker cleanup (subshell's rm is skipped when the conductor finishes first)
|
|
517
817
|
|
|
518
818
|
# Objective security gate (W4 bash-first, zero-LLM): before the cycle lands on
|
|
519
819
|
# the branch, secret-scan the worktree + audit deps. A scored security score can
|
|
@@ -553,10 +853,13 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
553
853
|
_shape=""
|
|
554
854
|
[ -n "$_axis" ] && _shape="shape=do:$(_tier)·${_axis}"
|
|
555
855
|
_model="model=${CONDUCTOR_MODEL}"
|
|
856
|
+
# worktags = the plan's dept+topic tags (the vocabulary real tasks carry) — the reputation
|
|
857
|
+
# harvest credits these tag-nodes so tasks:mine ranks by proven work (text/reputation.md § A).
|
|
858
|
+
_worktags=$(python3 "$(dirname "$0")/do-rank.py" --worktags "$SLUG" 2>/dev/null || true)
|
|
556
859
|
if [ -n "$_composite" ]; then
|
|
557
|
-
_signal learn "$SLUG" "$(_tier)" "$i" "composite=${_composite}" ${_shape:+"$_shape"} "$_model"
|
|
860
|
+
_signal learn "$SLUG" "$(_tier)" "$i" "composite=${_composite}" ${_shape:+"$_shape"} "$_model" ${_worktags:+"worktags=$_worktags"}
|
|
558
861
|
else
|
|
559
|
-
_signal learn "$SLUG" "$(_tier)" "$i" ${_shape:+"$_shape"} "$_model"
|
|
862
|
+
_signal learn "$SLUG" "$(_tier)" "$i" ${_shape:+"$_shape"} "$_model" ${_worktags:+"worktags=$_worktags"}
|
|
560
863
|
fi
|
|
561
864
|
done
|
|
562
865
|
|
|
@@ -576,6 +879,10 @@ fi
|
|
|
576
879
|
# away the reasoning to stay cheap, so the digest is the only thing left to read
|
|
577
880
|
# before an irreversible merge.
|
|
578
881
|
_merge_digest
|
|
882
|
+
# Close the loop on the origin task (tasks-do C1): the handoff-completion
|
|
883
|
+
# record — provenance line + task-status:done — lands once, at PLAN close,
|
|
884
|
+
# not per cycle (a plan's cycles all serve the same origin task).
|
|
885
|
+
bash "$(dirname "$0")/do-signal.sh" --task-link "$SLUG" "docs=${SLUG}" >/dev/null 2>&1 || true
|
|
579
886
|
echo ""
|
|
580
887
|
echo "[do-auto] ✓ plan complete on branch $BR — trunk ($BASE) untouched."
|
|
581
888
|
echo "[do-auto] read: .do-digest.md # what landed + gate verdict (reasoning was discarded)"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-consumer-sweep.sh — path-prefix table mapping a changed public surface to
|
|
3
|
+
# the commands that prove its consumers still build. W4's RECONCILES gate can
|
|
4
|
+
# see that A changed; it can't see that B silently broke because it imports A.
|
|
5
|
+
# This does — by rule, not by LLM judgment.
|
|
6
|
+
#
|
|
7
|
+
# Usage: do-consumer-sweep.sh [--exec] <changed-file>...
|
|
8
|
+
# do-consumer-sweep.sh --self-test
|
|
9
|
+
# default: prints one command per matched rule (no execution)
|
|
10
|
+
# --exec: also runs each printed command; any non-zero exit fails the sweep
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
cd "$(dirname "${BASH_SOURCE[0]}")/../.." || exit 2
|
|
13
|
+
|
|
14
|
+
EXEC=false
|
|
15
|
+
FILES=()
|
|
16
|
+
for a in "$@"; do
|
|
17
|
+
case "$a" in
|
|
18
|
+
--exec) EXEC=true ;;
|
|
19
|
+
--self-test) SELF_TEST=true ;;
|
|
20
|
+
*) FILES+=("$a") ;;
|
|
21
|
+
esac
|
|
22
|
+
done
|
|
23
|
+
SELF_TEST="${SELF_TEST:-false}"
|
|
24
|
+
|
|
25
|
+
# rule: <path-prefix or exact path>|<command> (one rule may repeat a prefix for multiple commands)
|
|
26
|
+
RULES='
|
|
27
|
+
packages/sdk/src/|(cd packages/cli && bun run test)
|
|
28
|
+
packages/sdk/src/|(cd packages/mcp && bunx tsc --noEmit)
|
|
29
|
+
schema/one.tql|(cd packages/sdk && bun run generate:base && git diff --exit-code src/generated/)
|
|
30
|
+
packages/sdk/src/receivers.ts|(cd one.ie/web && bunx vitest run tests/unit/done/surfaces.test.ts)
|
|
31
|
+
'
|
|
32
|
+
|
|
33
|
+
_match() {
|
|
34
|
+
local f="$1" prefix cmd matched=false
|
|
35
|
+
while IFS='|' read -r prefix cmd; do
|
|
36
|
+
[ -z "$prefix" ] && continue
|
|
37
|
+
case "$f" in
|
|
38
|
+
"$prefix"*) matched=true; echo "$cmd" ;;
|
|
39
|
+
esac
|
|
40
|
+
done <<< "$RULES"
|
|
41
|
+
$matched
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if $SELF_TEST; then
|
|
45
|
+
echo "[consumer-sweep] self-test: packages/sdk/src/client.ts"
|
|
46
|
+
out=$(_match "packages/sdk/src/client.ts" || true)
|
|
47
|
+
ok=true
|
|
48
|
+
echo "$out" | grep -q "cd packages/cli && bun run test" || { echo "[consumer-sweep] FAIL: cli rule missing" >&2; ok=false; }
|
|
49
|
+
echo "$out" | grep -q "cd packages/mcp && bunx tsc --noEmit" || { echo "[consumer-sweep] FAIL: mcp rule missing" >&2; ok=false; }
|
|
50
|
+
$ok && echo "[consumer-sweep] self-test OK" || exit 1
|
|
51
|
+
exit 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
[ "${#FILES[@]}" -eq 0 ] && { echo "[consumer-sweep] no changed files given — nothing to sweep"; exit 0; }
|
|
55
|
+
|
|
56
|
+
any=false
|
|
57
|
+
rc=0
|
|
58
|
+
for f in "${FILES[@]}"; do
|
|
59
|
+
cmds=$(_match "$f" || true)
|
|
60
|
+
[ -z "$cmds" ] && continue
|
|
61
|
+
any=true
|
|
62
|
+
while IFS= read -r cmd; do
|
|
63
|
+
[ -z "$cmd" ] && continue
|
|
64
|
+
echo "[consumer-sweep] $f → $cmd"
|
|
65
|
+
if $EXEC; then
|
|
66
|
+
eval "$cmd" || { echo "[consumer-sweep] FAIL: consumer command failed for $f: $cmd" >&2; rc=1; }
|
|
67
|
+
fi
|
|
68
|
+
done <<< "$cmds"
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
$any || echo "[consumer-sweep] no rule matched any changed file — nothing to sweep"
|
|
72
|
+
exit "$rc"
|