The BACKLOG_NUMS associative array was built to track which issue numbers
are in the backlog, but the DFS cycle-detection code used NODE_COLOR as
a membership guard instead. This meant deps pointing to non-backlog issues
were only skipped by coincidence (they weren't in NODE_COLOR either).
Three changes:
- Remove SC2034 suppression since BACKLOG_NUMS is now actually queried
- Initialize NODE_COLOR from BACKLOG_NUMS keys (all backlog issues) instead
of DEPS_OF keys (only issues with dependencies), so every backlog issue
gets a proper DFS color
- Replace the NODE_COLOR membership check with BACKLOG_NUMS in the DFS, so
the guard explicitly asks "is this dep a backlog issue?" rather than
relying on NODE_COLOR initialization as a proxy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add missing `set -euo pipefail` to three scripts per AGENTS.md conventions:
- lib/ci-helpers.sh
- lib/parse-deps.sh
- supervisor/supervisor-poll.sh
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the unreliable escalation JSONL system (supervisor/escalations-*.jsonl
consumed by gardener) with direct blocked label + diagnostic comment on the
original issue.
When a dev-agent or action-agent session fails (PHASE:failed, idle timeout,
crash, CI exhausted):
- Capture last 50 lines from tmux pane via tmux capture-pane
- Post a structured diagnostic comment on the issue (exit reason, timestamp,
PR number, tmux output)
- Label the issue "blocked" (instead of restoring "backlog")
- Remove in-progress label
Removed:
- Escalation JSONL write paths in dev-agent.sh, phase-handler.sh, dev-poll.sh,
action-agent.sh
- is_escalated() helper in dev-poll.sh
- Escalation triage (P2f section) in supervisor-poll.sh
- Escalation processing + recipe engine in gardener-poll.sh
- ci-escalation-recipes step from run-gardener.toml formula
- escalations*.jsonl from .gitignore
Added:
- post_blocked_diagnostic() shared helper in phase-handler.sh
- ensure_blocked_label_id() helper (creates label via API if not exists)
- is_blocked() helper in dev-poll.sh (replaces is_escalated)
- Blocked issues listing in supervisor/preflight.sh
Kept:
- Matrix notifications on failure (unchanged)
- CI fix counter logic (still tracks attempts)
- needs_human injection in supervisor/gardener (not escalation-related)
- Gardener grooming (gardener-agent.sh still invoked)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract shared is_infra_step() in lib/ci-helpers.sh capturing the union of
infra-detection heuristics from both P2e and classify_pipeline_failure():
- Clone/git step exit 128 (connection failure)
- Any step exit 137 (OOM/signal 9)
- Log-pattern matching (timeouts, connection failures)
Update classify_pipeline_failure() to use is_infra_step() with log fetching
and "any infra step" aggregation (matching P2e semantics). Simplify P2e to
delegate to classify_pipeline_failure(). Update P2f caller for new output
format ("infra <reason>").
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After sending P0/P1 alerts immediately, reset the variables so they are
excluded from the final consolidated ALL_ALERTS send at the end of the
script.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Send P0 and P1 alerts to Matrix immediately after detection, before
per-project checks run. Also guard check_project calls with || flog so
any API timeout or jq parse failure inside the per-project scan cannot
kill the script before alert delivery.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- supervisor-poll.sh: check PR state before retrigger; discard stale escalations
for closed/merged PRs instead of pushing to their branches
- supervisor-poll.sh: bump escalation ts to now on failed retrigger push, so
the 30-min cooldown resets and alert flooding is avoided on persistent failures
- ci-helpers.sh: require at least one confirmed infra step before returning
"infra"; prevents false-positive when all step names are empty strings
- ci-helpers.sh: clarify header comment to distinguish per-function requirements
- AGENTS.md: document classify_pipeline_failure() in ci-helpers.sh table row
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Skip cleanup for sessions in needs_human/awaiting_ci/awaiting_review phases
- On tmux display-message failure skip session instead of defaulting to epoch 0
- Use paginated PR lookups (page loop checking page size, not match count)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Fix SC2164: add || exit 1 to bare cd in update-prompt.sh
- Fix SC2155: separate declare and assign in env.sh, supervisor-poll.sh, dev-agent.sh
- Fix SC2034: inline suppression for vars used by sourced helpers
- Remove unused `mergeable` declaration, rename unused loop var to `_w`
- Remove || true from shellcheck CI step — failures are now blocking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move atomic mv inside gardener loop so reply is only claimed when a
matching needs_human session exists (fixes reply-loss regression)
- Delay rm of claimed file until after successful injection in both
supervisor and gardener (OOM/SIGKILL leaves file recoverable)
- Fix matrix_listener ack message: 'next poll' instead of 'next supervisor poll'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix dev-agent.sh comment: gardener-poll.sh is the backup injector, not review-poll.sh
- Add renotify marker cleanup to gardener injection path
- Use atomic mv to claim reply file, preventing double-injection race between supervisor and gardener
- Add break after supervisor injection for symmetry with gardener
- Remove overly prescriptive PHASE:awaiting_ci hardcode from injection instructions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- supervisor: skip *.done.jsonl in escalation glob (bug: wildcard matched
harb.done.jsonl producing spurious 'pending' log noise every cycle)
- supervisor: use wc -l instead of grep -c . for line counting (style nit)
- supervisor: consume gardener-esc-resolved.log via fixed() so escalation
resolutions appear in end-of-cycle supervisor reporting
- dev-poll: update all 'escalated to supervisor' log/matrix strings to
'escalated to gardener' (lines 263, 268, 344, 420)
- gardener: track _esc_total_created across all escalation entries and
write count to supervisor/gardener-esc-resolved.log after processing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dev-poll.sh: write escalations to per-project files
(supervisor/escalations-{PROJECT_NAME}.jsonl) and add "project" field
so each project's escalations are isolated; update is_escalated() to
read from the same per-project paths
- gardener-poll.sh: add escalation processing block that reads the
per-project escalation file, fetches CI logs via Woodpecker, and
creates per-file ShellCheck sub-issues or generic CI failure issues
labeled backlog — runs with the correct CODEBERG_API and
WOODPECKER_REPO_ID already loaded from the project TOML
- supervisor-poll.sh: remove the escalation processing block; replace
with a simple flog report counting pending escalations per project
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Race condition: mv escalations.jsonl to a PID-stamped snapshot before
processing so concurrent dev-poll appends go to a fresh file; rm snapshot
after loop — no entries are ever silently dropped
- SQL injection: validate ESC_PR_SHA is a 40-char hex string before
interpolating into the wpdb query
- sc_codes scope: compute per-file from file_errors (already filtered to
that file) instead of the entire step log; also switch grep to -F so
dots in filenames are not treated as regex wildcards
- step_pid validation: reject non-integer values from Woodpecker API before
passing as CLI argument
- Fallback body now distinguishes "CI logs unavailable" from "logs found
but issue creation API calls failed"
- ESC_GENERIC_FAIL: avoid leading blank line by using conditional separator
and fix code-block opening newline
- is_escalated(): remove dead esc_file/done_file locals; add Python-level
int() guard so empty/non-numeric issue or pr values fail cleanly instead
of producing a syntax error suppressed by 2>/dev/null
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- supervisor-poll.sh: replace P3 escalation log with actionable sub-issue creation.
For each entry in escalations.jsonl: fetch CI logs via woodpecker-cli, create one
sub-issue per file for ShellCheck failures, one combined issue for other CI failures,
or a fallback investigation issue if logs are unavailable. Move processed entries to
escalations.done.jsonl and clear escalations.jsonl.
- dev-poll.sh: add is_escalated() helper that checks both escalations.jsonl and
escalations.done.jsonl; use it (alongside ci_fix_count >= 3) in all three CI-fix
spawn paths so escalated PRs are skipped even if the ci-fixes tracker is reset.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- planner: filter CI and dev metrics by project name to prevent cross-project pollution
- planner: replace fragile awk JSONL filter with jq select()
- supervisor: add codeberg_count_paginated() helper; replace hardcoded limit=50 dev-metric API calls with paginated counts so projects with >50 issues report accurate blocked-ratio data
- supervisor: add 24h age filter to CI metric SQL query so stale pipelines are not re-emitted with a fresh timestamp
- supervisor: replace fragile awk key-order-dependent JSON filter in rotate_metrics() with jq select(); add safety guard to prevent overwriting file with empty result on parse failure
- supervisor: move mkdir -p for metrics dir to startup (once) instead of every emit_metric() call
- supervisor: guard _RAM_TOTAL_MB against empty value in bash arithmetic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- supervisor-poll.sh: append structured JSONL metrics on every poll
- infra metric (ram_used_pct, disk_used_pct, swap_mb) after Layer 1 checks
- ci metric (pipeline id, duration_min, status) per project via wpdb query
- dev metric (issues_in_backlog, issues_blocked, pr_open) per project via Codeberg API
- rotate_metrics() trims metrics/supervisor-metrics.jsonl to last 30 days on startup
- planner-agent.sh: reads last 7 days of metrics before Phase 2 gap analysis
- computes avg CI duration, success rate, RAM/disk utilization, blocked ratio
- injects summary into gap analysis prompt as "Operational metrics" section
- instructs planner to create optimization issues when metrics conflict with VISION.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace lib/parse-deps.py with lib/parse-deps.sh to keep the toolchain
all-bash. Rewrite supervisor P3b cycle detection and P3c stale dep check
as pure bash using associative arrays and DFS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single source of truth for dependency parsing, replacing three copies:
- dev-poll.sh get_deps() now calls parse-deps.py
- supervisor P3b/P3c import parse_deps() via importlib
Supports stdin, argument, and --json modes for different callers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add three new supervisor checks:
- P2c: alert when dev-agent reports "no ready issues" for 6+ consecutive polls
- P3b: detect circular dependency deadlocks via DFS cycle detection
- P3c: flag backlog issues blocked by deps open >30 days
Update supervisor PROMPT.md with guidance for Claude to resolve circular deps
by reading code context, and handle stale deps by checking relevance.
Gardener prompt now forbids bidirectional deps between sibling issues and
requires ## Related (not ## Dependencies) for cross-references.
Closes#16, Closes#17
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The supervisor agent was confusingly named "factory" (same as the
project). Rename directory, script, log, lock, status, and escalation
files. Update all references across scripts and docs.
FACTORY_ROOT env var unchanged (refers to project root, not agent).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>