fix(ultraplan-local): Bug 3 producers — frontmatter writes + ESM/CJS fix
Step 7 of v3.4.1 plan.
ultraplan-end-session-local Phase 3:
- Replace require()-of-ESM-module shim with node --input-type=module + import.
- Convert Phase 1 project enumeration to ESM as well so the file is uniformly
ESM (grep -c 'require(' commands/ultraplan-end-session-local.md → 0).
- Combined ESM block writes both .session-state.local.json (atomicWriteJson)
and sibling NEXT-SESSION-PROMPT.local.md (writeFileSync) so producers
succeed or fail together.
- Sibling markdown gets frontmatter: produced_by, produced_at, project.
ultraexecute-local Phases 8 / 2.55 / 4:
- Each phase that writes .session-state.local.json now also writes a sibling
NEXT-SESSION-PROMPT.local.md with frontmatter (produced_by:
ultraexecute-local, produced_at: ISO-8601, status). Phase 8 includes the
full ESM block; 2.55 / 4 reference the combined pattern.
- This is the producer side of the Bug 3 contract; consumer-side wire-up
(Phase 1.5 consistency check in /ultracontinue) lands in Step 8.
Tests: 346 green (no new tests this step — coverage comes via Step 8
integration test).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
46e036e1c3
commit
512ae322bd
2 changed files with 59 additions and 12 deletions
|
|
@ -414,6 +414,11 @@ Verify with `node lib/validators/session-state-validator.mjs --json
|
||||||
{project_dir}/.session-state.local.json`. On validator failure, emit a warning
|
{project_dir}/.session-state.local.json`. On validator failure, emit a warning
|
||||||
to stderr but do NOT block the stop; `progress.json` is still authoritative.
|
to stderr but do NOT block the stop; `progress.json` is still authoritative.
|
||||||
|
|
||||||
|
**Also write sibling `NEXT-SESSION-PROMPT.local.md`** with frontmatter
|
||||||
|
(`produced_by: ultraexecute-local`, `produced_at: <ISO-8601>`,
|
||||||
|
`status: stopped`) so the next-session producer-mismatch check has both
|
||||||
|
candidates available. Use the same combined ESM block pattern as Phase 8.
|
||||||
|
|
||||||
### Check 2 — Plan file is tracked by git
|
### Check 2 — Plan file is tracked by git
|
||||||
|
|
||||||
Run `git ls-files --error-unmatch {plan-path} 2>/dev/null`. If the plan file is
|
Run `git ls-files --error-unmatch {plan-path} 2>/dev/null`. If the plan file is
|
||||||
|
|
@ -898,6 +903,11 @@ the failed entry-condition session was supposed to consume. This lets
|
||||||
write pattern + validator check as Phase 2.55. On validator failure, warn
|
write pattern + validator check as Phase 2.55. On validator failure, warn
|
||||||
but do not block.
|
but do not block.
|
||||||
|
|
||||||
|
**Also write sibling `NEXT-SESSION-PROMPT.local.md`** with frontmatter
|
||||||
|
(`produced_by: ultraexecute-local`, `produced_at: <ISO-8601>`,
|
||||||
|
`status: stopped`) — same combined ESM pattern as Phase 8 — so Phase 1.5
|
||||||
|
of `/ultracontinue` can compare project-dir and plugin-root candidates.
|
||||||
|
|
||||||
If the entry condition **passes**:
|
If the entry condition **passes**:
|
||||||
```
|
```
|
||||||
Entry condition: PASS
|
Entry condition: PASS
|
||||||
|
|
@ -1340,6 +1350,27 @@ Use `lib/util/atomic-write.mjs` (`atomicWriteJson`) — same crash-safety as
|
||||||
--json {project_dir}/.session-state.local.json`. On validator failure, warn
|
--json {project_dir}/.session-state.local.json`. On validator failure, warn
|
||||||
to stderr but do NOT block — Phase 8 must always reach the final report.
|
to stderr but do NOT block — Phase 8 must always reach the final report.
|
||||||
|
|
||||||
|
**Also write sibling `NEXT-SESSION-PROMPT.local.md`** (Bug 3 frontmatter
|
||||||
|
contract — see `docs/HANDOVER-CONTRACTS.md` § Handover 7 Lifecycle) in the
|
||||||
|
project directory. The frontmatter MUST contain `produced_by: ultraexecute-local`
|
||||||
|
and `produced_at: <ISO-8601>` so `/ultracontinue` Phase 1.5 can detect
|
||||||
|
producer drift between project-dir and plugin-root candidates. Use a single
|
||||||
|
ESM inline block so state-file + prompt-file writes succeed or fail together:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
!`node --input-type=module -e "
|
||||||
|
import path from 'node:path';
|
||||||
|
import { writeFileSync } from 'node:fs';
|
||||||
|
import { atomicWriteJson } from './lib/util/atomic-write.mjs';
|
||||||
|
const [, dir, briefPath, label, status] = process.argv;
|
||||||
|
const now = new Date().toISOString();
|
||||||
|
const stateObj = { schema_version: 1, project: dir, next_session_brief_path: briefPath, next_session_label: label, status, updated_at: now };
|
||||||
|
atomicWriteJson(path.join(dir, '.session-state.local.json'), stateObj);
|
||||||
|
const promptBody = '---\\nproduced_by: ultraexecute-local\\nproduced_at: ' + now + '\\nproject: ' + dir + '\\nstatus: ' + status + '\\n---\\n\\n# ' + label + '\\n\\nResume via /ultracontinue.\\n';
|
||||||
|
writeFileSync(path.join(dir, 'NEXT-SESSION-PROMPT.local.md'), promptBody);
|
||||||
|
" '{project_dir}' '{next_session_brief_path}' '{next_session_label}' '{status}'`
|
||||||
|
```
|
||||||
|
|
||||||
This single insertion covers every multi-session execution path that
|
This single insertion covers every multi-session execution path that
|
||||||
converges here (Path A: successful single session, Path B: `--session N`
|
converges here (Path A: successful single session, Path B: `--session N`
|
||||||
explicit, Path C: compaction-survival recovery, Path D: standard plan
|
explicit, Path C: compaction-survival recovery, Path D: standard plan
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ Resolve the nearest `.claude/projects/*/brief.md` from cwd (the current working
|
||||||
directory). Use `node -e` enumeration (NOT shell glob — harness-mode safety):
|
directory). Use `node -e` enumeration (NOT shell glob — harness-mode safety):
|
||||||
|
|
||||||
```bash
|
```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).filter(d=>fs.existsSync(path.join(root,d,'brief.md'))).map(d=>path.join(root,d));dirs.forEach(p=>process.stdout.write(p+'\\n'));"`
|
!`node --input-type=module -e "import {existsSync, readdirSync} from 'node:fs'; import {join} from 'node:path'; const root='.claude/projects'; if(!existsSync(root)) process.exit(0); readdirSync(root).filter(d=>existsSync(join(root,d,'brief.md'))).forEach(d=>process.stdout.write(join(root,d)+'\\n'));"`
|
||||||
```
|
```
|
||||||
|
|
||||||
Decision tree:
|
Decision tree:
|
||||||
|
|
@ -89,7 +89,7 @@ Print to stderr and exit 1. **No interactive prompt** — this keeps the helper
|
||||||
headless-safe (per brief NFR; addresses adversarial-review major #11). If you
|
headless-safe (per brief NFR; addresses adversarial-review major #11). If you
|
||||||
want an interactive flow, use `/ultracontinue --help` to see the full pipeline.
|
want an interactive flow, use `/ultracontinue --help` to see the full pipeline.
|
||||||
|
|
||||||
## Phase 3 — Atomically write `.session-state.local.json`
|
## Phase 3 — Atomically write `.session-state.local.json` + sibling NEXT-SESSION-PROMPT.local.md
|
||||||
|
|
||||||
Write `<project-dir>/.session-state.local.json` with the schema-v1 object:
|
Write `<project-dir>/.session-state.local.json` with the schema-v1 object:
|
||||||
|
|
||||||
|
|
@ -105,21 +105,37 @@ Write `<project-dir>/.session-state.local.json` with the schema-v1 object:
|
||||||
```
|
```
|
||||||
|
|
||||||
Use the atomic-write util — write to `<path>.tmp`, then `rename` into place —
|
Use the atomic-write util — write to `<path>.tmp`, then `rename` into place —
|
||||||
to avoid partial-state on crash. Inline-call pattern:
|
to avoid partial-state on crash. The util is ESM, so invoke via
|
||||||
|
`node --input-type=module -e` with an `import` statement (a CommonJS shim
|
||||||
|
would throw `ERR_REQUIRE_ESM` on Node 18+ since `atomic-write.mjs` is ESM).
|
||||||
|
|
||||||
|
Under `node --input-type=module -e "<script>" arg1 arg2 arg3`, Node sets
|
||||||
|
`process.argv[0]` to the node binary path and user args start at
|
||||||
|
`process.argv[1]`. Adjust the destructure if your Node version differs.
|
||||||
|
|
||||||
|
This phase ALSO writes a sibling `NEXT-SESSION-PROMPT.local.md` in the
|
||||||
|
project directory with YAML frontmatter (`produced_by: ultraplan-end-session-local`,
|
||||||
|
`produced_at: <ISO-8601>`, `project: <project-dir>`). Both files are written
|
||||||
|
in a single ESM block so the writes succeed or fail together:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
!`node -e "
|
!`node --input-type=module -e "
|
||||||
const path=require('path');
|
import path from 'node:path';
|
||||||
const {atomicWriteJson}=require('./lib/util/atomic-write.mjs');
|
import { writeFileSync } from 'node:fs';
|
||||||
const obj={schema_version:1,project:process.argv[1],next_session_brief_path:process.argv[2],next_session_label:process.argv[3],status:'in_progress',updated_at:new Date().toISOString()};
|
import { atomicWriteJson } from './lib/util/atomic-write.mjs';
|
||||||
atomicWriteJson(path.join(process.argv[1],'.session-state.local.json'),obj);
|
const [, dir, brief, label] = process.argv;
|
||||||
console.log(path.join(process.argv[1],'.session-state.local.json'));
|
const now = new Date().toISOString();
|
||||||
|
const stateObj = { schema_version: 1, project: dir, next_session_brief_path: brief, next_session_label: label, status: 'in_progress', updated_at: now };
|
||||||
|
const stateFile = path.join(dir, '.session-state.local.json');
|
||||||
|
atomicWriteJson(stateFile, stateObj);
|
||||||
|
const promptFile = path.join(dir, 'NEXT-SESSION-PROMPT.local.md');
|
||||||
|
const promptBody = '---\\nproduced_by: ultraplan-end-session-local\\nproduced_at: ' + now + '\\nproject: ' + dir + '\\n---\\n\\n# ' + label + '\\n\\nResume via /ultracontinue.\\n';
|
||||||
|
writeFileSync(promptFile, promptBody);
|
||||||
|
console.log(stateFile);
|
||||||
|
console.log(promptFile);
|
||||||
" '<project-dir>' '<next-brief-path>' '<next-label>'`
|
" '<project-dir>' '<next-brief-path>' '<next-label>'`
|
||||||
```
|
```
|
||||||
|
|
||||||
(Note: `atomic-write.mjs` is ESM; if the inline `require()` form fails in your
|
|
||||||
Node version, fall back to `node --input-type=module -e "..."` with `import`.)
|
|
||||||
|
|
||||||
## Phase 4 — Validate + narrate
|
## Phase 4 — Validate + narrate
|
||||||
|
|
||||||
Validate the freshly-written state file:
|
Validate the freshly-written state file:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue