@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.
Files changed (111) hide show
  1. package/agents/w1-recon.md +9 -4
  2. package/agents/w2-decide.md +51 -28
  3. package/agents/w3-edit.md +70 -78
  4. package/agents/w4-verify.md +164 -69
  5. package/commands/browser.md +98 -35
  6. package/commands/cc-connect.md +6 -0
  7. package/commands/chat.md +13 -1
  8. package/commands/close.md +45 -19
  9. package/commands/create.md +20 -10
  10. package/commands/db-sync.md +5 -1
  11. package/commands/deploy.md +174 -87
  12. package/commands/do-autonomous.md +31 -20
  13. package/commands/do-improve.md +4 -4
  14. package/commands/do-show.md +4 -3
  15. package/commands/do.md +53 -13
  16. package/commands/go.md +75 -0
  17. package/commands/improve.md +4 -2
  18. package/commands/kill.md +6 -8
  19. package/commands/notify.md +15 -2
  20. package/commands/oo-push.md +24 -9
  21. package/commands/rag.md +6 -0
  22. package/commands/release.md +46 -25
  23. package/commands/restart.md +5 -4
  24. package/commands/see.md +45 -35
  25. package/commands/skill-create.md +38 -5
  26. package/commands/sync.md +21 -11
  27. package/hooks/scripts/auto-continue.sh +70 -0
  28. package/hooks/scripts/branch-pin.sh +85 -0
  29. package/hooks/scripts/git-add-guard.sh +16 -5
  30. package/hooks/scripts/post-edit-check.sh +16 -0
  31. package/hooks/scripts/session-start.sh +13 -8
  32. package/hooks/scripts/sync-priority-todo.sh +5 -0
  33. package/package.json +1 -1
  34. package/rules/api.md +9 -0
  35. package/rules/design.md +13 -3
  36. package/rules/documentation.md +30 -71
  37. package/rules/engine.md +8 -1
  38. package/rules/ui.md +5 -1
  39. package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
  40. package/scripts/browser-check.mjs +44 -103
  41. package/scripts/cc-events-proof.sh +13 -12
  42. package/scripts/chrome.mjs +381 -0
  43. package/scripts/do-analyze.sh +109 -4
  44. package/scripts/do-auto.sh +350 -43
  45. package/scripts/do-consumer-sweep.sh +72 -0
  46. package/scripts/do-derives-check.sh +88 -0
  47. package/scripts/do-fleet.sh +50 -1
  48. package/scripts/do-killswitch-audit.py +33 -14
  49. package/scripts/do-promise-lint.sh +150 -4
  50. package/scripts/do-promise-settle.sh +131 -9
  51. package/scripts/do-prove.sh +197 -17
  52. package/scripts/do-rank.py +606 -20
  53. package/scripts/do-rubric.py +21 -2
  54. package/scripts/do-signal.sh +157 -17
  55. package/scripts/do-smoke.sh +9 -0
  56. package/scripts/do-substrate-check.sh +49 -0
  57. package/scripts/do-tasks-wire-check.sh +81 -0
  58. package/scripts/do-tier.sh +90 -5
  59. package/scripts/do-ui-gate.sh +332 -0
  60. package/scripts/do-untracked-gate.sh +231 -0
  61. package/scripts/do-walk.sh +344 -0
  62. package/scripts/factory-check.sh +747 -0
  63. package/scripts/factory-repo.sh +1361 -0
  64. package/scripts/factory-tasks-check.sh +85 -0
  65. package/scripts/outcome-pull.ts +279 -0
  66. package/scripts/promise-manifest.mjs +167 -0
  67. package/scripts/reconcile-allow.txt +5 -0
  68. package/scripts/rubric-weights.json +64 -0
  69. package/scripts/spine-canary.sh +20 -0
  70. package/scripts/typedb-cluster-status.sh +128 -0
  71. package/scripts/typedb-env.sh +182 -0
  72. package/scripts/typedb-flap-recorder.sh +85 -0
  73. package/scripts/typedb-probes/containment-probe.py +88 -0
  74. package/scripts/typedb-probes/panic-probe-version.py +92 -0
  75. package/scripts/typedb-probes/panic-probe.py +77 -0
  76. package/scripts/typedb-scratch.sh +73 -0
  77. package/scripts/urls-lint.sh +58 -0
  78. package/scripts/w4-rubric.ts +96 -12
  79. package/scripts/wf-check.mjs +75 -0
  80. package/skills/ai-ui/SKILL.md +1 -1
  81. package/skills/astro/SKILL.md +177 -132
  82. package/skills/astro/add-content-collection.md +29 -13
  83. package/skills/astro/check-build.md +19 -13
  84. package/skills/astro/create-component.md +36 -9
  85. package/skills/astro/create-page.md +39 -10
  86. package/skills/astro/optimize-performance.md +36 -8
  87. package/skills/cli/SKILL.md +254 -220
  88. package/skills/dev/SKILL.md +1 -1
  89. package/skills/directory-autofill/SKILL.md +68 -0
  90. package/skills/docs/SKILL.md +4 -4
  91. package/skills/mcp/SKILL.md +186 -156
  92. package/skills/promise-make/SKILL.md +2 -2
  93. package/skills/puck/SKILL.md +465 -250
  94. package/skills/react19/SKILL.md +196 -296
  95. package/skills/reactflow/SKILL.md +387 -412
  96. package/skills/sdk/SKILL.md +167 -106
  97. package/skills/shadcn/SKILL.md +245 -201
  98. package/skills/signal/SKILL.md +5 -0
  99. package/skills/sui/SKILL.md +6 -1
  100. package/skills/tutorial/SKILL.md +15 -0
  101. package/skills/typedb/SKILL.md +427 -217
  102. package/skills/typedb/examples/python-patterns.py +11 -4
  103. package/skills/typedb/examples/query-patterns.tql +8 -3
  104. package/skills/typedb/examples/schema-patterns.tql +4 -2
  105. package/skills/typedb/reference/migration-2x-3x.md +19 -1
  106. package/skills/typedb/reference/python-driver.md +17 -1
  107. package/skills/voice/SKILL.md +117 -0
  108. package/skills/writer/SKILL.md +29 -12
  109. package/templates/template-feature.md +26 -1
  110. package/templates/template-tests.md +4 -0
  111. package/templates/template-todo.md +65 -10
@@ -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) SETUP_ONLY=true; shift ;;
27
- --max-cycles) MAX_CYCLES="$2"; shift 2 ;;
28
- --dry-run) DRY_RUN=true; shift ;;
29
- --gc) GC_ONLY=true; shift ;;
30
- -*) echo "unknown flag: $1" >&2; exit 1 ;;
31
- *) SLUG="$1"; shift ;;
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
- # the current branch. Safe-only: `worktree remove` refuses dirty, `branch -d`
37
- # refuses unmerged. Unmerged/dirty worktrees are listed, never touched.
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="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
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 file exists, we're already inside a parent loop — exit cleanly.
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
- echo "[do-auto] recursion guard: parent loop already running for $SLUG exiting" >&2
73
- exit 0
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. Cap each iteration; on overrun, terminate the conductor
103
- # so the iteration ends, ticks no box, and the stall guard takes over. Generous by
104
- # default (a healthy batch of 3 cycles runs ~20-25 min); override via env.
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
- # W1/W4 scripts (w1-recon.ts, w4-rubric.ts) always exit 2 and defer to agent
108
- # spawns through the Claude Code session (Claude Max subscription path).
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
- BASE="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
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" HEAD >/dev/null
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
- # Run the conductor in the background with a bash-native watchdog (macOS has no
497
- # `timeout`). If it overruns CONDUCTOR_TIMEOUT, the watchdog terminates both the
498
- # subshell and the claude process (matched by its --next-cycle command line).
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
- ( sleep "$CONDUCTOR_TIMEOUT"
504
- if kill -0 "$cond_pid" 2>/dev/null; then
505
- echo "[do-auto] conductor exceeded ${CONDUCTOR_TIMEOUT}s terminating (hang guard); stall guard will halt if no progress" >&2
506
- pkill -f "do ${SLUG} --next-cycle" 2>/dev/null || true
507
- kill -TERM "$cond_pid" 2>/dev/null || true
508
- sleep 5; kill -KILL "$cond_pid" 2>/dev/null || true
509
- fi ) &
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"