Files
orchestrator/pm/pm-decompose.sh

124 lines
4.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# pm-decompose.sh — given a high-level goal for a Gitea repo, ask claude to
# decompose it into 15 structured agent-issues and post each to Gitea.
#
# Usage: pm-decompose.sh <owner>/<repo> "<goal text>"
# Optional env knobs:
# REPO_STATE freeform string describing what's already in the repo
# (helps claude avoid duplicating work). Default: empty.
# MODEL which model the PM itself uses to think. Default: sonnet.
# GITEA_URL default https://gitea.dannyhaslund.dk
# GITEA_TOKEN required; if unset, reads /etc/agent/gitea-token
set -euo pipefail
REPO="${1:-}"
GOAL="${2:-}"
[[ -z "$REPO" || -z "$GOAL" ]] && { echo "usage: $0 <owner>/<repo> '<goal text>'" >&2; exit 64; }
GITEA_URL="${GITEA_URL:-https://gitea.dannyhaslund.dk}"
if [[ -z "${GITEA_TOKEN:-}" ]]; then
if [[ -r /etc/agent/gitea-token ]]; then
GITEA_TOKEN="$(cat /etc/agent/gitea-token)"
else
echo "GITEA_TOKEN unset and /etc/agent/gitea-token unreadable" >&2; exit 65
fi
fi
MODEL="${MODEL:-sonnet}"
REPO_STATE="${REPO_STATE:-(unknown; assume repo is empty or near-empty)}"
SCHEMA='{
"type": "object",
"required": ["issues"],
"properties": {
"issues": {
"type": "array",
"minItems": 1,
"maxItems": 5,
"items": {
"type": "object",
"required": ["title", "goal", "done_criteria", "model"],
"properties": {
"title": { "type": "string", "maxLength": 80 },
"goal": { "type": "string", "minLength": 20 },
"done_criteria": { "type": "array", "minItems": 1, "items": { "type": "string" } },
"hints": { "type": "string" },
"model": { "type": "string", "enum": ["sonnet", "opus"] }
}
}
}
}
}'
PROMPT="You are the project manager for an autonomous coding empire. Decompose the following goal for repo '$REPO' into 1 to 5 structured agent-issues that, when implemented in order, achieve the goal.
# Current repo state
$REPO_STATE
# Goal for this iteration
$GOAL
# Constraints on the issues you produce
- Each issue must fit in <30 minutes of autonomous agent time on Sonnet.
- Done criteria must be specific and mechanically verifiable (file exists,
endpoint returns X, test passes). No taste calls.
- Order issues by dependency (foundation first).
- Use 'opus' only for tasks that require design tradeoffs; routine
scaffolding/CRUD/refactors are 'sonnet'.
- Prefer fewer larger issues over many trivial ones, as long as each still
fits in 30 min.
- Do NOT call any tools — produce the JSON from the goal text alone.
Return only the JSON matching the schema."
TMP="$(mktemp)"
trap 'rm -f "$TMP"' EXIT
echo "[pm] decomposing goal into agent-issues (model=$MODEL)..." >&2
claude -p "$PROMPT" \
--model "$MODEL" \
--output-format json \
--json-schema "$SCHEMA" \
--allowedTools "" \
< /dev/null > "$TMP" 2>&1
# Parse structured output
issues_json="$(jq -c '.structured_output.issues // empty' "$TMP")"
if [[ -z "$issues_json" || "$issues_json" == "null" ]]; then
echo "[pm] ERROR: claude did not return structured_output.issues" >&2
echo "--- raw claude output ---" >&2
jq -r '.result // .error // .' "$TMP" >&2 | head -40 >&2
exit 1
fi
count="$(jq 'length' <<<"$issues_json")"
echo "[pm] claude proposed $count issue(s); posting to Gitea..." >&2
created_ids=()
for i in $(seq 0 $((count - 1))); do
issue="$(jq -c ".[$i]" <<<"$issues_json")"
title="$(jq -r .title <<<"$issue")"
goal_t="$(jq -r .goal <<<"$issue")"
done_b="$(jq -r '.done_criteria | map("- [ ] " + .) | join("\n")' <<<"$issue")"
hints="$(jq -r '.hints // ""' <<<"$issue")"
model="$(jq -r '.model // "sonnet"' <<<"$issue")"
body="$(printf '## Goal\n\n%s\n\n## Done criteria\n\n%s\n\n## Hints\n\n%s\n\n## Model\n\n%s\n' \
"$goal_t" "$done_b" "${hints:-(none)}" "$model")"
resp="$(curl -fsS -X POST \
-H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \
-d "$(jq -nc --arg t "$title" --arg b "$body" '{title:$t, body:$b}')" \
"$GITEA_URL/api/v1/repos/$REPO/issues")"
num="$(jq -r .number <<<"$resp")"
url="$(jq -r .html_url <<<"$resp")"
echo " #$num $title$url"
created_ids+=("$num")
done
echo "[pm] done. created ${#created_ids[@]} issue(s): ${created_ids[*]}" >&2