Step 7 av v4.1-execute (Wave 3, Session 4). Legg ny "## Profile (v4.1)"-seksjon i hver kommando-fil rett før "## Hard rules": - trekbrief.md: --profile + VOYAGE_PROFILE + premium default - trekresearch.md: + economy/balanced auto-disable external_research_enabled - trekplan.md: + plan.md frontmatter recording for inheritance - trekexecute.md: + 4-step resolution (flag > env > inheritance > default) - trekreview.md: + opus-default for review-deepening - trekcontinue.md: spesiell — INHERITANCE er default (ikke premium), --profile overstyr emitter stderr-advarsel Tester (13 nye, baseline 432 → 445): - 6 commands × 2 (--profile + VOYAGE_PROFILE) - trekcontinue.md "inheritance"-keyword Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
337 lines
15 KiB
Markdown
337 lines
15 KiB
Markdown
---
|
||
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: "[<project-dir> | --help]"
|
||
model: opus
|
||
---
|
||
|
||
# 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 `parseArgs($ARGUMENTS, 'trekcontinue')` from
|
||
`lib/parsers/arg-parser.mjs`. 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 <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 `parseArgs` has resolved `$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 './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:
|
||
|
||
```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 <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. `<plugin-root>/NEXT-SESSION-PROMPT.local.md` — operator-managed master file
|
||
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 both paths emitted as concrete
|
||
literal tokens (no template substitution at the Bash boundary):
|
||
|
||
```
|
||
node lib/validators/next-session-prompt-validator.mjs --json --consistency <RESOLVED-PATH-A> <RESOLVED-PATH-B>
|
||
```
|
||
|
||
Replace `<RESOLVED-PATH-A>` and `<RESOLVED-PATH-B>` with the two concrete
|
||
filesystem paths you have in your working context. 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 <project-dir>` 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 <path>` and prints a `{valid, errors, warnings}` JSON record:
|
||
|
||
```
|
||
node 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: 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 lib/validators/session-state-validator.mjs <path>`.
|
||
- **`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 a one-line JSON record to `${CLAUDE_PLUGIN_DATA}/trekcontinue-stats.jsonl`
|
||
if the env var is set; silently skip otherwise.
|
||
|
||
```json
|
||
{"ts":"<iso-8601>","project":"<project>","next_session_label":"<label>","status":"<status>"}
|
||
```
|
||
|
||
## 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`):
|
||
1. `--profile` flag (source: `flag`) — explicit override, emits stderr advisory
|
||
`[voyage] profile inheritance overridden by --profile flag: <plan-profile> → <flag-profile>`
|
||
2. `VOYAGE_PROFILE` env-var (source: `env`)
|
||
3. Plan-frontmatter `profile:` field (source: `inheritance`) — typical case
|
||
4. `premium` default for v4.0-style plans without `profile:` (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
|
||
```
|
||
|
||
Stats records emit `profile` and `profile_source` per Phase 5 record.
|
||
|
||
## Hard rules
|
||
|
||
- **Idempotent.** Running `/trekcontinue` twice 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.
|