Releases: OthmanAdi/planning-with-files
Release list
v3.9.0: plan resolution binds to a root, refusals are never silent
A thread's plan is now bound to a root, not guessed from the cwd
A Codex thread whose cwd was a shared parent injected an unrelated project's plan on every hook fire (#212, reported by @webwww123). Resolution was cwd relative with no notion of a thread, so with /workspace holding one plan and /workspace/project holding the real one, the parent's pointer was the only pointer the hook could see. The wrong plan then arrived as high priority context on every prompt and every matched tool call, competing with the user's own corrections.
PWF_PLAN_ROOTbinds resolution to a project root by absolute path.PLAN_IDis a cwd relative slug and structurally could not name a nested project's plan at all. A pin that does not resolve fails closed with a notice.- An ambiguous cwd refuses to inject. When the plan came from the shared pointer or the newest by mtime fallback and a project one directory below has its own live plan, nothing is injected and the notice names both escape hatches. An explicit pin,
PLAN_ID, or an attached session stays authoritative. - Applied to the canonical dispatcher, the
.codex/hooks.jsonroute, the Cursor hooks, and the shared resolver in both shell and PowerShell.
Three bugs found while verifying that report
PLANNING_DISABLED=1did nothing on eleven of thirteen install routes. Those SKILL.md variants still carried the v2.43 hook body inlined in their YAML scalars, so they never received the #195 opt-out, the symlink containment guard, nonce delimiters, the v3 attestation refusal, the ledger summary, orPWF_INJECT=smart, and they still wrote the SHA cache to the world writable/tmppath that moved to$XDG_CACHE_HOMEin v3.0.0. All eleven now dispatch to the versioned script, and a test fails the build if one drifts back.- The Stop hook could never find its script on six hosts. Codex, Cursor, Factory, CodeBuddy, Mastra and OpenCode inherited a discovery list naming only Claude Code paths, so completion checking silently did nothing. Discovery is also a first match wins loop now:
ls a b c | head -1returns the alphabetically first hit, so a stale marketplace copy outranked the host native one. - Eight shipped PowerShell scripts could not be parsed by Windows PowerShell 5.1. A BOM-less
.ps1is read as ANSI, and the UTF-8 bytes for an em dash end in0x94, which CP1252 maps to a closing curly quote, opening a string literal that never closes. Every dispatcher swallows the parse error. Dead on Windows: the Cursor injection hook, both.kiroasset scripts, andinit-sessionfor both Chinese variants, so those users could not create a plan at all.
Pi extension, provider errors and the status bar
agent_end never read its event (#211, reported by @killianMei), so a turn that ended with a provider error was treated as complete and got the auto continue follow up, which started another request into the same failing provider up to the limit, burying the original error. It now returns on a trailing assistant stopReason of error or aborted before the counter is touched, so an outage costs no retry budget.
The phase count is published from all four active handlers in every mode, including the all-phases-complete branch where the N/M to M/M transition reached the notification but never the bar. Bundled extension 1.2.3.
Prompt caching, answered with measurements
Injection is byte identical across fires in every context and mode, now asserted rather than assumed (#210, raised by @GlitterKill). Five routes were emitting unnormalized wall clock times because each builds its injection independently and none had received the v2.40 pass. The skill text also no longer attributes a whole-workflow token measurement to per tool call recitation.
Refusals are never silent
The session guard, the ambiguity refusal, a broken pin, and a missing script each say so once per turn and name the way out. plan-doctor reports a refusal as its own state instead of counting the notice as plan bytes and printing PASS.
Verification
Suite 311 to 411 passing, 282 to 453 subtests, 48 Pi extension tests, IDE folder sync clean. Built by Fable agents, reviewed by Opus, with a Sonnet recon fleet and a four agent adversarial pass that returned a do-not-ship verdict on the first build; four blockers, two introduced by the fix itself, were closed before release.
Full detail in CHANGELOG.md.
v3.8.2: session recovery finds dotted, spaced and emoji project paths
Fixed
Session recovery silently found nothing for any project path containing a dot, a space, or any other non-alphanumeric character (closes #209, reported by @seathatflowsinourveins).
Claude Code names ~/.claude/projects/ entries by folding every character outside [A-Za-z0-9-] to -. Three copies of session-catchup.py still used a manual replace chain that handled only /, \ and :, so they computed a directory name Claude Code never writes, no candidate matched, and main() returned at the exists() check with exit 0. Catchup after /clear produced nothing and reported nothing. Hidden directories such as ~/.dotfiles were the common case.
One of the three sits on a live install route. marketplace.json declares "source": "./", so the plugin root is the repository root and the SKILL.md restore block resolves ${CLAUDE_PLUGIN_ROOT}/scripts/session-catchup.py for every plugin user on Linux, macOS or Git Bash. Measured against a real store holding 89 sessions, the shipped resolver produced 0 bytes where the fixed one produces 11336 and recovers 166 messages.
An emoji in a folder name made a project unresolvable in every copy, including the canonical one. Claude Code walks the directory name as UTF-16, so a non-BMP character costs two dashes while the sanitizer counted codepoints and produced one. Folding now counts UTF-16 code units. The rules were measured against 24 real stores whose recorded cwd could be read: one model matches all 24, and it also showed that current versions fold _ while older stores kept it, so both spellings stay in the probe chain.
The 15 copies that already folded dots keep that behavior. .kiro is untouched because it ships a different program that never reads ~/.claude/projects.
Security
Two projects whose paths fold to the same ~/.claude/projects name could read each other's transcripts. The mapping is lossy, so client.acme and client-acme share one directory. Until this release the resolver did not fold those characters and simply missed the directory; folding correctly means it now finds it, so catchup filters transcripts by the cwd they record.
A transcript is skipped only when it positively records a different project. Transcripts that record none are kept, because the field is not present in every generation of the format. A directory whose transcripts all belong to another project is reported instead of used. The filter works per session rather than rejecting the whole directory, because in a collision both projects live there permanently and rejecting it would cost the project its own history. Reproduced with a planted canary before and after the fix.
Verification
Suite 305 to 311 passing, 282 subtests, sync-ide-folders.py --verify clean. The new parity suite discovers the copies with git ls-files and runs one vector table through each, so the drift that produced this issue fails the suite instead of hiding in a single file.
Thanks
seathatflowsinourveins reported the dot-folding mismatch with an exact blob reference, a working reproduction, and a correct reading of the silent return path (#209).
Full changelog: v3.8.1...v3.8.2
v3.8.1: Pi plan resolution anchored to the project, hardened to sh-resolver parity
This release closes #208 and the one gap v3.8.0 dogfooding surfaced.
Fixed
Pi extension: plan resolution no longer depends on the live shell cwd (closes #208, reported by @fd44fdg with a precise root-cause analysis). The Pi session cwd follows the shell, so an agent that changed into a subdirectory lost the project's plan entirely: resolution found nothing, the recitation went dark, and the "No task_plan.md found" warning fired on every write and edit. Resolution now anchors on the nearest ancestor directory carrying planning state, bounded by the .git repository boundary and a depth cap so a plan outside the repository can never leak into a session. Explicit PLAN_ID pins keep working from any subdirectory.
Every Pi injection now states which plan it resolved (plan: or plan: root). A stale .planning directory shadows a root task_plan.md by documented precedence; the label makes that visible instead of silent.
init-session created plans without the v3.8.0 Next Step section: the scripts write plans from an inline heredoc, not from the template files, so the section never reached real plans. All 26 heredoc copies fixed, with a regression test that asserts the created output rather than the template.
Bundled Pi extension bumped to 1.2.2. The npm package @tomxprime/planning-with-files is published by its package author; the npm side follows the repository.
Hardening shipped with the fix, found by the two-model release gate (an Opus adversarial pass plus a five-lens Sonnet reliability fleet): the Pi resolver now matches the sh resolver's slug validation and containment (a traversal PLAN_ID or a junctioned slug directory no longer resolves anything outside the project, fail-closed), every runtime consumer that takes a directory routes through the same anchor as plan resolution, and the injected plan label is sanitized.
New vitest coverage for the anchor walk and the parity hardening (35 tests green). Thanks to fd44fdg for the report and analysis.
v3.8.0: the Stop hook fires on every platform, session recovery finds the real store, smart injection
This release closes two silent failures that hit exactly the long-running story the skill exists for, then adds the injection controls that keep a long plan in the model's window.
Fixed
The Stop hook never fired on macOS or Linux, and was a silent no-op on every platform when CLAUDE_SKILL_DIR was unset. Two dispatch bugs stacked: the install path fallback used a shell default that can never substitute, and the PowerShell branch was selected everywhere because check-complete.ps1 ships on all platforms, so the dispatch died with a suppressed exit 127 wherever PowerShell is absent. Both the completion advisory and the v3 completion gate were dead in those environments. The scalar now selects targets by file existence and dispatches by platform. Windows output is unchanged. New tests execute the scalar end to end on both CI legs instead of string-matching its shape.
Session recovery searched a ~/.claude/projects directory that does not exist for macOS and Linux installs, nor for any project path containing an underscore: Claude Code keeps underscores and the leading dash of POSIX absolute paths, the mapper stripped both. Recovery after /clear silently found nothing in those cases. The mapper now probes the exact spelling first, keeps the legacy spellings as fallbacks, and settles ambiguity via the cwd recorded in the newest session file.
Also fixed: a stale SHA cache hit could report a false PLAN TAMPERED for a different project (the cache key now includes the project root), resolve-plan-dir.ps1 reached parity with the sh resolver (slug validation, task_plan.md requirement, fail-closed containment), and ledger-append.sh no longer truncates a CJK or emoji summary mid-codepoint.
Added
Opt-in structure-aware injection: PWF_INJECT=smart (or an inject-smart token in .mode) replaces the position-blind head-50 with the plan title, Goal, Next Step, Current Phase, phase counts, the full in_progress phase section, and the last three decisions, so late in a long task the active phase is always in the window. Default output stays byte-identical to v2.43.
A Next Step section in both plan templates plus a sixth reboot question (what am I about to do), tool result outcomes in the session catchup report, a macos-latest CI leg plus a BSD userland simulation harness that runs the script fleet without realpath, readlink, flock, or sha256sum, pinned line endings via .gitattributes, three problem-query docs pages, a rewritten llms.txt, and a rebuilt README with committed SVG benchmark charts that carry the internal benchmark framing inside the images.
Suite at 301 passed (from 217), 11 skipped, green on Windows, Linux, and the new macOS leg pending first run. Full details in the CHANGELOG.
v3.7.0: Agent Skills standard layout ships in-tree
v3.7.0
The Agent Skills standard layout now ships in-tree
.agents/skills/planning-with-files/ is the cross-tool Agent Skills path that Zed, Amp, Warp, Devin, Antigravity, Gemini CLI, Cursor, and the wider agentskills.io adopter list read natively. This release ships it inside the repo, so a plain git clone is discoverable by all of those tools with no per-tool setup. The mirror carries the full canonical surface: SKILL.md, references, all six templates including the autonomous plan template, and the complete script set (hook dispatchers, ledger tooling, plan-doctor).
It is wired into both maintenance systems so it cannot silently rot: sync-ide-folders.py gained a .agents manifest, and the SKILL.md joined the bump-version.py parity set plus the version-parity test (18 locked entries).
Also in this release
plan-doctor.sh(new in v3.6.0) now ships in every synced IDE skill folder, not only the two canonicalscripts/locations.docs/gemini.mdroutes new installs through the standard.agents/skills/path, which is version-locked, instead of the intentionally version-lagged.gemini/skills/variant (which stays for existing installs).AGENTS.mdand thebump-version.pydocstring now describe the actual 18-entry parity set.
Full details in the CHANGELOG.
v3.6.0: silent hook loss on native-coreutils Windows fixed, /plan-doctor self-check
v3.6.0
The headline: plan resolution and hook injection could go silently dark on Windows
A machine with a native Windows coreutils build on PATH (winget coreutils, uutils, and similar installs) gives Git Bash sessions a realpath that prints C:\-style backslash paths. The containment guard in resolve-plan-dir.sh and inject-plan.sh compares canonical paths with a forward-slash prefix pattern, so every comparison failed. The consequence: the resolver resolved nothing, injection emitted nothing, every hook exited 0, and nothing printed an error. The mechanisms this skill is built on were fully disabled on such machines with no visible symptom, and Linux CI could not reproduce it.
v3.6.0 backslash-normalizes canonical paths before comparison (pure shell, no extra process), canonicalizes the resolver root via . and candidates through their cwd-relative form (healing the 8.3 short-path mismatch that v3.2.0 fixed only in inject-plan.sh), and adds regression tests that reproduce the Windows path shape on every platform via a stub realpath, so ubuntu CI now guards this class.
Side effects of the same work: the long-standing local test_ledger.py hang on Windows is gone, and per-candidate grep/basename forks were replaced with shell builtins. One hook fire measures 289ms on the machine that measured 2.0 to 2.4s at v3.4.0.
New: /plan-doctor
Failures in this class are silent by design, so a broken install looks identical to "no plan yet". sh scripts/plan-doctor.sh (or /plan-doctor on the plugin route) reports in one pass: plan resolution, injection output, canonicalizer path shape, attestation state, install surfaces on the machine, and per-fire hook latency.
Docs
docs/installation.md now carries the install-route matrix (the plugin route ships commands/ and reliable hook registration; npx skills add and manual copies do not), the project-trust prerequisite that silently gates project-level skills, and a one-line CLAUDE.md trigger snippet: the July 2026 benchmark measured unforced skill engagement at 60 to 67% while always-loaded rules lines engaged 100%. The README gains a plan-mode handoff FAQ and updates the supported-agents answer to the 2026 .agents/skills standard landscape.
Full details in the CHANGELOG.
v3.5.1: Codex Windows WSL bash fix, Python discovery, Pi interactive-tool fix
Fixed
Codex Windows hooks kept failing after v3.4.1 and v3.5.0: root cause found. The shell resolver preferred C:\Windows\System32\bash.exe, the WSL launcher, whenever Git Bash usr\bin was not on PATH (the default Git for Windows install layout). On machines with WSL present but no distro installed, a common Docker Desktop setup, every shell hook failed silently. The resolver now skips WSL launchers (System32 and the Store WindowsApps alias) and continues to the Git for Windows probe. Extracted from PR #207 by @mahdiit, commit authorship preserved.
Windows Python discovery hardened. pwf-hook.cmd now honors PYTHON_BIN, probes standard uv and CPython install locations when Codex starts hooks with a reduced PATH, and quotes the interpreter path so installs under paths with spaces work. Also from PR #207.
Pi extension 1.2.1: recitations no longer break interactive tools. Messages queued by the tool_call hook with deliverAs: "steer" were consumed as the answer of an open AskUserQuestion dialog. All three tool_call sends now deliver as nextTurn. Issue #206, diagnosed with the exact mechanism by @jschmied.
Added
CI test workflow (PR #199 by @Yigtwxx, closes #197): pytest on ubuntu and windows runners plus the Pi vitest suite on every PR and push to master. Two test portability fixes from the first hosted run (PR #198).
Thanks
Mahdi (@mahdiit) for tracking the surviving Windows failure to the WSL launcher and contributing the fix in PR #207. jschmied for the precise #206 diagnosis. Yigtwxx for the CI workflow and test portability fixes.
v3.5.0: Codex Windows hooks, Pi nag fix, plan lifecycle docs
Fixed
- Codex hooks on Windows emitted invalid JSON and failed on Unicode (PR #205 by @yolo0731, closes #204). The Codex front door forwarded plain stdout where Codex expects
hookSpecificOutput.additionalContextor PreCompact JSON, and UTF-8 plan text broke on both decode (Windows code page) and encode (ensure_ascii=Falsethrough cmd.exe). Each event is now serialized in its supported Codex JSON shape with ASCII-safe output, shell output is decoded as UTF-8, PreToolUse plan text uses model-visibleadditionalContext, scoped plans resolve in PermissionRequest, SessionStart gainsclear|compact, and the.active_planpointer is written without a BOM. The containment resolver now fails closed. Audited clean before merge. - Closed and complete Pi plans kept nagging "Task incomplete" (#203 by @ziyu4huang).
resolveNewestPlanDirnow ranks bytask_plan.mdfile mtime instead of directory mtime,readPlanStatusparses the close marker, andagent_endand the auto-continue loop stop on a closed plan. Pi extension 1.2.0. - Four language commands referenced a skill namespace that does not exist; corrected to
planning-with-files:planning-with-files-<lang>.
Added
- Traditional Chinese
/plan-zhtcommand. - README now documents the full v3 command, hook, and mode surface (Claude Code and Pi command tables, a v3 long-running-agent features section, a hooks and modes reference, and a command-names note).
- Plan lifecycle documented in
docs/workflow.md(#202 by @kcinzgg).
Thanks
@yolo0731, @ziyu4huang, @kcinzgg.
Full changelog: https://github.com/OthmanAdi/planning-with-files/blob/master/CHANGELOG.md
v3.4.1: Codex hooks run on Windows
Codex hooks now run on Windows (closes #201)
Reported by @mahdiit: Codex hooks failed on Windows with hook exited with code 1 on every Bash call.
Cause
The .codex/hooks.json commands were POSIX only (sh, python3, 2>/dev/null, $HOME, a trailing || true). Codex on Windows runs that string through the native command interpreter, not a POSIX shell, so python3 hit the Microsoft Store alias, 2>/dev/null was an invalid path, and the || true success guard itself failed because true is not a Windows command. The chain exited non-zero and Codex reported the hook as failed.
Fix
Each hook now carries a per-hook commandWindows override, the mechanism OpenAI's hooks documentation sanctions. The POSIX command is untouched, so macOS and Linux are byte for byte unchanged. On Windows all seven hooks route through a new launcher, .codex/hooks/pwf-hook.cmd, which selects a real Python (py -3, falling back to python, never the Store python3 alias) and always exits 0. The three shell hooks route through a new front door, .codex/hooks/run_sh.py. codex_hook_adapter resolves the Git for Windows sh.exe by anchoring on git.exe, so the shell scripts run even when Git's usr\bin is off PATH.
On Windows you need Python reachable as py or python, and Git for Windows for the three shell hooks. See docs/codex.md.
Verification
Codex hook suites green (17 tests) plus a new cross-platform guard and a Windows-only end-to-end test. Reproduced and verified fixed on Windows, including the reporter's environment where Git's usr\bin is off PATH, and a no-Git case that degrades to a silent no-op.
Thanks @mahdiit for the report with the exact error and environment.
v3.4.0: PLANNING_DISABLED=1 opt-out for one-shot sessions
What changed
One-shot sessions that merely share a working directory with an active plan (a CI review bot run via codex exec, a read-only research agent, a nested orchestrator) were hijacked by the hooks: plan context injected, the actual output redirected into progress.md, and a fabricated completed phase appended to task_plan.md (#195, reported by @marcmuon).
v3.4.0 adds a documented per-invocation opt-out:
PLANNING_DISABLED=1 codex exec -o review.md '$code-review review this branch'With the variable set, every hook exits before reading the plan: no context injection, no follow-up messages, no plan-file writes. PreToolUse still emits its allow decision so tool calls proceed normally. The guard covers all Codex hook entry points, the Python adapter route, the canonical Claude Code dispatchers (inject-plan.sh, gate-stop.sh, check-complete.sh/.ps1), and ships in every distributed copy including the five language variants.
Also corrected docs/codex.md, which still described the pre-v3.1.0 blocking Stop hook. The decision: block half of #195 was already fixed in v3.1.0; the reporter was on a v2.41.0 bundle shipped by oh-my-codex.
Verification
- Python suite: 200 passed, 5 skipped, 0 failed (12 new opt-out tests)
- Functional smoke test on a live temp plan: with the variable set, no hook output, tool calls still allowed, plan files byte-for-byte unchanged
- scripts/sync-ide-folders.py --verify: all IDE folders in sync