499 lines
24 KiB
TOML
499 lines
24 KiB
TOML
# 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).
|
|
|
|
7. Bug-report detection: for each open unlabeled issue (no backlog, no
|
|
bug-report, no in-progress, no blocked, no underspecified, no vision,
|
|
no tech-debt), check whether it describes a user-facing bug with
|
|
reproduction steps. Criteria — ALL must be true:
|
|
a. Body describes broken behavior (something that should work but
|
|
doesn't), NOT a feature request or enhancement
|
|
b. Body contains steps to reproduce (numbered list, "steps to
|
|
reproduce" heading, or clear sequence of actions that trigger the bug)
|
|
c. Issue is not already labeled
|
|
|
|
If all criteria match, enrich the issue body and write the manifest actions:
|
|
|
|
Body enrichment (CRITICAL — turns raw reports into actionable investigation briefs):
|
|
Before writing the add_label action, construct an enriched body by appending
|
|
these sections to the original issue body:
|
|
|
|
a. ``## What was reported``
|
|
One or two sentence summary of the user's claim. Distill the broken
|
|
behavior concisely — what the user expected vs. what actually happened.
|
|
|
|
b. ``## Known context``
|
|
What can be inferred from the codebase without running anything:
|
|
- Which contracts/components/files are involved (use AGENTS.md layout
|
|
and file paths mentioned in the issue or body)
|
|
- What the expected behavior should be (from VISION.md, docs, code)
|
|
- Any recent changes to involved components:
|
|
git log --oneline -5 -- <paths>
|
|
- Related issues or prior fixes (cross-reference by number if known)
|
|
|
|
c. ``## Reproduction plan``
|
|
Concrete steps for a reproduce-agent or human. Be specific:
|
|
- Which environment to use (e.g. "start fresh stack with
|
|
\`./scripts/dev.sh restart --full\`")
|
|
- Which transactions or actions to execute (with \`cast\` commands,
|
|
API calls, or UI navigation steps where applicable)
|
|
- What state to check after each step (contract reads, API queries,
|
|
UI observations, log output)
|
|
|
|
d. ``## What needs verification``
|
|
Checkboxes distinguishing known facts from unknowns:
|
|
- ``- [ ]`` Does the reported behavior actually occur? (reproduce)
|
|
- ``- [ ]`` Is <component X> behaving as expected? (check state)
|
|
- ``- [ ]`` Is the data flow correct from <A> to <B>? (trace)
|
|
Tailor these to the specific bug — three to five items covering the
|
|
key unknowns a reproduce-agent must resolve.
|
|
|
|
e. Construct full new body = original body text + appended 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"
|
|
|
|
f. Write the add_label action:
|
|
echo '{"action":"add_label","issue":NNN,"label":"bug-report"}' >> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
|
|
echo "ACTION: labeled #NNN as bug-report — <reason>" >> "$RESULT_FILE"
|
|
|
|
Do NOT also add the backlog label — bug-report is a separate triage
|
|
track that feeds into reproduction automation.
|
|
|
|
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 using vault_request():
|
|
source "$(dirname "$0")/../lib/vault.sh"
|
|
TOML_CONTENT="# Vault action: <action_id>
|
|
context = \"<description of what decision/resource is needed>\"
|
|
unblocks = [\"#NNN\"]
|
|
|
|
[execution]
|
|
# Commands to run after approval
|
|
"
|
|
PR_NUM=$(vault_request "<action_id>" "$TOML_CONTENT")
|
|
echo "VAULT: filed PR #${PR_NUM} 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)"
|
|
|
|
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: <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.
|
|
|
|
8. Bug-report lifecycle — auto-close resolved parent issues:
|
|
For each open issue, check whether it is a parent that was decomposed
|
|
into sub-issues. A parent is identified by having OTHER issues whose
|
|
body contains "Decomposed from #N" where N is the parent's number.
|
|
|
|
Algorithm:
|
|
a. From the open issues fetched in step 1, collect all issue numbers.
|
|
b. For each open issue number N, search ALL issues (open AND closed)
|
|
for bodies containing "Decomposed from #N":
|
|
curl -sf -H "Authorization: token $FORGE_TOKEN" \
|
|
"$FORGE_API/issues?state=all&type=issues&limit=50" \
|
|
| jq -r --argjson n N \
|
|
'[.[] | select(.body != null) | select(.body | test("Decomposed from #" + ($n | tostring) + "\\b"))] | length'
|
|
If zero sub-issues found, skip — this is not a decomposed parent.
|
|
|
|
c. If sub-issues exist, check whether ALL of them are closed:
|
|
curl -sf -H "Authorization: token $FORGE_TOKEN" \
|
|
"$FORGE_API/issues?state=all&type=issues&limit=50" \
|
|
| jq -r --argjson n N \
|
|
'[.[] | select(.body != null) | select(.body | test("Decomposed from #" + ($n | tostring) + "\\b"))]
|
|
| {total: length, closed: [.[] | select(.state == "closed")] | length}
|
|
| .total == .closed'
|
|
If the result is "false", some sub-issues are still open — skip.
|
|
|
|
d. If ALL sub-issues are closed, collect sub-issue numbers and titles:
|
|
SUB_ISSUES=$(curl -sf -H "Authorization: token $FORGE_TOKEN" \
|
|
"$FORGE_API/issues?state=all&type=issues&limit=50" \
|
|
| jq -r --argjson n N \
|
|
'[.[] | select(.body != null) | select(.body | test("Decomposed from #" + ($n | tostring) + "\\b"))]
|
|
| .[] | "- #\(.number) \(.title)"')
|
|
|
|
e. Write a comment action listing the resolved sub-issues.
|
|
Use jq to build valid JSON (sub-issue titles may contain quotes/backslashes,
|
|
and SUB_ISSUES is multiline — raw interpolation would break JSONL):
|
|
COMMENT_BODY=$(printf 'All sub-issues have been resolved:\n%s\n\nClosing this parent issue as all decomposed work is complete.' "$SUB_ISSUES")
|
|
jq -n --argjson issue N --arg body "$COMMENT_BODY" \
|
|
'{action:"comment", issue: $issue, body: $body}' \
|
|
>> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
|
|
|
|
f. Write a close action:
|
|
jq -n --argjson issue N \
|
|
'{action:"close", issue: $issue, reason: "all sub-issues resolved"}' \
|
|
>> "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
|
|
|
|
g. Log the action:
|
|
echo "ACTION: closed #N — all sub-issues resolved" >> "$RESULT_FILE"
|
|
|
|
Edge cases:
|
|
- Already closed parent: skipped (only open issues are processed)
|
|
- No sub-issues found: skipped (not a decomposed issue)
|
|
- Multi-cause bugs: stays open until ALL sub-issues are closed
|
|
|
|
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. Bug-report detection — label qualifying issues before other classification
|
|
4. Bug-report lifecycle — close parents whose sub-issues are all resolved
|
|
5. Process tech-debt issues by score (impact/effort)
|
|
6. 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, discover structural changes, update stale files"
|
|
description = """
|
|
Maintain all AGENTS.md files by detecting structural drift since the last
|
|
review. Uses git history as the source of truth — not vibes.
|
|
|
|
## Part A: Discover what changed
|
|
|
|
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> -->
|
|
If no watermark exists, treat the file as fully stale (review everything).
|
|
|
|
4. Check for changes since the watermark:
|
|
git log --oneline <watermark>..HEAD -- <directory>
|
|
If zero changes, the file is current — skip it.
|
|
|
|
5. For each stale file, run a STRUCTURAL DIFF — this is the core of the step:
|
|
|
|
a. FILE INVENTORY: list files at watermark vs HEAD for this directory:
|
|
git ls-tree -r --name-only <watermark> -- <directory>
|
|
git ls-tree -r --name-only HEAD -- <directory>
|
|
Diff the two lists. Categorize:
|
|
- NEW files: in HEAD but not in watermark
|
|
- DELETED files: in watermark but not in HEAD
|
|
- Check AGENTS.md layout section: does it list each current file?
|
|
Files present in the directory but absent from the layout = GAPS.
|
|
Files listed in the layout but missing from the directory = LIES.
|
|
|
|
b. REFERENCE VALIDATION: extract every file path, function name, and
|
|
shell variable referenced in the AGENTS.md. For each:
|
|
- File paths: verify the file exists (ls or git ls-tree HEAD)
|
|
- Function names: grep for the definition in the codebase
|
|
- Script names: verify they exist where claimed
|
|
Any reference that fails validation is a LIE — flag it for correction.
|
|
|
|
c. SEMANTIC CHANGES: for files that existed at both watermark and HEAD,
|
|
check if they changed meaningfully:
|
|
git diff <watermark>..HEAD -- <directory>/*.sh <directory>/*.py <directory>/*.toml
|
|
Look for: new exported functions, removed functions, renamed files,
|
|
changed CLI flags, new environment variables, new configuration.
|
|
Ignore: internal refactors, comment changes, formatting.
|
|
|
|
6. For each stale file, apply corrections:
|
|
- Add NEW files to the layout section
|
|
- Remove DELETED files from the layout section
|
|
- Fix every LIE found in reference validation
|
|
- Add notes about significant SEMANTIC CHANGES
|
|
- Set the watermark to HEAD_SHA
|
|
- Conventions: document architecture and WHY, not implementation details
|
|
|
|
## Part B: Size limit enforcement
|
|
|
|
After all updates, count lines in the root AGENTS.md:
|
|
wc -l < "$PROJECT_REPO_ROOT/AGENTS.md"
|
|
|
|
If it exceeds 200 lines, split verbose sections into per-directory files
|
|
using progressive disclosure:
|
|
|
|
7. Identify sections that can be extracted to per-directory files.
|
|
Keep the root AGENTS.md as a table of contents — brief overview,
|
|
directory layout, summary tables with links to detail files.
|
|
|
|
8. For each extracted section, create a `{dir}/AGENTS.md` with:
|
|
- Line 1: watermark <!-- last-reviewed: <HEAD_SHA> -->
|
|
- The full section content, preserving structure and detail
|
|
|
|
9. Replace extracted sections in root with concise summaries + links.
|
|
|
|
10. Verify root is under 200 lines. If still over, extract more.
|
|
|
|
## Staging
|
|
|
|
11. Stage all AGENTS.md files created or changed:
|
|
find . -name "AGENTS.md" -not -path "./.git/*" -exec git add {} +
|
|
|
|
12. If no files need updating AND root is under 200 lines, skip 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"]
|