ktg-plugin-marketplace/plugins/ultraplan-local/commands/ultraplan-end-session-local.md
Kjell Tore Guttormsen 2690ab501f feat(ultraplan-local): add /ultraplan-end-session helper for informal multi-session flows [skip-docs]
Tiny helper command for ad-hoc multi-session flows that don't run through
/ultraexecute-local. Writes .session-state.local.json so /ultracontinue
can resume in a fresh chat. Required args (next-brief-path, next-label) —
no inline prompt, headless-safe. Validates via session-state-validator
and prints the same 3-line narration that /ultracontinue Phase 3 uses
(SC-8 cross-project consistency).

Step 9 of /ultracontinue v3.3.0. README/CLAUDE updates land in Step 11.
2026-05-01 20:58:46 +02:00

5.8 KiB

name description argument-hint model
ultraplan-end-session-local Mark the current session as complete and write session-state pointing at the next session. Helper for informal multi-session flows. <next-brief-path> <next-label> | --help 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 <brief> "<next-label>"   →  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 <next-brief-path> <next-label>
  /ultraplan-end-session-local --help

Both arguments are REQUIRED. No interactive prompt — headless-safe.

Writes <project-dir>/.session-state.local.json with:
  schema_version           1
  project                  <auto-resolved from cwd>
  next_session_brief_path  <next-brief-path argument>
  next_session_label       <next-label argument>
  status                   in_progress
  updated_at               <now, ISO-8601>

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):

!`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/<dir>/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 <project-dir>. 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 <next-brief-path> and <next-label> are required. If either is missing or empty:

Error: missing required args.
Usage: /ultraplan-end-session-local <next-brief-path> '<next-label>'

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 <project-dir>/.session-state.local.json with the schema-v1 object:

{
  "schema_version": 1,
  "project": "<project-dir>",
  "next_session_brief_path": "<arg 1>",
  "next_session_label": "<arg 2>",
  "status": "in_progress",
  "updated_at": "<now, ISO-8601>"
}

Use the atomic-write util — write to <path>.tmp, then rename into place — to avoid partial-state on crash. Inline-call pattern:

!`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'));
" '<project-dir>' '<next-brief-path>' '<next-label>'`

(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:

!`node lib/validators/session-state-validator.mjs --json <project-dir>/.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: <project-dir>/.session-state.local.json

Project: <project-dir>
Next session: <next-label>
Brief: <next-brief-path>

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.