feat: planner uses Claude tool access to navigate AGENTS.md tree (Closes #12)
Phase 1 rewritten: - Claude gets --dangerously-skip-permissions to navigate code, read diffs, and update AGENTS.md files directly - Per-file <!-- last-reviewed: SHA --> watermarks replace global marker - Sub-directory AGENTS.md files discovered and updated independently - ~200 line convention enforced via prompt - Changes committed on branch and PR'd Phase 2 now reads AGENTS.md tree + STATE.md for gap analysis. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
679c62e7cb
commit
3bb75f18be
1 changed files with 141 additions and 114 deletions
|
|
@ -1,10 +1,10 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# planner-agent.sh — Keep AGENTS.md current, then gap-analyse against VISION.md
|
# planner-agent.sh — Update AGENTS.md tree, then gap-analyse against VISION.md
|
||||||
#
|
#
|
||||||
# Two-phase planner run:
|
# Two-phase planner run:
|
||||||
# Phase 1: Review recent git history, suggest AGENTS.md updates via PR
|
# Phase 1: Navigate and update AGENTS.md tree using Claude with tool access
|
||||||
# Phase 2: Compare AGENTS.md vs VISION.md, create backlog issues for gaps
|
# Phase 2: Compare AGENTS.md + STATE.md vs VISION.md, create backlog issues
|
||||||
#
|
#
|
||||||
# Usage: planner-agent.sh (no args — uses env vars from .env / env.sh)
|
# Usage: planner-agent.sh (no args — uses env vars from .env / env.sh)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
@ -18,8 +18,7 @@ source "$FACTORY_ROOT/lib/env.sh"
|
||||||
|
|
||||||
LOG_FILE="$SCRIPT_DIR/planner.log"
|
LOG_FILE="$SCRIPT_DIR/planner.log"
|
||||||
CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-3600}"
|
CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-3600}"
|
||||||
MARKER_FILE="${PROJECT_REPO_ROOT}/.last-planner-sha"
|
STATE_FILE="${PROJECT_REPO_ROOT}/STATE.md"
|
||||||
AGENTS_FILE="${PROJECT_REPO_ROOT}/AGENTS.md"
|
|
||||||
VISION_FILE="${PROJECT_REPO_ROOT}/VISION.md"
|
VISION_FILE="${PROJECT_REPO_ROOT}/VISION.md"
|
||||||
|
|
||||||
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
|
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
|
||||||
|
|
@ -33,112 +32,119 @@ git pull --ff-only origin "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||||
HEAD_SHA=$(git rev-parse HEAD)
|
HEAD_SHA=$(git rev-parse HEAD)
|
||||||
log "--- Planner start (HEAD: ${HEAD_SHA:0:7}) ---"
|
log "--- Planner start (HEAD: ${HEAD_SHA:0:7}) ---"
|
||||||
|
|
||||||
# ── Determine git log range ─────────────────────────────────────────────
|
# ── Phase 1: Update AGENTS.md tree ──────────────────────────────────────
|
||||||
if [ -f "$MARKER_FILE" ]; then
|
log "Phase 1: updating AGENTS.md tree"
|
||||||
LAST_SHA=$(cat "$MARKER_FILE" 2>/dev/null | tr -d '[:space:]')
|
|
||||||
if git cat-file -e "$LAST_SHA" 2>/dev/null; then
|
# Find all AGENTS.md files and their watermarks
|
||||||
GIT_RANGE="${LAST_SHA}..HEAD"
|
AGENTS_FILES=$(find . -name "AGENTS.md" -not -path "./.git/*" | sort)
|
||||||
|
AGENTS_INFO=""
|
||||||
|
NEEDS_UPDATE=false
|
||||||
|
|
||||||
|
for f in $AGENTS_FILES; do
|
||||||
|
WATERMARK=$(grep -oP '(?<=<!-- last-reviewed: )[a-f0-9]+' "$f" 2>/dev/null | head -1 || true)
|
||||||
|
LINE_COUNT=$(wc -l < "$f")
|
||||||
|
if [ -n "$WATERMARK" ]; then
|
||||||
|
if git cat-file -e "$WATERMARK" 2>/dev/null; then
|
||||||
|
CHANGES=$(git log --oneline "${WATERMARK}..HEAD" -- "$(dirname "$f")" 2>/dev/null | wc -l || true)
|
||||||
else
|
else
|
||||||
log "WARNING: marker SHA ${LAST_SHA:0:7} not found, using 7-day window"
|
CHANGES="unknown"
|
||||||
first_sha=$(git log --format=%H --after='7 days ago' --reverse 2>/dev/null | head -1) || true
|
|
||||||
GIT_RANGE="${first_sha:-HEAD~20}..HEAD"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log "No marker file, using 7-day window"
|
WATERMARK="none"
|
||||||
first_sha=$(git log --format=%H --after='7 days ago' --reverse 2>/dev/null | head -1) || true
|
CHANGES="all"
|
||||||
GIT_RANGE="${first_sha:-HEAD~20}..HEAD"
|
|
||||||
fi
|
fi
|
||||||
|
AGENTS_INFO="${AGENTS_INFO} ${f} (${LINE_COUNT} lines, watermark: ${WATERMARK:0:7}, changes: ${CHANGES})\n"
|
||||||
|
[ "$CHANGES" != "0" ] && NEEDS_UPDATE=true
|
||||||
|
done
|
||||||
|
|
||||||
GIT_LOG=$(git log "$GIT_RANGE" --oneline --no-merges 2>/dev/null || true)
|
if [ "$NEEDS_UPDATE" = false ] && [ -n "$AGENTS_FILES" ]; then
|
||||||
COMMIT_COUNT=$(echo "$GIT_LOG" | grep -c '.' || true)
|
log "All AGENTS.md files up to date — skipping phase 1"
|
||||||
log "Range: $GIT_RANGE ($COMMIT_COUNT commits)"
|
|
||||||
|
|
||||||
# ── File tree (for context on what exists) ───────────────────────────────
|
|
||||||
FILE_TREE=$(find . -maxdepth 3 -type f \( -name '*.sol' -o -name '*.ts' -o -name '*.sh' -o -name '*.md' -o -name '*.json' \) \
|
|
||||||
! -path '*/node_modules/*' ! -path '*/.git/*' ! -path '*/out/*' ! -path '*/cache/*' ! -path '*/dist/*' \
|
|
||||||
2>/dev/null | sort | head -200)
|
|
||||||
|
|
||||||
# ── Phase 1: AGENTS.md maintenance ──────────────────────────────────────
|
|
||||||
if [ "$COMMIT_COUNT" -eq 0 ]; then
|
|
||||||
log "No new commits since last run — skipping AGENTS.md review"
|
|
||||||
else
|
else
|
||||||
log "Phase 1: reviewing AGENTS.md against recent changes"
|
# Create branch for changes
|
||||||
|
BRANCH_NAME="chore/planner-agents-$(date -u +%Y%m%d)"
|
||||||
|
git checkout -B "$BRANCH_NAME" 2>/dev/null
|
||||||
|
|
||||||
CURRENT_AGENTS=""
|
PHASE1_PROMPT="You maintain the AGENTS.md documentation tree for this repository.
|
||||||
[ -f "$AGENTS_FILE" ] && CURRENT_AGENTS=$(cat "$AGENTS_FILE")
|
Your job: keep every AGENTS.md file accurate, concise, and current.
|
||||||
|
|
||||||
if [ -z "$CURRENT_AGENTS" ]; then
|
## How AGENTS.md works
|
||||||
log "No AGENTS.md found — skipping phase 1"
|
- Each directory with significant logic has its own AGENTS.md
|
||||||
else
|
- Root AGENTS.md references sub-directory files
|
||||||
# Files changed in this range
|
- Each file has a watermark: \`<!-- last-reviewed: <sha> -->\` on line 1
|
||||||
FILES_CHANGED=$(git diff --name-only "$GIT_RANGE" 2>/dev/null | sort -u || true)
|
- The watermark tells you which commits are already reflected
|
||||||
|
|
||||||
PHASE1_PROMPT="You maintain AGENTS.md for the ${PROJECT_NAME} repository. AGENTS.md is the primary onboarding document — it describes the project's architecture, file layout, conventions, and how to work in the codebase.
|
## Current AGENTS.md files
|
||||||
|
$(echo -e "$AGENTS_INFO")
|
||||||
|
## Current HEAD: ${HEAD_SHA}
|
||||||
|
|
||||||
## Current AGENTS.md
|
## Your workflow
|
||||||
${CURRENT_AGENTS}
|
1. Read the root AGENTS.md. Note its watermark SHA.
|
||||||
|
2. Run \`git log --stat <watermark>..HEAD\` to see what changed since last review.
|
||||||
|
If watermark is 'none', use \`git log --stat -20\` for recent history.
|
||||||
|
3. For structural changes (new files, renames, major refactors), run \`git show <sha>\`
|
||||||
|
or read the affected source files to understand the change.
|
||||||
|
4. Follow references to sub-directory AGENTS.md files. Repeat steps 1-3 for each.
|
||||||
|
5. Update any AGENTS.md file that is stale or missing information about changes.
|
||||||
|
6. If a directory has significant logic but no AGENTS.md, create one.
|
||||||
|
|
||||||
## Recent commits (since last review)
|
## AGENTS.md conventions (follow these strictly)
|
||||||
${GIT_LOG}
|
- Max ~200 lines per file — if longer, split into sub-directory files
|
||||||
|
- Describe architecture and conventions (WHAT and WHY), not implementation details
|
||||||
|
- Link to source files for specifics: \`See [file.sol](path) for X\`
|
||||||
|
- Progressive disclosure: high-level in root, details in sub-directory files
|
||||||
|
- After updating a file, set its watermark to: \`<!-- last-reviewed: ${HEAD_SHA} -->\`
|
||||||
|
- The watermark MUST be the very first line of the file
|
||||||
|
|
||||||
## Files changed
|
## Important
|
||||||
${FILES_CHANGED}
|
- Only update files that are actually stale (have changes since watermark)
|
||||||
|
- Do NOT rewrite files that are already current
|
||||||
## Repository file tree (top 3 levels)
|
- Do NOT remove existing accurate content — only add, update, or restructure
|
||||||
${FILE_TREE}
|
- Keep the writing factual and architectural — no changelog language"
|
||||||
|
|
||||||
## Task
|
|
||||||
Review AGENTS.md against the recent changes. Output an UPDATED version of AGENTS.md that:
|
|
||||||
- Reflects any new directories, scripts, tools, or conventions introduced by the recent commits
|
|
||||||
- Removes or updates references to things that were deleted or renamed
|
|
||||||
- Keeps the existing structure, voice, and level of detail — this is a human-curated document, preserve its character
|
|
||||||
- Does NOT add issue/PR references — AGENTS.md is timeless documentation, not a changelog
|
|
||||||
- Does NOT rewrite sections that haven't changed — preserve the original text where possible
|
|
||||||
|
|
||||||
If AGENTS.md is already fully up to date and no changes are needed, output exactly: NO_CHANGES
|
|
||||||
|
|
||||||
Otherwise, output the complete updated AGENTS.md content (not a diff, the full file).
|
|
||||||
Do NOT wrap the output in markdown fences. Start directly with the file content."
|
|
||||||
|
|
||||||
PHASE1_OUTPUT=$(timeout "$CLAUDE_TIMEOUT" claude -p "$PHASE1_PROMPT" \
|
PHASE1_OUTPUT=$(timeout "$CLAUDE_TIMEOUT" claude -p "$PHASE1_PROMPT" \
|
||||||
--model sonnet \
|
--model sonnet \
|
||||||
--dangerously-skip-permissions \
|
--dangerously-skip-permissions \
|
||||||
|
--max-turns 30 \
|
||||||
2>/dev/null) || {
|
2>/dev/null) || {
|
||||||
log "ERROR: claude exited with code $? during phase 1"
|
EXIT_CODE=$?
|
||||||
# Update marker even on failure to avoid re-processing same range
|
log "ERROR: claude exited with code $EXIT_CODE during phase 1"
|
||||||
echo "$HEAD_SHA" > "$MARKER_FILE"
|
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if echo "$PHASE1_OUTPUT" | grep -q "^NO_CHANGES$"; then
|
log "Phase 1 claude finished ($(echo "$PHASE1_OUTPUT" | wc -c) bytes)"
|
||||||
log "AGENTS.md is up to date — no changes needed"
|
|
||||||
elif [ -n "$PHASE1_OUTPUT" ]; then
|
|
||||||
# Write updated AGENTS.md and create PR
|
|
||||||
TEMP_FILE=$(mktemp "${AGENTS_FILE}.XXXXXX")
|
|
||||||
printf '%s\n' "$PHASE1_OUTPUT" > "$TEMP_FILE"
|
|
||||||
mv "$TEMP_FILE" "$AGENTS_FILE"
|
|
||||||
|
|
||||||
if ! git diff --quiet "$AGENTS_FILE" 2>/dev/null; then
|
# Check if any files were modified
|
||||||
branch_name="chore/planner-agents-$(date -u +%Y%m%d)"
|
if git diff --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
|
||||||
git checkout -B "$branch_name" 2>/dev/null
|
log "No AGENTS.md changes — nothing to commit"
|
||||||
git add "$AGENTS_FILE"
|
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||||
git commit -m "chore: planner update AGENTS.md" --quiet 2>/dev/null
|
else
|
||||||
git push -f origin "$branch_name" --quiet 2>/dev/null || { log "ERROR: failed to push $branch_name"; git checkout "${PRIMARY_BRANCH}" 2>/dev/null; }
|
# Commit and push
|
||||||
git checkout "${PRIMARY_BRANCH}" 2>/dev/null
|
git add -A "*.md" 2>/dev/null || git add -A 2>/dev/null
|
||||||
# Restore AGENTS.md to master version after branch push
|
# Only add AGENTS.md files and STATE.md
|
||||||
git checkout "$AGENTS_FILE" 2>/dev/null || true
|
git diff --cached --name-only | grep -vE '(AGENTS|STATE)\.md$' | xargs -r git reset HEAD -- 2>/dev/null || true
|
||||||
|
|
||||||
|
if ! git diff --cached --quiet; then
|
||||||
|
git commit -m "chore: planner update AGENTS.md tree" --quiet 2>/dev/null
|
||||||
|
git push -f origin "$BRANCH_NAME" --quiet 2>/dev/null || {
|
||||||
|
log "ERROR: failed to push $BRANCH_NAME"
|
||||||
|
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||||
|
|
||||||
# Create or update PR
|
# Create or update PR
|
||||||
EXISTING_PR=$(codeberg_api GET "/pulls?state=open&limit=50" 2>/dev/null | \
|
EXISTING_PR=$(codeberg_api GET "/pulls?state=open&limit=50" 2>/dev/null | \
|
||||||
jq -r --arg branch "$branch_name" '.[] | select(.head.ref == $branch) | .number' | head -1)
|
jq -r --arg branch "$BRANCH_NAME" '.[] | select(.head.ref == $branch) | .number' | head -1)
|
||||||
if [ -z "$EXISTING_PR" ]; then
|
if [ -z "$EXISTING_PR" ]; then
|
||||||
PR_RESPONSE=$(codeberg_api POST "/pulls" \
|
PR_RESPONSE=$(codeberg_api POST "/pulls" \
|
||||||
"{\"title\":\"chore: planner update AGENTS.md\",\"head\":\"${branch_name}\",\"base\":\"${PRIMARY_BRANCH}\",\"body\":\"Automated AGENTS.md update based on recent commits (${COMMIT_COUNT} changes since last review).\"}" \
|
"$(jq -nc --arg h "$BRANCH_NAME" --arg b "$PRIMARY_BRANCH" \
|
||||||
|
'{title:"chore: planner update AGENTS.md tree",head:$h,base:$b,body:"Automated AGENTS.md tree update from git history analysis."}')" \
|
||||||
2>/dev/null)
|
2>/dev/null)
|
||||||
PR_NUM=$(echo "$PR_RESPONSE" | jq -r '.number // empty')
|
PR_NUM=$(echo "$PR_RESPONSE" | jq -r '.number // empty')
|
||||||
if [ -n "$PR_NUM" ]; then
|
if [ -n "$PR_NUM" ]; then
|
||||||
log "Created PR #${PR_NUM} for AGENTS.md update"
|
log "Created PR #${PR_NUM} for AGENTS.md update"
|
||||||
matrix_send "planner" "📋 PR #${PR_NUM}: planner update AGENTS.md (${COMMIT_COUNT} commits reviewed)" 2>/dev/null || true
|
matrix_send "planner" "📋 PR #${PR_NUM}: planner update AGENTS.md tree" 2>/dev/null || true
|
||||||
else
|
else
|
||||||
log "ERROR: failed to create PR"
|
log "ERROR: failed to create PR"
|
||||||
fi
|
fi
|
||||||
|
|
@ -146,20 +152,30 @@ Do NOT wrap the output in markdown fences. Start directly with the file content.
|
||||||
log "Updated existing PR #${EXISTING_PR}"
|
log "Updated existing PR #${EXISTING_PR}"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
log "AGENTS.md diff was empty after write — no PR needed"
|
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||||
fi
|
log "No AGENTS.md changes after filtering"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Update marker
|
|
||||||
echo "$HEAD_SHA" > "$MARKER_FILE"
|
|
||||||
log "Phase 1 done"
|
log "Phase 1 done"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Phase 2: Gap analysis ───────────────────────────────────────────────
|
# ── Phase 2: Gap analysis ───────────────────────────────────────────────
|
||||||
log "Phase 2: gap analysis"
|
log "Phase 2: gap analysis"
|
||||||
|
|
||||||
AGENTS_CONTENT=$(cat "$AGENTS_FILE" 2>/dev/null || true)
|
# Build project state from AGENTS.md tree + STATE.md
|
||||||
|
PROJECT_STATE=""
|
||||||
|
for f in $(find . -name "AGENTS.md" -not -path "./.git/*" | sort); do
|
||||||
|
PROJECT_STATE="${PROJECT_STATE}
|
||||||
|
### ${f}
|
||||||
|
$(cat "$f")
|
||||||
|
"
|
||||||
|
done
|
||||||
|
[ -f "$STATE_FILE" ] && PROJECT_STATE="${PROJECT_STATE}
|
||||||
|
### STATE.md
|
||||||
|
$(cat "$STATE_FILE")
|
||||||
|
"
|
||||||
|
|
||||||
VISION=""
|
VISION=""
|
||||||
[ -f "$VISION_FILE" ] && VISION=$(cat "$VISION_FILE")
|
[ -f "$VISION_FILE" ] && VISION=$(cat "$VISION_FILE")
|
||||||
|
|
||||||
|
|
@ -178,27 +194,34 @@ fi
|
||||||
|
|
||||||
OPEN_SUMMARY=$(echo "$OPEN_ISSUES" | jq -r '.[] | "#\(.number) [\(.labels | map(.name) | join(","))] \(.title)"' 2>/dev/null || true)
|
OPEN_SUMMARY=$(echo "$OPEN_ISSUES" | jq -r '.[] | "#\(.number) [\(.labels | map(.name) | join(","))] \(.title)"' 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Fetch vision-labeled issues specifically
|
||||||
|
VISION_ISSUES=$(echo "$OPEN_ISSUES" | jq -r '.[] | select(.labels | map(.name) | index("vision")) | "#\(.number) \(.title)\n\(.body)"' 2>/dev/null || true)
|
||||||
|
|
||||||
PHASE2_PROMPT="You are the planner for ${CODEBERG_REPO}. Your job: find gaps between the project vision and current reality.
|
PHASE2_PROMPT="You are the planner for ${CODEBERG_REPO}. Your job: find gaps between the project vision and current reality.
|
||||||
|
|
||||||
## VISION.md (human-maintained goals)
|
## VISION.md (human-maintained goals)
|
||||||
${VISION}
|
${VISION}
|
||||||
|
|
||||||
## AGENTS.md (current project state — the ground truth)
|
## Current project state (AGENTS.md tree + STATE.md)
|
||||||
${AGENTS_CONTENT}
|
${PROJECT_STATE}
|
||||||
|
|
||||||
|
## Vision-labeled issues (goal anchors)
|
||||||
|
${VISION_ISSUES:-"(none)"}
|
||||||
|
|
||||||
## All open issues
|
## All open issues
|
||||||
${OPEN_SUMMARY}
|
${OPEN_SUMMARY}
|
||||||
|
|
||||||
## Task
|
## Task
|
||||||
Identify gaps — things implied by VISION.md that are neither reflected in AGENTS.md nor covered by an existing open issue.
|
Identify gaps — things implied by VISION.md that are neither reflected in the project state nor covered by an existing open issue.
|
||||||
|
|
||||||
For each gap, output a JSON object (one per line, no array wrapper):
|
For each gap, output a JSON object (one per line, no array wrapper):
|
||||||
{\"title\": \"action-oriented title\", \"body\": \"problem statement + why it matters + rough approach\", \"depends\": [list of blocking issue numbers or empty]}
|
{\"title\": \"action-oriented title\", \"body\": \"problem statement + why it matters + rough approach\", \"depends\": [list of blocking issue numbers or empty]}
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
- Max 5 new issues — focus on highest-leverage gaps only
|
- Max 5 new issues — focus on highest-leverage gaps only
|
||||||
- Do NOT create issues for things already described in AGENTS.md (already done)
|
- Do NOT create issues for things already documented in AGENTS.md or STATE.md
|
||||||
- Do NOT create issues that overlap with ANY existing open issue, even partially
|
- Do NOT create issues that overlap with ANY existing open issue, even partially
|
||||||
|
- Do NOT create issues about vision items, tech-debt, or in-progress work
|
||||||
- Each title should be a plain, action-oriented sentence
|
- Each title should be a plain, action-oriented sentence
|
||||||
- Each body should explain: what's missing, why it matters for the vision, rough approach
|
- Each body should explain: what's missing, why it matters for the vision, rough approach
|
||||||
- Reference blocking issues by number in depends array
|
- Reference blocking issues by number in depends array
|
||||||
|
|
@ -209,7 +232,6 @@ Output ONLY the JSON lines (or NO_GAPS) — no preamble, no markdown fences."
|
||||||
|
|
||||||
PHASE2_OUTPUT=$(timeout "$CLAUDE_TIMEOUT" claude -p "$PHASE2_PROMPT" \
|
PHASE2_OUTPUT=$(timeout "$CLAUDE_TIMEOUT" claude -p "$PHASE2_PROMPT" \
|
||||||
--model sonnet \
|
--model sonnet \
|
||||||
--dangerously-skip-permissions \
|
|
||||||
2>/dev/null) || {
|
2>/dev/null) || {
|
||||||
log "ERROR: claude exited with code $? during phase 2"
|
log "ERROR: claude exited with code $? during phase 2"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
@ -222,18 +244,21 @@ if echo "$PHASE2_OUTPUT" | grep -q "NO_GAPS"; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ── Create issues from gap analysis ──────────────────────────────────────
|
# ── Create issues from gap analysis ──────────────────────────────────────
|
||||||
|
# Find backlog label ID
|
||||||
BACKLOG_LABEL_ID=$(codeberg_api GET "/labels" 2>/dev/null | \
|
BACKLOG_LABEL_ID=$(codeberg_api GET "/labels" 2>/dev/null | \
|
||||||
jq -r '.[] | select(.name == "backlog") | .id' 2>/dev/null || true)
|
jq -r '.[] | select(.name == "backlog") | .id' 2>/dev/null || true)
|
||||||
|
|
||||||
CREATED=0
|
CREATED=0
|
||||||
while IFS= read -r line; do
|
while IFS= read -r line; do
|
||||||
[ -z "$line" ] && continue
|
[ -z "$line" ] && continue
|
||||||
|
# Skip non-JSON lines
|
||||||
echo "$line" | jq -e . >/dev/null 2>&1 || continue
|
echo "$line" | jq -e . >/dev/null 2>&1 || continue
|
||||||
|
|
||||||
TITLE=$(echo "$line" | jq -r '.title')
|
TITLE=$(echo "$line" | jq -r '.title')
|
||||||
BODY=$(echo "$line" | jq -r '.body')
|
BODY=$(echo "$line" | jq -r '.body')
|
||||||
DEPS=$(echo "$line" | jq -r '.depends // [] | map("#\(.)") | join(", ")')
|
DEPS=$(echo "$line" | jq -r '.depends // [] | map("#\(.)") | join(", ")')
|
||||||
|
|
||||||
|
# Add dependency section if present
|
||||||
if [ -n "$DEPS" ] && [ "$DEPS" != "" ]; then
|
if [ -n "$DEPS" ] && [ "$DEPS" != "" ]; then
|
||||||
BODY="${BODY}
|
BODY="${BODY}
|
||||||
|
|
||||||
|
|
@ -241,7 +266,10 @@ while IFS= read -r line; do
|
||||||
${DEPS}"
|
${DEPS}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Create issue
|
||||||
CREATE_PAYLOAD=$(jq -nc --arg t "$TITLE" --arg b "$BODY" '{title:$t, body:$b}')
|
CREATE_PAYLOAD=$(jq -nc --arg t "$TITLE" --arg b "$BODY" '{title:$t, body:$b}')
|
||||||
|
|
||||||
|
# Add label if we found the backlog label ID
|
||||||
if [ -n "$BACKLOG_LABEL_ID" ]; then
|
if [ -n "$BACKLOG_LABEL_ID" ]; then
|
||||||
CREATE_PAYLOAD=$(echo "$CREATE_PAYLOAD" | jq --argjson lid "$BACKLOG_LABEL_ID" '.labels = [$lid]')
|
CREATE_PAYLOAD=$(echo "$CREATE_PAYLOAD" | jq --argjson lid "$BACKLOG_LABEL_ID" '.labels = [$lid]')
|
||||||
fi
|
fi
|
||||||
|
|
@ -249,7 +277,6 @@ ${DEPS}"
|
||||||
RESULT=$(codeberg_api POST "/issues" -d "$CREATE_PAYLOAD" 2>/dev/null || true)
|
RESULT=$(codeberg_api POST "/issues" -d "$CREATE_PAYLOAD" 2>/dev/null || true)
|
||||||
ISSUE_NUM=$(echo "$RESULT" | jq -r '.number // "?"' 2>/dev/null || echo "?")
|
ISSUE_NUM=$(echo "$RESULT" | jq -r '.number // "?"' 2>/dev/null || echo "?")
|
||||||
log "Created #${ISSUE_NUM}: ${TITLE}"
|
log "Created #${ISSUE_NUM}: ${TITLE}"
|
||||||
matrix_send "planner" "📋 Gap issue #${ISSUE_NUM}: ${TITLE}" 2>/dev/null || true
|
|
||||||
CREATED=$((CREATED + 1))
|
CREATED=$((CREATED + 1))
|
||||||
|
|
||||||
[ "$CREATED" -ge 5 ] && break
|
[ "$CREATED" -ge 5 ] && break
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue