16 KiB
| name | description | argument-hint |
|---|---|---|
| trekcontinue | Resume the next session in a multi-session trekplan project. Reads .session-state.local.json and immediately begins the next session. | [<project-dir> | --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/<project>/.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:
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, ORpositional[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 <project-dir> # explicit project directory
/trekcontinue --cleanup <project-dir> # dry-run: list stale files
/trekcontinue --cleanup --confirm <project-dir> # actually delete (requires status: completed)
/trekcontinue --help # this message
Reads .claude/projects/<project>/.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 <project-dir> (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 <project-dir>.
Usage:
/trekcontinue --cleanup <project-dir> # dry-run: list stale files
/trekcontinue --cleanup --confirm <project-dir> # 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)" '<RESOLVED-PROJECT-DIR>' '<MODE>'
Substitute <RESOLVED-PROJECT-DIR> with the literal positional[0]
value you have in your working context, and <MODE> 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 <project-dir>, got a markdown file path: <positional[0]>
Did you mean to paste the file path as a project directory?
Usage: /trekcontinue <project-dir>
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:
!`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-
.mdarg): 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 <project-dir>.)
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. <project-dir>/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.jsonexists, 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 <RESOLVED-PATH-A> <RESOLVED-PATH-B> --state-file <RESOLVED-PATH-STATE>Replace
<RESOLVED-PATH-A>and<RESOLVED-PATH-B>with the two concrete filesystem paths you have in your working context, and<RESOLVED-PATH-STATE>with the concrete path of the project's.session-state.local.json. The validator emits{valid, errors, warnings}JSON on stdout. -
If
valid: false(typicallyNEXT_SESSION_PROMPT_PRODUCER_MISMATCH): print the structurederrors[](each[code] messageon 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 <project-dir>after the current session closes, or remove by hand). -
If
valid: truewith aNEXT_SESSION_PROMPT_WALL_CLOCK_DRIFTwarning (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: truewith aNEXT_SESSION_PROMPT_STALE_IGNOREDwarning (one candidate is older than the state file'supdated_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 <path> and prints a {valid, errors, warnings} JSON record:
node ${CLAUDE_PLUGIN_ROOT}/lib/validators/session-state-validator.mjs --json <RESOLVED-ABSOLUTE-PATH-FROM-PHASE-1>
Replace <RESOLVED-ABSOLUTE-PATH-FROM-PHASE-1> 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: falsein JSON output: print the structurederrors[](each[code] messageon 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 <path>. valid: trueAND any warning hascode: SESSION_STATE_NOT_RESUMABLE(i.e.status: completed): print "no further sessions to resume; project complete" and exit cleanly.valid: trueAND status is one ofin_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.
node ${CLAUDE_PLUGIN_ROOT}/lib/stats/stats-append.mjs trekcontinue <<'JSON'
{"ts":"<iso-8601>","project":"<project>","next_session_label":"<label>","status":"<status>"}
JSON
The writer prints {"written": true, ...}. On "written": false (exit 1),
print its reason in one line and continue. A tracking failure never blocks
the main workflow, but it is reported, not skipped silently.
Profile (v4.1) — inheritance from plan-frontmatter
Accepts --profile <name> where <name> is economy, balanced, premium,
or a custom profile under voyage-profiles/. Default: premium.
Unlike the other voyage commands, /trekcontinue defaults to inheritance
from the resumed plan's frontmatter profile: field rather than to premium.
This keeps a multi-session run consistent across resumptions.
Resolution order (per lib/profiles/resolver.mjs resolveTrekcontinueProfile):
--profileflag (source:flag) — explicit override, emits stderr advisory[voyage] profile inheritance overridden by --profile flag: <plan-profile> → <flag-profile>VOYAGE_PROFILEenv-var (source:env)- Plan-frontmatter
profile:field (source:inheritance) — typical case premiumdefault for v4.0-style plans withoutprofile:(source:default)
The inherited profile drives phase_models.continue for the next session's
orchestration. Operators who explicitly want to switch profiles mid-run pass
--profile <name> and accept the advisory (e.g. drop from premium to
balanced mid-run to save cost on later sessions).
Examples:
/trekcontinue # inherits from plan.md
/trekcontinue --profile balanced # explicit override + advisory
VOYAGE_PROFILE=economy /trekcontinue # env-var override
Hard rules
- Idempotent. Running
/trekcontinuetwice in the same Claude session does not advance state — the writer (Phase 8 / hook / helper) advances state only when a session completes. - Zero secrets in the state file. Status, paths, labels — never API keys, never user content beyond filenames.
- NEVER auto-load via SessionStart. The command is operator-invoked only.
Auto-loading would re-introduce the stale-file risk noted in
feedback_next_session_prompt_manual.md. - No interactive prompts. Phases 0–4 must run without
AskUserQuestion. This keeps the command headless-safe.