Phase 1: PM agent (pm-decompose.sh) + issue-template + auditor stub
This commit is contained in:
45
pm/issue-template.md
Normal file
45
pm/issue-template.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Agent-issue template
|
||||||
|
|
||||||
|
Every issue intended for a dev agent should follow this shape. The `dev-task.sh`
|
||||||
|
wrapper reads `Model:` to pick the model and ignores the rest of the headings;
|
||||||
|
they're for the agent and for any auditor that later reviews the PR.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
<One paragraph. State what the agent must produce, not how.>
|
||||||
|
|
||||||
|
## Done criteria
|
||||||
|
|
||||||
|
- [ ] <Specific, verifiable check — file exists / endpoint returns X / test passes>
|
||||||
|
- [ ] <…>
|
||||||
|
|
||||||
|
## Hints
|
||||||
|
|
||||||
|
<Freeform context. Constraints, things to NOT do, references to existing code.>
|
||||||
|
|
||||||
|
## Model
|
||||||
|
|
||||||
|
sonnet
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rules of thumb
|
||||||
|
|
||||||
|
- **Scope tight.** An issue should fit in 30 min of agent time. If it feels
|
||||||
|
like a full feature, decompose it into 2–3 smaller issues with `depends_on`.
|
||||||
|
- **Done criteria are how the auditor checks the PR.** If a criterion can't
|
||||||
|
be verified mechanically (or by reading the diff), it's the wrong shape.
|
||||||
|
- **Don't tell the agent how**, tell it what the result must look like. Hints
|
||||||
|
are for constraints (e.g. "no new dependencies", "match existing style").
|
||||||
|
- **Pick `opus` only when the task involves design tradeoffs.** Routine
|
||||||
|
scaffolding, CRUD endpoints, and well-specified refactors are sonnet work.
|
||||||
|
- **Avoid taste calls.** "Make the UI look good" is not an agent task; "use
|
||||||
|
Tailwind utility classes for spacing" is.
|
||||||
|
|
||||||
|
## Anti-patterns
|
||||||
|
|
||||||
|
- ❌ "Refactor the whole repo for clarity" (no criterion — no PR will satisfy this)
|
||||||
|
- ❌ "Write a TODO app" (too broad — decompose)
|
||||||
|
- ❌ "Improve performance" (no measurable target)
|
||||||
|
- ✅ "Add GET /todos endpoint returning 2 hardcoded items as JSON"
|
||||||
|
- ✅ "Replace bcrypt with argon2id in auth/password.ts; existing tests must still pass"
|
||||||
123
pm/pm-decompose.sh
Executable file
123
pm/pm-decompose.sh
Executable 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 1–5 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
|
||||||
Reference in New Issue
Block a user