2026-03-20 12:11:58 +01:00
# formulas/run-gardener.toml — Gardener housekeeping formula
#
# Defines the gardener's complete run: grooming (Claude session via
2026-04-01 19:36:04 +00:00
# gardener-run.sh) + AGENTS.md maintenance + final commit-and-pr.
2026-03-20 12:11:58 +01:00
#
2026-04-01 19:36:04 +00:00
# Gardener has journaling via .profile (issue #97), so it learns from
# past runs and improves over time.
2026-03-20 12:11:58 +01:00
#
2026-04-01 19:36:04 +00:00
# Steps: preflight -> grooming -> dust-bundling -> agents-update -> commit-and-pr
2026-03-20 12:11:58 +01:00
name = "run-gardener"
2026-04-01 19:36:04 +00:00
description = "Mechanical housekeeping: grooming, dust bundling, docs update"
2026-03-20 12:11:58 +01:00
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
2026-03-22 23:58:50 +00:00
4 . Initialize the pending-actions manifest ( JSONL , converted to JSON at commit time ) :
printf '' > "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
2026-03-20 12:11:58 +01:00
"" "
# ─────────────────────────────────────────────────────────────────────
# 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
fix: gardener migration — run-gardener.toml via direct cron, remove legacy scripts (#490)
Rewrite gardener-run.sh as direct cron runner (matching supervisor/planner/
predictor pattern): lock guard, memory check, worktree, tmux session with
Claude sonnet + formulas/run-gardener.toml, phase monitoring, cleanup.
- Delete gardener-poll.sh and gardener-agent.sh (superseded)
- Extract consume_escalation_reply() to lib/formula-session.sh (shared
by gardener and supervisor, eliminates duplicate blocks)
- Update AGENTS.md, gardener/AGENTS.md, lib/AGENTS.md, CI smoke test,
and cross-references
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 13:09:17 +00:00
( Claude performs pre-checks inline before deeper analysis ) .
2026-03-20 12:11:58 +01:00
Pre-checks ( bash , zero tokens — detect problems before invoking Claude ) :
1 . Fetch all open issues :
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
curl -sf -H "Authorization: token $FORGE_TOKEN" \
"$FORGE_API/issues?state=open&type=issues&limit=50&sort=updated&direction=desc"
2026-03-20 12:11:58 +01:00
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 ) .
2026-04-05 20:10:18 +00:00
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
2026-04-06 10:35:01 +00:00
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"
2026-04-05 20:10:18 +00:00
Do NOT also add the backlog label — bug-report is a separate triage
track that feeds into reproduction automation .
2026-03-20 12:11:58 +01:00
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"
2026-03-23 12:32:36 +00:00
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 .
2026-03-20 12:11:58 +01:00
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" ) .
2026-03-21 10:41:31 +00:00
Do NOT close dust issues — the dust-bundling step auto-bundles groups
of 3 + into one backlog issue .
2026-03-20 12:11:58 +01:00
2026-03-26 09:09:58 +00:00
VAULT ( needs human decision or external resource ) :
2026-04-01 19:36:04 +00:00
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"
2026-03-20 12:11:58 +01:00
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)"
2026-04-01 19:36:04 +00:00
6 . Quality gate — backlog label enforcement :
2026-03-21 17:45:36 +00:00
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 :
2026-03-22 23:58:50 +00:00
a . Write a comment action to the manifest :
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
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"
2026-03-21 17:45:36 +00:00
Where < missing items > is a comma-separated list of what ' s absent
( e . g . "acceptance criteria, affected files" or just "affected files" ) .
2026-03-22 23:58:50 +00:00
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 :
2026-03-21 17:45:36 +00:00
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 .
2026-04-08 06:46:00 +00:00
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)" ' )
2026-04-08 07:02:05 +00:00
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"
2026-04-08 06:46:00 +00:00
f . Write a close action :
2026-04-08 07:02:05 +00:00
jq -n --argjson issue N \
'{action:"close", issue: $issue, reason: "all sub-issues resolved"}' \
> > "$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl"
2026-04-08 06:46:00 +00:00
g . Log the action :
2026-04-08 07:02:05 +00:00
echo "ACTION: closed #N — all sub-issues resolved" > > "$RESULT_FILE"
2026-04-08 06:46:00 +00:00
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
2026-03-20 12:11:58 +01:00
Processing order :
1 . Handle PRIORITY_blockers_starving_factory first — promote or resolve
2026-04-01 19:36:04 +00:00
2 . Quality gate — strip backlog from issues missing acceptance criteria or affected files
2026-04-05 20:10:18 +00:00
3 . Bug-report detection — label qualifying issues before other classification
2026-04-08 06:46:00 +00:00
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
2026-03-20 12:11:58 +01:00
2026-03-21 10:41:31 +00:00
Do NOT bundle dust yourself — the dust-bundling step handles accumulation ,
dedup , TTL expiry , and bundling into backlog issues .
2026-03-20 12:11:58 +01:00
CRITICAL : If this step fails for any reason , log the failure and move on .
"" "
needs = [ "preflight" ]
# ─────────────────────────────────────────────────────────────────────
2026-03-21 10:41:31 +00:00
# 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
2026-03-22 23:58:50 +00:00
- 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"
2026-03-21 10:41:31 +00:00
- 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 .
2026-03-20 12:11:58 +01:00
CRITICAL : If this step fails , log the failure and move on .
"" "
2026-04-01 19:36:04 +00:00
needs = [ "grooming" ]
2026-03-24 20:48:55 +00:00
# ─────────────────────────────────────────────────────────────────────
2026-04-01 19:36:04 +00:00
# Step 4: agents-update — AGENTS.md watermark staleness + size enforcement
2026-03-20 12:11:58 +01:00
# ─────────────────────────────────────────────────────────────────────
[ [ steps ] ]
id = "agents-update"
2026-03-21 12:25:55 +00:00
title = "Check AGENTS.md watermarks, update stale files, enforce size limit"
2026-03-20 12:11:58 +01:00
description = "" "
2026-03-21 12:25:55 +00:00
Check all AGENTS . md files for staleness , update any that are outdated , and
enforce the ~ 200 -line size limit via progressive disclosure splitting .
2026-03-20 12:11:58 +01:00
This keeps documentation fresh — runs 2 x / day so drift stays small .
2026-03-21 12:25:55 +00:00
## Part A: Watermark staleness check and update
2026-03-20 12:11:58 +01:00
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
2026-03-21 12:25:55 +00:00
- 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 .
2026-03-20 12:11:58 +01:00
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 .
"" "
2026-04-01 19:36:04 +00:00
needs = [ "dust-bundling" ]
2026-03-20 12:11:58 +01:00
# ─────────────────────────────────────────────────────────────────────
2026-04-01 19:36:04 +00:00
# Step 5: commit-and-pr — single commit with all file changes
2026-03-20 12:11:58 +01:00
# ─────────────────────────────────────────────────────────────────────
[ [ steps ] ]
id = "commit-and-pr"
2026-03-22 20:48:07 +00:00
title = "One commit with all file changes, push, create PR, monitor to merge"
2026-03-20 12:11:58 +01:00
description = "" "
2026-03-22 23:58:50 +00:00
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 .
2026-03-20 12:11:58 +01:00
2026-03-22 23:58:50 +00:00
1 . Convert the JSONL manifest to a JSON array :
2026-03-20 12:11:58 +01:00
cd "$PROJECT_REPO_ROOT"
2026-03-22 23:58:50 +00:00
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 :
2026-03-20 12:11:58 +01:00
git status --porcelain
2026-03-22 23:58:50 +00:00
If there are no file changes ( no AGENTS . md updates AND manifest is empty [ ] ) ,
skip to step 4 — no commit , no PR needed .
2026-03-20 12:11:58 +01:00
2026-03-22 23:58:50 +00:00
3 . If there are changes :
2026-03-20 12:11:58 +01:00
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 { } +
2026-03-22 23:58:50 +00:00
c . Stage the pending-actions manifest :
git add gardener / pending-actions . json
d . Also stage any other files the gardener modified ( if any ) :
2026-03-20 12:11:58 +01:00
git add -u
2026-03-22 23:58:50 +00:00
e . Commit :
2026-03-20 12:11:58 +01:00
git commit -m "chore: gardener housekeeping $(date -u +%Y-%m-%d)"
2026-03-22 23:58:50 +00:00
f . Push :
2026-03-20 12:11:58 +01:00
git push -u origin "$BRANCH"
2026-03-22 23:58:50 +00:00
g . Create a PR :
2026-03-22 20:48:07 +00:00
PR_RESPONSE = $ ( curl -sf -X POST \
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
-H "Authorization: token $FORGE_TOKEN" \
2026-03-20 12:11:58 +01:00
-H "Content-Type: application/json" \
fix: Replace Codeberg dependency with local Forgejo instance (#611)
- Add setup_forge() to bin/disinto: provisions Forgejo via Docker,
creates admin + bot users (dev-bot, review-bot), generates API
tokens, creates repo, and pushes code — all automated
- Rename env vars: CODEBERG_TOKEN→FORGE_TOKEN, REVIEW_BOT_TOKEN→
FORGE_REVIEW_TOKEN, CODEBERG_REPO→FORGE_REPO, CODEBERG_API→
FORGE_API, CODEBERG_WEB→FORGE_WEB, CODEBERG_BOT_USERNAMES→
FORGE_BOT_USERNAMES (with backwards-compat fallbacks)
- Rename API helpers: codeberg_api()→forge_api(), codeberg_api_all()
→forge_api_all() (with compat aliases)
- Add forge_url field to project TOML; load-project.sh derives
FORGE_API/FORGE_WEB from forge_url + repo
- Update parse_repo_slug() to accept any host URL, not just codeberg
- Forgejo data stored under ~/.disinto/forgejo/ (not in factory repo)
- Update all 58 files: agent scripts, formulas, docs, site HTML
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:57:12 +00:00
"$FORGE_API/pulls" \
2026-03-20 12:11:58 +01:00
-d ' { "title" : "chore: gardener housekeeping" ,
2026-03-22 20:48:07 +00:00
"head" : "'" $ BRANCH "'" , "base" : "'" $ PRIMARY_BRANCH "'" ,
2026-03-22 23:58:50 +00:00
"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." } ' )
2026-03-22 20:48:07 +00:00
PR_NUMBER = $ ( echo "$PR_RESPONSE" | jq -r '.number' )
2026-03-22 23:58:50 +00:00
h . Save PR number for orchestrator tracking :
2026-03-22 20:48:07 +00:00
echo "$PR_NUMBER" > / tmp / gardener-pr- $ { PROJECT_NAME } . txt
2026-04-01 19:36:04 +00:00
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 .
2026-03-22 20:48:07 +00:00
2026-03-22 23:58:50 +00:00
4 . If no file changes existed ( step 2 found nothing ) :
2026-04-01 19:36:04 +00:00
# Nothing to commit — the gardener has no work to do this run.
exit 0
2026-03-22 20:48:07 +00:00
2026-04-01 19:36:04 +00:00
5 . If PR creation fails , log the error and exit .
2026-03-20 12:11:58 +01:00
"" "
needs = [ "agents-update" ]