diff --git a/scripts/run-next-task.sh b/scripts/run-next-task.sh index 4f8a325..b96b129 100755 --- a/scripts/run-next-task.sh +++ b/scripts/run-next-task.sh @@ -1,12 +1,16 @@ #!/usr/bin/env bash # Plukker neste ugjorte oppgave fra tasks.md og starter en Claude Code-sesjon. -# Hopper over oppgaver med [?] (åpent spørsmål) eller [!] (blokkert), -# og oppgaver som avhenger av blokkerte faser. +# 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. # # 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 # 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 set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" @@ -29,20 +33,18 @@ declare -A PHASE_DEPS=( ) # --- Finn blokkerte faser --- -# En fase er blokkert hvis den har en [?] eller [!] oppgave +# En fase er blokkert hvis den har en [?], [!] eller [~] oppgave blocked_phases() { local phases="" for phase in $(seq 1 12); do - # Sjekk om fasen har blokkerte oppgaver - if grep -qP "^\- \[\?\] ${phase}\." "$TASKS" 2>/dev/null || \ - grep -qP "^\- \[!\] ${phase}\." "$TASKS" 2>/dev/null; then + if grep -qP "^\- \[(\?|!|~)\] ${phase}\." "$TASKS" 2>/dev/null; then phases="$phases $phase" fi done echo "$phases" } -# Sjekk om en fase er tilgjengelig (alle avhengigheter er ferdige eller i det minste ikke blokkert) +# Sjekk om en fase er tilgjengelig phase_available() { local phase=$1 local blocked="$2" @@ -52,7 +54,7 @@ phase_available() { return 1 fi - # Sjekk om avhengige faser er blokkert + # 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 @@ -67,21 +69,79 @@ phase_available() { 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 + return 0 + fi + return 1 +} + +# --- 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}; questions=${questions:-0}; blocked_count=${blocked_count:-0} - total=$((done + todo + questions + blocked_count)) + 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 @@ -100,24 +160,27 @@ 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 fase-nummer fra oppgave-ID (f.eks. "1.3" → "1") + # 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"; then + 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 faser:$blocked" + echo "Ingen tilgjengelige oppgaver. Blokkerte/pågående faser:$blocked" echo "Kjør --status for detaljer." else echo "Alle oppgaver er gjort!" @@ -133,61 +196,91 @@ if [[ "${1:-}" == "--dry" ]]; then exit 0 fi -PROMPT="$(cat <<'PROMPT_HEADER' -Du skal implementere neste oppgave i Synops-prosjektet. -Du jobber autonomt — ingen bruker er tilgjengelig for spørsmål underveis. +# --- 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 forgejo main 2>/dev/null || true -PROMPT_HEADER -) +# --- 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 -$(cat <<'PROMPT_BODY' - ## Arbeidsflyt -1. **Orienter deg.** Les `CLAUDE.md` for prosjektkontekst. Les dokumentene - som refereres i oppgaven. Les `tasks.md` for å forstå hvor prosjektet står. -2. **Implementer.** Skriv kode, kjør på server via SSH om nødvendig. -3. **Verifiser.** Kompilering, curl-test, kjør relevante tester. -4. **Oppdater dokumentasjon.** Hvis implementeringen avviker fra eksisterende +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 forgejo main\` først — andre + agenter kan ha pushet endringer. +3. **Implementer.** Skriv kode, kjør på server via SSH om nødvendig. +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. -5. **Oppdater tasks.md.** Endre `- [ ]` til `- [x]` for denne oppgaven. -6. **Commit og push.** Commit alle endringer (kode + docs + tasks.md) med - en beskrivende melding. Push til forgejo (bruk `tea` / `git push forgejo`). + 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 forgejo main\`). + Flere commits underveis er OK — push jevnlig så andre agenter ser fremgangen. ## Hvis noe blokkerer -Hvis du støter på noe som krever avklaring fra Vegard: -- Endre oppgavens status til `- [?]` i tasks.md -- Legg til innrykket tekst under oppgaven med spørsmålet: - ``` - - [?] 1.5 Authentik: opprett OIDC-provider... - > Spørsmål: Skal vi bruke implicit flow eller authorization code + PKCE? - > Kontekst: PKCE er sikrere men krever backend-støtte. - ``` -- Commit og push tasks.md slik at Vegard kan se spørsmålet. -- Avslutt sesjonen. Ikke start på neste oppgave. +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 oppgavens status til `- [!]` i tasks.md -- Dokumenter problemet under oppgaven. -- Commit og push. Avslutt. +- Endre status fra \`- [~]\` til \`- [!]\` i tasks.md +- Dokumenter problemet. Commit og push. Avslutt. ## Regler -- Jobb kun på denne ene oppgaven. +- 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/`. +- 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*. -PROMPT_BODY -)" +- 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." -echo "Starter Claude Code-sesjon..." +echo "Starter Claude Code-sesjon for oppgave ${task_id}..." cd "$ROOT" claude --print "$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 + fi + git add tasks.md + git commit -m "Tilbakestill oppgave ${task_id} etter feilet sesjon" --no-verify 2>/dev/null || true + git push forgejo main 2>/dev/null || true + fi +fi diff --git a/tasks.md b/tasks.md index 2174cdf..86303c1 100644 --- a/tasks.md +++ b/tasks.md @@ -6,12 +6,17 @@ Runner-scriptet plukker første ugjorte oppgave som ikke er blokkert. ## Statuser - `- [ ]` — Klar til å gjøres +- `- [~]` — Pågår. En agent jobber på denne. Andre agenter hopper over. - `- [x]` — Ferdig -- `- [?]` — Åpent spørsmål, trenger avklaring fra Vegard. Neste sesjon hopper over denne og alle som avhenger av den. -- `- [!]` — Blokkert av teknisk problem. Beskrivelse under oppgaven. +- `- [?]` — Åpent spørsmål, trenger avklaring fra Vegard. +- `- [!]` — Blokkert av teknisk problem. -Åpne spørsmål og blokkeringer skrives som innrykket tekst under oppgaven -med `>` prefix. Se eksisterende oppgaver for format. +`[~]`, `[?]` og `[!]` blokkerer alle oppgaver som avhenger av denne. +Detaljer skrives som innrykket tekst med `>` prefix under oppgaven. +Runner-scriptet legger automatisk til `> Påbegynt: ` for `[~]`. + +Hvis en `[~]`-oppgave har stått i >60 min uten commit, anta at +sesjonen krasjet. Kjør `run-next-task.sh --unstale` for å frigjøre. ## Avhengigheter