From a9590e4ed9ed2576805f0ea2db2b049c60ffb424 Mon Sep 17 00:00:00 2001 From: vegard Date: Thu, 19 Mar 2026 18:02:09 +0000 Subject: [PATCH] Oppdatert run-next-task.sh for filbaserte tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- scripts/run-next-task.sh | 422 +++++++++++++-------------------------- 1 file changed, 134 insertions(+), 288 deletions(-) diff --git a/scripts/run-next-task.sh b/scripts/run-next-task.sh index 13e5e5e..8ec9456 100755 --- a/scripts/run-next-task.sh +++ b/scripts/run-next-task.sh @@ -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: - > Kontekst: - \`\`\` -- 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