@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-folder.sh
CHANGED
|
@@ -36,7 +36,11 @@ fi
|
|
|
36
36
|
|
|
37
37
|
# 3. longest-prefix match against known buildable folders (one.ie/web BEFORE one.ie).
|
|
38
38
|
# bash 3.2-safe (macOS): no associative arrays — dedup a newline list with sort -u.
|
|
39
|
-
|
|
39
|
+
# channels/ and pay/ were ABSENT until 2026-09-03, and they are two of the five
|
|
40
|
+
# deployed services. A changed set touching only them answered "unmapped", which
|
|
41
|
+
# do-w4-gates then dropped on the floor -- no verify row at all, in either lane.
|
|
42
|
+
# pay/backend before pay, one.ie/web before one.ie: longest prefix must win.
|
|
43
|
+
folders="one.ie/web one.ie packages agents api schema sync backup channels pay/backend pay"
|
|
40
44
|
matched=""
|
|
41
45
|
for p in "${paths[@]}"; do
|
|
42
46
|
for f in $folders; do
|
|
@@ -56,7 +60,12 @@ printf '%s\n' "$matched" | while IFS= read -r f; do
|
|
|
56
60
|
pj="$ROOT/$f/package.json"
|
|
57
61
|
verify="null"; build="null"
|
|
58
62
|
if [ -f "$pj" ]; then
|
|
63
|
+
# Dev lane by default: verify:fast (tsc + related tests + pinned gates).
|
|
64
|
+
# FULL_VERIFY=1 (set by /close and ./deploy) asks for the whole suite.
|
|
59
65
|
jq -e '.scripts.verify' "$pj" >/dev/null 2>&1 && verify='"bun run verify"'
|
|
66
|
+
if [ "${FULL_VERIFY:-0}" != "1" ] && jq -e '.scripts["verify:fast"]' "$pj" >/dev/null 2>&1; then
|
|
67
|
+
verify='"bun run verify:fast"'
|
|
68
|
+
fi
|
|
60
69
|
jq -e '.scripts.build' "$pj" >/dev/null 2>&1 && build='"bun run build"'
|
|
61
70
|
fi
|
|
62
71
|
printf '{"folder":"%s","verify":%s,"build":%s,"doc_only":false}\n' "$f" "$verify" "$build"
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-next.sh <slug> [--claim] [--all] — stream the next READY cycle(s) of a plan.
|
|
3
|
+
#
|
|
4
|
+
# manifest: portable
|
|
5
|
+
#
|
|
6
|
+
# WHY. do-engine runs a plan as BATCHES: every cycle in a batch must finish before
|
|
7
|
+
# any cycle of the next one starts, so the batch costs its SLOWEST member and a
|
|
8
|
+
# cycle whose own dependencies closed an hour ago still waits at the barrier. The
|
|
9
|
+
# batch is not a real constraint — the DAG is. This emits the cycles whose
|
|
10
|
+
# dependencies are ACTUALLY closed, right now, so a worker can take one, do it,
|
|
11
|
+
# verify it, close it, and immediately ask for the next.
|
|
12
|
+
#
|
|
13
|
+
# Readiness is DERIVED, never read. The todo's `state: ready` / `state: blocked-on-C1,C2`
|
|
14
|
+
# words are written once when the plan is authored and nothing updates them as
|
|
15
|
+
# cycles close — the same second-source-of-truth seam that let a verified cycle
|
|
16
|
+
# stay `- [ ]` forever. The `blocked-on-` list is the edge set (authored, static,
|
|
17
|
+
# correct); whether those edges are SATISFIED is computed from the checkboxes,
|
|
18
|
+
# which are the live record do-tick.sh writes.
|
|
19
|
+
#
|
|
20
|
+
# --claim makes the emission ATOMIC across every session and worktree on this box:
|
|
21
|
+
# claim_take is mkdir(2)-arbitrated, so N workers racing the same plan each get a
|
|
22
|
+
# DIFFERENT cycle and never the same one. A claim evaporates when its owner dies
|
|
23
|
+
# or its lease expires, so a killed worker's cycle returns to the stream by itself
|
|
24
|
+
# — no coordinator, no queue, nothing to deadlock.
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
SLUG=""; DO_CLAIM=0; ALL=0
|
|
28
|
+
while [ $# -gt 0 ]; do
|
|
29
|
+
case "$1" in
|
|
30
|
+
--claim) DO_CLAIM=1 ;;
|
|
31
|
+
--all) ALL=1 ;;
|
|
32
|
+
-*) echo "do-next: unknown flag $1" >&2; exit 2 ;;
|
|
33
|
+
*) [ -n "$SLUG" ] && { echo "do-next: one slug only" >&2; exit 2; }; SLUG="$1" ;;
|
|
34
|
+
esac
|
|
35
|
+
shift
|
|
36
|
+
done
|
|
37
|
+
[ -n "$SLUG" ] || { echo "usage: do-next.sh <slug> [--claim] [--all]" >&2; exit 2; }
|
|
38
|
+
|
|
39
|
+
ROOT="${DO_NEXT_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
40
|
+
TODO="$ROOT/text/${SLUG}-todo.md"
|
|
41
|
+
[ -f "$TODO" ] || { echo "do-next: no such plan: $TODO" >&2; exit 3; }
|
|
42
|
+
|
|
43
|
+
# The cycle-line shape. Identical to do-tick.sh's reader and do-auto.sh's
|
|
44
|
+
# _remaining(); parity across all four implementations is proven behaviourally by
|
|
45
|
+
# do-cycle-shape-check.sh, which reads this file rather than a copy of it.
|
|
46
|
+
OPEN_RE='^[[:space:]]*- \[[ ~]\] \*{0,2}C[0-9]+\*{0,2} —'
|
|
47
|
+
CLOSED_RE='^[[:space:]]*- \[x\] \*{0,2}C[0-9]+\*{0,2} —'
|
|
48
|
+
|
|
49
|
+
closed_raw=$(grep -oE "$CLOSED_RE" "$TODO" 2>/dev/null || true)
|
|
50
|
+
closed=" $(printf '%s' "$closed_raw" | grep -oE 'C[0-9]+' 2>/dev/null | tr '\n' ' ' || true)"
|
|
51
|
+
open_lines=$(grep -E "$OPEN_RE" "$TODO" 2>/dev/null || true)
|
|
52
|
+
[ -n "$open_lines" ] || { echo "do-next: ${SLUG} — no open cycles; plan complete" >&2; exit 0; }
|
|
53
|
+
|
|
54
|
+
ready=""
|
|
55
|
+
while IFS= read -r line; do
|
|
56
|
+
[ -n "$line" ] || continue
|
|
57
|
+
cid=$(printf '%s' "$line" | grep -oE 'C[0-9]+' | head -1)
|
|
58
|
+
[ -n "$cid" ] || continue
|
|
59
|
+
# Edge set: `state: blocked-on-C1,C2`. No blocked-on clause ⇒ no edges ⇒ ready.
|
|
60
|
+
deps=$(printf '%s' "$line" | sed -nE 's/.*blocked-on-([C0-9,]+).*/\1/p' | tr ',' ' ')
|
|
61
|
+
blocked=0
|
|
62
|
+
for d in $deps; do
|
|
63
|
+
case "$closed" in *" $d "*) ;; *) blocked=1; break ;; esac
|
|
64
|
+
done
|
|
65
|
+
[ "$blocked" -eq 0 ] && ready="$ready $cid"
|
|
66
|
+
done <<EOF
|
|
67
|
+
$open_lines
|
|
68
|
+
EOF
|
|
69
|
+
|
|
70
|
+
ready=$(printf '%s' "$ready" | tr ' ' '\n' | grep -v '^$' || true)
|
|
71
|
+
[ -n "$ready" ] || { echo "do-next: ${SLUG} — cycles remain OPEN but every one is blocked on an unclosed dependency. Nothing to stream." >&2; exit 1; }
|
|
72
|
+
|
|
73
|
+
if [ "$DO_CLAIM" -eq 0 ]; then
|
|
74
|
+
[ "$ALL" -eq 1 ] && printf '%s\n' "$ready" || printf '%s\n' "$ready" | head -1
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# shellcheck source=lib/govern.sh
|
|
79
|
+
. "$(dirname "${BASH_SOURCE[0]}")/lib/govern.sh"
|
|
80
|
+
# Claim on behalf of the WORKER, not of this script. do-next.sh exits in
|
|
81
|
+
# milliseconds; if the claim tracked its lifetime it would evaporate immediately
|
|
82
|
+
# and the next racer would be handed the very cycle we just emitted. Default to
|
|
83
|
+
# the caller (PPID) — the shell, agent, or fleet loop that will actually do the
|
|
84
|
+
# work and whose death SHOULD return the cycle to the stream.
|
|
85
|
+
export GOVERN_CLAIM_PID="${DO_NEXT_OWNER_PID:-$PPID}"
|
|
86
|
+
# PPID is only the real worker when do-next is called directly. Called the
|
|
87
|
+
# natural way — `cid=$(do-next.sh <slug> --claim)` — the parent IS the command
|
|
88
|
+
# substitution's subshell, which dies the instant the value is assigned, so the
|
|
89
|
+
# claim would evaporate before the worker ever saw the cycle it was handed.
|
|
90
|
+
# minhold keeps the ground held for a grace window regardless of owner liveness;
|
|
91
|
+
# the worker adopts it by re-claiming under its own pid (claim_take is idempotent
|
|
92
|
+
# for the same owner), and the lease TTL still bounds the whole thing.
|
|
93
|
+
export GOVERN_CLAIM_MIN_HOLD_SECS="${DO_NEXT_MIN_HOLD_SECS:-300}"
|
|
94
|
+
took=""
|
|
95
|
+
for cid in $ready; do
|
|
96
|
+
if claim_take "do-${SLUG}-${cid}" "${SLUG}/${cid}" >/dev/null 2>&1; then
|
|
97
|
+
took="$took $cid"
|
|
98
|
+
[ "$ALL" -eq 1 ] || break
|
|
99
|
+
fi
|
|
100
|
+
done
|
|
101
|
+
took=$(printf '%s' "$took" | tr ' ' '\n' | grep -v '^$' || true)
|
|
102
|
+
if [ -z "$took" ]; then
|
|
103
|
+
echo "do-next: ${SLUG} — every ready cycle is already claimed by a live worker. Nothing to take." >&2
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
printf '%s\n' "$took"
|
|
@@ -278,11 +278,23 @@ while [ "$i" -lt "$ITERS" ]; do
|
|
|
278
278
|
fi
|
|
279
279
|
|
|
280
280
|
# LIVE: build the plan (do-auto worktrees + cycles + W4 + digest)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
#
|
|
282
|
+
# The log lives BESIDE the worktrees, never inside one. It used to be
|
|
283
|
+
# "$WT_BASE/$pick/.orchestrate.log", which forced a `mkdir -p` of the worktree
|
|
284
|
+
# path before do-auto ran — and that plain directory made do-auto's old
|
|
285
|
+
# `rev-parse --git-dir` probe report "resuming worktree". `git worktree add`
|
|
286
|
+
# then never ran, and every cycle commit targeted the MAIN tree's HEAD, where
|
|
287
|
+
# _safe_stage refuses to stage: orchestrated cycles committed NOTHING, with no
|
|
288
|
+
# error. do-auto now discriminates properly (see _is_worktree there); this side
|
|
289
|
+
# removes the trigger, so neither half depends on the other being correct.
|
|
290
|
+
#
|
|
291
|
+
# Keeping it outside also means the log survives `git worktree remove`, which
|
|
292
|
+
# is when you most want to read it.
|
|
293
|
+
log="$WT_BASE/.logs/$pick.log" # not `local` — this loop is top-level, not a function
|
|
294
|
+
mkdir -p "$WT_BASE/.logs"
|
|
295
|
+
echo " ▶ building (log: $log)" | tee -a "$RUNLOG"
|
|
296
|
+
if "$AUTO" "$pick" >"$log" 2>&1; then
|
|
297
|
+
_link_deps "$WT_BASE/$pick" # do-auto cut the worktree; ensure deps are linked
|
|
286
298
|
if _reflect "$pick" | tee -a "$RUNLOG"; then
|
|
287
299
|
echo " ✅ $pick — VERIFIED, merge-ready on branch do/$pick" | tee -a "$RUNLOG"
|
|
288
300
|
BUILT+=("$pick")
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// do-plan-json.mjs — parse a /do plan into JSON, deterministically. No LLM.
|
|
2
|
+
//
|
|
3
|
+
// WHY (2026-08-31): do-engine.js runs with no filesystem access, so it asked an
|
|
4
|
+
// LLM agent for "the raw, verbatim content of text/<slug>-todo.md". An LLM will
|
|
5
|
+
// not return a 30KB file verbatim — it returned 6.8KB ending "(Full todo text
|
|
6
|
+
// continues...)". The guard reading open cycles out of that text saw NONE, and
|
|
7
|
+
// four launches burned ~3M subagent tokens building nothing.
|
|
8
|
+
//
|
|
9
|
+
// The plan is a deterministic fact on disk. Reading it through a probabilistic
|
|
10
|
+
// channel is the defect. This is the deterministic channel; the agent's only job
|
|
11
|
+
// becomes "run this, paste stdout". Same discipline as do-auto.sh's _remaining(),
|
|
12
|
+
// which is grep and has never had this failure.
|
|
13
|
+
//
|
|
14
|
+
// Exits non-zero and emits NOTHING when the plan is unreadable — a partial plan
|
|
15
|
+
// must never read as a runnable one.
|
|
16
|
+
import fs from 'node:fs'
|
|
17
|
+
import path from 'node:path'
|
|
18
|
+
import crypto from 'node:crypto'
|
|
19
|
+
|
|
20
|
+
const slug = process.argv[2]
|
|
21
|
+
if (!slug) { console.error('usage: do-plan-json.mjs <slug>'); process.exit(2) }
|
|
22
|
+
|
|
23
|
+
const repo = path.resolve(new URL('../..', import.meta.url).pathname)
|
|
24
|
+
|
|
25
|
+
// READ THE TODO THE ENGINE WRITES.
|
|
26
|
+
//
|
|
27
|
+
// do-tick.sh runs inside the plan WORKTREE, so a closed cycle is ticked in
|
|
28
|
+
// .do-worktrees/<slug>/text/<slug>-todo.md. This parser read main's copy, where
|
|
29
|
+
// those cycles are still `- [ ]` until the branch merges. So a relaunch computed
|
|
30
|
+
// openCycles from main and rebuilt work the worktree had already proven --
|
|
31
|
+
// measured 2026-09-01: a relaunch of `lifecycle` re-ran C0, C2 and C3 (all three
|
|
32
|
+
// closed and committed on do/lifecycle) before it could reach the C1/C5 it was
|
|
33
|
+
// launched for.
|
|
34
|
+
//
|
|
35
|
+
// Same read/write divergence that stalled the engine this morning, one level up:
|
|
36
|
+
// the tick and the reader were looking at two different files. The worktree copy
|
|
37
|
+
// is the live record while a plan is in flight; main's is the record after merge.
|
|
38
|
+
// Prefer the worktree when it exists, and SAY which one was read so a surprising
|
|
39
|
+
// openCycles is one line of output away from being explained.
|
|
40
|
+
const wtTodo = path.join(repo, '.do-worktrees', slug, 'text', `${slug}-todo.md`)
|
|
41
|
+
const mainTodo = path.join(repo, 'text', `${slug}-todo.md`)
|
|
42
|
+
const todo = (process.env.DO_PLAN_TODO) || (fs.existsSync(wtTodo) ? wtTodo : mainTodo)
|
|
43
|
+
if (!fs.existsSync(todo)) { console.error(`do-plan-json: no such plan: text/${slug}-todo.md`); process.exit(3) }
|
|
44
|
+
|
|
45
|
+
const t = fs.readFileSync(todo, 'utf8')
|
|
46
|
+
const fail = (m) => { console.error(`do-plan-json: ${m}`); process.exit(4) }
|
|
47
|
+
|
|
48
|
+
const fm = t.match(/^---\n([\s\S]*?)\n---/)
|
|
49
|
+
if (!fm) fail('no frontmatter')
|
|
50
|
+
const F = fm[1]
|
|
51
|
+
|
|
52
|
+
// batches: `- [C0, C2]` rows under `batches:`
|
|
53
|
+
//
|
|
54
|
+
// DEFECT this shape exists to prevent (measured 2026-09-08 on text/ads-loop-todo.md):
|
|
55
|
+
// the capture used to be /^batches:\n((?:[ \t]+- \[.*\]\n?)+)/m. A trailing
|
|
56
|
+
// `# comment` after the closing `]` is legal YAML and carried by most plans in
|
|
57
|
+
// text/ — it leaves text after `]`, so `\n?` matched empty, the `+` quantifier
|
|
58
|
+
// stopped at that row, and EVERY batch from there on was dropped. Nothing
|
|
59
|
+
// called fail(): the script still exited 0 and still emitted a well-formed
|
|
60
|
+
// object. ads-loop parsed as [["C1"…"C9"]] instead of [["C1"…"C9"],["C10"]],
|
|
61
|
+
// so /do would have run nine cycles of a ten-cycle plan and reported complete.
|
|
62
|
+
//
|
|
63
|
+
// Two changes, and the second is the load-bearing one:
|
|
64
|
+
// 1. a row may carry a trailing comment and trailing whitespace;
|
|
65
|
+
// 2. the block is delimited by INDENTATION (YAML's own rule) and every line
|
|
66
|
+
// inside it must parse — a line that does not is a REFUSAL, never a
|
|
67
|
+
// shorter list. Comment-tolerance alone would leave the next unreadable
|
|
68
|
+
// row failing in exactly the same silent way.
|
|
69
|
+
// Blank lines and whole-line `#` comments inside the block are legal and skipped.
|
|
70
|
+
// Inline flow style (`batches: [[C0]]`) still yields [] and fails at the
|
|
71
|
+
// `no batches:` invariant below, exactly as before.
|
|
72
|
+
const BATCH_ROW = /^[ \t]+-[ \t]*\[([^\]]*)\][ \t]*(?:#.*)?$/
|
|
73
|
+
const fLines = F.split('\n')
|
|
74
|
+
const bStart = fLines.findIndex((l) => /^batches:[ \t]*$/.test(l))
|
|
75
|
+
let batches = []
|
|
76
|
+
if (bStart >= 0) {
|
|
77
|
+
const rows = []
|
|
78
|
+
const bad = []
|
|
79
|
+
for (let i = bStart + 1; i < fLines.length; i++) {
|
|
80
|
+
const line = fLines[i]
|
|
81
|
+
if (!line.trim()) continue // blank line inside the block
|
|
82
|
+
if (!/^[ \t]/.test(line)) break // a column-0 line is the next key: block over
|
|
83
|
+
if (/^[ \t]*#/.test(line)) continue // whole-line comment
|
|
84
|
+
const m = line.match(BATCH_ROW)
|
|
85
|
+
if (m) rows.push(m[1])
|
|
86
|
+
else bad.push(line)
|
|
87
|
+
}
|
|
88
|
+
if (bad.length) {
|
|
89
|
+
fail(
|
|
90
|
+
`\`batches:\` carries ${bad.length} row(s) this parser cannot read. ` +
|
|
91
|
+
'Refusing: a truncated batch list must never read as a complete one. ' +
|
|
92
|
+
`A row is \`- [C1, C2]\` with an optional trailing # comment. First offender: ${JSON.stringify(bad[0])}`,
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
batches = rows.map((r) => r.split(',').map((s) => s.trim()).filter(Boolean))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// outcome: the kill-switch. A YAML double-quoted scalar carries \" and \\ escapes;
|
|
99
|
+
// stripping the outer quotes WITHOUT unescaping leaves a stray backslash that
|
|
100
|
+
// JSON.stringify then doubles, emitting invalid JSON (measured: lifecycle-money,
|
|
101
|
+
// lifecycle-human, both of whose outcomes contain a quoted grep pattern).
|
|
102
|
+
function unquote(raw) {
|
|
103
|
+
if (raw.length > 1 && raw.startsWith('"') && raw.endsWith('"')) {
|
|
104
|
+
return raw.slice(1, -1).replace(/\\(["\\])/g, '$1')
|
|
105
|
+
}
|
|
106
|
+
if (raw.length > 1 && raw.startsWith("'") && raw.endsWith("'")) {
|
|
107
|
+
return raw.slice(1, -1).replace(/''/g, "'")
|
|
108
|
+
}
|
|
109
|
+
return raw
|
|
110
|
+
}
|
|
111
|
+
let outcome = ''
|
|
112
|
+
const om = F.match(/^outcome:[ \t]*(?:\|[-+]?|>[-+]?)?[ \t]*(.*)$/m)
|
|
113
|
+
if (om) {
|
|
114
|
+
if (om[1].trim()) outcome = unquote(om[1].trim())
|
|
115
|
+
else {
|
|
116
|
+
const rest = F.slice(F.indexOf(om[0]) + om[0].length)
|
|
117
|
+
const blk = rest.match(/^\n((?:[ \t]+.*\n?)+)/)
|
|
118
|
+
if (blk) outcome = blk[1].split('\n').map((l) => l.trim()).filter(Boolean).join(' ')
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// The Status kanban — the same fact do-auto.sh's _remaining() greps.
|
|
123
|
+
const openCycles = []
|
|
124
|
+
for (const m of t.matchAll(/^[ \t]*- \[[ ~]\] \*{0,2}(C\d+)\*{0,2} —/gm)) {
|
|
125
|
+
if (!openCycles.includes(m[1])) openCycles.push(m[1])
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// closed + deps — the REAL DAG, so a scheduler can stream instead of marching.
|
|
129
|
+
// `batches:` is the author's flattening of this graph into rounds, and a round
|
|
130
|
+
// costs its slowest member: a cycle whose own dependencies closed an hour ago
|
|
131
|
+
// still waits at the barrier for an unrelated sibling. The edges themselves live
|
|
132
|
+
// in the kanban's `state: blocked-on-C1,C2` clause. Emitting them lets the engine
|
|
133
|
+
// start a cycle the moment ITS dependencies pass. Same source do-next.sh reads.
|
|
134
|
+
const closedCycles = []
|
|
135
|
+
for (const m of t.matchAll(/^[ \t]*- \[x\] \*{0,2}(C\d+)\*{0,2} —/gm)) {
|
|
136
|
+
if (!closedCycles.includes(m[1])) closedCycles.push(m[1])
|
|
137
|
+
}
|
|
138
|
+
const deps = {}
|
|
139
|
+
for (const m of t.matchAll(/^[ \t]*- \[[x ~]\] \*{0,2}(C\d+)\*{0,2} —(.*)$/gm)) {
|
|
140
|
+
const d = m[2].match(/blocked-on-([C0-9,]+)/)
|
|
141
|
+
deps[m[1]] = d ? d[1].split(',').map((x) => x.trim()).filter(Boolean) : []
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Cycle headers: "## C3 — title ⚡/★ marker"
|
|
145
|
+
const cycles = {}
|
|
146
|
+
const heads = [...t.matchAll(/^##[ \t]+(C\d+)[ \t]+—[ \t]+(.+)$/gm)]
|
|
147
|
+
heads.forEach((m, i) => {
|
|
148
|
+
const body = t.slice(m.index, i + 1 < heads.length ? heads[i + 1].index : t.length)
|
|
149
|
+
// Repo-relative paths named anywhere in the cycle body — W1's recon list.
|
|
150
|
+
// ALWAYS repo-relative: an absolute path double-prefixes ${WT} downstream, and
|
|
151
|
+
// an LLM asked for "the file list" returns absolute paths about half the time.
|
|
152
|
+
const files = [...new Set(
|
|
153
|
+
[...body.matchAll(/(?<![\w/.-])((?:one\.ie|packages|channels|api|schema|pay|sync|backup|text|\.claude)\/[A-Za-z0-9._\/\[\]-]+\.[a-z]{2,5})/g)]
|
|
154
|
+
.map((f) => f[1].replace(/[.,)`]+$/, '')),
|
|
155
|
+
)]
|
|
156
|
+
// A path is only a recon hint if it EXISTS. Plan prose abbreviates
|
|
157
|
+
// ("api/events.ts" for one.ie/web/src/pages/api/events.ts), and a hint that
|
|
158
|
+
// resolves to nothing sends W1 hunting for a file that was never there.
|
|
159
|
+
// Existence-filtering makes the list self-validating instead of plausible.
|
|
160
|
+
.filter((f) => fs.existsSync(path.join(repo, f)))
|
|
161
|
+
.slice(0, 12)
|
|
162
|
+
cycles[m[1]] = {
|
|
163
|
+
title: m[2].replace(/\s*[⚡★].*$/, '').trim(),
|
|
164
|
+
// schema tier: touches persistent/irreversible shape (same patterns as do-tier.sh)
|
|
165
|
+
schema: /\.tql\b|migrations\/\d|\.sql\b|\.move\b|\.sol\b/.test(body),
|
|
166
|
+
fableMandatory: /\[Fable\s*—\s*mandatory\]/i.test(body),
|
|
167
|
+
files,
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// Invariants — refuse to emit a plan that cannot be run.
|
|
172
|
+
if (!batches.length) fail('frontmatter has no `batches:` — nothing to schedule')
|
|
173
|
+
if (!openCycles.length) {
|
|
174
|
+
fail('no open cycles in the Status kanban. Lines must match /^[ \\t]*- \\[[ ~]\\] C<n> — /; ' +
|
|
175
|
+
'a "Batch N - [ ] C0 — ..." prefix breaks the ^ anchor and hides the cycle')
|
|
176
|
+
}
|
|
177
|
+
const orphan = batches.flat().filter((c) => !cycles[c])
|
|
178
|
+
if (orphan.length) fail(`batches name cycles with no "## Cn —" header: ${orphan.join(', ')}`)
|
|
179
|
+
if (!outcome) fail('frontmatter has no `outcome:` kill-switch')
|
|
180
|
+
|
|
181
|
+
// shared_recon: the author's own explicit file list, existence-checked the same way.
|
|
182
|
+
const srm = F.match(/^shared_recon:\n((?:[ \t]+- .*\n?)+)/m)
|
|
183
|
+
const sharedRecon = srm
|
|
184
|
+
? srm[1].trim().split('\n').map((l) => l.replace(/^[\s-]+/, '').trim())
|
|
185
|
+
.filter((f) => f && fs.existsSync(path.join(repo, f)))
|
|
186
|
+
: []
|
|
187
|
+
|
|
188
|
+
process.stdout.write(JSON.stringify({
|
|
189
|
+
slug,
|
|
190
|
+
sharedRecon,
|
|
191
|
+
todo: path.relative(repo, todo),
|
|
192
|
+
todoSource: todo === mainTodo ? 'main' : 'worktree',
|
|
193
|
+
worktree: path.join(repo, '.do-worktrees', slug),
|
|
194
|
+
outcome,
|
|
195
|
+
batches,
|
|
196
|
+
openCycles,
|
|
197
|
+
closedCycles,
|
|
198
|
+
deps,
|
|
199
|
+
cycles,
|
|
200
|
+
sha256: crypto.createHash('sha256').update(t).digest('hex').slice(0, 16),
|
|
201
|
+
}, null, 2) + '\n')
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-plan-json.sh <slug> — thin wrapper over do-plan-json.mjs (the parser).
|
|
3
|
+
# manifest: portable
|
|
4
|
+
# Kept as a .sh so callers match every other script in this dir; the parser is a
|
|
5
|
+
# real .mjs file rather than a quoted heredoc, because embedding it inline is how
|
|
6
|
+
# a stray backslash silently became invalid JSON.
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
exec node "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/do-plan-json.mjs" "$@"
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-preflight.sh <slug> [--fix] — refuse to launch a plan into a broken environment.
|
|
3
|
+
#
|
|
4
|
+
# manifest: needs-env
|
|
5
|
+
#
|
|
6
|
+
# WHY. Every expensive failure on 2026-08-31 was an ENVIRONMENT fault discovered
|
|
7
|
+
# only AFTER the run had spent itself:
|
|
8
|
+
#
|
|
9
|
+
# - an unreachable model made W2 return nothing; W3 edited nothing, W4 verified
|
|
10
|
+
# an empty diff, and the plan reported "batch total loss". 41 agents and 3.7M
|
|
11
|
+
# tokens to learn that a model name was wrong.
|
|
12
|
+
# - `.env` was not linked into the worktree, so real-TypeDB tests failed with
|
|
13
|
+
# no_gateway_key and two verifiers hand-classified 4-7 red files as
|
|
14
|
+
# "environmental" before they could score their gate.
|
|
15
|
+
# - four abandoned gates held the box at load 138 and one held a governor slot,
|
|
16
|
+
# so live cycles starved and verifiers began SKIPPING their gates and passing
|
|
17
|
+
# cycles anyway.
|
|
18
|
+
# - a cycle line the writers could tick was invisible to the engine's reader.
|
|
19
|
+
#
|
|
20
|
+
# Not one of those is a code defect, and every one is checkable in seconds. This
|
|
21
|
+
# is the cheap deterministic gate that runs first, so a run is never spent
|
|
22
|
+
# discovering something a grep could have said.
|
|
23
|
+
#
|
|
24
|
+
# Exit 0 = safe to launch. Exit 1 = a blocker (do not launch). Exit 2 = usage.
|
|
25
|
+
set -uo pipefail
|
|
26
|
+
|
|
27
|
+
SLUG="${1:-}"; shift 2>/dev/null || true
|
|
28
|
+
FIX=0
|
|
29
|
+
for f in "$@"; do case "$f" in --fix) FIX=1 ;; esac; done
|
|
30
|
+
[ -n "$SLUG" ] || { echo "usage: do-preflight.sh <slug> [--fix]" >&2; exit 2; }
|
|
31
|
+
|
|
32
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
33
|
+
WT="$ROOT/.do-worktrees/$SLUG"
|
|
34
|
+
bad=0; warn=0
|
|
35
|
+
ok() { printf ' ok %s\n' "$1"; }
|
|
36
|
+
fail() { printf ' FAIL %s\n' "$1"; bad=$((bad+1)); }
|
|
37
|
+
note() { printf ' warn %s\n' "$1"; warn=$((warn+1)); }
|
|
38
|
+
|
|
39
|
+
echo "do-preflight: $SLUG"
|
|
40
|
+
|
|
41
|
+
# 1. The plan must PARSE. do-plan-json refuses (non-zero, no stdout) on a plan
|
|
42
|
+
# missing worktree/outcome/batches/cycles, which is the failure that used to
|
|
43
|
+
# surface as an LLM summarising a 30KB file down to 6.8KB.
|
|
44
|
+
if plan=$(node "$ROOT/.claude/scripts/do-plan-json.mjs" "$SLUG" 2>&1); then
|
|
45
|
+
ok "plan parses ($(printf '%s' "$plan" | python3 -c 'import json,sys; d=json.load(sys.stdin); print(len(d["cycles"]), "cycles,", len(d["batches"]), "batches")' 2>/dev/null || echo 'ok'))"
|
|
46
|
+
else
|
|
47
|
+
fail "plan does not parse: $(printf '%s' "$plan" | head -1)"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# 2. Worktree + the gitignored deps a worktree cannot inherit. A missing .env is
|
|
51
|
+
# not a slow test -- it makes a verifier argue with its own gate and teaches
|
|
52
|
+
# whoever reads the report that red is normal.
|
|
53
|
+
if [ -d "$WT" ]; then
|
|
54
|
+
ok "worktree present"
|
|
55
|
+
for dep in .env node_modules; do
|
|
56
|
+
for where in "one.ie/web/$dep" "$dep"; do
|
|
57
|
+
[ -e "$ROOT/$where" ] || continue
|
|
58
|
+
if [ -e "$WT/$where" ]; then ok "linked: $where"; else
|
|
59
|
+
if [ "$FIX" = "1" ]; then
|
|
60
|
+
mkdir -p "$(dirname "$WT/$where")" && ln -s "$ROOT/$where" "$WT/$where" 2>/dev/null \
|
|
61
|
+
&& ok "linked: $where (repaired)" || fail "could not link $where"
|
|
62
|
+
else
|
|
63
|
+
fail "MISSING in worktree: $where — real-infra tests will fail as 'environmental'. Re-run with --fix"
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
break
|
|
67
|
+
done
|
|
68
|
+
done
|
|
69
|
+
else
|
|
70
|
+
note "no worktree yet at .do-worktrees/$SLUG (do-auto --setup-only will cut one)"
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# 3. The cycle-line shape must agree across its four implementations, or the
|
|
74
|
+
# engine reads a different set of open cycles than the writers can tick.
|
|
75
|
+
if bash "$ROOT/.claude/scripts/do-cycle-shape-check.sh" >/dev/null 2>&1; then
|
|
76
|
+
ok "cycle-line shape parity (reader == writer)"
|
|
77
|
+
else
|
|
78
|
+
fail "cycle-shape parity RED — run .claude/scripts/do-cycle-shape-check.sh"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# 4. A workflow with a syntax error does not fail loudly; it never launches.
|
|
82
|
+
if node "$ROOT/.claude/scripts/wf-check.mjs" >/dev/null 2>&1; then
|
|
83
|
+
ok "workflow scripts parse + seam checks"
|
|
84
|
+
else
|
|
85
|
+
fail "wf-check RED — run node .claude/scripts/wf-check.mjs"
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
# 5. The box. A saturated box is why verifiers started skipping gates and
|
|
89
|
+
# reporting passes anyway, so this is an ACCURACY check, not only a speed one.
|
|
90
|
+
orph=$(bash "$ROOT/.claude/scripts/gate-reaper.sh" --once --dry-run 2>/dev/null | grep -c 'WOULD reap' || true)
|
|
91
|
+
if [ "${orph:-0}" -gt 0 ]; then
|
|
92
|
+
if [ "$FIX" = "1" ]; then
|
|
93
|
+
bash "$ROOT/.claude/scripts/gate-reaper.sh" --once >/dev/null 2>&1
|
|
94
|
+
ok "reaped ${orph} abandoned gate(s)"
|
|
95
|
+
else
|
|
96
|
+
fail "${orph} abandoned gate(s) burning the box — re-run with --fix, or gate-reaper.sh --once"
|
|
97
|
+
fi
|
|
98
|
+
else
|
|
99
|
+
ok "no abandoned gates"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
head=$(bash -c ". '$ROOT/.claude/scripts/lib/govern.sh'; gate_headroom" 2>/dev/null || echo 99)
|
|
103
|
+
loadi=$(uptime | sed -E 's/.*load averages?: ([0-9.]+).*/\1/' | cut -d. -f1)
|
|
104
|
+
cores=$(sysctl -n hw.ncpu 2>/dev/null || echo 8)
|
|
105
|
+
if [ "${loadi:-0}" -gt $(( cores * 3 )) ]; then
|
|
106
|
+
note "load ${loadi} on ${cores} cores — gates will run slow; the governor will queue them"
|
|
107
|
+
else
|
|
108
|
+
ok "load ${loadi} on ${cores} cores, memory funds ${head} concurrent cycle(s)"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
echo
|
|
112
|
+
if [ "$bad" -gt 0 ]; then
|
|
113
|
+
echo "do-preflight: $bad blocker(s) — DO NOT LAUNCH. Every one of these is cheaper to fix now than to discover mid-run."
|
|
114
|
+
exit 1
|
|
115
|
+
fi
|
|
116
|
+
echo "do-preflight: clear${warn:+ ($warn warning(s))} — safe to launch $SLUG"
|
|
117
|
+
exit 0
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# do-project.sh — project a promise's deliverables: into an idempotent subtask spec.
|
|
3
|
+
# Parent = the promise (born with tags slug:<slug> — resolveTaskBySlug cannot retrofit).
|
|
4
|
+
# One child per deliverable; notes first line is accept: <that accept:> verbatim.
|
|
5
|
+
# Emits JSON. Does not POST. Re-projecting with --existing <prior.json> yields new_rows=0.
|
|
6
|
+
# Usage: do-project.sh <slug> [--existing prior.json] | do-project.sh --self-test
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
10
|
+
|
|
11
|
+
# Lifted from do-promise-lint.sh — do not fork a second YAML parser.
|
|
12
|
+
unquote() {
|
|
13
|
+
local line="$1"
|
|
14
|
+
case "$line" in
|
|
15
|
+
\"*) printf '%s' "$line" | sed -E 's/^"(.*)"[[:space:]]*(#.*)?$/\1/' | sed 's/\\"/"/g' ;;
|
|
16
|
+
\'*) printf '%s' "$line" | sed -E "s/^'(.*)'[[:space:]]*(#.*)?\$/\1/" | sed "s/''/'/g" ;;
|
|
17
|
+
*) printf '%s' "$line" | sed -E 's/[[:space:]]+#.*$//' ;;
|
|
18
|
+
esac
|
|
19
|
+
}
|
|
20
|
+
fm() { awk '/^---[[:space:]]*$/{c++;next} c==1{print} c>=2{exit}' "$1"; }
|
|
21
|
+
extract_proof() {
|
|
22
|
+
local line
|
|
23
|
+
line="$(fm "$1" | awk '/^proof:/{print;exit}')"
|
|
24
|
+
[ -z "$line" ] && return 1
|
|
25
|
+
line="${line#proof:}"
|
|
26
|
+
unquote "$(printf '%s' "$line" | sed -E 's/^[[:space:]]+//')"
|
|
27
|
+
}
|
|
28
|
+
deliverables_block() {
|
|
29
|
+
fm "$1" | awk '/^deliverables:/{on=1;next} on && /^[a-zA-Z_-]+:/{exit} on{print}'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if [ "${1:-}" = "--self-test" ]; then
|
|
33
|
+
S="$(cd "$(dirname "$0")" && pwd)"; f=0
|
|
34
|
+
dir="$(mktemp -d)"; trap 'rm -rf "$dir"' EXIT
|
|
35
|
+
cat >"$dir/proj.md" <<'F'
|
|
36
|
+
---
|
|
37
|
+
title: Project fixture
|
|
38
|
+
slug: proj
|
|
39
|
+
deliverables:
|
|
40
|
+
- item: "alpha row"
|
|
41
|
+
accept: "test -f a"
|
|
42
|
+
- item: "beta row"
|
|
43
|
+
accept: "test -f b"
|
|
44
|
+
proof: "test -f a && test -f b"
|
|
45
|
+
---
|
|
46
|
+
F
|
|
47
|
+
cat >"$dir/gap.md" <<'F'
|
|
48
|
+
---
|
|
49
|
+
title: Gap
|
|
50
|
+
slug: gap
|
|
51
|
+
deliverables:
|
|
52
|
+
- item: "alpha row"
|
|
53
|
+
proof: "true"
|
|
54
|
+
---
|
|
55
|
+
F
|
|
56
|
+
out1="$(PROMISE_FILE="$dir/proj.md" "$S/do-project.sh" proj)" || { echo " FAIL first project exited $?"; exit 1; }
|
|
57
|
+
echo "$out1" >"$dir/once.json"
|
|
58
|
+
case "$out1" in *"\"slug:proj\""*) printf ' ok parent carries slug:proj\n' ;;
|
|
59
|
+
*) printf ' FAIL parent missing slug:proj: %s\n' "$out1"; f=1 ;; esac
|
|
60
|
+
case "$out1" in *"accept: test -f a"*) printf ' ok child carries accept: verbatim\n' ;;
|
|
61
|
+
*) printf ' FAIL child missing accept: %s\n' "$out1"; f=1 ;; esac
|
|
62
|
+
case "$out1" in *"\"new_rows\":3"*) printf ' ok first project inserts 3 rows\n' ;;
|
|
63
|
+
*) printf ' FAIL first new_rows: %s\n' "$out1"; f=1 ;; esac
|
|
64
|
+
out2="$(PROMISE_FILE="$dir/proj.md" "$S/do-project.sh" proj --existing "$dir/once.json")" || { echo " FAIL second project exited $?"; f=1; out2=""; }
|
|
65
|
+
case "$out2" in *"\"new_rows\":0"*) printf ' ok second project new_rows=0\n' ;;
|
|
66
|
+
*) printf ' FAIL second diff not empty: %s\n' "$out2"; f=1 ;; esac
|
|
67
|
+
set +e
|
|
68
|
+
PROMISE_FILE="$dir/gap.md" "$S/do-project.sh" gap >/dev/null 2>&1; rc=$?
|
|
69
|
+
set -e
|
|
70
|
+
[ "$rc" -ne 0 ] && printf ' ok missing accept: refused (exit %s)\n' "$rc" \
|
|
71
|
+
|| { printf ' FAIL missing accept: should refuse\n'; f=1; }
|
|
72
|
+
[ "$f" -eq 0 ] && echo "do-project: self-test green" || echo "do-project: self-test FAILED"
|
|
73
|
+
exit "$f"
|
|
74
|
+
fi
|
|
75
|
+
|
|
76
|
+
slug="${1:-}"
|
|
77
|
+
[ -z "$slug" ] && { echo "usage: do-project.sh <slug> [--existing prior.json] | --self-test" >&2; exit 4; }
|
|
78
|
+
shift
|
|
79
|
+
existing=""
|
|
80
|
+
while [ $# -gt 0 ]; do
|
|
81
|
+
case "$1" in
|
|
82
|
+
--existing) existing="${2:-}"; shift 2 ;;
|
|
83
|
+
*) echo "unknown flag $1" >&2; exit 4 ;;
|
|
84
|
+
esac
|
|
85
|
+
done
|
|
86
|
+
|
|
87
|
+
file="${PROMISE_FILE:-$ROOT/text/$slug.md}"
|
|
88
|
+
[ -f "$file" ] || { echo "do-project: no promise at ${file#"$ROOT"/}" >&2; exit 3; }
|
|
89
|
+
|
|
90
|
+
block="$(deliverables_block "$file")"
|
|
91
|
+
[ -z "$block" ] && { echo "do-project: no deliverables: in ${file#"$ROOT"/}" >&2; exit 1; }
|
|
92
|
+
|
|
93
|
+
items=""; accepts=""
|
|
94
|
+
while IFS= read -r raw; do
|
|
95
|
+
[ -z "$raw" ] && continue
|
|
96
|
+
val="$(unquote "$(printf '%s' "$raw" | sed -E 's/^[[:space:]]*-[[:space:]]*item:[[:space:]]*//')")"
|
|
97
|
+
items="${items}${val}
|
|
98
|
+
"
|
|
99
|
+
done <<EOF
|
|
100
|
+
$(printf '%s\n' "$block" | grep '^[[:space:]]*-[[:space:]]*item:' || true)
|
|
101
|
+
EOF
|
|
102
|
+
while IFS= read -r raw; do
|
|
103
|
+
[ -z "$raw" ] && continue
|
|
104
|
+
val="$(unquote "$(printf '%s' "$raw" | sed -E 's/^[[:space:]]*accept:[[:space:]]*//')")"
|
|
105
|
+
accepts="${accepts}${val}
|
|
106
|
+
"
|
|
107
|
+
done <<EOF
|
|
108
|
+
$(printf '%s\n' "$block" | grep '^[[:space:]]*accept:' || true)
|
|
109
|
+
EOF
|
|
110
|
+
|
|
111
|
+
n_items="$(printf '%s' "$items" | grep -c . || true)"
|
|
112
|
+
n_acc="$(printf '%s' "$accepts" | grep -c . || true)"
|
|
113
|
+
if [ "$n_items" -lt 1 ] || [ "$n_items" -ne "$n_acc" ]; then
|
|
114
|
+
echo "do-project: refuse — $n_items item(s) but $n_acc accept(s); every deliverable needs accept:" >&2
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
title="$(fm "$file" | awk '/^title:/{sub(/^title:[[:space:]]*/,"");print;exit}')"
|
|
119
|
+
title="$(unquote "$title")"
|
|
120
|
+
[ -z "$title" ] && title="$slug"
|
|
121
|
+
proof="$(extract_proof "$file" || true)"
|
|
122
|
+
|
|
123
|
+
pairf="$(mktemp)"
|
|
124
|
+
paste -d $'\t' <(printf '%s' "$items" | grep -v '^$') <(printf '%s' "$accepts" | grep -v '^$') >"$pairf"
|
|
125
|
+
python3 - "$slug" "$title" "$proof" "$existing" "$pairf" <<'PY'
|
|
126
|
+
import json, sys
|
|
127
|
+
slug, title, proof, existing_path, pairf = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
|
|
128
|
+
pairs = [ln.rstrip("\n").split("\t", 1) for ln in open(pairf) if ln.strip()]
|
|
129
|
+
if not proof:
|
|
130
|
+
proof = " && ".join(p[1] for p in pairs)
|
|
131
|
+
prior = {"parent": None, "children": []}
|
|
132
|
+
if existing_path:
|
|
133
|
+
prior = json.load(open(existing_path))
|
|
134
|
+
prior_tags = (prior.get("parent") or {}).get("tags") or []
|
|
135
|
+
parent_exists = ("slug:%s" % slug) in prior_tags
|
|
136
|
+
prior_titles = {c.get("title") for c in (prior.get("children") or []) if c.get("title")}
|
|
137
|
+
parent = {
|
|
138
|
+
"title": title,
|
|
139
|
+
"tags": ["slug:%s" % slug],
|
|
140
|
+
"notes": "accept: %s" % proof,
|
|
141
|
+
"insert": not parent_exists,
|
|
142
|
+
"create": "tasks:create",
|
|
143
|
+
}
|
|
144
|
+
children, new_rows = [], 1 if parent["insert"] else 0
|
|
145
|
+
for item, accept in pairs:
|
|
146
|
+
ins = item not in prior_titles
|
|
147
|
+
children.append({
|
|
148
|
+
"title": item,
|
|
149
|
+
"notes": "accept: %s" % accept,
|
|
150
|
+
"insert": ins,
|
|
151
|
+
"steps": ["tasks:subtask", "tasks:notes"],
|
|
152
|
+
})
|
|
153
|
+
if ins:
|
|
154
|
+
new_rows += 1
|
|
155
|
+
print(json.dumps({"parent": parent, "children": children, "new_rows": new_rows}, separators=(",", ":")))
|
|
156
|
+
PY
|
|
157
|
+
rm -f "$pairf"
|