fix(ultraplan-local): Bug 1 — strict --help match + .md-arg diagnostic + Date.parse sort
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 <project-dir>" + "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.
This commit is contained in:
parent
06c0a0a86b
commit
100ffe94f1
1 changed files with 43 additions and 18 deletions
|
|
@ -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 `<project-dir>`. 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 <project-dir>, got a markdown file path: <positional[0]>
|
||||
Did you mean to paste the file path as a project directory?
|
||||
Usage: /ultracontinue-local <project-dir>
|
||||
```
|
||||
|
||||
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 `<project-dir>`,
|
||||
then exit:
|
||||
```
|
||||
Multiple active multi-session projects found:
|
||||
.claude/projects/<a>/.session-state.local.json updated <ts>
|
||||
.claude/projects/<b>/.session-state.local.json updated <ts>
|
||||
Re-run as: /ultracontinue <project-dir>
|
||||
```
|
||||
- **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 <project-dir>`.)
|
||||
|
||||
## Phase 2 — Validate the state file
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue