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>
268 lines
12 KiB
TOML
268 lines
12 KiB
TOML
# formulas/run-gardener.toml — Gardener housekeeping formula
|
|
#
|
|
# Defines the gardener's complete run: grooming (Claude session via
|
|
# gardener-agent.sh) + blocked-review + AGENTS.md maintenance + final
|
|
# commit-and-pr.
|
|
#
|
|
# No memory, no journal. The gardener does mechanical housekeeping
|
|
# based on current state — it doesn't need to remember past runs.
|
|
#
|
|
# Steps: preflight → grooming → blocked-review → agents-update → commit-and-pr
|
|
|
|
name = "run-gardener"
|
|
description = "Mechanical housekeeping: grooming, blocked review, docs update"
|
|
version = 1
|
|
|
|
[context]
|
|
files = ["AGENTS.md", "VISION.md", "README.md"]
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Step 1: preflight
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
[[steps]]
|
|
id = "preflight"
|
|
title = "Pull latest code"
|
|
description = """
|
|
Set up the working environment for this gardener run.
|
|
|
|
1. Change to the project repository:
|
|
cd "$PROJECT_REPO_ROOT"
|
|
|
|
2. Pull the latest code:
|
|
git fetch origin "$PRIMARY_BRANCH" --quiet
|
|
git checkout "$PRIMARY_BRANCH" --quiet
|
|
git pull --ff-only origin "$PRIMARY_BRANCH" --quiet
|
|
|
|
3. Record the current HEAD SHA for AGENTS.md watermarks:
|
|
HEAD_SHA=$(git rev-parse HEAD)
|
|
echo "$HEAD_SHA" > /tmp/gardener-head-sha
|
|
"""
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Step 2: grooming — Claude-driven backlog grooming
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
[[steps]]
|
|
id = "grooming"
|
|
title = "Backlog grooming — triage all open issues"
|
|
description = """
|
|
Groom the open issue backlog. This step is the core Claude-driven analysis
|
|
(currently implemented in gardener-agent.sh with bash pre-checks).
|
|
|
|
Pre-checks (bash, zero tokens — detect problems before invoking Claude):
|
|
|
|
1. Fetch all open issues:
|
|
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
|
"$CODEBERG_API/issues?state=open&type=issues&limit=50&sort=updated&direction=desc"
|
|
|
|
2. Duplicate detection: compare issue titles pairwise. Normalize
|
|
(lowercase, strip prefixes like feat:/fix:/refactor:, collapse whitespace)
|
|
and flag pairs with >60% word overlap as possible duplicates.
|
|
|
|
3. Missing acceptance criteria: flag issues with body < 100 chars and
|
|
no checkboxes (- [ ] or - [x]).
|
|
|
|
4. Stale issues: flag issues with no update in 14+ days.
|
|
|
|
5. Blockers starving the factory (HIGHEST PRIORITY): find issues that
|
|
block backlog items but are NOT themselves labeled backlog. These
|
|
starve the dev-agent completely. Extract deps from ## Dependencies /
|
|
## Depends on / ## Blocked by sections of backlog issues and check
|
|
if each dependency is open + not backlog-labeled.
|
|
|
|
6. Tech-debt promotion: list all tech-debt labeled issues — goal is to
|
|
process them all (promote to backlog or classify as dust).
|
|
|
|
For each issue, choose ONE action and write to result file:
|
|
|
|
ACTION (substantial — promote, close duplicate, add acceptance criteria):
|
|
echo "ACTION: promoted #NNN to backlog — <reason>" >> "$RESULT_FILE"
|
|
echo "ACTION: closed #NNN as duplicate of #OLDER" >> "$RESULT_FILE"
|
|
|
|
DUST (trivial — single-line edit, rename, comment, style, whitespace):
|
|
echo 'DUST: {"issue": NNN, "group": "<file-or-subsystem>", "title": "...", "reason": "..."}' >> "$RESULT_FILE"
|
|
Group by file or subsystem (e.g. "gardener", "lib/env.sh", "dev-poll").
|
|
Do NOT close dust issues — the script auto-bundles groups of 3+ into
|
|
one backlog issue.
|
|
|
|
ESCALATE (needs human decision):
|
|
printf 'ESCALATE\n1. #NNN "title" — reason (a) option1 (b) option2\n' >> "$RESULT_FILE"
|
|
|
|
CLEAN (only if truly nothing to do):
|
|
echo 'CLEAN' >> "$RESULT_FILE"
|
|
|
|
Dust vs ore rules:
|
|
Dust: comment fix, variable rename, whitespace/formatting, single-line edit, trivial cleanup with no behavior change
|
|
Ore: multi-file changes, behavioral fixes, architectural improvements, security/correctness issues
|
|
|
|
Sibling dependency rule (CRITICAL):
|
|
Issues from the same PR review or code audit are SIBLINGS — independent work items.
|
|
NEVER add bidirectional ## Dependencies between siblings (creates deadlocks).
|
|
Use ## Related for cross-references: "## Related\n- #NNN (sibling)"
|
|
|
|
Processing order:
|
|
1. Handle PRIORITY_blockers_starving_factory first — promote or resolve
|
|
2. Process tech-debt issues by score (impact/effort)
|
|
3. Classify remaining items as dust or escalate
|
|
|
|
After processing, dust items are collected into gardener/dust.jsonl.
|
|
When a group accumulates 3+ distinct issues, create one bundled backlog
|
|
issue, close the source issues with cross-reference comments, and remove
|
|
bundled items from the staging file.
|
|
|
|
CRITICAL: If this step fails for any reason, log the failure and move on.
|
|
"""
|
|
needs = ["preflight"]
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Step 3: blocked-review — triage blocked issues
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
[[steps]]
|
|
id = "blocked-review"
|
|
title = "Review issues labeled blocked"
|
|
description = """
|
|
Review all issues labeled 'blocked' and decide their fate.
|
|
(See issue #352 for the blocked label convention.)
|
|
|
|
1. Look up the 'blocked' label ID (Gitea needs integer IDs for label removal):
|
|
BLOCKED_LABEL_ID=$(curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
|
"$CODEBERG_API/labels" | jq -r '.[] | select(.name == "blocked") | .id')
|
|
If the lookup fails, skip label removal and just post comments.
|
|
|
|
2. Fetch all blocked issues:
|
|
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
|
"$CODEBERG_API/issues?state=open&type=issues&labels=blocked&limit=50"
|
|
|
|
3. For each blocked issue, read the full body and comments:
|
|
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
|
"$CODEBERG_API/issues/<number>"
|
|
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
|
"$CODEBERG_API/issues/<number>/comments"
|
|
|
|
4. Check dependencies — extract issue numbers from ## Dependencies /
|
|
## Depends on / ## Blocked by sections. For each dependency:
|
|
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
|
"$CODEBERG_API/issues/<dep_number>"
|
|
Check if the dependency is now closed.
|
|
|
|
5. For each blocked issue, choose ONE action:
|
|
|
|
UNBLOCK — all dependencies are now closed or the blocking condition resolved:
|
|
a. Remove the 'blocked' label (using ID from step 1):
|
|
curl -sf -X DELETE -H "Authorization: token $CODEBERG_TOKEN" \
|
|
"$CODEBERG_API/issues/<number>/labels/$BLOCKED_LABEL_ID"
|
|
b. Add context comment explaining what changed:
|
|
curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
"$CODEBERG_API/issues/<number>/comments" \
|
|
-d '{"body":"Unblocked: <explanation of what resolved the blocker>"}'
|
|
|
|
NEEDS HUMAN — blocking condition is ambiguous, requires architectural
|
|
decision, or involves external factors:
|
|
a. Post a diagnostic comment explaining what you found and what
|
|
decision is needed
|
|
b. Leave the 'blocked' label in place
|
|
|
|
CLOSE — issue is stale (blocked 30+ days with no progress on blocker),
|
|
the blocker is wontfix, or the issue is no longer relevant:
|
|
a. Post a comment explaining why:
|
|
curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
"$CODEBERG_API/issues/<number>/comments" \
|
|
-d '{"body":"Closing: <reason — stale blocker, no longer relevant, etc.>"}'
|
|
b. Close the issue:
|
|
curl -sf -X PATCH -H "Authorization: token $CODEBERG_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
"$CODEBERG_API/issues/<number>" \
|
|
-d '{"state":"closed"}'
|
|
|
|
CRITICAL: If this step fails, log the failure and move on.
|
|
"""
|
|
needs = ["grooming"]
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Step 4: agents-update — AGENTS.md watermark staleness check
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
[[steps]]
|
|
id = "agents-update"
|
|
title = "Check AGENTS.md watermarks, update stale files"
|
|
description = """
|
|
Check all AGENTS.md files for staleness and update any that are outdated.
|
|
This keeps documentation fresh — runs 2x/day so drift stays small.
|
|
|
|
1. Read the HEAD SHA from preflight:
|
|
HEAD_SHA=$(cat /tmp/gardener-head-sha)
|
|
|
|
2. Find all AGENTS.md files:
|
|
find "$PROJECT_REPO_ROOT" -name "AGENTS.md" -not -path "*/.git/*"
|
|
|
|
3. For each file, read the watermark from line 1:
|
|
<!-- last-reviewed: <sha> -->
|
|
|
|
4. Check for changes since the watermark:
|
|
git log --oneline <watermark>..HEAD -- <directory>
|
|
If zero changes, the file is current — skip it.
|
|
|
|
5. For stale files:
|
|
- Read the AGENTS.md and the source files in that directory
|
|
- Update the documentation to reflect code changes since the watermark
|
|
- Set the watermark to the HEAD SHA from the preflight step
|
|
- Conventions: max ~200 lines, architecture and WHY not implementation details
|
|
|
|
6. Stage ONLY the AGENTS.md files you changed — do NOT commit yet.
|
|
All git writes happen in the commit-and-pr step at the end.
|
|
|
|
7. If no AGENTS.md files need updating, skip this step entirely.
|
|
|
|
CRITICAL: If this step fails for any reason, log the failure and move on.
|
|
Do NOT let an AGENTS.md failure prevent the commit-and-pr step.
|
|
"""
|
|
needs = ["blocked-review"]
|
|
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
# Step 6: commit-and-pr — single commit with all file changes
|
|
# ─────────────────────────────────────────────────────────────────────
|
|
|
|
[[steps]]
|
|
id = "commit-and-pr"
|
|
title = "One commit with all file changes, push, create PR"
|
|
description = """
|
|
Collect all file changes from this run (AGENTS.md updates) into a single commit.
|
|
API calls (issue creation, PR comments, closures) already happened during the
|
|
run — only file changes need the PR.
|
|
|
|
1. Check for staged or unstaged changes:
|
|
cd "$PROJECT_REPO_ROOT"
|
|
git status --porcelain
|
|
|
|
If there are no file changes, skip this entire step — no commit, no PR.
|
|
|
|
2. If there are changes:
|
|
a. Create a branch:
|
|
BRANCH="chore/gardener-$(date -u +%Y%m%d-%H%M)"
|
|
git checkout -B "$BRANCH"
|
|
b. Stage all modified AGENTS.md files:
|
|
find . -name "AGENTS.md" -not -path "./.git/*" -exec git add {} +
|
|
c. Also stage any other files the gardener modified (if any):
|
|
git add -u
|
|
d. Commit:
|
|
git commit -m "chore: gardener housekeeping $(date -u +%Y-%m-%d)"
|
|
e. Push:
|
|
git push -u origin "$BRANCH"
|
|
f. Create a PR:
|
|
curl -sf -X POST \
|
|
-H "Authorization: token $CODEBERG_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
"$CODEBERG_API/pulls" \
|
|
-d '{"title":"chore: gardener housekeeping",
|
|
"head":"<branch>","base":"<primary-branch>",
|
|
"body":"Automated gardener housekeeping — AGENTS.md updates.\n\nReview-agent fast-tracks doc-only PRs."}'
|
|
g. Return to primary branch:
|
|
git checkout "$PRIMARY_BRANCH"
|
|
|
|
3. If the PR creation fails (e.g. no changes after staging), log and continue.
|
|
"""
|
|
needs = ["agents-update"]
|