@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-auto.sh
CHANGED
|
@@ -22,44 +22,130 @@ GC_ONLY=false
|
|
|
22
22
|
SETUP_ONLY=false
|
|
23
23
|
SELF_TEST_LIFECYCLE=false
|
|
24
24
|
SELF_TEST_KANBAN=false
|
|
25
|
+
SELF_TEST_DAEMON=false
|
|
26
|
+
STATUS_ONLY=false
|
|
27
|
+
DAEMON=false
|
|
25
28
|
|
|
26
29
|
while [ "$#" -gt 0 ]; do
|
|
27
30
|
case "$1" in
|
|
28
31
|
--setup-only) SETUP_ONLY=true; shift ;;
|
|
32
|
+
--status) STATUS_ONLY=true; shift ;;
|
|
33
|
+
--daemon) DAEMON=true; shift ;;
|
|
29
34
|
--max-cycles) MAX_CYCLES="$2"; shift 2 ;;
|
|
30
35
|
--dry-run) DRY_RUN=true; shift ;;
|
|
31
36
|
--gc) GC_ONLY=true; shift ;;
|
|
32
37
|
--self-test-lifecycle) SELF_TEST_LIFECYCLE=true; shift ;;
|
|
33
38
|
--self-test-kanban) SELF_TEST_KANBAN=true; shift ;;
|
|
39
|
+
--self-test-daemon) SELF_TEST_DAEMON=true; shift ;;
|
|
34
40
|
-*) echo "unknown flag: $1" >&2; exit 1 ;;
|
|
35
41
|
*) SLUG="$1"; shift ;;
|
|
36
42
|
esac
|
|
37
43
|
done
|
|
38
44
|
|
|
39
|
-
# Standalone GC: sweep
|
|
40
|
-
# trunk
|
|
41
|
-
#
|
|
42
|
-
#
|
|
45
|
+
# Standalone GC: sweep every worktree whose branch is fully merged into the
|
|
46
|
+
# RELEASE trunk. Safe-only: `worktree remove` refuses a dirty tree and
|
|
47
|
+
# `branch -d` refuses an unmerged branch, so live work is listed, never touched.
|
|
48
|
+
#
|
|
49
|
+
# TWO DIRECTORIES, not one. `.do-worktrees/<slug>` carries branch `do/<slug>`;
|
|
50
|
+
# `.claude/worktrees/<name>` carries whatever branch is checked out there, which
|
|
51
|
+
# is the door root CLAUDE.md sends every session through. Only the first was ever
|
|
52
|
+
# swept, and the canon said so out loud — "swept by nothing" — for as long as the
|
|
53
|
+
# second existed. Measured 2026-09-06: 36 worktrees, 24 of them fully landed,
|
|
54
|
+
# 6.4G, and the box at load 16 funding ONE concurrent gate because each one costs
|
|
55
|
+
# a tsserver. Sweeping 17 took load to 3.5.
|
|
56
|
+
#
|
|
57
|
+
# THE BASE IS `main`, DELIBERATELY, AND NOT `dev`. Branches are cut from dev and
|
|
58
|
+
# land into dev (§ the loop), so measuring against dev would delete a worktree
|
|
59
|
+
# the moment its work integrated — which is BEFORE the dev→main PR is reviewed.
|
|
60
|
+
# A reviewer asking for changes would find the branch already gone. A branch is
|
|
61
|
+
# finished when it is released, not when it is integrated. DO_BASE_REF overrides.
|
|
43
62
|
if $GC_ONLY; then
|
|
44
|
-
BASE="${
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
BASE="${DO_GC_BASE:-${DO_BASE_REF_GC:-main}}"
|
|
64
|
+
# RESOLVE ANY COMMIT-ISH, AND NEVER SUBSTITUTE ONE SILENTLY. This used to be
|
|
65
|
+
# `show-ref --verify refs/heads/$BASE`, which accepts only a LOCAL branch — so
|
|
66
|
+
# `DO_GC_BASE=origin/main`, the obvious way to sweep against the real trunk,
|
|
67
|
+
# failed the check and fell back to the invoking branch. That is the branch the
|
|
68
|
+
# override exists to escape, and the run reported `base=main` as if it had
|
|
69
|
+
# worked. Measured 2026-09-07: local main sat 131 commits behind origin/main,
|
|
70
|
+
# so every landed branch read as "142 unmerged commit(s)" and the sweep hoarded
|
|
71
|
+
# 1.0 GB of finished worktrees. A base that cannot be resolved is now a REFUSAL,
|
|
72
|
+
# not a fallback — the whole point of --gc is that it never deletes live work,
|
|
73
|
+
# and measuring against the wrong ref is how it would.
|
|
74
|
+
if ! git rev-parse --verify --quiet "${BASE}^{commit}" >/dev/null 2>&1; then
|
|
75
|
+
echo "[do-auto] gc: base '$BASE' does not resolve to a commit — refusing to sweep" >&2
|
|
76
|
+
echo "[do-auto] (a wrong base makes landed work look unmerged, and unlanded work look landed)" >&2
|
|
77
|
+
exit 2
|
|
78
|
+
fi
|
|
79
|
+
# Say what was measured against, so a STALE base is visible in the output
|
|
80
|
+
# rather than inferred from a surprising verdict. `main` 131 commits behind its
|
|
81
|
+
# upstream is not a wrong ref — it is a right ref pointing at the wrong commit,
|
|
82
|
+
# and nothing in the old output could tell those apart.
|
|
83
|
+
BASE_SHA="$(git rev-parse --short "${BASE}^{commit}")"
|
|
84
|
+
BASE_NOTE=""
|
|
85
|
+
if _gc_up="$(git rev-parse --abbrev-ref --symbolic-full-name "${BASE}@{upstream}" 2>/dev/null)"; then
|
|
86
|
+
_gc_behind="$(git rev-list --count "${BASE}..${_gc_up}" 2>/dev/null || echo 0)"
|
|
87
|
+
[ "${_gc_behind:-0}" -gt 0 ] && BASE_NOTE=" — STALE: ${_gc_behind} commit(s) behind ${_gc_up}"
|
|
88
|
+
fi
|
|
89
|
+
echo "[do-auto] gc: measuring against ${BASE} (${BASE_SHA})${BASE_NOTE}"
|
|
90
|
+
# Never sweep the branches the loop itself stands on, whatever their state.
|
|
91
|
+
GC_KEEP="${GC_KEEP:-main dev release}"
|
|
47
92
|
removed=0; kept=0
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
93
|
+
|
|
94
|
+
# The predicate lives in lib/gc-finished.sh — shared, so its red proof
|
|
95
|
+
# (gc-content-check.sh) drives the same code the sweep runs.
|
|
96
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib/gc-finished.sh"
|
|
97
|
+
|
|
98
|
+
_gc_one() { # <worktree-dir> <branch>
|
|
99
|
+
local wt="$1" b="$2"
|
|
100
|
+
case " $GC_KEEP " in *" $b "*) echo "[do-auto] gc: $wt is $b — infrastructure, kept"; kept=$((kept+1)); return ;; esac
|
|
101
|
+
|
|
102
|
+
if ! gc_finished "$BASE" "$b"; then
|
|
103
|
+
echo "[do-auto] gc: $b carries $(gc_carries "$BASE" "$b") path(s) $BASE does not have — kept"; kept=$((kept+1)); return
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
# Finished. The only thing that still earns a keep is UNCOMMITTED work — that
|
|
107
|
+
# is content no merge has seen and no ref points at, so losing it loses it.
|
|
108
|
+
local real; real="$(gc_real_dirt "$wt")"
|
|
109
|
+
if [ -n "$real" ]; then
|
|
110
|
+
echo "[do-auto] gc: $b is in $BASE but its worktree has uncommitted work — kept:"
|
|
111
|
+
echo "$real" | sed "s/^/[do-auto] gc: /"
|
|
112
|
+
kept=$((kept+1)); return
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# --force gets past ephemeral dirt only: the check above already refused
|
|
116
|
+
# anything that was work. `branch -d` asks "is it merged into HEAD", which is
|
|
117
|
+
# the wrong question here (HEAD is usually main and $BASE is usually dev), so
|
|
118
|
+
# -D is correct once content identity is proven — and it is proven, above.
|
|
119
|
+
if git worktree remove --force "$wt" 2>/dev/null; then
|
|
120
|
+
git branch -D "$b" >/dev/null 2>&1 \
|
|
121
|
+
&& echo "[do-auto] gc: removed $wt + branch $b (content already in $BASE)" \
|
|
122
|
+
|| echo "[do-auto] gc: removed $wt (branch $b kept — could not delete ref)"
|
|
123
|
+
removed=$((removed+1))
|
|
58
124
|
else
|
|
59
|
-
echo "[do-auto] gc: $b
|
|
125
|
+
echo "[do-auto] gc: $b is in $BASE but its worktree would not remove — inspect $wt"; kept=$((kept+1))
|
|
60
126
|
fi
|
|
61
|
-
|
|
62
|
-
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# ENUMERATE FROM GIT, NOT FROM A GLOB. `for wt in .claude/worktrees/*/` is
|
|
130
|
+
# relative to the INVOKING tree, and a worktree contains no worktrees — so run
|
|
131
|
+
# from anywhere but main and the sweep matches nothing and reports success.
|
|
132
|
+
# Measured 2026-09-06 on the first run of this code: `removed=0 kept=0` with 19
|
|
133
|
+
# worktrees live. It is the same trap the canon already records for land.sh and
|
|
134
|
+
# release.sh ("Run MAIN's copy"), and a glob cannot be told apart from a clean
|
|
135
|
+
# sweep by its output. `git worktree list` answers with absolute paths from any
|
|
136
|
+
# tree, so there is no copy to run.
|
|
137
|
+
SELF="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
138
|
+
while IFS= read -r wt; do
|
|
139
|
+
[ -d "$wt" ] || continue
|
|
140
|
+
case "$wt" in *"/.do-worktrees/"*|*"/.claude/worktrees/"*) ;; *) continue ;; esac
|
|
141
|
+
# Never saw off the branch you are sitting on.
|
|
142
|
+
[ "$wt" = "$SELF" ] && { echo "[do-auto] gc: $wt is the invoking tree — kept"; kept=$((kept+1)); continue; }
|
|
143
|
+
b="$(git -C "$wt" branch --show-current 2>/dev/null)"
|
|
144
|
+
[ -n "$b" ] || { echo "[do-auto] gc: $wt is detached — kept"; kept=$((kept+1)); continue; }
|
|
145
|
+
_gc_one "$wt" "$b"
|
|
146
|
+
done < <(git worktree list --porcelain | sed -n 's/^worktree //p')
|
|
147
|
+
|
|
148
|
+
echo "[do-auto] gc done: removed=$removed kept=$kept (base=$BASE ${BASE_SHA}${BASE_NOTE})"
|
|
63
149
|
exit 0
|
|
64
150
|
fi
|
|
65
151
|
|
|
@@ -74,10 +160,21 @@ _remaining() {
|
|
|
74
160
|
# "C2's collapsed into C1" is never mistaken for a real Status-block line.
|
|
75
161
|
# `^[[:space:]]*` (zero-or-more, not one-or-more) so the --self-test-lifecycle
|
|
76
162
|
# fixture (no leading indentation) still matches.
|
|
77
|
-
local n; n=$(grep -cE '^[[:space:]]*- \[[ ~]\] C[0-9]
|
|
163
|
+
local n; n=$(grep -cE '^[[:space:]]*- \[[ ~]\] \*{0,2}C[0-9]+\*{0,2} —' "$TODO" 2>/dev/null) || true
|
|
78
164
|
echo "${n:-0}"
|
|
79
165
|
}
|
|
80
166
|
|
|
167
|
+
# The PLAN cycle this iteration is about to build — the first open C<n> in the Status
|
|
168
|
+
# block, same lines _remaining counts. Every emission keys on this, never on the loop
|
|
169
|
+
# ordinal `$i`: `i` restarts at 0 on a resume, so a resumed run building C3 would emit
|
|
170
|
+
# cycle:1, and the projection's run key `do:<slug>:c1` would silently append C3's trace
|
|
171
|
+
# to C1's closed run (text/do-as-workflow.md). Falls back to the caller's ordinal when
|
|
172
|
+
# the todo carries no Status block.
|
|
173
|
+
_plan_cycle() {
|
|
174
|
+
local n; n=$(grep -oE '^[[:space:]]*- \[[ ~]\] \*{0,2}C[0-9]+\*{0,2} —' "$TODO" 2>/dev/null | grep -oE 'C[0-9]+' | head -1 | tr -d 'C') || true
|
|
175
|
+
echo "${n:-${1:-0}}"
|
|
176
|
+
}
|
|
177
|
+
|
|
81
178
|
# IC3: keep the Status-block checkbox in sync with each cycle's own wave checkboxes —
|
|
82
179
|
# a cycle is done iff its `## C<n>` section has zero remaining `- [ ] W` lines.
|
|
83
180
|
# POSIX-portable (no gawk-only 3-arg match()) — works with BSD awk/sed (macOS) and GNU.
|
|
@@ -110,7 +207,7 @@ _sync_status() {
|
|
|
110
207
|
if ! printf '%s\n' "$section" | grep -qE '^[[:space:]]*- \[ \] W'; then
|
|
111
208
|
# No open wave line left in this cycle's section — flip the Status-block
|
|
112
209
|
# 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
|
|
210
|
+
sed -i.bak -E "s/^([[:space:]]*- \\[)[ ~](\\] \\*{0,2}${cid}\\*{0,2} —)/\\1x\\2/" "$todo" 2>/dev/null || true
|
|
114
211
|
rm -f "${todo}.bak"
|
|
115
212
|
fi
|
|
116
213
|
done <<<"$cid_lines"
|
|
@@ -164,6 +261,128 @@ _write_sentinel() {
|
|
|
164
261
|
"$pid" "$(_sentinel_port "$slug")" "$iteration" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$path"
|
|
165
262
|
}
|
|
166
263
|
|
|
264
|
+
# --self-test-daemon: prove the liveness guard can go RED. A guard that only ever
|
|
265
|
+
# reports success is the bug it is meant to prevent (this one shipped as
|
|
266
|
+
# `ps aux | grep -c do-auto.sh`, which reported a loop that had already died).
|
|
267
|
+
# True when this conductor run looks like "the API was unreachable": it died fast
|
|
268
|
+
# AND its output carries a transport error. Pure so --self-test-daemon can prove
|
|
269
|
+
# both verdicts — the guard that burned 30 iterations shipped untested.
|
|
270
|
+
_netfail_run() {
|
|
271
|
+
local cond_secs="$1" logfile="$2"
|
|
272
|
+
[ "${cond_secs:-999}" -lt 90 ] || return 1
|
|
273
|
+
[ -f "$logfile" ] || return 1
|
|
274
|
+
tail -c 2000 "$logfile" 2>/dev/null \
|
|
275
|
+
| grep -qE 'Unable to connect to API|ENOTFOUND|ECONNREFUSED|getaddrinfo' || return 1
|
|
276
|
+
return 0
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# The HEAD of the last commit that touched something OTHER than loop bookkeeping.
|
|
280
|
+
# Plain HEAD is not a progress signal: a conductor that dies instantly still gets a
|
|
281
|
+
# commit, because .do-conductor.out (the heartbeat log) and todo.md's timestamp are
|
|
282
|
+
# inside the worktree. On 2026-08-07 that turned a 2-iteration stall halt into a
|
|
283
|
+
# 30-iteration burn — 28 subprocesses, each dying on ENOTFOUND, each committing one
|
|
284
|
+
# line of error text that the stall guard scored as work.
|
|
285
|
+
_substantive_head() {
|
|
286
|
+
local wt="$1" sha
|
|
287
|
+
for sha in $(git -C "$wt" log --format=%H -200 2>/dev/null); do
|
|
288
|
+
if git -C "$wt" show --name-only --format= "$sha" 2>/dev/null \
|
|
289
|
+
| grep -qvE '^(\.do-|\.w[0-9]|todo\.md$|$)'; then
|
|
290
|
+
echo "$sha"; return 0
|
|
291
|
+
fi
|
|
292
|
+
done
|
|
293
|
+
echo ""
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
# Stalled ONLY if nothing moved on ANY axis we can observe. Cycle checkboxes are
|
|
297
|
+
# too coarse to be the sole signal: a wide cycle (blocks C4) commits real work for
|
|
298
|
+
# an hour — 97 registrations archived, three commits — while its cycle box stays
|
|
299
|
+
# open until W4 closes it, so a box-only guard halts a run that is plainly working.
|
|
300
|
+
# Returns 0 (true, stalled) only when the box count AND the branch head are both
|
|
301
|
+
# unchanged from the previous iteration.
|
|
302
|
+
_stall_check() {
|
|
303
|
+
local remaining="$1" prev_remaining="$2" head="$3" prev_head="$4"
|
|
304
|
+
[ "$remaining" -eq "$prev_remaining" ] || return 1
|
|
305
|
+
[ -n "$prev_head" ] && [ "$head" != "$prev_head" ] && return 1
|
|
306
|
+
return 0
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if ${SELF_TEST_DAEMON:-false}; then
|
|
310
|
+
rc=0
|
|
311
|
+
_t=$(mktemp -d); _self="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
|
|
312
|
+
cd "$_t" || exit 1
|
|
313
|
+
mkdir -p text .do-worktrees
|
|
314
|
+
|
|
315
|
+
# 1. --status on a slug with no loop and open cycles must say NOT RUNNING (exit 3).
|
|
316
|
+
printf -- '- [ ] C1 — nothing\n' > text/ghost-todo.md
|
|
317
|
+
st=0; out=$(bash "$_self" ghost --status 2>&1) || st=$?
|
|
318
|
+
if [ "$st" -eq 3 ] && grep -q "NOT RUNNING" <<<"$out"; then
|
|
319
|
+
echo "[self-test-daemon] PASS: absent loop reports NOT RUNNING (exit 3)"
|
|
320
|
+
else
|
|
321
|
+
echo "[self-test-daemon] FAIL: absent loop reported '$out' (exit $st, wanted 3)" >&2; rc=1
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
# 2. A loop that dies on startup must make --daemon exit NON-ZERO. This is the
|
|
325
|
+
# regression: `nohup setsid ...` died instantly and the launch read as success.
|
|
326
|
+
# No text/<slug>-todo.md → the child exits 1 → the owner file never appears.
|
|
327
|
+
st=0; out=$(DO_DAEMON_LOG="$_t/d.log" bash "$_self" nosuchplan --daemon 2>&1) || st=$?
|
|
328
|
+
if [ "$st" -ne 0 ] && grep -q "is NOT running" <<<"$out"; then
|
|
329
|
+
echo "[self-test-daemon] PASS: a dead launch fails loud (exit $st)"
|
|
330
|
+
else
|
|
331
|
+
echo "[self-test-daemon] FAIL: dead launch reported '$out' (exit $st, wanted non-zero)" >&2; rc=1
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# 3. A live owner file must read RUNNING — the guard is not merely always-red.
|
|
335
|
+
mkdir -p .do-worktrees/alive
|
|
336
|
+
printf '{"pid":%d,"slug":"alive","started_at":"now"}\n' $$ > .do-worktrees/alive/.do-loop-owner
|
|
337
|
+
printf -- '- [ ] C1 — nothing\n' > text/alive-todo.md
|
|
338
|
+
st=0; out=$(bash "$_self" alive --status 2>&1) || st=$?
|
|
339
|
+
if [ "$st" -eq 0 ] && grep -q "RUNNING" <<<"$out"; then
|
|
340
|
+
echo "[self-test-daemon] PASS: a live loop reports RUNNING (exit 0)"
|
|
341
|
+
else
|
|
342
|
+
echo "[self-test-daemon] FAIL: live loop reported '$out' (exit $st, wanted 0)" >&2; rc=1
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
# 4. the stall decision itself: a committing cycle is NOT stalled.
|
|
346
|
+
if _stall_check 6 6 "sha1" "sha1"; then
|
|
347
|
+
echo "[self-test-daemon] PASS: no boxes + no commits = stalled"
|
|
348
|
+
else
|
|
349
|
+
echo "[self-test-daemon] FAIL: flat state should count as stalled" >&2; rc=1
|
|
350
|
+
fi
|
|
351
|
+
if _stall_check 6 6 "sha2" "sha1"; then
|
|
352
|
+
echo "[self-test-daemon] FAIL: a new commit must clear the stall" >&2; rc=1
|
|
353
|
+
else
|
|
354
|
+
echo "[self-test-daemon] PASS: same boxes but a new commit = progress"
|
|
355
|
+
fi
|
|
356
|
+
if _stall_check 5 6 "sha1" "sha1"; then
|
|
357
|
+
echo "[self-test-daemon] FAIL: a ticked cycle must clear the stall" >&2; rc=1
|
|
358
|
+
else
|
|
359
|
+
echo "[self-test-daemon] PASS: a closed cycle = progress"
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# 5. the connectivity verdict: fast death + transport error = unreachable API.
|
|
363
|
+
printf 'API Error: Unable to connect to API (ENOTFOUND)\n' > "$_t/net.log"
|
|
364
|
+
if _netfail_run 3 "$_t/net.log"; then
|
|
365
|
+
echo "[self-test-daemon] PASS: fast death + ENOTFOUND = unreachable"
|
|
366
|
+
else
|
|
367
|
+
echo "[self-test-daemon] FAIL: should have flagged an unreachable API" >&2; rc=1
|
|
368
|
+
fi
|
|
369
|
+
if _netfail_run 4000 "$_t/net.log"; then
|
|
370
|
+
echo "[self-test-daemon] FAIL: a long run is not a connectivity failure" >&2; rc=1
|
|
371
|
+
else
|
|
372
|
+
echo "[self-test-daemon] PASS: a long run is never scored unreachable"
|
|
373
|
+
fi
|
|
374
|
+
printf 'W4 verify failed: 3 tests red\n' > "$_t/work.log"
|
|
375
|
+
if _netfail_run 3 "$_t/work.log"; then
|
|
376
|
+
echo "[self-test-daemon] FAIL: a real build failure must not read as network" >&2; rc=1
|
|
377
|
+
else
|
|
378
|
+
echo "[self-test-daemon] PASS: a fast REAL failure is not a network failure"
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
cd /; rm -rf "$_t"
|
|
382
|
+
[ "$rc" -eq 0 ] && echo "[self-test-daemon] all checks passed"
|
|
383
|
+
exit "$rc"
|
|
384
|
+
fi
|
|
385
|
+
|
|
167
386
|
if $SELF_TEST_LIFECYCLE; then
|
|
168
387
|
rc=0
|
|
169
388
|
tmp=$(mktemp -d)
|
|
@@ -247,6 +466,31 @@ EOF
|
|
|
247
466
|
else
|
|
248
467
|
echo "[self-test-kanban] FAIL: count wrong before=$before after=$after (expected 1,0)" >&2; rc=1
|
|
249
468
|
fi
|
|
469
|
+
# _plan_cycle: the run key must name the PLAN cycle, not the loop ordinal. C1 is
|
|
470
|
+
# already ticked here, so a resumed run (loop ordinal 1) building C2 must emit 2 —
|
|
471
|
+
# emitting 1 would append C2's trace to C1's closed run (text/do-as-workflow.md).
|
|
472
|
+
cat > "$TODO" <<'EOF'
|
|
473
|
+
## Status
|
|
474
|
+
|
|
475
|
+
Batch 1
|
|
476
|
+
- [x] C1 — done state: done
|
|
477
|
+
- [x] W1 · W2 · W3 · W4
|
|
478
|
+
- [ ] C2 — next state: ready
|
|
479
|
+
- [ ] W1 · W2 · W3 · W4
|
|
480
|
+
EOF
|
|
481
|
+
pc=$(_plan_cycle 1)
|
|
482
|
+
if [ "$pc" = "2" ]; then
|
|
483
|
+
echo "[self-test-kanban] PASS: _plan_cycle resolves the open cycle (2), not the loop ordinal (1)"
|
|
484
|
+
else
|
|
485
|
+
echo "[self-test-kanban] FAIL: _plan_cycle=$pc (expected 2)" >&2; rc=1
|
|
486
|
+
fi
|
|
487
|
+
: > "$TODO" # no Status block at all → fall back to the caller's ordinal
|
|
488
|
+
pc=$(_plan_cycle 7)
|
|
489
|
+
if [ "$pc" = "7" ]; then
|
|
490
|
+
echo "[self-test-kanban] PASS: _plan_cycle falls back to the ordinal with no Status block"
|
|
491
|
+
else
|
|
492
|
+
echo "[self-test-kanban] FAIL: _plan_cycle fallback=$pc (expected 7)" >&2; rc=1
|
|
493
|
+
fi
|
|
250
494
|
rm -rf "$tmp"
|
|
251
495
|
[ "$rc" -eq 0 ] && echo "[self-test-kanban] all checks passed"
|
|
252
496
|
exit "$rc"
|
|
@@ -260,6 +504,76 @@ if ! printf '%s' "$SLUG" | grep -qE '^[a-zA-Z0-9][a-zA-Z0-9_-]*$'; then
|
|
|
260
504
|
echo "[do-auto] unsafe slug (must be alphanumeric + hyphens/underscores): $SLUG" >&2; exit 1
|
|
261
505
|
fi
|
|
262
506
|
|
|
507
|
+
# ── Liveness is READ, never inferred ──────────────────────────────────────────
|
|
508
|
+
# The loop owns `.do-loop-owner` for its whole life (pid = this script's own $$,
|
|
509
|
+
# removed by an EXIT trap). The older `.do-loop-running` sentinel tracks the
|
|
510
|
+
# CONDUCTOR of the current iteration, so it is legitimately absent between
|
|
511
|
+
# iterations — reading it to answer "is the loop up?" reports a false DEAD.
|
|
512
|
+
#
|
|
513
|
+
# WHY THIS EXISTS: a launch was reported as running when it had died instantly
|
|
514
|
+
# (`nohup setsid ...` — setsid does not exist on macOS). The check used was
|
|
515
|
+
# `ps aux | grep -c do-auto.sh`, which cannot go red: it counts its own shell,
|
|
516
|
+
# a stale match, or nothing, and every outcome reads as plausible. A check that
|
|
517
|
+
# cannot fail is not a check. Liveness now has exactly one answer, and the only
|
|
518
|
+
# supported way to start a detached loop (--daemon) refuses to return success
|
|
519
|
+
# until it has SEEN that answer say RUNNING.
|
|
520
|
+
_owner_file() { echo ".do-worktrees/${1}/.do-loop-owner"; }
|
|
521
|
+
|
|
522
|
+
_loop_pid() {
|
|
523
|
+
local f; f=$(_owner_file "$1")
|
|
524
|
+
[ -f "$f" ] || return 1
|
|
525
|
+
local pid; pid=$(jq -r '.pid // empty' "$f" 2>/dev/null || true)
|
|
526
|
+
[ -n "$pid" ] && kill -0 "$pid" 2>/dev/null || return 1
|
|
527
|
+
echo "$pid"
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if $STATUS_ONLY; then
|
|
531
|
+
[ -n "$SLUG" ] || { echo "usage: do-auto.sh <slug> --status" >&2; exit 1; }
|
|
532
|
+
TODO="text/${SLUG}-todo.md"
|
|
533
|
+
wt=".do-worktrees/${SLUG}"
|
|
534
|
+
[ -f "$wt/text/${SLUG}-todo.md" ] && TODO="$wt/text/${SLUG}-todo.md"
|
|
535
|
+
rem=$(_remaining)
|
|
536
|
+
if pid=$(_loop_pid "$SLUG"); then
|
|
537
|
+
f=$(_owner_file "$SLUG")
|
|
538
|
+
echo "[do-auto] RUNNING — slug=$SLUG pid=$pid started=$(jq -r '.started_at // "?"' "$f" 2>/dev/null) cycles-remaining=$rem"
|
|
539
|
+
exit 0
|
|
540
|
+
fi
|
|
541
|
+
if [ "$rem" -eq 0 ] && [ -f "$TODO" ]; then
|
|
542
|
+
echo "[do-auto] COMPLETE — slug=$SLUG no open cycles"
|
|
543
|
+
exit 4
|
|
544
|
+
fi
|
|
545
|
+
echo "[do-auto] NOT RUNNING — slug=$SLUG cycles-remaining=$rem"
|
|
546
|
+
exit 3
|
|
547
|
+
fi
|
|
548
|
+
|
|
549
|
+
if $DAEMON; then
|
|
550
|
+
[ -n "$SLUG" ] || { echo "usage: do-auto.sh <slug> --daemon" >&2; exit 1; }
|
|
551
|
+
if pid=$(_loop_pid "$SLUG"); then
|
|
552
|
+
echo "[do-auto] already running for $SLUG (pid $pid) — not starting a second loop" >&2
|
|
553
|
+
exit 0
|
|
554
|
+
fi
|
|
555
|
+
log="${DO_DAEMON_LOG:-.do-worktrees/${SLUG}.loop.log}"
|
|
556
|
+
mkdir -p "$(dirname "$log")" 2>/dev/null || true
|
|
557
|
+
# Portable detach: nohup + background + disown. NO setsid — it is Linux-only and
|
|
558
|
+
# its absence is precisely the silent death this guard exists to catch.
|
|
559
|
+
nohup "$0" "$SLUG" ${MAX_CYCLES:+--max-cycles "$MAX_CYCLES"} > "$log" 2>&1 < /dev/null &
|
|
560
|
+
child=$!
|
|
561
|
+
disown "$child" 2>/dev/null || true
|
|
562
|
+
# Do not trust the fork. Wait until the loop PUBLISHES liveness, or fail loud.
|
|
563
|
+
for _ in $(seq 1 60); do
|
|
564
|
+
if _loop_pid "$SLUG" >/dev/null; then
|
|
565
|
+
echo "[do-auto] daemon up — slug=$SLUG pid=$(_loop_pid "$SLUG") log=$log"
|
|
566
|
+
echo "[do-auto] check: bash .claude/scripts/do-auto.sh $SLUG --status"
|
|
567
|
+
exit 0
|
|
568
|
+
fi
|
|
569
|
+
kill -0 "$child" 2>/dev/null || break
|
|
570
|
+
sleep 1
|
|
571
|
+
done
|
|
572
|
+
echo "[do-auto] FATAL: daemon did not come up for $SLUG — the loop is NOT running." >&2
|
|
573
|
+
echo "[do-auto] log tail: $(tail -c 600 "$log" 2>/dev/null | tr '\n' ' ')" >&2
|
|
574
|
+
exit 1
|
|
575
|
+
fi
|
|
576
|
+
|
|
263
577
|
# Recursion guard: the conductor subprocess runs `/do <slug> --next-cycle` inside
|
|
264
578
|
# the worktree. A buggy conductor may call do-auto.sh again despite the --next-cycle
|
|
265
579
|
# flag. If our JSON sentinel exists AND its pid is alive, we're already inside a
|
|
@@ -314,7 +628,14 @@ CONDUCTOR_EFFORT="${DO_CONDUCTOR_EFFORT:-medium}"
|
|
|
314
628
|
# NO change, with an absolute DO_HARD_CAP backstop against a pathological write-loop.
|
|
315
629
|
# DO_CONDUCTOR_TIMEOUT is retained as the default hard-cap for back-compat.
|
|
316
630
|
CONDUCTOR_TIMEOUT="${DO_CONDUCTOR_TIMEOUT:-2400}"
|
|
317
|
-
|
|
631
|
+
# 60 min of TOTAL silence = hung. Not 10, and not 30 — both were measured too
|
|
632
|
+
# tight on blocks C4 (killed at 603s, then at 1922s, both times mid-real-work): an apex W2 (Opus/Fable deciding a wide
|
|
633
|
+
# cut — blocks C4 reasons over 376 registrations) legitimately spends 10-25 min
|
|
634
|
+
# inside a single Agent spawn, writing nothing and printing nothing. At 600s the
|
|
635
|
+
# guard killed a working conductor every iteration and the run could never finish
|
|
636
|
+
# that cycle. HARD_CAP (3x CONDUCTOR_TIMEOUT, default 7200s) remains the backstop
|
|
637
|
+
# against a genuinely wedged conductor, so raising this loses no real protection.
|
|
638
|
+
IDLE_SECS="${DO_IDLE_SECS:-3600}"
|
|
318
639
|
HARD_CAP="${DO_HARD_CAP:-$(( CONDUCTOR_TIMEOUT > 0 ? CONDUCTOR_TIMEOUT * 3 : 7200 ))}" # absolute backstop
|
|
319
640
|
POLL_SECS="${DO_POLL_SECS:-30}"
|
|
320
641
|
|
|
@@ -346,12 +667,33 @@ POLL_SECS="${DO_POLL_SECS:-30}"
|
|
|
346
667
|
# branch would otherwise seed every new do/<slug> with that branch's commits
|
|
347
668
|
# (plans cross-contaminating across concurrent sessions, 2026-07-21).
|
|
348
669
|
# DO_BASE_REF overrides for a deliberate stack on another branch.
|
|
349
|
-
|
|
670
|
+
# `dev` since 2026-09-06: it is the integration branch branches land into, so
|
|
671
|
+
# cutting from main would start every cycle missing what dev already holds.
|
|
672
|
+
BASE="${DO_BASE_REF:-dev}"
|
|
350
673
|
git show-ref --verify --quiet "refs/heads/$BASE" \
|
|
351
674
|
|| BASE="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
|
|
352
675
|
BR="do/${SLUG}"
|
|
353
676
|
WT=".do-worktrees/${SLUG}"
|
|
354
677
|
|
|
678
|
+
# Publish liveness the moment the worktree path is known, and retract it on ANY
|
|
679
|
+
# exit (clean, halt, kill, crash). This file — not `ps | grep` — is the single
|
|
680
|
+
# answer to "is the loop running?", and --daemon blocks until it appears.
|
|
681
|
+
_OWNER="$WT/.do-loop-owner"
|
|
682
|
+
_publish_owner() {
|
|
683
|
+
mkdir -p "$WT" 2>/dev/null || true
|
|
684
|
+
printf '{"pid":%d,"slug":"%s","started_at":"%s"}\n' \
|
|
685
|
+
"$$" "$SLUG" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$_OWNER" 2>/dev/null || true
|
|
686
|
+
}
|
|
687
|
+
_retract_owner() {
|
|
688
|
+
[ -f "$_OWNER" ] || return 0
|
|
689
|
+
local own; own=$(jq -r '.pid // empty' "$_OWNER" 2>/dev/null || true)
|
|
690
|
+
[ "$own" = "$$" ] && rm -f "$_OWNER"
|
|
691
|
+
}
|
|
692
|
+
if ! $SETUP_ONLY && ! $DRY_RUN; then
|
|
693
|
+
_publish_owner
|
|
694
|
+
trap _retract_owner EXIT INT TERM
|
|
695
|
+
fi
|
|
696
|
+
|
|
355
697
|
# Symlink node_modules + built SDK dist into the worktree. A git worktree omits
|
|
356
698
|
# gitignored dirs (node_modules), so without this a worktree can't typecheck, run
|
|
357
699
|
# vitest, or build — the kill-switch would fail for lack of deps, not lack of code.
|
|
@@ -360,8 +702,33 @@ WT=".do-worktrees/${SLUG}"
|
|
|
360
702
|
_link_deps() {
|
|
361
703
|
local wt="$1" root nm
|
|
362
704
|
root="$(git rev-parse --show-toplevel 2>/dev/null)" || return 0
|
|
705
|
+
# pay/backend + channels are here because one.ie/web's OWN vitest suite reaches
|
|
706
|
+
# across into them (tests/e2e/wallets-refined.test.ts imports
|
|
707
|
+
# pay/backend/src/protocol, tests/unit/chat-block-tool-filter.test.ts imports
|
|
708
|
+
# channels/src/agents/builder). Without their node_modules those two suites fail
|
|
709
|
+
# to COLLECT — zero tests run, vitest exits 1 — so `bun run verify` in a worktree
|
|
710
|
+
# went red for a missing dep rather than a real fault (measured 2026-08-04).
|
|
711
|
+
# .env belongs in this list for exactly the reason node_modules does: it is
|
|
712
|
+
# gitignored, so a worktree never gets one, and the suites that need credentials
|
|
713
|
+
# then fail for lack of a KEY rather than lack of correct code. Measured
|
|
714
|
+
# 2026-08-31: two independent W4 verifiers both reported
|
|
715
|
+
# `fixture write failed (status=0 error=no_gateway_key)` across 4-7 real-TypeDB
|
|
716
|
+
# test files and had to hand-classify them as environmental — noise that makes a
|
|
717
|
+
# verifier argue with its own gate. Symlinked, never copied: one file of truth,
|
|
718
|
+
# and a rotated key is picked up by every live worktree at once.
|
|
719
|
+
#
|
|
720
|
+
# The credential file honours ONE_ENV_FILE (DO_ENV_FILE is the accepted
|
|
721
|
+
# alias), like every other credential-reading script in the harness —
|
|
722
|
+
# factory-repo.sh --check-env-indirection is the gate. A RELATIVE
|
|
723
|
+
# ONE_ENV_FILE is linked at the same relative path inside the worktree; an
|
|
724
|
+
# ABSOLUTE one is reachable from anywhere already and needs no link.
|
|
725
|
+
local env_file="${ONE_ENV_FILE:-${DO_ENV_FILE:-one.ie/web/.env}}"
|
|
726
|
+
local env_links="pay/backend/.env channels/.env"
|
|
727
|
+
case "$env_file" in /*) ;; *) env_links="$env_file $env_links" ;; esac
|
|
363
728
|
for nm in node_modules one.ie/web/node_modules packages/node_modules \
|
|
364
|
-
packages/sdk/dist packages/sdk/node_modules
|
|
729
|
+
packages/sdk/dist packages/sdk/node_modules \
|
|
730
|
+
pay/backend/node_modules channels/node_modules \
|
|
731
|
+
$env_links; do
|
|
365
732
|
if [ -e "$root/$nm" ] && [ ! -e "$wt/$nm" ]; then
|
|
366
733
|
mkdir -p "$wt/$(dirname "$nm")"
|
|
367
734
|
ln -s "$root/$nm" "$wt/$nm" 2>/dev/null && echo "[do-auto] linked $nm" || true
|
|
@@ -369,18 +736,80 @@ _link_deps() {
|
|
|
369
736
|
done
|
|
370
737
|
}
|
|
371
738
|
|
|
739
|
+
# Is $WT a REAL linked worktree, or just a directory that happens to sit inside
|
|
740
|
+
# the repo? `rev-parse --git-dir` cannot tell you: run from any plain directory
|
|
741
|
+
# under the repo root it walks UP, finds the main tree's .git, and exits 0 —
|
|
742
|
+
# verified 2026-08-02, `mkdir .do-worktrees/x && git -C .do-worktrees/x
|
|
743
|
+
# rev-parse --git-dir` prints the MAIN /Users/toc/Server/one-ie/.git.
|
|
744
|
+
#
|
|
745
|
+
# That is not academic. do-orchestrate.sh used to `mkdir -p` the worktree path so
|
|
746
|
+
# it could redirect a log into it BEFORE calling do-auto; this probe then said
|
|
747
|
+
# "resuming worktree", `git worktree add` never ran, and every later
|
|
748
|
+
# `git -C "$WT" commit` targeted the MAIN TREE'S HEAD — where _safe_stage
|
|
749
|
+
# correctly refuses to stage. Orchestrated cycles committed nothing, silently.
|
|
750
|
+
#
|
|
751
|
+
# --show-toplevel is the discriminator: from a real worktree it returns that
|
|
752
|
+
# worktree's own root; from a plain subdirectory it returns the main tree's root.
|
|
753
|
+
# Compare it against the resolved path and the two cases separate cleanly.
|
|
754
|
+
_is_worktree() {
|
|
755
|
+
local wt="$1" abs top
|
|
756
|
+
abs="$(cd "$wt" 2>/dev/null && pwd -P)" || return 1
|
|
757
|
+
[ -n "$abs" ] || return 1
|
|
758
|
+
top="$(git -C "$wt" rev-parse --show-toplevel 2>/dev/null)" || return 1
|
|
759
|
+
[ "$top" = "$abs" ]
|
|
760
|
+
}
|
|
761
|
+
|
|
372
762
|
_setup_worktree() {
|
|
373
763
|
# Idempotent: reuse an existing worktree (resume), else attach one to an
|
|
374
764
|
# existing branch (prior halt), else create branch+worktree fresh from HEAD.
|
|
375
|
-
|
|
765
|
+
#
|
|
766
|
+
# A pre-existing PLAIN directory at $WT is fatal, not recoverable: `git
|
|
767
|
+
# worktree add` refuses a non-empty path, and silently proceeding is the bug
|
|
768
|
+
# documented above. Empty is fine — git accepts an existing empty dir — so
|
|
769
|
+
# clear that case and fail loudly on anything else.
|
|
770
|
+
if [ -d "$WT" ] && ! _is_worktree "$WT"; then
|
|
771
|
+
# OUR OWN liveness file is not foreign content. _publish_owner() runs long
|
|
772
|
+
# before this (it fires the moment $WT is known, so --daemon can block on
|
|
773
|
+
# it) and it `mkdir -p "$WT"` + writes .do-loop-owner. On a slug whose
|
|
774
|
+
# worktree does not exist yet, that left a plain directory holding exactly
|
|
775
|
+
# one file — ours — so the rmdir below failed and every NEW slug FATALed
|
|
776
|
+
# here. The file is deleted again on exit, so the directory read as empty
|
|
777
|
+
# to anyone who looked afterwards, which is what made this hard to see.
|
|
778
|
+
# Drop it, then re-publish after `git worktree add` succeeds.
|
|
779
|
+
rm -f "$WT/.do-loop-owner"
|
|
780
|
+
rmdir "$WT" 2>/dev/null || {
|
|
781
|
+
echo "[do-auto] FATAL: $WT exists but is not a git worktree, and is not empty." >&2
|
|
782
|
+
echo "[do-auto] Commits there would land on the MAIN tree's HEAD. Refusing." >&2
|
|
783
|
+
echo "[do-auto] Inspect it, then: rm -rf '$WT' && git worktree prune" >&2
|
|
784
|
+
return 1
|
|
785
|
+
}
|
|
786
|
+
echo "[do-auto] cleared empty non-worktree dir at $WT"
|
|
787
|
+
fi
|
|
788
|
+
|
|
789
|
+
if _is_worktree "$WT"; then
|
|
376
790
|
echo "[do-auto] resuming worktree $WT on $BR"
|
|
377
|
-
elif git show-ref --verify --quiet "refs/heads/$BR"; then
|
|
378
|
-
echo "[do-auto] re-attaching worktree $WT to existing branch $BR"
|
|
379
|
-
git worktree add "$WT" "$BR" >/dev/null
|
|
380
791
|
else
|
|
381
|
-
|
|
382
|
-
git
|
|
792
|
+
# Both add-paths below fail with "already registered" if a previous run left
|
|
793
|
+
# a stale entry in .git/worktrees/<slug> — the directory deleted by hand but
|
|
794
|
+
# never deregistered. prune drops ONLY registrations whose working dir is
|
|
795
|
+
# gone, so it can never touch a live worktree; do-orchestrate.sh:178 already
|
|
796
|
+
# relies on that. Needed here because the clear-empty-dir branch above now
|
|
797
|
+
# routes more cases into `git worktree add` than the old probe ever did.
|
|
798
|
+
git worktree prune 2>/dev/null || true
|
|
799
|
+
|
|
800
|
+
if git show-ref --verify --quiet "refs/heads/$BR"; then
|
|
801
|
+
echo "[do-auto] re-attaching worktree $WT to existing branch $BR"
|
|
802
|
+
git worktree add "$WT" "$BR" >/dev/null
|
|
803
|
+
else
|
|
804
|
+
echo "[do-auto] creating worktree $WT on new branch $BR (from $BASE)"
|
|
805
|
+
git worktree add -b "$BR" "$WT" "$BASE" >/dev/null
|
|
806
|
+
fi
|
|
383
807
|
fi
|
|
808
|
+
|
|
809
|
+
# Re-publish liveness: the clear-empty-dir branch above deletes our owner file
|
|
810
|
+
# so `git worktree add` sees an empty path. Without this, a fresh slug runs
|
|
811
|
+
# with no liveness file and `--status` reports it dead while it is building.
|
|
812
|
+
_publish_owner
|
|
384
813
|
_link_deps "$WT"
|
|
385
814
|
|
|
386
815
|
# Carry the plan's own artifacts into the worktree. The spine walk may have
|
|
@@ -496,6 +925,12 @@ if ! $DRY_RUN; then
|
|
|
496
925
|
if [ ! -f "$WT/.task-created" ]; then
|
|
497
926
|
_title=$(grep -m1 '^title:' "$TODO" 2>/dev/null | sed 's/^title:[[:space:]]*//' | tr -d '"' || true)
|
|
498
927
|
bash "$(dirname "$0")/do-signal.sh" --task-create "$SLUG" "${_title:-$SLUG}" "tags=do:cycle" >/dev/null 2>&1 || true
|
|
928
|
+
# ...and one task PER CYCLE, so the substrate holds the whole plan rather than
|
|
929
|
+
# a single row standing in for it. Measured 2026-09-01: lifecycle carries 10
|
|
930
|
+
# cycles and 13 deliverables; exactly ONE task reached the substrate, which is
|
|
931
|
+
# why `signal_tasks_cycle` had zero callers (the tasks it sets status on did
|
|
932
|
+
# not exist) and why do-next.sh re-derives readiness from markdown checkboxes.
|
|
933
|
+
bash "$(dirname "$0")/do-signal.sh" --task-plan "$SLUG" >/dev/null 2>&1 || true
|
|
499
934
|
touch "$WT/.task-created"
|
|
500
935
|
fi
|
|
501
936
|
else
|
|
@@ -514,8 +949,16 @@ _tier() {
|
|
|
514
949
|
}
|
|
515
950
|
|
|
516
951
|
# Emit a world:do-event signal at a cycle boundary. Fire-and-forget — never blocks the loop.
|
|
952
|
+
# stderr is NOT swallowed: do-signal's dark-cycle guard writes there, and a worktree
|
|
953
|
+
# that emits nothing must say so rather than looking like a cycle that never ran.
|
|
517
954
|
_signal() {
|
|
518
|
-
bash "$(dirname "$0")/do-signal.sh" "$@"
|
|
955
|
+
bash "$(dirname "$0")/do-signal.sh" "$@" || true
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
# Which wave checkboxes are ticked right now — the same `- [x] W` lines _sync_status
|
|
959
|
+
# reads. Used as the wave-trace fallback when the conductor appended no census lines.
|
|
960
|
+
_waves_ticked() {
|
|
961
|
+
grep -oE '^[[:space:]]*- \[x\] W[0-9]' "$1" 2>/dev/null | grep -oE 'W[0-9]' | sort -u | tr '\n' ' ' || true
|
|
519
962
|
}
|
|
520
963
|
|
|
521
964
|
_plan_done() {
|
|
@@ -584,7 +1027,7 @@ _merge_digest() {
|
|
|
584
1027
|
# cycle commits. Written to trunk's root (.do-digest.md) where the human stands.
|
|
585
1028
|
local digest=".do-digest.md"
|
|
586
1029
|
local cycles shortstat trust composite open mb behind agents
|
|
587
|
-
cycles=$(grep -cE '\[[xX]\] C[0-9]+' "$TODO" 2>/dev/null || true); cycles=${cycles:-0}
|
|
1030
|
+
cycles=$(grep -cE '\[[xX]\] \*{0,2}C[0-9]+' "$TODO" 2>/dev/null || true); cycles=${cycles:-0}
|
|
588
1031
|
# Diff from the MERGE-BASE, not BASE..BR. A two-dot BASE..BR diff conflates
|
|
589
1032
|
# "what this branch added" with "what trunk advanced while we built" (the
|
|
590
1033
|
# latter shows as spurious reversions), inflating the digest. merge-base..BR
|
|
@@ -667,25 +1110,45 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
667
1110
|
|
|
668
1111
|
# Stall guard: a cycle that ticks no box means the subprocess halted/errored.
|
|
669
1112
|
# Retrying the identical state forever just burns subprocesses — halt after 2 stalls.
|
|
670
|
-
|
|
1113
|
+
# Connectivity abort: a conductor that dies in seconds with a transport error
|
|
1114
|
+
# built nothing and will keep building nothing until the network returns. Burning
|
|
1115
|
+
# the whole --max-cycles budget on it (30 dead subprocesses, measured 2026-08-07)
|
|
1116
|
+
# teaches nothing and buries the real state. Three in a row = stop and say why.
|
|
1117
|
+
if _netfail_run "${_cond_secs:-999}" "$WT/.do-conductor.out"; then
|
|
1118
|
+
netfail=$((${netfail:-0} + 1))
|
|
1119
|
+
if [ "$netfail" -ge 3 ]; then
|
|
1120
|
+
echo "[do-auto] FATAL: the conductor failed to reach the API 3 times in a row (last run ${_cond_secs}s)." >&2
|
|
1121
|
+
echo "[do-auto] This is a connectivity problem, not a plan problem — nothing was built." >&2
|
|
1122
|
+
echo "[do-auto] Check the network, then re-run: bash .claude/scripts/do-auto.sh $SLUG --daemon" >&2
|
|
1123
|
+
echo "[do-auto] WIP preserved on branch $BR (worktree $WT)." >&2
|
|
1124
|
+
_signal halt "$SLUG" "$(_tier)" "$(_plan_cycle "$i")" "reason=api-unreachable" 2>/dev/null || true
|
|
1125
|
+
exit 1
|
|
1126
|
+
fi
|
|
1127
|
+
else
|
|
1128
|
+
netfail=0
|
|
1129
|
+
fi
|
|
1130
|
+
|
|
1131
|
+
_cur_head=$(_substantive_head "$WT")
|
|
1132
|
+
if _stall_check "$remaining" "$prev_remaining" "$_cur_head" "${prev_head:-}"; then
|
|
671
1133
|
stall=$((stall + 1))
|
|
672
1134
|
if [ "$stall" -ge 2 ]; then
|
|
673
1135
|
echo "[do-auto] no progress for 2 iterations (${remaining} cycle(s) stuck) — halting." >&2
|
|
674
|
-
echo "[do-auto] The last cycle ticked no checkbox. Inspect $TODO, fix the blocker, then re-run /do $SLUG." >&2
|
|
1136
|
+
echo "[do-auto] The last cycle ticked no checkbox AND committed nothing. Inspect $TODO, fix the blocker, then re-run /do $SLUG." >&2
|
|
675
1137
|
echo "[do-auto] WIP preserved on branch $BR (worktree $WT)." >&2
|
|
676
|
-
_signal halt "$SLUG" "$(_tier)" "$i" "reason=stall"
|
|
1138
|
+
_signal halt "$SLUG" "$(_tier)" "$(_plan_cycle "$i")" "reason=stall"
|
|
677
1139
|
exit 1
|
|
678
1140
|
fi
|
|
679
1141
|
else
|
|
680
1142
|
stall=0
|
|
681
1143
|
fi
|
|
1144
|
+
prev_head="$_cur_head"
|
|
682
1145
|
prev_remaining=$remaining
|
|
683
1146
|
|
|
684
1147
|
trust=$(_trust)
|
|
685
1148
|
if [ "$trust" = "cautious" ]; then
|
|
686
1149
|
echo "[do-auto] trust=cautious — halting. Fix the issue, then re-run /do $SLUG."
|
|
687
1150
|
echo "[do-auto] WIP preserved on branch $BR (worktree $WT)."
|
|
688
|
-
_signal halt "$SLUG" "$(_tier)" "$i" "reason=trust-cautious"
|
|
1151
|
+
_signal halt "$SLUG" "$(_tier)" "$(_plan_cycle "$i")" "reason=trust-cautious"
|
|
689
1152
|
exit 1
|
|
690
1153
|
fi
|
|
691
1154
|
|
|
@@ -720,11 +1183,19 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
720
1183
|
fi
|
|
721
1184
|
|
|
722
1185
|
# Emit cycle-open signal (world:do-event type:aim) before the conductor starts.
|
|
723
|
-
|
|
1186
|
+
# One plan-cycle number for EVERY emission this iteration — resolved BEFORE the
|
|
1187
|
+
# conductor runs, since the conductor ticks the cycle closed and the post-wait
|
|
1188
|
+
# emissions would otherwise resolve to the NEXT cycle.
|
|
1189
|
+
_cyc=$(_plan_cycle "$i")
|
|
1190
|
+
_signal aim "$SLUG" "$(_tier)" "$_cyc"
|
|
724
1191
|
# Lease the plan's substrate task (tasks-do C1/C5 — claim = lease, same verb
|
|
725
|
-
# do-fleet.sh checks before spawning a worktree).
|
|
726
|
-
#
|
|
727
|
-
|
|
1192
|
+
# do-fleet.sh checks before spawning a worktree). Asserted: a TypeDB-down or
|
|
1193
|
+
# gateway miss is logged (never disguised as ok) and never aborts the cycle.
|
|
1194
|
+
_claim_rc=0
|
|
1195
|
+
bash "$(dirname "$0")/do-signal.sh" --task-claim "$SLUG" >/dev/null || _claim_rc=$?
|
|
1196
|
+
if [ "$_claim_rc" -ne 0 ]; then
|
|
1197
|
+
echo "board-write: claim FAILED (exit ${_claim_rc})" >&2
|
|
1198
|
+
fi
|
|
728
1199
|
|
|
729
1200
|
# C6: Trail confidence hint — query KV trail before spawning the conductor.
|
|
730
1201
|
# If confidence ≥ 0.8, write .do-trail-hint.json in the worktree so W1 can skip W2.
|
|
@@ -764,14 +1235,33 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
764
1235
|
_iter_sha=$(git -C "$WT" rev-parse HEAD 2>/dev/null || echo "")
|
|
765
1236
|
_iter_remaining="$remaining"
|
|
766
1237
|
|
|
1238
|
+
# Wave-trace baselines, captured BEFORE the conductor runs (text/do-as-workflow.md).
|
|
1239
|
+
_census_base=0
|
|
1240
|
+
[ -f "$WT/.do-census.jsonl" ] && _census_base=$(wc -l < "$WT/.do-census.jsonl" 2>/dev/null | tr -d ' ')
|
|
1241
|
+
_waves_base=$(_waves_ticked "$TODO")
|
|
1242
|
+
|
|
767
1243
|
# Run the conductor in the background with a bash-native, PROGRESS-BASED watchdog
|
|
768
1244
|
# (macOS has no `timeout`). It kills the conductor only after IDLE_SECS of worktree
|
|
769
1245
|
# SILENCE (or the HARD_CAP absolute backstop) — never mid-edit on a slow-but-live
|
|
770
1246
|
# cycle. Matches the claude process by its --next-cycle command line.
|
|
771
1247
|
# Unset Claude Code session vars so the subprocess starts a fresh session instead
|
|
772
1248
|
# of being blocked by the anti-nesting guard (CLAUDECODE/CLAUDE_CODE_CHILD_SESSION).
|
|
773
|
-
|
|
1249
|
+
# CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 — wait indefinitely for the conductor's
|
|
1250
|
+
# background tasks. The CLI default terminates a -p run whose background work is
|
|
1251
|
+
# still going after 600s, which kills a LIVE conductor mid-decision: a wide W2
|
|
1252
|
+
# (blocks C4 reasons over 376 registrations) always exceeds it, so that cycle can
|
|
1253
|
+
# never close and every iteration burns identically until the stall guard halts.
|
|
1254
|
+
# Bounding the conductor is already do-auto's own job, and its guards are the ones
|
|
1255
|
+
# that can tell alive-but-slow from hung: IDLE_SECS (no worktree write) and
|
|
1256
|
+
# HARD_CAP. Deferring to a print-wait ceiling that cannot see the worktree trades a
|
|
1257
|
+
# real halt for a fake one.
|
|
1258
|
+
# The conductor's own output is a heartbeat: tee it into the worktree so the
|
|
1259
|
+
# find -newer probe below counts "the conductor is still talking" as progress.
|
|
1260
|
+
# Without this, the only progress signal is a file write — and a wide W2 spends
|
|
1261
|
+
# its whole life inside one Agent spawn, writing nothing.
|
|
1262
|
+
( cd "$WT" && unset CLAUDECODE CLAUDE_CODE_CHILD_SESSION CLAUDE_CODE_SESSION_ID AI_AGENT && CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS="${DO_BG_WAIT_CEILING_MS:-0}" claude --model "$CONDUCTOR_MODEL" --effort "$CONDUCTOR_EFFORT" --dangerously-skip-permissions -p "/do $SLUG --next-cycle${AUTO_FLAG}" ) > >(tee -a "$WT/.do-conductor.out") 2>&1 &
|
|
774
1263
|
cond_pid=$!
|
|
1264
|
+
_cond_t0=$(date +%s)
|
|
775
1265
|
_write_sentinel "$WT/.do-loop-running" "$cond_pid" "$i" "$SLUG"
|
|
776
1266
|
# Progress signal (F11): ANY file written under the worktree since the last poll —
|
|
777
1267
|
# code edits, new files, commits, AND gitignored state (.w2-spec.json, .w1-cache/,
|
|
@@ -807,14 +1297,62 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
807
1297
|
# more common case of "done but not exiting."
|
|
808
1298
|
TODO="$WT/text/${SLUG}-todo.md" _reap_watcher "$WT" "$cond_pid" "$_iter_sha" "$_iter_remaining" &
|
|
809
1299
|
reap_pid=$!
|
|
1300
|
+
# Board heartbeat (IC: the lease must outlive the cycle). `tasks:claim` stamps
|
|
1301
|
+
# `updated-at-ms`; a `picked` row older than CLAIM_LEASE_TTL_MS (45 min,
|
|
1302
|
+
# one.ie/web/src/lib/resolvers/tasks.ts:92) is STALE, and both `tasks:reap` (which
|
|
1303
|
+
# tasks-loop.sh runs FIRST on every pass) and `tasks:claim`'s own stale branch will
|
|
1304
|
+
# reopen it and hand the row to another worker. do-auto wrote exactly three board
|
|
1305
|
+
# events per plan — create/claim/link — with nothing between claim and link, while
|
|
1306
|
+
# p100 fleet duration is 79 min. So a live cycle crossed the TTL and its work could
|
|
1307
|
+
# be double-claimed, discovered only at merge. This ticker is what makes the lease
|
|
1308
|
+
# honest; the per-wave heartbeat below is the progress trace, not the keep-alive.
|
|
1309
|
+
# Default 900s = 3x margin inside the TTL. Fire-and-forget like every other signal.
|
|
1310
|
+
( while kill -0 "$cond_pid" 2>/dev/null; do
|
|
1311
|
+
sleep "${DO_HEARTBEAT_SECS:-900}"
|
|
1312
|
+
kill -0 "$cond_pid" 2>/dev/null || break
|
|
1313
|
+
_hb_waves=$(_waves_ticked "$TODO" 2>/dev/null | tr '\n' ' ' | sed 's/ *$//')
|
|
1314
|
+
bash "$(dirname "$0")/do-signal.sh" --task-heartbeat "$SLUG" \
|
|
1315
|
+
"cycle ${_cyc} in flight — waves: ${_hb_waves:-none yet}" >/dev/null 2>&1 || true
|
|
1316
|
+
done ) &
|
|
1317
|
+
hb_pid=$!
|
|
810
1318
|
wait "$cond_pid" 2>/dev/null || true
|
|
1319
|
+
_cond_secs=$(( $(date +%s) - ${_cond_t0:-$(date +%s)} ))
|
|
811
1320
|
kill "$wd_pid" 2>/dev/null || true # conductor finished first — cancel the watchdog
|
|
812
1321
|
kill "$reap_pid" 2>/dev/null || true # conductor finished first — cancel the reap watcher
|
|
1322
|
+
kill "$hb_pid" 2>/dev/null || true # conductor finished first — cancel the board heartbeat
|
|
813
1323
|
# Kill any child do-auto.sh the conductor may have spawned despite --next-cycle.
|
|
814
1324
|
pkill -f "do-auto.sh ${SLUG}" 2>/dev/null || true
|
|
815
1325
|
sleep 1
|
|
816
1326
|
rm -f "$WT/.do-loop-running" "${_wd_marker:-}" # marker cleanup (subshell's rm is skipped when the conductor finishes first)
|
|
817
1327
|
|
|
1328
|
+
# Wave trace (text/do-as-workflow.md deliverable 1) — one `wave` event per wave this
|
|
1329
|
+
# iteration ticked, in order, so the run record carries the wave-by-wave trace and a
|
|
1330
|
+
# W3.5 reloop shows as repeated w3/w4 entries. Drained in the FOREGROUND after the
|
|
1331
|
+
# wait: the watchdog subshell is killed the instant the conductor exits, which would
|
|
1332
|
+
# systematically drop the closing W4. The census is the primary source (it records
|
|
1333
|
+
# every agent settle, reloops included); it is an instruction to the conductor, not a
|
|
1334
|
+
# mechanical guarantee, so a cycle that appended none falls back to the wave
|
|
1335
|
+
# checkboxes it DID tick. Fire-and-forget, like every other _signal.
|
|
1336
|
+
_census_now=0
|
|
1337
|
+
[ -f "$WT/.do-census.jsonl" ] && _census_now=$(wc -l < "$WT/.do-census.jsonl" 2>/dev/null | tr -d ' ')
|
|
1338
|
+
_new_waves=""
|
|
1339
|
+
if [ "${_census_now:-0}" -gt "${_census_base:-0}" ]; then
|
|
1340
|
+
_new_waves=$(tail -n "+$((_census_base + 1))" "$WT/.do-census.jsonl" 2>/dev/null \
|
|
1341
|
+
| sed -n 's/.*"wave"[[:space:]]*:[[:space:]]*"\(W[0-9]\)".*/\1/p' || true)
|
|
1342
|
+
else
|
|
1343
|
+
for _w in $(_waves_ticked "$TODO"); do
|
|
1344
|
+
case " ${_waves_base} " in *" ${_w} "*) ;; *) _new_waves="${_new_waves} ${_w}" ;; esac
|
|
1345
|
+
done
|
|
1346
|
+
fi
|
|
1347
|
+
for _w in $_new_waves; do
|
|
1348
|
+
_signal wave "$SLUG" "$(_tier)" "$_cyc" "wave=${_w}"
|
|
1349
|
+
# Same wave close, second surface: a tasks:comment bumps `updated-at-ms`, so the
|
|
1350
|
+
# task sheet's timeline becomes the wave-by-wave trace a human watches at
|
|
1351
|
+
# /tasks/board — and each one re-arms the lease. Mirrors do-engine.js:427.
|
|
1352
|
+
bash "$(dirname "$0")/do-signal.sh" --task-heartbeat "$SLUG" \
|
|
1353
|
+
"cycle ${_cyc} · ${_w} close" >/dev/null 2>&1 || true
|
|
1354
|
+
done
|
|
1355
|
+
|
|
818
1356
|
# Objective security gate (W4 bash-first, zero-LLM): before the cycle lands on
|
|
819
1357
|
# the branch, secret-scan the worktree + audit deps. A scored security score can
|
|
820
1358
|
# miss a leaked key or known-vuln dep; this is the deterministic backstop — a
|
|
@@ -823,7 +1361,7 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
823
1361
|
if ! _secret_gate "$WT"; then
|
|
824
1362
|
echo "[do-auto] secret-scan / bun audit found an issue — NOT committing cycle ${i}." >&2
|
|
825
1363
|
echo "[do-auto] Inspect $WT, remove the secret/vuln, then re-run /do $SLUG. WIP preserved on $BR." >&2
|
|
826
|
-
_signal halt "$SLUG" "$(_tier)" "$
|
|
1364
|
+
_signal halt "$SLUG" "$(_tier)" "$_cyc" "reason=secret-gate"
|
|
827
1365
|
exit 1
|
|
828
1366
|
fi
|
|
829
1367
|
|
|
@@ -857,9 +1395,9 @@ while [ "$i" -lt "$MAX_CYCLES" ]; do
|
|
|
857
1395
|
# harvest credits these tag-nodes so tasks:mine ranks by proven work (text/reputation.md § A).
|
|
858
1396
|
_worktags=$(python3 "$(dirname "$0")/do-rank.py" --worktags "$SLUG" 2>/dev/null || true)
|
|
859
1397
|
if [ -n "$_composite" ]; then
|
|
860
|
-
_signal learn "$SLUG" "$(_tier)" "$
|
|
1398
|
+
_signal learn "$SLUG" "$(_tier)" "$_cyc" "composite=${_composite}" ${_shape:+"$_shape"} "$_model" ${_worktags:+"worktags=$_worktags"}
|
|
861
1399
|
else
|
|
862
|
-
_signal learn "$SLUG" "$(_tier)" "$
|
|
1400
|
+
_signal learn "$SLUG" "$(_tier)" "$_cyc" ${_shape:+"$_shape"} "$_model" ${_worktags:+"worktags=$_worktags"}
|
|
863
1401
|
fi
|
|
864
1402
|
done
|
|
865
1403
|
|
|
@@ -868,7 +1406,11 @@ if $DRY_RUN; then exit 0; fi
|
|
|
868
1406
|
if [ "$i" -ge "$MAX_CYCLES" ]; then
|
|
869
1407
|
echo "[do-auto] hit --max-cycles $MAX_CYCLES — halting"
|
|
870
1408
|
echo "[do-auto] WIP preserved on branch $BR (worktree $WT)."
|
|
871
|
-
_signal halt "$SLUG" "$(_tier)" "$i" "reason=max-cycles"
|
|
1409
|
+
_signal halt "$SLUG" "$(_tier)" "$(_plan_cycle "$i")" "reason=max-cycles"
|
|
1410
|
+
# A halt still closes. --status timeout is the honest word: the lease stays
|
|
1411
|
+
# held and the task is NOT marked done, so the next run resumes rather than
|
|
1412
|
+
# re-opening finished work. Rule 1 has no exception for halts.
|
|
1413
|
+
bash "$(dirname "$0")/do-close.sh" "$SLUG" --status timeout || true
|
|
872
1414
|
exit 1
|
|
873
1415
|
fi
|
|
874
1416
|
|
|
@@ -881,8 +1423,16 @@ fi
|
|
|
881
1423
|
_merge_digest
|
|
882
1424
|
# Close the loop on the origin task (tasks-do C1): the handoff-completion
|
|
883
1425
|
# 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
|
-
|
|
1426
|
+
# not per cycle (a plan's cycles all serve the same origin task). Asserted:
|
|
1427
|
+
# a failed link is logged with its exit, never disguised as ok; the plan
|
|
1428
|
+
# still completed its code.
|
|
1429
|
+
#
|
|
1430
|
+
# do-close.sh is the whole close, not just the link: the board write (through
|
|
1431
|
+
# this same slug door), the receipt check that gates it, the rubric dims, the
|
|
1432
|
+
# feedback pheromone and the promise settle — bounded, in ~5s, and whatever it
|
|
1433
|
+
# cannot close it FILES with the command that finishes it. It exits 0 by design
|
|
1434
|
+
# even with open legs; a close that can fail a plan is a close plans skip.
|
|
1435
|
+
bash "$(dirname "$0")/do-close.sh" "$SLUG" --composite "${_composite:-}" || true
|
|
886
1436
|
echo ""
|
|
887
1437
|
echo "[do-auto] ✓ plan complete on branch $BR — trunk ($BASE) untouched."
|
|
888
1438
|
echo "[do-auto] read: .do-digest.md # what landed + gate verdict (reasoning was discarded)"
|