Merge pull request 'fix: feat: gardener defers all repo actions to a manifest — review gate covers grooming decisions, not just docs (#572)' (#575) from fix/issue-572 into main
This commit is contained in:
commit
a2438cb580
3 changed files with 237 additions and 86 deletions
|
|
@ -37,6 +37,9 @@ Set up the working environment for this gardener run.
|
|||
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"
|
||||
"""
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -108,16 +111,10 @@ Sibling dependency rule (CRITICAL):
|
|||
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. Post a comment explaining the violation:
|
||||
curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/issues/<number>/comments" \
|
||||
-d '{"body":"Closing: violates AD-NNN (<decision summary>). See AGENTS.md § Architecture Decisions."}'
|
||||
b. Close the issue:
|
||||
curl -sf -X PATCH -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/issues/<number>" \
|
||||
-d '{"state":"closed"}'
|
||||
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"
|
||||
|
||||
|
|
@ -134,20 +131,13 @@ Sibling dependency rule (CRITICAL):
|
|||
"## Affected files" section with at least one file path
|
||||
|
||||
If either section is missing:
|
||||
a. Look up the 'backlog' label ID:
|
||||
BACKLOG_LABEL_ID=$(curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/labels" | jq -r '.[] | select(.name == "backlog") | .id')
|
||||
b. Post a comment listing what's missing:
|
||||
curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/issues/<number>/comments" \
|
||||
-d '{"body":"This issue is missing required sections. Please use the issue templates at `.codeberg/ISSUE_TEMPLATE/` — needs: <missing items>."}'
|
||||
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 `.codeberg/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").
|
||||
c. Remove the 'backlog' label:
|
||||
curl -sf -X DELETE -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/issues/<number>/labels/$BACKLOG_LABEL_ID"
|
||||
d. Log to the result file:
|
||||
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 —
|
||||
|
|
@ -203,16 +193,11 @@ session, so changes there would be lost.
|
|||
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
|
||||
- Look up the backlog label ID:
|
||||
BACKLOG_LABEL_ID=$(curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/labels" | jq -r '.[] | select(.name == "backlog") | .id')
|
||||
- Create a bundled backlog issue:
|
||||
curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" "$CODEBERG_API/issues" \
|
||||
-d '{"title":"fix: bundled dust cleanup — GROUP","body":"...","labels":[LABEL_ID]}'
|
||||
- Close each source issue with a cross-reference comment:
|
||||
curl ... "$CODEBERG_API/issues/NNN/comments" -d '{"body":"Bundled into #NEW"}'
|
||||
curl ... "$CODEBERG_API/issues/NNN" -d '{"state":"closed"}'
|
||||
- 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"
|
||||
|
||||
|
|
@ -233,57 +218,42 @@ description = """
|
|||
Review all issues labeled 'blocked' and decide their fate.
|
||||
(See issue #352 for the blocked label convention.)
|
||||
|
||||
1. Look up the 'blocked' label ID (Gitea needs integer IDs for label removal):
|
||||
BLOCKED_LABEL_ID=$(curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/labels" | jq -r '.[] | select(.name == "blocked") | .id')
|
||||
If the lookup fails, skip label removal and just post comments.
|
||||
|
||||
2. Fetch all blocked issues:
|
||||
1. Fetch all blocked issues:
|
||||
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/issues?state=open&type=issues&labels=blocked&limit=50"
|
||||
|
||||
3. For each blocked issue, read the full body and comments:
|
||||
2. For each blocked issue, read the full body and comments:
|
||||
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/issues/<number>"
|
||||
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/issues/<number>/comments"
|
||||
|
||||
4. Check dependencies — extract issue numbers from ## Dependencies /
|
||||
3. Check dependencies — extract issue numbers from ## Dependencies /
|
||||
## Depends on / ## Blocked by sections. For each dependency:
|
||||
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/issues/<dep_number>"
|
||||
Check if the dependency is now closed.
|
||||
|
||||
5. For each blocked issue, choose ONE action:
|
||||
4. For each blocked issue, choose ONE action:
|
||||
|
||||
UNBLOCK — all dependencies are now closed or the blocking condition resolved:
|
||||
a. Remove the 'blocked' label (using ID from step 1):
|
||||
curl -sf -X DELETE -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/issues/<number>/labels/$BLOCKED_LABEL_ID"
|
||||
b. Add context comment explaining what changed:
|
||||
curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/issues/<number>/comments" \
|
||||
-d '{"body":"Unblocked: <explanation of what resolved the blocker>"}'
|
||||
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. Post a diagnostic comment explaining what you found and what
|
||||
decision is needed
|
||||
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. Post a comment explaining why:
|
||||
curl -sf -X POST -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/issues/<number>/comments" \
|
||||
-d '{"body":"Closing: <reason — stale blocker, no longer relevant, etc.>"}'
|
||||
b. Close the issue:
|
||||
curl -sf -X PATCH -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/issues/<number>" \
|
||||
-d '{"state":"closed"}'
|
||||
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.
|
||||
"""
|
||||
|
|
@ -421,49 +391,63 @@ needs = ["blocked-review"]
|
|||
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) into a single commit.
|
||||
API calls (issue creation, PR comments, closures) already happened during the
|
||||
run — only file changes need the PR.
|
||||
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. Check for staged or unstaged changes:
|
||||
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, skip to step 3 — no commit, no PR needed.
|
||||
If there are no file changes (no AGENTS.md updates AND manifest is empty []),
|
||||
skip to step 4 — no commit, no PR needed.
|
||||
|
||||
2. If there are changes:
|
||||
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. Also stage any other files the gardener modified (if any):
|
||||
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
|
||||
d. Commit:
|
||||
e. Commit:
|
||||
git commit -m "chore: gardener housekeeping $(date -u +%Y-%m-%d)"
|
||||
e. Push:
|
||||
f. Push:
|
||||
git push -u origin "$BRANCH"
|
||||
f. Create a PR:
|
||||
g. Create a PR:
|
||||
PR_RESPONSE=$(curl -sf -X POST \
|
||||
-H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/pulls" \
|
||||
-d '{"title":"chore: gardener housekeeping",
|
||||
"head":"'"$BRANCH"'","base":"'"$PRIMARY_BRANCH"'",
|
||||
"body":"Automated gardener housekeeping — AGENTS.md updates.\\n\\nReview-agent fast-tracks doc-only PRs."}')
|
||||
"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')
|
||||
g. Save PR number for orchestrator tracking:
|
||||
h. Save PR number for orchestrator tracking:
|
||||
echo "$PR_NUMBER" > /tmp/gardener-pr-${PROJECT_NAME}.txt
|
||||
h. Signal the orchestrator to monitor CI:
|
||||
i. Signal the orchestrator to monitor CI:
|
||||
echo "PHASE:awaiting_ci" > "$PHASE_FILE"
|
||||
i. STOP and WAIT. Do NOT return to the primary branch.
|
||||
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.
|
||||
|
||||
3. If no file changes existed (step 1 found nothing):
|
||||
4. If no file changes existed (step 2 found nothing):
|
||||
echo "PHASE:done" > "$PHASE_FILE"
|
||||
|
||||
4. If PR creation fails, log the error and write PHASE:failed.
|
||||
5. If PR creation fails, log the error and write PHASE:failed.
|
||||
"""
|
||||
needs = ["agents-update"]
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ runs directly from cron like the planner, predictor, and supervisor.
|
|||
- `gardener/gardener-run.sh` — Cron wrapper + orchestrator: lock, memory guard,
|
||||
consumes escalation replies, sources disinto project config, creates tmux session,
|
||||
injects formula prompt, monitors phase file, handles crash recovery via
|
||||
`run_formula_and_monitor`
|
||||
`run_formula_and_monitor`, executes pending-actions manifest after PR merge
|
||||
- `formulas/run-gardener.toml` — Execution spec: preflight, grooming, dust-bundling, blocked-review, agents-update, commit-and-pr
|
||||
- `gardener/pending-actions.json` — Manifest of deferred repo actions (label changes,
|
||||
closures, comments, issue creation). Written during grooming steps, committed to the
|
||||
PR, reviewed alongside AGENTS.md changes, executed by gardener-run.sh after merge.
|
||||
|
||||
**Environment variables consumed**:
|
||||
- `CODEBERG_TOKEN`, `CODEBERG_REPO`, `CODEBERG_API`, `PROJECT_NAME`, `PROJECT_REPO_ROOT`
|
||||
|
|
@ -27,5 +30,7 @@ runs directly from cron like the planner, predictor, and supervisor.
|
|||
|
||||
**Lifecycle**: gardener-run.sh (cron 0,6,12,18) → lock + memory guard →
|
||||
consume escalation replies → load formula + context → create tmux session →
|
||||
Claude grooms backlog, bundles dust, reviews blocked issues, updates AGENTS.md,
|
||||
commits and creates PR → `PHASE:done`.
|
||||
Claude grooms backlog (writes proposed actions to manifest), bundles dust,
|
||||
reviews blocked issues, updates AGENTS.md, commits manifest + docs to PR →
|
||||
review-agent reviews all proposed actions → after merge, gardener-run.sh
|
||||
executes manifest actions via API → `PHASE:done`.
|
||||
|
|
|
|||
|
|
@ -66,13 +66,24 @@ build_context_block AGENTS.md
|
|||
SCRATCH_CONTEXT=$(read_scratch_context "$SCRATCH_FILE")
|
||||
SCRATCH_INSTRUCTION=$(build_scratch_instruction "$SCRATCH_FILE")
|
||||
|
||||
# ── Build prompt (gardener needs extra API endpoints for issue management) ─
|
||||
# ── Build prompt (manifest format reference for deferred actions) ─────────
|
||||
GARDENER_API_EXTRA="
|
||||
Relabel: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PUT -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/labels' -d '{\"labels\":[LABEL_ID]}'
|
||||
Comment: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X POST -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}/comments' -d '{\"body\":\"...\"}'
|
||||
Close: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}' -d '{\"state\":\"closed\"}'
|
||||
Edit body: curl -sf -H \"Authorization: token \$CODEBERG_TOKEN\" -X PATCH -H 'Content-Type: application/json' '${CODEBERG_API}/issues/{number}' -d '{\"body\":\"new body\"}'
|
||||
"
|
||||
|
||||
## Pending-actions manifest (REQUIRED)
|
||||
All repo mutations (comments, closures, label changes, issue creation) MUST be
|
||||
written to the JSONL manifest instead of calling APIs directly. Append one JSON
|
||||
object per line to: \$PROJECT_REPO_ROOT/gardener/pending-actions.jsonl
|
||||
|
||||
Supported actions:
|
||||
{\"action\":\"add_label\", \"issue\":NNN, \"label\":\"priority\"}
|
||||
{\"action\":\"remove_label\", \"issue\":NNN, \"label\":\"backlog\"}
|
||||
{\"action\":\"close\", \"issue\":NNN, \"reason\":\"already implemented\"}
|
||||
{\"action\":\"comment\", \"issue\":NNN, \"body\":\"Relates to issue 1031\"}
|
||||
{\"action\":\"create_issue\", \"title\":\"...\", \"body\":\"...\", \"labels\":[\"backlog\"]}
|
||||
{\"action\":\"edit_body\", \"issue\":NNN, \"body\":\"new body\"}
|
||||
|
||||
The commit-and-pr step converts JSONL to JSON array. The orchestrator executes
|
||||
actions after the PR merges. Do NOT call mutation APIs directly during the run."
|
||||
build_prompt_footer "$GARDENER_API_EXTRA"
|
||||
|
||||
# Extend phase protocol with merge-through instructions for compaction survival
|
||||
|
|
@ -121,6 +132,154 @@ ${PROMPT_FOOTER}"
|
|||
# Handles CI polling, review injection, merge, and cleanup after PR creation.
|
||||
# Lighter than dev/phase-handler.sh — tailored for gardener doc-only PRs.
|
||||
|
||||
# ── Post-merge manifest execution ─────────────────────────────────────
|
||||
# Reads gardener/pending-actions.json and executes each action via API.
|
||||
# Failed actions are logged but do not block completion.
|
||||
# shellcheck disable=SC2317 # called indirectly via _gardener_merge
|
||||
_gardener_execute_manifest() {
|
||||
local manifest_file="$PROJECT_REPO_ROOT/gardener/pending-actions.json"
|
||||
if [ ! -f "$manifest_file" ]; then
|
||||
log "manifest: no pending-actions.json — skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local count
|
||||
count=$(jq 'length' "$manifest_file" 2>/dev/null || echo 0)
|
||||
if [ "$count" -eq 0 ]; then
|
||||
log "manifest: empty — skipping"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log "manifest: executing ${count} actions"
|
||||
|
||||
local i=0
|
||||
while [ "$i" -lt "$count" ]; do
|
||||
local action issue
|
||||
action=$(jq -r ".[$i].action" "$manifest_file")
|
||||
issue=$(jq -r ".[$i].issue // empty" "$manifest_file")
|
||||
|
||||
case "$action" in
|
||||
add_label)
|
||||
local label label_id
|
||||
label=$(jq -r ".[$i].label" "$manifest_file")
|
||||
label_id=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${CODEBERG_API}/labels" | jq -r --arg n "$label" \
|
||||
'.[] | select(.name == $n) | .id') || true
|
||||
if [ -n "$label_id" ]; then
|
||||
if curl -sf -X POST -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
"${CODEBERG_API}/issues/${issue}/labels" \
|
||||
-d "{\"labels\":[${label_id}]}" >/dev/null 2>&1; then
|
||||
log "manifest: add_label '${label}' to #${issue}"
|
||||
else
|
||||
log "manifest: FAILED add_label '${label}' to #${issue}"
|
||||
fi
|
||||
else
|
||||
log "manifest: FAILED add_label — label '${label}' not found"
|
||||
fi
|
||||
;;
|
||||
|
||||
remove_label)
|
||||
local label label_id
|
||||
label=$(jq -r ".[$i].label" "$manifest_file")
|
||||
label_id=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${CODEBERG_API}/labels" | jq -r --arg n "$label" \
|
||||
'.[] | select(.name == $n) | .id') || true
|
||||
if [ -n "$label_id" ]; then
|
||||
if curl -sf -X DELETE -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${CODEBERG_API}/issues/${issue}/labels/${label_id}" >/dev/null 2>&1; then
|
||||
log "manifest: remove_label '${label}' from #${issue}"
|
||||
else
|
||||
log "manifest: FAILED remove_label '${label}' from #${issue}"
|
||||
fi
|
||||
else
|
||||
log "manifest: FAILED remove_label — label '${label}' not found"
|
||||
fi
|
||||
;;
|
||||
|
||||
close)
|
||||
local reason
|
||||
reason=$(jq -r ".[$i].reason // empty" "$manifest_file")
|
||||
if curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
"${CODEBERG_API}/issues/${issue}" \
|
||||
-d '{"state":"closed"}' >/dev/null 2>&1; then
|
||||
log "manifest: closed #${issue} (${reason})"
|
||||
else
|
||||
log "manifest: FAILED close #${issue}"
|
||||
fi
|
||||
;;
|
||||
|
||||
comment)
|
||||
local body escaped_body
|
||||
body=$(jq -r ".[$i].body" "$manifest_file")
|
||||
escaped_body=$(printf '%s' "$body" | jq -Rs '.')
|
||||
if curl -sf -X POST -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
"${CODEBERG_API}/issues/${issue}/comments" \
|
||||
-d "{\"body\":${escaped_body}}" >/dev/null 2>&1; then
|
||||
log "manifest: commented on #${issue}"
|
||||
else
|
||||
log "manifest: FAILED comment on #${issue}"
|
||||
fi
|
||||
;;
|
||||
|
||||
create_issue)
|
||||
local title body labels escaped_title escaped_body label_ids
|
||||
title=$(jq -r ".[$i].title" "$manifest_file")
|
||||
body=$(jq -r ".[$i].body" "$manifest_file")
|
||||
labels=$(jq -r ".[$i].labels // [] | .[]" "$manifest_file")
|
||||
escaped_title=$(printf '%s' "$title" | jq -Rs '.')
|
||||
escaped_body=$(printf '%s' "$body" | jq -Rs '.')
|
||||
# Resolve label names to IDs
|
||||
label_ids="[]"
|
||||
if [ -n "$labels" ]; then
|
||||
local all_labels ids_json=""
|
||||
all_labels=$(curl -sf -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
"${CODEBERG_API}/labels") || true
|
||||
while IFS= read -r lname; do
|
||||
local lid
|
||||
lid=$(echo "$all_labels" | jq -r --arg n "$lname" \
|
||||
'.[] | select(.name == $n) | .id') || true
|
||||
[ -n "$lid" ] && ids_json="${ids_json:+${ids_json},}${lid}"
|
||||
done <<< "$labels"
|
||||
[ -n "$ids_json" ] && label_ids="[${ids_json}]"
|
||||
fi
|
||||
if curl -sf -X POST -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
"${CODEBERG_API}/issues" \
|
||||
-d "{\"title\":${escaped_title},\"body\":${escaped_body},\"labels\":${label_ids}}" >/dev/null 2>&1; then
|
||||
log "manifest: created issue '${title}'"
|
||||
else
|
||||
log "manifest: FAILED create_issue '${title}'"
|
||||
fi
|
||||
;;
|
||||
|
||||
edit_body)
|
||||
local body escaped_body
|
||||
body=$(jq -r ".[$i].body" "$manifest_file")
|
||||
escaped_body=$(printf '%s' "$body" | jq -Rs '.')
|
||||
if curl -sf -X PATCH -H "Authorization: token ${CODEBERG_TOKEN}" \
|
||||
-H 'Content-Type: application/json' \
|
||||
"${CODEBERG_API}/issues/${issue}" \
|
||||
-d "{\"body\":${escaped_body}}" >/dev/null 2>&1; then
|
||||
log "manifest: edited body of #${issue}"
|
||||
else
|
||||
log "manifest: FAILED edit_body #${issue}"
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
log "manifest: unknown action '${action}' — skipping"
|
||||
;;
|
||||
esac
|
||||
|
||||
i=$((i + 1))
|
||||
done
|
||||
|
||||
log "manifest: execution complete (${count} actions processed)"
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2317 # called indirectly by monitor_phase_loop
|
||||
_gardener_merge() {
|
||||
local merge_response merge_http_code
|
||||
|
|
@ -133,6 +292,7 @@ _gardener_merge() {
|
|||
|
||||
if [ "$merge_http_code" = "200" ] || [ "$merge_http_code" = "204" ]; then
|
||||
log "gardener PR #${_GARDENER_PR} merged"
|
||||
_gardener_execute_manifest
|
||||
printf 'PHASE:done\n' > "$PHASE_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -144,6 +304,7 @@ _gardener_merge() {
|
|||
"${CODEBERG_API}/pulls/${_GARDENER_PR}" | jq -r '.merged // false') || true
|
||||
if [ "$pr_merged" = "true" ]; then
|
||||
log "gardener PR #${_GARDENER_PR} already merged"
|
||||
_gardener_execute_manifest
|
||||
printf 'PHASE:done\n' > "$PHASE_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -422,6 +583,7 @@ Then stop and wait."
|
|||
|
||||
if [ "$pr_merged" = "true" ]; then
|
||||
log "gardener PR #${_GARDENER_PR} merged externally"
|
||||
_gardener_execute_manifest
|
||||
printf 'PHASE:done\n' > "$PHASE_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue