fix: address review — update architect/AGENTS.md, fix pagination and section targeting in sprint-filer.sh
- architect/AGENTS.md: update responsibilities, state transitions, vision lifecycle, and execution sections to reflect read-only role and filer-bot architecture (#764) - lib/sprint-filer.sh: add filer_api_all() paginated fetch helper; fix subissue_exists() and check_and_close_completed_visions() to paginate instead of using fixed limits that miss issues on large trackers - lib/sprint-filer.sh: fix extract_vision_issue() to look specifically in the "## Vision issues" section before falling back to first #N in file Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2c9b8e386f
commit
0be36dd502
2 changed files with 83 additions and 23 deletions
|
|
@ -10,9 +10,9 @@ converses with humans through PR comments.
|
||||||
## Role
|
## Role
|
||||||
|
|
||||||
- **Input**: Vision issues from VISION.md, prerequisite tree from ops repo
|
- **Input**: Vision issues from VISION.md, prerequisite tree from ops repo
|
||||||
- **Output**: Sprint proposals as PRs on the ops repo, sub-issue files
|
- **Output**: Sprint proposals as PRs on the ops repo (with embedded `## Sub-issues` blocks)
|
||||||
- **Mechanism**: Bash-driven orchestration in `architect-run.sh`, pitching formula via `formulas/run-architect.toml`
|
- **Mechanism**: Bash-driven orchestration in `architect-run.sh`, pitching formula via `formulas/run-architect.toml`
|
||||||
- **Identity**: `architect-bot` on Forgejo
|
- **Identity**: `architect-bot` on Forgejo (READ-ONLY on project repo, write on ops repo only — #764)
|
||||||
|
|
||||||
## Responsibilities
|
## Responsibilities
|
||||||
|
|
||||||
|
|
@ -24,16 +24,17 @@ converses with humans through PR comments.
|
||||||
acceptance criteria and dependencies
|
acceptance criteria and dependencies
|
||||||
4. **Human conversation**: Respond to PR comments, refine sprint proposals based
|
4. **Human conversation**: Respond to PR comments, refine sprint proposals based
|
||||||
on human feedback
|
on human feedback
|
||||||
5. **Sub-issue filing**: After design forks are resolved, file concrete sub-issues
|
5. **Sub-issue definition**: Define concrete sub-issues in the `## Sub-issues`
|
||||||
for implementation
|
block of the sprint spec. Filing is handled by `filer-bot` after sprint PR
|
||||||
|
merge (#764)
|
||||||
|
|
||||||
## Formula
|
## Formula
|
||||||
|
|
||||||
The architect pitching is driven by `formulas/run-architect.toml`. This formula defines
|
The architect pitching is driven by `formulas/run-architect.toml`. This formula defines
|
||||||
the steps for:
|
the steps for:
|
||||||
- Research: analyzing vision items and prerequisite tree
|
- Research: analyzing vision items and prerequisite tree
|
||||||
- Pitch: creating structured sprint PRs
|
- Pitch: creating structured sprint PRs with embedded `## Sub-issues` blocks
|
||||||
- Sub-issue filing: creating concrete implementation issues
|
- Design Q&A: refining the sprint via PR comments after human ACCEPT
|
||||||
|
|
||||||
## Bash-driven orchestration
|
## Bash-driven orchestration
|
||||||
|
|
||||||
|
|
@ -57,22 +58,31 @@ APPROVED review → start design questions (model posts Q1:, adds Design forks s
|
||||||
↓
|
↓
|
||||||
Answers received → continue Q&A (model processes answers, posts follow-ups)
|
Answers received → continue Q&A (model processes answers, posts follow-ups)
|
||||||
↓
|
↓
|
||||||
All forks resolved → sub-issue filing (model files implementation issues)
|
All forks resolved → finalize ## Sub-issues section in sprint spec
|
||||||
|
↓
|
||||||
|
Sprint PR merged → filer-bot files sub-issues on project repo (#764)
|
||||||
↓
|
↓
|
||||||
REJECT review → close PR + journal (model processes rejection, bash merges PR)
|
REJECT review → close PR + journal (model processes rejection, bash merges PR)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Vision issue lifecycle
|
### Vision issue lifecycle
|
||||||
|
|
||||||
Vision issues decompose into sprint sub-issues tracked via "Decomposed from #N" in sub-issue bodies. The architect automatically closes vision issues when all sub-issues are closed:
|
Vision issues decompose into sprint sub-issues. Sub-issues are defined in the
|
||||||
|
`## Sub-issues` block of the sprint spec (between `<!-- filer:begin -->` and
|
||||||
|
`<!-- filer:end -->` markers) and filed by `filer-bot` after the sprint PR merges
|
||||||
|
on the ops repo (#764).
|
||||||
|
|
||||||
1. Before picking new vision issues, the architect checks each open vision issue
|
Each filer-created sub-issue carries a `<!-- decomposed-from: #<vision>, sprint: <slug>, id: <id> -->`
|
||||||
2. For each, it queries merged sprint PRs — **only PRs whose title or body reference the specific vision issue** (matched via `#N` pattern, filtering out unrelated PRs that happen to close unrelated issues) (#735/#736)
|
marker in its body for idempotency and traceability.
|
||||||
3. Extracts sub-issue numbers from those PRs, excluding the vision issue itself
|
|
||||||
4. If all sub-issues are closed, posts a summary comment listing completed sub-issues (with an idempotency guard: checks both comment presence AND `.state == "closed"` — if the comment exists but the issue is still open, retries the close rather than returning early) (#737)
|
|
||||||
5. The vision issue is then closed automatically
|
|
||||||
|
|
||||||
This ensures vision issues transition from `open` → `closed` once their work is complete, without manual intervention. The #N-scoped matching prevents false positives where unrelated sub-issues would incorrectly trigger vision issue closure.
|
The filer-bot (via `lib/sprint-filer.sh`) handles vision lifecycle:
|
||||||
|
1. After filing sub-issues, adds `in-progress` label to the vision issue
|
||||||
|
2. On each run, checks if all sub-issues for a vision are closed
|
||||||
|
3. If all closed, posts a summary comment and closes the vision issue
|
||||||
|
|
||||||
|
The architect no longer writes to the project repo — it is read-only (#764).
|
||||||
|
All project-repo writes (issue filing, label management, vision closure) are
|
||||||
|
handled by filer-bot with its narrowly-scoped `FORGE_FILER_TOKEN`.
|
||||||
|
|
||||||
### Session management
|
### Session management
|
||||||
|
|
||||||
|
|
@ -95,7 +105,9 @@ Run via `architect/architect-run.sh`, which:
|
||||||
- Selects up to `pitch_budget` (3 - open architect PRs) remaining vision issues
|
- Selects up to `pitch_budget` (3 - open architect PRs) remaining vision issues
|
||||||
- For each selected issue, invokes stateless `claude -p` with issue body + context
|
- For each selected issue, invokes stateless `claude -p` with issue body + context
|
||||||
- Creates PRs directly from pitch content (no scratch files)
|
- Creates PRs directly from pitch content (no scratch files)
|
||||||
- Agent is invoked only for response processing (ACCEPT/REJECT handling)
|
- Agent is invoked for stateless pitch generation and response processing (ACCEPT/REJECT handling)
|
||||||
|
- NOTE: architect-bot is read-only on the project repo (#764) — sub-issue filing
|
||||||
|
and in-progress label management are handled by filer-bot after sprint PR merge
|
||||||
|
|
||||||
**Multi-sprint pitching**: The architect pitches up to 3 sprints per run. Bash handles all state management:
|
**Multi-sprint pitching**: The architect pitches up to 3 sprints per run. Bash handles all state management:
|
||||||
- Fetches Forgejo API data (vision issues, open PRs, merged PRs)
|
- Fetches Forgejo API data (vision issues, open PRs, merged PRs)
|
||||||
|
|
@ -120,4 +132,5 @@ empty file not created, just document it).
|
||||||
- #100: Architect formula — research + design fork identification
|
- #100: Architect formula — research + design fork identification
|
||||||
- #101: Architect formula — sprint PR creation with questions
|
- #101: Architect formula — sprint PR creation with questions
|
||||||
- #102: Architect formula — answer parsing + sub-issue filing
|
- #102: Architect formula — answer parsing + sub-issue filing
|
||||||
|
- #764: Permission scoping — architect read-only on project repo, filer-bot files sub-issues
|
||||||
- #491: Refactor — bash-driven design phase with stateful session resumption
|
- #491: Refactor — bash-driven design phase with stateful session resumption
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,31 @@ filer_log() {
|
||||||
: "${FORGE_FILER_TOKEN:?sprint-filer.sh requires FORGE_FILER_TOKEN}"
|
: "${FORGE_FILER_TOKEN:?sprint-filer.sh requires FORGE_FILER_TOKEN}"
|
||||||
: "${FORGE_API:?sprint-filer.sh requires FORGE_API}"
|
: "${FORGE_API:?sprint-filer.sh requires FORGE_API}"
|
||||||
|
|
||||||
|
# ── Paginated Forgejo API fetch ──────────────────────────────────────────
|
||||||
|
# Fetches all pages of a Forgejo API list endpoint and merges into one JSON array.
|
||||||
|
# Args: api_path (e.g. /issues?state=all&type=issues)
|
||||||
|
# Output: merged JSON array to stdout
|
||||||
|
filer_api_all() {
|
||||||
|
local path_prefix="$1"
|
||||||
|
local sep page page_items count all_items="[]"
|
||||||
|
case "$path_prefix" in
|
||||||
|
*"?"*) sep="&" ;;
|
||||||
|
*) sep="?" ;;
|
||||||
|
esac
|
||||||
|
page=1
|
||||||
|
while true; do
|
||||||
|
page_items=$(curl -sf -H "Authorization: token ${FORGE_FILER_TOKEN}" \
|
||||||
|
"${FORGE_API}${path_prefix}${sep}limit=50&page=${page}" 2>/dev/null) || page_items="[]"
|
||||||
|
count=$(printf '%s' "$page_items" | jq 'length' 2>/dev/null) || count=0
|
||||||
|
[ -z "$count" ] && count=0
|
||||||
|
[ "$count" -eq 0 ] && break
|
||||||
|
all_items=$(printf '%s\n%s' "$all_items" "$page_items" | jq -s 'add')
|
||||||
|
[ "$count" -lt 50 ] && break
|
||||||
|
page=$((page + 1))
|
||||||
|
done
|
||||||
|
printf '%s' "$all_items"
|
||||||
|
}
|
||||||
|
|
||||||
# ── Parse sub-issues block from a sprint markdown file ───────────────────
|
# ── Parse sub-issues block from a sprint markdown file ───────────────────
|
||||||
# Extracts the YAML-in-markdown between <!-- filer:begin --> and <!-- filer:end -->
|
# Extracts the YAML-in-markdown between <!-- filer:begin --> and <!-- filer:end -->
|
||||||
# Args: sprint_file_path
|
# Args: sprint_file_path
|
||||||
|
|
@ -93,11 +118,36 @@ parse_subissues_block() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── Extract vision issue number from sprint file ─────────────────────────
|
# ── Extract vision issue number from sprint file ─────────────────────────
|
||||||
# Looks for "## Vision issues" section with "#N" references
|
# Looks for "#N" references specifically in the "## Vision issues" section
|
||||||
|
# to avoid picking up cross-links or related-issue mentions earlier in the file.
|
||||||
|
# Falls back to first #N in the file if no "## Vision issues" section found.
|
||||||
# Args: sprint_file_path
|
# Args: sprint_file_path
|
||||||
# Output: first vision issue number found
|
# Output: first vision issue number found
|
||||||
extract_vision_issue() {
|
extract_vision_issue() {
|
||||||
local sprint_file="$1"
|
local sprint_file="$1"
|
||||||
|
|
||||||
|
# Try to extract from "## Vision issues" section first
|
||||||
|
local in_section=false
|
||||||
|
local result=""
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [[ "$line" =~ ^##[[:space:]]+Vision[[:space:]]+issues ]]; then
|
||||||
|
in_section=true
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
# Stop at next heading
|
||||||
|
if [ "$in_section" = true ] && [[ "$line" =~ ^## ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ "$in_section" = true ]; then
|
||||||
|
result=$(printf '%s' "$line" | grep -oE '#[0-9]+' | head -1 | tr -d '#')
|
||||||
|
if [ -n "$result" ]; then
|
||||||
|
printf '%s' "$result"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < "$sprint_file"
|
||||||
|
|
||||||
|
# Fallback: first #N in the entire file
|
||||||
grep -oE '#[0-9]+' "$sprint_file" | head -1 | tr -d '#'
|
grep -oE '#[0-9]+' "$sprint_file" | head -1 | tr -d '#'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,10 +305,9 @@ subissue_exists() {
|
||||||
|
|
||||||
local marker="<!-- decomposed-from: #${vision_issue}, sprint: ${sprint_slug}, id: ${subissue_id} -->"
|
local marker="<!-- decomposed-from: #${vision_issue}, sprint: ${sprint_slug}, id: ${subissue_id} -->"
|
||||||
|
|
||||||
# Search for issues with this exact marker
|
# Search all issues (paginated) for the exact marker
|
||||||
local issues_json
|
local issues_json
|
||||||
issues_json=$(curl -sf -H "Authorization: token ${FORGE_FILER_TOKEN}" \
|
issues_json=$(filer_api_all "/issues?state=all&type=issues")
|
||||||
"${FORGE_API}/issues?state=all&limit=50&type=issues" 2>/dev/null) || issues_json="[]"
|
|
||||||
|
|
||||||
if printf '%s' "$issues_json" | jq -e --arg marker "$marker" \
|
if printf '%s' "$issues_json" | jq -e --arg marker "$marker" \
|
||||||
'[.[] | select(.body // "" | contains($marker))] | length > 0' >/dev/null 2>&1; then
|
'[.[] | select(.body // "" | contains($marker))] | length > 0' >/dev/null 2>&1; then
|
||||||
|
|
@ -444,8 +493,7 @@ check_and_close_completed_visions() {
|
||||||
filer_log "Checking for vision issues with all sub-issues complete..."
|
filer_log "Checking for vision issues with all sub-issues complete..."
|
||||||
|
|
||||||
local vision_issues_json
|
local vision_issues_json
|
||||||
vision_issues_json=$(curl -sf -H "Authorization: token ${FORGE_FILER_TOKEN}" \
|
vision_issues_json=$(filer_api_all "/issues?labels=vision&state=open")
|
||||||
"${FORGE_API}/issues?labels=vision&state=open&limit=100" 2>/dev/null) || vision_issues_json="[]"
|
|
||||||
|
|
||||||
if [ "$vision_issues_json" = "[]" ] || [ "$vision_issues_json" = "null" ]; then
|
if [ "$vision_issues_json" = "[]" ] || [ "$vision_issues_json" = "null" ]; then
|
||||||
filer_log "No open vision issues found"
|
filer_log "No open vision issues found"
|
||||||
|
|
@ -453,8 +501,7 @@ check_and_close_completed_visions() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local all_issues
|
local all_issues
|
||||||
all_issues=$(curl -sf -H "Authorization: token ${FORGE_FILER_TOKEN}" \
|
all_issues=$(filer_api_all "/issues?state=all&type=issues")
|
||||||
"${FORGE_API}/issues?state=all&limit=200&type=issues" 2>/dev/null) || all_issues="[]"
|
|
||||||
|
|
||||||
local vision_nums
|
local vision_nums
|
||||||
vision_nums=$(printf '%s' "$vision_issues_json" | jq -r '.[].number' 2>/dev/null) || return 0
|
vision_nums=$(printf '%s' "$vision_issues_json" | jq -r '.[].number' 2>/dev/null) || return 0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue