# formulas/run-gardener.toml — Gardener housekeeping formula # # Defines the gardener's complete run: grooming (Claude session via # gardener-run.sh) + AGENTS.md maintenance + final commit-and-pr. # # Gardener has journaling via .profile (issue #97), so it learns from # past runs and improves over time. # # Steps: preflight -> grooming -> dust-bundling -> agents-update -> commit-and-pr name = "run-gardener" description = "Mechanical housekeeping: grooming, dust bundling, 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 — " >> "$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":""}' >> "$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": "", "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 using vault_request(): source "$(dirname "$0")/../lib/vault.sh" TOML_CONTENT="# Vault action: context = \"\" unblocks = [\"#NNN\"] [execution] # Commands to run after approval " PR_NUM=$(vault_request "" "$TOML_CONTENT") echo "VAULT: filed PR #${PR_NUM} for #NNN — " >> "$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)" 6. 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: ."}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl" Where 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: " >> "$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. Quality gate — strip backlog from issues missing acceptance criteria or affected files 3. Process tech-debt issues by score (impact/effort) 4. 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. """ needs = ["grooming"] # ───────────────────────────────────────────────────────────────────── # Step 4: 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: 4. Check for changes since the watermark: git log --oneline ..HEAD -- 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 - The full section content (role, trigger, key files, env vars, lifecycle) - Keep the same markdown structure and detail level Example for dev/: ``` # 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 = ["dust-bundling"] # ───────────────────────────────────────────────────────────────────── # Step 5: 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. The orchestrator handles CI/review via pr_walk_to_merge. The gardener stays alive to inject CI results and review feedback as they come in, then executes the pending-actions manifest after merge. 4. If no file changes existed (step 2 found nothing): # Nothing to commit — the gardener has no work to do this run. exit 0 5. If PR creation fails, log the error and exit. """ needs = ["agents-update"]