Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion INDEX.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -12,7 +12,7 @@ follow the cross-pointers in their index or take the next matching seeded domain
| Domain | Status | Route here when |
|--------|--------|-----------------|
| [databases](wiki/databases/index.md) | **seeded** | Designing schemas/tables/keys, choosing or evaluating indexes, writing or optimizing queries, choosing transaction/isolation behavior, surveying live data to derive a rule, verifying additive migrations |
| [backend](wiki/backend/index.md) | **seeded** | Server-side application code — language-agnostic (`common/`: API contracts, call-site enumeration before a contract change, idempotency, JWT, timeouts/retries, caching, jobs, transactions in app code, shared state/pools, errors, consuming LLM APIs (completion validation, context budgeting), consuming external-API responses, externally-owned defaults, object-storage references) plus stack subtrees: `java/` (JPA, Spring proxies, JVM threads/memory), `node/` (event loop, promises, runtime validation, shutdown), `python/` (GIL/asyncio, pydantic, WSGI/ASGI workers, language traps) |
| [backend](wiki/backend/index.md) | **seeded** | Server-side application code — language-agnostic (`common/`: API contracts, call-site enumeration before a contract change, idempotency, JWT, timeouts/retries, caching, jobs, transactions in app code, shared state/pools, errors, consuming LLM APIs (completion validation, context budgeting), consuming external-API responses, externally-owned defaults, object-storage references, sync-vs-async integration choice, WebSocket/SSE connection lifecycle) plus stack subtrees: `java/` (JPA, Spring proxies, JVM threads/memory), `node/` (event loop, promises, runtime validation, shutdown), `python/` (GIL/asyncio, pydantic, WSGI/ASGI workers, language traps) |
| [frontend](wiki/frontend/index.md) | **seeded** | Web UI code: state placement, rendering performance, in-UI data fetching (races, infinite scroll), auth token handling, forms, XSS-safe output, accessibility |
| [infrastructure](wiki/infrastructure/index.md) | **seeded** | CI/CD pipelines, secrets in build/deploy, container image builds, rollout/rollback strategy, observability (logs/metrics/alerting), per-environment/path-valued config, multi-agent orchestration (worker liveness signals, shared run state, tmux pane delivery, completion gates, worktree-isolated workers) |
| [testing](wiki/testing/index.md) | **seeded** | Writing or structuring automated tests: level choice, cases/assertions, test data, mock decisions, flaky tests (release-process quality → qa) |
Expand Down
1 change: 1 addition & 0 deletions log.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,3 +44,4 @@ Append-only. Format: `## [YYYY-MM-DD] <ingest|revise|lint|gap|contradiction|drif
## [2026-08-06] dedup | Cross-PR duplicate clusters collapsed: client-side rate limiting (8 PR versions at 3 paths → 1), call-site enumeration (7 versions at 7 paths → additions folded into the already-merged canonical page), stderr/exit-0 diagnostics (4 versions at 4 paths → 1), macOS sysroot (2→1), env-var off switch (2→1), completion predicates (2→1), robots.txt (2→1), harness-mediated results (2→1), leaked test artifacts (2→1), orchestration category naming (orchestration vs agent-orchestration → agent-orchestration). Kept distinct after trigger comparison: differential-testing vs differential-run-agreement (setup vs interpretation), unset-versus-empty-parameters vs env-var-off-switches (semantics vs design, cross-linked), import-time-side-effects vs test-level-choice edge (tactics vs level choice, cross-linked). Merged-main near-dup scan (Jaccard over title+trigger, 141 pages): 0 pairs — no duplication among previously merged content.
## [2026-08-06] ingest | Reconciled post-consolidation flush PRs #42–#43 (created against pre-#44 main). Folded unique content: platforms/filesystems/permissions-and-exec-bits (+`sh "$SCRIPT"` interpreter-invocation rows — stubs need no exec bit, avoids EDR chmod+x heuristics), platforms/shells/command-text-inspected-before-execution (+gate-AUTHOR step 8: parse all three POSIX quoting forms, expand only ~/$HOME/${HOME}; red-then-green bats evidence), testing/quality/checks-that-cannot-pass (+vacuously-green pre-implementation test row), platforms/processes/tool-diagnostics-without-a-failing-exit-code (+repeat-suppression edge, +clang reproduction source), infrastructure/ci-cd/secrets-handling (+independent credential channels row: gh token vs git SSH vs API token, git push --dry-run / author:@me probes), testing/mocking/what-to-mock (+ESM read-only-bindings DI rows, +PATH-emptied tripwire negative-proof). Dropped as duplicates of the #44 state: #42's rate-limit row (already in timeouts-and-retries + client-side-rate-limiting), #42's warning-only-diagnostics page (same trigger as tool-diagnostics-without-a-failing-exit-code), #42's leak-attribution row (already in test-data-and-isolation).
## [2026-08-12] revise | routing: disjoint scopes for the doc-gate cluster (testing/quality ↔ qa/document-verification) and the flaky pair (testing/flaky ↔ debugging/concurrency); INDEX backend LLM phrasing; databases→backup cross-pointer (#37)
## [2026-08-17] ingest | Wiki-audit gap seeds G1-G6 (issue #38, 6 new pages). New categories: backend/common/architecture (sync-vs-async-integration — direct call vs queue vs event by consistency/latency/failure-isolation, AWS Prescriptive Guidance), backend/common/realtime (websocket-sse-lifecycle — auth at handshake, ping/pong dead-peer detection, SSE retry/Last-Event-ID reconnect vs WebSocket client-side backoff, backpressure bounding, shutdown draining; RFC 6455 + WHATWG SSE spec). Existing categories: backend/common/api-design +2 (cors-and-preflight — simple vs preflighted requests, wildcard-forbidden-with-credentials; api-versioning-and-breaking-changes — backward-compatible vs breaking change classification, Sunset header deprecation signal; MDN, Fetch spec, Stripe versioning/upgrades docs, RFC 8594), infrastructure/deploy +1 (feature-flag-lifecycle — release/experiment/ops/permissioning categories, toggle-as-inventory, removal-task-on-introduction; Fowler FeatureToggles), databases/operations +1 (data-backfill-migrations — batch/transaction sizing so a backfill doesn't hold row locks for its full duration, resumability, completion verification; strong_migrations, Retool Postgres migrations guide). All cited URLs live-fetched and verified this session. G1's other two categories.md sub-topics (module-boundaries-and-layering, event-driven-adoption-criteria) intentionally not written — narrower single-page scope per t38 plan D2; left as future ingest candidates on issue #38. G7-G10 remain open on the issue, out of this task's scope.
15 changes: 13 additions & 2 deletions skills/orchestrate/SKILL.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -238,7 +238,12 @@ so every `launch-session.sh` gets a collision-proof name `lo-<n>-<run-id>` (reus
exact name for later `send-prompt.sh`); the script also exports the guardrails
escalation env into each worker. Trust-screen wording drifts between CLI releases —
if a launch hangs, set `LO_READY_EXTRA` / `LO_TRUST_EXTRA` (substrings) or
`LO_READY_TIMEOUT`.
`LO_READY_TIMEOUT`. `status-update.sh` resolves the status file's `session` field
from `tmux display-message -p '#S'` only when the caller is itself inside tmux
(`$TMUX` set) — a coordinator-side call (this shell, not a worker's tmux pane)
must pass `STATUS_SESSION=<lo-n-runid>` explicitly, or the record's `session`
field is left absent rather than guessed from whatever tmux session happens to
be active.
`watch-status.sh` now exits **5** on a pending guardrails escalation (approve/deny,
clear `.orchestration/escalations/`, then DELIVER the outcome to the now-idle
worker with `scripts/send-prompt.sh send lo-<n> "approved — re-run: <cmd>, then
Expand DownExpand Up@@ -479,7 +484,13 @@ exits — handle, then relaunch watch with the same target:
recurs while `questions/<task>.json` exists, like exit 5; exit 5 wins when both
are pending): read the record (`{ts, taskId, question, options, worktree}`),
answer with `scripts/send-prompt.sh send lo-<n> "<answer>"`, delete the record
file, relaunch watch.
file. If the task's status was recorded `phase=failed` when it asked the
question, reset it to the phase you actually observe (read the worker pane
first) BEFORE relaunching watch — the reset IS a normal status write, not a
new phase word: `STATUS_DIR=<dir> STATUS_SESSION=<lo-n-runid> sh
scripts/status-update.sh <task> <observed-phase> note="reset after exit-6
answer"` — otherwise `watch-status.sh` counts the stale `failed` phase and
aborts with exit 3 again on the very next poll. Then relaunch watch.
- **7 — stalled live worker** (prints `[watch] worker stalled — <task>:<session>`;
the weakest signal — failed(3) and all-reached(0) win over it; driven by
`tmux-worker-stalled.sh`, silence threshold `LO_STALL_SEC` default 600s; a
Expand Down
32 changes: 32 additions & 0 deletions skills/orchestrate/scripts/send-prompt.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,8 @@
# env:
# LO_QUEUED_PATTERN substring meaning "queued behind a busy turn"
# LO_BUSY_PATTERN substring meaning "mid-turn" (state only)
# LO_PASTED_PATTERN substring meaning "collapsed to an unsubmitted paste
# placeholder" (issue #96, default: [Pasted text)
# LO_PANE_TAIL_LINES non-empty pane lines searched for those (default 6)
# LO_CONFIRM_DELAY seconds to settle before classifying a send (default 1)
# LO_PICKUP_TIMEOUT wait deadline in seconds (default 180)
Expand All@@ -54,6 +56,12 @@ TMUX_BIN=$(command -v tmux) || { echo "send-prompt: tmux not found" >&2; exit 12
queued_pat="${LO_QUEUED_PATTERN:-Press up to edit queued messages}"
busy_pat="${LO_BUSY_PATTERN:-esc to interrupt}"

# Issue #96: a send can collapse into an unsubmitted "[Pasted text #N ...]"
# placeholder left sitting in the input box — tmux still reports the send-keys
# call as successful. launch-session.sh's submit-confirm loop proved the
# remedy (a further Enter); cmd_send ports the same detection, bounded.
pasted_pat="${LO_PASTED_PATTERN:-[Pasted text}"

# Every grep against these uses `-- "$pat"`. A pattern beginning with '-' is
# otherwise parsed as a grep flag ("unrecognized option"), which is the same
# defect the '--' before the send-keys payload guards against.
Expand DownExpand Up@@ -183,6 +191,30 @@ cmd_send() {
note_pane "$1"
echo "queued"; exit 4
fi

# [Pasted text] guard (issue #96): the placeholder proves "buffered, not
# submitted" the same way queued_pat proves "buffered, not consumed" — so it
# gets the same retry-before-verdict treatment. Bounded to 3 extra Enters,
# mirroring launch-session.sh's submit-confirm loop; a pane still stuck after
# that is reported unconfirmed, never guessed delivered.
pasted_attempts=0
while printf '%s' "$after" | grep -qF -- "$pasted_pat" 2>/dev/null \
&& [ "$pasted_attempts" -lt 3 ]; do
if ! "$TMUX_BIN" send-keys -t "$(target_pane "$1")" Enter; then
session_alive "$1" || { echo "gone"; exit 3; }
echo "send-prompt: send-keys failed for live session '$1'" >&2
exit 6
fi
pasted_attempts=$((pasted_attempts + 1))
sleep "$confirm_delay"
after=$(pane_tail "$1") || {
echo "send-prompt: capture-pane failed for live session '$1'" >&2; exit 6; }
done
if printf '%s' "$after" | grep -qF -- "$pasted_pat" 2>/dev/null; then
note_pane "$1"
echo "unconfirmed"; exit 7
fi

if [ "$after" != "$before" ]; then
echo "delivered"; exit 0
fi
Expand Down
10 changes: 8 additions & 2 deletions skills/orchestrate/scripts/status-update.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,8 +22,14 @@ now=$(date -u +%Y-%m-%dT%H:%M:%SZ) # cross-platform (GNU/BSD) iso-8601 UTC
wt=$(pwd -P) # physical path so loop-gate can match it
# Record the tmux session name so watch-status can detect a dead worker. The
# worker runs inside its tmux session; allow an explicit override (orchestrator
# / tests) via STATUS_SESSION.
sess="${STATUS_SESSION:-$(tmux display-message -p '#S' 2>/dev/null || true)}"
# / tests) via STATUS_SESSION. Only ask tmux when actually inside one ($TMUX
# set) — outside tmux, `tmux display-message` still exits 0 and answers with
# the server's most-recently-active session, which is not this caller's (#97).
if [ -n "${TMUX:-}" ]; then
sess="${STATUS_SESSION:-$(tmux display-message -p '#S' 2>/dev/null || true)}"
else
sess="${STATUS_SESSION:-}"
fi

# Collect the extra key=value pairs into one JSON object first (in memory — no
# file writes), so the whole record lands in a single atomic write below.
Expand Down
76 changes: 76 additions & 0 deletions tests/send-prompt.bats
Original file line numberDiff line numberDiff line change
Expand Up@@ -228,6 +228,82 @@ mk_busy() {
[ "$status" -eq 1 ]
}

# ------------------------------------ send: [Pasted text] guard (issue #96)
#
# cmd_send must never report "delivered" while the prompt sits as an
# unsubmitted "[Pasted text #N]" placeholder. A real tmux pane cannot be
# driven through an exact multi-call capture-pane sequence (before -> after ->
# after each retry) deterministically, so these cases use a scripted fake tmux
# on PATH — same technique as the `keys` fake below, extended with a captures
# script so each successive capture-pane call returns the next scripted line.

# $FAKE_CAPTURES holds one pane-snapshot per line; capture-pane returns the
# Nth line on its Nth call (clamped to the last line once exhausted), so a
# test can script exactly what cmd_send sees on the before-capture, the
# post-send capture, and each retry re-capture.
_use_fake_tmux_send() {
mkdir -p "$STUB_ROOT/bin"
FAKE_SEND_LOG="$STUB_ROOT/sends.log"; : > "$FAKE_SEND_LOG"
FAKE_CAPTURE_N="$STUB_ROOT/capture_n"; echo 0 > "$FAKE_CAPTURE_N"
FAKE_CAPTURES="$STUB_ROOT/captures"; : > "$FAKE_CAPTURES"
FAKE_ALIVE_FILE="$STUB_ROOT/alive"; : > "$FAKE_ALIVE_FILE"
export FAKE_SEND_LOG FAKE_CAPTURE_N FAKE_CAPTURES FAKE_ALIVE_FILE
cat > "$STUB_ROOT/bin/tmux" <<'FAKE'
#!/bin/sh
verb="$1"; shift
case "$verb" in
has-session) [ -e "$FAKE_ALIVE_FILE" ]; exit $? ;;
capture-pane)
n=$(cat "$FAKE_CAPTURE_N")
total=$(wc -l < "$FAKE_CAPTURES" | tr -d ' ')
idx=$((n + 1))
[ "$idx" -gt "$total" ] && idx="$total"
sed -n "${idx}p" "$FAKE_CAPTURES"
echo $((n + 1)) > "$FAKE_CAPTURE_N"
exit 0 ;;
send-keys)
printf '%s\n' "$*" >> "$FAKE_SEND_LOG"
exit 0 ;;
esac
exit 0
FAKE
chmod +x "$STUB_ROOT/bin/tmux"
PATH="$STUB_ROOT/bin:$PATH"; export PATH
}
_enter_count() { grep -cx -- "-t =$S: Enter" "$FAKE_SEND_LOG"; }

@test "send: a [Pasted text] placeholder cleared by one retry is delivered" {
_use_fake_tmux_send
printf '%s\n' 'READY>' '[Pasted text #1 +2 lines]' 'T3_PASTE_RAN' > "$FAKE_CAPTURES"
run --separate-stderr sh "$SP" send "$S" 'echo T3_PASTE'
[ "$status" -eq 0 ]
[ "$output" = "delivered" ]
# submit Enter + exactly one retry Enter, never more once the placeholder clears
[ "$(_enter_count)" -eq 2 ]
}

@test "send: a [Pasted text] placeholder that never clears is unconfirmed, not delivered" {
_use_fake_tmux_send
printf '%s\n' 'READY>' '[Pasted text #1]' '[Pasted text #1]' '[Pasted text #1]' '[Pasted text #1]' \
> "$FAKE_CAPTURES"
run --separate-stderr sh "$SP" send "$S" 'echo T3_STUCK'
[ "$status" -eq 7 ]
[ "$output" = "unconfirmed" ]
# submit Enter + exactly 3 bounded retries — never an unbounded loop
[ "$(_enter_count)" -eq 4 ]
}

@test "boundary: a placeholder alongside the queued indicator reports queued, not unconfirmed" {
# Order is unchanged: the existing queued_pat check still wins over the new
# placeholder guard, so no retry Enter is sent at all.
_use_fake_tmux_send
printf '%s\n' 'READY>' '[Pasted text #1] Press up to edit queued messages' > "$FAKE_CAPTURES"
run --separate-stderr sh "$SP" send "$S" 'echo T3_BOTH'
[ "$status" -eq 4 ]
[ "$output" = "queued" ]
[ "$(_enter_count)" -eq 1 ]
}

# ---------------------------------------------------------------- wait

@test "wait: returns picked-up once the worker drains its queue" {
Expand Down
33 changes: 33 additions & 0 deletions tests/status-update.bats
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,18 @@
setup() {
SU="${BATS_TEST_DIRNAME}/../skills/orchestrate/scripts/status-update.sh"
export STATUS_DIR="${BATS_TEST_TMPDIR}/status"
# Session resolution reads $TMUX/$STATUS_SESSION from the ambient environment
# (issue #97: a suite launched from inside a real tmux session would
# otherwise silently inherit it and mask the non-tmux regression below).
unset TMUX STATUS_SESSION
}

mk_tmux_stub() { # $1 = session name the stub's `display-message -p '#S'` answers with
d="${BATS_TEST_TMPDIR}/tmuxbin"
mkdir -p "$d"
printf '#!/bin/sh\necho "%s"\n' "$1" > "$d/tmux"
chmod +x "$d/tmux"
printf '%s' "$d"
}

@test "writes phase, timestamp, worktree and extras in one valid-JSON record" {
Expand DownExpand Up@@ -36,3 +48,24 @@ setup() {
( cd "$BATS_TEST_TMPDIR" && STATUS_SESSION=lo-7 bash "$SU" t4 implementing )
[ "$(jq -r '.session' "$STATUS_DIR/t4.json")" = "lo-7" ]
}

@test "TMUX set, no STATUS_SESSION: asks tmux and records its answer (normal)" {
stub="$(mk_tmux_stub inside-session)"
run env TMUX=fake PATH="$stub:$PATH" bash "$SU" t5 implementing
[ "$status" -eq 0 ]
[ "$(jq -r '.session' "$STATUS_DIR/t5.json")" = "inside-session" ]
}

@test "TMUX unset: tmux is never consulted, session field stays absent (regression, issue #97)" {
stub="$(mk_tmux_stub wrongly-resolved-session)"
run env PATH="$stub:$PATH" bash "$SU" t6 implementing
[ "$status" -eq 0 ]
[ "$(jq -r '.session // "MISSING"' "$STATUS_DIR/t6.json")" = "MISSING" ]
}

@test "STATUS_SESSION wins outside tmux even with tmux on PATH (boundary)" {
stub="$(mk_tmux_stub wrongly-resolved-session)"
run env STATUS_SESSION=explicit PATH="$stub:$PATH" bash "$SU" t7 implementing
[ "$status" -eq 0 ]
[ "$(jq -r '.session' "$STATUS_DIR/t7.json")" = "explicit" ]
}
4 changes: 2 additions & 2 deletions tests/wiki-lint-prohibitions.bats
Original file line numberDiff line numberDiff line change
Expand Up@@ -18,11 +18,11 @@ setup() {

# --- normal: the real corpus is already compliant ---------------------------

@test "real wiki: exits 0 with 0 violations and 61 directive units" {
@test "real wiki: exits 0 with 0 violations and 64 directive units" {
cd "$REPO_ROOT" || return 1
run node "$CHECKER" wiki
[ "$status" -eq 0 ]
[[ "$output" == *"directives: 61"* ]]
[[ "$output" == *"directives: 64"* ]]
[[ "$output" == *"violations: 0"* ]]
}

Expand Down
Loading
Loading