diff --git a/plugins/ultraplan-local/commands/ultracontinue-local.md b/plugins/ultraplan-local/commands/ultracontinue-local.md index b3e3853..2159c79 100644 --- a/plugins/ultraplan-local/commands/ultracontinue-local.md +++ b/plugins/ultraplan-local/commands/ultracontinue-local.md @@ -30,8 +30,17 @@ See **Handover 7** in `docs/HANDOVER-CONTRACTS.md` for the full 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. +Parse `$ARGUMENTS` with `parseArgs($ARGUMENTS, 'ultracontinue')` 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. ``` /ultracontinue — Resume the next session in a multi-session ultraplan project. @@ -65,32 +74,48 @@ Typical flow: ## Phase 1 — Resolve project directory -If `$ARGUMENTS` is non-empty and not `--help`/`-h`, treat the first positional -argument as the explicit ``. Otherwise auto-discover via Bash by -enumerating `.claude/projects/*/.session-state.local.json` paths with `node -e` -(NOT shell glob — harness-mode safety): +The parsed `positional[0]` from Phase 0 is the explicit project-dir argument, +when present. Otherwise (empty `$ARGUMENTS` or whitespace-only) auto-discover. -```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).map(d=>path.join(root,d,'.session-state.local.json')).filter(p=>fs.existsSync(p));dirs.forEach(p=>process.stdout.write(p+'\\n'));"` +### 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: /ultracontinue-local ``` -Decision tree: +### 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 /ultrabrief-local or /ultraplan-local. ``` -- **1 candidate (or explicit arg):** continue to Phase 2 with that path. -- **>1 candidates and no explicit arg:** print all paths sorted by `updated_at` - descending, instruct the user to disambiguate via explicit ``, - then exit: - ``` - Multiple active multi-session projects found: - .claude/projects//.session-state.local.json updated - .claude/projects//.session-state.local.json updated - Re-run as: /ultracontinue - ``` +- **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 + `/ultracontinue `.) ## Phase 2 — Validate the state file