fix: feat: planner as cron-driven formula (no issue tracking) (#232)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b4dce50f2c
commit
6c7557e87b
8 changed files with 305 additions and 647 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -18,3 +18,6 @@ metrics/supervisor-metrics.jsonl
|
|||
.DS_Store
|
||||
dev/ci-fixes-*.json
|
||||
gardener/dust.jsonl
|
||||
|
||||
# Planner persistent memory (local only)
|
||||
planner/MEMORY.md
|
||||
|
|
|
|||
|
|
@ -161,7 +161,6 @@ check_script gardener/gardener-agent.sh
|
|||
check_script gardener/gardener-poll.sh
|
||||
check_script review/review-pr.sh
|
||||
check_script review/review-poll.sh
|
||||
check_script planner/planner-agent.sh
|
||||
check_script planner/planner-poll.sh
|
||||
check_script supervisor/supervisor-poll.sh
|
||||
check_script supervisor/update-prompt.sh
|
||||
|
|
|
|||
32
AGENTS.md
32
AGENTS.md
|
|
@ -17,7 +17,7 @@ disinto/
|
|||
├── dev/ dev-poll.sh, dev-agent.sh, phase-handler.sh — issue implementation
|
||||
├── review/ review-poll.sh, review-pr.sh — PR review
|
||||
├── gardener/ gardener-poll.sh, gardener-agent.sh — backlog grooming
|
||||
├── planner/ planner-poll.sh, planner-agent.sh — vision gap analysis
|
||||
├── planner/ planner-poll.sh — files action issue for run-planner formula
|
||||
│ prediction-poll.sh, prediction-agent.sh — evidence-based predictions
|
||||
├── supervisor/ supervisor-poll.sh — health monitoring
|
||||
├── vault/ vault-poll.sh, vault-agent.sh, vault-fire.sh — action gating
|
||||
|
|
@ -148,25 +148,33 @@ P3 (degraded PRs, circular deps, stale deps), P4 (housekeeping).
|
|||
|
||||
### Planner (`planner/`)
|
||||
|
||||
**Role**: Three-phase planning. Phase 1: update the AGENTS.md documentation
|
||||
tree to reflect recent code changes. Phase 1.5: triage `prediction/unreviewed`
|
||||
issues filed by the [Predictor](#predictor-planner) — accept as action/backlog
|
||||
issues or dismiss as noise. Phase 2: gap-analyse VISION.md vs current project
|
||||
state (including accepted predictions), create up to 5 backlog issues for the
|
||||
highest-leverage gaps.
|
||||
**Role**: Five-phase strategic planning, executed as an action formula.
|
||||
Phase 0 (preflight): pull latest code, load persistent memory from
|
||||
`planner/MEMORY.md`. Phase 1: update the AGENTS.md documentation tree to
|
||||
reflect recent code changes (fast-track PR). Phase 1.5: triage
|
||||
`prediction/unreviewed` issues filed by the [Predictor](#predictor-planner) —
|
||||
accept as action/backlog issues or dismiss as noise. Phase 2: strategic planning
|
||||
via resource+leverage gap analysis — reasons about VISION.md, RESOURCES.md,
|
||||
formula catalog, and project state to create up to 5 backlog issues prioritized
|
||||
by leverage. Phase 3: persist learnings to `planner/MEMORY.md`.
|
||||
|
||||
**Trigger**: `planner-poll.sh` runs weekly via cron.
|
||||
**Trigger**: `planner-poll.sh` runs weekly via cron. It files an `action`
|
||||
issue referencing `formulas/run-planner.toml`; the [action-agent](#action-action)
|
||||
picks it up and executes the planning steps in an interactive Claude tmux session.
|
||||
|
||||
**Key files**:
|
||||
- `planner/planner-poll.sh` — Cron wrapper: lock, memory guard, runs planner-agent.sh
|
||||
- `planner/planner-agent.sh` — Phase 1: uses `claude -p --model sonnet --max-turns 30` (one-shot with tool access) to read/update AGENTS.md files. Phase 1.5: fetches `prediction/unreviewed` issues and uses `claude -p --model sonnet` to triage each prediction (ACCEPT_ACTION, ACCEPT_BACKLOG, or DISMISS); creates corresponding action/backlog issues and relabels predictions to `prediction/backlog` or closes them. Phase 2: uses `claude -p --model sonnet` to compare AGENTS.md tree vs VISION.md (plus accepted predictions from Phase 1.5) and create gap issues. All phases are one-shot (`claude -p`), not interactive sessions
|
||||
- `planner/planner-poll.sh` — Cron wrapper: memory guard, dedup check, files action issue
|
||||
- `formulas/run-planner.toml` — Execution spec: five steps (preflight, agents-update,
|
||||
prediction-triage, strategic-planning, memory-update) with `needs` dependencies.
|
||||
Steps 2 and 3 are independent; step 4 depends on both. Claude executes all steps
|
||||
in a single interactive session with tool access
|
||||
- `planner/MEMORY.md` — Persistent memory across runs (gitignored, local only)
|
||||
|
||||
**Future direction**: The [Predictor](#predictor-planner) already reads `evidence/` JSON and files prediction issues for the planner to triage. The next step is evidence-gated deployment (see `docs/EVIDENCE-ARCHITECTURE.md`): replacing human "ship it" decisions with automated gates across dimensions (holdout, red-team, user-test, evolution fitness, protocol metrics, funnel). Not yet implemented.
|
||||
|
||||
**Environment variables consumed**:
|
||||
**Environment variables consumed** (by the action-agent session):
|
||||
- `CODEBERG_TOKEN`, `CODEBERG_REPO`, `CODEBERG_API`, `PROJECT_NAME`, `PROJECT_REPO_ROOT`
|
||||
- `PRIMARY_BRANCH`
|
||||
- `CLAUDE_TIMEOUT`
|
||||
- `MATRIX_TOKEN`, `MATRIX_ROOM_ID`, `MATRIX_HOMESERVER`
|
||||
|
||||
### Predictor (`planner/`)
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ This ensures dev-agent can't merge its own PRs — it must wait for review-agent
|
|||
|
||||
### Required: Seed the `AGENTS.md` tree
|
||||
|
||||
The planner-agent maintains an `AGENTS.md` tree — architecture docs with
|
||||
The planner maintains an `AGENTS.md` tree — architecture docs with
|
||||
per-file `<!-- last-reviewed: SHA -->` watermarks. You must seed this before
|
||||
the first planner run, otherwise the planner sees no watermarks and treats the
|
||||
entire repo as "new", generating a noisy first-run diff.
|
||||
|
|
@ -134,7 +134,7 @@ entire repo as "new", generating a noisy first-run diff.
|
|||
5. Commit and push. The planner will now see 0 changes on its first run and
|
||||
only update files when real commits land.
|
||||
|
||||
See `planner/planner-agent.sh` for the full AGENTS.md conventions.
|
||||
See `formulas/run-planner.toml` (agents-update step) for the full AGENTS.md conventions.
|
||||
|
||||
## 3. Write Good Issues
|
||||
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ disinto/
|
|||
│ └── best-practices.md # Gardener knowledge base
|
||||
├── planner/
|
||||
│ ├── planner-poll.sh # Cron entry: weekly vision gap analysis
|
||||
│ └── planner-agent.sh # Updates AGENTS.md, creates backlog issues (claude -p)
|
||||
│ └── (formula-driven) # run-planner.toml executed by action-agent
|
||||
├── vault/
|
||||
│ ├── vault-poll.sh # Cron entry: process pending dangerous actions
|
||||
│ ├── vault-agent.sh # Classifies and routes actions (claude -p)
|
||||
|
|
|
|||
229
formulas/run-planner.toml
Normal file
229
formulas/run-planner.toml
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
# formulas/run-planner.toml — Strategic planning formula
|
||||
#
|
||||
# Executed by the action-agent via cron-filed action issues.
|
||||
# planner-poll.sh files an action issue referencing this formula weekly;
|
||||
# action-poll.sh picks it up and spawns a tmux session where Claude
|
||||
# executes these steps autonomously.
|
||||
|
||||
name = "run-planner"
|
||||
description = "Strategic planning: update docs, triage predictions, resource+leverage gap analysis"
|
||||
version = 1
|
||||
|
||||
[context]
|
||||
files = ["VISION.md", "AGENTS.md", "RESOURCES.md"]
|
||||
|
||||
[[steps]]
|
||||
id = "preflight"
|
||||
title = "Pull latest code and load planner memory"
|
||||
description = """
|
||||
Set up the working environment for this planning run.
|
||||
|
||||
1. Change to the project repository:
|
||||
cd "$PROJECT_REPO_ROOT"
|
||||
|
||||
2. Pull the latest code:
|
||||
git fetch origin "$PRIMARY_BRANCH" --quiet
|
||||
git checkout "$PRIMARY_BRANCH" --quiet
|
||||
git pull --ff-only origin "$PRIMARY_BRANCH" --quiet
|
||||
|
||||
3. Record the current HEAD SHA — you will need it for AGENTS.md watermarks:
|
||||
HEAD_SHA=$(git rev-parse HEAD)
|
||||
echo "$HEAD_SHA" > /tmp/planner-head-sha
|
||||
|
||||
4. Read the planner memory file at: $FACTORY_ROOT/planner/MEMORY.md
|
||||
If it does not exist, this is the first planning run.
|
||||
Keep this memory context in mind for all subsequent steps.
|
||||
"""
|
||||
|
||||
[[steps]]
|
||||
id = "agents-update"
|
||||
title = "Update AGENTS.md documentation tree"
|
||||
description = """
|
||||
Check all AGENTS.md files for staleness and update any that are outdated.
|
||||
|
||||
1. Read the HEAD SHA from preflight:
|
||||
HEAD_SHA=$(cat /tmp/planner-head-sha)
|
||||
|
||||
2. Find all AGENTS.md files:
|
||||
find "$PROJECT_REPO_ROOT" -name "AGENTS.md" -not -path "*/.git/*"
|
||||
|
||||
3. For each file, read the watermark from line 1:
|
||||
<!-- last-reviewed: <sha> -->
|
||||
|
||||
4. Check for changes since the watermark:
|
||||
git log --oneline <watermark>..HEAD -- <directory>
|
||||
If zero changes, the file is current — skip it.
|
||||
|
||||
5. For stale files:
|
||||
- Read the AGENTS.md and the source files in that directory
|
||||
- Update the documentation to reflect code changes since the watermark
|
||||
- Set the watermark to the HEAD SHA from the preflight step
|
||||
- Conventions: max ~200 lines, architecture and WHY not implementation details
|
||||
|
||||
6. If you made changes:
|
||||
a. Create a branch:
|
||||
git checkout -B "chore/planner-agents-$(date -u +%Y%m%d)"
|
||||
b. Stage only AGENTS.md files:
|
||||
find . -name "AGENTS.md" -not -path "./.git/*" -exec git add {} +
|
||||
c. Commit:
|
||||
git commit -m "chore: planner update AGENTS.md tree"
|
||||
d. Push:
|
||||
git push -f origin "chore/planner-agents-$(date -u +%Y%m%d)"
|
||||
e. Create a PR (failure here is non-fatal — log and continue):
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/pulls" \
|
||||
-d '{"title":"chore: planner update AGENTS.md tree",
|
||||
"head":"<branch>","base":"<primary-branch>",
|
||||
"body":"Automated AGENTS.md update — review-agent fast-tracks doc-only PRs."}'
|
||||
f. Return to primary branch:
|
||||
git checkout "$PRIMARY_BRANCH"
|
||||
|
||||
7. If no AGENTS.md files need updating, skip this step entirely.
|
||||
|
||||
CRITICAL: If this step fails for any reason, log the failure and move on.
|
||||
Do NOT let an AGENTS.md failure prevent prediction triage or strategic planning.
|
||||
"""
|
||||
needs = ["preflight"]
|
||||
|
||||
[[steps]]
|
||||
id = "prediction-triage"
|
||||
title = "Triage prediction/unreviewed issues"
|
||||
description = """
|
||||
Triage prediction issues filed by the predictor (goblin).
|
||||
|
||||
1. Fetch unreviewed predictions:
|
||||
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/issues?state=open&type=issues&labels=prediction%2Funreviewed&limit=50"
|
||||
|
||||
If there are none, note that and proceed to strategic-planning.
|
||||
|
||||
2. Read available formulas from $FACTORY_ROOT/formulas/*.toml so you know
|
||||
what actions can be dispatched.
|
||||
|
||||
3. Fetch all open issues to check for overlap:
|
||||
curl -sf -H "Authorization: token $CODEBERG_TOKEN" \
|
||||
"$CODEBERG_API/issues?state=open&type=issues&limit=50"
|
||||
|
||||
4. For each prediction, read the title and body. Decide:
|
||||
- ACCEPT_ACTION: maps to an available formula -> create an action issue
|
||||
with YAML front matter referencing the formula name and vars
|
||||
- ACCEPT_BACKLOG: warrants dev work -> create a backlog issue
|
||||
- DISMISS: noise, already covered by an open issue, or not actionable ->
|
||||
post an explanation comment, then close the prediction issue
|
||||
|
||||
5. For each accepted prediction:
|
||||
- Create the new issue with the 'backlog' label (or 'action' label for
|
||||
formula-matching actions)
|
||||
- Remove 'prediction/unreviewed' label from the original prediction
|
||||
- Add 'prediction/backlog' label to the original prediction
|
||||
- Note what you accepted — you will need it for strategic-planning
|
||||
|
||||
6. Validation: if you reference a formula, verify it exists on disk.
|
||||
Fall back to a freeform backlog issue for unknown formulas.
|
||||
|
||||
Be decisive — the predictor intentionally over-signals; your job is to filter.
|
||||
|
||||
CRITICAL: If this step fails, log the failure and move on to strategic-planning.
|
||||
"""
|
||||
needs = ["preflight"]
|
||||
|
||||
[[steps]]
|
||||
id = "strategic-planning"
|
||||
title = "Strategic planning — resource+leverage gap analysis"
|
||||
description = """
|
||||
This is the core planning step. Reason about leverage and create
|
||||
the highest-impact issues.
|
||||
|
||||
Read these inputs:
|
||||
- VISION.md — where we want to be
|
||||
- All AGENTS.md files — what exists today
|
||||
- $FACTORY_ROOT/RESOURCES.md — what we have (may not exist)
|
||||
- $FACTORY_ROOT/formulas/*.toml — what actions can be dispatched
|
||||
- Open issues (fetched via API) — what's already planned
|
||||
- $FACTORY_ROOT/metrics/supervisor-metrics.jsonl — operational trends (may not exist)
|
||||
- Planner memory (loaded in preflight)
|
||||
- Accepted predictions from the triage step
|
||||
|
||||
Reason through these five questions:
|
||||
|
||||
1. **What resources do you need that you don't have?**
|
||||
Analytics, domains, accounts, compute, integrations — things required
|
||||
by the vision that aren't in RESOURCES.md or aren't set up yet.
|
||||
|
||||
2. **What resources are underutilized?**
|
||||
Compute capacity idle most of the day. Domains with no traffic.
|
||||
CI capacity unused at night. Accounts not being leveraged.
|
||||
|
||||
3. **What's the highest-leverage action?**
|
||||
The one thing that unblocks the most progress toward the vision.
|
||||
Can you dispatch a formula for it?
|
||||
|
||||
4. **What task gaps remain?**
|
||||
Things in VISION.md not covered by open issues or the current
|
||||
project state.
|
||||
|
||||
5. **What should be deferred?**
|
||||
Things that depend on blocked resources or aren't high-leverage
|
||||
right now. Do NOT create issues for these.
|
||||
|
||||
Then create up to 5 issues, prioritized by leverage:
|
||||
|
||||
For formula-matching gaps, include YAML front matter in the body:
|
||||
---
|
||||
formula: <name>
|
||||
vars:
|
||||
key: "value"
|
||||
---
|
||||
<explanation of why this matters>
|
||||
|
||||
For freeform gaps:
|
||||
<problem statement + why it matters for the vision + rough approach>
|
||||
|
||||
Create each issue via the API with the 'backlog' label:
|
||||
curl -sf -X POST \
|
||||
-H "Authorization: token $CODEBERG_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$CODEBERG_API/issues" \
|
||||
-d '{"title":"...","body":"...","labels":[<backlog_label_id>]}'
|
||||
|
||||
Rules:
|
||||
- Max 5 new issues — highest leverage first
|
||||
- Do NOT create issues that overlap with ANY existing open issue
|
||||
- Do NOT create issues for items you identified as "deferred"
|
||||
- Each body: what's missing, why it matters, rough approach
|
||||
- When deploying/operating, reference the resource alias from RESOURCES.md
|
||||
- Add ## Depends on section for issues that depend on other open issues
|
||||
- Only reference formulas that exist in formulas/*.toml
|
||||
- When metrics show systemic problems, create optimization issues
|
||||
|
||||
If there are no gaps, note that the backlog is aligned with the vision.
|
||||
"""
|
||||
needs = ["agents-update", "prediction-triage"]
|
||||
|
||||
[[steps]]
|
||||
id = "memory-update"
|
||||
title = "Persist learnings to planner/MEMORY.md"
|
||||
description = """
|
||||
Reflect on this planning run and write the updated memory file.
|
||||
|
||||
Write to: $FACTORY_ROOT/planner/MEMORY.md (replace the entire file)
|
||||
|
||||
Include:
|
||||
- Date of this run
|
||||
- What was observed (resource state, metric trends, project progress)
|
||||
- What was decided (issues created, predictions triaged, what was deferred)
|
||||
- Patterns and learnings useful for future planning runs
|
||||
- Things to watch for next time
|
||||
|
||||
Rules:
|
||||
- Keep under 100 lines total
|
||||
- Replace the file contents — prune outdated entries from previous runs
|
||||
- Focus on PATTERNS and LEARNINGS, not transient state
|
||||
- Do NOT include specific issue counts or numbers that will be stale
|
||||
- Most recent entries at top
|
||||
|
||||
Format: simple markdown with dated sections.
|
||||
"""
|
||||
needs = ["strategic-planning"]
|
||||
|
|
@ -1,622 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# planner-agent.sh — Update AGENTS.md tree, triage predictions, gap-analyse
|
||||
#
|
||||
# Three-phase planner run:
|
||||
# Phase 1: Navigate and update AGENTS.md tree using Claude with tool access
|
||||
# Phase 1.5: Triage prediction/unreviewed issues from the predictor (goblin)
|
||||
# Phase 2: Compare AGENTS.md vs VISION.md, create backlog issues for gaps
|
||||
#
|
||||
# Usage: planner-agent.sh (no args — uses env vars from .env / env.sh)
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
FACTORY_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# shellcheck source=../lib/env.sh
|
||||
source "$FACTORY_ROOT/lib/env.sh"
|
||||
|
||||
LOG_FILE="$SCRIPT_DIR/planner.log"
|
||||
CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-3600}"
|
||||
VISION_FILE="${PROJECT_REPO_ROOT}/VISION.md"
|
||||
RESOURCES_FILE="${FACTORY_ROOT}/RESOURCES.md"
|
||||
|
||||
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%S)Z] $*" >> "$LOG_FILE"; }
|
||||
|
||||
# ── Preflight ────────────────────────────────────────────────────────────
|
||||
cd "$PROJECT_REPO_ROOT"
|
||||
git fetch origin "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||
git pull --ff-only origin "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||
|
||||
HEAD_SHA=$(git rev-parse HEAD)
|
||||
log "--- Planner start (HEAD: ${HEAD_SHA:0:7}) ---"
|
||||
|
||||
# ── Phase 1: Update AGENTS.md tree ──────────────────────────────────────
|
||||
log "Phase 1: updating AGENTS.md tree"
|
||||
|
||||
# Find all AGENTS.md files and their watermarks
|
||||
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
|
||||
CHANGES="unknown"
|
||||
fi
|
||||
else
|
||||
WATERMARK="none"
|
||||
CHANGES="all"
|
||||
fi
|
||||
AGENTS_INFO="${AGENTS_INFO} ${f} (${LINE_COUNT} lines, watermark: ${WATERMARK:0:7}, changes: ${CHANGES})\n"
|
||||
[ "$CHANGES" != "0" ] && NEEDS_UPDATE=true
|
||||
done
|
||||
|
||||
if [ "$NEEDS_UPDATE" = false ] && [ -n "$AGENTS_FILES" ]; then
|
||||
log "All AGENTS.md files up to date — skipping phase 1"
|
||||
else
|
||||
# Create branch for changes
|
||||
BRANCH_NAME="chore/planner-agents-$(date -u +%Y%m%d)"
|
||||
git checkout -B "$BRANCH_NAME" 2>/dev/null
|
||||
|
||||
PHASE1_PROMPT="You maintain the AGENTS.md documentation tree for this repository.
|
||||
Your job: keep every AGENTS.md file accurate, concise, and current.
|
||||
|
||||
## How AGENTS.md works
|
||||
- Each directory with significant logic has its own AGENTS.md
|
||||
- Root AGENTS.md references sub-directory files
|
||||
- Each file has a watermark: \`<!-- last-reviewed: <sha> -->\` on line 1
|
||||
- The watermark tells you which commits are already reflected
|
||||
|
||||
## Current AGENTS.md files
|
||||
$(echo -e "$AGENTS_INFO")
|
||||
## Current HEAD: ${HEAD_SHA}
|
||||
|
||||
## Your workflow
|
||||
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.
|
||||
|
||||
## AGENTS.md conventions (follow these strictly)
|
||||
- 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
|
||||
|
||||
## Important
|
||||
- Only update files that are actually stale (have changes since watermark)
|
||||
- Do NOT rewrite files that are already current
|
||||
- Do NOT remove existing accurate content — only add, update, or restructure
|
||||
- Keep the writing factual and architectural — no changelog language"
|
||||
|
||||
PHASE1_OUTPUT=$(timeout "$CLAUDE_TIMEOUT" claude -p "$PHASE1_PROMPT" \
|
||||
--model sonnet \
|
||||
--dangerously-skip-permissions \
|
||||
--max-turns 30 \
|
||||
2>/dev/null) || {
|
||||
EXIT_CODE=$?
|
||||
log "ERROR: claude exited with code $EXIT_CODE during phase 1"
|
||||
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
log "Phase 1 claude finished ($(echo "$PHASE1_OUTPUT" | wc -c) bytes)"
|
||||
|
||||
# Check if any files were modified
|
||||
if git diff --quiet && [ -z "$(git ls-files --others --exclude-standard)" ]; then
|
||||
log "No AGENTS.md changes — nothing to commit"
|
||||
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||
else
|
||||
# Commit and push
|
||||
find . -name "AGENTS.md" -not -path "./.git/*" -exec git add {} +
|
||||
|
||||
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
|
||||
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)
|
||||
if [ -z "$EXISTING_PR" ]; then
|
||||
PR_RESPONSE=$(codeberg_api POST "/pulls" \
|
||||
"$(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)
|
||||
PR_NUM=$(echo "$PR_RESPONSE" | jq -r '.number // empty')
|
||||
if [ -n "$PR_NUM" ]; then
|
||||
log "Created PR #${PR_NUM} for AGENTS.md update"
|
||||
matrix_send "planner" "📋 PR #${PR_NUM}: planner update AGENTS.md tree" 2>/dev/null || true
|
||||
else
|
||||
log "ERROR: failed to create PR"
|
||||
fi
|
||||
else
|
||||
log "Updated existing PR #${EXISTING_PR}"
|
||||
fi
|
||||
else
|
||||
git checkout "${PRIMARY_BRANCH}" --quiet 2>/dev/null || true
|
||||
log "No AGENTS.md changes after filtering"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "Phase 1 done"
|
||||
fi
|
||||
|
||||
# ── Phase 1.5: Prediction Triage ──────────────────────────────────────
|
||||
log "Phase 1.5: prediction triage"
|
||||
|
||||
PRED_ISSUES=$(codeberg_api GET "/issues?state=open&type=issues&labels=prediction%2Funreviewed&limit=50" 2>/dev/null || true)
|
||||
PRED_COUNT=0
|
||||
if [ -n "$PRED_ISSUES" ] && [ "$PRED_ISSUES" != "null" ]; then
|
||||
PRED_COUNT=$(printf '%s' "$PRED_ISSUES" | jq 'length' 2>/dev/null || echo 0)
|
||||
fi
|
||||
|
||||
ACCEPTED_PREDICTIONS=""
|
||||
|
||||
if [ "${PRED_COUNT:-0}" -gt 0 ] 2>/dev/null; then
|
||||
log "Found $PRED_COUNT prediction/unreviewed issues to triage"
|
||||
|
||||
# Build prediction details for the prompt
|
||||
PRED_DETAILS=$(printf '%s' "$PRED_ISSUES" | jq -r \
|
||||
'.[] | "### Prediction #\(.number): \(.title)\n\(.body)\n"' 2>/dev/null || true)
|
||||
|
||||
# Look up label IDs
|
||||
ALL_LABELS=$(codeberg_api GET "/labels" 2>/dev/null || true)
|
||||
UNREVIEWED_LABEL_ID=$(printf '%s' "$ALL_LABELS" | \
|
||||
jq -r '.[] | select(.name == "prediction/unreviewed") | .id' 2>/dev/null || true)
|
||||
BACKLOG_PRED_LABEL_ID=$(printf '%s' "$ALL_LABELS" | \
|
||||
jq -r '.[] | select(.name == "prediction/backlog") | .id' 2>/dev/null || true)
|
||||
BACKLOG_LABEL_ID_TRIAGE=$(printf '%s' "$ALL_LABELS" | \
|
||||
jq -r '.[] | select(.name == "backlog") | .id' 2>/dev/null || true)
|
||||
|
||||
# Create prediction/backlog label if missing
|
||||
if [ -z "$BACKLOG_PRED_LABEL_ID" ]; then
|
||||
LABEL_RESULT=$(codeberg_api POST "/labels" \
|
||||
-d "$(jq -nc '{name:"prediction/backlog", color:"#0e8a16", description:"Triaged prediction — accepted by planner"}')" \
|
||||
2>/dev/null || true)
|
||||
BACKLOG_PRED_LABEL_ID=$(printf '%s' "$LABEL_RESULT" | jq -r '.id // empty' 2>/dev/null || true)
|
||||
if [ -n "$BACKLOG_PRED_LABEL_ID" ]; then
|
||||
log "Created 'prediction/backlog' label (id: $BACKLOG_PRED_LABEL_ID)"
|
||||
else
|
||||
log "WARN: failed to create 'prediction/backlog' label"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Build formula catalog for triage prompt
|
||||
TRIAGE_FORMULA_CATALOG=""
|
||||
if [ -d "$FACTORY_ROOT/formulas" ]; then
|
||||
for _tf in "$FACTORY_ROOT/formulas"/*.toml; do
|
||||
[ -f "$_tf" ] || continue
|
||||
TRIAGE_FORMULA_CATALOG="${TRIAGE_FORMULA_CATALOG}
|
||||
--- $(basename "$_tf" .toml) ---
|
||||
$(cat "$_tf")
|
||||
"
|
||||
done
|
||||
fi
|
||||
|
||||
# Fetch open issues summary for overlap check
|
||||
_TRIAGE_ISSUES=$(codeberg_api GET "/issues?state=open&type=issues&limit=50&sort=updated&direction=desc" 2>/dev/null || true)
|
||||
TRIAGE_OPEN_SUMMARY=""
|
||||
if [ -n "$_TRIAGE_ISSUES" ] && [ "$_TRIAGE_ISSUES" != "null" ]; then
|
||||
TRIAGE_OPEN_SUMMARY=$(printf '%s' "$_TRIAGE_ISSUES" | \
|
||||
jq -r '.[] | "#\(.number) [\(.labels | map(.name) | join(","))] \(.title)"' 2>/dev/null || true)
|
||||
fi
|
||||
|
||||
# Load VISION for context
|
||||
TRIAGE_VISION=""
|
||||
[ -f "$VISION_FILE" ] && TRIAGE_VISION=$(cat "$VISION_FILE")
|
||||
|
||||
TRIAGE_PROMPT="You are the planner for ${CODEBERG_REPO}. The predictor has filed these observations:
|
||||
|
||||
${PRED_DETAILS}
|
||||
|
||||
For each prediction, decide:
|
||||
- ACCEPT_ACTION: maps to a formula → emit action issue JSON
|
||||
- ACCEPT_BACKLOG: warrants dev work → emit backlog issue JSON
|
||||
- DISMISS: noise, already covered, or not actionable → close with reason
|
||||
|
||||
Context for your decisions:
|
||||
|
||||
## VISION.md
|
||||
${TRIAGE_VISION:-"(not found)"}
|
||||
|
||||
## Available formulas
|
||||
${TRIAGE_FORMULA_CATALOG:-"(no formulas available)"}
|
||||
|
||||
## All open issues (check for overlap)
|
||||
${TRIAGE_OPEN_SUMMARY:-"(could not fetch)"}
|
||||
|
||||
## Output format
|
||||
|
||||
For each prediction, output one JSON object per line (no array wrapper, no markdown fences):
|
||||
|
||||
{\"prediction\": <issue-number>, \"decision\": \"ACCEPT_ACTION\", \"title\": \"action title\", \"formula\": \"formula-name\", \"vars\": {\"var1\": \"value1\"}, \"reason\": \"why\"}
|
||||
{\"prediction\": <issue-number>, \"decision\": \"ACCEPT_BACKLOG\", \"title\": \"backlog issue title\", \"body\": \"problem + approach\", \"reason\": \"why\"}
|
||||
{\"prediction\": <issue-number>, \"decision\": \"DISMISS\", \"reason\": \"why this is noise or already covered\"}
|
||||
|
||||
## Rules
|
||||
- Triage ALL predictions — every prediction must appear in output
|
||||
- Only use ACCEPT_ACTION when the prediction clearly maps to an available formula
|
||||
- ACCEPT_BACKLOG for predictions that warrant real dev work
|
||||
- DISMISS predictions that are noise, already covered by open issues, or not actionable
|
||||
- Be decisive — the predictor intentionally over-signals; your job is to filter
|
||||
|
||||
Output ONLY the JSON lines — no preamble, no markdown fences."
|
||||
|
||||
TRIAGE_OUTPUT=$(timeout "$CLAUDE_TIMEOUT" claude -p "$TRIAGE_PROMPT" \
|
||||
--model sonnet \
|
||||
2>/dev/null) || {
|
||||
log "ERROR: claude exited with code $? during phase 1.5 — skipping triage"
|
||||
TRIAGE_OUTPUT=""
|
||||
}
|
||||
|
||||
if [ -n "$TRIAGE_OUTPUT" ]; then
|
||||
log "Phase 1.5 claude finished ($(printf '%s' "$TRIAGE_OUTPUT" | wc -c) bytes)"
|
||||
|
||||
# Build valid formula list for validation
|
||||
TRIAGE_VALID_FORMULAS=""
|
||||
if [ -d "$FACTORY_ROOT/formulas" ]; then
|
||||
for _vf in "$FACTORY_ROOT/formulas"/*.toml; do
|
||||
[ -f "$_vf" ] || continue
|
||||
TRIAGE_VALID_FORMULAS="${TRIAGE_VALID_FORMULAS} $(basename "$_vf" .toml)"
|
||||
done
|
||||
fi
|
||||
|
||||
TRIAGE_ACCEPTED=0
|
||||
TRIAGE_DISMISSED=0
|
||||
while IFS= read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
printf '%s' "$line" | jq -e . >/dev/null 2>&1 || continue
|
||||
|
||||
PRED_NUM=$(printf '%s' "$line" | jq -r '.prediction')
|
||||
DECISION=$(printf '%s' "$line" | jq -r '.decision')
|
||||
REASON=$(printf '%s' "$line" | jq -r '.reason // ""')
|
||||
|
||||
case "$DECISION" in
|
||||
ACCEPT_ACTION)
|
||||
A_TITLE=$(printf '%s' "$line" | jq -r '.title')
|
||||
A_FORMULA=$(printf '%s' "$line" | jq -r '.formula // empty')
|
||||
|
||||
# Validate formula against on-disk catalog
|
||||
if [ -n "$A_FORMULA" ] && ! echo "$TRIAGE_VALID_FORMULAS" | grep -qw "$A_FORMULA"; then
|
||||
log "WARN: unknown formula '${A_FORMULA}' for prediction #${PRED_NUM} — falling back to ACCEPT_BACKLOG"
|
||||
A_FORMULA=""
|
||||
fi
|
||||
|
||||
if [ -n "$A_FORMULA" ]; then
|
||||
A_VARS_YAML=$(printf '%s' "$line" | jq -r '
|
||||
.vars // {} | to_entries |
|
||||
map(" " + .key + ": " + (.value | @json)) | join("\n")')
|
||||
|
||||
A_BODY="---
|
||||
formula: ${A_FORMULA}
|
||||
vars:
|
||||
${A_VARS_YAML}
|
||||
---
|
||||
|
||||
Triaged from prediction #${PRED_NUM}.
|
||||
Reason: ${REASON}"
|
||||
else
|
||||
A_BODY="Triaged from prediction #${PRED_NUM}.
|
||||
Reason: ${REASON}"
|
||||
fi
|
||||
|
||||
A_PAYLOAD=$(jq -nc --arg t "$A_TITLE" --arg b "$A_BODY" '{title:$t, body:$b}')
|
||||
if [ -n "$BACKLOG_LABEL_ID_TRIAGE" ]; then
|
||||
A_PAYLOAD=$(printf '%s' "$A_PAYLOAD" | jq --argjson lid "$BACKLOG_LABEL_ID_TRIAGE" '.labels = [$lid]')
|
||||
fi
|
||||
|
||||
A_RESULT=$(codeberg_api POST "/issues" -d "$A_PAYLOAD" 2>/dev/null || true)
|
||||
A_ISSUE=$(printf '%s' "$A_RESULT" | jq -r '.number // "?"' 2>/dev/null || echo "?")
|
||||
log "Created action issue #${A_ISSUE} from prediction #${PRED_NUM} (formula: ${A_FORMULA:-freeform})"
|
||||
matrix_send "planner" "📋 Action #${A_ISSUE} from prediction #${PRED_NUM} [${A_FORMULA:-freeform}]: ${A_TITLE}" 2>/dev/null || true
|
||||
|
||||
ACCEPTED_PREDICTIONS="${ACCEPTED_PREDICTIONS}
|
||||
- Prediction #${PRED_NUM} → action issue #${A_ISSUE} (formula: ${A_FORMULA:-freeform}): ${A_TITLE}"
|
||||
TRIAGE_ACCEPTED=$((TRIAGE_ACCEPTED + 1))
|
||||
|
||||
# Relabel: prediction/unreviewed → prediction/backlog
|
||||
if [ -n "$UNREVIEWED_LABEL_ID" ]; then
|
||||
codeberg_api DELETE "/issues/${PRED_NUM}/labels/${UNREVIEWED_LABEL_ID}" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "$BACKLOG_PRED_LABEL_ID" ]; then
|
||||
codeberg_api POST "/issues/${PRED_NUM}/labels" \
|
||||
-d "$(jq -nc --argjson lid "$BACKLOG_PRED_LABEL_ID" '{labels:[$lid]}')" 2>/dev/null || true
|
||||
fi
|
||||
;;
|
||||
|
||||
ACCEPT_BACKLOG)
|
||||
B_TITLE=$(printf '%s' "$line" | jq -r '.title')
|
||||
B_BODY=$(printf '%s' "$line" | jq -r '.body // ""')
|
||||
B_BODY="${B_BODY}
|
||||
|
||||
Triaged from prediction #${PRED_NUM}.
|
||||
Reason: ${REASON}"
|
||||
|
||||
B_PAYLOAD=$(jq -nc --arg t "$B_TITLE" --arg b "$B_BODY" '{title:$t, body:$b}')
|
||||
if [ -n "$BACKLOG_LABEL_ID_TRIAGE" ]; then
|
||||
B_PAYLOAD=$(printf '%s' "$B_PAYLOAD" | jq --argjson lid "$BACKLOG_LABEL_ID_TRIAGE" '.labels = [$lid]')
|
||||
fi
|
||||
|
||||
B_RESULT=$(codeberg_api POST "/issues" -d "$B_PAYLOAD" 2>/dev/null || true)
|
||||
B_ISSUE=$(printf '%s' "$B_RESULT" | jq -r '.number // "?"' 2>/dev/null || echo "?")
|
||||
log "Created backlog issue #${B_ISSUE} from prediction #${PRED_NUM}"
|
||||
matrix_send "planner" "📋 Backlog #${B_ISSUE} from prediction #${PRED_NUM}: ${B_TITLE}" 2>/dev/null || true
|
||||
|
||||
ACCEPTED_PREDICTIONS="${ACCEPTED_PREDICTIONS}
|
||||
- Prediction #${PRED_NUM} → backlog issue #${B_ISSUE}: ${B_TITLE}"
|
||||
TRIAGE_ACCEPTED=$((TRIAGE_ACCEPTED + 1))
|
||||
|
||||
# Relabel: prediction/unreviewed → prediction/backlog
|
||||
if [ -n "$UNREVIEWED_LABEL_ID" ]; then
|
||||
codeberg_api DELETE "/issues/${PRED_NUM}/labels/${UNREVIEWED_LABEL_ID}" 2>/dev/null || true
|
||||
fi
|
||||
if [ -n "$BACKLOG_PRED_LABEL_ID" ]; then
|
||||
codeberg_api POST "/issues/${PRED_NUM}/labels" \
|
||||
-d "$(jq -nc --argjson lid "$BACKLOG_PRED_LABEL_ID" '{labels:[$lid]}')" 2>/dev/null || true
|
||||
fi
|
||||
;;
|
||||
|
||||
DISMISS)
|
||||
codeberg_api POST "/issues/${PRED_NUM}/comments" \
|
||||
-d "$(jq -nc --arg b "Dismissed by planner triage: ${REASON}" '{body:$b}')" 2>/dev/null || true
|
||||
codeberg_api PATCH "/issues/${PRED_NUM}" \
|
||||
-d '{"state":"closed"}' 2>/dev/null || true
|
||||
log "Dismissed prediction #${PRED_NUM}: ${REASON}"
|
||||
TRIAGE_DISMISSED=$((TRIAGE_DISMISSED + 1))
|
||||
;;
|
||||
|
||||
*)
|
||||
log "WARN: unknown triage decision '${DECISION}' for prediction #${PRED_NUM}"
|
||||
;;
|
||||
esac
|
||||
done <<< "$TRIAGE_OUTPUT"
|
||||
|
||||
log "Phase 1.5 done — accepted: $TRIAGE_ACCEPTED, dismissed: $TRIAGE_DISMISSED"
|
||||
fi
|
||||
else
|
||||
log "No prediction/unreviewed issues found — skipping triage"
|
||||
fi
|
||||
|
||||
# ── Phase 2: Gap analysis ───────────────────────────────────────────────
|
||||
log "Phase 2: gap analysis"
|
||||
|
||||
# Build project state from AGENTS.md tree
|
||||
PROJECT_STATE=""
|
||||
for f in $(find . -name "AGENTS.md" -not -path "./.git/*" | sort); do
|
||||
PROJECT_STATE="${PROJECT_STATE}
|
||||
### ${f}
|
||||
$(cat "$f")
|
||||
"
|
||||
done
|
||||
|
||||
VISION=""
|
||||
[ -f "$VISION_FILE" ] && VISION=$(cat "$VISION_FILE")
|
||||
|
||||
if [ -z "$VISION" ]; then
|
||||
log "No VISION.md found — skipping gap analysis"
|
||||
log "--- Planner done ---"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
RESOURCES=""
|
||||
[ -f "$RESOURCES_FILE" ] && RESOURCES=$(cat "$RESOURCES_FILE")
|
||||
|
||||
# Fetch open issues (all labels)
|
||||
OPEN_ISSUES=$(codeberg_api GET "/issues?state=open&type=issues&limit=50&sort=updated&direction=desc" 2>/dev/null || true)
|
||||
if [ -z "$OPEN_ISSUES" ] || [ "$OPEN_ISSUES" = "null" ]; then
|
||||
log "Failed to fetch open issues"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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)
|
||||
|
||||
# Read supervisor metrics for trend analysis (last 7 days)
|
||||
METRICS_FILE="${FACTORY_ROOT}/metrics/supervisor-metrics.jsonl"
|
||||
METRICS_SUMMARY="(no metrics data — supervisor has not yet written metrics)"
|
||||
if [ -f "$METRICS_FILE" ] && [ -s "$METRICS_FILE" ]; then
|
||||
_METRICS_CUTOFF=$(date -u -d '7 days ago' +%Y-%m-%dT%H:%M)
|
||||
METRICS_SUMMARY=$(jq -c --arg cutoff "$_METRICS_CUTOFF" 'select(.ts >= $cutoff)' \
|
||||
"$METRICS_FILE" 2>/dev/null | \
|
||||
jq -rs --arg proj "${PROJECT_NAME:-}" '
|
||||
( [.[] | select(.type=="ci" and .project==$proj) | .duration_min] | if length>0 then add/length|round else null end ) as $ci_avg |
|
||||
( [.[] | select(.type=="ci" and .project==$proj) | select(.status=="success")] | length ) as $ci_ok |
|
||||
( [.[] | select(.type=="ci" and .project==$proj)] | length ) as $ci_n |
|
||||
( [.[] | select(.type=="infra") | .ram_used_pct] | if length>0 then add/length|round else null end ) as $ram_avg |
|
||||
( [.[] | select(.type=="infra") | .disk_used_pct] | if length>0 then add/length|round else null end ) as $disk_avg |
|
||||
( [.[] | select(.type=="dev" and .project==$proj)] | last ) as $dev_last |
|
||||
"CI (\($ci_n) pipelines): avg \(if $ci_avg then "\($ci_avg)min" else "n/a" end), success rate \(if $ci_n > 0 then "\($ci_ok * 100 / $ci_n | round)%" else "n/a" end)\n" +
|
||||
"Infra: avg RAM \(if $ram_avg then "\($ram_avg)%" else "n/a" end) used, avg disk \(if $disk_avg then "\($disk_avg)%" else "n/a" end) used\n" +
|
||||
"Dev (latest): \(if $dev_last then "\($dev_last.issues_in_backlog) in backlog, \($dev_last.issues_blocked) blocked (\(if $dev_last.issues_in_backlog > 0 then $dev_last.issues_blocked * 100 / $dev_last.issues_in_backlog | round else 0 end)% blocked), \($dev_last.pr_open) open PRs" else "n/a" end)
|
||||
' 2>/dev/null) || METRICS_SUMMARY="(metrics parse error)"
|
||||
log "Metrics: ${METRICS_SUMMARY:0:120}"
|
||||
fi
|
||||
|
||||
# ── Build formula catalog ──────────────────────────────────────────────
|
||||
FORMULA_DIR="$FACTORY_ROOT/formulas"
|
||||
FORMULA_CATALOG=""
|
||||
if [ -d "$FORMULA_DIR" ]; then
|
||||
for formula_file in "$FORMULA_DIR"/*.toml; do
|
||||
[ -f "$formula_file" ] || continue
|
||||
formula_name=$(basename "$formula_file" .toml)
|
||||
FORMULA_CATALOG="${FORMULA_CATALOG}
|
||||
--- ${formula_name} ---
|
||||
$(cat "$formula_file")
|
||||
"
|
||||
done
|
||||
fi
|
||||
|
||||
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}
|
||||
|
||||
## Current project state (AGENTS.md tree)
|
||||
${PROJECT_STATE}
|
||||
|
||||
## RESOURCES.md (shared factory infrastructure)
|
||||
${RESOURCES:-"(not found — copy RESOURCES.example.md to RESOURCES.md and fill in your infrastructure)"}
|
||||
|
||||
## Vision-labeled issues (goal anchors)
|
||||
${VISION_ISSUES:-"(none)"}
|
||||
|
||||
## All open issues
|
||||
${OPEN_SUMMARY}
|
||||
|
||||
## Recently accepted predictions (from triage)
|
||||
${ACCEPTED_PREDICTIONS:-"(none — no predictions were triaged this cycle)"}
|
||||
|
||||
## Operational metrics (last 7 days from supervisor)
|
||||
${METRICS_SUMMARY}
|
||||
|
||||
## Available formulas (from formulas/ directory)
|
||||
When a gap maps directly to one of these formula types, emit a formula instance
|
||||
instead of a freeform issue. Only use a formula when the gap clearly fits —
|
||||
do not force-fit gaps into formulas. Fill in the formula vars with concrete values.
|
||||
|
||||
${FORMULA_CATALOG:-"(no formulas available)"}
|
||||
|
||||
## Task
|
||||
Identify gaps — things implied by VISION.md that are neither reflected in the project state nor covered by an existing open issue.
|
||||
When a gap involves deploying, hosting, or operating a service, reference the specific resource alias from RESOURCES.md (e.g. \"deploy to <host-alias>\") so issues are actionable.
|
||||
|
||||
For each gap, output a JSON object (one per line, no array wrapper).
|
||||
|
||||
If a gap matches an available formula, emit a formula instance:
|
||||
{\"title\": \"action-oriented title\", \"formula\": \"<formula-name>\", \"vars\": {\"var1\": \"value1\", ...}, \"body\": \"why this matters\", \"depends\": [...]}
|
||||
|
||||
Otherwise, emit a freeform issue:
|
||||
{\"title\": \"action-oriented title\", \"body\": \"problem statement + why it matters + rough approach\", \"depends\": [...]}
|
||||
|
||||
## Rules
|
||||
- Max 5 new issues — focus on highest-leverage gaps only
|
||||
- Do NOT create issues for things already documented in AGENTS.md
|
||||
- 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 body should explain: what's missing, why it matters for the vision, rough approach
|
||||
- Reference blocking issues by number in depends array
|
||||
- Prefer formula instances when a gap clearly fits a known formula — but freeform is fine when none matches
|
||||
- When metrics indicate a systemic problem conflicting with VISION.md (slow CI, high blocked ratio, disk pressure), create an optimization issue even if not explicitly in VISION.md
|
||||
|
||||
If there are no gaps, output exactly: NO_GAPS
|
||||
|
||||
Output ONLY the JSON lines (or NO_GAPS) — no preamble, no markdown fences."
|
||||
|
||||
PHASE2_OUTPUT=$(timeout "$CLAUDE_TIMEOUT" claude -p "$PHASE2_PROMPT" \
|
||||
--model sonnet \
|
||||
2>/dev/null) || {
|
||||
log "ERROR: claude exited with code $? during phase 2"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if echo "$PHASE2_OUTPUT" | grep -q "NO_GAPS"; then
|
||||
log "No gaps found — backlog is aligned with vision"
|
||||
log "--- Planner done ---"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Create issues from gap analysis ──────────────────────────────────────
|
||||
# Find backlog label ID
|
||||
BACKLOG_LABEL_ID=$(codeberg_api GET "/labels" 2>/dev/null | \
|
||||
jq -r '.[] | select(.name == "backlog") | .id' 2>/dev/null || true)
|
||||
|
||||
# Build list of valid formula names from disk for validation
|
||||
VALID_FORMULAS=""
|
||||
if [ -d "$FORMULA_DIR" ]; then
|
||||
for _vf in "$FORMULA_DIR"/*.toml; do
|
||||
[ -f "$_vf" ] || continue
|
||||
VALID_FORMULAS="${VALID_FORMULAS} $(basename "$_vf" .toml)"
|
||||
done
|
||||
fi
|
||||
|
||||
CREATED=0
|
||||
while IFS= read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
# Skip non-JSON lines
|
||||
echo "$line" | jq -e . >/dev/null 2>&1 || continue
|
||||
|
||||
TITLE=$(echo "$line" | jq -r '.title')
|
||||
DEPS=$(echo "$line" | jq -r '.depends // [] | map("#\(.)") | join(", ")')
|
||||
|
||||
# Check if this is a formula instance and validate against on-disk catalog
|
||||
FORMULA_NAME=$(echo "$line" | jq -r '.formula // empty')
|
||||
if [ -n "$FORMULA_NAME" ]; then
|
||||
if ! echo "$VALID_FORMULAS" | grep -qw "$FORMULA_NAME"; then
|
||||
log "WARN: Claude emitted unknown formula '${FORMULA_NAME}' — falling back to freeform"
|
||||
FORMULA_NAME=""
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$FORMULA_NAME" ]; then
|
||||
# Build YAML front matter for formula instance (values quoted for safety)
|
||||
# Note: formula label is NOT applied — dev-agent does not yet support
|
||||
# formula dispatch, so adding the label would block the dev pipeline.
|
||||
# The YAML front matter is enough structure for future formula dispatch.
|
||||
VARS_YAML=$(echo "$line" | jq -r '
|
||||
.vars // {} | to_entries |
|
||||
map(" " + .key + ": " + (.value | @json)) | join("\n")')
|
||||
EXTRA_BODY=$(echo "$line" | jq -r '.body // ""')
|
||||
BODY="---
|
||||
formula: ${FORMULA_NAME}
|
||||
vars:
|
||||
${VARS_YAML}
|
||||
---
|
||||
|
||||
${EXTRA_BODY}"
|
||||
else
|
||||
BODY=$(echo "$line" | jq -r '.body')
|
||||
fi
|
||||
|
||||
# Add dependency section if present
|
||||
if [ -n "$DEPS" ] && [ "$DEPS" != "" ]; then
|
||||
BODY="${BODY}
|
||||
|
||||
## Depends on
|
||||
${DEPS}"
|
||||
fi
|
||||
|
||||
# Create issue (backlog label only — no formula label until dev-agent supports dispatch)
|
||||
CREATE_PAYLOAD=$(jq -nc --arg t "$TITLE" --arg b "$BODY" '{title:$t, body:$b}')
|
||||
|
||||
if [ -n "$BACKLOG_LABEL_ID" ]; then
|
||||
CREATE_PAYLOAD=$(echo "$CREATE_PAYLOAD" | jq --argjson lid "$BACKLOG_LABEL_ID" '.labels = [$lid]')
|
||||
fi
|
||||
|
||||
RESULT=$(codeberg_api POST "/issues" -d "$CREATE_PAYLOAD" 2>/dev/null || true)
|
||||
ISSUE_NUM=$(echo "$RESULT" | jq -r '.number // "?"' 2>/dev/null || echo "?")
|
||||
|
||||
if [ -n "$FORMULA_NAME" ]; then
|
||||
log "Created #${ISSUE_NUM} (formula:${FORMULA_NAME}): ${TITLE}"
|
||||
matrix_send "planner" "📋 Formula issue #${ISSUE_NUM} [${FORMULA_NAME}]: ${TITLE}" 2>/dev/null || true
|
||||
else
|
||||
log "Created #${ISSUE_NUM}: ${TITLE}"
|
||||
matrix_send "planner" "📋 Gap issue #${ISSUE_NUM}: ${TITLE}" 2>/dev/null || true
|
||||
fi
|
||||
CREATED=$((CREATED + 1))
|
||||
|
||||
[ "$CREATED" -ge 5 ] && break
|
||||
done <<< "$PHASE2_OUTPUT"
|
||||
|
||||
log "Phase 2 done — created $CREATED issues"
|
||||
log "--- Planner done ---"
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# planner-poll.sh — Cron wrapper for planner-agent
|
||||
# planner-poll.sh — Cron wrapper: files action issue for run-planner formula
|
||||
#
|
||||
# Runs weekly (or on-demand). Guards against concurrent runs and low memory.
|
||||
# Files an action issue referencing formulas/run-planner.toml; the action-agent
|
||||
# picks it up and executes the planning steps in an interactive Claude session.
|
||||
# =============================================================================
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -38,14 +40,53 @@ fi
|
|||
|
||||
log "--- Planner poll start ---"
|
||||
|
||||
# ── Run planner agent ─────────────────────────────────────────────────────
|
||||
"$SCRIPT_DIR/planner-agent.sh" 2>&1 | while IFS= read -r line; do
|
||||
log " $line"
|
||||
done
|
||||
|
||||
EXIT_CODE=${PIPESTATUS[0]}
|
||||
if [ "$EXIT_CODE" -ne 0 ]; then
|
||||
log "poll: planner-agent exited with code $EXIT_CODE"
|
||||
# ── Dedup: skip if an open run-planner action issue already exists ────────
|
||||
OPEN_ACTIONS=$(codeberg_api GET "/issues?state=open&type=issues&labels=action&limit=50" 2>/dev/null || true)
|
||||
if [ -n "$OPEN_ACTIONS" ] && [ "$OPEN_ACTIONS" != "null" ]; then
|
||||
EXISTING=$(printf '%s' "$OPEN_ACTIONS" | \
|
||||
jq '[.[] | select(.title | test("run-planner"))] | length' 2>/dev/null || echo 0)
|
||||
if [ "${EXISTING:-0}" -gt 0 ]; then
|
||||
log "poll: open run-planner action issue already exists — skipping"
|
||||
log "--- Planner poll done ---"
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Fetch 'action' label ID ──────────────────────────────────────────────
|
||||
ACTION_LABEL_ID=$(codeberg_api GET "/labels" 2>/dev/null | \
|
||||
jq -r '.[] | select(.name == "action") | .id' 2>/dev/null || true)
|
||||
|
||||
if [ -z "$ACTION_LABEL_ID" ]; then
|
||||
log "ERROR: 'action' label not found — cannot file planner issue"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── File action issue ─────────────────────────────────────────────────────
|
||||
ISSUE_BODY="---
|
||||
formula: run-planner
|
||||
---
|
||||
|
||||
Periodic strategic planning run. The action-agent reads \`formulas/run-planner.toml\`
|
||||
and executes the five phases: preflight, AGENTS.md update, prediction triage,
|
||||
strategic planning (resource+leverage gap analysis), and memory update.
|
||||
|
||||
Filed automatically by \`planner-poll.sh\`."
|
||||
|
||||
PAYLOAD=$(jq -nc \
|
||||
--arg title "action: run-planner — periodic strategic planning" \
|
||||
--arg body "$ISSUE_BODY" \
|
||||
--argjson labels "[$ACTION_LABEL_ID]" \
|
||||
'{title: $title, body: $body, labels: $labels}')
|
||||
|
||||
RESULT=$(codeberg_api POST "/issues" -d "$PAYLOAD" 2>/dev/null || true)
|
||||
ISSUE_NUM=$(printf '%s' "$RESULT" | jq -r '.number // empty' 2>/dev/null || true)
|
||||
|
||||
if [ -z "$ISSUE_NUM" ]; then
|
||||
log "ERROR: failed to create action issue for run-planner"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Filed action issue #${ISSUE_NUM} for run-planner formula"
|
||||
matrix_send "planner" "Filed action #${ISSUE_NUM}: run-planner — periodic strategic planning" 2>/dev/null || true
|
||||
|
||||
log "--- Planner poll done ---"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue