From 512ae322bd54c80fa17ce5c16856952dc22a4db0 Mon Sep 17 00:00:00 2001 From: Kjell Tore Guttormsen Date: Mon, 4 May 2026 17:37:21 +0200 Subject: [PATCH] =?UTF-8?q?fix(ultraplan-local):=20Bug=203=20producers=20?= =?UTF-8?q?=E2=80=94=20frontmatter=20writes=20+=20ESM/CJS=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../commands/ultraexecute-local.md | 31 ++++++++++++++ .../commands/ultraplan-end-session-local.md | 40 +++++++++++++------ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/plugins/ultraplan-local/commands/ultraexecute-local.md b/plugins/ultraplan-local/commands/ultraexecute-local.md index c7bb0b7..ebfeff2 100644 --- a/plugins/ultraplan-local/commands/ultraexecute-local.md +++ b/plugins/ultraplan-local/commands/ultraexecute-local.md @@ -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 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: `, +`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 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 but do not block. +**Also write sibling `NEXT-SESSION-PROMPT.local.md`** with frontmatter +(`produced_by: ultraexecute-local`, `produced_at: `, +`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**: ``` 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 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: ` 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 converges here (Path A: successful single session, Path B: `--session N` explicit, Path C: compaction-survival recovery, Path D: standard plan diff --git a/plugins/ultraplan-local/commands/ultraplan-end-session-local.md b/plugins/ultraplan-local/commands/ultraplan-end-session-local.md index 0e7c95e..c26406c 100644 --- a/plugins/ultraplan-local/commands/ultraplan-end-session-local.md +++ b/plugins/ultraplan-local/commands/ultraplan-end-session-local.md @@ -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): ```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: @@ -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 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 `/.session-state.local.json` with the schema-v1 object: @@ -105,21 +105,37 @@ Write `/.session-state.local.json` with the schema-v1 object: ``` Use the atomic-write util — write to `.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 "