disinto/formulas/run-gardener.toml

554 lines
27 KiB
TOML
Raw Normal View History

# formulas/run-gardener.toml — Gardener housekeeping formula
#
# Defines the gardener's complete run: grooming (Claude session via
# gardener-run.sh) + blocked-review + AGENTS.md maintenance + final
fix: refactor: replace escalation JSONL with blocked label + diagnostic comment (#352) 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>
2026-03-21 04:18:43 +00:00
# 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 → dust-bundling → blocked-review → stale-pr-recycle → agents-update → commit-and-pr
name = "run-gardener"
fix: refactor: replace escalation JSONL with blocked label + diagnostic comment (#352) 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>
2026-03-21 04:18:43 +00:00
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
4. Initialize the pending-actions manifest (JSONL, converted to JSON at commit time):
printf '' > "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
"""
# ─────────────────────────────────────────────────────────────────────
# 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
(Claude performs pre-checks inline before deeper analysis).
Pre-checks (bash, zero tokens detect problems before invoking Claude):
1. Fetch all open issues:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_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"
Body enrichment on promotion (CRITICAL prevents quality-gate bounce):
When promoting ANY issue to backlog, you MUST enrich the issue body so
it passes the quality gate (step 8) on the next gardener run. Before
writing the add_label manifest action:
a. Check whether the body already contains ``## Acceptance criteria``
(with at least one ``- [ ]`` checkbox) and ``## Affected files``
(with at least one file path). If both are present, skip to (d).
b. If ``## Affected files`` is missing, infer from the body — look for
file paths (e.g. ``lib/agent-session.sh:266``), function names,
script names, or directory references. Use the AGENTS.md directory
layout to resolve ambiguous mentions (e.g. "gardener"
``gardener/gardener-run.sh``, "dev-poll" ``dev/dev-poll.sh``).
Format as a bulleted list under a ``## Affected files`` heading.
c. If ``## Acceptance criteria`` is missing, derive ``- [ ]`` checkboxes
from the problem description each a verifiable condition the fix
must satisfy.
d. Construct the full new body = original body text + appended missing
sections. Write an edit_body action BEFORE the add_label action:
echo '{"action":"edit_body","issue":NNN,"body":"<full new body>"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
e. Write the add_label action:
echo '{"action":"add_label","issue":NNN,"label":"backlog"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
This ensures promoted issues already have the required sections when
the next gardener run's quality gate inspects them.
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 dust-bundling step auto-bundles groups
of 3+ into one backlog issue.
VAULT (needs human decision or external resource):
File a vault procurement item at $PROJECT_REPO_ROOT/vault/pending/<id>.md:
# <What decision or resource is needed>
## What
<description>
## Why
<which issue this unblocks>
## Unblocks
- #NNN — <title>
Log: echo "VAULT: filed vault/pending/<id>.md for #NNN — <reason>" >> "$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)"
7. Architecture decision alignment check (AD check):
For each open issue labeled 'backlog', check whether the issue
contradicts any architecture decision listed in the
## Architecture Decisions section of AGENTS.md.
Read AGENTS.md and extract the AD table. For each backlog issue,
compare the issue title and body against each AD. If an issue
clearly violates an AD:
a. Write a comment action to the manifest:
echo '{"action":"comment","issue":NNN,"body":"Closing: violates AD-NNN (<decision summary>). See AGENTS.md § Architecture Decisions."}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
b. Write a close action to the manifest:
echo '{"action":"close","issue":NNN,"reason":"violates AD-NNN"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
c. Log to the result file:
echo "ACTION: closed #NNN — violates AD-NNN" >> "$RESULT_FILE"
Only close for clear, unambiguous violations. If the issue is
borderline or could be interpreted as compatible, leave it open
and file a VAULT item for human decision instead.
8. Quality gate backlog label enforcement:
For each open issue labeled 'backlog', verify it has the required
sections for dev-agent pickup:
a. Acceptance criteria body must contain at least one checkbox
(``- [ ]`` or ``- [x]``)
b. Affected files body must contain an "Affected files" or
"## Affected files" section with at least one file path
If either section is missing:
a. Write a comment action to the manifest:
echo '{"action":"comment","issue":NNN,"body":"This issue is missing required sections. Please use the issue templates at `.forgejo/ISSUE_TEMPLATE/` — needs: <missing items>."}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
Where <missing items> is a comma-separated list of what's absent
(e.g. "acceptance criteria, affected files" or just "affected files").
b. Write a remove_label action to the manifest:
echo '{"action":"remove_label","issue":NNN,"label":"backlog"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
c. Log to the result file:
echo "ACTION: stripped backlog from #NNN — missing: <missing items>" >> "$RESULT_FILE"
Well-structured issues (both sections present) are left untouched
they are ready for dev-agent pickup.
Processing order:
1. Handle PRIORITY_blockers_starving_factory first promote or resolve
2. AD alignment check close backlog issues that violate architecture decisions
3. Quality gate strip backlog from issues missing acceptance criteria or affected files
4. Process tech-debt issues by score (impact/effort)
5. Classify remaining items as dust or route to vault
Do NOT bundle dust yourself the dust-bundling step handles accumulation,
dedup, TTL expiry, and bundling into backlog issues.
CRITICAL: If this step fails for any reason, log the failure and move on.
"""
needs = ["preflight"]
# ─────────────────────────────────────────────────────────────────────
# Step 3: dust-bundling — accumulate, expire, and bundle dust items
# ─────────────────────────────────────────────────────────────────────
[[steps]]
id = "dust-bundling"
title = "Accumulate dust, expire stale entries, and bundle groups"
description = """
Process DUST items emitted during grooming. This step maintains the
persistent dust accumulator at $PROJECT_REPO_ROOT/gardener/dust.jsonl.
IMPORTANT: Use $PROJECT_REPO_ROOT/gardener/dust.jsonl (the main repo
checkout), NOT the worktree copy the worktree is destroyed after the
session, so changes there would be lost.
1. Collect DUST JSON lines emitted during grooming (from the result file
or your notes). Each has: {"issue": NNN, "group": "...", "title": "...", "reason": "..."}
2. Deduplicate: read existing dust.jsonl and skip any issue numbers that
are already staged:
DUST_FILE="$PROJECT_REPO_ROOT/gardener/dust.jsonl"
touch "$DUST_FILE"
EXISTING=$(jq -r '.issue' "$DUST_FILE" 2>/dev/null | sort -nu || true)
For each new dust item, check if its issue number is in EXISTING.
Add new entries with a timestamp:
echo '{"issue":NNN,"group":"...","title":"...","reason":"...","ts":"YYYY-MM-DDTHH:MM:SSZ"}' >> "$DUST_FILE"
3. Expire stale entries (30-day TTL):
CUTOFF=$(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%SZ)
jq -c --arg c "$CUTOFF" 'select(.ts >= $c)' "$DUST_FILE" > "${DUST_FILE}.tmp" && mv "${DUST_FILE}.tmp" "$DUST_FILE"
4. Bundle groups with 3+ distinct issues:
a. Count distinct issues per group:
jq -r '[.group, (.issue | tostring)] | join("\\t")' "$DUST_FILE" | sort -u | cut -f1 | sort | uniq -c | sort -rn
b. For each group with count >= 3:
- Collect issue details and distinct issue numbers for the group
- Write a create_issue action to the manifest:
echo '{"action":"create_issue","title":"fix: bundled dust cleanup — GROUP","body":"...","labels":["backlog"]}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
- Write comment + close actions for each source issue:
echo '{"action":"comment","issue":NNN,"body":"Bundled into dust cleanup issue for GROUP"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
echo '{"action":"close","issue":NNN,"reason":"bundled into dust cleanup for GROUP"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
- Remove bundled items from dust.jsonl:
jq -c --arg g "GROUP" 'select(.group != $g)' "$DUST_FILE" > "${DUST_FILE}.tmp" && mv "${DUST_FILE}.tmp" "$DUST_FILE"
5. If no DUST items were emitted and no groups are ripe, skip this step.
CRITICAL: If this step fails, log the failure and move on to blocked-review.
"""
needs = ["grooming"]
# ─────────────────────────────────────────────────────────────────────
# Step 4: 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. Fetch all blocked issues:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues?state=open&type=issues&labels=blocked&limit=50"
2. For each blocked issue, read the full body and comments:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues/<number>"
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues/<number>/comments"
3. Check dependencies extract issue numbers from ## Dependencies /
## Depends on / ## Blocked by sections. For each dependency:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues/<dep_number>"
Check if the dependency is now closed.
4. For each blocked issue, choose ONE action:
UNBLOCK all dependencies are now closed or the blocking condition resolved:
a. Write a remove_label action to the manifest:
echo '{"action":"remove_label","issue":NNN,"label":"blocked"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
b. Write a comment action to the manifest:
echo '{"action":"comment","issue":NNN,"body":"Unblocked: <explanation of what resolved the blocker>"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
NEEDS HUMAN blocking condition is ambiguous, requires architectural
decision, or involves external factors:
a. Write a comment action to the manifest:
echo '{"action":"comment","issue":NNN,"body":"<diagnostic: what you found and what decision is needed>"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
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. Write a comment action to the manifest:
echo '{"action":"comment","issue":NNN,"body":"Closing: <reason — stale blocker, no longer relevant, etc.>"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
b. Write a close action to the manifest:
echo '{"action":"close","issue":NNN,"reason":"<stale blocker / no longer relevant / etc.>"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
CRITICAL: If this step fails, log the failure and move on.
"""
needs = ["dust-bundling"]
# ─────────────────────────────────────────────────────────────────────
# Step 5: stale-pr-recycle — recycle stale failed PRs back to backlog
# ─────────────────────────────────────────────────────────────────────
[[steps]]
id = "stale-pr-recycle"
title = "Recycle stale failed PRs back to backlog"
description = """
Detect open PRs where CI has failed and no work has happened in 24+ hours.
These represent abandoned dev-agent attempts recycle them so the pipeline
can retry with a fresh session.
1. Fetch all open PRs:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/pulls?state=open&limit=50"
2. For each PR, check all four conditions before recycling:
a. CI failed get the HEAD SHA from the PR's head.sha field, then:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/commits/<head_sha>/status"
Only proceed if the combined state is "failure" or "error".
Skip PRs with "success", "pending", or no CI status.
b. Last push > 24 hours ago get the commit details:
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/git/commits/<head_sha>"
Parse the committer.date field. Only proceed if it is older than:
$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)
c. Linked issue exists extract the issue number from the PR body.
Look for "Fixes #NNN" or "ixes #NNN" patterns (case-insensitive).
If no linked issue found, skip this PR (cannot reset labels).
d. No active tmux session check:
tmux has-session -t "dev-${PROJECT_NAME}-<issue_number>" 2>/dev/null
If a session exists, someone may still be working skip this PR.
3. For each PR that passes all checks (failed CI, 24+ hours stale,
linked issue found, no active session):
a. Write a comment on the PR explaining the recycle:
echo '{"action":"comment","issue":<pr_number>,"body":"Recycling stale CI failure for fresh attempt. Previous PR: #<pr_number>"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
b. Write a close_pr action:
echo '{"action":"close_pr","pr":<pr_number>}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
c. Remove the in-progress label from the linked issue:
echo '{"action":"remove_label","issue":<issue_number>,"label":"in-progress"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
d. Add the backlog label to the linked issue:
echo '{"action":"add_label","issue":<issue_number>,"label":"backlog"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
e. Log to result file:
echo "ACTION: recycled PR #<pr_number> (linked issue #<issue_number>) — stale CI failure" >> "$RESULT_FILE"
4. If no stale failed PRs found, skip this step.
CRITICAL: If this step fails, log the failure and move on to agents-update.
"""
needs = ["blocked-review"]
# ─────────────────────────────────────────────────────────────────────
# Step 6: agents-update — AGENTS.md watermark staleness + size enforcement
# ─────────────────────────────────────────────────────────────────────
[[steps]]
id = "agents-update"
title = "Check AGENTS.md watermarks, update stale files, enforce size limit"
description = """
Check all AGENTS.md files for staleness, update any that are outdated, and
enforce the ~200-line size limit via progressive disclosure splitting.
This keeps documentation fresh runs 2x/day so drift stays small.
## Part A: Watermark staleness check and update
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: architecture and WHY not implementation details
## Part B: Size limit enforcement (progressive disclosure split)
After all updates are done, count lines in the root AGENTS.md:
wc -l < "$PROJECT_REPO_ROOT/AGENTS.md"
If the root AGENTS.md exceeds 200 lines, perform a progressive disclosure
split. The principle: agent reads the map, drills into detail only when
needed. You wouldn't dump a 500-page wiki on a new hire's first morning.
6. Identify per-directory sections to extract. Each agent section under
"## Agents" (e.g. "### Dev (`dev/`)", "### Review (`review/`)") and
each helper section (e.g. "### Shared helpers (`lib/`)") is a candidate.
Also extract verbose subsections like "## Issue lifecycle and label
conventions" and "## Phase-Signaling Protocol" into docs/ or the
relevant directory.
7. For each section to extract, create a `{dir}/AGENTS.md` file with:
- Line 1: watermark <!-- last-reviewed: <HEAD_SHA> -->
- The full section content (role, trigger, key files, env vars, lifecycle)
- Keep the same markdown structure and detail level
Example for dev/:
```
<!-- last-reviewed: abc123 -->
# Dev Agent
**Role**: Implement issues autonomously ...
**Trigger**: dev-poll.sh runs every 10 min ...
**Key files**: ...
**Environment variables consumed**: ...
**Lifecycle**: ...
```
8. Replace extracted sections in the root AGENTS.md with a concise
directory map table. The root file keeps ONLY:
- Watermark (line 1)
- ## What this repo is (brief overview)
- ## Directory layout (existing tree)
- ## Tech stack
- ## Coding conventions
- ## How to lint and test
- ## Agents — replaced with a summary table pointing to per-dir files:
## Agents
| Agent | Directory | Role | Guide |
|-------|-----------|------|-------|
| Dev | dev/ | Issue implementation | [dev/AGENTS.md](dev/AGENTS.md) |
| Review | review/ | PR review | [review/AGENTS.md](review/AGENTS.md) |
| Gardener | gardener/ | Backlog grooming | [gardener/AGENTS.md](gardener/AGENTS.md) |
| ... | ... | ... | ... |
- ## Shared helpers — replaced with a brief pointer:
"See [lib/AGENTS.md](lib/AGENTS.md) for the full helper reference."
Keep the summary table if it fits, or move it to lib/AGENTS.md.
- ## Issue lifecycle and label conventions — keep a brief summary
(labels table + dependency convention) or move verbose parts to
docs/PHASE-PROTOCOL.md
- ## Architecture Decisions — keep in root (humans write, agents enforce)
- ## Phase-Signaling Protocol — keep a brief summary with pointer:
"See [docs/PHASE-PROTOCOL.md](docs/PHASE-PROTOCOL.md) for the full spec."
9. Verify the root AGENTS.md is now under 200 lines:
LINE_COUNT=$(wc -l < "$PROJECT_REPO_ROOT/AGENTS.md")
if [ "$LINE_COUNT" -gt 200 ]; then
echo "WARNING: root AGENTS.md still $LINE_COUNT lines after split"
fi
If still over 200, trim further move more detail into per-directory
files. The root should read like a table of contents, not an encyclopedia.
10. Each new per-directory AGENTS.md must have a watermark on line 1.
The gardener maintains freshness for ALL AGENTS.md files root and
per-directory using the same watermark mechanism from Part A.
## Staging
11. Stage ALL AGENTS.md files you created or changed do NOT commit yet.
All git writes happen in the commit-and-pr step at the end:
find . -name "AGENTS.md" -not -path "./.git/*" -exec git add {} +
12. If no AGENTS.md files need updating AND root is under 200 lines,
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 = ["stale-pr-recycle"]
# ─────────────────────────────────────────────────────────────────────
# Step 7: commit-and-pr — single commit with all file changes
# ─────────────────────────────────────────────────────────────────────
[[steps]]
id = "commit-and-pr"
title = "One commit with all file changes, push, create PR, monitor to merge"
description = """
Collect all file changes from this run (AGENTS.md updates + pending-actions
manifest) into a single commit. All repo mutation API calls (comments, closures,
label changes, issue creation) are deferred to the manifest the orchestrator
executes them after the PR merges.
1. Convert the JSONL manifest to a JSON array:
cd "$PROJECT_REPO_ROOT"
JSONL_FILE="$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
JSON_FILE="$PROJECT_REPO_ROOT/gardener/pending-actions.json"
if [ -s "$JSONL_FILE" ]; then
jq -s '.' "$JSONL_FILE" > "$JSON_FILE"
else
echo '[]' > "$JSON_FILE"
fi
rm -f "$JSONL_FILE"
2. Check for staged or unstaged changes:
git status --porcelain
If there are no file changes (no AGENTS.md updates AND manifest is empty []),
skip to step 4 no commit, no PR needed.
3. 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. Stage the pending-actions manifest:
git add gardener/pending-actions.json
d. Also stage any other files the gardener modified (if any):
git add -u
e. Commit:
git commit -m "chore: gardener housekeeping $(date -u +%Y-%m-%d)"
f. Push:
git push -u origin "$BRANCH"
g. Create a PR:
PR_RESPONSE=$(curl -sf -X POST \
-H "Authorization: token $FORGE_TOKEN" \
-H "Content-Type: application/json" \
"$FORGE_API/pulls" \
-d '{"title":"chore: gardener housekeeping",
"head":"'"$BRANCH"'","base":"'"$PRIMARY_BRANCH"'",
"body":"Automated gardener housekeeping — AGENTS.md updates + pending actions manifest.\\n\\nReview `gardener/pending-actions.json` for proposed grooming actions (label changes, closures, comments). These execute after merge."}')
PR_NUMBER=$(echo "$PR_RESPONSE" | jq -r '.number')
h. Save PR number for orchestrator tracking:
echo "$PR_NUMBER" > /tmp/gardener-pr-${PROJECT_NAME}.txt
i. Signal the orchestrator to monitor CI:
echo "PHASE:awaiting_ci" > "$PHASE_FILE"
j. STOP and WAIT. Do NOT return to the primary branch.
The orchestrator polls CI, injects results and review feedback.
When you receive injected CI or review feedback, follow its
instructions, then write PHASE:awaiting_ci and wait again.
4. If no file changes existed (step 2 found nothing):
echo "PHASE:done" > "$PHASE_FILE"
5. If PR creation fails, log the error and write PHASE:failed.
"""
needs = ["agents-update"]