diff --git a/plugins/ultraplan-local/commands/ultraplan-end-session-local.md b/plugins/ultraplan-local/commands/ultraplan-end-session-local.md new file mode 100644 index 0000000..0e7c95e --- /dev/null +++ b/plugins/ultraplan-local/commands/ultraplan-end-session-local.md @@ -0,0 +1,156 @@ +--- +name: ultraplan-end-session-local +description: Mark the current session as complete and write session-state pointing at the next session. Helper for informal multi-session flows. +argument-hint: " | --help" +model: sonnet +--- + +# Ultraplan End-Session Local v1.0 + +Tiny helper for **informal** multi-session flows (no formal plan with +Execution Strategy). Writes a `.session-state.local.json` pointing at the +next session so `/ultracontinue` can resume in a fresh Claude chat. + +For formal flows (a plan produced by `/ultraplan-local --project`), +`/ultraexecute-local` Phase 8 already writes the state file — this helper +is unnecessary there. Use this command for ad-hoc release runs, manual +multi-session handovers, or any flow that does not run through +`/ultraexecute-local`. + +Pipeline position: + +``` +... session N work ... +/ultraplan-end-session-local "" → writes state +... session boundary, fresh chat ... +/ultracontinue → reads state, starts session N+1 +``` + +See **Handover 7** in `docs/HANDOVER-CONTRACTS.md` for the schema. + +## Phase 0 — `--help` handling + +If `$ARGUMENTS` contains `--help` or `-h`, print the usage block below and exit +cleanly. Do NOT proceed to any further phase. + +``` +/ultraplan-end-session-local — Mark current session done; point at next session. + +Usage: + /ultraplan-end-session-local + /ultraplan-end-session-local --help + +Both arguments are REQUIRED. No interactive prompt — headless-safe. + +Writes /.session-state.local.json with: + schema_version 1 + project + next_session_brief_path + next_session_label + status in_progress + updated_at + +Then validates via lib/validators/session-state-validator.mjs and prints +the same 3-line narration that /ultracontinue will show in the next session. + +Example: + /ultraplan-end-session-local .claude/projects/2026-05-01-feature/brief.md "Session 2 of 3" +``` + +## Phase 1 — Resolve project directory + +Resolve the nearest `.claude/projects/*/brief.md` from cwd (the current working +directory). Use `node -e` enumeration (NOT shell glob — harness-mode safety): + +```bash +!`node -e "const fs=require('fs'),path=require('path');const root='.claude/projects';if(!fs.existsSync(root)){process.exit(0)}const dirs=fs.readdirSync(root).filter(d=>fs.existsSync(path.join(root,d,'brief.md'))).map(d=>path.join(root,d));dirs.forEach(p=>process.stdout.write(p+'\\n'));"` +``` + +Decision tree: + +- **0 candidates:** print error to stderr — "no `.claude/projects//brief.md` + found under cwd; cannot determine project directory" — and exit 1. Do NOT + fall back to a synthesized path. +- **1 candidate:** use it as ``. Continue. +- **>1 candidates:** print all paths and ask the operator to `cd` into the + intended project directory before retrying. Exit 1. + +## Phase 2 — Required args check (headless-safe) + +Read `$ARGUMENTS`. Both `` and `` are required. +If either is missing or empty: + +``` +Error: missing required args. +Usage: /ultraplan-end-session-local '' +``` + +Print to stderr and exit 1. **No interactive prompt** — this keeps the helper +headless-safe (per brief NFR; addresses adversarial-review major #11). If you +want an interactive flow, use `/ultracontinue --help` to see the full pipeline. + +## Phase 3 — Atomically write `.session-state.local.json` + +Write `/.session-state.local.json` with the schema-v1 object: + +```json +{ + "schema_version": 1, + "project": "", + "next_session_brief_path": "", + "next_session_label": "", + "status": "in_progress", + "updated_at": "" +} +``` + +Use the atomic-write util — write to `.tmp`, then `rename` into place — +to avoid partial-state on crash. Inline-call pattern: + +```bash +!`node -e " +const path=require('path'); +const {atomicWriteJson}=require('./lib/util/atomic-write.mjs'); +const obj={schema_version:1,project:process.argv[1],next_session_brief_path:process.argv[2],next_session_label:process.argv[3],status:'in_progress',updated_at:new Date().toISOString()}; +atomicWriteJson(path.join(process.argv[1],'.session-state.local.json'),obj); +console.log(path.join(process.argv[1],'.session-state.local.json')); +" '' '' ''` +``` + +(Note: `atomic-write.mjs` is ESM; if the inline `require()` form fails in your +Node version, fall back to `node --input-type=module -e "..."` with `import`.) + +## Phase 4 — Validate + narrate + +Validate the freshly-written state file: + +```bash +!`node lib/validators/session-state-validator.mjs --json /.session-state.local.json` +``` + +If `valid: true`, print the success block matching `/ultracontinue` Phase 3 +narration (SC-8 cross-project consistency — same template both sides): + +``` +Session state written: /.session-state.local.json + +Project: +Next session: +Brief: + +In a fresh Claude session, run /ultracontinue to resume. +``` + +If `valid: false`, print the structured `errors[]` and exit 1. Investigate +before retrying — usually means a bad path or label argument. + +## Hard rules + +- **Required args, no defaults.** Never invent a brief path or session label. + If args are missing, fail loud. +- **Atomic write only.** Tmp + rename — no partial state files on disk. +- **Zero secrets.** Status, paths, labels — never API keys, never user content + beyond filenames. +- **NEVER auto-invoke this command.** It is operator-typed only at session-end. +- **Idempotent within a session.** Running twice with the same args + overwrites cleanly (atomic rename); does not double-advance.