From 5688512898be5555ccee6bcd3b857114381a32c5 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Fri, 1 May 2026 20:53:47 +0200 Subject: [PATCH] docs(ultraplan-local): add Handover 7 + doc-consistency pins for /ultracontinue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Handover 7 (.session-state.local.json) section to HANDOVER-CONTRACTS.md documenting the multi-session-resume contract: - Producers: ultraexecute Phase 8/2.55/4 + helper command + future graceful-handoff v2.2 + pre-compact-flush refresh - Consumer: /ultracontinue (read-only) - Schema v1: schema_version, project, next_session_brief_path, next_session_label, status (5-value enum), updated_at - Forward-compat: unknown top-level keys silently tolerated (drift-WARN) - Path: .claude/projects//.session-state.local.json (gitignored) - Failure modes mapped to validator error codes Also updates the validator → handover map and Versioning + Stability tables to include Handover 7. Extends tests/lib/doc-consistency.test.mjs with three new pins: 1. HANDOVER-CONTRACTS.md contains Handover 7 section 2. session-state-validator.mjs exposes the standard CLI shim 3. CLAUDE.md mentions /ultracontinue-local Adds the /ultracontinue-local row to the plugin CLAUDE.md commands table — minimum viable to keep the existing 'CLAUDE.md commands table mentions every commands/*.md file' iteration test green. Step 11 (Session 2b) will expand to full README + CLAUDE.md narrative documentation. Test suite: 182 → 185 (3 new doc-consistency pins, zero regressions). Co-Authored-By: Claude Opus 4.7 --- plugins/ultraplan-local/CLAUDE.md | 1 + .../docs/HANDOVER-CONTRACTS.md | 65 +++++++++++++++++++ .../tests/lib/doc-consistency.test.mjs | 31 +++++++++ 3 files changed, 97 insertions(+) diff --git a/plugins/ultraplan-local/CLAUDE.md b/plugins/ultraplan-local/CLAUDE.md index d82a43f..8dc9e24 100644 --- a/plugins/ultraplan-local/CLAUDE.md +++ b/plugins/ultraplan-local/CLAUDE.md @@ -15,6 +15,7 @@ Deep implementation planning and research with an explicit brief step, specializ | `/ultraplan-local` | Plan — brief-reviewer, explore, plan, review. Requires `--brief` or `--project`. Auto-discovers `architecture/overview.md` if present | opus | | `/ultraexecute-local` | Execute — disciplined plan/session-spec executor with failure recovery | opus | | `/ultrareview-local` | Review — independent post-hoc review of delivered code against the brief. Produces `review.md` with severity-tagged findings (Handover 6) | opus | +| `/ultracontinue-local` | Continue — resumes the next session of a multi-session ultraplan project. Reads `.session-state.local.json` (Handover 7) and immediately begins executing | opus | ### /ultrabrief-local modes diff --git a/plugins/ultraplan-local/docs/HANDOVER-CONTRACTS.md b/plugins/ultraplan-local/docs/HANDOVER-CONTRACTS.md index ef4de14..b3654ad 100644 --- a/plugins/ultraplan-local/docs/HANDOVER-CONTRACTS.md +++ b/plugins/ultraplan-local/docs/HANDOVER-CONTRACTS.md @@ -15,6 +15,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated: | `plan.md` | `plan_version` (frontmatter) | `1.7` | | `progress.json` | `schema_version` (top-level) | `"1"` | | `review.md` | `review_version` (frontmatter) | `1.0` | +| `.session-state.local.json` | `schema_version` (top-level) | `1` (number) | ## Breaking-change protocol @@ -34,6 +35,7 @@ Each artifact carries an explicit version field. Schema bumps are coordinated: | 4. plan → execute | `lib/validators/plan-validator.mjs` | | 5. progress.json (resume) | `lib/validators/progress-validator.mjs` | | 6. review → plan | `lib/validators/review-validator.mjs` | +| 7. session-state (multi-session resume) | `lib/validators/session-state-validator.mjs` | Every validator exposes a CLI: `node lib/validators/.mjs --json ` returns `{valid, errors[], warnings[], parsed}`. Errors and warnings have stable `code` fields for downstream tooling. @@ -350,6 +352,68 @@ The validator (`lib/validators/review-validator.mjs`) exposes the same CLI as th --- +## Handover 7 — `.session-state.local.json` + +**Handover 7 enables zero-friction multi-session resumption.** Where Handover 5 (`progress.json`) makes a single execute run resumable after a crash inside that session, Handover 7 makes a *multi-session* plan resumable across fresh Claude Code chats. The state file is the contract; any session-end mechanism may write it; `/ultracontinue` only reads. + +**Producer:** +- `/ultraexecute-local` Phase 8 (canonical convergence — every completed/failed/stopped/partial run that reaches the final report) +- `/ultraexecute-local` Phase 2.55 (Check 1 — dirty-tree pre-flight stop) +- `/ultraexecute-local` Phase 4 (entry-condition stop) +- `/ultraplan-end-session-local` (informal multi-session helper — Step 9 of v3.3.0) +- *Future:* `graceful-handoff` v2.2 may dual-write here as part of its session-rescue artifact (additive — extra fields tolerated, see Body invariants). +- `hooks/scripts/pre-compact-flush.mjs` *refreshes* `updated_at` on existing state files (status `in_progress` or `partial` only). Never creates the file; never changes status or owned fields. + +**Consumer:** `/ultracontinue` (read-only). Reads the file, validates it, narrates a 3-line summary, then begins executing the next session by reading `next_session_brief_path`. + +**Path conventions:** +- Per-project: `.claude/projects/{YYYY-MM-DD}-{slug}/.session-state.local.json` — one file per project directory. +- Gitignored at the plugin level via `*.local.json` (added in v3.3.0). State files MUST NOT be committed — they may contain absolute project paths and label strings that vary per-machine. +- For `--session N` parallel multi-session runs the parent's Phase 8 aggregate write IS the canonical state. Child session writes (inside their worktrees) are ephemeral; the worktree is cleaned up after merge so child state is intentionally discarded. + +**Frontmatter schema:** N/A — file is JSON, not Markdown. Top-level keys: + +| Field | Type | Required | Allowed values | Notes | +|---|---|---|---|---| +| `schema_version` | number | yes | `1` (current) | Bump on breaking changes only | +| `project` | string | yes | absolute or repo-relative path | Project directory containing brief/plan | +| `next_session_brief_path` | string | yes | path to a brief or session-spec | Validator soft-checks file existence (warning, not error) | +| `next_session_label` | string | yes | human-readable label | e.g. "Session 2b" or "Continue" | +| `status` | string | yes | `in_progress \| partial \| failed \| stopped \| completed` | Mirrors progress.json status. `completed` triggers SESSION_STATE_NOT_RESUMABLE warning (valid:true) | +| `updated_at` | string | yes | ISO-8601 timestamp | Refreshed by pre-compact-flush on resumable statuses | + +**Body invariants:** N/A (JSON). + +**Forward-compat — drift-WARN principle:** Unknown top-level keys are **silently tolerated**. The validator does not warn on extras. This is a load-bearing decision: it lets future writers (graceful-handoff v2.2, custom plugin extensions) add metadata fields without breaking `/ultracontinue`. Mirrors Handover 3's discovery-only, drift-WARN posture. + +**Validation strategy:** + +| Layer | When | What | +|---|---|---| +| JSON parse | every read | `JSON.parse` → `SESSION_STATE_PARSE_ERROR` on failure | +| Required fields | every read | All six top-level keys present → `SESSION_STATE_MISSING_FIELD` on absence | +| Schema version | every read | Numeric `1` → `SESSION_STATE_SCHEMA_MISMATCH` otherwise | +| Status enum | every read | Must be one of the five values → `SESSION_STATE_INVALID_STATUS` otherwise | +| Resumability | every read | `completed` emits `SESSION_STATE_NOT_RESUMABLE` warning but valid:true | +| Path shape | every read | `next_session_brief_path` must be non-empty string → `SESSION_STATE_INVALID_PATH` otherwise | +| Timestamp shape | every read | `updated_at` parses via `Date.parse` → `SESSION_STATE_INVALID_TIMESTAMP` otherwise | +| Unknown keys | every read | Tolerated silently (drift-WARN forward-compat) | + +The validator (`lib/validators/session-state-validator.mjs`) exposes the standard CLI: `node lib/validators/session-state-validator.mjs --json `. Returns `{valid, errors[], warnings[], parsed}`. Exit code 0 on valid, 1 on invalid, 2 on usage error. + +**Versioning:** Current is `1` (number). Schema is **additive only** — new optional fields land without bumping schema_version (forward-compat tolerates them). A breaking change (renaming a field, narrowing the status enum) requires bumping schema_version to `2`, adding migration support in the validator, and following the breaking-change protocol above. + +**Failure modes:** +- `SESSION_STATE_NOT_FOUND` → `/ultracontinue` exits with cold-start message ("no active multi-session project here; start with `/ultrabrief-local` or `/ultraplan-local`") +- `SESSION_STATE_PARSE_ERROR` → halt with structured error; user fixes JSON +- `SESSION_STATE_MISSING_FIELD` → halt; suggests running validator directly +- `SESSION_STATE_SCHEMA_MISMATCH` → halt; future `1` → `2` migration path will warn instead +- `SESSION_STATE_INVALID_STATUS` → halt; protects against typo'd writers +- `SESSION_STATE_NOT_RESUMABLE` → warning; `/ultracontinue` exits cleanly with "no further sessions to resume; project complete" +- Validator failures during writer Phase 8 emit a stderr warning but DO NOT block the session-end report. `progress.json` remains the authoritative record of what was attempted. + +--- + ## Stability summary | Handover | Validation strength | Owner | Risk | @@ -360,5 +424,6 @@ The validator (`lib/validators/review-validator.mjs`) exposes the same CLI as th | 4. plan → execute | **strict, both ends** | this plugin | medium — Opus 4.7 narrative drift requires constant vigilance | | 5. progress.json | shape + resume readiness | this plugin | medium — drift during compaction handled by pre-compact-flush hook (CC v2.1.105+) | | 6. review → plan | strict at write, soft at read | this plugin | low — additive feedback loop; consumer falls back gracefully when source_findings is absent | +| 7. session-state (multi-session resume) | required-fields + status enum + drift-WARN extras | this plugin | low — readers tolerate unknown keys; writers are owned by ultraexecute Phase 8 + helper command | When extending the plugin or adding a new pipeline stage, follow the same pattern: produce an artifact with a versioned frontmatter (or `schema_version` for JSON), write a validator under `lib/validators/`, add fixtures under `tests/fixtures/`, and add an entry to this document. diff --git a/plugins/ultraplan-local/tests/lib/doc-consistency.test.mjs b/plugins/ultraplan-local/tests/lib/doc-consistency.test.mjs index 56dc169..5890765 100644 --- a/plugins/ultraplan-local/tests/lib/doc-consistency.test.mjs +++ b/plugins/ultraplan-local/tests/lib/doc-consistency.test.mjs @@ -109,6 +109,19 @@ test('HANDOVER-CONTRACTS.md contains Handover 6 section', () => { ); }); +test('HANDOVER-CONTRACTS.md contains Handover 7 section (session-state)', () => { + const text = read('docs/HANDOVER-CONTRACTS.md'); + assert.ok( + text.includes('## Handover 7'), + 'docs/HANDOVER-CONTRACTS.md should document Handover 7 (.session-state.local.json) ' + + 'consumed by /ultracontinue', + ); + assert.ok( + text.includes('.session-state.local.json'), + 'Handover 7 section should name the artifact path', + ); +}); + test('review-validator has CLI shim', () => { const text = read('lib/validators/review-validator.mjs'); assert.ok( @@ -118,6 +131,24 @@ test('review-validator has CLI shim', () => { ); }); +test('session-state-validator has CLI shim', () => { + const text = read('lib/validators/session-state-validator.mjs'); + assert.ok( + text.includes('import.meta.url === '), + 'lib/validators/session-state-validator.mjs should expose the standard CLI shim ' + + '(if (import.meta.url === `file://${process.argv[1]}`)) so /ultracontinue can call it from Bash', + ); +}); + +test('CLAUDE.md mentions /ultracontinue-local command', () => { + const md = read('CLAUDE.md'); + assert.ok( + md.includes('/ultracontinue-local') || md.includes('ultracontinue-local'), + 'CLAUDE.md should document /ultracontinue-local in the Commands table ' + + '(added in v3.3.0 alongside the new command file)', + ); +}); + test('rule-catalogue has exactly 12 entries', async () => { const mod = await import('../../lib/review/rule-catalogue.mjs'); assert.strictEqual(