Phase 1: PM agent (pm-decompose.sh) + issue-template + auditor stub

This commit is contained in:
danny8632
2026-05-12 06:54:51 +00:00
parent ee278f8833
commit f3c38f1017
2 changed files with 168 additions and 0 deletions

123
pm/pm-decompose.sh Executable file
View File

@@ -0,0 +1,123 @@
#!/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