feat(graceful-handoff): 2.0 — migrate to skills/ with disable-model-invocation [skip-docs]
Step 1 of v2.0 plan. Hard cut from commands/ to skills/ per Anthropic recommendation for new plugins. Frontmatter sets disable-model-invocation: true and pins model: claude-sonnet-4-6. Docs (README, CLAUDE.md, root README) deferred to Step 9 per plan.
This commit is contained in:
parent
65c9242160
commit
1a65d8e4d5
12 changed files with 331 additions and 355 deletions
|
|
@ -92,6 +92,8 @@ v2.1 (non-breaking) replaced the hardcoded Q1–Q8 interview with a dynamic, qua
|
|||
|
||||
v1.7 self-verifying chain (preserved): a step may not be marked `completed` unless its manifest verifies. v1.8 Opus 4.7 literalism fixes (preserved): literal Step+Manifest template, forbidden narrative headers, schema self-check.
|
||||
|
||||
v3.1.0 (in progress) adds a `lib/`-tree of zero-dep validators (`brief-validator`, `research-validator`, `plan-validator`, `progress-validator`, `architecture-discovery`) wired into the four commands as CLI shims, plus 109 `node:test` cases and a doc-consistency invariant test. The Phase 5.5 schema self-check now runs as `node lib/validators/plan-validator.mjs --strict` instead of three `grep -cE` calls — same checks, single source of truth, machine-readable error codes. Architecture discovery treats the `ultra-cc-architect` contract as drift-WARN, never drift-FAIL. Forking the plugin? `npm test` is the readiness gate.
|
||||
|
||||
Defense-in-depth security: plugin hooks block destructive commands and sensitive path writes, prompt-level denylist works in headless sessions, pre-execution plan scan catches dangerous commands before they run, scoped `--allowedTools` replaces `--dangerously-skip-permissions` in parallel sessions.
|
||||
|
||||
Modes: default, brief-driven, project-scoped, research-enriched, foreground, quick, decompose, export
|
||||
|
|
|
|||
|
|
@ -1,329 +0,0 @@
|
|||
---
|
||||
name: graceful-handoff
|
||||
description: Produser handoff-artefakt, commit+push, og copy-paste-prompt for neste sesjon. Bruk når du nærmer deg 60-70% kontekst og må fortsette arbeidet i en ny sesjon uten tap.
|
||||
argument-hint: "[topic-slug] [--no-commit] [--dry-run]"
|
||||
allowed-tools: Read, Write, Edit, Bash, Glob
|
||||
---
|
||||
|
||||
# Graceful Handoff — sesjonsoverlevering
|
||||
|
||||
Produser en komplett handoff-pakke i ett steg: analyser state, skriv NEXT-SESSION-artefakt, oppdater REMEMBER/TODO, commit+push hvis mulig, og print en copy-paste-prompt for neste sesjon.
|
||||
|
||||
**Tidsbudsjett:** Hele kjøringen (fase 1-6) skal ligge under 60 sekunder reell tid. Bruker er typisk på 60-70% kontekst når de trigger dette — ikke bruk Agent-delegering eller WebSearch.
|
||||
|
||||
## Argumenter
|
||||
|
||||
| Argument | Effekt |
|
||||
|----------|--------|
|
||||
| `[topic-slug]` | Kebab-case slug som styrer filnavnet. Default: `NEXT-SESSION-PROMPT.local.md`. Med slug: `NEXT-SESSION-<slug>.local.md` |
|
||||
| `--no-commit` | Hopp over fase 5 (commit+push). Bruker håndterer commit manuelt |
|
||||
| `--dry-run` | Ikke skriv filer eller gjør git-operasjoner. Print kun hva som ville skjedd |
|
||||
|
||||
Parse argumenter fra `$ARGUMENTS` (kombinert streng). Støtt flag i vilkårlig rekkefølge. Første ikke-flag-token er slug.
|
||||
|
||||
---
|
||||
|
||||
## Fase 1 — Detekter arbeidsmappe og aktivt prosjekt
|
||||
|
||||
Kjør i parallell (én Bash-melding med flere tool calls):
|
||||
|
||||
```bash
|
||||
pwd
|
||||
git rev-parse --show-toplevel 2>/dev/null
|
||||
git status --porcelain
|
||||
git log --oneline -10
|
||||
git branch --show-current
|
||||
```
|
||||
|
||||
Let oppover i tre etter plugin-markør (maks 5 nivåer):
|
||||
|
||||
```bash
|
||||
# Fra cwd og oppover, stopp på første treff
|
||||
cur="$(pwd)"
|
||||
for i in 1 2 3 4 5; do
|
||||
if [ -f "$cur/.claude-plugin/plugin.json" ]; then
|
||||
echo "PLUGIN_ROOT=$cur"
|
||||
break
|
||||
fi
|
||||
cur="$(dirname "$cur")"
|
||||
[ "$cur" = "/" ] && break
|
||||
done
|
||||
```
|
||||
|
||||
Finn ultraplan-prosjekt (nyeste først):
|
||||
|
||||
```bash
|
||||
find . -maxdepth 4 -path '*/.claude/projects/*/brief.md' 2>/dev/null | sort -r | head -3
|
||||
find . -maxdepth 4 -path '*/.claude/projects/*/progress.json' 2>/dev/null | sort -r | head -3
|
||||
```
|
||||
|
||||
For hver progress.json-treff: kjør `jq -r '.status' <file>` for å se om prosjektet er aktivt.
|
||||
|
||||
## Fase 2 — Identifiser handoff-type
|
||||
|
||||
Prioritet: **multi-sesjon > plugin-arbeid > enkelt-oppgave**
|
||||
|
||||
| Type | Betingelse |
|
||||
|------|------------|
|
||||
| `multi-sesjon` | Finnes `.claude/projects/<NN>/progress.json` med `status != "completed"` OG `status != null`. Eller: finnes `.claude/projects/<NN>/plan.md` uten progress.json |
|
||||
| `plugin-arbeid` | Fase 1 fant `PLUGIN_ROOT` OG ingen aktiv multi-sesjon |
|
||||
| `enkelt-oppgave` | Ingen av over |
|
||||
|
||||
Lagre i intern state: `HANDOFF_TYPE`, `PROJECT_DIR` (hvis multi-sesjon), `PLUGIN_ROOT` (hvis plugin-arbeid), `WRITE_DIR` (hvor artefakten skal skrives).
|
||||
|
||||
Write-dir-logikk:
|
||||
- `multi-sesjon` → `WRITE_DIR = <PROJECT_DIR>`
|
||||
- `plugin-arbeid` → `WRITE_DIR = <PLUGIN_ROOT>`
|
||||
- `enkelt-oppgave` → `WRITE_DIR = $(pwd)`
|
||||
|
||||
Rapporter til bruker i en kort linje:
|
||||
|
||||
```
|
||||
Handoff-type: plugin-arbeid (llm-security). Skriver til /Users/ktg/.../plugins/llm-security/
|
||||
```
|
||||
|
||||
Hvis overlapp eksisterer (f.eks. plugin-arbeid innenfor et aktivt ultraplan-prosjekt): rapporter begge, men velg multi-sesjon som primær.
|
||||
|
||||
## Fase 3 — Skriv/oppdater NEXT-SESSION-artefakt
|
||||
|
||||
Filnavn:
|
||||
- Hvis `[topic-slug]` gitt: `NEXT-SESSION-<slug>.local.md`
|
||||
- Ellers: `NEXT-SESSION-PROMPT.local.md`
|
||||
|
||||
Full sti: `<WRITE_DIR>/<filename>`.
|
||||
|
||||
**Hvis filen finnes**: les den først, bevar "Hvorfor dette eksisterer"-seksjonen hvis den er relevant, oppdater status-seksjoner med dagens dato og aktuell state.
|
||||
|
||||
**Template** (samme 7-seksjons-struktur som llm-security/config-audit bruker):
|
||||
|
||||
```markdown
|
||||
# NEXT-SESSION-PROMPT — <plugin-eller-prosjekt-navn> <kort-tittel>
|
||||
|
||||
## Hvorfor dette eksisterer
|
||||
|
||||
<1-3 setninger om hva bruker jobber med og hvorfor denne sesjonen stoppet. Hent fra: siste commits, brief.md hvis multi-sesjon, git diff, samtale-kontekst. Vær konkret — ingen fluff.>
|
||||
|
||||
## Status ved sesjonshåndoff
|
||||
|
||||
### ✅ Ferdig
|
||||
|
||||
<List commits fra denne sesjonen med SHA + hva hver gjorde. Eks:
|
||||
- `a9fb328` fix(config-audit): complete conflict-project fixture
|
||||
- `94ce701` test(config-audit): add Opus 4.7 pattern fixtures
|
||||
|
||||
Hvis ingen commits: skriv "Ingen nye commits i denne sesjonen".>
|
||||
|
||||
### ⏳ Ikke startet / delvis
|
||||
|
||||
<Konkrete neste steg. Nevn filnavn og linjenummer hvis du vet dem. Eks:
|
||||
- Commit 2: Kontekst-bevisst entropy — scanners/entropy-scanner.mjs:40-47, 90-137
|
||||
- Commit 3: Policy-integrasjon
|
||||
|
||||
Hvis det finnes en plan-fil: peker til den.>
|
||||
|
||||
### ⚠️ Brutt / kjent risiko
|
||||
|
||||
<Tester som feiler, uncommittede endringer, antakelser som ikke er verifisert. Eks:
|
||||
- 25/80 tester feiler i tests/lib/severity.test.mjs (forventet — tester gammel formel)
|
||||
- Commit 1 er lokal, ikke pushet
|
||||
|
||||
Hvis ingenting er brutt: skriv "Ingen kjente broken tester".>
|
||||
|
||||
## Slik fortsetter du
|
||||
|
||||
1. `cd <absolute-path-til-WRITE_DIR>`
|
||||
2. `cat <filename>` — les denne filen igjen
|
||||
3. `git log --oneline -5` og `git status`
|
||||
<4. (hvis multi-sesjon) Les planen: `cat .claude/projects/<slug>/plan.md`>
|
||||
<5. Start med: <konkret neste handling>>
|
||||
|
||||
## Push-policy
|
||||
|
||||
- Direkte push til `main` på Forgejo er pre-autorisert (ikke spør om tillatelse)
|
||||
- Aldri GitHub — kun Forgejo (`git.fromaitochitta.com`)
|
||||
<evt. prosjekt- eller plugin-spesifikke regler, f.eks. versjonsbump-timing>
|
||||
|
||||
## Verifiseringskommandoer
|
||||
|
||||
```bash
|
||||
<Copy-paste-klare sjekker som beviser state. Eks:
|
||||
git log --oneline -5 # forventet: <siste commit SHA>
|
||||
git status # forventet: clean (eller <N> uncommittede)
|
||||
node --test tests/ # forventet: alle grønne
|
||||
>
|
||||
```
|
||||
|
||||
## Husk
|
||||
|
||||
- <Gotchas og antakelser som ikke er åpenbare fra koden. Eks:
|
||||
- Hooks (pre-edit-secrets, pre-write-pathguard) blokkerer writes til .claude-plugin/
|
||||
- Versjonsbump krever sync i 7+ filer — bruk grep-mønster
|
||||
- Opus 4.7 fyller kontekst raskt — kjør /graceful-handoff ved 60-70%>
|
||||
```
|
||||
|
||||
**Innholdsregler:**
|
||||
- Ikke fyll inn plassholdere med placeholder-tekst. Les faktisk git-state, samtalekontekst, og evt. brief.md/plan.md for å fylle inn ekte info.
|
||||
- Hvis en seksjon er irrelevant (f.eks. ingen push-policy avvik): skriv én setning som sier det, ikke utelat seksjonen (strukturen må være konsistent).
|
||||
- Tone: nøktern, norsk, teknisk. Ingen "gøy å jobbe med deg" eller "vi har kommet langt". Hvis brukeren var sint eller frustrert: nevn det kort i "Husk" slik at neste sesjon ikke trår i samme fellen.
|
||||
|
||||
Hvis `--dry-run`: print artefakten til stdout i stedet for å skrive.
|
||||
|
||||
## Fase 4 — Oppdater REMEMBER.md og TODO.md
|
||||
|
||||
**Lokasjon:**
|
||||
- `plugin-arbeid` → `<PLUGIN_ROOT>/REMEMBER.md` og `<PLUGIN_ROOT>/TODO.md`
|
||||
- `multi-sesjon` → `<PROJECT_DIR>/REMEMBER.md` og `<PROJECT_DIR>/TODO.md` (hvis de finnes; ellers skip — ultraplan-prosjekter har ikke alltid disse)
|
||||
- `enkelt-oppgave` → `$(pwd)/REMEMBER.md` og `$(pwd)/TODO.md` (kun hvis de allerede finnes; ikke opprett nye i random mapper)
|
||||
|
||||
**REMEMBER.md-oppdatering:**
|
||||
|
||||
Hvis filen finnes — les, oppdater:
|
||||
- Øverst: `## Sist oppdatert\n<YYYY-MM-DD>` (absolutt dato, ikke "i dag")
|
||||
- "PÅGÅENDE"-seksjon (eller lag den): plan-sti hvis relevant, 3-5 status-bullets (✅ ferdig, ⏳ pågående, ⚠️ blokkert)
|
||||
- Flytt tidligere "PÅGÅENDE" til "TIDLIGERE" med datostempel hvis den peker på annet arbeid
|
||||
|
||||
Hvis filen ikke finnes og type er `plugin-arbeid`: opprett minimal REMEMBER.md med dagens dato og én PÅGÅENDE-bullet. Ellers skip.
|
||||
|
||||
**TODO.md-oppdatering:**
|
||||
|
||||
Hvis filen finnes — les, oppdater:
|
||||
- Flytt items som ble gjort denne sesjonen til en "✅ Ferdig denne sesjonen (YYYY-MM-DD)"-seksjon øverst
|
||||
- Legg til items for neste sesjon under "⏳ Neste"
|
||||
- Ikke slett — kommenter ut eller flytt
|
||||
|
||||
Hvis filen ikke finnes: skip (ikke push onboarding i en handoff).
|
||||
|
||||
**Gitignore-verifisering:** Etter skriving, kjør `git check-ignore -v <path>` for hver fil. Hvis filen IKKE er ignorert: rapporter til bruker som advarsel (ikke blokker, men bruker må vite).
|
||||
|
||||
Hvis `--dry-run`: print diff i stedet for å skrive.
|
||||
|
||||
## Fase 5 — Commit + push hvis ucommittet arbeid finnes
|
||||
|
||||
Skip hele fasen hvis `--no-commit` eller `--dry-run`.
|
||||
|
||||
Skip hvis `git status --porcelain` er tom (ingenting å commite).
|
||||
|
||||
**Steg 5a — Klassifiser endringer:**
|
||||
|
||||
```bash
|
||||
git diff --stat
|
||||
git diff --cached --stat
|
||||
git status --porcelain
|
||||
```
|
||||
|
||||
Kategoriser endrede filer for å utlede commit-type:
|
||||
|
||||
| Filmønster | Type |
|
||||
|------------|------|
|
||||
| `tests/**`, `**/*.test.*` | `test` |
|
||||
| `*.md`, `README`, `CHANGELOG`, `CLAUDE.md` (kun docs-endring) | `docs` |
|
||||
| `plugins/<name>/hooks/**`, `plugins/<name>/scanners/**` (ny funksjon) | `feat` |
|
||||
| Samme mønster, men fikser eksisterende atferd | `fix` |
|
||||
| `package.json`, config, lockfile kun | `chore` |
|
||||
| Blandet | Bruk dominerende kategori; hvis uklart, `chore` |
|
||||
|
||||
Scope: plugin-navn hvis `plugin-arbeid` eller hvis alle endringer er i én plugin. Ellers ingen scope.
|
||||
|
||||
**Steg 5b — Generer commit-melding:**
|
||||
|
||||
Format: `<type>(<scope>): <kort beskrivelse>` (Conventional Commits).
|
||||
|
||||
Krav:
|
||||
- Første linje ≤ 72 tegn
|
||||
- Beskrivelse på norsk eller engelsk (match eksisterende commits i repoet — sjekk `git log --oneline -10`)
|
||||
- Beskriv **hva** som endres, ikke hvorfor (hvorfor hører hjemme i body hvis nødvendig)
|
||||
- Hvis flere uavhengige endringer: vurder om de skal splittes i to commits. Hvis bruker er i hastverk (kontekst-trøskel): ett commit med samlet beskrivelse er akseptabelt, men noter at det er samlet i body.
|
||||
|
||||
Eksempler (match stil fra eksisterende repo):
|
||||
- `test(config-audit): add Opus 4.7 pattern fixtures`
|
||||
- `fix(llm-security): complete conflict-project fixture for CNF cross-scope tests`
|
||||
- `feat(graceful-handoff): initial plugin with /graceful-handoff command`
|
||||
|
||||
Body (valgfri, bruk hvis commit dekker flere endringer):
|
||||
```
|
||||
- Endring 1
|
||||
- Endring 2
|
||||
|
||||
Samlet som ett commit pga. graceful-handoff-flyt.
|
||||
```
|
||||
|
||||
**Steg 5c — Vis meldingen og commit:**
|
||||
|
||||
Print den genererte meldingen ordrett til bruker FØR commit:
|
||||
|
||||
```
|
||||
Commit-melding:
|
||||
---
|
||||
feat(graceful-handoff): initial plugin with /graceful-handoff command
|
||||
---
|
||||
```
|
||||
|
||||
Deretter:
|
||||
|
||||
```bash
|
||||
git add -A # eller eksplisitt filvalg hvis endringene spenner flere uavhengige områder
|
||||
git commit -m "$(cat <<'EOF'
|
||||
<meldingen>
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
Hvis commit feiler pga. pre-commit hook (secrets, pathguard, osv.): STOPP, rapporter feilmeldingen, **ikke** bypass med `--no-verify`. Be bruker håndtere manuelt.
|
||||
|
||||
**Steg 5d — Push:**
|
||||
|
||||
```bash
|
||||
BRANCH="$(git branch --show-current)"
|
||||
if [ -n "$BRANCH" ]; then
|
||||
git push origin "$BRANCH"
|
||||
else
|
||||
git push origin HEAD
|
||||
fi
|
||||
```
|
||||
|
||||
Bekreft push med `git rev-parse @{u}` (at upstream er satt) eller hent siste output fra push-kommandoen.
|
||||
|
||||
Hvis push feiler: rapporter, men ikke force-push. Bruker kan håndtere manuelt.
|
||||
|
||||
## Fase 6 — Print copy-paste-prompt for neste sesjon
|
||||
|
||||
Print en kompakt prompt til terminal som bruker kan kopiere direkte inn i ny Claude Code-sesjon:
|
||||
|
||||
```
|
||||
════════════════════════════════════════════════════════════
|
||||
NESTE SESJON — copy-paste til ny Claude:
|
||||
════════════════════════════════════════════════════════════
|
||||
|
||||
cd <absolute-WRITE_DIR>
|
||||
cat <NEXT-SESSION-filnavn>
|
||||
git log --oneline -5
|
||||
git status
|
||||
|
||||
Fortsett fra <konkret neste handling - én setning>.
|
||||
|
||||
════════════════════════════════════════════════════════════
|
||||
Artefakt: <full sti til NEXT-SESSION-filen>
|
||||
Commit: <siste SHA eller "ingen endringer">
|
||||
Push: <"pushet til Forgejo" eller "skippet (flag / ingenting)">
|
||||
════════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
Dette er den KRITISKE output-en — hvis alt annet feiler, må minst denne blokken nå bruker slik at de kan fortsette. Bruk én final assistant-tekst-melding til å printe dette.
|
||||
|
||||
---
|
||||
|
||||
## Tidsbudsjett og escalation
|
||||
|
||||
- Hele kommandoen: < 60 sekunder
|
||||
- Hvis du oppdager at du trenger å lese >5 filer eller kjøre >3 Agent-delegeringer: stopp, skriv en minimal handoff med det du har, og noter i "Brutt/risiko" at handoff var ufullstendig.
|
||||
- **Aldri Agent-delegering i denne kommandoen** — main-sesjonen er raskere enn å spinne opp en subagent for noe mekanisk som dette.
|
||||
- **Aldri WebSearch** — ingen ekstern info trengs.
|
||||
|
||||
## Fallback hvis state er helt uklart
|
||||
|
||||
Hvis fase 1-2 ikke kan identifisere noen av de tre handoff-typene (f.eks. cwd er utenfor git, ingen plugin-markør, ingen ultraplan-prosjekt):
|
||||
|
||||
1. Sett `HANDOFF_TYPE = enkelt-oppgave`
|
||||
2. `WRITE_DIR = $(pwd)`
|
||||
3. Varsle bruker: "Kunne ikke detektere handoff-type. Skriver til cwd."
|
||||
4. Fortsett som vanlig — prompten er fremdeles nyttig
|
||||
98
plugins/graceful-handoff/skills/graceful-handoff/SKILL.md
Normal file
98
plugins/graceful-handoff/skills/graceful-handoff/SKILL.md
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
name: graceful-handoff
|
||||
description: Produser handoff-artefakt, commit+push, og copy-paste-prompt for neste sesjon. Bruk når du nærmer deg 60-70% kontekst og må fortsette arbeidet i en ny sesjon uten tap.
|
||||
argument-hint: "[topic-slug] [--no-commit] [--dry-run]"
|
||||
disable-model-invocation: true
|
||||
model: claude-sonnet-4-6
|
||||
allowed-tools: Bash(git:*) Bash(jq:*) Bash(node:*) Bash(find:*) Bash(pwd:*) Read Write Glob
|
||||
---
|
||||
|
||||
# Graceful Handoff — sesjonsoverlevering v2.0
|
||||
|
||||
Orkestrerer JSON-pipeline-skriptet og fyller copy-paste-template-en for neste sesjon. Selve pipelinen (state-deteksjon, classification, fil-skriving, commit, push) er deterministisk og lever i `scripts/handoff-pipeline.mjs`. Denne skill-en er en tynn wrapper.
|
||||
|
||||
**Tidsbudsjett:** Hele kjøringen skal ligge under 60 sekunder reell tid. Bruker er typisk på 60-70% kontekst når de trigger dette — ingen Agent-delegering, ingen WebSearch.
|
||||
|
||||
## Hvordan kjøres
|
||||
|
||||
1. **Parse `$ARGUMENTS`** (kombinert streng). Støtt flag i vilkårlig rekkefølge.
|
||||
- `[topic-slug]` — kebab-case, styrer filnavnet
|
||||
- `--no-commit` — hopp over commit/push, bruker håndterer manuelt
|
||||
- `--dry-run` — print hva som ville skjedd, ingen filer/git
|
||||
- `--no-push` — commit OK men ikke push (Stop hook bruker dette i auto-eksekvering)
|
||||
- `--auto` — non-interactive, auto-Y på commit-bekreftelse (kun for hooks)
|
||||
- `--non-interactive` — uten `--auto`: feil; med `--auto`: kjør uten prompts
|
||||
|
||||
2. **Kjør pipeline-skriptet:**
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/scripts/handoff-pipeline.mjs <args>
|
||||
```
|
||||
|
||||
3. **Parse JSON-output** fra stdout. Forventet schema:
|
||||
```json
|
||||
{
|
||||
"handoff_type": "multi-sesjon | plugin-arbeid | enkelt-oppgave",
|
||||
"write_dir": "/abs/path",
|
||||
"artifact_path": "/abs/path/NEXT-SESSION-...",
|
||||
"next_steps": ["..."],
|
||||
"git_status": { "branch": "...", "dirty": true, "ahead": 2 },
|
||||
"commit_message": "...",
|
||||
"actions_taken": ["wrote artifact", "committed", "pushed"],
|
||||
"errors": []
|
||||
}
|
||||
```
|
||||
|
||||
4. **Hvis `errors[]` non-empty:** rapporter feilene til bruker, ikke fortsett. Foreslå manuelle skritt fra `next_steps`.
|
||||
|
||||
5. **Hvis interaktiv (default):** Skriptet skriver commit-bekreftelses-prompten til stderr. Modellen leser stderr-output og presenterer Y/n-valget til bruker via AskUserQuestion. Send svaret tilbake til skriptet via stdin. (NB: I denne skill-konteksten kan modellen også vise commit-meldingen direkte og spørre — fleksibelt.)
|
||||
|
||||
6. **Når ferdig:** Print copy-paste-prompt fra `next_steps` JSON til bruker:
|
||||
|
||||
```
|
||||
════════════════════════════════════════════════════════════
|
||||
NESTE SESJON — copy-paste til ny Claude:
|
||||
════════════════════════════════════════════════════════════
|
||||
|
||||
cd <absolute-WRITE_DIR>
|
||||
cat <NEXT-SESSION-filnavn>
|
||||
git log --oneline -5
|
||||
git status
|
||||
|
||||
Fortsett fra <konkret neste handling — én setning>.
|
||||
|
||||
════════════════════════════════════════════════════════════
|
||||
Artefakt: <full sti til NEXT-SESSION-filen>
|
||||
Commit: <siste SHA eller "ingen endringer">
|
||||
Push: <"pushet til Forgejo" eller "skippet (flag / ingenting)">
|
||||
════════════════════════════════════════════════════════════
|
||||
```
|
||||
|
||||
## Når brukes den
|
||||
|
||||
- **Manuelt:** kjør `/graceful-handoff` selv ved 60-70% kontekst
|
||||
- **Automatisk:** Stop hook kaller `handoff-pipeline.mjs --auto --no-push` ved estimert ≥70%. Skill-en invokeres IKKE i auto-modus — hook-en kaller skriptet direkte for å bevare `disable-model-invocation: true`.
|
||||
|
||||
## Hva blir skrevet
|
||||
|
||||
- `NEXT-SESSION-PROMPT.local.md` (eller `NEXT-SESSION-<slug>.local.md`) i riktig WRITE_DIR
|
||||
- `REMEMBER.md` oppdatert hvis den finnes
|
||||
- `TODO.md` oppdatert hvis den finnes
|
||||
- Git commit + push (med mindre `--no-commit` eller `--no-push`)
|
||||
|
||||
## Push-policy
|
||||
|
||||
- Direkte push til `main` på Forgejo er pre-autorisert
|
||||
- Aldri GitHub — kun Forgejo (`git.fromaitochitta.com`)
|
||||
- Pre-commit hooks respekteres uten `--no-verify`
|
||||
|
||||
## Begrensninger (v2.0)
|
||||
|
||||
- Auto-eksekvering ved kontekst-terskel er approksimasjon basert på transcript-størrelse, ikke Claude's reelle kontekst-måling. Estimat kan avvike ±10% — terskel satt konservativt til 70%.
|
||||
- statusLine-plassering i `hooks/hooks.json` er antakelse; smoke-test før release.
|
||||
- `disable-model-invocation: true` har en åpen issue (#26251) som potensielt kan blokkere user-invocation. Verifiser med smoke-test.
|
||||
|
||||
## Feilsøking
|
||||
|
||||
- Pipeline-skriptet feiler: kjør med `--dry-run` for å se hva det ville gjort
|
||||
- Git-state uvanlig (detached HEAD, ingen remote): pipeline returnerer `errors[]`, ikke crash
|
||||
- Stop hook trigger for tidlig/sent: terskel kan justeres i `hooks/scripts/stop-context-monitor.mjs` (look for `0.70`)
|
||||
61
plugins/graceful-handoff/tests/skill-structure.test.mjs
Normal file
61
plugins/graceful-handoff/tests/skill-structure.test.mjs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// skill-structure.test.mjs — Verifies SKILL.md frontmatter and commands/ deletion.
|
||||
|
||||
import { test } from 'node:test';
|
||||
import { strict as assert } from 'node:assert';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const PLUGIN_ROOT = join(__dirname, '..');
|
||||
|
||||
test('SKILL.md exists at expected path', () => {
|
||||
const skillPath = join(PLUGIN_ROOT, 'skills', 'graceful-handoff', 'SKILL.md');
|
||||
assert.ok(existsSync(skillPath), `SKILL.md missing at ${skillPath}`);
|
||||
});
|
||||
|
||||
test('commands/ directory is deleted (hard cut to skills/)', () => {
|
||||
const commandsDir = join(PLUGIN_ROOT, 'commands');
|
||||
assert.ok(!existsSync(commandsDir), 'commands/ directory still exists — should be deleted in v2.0');
|
||||
});
|
||||
|
||||
test('SKILL.md has disable-model-invocation: true', () => {
|
||||
const skillPath = join(PLUGIN_ROOT, 'skills', 'graceful-handoff', 'SKILL.md');
|
||||
const content = readFileSync(skillPath, 'utf-8');
|
||||
assert.match(content, /^disable-model-invocation: true$/m);
|
||||
});
|
||||
|
||||
test('SKILL.md has model: claude-sonnet-4-6', () => {
|
||||
const skillPath = join(PLUGIN_ROOT, 'skills', 'graceful-handoff', 'SKILL.md');
|
||||
const content = readFileSync(skillPath, 'utf-8');
|
||||
assert.match(content, /^model: claude-sonnet-4-6$/m);
|
||||
});
|
||||
|
||||
test('SKILL.md has Bash sub-scoped allowed-tools', () => {
|
||||
const skillPath = join(PLUGIN_ROOT, 'skills', 'graceful-handoff', 'SKILL.md');
|
||||
const content = readFileSync(skillPath, 'utf-8');
|
||||
assert.match(content, /Bash\(git:\*\)/);
|
||||
assert.match(content, /Bash\(node:\*\)/);
|
||||
});
|
||||
|
||||
test('SKILL.md does not pre-approve curl or wget', () => {
|
||||
const skillPath = join(PLUGIN_ROOT, 'skills', 'graceful-handoff', 'SKILL.md');
|
||||
const content = readFileSync(skillPath, 'utf-8');
|
||||
// Frontmatter only — find the allowed-tools line
|
||||
const allowedToolsLine = content.match(/^allowed-tools:.*$/m);
|
||||
assert.ok(allowedToolsLine, 'allowed-tools line missing');
|
||||
assert.doesNotMatch(allowedToolsLine[0], /\bcurl\b/);
|
||||
assert.doesNotMatch(allowedToolsLine[0], /\bwget\b/);
|
||||
});
|
||||
|
||||
test('SKILL.md body references handoff-pipeline.mjs', () => {
|
||||
const skillPath = join(PLUGIN_ROOT, 'skills', 'graceful-handoff', 'SKILL.md');
|
||||
const content = readFileSync(skillPath, 'utf-8');
|
||||
assert.match(content, /handoff-pipeline\.mjs/);
|
||||
});
|
||||
|
||||
test('SKILL.md body has Tidsbudsjett (time budget) note', () => {
|
||||
const skillPath = join(PLUGIN_ROOT, 'skills', 'graceful-handoff', 'SKILL.md');
|
||||
const content = readFileSync(skillPath, 'utf-8');
|
||||
assert.match(content, /Tidsbudsjett/);
|
||||
});
|
||||
|
|
@ -90,6 +90,25 @@ If `{project_dir}/architecture/overview.md` exists (typically produced by the se
|
|||
| contrarian-researcher | sonnet | Counter-evidence, overlooked alternatives |
|
||||
| gemini-bridge | sonnet | Gemini Deep Research second opinion (conditional) |
|
||||
|
||||
## Quality infrastructure (v3.1.0)
|
||||
|
||||
`lib/` contains zero-dep validators and parsers wired into the four commands:
|
||||
|
||||
- `lib/util/{frontmatter,result}.mjs` — shared YAML-frontmatter parser + Result helpers
|
||||
- `lib/parsers/{plan-schema,manifest-yaml,project-discovery,arg-parser,bash-normalize}.mjs` — pure parsers (no I/O), unit-tested
|
||||
- `lib/validators/{brief,research,plan,progress}-validator.mjs` — schema validators with CLI shims (`node lib/validators/X.mjs --json <path>`)
|
||||
- `lib/validators/architecture-discovery.mjs` — drift-WARN external-contract discovery for `architecture/overview.md`
|
||||
|
||||
Wiring points (replaces previous prose-grep instructions):
|
||||
- `/ultrabrief-local` Phase 4g → `brief-validator` (post-write sanity check)
|
||||
- `/ultraplan-local` Phase 1 → `brief-validator --soft`, `research-validator --dir`, `architecture-discovery`
|
||||
- `planning-orchestrator` Phase 5.5 → `plan-validator --strict` (replaces 3 `grep -cE` calls)
|
||||
- `/ultraexecute-local --validate` → `plan-validator --strict` + `progress-validator`
|
||||
|
||||
Tests under `tests/**/*.test.mjs` (109 tests, 0 deps). `npm test` is the fork-readiness gate.
|
||||
|
||||
Doc-consistency test at `tests/lib/doc-consistency.test.mjs` pins agent-table count, command-table coverage, plan_version invariant, and settings.json scope cleanliness.
|
||||
|
||||
## Architecture
|
||||
|
||||
**Brief:** 7-phase workflow: Parse mode → Create project dir → Phase 3 completeness loop (section-driven, no question cap) → Phase 4 draft/review/revise with `brief-reviewer` as stop-gate (max 3 iterations; gate = all dimensions ≥ 4 and research plan = 5) → Finalize (`brief.md` on pass, or `brief_quality: partial` on cap/force-stop) → Manual/auto opt-in → Stats. Always interactive. Auto mode runs research + plan inline in the main context (v2.4.0).
|
||||
|
|
|
|||
|
|
@ -473,6 +473,31 @@ v2.0.0 is a **breaking release**. See [MIGRATION.md](MIGRATION.md) for a step-by
|
|||
| Requires GitHub | Yes | Yes | No | **No** |
|
||||
| Cross-platform | Web only | Web only | Desktop | **Mac, Linux, Windows** |
|
||||
|
||||
## Quality infrastructure (since v3.1.0)
|
||||
|
||||
The plugin ships with `node:test`-based unit tests and a `lib/` directory of pure-JS validators wired into the commands. Forking the plugin for internal use? Run `npm test` to confirm the parsers, validators, and doc-consistency invariants still hold:
|
||||
|
||||
```bash
|
||||
cd plugins/ultraplan-local
|
||||
npm test # runs all tests under tests/**/*.test.mjs
|
||||
```
|
||||
|
||||
Validators (zero npm deps, hand-rolled YAML subset):
|
||||
|
||||
| Module | Purpose |
|
||||
|---|---|
|
||||
| `lib/validators/brief-validator.mjs` | brief.md frontmatter + state machine (research_topics + status coherence) + body sections |
|
||||
| `lib/validators/research-validator.mjs` | research-brief frontmatter (confidence ∈ [0,1], dimensions ≥ 1) + body sections; `--dir` mode validates a whole `research/` folder |
|
||||
| `lib/validators/plan-validator.mjs` | wraps plan-schema + manifest-yaml; enforces v1.7 step heading, manifest count match, and forbidden-narrative-form denylist (`### Fase/Phase/Stage/Steg N`) — replaces the Phase 5.5 grep checks |
|
||||
| `lib/validators/progress-validator.mjs` | progress.json shape (schema_version, status enum, current_step in range) + resume-readiness check |
|
||||
| `lib/validators/architecture-discovery.mjs` | EXTERNAL CONTRACT — drift-WARN, never drift-FAIL. Discovers `architecture/overview.md` (owned by the separate `ultra-cc-architect` plugin) and tolerates non-canonical filenames with warnings. |
|
||||
|
||||
Each module exposes a CLI: `node lib/validators/<name>.mjs --json <path>` returns structured `{valid, errors, warnings, parsed}`. Commands invoke the CLI as their schema check.
|
||||
|
||||
A doc-consistency test (`tests/lib/doc-consistency.test.mjs`) pins prose-vs-source invariants — the agent table in `CLAUDE.md` must match the `agents/*.md` file count, every command's frontmatter `name:` must match its filename, and `templates/plan-template.md` must declare `plan_version: 1.7`.
|
||||
|
||||
Borrowed pattern from `llm-security` (commit `97c5c9d`); extending the plugin should preserve the invariants the test pins.
|
||||
|
||||
## Known limitations
|
||||
|
||||
**Infrastructure-as-code (IaC) gets reduced value.** The exploration agents are designed for application code. Terraform, Helm, Pulumi, CDK projects will get a plan, but agents like `architecture-mapper` and `test-strategist` produce less useful output for IaC. Use ultraplan-local for the structural plan, then supplement IaC-specific steps manually.
|
||||
|
|
|
|||
|
|
@ -320,28 +320,33 @@ If any validation fails, fix the plan before handing to Phase 6 review.
|
|||
### Phase 5.5 — Schema self-check (REQUIRED before Phase 6)
|
||||
|
||||
After writing the plan file, verify the output conforms to the executor's
|
||||
parser BEFORE handing to plan-critic. Use Bash to grep the plan file:
|
||||
parser BEFORE handing to plan-critic. Run the plan validator:
|
||||
|
||||
```bash
|
||||
# Count canonical step headings
|
||||
grep -c '^### Step [0-9]\+: ' "$plan_path"
|
||||
|
||||
# Count manifest blocks
|
||||
grep -c '^ manifest:' "$plan_path"
|
||||
|
||||
# Detect forbidden narrative formats
|
||||
grep -cE '^(##|###) (Fase|Phase|Stage) [0-9]' "$plan_path"
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-validator.mjs --strict --json "$plan_path"
|
||||
```
|
||||
|
||||
**Pass criteria:**
|
||||
- Step count ≥ 1
|
||||
- Manifest count == Step count
|
||||
- Forbidden narrative count == 0
|
||||
**Pass criteria:** validator exits 0 with `valid: true` in its JSON output.
|
||||
Internally the validator enforces (same checks as before, now in one place):
|
||||
- Step count ≥ 1, numbering is 1..N contiguous
|
||||
- Per-step Manifest YAML present, parses, and `commit_message_pattern` compiles
|
||||
- Step count == manifest count
|
||||
- Zero forbidden narrative headings (`### Fase N`, `### Phase N`, `### Stage N`,
|
||||
`### Steg N`)
|
||||
- `plan_version: 1.7` declared (warning only if older / missing)
|
||||
|
||||
**If the plan fails schema self-check:** rewrite the Implementation Plan
|
||||
section using the exact literal template shown earlier in Phase 5. Do NOT
|
||||
proceed to Phase 6 with a schema-failing plan — plan-critic cannot repair
|
||||
format drift, only content issues.
|
||||
Each error has a `code` field — read these to localize the fix. Common codes:
|
||||
- `PLAN_FORBIDDEN_HEADING` — narrative drift; rewrite the section using the
|
||||
literal template from Phase 5
|
||||
- `PLAN_MANIFEST_COUNT_MISMATCH` — at least one step lost its manifest block
|
||||
- `MANIFEST_PATTERN_INVALID` — a `commit_message_pattern` does not compile;
|
||||
check escaping (use `\\(` not `\(` in YAML double-quoted strings)
|
||||
- `PLAN_STEP_NUMBERING` — steps skip a number; renumber sequentially
|
||||
|
||||
**If the plan fails schema self-check:** rewrite the offending section using
|
||||
the exact literal template shown earlier in Phase 5. Do NOT proceed to Phase 6
|
||||
with a schema-failing plan — plan-critic cannot repair format drift, only
|
||||
content issues.
|
||||
|
||||
### Failure recovery (REQUIRED for every step)
|
||||
|
||||
|
|
|
|||
|
|
@ -460,11 +460,25 @@ After the loop exits (pass, cap, or force-stop), ensure:
|
|||
Populate the "How to continue" footer with the actual project path and
|
||||
topic questions.
|
||||
|
||||
**Schema sanity check (since v3.1.0):** before reporting, run the brief
|
||||
validator. This catches frontmatter typos and state-machine inconsistencies
|
||||
the brief-reviewer rubric does not check (e.g. `research_status: skipped`
|
||||
with `research_topics: 3` and no `brief_quality: partial`).
|
||||
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --json "{PROJECT_DIR}/brief.md"
|
||||
```
|
||||
|
||||
If the validator returns errors, report them to the user and offer to
|
||||
re-enter Phase 4 with the validator's hints in scope. If only warnings,
|
||||
note them in the final report.
|
||||
|
||||
Report:
|
||||
```
|
||||
Brief written: {PROJECT_DIR}/brief.md
|
||||
Review iterations: {1..3}
|
||||
Final quality: {complete | partial}
|
||||
Validator: {PASS | warnings(N)}
|
||||
Research topics identified: {N}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -187,6 +187,23 @@ report. Do NOT run security scan, do NOT touch progress files, do NOT
|
|||
execute any steps. This gives the user a fast sanity-check of plan
|
||||
schema compliance without side effects.
|
||||
|
||||
**Preferred path (since v3.1.0):** invoke the plan validator directly. It
|
||||
returns the same diagnostic info Phase 2 derives in prose, with stable
|
||||
error codes for downstream tooling:
|
||||
|
||||
```bash
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/plan-validator.mjs --strict --json "{path}"
|
||||
|
||||
# When --project is in scope and progress.json exists, also validate it:
|
||||
[ -f "{project_dir}/progress.json" ] && \
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/progress-validator.mjs --json "{project_dir}/progress.json"
|
||||
```
|
||||
|
||||
Map the validator's `code` field to the error templates below (e.g.
|
||||
`PLAN_FORBIDDEN_HEADING` → "Detected heading format" branch). When both
|
||||
calls exit 0, render the READY report. Otherwise render FAIL with the
|
||||
validator's first error code + message.
|
||||
|
||||
If Phase 2 parsing succeeded (no fatal errors, every step has a valid
|
||||
Manifest block in strict mode, or synthesized manifests in legacy mode):
|
||||
|
||||
|
|
|
|||
|
|
@ -61,11 +61,27 @@ Parse `$ARGUMENTS` for mode flags. Order of precedence:
|
|||
Missing: {dir}/brief.md
|
||||
```
|
||||
- Set **project_dir = {dir}**, **brief_path = {dir}/brief.md**.
|
||||
- **Validate inputs** (soft mode — warnings do not block, errors do):
|
||||
```bash
|
||||
# Brief schema sanity check (frontmatter + state machine, soft on body sections)
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/brief-validator.mjs --soft --json "{dir}/brief.md"
|
||||
|
||||
# Research briefs (if any) — drift-warn only, none of these block the run
|
||||
[ -d "{dir}/research" ] && \
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/research-validator.mjs --soft --dir "{dir}/research" --json
|
||||
|
||||
# Architecture note discovery (EXTERNAL CONTRACT — drift-WARN, never drift-FAIL)
|
||||
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/architecture-discovery.mjs --json "{dir}"
|
||||
```
|
||||
Each call exits 0 on success or with a structured JSON error report on stderr.
|
||||
Surface any warnings in the user-facing summary at Phase 3, but do not abort.
|
||||
- Set **has_research_brief = true** if `{dir}/research/*.md` matches ≥ 1 file.
|
||||
- Set **has_architecture_note = true** if `{dir}/architecture/overview.md` exists.
|
||||
If set, **architecture_note_path = {dir}/architecture/overview.md**. Produced by
|
||||
the optional `/ultra-cc-architect-local` command from the separate `ultra-cc-architect`
|
||||
plugin. Missing file is fine — this is additive discovery, not a requirement.
|
||||
- Read the architecture-discovery JSON output: set **has_architecture_note = true**
|
||||
if `found == true`. The discovery module emits warnings if the file lives at a
|
||||
non-canonical path (e.g. `architecture-overview.md`); preserve them for the
|
||||
user-facing summary. If set, **architecture_note_path = {result.overview}**.
|
||||
Produced by the optional `/ultra-cc-architect-local` command from the separate
|
||||
`ultra-cc-architect` plugin. Missing file is fine — additive discovery, not required.
|
||||
|
||||
4. **`--brief <path>`** — extract the brief path. If the file does not exist:
|
||||
```
|
||||
|
|
|
|||
|
|
@ -79,10 +79,30 @@ export function parseFrontmatter(yamlText) {
|
|||
while (j < lines.length) {
|
||||
const next = lines[j];
|
||||
if (next.trim() === '') { j++; continue; }
|
||||
const m2 = next.match(/^\s+-\s+(.*)$/);
|
||||
if (!m2) break;
|
||||
list.push(parseScalar(m2[1]));
|
||||
j++;
|
||||
const itemMatch = next.match(/^(\s+)-\s+(.*)$/);
|
||||
if (!itemMatch) break;
|
||||
const itemIndent = itemMatch[1].length;
|
||||
const firstContent = itemMatch[2];
|
||||
const dictKeyMatch = firstContent.match(/^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/);
|
||||
if (dictKeyMatch) {
|
||||
const item = {};
|
||||
item[dictKeyMatch[1]] = parseScalar(dictKeyMatch[2]);
|
||||
let k = j + 1;
|
||||
while (k < lines.length) {
|
||||
const cont = lines[k];
|
||||
if (cont.trim() === '') { k++; continue; }
|
||||
const contMatch = cont.match(/^(\s+)([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$/);
|
||||
if (!contMatch) break;
|
||||
if (contMatch[1].length <= itemIndent + 1) break;
|
||||
item[contMatch[2]] = parseScalar(contMatch[3]);
|
||||
k++;
|
||||
}
|
||||
list.push(item);
|
||||
j = k;
|
||||
} else {
|
||||
list.push(parseScalar(firstContent));
|
||||
j++;
|
||||
}
|
||||
}
|
||||
if (list.length > 0) {
|
||||
out[key] = list;
|
||||
|
|
|
|||
|
|
@ -99,6 +99,34 @@ test('parseManifest — commit_message_pattern compiles via new RegExp', () => {
|
|||
assert.ok(!re.test('chore: not it'));
|
||||
});
|
||||
|
||||
test('parseManifest — must_contain list-of-dicts (real-world template form)', () => {
|
||||
const body = `### Step 1: Real
|
||||
- Manifest:
|
||||
\`\`\`yaml
|
||||
manifest:
|
||||
expected_paths:
|
||||
- a.json
|
||||
- b.md
|
||||
min_file_count: 2
|
||||
commit_message_pattern: "^chore:"
|
||||
bash_syntax_check: []
|
||||
forbidden_paths:
|
||||
- CHANGELOG.md
|
||||
must_contain:
|
||||
- path: a.json
|
||||
pattern: '"version": "2\\.3\\.0"'
|
||||
- path: b.md
|
||||
pattern: "version-blue"
|
||||
\`\`\`
|
||||
`;
|
||||
const r = parseManifest(body);
|
||||
assert.equal(r.valid, true, JSON.stringify(r.errors));
|
||||
assert.equal(r.parsed.must_contain.length, 2);
|
||||
assert.equal(r.parsed.must_contain[0].path, 'a.json');
|
||||
assert.equal(r.parsed.must_contain[1].path, 'b.md');
|
||||
assert.equal(r.parsed.forbidden_paths[0], 'CHANGELOG.md');
|
||||
});
|
||||
|
||||
test('validateAllManifests — aggregates per-step issues', () => {
|
||||
const steps = [
|
||||
{ n: 1, body: STEP_BODY_GOOD },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue