From 100ffe94f18be3de984b12074b592567d4b67e08 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Mon, 4 May 2026 16:38:04 +0200 Subject: [PATCH] =?UTF-8?q?fix(ultraplan-local):=20Bug=201=20=E2=80=94=20s?= =?UTF-8?q?trict=20--help=20match=20+=20.md-arg=20diagnostic=20+=20Date.pa?= =?UTF-8?q?rse=20sort?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 3 of v3.4.1 hot-fix plan. Three fixes in commands/ultracontinue-local.md: - Phase 0: replace "$ARGUMENTS contains --help or -h" with parsed-arg dispatch via parseArgs(...,'ultracontinue'). Usage block fires only when flags['--help'] === true OR positional[0] === '-h'. Empty, whitespace, and project-dir args fall through to Phase 1 (auto-discovery), which is the operator-default invocation. - Phase 1.a: NEW — reject .md positional arg with SC-2 diagnostic ("expected " + "did you mean to paste"). Operators pasting a NEXT-SESSION-PROMPT.local.md path see a clear error instead of a confusing fallthrough. - Phase 1.b: auto-discovery node -e now emits {path, updated_at} JSON per candidate; Phase 1 sorts numerically via Date.parse(updated_at) DESC instead of lexicographic compare. Newest in_progress wins, including across year-boundary timestamps. All 4 Step 2 regression tests now green; full suite 322 → 333 passing. --- .../commands/ultracontinue-local.md | 63 +++++++++++++------ 1 file changed, 44 insertions(+), 19 deletions(-) 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