Oppdatert run-next-task.sh for filbaserte tasks
Plukker fra tasks/*.md, flytter til active/, done/ ved fullføring. Støtter --loop (kjør kontinuerlig), --dry (forhåndsvis), --status. Krasj-deteksjon: stale oppgaver i active/ >60 min frigjøres. 30 min timeout per oppgave. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b7de73d5d5
commit
a9590e4ed9
1 changed files with 134 additions and 288 deletions
|
|
@ -1,314 +1,160 @@
|
|||
#!/usr/bin/env bash
|
||||
# Plukker neste ugjorte oppgave fra tasks.md og starter en Claude Code-sesjon.
|
||||
# Hopper over oppgaver som er pågående [~], har åpent spørsmål [?], eller
|
||||
# er blokkert [!], inkludert alle oppgaver som avhenger av dem.
|
||||
#
|
||||
# Støtter parallell kjøring: markerer oppgaven som [~] (pågår) før start,
|
||||
# setter den til [x] eller tilbake til [ ] avhengig av resultat.
|
||||
#
|
||||
# Plukk neste oppgave fra tasks/ og kjør med Claude Code.
|
||||
# Bruk:
|
||||
# ./scripts/run-next-task.sh # kjør neste oppgave
|
||||
# ./scripts/run-next-task.sh --dry # vis hvilken oppgave som er neste
|
||||
# ./scripts/run-next-task.sh --status # vis status for alle oppgaver
|
||||
# ./scripts/run-next-task.sh --unstale # frigjør [~] oppgaver eldre enn 60 min
|
||||
# ./scripts/run-next-task.sh # kjør én oppgave
|
||||
# ./scripts/run-next-task.sh --loop # kjør oppgaver i loop
|
||||
# ./scripts/run-next-task.sh --dry # vis neste uten å kjøre
|
||||
# ./scripts/run-next-task.sh --status # vis status
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
TASKS="$ROOT/tasks.md"
|
||||
TASKS="$ROOT/tasks"
|
||||
ACTIVE="$TASKS/active"
|
||||
DONE="$TASKS/done"
|
||||
LOGS="$ROOT/logs"
|
||||
LOCKFILE="/tmp/synops-task-runner.lock"
|
||||
|
||||
# --- Avhengighetskart: fase → faser den avhenger av ---
|
||||
declare -A PHASE_DEPS=(
|
||||
[1]=""
|
||||
[2]="1"
|
||||
[3]="2"
|
||||
[4]="2"
|
||||
[5]="3 4"
|
||||
[6]="2"
|
||||
[7]="6"
|
||||
[8]="5"
|
||||
[9]="3"
|
||||
[10]="2"
|
||||
[11]="5 6 7"
|
||||
[12]="1 2 3 4 5 6 7 8 9 10 11 13 14 15 16 17 18 19 20 21"
|
||||
[13]="3 4"
|
||||
[14]="6 13"
|
||||
[15]="3 10"
|
||||
[16]="11 13"
|
||||
[17]=""
|
||||
[18]="10 13"
|
||||
[19]="3 13"
|
||||
[20]="19"
|
||||
[21]="2"
|
||||
[22]="12"
|
||||
[23]="22"
|
||||
[24]="23"
|
||||
[25]="24"
|
||||
[26]="25"
|
||||
[27]="25"
|
||||
[28]="25"
|
||||
[29]="25"
|
||||
[30]="25"
|
||||
)
|
||||
mkdir -p "$ACTIVE" "$DONE" "$LOGS"
|
||||
|
||||
# --- Finn blokkerte faser ---
|
||||
# En fase er blokkert hvis den har en [?], [!] eller [~] oppgave
|
||||
blocked_phases() {
|
||||
local phases=""
|
||||
for phase in $(seq 1 21); do
|
||||
if grep -qP "^\- \[(\?|!|~)\] ${phase}\." "$TASKS" 2>/dev/null; then
|
||||
phases="$phases $phase"
|
||||
# Lås — hindrer dobbeltstart
|
||||
if [ -f "$LOCKFILE" ]; then
|
||||
PID=$(cat "$LOCKFILE" 2>/dev/null || echo "")
|
||||
if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then
|
||||
echo "Allerede en task-runner aktiv (PID $PID)"
|
||||
exit 0
|
||||
fi
|
||||
rm -f "$LOCKFILE"
|
||||
fi
|
||||
echo $$ > "$LOCKFILE"
|
||||
trap 'rm -f "$LOCKFILE"' EXIT
|
||||
|
||||
# --- Krasj-deteksjon: frigjør stale oppgaver ---
|
||||
unstale() {
|
||||
for f in "$ACTIVE"/*.md; do
|
||||
[ -f "$f" ] || continue
|
||||
age_min=$(( ($(date +%s) - $(stat -c %Y "$f")) / 60 ))
|
||||
if [ "$age_min" -gt 60 ]; then
|
||||
echo "Frigjør stale oppgave: $(basename "$f") ($age_min min)"
|
||||
mv "$f" "$TASKS/"
|
||||
fi
|
||||
done
|
||||
echo "$phases"
|
||||
}
|
||||
|
||||
# Sjekk om en fase er tilgjengelig
|
||||
phase_available() {
|
||||
local phase=$1
|
||||
local blocked="$2"
|
||||
# --- Status ---
|
||||
if [[ "${1:-}" == "--status" ]]; then
|
||||
todo=$(ls "$TASKS"/*.md 2>/dev/null | grep -v README | wc -l)
|
||||
active=$(ls "$ACTIVE"/*.md 2>/dev/null | wc -l)
|
||||
done_count=$(ls "$DONE"/*.md 2>/dev/null | wc -l)
|
||||
echo "=== Synops oppgavestatus ==="
|
||||
echo "Gjenstår: $todo"
|
||||
echo "Aktiv: $active"
|
||||
echo "Fullført: $done_count"
|
||||
if [ "$active" -gt 0 ]; then
|
||||
echo ""
|
||||
echo "--- Aktive ---"
|
||||
ls "$ACTIVE"/*.md 2>/dev/null | xargs -I{} basename {}
|
||||
fi
|
||||
echo ""
|
||||
echo "--- Neste 5 ---"
|
||||
ls "$TASKS"/*.md 2>/dev/null | grep -v README | sort | head -5 | xargs -I{} basename {}
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Sjekk om denne fasen selv er blokkert
|
||||
if echo "$blocked" | grep -qw "$phase"; then
|
||||
# --- Kjør én oppgave ---
|
||||
run_one() {
|
||||
unstale
|
||||
|
||||
TASK=$(ls "$TASKS"/*.md 2>/dev/null | grep -v README | sort | head -1)
|
||||
if [ -z "$TASK" ]; then
|
||||
echo "Ingen oppgaver å gjøre"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Sjekk om avhengige faser er blokkert eller har ugjorte oppgaver
|
||||
local deps="${PHASE_DEPS[$phase]}"
|
||||
for dep in $deps; do
|
||||
if echo "$blocked" | grep -qw "$dep"; then
|
||||
return 1
|
||||
fi
|
||||
# Sjekk at alle oppgaver i avhengig fase er ferdige
|
||||
if grep -qP "^\- \[ \] ${dep}\." "$TASKS" 2>/dev/null; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
TASKNAME=$(basename "$TASK")
|
||||
echo "=== Plukker: $TASKNAME ==="
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Sjekk at forrige oppgave i samme fase er ferdig
|
||||
prev_task_done() {
|
||||
local task_id=$1 # f.eks. "2.4"
|
||||
local phase=$(echo "$task_id" | cut -d. -f1)
|
||||
local num=$(echo "$task_id" | cut -d. -f2)
|
||||
|
||||
if [[ "$num" == "1" ]]; then
|
||||
return 0 # Første oppgave i fasen, ingen forrige
|
||||
fi
|
||||
|
||||
local prev_num=$((num - 1))
|
||||
local prev_id="${phase}.${prev_num}"
|
||||
|
||||
# Forrige oppgave må være [x]
|
||||
if grep -qP "^\- \[x\] ${prev_id} " "$TASKS" 2>/dev/null; then
|
||||
if [[ "${1:-}" == "--dry" ]]; then
|
||||
cat "$TASK"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
|
||||
# Flytt til active
|
||||
mv "$TASK" "$ACTIVE/$TASKNAME"
|
||||
|
||||
TASK_CONTENT=$(cat "$ACTIVE/$TASKNAME")
|
||||
|
||||
cd "$ROOT"
|
||||
git pull origin main 2>/dev/null || true
|
||||
|
||||
# Kjør Claude Code
|
||||
LOGFILE="$LOGS/task-$(date +%Y%m%d-%H%M)-$TASKNAME.log"
|
||||
|
||||
timeout 1800 claude -p "
|
||||
Du har fått denne oppgaven:
|
||||
|
||||
$TASK_CONTENT
|
||||
|
||||
Arbeidsmappe: $ROOT
|
||||
|
||||
Arbeidsflyt:
|
||||
1. Les CLAUDE.md for prosjektkontekst
|
||||
2. Les refererte docs/proposals
|
||||
3. Implementer
|
||||
4. Verifiser (cargo check, npm run build)
|
||||
5. Commit og push
|
||||
6. Rapporter hva du gjorde
|
||||
|
||||
Regler:
|
||||
- Les relevante filer før du endrer dem
|
||||
- Gjør minimale, fokuserte endringer
|
||||
- Push jevnlig underveis
|
||||
" --dangerously-skip-permissions 2>&1 | tee "$LOGFILE"
|
||||
|
||||
EXIT_CODE=${PIPESTATUS[0]}
|
||||
|
||||
if [ "$EXIT_CODE" -eq 0 ]; then
|
||||
mv "$ACTIVE/$TASKNAME" "$DONE/$(date +%Y-%m-%d)-$TASKNAME"
|
||||
echo "=== Fullført: $TASKNAME ==="
|
||||
# Commit task-flytt
|
||||
cd "$ROOT"
|
||||
git add -A tasks/ && git commit -m "Task fullført: $TASKNAME" --no-verify 2>/dev/null || true
|
||||
git push origin main 2>/dev/null || true
|
||||
return 0
|
||||
else
|
||||
mv "$ACTIVE/$TASKNAME" "$TASKS/$TASKNAME"
|
||||
echo "=== Feilet (exit $EXIT_CODE): $TASKNAME ==="
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# --- Frigjør stale [~] oppgaver ---
|
||||
if [[ "${1:-}" == "--unstale" ]]; then
|
||||
echo "Sjekker etter stale [~] oppgaver (>60 min)..."
|
||||
now=$(date +%s)
|
||||
changed=false
|
||||
while IFS= read -r line; do
|
||||
# Hent timestamp fra linjen under (> Påbegynt: 2026-03-17T14:30)
|
||||
line_num=$(echo "$line" | cut -d: -f1)
|
||||
next_line=$((line_num + 1))
|
||||
ts=$(sed -n "${next_line}p" "$TASKS" | grep -oP '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}' || echo "")
|
||||
if [[ -n "$ts" ]]; then
|
||||
started=$(date -d "$ts" +%s 2>/dev/null || echo 0)
|
||||
elapsed=$(( (now - started) / 60 ))
|
||||
if [[ $elapsed -gt 60 ]]; then
|
||||
task_text=$(echo "$line" | cut -d: -f2-)
|
||||
echo " Frigjør (${elapsed} min gammel): $task_text"
|
||||
sed -i "${line_num}s/^\- \[~\]/- [ ]/" "$TASKS"
|
||||
sed -i "${next_line}d" "$TASKS" # Fjern påbegynt-linjen
|
||||
changed=true
|
||||
fi
|
||||
fi
|
||||
done < <(grep -n '^\- \[~\]' "$TASKS")
|
||||
if $changed; then
|
||||
echo "Oppdatert. Commit manuelt om ønsket."
|
||||
else
|
||||
echo "Ingen stale oppgaver funnet."
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Status-visning ---
|
||||
if [[ "${1:-}" == "--status" ]]; then
|
||||
echo "=== Synops oppgavestatus ==="
|
||||
echo ""
|
||||
done=$(grep -cP '^\- \[x\]' "$TASKS" || true)
|
||||
todo=$(grep -cP '^\- \[ \]' "$TASKS" || true)
|
||||
in_progress=$(grep -cP '^\- \[~\]' "$TASKS" || true)
|
||||
questions=$(grep -cP '^\- \[\?\]' "$TASKS" || true)
|
||||
blocked_count=$(grep -cP '^\- \[!\]' "$TASKS" || true)
|
||||
done=${done:-0}; todo=${todo:-0}; in_progress=${in_progress:-0}
|
||||
questions=${questions:-0}; blocked_count=${blocked_count:-0}
|
||||
total=$((done + todo + in_progress + questions + blocked_count))
|
||||
echo "Ferdige: $done / $total"
|
||||
echo "Pågår: $in_progress"
|
||||
echo "Gjenstår: $todo"
|
||||
echo "Spørsmål: $questions"
|
||||
echo "Blokkert: $blocked_count"
|
||||
echo ""
|
||||
if [[ $in_progress -gt 0 ]]; then
|
||||
echo "--- Pågår ---"
|
||||
grep -A1 '^\- \[~\]' "$TASKS" || true
|
||||
echo ""
|
||||
fi
|
||||
if [[ $questions -gt 0 ]]; then
|
||||
echo "--- Åpne spørsmål ---"
|
||||
grep -A2 '^\- \[\?\]' "$TASKS" || true
|
||||
echo ""
|
||||
fi
|
||||
if [[ $blocked_count -gt 0 ]]; then
|
||||
echo "--- Blokkerte oppgaver ---"
|
||||
grep -A2 '^\- \[!\]' "$TASKS" || true
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Finn neste oppgave ---
|
||||
blocked=$(blocked_phases)
|
||||
|
||||
next_task=""
|
||||
line_num=""
|
||||
task_text=""
|
||||
task_id=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
num=$(echo "$line" | cut -d: -f1)
|
||||
text=$(echo "$line" | cut -d: -f2- | sed 's/^- \[ \] //')
|
||||
# Hent oppgave-ID (f.eks. "1.3")
|
||||
id=$(echo "$text" | grep -oP '^\d+\.\d+' || echo "")
|
||||
phase=$(echo "$text" | grep -oP '^\d+' || echo "")
|
||||
|
||||
if [[ -n "$phase" ]] && phase_available "$phase" "$blocked" && prev_task_done "$id"; then
|
||||
next_task="$line"
|
||||
line_num="$num"
|
||||
task_text="$text"
|
||||
task_id="$id"
|
||||
break
|
||||
fi
|
||||
done < <(grep -n '^\- \[ \]' "$TASKS")
|
||||
|
||||
if [[ -z "$next_task" ]]; then
|
||||
if [[ -n "$blocked" ]]; then
|
||||
echo "Ingen tilgjengelige oppgaver. Blokkerte/pågående faser:$blocked"
|
||||
echo "Kjør --status for detaljer."
|
||||
else
|
||||
echo "Alle oppgaver er gjort!"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Neste oppgave (linje $line_num):"
|
||||
echo " $task_text"
|
||||
echo ""
|
||||
|
||||
# --- Dry run ---
|
||||
if [[ "${1:-}" == "--dry" ]]; then
|
||||
unstale
|
||||
TASK=$(ls "$TASKS"/*.md 2>/dev/null | grep -v README | sort | head -1)
|
||||
if [ -z "$TASK" ]; then
|
||||
echo "Ingen oppgaver"
|
||||
else
|
||||
echo "Neste: $(basename "$TASK")"
|
||||
echo "---"
|
||||
cat "$TASK"
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Marker oppgaven som pågående ---
|
||||
timestamp=$(date +%Y-%m-%dT%H:%M)
|
||||
sed -i "${line_num}s/^\- \[ \]/- [~]/" "$TASKS"
|
||||
sed -i "${line_num}a\\ > Påbegynt: ${timestamp}" "$TASKS"
|
||||
cd "$ROOT"
|
||||
git add tasks.md
|
||||
git commit -m "Starter oppgave ${task_id}" --no-verify 2>/dev/null || true
|
||||
git push origin main 2>/dev/null || true
|
||||
|
||||
# --- Bygg prompt ---
|
||||
PROMPT="Du skal implementere neste oppgave i Synops-prosjektet.
|
||||
Du jobber autonomt — ingen bruker er tilgjengelig for spørsmål underveis.
|
||||
|
||||
## Oppgave
|
||||
$task_text
|
||||
|
||||
## Arbeidsflyt
|
||||
|
||||
1. **Orienter deg.** Les \`CLAUDE.md\` for prosjektkontekst. Les dokumentene
|
||||
som refereres i oppgaven. Les \`tasks.md\` for å forstå hvor prosjektet står
|
||||
(hvilke oppgaver er ferdige, hva er tilgjengelig).
|
||||
2. **Pull siste endringer.** Kjør \`git pull origin main\` først — andre
|
||||
agenter kan ha pushet endringer.
|
||||
3. **Implementer.** Skriv kode. Du kjører direkte på serveren — bygg, test og deploy her.
|
||||
4. **Verifiser.** Kompilering, curl-test, kjør relevante tester.
|
||||
5. **Oppdater dokumentasjon.** Hvis implementeringen avviker fra eksisterende
|
||||
docs, oppdater dem. Nye tekniske beslutninger dokumenteres i relevante
|
||||
filer under \`docs/\`. Docs skal alltid reflektere faktisk tilstand.
|
||||
6. **Oppdater tasks.md.** Endre \`- [~]\` til \`- [x]\` for oppgave ${task_id}.
|
||||
Fjern \`> Påbegynt: ...\`-linjen under oppgaven.
|
||||
7. **Commit og push.** Commit alle endringer (kode + docs + tasks.md) med
|
||||
en beskrivende melding. Push til forgejo (\`git push origin main\`).
|
||||
Flere commits underveis er OK — push jevnlig så andre agenter ser fremgangen.
|
||||
|
||||
## Hvis noe blokkerer
|
||||
|
||||
Hvis du trenger avklaring fra Vegard:
|
||||
- Endre oppgavens status fra \`- [~]\` til \`- [?]\` i tasks.md
|
||||
- Fjern påbegynt-linjen, legg til spørsmålet:
|
||||
\`\`\`
|
||||
- [?] ${task_id} ...
|
||||
> Spørsmål: <ditt spørsmål>
|
||||
> Kontekst: <hvorfor dette er uklart>
|
||||
\`\`\`
|
||||
- Commit og push. Avslutt sesjonen.
|
||||
|
||||
Hvis du støter på et teknisk problem du ikke kan løse:
|
||||
- Endre status fra \`- [~]\` til \`- [!]\` i tasks.md
|
||||
- Dokumenter problemet. Commit og push. Avslutt.
|
||||
|
||||
## Regler
|
||||
|
||||
- Jobb kun på oppgave ${task_id}. Ikke start på neste.
|
||||
- Ikke deploy til produksjon uten eksplisitt godkjenning.
|
||||
- Følg eksisterende arkitektur og konvensjoner i \`docs/\`.
|
||||
- Hold det enkelt — minimum viable for oppgaven.
|
||||
- Dokumentasjon er like viktig som kode. Neste sesjon har ingen kontekst
|
||||
fra denne — alt den vet kommer fra kode, docs og git-historikk.
|
||||
- Skriv commit-meldinger som forklarer *hvorfor*, ikke bare *hva*.
|
||||
- Push jevnlig underveis — ikke samle opp alt til slutt.
|
||||
- Du kan spinne opp subagenter (Agent-tool) for parallelt arbeid der det
|
||||
er egnet — f.eks. research i docs, utforske kodebasen, eller kjøre
|
||||
uavhengige deloppgaver samtidig. Bruk det aktivt for å jobbe effektivt."
|
||||
|
||||
# --- Velg modell og effort basert på fase ---
|
||||
CLAUDE_ARGS="--dangerously-skip-permissions"
|
||||
phase_num="${task_id%%.*}"
|
||||
case "$phase_num" in
|
||||
23) # Validering krever grundig gjennomgang
|
||||
CLAUDE_ARGS="$CLAUDE_ARGS --model claude-opus-4-6"
|
||||
echo "Fase 23 (validering): bruker Opus med høy effort"
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Starter Claude Code-sesjon for oppgave ${task_id}..."
|
||||
cd "$ROOT"
|
||||
claude -p $CLAUDE_ARGS "$PROMPT"
|
||||
exit_code=$?
|
||||
|
||||
# --- Hvis claude krasjer, sett oppgaven tilbake til [ ] ---
|
||||
if [[ $exit_code -ne 0 ]]; then
|
||||
echo "Claude-sesjonen feilet (exit code $exit_code). Tilbakestiller oppgave ${task_id}."
|
||||
# Sjekk om oppgaven fortsatt er [~] (ikke allerede endret av sesjonen)
|
||||
if grep -qP "^\- \[~\] ${task_id} " "$TASKS"; then
|
||||
sed -i "/^\- \[~\] ${task_id} /s/^\- \[~\]/- [ ]/" "$TASKS"
|
||||
# Fjern påbegynt-linjen
|
||||
line_after=$(grep -n "^\- \[ \] ${task_id} " "$TASKS" | cut -d: -f1)
|
||||
if [[ -n "$line_after" ]]; then
|
||||
next=$((line_after + 1))
|
||||
if sed -n "${next}p" "$TASKS" | grep -q '> Påbegynt:'; then
|
||||
sed -i "${next}d" "$TASKS"
|
||||
fi
|
||||
# --- Loop-modus ---
|
||||
if [[ "${1:-}" == "--loop" ]]; then
|
||||
echo "=== Task runner loop startet ($(date)) ==="
|
||||
while true; do
|
||||
if ! run_one; then
|
||||
echo "Ingen flere oppgaver eller feil. Venter 5 min..."
|
||||
sleep 300
|
||||
else
|
||||
echo "Venter 30 sek før neste oppgave..."
|
||||
sleep 30
|
||||
fi
|
||||
git add tasks.md
|
||||
git commit -m "Tilbakestill oppgave ${task_id} etter feilet sesjon" --no-verify 2>/dev/null || true
|
||||
git push origin main 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# --- Enkel kjøring ---
|
||||
run_one
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue