--- name: trekcontinue description: Resume the next session in a multi-session trekplan project. Reads .session-state.local.json and immediately begins the next session. argument-hint: "[ | --help]" --- # Ultracontinue Local v1.0 Zero-friction multi-session resumption. In a fresh Claude Code session, type `/trekcontinue` — the command reads the per-project state file (`.claude/projects//.session-state.local.json`), shows a 3-line summary, and immediately begins executing the next session. The state file is the contract. Any session-end mechanism may write it (`/trekexecute` Phase 8 / Phase 2.55 / Phase 4, the `/trekendsession` helper, or — in the future — `graceful-handoff`). This command only reads. Pipeline position: ``` /trekplan → plan.md /trekexecute → progress.json + .session-state.local.json ... session boundary, fresh chat ... /trekcontinue → reads .session-state.local.json, starts next session ``` See **Handover 7** in `docs/HANDOVER-CONTRACTS.md` for the full schema. ## Phase 0 — `--help` handling Parse `$ARGUMENTS` with the shared arg-parser. Run first, exactly like this: ```bash node ${CLAUDE_PLUGIN_ROOT}/lib/parsers/arg-parser.mjs --command trekcontinue --stdin <<'VOYAGE_ARGS_EOF_7F3C9E' $ARGUMENTS VOYAGE_ARGS_EOF_7F3C9E ``` The arguments reach the parser as the one line inside the quoted heredoc, on stdin, never as shell words. It prints the parse result as JSON. On exit 1, print its `errors` and stop. When it prints no JSON, say that the parser did not run and stop. Read `flags` and `positional` from the JSON it prints. Dispatch the usage block ONLY when one of these two conditions equals exactly true (no substring search, no "contains" check): - `flags['--help'] === true`, OR - `positional[0] === '-h'` (single-dash short form — the parser keeps it as positional because the schema does not declare an alias). In every other case — including when `$ARGUMENTS` is empty, whitespace-only, the literal empty string `""`, or a positional project-dir — fall through to Phase 1. Do NOT print the usage block on empty args. ``` /trekcontinue — Resume the next session in a multi-session trekplan project. Usage: /trekcontinue # auto-discover state file under cwd /trekcontinue # explicit project directory /trekcontinue --cleanup # dry-run: list stale files /trekcontinue --cleanup --confirm # actually delete (requires status: completed) /trekcontinue --help # this message Reads .claude/projects//.session-state.local.json (per-project, gitignored). On a valid resumable state, prints a 3-line summary and begins executing the next session immediately. No interactive confirmation prompt. State-file schema (v1): schema_version 1 project string next_session_brief_path string (validator soft-checks file existence) next_session_label string status in_progress | partial | failed | stopped | completed (completed → no further sessions to resume) updated_at ISO-8601 timestamp (unknown top-level keys are tolerated — forward-compat for graceful-handoff v2.2) Typical flow: /trekbrief # produces brief.md /trekplan --project ... # produces plan.md /trekexecute --project .. # writes session-state on session-end ... (fresh Claude chat) ... /trekcontinue # reads session-state, runs next session ``` ## Phase 0.5 — Cleanup mode dispatch After the parser has printed its parse of `$ARGUMENTS`, check the parsed `flags` object directly (NOT a string contains-check on raw `$ARGUMENTS` — that substring pattern was the root cause of Bug 1). If `flags['--cleanup'] === true`, switch into the terminal cleanup flow and do NOT proceed to Phase 1 or any later phase. **Required positional:** an explicit `` (`positional[0]`). There is no "clean all" mode — accidental wholesale deletion would be irreversible. If `positional[0]` is missing, empty, or starts with `-`, print this usage block to stderr and exit non-zero: ``` Error: /trekcontinue --cleanup requires . Usage: /trekcontinue --cleanup # dry-run: list stale files /trekcontinue --cleanup --confirm # actually delete (status: completed) ``` **Compute mode from parsed flags:** ``` dryRun = (flags['--confirm'] !== true) confirm = (flags['--confirm'] === true) ``` **Invoke cleanup inline.** Emit the concrete project-dir path as a literal token in the Bash command — never a template placeholder — same anti-substitution rule as Phase 2: ``` node --input-type=module -e "import {cleanupProject} from '${CLAUDE_PLUGIN_ROOT}/lib/util/cleanup.mjs'; const [, dir, mode] = process.argv; const r = cleanupProject(dir, {dryRun: mode !== 'confirm', confirm: mode === 'confirm'}); console.log(JSON.stringify(r, null, 2)); process.exit(r.valid ? 0 : 1)" '' '' ``` Substitute `` with the literal `positional[0]` value you have in your working context, and `` with either the literal string `dryrun` or the literal string `confirm` based on the booleans above. The validator emits a `{valid, errors, warnings, parsed}` JSON record. Print it to stdout. Exit with the validator's exit code. **Cleanup is a terminal mode.** It must not fall through to Phase 1/2/3/4. Operators who want to resume after cleanup must invoke `/trekcontinue` again without `--cleanup`. ## Phase 1 — Resolve project directory The parsed `positional[0]` from Phase 0 is the explicit project-dir argument, when present. Otherwise (empty `$ARGUMENTS` or whitespace-only) auto-discover. ### Step 1.a — Reject `.md` positional argument (SC-2) If `positional[0]` is non-empty AND ends in `.md`, the user almost certainly pasted a `NEXT-SESSION-PROMPT.local.md` path instead of a project directory. Print the following diagnostic to stderr and exit non-zero. Do NOT proceed. ``` Error: expected , got a markdown file path: Did you mean to paste the file path as a project directory? Usage: /trekcontinue ``` ### Step 1.b — Auto-discover candidates When no explicit project-dir was given, enumerate `.claude/projects/*/.session-state.local.json` paths with `node -e` (NOT shell glob — harness-mode safety) and emit each as one JSON line of `{"path": ..., "updated_at": ...}` so Phase 1 can sort numerically: ```bash !`node -e "const fs=require('fs'),path=require('path');const root='.claude/projects';if(!fs.existsSync(root))process.exit(0);for(const d of fs.readdirSync(root)){const p=path.join(root,d,'.session-state.local.json');if(!fs.existsSync(p))continue;let u='';try{u=(JSON.parse(fs.readFileSync(p,'utf8'))||{}).updated_at||''}catch(_){};process.stdout.write(JSON.stringify({path:p,updated_at:u})+'\\n');}"` ``` Sort the emitted candidates by `Date.parse(updated_at)` descending (newest first) — numeric comparison, NOT lexicographic string compare. The newest resumable state wins. ### Step 1.c — Decision tree - **0 candidates and no explicit arg:** print SC-2 cold-start message and exit: ``` No active multi-session project here. Start with /trekbrief or /trekplan. ``` - **1 candidate (or explicit non-`.md` arg):** continue to Phase 2 with that path. - **>1 candidates and no explicit arg:** with the Date.parse sort applied, the newest resumable state wins automatically and the command continues to Phase 2 with that path. (Operators who want a different candidate re-invoke as `/trekcontinue `.) ## Phase 1.5 — Frontmatter consistency check Bug 3 contract: producers (`/trekexecute`, `/trekendsession`) write `NEXT-SESSION-PROMPT.local.md` with YAML frontmatter (`produced_by:`, `produced_at:`). Multiple producers may have written candidates in different locations; this phase refuses ambiguity before validating the state file. After resolving the project directory and state-file path, look for two `NEXT-SESSION-PROMPT.local.md` candidates: a. `NEXT-SESSION-PROMPT.local.md` at the repo root (`git rev-parse --show-toplevel`) — operator-managed master file, written by the operator or an external producer, never by Voyage b. `/NEXT-SESSION-PROMPT.local.md` — producer-written sibling **If both exist:** - Read both via the **Read tool** (NOT Bash — same anti-substitution rule as Phase 2). - Invoke the consistency validator with the paths emitted as concrete literal tokens (no template substitution at the Bash boundary). When the project's `.session-state.local.json` exists, pass it with `--state-file` (else run the same line without the option): ``` node ${CLAUDE_PLUGIN_ROOT}/lib/validators/next-session-prompt-validator.mjs --json --consistency --state-file ``` Replace `` and `` with the two concrete filesystem paths you have in your working context, and `` with the concrete path of the project's `.session-state.local.json`. The validator emits `{valid, errors, warnings}` JSON on stdout. - **If `valid: false`** (typically `NEXT_SESSION_PROMPT_PRODUCER_MISMATCH`): print the structured `errors[]` (each `[code] message` on its own line), list both candidate paths, and exit non-zero. Do NOT proceed to Phase 2. Resolve the conflict by deleting the stale candidate (run `/trekcontinue --cleanup --confirm ` after the current session closes, or remove by hand). - **If `valid: true` with a `NEXT_SESSION_PROMPT_WALL_CLOCK_DRIFT` warning** (one of the candidates is more than 24h old): print the warning to stderr but continue — long pauses (weekend, vacation) are not failures. - **If `valid: true` with a `NEXT_SESSION_PROMPT_STALE_IGNORED` warning** (one candidate is older than the state file's `updated_at`): print the warning and continue. The state-anchored check is the primary refusal signal; staleness simply rejects the older candidate. **If only one exists:** continue to Phase 2. No comparison needed. **If neither exists:** continue to Phase 2. Legacy projects and first-run flows have no NEXT-SESSION-PROMPT files. ## Phase 2 — Validate the state file Phase 1 resolved a concrete state-file path. That path is a real string in your working context — never a template. Phase 2 must read and validate the state file without any placeholder substitution. ### Step 2.a — Read the file with the Read tool (no Bash) Use the **Read tool** on the resolved state-file path from Phase 1. Do NOT use Bash for the read. The Read tool is deterministic and not subject to shell-substitution errors. Parse the returned JSON body programmatically. ### Step 2.b — Schema-validate the parsed object Verify the schema by invoking the existing validator CLI shim. Emit the resolved absolute path as a literal string token in the Bash command — the exact same string you just passed to the Read tool. The validator accepts `--json ` and prints a `{valid, errors, warnings}` JSON record: ``` node ${CLAUDE_PLUGIN_ROOT}/lib/validators/session-state-validator.mjs --json ``` Replace `` with the actual path string at the time you issue the Bash call. There is no template engine; the string is substituted by you, the model, before the Bash tool sees the command. **Anti-substitution invariant.** If you ever find yourself about to emit a literal angle-bracket placeholder, or any other unresolved variable name, to the Bash tool — STOP. The resolved path is a concrete value you already have from Phase 1; emit the value, not a placeholder for it. ### Step 2.c — Interpret the result - **Validator exit code != 0 OR `valid: false` in JSON output:** print the structured `errors[]` (each `[code] message` on its own line) and exit. Do not proceed to narration. Suggest running the validator directly for follow-up: `node ${CLAUDE_PLUGIN_ROOT}/lib/validators/session-state-validator.mjs `. - **`valid: true` AND any warning has `code: SESSION_STATE_NOT_RESUMABLE`** (i.e. `status: completed`): print "no further sessions to resume; project complete" and exit cleanly. - **`valid: true` AND status is one of `in_progress | partial | failed | stopped`:** proceed to Phase 3. ## Phase 3 — Narrate 3-line summary Print this exact template (using values from the validated `parsed` object): ``` Project: {project} Next session: {next_session_label} Brief: {next_session_brief_path} ``` No interactive confirmation prompt — per the brief NFR ("ingen prompts, men la informasjon synes"). The 3-line block is informational only. ## Phase 4 — Begin execution Read the file at `next_session_brief_path` (it is the brief that the next session is supposed to execute — typically the same `brief.md` for single-brief multi-session plans, or a session-specific spec for parallel session decomposition). Understand the task and begin executing per the standard trekplan pipeline. The user did not type a separate "start" command — `/trekcontinue` is the start. If the brief file does not exist (validator emits a warning but does not fail), print: `Warning: next_session_brief_path "{path}" does not exist on disk. Cannot continue automatically.` and exit. Do not guess. ## Phase 5 — Stats tracking Append one record to `trekcontinue-stats.jsonl` in the plugin data directory. Run it exactly like this. The record goes on stdin, and the writer resolves the directory: `CLAUDE_PLUGIN_DATA`, which the Bash tool env does not carry, else the directory `scripts/yardstick.mjs` reads. ```bash node ${CLAUDE_PLUGIN_ROOT}/lib/stats/stats-append.mjs trekcontinue <<'JSON' {"ts":"","project":"","next_session_label":"