Compare commits

...
37 Commits
Author SHA1 Message Date
OthmanAdi bb292cba44 release: v3.6.0 (silent hook loss on native-coreutils Windows fixed, /plan-doctor, install matrix)
17-file parity bump (canonical + 5 language + 8 IDE SKILL.md variants,
plugin.json, marketplace.json, CITATION.cff). .continue, .gemini, .pi,
.kiro remain on their own versioning per release scope. CHANGELOG entry,
README release row, version heading, plan-mode handoff FAQ, and the
supported-agents answer refreshed to the 2026 .agents/skills landscape.
2026-07-18 11:54:55 +02:00
OthmanAdi 1a2d95eeb0 fix: set executable bit on plan-doctor.sh (matches resolve-plan-dir.sh) 2026-07-18 11:54:55 +02:00
OthmanAdi 0ec48c1c13 docs: install-route matrix, trust prerequisite, trigger snippet, stale reference cleanup
installation.md now states what each install route actually ships (the
plugin route carries commands/ and reliable hook registration; npx
skills and manual copies do not), documents the project-trust gate that
silently disables project-level skills, and offers a one-line CLAUDE.md
trigger snippet: the July 2026 benchmark measured unforced skill
engagement at 60-67% while always-loaded rules lines engaged 100%.

The last two variant copies still teaching Manus-2025 "one tool call
per turn" (.factory, .mastracode) get the 2026 parallel-host update the
canonical reference.md already carries. kilocode.md notes the Windsurf
to Devin Desktop transition.
2026-07-18 11:54:33 +02:00
OthmanAdi 0d286ac530 feat: add /plan-doctor self-check command
Every failure in the resolution and injection class is silent by design
(hooks always exit 0), so a broken install looks identical to "no plan
yet". plan-doctor.sh answers in one pass: does resolution work here and
which plan wins, does injection emit plan context, what path shape does
the canonicalizer produce, is the plan attested, which install surfaces
exist, and what one hook fire costs in wall-clock.

Dual-shipped in both scripts/ locations and added to the parity test.
From the July 2026 benchmark improvement backlog (P0 trigger and
install-health items).
2026-07-18 11:54:33 +02:00
OthmanAdi 78a526dc60 fix: normalize Windows-native canonical paths in plan containment guards
A native Windows coreutils build on PATH gives sh a realpath that prints
C:\-style backslash paths. The containment prefix match in
resolve-plan-dir.sh and inject-plan.sh is written with forward slashes,
so every canonical pair mismatched: the resolver resolved nothing and
inject-plan.sh emitted nothing, exit 0, no error. Plan resolution and
hook injection were fully and silently disabled on such machines.

Canonical paths are now backslash-normalized (pure shell, no extra
process) before comparison. The resolver also canonicalizes its root via
"." and checks candidates through their cwd-relative form, healing the
8.3 short-path mismatch the v3.2.0 fix only applied to inject-plan.sh.
Per-candidate grep and basename forks are replaced with case patterns
and parameter expansion; the containment root is computed once per run.

Regression tests use a PATH-prepended stub realpath that reproduces the
backslash output shape on every platform, so Linux CI guards this
Windows-only field failure. Also cures the long-standing local
test_ledger.py hang on Windows.
2026-07-18 11:54:22 +02:00
OthmanAdi f90780c92f release: v3.5.1 (Codex WSL bash resolver skip, Windows Python discovery, Pi interactive-tool fix)
Codex on Windows: extend the WSL launcher skip to the Store WindowsApps
alias and add regression tests for both launcher locations. Fold the CI
workflow and test portability work (PRs #198, #199 by @Yigtwxx) into the
release entry.

Pi extension 1.2.1: deliver tool_call recitations and the tamper notice
as nextTurn instead of steer so they can no longer be consumed by
interactive tools blocking the turn (issue #206, diagnosed by @jschmied).

Version bump to 3.5.1 across the 17-file parity set, CHANGELOG,
CONTRIBUTORS, README badge and releases table.
2026-07-14 16:33:15 +02:00
Mahdi Yousefi 59b04ab2a7 fix(codex): skip the WSL bash launcher and harden Windows Python discovery
The Codex shell resolver picked C:\Windows\System32\bash.exe (the WSL
launcher) whenever Git Bash usr\bin was not on PATH, so every shell hook
failed on machines where WSL is present without a distro (the common
Docker Desktop setup). The resolver now skips System32 and continues to
the Git for Windows probe.

pwf-hook.cmd now honors PYTHON_BIN, falls back to py/python on PATH, and
probes standard uv and CPython install locations for the case where Codex
starts hooks with a reduced PATH. The interpreter path is quoted so
installs under paths with spaces work.

Extracted from PR #207. Follow-up to #201 and #204.
2026-07-14 16:20:08 +02:00
OthmanAdi f13118cadf release: v3.5.0 (Codex Windows hooks, Pi nag fix, plan lifecycle docs)
Ships PR #205 (@yolo0731) plus the #203 Pi close-marker fix, the #202 lifecycle
documentation, the language-command namespace correction, and the README v3
command and hook surface.

Bumps the 17-file parity set to 3.5.0 (.continue, .gemini, .pi, and .kiro stay
behind by design). Adds the v3.5.0 CHANGELOG entry, credits @yolo0731,
@ziyu4huang, and @kcinzgg in CONTRIBUTORS, and updates the README version badge
and releases table.
2026-07-13 08:19:25 +02:00
OthmanAdi 11b0ff6750 docs: document plan lifecycle (#202) and surface the v3 command, hook, and mode surface
Adds an "After Completion" section to docs/workflow.md stating that the planning
files are ephemeral working memory, gitignored by default, and not archived
automatically, with guidance on retaining a completed plan and a note that a
completion-triggered archive step is a welcome opt-in extension. Pointers added in
docs/quickstart.md and a README FAQ entry. This writes down the lifecycle that
issue #14 answered only informally (#202).

Expands the README, additively and in the visible body, to document the command
surface that shipped across v2.33 through v3.4 but was never listed. The Claude
Code command table gains pwf, plan-goal, plan-loop, plan-attest, and the language
commands. New sections cover the Pi extension commands, a v3 long-running-agent
feature list, a hooks and modes reference across Claude Code, Codex, and Pi, and a
note on command names versus skill names (there is no /pwf-de and no
/planning-with-files:planning-with-files-goal). The File Structure block now lists
all 12 command files.
2026-07-13 08:15:03 +02:00
OthmanAdi 4340fc2a9c fix: correct language command skill namespace and add Traditional Chinese command
The Arabic, German, Spanish, and Chinese plan commands told the model to invoke
the skill under planning-with-files-<lang>:planning-with-files-<lang>, a plugin
namespace that does not exist. The skills are registered under the single plugin
namespace planning-with-files:planning-with-files-<lang>, which the English
plan, pwf, and start commands already use. Corrected all four.

Adds commands/plan-zht.md so Traditional Chinese has a /plan-zht command to match
its shipped skill, closing the ar/de/es/zh/zht command parity gap.
2026-07-13 08:09:50 +02:00
OthmanAdi f1b54f5b0d fix: stop closed and complete Pi plans re-emitting the incomplete nag (#203)
The Pi extension kept sending "Task incomplete (N/M)" follow-up messages for
many turns after a plan was completed and closed. Two causes.

resolveNewestPlanDir ranked candidate .planning/<slug>/ directories by
directory mtime. Editing a task_plan.md's contents does not change its
directory mtime, so a plan that was just finished lost to an older,
still-incomplete sibling, and agent_end read that sibling (reporting a stale
3/4) and nagged. It now ranks by the task_plan.md file mtime.

The extension also had no close-marker awareness: PlanStatus carried no closed
field and readPlanStatus never parsed the pwf close marker. readPlanStatus now
sets status.closed, agent_end returns early on a closed plan, and the plan-loop
tick stops on close. A PWF_DEBUG-gated diagnostic logs cwd, planId, closed, and
the phase count before the nag for field diagnosis.

Adds vitest coverage (a closed plan sends no follow-up; newest-plan resolution
ranks by file mtime, verified with a negative control) and bumps the bundled
extension to 1.2.0.

Reported by @ziyu4huang with an exported-function trace.
2026-07-13 08:09:50 +02:00
yolo0731 242a5c95c3 fix: persist secure Codex plan recovery 2026-07-13 07:56:09 +02:00
yolo0731 055a6af7f8 fix: keep Windows active plan pointers parseable 2026-07-13 07:56:09 +02:00
yolo0731 a52877e2ae fix: make Codex Windows hooks protocol-safe 2026-07-13 07:56:09 +02:00
OthmanAdi e8ce66f962 release: v3.4.1 (Codex hooks run on Windows, closes #201)
Codex on Windows runs each hook command through the native command
interpreter, not a POSIX shell. The .codex/hooks.json commands were POSIX
only (sh, python3, 2>/dev/null, $HOME, trailing || true), so python3 hit
the Microsoft Store alias, the redirection was an invalid path, and the
|| true success guard failed because true is not a Windows command. Every
hook exited non-zero and Codex reported "hook exited with code 1".

Add a per-hook commandWindows override, the sanctioned Codex mechanism.
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 picks 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.run_shell_script resolves the Git for Windows sh.exe by
anchoring on git.exe and the standard install roots, so the shell scripts
run even when Git's usr\bin is off PATH, and hands session-catchup.py a
real interpreter via PYTHON_BIN. docs/codex.md Windows section rewritten.

Version bump 3.4.0 to 3.4.1 across the 17-file parity set. New tests: a
cross-platform guard that every hook declares a commandWindows free of the
POSIX tokens that break on Windows, and a Windows-only run_sh.py end-to-end
test. Reported by @mahdiit.
2026-07-12 13:01:02 +02:00
Yigtwxx bdef0e0aa0 ci: commit Pi extension lockfile and switch vitest job to npm ci
The vitest job ran npm install because no lockfile was committed, so
dependency resolution could drift between runs. Committing
package-lock.json makes installs reproducible, lets the job use npm ci,
and enables setup-node npm caching keyed on the lockfile.

Verified locally: npm ci from a clean node_modules, then vitest run,
21/21 tests pass.
2026-07-09 16:35:25 +02:00
OthmanAdi 7efb050593 docs: credit @Yigtwxx for CI test workflow and test-portability fixes (#197, #198, #199)
CHANGELOG [Unreleased] entry plus CONTRIBUTORS record for the pytest +
vitest CI workflow (#199) and the two hosted-runner test-portability
fixes (#198). No version bump: changes touch .github/ and tests/ only,
none of the 19 version-bump targets.
2026-07-08 21:15:20 +02:00
Yigtwxx ea8e9093c1 ci: run pytest on ubuntu and windows plus Pi extension vitest 2026-07-08 21:11:14 +02:00
Yigtwxx 8209f7a387 fix: make containment and path-sanitize tests portable to CI runners
Running the suite on GitHub-hosted runners for the first time exposed two
latent test-portability failures; neither is a production bug.

tests/test_containment.py: on windows-latest (symlink-capable, unlike most
local Windows setups) the escaping-symlink test ran and failed because the
resolver's stdout is a POSIX-style Git Bash path (/tmp is the mounted user
temp dir), while the assertion canonicalized it with os.path.realpath, which
reads it as C:\tmp. The resolver rejected the escape correctly. Map the
sh-emitted path back to its host form with cygpath before comparing, same
comparison-side normalization as the Hermes adapter test fix in 0d028d4.

tests/test_path_fix.py: the two Windows-shaped vectors fail on any POSIX
host because Path.resolve() treats C:/Users/... as a relative path and
prepends the CWD. In production the sanitizer only ever receives real POSIX
paths on POSIX hosts, so the branch under test is Windows-only; restrict the
two tests to Windows, mirroring the existing skipIf on the Unix vector.
2026-07-08 21:10:09 +02:00
OthmanAdi 565d4ffab4 docs: sharpen competitive benchmark framing, lead with the recovery result
Rewrites the Benchmark v1 page and evals.md Test 5 to lead with what pwf wins:
a context-death resume in 5.0 turns (2.7x faster than a raw agent, 40 percent
fewer than the next method), correctness parity at 77/77, and the guarantees
table where pwf is the only method with automatic recovery, re-surfacing,
tamper-detection, isolation, and compaction. Triggering is reframed around the
like-for-like result (pwf leads superpowers 5 to 0). The single-machine Windows
per-fire timing is qualified as a local implementation detail under profiling,
not a headline. All numbers are unchanged and remain reproducible from the raw
runs. Docs only: no skill, hook, script, manifest, or version change.
2026-07-07 16:25:49 +02:00
OthmanAdi fbc562f5f4 docs: add competitive benchmark v1 (Test 5) and animated summary page
First head-to-head of planning-with-files v3.4.0 against six alternative
planning methods (native, filesystem-notes, naive-plan, superpowers,
spec-kit, memory-bank) in one harness on claude-opus-4-8, graded by
deterministic oracles only. 77 cells, all disk-verified.

Reported honestly, losses first-class: task outcomes saturate (77/77 pass,
no arm wins on pass rate at this task size); unforced trigger rate is 60 to
67 percent and is the top backlog item; overhead is real (+50 to 100 percent
cost on small tasks, ~2s per hook fire on Windows). Where pwf leads: a resume
after context death takes 5.0 turns versus 8.3 to 13.3 for rivals, when the
plan exists. Adds docs/evals.md Test 5, docs/benchmark/index.html, one README
badge. No version bump, no skill, hook, script, or manifest changes.
2026-07-07 07:32:26 +02:00
OthmanAdi d71b3be47b release: v3.4.0 (PLANNING_DISABLED=1 opt-out for one-shot sessions, closes #195) 2026-07-06 07:20:12 +02:00
OthmanAdi ee892664af feat: honor PLANNING_DISABLED=1 per-invocation opt-out across all hook entry points (closes #195) 2026-07-06 07:19:12 +02:00
OthmanAdi 34d4a7edd4 release: v3.3.0 (Pi extension /plan-execute approval gate, PR #193 merged, closes #190) 2026-07-06 06:22:09 +02:00
diksha adbdc8e13a feat: gate Pi hooks behind plan-execute
Add an explicit /plan-execute approval command for the Pi extension so task_plan.md can be drafted and reviewed without activating plan injection, pre-tool recitations, post-write reminders, or auto-continue.

Scope approval to the current session and plan path, reset it on session lifecycle events, and document the passive review flow with focused runtime and docs coverage.
2026-07-06 06:19:06 +02:00
OthmanAdi 8459756b18 release: v3.2.0 (v3 health audit: session-catchup + inject-plan Windows fixes, 0/0-phases guard, analytics templates, 2 PRs merged)
Version bumped to 3.2.0 across the 17 parity-locked files via
scripts/bump-version.py. .continue, .gemini, .pi, and .kiro lag
intentionally, now documented directly in AGENTS.md.

CHANGELOG, CONTRIBUTORS, and README updated. docs/evals.md gets a new
Test 4 documenting the functional verification of v3's long-running-session
mechanism on Windows, the two bugs it found, and what was fixed.
2026-07-03 17:03:43 +02:00
Igor Costa fb8127682d Add Autohand Code setup docs 2026-07-03 16:56:32 +02:00
stephen-abc cc7c67c511 fix: resolve windows test encoding errors and correct installation docs 2026-07-03 16:56:25 +02:00
OthmanAdi 7eb674845a security: add SECURITY.md and enable private vulnerability reporting (closes #188)
@AvitalAviv reported having a possible vulnerability to disclose with no
private channel to do it through. Enabled GitHub's private vulnerability
reporting for the repo (Security tab, Report a vulnerability) and added
SECURITY.md documenting the reporting path, scope (hook injection, path
resolution, supply chain), and response expectations, so future reports have
both the GitHub-native flow and a documented policy to point to.
2026-07-03 16:55:52 +02:00
OthmanAdi 22055786df docs: fix templates directory mislabel in docs/codebuddy.md
Three sections pointed users at .codebuddy/skills/planning-with-files/references/
for the task_plan.md, findings.md, and progress.md starter templates. Those
three files live in the skill's templates/ directory; references/ holds
examples.md and reference.md instead. Same mislabel PR #187 fixes in
antigravity.md, on a file outside that PR's scope.
2026-07-03 16:55:45 +02:00
OthmanAdi d7c805b3f7 docs: correct contributor PR merge guidance in AGENTS.md
AGENTS.md's own release checklist told agents to squash-merge contributor
PRs: "git merge --squash origin/branch, then a single commit as OthmanAdi."
That reassigns the contributor's commit authorship to whoever runs the local
commit, which is the exact mistake CLAUDE.md and this project's own history
already learned to avoid (v2.40.1 cycle). AGENTS.md describes itself as "the
canonical, session-portable reference for how every agent working in this
repo must handle commits," so a future session following it verbatim would
have reintroduced the bug it was written to prevent.

Replaced both the checklist step and the summary bullet with cherry-pick or
gh pr merge --rebase, matching CLAUDE.md and CONTRIBUTORS.md's own authorship
guidance, which this contradicted. Also documented that scripts/bump-version.py
excludes four files from the parity bump, not two: .continue and .gemini as
already noted, plus .pi (own npm version) and .kiro (own -kiro-suffixed
scheme) which recent CHANGELOG entries already cited as "per AGENTS.md release
scope" without this file actually saying so.
2026-07-03 16:55:43 +02:00
OthmanAdi ed46cfc18e fix: ship analytics templates into the canonical skill package (addresses #103)
templates/analytics_task_plan.md and templates/analytics_findings.md existed
at the repo root (added v2.29.0 for #103) but were never copied into
skills/planning-with-files/templates/, the directory init-session.sh actually
reads inside the installed skill package. The --template analytics flag has
been silently falling back to the generic default templates for every plugin
or skill-only install since the feature shipped; only the loose repo-root
scripts and the .hermes mirror had working copies.

Copied both files into the canonical templates/ dir and added them to
sync-ide-folders.py's TEMPLATES list so future syncs carry them to every IDE
mirror automatically, then ran the sync once to backfill the seven mirrors
that share the canonical templates dir. Verified init-session.sh --template
analytics now produces the real analytics-phase task_plan.md instead of the
generic default.

This does not add the standalone analytics subagent #103 originally asked
for; the template-and-flag mode is what shipped in v2.29.0 and this fix makes
it actually work as documented.
2026-07-03 16:55:31 +02:00
OthmanAdi 0d028d4480 fix: normalize resolved path before comparison in Hermes adapter test
test_installed_plugin_resolves_repo_assets_for_completion_check compared the
skill_root the production code returns (canonicalized via Path.resolve(),
which normalizes Windows 8.3 short-name aliases such as OASRVA~1 to the real
account name) against an unresolved path built from tempfile's raw TEMP
value. On accounts where TEMP itself is short-name-aliased, the two sides
disagree even though they point at the same directory. Resolving the expected
side the same way fixes the false failure without changing what the test
verifies.
2026-07-03 16:55:07 +02:00
OthmanAdi 65c768dfa8 fix: task_plan.md without ### Phase headings falsely reported as 0/0 phases (closes #191)
Every Stop-hook style adapter (Codex, Cursor, GitHub Copilot's AgentStop, and
the canonical Claude Code check-complete.sh/.ps1 used by the gate and
advisory paths) checked only that task_plan.md exists, never that it contains
any "### Phase" headings. A task_plan.md that exists but isn't a structured
plan gives TOTAL=0, which fell through to the same branch as an in-progress
plan, producing "Task in progress (0/0 phases complete)" or, worse, Cursor and
GitHub Copilot's variants auto-continuing with "0/0 phases done ... continue
working on the remaining phases."

Reported against Codex specifically in #191 by @mixian939, with a correct
diagnosis and suggested fix. Auditing the same pattern found it in the
canonical scripts and two other IDE adapters too, so the guard lands
everywhere at once: a TOTAL=0 check that exits silently (or emits empty JSON
for Copilot's schema) before any status message is built.

Propagated to every check-complete.sh/.ps1 copy that shares this logic
(canonical, all sync-ide-folders.py-managed IDE mirrors via a sync run, the
five language-variant skills, and the standalone .kiro copy) so the fix is not
Claude-Code-only. inject-plan.sh, gate-stop.sh and the ledger scripts already
build on check-complete.sh's TOTAL and needed no separate change.
2026-07-03 16:55:00 +02:00
OthmanAdi 1e60b06929 fix: inject-plan.sh containment guard silently dropped plan injection under aliased paths
is_within_root() canonicalized the root via the $PWD string but canonicalized
candidates via a relative path (".", ".planning/<id>"), which resolves through
getcwd() instead. On this machine's TEMP (an 8.3 short-name path,
C:\Users\OASRVA~1\...) and under the MSYS /tmp mount alias, those two
canonicalization routes land on differently-spelled paths for the same
physical directory, so the prefix-match containment check fails and the hook
exits 0 with zero output: no plan re-injection, no tamper warning, no error.
A user would have no way to know it stopped working.

Root cause was the asymmetry, not either canonicalization call individually.
Fixed by canonicalizing the root the same way candidates already are, via the
relative "." token instead of the $PWD variable, so both sides resolve through
the same physical-cwd code path. resolve-plan-dir.sh already worked correctly
for the same reason (it builds absolute candidates), which is what pointed at
the fix.
2026-07-03 16:54:32 +02:00
OthmanAdi 5058565588 fix: session-catchup.py Windows path sanitization and encoding
get_project_dir_claude only replaced forward slashes, so a Windows path
(C:\Users\... or Git Bash's /c/Users/...) never sanitized to Claude's real
project directory name, and the function silently returned early every time.
Three open() calls also had no explicit encoding, so a session log with any
non-ASCII content raised UnicodeDecodeError on Windows' default cp1252 codec,
an error the bare except clauses swallowed. Together these meant the "resume
after /clear" feature did nothing on Windows, with no visible error.

Fixed by branching on whether the path looks Windows-shaped (contains ':' or
'\') before sanitizing, converting Git Bash drive-letter paths first, and
adding encoding='utf-8', errors='replace' to the three reads. Genuine Unix
absolute paths take the untouched legacy branch, so macOS/Linux behavior is
unchanged.

tests/test_path_fix.py previously reimplemented the sanitizer instead of
importing the real module ("avoids import issues" per its own comment), so
the suite stayed green while the shipped script stayed broken. It now loads
scripts/session-catchup.py via importlib and exercises the actual function,
plus a regression case for the untouched Unix branch.
2026-07-03 16:54:20 +02:00
OthmanAdi 8f5a3c2e1c release: v3.1.3 (hotfix: quote SKILL.md description, fix invalid YAML frontmatter from v3.1.2)
The v3.1.2 description refresh added a colon (AI coding agents: keeps), but the English SKILL.md carry the description field unquoted, so the frontmatter became invalid YAML (mapping values are not allowed here), which can break skill loading and the model-triggering description. v3.1.3 quotes the description across the canonical file and the seven English IDE variants; the parsed value is identical and the translated variants were already quoted. The clawhub-upload bundle was corrected the same way.

Adds tests/test_skill_frontmatter_valid.py: loads every SKILL.md frontmatter as YAML and guards against an unquoted description containing a colon-space. The version-parity check is a regex and missed this. Version bumped to 3.1.3 across the 17 parity files. Suite: 184 passed, 4 skipped.
2026-06-16 17:16:10 +02:00
169 changed files with 8976 additions and 398 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
"name": "planning-with-files",
"source": "./",
"description": "Persistent file-based planning for AI coding agents. Crash-proof markdown plans that survive context loss, /clear, and crashes, with an opt-in completion gate and multi-agent shared state on disk. Manus-style, installs across 60+ agents via the SKILL.md standard.",
"version": "3.1.2"
"version": "3.6.0"
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "planning-with-files",
"version": "3.1.2",
"version": "3.6.0",
"description": "Persistent file-based planning for AI coding agents. Crash-proof markdown plans (task_plan.md, findings.md, progress.md) that survive context loss and /clear, with an opt-in completion gate and multi-agent shared state. Manus-style. Works with Claude Code, Codex CLI, Cursor, Kiro, OpenCode and 60+ agents via the SKILL.md standard. Includes Arabic, German, Spanish, and Chinese (Simplified and Traditional).",
"author": {
"name": "OthmanAdi",
@@ -1,6 +1,6 @@
---
name: planning-with-files
description: Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear.
description: "Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear."
user-invocable: true
allowed-tools: "Read Write Edit Bash Glob Grep"
hooks:
@@ -28,7 +28,7 @@ hooks:
- type: command
command: "RESOLVED=\"\"; SCOPE=\"\"; SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'; if [ -n \"${PLAN_ID:-}\" ] && printf \"%s\" \"$PLAN_ID\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${PLAN_ID}\" ]; then RESOLVED=\".planning/${PLAN_ID}\"; SCOPE=\"scoped\"; elif [ -f .planning/.active_plan ]; then AP=$(tr -d '\\r\\n[:space:]' < .planning/.active_plan 2>/dev/null); if [ -n \"$AP\" ] && printf \"%s\" \"$AP\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${AP}\" ]; then RESOLVED=\".planning/${AP}\"; SCOPE=\"scoped\"; fi; fi; if [ -z \"$RESOLVED\" ] && [ -d .planning ]; then NEWEST=\"\"; NEWEST_MT=0; for d in .planning/*/; do d=\"${d%/}\"; n=$(basename \"$d\"); case \"$n\" in .*) continue;; esac; printf \"%s\" \"$n\" | grep -Eq \"$SLUG_RE\" || continue; [ -f \"$d/task_plan.md\" ] || continue; m=$(stat -c '%Y' \"$d\" 2>/dev/null || stat -f '%m' \"$d\" 2>/dev/null || date -r \"$d\" +%s 2>/dev/null || echo 0); if [ \"$m\" -gt \"$NEWEST_MT\" ] 2>/dev/null; then NEWEST_MT=\"$m\"; NEWEST=\"$d\"; fi; done; [ -n \"$NEWEST\" ] && { RESOLVED=\"$NEWEST\"; SCOPE=\"scoped\"; }; fi; if [ -z \"$RESOLVED\" ] && [ -f task_plan.md ]; then RESOLVED=\".\"; SCOPE=\"root\"; fi; [ -z \"$RESOLVED\" ] && exit 0; if [ \"$SCOPE\" = \"root\" ]; then PLAN_FILE=\"task_plan.md\"; PROGRESS_FILE=\"progress.md\"; ATTEST=\"\"; [ -f .plan-attestation ] && ATTEST=$(tr -d '\\r\\n[:space:]' < .plan-attestation 2>/dev/null); else PLAN_FILE=\"${RESOLVED}/task_plan.md\"; PROGRESS_FILE=\"${RESOLVED}/progress.md\"; ATTEST=\"\"; [ -f \"${RESOLVED}/.attestation\" ] && ATTEST=$(tr -d '\\r\\n[:space:]' < \"${RESOLVED}/.attestation\" 2>/dev/null); fi; [ -f \"$PLAN_FILE\" ] || exit 0; TAMPERED=0; ACTUAL=\"\"; if [ -n \"$ATTEST\" ]; then CD=\"${TMPDIR:-/tmp}/pwf-sha\"; mkdir -p \"$CD\" 2>/dev/null; KEY=$(printf \"%s\" \"$PLAN_FILE\" | { sha256sum 2>/dev/null || shasum -a 256 2>/dev/null; } | awk '{print $1}' | cut -c1-16); MT=$(stat -c '%Y' \"$PLAN_FILE\" 2>/dev/null || stat -f '%m' \"$PLAN_FILE\" 2>/dev/null || date -r \"$PLAN_FILE\" +%s 2>/dev/null || echo 0); CF=\"$CD/$KEY\"; CM=\"\"; CS=\"\"; if [ -f \"$CF\" ]; then CM=$(sed -n 1p \"$CF\" 2>/dev/null); CS=$(sed -n 2p \"$CF\" 2>/dev/null); fi; if [ -n \"$MT\" ] && [ \"$MT\" = \"$CM\" ] && [ -n \"$CS\" ]; then ACTUAL=\"$CS\"; else ACTUAL=$( (sha256sum \"$PLAN_FILE\" 2>/dev/null || shasum -a 256 \"$PLAN_FILE\" 2>/dev/null) | awk '{print $1}'); [ -n \"$ACTUAL\" ] && [ -n \"$MT\" ] && printf \"%s\\n%s\\n\" \"$MT\" \"$ACTUAL\" > \"$CF\" 2>/dev/null; fi; [ \"$ACTUAL\" != \"$ATTEST\" ] && TAMPERED=1; fi; echo '[planning-with-files] PreCompact: context compaction is about to occur.'; echo 'Before compaction completes: ensure progress.md captures recent actions and task_plan.md status reflects current phase.'; echo 'task_plan.md, findings.md, progress.md remain on disk and will be re-read after compaction.'; [ -n \"$ATTEST\" ] && echo \"Plan-SHA256 at compaction: $ATTEST\"; exit 0"
metadata:
version: "3.1.2"
version: "3.6.0"
---
@@ -20,6 +20,10 @@ param(
[switch]$Gate
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if ($PlanFile -ne "") {
$PlanDir = Split-Path -Parent $PlanFile
if ($PlanDir -eq "") { $PlanDir = "." }
@@ -73,6 +77,13 @@ $COMPLETE = [Math]::Max($completePrimary, $completeInline)
$IN_PROGRESS = [Math]::Max($inProgressPrimary, $inProgressInline)
$PENDING = [Math]::Max($pendingPrimary, $pendingInline)
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
@@ -30,6 +30,10 @@
# interactive terminal (TTY) is skipped entirely. No data on stdin is treated
# as stop_hook_active=false.
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
GATE=0
PLAN_FILE=""
for _arg in "$@"; do
@@ -97,6 +101,13 @@ if [ "$PENDING_INLINE" -gt "$PENDING_PRIMARY" ]; then PENDING="$PENDING_INLINE";
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
@@ -24,13 +24,42 @@ ACTIVE_FILE="${PLAN_ROOT}/.active_plan"
# "feature-foo". The intent is to filter garbage content (e.g. a corrupt
# .active_plan file containing only whitespace or random text) without
# enforcing a date prefix that would break backward compatibility.
SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'
# Pure-sh case patterns; semantics match the previous
# grep -E '^[A-Za-z0-9_][A-Za-z0-9._-]*$' exactly, without a grep fork per
# candidate (the newest-mtime scan calls this once per plan dir).
slug_is_valid() {
case "$1" in
'') return 1 ;;
*[!A-Za-z0-9._-]*) return 1 ;;
[A-Za-z0-9_]*) return 0 ;;
esac
printf "%s" "$1" | grep -Eq "${SLUG_RE}"
return 1
}
# Pure-sh backslash-to-forward-slash normalizer; result lands in $NORM_OUT.
# Windows-native coreutils builds (e.g. C:\Program Files\coreutils on PATH
# ahead of Git's usr/bin) canonicalize MSYS-style /c/... input to C:\-style
# backslash output. The containment prefix match below is written with forward
# slashes, so without this normalization every canonical pair mismatches and
# resolution silently fails. On POSIX systems paths contain no backslash and
# this is the identity. A literal backslash in a Unix filename normalizes to
# "/" and at worst fails containment — the safe direction. No subshell, no
# fork: plain parameter expansion in a loop.
norm_slashes() {
NORM_OUT=""
_ns_rest="$1"
while :; do
case "${_ns_rest}" in
*\\*)
NORM_OUT="${NORM_OUT}${_ns_rest%%\\*}/"
_ns_rest="${_ns_rest#*\\}"
;;
*)
NORM_OUT="${NORM_OUT}${_ns_rest}"
break
;;
esac
done
}
# Portable path canonicalizer. realpath first (Linux, modern coreutils),
@@ -63,19 +92,50 @@ canonicalize() {
# path under the project root (the CWD the script runs from). A symlink inside
# a valid slug dir pointing at /etc or outside the workspace would otherwise let
# the hooks hash and inject an arbitrary file. On any violation we return 1 so
# the caller treats the candidate as unresolved and falls back safely. If
# canonicalization is unavailable for BOTH paths we fail open (return 0) to keep
# legacy behavior byte-equivalent on minimal shells that lack realpath/readlink
# and python; the SLUG_RE check already blocks traversal in the slug name.
# the caller treats the candidate as unresolved and falls back safely.
#
# The root canonicalizes via the relative token "." rather than the $PWD
# string. On some Windows/MSYS setups (8.3 short names, the /tmp mount alias)
# realpath("$PWD") and realpath(relative-candidate) resolve through different
# code paths and land on differently-spelled-but-equal targets, so the prefix
# match below fails and resolution silently goes dark. "." resolves through
# the same physical-cwd path candidates already use (same fix inject-plan.sh
# received earlier; the resolver kept the $PWD form until now). Both sides are
# backslash-normalized before comparison for Windows-native canonicalizers.
# The root is computed once per run: the newest-mtime scan calls this guard
# per plan dir, and each canonicalize costs a process spawn on Windows.
ROOT_REAL=""
ROOT_REAL_SET=0
is_within_root() {
candidate="$1"
root_real="$(canonicalize "${PWD}")" || root_real=""
cand_real="$(canonicalize "${candidate}")" || cand_real=""
if [ -z "${root_real}" ] || [ -z "${cand_real}" ]; then
return 0
if [ "${ROOT_REAL_SET}" = "0" ]; then
ROOT_REAL="$(canonicalize ".")" || ROOT_REAL=""
norm_slashes "${ROOT_REAL}"
ROOT_REAL="${NORM_OUT}"
ROOT_REAL_SET=1
fi
# Canonicalize the candidate through its cwd-RELATIVE form whenever it
# lives under ${PWD}. The candidate string is built from ${PWD} (an MSYS
# long-form spelling), while the root canonicalizes from "." (the process
# cwd, which a caller may have set with an 8.3 short-form string). A
# Windows-native realpath does not unify those spellings, so canonicalizing
# both sides from the same cwd base is the only spelling-stable comparison.
# The emitted result keeps the original absolute candidate — only the
# containment check uses the relative form.
case "${candidate}" in
"${PWD}"/*) check_target=".${candidate#"${PWD}"}" ;;
*) check_target="${candidate}" ;;
esac
cand_real="$(canonicalize "${check_target}")" || cand_real=""
norm_slashes "${cand_real}"
cand_real="${NORM_OUT}"
if [ -z "${ROOT_REAL}" ] || [ -z "${cand_real}" ]; then
# Slug validation blocks textual traversal, but only successful
# canonicalization can rule out a symlink/junction escape.
return 1
fi
case "${cand_real}" in
"${root_real}"|"${root_real}"/*) return 0 ;;
"${ROOT_REAL}"|"${ROOT_REAL}"/*) return 0 ;;
*) return 1 ;;
esac
}
@@ -119,6 +179,12 @@ resolve_from_env() {
resolve_from_active_file() {
[ -f "${ACTIVE_FILE}" ] || return 1
plan_id="$(tr -d '\r\n[:space:]' < "${ACTIVE_FILE}")"
# UTF-8 BOM is not part of the plan id. POSIX printf octal escapes keep
# this portable across GNU/BSD sed variants and Git-for-Windows sh.
utf8_bom="$(printf '\357\273\277')"
case "${plan_id}" in
"${utf8_bom}"*) plan_id="${plan_id#"${utf8_bom}"}" ;;
esac
slug_is_valid "${plan_id}" || return 1
candidate="${PLAN_ROOT}/${plan_id}"
if [ -d "${candidate}" ] && is_within_root "${candidate}"; then
@@ -137,7 +203,7 @@ resolve_latest_dir() {
for entry in "${PLAN_ROOT}"/*/; do
[ -d "${entry}" ] || continue
clean="${entry%/}"
name="$(basename "${clean}")"
name="${clean##*/}"
case "${name}" in
.*) continue ;;
esac
@@ -14,6 +14,30 @@ import os
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
def configure_utf8_stdio() -> None:
"""Make catchup output deterministic on Windows legacy code pages.
Codex sessions and planning files are UTF-8 and can contain arbitrary
Unicode. Windows PowerShell may nevertheless launch Python with a cp1252
(or another OEM/ANSI) stdout codec. A report containing Chinese text then
used to fail at the first ``print`` with ``UnicodeEncodeError``. Configure
both streams before any report is emitted; ``errors='replace'`` also keeps
this advisory hook fail-safe if a malformed surrogate reaches the output.
"""
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, 'reconfigure', None)
if callable(reconfigure):
try:
reconfigure(encoding='utf-8', errors='replace')
except (OSError, ValueError):
# Replaced/captured streams may not permit reconfiguration.
# The hook remains advisory, so retain the existing stream.
pass
configure_utf8_stdio()
try:
import orjson
except ImportError:
@@ -1,8 +1,8 @@
# planning-with-files: set or display the active plan pointer (PowerShell).
#
# Usage:
# .\set-active-plan.ps1 <plan_id> — pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 — print the current active plan (if any)
# .\set-active-plan.ps1 <plan_id> - pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 - print the current active plan (if any)
param(
[string]$PlanId = ""
@@ -19,7 +19,7 @@ if ($PlanId -eq "") {
Write-Output "Active plan: $current"
Write-Output "Path: $planDir"
} elseif ($current -ne "") {
Write-Output "Active plan pointer: $current (directory not found — stale pointer)"
Write-Output "Active plan pointer: $current (directory not found - stale pointer)"
} else {
Write-Output "No active plan set."
}
@@ -41,7 +41,8 @@ if (-not (Test-Path $PlanRoot)) {
New-Item -ItemType Directory -Path $PlanRoot -Force | Out-Null
}
Set-Content -Path $ActiveFile -Value $PlanId -Encoding UTF8 -NoNewline
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($ActiveFile, $PlanId, $utf8NoBom)
Write-Output "Active plan set to: $PlanId"
Write-Output "Path: $PlanDir"
@@ -0,0 +1,85 @@
# Findings & Decisions
<!--
WHAT: Knowledge base for your analytics session. Stores data sources, hypotheses, and results.
WHY: Context windows are limited. This file is your "external memory" for analytical work.
WHEN: Update after ANY discovery, especially after running queries or viewing charts.
-->
## Data Sources
<!--
WHAT: Every data source you connected to, with schema details and quality notes.
WHY: Knowing where your data came from and its limitations is critical for reproducibility.
EXAMPLE:
| user_events | PostgreSQL prod replica | 2.3M rows | user_id, event_type, ts | 0.2% null user_id |
| revenue.csv | Finance team export | 45K rows | account_id, mrr, churn_date | Complete, no nulls |
-->
| Source | Location | Size | Key Fields | Quality Notes |
|--------|----------|------|------------|---------------|
| | | | | |
## Hypothesis Log
<!--
WHAT: Each hypothesis you tested, the method used, and the result.
WHY: Structured tracking prevents p-hacking and makes your reasoning auditable.
EXAMPLE:
| H1: Churn > 50% for low-activity users | Chi-squared test | Confirmed (p=0.003) | High |
| H2: Feature X correlates with retention | Pearson correlation | Rejected (r=0.08) | High |
-->
| Hypothesis | Test Method | Result | Confidence |
|------------|-------------|--------|------------|
| | | | |
## Query Results
<!--
WHAT: Key queries you ran and what they revealed.
WHY: Queries are ephemeral - if you don't write down the results, they're lost on context reset.
WHEN: After EVERY significant query. Don't wait.
EXAMPLE:
### Churn rate by activity segment
Query: SELECT activity_bucket, COUNT(*), AVG(churned) FROM user_segments GROUP BY 1
Result: Low activity: 62% churn, Medium: 28%, High: 8%
Interpretation: Strong inverse relationship between activity and churn
-->
<!-- Record query, result summary, and interpretation for each significant query -->
## Statistical Findings
<!--
WHAT: Formal statistical test results with all relevant metrics.
WHY: Recording p-values, effect sizes, and confidence intervals makes results reproducible.
EXAMPLE:
| Chi-squared (churn ~ activity) | p=0.003 | Cramer's V=0.31 | Reject null: activity segments differ significantly in churn |
| Pearson (feature_x ~ retention) | p=0.42 | r=0.08 | Fail to reject: no meaningful correlation |
-->
| Test | p-value | Effect Size | Conclusion |
|------|---------|-------------|------------|
| | | | |
## Technical Decisions
<!--
WHAT: Analytical method choices with reasoning.
EXAMPLE:
| Use log transform on revenue | Right-skewed distribution, normalizes for parametric tests |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| | |
## Resources
<!-- URLs, file paths, documentation links -->
-
## Visual/Browser Findings
<!--
CRITICAL: Update after viewing charts, dashboards, or browser results.
Multimodal content doesn't persist in context - capture as text immediately.
-->
-
---
*Update this file after every 2 view/browser/search operations*
*This prevents visual information from being lost*
@@ -0,0 +1,106 @@
# Task Plan: [Analytics Project Description]
<!--
WHAT: Roadmap for a data analytics or exploration session.
WHY: Analytics workflows have different phases than software development — hypothesis testing,
data quality checks, and statistical validation don't map to a generic build cycle.
WHEN: Create this FIRST before starting any data exploration. Update after each phase.
-->
## Goal
<!--
WHAT: One clear sentence describing what you're trying to learn or produce.
EXAMPLE: "Determine which user segments have the highest churn risk using last 90 days of activity data."
-->
[One sentence describing the analytical objective]
## Current Phase
<!--
WHAT: Which phase you're currently working on (e.g., "Phase 1", "Phase 3").
WHY: Quick reference for where you are. Update this as you progress.
-->
Phase 1
## Phases
### Phase 1: Data Discovery
<!--
WHAT: Connect to data sources, understand schemas, assess data quality.
WHY: Bad data produces bad analysis. This phase prevents wasted effort on unreliable inputs.
-->
- [ ] Identify and connect to data sources
- [ ] Document schemas and field descriptions in findings.md
- [ ] Assess data quality (nulls, duplicates, outliers, date ranges)
- [ ] Estimate dataset size and query performance
- **Status:** in_progress
### Phase 2: Exploratory Analysis
<!--
WHAT: Distributions, correlations, outliers, initial patterns.
WHY: Understanding the shape of your data before testing hypotheses prevents false conclusions.
-->
- [ ] Compute summary statistics for key variables
- [ ] Visualize distributions and relationships
- [ ] Identify outliers and anomalies
- [ ] Document initial patterns in findings.md
- **Status:** pending
### Phase 3: Hypothesis Testing
<!--
WHAT: Formalize hypotheses, run statistical tests, validate findings.
WHY: Moving from "it looks like X" to "we can confidently say X" requires structured testing.
-->
- [ ] Formalize hypotheses from exploratory phase
- [ ] Select appropriate statistical tests
- [ ] Run tests and record results in findings.md
- [ ] Validate findings against holdout data or alternative methods
- **Status:** pending
### Phase 4: Synthesis & Reporting
<!--
WHAT: Summarize findings, create visualizations, document conclusions.
WHY: Analysis without clear communication is wasted work. This phase produces the deliverable.
-->
- [ ] Summarize key findings with supporting evidence
- [ ] Create final visualizations
- [ ] Document conclusions and recommendations
- [ ] Note limitations and areas for further investigation
- **Status:** pending
## Hypotheses
<!--
WHAT: Questions you're investigating, stated as testable hypotheses.
WHY: Explicit hypotheses prevent fishing expeditions and keep analysis focused.
EXAMPLE:
1. Users who logged in < 3 times in the last 30 days have > 50% churn rate (H1)
2. Feature X adoption correlates with retention (r > 0.3) (H2)
-->
1. [Hypothesis to test]
2. [Hypothesis to test]
## Decisions Made
<!--
WHAT: Analytical decisions with reasoning (e.g., choosing a test, filtering criteria).
EXAMPLE:
| Use median instead of mean | Revenue data is heavily right-skewed |
| Filter to last 90 days | Earlier data uses a different tracking schema |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Errors Encountered
<!--
WHAT: Every error you encounter, what attempt number it was, and how you resolved it.
EXAMPLE:
| Query timeout on raw table | 1 | Added date partition filter |
| Null join keys in user_events | 2 | Inner join instead of left join, documented data loss |
-->
| Error | Attempt | Resolution |
|-------|---------|------------|
| | 1 | |
## Notes
- Update phase status as you progress: pending -> in_progress -> complete
- Re-read this plan before major analytical decisions
- Log ALL errors - they help avoid repetition
- Write query results and visual findings to findings.md immediately
+10 -3
View File
@@ -2,11 +2,12 @@
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"matcher": "startup|resume|clear|compact",
"hooks": [
{
"type": "command",
"command": "sh .codex/hooks/session-start.sh 2>/dev/null || sh \"$HOME/.codex/hooks/session-start.sh\" 2>/dev/null || true",
"commandWindows": "cmd /c .codex\\hooks\\pwf-hook.cmd run_sh.py session-start.sh",
"statusMessage": "Loading planning context"
}
]
@@ -17,7 +18,8 @@
"hooks": [
{
"type": "command",
"command": "sh .codex/hooks/user-prompt-submit.sh 2>/dev/null || sh \"$HOME/.codex/hooks/user-prompt-submit.sh\" 2>/dev/null || true"
"command": "sh .codex/hooks/user-prompt-submit.sh 2>/dev/null || sh \"$HOME/.codex/hooks/user-prompt-submit.sh\" 2>/dev/null || true",
"commandWindows": "cmd /c .codex\\hooks\\pwf-hook.cmd run_sh.py user-prompt-submit.sh"
}
]
}
@@ -29,6 +31,7 @@
{
"type": "command",
"command": "python3 .codex/hooks/pre_tool_use.py 2>/dev/null || python3 \"$HOME/.codex/hooks/pre_tool_use.py\" 2>/dev/null || true",
"commandWindows": "cmd /c .codex\\hooks\\pwf-hook.cmd pre_tool_use.py",
"statusMessage": "Checking plan before Bash"
}
]
@@ -39,7 +42,8 @@
"hooks": [
{
"type": "command",
"command": "python3 .codex/hooks/permission_request.py 2>/dev/null || python3 \"$HOME/.codex/hooks/permission_request.py\" 2>/dev/null || true"
"command": "python3 .codex/hooks/permission_request.py 2>/dev/null || python3 \"$HOME/.codex/hooks/permission_request.py\" 2>/dev/null || true",
"commandWindows": "cmd /c .codex\\hooks\\pwf-hook.cmd permission_request.py"
}
]
}
@@ -51,6 +55,7 @@
{
"type": "command",
"command": "python3 .codex/hooks/post_tool_use.py 2>/dev/null || python3 \"$HOME/.codex/hooks/post_tool_use.py\" 2>/dev/null || true",
"commandWindows": "cmd /c .codex\\hooks\\pwf-hook.cmd post_tool_use.py",
"statusMessage": "Reviewing Bash against plan"
}
]
@@ -63,6 +68,7 @@
{
"type": "command",
"command": "sh .codex/hooks/pre-compact.sh 2>/dev/null || sh \"$HOME/.codex/hooks/pre-compact.sh\" 2>/dev/null || true",
"commandWindows": "cmd /c .codex\\hooks\\pwf-hook.cmd run_sh.py pre-compact.sh",
"statusMessage": "Preparing planning context before compact"
}
]
@@ -74,6 +80,7 @@
{
"type": "command",
"command": "python3 .codex/hooks/stop.py 2>/dev/null || python3 \"$HOME/.codex/hooks/stop.py\" 2>/dev/null || true",
"commandWindows": "cmd /c .codex\\hooks\\pwf-hook.cmd stop.py",
"timeout": 30
}
]
+69 -3
View File
@@ -3,6 +3,7 @@ from __future__ import annotations
import json
import os
import shutil
import subprocess
import sys
from pathlib import Path
@@ -45,6 +46,8 @@ def is_session_attached(root: Path, session_id: str | None) -> bool:
existing single-session users are not broken on upgrade.
Isolation mode: return True only when the session has an attached sentinel.
"""
if os.environ.get("PLANNING_DISABLED", "") == "1":
return False # issue #195: explicit per-invocation opt-out (one-shot exec/CI)
sessions_dir = root / ".planning" / "sessions"
if not sessions_dir.exists():
return True # legacy — no sessions dir means single-session setup
@@ -56,7 +59,7 @@ def is_session_attached(root: Path, session_id: str | None) -> bool:
def emit_json(payload: dict[str, Any]) -> None:
if not payload:
return
json.dump(payload, sys.stdout, ensure_ascii=False)
json.dump(payload, sys.stdout, ensure_ascii=True)
sys.stdout.write("\n")
@@ -70,13 +73,76 @@ def parse_json(text: str) -> dict[str, Any]:
return payload if isinstance(payload, dict) else {}
def run_shell_script(script_name: str, cwd: Path) -> tuple[str, str]:
def _windows_git_bash() -> tuple[str | None, list[str]]:
"""Locate git-for-windows sh.exe plus the unix-tools dirs the .sh hooks need.
Returns (sh_path, extra_path_dirs). A default Git-for-Windows install puts
only cmd\\git.exe on PATH, not usr\\bin\\sh.exe or the coreutils (grep, head,
date, tr) the hook scripts call and re-invoke via nested `sh`. So when `sh`
is not directly on PATH we anchor on git.exe (or the standard install roots)
and probe for sh.exe and its sibling bin dirs. This is exactly issue #201:
the reporter had git bash installed but its usr\\bin was not on PATH.
"""
system32 = (Path(os.environ.get("SystemRoot", r"C:\Windows")) / "System32").resolve()
for exe in ("sh", "bash"):
found = shutil.which(exe)
if found:
candidate = Path(found).resolve()
parent = candidate.parent
# Windows' bash.exe is a WSL launcher, not a POSIX shell. Selecting
# it before Git Bash makes every shell hook fail when WSL has no
# installed distro (the common Git-for-Windows-only setup), and even
# a working WSL bash cannot run C:\ script paths. The Store alias
# under WindowsApps is the same launcher.
if parent != system32 and parent.name.lower() != "windowsapps":
return str(candidate), [str(parent)]
roots: list[Path] = []
git = shutil.which("git")
if git:
# <root>\cmd\git.exe or <root>\bin\git.exe -> <root>
roots.append(Path(git).resolve().parent.parent)
for env_var in ("ProgramW6432", "ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"):
base = os.environ.get(env_var)
if base:
roots.append(Path(base) / "Git")
roots.append(Path(base) / "Programs" / "Git")
for root in roots:
sh_exe = root / "usr" / "bin" / "sh.exe"
if sh_exe.exists():
extra = [root / "usr" / "bin", root / "bin", root / "mingw64" / "bin"]
return str(sh_exe), [str(d) for d in extra if d.exists()]
return None, []
def run_shell_script(script_name: str, cwd: Path, *args: str) -> tuple[str, str]:
sh_cmd = "sh"
env = None
if os.name == "nt":
sh_path, extra_dirs = _windows_git_bash()
if sh_path is None:
# No git bash reachable: run nothing rather than crash. An advisory
# hook must never surface an error (issue #201). docs/codex.md tells
# Windows users to install Git for Windows to enable these hooks.
return "", ""
sh_cmd = sh_path
env = os.environ.copy()
if extra_dirs:
env["PATH"] = os.pathsep.join(extra_dirs) + os.pathsep + env.get("PATH", "")
# session-catchup.py resolves via $PYTHON_BIN first; hand it the real
# interpreter so it never falls back to the Store python3.exe stub.
env.setdefault("PYTHON_BIN", sys.executable)
result = subprocess.run(
["sh", str(HOOK_DIR / script_name)],
[sh_cmd, str(HOOK_DIR / script_name), *args],
cwd=str(cwd),
text=True,
encoding="utf-8",
errors="replace",
capture_output=True,
check=False,
env=env,
)
return result.stdout.strip(), result.stderr.strip()
+4 -1
View File
@@ -17,7 +17,10 @@ def main() -> None:
if not adapter.is_session_attached(root, adapter.session_id_from_payload(payload)):
return
plan = root / "task_plan.md"
# Pass a relative plan root so the shell resolver returns a path that is
# valid in both Git Bash and native Windows Python.
plan_dir, _ = adapter.run_shell_script("resolve-plan-dir.sh", root, ".planning")
plan = root / plan_dir / "task_plan.md" if plan_dir else root / "task_plan.md"
if not plan.exists():
return
+3
View File
@@ -1,6 +1,9 @@
#!/bin/bash
# planning-with-files: Post-tool-use hook for Codex
# issue #195: per-invocation opt-out for one-shot/CI sessions.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
PLAN_DIR="$(sh "${HOOK_DIR}/resolve-plan-dir.sh" 2>/dev/null)"
PLAN_FILE="${PLAN_DIR:+${PLAN_DIR}/}task_plan.md"
+3
View File
@@ -2,6 +2,9 @@
# planning-with-files: PreCompact hook for Codex
# Reminds the agent to flush progress before context compaction.
# issue #195: per-invocation opt-out for one-shot/CI sessions.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
PLAN_DIR="$(sh "${HOOK_DIR}/resolve-plan-dir.sh" 2>/dev/null)"
PLAN_FILE="${PLAN_DIR:+${PLAN_DIR}/}task_plan.md"
+7
View File
@@ -1,6 +1,13 @@
#!/bin/bash
# planning-with-files: Pre-tool-use hook for Codex
# issue #195: per-invocation opt-out for one-shot/CI sessions. Still emit the
# allow decision so the tool call proceeds; only the plan context is skipped.
if [ "${PLANNING_DISABLED:-}" = "1" ]; then
echo '{"decision": "allow"}'
exit 0
fi
HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
PLAN_DIR="$(sh "${HOOK_DIR}/resolve-plan-dir.sh" 2>/dev/null)"
PLAN_FILE="${PLAN_DIR:+${PLAN_DIR}/}task_plan.md"
+6 -2
View File
@@ -9,7 +9,6 @@ def main() -> None:
root = adapter.cwd_from_payload(payload)
if not adapter.is_session_attached(root, adapter.session_id_from_payload(payload)):
adapter.emit_json({"decision": "allow"})
return
stdout, stderr = adapter.run_shell_script("pre-tool-use.sh", root)
@@ -21,7 +20,12 @@ def main() -> None:
return
if stderr:
adapter.emit_json({"systemMessage": stderr})
adapter.emit_json({
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"additionalContext": stderr,
}
})
if __name__ == "__main__":
+23
View File
@@ -0,0 +1,23 @@
@echo off
setlocal
rem planning-with-files: Windows hook launcher for Codex (issue #201).
rem Codex runs each hook's commandWindows; on Windows that routes here as:
rem cmd /c .codex\hooks\pwf-hook.cmd <script.py> [args...]
rem A .cmd is the one form that runs identically whether Codex spawns the hook
rem via cmd.exe, PowerShell, or direct CreateProcess, and it lets us fall back
rem from the py launcher to python.exe. The Microsoft Store python3.exe alias is
rem deliberately never selected. Always exits 0 so an advisory hook cannot
rem report "hook exited with code 1".
set "PY_EXE="
set "PY_ARGS="
if defined PYTHON_BIN if exist "%PYTHON_BIN%" set "PY_EXE=%PYTHON_BIN%"
if not defined PY_EXE where py >nul 2>nul && set "PY_EXE=py" && set "PY_ARGS=-3"
if not defined PY_EXE where python >nul 2>nul && set "PY_EXE=python"
rem Codex can start hooks with a reduced PATH. Probe the normal uv and CPython
rem install locations so a valid user installation still works in that case.
if not defined PY_EXE for /d %%D in ("%APPDATA%\uv\python\*") do if exist "%%~fD\python.exe" if not defined PY_EXE set "PY_EXE=%%~fD\python.exe"
if not defined PY_EXE for /d %%D in ("%LOCALAPPDATA%\uv\python\*") do if exist "%%~fD\python.exe" if not defined PY_EXE set "PY_EXE=%%~fD\python.exe"
if not defined PY_EXE for /d %%D in ("%LOCALAPPDATA%\Programs\Python\Python*") do if exist "%%~fD\python.exe" if not defined PY_EXE set "PY_EXE=%%~fD\python.exe"
if not defined PY_EXE exit /b 0
"%PY_EXE%" %PY_ARGS% "%~dp0%~1" %2 %3 %4 %5
exit /b 0
+8 -70
View File
@@ -1,76 +1,14 @@
#!/bin/sh
# planning-with-files: resolve active plan directory.
# planning-with-files: global Codex hook resolver entry point.
#
# Resolution order:
# 1. $PLAN_ID env var → ./.planning/$PLAN_ID/ if exists
# 2. ./.planning/.active_plan content → matching dir if exists
# 3. Newest ./.planning/<dir>/ by mtime
# 4. Otherwise empty stdout (caller falls back to legacy ./task_plan.md)
#
# Always exits 0. Never errors out the agent loop.
#
# Usage:
# PLAN_DIR="$(sh scripts/resolve-plan-dir.sh)"
# PLAN_FILE="${PLAN_DIR:+$PLAN_DIR/}task_plan.md"
# The resolver implementation is owned by the installed skill. Keep this file
# as a thin forwarding shim so Hook and helper callers cannot silently drift
# onto different slug/containment rules again.
set -u
PLAN_ROOT="${1:-${PWD}/.planning}"
ACTIVE_FILE="${PLAN_ROOT}/.active_plan"
HOOK_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" 2>/dev/null && pwd)" || exit 0
CANONICAL_RESOLVER="${HOOK_DIR}/../skills/planning-with-files/scripts/resolve-plan-dir.sh"
resolve_from_env() {
plan_id="${PLAN_ID:-}"
[ -z "${plan_id}" ] && return 1
candidate="${PLAN_ROOT}/${plan_id}"
if [ -d "${candidate}" ]; then
printf "%s\n" "${candidate}"
return 0
fi
return 1
}
resolve_from_active_file() {
[ -f "${ACTIVE_FILE}" ] || return 1
plan_id="$(tr -d '\r\n' < "${ACTIVE_FILE}")"
[ -z "${plan_id}" ] && return 1
candidate="${PLAN_ROOT}/${plan_id}"
if [ -d "${candidate}" ]; then
printf "%s\n" "${candidate}"
return 0
fi
return 1
}
resolve_latest_dir() {
[ -d "${PLAN_ROOT}" ] || return 1
# Portable newest-mtime selector. Avoid `ls -t` BSD/GNU drift.
# Only consider dirs that contain task_plan.md — skips system dirs like sessions/.
latest=""
latest_mtime=0
for entry in "${PLAN_ROOT}"/*/; do
[ -d "${entry}" ] || continue
# Strip trailing slash
clean="${entry%/}"
# Skip hidden dirs
case "$(basename "${clean}")" in
.*) continue ;;
esac
# Skip dirs that are not plan dirs
[ -f "${clean}/task_plan.md" ] || continue
mtime="$(date -r "${clean}" +%s 2>/dev/null || stat -c '%Y' "${clean}" 2>/dev/null || echo 0)"
if [ "${mtime}" -gt "${latest_mtime}" ] 2>/dev/null; then
latest_mtime="${mtime}"
latest="${clean}"
fi
done
if [ -n "${latest}" ]; then
printf "%s\n" "${latest}"
return 0
fi
return 1
}
if resolve_from_env; then exit 0; fi
if resolve_from_active_file; then exit 0; fi
if resolve_latest_dir; then exit 0; fi
exit 0
[ -f "${CANONICAL_RESOLVER}" ] || exit 0
exec sh "${CANONICAL_RESOLVER}" "$@"
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""planning-with-files: Windows front-door for the pure-shell Codex hooks.
The three shell-only hooks (session-start, user-prompt-submit, pre-compact) are
invoked directly as ``sh <script>.sh`` on macOS/Linux. On Windows their
``commandWindows`` routes here:
cmd /c .codex\\hooks\\pwf-hook.cmd run_sh.py session-start.sh
We reuse the adapter's shell resolver, which locates the git-for-windows
``sh.exe`` and puts its coreutils on PATH, then run the same ``.sh`` the unix
hook runs and print its stdout. Never used on unix. Always exits 0.
"""
from __future__ import annotations
import json
import sys
import codex_hook_adapter as adapter
def main() -> None:
if len(sys.argv) < 2:
return
script_name = sys.argv[1]
payload = adapter.load_payload()
root = adapter.cwd_from_payload(payload)
stdout, _ = adapter.run_shell_script(script_name, root)
if not stdout:
return
context_events = {
"session-start.sh": "SessionStart",
"user-prompt-submit.sh": "UserPromptSubmit",
}
if script_name in context_events:
result = {
"hookSpecificOutput": {
"hookEventName": context_events[script_name],
"additionalContext": stdout,
}
}
sys.stdout.write(json.dumps(result, ensure_ascii=True) + "\n")
return
if script_name == "pre-compact.sh":
result = {
"continue": True,
"systemMessage": stdout,
}
sys.stdout.write(json.dumps(result, ensure_ascii=True) + "\n")
return
sys.stdout.write(stdout + "\n")
if __name__ == "__main__":
raise SystemExit(adapter.main_guard(main))
+4
View File
@@ -2,6 +2,10 @@
# planning-with-files: SessionStart hook for Codex
# Runs session catchup, then reuses the same prompt context hook as UserPromptSubmit.
# issue #195: per-invocation opt-out for one-shot/CI sessions (e.g. codex exec)
# that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
CODEX_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
SKILL_DIR="$CODEX_ROOT/skills/planning-with-files"
+9
View File
@@ -1,6 +1,9 @@
#!/bin/bash
# planning-with-files: Stop hook for Codex
# issue #195: per-invocation opt-out for one-shot/CI sessions.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
PLAN_DIR="$(sh "${HOOK_DIR}/resolve-plan-dir.sh" 2>/dev/null)"
PLAN_FILE="${PLAN_DIR:+${PLAN_DIR}/}task_plan.md"
@@ -25,6 +28,12 @@ fi
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: a task_plan.md with no "### Phase" headings is not phase-structured.
# Without this guard the hook emits a false "0/0 phases complete" followup_message.
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
echo "{\"followup_message\": \"[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting.\"}"
exit 0
+4
View File
@@ -1,6 +1,10 @@
#!/bin/bash
# planning-with-files: User prompt submit hook for Codex
# issue #195: per-invocation opt-out for one-shot/CI sessions (e.g. codex exec)
# that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
PLAN_DIR="$(sh "${HOOK_DIR}/resolve-plan-dir.sh" 2>/dev/null)"
PLAN_FILE="${PLAN_DIR:+${PLAN_DIR}/}task_plan.md"
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: planning-with-files
description: Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear.
description: "Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear."
user-invocable: true
allowed-tools: "Read Write Edit Bash Glob Grep"
hooks:
@@ -28,7 +28,7 @@ hooks:
- type: command
command: "RESOLVED=\"\"; SCOPE=\"\"; SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'; if [ -n \"${PLAN_ID:-}\" ] && printf \"%s\" \"$PLAN_ID\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${PLAN_ID}\" ]; then RESOLVED=\".planning/${PLAN_ID}\"; SCOPE=\"scoped\"; elif [ -f .planning/.active_plan ]; then AP=$(tr -d '\\r\\n[:space:]' < .planning/.active_plan 2>/dev/null); if [ -n \"$AP\" ] && printf \"%s\" \"$AP\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${AP}\" ]; then RESOLVED=\".planning/${AP}\"; SCOPE=\"scoped\"; fi; fi; if [ -z \"$RESOLVED\" ] && [ -d .planning ]; then NEWEST=\"\"; NEWEST_MT=0; for d in .planning/*/; do d=\"${d%/}\"; n=$(basename \"$d\"); case \"$n\" in .*) continue;; esac; printf \"%s\" \"$n\" | grep -Eq \"$SLUG_RE\" || continue; [ -f \"$d/task_plan.md\" ] || continue; m=$(stat -c '%Y' \"$d\" 2>/dev/null || stat -f '%m' \"$d\" 2>/dev/null || date -r \"$d\" +%s 2>/dev/null || echo 0); if [ \"$m\" -gt \"$NEWEST_MT\" ] 2>/dev/null; then NEWEST_MT=\"$m\"; NEWEST=\"$d\"; fi; done; [ -n \"$NEWEST\" ] && { RESOLVED=\"$NEWEST\"; SCOPE=\"scoped\"; }; fi; if [ -z \"$RESOLVED\" ] && [ -f task_plan.md ]; then RESOLVED=\".\"; SCOPE=\"root\"; fi; [ -z \"$RESOLVED\" ] && exit 0; if [ \"$SCOPE\" = \"root\" ]; then PLAN_FILE=\"task_plan.md\"; PROGRESS_FILE=\"progress.md\"; ATTEST=\"\"; [ -f .plan-attestation ] && ATTEST=$(tr -d '\\r\\n[:space:]' < .plan-attestation 2>/dev/null); else PLAN_FILE=\"${RESOLVED}/task_plan.md\"; PROGRESS_FILE=\"${RESOLVED}/progress.md\"; ATTEST=\"\"; [ -f \"${RESOLVED}/.attestation\" ] && ATTEST=$(tr -d '\\r\\n[:space:]' < \"${RESOLVED}/.attestation\" 2>/dev/null); fi; [ -f \"$PLAN_FILE\" ] || exit 0; TAMPERED=0; ACTUAL=\"\"; if [ -n \"$ATTEST\" ]; then CD=\"${TMPDIR:-/tmp}/pwf-sha\"; mkdir -p \"$CD\" 2>/dev/null; KEY=$(printf \"%s\" \"$PLAN_FILE\" | { sha256sum 2>/dev/null || shasum -a 256 2>/dev/null; } | awk '{print $1}' | cut -c1-16); MT=$(stat -c '%Y' \"$PLAN_FILE\" 2>/dev/null || stat -f '%m' \"$PLAN_FILE\" 2>/dev/null || date -r \"$PLAN_FILE\" +%s 2>/dev/null || echo 0); CF=\"$CD/$KEY\"; CM=\"\"; CS=\"\"; if [ -f \"$CF\" ]; then CM=$(sed -n 1p \"$CF\" 2>/dev/null); CS=$(sed -n 2p \"$CF\" 2>/dev/null); fi; if [ -n \"$MT\" ] && [ \"$MT\" = \"$CM\" ] && [ -n \"$CS\" ]; then ACTUAL=\"$CS\"; else ACTUAL=$( (sha256sum \"$PLAN_FILE\" 2>/dev/null || shasum -a 256 \"$PLAN_FILE\" 2>/dev/null) | awk '{print $1}'); [ -n \"$ACTUAL\" ] && [ -n \"$MT\" ] && printf \"%s\\n%s\\n\" \"$MT\" \"$ACTUAL\" > \"$CF\" 2>/dev/null; fi; [ \"$ACTUAL\" != \"$ATTEST\" ] && TAMPERED=1; fi; echo '[planning-with-files] PreCompact: context compaction is about to occur.'; echo 'Before compaction completes: ensure progress.md captures recent actions and task_plan.md status reflects current phase.'; echo 'task_plan.md, findings.md, progress.md remain on disk and will be re-read after compaction.'; [ -n \"$ATTEST\" ] && echo \"Plan-SHA256 at compaction: $ATTEST\"; exit 0"
metadata:
version: "3.1.2"
version: "3.6.0"
---
@@ -20,6 +20,10 @@ param(
[switch]$Gate
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if ($PlanFile -ne "") {
$PlanDir = Split-Path -Parent $PlanFile
if ($PlanDir -eq "") { $PlanDir = "." }
@@ -73,6 +77,13 @@ $COMPLETE = [Math]::Max($completePrimary, $completeInline)
$IN_PROGRESS = [Math]::Max($inProgressPrimary, $inProgressInline)
$PENDING = [Math]::Max($pendingPrimary, $pendingInline)
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
@@ -30,6 +30,10 @@
# interactive terminal (TTY) is skipped entirely. No data on stdin is treated
# as stop_hook_active=false.
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
GATE=0
PLAN_FILE=""
for _arg in "$@"; do
@@ -97,6 +101,13 @@ if [ "$PENDING_INLINE" -gt "$PENDING_PRIMARY" ]; then PENDING="$PENDING_INLINE";
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
@@ -24,13 +24,42 @@ ACTIVE_FILE="${PLAN_ROOT}/.active_plan"
# "feature-foo". The intent is to filter garbage content (e.g. a corrupt
# .active_plan file containing only whitespace or random text) without
# enforcing a date prefix that would break backward compatibility.
SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'
# Pure-sh case patterns; semantics match the previous
# grep -E '^[A-Za-z0-9_][A-Za-z0-9._-]*$' exactly, without a grep fork per
# candidate (the newest-mtime scan calls this once per plan dir).
slug_is_valid() {
case "$1" in
'') return 1 ;;
*[!A-Za-z0-9._-]*) return 1 ;;
[A-Za-z0-9_]*) return 0 ;;
esac
printf "%s" "$1" | grep -Eq "${SLUG_RE}"
return 1
}
# Pure-sh backslash-to-forward-slash normalizer; result lands in $NORM_OUT.
# Windows-native coreutils builds (e.g. C:\Program Files\coreutils on PATH
# ahead of Git's usr/bin) canonicalize MSYS-style /c/... input to C:\-style
# backslash output. The containment prefix match below is written with forward
# slashes, so without this normalization every canonical pair mismatches and
# resolution silently fails. On POSIX systems paths contain no backslash and
# this is the identity. A literal backslash in a Unix filename normalizes to
# "/" and at worst fails containment — the safe direction. No subshell, no
# fork: plain parameter expansion in a loop.
norm_slashes() {
NORM_OUT=""
_ns_rest="$1"
while :; do
case "${_ns_rest}" in
*\\*)
NORM_OUT="${NORM_OUT}${_ns_rest%%\\*}/"
_ns_rest="${_ns_rest#*\\}"
;;
*)
NORM_OUT="${NORM_OUT}${_ns_rest}"
break
;;
esac
done
}
# Portable path canonicalizer. realpath first (Linux, modern coreutils),
@@ -63,19 +92,50 @@ canonicalize() {
# path under the project root (the CWD the script runs from). A symlink inside
# a valid slug dir pointing at /etc or outside the workspace would otherwise let
# the hooks hash and inject an arbitrary file. On any violation we return 1 so
# the caller treats the candidate as unresolved and falls back safely. If
# canonicalization is unavailable for BOTH paths we fail open (return 0) to keep
# legacy behavior byte-equivalent on minimal shells that lack realpath/readlink
# and python; the SLUG_RE check already blocks traversal in the slug name.
# the caller treats the candidate as unresolved and falls back safely.
#
# The root canonicalizes via the relative token "." rather than the $PWD
# string. On some Windows/MSYS setups (8.3 short names, the /tmp mount alias)
# realpath("$PWD") and realpath(relative-candidate) resolve through different
# code paths and land on differently-spelled-but-equal targets, so the prefix
# match below fails and resolution silently goes dark. "." resolves through
# the same physical-cwd path candidates already use (same fix inject-plan.sh
# received earlier; the resolver kept the $PWD form until now). Both sides are
# backslash-normalized before comparison for Windows-native canonicalizers.
# The root is computed once per run: the newest-mtime scan calls this guard
# per plan dir, and each canonicalize costs a process spawn on Windows.
ROOT_REAL=""
ROOT_REAL_SET=0
is_within_root() {
candidate="$1"
root_real="$(canonicalize "${PWD}")" || root_real=""
cand_real="$(canonicalize "${candidate}")" || cand_real=""
if [ -z "${root_real}" ] || [ -z "${cand_real}" ]; then
return 0
if [ "${ROOT_REAL_SET}" = "0" ]; then
ROOT_REAL="$(canonicalize ".")" || ROOT_REAL=""
norm_slashes "${ROOT_REAL}"
ROOT_REAL="${NORM_OUT}"
ROOT_REAL_SET=1
fi
# Canonicalize the candidate through its cwd-RELATIVE form whenever it
# lives under ${PWD}. The candidate string is built from ${PWD} (an MSYS
# long-form spelling), while the root canonicalizes from "." (the process
# cwd, which a caller may have set with an 8.3 short-form string). A
# Windows-native realpath does not unify those spellings, so canonicalizing
# both sides from the same cwd base is the only spelling-stable comparison.
# The emitted result keeps the original absolute candidate — only the
# containment check uses the relative form.
case "${candidate}" in
"${PWD}"/*) check_target=".${candidate#"${PWD}"}" ;;
*) check_target="${candidate}" ;;
esac
cand_real="$(canonicalize "${check_target}")" || cand_real=""
norm_slashes "${cand_real}"
cand_real="${NORM_OUT}"
if [ -z "${ROOT_REAL}" ] || [ -z "${cand_real}" ]; then
# Slug validation blocks textual traversal, but only successful
# canonicalization can rule out a symlink/junction escape.
return 1
fi
case "${cand_real}" in
"${root_real}"|"${root_real}"/*) return 0 ;;
"${ROOT_REAL}"|"${ROOT_REAL}"/*) return 0 ;;
*) return 1 ;;
esac
}
@@ -119,6 +179,12 @@ resolve_from_env() {
resolve_from_active_file() {
[ -f "${ACTIVE_FILE}" ] || return 1
plan_id="$(tr -d '\r\n[:space:]' < "${ACTIVE_FILE}")"
# UTF-8 BOM is not part of the plan id. POSIX printf octal escapes keep
# this portable across GNU/BSD sed variants and Git-for-Windows sh.
utf8_bom="$(printf '\357\273\277')"
case "${plan_id}" in
"${utf8_bom}"*) plan_id="${plan_id#"${utf8_bom}"}" ;;
esac
slug_is_valid "${plan_id}" || return 1
candidate="${PLAN_ROOT}/${plan_id}"
if [ -d "${candidate}" ] && is_within_root "${candidate}"; then
@@ -137,7 +203,7 @@ resolve_latest_dir() {
for entry in "${PLAN_ROOT}"/*/; do
[ -d "${entry}" ] || continue
clean="${entry%/}"
name="$(basename "${clean}")"
name="${clean##*/}"
case "${name}" in
.*) continue ;;
esac
@@ -14,6 +14,30 @@ import os
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
def configure_utf8_stdio() -> None:
"""Make catchup output deterministic on Windows legacy code pages.
Codex sessions and planning files are UTF-8 and can contain arbitrary
Unicode. Windows PowerShell may nevertheless launch Python with a cp1252
(or another OEM/ANSI) stdout codec. A report containing Chinese text then
used to fail at the first ``print`` with ``UnicodeEncodeError``. Configure
both streams before any report is emitted; ``errors='replace'`` also keeps
this advisory hook fail-safe if a malformed surrogate reaches the output.
"""
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, 'reconfigure', None)
if callable(reconfigure):
try:
reconfigure(encoding='utf-8', errors='replace')
except (OSError, ValueError):
# Replaced/captured streams may not permit reconfiguration.
# The hook remains advisory, so retain the existing stream.
pass
configure_utf8_stdio()
try:
import orjson
except ImportError:
@@ -1,8 +1,8 @@
# planning-with-files: set or display the active plan pointer (PowerShell).
#
# Usage:
# .\set-active-plan.ps1 <plan_id> — pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 — print the current active plan (if any)
# .\set-active-plan.ps1 <plan_id> - pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 - print the current active plan (if any)
param(
[string]$PlanId = ""
@@ -19,7 +19,7 @@ if ($PlanId -eq "") {
Write-Output "Active plan: $current"
Write-Output "Path: $planDir"
} elseif ($current -ne "") {
Write-Output "Active plan pointer: $current (directory not found — stale pointer)"
Write-Output "Active plan pointer: $current (directory not found - stale pointer)"
} else {
Write-Output "No active plan set."
}
@@ -41,7 +41,8 @@ if (-not (Test-Path $PlanRoot)) {
New-Item -ItemType Directory -Path $PlanRoot -Force | Out-Null
}
Set-Content -Path $ActiveFile -Value $PlanId -Encoding UTF8 -NoNewline
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($ActiveFile, $PlanId, $utf8NoBom)
Write-Output "Active plan set to: $PlanId"
Write-Output "Path: $PlanDir"
@@ -0,0 +1,85 @@
# Findings & Decisions
<!--
WHAT: Knowledge base for your analytics session. Stores data sources, hypotheses, and results.
WHY: Context windows are limited. This file is your "external memory" for analytical work.
WHEN: Update after ANY discovery, especially after running queries or viewing charts.
-->
## Data Sources
<!--
WHAT: Every data source you connected to, with schema details and quality notes.
WHY: Knowing where your data came from and its limitations is critical for reproducibility.
EXAMPLE:
| user_events | PostgreSQL prod replica | 2.3M rows | user_id, event_type, ts | 0.2% null user_id |
| revenue.csv | Finance team export | 45K rows | account_id, mrr, churn_date | Complete, no nulls |
-->
| Source | Location | Size | Key Fields | Quality Notes |
|--------|----------|------|------------|---------------|
| | | | | |
## Hypothesis Log
<!--
WHAT: Each hypothesis you tested, the method used, and the result.
WHY: Structured tracking prevents p-hacking and makes your reasoning auditable.
EXAMPLE:
| H1: Churn > 50% for low-activity users | Chi-squared test | Confirmed (p=0.003) | High |
| H2: Feature X correlates with retention | Pearson correlation | Rejected (r=0.08) | High |
-->
| Hypothesis | Test Method | Result | Confidence |
|------------|-------------|--------|------------|
| | | | |
## Query Results
<!--
WHAT: Key queries you ran and what they revealed.
WHY: Queries are ephemeral - if you don't write down the results, they're lost on context reset.
WHEN: After EVERY significant query. Don't wait.
EXAMPLE:
### Churn rate by activity segment
Query: SELECT activity_bucket, COUNT(*), AVG(churned) FROM user_segments GROUP BY 1
Result: Low activity: 62% churn, Medium: 28%, High: 8%
Interpretation: Strong inverse relationship between activity and churn
-->
<!-- Record query, result summary, and interpretation for each significant query -->
## Statistical Findings
<!--
WHAT: Formal statistical test results with all relevant metrics.
WHY: Recording p-values, effect sizes, and confidence intervals makes results reproducible.
EXAMPLE:
| Chi-squared (churn ~ activity) | p=0.003 | Cramer's V=0.31 | Reject null: activity segments differ significantly in churn |
| Pearson (feature_x ~ retention) | p=0.42 | r=0.08 | Fail to reject: no meaningful correlation |
-->
| Test | p-value | Effect Size | Conclusion |
|------|---------|-------------|------------|
| | | | |
## Technical Decisions
<!--
WHAT: Analytical method choices with reasoning.
EXAMPLE:
| Use log transform on revenue | Right-skewed distribution, normalizes for parametric tests |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| | |
## Resources
<!-- URLs, file paths, documentation links -->
-
## Visual/Browser Findings
<!--
CRITICAL: Update after viewing charts, dashboards, or browser results.
Multimodal content doesn't persist in context - capture as text immediately.
-->
-
---
*Update this file after every 2 view/browser/search operations*
*This prevents visual information from being lost*
@@ -0,0 +1,106 @@
# Task Plan: [Analytics Project Description]
<!--
WHAT: Roadmap for a data analytics or exploration session.
WHY: Analytics workflows have different phases than software development — hypothesis testing,
data quality checks, and statistical validation don't map to a generic build cycle.
WHEN: Create this FIRST before starting any data exploration. Update after each phase.
-->
## Goal
<!--
WHAT: One clear sentence describing what you're trying to learn or produce.
EXAMPLE: "Determine which user segments have the highest churn risk using last 90 days of activity data."
-->
[One sentence describing the analytical objective]
## Current Phase
<!--
WHAT: Which phase you're currently working on (e.g., "Phase 1", "Phase 3").
WHY: Quick reference for where you are. Update this as you progress.
-->
Phase 1
## Phases
### Phase 1: Data Discovery
<!--
WHAT: Connect to data sources, understand schemas, assess data quality.
WHY: Bad data produces bad analysis. This phase prevents wasted effort on unreliable inputs.
-->
- [ ] Identify and connect to data sources
- [ ] Document schemas and field descriptions in findings.md
- [ ] Assess data quality (nulls, duplicates, outliers, date ranges)
- [ ] Estimate dataset size and query performance
- **Status:** in_progress
### Phase 2: Exploratory Analysis
<!--
WHAT: Distributions, correlations, outliers, initial patterns.
WHY: Understanding the shape of your data before testing hypotheses prevents false conclusions.
-->
- [ ] Compute summary statistics for key variables
- [ ] Visualize distributions and relationships
- [ ] Identify outliers and anomalies
- [ ] Document initial patterns in findings.md
- **Status:** pending
### Phase 3: Hypothesis Testing
<!--
WHAT: Formalize hypotheses, run statistical tests, validate findings.
WHY: Moving from "it looks like X" to "we can confidently say X" requires structured testing.
-->
- [ ] Formalize hypotheses from exploratory phase
- [ ] Select appropriate statistical tests
- [ ] Run tests and record results in findings.md
- [ ] Validate findings against holdout data or alternative methods
- **Status:** pending
### Phase 4: Synthesis & Reporting
<!--
WHAT: Summarize findings, create visualizations, document conclusions.
WHY: Analysis without clear communication is wasted work. This phase produces the deliverable.
-->
- [ ] Summarize key findings with supporting evidence
- [ ] Create final visualizations
- [ ] Document conclusions and recommendations
- [ ] Note limitations and areas for further investigation
- **Status:** pending
## Hypotheses
<!--
WHAT: Questions you're investigating, stated as testable hypotheses.
WHY: Explicit hypotheses prevent fishing expeditions and keep analysis focused.
EXAMPLE:
1. Users who logged in < 3 times in the last 30 days have > 50% churn rate (H1)
2. Feature X adoption correlates with retention (r > 0.3) (H2)
-->
1. [Hypothesis to test]
2. [Hypothesis to test]
## Decisions Made
<!--
WHAT: Analytical decisions with reasoning (e.g., choosing a test, filtering criteria).
EXAMPLE:
| Use median instead of mean | Revenue data is heavily right-skewed |
| Filter to last 90 days | Earlier data uses a different tracking schema |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Errors Encountered
<!--
WHAT: Every error you encounter, what attempt number it was, and how you resolved it.
EXAMPLE:
| Query timeout on raw table | 1 | Added date partition filter |
| Null join keys in user_events | 2 | Inner join instead of left join, documented data loss |
-->
| Error | Attempt | Resolution |
|-------|---------|------------|
| | 1 | |
## Notes
- Update phase status as you progress: pending -> in_progress -> complete
- Re-read this plan before major analytical decisions
- Log ALL errors - they help avoid repetition
- Write query results and visual findings to findings.md immediately
@@ -20,6 +20,10 @@ param(
[switch]$Gate
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if ($PlanFile -ne "") {
$PlanDir = Split-Path -Parent $PlanFile
if ($PlanDir -eq "") { $PlanDir = "." }
@@ -73,6 +77,13 @@ $COMPLETE = [Math]::Max($completePrimary, $completeInline)
$IN_PROGRESS = [Math]::Max($inProgressPrimary, $inProgressInline)
$PENDING = [Math]::Max($pendingPrimary, $pendingInline)
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
@@ -30,6 +30,10 @@
# interactive terminal (TTY) is skipped entirely. No data on stdin is treated
# as stop_hook_active=false.
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
GATE=0
PLAN_FILE=""
for _arg in "$@"; do
@@ -97,6 +101,13 @@ if [ "$PENDING_INLINE" -gt "$PENDING_PRIMARY" ]; then PENDING="$PENDING_INLINE";
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
@@ -24,13 +24,42 @@ ACTIVE_FILE="${PLAN_ROOT}/.active_plan"
# "feature-foo". The intent is to filter garbage content (e.g. a corrupt
# .active_plan file containing only whitespace or random text) without
# enforcing a date prefix that would break backward compatibility.
SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'
# Pure-sh case patterns; semantics match the previous
# grep -E '^[A-Za-z0-9_][A-Za-z0-9._-]*$' exactly, without a grep fork per
# candidate (the newest-mtime scan calls this once per plan dir).
slug_is_valid() {
case "$1" in
'') return 1 ;;
*[!A-Za-z0-9._-]*) return 1 ;;
[A-Za-z0-9_]*) return 0 ;;
esac
printf "%s" "$1" | grep -Eq "${SLUG_RE}"
return 1
}
# Pure-sh backslash-to-forward-slash normalizer; result lands in $NORM_OUT.
# Windows-native coreutils builds (e.g. C:\Program Files\coreutils on PATH
# ahead of Git's usr/bin) canonicalize MSYS-style /c/... input to C:\-style
# backslash output. The containment prefix match below is written with forward
# slashes, so without this normalization every canonical pair mismatches and
# resolution silently fails. On POSIX systems paths contain no backslash and
# this is the identity. A literal backslash in a Unix filename normalizes to
# "/" and at worst fails containment — the safe direction. No subshell, no
# fork: plain parameter expansion in a loop.
norm_slashes() {
NORM_OUT=""
_ns_rest="$1"
while :; do
case "${_ns_rest}" in
*\\*)
NORM_OUT="${NORM_OUT}${_ns_rest%%\\*}/"
_ns_rest="${_ns_rest#*\\}"
;;
*)
NORM_OUT="${NORM_OUT}${_ns_rest}"
break
;;
esac
done
}
# Portable path canonicalizer. realpath first (Linux, modern coreutils),
@@ -63,19 +92,50 @@ canonicalize() {
# path under the project root (the CWD the script runs from). A symlink inside
# a valid slug dir pointing at /etc or outside the workspace would otherwise let
# the hooks hash and inject an arbitrary file. On any violation we return 1 so
# the caller treats the candidate as unresolved and falls back safely. If
# canonicalization is unavailable for BOTH paths we fail open (return 0) to keep
# legacy behavior byte-equivalent on minimal shells that lack realpath/readlink
# and python; the SLUG_RE check already blocks traversal in the slug name.
# the caller treats the candidate as unresolved and falls back safely.
#
# The root canonicalizes via the relative token "." rather than the $PWD
# string. On some Windows/MSYS setups (8.3 short names, the /tmp mount alias)
# realpath("$PWD") and realpath(relative-candidate) resolve through different
# code paths and land on differently-spelled-but-equal targets, so the prefix
# match below fails and resolution silently goes dark. "." resolves through
# the same physical-cwd path candidates already use (same fix inject-plan.sh
# received earlier; the resolver kept the $PWD form until now). Both sides are
# backslash-normalized before comparison for Windows-native canonicalizers.
# The root is computed once per run: the newest-mtime scan calls this guard
# per plan dir, and each canonicalize costs a process spawn on Windows.
ROOT_REAL=""
ROOT_REAL_SET=0
is_within_root() {
candidate="$1"
root_real="$(canonicalize "${PWD}")" || root_real=""
cand_real="$(canonicalize "${candidate}")" || cand_real=""
if [ -z "${root_real}" ] || [ -z "${cand_real}" ]; then
return 0
if [ "${ROOT_REAL_SET}" = "0" ]; then
ROOT_REAL="$(canonicalize ".")" || ROOT_REAL=""
norm_slashes "${ROOT_REAL}"
ROOT_REAL="${NORM_OUT}"
ROOT_REAL_SET=1
fi
# Canonicalize the candidate through its cwd-RELATIVE form whenever it
# lives under ${PWD}. The candidate string is built from ${PWD} (an MSYS
# long-form spelling), while the root canonicalizes from "." (the process
# cwd, which a caller may have set with an 8.3 short-form string). A
# Windows-native realpath does not unify those spellings, so canonicalizing
# both sides from the same cwd base is the only spelling-stable comparison.
# The emitted result keeps the original absolute candidate — only the
# containment check uses the relative form.
case "${candidate}" in
"${PWD}"/*) check_target=".${candidate#"${PWD}"}" ;;
*) check_target="${candidate}" ;;
esac
cand_real="$(canonicalize "${check_target}")" || cand_real=""
norm_slashes "${cand_real}"
cand_real="${NORM_OUT}"
if [ -z "${ROOT_REAL}" ] || [ -z "${cand_real}" ]; then
# Slug validation blocks textual traversal, but only successful
# canonicalization can rule out a symlink/junction escape.
return 1
fi
case "${cand_real}" in
"${root_real}"|"${root_real}"/*) return 0 ;;
"${ROOT_REAL}"|"${ROOT_REAL}"/*) return 0 ;;
*) return 1 ;;
esac
}
@@ -119,6 +179,12 @@ resolve_from_env() {
resolve_from_active_file() {
[ -f "${ACTIVE_FILE}" ] || return 1
plan_id="$(tr -d '\r\n[:space:]' < "${ACTIVE_FILE}")"
# UTF-8 BOM is not part of the plan id. POSIX printf octal escapes keep
# this portable across GNU/BSD sed variants and Git-for-Windows sh.
utf8_bom="$(printf '\357\273\277')"
case "${plan_id}" in
"${utf8_bom}"*) plan_id="${plan_id#"${utf8_bom}"}" ;;
esac
slug_is_valid "${plan_id}" || return 1
candidate="${PLAN_ROOT}/${plan_id}"
if [ -d "${candidate}" ] && is_within_root "${candidate}"; then
@@ -137,7 +203,7 @@ resolve_latest_dir() {
for entry in "${PLAN_ROOT}"/*/; do
[ -d "${entry}" ] || continue
clean="${entry%/}"
name="$(basename "${clean}")"
name="${clean##*/}"
case "${name}" in
.*) continue ;;
esac
@@ -14,6 +14,30 @@ import os
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
def configure_utf8_stdio() -> None:
"""Make catchup output deterministic on Windows legacy code pages.
Codex sessions and planning files are UTF-8 and can contain arbitrary
Unicode. Windows PowerShell may nevertheless launch Python with a cp1252
(or another OEM/ANSI) stdout codec. A report containing Chinese text then
used to fail at the first ``print`` with ``UnicodeEncodeError``. Configure
both streams before any report is emitted; ``errors='replace'`` also keeps
this advisory hook fail-safe if a malformed surrogate reaches the output.
"""
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, 'reconfigure', None)
if callable(reconfigure):
try:
reconfigure(encoding='utf-8', errors='replace')
except (OSError, ValueError):
# Replaced/captured streams may not permit reconfiguration.
# The hook remains advisory, so retain the existing stream.
pass
configure_utf8_stdio()
try:
import orjson
except ImportError:
@@ -1,8 +1,8 @@
# planning-with-files: set or display the active plan pointer (PowerShell).
#
# Usage:
# .\set-active-plan.ps1 <plan_id> — pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 — print the current active plan (if any)
# .\set-active-plan.ps1 <plan_id> - pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 - print the current active plan (if any)
param(
[string]$PlanId = ""
@@ -19,7 +19,7 @@ if ($PlanId -eq "") {
Write-Output "Active plan: $current"
Write-Output "Path: $planDir"
} elseif ($current -ne "") {
Write-Output "Active plan pointer: $current (directory not found — stale pointer)"
Write-Output "Active plan pointer: $current (directory not found - stale pointer)"
} else {
Write-Output "No active plan set."
}
@@ -41,7 +41,8 @@ if (-not (Test-Path $PlanRoot)) {
New-Item -ItemType Directory -Path $PlanRoot -Force | Out-Null
}
Set-Content -Path $ActiveFile -Value $PlanId -Encoding UTF8 -NoNewline
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($ActiveFile, $PlanId, $utf8NoBom)
Write-Output "Active plan set to: $PlanId"
Write-Output "Path: $PlanDir"
+6
View File
@@ -32,6 +32,12 @@ fi
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: no "### Phase" headings -> not phase-structured. Avoid the false
# "0/0 phases done ... continue working" auto-continue message.
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
# All phases complete — provide re-entry guidance
echo "{\"followup_message\": \"[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting.\"}"
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: planning-with-files
description: Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear.
description: "Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear."
user-invocable: true
allowed-tools: "Read Write Edit Bash Glob Grep"
hooks:
@@ -28,7 +28,7 @@ hooks:
- type: command
command: "RESOLVED=\"\"; SCOPE=\"\"; SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'; if [ -n \"${PLAN_ID:-}\" ] && printf \"%s\" \"$PLAN_ID\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${PLAN_ID}\" ]; then RESOLVED=\".planning/${PLAN_ID}\"; SCOPE=\"scoped\"; elif [ -f .planning/.active_plan ]; then AP=$(tr -d '\\r\\n[:space:]' < .planning/.active_plan 2>/dev/null); if [ -n \"$AP\" ] && printf \"%s\" \"$AP\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${AP}\" ]; then RESOLVED=\".planning/${AP}\"; SCOPE=\"scoped\"; fi; fi; if [ -z \"$RESOLVED\" ] && [ -d .planning ]; then NEWEST=\"\"; NEWEST_MT=0; for d in .planning/*/; do d=\"${d%/}\"; n=$(basename \"$d\"); case \"$n\" in .*) continue;; esac; printf \"%s\" \"$n\" | grep -Eq \"$SLUG_RE\" || continue; [ -f \"$d/task_plan.md\" ] || continue; m=$(stat -c '%Y' \"$d\" 2>/dev/null || stat -f '%m' \"$d\" 2>/dev/null || date -r \"$d\" +%s 2>/dev/null || echo 0); if [ \"$m\" -gt \"$NEWEST_MT\" ] 2>/dev/null; then NEWEST_MT=\"$m\"; NEWEST=\"$d\"; fi; done; [ -n \"$NEWEST\" ] && { RESOLVED=\"$NEWEST\"; SCOPE=\"scoped\"; }; fi; if [ -z \"$RESOLVED\" ] && [ -f task_plan.md ]; then RESOLVED=\".\"; SCOPE=\"root\"; fi; [ -z \"$RESOLVED\" ] && exit 0; if [ \"$SCOPE\" = \"root\" ]; then PLAN_FILE=\"task_plan.md\"; PROGRESS_FILE=\"progress.md\"; ATTEST=\"\"; [ -f .plan-attestation ] && ATTEST=$(tr -d '\\r\\n[:space:]' < .plan-attestation 2>/dev/null); else PLAN_FILE=\"${RESOLVED}/task_plan.md\"; PROGRESS_FILE=\"${RESOLVED}/progress.md\"; ATTEST=\"\"; [ -f \"${RESOLVED}/.attestation\" ] && ATTEST=$(tr -d '\\r\\n[:space:]' < \"${RESOLVED}/.attestation\" 2>/dev/null); fi; [ -f \"$PLAN_FILE\" ] || exit 0; TAMPERED=0; ACTUAL=\"\"; if [ -n \"$ATTEST\" ]; then CD=\"${TMPDIR:-/tmp}/pwf-sha\"; mkdir -p \"$CD\" 2>/dev/null; KEY=$(printf \"%s\" \"$PLAN_FILE\" | { sha256sum 2>/dev/null || shasum -a 256 2>/dev/null; } | awk '{print $1}' | cut -c1-16); MT=$(stat -c '%Y' \"$PLAN_FILE\" 2>/dev/null || stat -f '%m' \"$PLAN_FILE\" 2>/dev/null || date -r \"$PLAN_FILE\" +%s 2>/dev/null || echo 0); CF=\"$CD/$KEY\"; CM=\"\"; CS=\"\"; if [ -f \"$CF\" ]; then CM=$(sed -n 1p \"$CF\" 2>/dev/null); CS=$(sed -n 2p \"$CF\" 2>/dev/null); fi; if [ -n \"$MT\" ] && [ \"$MT\" = \"$CM\" ] && [ -n \"$CS\" ]; then ACTUAL=\"$CS\"; else ACTUAL=$( (sha256sum \"$PLAN_FILE\" 2>/dev/null || shasum -a 256 \"$PLAN_FILE\" 2>/dev/null) | awk '{print $1}'); [ -n \"$ACTUAL\" ] && [ -n \"$MT\" ] && printf \"%s\\n%s\\n\" \"$MT\" \"$ACTUAL\" > \"$CF\" 2>/dev/null; fi; [ \"$ACTUAL\" != \"$ATTEST\" ] && TAMPERED=1; fi; echo '[planning-with-files] PreCompact: context compaction is about to occur.'; echo 'Before compaction completes: ensure progress.md captures recent actions and task_plan.md status reflects current phase.'; echo 'task_plan.md, findings.md, progress.md remain on disk and will be re-read after compaction.'; [ -n \"$ATTEST\" ] && echo \"Plan-SHA256 at compaction: $ATTEST\"; exit 0"
metadata:
version: "3.1.2"
version: "3.6.0"
---
@@ -0,0 +1,85 @@
# Findings & Decisions
<!--
WHAT: Knowledge base for your analytics session. Stores data sources, hypotheses, and results.
WHY: Context windows are limited. This file is your "external memory" for analytical work.
WHEN: Update after ANY discovery, especially after running queries or viewing charts.
-->
## Data Sources
<!--
WHAT: Every data source you connected to, with schema details and quality notes.
WHY: Knowing where your data came from and its limitations is critical for reproducibility.
EXAMPLE:
| user_events | PostgreSQL prod replica | 2.3M rows | user_id, event_type, ts | 0.2% null user_id |
| revenue.csv | Finance team export | 45K rows | account_id, mrr, churn_date | Complete, no nulls |
-->
| Source | Location | Size | Key Fields | Quality Notes |
|--------|----------|------|------------|---------------|
| | | | | |
## Hypothesis Log
<!--
WHAT: Each hypothesis you tested, the method used, and the result.
WHY: Structured tracking prevents p-hacking and makes your reasoning auditable.
EXAMPLE:
| H1: Churn > 50% for low-activity users | Chi-squared test | Confirmed (p=0.003) | High |
| H2: Feature X correlates with retention | Pearson correlation | Rejected (r=0.08) | High |
-->
| Hypothesis | Test Method | Result | Confidence |
|------------|-------------|--------|------------|
| | | | |
## Query Results
<!--
WHAT: Key queries you ran and what they revealed.
WHY: Queries are ephemeral - if you don't write down the results, they're lost on context reset.
WHEN: After EVERY significant query. Don't wait.
EXAMPLE:
### Churn rate by activity segment
Query: SELECT activity_bucket, COUNT(*), AVG(churned) FROM user_segments GROUP BY 1
Result: Low activity: 62% churn, Medium: 28%, High: 8%
Interpretation: Strong inverse relationship between activity and churn
-->
<!-- Record query, result summary, and interpretation for each significant query -->
## Statistical Findings
<!--
WHAT: Formal statistical test results with all relevant metrics.
WHY: Recording p-values, effect sizes, and confidence intervals makes results reproducible.
EXAMPLE:
| Chi-squared (churn ~ activity) | p=0.003 | Cramer's V=0.31 | Reject null: activity segments differ significantly in churn |
| Pearson (feature_x ~ retention) | p=0.42 | r=0.08 | Fail to reject: no meaningful correlation |
-->
| Test | p-value | Effect Size | Conclusion |
|------|---------|-------------|------------|
| | | | |
## Technical Decisions
<!--
WHAT: Analytical method choices with reasoning.
EXAMPLE:
| Use log transform on revenue | Right-skewed distribution, normalizes for parametric tests |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| | |
## Resources
<!-- URLs, file paths, documentation links -->
-
## Visual/Browser Findings
<!--
CRITICAL: Update after viewing charts, dashboards, or browser results.
Multimodal content doesn't persist in context - capture as text immediately.
-->
-
---
*Update this file after every 2 view/browser/search operations*
*This prevents visual information from being lost*
@@ -0,0 +1,106 @@
# Task Plan: [Analytics Project Description]
<!--
WHAT: Roadmap for a data analytics or exploration session.
WHY: Analytics workflows have different phases than software development — hypothesis testing,
data quality checks, and statistical validation don't map to a generic build cycle.
WHEN: Create this FIRST before starting any data exploration. Update after each phase.
-->
## Goal
<!--
WHAT: One clear sentence describing what you're trying to learn or produce.
EXAMPLE: "Determine which user segments have the highest churn risk using last 90 days of activity data."
-->
[One sentence describing the analytical objective]
## Current Phase
<!--
WHAT: Which phase you're currently working on (e.g., "Phase 1", "Phase 3").
WHY: Quick reference for where you are. Update this as you progress.
-->
Phase 1
## Phases
### Phase 1: Data Discovery
<!--
WHAT: Connect to data sources, understand schemas, assess data quality.
WHY: Bad data produces bad analysis. This phase prevents wasted effort on unreliable inputs.
-->
- [ ] Identify and connect to data sources
- [ ] Document schemas and field descriptions in findings.md
- [ ] Assess data quality (nulls, duplicates, outliers, date ranges)
- [ ] Estimate dataset size and query performance
- **Status:** in_progress
### Phase 2: Exploratory Analysis
<!--
WHAT: Distributions, correlations, outliers, initial patterns.
WHY: Understanding the shape of your data before testing hypotheses prevents false conclusions.
-->
- [ ] Compute summary statistics for key variables
- [ ] Visualize distributions and relationships
- [ ] Identify outliers and anomalies
- [ ] Document initial patterns in findings.md
- **Status:** pending
### Phase 3: Hypothesis Testing
<!--
WHAT: Formalize hypotheses, run statistical tests, validate findings.
WHY: Moving from "it looks like X" to "we can confidently say X" requires structured testing.
-->
- [ ] Formalize hypotheses from exploratory phase
- [ ] Select appropriate statistical tests
- [ ] Run tests and record results in findings.md
- [ ] Validate findings against holdout data or alternative methods
- **Status:** pending
### Phase 4: Synthesis & Reporting
<!--
WHAT: Summarize findings, create visualizations, document conclusions.
WHY: Analysis without clear communication is wasted work. This phase produces the deliverable.
-->
- [ ] Summarize key findings with supporting evidence
- [ ] Create final visualizations
- [ ] Document conclusions and recommendations
- [ ] Note limitations and areas for further investigation
- **Status:** pending
## Hypotheses
<!--
WHAT: Questions you're investigating, stated as testable hypotheses.
WHY: Explicit hypotheses prevent fishing expeditions and keep analysis focused.
EXAMPLE:
1. Users who logged in < 3 times in the last 30 days have > 50% churn rate (H1)
2. Feature X adoption correlates with retention (r > 0.3) (H2)
-->
1. [Hypothesis to test]
2. [Hypothesis to test]
## Decisions Made
<!--
WHAT: Analytical decisions with reasoning (e.g., choosing a test, filtering criteria).
EXAMPLE:
| Use median instead of mean | Revenue data is heavily right-skewed |
| Filter to last 90 days | Earlier data uses a different tracking schema |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Errors Encountered
<!--
WHAT: Every error you encounter, what attempt number it was, and how you resolved it.
EXAMPLE:
| Query timeout on raw table | 1 | Added date partition filter |
| Null join keys in user_events | 2 | Inner join instead of left join, documented data loss |
-->
| Error | Attempt | Resolution |
|-------|---------|------------|
| | 1 | |
## Notes
- Update phase status as you progress: pending -> in_progress -> complete
- Re-read this plan before major analytical decisions
- Log ALL errors - they help avoid repetition
- Write query results and visual findings to findings.md immediately
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: planning-with-files
description: Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear.
description: "Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear."
user-invocable: true
allowed-tools: "Read Write Edit Bash Glob Grep"
hooks:
@@ -28,7 +28,7 @@ hooks:
- type: command
command: "RESOLVED=\"\"; SCOPE=\"\"; SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'; if [ -n \"${PLAN_ID:-}\" ] && printf \"%s\" \"$PLAN_ID\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${PLAN_ID}\" ]; then RESOLVED=\".planning/${PLAN_ID}\"; SCOPE=\"scoped\"; elif [ -f .planning/.active_plan ]; then AP=$(tr -d '\\r\\n[:space:]' < .planning/.active_plan 2>/dev/null); if [ -n \"$AP\" ] && printf \"%s\" \"$AP\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${AP}\" ]; then RESOLVED=\".planning/${AP}\"; SCOPE=\"scoped\"; fi; fi; if [ -z \"$RESOLVED\" ] && [ -d .planning ]; then NEWEST=\"\"; NEWEST_MT=0; for d in .planning/*/; do d=\"${d%/}\"; n=$(basename \"$d\"); case \"$n\" in .*) continue;; esac; printf \"%s\" \"$n\" | grep -Eq \"$SLUG_RE\" || continue; [ -f \"$d/task_plan.md\" ] || continue; m=$(stat -c '%Y' \"$d\" 2>/dev/null || stat -f '%m' \"$d\" 2>/dev/null || date -r \"$d\" +%s 2>/dev/null || echo 0); if [ \"$m\" -gt \"$NEWEST_MT\" ] 2>/dev/null; then NEWEST_MT=\"$m\"; NEWEST=\"$d\"; fi; done; [ -n \"$NEWEST\" ] && { RESOLVED=\"$NEWEST\"; SCOPE=\"scoped\"; }; fi; if [ -z \"$RESOLVED\" ] && [ -f task_plan.md ]; then RESOLVED=\".\"; SCOPE=\"root\"; fi; [ -z \"$RESOLVED\" ] && exit 0; if [ \"$SCOPE\" = \"root\" ]; then PLAN_FILE=\"task_plan.md\"; PROGRESS_FILE=\"progress.md\"; ATTEST=\"\"; [ -f .plan-attestation ] && ATTEST=$(tr -d '\\r\\n[:space:]' < .plan-attestation 2>/dev/null); else PLAN_FILE=\"${RESOLVED}/task_plan.md\"; PROGRESS_FILE=\"${RESOLVED}/progress.md\"; ATTEST=\"\"; [ -f \"${RESOLVED}/.attestation\" ] && ATTEST=$(tr -d '\\r\\n[:space:]' < \"${RESOLVED}/.attestation\" 2>/dev/null); fi; [ -f \"$PLAN_FILE\" ] || exit 0; TAMPERED=0; ACTUAL=\"\"; if [ -n \"$ATTEST\" ]; then CD=\"${TMPDIR:-/tmp}/pwf-sha\"; mkdir -p \"$CD\" 2>/dev/null; KEY=$(printf \"%s\" \"$PLAN_FILE\" | { sha256sum 2>/dev/null || shasum -a 256 2>/dev/null; } | awk '{print $1}' | cut -c1-16); MT=$(stat -c '%Y' \"$PLAN_FILE\" 2>/dev/null || stat -f '%m' \"$PLAN_FILE\" 2>/dev/null || date -r \"$PLAN_FILE\" +%s 2>/dev/null || echo 0); CF=\"$CD/$KEY\"; CM=\"\"; CS=\"\"; if [ -f \"$CF\" ]; then CM=$(sed -n 1p \"$CF\" 2>/dev/null); CS=$(sed -n 2p \"$CF\" 2>/dev/null); fi; if [ -n \"$MT\" ] && [ \"$MT\" = \"$CM\" ] && [ -n \"$CS\" ]; then ACTUAL=\"$CS\"; else ACTUAL=$( (sha256sum \"$PLAN_FILE\" 2>/dev/null || shasum -a 256 \"$PLAN_FILE\" 2>/dev/null) | awk '{print $1}'); [ -n \"$ACTUAL\" ] && [ -n \"$MT\" ] && printf \"%s\\n%s\\n\" \"$MT\" \"$ACTUAL\" > \"$CF\" 2>/dev/null; fi; [ \"$ACTUAL\" != \"$ATTEST\" ] && TAMPERED=1; fi; echo '[planning-with-files] PreCompact: context compaction is about to occur.'; echo 'Before compaction completes: ensure progress.md captures recent actions and task_plan.md status reflects current phase.'; echo 'task_plan.md, findings.md, progress.md remain on disk and will be re-read after compaction.'; [ -n \"$ATTEST\" ] && echo \"Plan-SHA256 at compaction: $ATTEST\"; exit 0"
metadata:
version: "3.1.2"
version: "3.6.0"
---
# Planning with Files
@@ -178,7 +178,7 @@ Manus operates in a continuous 7-step loop:
## Critical Constraints
- **Single-Action Execution:** ONE tool call per turn. No parallel execution.
- **Single-Action Execution (Manus 2025 original constraint):** ONE tool call per turn, no parallel execution. This documents Manus's 2025 sandbox practice. **2026 update:** modern hosts (Claude Code, Codex CLI) support parallel tool calls and subagents, so this constraint no longer applies as written. The plan file, not the one-call-per-turn rule, remains the coordination point: parallel calls and subagents share state through the durable markdown plan on disk.
- **Plan is Required:** Agent must ALWAYS know: goal, current phase, remaining phases
- **Files are Memory:** Context = volatile. Filesystem = persistent.
- **Never Repeat Failures:** If action failed, next action MUST be different
@@ -20,6 +20,10 @@ param(
[switch]$Gate
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if ($PlanFile -ne "") {
$PlanDir = Split-Path -Parent $PlanFile
if ($PlanDir -eq "") { $PlanDir = "." }
@@ -73,6 +77,13 @@ $COMPLETE = [Math]::Max($completePrimary, $completeInline)
$IN_PROGRESS = [Math]::Max($inProgressPrimary, $inProgressInline)
$PENDING = [Math]::Max($pendingPrimary, $pendingInline)
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
@@ -30,6 +30,10 @@
# interactive terminal (TTY) is skipped entirely. No data on stdin is treated
# as stop_hook_active=false.
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
GATE=0
PLAN_FILE=""
for _arg in "$@"; do
@@ -97,6 +101,13 @@ if [ "$PENDING_INLINE" -gt "$PENDING_PRIMARY" ]; then PENDING="$PENDING_INLINE";
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
@@ -24,13 +24,42 @@ ACTIVE_FILE="${PLAN_ROOT}/.active_plan"
# "feature-foo". The intent is to filter garbage content (e.g. a corrupt
# .active_plan file containing only whitespace or random text) without
# enforcing a date prefix that would break backward compatibility.
SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'
# Pure-sh case patterns; semantics match the previous
# grep -E '^[A-Za-z0-9_][A-Za-z0-9._-]*$' exactly, without a grep fork per
# candidate (the newest-mtime scan calls this once per plan dir).
slug_is_valid() {
case "$1" in
'') return 1 ;;
*[!A-Za-z0-9._-]*) return 1 ;;
[A-Za-z0-9_]*) return 0 ;;
esac
printf "%s" "$1" | grep -Eq "${SLUG_RE}"
return 1
}
# Pure-sh backslash-to-forward-slash normalizer; result lands in $NORM_OUT.
# Windows-native coreutils builds (e.g. C:\Program Files\coreutils on PATH
# ahead of Git's usr/bin) canonicalize MSYS-style /c/... input to C:\-style
# backslash output. The containment prefix match below is written with forward
# slashes, so without this normalization every canonical pair mismatches and
# resolution silently fails. On POSIX systems paths contain no backslash and
# this is the identity. A literal backslash in a Unix filename normalizes to
# "/" and at worst fails containment — the safe direction. No subshell, no
# fork: plain parameter expansion in a loop.
norm_slashes() {
NORM_OUT=""
_ns_rest="$1"
while :; do
case "${_ns_rest}" in
*\\*)
NORM_OUT="${NORM_OUT}${_ns_rest%%\\*}/"
_ns_rest="${_ns_rest#*\\}"
;;
*)
NORM_OUT="${NORM_OUT}${_ns_rest}"
break
;;
esac
done
}
# Portable path canonicalizer. realpath first (Linux, modern coreutils),
@@ -63,19 +92,50 @@ canonicalize() {
# path under the project root (the CWD the script runs from). A symlink inside
# a valid slug dir pointing at /etc or outside the workspace would otherwise let
# the hooks hash and inject an arbitrary file. On any violation we return 1 so
# the caller treats the candidate as unresolved and falls back safely. If
# canonicalization is unavailable for BOTH paths we fail open (return 0) to keep
# legacy behavior byte-equivalent on minimal shells that lack realpath/readlink
# and python; the SLUG_RE check already blocks traversal in the slug name.
# the caller treats the candidate as unresolved and falls back safely.
#
# The root canonicalizes via the relative token "." rather than the $PWD
# string. On some Windows/MSYS setups (8.3 short names, the /tmp mount alias)
# realpath("$PWD") and realpath(relative-candidate) resolve through different
# code paths and land on differently-spelled-but-equal targets, so the prefix
# match below fails and resolution silently goes dark. "." resolves through
# the same physical-cwd path candidates already use (same fix inject-plan.sh
# received earlier; the resolver kept the $PWD form until now). Both sides are
# backslash-normalized before comparison for Windows-native canonicalizers.
# The root is computed once per run: the newest-mtime scan calls this guard
# per plan dir, and each canonicalize costs a process spawn on Windows.
ROOT_REAL=""
ROOT_REAL_SET=0
is_within_root() {
candidate="$1"
root_real="$(canonicalize "${PWD}")" || root_real=""
cand_real="$(canonicalize "${candidate}")" || cand_real=""
if [ -z "${root_real}" ] || [ -z "${cand_real}" ]; then
return 0
if [ "${ROOT_REAL_SET}" = "0" ]; then
ROOT_REAL="$(canonicalize ".")" || ROOT_REAL=""
norm_slashes "${ROOT_REAL}"
ROOT_REAL="${NORM_OUT}"
ROOT_REAL_SET=1
fi
# Canonicalize the candidate through its cwd-RELATIVE form whenever it
# lives under ${PWD}. The candidate string is built from ${PWD} (an MSYS
# long-form spelling), while the root canonicalizes from "." (the process
# cwd, which a caller may have set with an 8.3 short-form string). A
# Windows-native realpath does not unify those spellings, so canonicalizing
# both sides from the same cwd base is the only spelling-stable comparison.
# The emitted result keeps the original absolute candidate — only the
# containment check uses the relative form.
case "${candidate}" in
"${PWD}"/*) check_target=".${candidate#"${PWD}"}" ;;
*) check_target="${candidate}" ;;
esac
cand_real="$(canonicalize "${check_target}")" || cand_real=""
norm_slashes "${cand_real}"
cand_real="${NORM_OUT}"
if [ -z "${ROOT_REAL}" ] || [ -z "${cand_real}" ]; then
# Slug validation blocks textual traversal, but only successful
# canonicalization can rule out a symlink/junction escape.
return 1
fi
case "${cand_real}" in
"${root_real}"|"${root_real}"/*) return 0 ;;
"${ROOT_REAL}"|"${ROOT_REAL}"/*) return 0 ;;
*) return 1 ;;
esac
}
@@ -119,6 +179,12 @@ resolve_from_env() {
resolve_from_active_file() {
[ -f "${ACTIVE_FILE}" ] || return 1
plan_id="$(tr -d '\r\n[:space:]' < "${ACTIVE_FILE}")"
# UTF-8 BOM is not part of the plan id. POSIX printf octal escapes keep
# this portable across GNU/BSD sed variants and Git-for-Windows sh.
utf8_bom="$(printf '\357\273\277')"
case "${plan_id}" in
"${utf8_bom}"*) plan_id="${plan_id#"${utf8_bom}"}" ;;
esac
slug_is_valid "${plan_id}" || return 1
candidate="${PLAN_ROOT}/${plan_id}"
if [ -d "${candidate}" ] && is_within_root "${candidate}"; then
@@ -137,7 +203,7 @@ resolve_latest_dir() {
for entry in "${PLAN_ROOT}"/*/; do
[ -d "${entry}" ] || continue
clean="${entry%/}"
name="$(basename "${clean}")"
name="${clean##*/}"
case "${name}" in
.*) continue ;;
esac
@@ -14,6 +14,30 @@ import os
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
def configure_utf8_stdio() -> None:
"""Make catchup output deterministic on Windows legacy code pages.
Codex sessions and planning files are UTF-8 and can contain arbitrary
Unicode. Windows PowerShell may nevertheless launch Python with a cp1252
(or another OEM/ANSI) stdout codec. A report containing Chinese text then
used to fail at the first ``print`` with ``UnicodeEncodeError``. Configure
both streams before any report is emitted; ``errors='replace'`` also keeps
this advisory hook fail-safe if a malformed surrogate reaches the output.
"""
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, 'reconfigure', None)
if callable(reconfigure):
try:
reconfigure(encoding='utf-8', errors='replace')
except (OSError, ValueError):
# Replaced/captured streams may not permit reconfiguration.
# The hook remains advisory, so retain the existing stream.
pass
configure_utf8_stdio()
try:
import orjson
except ImportError:
@@ -1,8 +1,8 @@
# planning-with-files: set or display the active plan pointer (PowerShell).
#
# Usage:
# .\set-active-plan.ps1 <plan_id> — pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 — print the current active plan (if any)
# .\set-active-plan.ps1 <plan_id> - pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 - print the current active plan (if any)
param(
[string]$PlanId = ""
@@ -19,7 +19,7 @@ if ($PlanId -eq "") {
Write-Output "Active plan: $current"
Write-Output "Path: $planDir"
} elseif ($current -ne "") {
Write-Output "Active plan pointer: $current (directory not found — stale pointer)"
Write-Output "Active plan pointer: $current (directory not found - stale pointer)"
} else {
Write-Output "No active plan set."
}
@@ -41,7 +41,8 @@ if (-not (Test-Path $PlanRoot)) {
New-Item -ItemType Directory -Path $PlanRoot -Force | Out-Null
}
Set-Content -Path $ActiveFile -Value $PlanId -Encoding UTF8 -NoNewline
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($ActiveFile, $PlanId, $utf8NoBom)
Write-Output "Active plan set to: $PlanId"
Write-Output "Path: $PlanDir"
@@ -0,0 +1,85 @@
# Findings & Decisions
<!--
WHAT: Knowledge base for your analytics session. Stores data sources, hypotheses, and results.
WHY: Context windows are limited. This file is your "external memory" for analytical work.
WHEN: Update after ANY discovery, especially after running queries or viewing charts.
-->
## Data Sources
<!--
WHAT: Every data source you connected to, with schema details and quality notes.
WHY: Knowing where your data came from and its limitations is critical for reproducibility.
EXAMPLE:
| user_events | PostgreSQL prod replica | 2.3M rows | user_id, event_type, ts | 0.2% null user_id |
| revenue.csv | Finance team export | 45K rows | account_id, mrr, churn_date | Complete, no nulls |
-->
| Source | Location | Size | Key Fields | Quality Notes |
|--------|----------|------|------------|---------------|
| | | | | |
## Hypothesis Log
<!--
WHAT: Each hypothesis you tested, the method used, and the result.
WHY: Structured tracking prevents p-hacking and makes your reasoning auditable.
EXAMPLE:
| H1: Churn > 50% for low-activity users | Chi-squared test | Confirmed (p=0.003) | High |
| H2: Feature X correlates with retention | Pearson correlation | Rejected (r=0.08) | High |
-->
| Hypothesis | Test Method | Result | Confidence |
|------------|-------------|--------|------------|
| | | | |
## Query Results
<!--
WHAT: Key queries you ran and what they revealed.
WHY: Queries are ephemeral - if you don't write down the results, they're lost on context reset.
WHEN: After EVERY significant query. Don't wait.
EXAMPLE:
### Churn rate by activity segment
Query: SELECT activity_bucket, COUNT(*), AVG(churned) FROM user_segments GROUP BY 1
Result: Low activity: 62% churn, Medium: 28%, High: 8%
Interpretation: Strong inverse relationship between activity and churn
-->
<!-- Record query, result summary, and interpretation for each significant query -->
## Statistical Findings
<!--
WHAT: Formal statistical test results with all relevant metrics.
WHY: Recording p-values, effect sizes, and confidence intervals makes results reproducible.
EXAMPLE:
| Chi-squared (churn ~ activity) | p=0.003 | Cramer's V=0.31 | Reject null: activity segments differ significantly in churn |
| Pearson (feature_x ~ retention) | p=0.42 | r=0.08 | Fail to reject: no meaningful correlation |
-->
| Test | p-value | Effect Size | Conclusion |
|------|---------|-------------|------------|
| | | | |
## Technical Decisions
<!--
WHAT: Analytical method choices with reasoning.
EXAMPLE:
| Use log transform on revenue | Right-skewed distribution, normalizes for parametric tests |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| | |
## Resources
<!-- URLs, file paths, documentation links -->
-
## Visual/Browser Findings
<!--
CRITICAL: Update after viewing charts, dashboards, or browser results.
Multimodal content doesn't persist in context - capture as text immediately.
-->
-
---
*Update this file after every 2 view/browser/search operations*
*This prevents visual information from being lost*
@@ -0,0 +1,106 @@
# Task Plan: [Analytics Project Description]
<!--
WHAT: Roadmap for a data analytics or exploration session.
WHY: Analytics workflows have different phases than software development — hypothesis testing,
data quality checks, and statistical validation don't map to a generic build cycle.
WHEN: Create this FIRST before starting any data exploration. Update after each phase.
-->
## Goal
<!--
WHAT: One clear sentence describing what you're trying to learn or produce.
EXAMPLE: "Determine which user segments have the highest churn risk using last 90 days of activity data."
-->
[One sentence describing the analytical objective]
## Current Phase
<!--
WHAT: Which phase you're currently working on (e.g., "Phase 1", "Phase 3").
WHY: Quick reference for where you are. Update this as you progress.
-->
Phase 1
## Phases
### Phase 1: Data Discovery
<!--
WHAT: Connect to data sources, understand schemas, assess data quality.
WHY: Bad data produces bad analysis. This phase prevents wasted effort on unreliable inputs.
-->
- [ ] Identify and connect to data sources
- [ ] Document schemas and field descriptions in findings.md
- [ ] Assess data quality (nulls, duplicates, outliers, date ranges)
- [ ] Estimate dataset size and query performance
- **Status:** in_progress
### Phase 2: Exploratory Analysis
<!--
WHAT: Distributions, correlations, outliers, initial patterns.
WHY: Understanding the shape of your data before testing hypotheses prevents false conclusions.
-->
- [ ] Compute summary statistics for key variables
- [ ] Visualize distributions and relationships
- [ ] Identify outliers and anomalies
- [ ] Document initial patterns in findings.md
- **Status:** pending
### Phase 3: Hypothesis Testing
<!--
WHAT: Formalize hypotheses, run statistical tests, validate findings.
WHY: Moving from "it looks like X" to "we can confidently say X" requires structured testing.
-->
- [ ] Formalize hypotheses from exploratory phase
- [ ] Select appropriate statistical tests
- [ ] Run tests and record results in findings.md
- [ ] Validate findings against holdout data or alternative methods
- **Status:** pending
### Phase 4: Synthesis & Reporting
<!--
WHAT: Summarize findings, create visualizations, document conclusions.
WHY: Analysis without clear communication is wasted work. This phase produces the deliverable.
-->
- [ ] Summarize key findings with supporting evidence
- [ ] Create final visualizations
- [ ] Document conclusions and recommendations
- [ ] Note limitations and areas for further investigation
- **Status:** pending
## Hypotheses
<!--
WHAT: Questions you're investigating, stated as testable hypotheses.
WHY: Explicit hypotheses prevent fishing expeditions and keep analysis focused.
EXAMPLE:
1. Users who logged in < 3 times in the last 30 days have > 50% churn rate (H1)
2. Feature X adoption correlates with retention (r > 0.3) (H2)
-->
1. [Hypothesis to test]
2. [Hypothesis to test]
## Decisions Made
<!--
WHAT: Analytical decisions with reasoning (e.g., choosing a test, filtering criteria).
EXAMPLE:
| Use median instead of mean | Revenue data is heavily right-skewed |
| Filter to last 90 days | Earlier data uses a different tracking schema |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Errors Encountered
<!--
WHAT: Every error you encounter, what attempt number it was, and how you resolved it.
EXAMPLE:
| Query timeout on raw table | 1 | Added date partition filter |
| Null join keys in user_events | 2 | Inner join instead of left join, documented data loss |
-->
| Error | Attempt | Resolution |
|-------|---------|------------|
| | 1 | |
## Notes
- Update phase status as you progress: pending -> in_progress -> complete
- Re-read this plan before major analytical decisions
- Log ALL errors - they help avoid repetition
- Write query results and visual findings to findings.md immediately
@@ -20,6 +20,10 @@ param(
[switch]$Gate
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if ($PlanFile -ne "") {
$PlanDir = Split-Path -Parent $PlanFile
if ($PlanDir -eq "") { $PlanDir = "." }
@@ -73,6 +77,13 @@ $COMPLETE = [Math]::Max($completePrimary, $completeInline)
$IN_PROGRESS = [Math]::Max($inProgressPrimary, $inProgressInline)
$PENDING = [Math]::Max($pendingPrimary, $pendingInline)
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
@@ -30,6 +30,10 @@
# interactive terminal (TTY) is skipped entirely. No data on stdin is treated
# as stop_hook_active=false.
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
GATE=0
PLAN_FILE=""
for _arg in "$@"; do
@@ -97,6 +101,13 @@ if [ "$PENDING_INLINE" -gt "$PENDING_PRIMARY" ]; then PENDING="$PENDING_INLINE";
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
@@ -24,13 +24,42 @@ ACTIVE_FILE="${PLAN_ROOT}/.active_plan"
# "feature-foo". The intent is to filter garbage content (e.g. a corrupt
# .active_plan file containing only whitespace or random text) without
# enforcing a date prefix that would break backward compatibility.
SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'
# Pure-sh case patterns; semantics match the previous
# grep -E '^[A-Za-z0-9_][A-Za-z0-9._-]*$' exactly, without a grep fork per
# candidate (the newest-mtime scan calls this once per plan dir).
slug_is_valid() {
case "$1" in
'') return 1 ;;
*[!A-Za-z0-9._-]*) return 1 ;;
[A-Za-z0-9_]*) return 0 ;;
esac
printf "%s" "$1" | grep -Eq "${SLUG_RE}"
return 1
}
# Pure-sh backslash-to-forward-slash normalizer; result lands in $NORM_OUT.
# Windows-native coreutils builds (e.g. C:\Program Files\coreutils on PATH
# ahead of Git's usr/bin) canonicalize MSYS-style /c/... input to C:\-style
# backslash output. The containment prefix match below is written with forward
# slashes, so without this normalization every canonical pair mismatches and
# resolution silently fails. On POSIX systems paths contain no backslash and
# this is the identity. A literal backslash in a Unix filename normalizes to
# "/" and at worst fails containment — the safe direction. No subshell, no
# fork: plain parameter expansion in a loop.
norm_slashes() {
NORM_OUT=""
_ns_rest="$1"
while :; do
case "${_ns_rest}" in
*\\*)
NORM_OUT="${NORM_OUT}${_ns_rest%%\\*}/"
_ns_rest="${_ns_rest#*\\}"
;;
*)
NORM_OUT="${NORM_OUT}${_ns_rest}"
break
;;
esac
done
}
# Portable path canonicalizer. realpath first (Linux, modern coreutils),
@@ -63,19 +92,50 @@ canonicalize() {
# path under the project root (the CWD the script runs from). A symlink inside
# a valid slug dir pointing at /etc or outside the workspace would otherwise let
# the hooks hash and inject an arbitrary file. On any violation we return 1 so
# the caller treats the candidate as unresolved and falls back safely. If
# canonicalization is unavailable for BOTH paths we fail open (return 0) to keep
# legacy behavior byte-equivalent on minimal shells that lack realpath/readlink
# and python; the SLUG_RE check already blocks traversal in the slug name.
# the caller treats the candidate as unresolved and falls back safely.
#
# The root canonicalizes via the relative token "." rather than the $PWD
# string. On some Windows/MSYS setups (8.3 short names, the /tmp mount alias)
# realpath("$PWD") and realpath(relative-candidate) resolve through different
# code paths and land on differently-spelled-but-equal targets, so the prefix
# match below fails and resolution silently goes dark. "." resolves through
# the same physical-cwd path candidates already use (same fix inject-plan.sh
# received earlier; the resolver kept the $PWD form until now). Both sides are
# backslash-normalized before comparison for Windows-native canonicalizers.
# The root is computed once per run: the newest-mtime scan calls this guard
# per plan dir, and each canonicalize costs a process spawn on Windows.
ROOT_REAL=""
ROOT_REAL_SET=0
is_within_root() {
candidate="$1"
root_real="$(canonicalize "${PWD}")" || root_real=""
cand_real="$(canonicalize "${candidate}")" || cand_real=""
if [ -z "${root_real}" ] || [ -z "${cand_real}" ]; then
return 0
if [ "${ROOT_REAL_SET}" = "0" ]; then
ROOT_REAL="$(canonicalize ".")" || ROOT_REAL=""
norm_slashes "${ROOT_REAL}"
ROOT_REAL="${NORM_OUT}"
ROOT_REAL_SET=1
fi
# Canonicalize the candidate through its cwd-RELATIVE form whenever it
# lives under ${PWD}. The candidate string is built from ${PWD} (an MSYS
# long-form spelling), while the root canonicalizes from "." (the process
# cwd, which a caller may have set with an 8.3 short-form string). A
# Windows-native realpath does not unify those spellings, so canonicalizing
# both sides from the same cwd base is the only spelling-stable comparison.
# The emitted result keeps the original absolute candidate — only the
# containment check uses the relative form.
case "${candidate}" in
"${PWD}"/*) check_target=".${candidate#"${PWD}"}" ;;
*) check_target="${candidate}" ;;
esac
cand_real="$(canonicalize "${check_target}")" || cand_real=""
norm_slashes "${cand_real}"
cand_real="${NORM_OUT}"
if [ -z "${ROOT_REAL}" ] || [ -z "${cand_real}" ]; then
# Slug validation blocks textual traversal, but only successful
# canonicalization can rule out a symlink/junction escape.
return 1
fi
case "${cand_real}" in
"${root_real}"|"${root_real}"/*) return 0 ;;
"${ROOT_REAL}"|"${ROOT_REAL}"/*) return 0 ;;
*) return 1 ;;
esac
}
@@ -119,6 +179,12 @@ resolve_from_env() {
resolve_from_active_file() {
[ -f "${ACTIVE_FILE}" ] || return 1
plan_id="$(tr -d '\r\n[:space:]' < "${ACTIVE_FILE}")"
# UTF-8 BOM is not part of the plan id. POSIX printf octal escapes keep
# this portable across GNU/BSD sed variants and Git-for-Windows sh.
utf8_bom="$(printf '\357\273\277')"
case "${plan_id}" in
"${utf8_bom}"*) plan_id="${plan_id#"${utf8_bom}"}" ;;
esac
slug_is_valid "${plan_id}" || return 1
candidate="${PLAN_ROOT}/${plan_id}"
if [ -d "${candidate}" ] && is_within_root "${candidate}"; then
@@ -137,7 +203,7 @@ resolve_latest_dir() {
for entry in "${PLAN_ROOT}"/*/; do
[ -d "${entry}" ] || continue
clean="${entry%/}"
name="$(basename "${clean}")"
name="${clean##*/}"
case "${name}" in
.*) continue ;;
esac
@@ -14,6 +14,30 @@ import os
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
def configure_utf8_stdio() -> None:
"""Make catchup output deterministic on Windows legacy code pages.
Codex sessions and planning files are UTF-8 and can contain arbitrary
Unicode. Windows PowerShell may nevertheless launch Python with a cp1252
(or another OEM/ANSI) stdout codec. A report containing Chinese text then
used to fail at the first ``print`` with ``UnicodeEncodeError``. Configure
both streams before any report is emitted; ``errors='replace'`` also keeps
this advisory hook fail-safe if a malformed surrogate reaches the output.
"""
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, 'reconfigure', None)
if callable(reconfigure):
try:
reconfigure(encoding='utf-8', errors='replace')
except (OSError, ValueError):
# Replaced/captured streams may not permit reconfiguration.
# The hook remains advisory, so retain the existing stream.
pass
configure_utf8_stdio()
try:
import orjson
except ImportError:
@@ -1,8 +1,8 @@
# planning-with-files: set or display the active plan pointer (PowerShell).
#
# Usage:
# .\set-active-plan.ps1 <plan_id> — pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 — print the current active plan (if any)
# .\set-active-plan.ps1 <plan_id> - pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 - print the current active plan (if any)
param(
[string]$PlanId = ""
@@ -19,7 +19,7 @@ if ($PlanId -eq "") {
Write-Output "Active plan: $current"
Write-Output "Path: $planDir"
} elseif ($current -ne "") {
Write-Output "Active plan pointer: $current (directory not found — stale pointer)"
Write-Output "Active plan pointer: $current (directory not found - stale pointer)"
} else {
Write-Output "No active plan set."
}
@@ -41,7 +41,8 @@ if (-not (Test-Path $PlanRoot)) {
New-Item -ItemType Directory -Path $PlanRoot -Force | Out-Null
}
Set-Content -Path $ActiveFile -Value $PlanId -Encoding UTF8 -NoNewline
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($ActiveFile, $PlanId, $utf8NoBom)
Write-Output "Active plan set to: $PlanId"
Write-Output "Path: $PlanDir"
@@ -0,0 +1,85 @@
# Findings & Decisions
<!--
WHAT: Knowledge base for your analytics session. Stores data sources, hypotheses, and results.
WHY: Context windows are limited. This file is your "external memory" for analytical work.
WHEN: Update after ANY discovery, especially after running queries or viewing charts.
-->
## Data Sources
<!--
WHAT: Every data source you connected to, with schema details and quality notes.
WHY: Knowing where your data came from and its limitations is critical for reproducibility.
EXAMPLE:
| user_events | PostgreSQL prod replica | 2.3M rows | user_id, event_type, ts | 0.2% null user_id |
| revenue.csv | Finance team export | 45K rows | account_id, mrr, churn_date | Complete, no nulls |
-->
| Source | Location | Size | Key Fields | Quality Notes |
|--------|----------|------|------------|---------------|
| | | | | |
## Hypothesis Log
<!--
WHAT: Each hypothesis you tested, the method used, and the result.
WHY: Structured tracking prevents p-hacking and makes your reasoning auditable.
EXAMPLE:
| H1: Churn > 50% for low-activity users | Chi-squared test | Confirmed (p=0.003) | High |
| H2: Feature X correlates with retention | Pearson correlation | Rejected (r=0.08) | High |
-->
| Hypothesis | Test Method | Result | Confidence |
|------------|-------------|--------|------------|
| | | | |
## Query Results
<!--
WHAT: Key queries you ran and what they revealed.
WHY: Queries are ephemeral - if you don't write down the results, they're lost on context reset.
WHEN: After EVERY significant query. Don't wait.
EXAMPLE:
### Churn rate by activity segment
Query: SELECT activity_bucket, COUNT(*), AVG(churned) FROM user_segments GROUP BY 1
Result: Low activity: 62% churn, Medium: 28%, High: 8%
Interpretation: Strong inverse relationship between activity and churn
-->
<!-- Record query, result summary, and interpretation for each significant query -->
## Statistical Findings
<!--
WHAT: Formal statistical test results with all relevant metrics.
WHY: Recording p-values, effect sizes, and confidence intervals makes results reproducible.
EXAMPLE:
| Chi-squared (churn ~ activity) | p=0.003 | Cramer's V=0.31 | Reject null: activity segments differ significantly in churn |
| Pearson (feature_x ~ retention) | p=0.42 | r=0.08 | Fail to reject: no meaningful correlation |
-->
| Test | p-value | Effect Size | Conclusion |
|------|---------|-------------|------------|
| | | | |
## Technical Decisions
<!--
WHAT: Analytical method choices with reasoning.
EXAMPLE:
| Use log transform on revenue | Right-skewed distribution, normalizes for parametric tests |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| | |
## Resources
<!-- URLs, file paths, documentation links -->
-
## Visual/Browser Findings
<!--
CRITICAL: Update after viewing charts, dashboards, or browser results.
Multimodal content doesn't persist in context - capture as text immediately.
-->
-
---
*Update this file after every 2 view/browser/search operations*
*This prevents visual information from being lost*
@@ -0,0 +1,106 @@
# Task Plan: [Analytics Project Description]
<!--
WHAT: Roadmap for a data analytics or exploration session.
WHY: Analytics workflows have different phases than software development — hypothesis testing,
data quality checks, and statistical validation don't map to a generic build cycle.
WHEN: Create this FIRST before starting any data exploration. Update after each phase.
-->
## Goal
<!--
WHAT: One clear sentence describing what you're trying to learn or produce.
EXAMPLE: "Determine which user segments have the highest churn risk using last 90 days of activity data."
-->
[One sentence describing the analytical objective]
## Current Phase
<!--
WHAT: Which phase you're currently working on (e.g., "Phase 1", "Phase 3").
WHY: Quick reference for where you are. Update this as you progress.
-->
Phase 1
## Phases
### Phase 1: Data Discovery
<!--
WHAT: Connect to data sources, understand schemas, assess data quality.
WHY: Bad data produces bad analysis. This phase prevents wasted effort on unreliable inputs.
-->
- [ ] Identify and connect to data sources
- [ ] Document schemas and field descriptions in findings.md
- [ ] Assess data quality (nulls, duplicates, outliers, date ranges)
- [ ] Estimate dataset size and query performance
- **Status:** in_progress
### Phase 2: Exploratory Analysis
<!--
WHAT: Distributions, correlations, outliers, initial patterns.
WHY: Understanding the shape of your data before testing hypotheses prevents false conclusions.
-->
- [ ] Compute summary statistics for key variables
- [ ] Visualize distributions and relationships
- [ ] Identify outliers and anomalies
- [ ] Document initial patterns in findings.md
- **Status:** pending
### Phase 3: Hypothesis Testing
<!--
WHAT: Formalize hypotheses, run statistical tests, validate findings.
WHY: Moving from "it looks like X" to "we can confidently say X" requires structured testing.
-->
- [ ] Formalize hypotheses from exploratory phase
- [ ] Select appropriate statistical tests
- [ ] Run tests and record results in findings.md
- [ ] Validate findings against holdout data or alternative methods
- **Status:** pending
### Phase 4: Synthesis & Reporting
<!--
WHAT: Summarize findings, create visualizations, document conclusions.
WHY: Analysis without clear communication is wasted work. This phase produces the deliverable.
-->
- [ ] Summarize key findings with supporting evidence
- [ ] Create final visualizations
- [ ] Document conclusions and recommendations
- [ ] Note limitations and areas for further investigation
- **Status:** pending
## Hypotheses
<!--
WHAT: Questions you're investigating, stated as testable hypotheses.
WHY: Explicit hypotheses prevent fishing expeditions and keep analysis focused.
EXAMPLE:
1. Users who logged in < 3 times in the last 30 days have > 50% churn rate (H1)
2. Feature X adoption correlates with retention (r > 0.3) (H2)
-->
1. [Hypothesis to test]
2. [Hypothesis to test]
## Decisions Made
<!--
WHAT: Analytical decisions with reasoning (e.g., choosing a test, filtering criteria).
EXAMPLE:
| Use median instead of mean | Revenue data is heavily right-skewed |
| Filter to last 90 days | Earlier data uses a different tracking schema |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Errors Encountered
<!--
WHAT: Every error you encounter, what attempt number it was, and how you resolved it.
EXAMPLE:
| Query timeout on raw table | 1 | Added date partition filter |
| Null join keys in user_events | 2 | Inner join instead of left join, documented data loss |
-->
| Error | Attempt | Resolution |
|-------|---------|------------|
| | 1 | |
## Notes
- Update phase status as you progress: pending -> in_progress -> complete
- Re-read this plan before major analytical decisions
- Log ALL errors - they help avoid repetition
- Write query results and visual findings to findings.md immediately
+7
View File
@@ -35,6 +35,13 @@ fi
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: no "### Phase" headings -> not phase-structured. Emit empty JSON
# instead of a false "0/0 phases done" additionalContext.
if [ "$TOTAL" -eq 0 ]; then
echo '{}'
exit 0
fi
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
MSG="[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"AgentStop\",\"additionalContext\":\"$MSG\"}}"
+64
View File
@@ -0,0 +1,64 @@
# Tests: runs the pytest suite on Linux and Windows plus the Pi extension
# vitest suite on every PR and push to master.
# The pytest suite also runs scripts/sync-ide-folders.py --verify via
# tests/test_canonical_script_sync.py, so cross-copy parity is covered.
name: Tests
on:
push:
branches: [master]
pull_request:
branches: [master]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
pytest:
name: pytest (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install test dependencies
# pyyaml is optional at test time; without it the YAML frontmatter
# test in tests/test_skill_frontmatter_valid.py silently skips.
run: python -m pip install pytest pyyaml
- name: Assert sh is available
# Many hook tests guard on shutil.which("sh") and skip when absent.
# Fail loudly instead of silently losing coverage (Git Bash provides
# sh.exe on windows-latest).
run: python -c "import shutil, sys; sys.exit(0 if shutil.which('sh') else 1)"
- name: Run pytest
run: python -m pytest tests/ -q
vitest:
name: vitest (Pi extension)
runs-on: ubuntu-latest
timeout-minutes: 10
defaults:
run:
working-directory: .pi/skills/planning-with-files/extensions/planning-with-files
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: .pi/skills/planning-with-files/extensions/planning-with-files/package-lock.json
- name: Install dependencies
run: npm ci --no-audit --no-fund
- name: Run vitest
run: npm test
+2 -2
View File
@@ -1,8 +1,8 @@
---
name: planning-with-files
description: Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Hermes adaptation with minimal notes.
description: "Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Hermes adaptation with minimal notes."
metadata:
version: "3.1.2"
version: "3.6.0"
---
> Hermes note: lifecycle automation for this skill is provided by the Hermes adapter plugin in `.hermes/plugins/planning-with-files/`.
@@ -6,6 +6,10 @@ param(
[string]$PlanFile = "task_plan.md"
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if (-not (Test-Path $PlanFile)) {
Write-Host '[planning-with-files] No task_plan.md found -- no active planning session.'
exit 0
@@ -3,6 +3,10 @@
# Always exits 0 — uses stdout for status reporting
# Used by Stop hook to report task completion status
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
PLAN_FILE="${1:-task_plan.md}"
if [ ! -f "$PLAN_FILE" ]; then
@@ -5,6 +5,10 @@ param(
[string]$PlanFile = ".kiro/plan/task_plan.md"
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if (-not (Test-Path $PlanFile)) {
Write-Host "[planning-with-files] No task plan at $PlanFile — run bootstrap or specify path."
exit 0
@@ -2,6 +2,10 @@
# Report phase completion status for task_plan.md (stdout only, always exit 0).
# Default plan path: .kiro/plan/task_plan.md (relative to current directory).
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
PLAN_FILE="${1:-.kiro/plan/task_plan.md}"
if [ ! -f "$PLAN_FILE" ]; then
@@ -25,6 +29,11 @@ fi
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: TOTAL=0 -> not phase-structured, stay silent
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL)."
else
@@ -1,6 +1,6 @@
---
name: planning-with-files
description: Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear.
description: "Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear."
user-invocable: true
allowed-tools: "Read Write Edit Bash Glob Grep"
hooks:
@@ -28,7 +28,7 @@ hooks:
- type: command
command: "RESOLVED=\"\"; SCOPE=\"\"; SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'; if [ -n \"${PLAN_ID:-}\" ] && printf \"%s\" \"$PLAN_ID\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${PLAN_ID}\" ]; then RESOLVED=\".planning/${PLAN_ID}\"; SCOPE=\"scoped\"; elif [ -f .planning/.active_plan ]; then AP=$(tr -d '\\r\\n[:space:]' < .planning/.active_plan 2>/dev/null); if [ -n \"$AP\" ] && printf \"%s\" \"$AP\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${AP}\" ]; then RESOLVED=\".planning/${AP}\"; SCOPE=\"scoped\"; fi; fi; if [ -z \"$RESOLVED\" ] && [ -d .planning ]; then NEWEST=\"\"; NEWEST_MT=0; for d in .planning/*/; do d=\"${d%/}\"; n=$(basename \"$d\"); case \"$n\" in .*) continue;; esac; printf \"%s\" \"$n\" | grep -Eq \"$SLUG_RE\" || continue; [ -f \"$d/task_plan.md\" ] || continue; m=$(stat -c '%Y' \"$d\" 2>/dev/null || stat -f '%m' \"$d\" 2>/dev/null || date -r \"$d\" +%s 2>/dev/null || echo 0); if [ \"$m\" -gt \"$NEWEST_MT\" ] 2>/dev/null; then NEWEST_MT=\"$m\"; NEWEST=\"$d\"; fi; done; [ -n \"$NEWEST\" ] && { RESOLVED=\"$NEWEST\"; SCOPE=\"scoped\"; }; fi; if [ -z \"$RESOLVED\" ] && [ -f task_plan.md ]; then RESOLVED=\".\"; SCOPE=\"root\"; fi; [ -z \"$RESOLVED\" ] && exit 0; if [ \"$SCOPE\" = \"root\" ]; then PLAN_FILE=\"task_plan.md\"; PROGRESS_FILE=\"progress.md\"; ATTEST=\"\"; [ -f .plan-attestation ] && ATTEST=$(tr -d '\\r\\n[:space:]' < .plan-attestation 2>/dev/null); else PLAN_FILE=\"${RESOLVED}/task_plan.md\"; PROGRESS_FILE=\"${RESOLVED}/progress.md\"; ATTEST=\"\"; [ -f \"${RESOLVED}/.attestation\" ] && ATTEST=$(tr -d '\\r\\n[:space:]' < \"${RESOLVED}/.attestation\" 2>/dev/null); fi; [ -f \"$PLAN_FILE\" ] || exit 0; TAMPERED=0; ACTUAL=\"\"; if [ -n \"$ATTEST\" ]; then CD=\"${TMPDIR:-/tmp}/pwf-sha\"; mkdir -p \"$CD\" 2>/dev/null; KEY=$(printf \"%s\" \"$PLAN_FILE\" | { sha256sum 2>/dev/null || shasum -a 256 2>/dev/null; } | awk '{print $1}' | cut -c1-16); MT=$(stat -c '%Y' \"$PLAN_FILE\" 2>/dev/null || stat -f '%m' \"$PLAN_FILE\" 2>/dev/null || date -r \"$PLAN_FILE\" +%s 2>/dev/null || echo 0); CF=\"$CD/$KEY\"; CM=\"\"; CS=\"\"; if [ -f \"$CF\" ]; then CM=$(sed -n 1p \"$CF\" 2>/dev/null); CS=$(sed -n 2p \"$CF\" 2>/dev/null); fi; if [ -n \"$MT\" ] && [ \"$MT\" = \"$CM\" ] && [ -n \"$CS\" ]; then ACTUAL=\"$CS\"; else ACTUAL=$( (sha256sum \"$PLAN_FILE\" 2>/dev/null || shasum -a 256 \"$PLAN_FILE\" 2>/dev/null) | awk '{print $1}'); [ -n \"$ACTUAL\" ] && [ -n \"$MT\" ] && printf \"%s\\n%s\\n\" \"$MT\" \"$ACTUAL\" > \"$CF\" 2>/dev/null; fi; [ \"$ACTUAL\" != \"$ATTEST\" ] && TAMPERED=1; fi; echo '[planning-with-files] PreCompact: context compaction is about to occur.'; echo 'Before compaction completes: ensure progress.md captures recent actions and task_plan.md status reflects current phase.'; echo 'task_plan.md, findings.md, progress.md remain on disk and will be re-read after compaction.'; [ -n \"$ATTEST\" ] && echo \"Plan-SHA256 at compaction: $ATTEST\"; exit 0"
metadata:
version: "3.1.2"
version: "3.6.0"
---
@@ -178,7 +178,7 @@ Manus operates in a continuous 7-step loop:
## Critical Constraints
- **Single-Action Execution:** ONE tool call per turn. No parallel execution.
- **Single-Action Execution (Manus 2025 original constraint):** ONE tool call per turn, no parallel execution. This documents Manus's 2025 sandbox practice. **2026 update:** modern hosts (Claude Code, Codex CLI) support parallel tool calls and subagents, so this constraint no longer applies as written. The plan file, not the one-call-per-turn rule, remains the coordination point: parallel calls and subagents share state through the durable markdown plan on disk.
- **Plan is Required:** Agent must ALWAYS know: goal, current phase, remaining phases
- **Files are Memory:** Context = volatile. Filesystem = persistent.
- **Never Repeat Failures:** If action failed, next action MUST be different
@@ -6,6 +6,10 @@ param(
[string]$PlanFile = "task_plan.md"
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if (-not (Test-Path $PlanFile)) {
Write-Host '[planning-with-files] No task_plan.md found -- no active planning session.'
exit 0
@@ -3,6 +3,10 @@
# Always exits 0 — uses stdout for status reporting
# Used by Stop hook to report task completion status
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
PLAN_FILE="${1:-task_plan.md}"
if [ ! -f "$PLAN_FILE" ]; then
@@ -1,6 +1,6 @@
---
name: planning-with-files
description: Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear.
description: "Manus-style persistent file-based planning for AI coding agents: keeps task_plan.md, findings.md, and progress.md on disk so work survives context loss and /clear. Use when asked to plan out, break down, or organize a multi-step project, research task, or any work requiring 5+ tool calls. Supports automatic session recovery after /clear."
user-invocable: true
allowed-tools: "Read Write Edit Bash Glob Grep"
hooks:
@@ -28,7 +28,7 @@ hooks:
- type: command
command: "RESOLVED=\"\"; SCOPE=\"\"; SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'; if [ -n \"${PLAN_ID:-}\" ] && printf \"%s\" \"$PLAN_ID\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${PLAN_ID}\" ]; then RESOLVED=\".planning/${PLAN_ID}\"; SCOPE=\"scoped\"; elif [ -f .planning/.active_plan ]; then AP=$(tr -d '\\r\\n[:space:]' < .planning/.active_plan 2>/dev/null); if [ -n \"$AP\" ] && printf \"%s\" \"$AP\" | grep -Eq \"$SLUG_RE\" && [ -d \".planning/${AP}\" ]; then RESOLVED=\".planning/${AP}\"; SCOPE=\"scoped\"; fi; fi; if [ -z \"$RESOLVED\" ] && [ -d .planning ]; then NEWEST=\"\"; NEWEST_MT=0; for d in .planning/*/; do d=\"${d%/}\"; n=$(basename \"$d\"); case \"$n\" in .*) continue;; esac; printf \"%s\" \"$n\" | grep -Eq \"$SLUG_RE\" || continue; [ -f \"$d/task_plan.md\" ] || continue; m=$(stat -c '%Y' \"$d\" 2>/dev/null || stat -f '%m' \"$d\" 2>/dev/null || date -r \"$d\" +%s 2>/dev/null || echo 0); if [ \"$m\" -gt \"$NEWEST_MT\" ] 2>/dev/null; then NEWEST_MT=\"$m\"; NEWEST=\"$d\"; fi; done; [ -n \"$NEWEST\" ] && { RESOLVED=\"$NEWEST\"; SCOPE=\"scoped\"; }; fi; if [ -z \"$RESOLVED\" ] && [ -f task_plan.md ]; then RESOLVED=\".\"; SCOPE=\"root\"; fi; [ -z \"$RESOLVED\" ] && exit 0; if [ \"$SCOPE\" = \"root\" ]; then PLAN_FILE=\"task_plan.md\"; PROGRESS_FILE=\"progress.md\"; ATTEST=\"\"; [ -f .plan-attestation ] && ATTEST=$(tr -d '\\r\\n[:space:]' < .plan-attestation 2>/dev/null); else PLAN_FILE=\"${RESOLVED}/task_plan.md\"; PROGRESS_FILE=\"${RESOLVED}/progress.md\"; ATTEST=\"\"; [ -f \"${RESOLVED}/.attestation\" ] && ATTEST=$(tr -d '\\r\\n[:space:]' < \"${RESOLVED}/.attestation\" 2>/dev/null); fi; [ -f \"$PLAN_FILE\" ] || exit 0; TAMPERED=0; ACTUAL=\"\"; if [ -n \"$ATTEST\" ]; then CD=\"${TMPDIR:-/tmp}/pwf-sha\"; mkdir -p \"$CD\" 2>/dev/null; KEY=$(printf \"%s\" \"$PLAN_FILE\" | { sha256sum 2>/dev/null || shasum -a 256 2>/dev/null; } | awk '{print $1}' | cut -c1-16); MT=$(stat -c '%Y' \"$PLAN_FILE\" 2>/dev/null || stat -f '%m' \"$PLAN_FILE\" 2>/dev/null || date -r \"$PLAN_FILE\" +%s 2>/dev/null || echo 0); CF=\"$CD/$KEY\"; CM=\"\"; CS=\"\"; if [ -f \"$CF\" ]; then CM=$(sed -n 1p \"$CF\" 2>/dev/null); CS=$(sed -n 2p \"$CF\" 2>/dev/null); fi; if [ -n \"$MT\" ] && [ \"$MT\" = \"$CM\" ] && [ -n \"$CS\" ]; then ACTUAL=\"$CS\"; else ACTUAL=$( (sha256sum \"$PLAN_FILE\" 2>/dev/null || shasum -a 256 \"$PLAN_FILE\" 2>/dev/null) | awk '{print $1}'); [ -n \"$ACTUAL\" ] && [ -n \"$MT\" ] && printf \"%s\\n%s\\n\" \"$MT\" \"$ACTUAL\" > \"$CF\" 2>/dev/null; fi; [ \"$ACTUAL\" != \"$ATTEST\" ] && TAMPERED=1; fi; echo '[planning-with-files] PreCompact: context compaction is about to occur.'; echo 'Before compaction completes: ensure progress.md captures recent actions and task_plan.md status reflects current phase.'; echo 'task_plan.md, findings.md, progress.md remain on disk and will be re-read after compaction.'; [ -n \"$ATTEST\" ] && echo \"Plan-SHA256 at compaction: $ATTEST\"; exit 0"
metadata:
version: "3.1.2"
version: "3.6.0"
---
@@ -6,6 +6,10 @@ param(
[string]$PlanFile = "task_plan.md"
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if (-not (Test-Path $PlanFile)) {
Write-Host '[planning-with-files] No task_plan.md found -- no active planning session.'
exit 0
@@ -3,6 +3,10 @@
# Always exits 0 — uses stdout for status reporting
# Used by Stop hook to report task completion status
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
PLAN_FILE="${1:-task_plan.md}"
if [ ! -f "$PLAN_FILE" ]; then
@@ -0,0 +1,85 @@
# Findings & Decisions
<!--
WHAT: Knowledge base for your analytics session. Stores data sources, hypotheses, and results.
WHY: Context windows are limited. This file is your "external memory" for analytical work.
WHEN: Update after ANY discovery, especially after running queries or viewing charts.
-->
## Data Sources
<!--
WHAT: Every data source you connected to, with schema details and quality notes.
WHY: Knowing where your data came from and its limitations is critical for reproducibility.
EXAMPLE:
| user_events | PostgreSQL prod replica | 2.3M rows | user_id, event_type, ts | 0.2% null user_id |
| revenue.csv | Finance team export | 45K rows | account_id, mrr, churn_date | Complete, no nulls |
-->
| Source | Location | Size | Key Fields | Quality Notes |
|--------|----------|------|------------|---------------|
| | | | | |
## Hypothesis Log
<!--
WHAT: Each hypothesis you tested, the method used, and the result.
WHY: Structured tracking prevents p-hacking and makes your reasoning auditable.
EXAMPLE:
| H1: Churn > 50% for low-activity users | Chi-squared test | Confirmed (p=0.003) | High |
| H2: Feature X correlates with retention | Pearson correlation | Rejected (r=0.08) | High |
-->
| Hypothesis | Test Method | Result | Confidence |
|------------|-------------|--------|------------|
| | | | |
## Query Results
<!--
WHAT: Key queries you ran and what they revealed.
WHY: Queries are ephemeral - if you don't write down the results, they're lost on context reset.
WHEN: After EVERY significant query. Don't wait.
EXAMPLE:
### Churn rate by activity segment
Query: SELECT activity_bucket, COUNT(*), AVG(churned) FROM user_segments GROUP BY 1
Result: Low activity: 62% churn, Medium: 28%, High: 8%
Interpretation: Strong inverse relationship between activity and churn
-->
<!-- Record query, result summary, and interpretation for each significant query -->
## Statistical Findings
<!--
WHAT: Formal statistical test results with all relevant metrics.
WHY: Recording p-values, effect sizes, and confidence intervals makes results reproducible.
EXAMPLE:
| Chi-squared (churn ~ activity) | p=0.003 | Cramer's V=0.31 | Reject null: activity segments differ significantly in churn |
| Pearson (feature_x ~ retention) | p=0.42 | r=0.08 | Fail to reject: no meaningful correlation |
-->
| Test | p-value | Effect Size | Conclusion |
|------|---------|-------------|------------|
| | | | |
## Technical Decisions
<!--
WHAT: Analytical method choices with reasoning.
EXAMPLE:
| Use log transform on revenue | Right-skewed distribution, normalizes for parametric tests |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| | |
## Resources
<!-- URLs, file paths, documentation links -->
-
## Visual/Browser Findings
<!--
CRITICAL: Update after viewing charts, dashboards, or browser results.
Multimodal content doesn't persist in context - capture as text immediately.
-->
-
---
*Update this file after every 2 view/browser/search operations*
*This prevents visual information from being lost*
@@ -0,0 +1,106 @@
# Task Plan: [Analytics Project Description]
<!--
WHAT: Roadmap for a data analytics or exploration session.
WHY: Analytics workflows have different phases than software development — hypothesis testing,
data quality checks, and statistical validation don't map to a generic build cycle.
WHEN: Create this FIRST before starting any data exploration. Update after each phase.
-->
## Goal
<!--
WHAT: One clear sentence describing what you're trying to learn or produce.
EXAMPLE: "Determine which user segments have the highest churn risk using last 90 days of activity data."
-->
[One sentence describing the analytical objective]
## Current Phase
<!--
WHAT: Which phase you're currently working on (e.g., "Phase 1", "Phase 3").
WHY: Quick reference for where you are. Update this as you progress.
-->
Phase 1
## Phases
### Phase 1: Data Discovery
<!--
WHAT: Connect to data sources, understand schemas, assess data quality.
WHY: Bad data produces bad analysis. This phase prevents wasted effort on unreliable inputs.
-->
- [ ] Identify and connect to data sources
- [ ] Document schemas and field descriptions in findings.md
- [ ] Assess data quality (nulls, duplicates, outliers, date ranges)
- [ ] Estimate dataset size and query performance
- **Status:** in_progress
### Phase 2: Exploratory Analysis
<!--
WHAT: Distributions, correlations, outliers, initial patterns.
WHY: Understanding the shape of your data before testing hypotheses prevents false conclusions.
-->
- [ ] Compute summary statistics for key variables
- [ ] Visualize distributions and relationships
- [ ] Identify outliers and anomalies
- [ ] Document initial patterns in findings.md
- **Status:** pending
### Phase 3: Hypothesis Testing
<!--
WHAT: Formalize hypotheses, run statistical tests, validate findings.
WHY: Moving from "it looks like X" to "we can confidently say X" requires structured testing.
-->
- [ ] Formalize hypotheses from exploratory phase
- [ ] Select appropriate statistical tests
- [ ] Run tests and record results in findings.md
- [ ] Validate findings against holdout data or alternative methods
- **Status:** pending
### Phase 4: Synthesis & Reporting
<!--
WHAT: Summarize findings, create visualizations, document conclusions.
WHY: Analysis without clear communication is wasted work. This phase produces the deliverable.
-->
- [ ] Summarize key findings with supporting evidence
- [ ] Create final visualizations
- [ ] Document conclusions and recommendations
- [ ] Note limitations and areas for further investigation
- **Status:** pending
## Hypotheses
<!--
WHAT: Questions you're investigating, stated as testable hypotheses.
WHY: Explicit hypotheses prevent fishing expeditions and keep analysis focused.
EXAMPLE:
1. Users who logged in < 3 times in the last 30 days have > 50% churn rate (H1)
2. Feature X adoption correlates with retention (r > 0.3) (H2)
-->
1. [Hypothesis to test]
2. [Hypothesis to test]
## Decisions Made
<!--
WHAT: Analytical decisions with reasoning (e.g., choosing a test, filtering criteria).
EXAMPLE:
| Use median instead of mean | Revenue data is heavily right-skewed |
| Filter to last 90 days | Earlier data uses a different tracking schema |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Errors Encountered
<!--
WHAT: Every error you encounter, what attempt number it was, and how you resolved it.
EXAMPLE:
| Query timeout on raw table | 1 | Added date partition filter |
| Null join keys in user_events | 2 | Inner join instead of left join, documented data loss |
-->
| Error | Attempt | Resolution |
|-------|---------|------------|
| | 1 | |
## Notes
- Update phase status as you progress: pending -> in_progress -> complete
- Re-read this plan before major analytical decisions
- Log ALL errors - they help avoid repetition
- Write query results and visual findings to findings.md immediately
+12 -4
View File
@@ -53,10 +53,11 @@ Or:
The bundled extension maps Claude-style behavior onto Pi events:
- `session_start` - session catchup
- `before_agent_start` - plan reminder/injection
- `tool_call` - pre-tool recitation equivalent
- `tool_result` - post-write reminder
- `agent_end` - incomplete-task auto-continue (limit 3)
- passive plan status before approval
- `before_agent_start` - plan reminder/injection after `/plan-execute`
- `tool_call` - pre-tool recitation equivalent after `/plan-execute`
- `tool_result` - post-write reminder after `/plan-execute`
- `agent_end` - incomplete-task auto-continue after `/plan-execute` (limit 3)
- `session_before_compact` - pre-compaction reminder
Attestation is supported. If `task_plan.md` differs from approved hash, plan injection is blocked with:
@@ -98,9 +99,16 @@ Or settings:
- `/plan-status`
- `/plan-attest [--show|--clear]`
- `/plan-execute`
- `/plan-execute reset`
- `/plan-goal <text|default|clear>`
- `/plan-loop [interval] [prompt]` (`stop` to cancel)
Draft and review `task_plan.md` first. The extension stays passive until you
approve the active plan with `/plan-execute`; after that, plan injection,
pre-tool reminders, post-write reminders, and auto-continue are enabled for the
current session and plan.
---
## Session Recovery
+5 -2
View File
@@ -37,8 +37,9 @@ Before ANY complex task:
1. **Create `task_plan.md`** — Use [templates/task_plan.md](templates/task_plan.md) as reference
2. **Create `findings.md`** — Use [templates/findings.md](templates/findings.md) as reference
3. **Create `progress.md`** — Use [templates/progress.md](templates/progress.md) as reference
4. **Re-read plan before decisions** — Refreshes goals in attention window
5. **Update after each phase** — Mark complete, log errors
4. **Wait for approval before execution** — In Pi, hooks stay passive until the user runs `/plan-execute`
5. **Re-read plan before decisions** — Refreshes goals in attention window
6. **Update after each phase** — Mark complete, log errors
> **Note:** Planning files go in your project root, not the skill installation folder.
@@ -217,6 +218,8 @@ Modes:
Commands:
- `/plan-status`
- `/plan-attest [--show|--clear]`
- `/plan-execute` (approve the active plan and enable hook activation)
- `/plan-execute reset` (return the active plan to passive review mode)
- `/plan-goal <text|default|clear>`
- `/plan-loop [interval] [prompt]` (use `stop` to cancel)
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
@@ -88,6 +88,10 @@ function completePlan(): string {
].join("\n");
}
function closedIncompletePlan(): string {
return incompletePlan() + "\n<!-- pwf: closed -->\n";
}
function attestPlan(cwd: string, content: string): void {
writeFileSync(join(cwd, ".planning", "demo", ".attestation"), sha256(content));
}
@@ -144,6 +148,16 @@ async function emit(pi: MockPi, eventName: string, event: any, ctx: MockContext)
return handler?.(event, ctx);
}
async function runCommand(pi: MockPi, name: string, args: string, ctx: MockContext): Promise<void> {
const command = pi.commands.get(name);
expect(command, `missing command: ${name}`).toBeDefined();
await command?.handler(args, ctx);
}
async function approvePlan(pi: MockPi, ctx: MockContext): Promise<void> {
await runCommand(pi, "plan-execute", "", ctx);
}
beforeEach(() => {
originalEnv = { ...process.env };
process.env.PWF_MODE = "parity";
@@ -176,6 +190,12 @@ describe("Pi extension runtime handlers", () => {
]);
});
it("registers plan-execute command for explicit hook activation", () => {
const pi = loadExtension();
expect(Array.from(pi.commands.keys()).sort()).toContain("plan-execute");
});
it("session_start initializes visible plan state for an attached plan directory", async () => {
const cwd = makeWorkspace();
const pi = loadExtension();
@@ -183,7 +203,24 @@ describe("Pi extension runtime handlers", () => {
await emit(pi, "session_start", { reason: "resume" }, ctx);
expect(ctx.ui.setStatus).toHaveBeenCalledWith("planning-with-files", "1/2 phases complete");
expect(ctx.ui.setStatus).toHaveBeenCalledWith(
"planning-with-files",
"1/2 phases complete — run /plan-execute to activate hooks",
);
});
it("before_agent_start stays passive before plan-execute approval", async () => {
const cwd = makeWorkspace();
const pi = loadExtension();
const ctx = createContext(cwd);
const result = await emit(pi, "before_agent_start", {}, ctx);
expect(result).toBeUndefined();
expect(ctx.ui.setStatus).toHaveBeenCalledWith(
"planning-with-files",
"1/2 phases complete — run /plan-execute to activate hooks",
);
});
it("before_agent_start injects canonical skill content when attestation matches", async () => {
@@ -193,6 +230,7 @@ describe("Pi extension runtime handlers", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
const result = await emit(pi, "before_agent_start", {}, ctx);
expect(result.message).toMatchObject({
@@ -211,11 +249,11 @@ describe("Pi extension runtime handlers", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await runCommand(pi, "plan-execute", "", ctx);
const result = await emit(pi, "before_agent_start", {}, ctx);
expect(result.message.content).toContain("[PLAN TAMPERED");
expect(result.message.content).toContain("injection blocked");
expect(result.message.display).toBe(true);
expect(ctx.ui.notify).toHaveBeenCalledWith(expect.stringContaining("[PLAN TAMPERED"), "error");
expect(result).toBeUndefined();
});
it("tool_call records a pre-tool reminder against the active leaf", async () => {
@@ -223,6 +261,7 @@ describe("Pi extension runtime handlers", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
await emit(pi, "tool_call", { toolName: "write", input: {} }, ctx);
await emit(pi, "tool_call", { toolName: "write", input: {} }, ctx);
@@ -232,7 +271,7 @@ describe("Pi extension runtime handlers", () => {
content: expect.stringContaining("PreToolUse recitation"),
display: false,
}),
{ deliverAs: "steer", triggerTurn: false },
{ deliverAs: "nextTurn", triggerTurn: false },
);
});
@@ -241,6 +280,7 @@ describe("Pi extension runtime handlers", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
const result = await emit(
pi,
"tool_result",
@@ -257,6 +297,20 @@ describe("Pi extension runtime handlers", () => {
]);
});
it("agent_end does not auto-continue before plan-execute approval", async () => {
const cwd = makeWorkspace();
const pi = loadExtension();
const ctx = createContext(cwd);
await emit(pi, "agent_end", {}, ctx);
expect(pi.sendUserMessage).not.toHaveBeenCalled();
expect(ctx.ui.notify).toHaveBeenCalledWith(
"[planning-with-files] Task incomplete (1/2). Run /plan-execute to activate hooks.",
"warning",
);
});
it("agent_end flushes final complete-plan state without scheduling a follow-up", async () => {
const cwd = makeWorkspace(completePlan());
const pi = loadExtension();
@@ -278,6 +332,7 @@ describe("Pi extension runtime handlers", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
await emit(pi, "session_before_compact", {}, ctx);
expect(ctx.ui.notify).toHaveBeenCalledWith(
@@ -298,8 +353,10 @@ describe("Pi extension runtime handlers", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
await emit(pi, "tool_call", { toolName: "write", input: {} }, ctx);
await emit(pi, "session_shutdown", {}, ctx);
await approvePlan(pi, ctx);
await emit(pi, "tool_call", { toolName: "write", input: {} }, ctx);
expect(pi.sendMessage).toHaveBeenCalledTimes(2);
@@ -310,6 +367,7 @@ describe("Pi extension runtime handlers", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
await emit(pi, "tool_call", { toolName: "write", input: {} }, ctx);
await emit(pi, "input", { source: "extension", text: "internal" }, ctx);
await emit(pi, "tool_call", { toolName: "write", input: {} }, ctx);
@@ -318,6 +376,41 @@ describe("Pi extension runtime handlers", () => {
expect(pi.sendMessage).toHaveBeenCalledTimes(2);
});
it("agent_end sends no follow-up when the plan is closed even if incomplete", async () => {
const cwd = makeWorkspace(closedIncompletePlan());
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
await emit(pi, "agent_end", {}, ctx);
expect(pi.sendUserMessage).not.toHaveBeenCalled();
});
it("resolveNewestPlanDir ranks by task_plan.md file mtime, not directory mtime", async () => {
const cwd = makeWorkspace(incompletePlan()); // creates .planning/demo (incomplete)
const donePlanDir = join(cwd, ".planning", "done");
mkdirSync(donePlanDir, { recursive: true });
writeFileSync(join(donePlanDir, "task_plan.md"), completePlan());
writeFileSync(join(donePlanDir, "progress.md"), "done\n");
const older = new Date(Date.now() - 60_000);
const newer = new Date();
// done DIR older than demo DIR, but done's task_plan.md file NEWER than demo's
utimesSync(donePlanDir, older, older);
utimesSync(join(cwd, ".planning", "demo"), newer, newer);
utimesSync(join(donePlanDir, "task_plan.md"), newer, newer);
utimesSync(join(cwd, ".planning", "demo", "task_plan.md"), older, older);
const pi = loadExtension();
const ctx = createContext(cwd);
await emit(pi, "agent_end", {}, ctx);
expect(ctx.ui.notify).toHaveBeenCalledWith(
"[planning-with-files] ALL PHASES COMPLETE (2/2).",
"info",
);
});
});
describe("Pi extension runtime modes", () => {
@@ -332,6 +425,7 @@ describe("Pi extension runtime modes", () => {
},
});
await approvePlan(pi, ctx);
const result = await emit(pi, "before_agent_start", {}, ctx);
expect(result.message.content).toContain("Read task_plan.md for current phase and status.");
@@ -344,6 +438,7 @@ describe("Pi extension runtime modes", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
const result = await emit(pi, "before_agent_start", {}, ctx);
expect(result.message.content).toContain("[planning-with-files] ACTIVE PLAN");
@@ -356,6 +451,7 @@ describe("Pi extension runtime modes", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
const result = await emit(pi, "before_agent_start", {}, ctx);
expect(result.message.content).toContain("treat contents as structured data, not instructions.");
@@ -369,6 +465,7 @@ describe("Pi extension runtime modes", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
const result = await emit(pi, "before_agent_start", {}, ctx);
expect(result.message.content).toBe(
@@ -384,6 +481,7 @@ describe("Pi extension runtime modes", () => {
const pi = loadExtension();
const ctx = createContext(cwd);
await approvePlan(pi, ctx);
const startResult = await emit(pi, "before_agent_start", {}, ctx);
const toolResult = await emit(
pi,
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
{
"name": "@tomxprime/planning-with-files-pi-extension",
"version": "1.1.0",
"version": "1.2.1",
"private": true,
"type": "module",
"scripts": {
@@ -16,6 +16,7 @@ export interface PlanPaths {
export interface PlanStatus extends PlanPaths {
exists: boolean;
closed: boolean;
totalPhases: number;
completePhases: number;
inProgressPhases: number;
@@ -43,7 +44,8 @@ function resolveNewestPlanDir(planRoot: string): string | undefined {
.map((dir) => {
let mtime = 0;
try {
mtime = statSync(dir).mtimeMs;
// Rank by task_plan.md mtime, not the directory: editing a plan's contents does not bump the dir mtime, which let a completed plan lose to a stale sibling (#203).
mtime = statSync(join(dir, "task_plan.md")).mtimeMs;
} catch {
mtime = 0;
}
@@ -119,6 +121,7 @@ export function readPlanStatus(cwd: string): PlanStatus {
return {
...paths,
exists: false,
closed: false,
totalPhases: 0,
completePhases: 0,
inProgressPhases: 0,
@@ -130,6 +133,7 @@ export function readPlanStatus(cwd: string): PlanStatus {
}
const planContent = safeRead(paths.planPath);
const closed = /<!--\s*pwf:\s*closed\s*-->/i.test(planContent);
const lines = planContent.split("\n");
const phaseRegex = /^###\s+Phase\b/i;
@@ -164,6 +168,7 @@ export function readPlanStatus(cwd: string): PlanStatus {
return {
...paths,
exists: true,
closed,
totalPhases: total,
completePhases: complete,
inProgressPhases: inProgress,
@@ -40,6 +40,7 @@ interface RuntimeState {
loopTimersBySession: Map<string, ReturnType<typeof setInterval>>;
goalBySession: Map<string, string>;
preToolQueuedByLeaf: Set<string>;
executionApprovedBySessionPlan: Set<string>;
}
interface ExecResult {
@@ -120,6 +121,14 @@ function clearSessionPrefixMap(state: RuntimeState, sessionId: string): void {
}
}
function clearSessionExecutionApprovals(state: RuntimeState, sessionId: string): void {
for (const key of state.executionApprovedBySessionPlan.keys()) {
if (key.startsWith(`${sessionId}:`)) {
state.executionApprovedBySessionPlan.delete(key);
}
}
}
function isAttachedSession(ctx: ExtensionContext): boolean {
return isSessionAttached(ctx.cwd, getSessionId(ctx));
}
@@ -257,6 +266,14 @@ function buildPreToolParityRecitation(status: PlanStatus): string {
].join("\n");
}
function isExecutionApproved(state: RuntimeState, ctx: ExtensionContext, status: PlanStatus): boolean {
return state.executionApprovedBySessionPlan.has(getPlanSessionKey(ctx, status));
}
function setPassivePlanStatus(ctx: ExtensionContext, status: PlanStatus): void {
ctx.ui.setStatus(PKG_NAME, `${summarizePlan(status)} — run /plan-execute to activate hooks`);
}
// Word-boundary regex check so legitimate commands like
// `git push origin feature/draft-notification` don't trigger the warning, but
// destructive variants like `git push --force` or `git push --mirror` still do.
@@ -329,6 +346,42 @@ function registerCommands(pi: ExtensionAPI, state: RuntimeState): void {
},
});
pi.registerCommand("plan-execute", {
description: "Approve the active plan and enable planning-with-files hook activation",
handler: async (args, ctx) => {
const status = readPlanStatus(ctx.cwd);
if (!status.exists) {
ctx.ui.notify("No active plan (task_plan.md not found)", "warning");
return;
}
const planKey = getPlanSessionKey(ctx, status);
const normalized = args.trim().toLowerCase();
if (["clear", "off", "reset", "disable"].includes(normalized)) {
state.executionApprovedBySessionPlan.delete(planKey);
ctx.ui.notify(`Plan execution approval cleared: ${summarizePlan(status)}`, "info");
setPassivePlanStatus(ctx, status);
return;
}
const attestation = checkPlanAttestation(status);
if (attestation.tampered) {
ctx.ui.notify(buildTamperMessage(status), "error");
return;
}
state.executionApprovedBySessionPlan.add(planKey);
ctx.ui.notify(
[
`Plan execution approved: ${summarizePlan(status)}`,
`Plan path: ${status.planPath}`,
"planning-with-files hooks are now active for this session and plan.",
].join("\n"),
"info",
);
},
});
pi.registerCommand("plan-loop", {
description: "Start/stop planning loop ticks (default: 10m)",
handler: async (args, ctx: ExtensionCommandContext) => {
@@ -356,7 +409,7 @@ function registerCommands(pi: ExtensionAPI, state: RuntimeState): void {
const status = readPlanStatus(ctx.cwd);
if (!status.exists) return;
if (isAllPhasesComplete(status)) {
if (isAllPhasesComplete(status) || status.closed) {
const active = state.loopTimersBySession.get(sessionId);
if (active) clearInterval(active);
state.loopTimersBySession.delete(sessionId);
@@ -387,6 +440,7 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
loopTimersBySession: new Map(),
goalBySession: new Map(),
preToolQueuedByLeaf: new Set(),
executionApprovedBySessionPlan: new Set(),
};
registerCommands(pi, state);
@@ -394,6 +448,7 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
pi.on("session_start", async (event, ctx) => {
const sessionId = getSessionId(ctx);
clearSessionPrefixMap(state, sessionId);
clearSessionExecutionApprovals(state, sessionId);
if (!isAttachedSession(ctx)) {
ctx.ui.setStatus(PKG_NAME, "session not attached to planning context");
@@ -406,7 +461,7 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
const status = readPlanStatus(ctx.cwd);
if (status.exists) {
ctx.ui.setStatus(PKG_NAME, summarizePlan(status));
setPassivePlanStatus(ctx, status);
}
});
@@ -416,6 +471,7 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
if (timer) clearInterval(timer);
state.loopTimersBySession.delete(sessionId);
clearSessionPrefixMap(state, sessionId);
clearSessionExecutionApprovals(state, sessionId);
});
pi.on("input", async (event, ctx) => {
@@ -429,6 +485,11 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
const status = readPlanStatus(ctx.cwd);
if (!status.exists) return;
if (!isExecutionApproved(state, ctx, status)) {
setPassivePlanStatus(ctx, status);
return;
}
const mode = deriveEffectiveMode(resolveConfiguredMode(ctx.cwd), ctx);
const attestation = checkPlanAttestation(status);
@@ -467,7 +528,12 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
const leafKey = `${sessionId}:${leafId}`;
const trackableTools = new Set(["write", "edit", "bash", "read", "grep", "find", "ls"]);
if (status.exists && trackableTools.has(event.toolName) && !state.preToolQueuedByLeaf.has(leafKey)) {
if (
status.exists &&
isExecutionApproved(state, ctx, status) &&
trackableTools.has(event.toolName) &&
!state.preToolQueuedByLeaf.has(leafKey)
) {
state.preToolQueuedByLeaf.add(leafKey);
const attestation = checkPlanAttestation(status);
if (attestation.tampered) {
@@ -477,7 +543,7 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
content: buildTamperMessage(status),
display: true,
},
{ deliverAs: "steer", triggerTurn: false },
{ deliverAs: "nextTurn", triggerTurn: false },
);
} else if (mode === "parity") {
pi.sendMessage(
@@ -486,7 +552,7 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
content: buildPreToolParityRecitation(status),
display: false,
},
{ deliverAs: "steer", triggerTurn: false },
{ deliverAs: "nextTurn", triggerTurn: false },
);
} else if (mode === "cache-safe") {
pi.sendMessage(
@@ -495,7 +561,7 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
content: PRE_TOOL_CACHE_SAFE_REMINDER,
display: false,
},
{ deliverAs: "steer", triggerTurn: false },
{ deliverAs: "nextTurn", triggerTurn: false },
);
}
}
@@ -518,6 +584,10 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
const status = readPlanStatus(ctx.cwd);
if (!status.exists) return;
if (!isExecutionApproved(state, ctx, status)) {
setPassivePlanStatus(ctx, status);
return;
}
const mode = deriveEffectiveMode(resolveConfiguredMode(ctx.cwd), ctx);
if (mode === "parity") {
@@ -537,6 +607,10 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
const sessionId = getSessionId(ctx);
const planKey = getPlanSessionKey(ctx, status);
if (status.closed) {
state.autoContinueCountBySessionPlan.set(planKey, 0);
return;
}
const mode = deriveEffectiveMode(resolveConfiguredMode(ctx.cwd), ctx);
if (isAllPhasesComplete(status)) {
@@ -550,6 +624,15 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
if (!isPlanIncomplete(status)) return;
if (!isExecutionApproved(state, ctx, status)) {
ctx.ui.notify(
`[planning-with-files] Task incomplete (${status.completePhases}/${status.totalPhases}). Run /plan-execute to activate hooks.`,
"warning",
);
setPassivePlanStatus(ctx, status);
return;
}
if (mode === "notify") {
ctx.ui.notify(
`[planning-with-files] Task incomplete (${status.completePhases}/${status.totalPhases}). Continue manually.`,
@@ -574,6 +657,12 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
"Update progress.md with what was done, then read task_plan.md and continue remaining phases." +
(goal ? ` Goal: ${goal}` : "");
if (process.env.PWF_DEBUG) {
console.error(
`[planning-with-files] agent_end nag: cwd=${ctx.cwd} planId=${status.planId ?? "root"} closed=${status.closed} phases=${status.completePhases}/${status.totalPhases}`,
);
}
pi.sendUserMessage(continueMessage, { deliverAs: "followUp" });
});
@@ -583,6 +672,12 @@ export default function planningWithFilesExtension(pi: ExtensionAPI): void {
const status = readPlanStatus(ctx.cwd);
if (!status.exists) return;
if (!isExecutionApproved(state, ctx, status)) {
ctx.ui.notify("[planning-with-files] PreCompact: flush progress.md and task_plan.md updates.", "info");
setPassivePlanStatus(ctx, status);
return;
}
const attestation = checkPlanAttestation(status);
const reminder = [
"[planning-with-files] PreCompact: context compaction is about to occur.",
@@ -20,6 +20,10 @@ param(
[switch]$Gate
)
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
if ($env:PLANNING_DISABLED -eq '1') { exit 0 }
if ($PlanFile -ne "") {
$PlanDir = Split-Path -Parent $PlanFile
if ($PlanDir -eq "") { $PlanDir = "." }
@@ -73,6 +77,13 @@ $COMPLETE = [Math]::Max($completePrimary, $completeInline)
$IN_PROGRESS = [Math]::Max($inProgressPrimary, $inProgressInline)
$PENDING = [Math]::Max($pendingPrimary, $pendingInline)
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
@@ -30,6 +30,10 @@
# interactive terminal (TTY) is skipped entirely. No data on stdin is treated
# as stop_hook_active=false.
# issue #195: per-invocation opt-out (PLANNING_DISABLED=1) for one-shot/CI
# sessions that share a cwd with a plan but never opted into it.
[ "${PLANNING_DISABLED:-}" = "1" ] && exit 0
GATE=0
PLAN_FILE=""
for _arg in "$@"; do
@@ -97,6 +101,13 @@ if [ "$PENDING_INLINE" -gt "$PENDING_PRIMARY" ]; then PENDING="$PENDING_INLINE";
: "${IN_PROGRESS:=0}"
: "${PENDING:=0}"
# issue #191: no "### Phase" headings -> not a phase-structured plan. Report
# nothing rather than a false "0/0 phases complete" status. With TOTAL=0 the
# gate can never legitimately block (IN_PROGRESS is also 0), so exit is safe.
if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
@@ -24,13 +24,42 @@ ACTIVE_FILE="${PLAN_ROOT}/.active_plan"
# "feature-foo". The intent is to filter garbage content (e.g. a corrupt
# .active_plan file containing only whitespace or random text) without
# enforcing a date prefix that would break backward compatibility.
SLUG_RE='^[A-Za-z0-9_][A-Za-z0-9._-]*$'
# Pure-sh case patterns; semantics match the previous
# grep -E '^[A-Za-z0-9_][A-Za-z0-9._-]*$' exactly, without a grep fork per
# candidate (the newest-mtime scan calls this once per plan dir).
slug_is_valid() {
case "$1" in
'') return 1 ;;
*[!A-Za-z0-9._-]*) return 1 ;;
[A-Za-z0-9_]*) return 0 ;;
esac
printf "%s" "$1" | grep -Eq "${SLUG_RE}"
return 1
}
# Pure-sh backslash-to-forward-slash normalizer; result lands in $NORM_OUT.
# Windows-native coreutils builds (e.g. C:\Program Files\coreutils on PATH
# ahead of Git's usr/bin) canonicalize MSYS-style /c/... input to C:\-style
# backslash output. The containment prefix match below is written with forward
# slashes, so without this normalization every canonical pair mismatches and
# resolution silently fails. On POSIX systems paths contain no backslash and
# this is the identity. A literal backslash in a Unix filename normalizes to
# "/" and at worst fails containment — the safe direction. No subshell, no
# fork: plain parameter expansion in a loop.
norm_slashes() {
NORM_OUT=""
_ns_rest="$1"
while :; do
case "${_ns_rest}" in
*\\*)
NORM_OUT="${NORM_OUT}${_ns_rest%%\\*}/"
_ns_rest="${_ns_rest#*\\}"
;;
*)
NORM_OUT="${NORM_OUT}${_ns_rest}"
break
;;
esac
done
}
# Portable path canonicalizer. realpath first (Linux, modern coreutils),
@@ -63,19 +92,50 @@ canonicalize() {
# path under the project root (the CWD the script runs from). A symlink inside
# a valid slug dir pointing at /etc or outside the workspace would otherwise let
# the hooks hash and inject an arbitrary file. On any violation we return 1 so
# the caller treats the candidate as unresolved and falls back safely. If
# canonicalization is unavailable for BOTH paths we fail open (return 0) to keep
# legacy behavior byte-equivalent on minimal shells that lack realpath/readlink
# and python; the SLUG_RE check already blocks traversal in the slug name.
# the caller treats the candidate as unresolved and falls back safely.
#
# The root canonicalizes via the relative token "." rather than the $PWD
# string. On some Windows/MSYS setups (8.3 short names, the /tmp mount alias)
# realpath("$PWD") and realpath(relative-candidate) resolve through different
# code paths and land on differently-spelled-but-equal targets, so the prefix
# match below fails and resolution silently goes dark. "." resolves through
# the same physical-cwd path candidates already use (same fix inject-plan.sh
# received earlier; the resolver kept the $PWD form until now). Both sides are
# backslash-normalized before comparison for Windows-native canonicalizers.
# The root is computed once per run: the newest-mtime scan calls this guard
# per plan dir, and each canonicalize costs a process spawn on Windows.
ROOT_REAL=""
ROOT_REAL_SET=0
is_within_root() {
candidate="$1"
root_real="$(canonicalize "${PWD}")" || root_real=""
cand_real="$(canonicalize "${candidate}")" || cand_real=""
if [ -z "${root_real}" ] || [ -z "${cand_real}" ]; then
return 0
if [ "${ROOT_REAL_SET}" = "0" ]; then
ROOT_REAL="$(canonicalize ".")" || ROOT_REAL=""
norm_slashes "${ROOT_REAL}"
ROOT_REAL="${NORM_OUT}"
ROOT_REAL_SET=1
fi
# Canonicalize the candidate through its cwd-RELATIVE form whenever it
# lives under ${PWD}. The candidate string is built from ${PWD} (an MSYS
# long-form spelling), while the root canonicalizes from "." (the process
# cwd, which a caller may have set with an 8.3 short-form string). A
# Windows-native realpath does not unify those spellings, so canonicalizing
# both sides from the same cwd base is the only spelling-stable comparison.
# The emitted result keeps the original absolute candidate — only the
# containment check uses the relative form.
case "${candidate}" in
"${PWD}"/*) check_target=".${candidate#"${PWD}"}" ;;
*) check_target="${candidate}" ;;
esac
cand_real="$(canonicalize "${check_target}")" || cand_real=""
norm_slashes "${cand_real}"
cand_real="${NORM_OUT}"
if [ -z "${ROOT_REAL}" ] || [ -z "${cand_real}" ]; then
# Slug validation blocks textual traversal, but only successful
# canonicalization can rule out a symlink/junction escape.
return 1
fi
case "${cand_real}" in
"${root_real}"|"${root_real}"/*) return 0 ;;
"${ROOT_REAL}"|"${ROOT_REAL}"/*) return 0 ;;
*) return 1 ;;
esac
}
@@ -119,6 +179,12 @@ resolve_from_env() {
resolve_from_active_file() {
[ -f "${ACTIVE_FILE}" ] || return 1
plan_id="$(tr -d '\r\n[:space:]' < "${ACTIVE_FILE}")"
# UTF-8 BOM is not part of the plan id. POSIX printf octal escapes keep
# this portable across GNU/BSD sed variants and Git-for-Windows sh.
utf8_bom="$(printf '\357\273\277')"
case "${plan_id}" in
"${utf8_bom}"*) plan_id="${plan_id#"${utf8_bom}"}" ;;
esac
slug_is_valid "${plan_id}" || return 1
candidate="${PLAN_ROOT}/${plan_id}"
if [ -d "${candidate}" ] && is_within_root "${candidate}"; then
@@ -137,7 +203,7 @@ resolve_latest_dir() {
for entry in "${PLAN_ROOT}"/*/; do
[ -d "${entry}" ] || continue
clean="${entry%/}"
name="$(basename "${clean}")"
name="${clean##*/}"
case "${name}" in
.*) continue ;;
esac
@@ -14,6 +14,30 @@ import os
from pathlib import Path
from typing import Any, Dict, Iterable, List, Optional, Tuple
def configure_utf8_stdio() -> None:
"""Make catchup output deterministic on Windows legacy code pages.
Codex sessions and planning files are UTF-8 and can contain arbitrary
Unicode. Windows PowerShell may nevertheless launch Python with a cp1252
(or another OEM/ANSI) stdout codec. A report containing Chinese text then
used to fail at the first ``print`` with ``UnicodeEncodeError``. Configure
both streams before any report is emitted; ``errors='replace'`` also keeps
this advisory hook fail-safe if a malformed surrogate reaches the output.
"""
for stream in (sys.stdout, sys.stderr):
reconfigure = getattr(stream, 'reconfigure', None)
if callable(reconfigure):
try:
reconfigure(encoding='utf-8', errors='replace')
except (OSError, ValueError):
# Replaced/captured streams may not permit reconfiguration.
# The hook remains advisory, so retain the existing stream.
pass
configure_utf8_stdio()
try:
import orjson
except ImportError:
@@ -1,8 +1,8 @@
# planning-with-files: set or display the active plan pointer (PowerShell).
#
# Usage:
# .\set-active-plan.ps1 <plan_id> — pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 — print the current active plan (if any)
# .\set-active-plan.ps1 <plan_id> - pin .planning\.active_plan to plan_id
# .\set-active-plan.ps1 - print the current active plan (if any)
param(
[string]$PlanId = ""
@@ -19,7 +19,7 @@ if ($PlanId -eq "") {
Write-Output "Active plan: $current"
Write-Output "Path: $planDir"
} elseif ($current -ne "") {
Write-Output "Active plan pointer: $current (directory not found — stale pointer)"
Write-Output "Active plan pointer: $current (directory not found - stale pointer)"
} else {
Write-Output "No active plan set."
}
@@ -41,7 +41,8 @@ if (-not (Test-Path $PlanRoot)) {
New-Item -ItemType Directory -Path $PlanRoot -Force | Out-Null
}
Set-Content -Path $ActiveFile -Value $PlanId -Encoding UTF8 -NoNewline
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($ActiveFile, $PlanId, $utf8NoBom)
Write-Output "Active plan set to: $PlanId"
Write-Output "Path: $PlanDir"
@@ -0,0 +1,85 @@
# Findings & Decisions
<!--
WHAT: Knowledge base for your analytics session. Stores data sources, hypotheses, and results.
WHY: Context windows are limited. This file is your "external memory" for analytical work.
WHEN: Update after ANY discovery, especially after running queries or viewing charts.
-->
## Data Sources
<!--
WHAT: Every data source you connected to, with schema details and quality notes.
WHY: Knowing where your data came from and its limitations is critical for reproducibility.
EXAMPLE:
| user_events | PostgreSQL prod replica | 2.3M rows | user_id, event_type, ts | 0.2% null user_id |
| revenue.csv | Finance team export | 45K rows | account_id, mrr, churn_date | Complete, no nulls |
-->
| Source | Location | Size | Key Fields | Quality Notes |
|--------|----------|------|------------|---------------|
| | | | | |
## Hypothesis Log
<!--
WHAT: Each hypothesis you tested, the method used, and the result.
WHY: Structured tracking prevents p-hacking and makes your reasoning auditable.
EXAMPLE:
| H1: Churn > 50% for low-activity users | Chi-squared test | Confirmed (p=0.003) | High |
| H2: Feature X correlates with retention | Pearson correlation | Rejected (r=0.08) | High |
-->
| Hypothesis | Test Method | Result | Confidence |
|------------|-------------|--------|------------|
| | | | |
## Query Results
<!--
WHAT: Key queries you ran and what they revealed.
WHY: Queries are ephemeral - if you don't write down the results, they're lost on context reset.
WHEN: After EVERY significant query. Don't wait.
EXAMPLE:
### Churn rate by activity segment
Query: SELECT activity_bucket, COUNT(*), AVG(churned) FROM user_segments GROUP BY 1
Result: Low activity: 62% churn, Medium: 28%, High: 8%
Interpretation: Strong inverse relationship between activity and churn
-->
<!-- Record query, result summary, and interpretation for each significant query -->
## Statistical Findings
<!--
WHAT: Formal statistical test results with all relevant metrics.
WHY: Recording p-values, effect sizes, and confidence intervals makes results reproducible.
EXAMPLE:
| Chi-squared (churn ~ activity) | p=0.003 | Cramer's V=0.31 | Reject null: activity segments differ significantly in churn |
| Pearson (feature_x ~ retention) | p=0.42 | r=0.08 | Fail to reject: no meaningful correlation |
-->
| Test | p-value | Effect Size | Conclusion |
|------|---------|-------------|------------|
| | | | |
## Technical Decisions
<!--
WHAT: Analytical method choices with reasoning.
EXAMPLE:
| Use log transform on revenue | Right-skewed distribution, normalizes for parametric tests |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| | |
## Resources
<!-- URLs, file paths, documentation links -->
-
## Visual/Browser Findings
<!--
CRITICAL: Update after viewing charts, dashboards, or browser results.
Multimodal content doesn't persist in context - capture as text immediately.
-->
-
---
*Update this file after every 2 view/browser/search operations*
*This prevents visual information from being lost*
@@ -0,0 +1,106 @@
# Task Plan: [Analytics Project Description]
<!--
WHAT: Roadmap for a data analytics or exploration session.
WHY: Analytics workflows have different phases than software development — hypothesis testing,
data quality checks, and statistical validation don't map to a generic build cycle.
WHEN: Create this FIRST before starting any data exploration. Update after each phase.
-->
## Goal
<!--
WHAT: One clear sentence describing what you're trying to learn or produce.
EXAMPLE: "Determine which user segments have the highest churn risk using last 90 days of activity data."
-->
[One sentence describing the analytical objective]
## Current Phase
<!--
WHAT: Which phase you're currently working on (e.g., "Phase 1", "Phase 3").
WHY: Quick reference for where you are. Update this as you progress.
-->
Phase 1
## Phases
### Phase 1: Data Discovery
<!--
WHAT: Connect to data sources, understand schemas, assess data quality.
WHY: Bad data produces bad analysis. This phase prevents wasted effort on unreliable inputs.
-->
- [ ] Identify and connect to data sources
- [ ] Document schemas and field descriptions in findings.md
- [ ] Assess data quality (nulls, duplicates, outliers, date ranges)
- [ ] Estimate dataset size and query performance
- **Status:** in_progress
### Phase 2: Exploratory Analysis
<!--
WHAT: Distributions, correlations, outliers, initial patterns.
WHY: Understanding the shape of your data before testing hypotheses prevents false conclusions.
-->
- [ ] Compute summary statistics for key variables
- [ ] Visualize distributions and relationships
- [ ] Identify outliers and anomalies
- [ ] Document initial patterns in findings.md
- **Status:** pending
### Phase 3: Hypothesis Testing
<!--
WHAT: Formalize hypotheses, run statistical tests, validate findings.
WHY: Moving from "it looks like X" to "we can confidently say X" requires structured testing.
-->
- [ ] Formalize hypotheses from exploratory phase
- [ ] Select appropriate statistical tests
- [ ] Run tests and record results in findings.md
- [ ] Validate findings against holdout data or alternative methods
- **Status:** pending
### Phase 4: Synthesis & Reporting
<!--
WHAT: Summarize findings, create visualizations, document conclusions.
WHY: Analysis without clear communication is wasted work. This phase produces the deliverable.
-->
- [ ] Summarize key findings with supporting evidence
- [ ] Create final visualizations
- [ ] Document conclusions and recommendations
- [ ] Note limitations and areas for further investigation
- **Status:** pending
## Hypotheses
<!--
WHAT: Questions you're investigating, stated as testable hypotheses.
WHY: Explicit hypotheses prevent fishing expeditions and keep analysis focused.
EXAMPLE:
1. Users who logged in < 3 times in the last 30 days have > 50% churn rate (H1)
2. Feature X adoption correlates with retention (r > 0.3) (H2)
-->
1. [Hypothesis to test]
2. [Hypothesis to test]
## Decisions Made
<!--
WHAT: Analytical decisions with reasoning (e.g., choosing a test, filtering criteria).
EXAMPLE:
| Use median instead of mean | Revenue data is heavily right-skewed |
| Filter to last 90 days | Earlier data uses a different tracking schema |
-->
| Decision | Rationale |
|----------|-----------|
| | |
## Errors Encountered
<!--
WHAT: Every error you encounter, what attempt number it was, and how you resolved it.
EXAMPLE:
| Query timeout on raw table | 1 | Added date partition filter |
| Null join keys in user_events | 2 | Inner join instead of left join, documented data loss |
-->
| Error | Attempt | Resolution |
|-------|---------|------------|
| | 1 | |
## Notes
- Update phase status as you progress: pending -> in_progress -> complete
- Re-read this plan before major analytical decisions
- Log ALL errors - they help avoid repetition
- Write query results and visual findings to findings.md immediately
+11 -5
View File
@@ -6,9 +6,9 @@ This file is the canonical, session-portable reference for how every agent worki
## Commit rules
- **Author**: OthmanAdi only. NEVER add `Co-Authored-By:` trailers.
- **Author**: OthmanAdi only for release/maintenance commits. NEVER add `Co-Authored-By:` trailers.
- **Format**: Conventional Commits — `fix:`, `feat:`, `release:`, `docs:` prefixes.
- **One squashed commit per release or PR merge.**
- **One release commit on top of the contributor commit(s), never a squash of the contributor's work.** `git merge --squash` rewrites the committer as whoever runs the local commit, which reassigns the contributor's authorship to OthmanAdi — this happened once (v2.40.1 cycle) and is exactly what this rule exists to prevent. Squashing is fine only for collapsing your OWN WIP commits before pushing.
- No `--no-verify`. No force push to master except tag ref updates.
- Contributors are credited in CHANGELOG `### Thanks` and `CONTRIBUTORS.md`, never in commit trailers.
@@ -19,7 +19,7 @@ This file is the canonical, session-portable reference for how every agent worki
1. `gh issue view N` and `gh pr view N` — read both in full.
2. Verify the bug is real: find the exact file/line, grep for the pattern, confirm reporter is correct.
3. `python -m pytest tests/ -q` — all tests pass before touching anything.
4. Squash merge: `git fetch origin branch && git merge --squash origin/branch`, then a single commit as OthmanAdi.
4. Merge preserving contributor authorship: `git fetch origin pull/N/head:pr-N && git cherry-pick <pr-head-sha>`, or `gh pr merge --rebase`. Do NOT use `git merge --squash` — it collapses the contributor's commit and reassigns the `Author:` field to whoever runs the local commit, destroying their credit in `git log`.
5. CHANGELOG — new version entry at top, `### Fixed`/`### Added`/`### Changed`, sachlich, no em-dashes.
6. CONTRIBUTORS.md — add reporter/contributor, bump "Total Contributors: N+", update "Last updated" date.
7. Version bump across all 19 files (see table below).
@@ -57,7 +57,12 @@ All 19 files must be bumped to the same version string every release.
| `.claude-plugin/marketplace.json` | Marketplace metadata |
| `CITATION.cff` | Citation file |
**NOT bumped automatically**: `.continue/`, `.gemini/` — intentionally behind. Do not bump without an explicit scope decision.
**NOT bumped automatically**: `scripts/bump-version.py`'s `LAGGING_FILES` list currently excludes four files, not two — this table only tracked two until this correction:
- `.continue/skills/planning-with-files/SKILL.md`, `.gemini/skills/planning-with-files/SKILL.md` — intentionally behind. Do not bump without an explicit scope decision.
- `.pi/skills/planning-with-files/SKILL.md` — carries its own npm package version (`@tomxprime/planning-with-files`), not the canonical `metadata.version` field the parity set shares.
- `.kiro/skills/planning-with-files/SKILL.md` — carries its own `-kiro`-suffixed scheme (e.g. `3.0.0-kiro`), bumped on Kiro-relevant changes rather than every canonical release.
Recent CHANGELOG entries (v3.1.1–v3.1.3) already describe this 4-file exclusion as "per AGENTS.md release scope" — this section previously did not actually say so. It does now.
---
@@ -145,7 +150,8 @@ Thanks: @handle for reporting issue #N.
## Quick reference: what NOT to do
- Do not add Co-Authored-By to any commit.
- Do not bump .continue or .gemini without explicit instruction.
- Do not bump .continue or .gemini without explicit instruction. .pi and .kiro track their own version schemes and are never bumped by `scripts/bump-version.py`.
- Do not `git merge --squash` a contributor PR — it reassigns their commit authorship. Use cherry-pick or `gh pr merge --rebase`.
- Do not edit `task_plan.md` or `DESIGN.md` directly (user-owned contracts).
- Do not log subagent returns into `task_plan.md` — use `progress.md`.
- Do not use em-dashes in any user-facing prose.
+205
View File
@@ -2,6 +2,211 @@
All notable changes to this project will be documented in this file.
## [3.6.0] - 2026-07-18
### Fixed
- **Plan resolution and hook injection went silently dark on machines with Windows-native coreutils on PATH.** A native coreutils build (for example `C:\Program Files\coreutils`, increasingly common via winget or uutils) shadows Git Bash's tools inside `sh`, and its `realpath` canonicalizes MSYS `/c/...` input to `C:\`-style backslash output. 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 resolver resolved nothing, `inject-plan.sh` emitted nothing, and every hook fire exited 0 with no error. The plan mechanisms this skill is built on were fully disabled on such machines with no visible symptom, and Linux CI could never reproduce it. Canonical paths are now backslash-normalized (pure-shell, no extra process) before comparison. Found live during a repo audit on a machine where 33 local tests failed while CI stayed green.
- **The resolver's containment root still canonicalized `$PWD` instead of `.`.** The v3.2.0 fix for 8.3 short-name and `/tmp`-alias mismatches landed in `inject-plan.sh` only; `resolve-plan-dir.sh` kept the old form and additionally canonicalized absolute candidates built from the `$PWD` string, which a Windows-native `realpath` does not spell the same way as a short-form process cwd. The resolver now canonicalizes the root via `.` and checks candidates through their cwd-relative form, so both sides resolve through the same physical-cwd base. Emitted output is unchanged.
- **The local `test_ledger.py` hang on Windows is gone.** Known since v3.4.1 as "hangs locally, green on CI"; it stopped reproducing once the containment comparison was fixed. The ledger suite (12 tests) now passes locally in under 15 seconds.
### Added
- **`/plan-doctor` self-check command** (`commands/plan-doctor.md`, `scripts/plan-doctor.sh`, dual-shipped and parity-tested). Every failure in this class is silent by design (hooks always exit 0), so a broken install looks identical to "no plan yet". The doctor answers, in one pass: does resolution work here and which plan wins, does injection actually emit plan context, what path shape does the canonicalizer produce (warns about the pre-v3.6.0 dark condition), is the plan attested, which install surfaces exist on this machine, and what one hook fire costs in wall-clock. From the July 2026 benchmark improvement backlog.
- **Install-route matrix and trust prerequisite in `docs/installation.md`.** The plugin route ships `commands/` and registers hooks reliably; `npx skills add` and manual skill copies ship no slash commands, and SKILL.md frontmatter hooks have been observed unregistered on project-level installs (headless Claude Code 2.1.201, July 2026 benchmark). Project trust (`hasTrustDialogAccepted`) silently gates project-level skills. Both conditions are now documented with the doctor as the verification step.
- **Belt-and-suspenders trigger line for CLAUDE.md.** The July 2026 benchmark measured unforced skill engagement at 60-67% while always-loaded rules-file instructions engaged 100%. Install docs now offer a one-line CLAUDE.md snippet that makes engagement deterministic while the skill description keeps handling discovery.
- **Regression tests for the backslash-canonicalizer class** (`tests/test_containment.py`): a PATH-prepended stub `realpath` reproduces the Windows-native output shape on every platform, so ubuntu CI now guards a Windows-only field failure.
### Changed
- **Hook fire cost dropped.** Per-candidate `grep -Eq` slug checks and `basename` calls in `resolve-plan-dir.sh` and `inject-plan.sh` were replaced with equivalent shell builtins (case patterns and parameter expansion), and the resolver computes its containment root once per run instead of once per candidate. One `inject-plan.sh` fire measures 289ms wall-clock on the same machine that measured 2.0-2.4s at v3.4.0 (July 2026 benchmark, F4). Output is byte-identical; the legacy invariant holds.
- README documents v3.6.0, adds a plan-mode handoff FAQ (how an accepted plan-mode plan becomes `task_plan.md` phases, answering the open question in #19), and updates the supported-agents answer to the current `.agents/skills/` standard landscape. The stale Manus-2025 "one tool call per turn" guidance was replaced with the 2026 parallel-host update in the last two variant copies that still carried it (`.factory`, `.mastracode`).
### Verification
- Full suite green locally on the machine that reproduced the bug: 205 passed plus 12 ledger tests, 5 skipped, 0 failed (was 33 failed before the fix). Live smoke: `sh scripts/inject-plan.sh` emits plan context in a repo with an active plan, `scripts/plan-doctor.sh` reports PASS on resolution and injection with the Windows-native canonicalizer present.
## [3.5.1] - 2026-07-14
### Fixed
- **Codex Windows hook resolver defaulted to the WSL bash launcher** (extracted from PR #207 by @mahdiit). The shell resolver in `codex_hook_adapter` preferred `C:\Windows\System32\bash.exe`, the WSL launcher, whenever Git Bash's `usr\bin` was not on PATH, the default Git for Windows install layout. On machines where WSL is present but no distro is installed, a common Docker Desktop setup, that launcher exists on disk but fails immediately, so every shell hook silently failed. This is why #201 and #204 kept resurfacing on some Windows machines even after those releases shipped. The resolver now skips WSL launchers, both the System32 binary and the Microsoft Store WindowsApps alias, and continues on to the Git for Windows probe.
- **`pwf-hook.cmd` lost the Python interpreter under Codex's reduced PATH** (also from PR #207). Codex starts hooks with a trimmed environment, so the launcher's plain `py -3` / `python` lookup often found nothing. `pwf-hook.cmd` now honors a `PYTHON_BIN` override, probes the standard `uv` and CPython install locations, and quotes the interpreter path so installs under a path containing spaces still resolve.
- **Pi extension 1.2.1: pre-tool recitations and the tamper notice were consumed by interactive tool dialogs** (#206, diagnosed with the exact mechanism by @jschmied). The `tool_call` hook delivered queued planning text as steer, but when an interactive tool such as AskUserQuestion blocked the turn on its own custom UI, the steer text was read as the dialog's answer instead of reaching the model. Recitations and the tamper notice are now delivered as `nextTurn`, landing after the interactive tool releases the turn instead of being swallowed by it. Bundled Pi extension bumped to 1.2.1.
- **Two test-portability failures surfaced by the first hosted-runner run** (PR #198 by @Yigtwxx, test files only, no production script changes). `tests/test_containment.py` canonicalized the resolver's Git Bash POSIX path with `os.path.realpath`, which read `/tmp/...` as `C:\tmp` and failed the escaping-symlink containment assertion on symlink-capable `windows-latest`; a `host_realpath()` helper now maps the path back with `cygpath` before comparing. `tests/test_path_fix.py` ran two Windows-shaped path vectors on `ubuntu-latest`, where `Path.resolve()` treats `C:/...` as relative and prepends the CWD; both now skip unless `os.name == "nt"`, the only platform where that input shape is meaningful. In both cases the resolver rejected the escape correctly and only the test comparison was wrong.
### Added
- **CI test workflow** (`.github/workflows/tests.yml`, PR #199 by @Yigtwxx, closes #197). Runs the pytest suite on `ubuntu-latest` and `windows-latest` (Python 3.12) plus the Pi extension vitest suite on `ubuntu-latest` (Node 22), on every pull request and push to master. Until now the only CI was the Tessl skill-prose review, so nothing ran the 200+ pytest tests or the 21 Pi vitest tests that CONTRIBUTING.md asks contributors to run before opening a PR. The `windows-latest` leg targets the recurring Windows regression class (the v3.2.0 audit found `session-catchup.py` silently non-functional on Windows). Workflow permissions are `contents: read` only, `fail-fast: false` keeps a Windows-only failure visible instead of masked by a cancel, and an explicit step asserts `sh` is on PATH so the roughly ten hook tests that skip without it cannot silently drop coverage.
### Verification
- Python suite green on master before merge (200 passed, 5 skipped on a non-symlink-capable Windows host). Contributor's hosted-runner run on PR #199 green on all three jobs: pytest on ubuntu with symlink containment tests running, the equivalent profile on windows, and 21 vitest.
- Supply-chain review on PR #198 and #199: no new runtime dependencies, no install scripts, no bin shims. The workflow installs `pytest` and `pyyaml` for the pytest job and runs `npm install` against the Pi extension's existing devDependencies (vitest, typescript, @types/node). It uses `pull_request` (not `pull_request_target`), so fork PRs run with a read-only token and no secret access.
### Thanks
- Mahdi (@mahdiit) for producing the Codex Windows WSL bash launcher fix and the Windows Python discovery hardening (PR #207), using Codex Terra after v3.4.1 still failed to clear the hook errors on his own machine.
- jschmied for diagnosing #206 down to the exact runtime location, steer delivered recitations consumed by AskUserQuestion's interactive dialog, and for the `nextTurn` delivery fix that shipped.
- Yigtwxx (Yiğit) for filing the CI gap (#197) and landing both the test-portability fixes (#198) and the pytest plus vitest workflow (#199).
## [3.5.0] - 2026-07-13
### Fixed
- **Codex hooks on Windows emitted invalid JSON and failed on Unicode** (PR #205 by @yolo0731, closes #204). On Windows the Codex front door forwarded plain `[planning-with-files]` stdout where Codex expects `hookSpecificOutput.additionalContext` (SessionStart, UserPromptSubmit) or a common-fields object (PreCompact), so those hooks were rejected. UTF-8 plan text also broke twice, once decoding shell output through the Windows code page in `subprocess.run(text=True)` and again writing `ensure_ascii=False` JSON through cmd.exe. The fix serializes each event in its supported Codex JSON shape with ASCII-safe output, decodes shell output as UTF-8 with `errors="replace"`, routes PreToolUse plan text through model-visible `additionalContext`, resolves scoped `.planning/<slug>/` plans in PermissionRequest, adds `clear|compact` to the SessionStart matcher, and writes the `.active_plan` pointer as UTF-8 without a BOM. The containment resolver also now fails closed when canonicalization is unavailable. 34 files, with the three-file script triple applied identically across every adapter copy. Scope is Windows Codex, matching the report.
- **Closed and complete plans kept nagging "Task incomplete" in the Pi extension** (#203, reported by @ziyu4huang). `resolveNewestPlanDir` ranked plan directories by directory mtime, which does not change when a `task_plan.md`'s contents are edited, so a finished plan lost to an older incomplete sibling and `agent_end` nagged with that sibling's stale count. Resolution now ranks by the `task_plan.md` file mtime. The extension also had no close-marker awareness: `readPlanStatus` now parses the pwf close marker and exposes `status.closed`, `agent_end` returns early on a closed plan, and the auto-continue loop stops on close. A `PWF_DEBUG` diagnostic logs the resolved cwd, plan id, closed state, and phase count before the nag. Bundled Pi extension bumped to 1.2.0.
- **Four language commands invoked a skill namespace that does not exist.** `commands/plan-ar.md`, `plan-de.md`, `plan-es.md`, and `plan-zh.md` referenced `planning-with-files-<lang>:planning-with-files-<lang>`; the skills are registered under the single plugin namespace `planning-with-files:planning-with-files-<lang>`, which the English commands already used. Corrected all four.
### Added
- **Traditional Chinese slash command** (`commands/plan-zht.md`, `/plan-zht`). The `planning-with-files-zht` skill shipped without a matching command; this closes the ar/de/es/zh/zht command parity gap.
- **README now documents the full v3 command, hook, and mode surface.** The visible command table listed only three commands with v2.11.0 and v2.15.0 tags while the plugin ships `plan-goal`, `plan-loop`, `plan-attest`, `pwf`, and the language commands. Added visible sections for the Claude Code and Pi command tables, a v3 long-running-agent features section, a hooks-and-modes reference across Claude Code, Codex, and Pi, and a "command names vs skill names" note that states there is no `/pwf-de` or `/planning-with-files:planning-with-files-goal`. All additive.
- **Plan lifecycle is now documented** (#202, asked by @kcinzgg). `docs/workflow.md` gains an "After Completion" section stating that planning files are ephemeral working memory, gitignored by default, and not archived automatically, with guidance on retaining a completed plan and a note that a completion-triggered archive step is a welcome opt-in extension. Pointers added in `docs/quickstart.md` and the README FAQ. This writes down the intent that issue #14 answered informally.
### Security
- Independent supply-chain audit of PR #205 before merge (a classifier plus two adversarial containment passes, all clean): no new dependencies, no install scripts, no bin shims, no network calls, no eval of untrusted input. The one security-relevant change (containment moving from fail-open to fail-closed) is a hardening, covered by an added junction-escape rejection test.
### Thanks
- @yolo0731 for the Codex Windows hook fix (#204, PR #205), the protocol-safe JSON serialization and the UTF-8 handling.
- @ziyu4huang for the precise #203 diagnosis, verified against the extension's own exported functions.
- @kcinzgg for the #202 question that surfaced the undocumented plan lifecycle.
## [3.4.1] - 2026-07-12
### Fixed
- **Codex hooks failed on Windows with "hook exited with code 1"** (closes #201, reported by @mahdiit). Every hook in `.codex/hooks.json` carried only a POSIX `command` (`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 on every Bash call. Reproduced on Windows: the PostToolUse command exits 1 when Git's `usr\bin` (home of `sh` and `true`) is not on PATH, which is the default Git for Windows layout.
### Added
- **Windows hook execution for Codex** via the per-hook `commandWindows` override, the mechanism OpenAI's hooks documentation sanctions. The POSIX `command` is untouched, so macOS and Linux stay 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 so an advisory hook cannot surface an error. The four Python hooks run their entry point directly; the three shell hooks route through a new front door, `.codex/hooks/run_sh.py`. `codex_hook_adapter.run_shell_script` now resolves the Git for Windows `sh.exe` by anchoring on `git.exe` and the standard install roots, so the shell scripts run even when Git's `usr\bin` is off PATH, and it hands `session-catchup.py` a real interpreter through `PYTHON_BIN`. Without Git for Windows the three shell hooks degrade to a silent no-op instead of an error, and the four Python hooks still work. The `docs/codex.md` Windows section was rewritten (it previously stated hooks were disabled on Windows).
### Verification
- Codex hook suites green: `tests/test_codex_hooks.py` (11 tests, including a cross-platform guard that every hook declares a `commandWindows` free of the POSIX tokens that break on Windows, and a Windows-only end-to-end test of the `run_sh.py` front door) and `tests/test_codex_session_isolation.py` (6 tests). Version parity and frontmatter suites green after the bump. End-to-end on Windows: PostToolUse, SessionStart, and PreToolUse all exit 0 with correct output, including a simulation of the reporter's environment (Git installed, `usr\bin` off PATH) where the `git.exe` anchor resolves `sh`, and a no-Git case that degrades to a silent no-op.
- Supply-chain review: no new runtime dependencies, no install scripts, no bin shims. Two new files under `.codex/hooks/` (`pwf-hook.cmd`, `run_sh.py`), both Windows-only entry points that reuse the existing adapter and shell scripts.
### Thanks
- @mahdiit (Mahdi) for reporting the Codex Windows hook failure (#201) with the exact error and environment.
## [3.4.0] - 2026-07-06
### Added
- **`PLANNING_DISABLED=1` per-invocation opt-out** (closes #195, reported by @marcmuon). One-shot sessions that 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`. All Codex hook entry points (`session-start.sh`, `user-prompt-submit.sh`, `pre-tool-use.sh`, `post-tool-use.sh`, `stop.sh`, `pre-compact.sh`, plus the Python adapter route via `codex_hook_adapter.is_session_attached`) and the canonical dispatchers (`inject-plan.sh`, `gate-stop.sh`, `check-complete.sh`/`.ps1`) now exit before reading the plan when `PLANNING_DISABLED=1` is set in the environment. PreToolUse still emits its allow decision so tool calls proceed normally. The guard ships in every distributed copy: canonical scripts, the skill package, all sync-managed IDE mirrors, the five language variants, the standalone `.kiro` copy, and the `clawhub-upload` bundle. Usage documented in `docs/codex.md`. New `tests/test_planning_disabled_optout.py` (12 tests) covers baseline behavior, disabled behavior, the #195 acceptance criterion (plan files byte-for-byte unchanged after a full disabled hook pass), and a guard-presence sweep across every copy.
### Fixed
- **`docs/codex.md` still described the pre-v3.1.0 blocking Stop hook.** The hook table said Stop "blocks once when phases are incomplete"; that block path was removed in v3.1.0 (PR #180) and the installed hook has emitted an advisory reminder since. The row now matches the shipped behavior. The `decision: block` half of #195 was reported against a v2.41.0 bundle shipped by oh-my-codex; current releases do not block.
### Verification
- Python suite: 200 passed, 5 skipped, 0 failed (12 new opt-out tests).
- Functional smoke test on a live temp plan: baseline injection unchanged without the variable; with it set, no hook output, tool calls still allowed, plan files byte-identical afterwards.
- `scripts/sync-ide-folders.py --verify`: all IDE folders in sync. All modified `.sh` files pass `sh -n`; all modified `.ps1` files parse clean.
### Thanks
- @marcmuon (Marc Kelechava) for the precise report separating this failure from #178 and #146, with reproductions for both symptoms, the root-cause file list, and acceptance criteria this release implements directly (#195).
## [3.3.0] - 2026-07-06
### Added
- **`/plan-execute` approval gate for the Pi extension** (PR #193 by @Dikshj, closes #190, requested by @lazyst). The Pi extension hooks previously activated as soon as `task_plan.md` existed on disk: plan injection on `before_agent_start`, pre-tool recitation on `tool_call`, post-write reminders on `tool_result`, and auto-continue on `agent_end` could all start while the user was still reviewing a draft plan. The extension now stays passive until the user approves the active plan with `/plan-execute`; before approval it shows a status line ("run /plan-execute to activate hooks") and nothing else. Approval is scoped to the current session and plan path, is cleared on session lifecycle events, and `/plan-execute reset` returns the plan to passive review mode. A plan whose SHA-256 attestation shows tampering cannot be approved. This gates initial hook activation only; the v3 gate mode (which gates stopping on an incomplete plan) is unchanged. Pi docs, Pi skill docs, and runtime tests cover the passive review flow.
### Verification
- Python suite: 188 passed, 5 skipped, 0 failed.
- Pi extension vitest suite: 21 passed (2 files).
- `scripts/sync-ide-folders.py --verify`: all IDE folders in sync.
- Supply-chain review on PR #193: no new dependencies, no install scripts, no bin shims, no network calls; changes confined to the Pi extension runtime, its tests, and documentation. The gate itself tightens the injection path, since a tampered or unreviewed plan can no longer reach model context automatically.
### Thanks
- @Dikshj (diksha) for implementing the /plan-execute approval gate with runtime and docs test coverage (PR #193).
- @lazyst for the feature request and the precise passive-until-confirmed workflow description (#190).
## [3.2.0] - 2026-07-03
A repository health audit covering the v3 long-running-session mechanism, the
open issue backlog, and two community pull requests. The headline finding:
`session-catchup.py`, the mechanism behind "resume after /clear," did nothing
on Windows. Both that and a related silent-injection bug are fixed here,
along with the false "0/0 phases" status reported in #191.
### Fixed
- **`session-catchup.py` was non-functional on Windows** (the "resume after /clear" feature). `get_project_dir_claude` only replaced forward slashes, so a Windows-style path (`C:\Users\...` or Git Bash's `/c/Users/...`) never sanitized to Claude's actual project-directory name, and the function always returned early with no output and no error. Three `open()` calls also had no explicit encoding, so a session log containing any non-ASCII text raised `UnicodeDecodeError` on Windows' default `cp1252` codec, an error the surrounding `except` clauses swallowed silently. Fixed by detecting Windows-shaped paths before sanitizing and adding `encoding='utf-8', errors='replace'` to the three reads; genuine Unix absolute paths take the same code path as before. `tests/test_path_fix.py` previously reimplemented the sanitizer instead of importing the real module, so the suite stayed green while the shipped script stayed broken; it now imports and exercises the actual function.
- **`inject-plan.sh`'s containment guard silently dropped plan injection and tamper detection under aliased paths.** `is_within_root()` canonicalized the project root from the `$PWD` string but canonicalized candidates from a relative path, and on a Windows account with an 8.3 short-name `TEMP` (or any path reached through the MSYS `/tmp` mount), the two resolve to differently-spelled versions of the same directory. The prefix-match check then failed and the hook exited with zero output: no plan re-injection, no tamper warning, nothing visible. Fixed by canonicalizing the root the same way candidates already are.
- **Task plans without `### Phase` headings falsely reported "0/0 phases complete"** (#191, reported by @mixian939 against Codex). `check-complete.sh`/`.ps1` and several IDE-specific Stop hooks counted `### Phase` headings but never checked whether the count was zero before reporting a status, so an unstructured `task_plan.md` produced a false "Task in progress (0/0 phases complete)" message, or in Cursor and GitHub Copilot's adapters an auto-continue nudge to keep working on a plan that was never structured to begin with. Fixed with a `TOTAL=0` guard everywhere the pattern appeared: the canonical Claude Code scripts, `.codex`, `.cursor`, GitHub Copilot's `agent-stop.sh`, all `sync-ide-folders.py`-managed IDE mirrors, the five language-variant skills, and the standalone `.kiro` copy.
- **`--template analytics` silently produced the default templates instead of the analytics-specific ones** (addresses #103). `templates/analytics_task_plan.md` and `templates/analytics_findings.md` (added v2.29.0) were never copied into `skills/planning-with-files/templates/`, the directory the installed skill package actually reads, so every plugin or skill-only install fell back to the generic templates. Copied both files into the canonical templates directory and added them to `sync-ide-folders.py`'s sync list, backfilling seven IDE mirrors.
- **Windows test suite encoding errors and stale installation docs** (PR #187 by @Stephen-abc). `subprocess.run`/`Popen` calls across 15 test files had no explicit encoding, which raises `UnicodeDecodeError` on Windows accounts whose default codepage isn't UTF-8. `docs/adal.md`, `docs/antigravity.md`, `docs/kilocode.md`, and `docs/openclaw.md` also referenced `.adal/`, `.agent/`, and `.kilocode/` source paths removed in v2.24.0, and antigravity.md's templates were mislabeled as living in `references/` instead of `templates/`. Fixed the same mislabel in `docs/codebuddy.md`, which was outside PR #187's scope.
- **Hermes adapter test failed on Windows accounts with an 8.3-short-name `TEMP`.** The test compared a `Path.resolve()`-canonicalized production result against an unresolved expected path, so accounts where `TEMP` itself resolves to a short-name alias (`OASRVA~1` vs the real account name) failed a test that was actually passing correctly. Resolved the expected side the same way.
- **`AGENTS.md` told agents to squash-merge contributor PRs.** `git merge --squash` reassigns the contributor's commit authorship to whoever runs the local commit: the exact mistake this project's release protocol already exists to prevent (v2.40.1 cycle). Replaced with cherry-pick / `gh pr merge --rebase` guidance, matching CLAUDE.md. Also documented that `.pi` and `.kiro` lag the parity-locked version bump alongside `.continue` and `.gemini`, which recent CHANGELOG entries already assumed AGENTS.md said.
### Added
- **`docs/autohand.md`** (PR #192 by @igorcosta): setup guide for Autohand Code, added to the supported-IDEs table (18+ platforms).
- **`SECURITY.md`** and GitHub private vulnerability reporting enabled (closes #188, requested by @AvitalAviv).
### Changed
- Version bumped to 3.2.0 across the 17 parity-locked files via `scripts/bump-version.py`. `.continue`, `.gemini`, `.pi`, and `.kiro` lag intentionally, now documented in AGENTS.md itself.
### Verification
- Python suite: 186 passed, 5 skipped, 0 failed (up from 184/4/0; two new path-sanitizer tests in `test_path_fix.py`).
- `scripts/sync-ide-folders.py --verify`: all IDE folders in sync.
- Functionally re-verified the long-running-session mechanism end to end on Windows after the fixes: init, check-complete, attestation lock/tamper-detect/clear, parallel-plan resolution, and session-catchup all confirmed working in both sh and PowerShell.
- Supply-chain review on both merged PRs: docs and test files only, no dependency, install script, or bin shim in either.
### Thanks
- @mixian939 for the detailed #191 report and root-cause diagnosis against Codex, which led to finding and fixing the same defect in the canonical scripts and two other IDE adapters.
- @Stephen-abc (Wang Jun) for the Windows test encoding fix and installation doc corrections (PR #187).
- @igorcosta (Igor Costa, Autohand) for the Autohand Code setup docs (PR #192).
- @AvitalAviv for flagging the missing private vulnerability disclosure channel (#188).
- @mvanhorn for the original analytics templates (v2.29.0, #103) that this release finally ships into the installed skill package.
## [3.1.3] - 2026-06-16
A hotfix for a frontmatter regression introduced in v3.1.2. The refreshed description shipped in v3.1.2 contains a colon, and the English SKILL.md carry the `description` field unquoted, so the frontmatter became invalid YAML. This release quotes the description and adds a test that validates every SKILL.md frontmatter as YAML, so the class cannot ship again.
### Fixed
- **SKILL.md frontmatter was invalid YAML in v3.1.2** (regression from the v3.1.2 description refresh). The new description "Manus-style persistent file-based planning for AI coding agents: keeps ..." contains a colon followed by a space. The English SKILL.md carry `description` unquoted, so a YAML loader reads the `: ` as a nested mapping and rejects the frontmatter with "mapping values are not allowed here". This affected the canonical file and the seven English IDE variants (`.codebuddy`, `.codex`, `.cursor`, `.factory`, `.hermes`, `.mastracode`, `.opencode`) and could break skill loading and the model-triggering description field. The description is now wrapped in double quotes, matching the already-quoted translated variants. The parsed value is identical, so model triggering is unchanged. The `clawhub-upload` staging bundle was corrected the same way.
### Added
- **Frontmatter validation test** (`tests/test_skill_frontmatter_valid.py`): loads every `SKILL.md` frontmatter as YAML and asserts a non-empty string description, plus a dependency-free check that no unquoted description contains `: `. The version-parity check is a regex and could not catch this regression.
### Changed
- Version bumped to 3.1.3 across the 17 parity-locked files via `scripts/bump-version.py`. `.continue`, `.gemini`, `.pi`, and `.kiro` lag intentionally per AGENTS.md release scope.
### Verification
- Python suite: 184 passed, 4 skipped, 0 failed, up from 180 with the four new frontmatter assertions.
- Every SKILL.md in the repo, including the translated variants and the lagging `.continue`, `.gemini`, `.pi`, and `.kiro` variants, parses as valid YAML.
## [3.1.2] - 2026-06-16
A documentation patch. The session-catchup command in the skill body assumed the plugin runtime had set `${CLAUDE_PLUGIN_ROOT}`, so a skill-only install that ran the documented command in a normal shell got an empty variable and a broken path. This release adds a fallback across the affected variants, fixes the same class of bug in the `.hermes` variant, and refreshes the skill description to lead with the current positioning.
+1 -1
View File
@@ -7,7 +7,7 @@ authors:
- family-names: Adi
given-names: Ahmad Othman Ammar
alias: OthmanAdi
version: "3.1.2"
version: "3.6.0"
date-released: "2026-06-16"
license: MIT
url: "https://github.com/OthmanAdi/planning-with-files"
+50 -2
View File
@@ -106,6 +106,34 @@ These amazing people have contributed code, documentation, or significant improv
### Other Contributors
- **[@jschmied](https://github.com/jschmied)** - [Issue #206](https://github.com/OthmanAdi/planning-with-files/issues/206)
- Diagnosed that pre-tool recitations and the tamper notice queued by the Pi extension's `tool_call` hook were delivered as steer, and that an interactive tool such as AskUserQuestion blocking the turn on its own custom UI consumed that steer text as the dialog's answer instead of letting it reach the model, pinning down the precise runtime location of the collision and the `nextTurn` delivery fix that shipped
- **Impact:** v3.5.1 delivers Pi extension recitations and the tamper notice as `nextTurn` instead of steer, so interactive tool dialogs no longer swallow injected planning text; bundled Pi extension bumped to 1.2.1
- **[@yolo0731](https://github.com/yolo0731)** (yoloyq) - [PR #205](https://github.com/OthmanAdi/planning-with-files/pull/205), closes [Issue #204](https://github.com/OthmanAdi/planning-with-files/issues/204)
- Fixed the Codex hooks on Windows emitting invalid JSON and failing on Unicode: plain `[planning-with-files]` stdout where Codex expects `hookSpecificOutput.additionalContext` or a common-fields object, UTF-8 shell output decoded through the Windows code page, and `ensure_ascii=False` JSON written through cmd.exe
- Serialized each event in its supported Codex JSON shape with ASCII-safe output, decoded shell output as UTF-8, routed PreToolUse plan text through model-visible `additionalContext`, resolved scoped plans in PermissionRequest, added `clear|compact` SessionStart sources, wrote the `.active_plan` pointer without a BOM, and hardened the containment resolver to fail closed
- **Impact:** v3.5.0 makes the full Codex hook surface work on Windows across active-plan, no-plan, disabled, malformed-input, and Unicode scenarios
- **[@ziyu4huang](https://github.com/ziyu4huang)** (Ziyu Huang) - [Issue #203](https://github.com/OthmanAdi/planning-with-files/issues/203)
- Reported that a closed and complete plan kept emitting "Task incomplete" nags for many turns, with a root-cause trace against the Pi extension's own exported `resolvePlanPaths`, `readPlanStatus`, and `isPlanIncomplete`
- **Impact:** v3.5.0 ranks newest-plan resolution by `task_plan.md` file mtime, adds close-marker awareness, and gates the nag on `status.closed`
- **[@kcinzgg](https://github.com/kcinzgg)** (Kcinzgg) - [Issue #202](https://github.com/OthmanAdi/planning-with-files/issues/202)
- Asked for a direct statement of the plan-file lifecycle and why there is no archiving step, noting the earlier #14 had no visible resolution
- **Impact:** v3.5.0 documents the ephemeral-working-memory lifecycle in `docs/workflow.md`, with pointers from `quickstart.md` and the README FAQ
- **[@mahdiit](https://github.com/mahdiit)** (Mahdi) - [Issue #201](https://github.com/OthmanAdi/planning-with-files/issues/201), [PR #207](https://github.com/OthmanAdi/planning-with-files/pull/207)
- Reported that Codex hooks failed on Windows with `hook exited with code 1`: the `.codex/hooks.json` commands were POSIX only, so the Windows command interpreter choked on `python3` (the Store alias), `2>/dev/null`, and the `|| true` success guard whose `true` is not a Windows command
- PR #207: after v3.4.1 still did not clear the hook errors on his machine, traced the remaining failure to the shell resolver preferring the WSL bash launcher over Git Bash whenever `usr\bin` was off PATH, and to `pwf-hook.cmd` losing the Python interpreter under Codex's reduced PATH; produced the fix with Codex Terra, having the resolver skip WSL launchers and adding a `PYTHON_BIN` override plus standard install-location probing to the launcher
- **Impact:** v3.4.1 adds per-hook `commandWindows` overrides, a `pwf-hook.cmd` launcher, a `run_sh.py` front door, and a git-bash resolver so all seven Codex hooks run on Windows; v3.5.1 closes the remaining WSL-launcher and Python-discovery gaps that PR #207 found
- **[@Dikshj](https://github.com/Dikshj)** (diksha) - [PR #193](https://github.com/OthmanAdi/planning-with-files/pull/193), closes [Issue #190](https://github.com/OthmanAdi/planning-with-files/issues/190)
- Added the `/plan-execute` approval command to the Pi extension: hooks previously activated the moment `task_plan.md` existed, so plan injection, pre-tool recitation, post-write reminders, and auto-continue could start while the user was still reviewing a draft plan
- The extension now stays passive (status line only) until the user approves the active plan; approval is scoped to the current session and plan path, resets on session lifecycle events, and `/plan-execute reset` returns to passive review mode
- A plan with a tampered SHA-256 attestation cannot be approved, and the runtime test suite plus Python docs guards cover the passive review flow
- **Impact:** Pi users can draft and review a plan without the extension pushing execution before they have confirmed it, closing the workflow gap reported in #190
- **[@2023Anita](https://github.com/2023Anita)** - [PR #180](https://github.com/OthmanAdi/planning-with-files/pull/180), [Issue #178](https://github.com/OthmanAdi/planning-with-files/issues/178)
- Made the Codex Stop hook non-blocking for incomplete plans: `.codex/hooks/stop.py` previously emitted `{"decision": "block"}` on the first stop while phases were still pending, which pushed the agent to continue into the next phase without the user asking
- Collapsed the conditional block path to a single advisory `systemMessage` and rewrote `.codex/hooks/stop.sh` to drop the imperative "continue working on the remaining phases" wording, keeping only the progress-sync reminder
@@ -275,6 +303,22 @@ These amazing people have contributed code, documentation, or significant improv
- Linked the upstream OpenAI page so the canonical key change was easy to verify
- **Impact:** v2.39.0 swaps the docs to `hooks = true` in four sites with an alias note, so new users get the canonical key while users on older configs are not pushed to migrate
- **[@Stephen-abc](https://github.com/Stephen-abc)** (Wang Jun) - [PR #187](https://github.com/OthmanAdi/planning-with-files/pull/187)
- Fixed `UnicodeDecodeError` across 15 test files by adding explicit `encoding="utf-8"` to `subprocess.run`/`Popen` calls, which fail on Windows accounts whose default codepage isn't UTF-8
- Corrected `docs/adal.md`, `docs/antigravity.md`, `docs/kilocode.md`, and `docs/openclaw.md`, which referenced IDE-specific source folders (`.adal/`, `.agent/`, `.kilocode/`) removed in v2.24.0, and fixed a `references/` versus `templates/` mislabel in antigravity.md
- **Impact:** v3.2.0 test suite is now robust on non-UTF-8-default Windows accounts, and four installation guides point at paths that actually exist
- **[@igorcosta](https://github.com/igorcosta)** (Igor Costa, Autohand) - [PR #192](https://github.com/OthmanAdi/planning-with-files/pull/192)
- Added `docs/autohand.md`, a setup guide for Autohand Code covering user-level and project-level Agent Skills installs
- Added Autohand Code to the README's supported-IDEs table
- **Impact:** planning-with-files now documents a supported install path for Autohand Code alongside the other 17+ platforms
- **[@Yigtwxx](https://github.com/Yigtwxx)** (Yiğit Erdoğan) - [PR #198](https://github.com/OthmanAdi/planning-with-files/pull/198), [PR #199](https://github.com/OthmanAdi/planning-with-files/pull/199), [Issue #197](https://github.com/OthmanAdi/planning-with-files/issues/197)
- Filed #197 after running the suite on GitHub-hosted runners from a fork: the repo had a 200+ test pytest suite and a 21-test Pi vitest suite that CONTRIBUTING.md asks contributors to run, but no workflow ran either; the only CI was the Tessl skill-prose review
- PR #199 added `.github/workflows/tests.yml`: pytest on `ubuntu-latest` and `windows-latest` (Python 3.12) plus the Pi extension vitest suite (Node 22), on every pull request and push to master, with `contents: read` permissions, `fail-fast: false`, and an explicit `sh`-on-PATH assertion so hook tests cannot silently skip
- PR #198 fixed the two latent test-portability failures the first hosted run exposed, test-side only: a `host_realpath()` helper that maps the resolver's Git Bash POSIX path back with `cygpath` before the containment comparison, and `skipUnless(os.name == "nt")` on two Windows-shaped path vectors that fail on POSIX where `Path.resolve()` treats `C:/...` as relative
- **Impact:** every PR now runs the real test suite on both Linux and Windows, catching the recurring Windows regression class (the v3.2.0 audit found `session-catchup.py` silently broken on Windows) at PR time instead of at release audit time
## Community Forks
These developers have created forks that extend the functionality:
@@ -299,6 +343,10 @@ Thank you to everyone who reported issues, provided feedback, and helped test fi
- [@st01cs](https://github.com/st01cs) - Issue #28 (Devis fork discussion)
- [@wqh17101](https://github.com/wqh17101) - Issue #11 testing and confirmation
- [@luyanfeng](https://github.com/luyanfeng) - Issue #172 (OpenCode install/verify paths doubled the folder segment in docs/opencode.md; fixed in v2.43.0)
- [@mixian939](https://github.com/mixian939) - Issue #191 (Codex hooks reporting a false "0/0 phases complete" status for an unstructured task_plan.md, with a full root-cause diagnosis and suggested fix; the audit this triggered found and fixed the same defect in the canonical scripts and two other IDE adapters, fixed in v3.2.0)
- [@AvitalAviv](https://github.com/AvitalAviv) - Issue #188 (flagged that the repo had no private vulnerability disclosure channel; private vulnerability reporting is now enabled and documented in SECURITY.md)
- [@lazyst](https://github.com/lazyst) - Issue #190 (feature request describing the Pi extension activating hooks on a draft plan before user confirmation, with the exact passive-until-confirmed behavior that shipped as `/plan-execute` in v3.3.0)
- [@marcmuon](https://github.com/marcmuon) (Marc Kelechava) - Issue #195 (one-shot `codex exec` sessions sharing a cwd with an incomplete plan got hijacked and mutated orchestrator-owned plan files; the report's reproductions, root-cause file list, and acceptance criteria shipped directly as the `PLANNING_DISABLED=1` opt-out in v3.4.0)
And many others who have starred, forked, and shared this project!
@@ -326,6 +374,6 @@ If you've contributed and don't see your name here, please open an issue! We wan
---
**Total Contributors:** 46+ and growing!
**Total Contributors:** 50+ and growing!
*Last updated: June 13, 2026*
*Last updated: July 14, 2026*
+90 -5
View File
@@ -12,12 +12,13 @@
[![Benchmark](https://img.shields.io/badge/Benchmark-96.7%25_pass_rate_(v2.21.0%2C_sonnet--4--6)-brightgreen)](docs/evals.md)
[![A/B Verified](https://img.shields.io/badge/A%2FB_Blind-3%2F3_wins-brightgreen)](docs/evals.md)
[![Competitive Benchmark](https://img.shields.io/badge/Benchmark_v1-2.7x_faster_recovery_vs_6_methods-brightgreen)](docs/evals.md#test-5-competitive-benchmark-v1-seven-planning-methods-head-to-head-2026-07-06-internal)
[![SkillCheck Validated](https://img.shields.io/badge/SkillCheck-Validated-4c1)](https://getskillcheck.com)
[![Security Verified](https://img.shields.io/badge/Security-Audited_%26_Fixed_v2.21.0-blue)](docs/evals.md)
[![Skills Playground](https://skillsplayground.com/badges/installs/othmanadi-planning-with-files-planning-with-files.svg)](https://skillsplayground.com/skills/othmanadi-planning-with-files-planning-with-files/)
[![Downloads](https://skill-history.com/badge/othmanadi/planning-with-files.svg)](https://skill-history.com/othmanadi/planning-with-files)
[![Version](https://img.shields.io/badge/version-3.1.2-brightgreen)](https://github.com/OthmanAdi/planning-with-files/releases)
[![Version](https://img.shields.io/badge/version-3.5.1-brightgreen)](https://github.com/OthmanAdi/planning-with-files/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Closed Issues](https://img.shields.io/github/issues-closed/OthmanAdi/planning-with-files?color=success)](https://github.com/OthmanAdi/planning-with-files/issues?q=is%3Aissue+is%3Aclosed)
[![Closed PRs](https://img.shields.io/github/issues-pr-closed/OthmanAdi/planning-with-files?color=success)](https://github.com/OthmanAdi/planning-with-files/pulls?q=is%3Apr+is%3Aclosed)
@@ -73,10 +74,18 @@ See the full list of everyone who made this project better in [CONTRIBUTORS.md](
<details>
<summary><strong>📦 Releases & Session Recovery</strong></summary>
### Current Version: v3.1.2
### Current Version: v3.6.0
| Version | Highlights |
|---------|------------|
| **v3.6.0** | **Plan resolution and hook injection survive Windows-native coreutils, new `/plan-doctor` self-check.** A machine with a native Windows coreutils build on PATH (its `realpath` prints `C:\` backslash paths) failed the containment prefix match in `resolve-plan-dir.sh` and `inject-plan.sh`, so plan resolution and every hook injection went silently dark: no error, no plan context, 33 local test failures that Linux CI could never see. Canonical paths are now slash-normalized before comparison, the resolver canonicalizes root and candidate through the same cwd base (healing the 8.3 short-path mismatch the v3.2.0 fix left in the resolver), and per-candidate `grep` and `basename` forks were replaced with shell builtins: one hook fire measures 289ms on the machine that measured 2.0s at v3.4.0. New `/plan-doctor` command (`scripts/plan-doctor.sh`) reports resolution, injection, canonicalizer path shape, attestation state, install surfaces, and per-fire latency in one pass, because every failure in this class is silent by design. The long-standing local `test_ledger.py` hang on Windows resolved with the same fix; the full suite (217 tests) is green on Windows again. Install docs gained the route matrix (plugin vs npx, which surfaces each ships), the project-trust prerequisite, and a belt-and-suspenders CLAUDE.md trigger line from the July 2026 benchmark backlog. |
| **v3.5.1** | Codex Windows shell resolver skips WSL bash launchers, `pwf-hook.cmd` hardens Python discovery, and Pi recitations are delivered as `nextTurn` so interactive tools are not broken. |
| **v3.5.0** | **Codex Windows hooks emit valid JSON and survive Unicode (PR #205 by @yolo0731, closes #204), the Pi extension stops re-nagging closed and complete plans (#203 by @ziyu4huang), and the plan lifecycle is documented (#202 by @kcinzgg).** On Windows the Codex front door forwarded plain stdout where Codex expects `hookSpecificOutput.additionalContext` or PreCompact JSON, and UTF-8 broke on both decode and encode; the fix serializes each event in its supported shape with ASCII-safe output, decodes shell output as UTF-8, and writes the `.active_plan` pointer without a BOM, with the containment resolver hardened to fail closed (audited clean before merge). The Pi `resolveNewestPlanDir` now ranks by `task_plan.md` file mtime instead of directory mtime and honors the close marker, so a finished plan no longer loses to an incomplete sibling. Four language commands that referenced a non-existent skill namespace were corrected, a Traditional Chinese `/plan-zht` command was added, and the README now documents the full v3 command, hook, and mode surface that had shipped but was never listed. |
| **v3.4.1** | **Codex hooks now run on Windows (closes #201, reported by @mahdiit).** The `.codex/hooks.json` commands were POSIX only, so on Windows the native command interpreter choked on `python3` (the Store alias), `2>/dev/null`, and the `\|\| true` success guard whose `true` is not a Windows command, and every hook reported `hook exited with code 1`. Each hook now carries a per-hook `commandWindows` override (the POSIX `command` is untouched, so macOS and Linux are unchanged) that routes through a new `pwf-hook.cmd` launcher (real Python via `py -3`, falling back to `python`, never the Store `python3` alias) and, for the three shell hooks, a new `run_sh.py` front door. `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. Codex hook suites green (17 tests) plus new cross-platform and Windows-only coverage. |
| **v3.4.0** | **`PLANNING_DISABLED=1` per-invocation opt-out for one-shot sessions (closes #195, reported by @marcmuon).** One-shot `codex exec` runs (CI review bots, read-only research agents, nested orchestrators) that merely shared a cwd with an incomplete plan were hijacked: plan context injected, the actual output redirected into `progress.md`, a fabricated completed phase appended to `task_plan.md`. All Codex hook entry points, the Python adapter route, and the canonical dispatchers (`inject-plan.sh`, `gate-stop.sh`, `check-complete.sh`/`.ps1`) now exit before reading the plan when `PLANNING_DISABLED=1` is set; PreToolUse keeps emitting its allow decision so tool calls proceed. The guard ships in every distributed copy including the five language variants. Also corrected `docs/codex.md`'s stale claim that the Stop hook blocks (non-blocking since v3.1.0). Suite at 200 passed. |
| **v3.3.0** | **Pi extension hooks now wait for explicit user approval via `/plan-execute`** (PR #193 by @Dikshj, closes #190, requested by @lazyst). The Pi extension previously activated all hooks as soon as `task_plan.md` existed, so plan injection, pre-tool recitation, post-write reminders, and auto-continue could start while the user was still reviewing a draft plan. The extension now stays passive with a status line until the active plan is approved with `/plan-execute`; approval is scoped to the current session and plan path, resets on session lifecycle events, and `/plan-execute reset` returns to passive review. A plan with a tampered SHA-256 attestation cannot be approved. Suite at 188 passed plus 21 Pi extension vitest tests. |
| **v3.2.0** | **Repository health audit: session-catchup.py was non-functional on Windows, plus the "0/0 phases" false status (closes #191, addresses #103, closes #188).** `session-catchup.py`, the mechanism behind "resume after `/clear`," never sanitized Windows-style paths correctly and had no explicit encoding on three reads, so it silently did nothing on Windows with no error. `inject-plan.sh`'s containment guard also silently dropped plan injection and tamper detection under 8.3 short-name or `/tmp`-aliased paths. Both fixed. Separately, `check-complete.sh`/`.ps1` and three IDE-specific Stop hooks reported a false "0/0 phases complete" for any `task_plan.md` without `### Phase` headings (#191, reported by @mixian939); fixed everywhere the pattern appeared, canonical scripts included. The `--template analytics` flag (v2.29.0) was silently falling back to default templates in every installed skill package; the analytics templates now actually ship where `init-session.sh` reads them. Merged PR #187 (@Stephen-abc: Windows test-encoding fixes, stale install-path docs) and PR #192 (@igorcosta: Autohand Code setup docs). Added `SECURITY.md` and enabled private vulnerability reporting (@AvitalAviv). Corrected `AGENTS.md`'s contributor-PR-merge guidance, which told agents to squash-merge in a way that reassigns authorship. Suite at 186 passed, 5 skipped, 0 failed. |
| **v3.1.3** | **Hotfix: SKILL.md frontmatter was invalid YAML in v3.1.2.** The v3.1.2 description refresh added a colon, and the English SKILL.md keep `description` unquoted, so YAML rejected the frontmatter ("mapping values are not allowed here"), which could break skill loading and the model-triggering description. v3.1.3 quotes the description (matching the already-quoted translated variants; the parsed value is identical) across the canonical file and the seven English IDE variants, and adds `tests/test_skill_frontmatter_valid.py` to validate every SKILL.md frontmatter as YAML. Suite at 184 passed. |
| **v3.1.2** | **Session-catchup command works outside the plugin runtime, `.hermes` parity, refreshed skill description** (PR #186 by @shunfeng8421 closes #185, reported by @xwang118). The documented Restore Context command used `${CLAUDE_PLUGIN_ROOT}`, which the plugin runtime sets only inside hook execution, so a skill-only install (`npx skills add`, Codex, Cursor) running it in a shell got an empty variable and a broken `/scripts/...` path. Now `SKILL_DIR="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude/skills/planning-with-files}"` on the canonical file, `.codebuddy`, and the five language variants; the `.hermes` variant got the same fallback for `$HERMES_HOME`. The eight English SKILL.md descriptions were refreshed to lead with planning for AI coding agents and context-loss survival, with the `Use when` trigger and translated variants unchanged. Documentation only; suite at 180 passed. |
| **v3.1.1** | **Codex verification command checks the canonical `hooks` feature flag** (PR #184 by @Fat-Jan). The `docs/codex.md` verify block ran `codex features list \| rg '^codex_hooks\s'`, but Codex moved its canonical feature key from `codex_hooks` to `hooks` in 0.129.0 (openai/codex#20522). The alias still resolves in config, yet `codex features list` prints only `hooks`, so the old pattern matched nothing on current Codex and routed correctly configured users to the upgrade path. The command now greps `^(hooks\|codex_hooks)\s` and the troubleshooting sentence covers both names, matching the `hooks = true` guidance carried in the file since v2.39.0. Documentation only; suite at 180 passed. |
| **v3.1.0** | **Codex Stop hook no longer blocks on an incomplete plan, native Codex PreCompact parity, Pi extension test suite, and accurate SHA-cache docs** (PR #180 by @2023Anita closes #178, PR #181 by @GongYuanCaiJi, PR #175 and PR #174 by @mvanhorn close #163 and #164). The `.codex` Stop adapter dropped the `{"decision":"block"}` path that pushed Codex agents to auto-continue unfinished phases; it now emits an advisory progress-sync reminder only, matching the v3 principle that an incomplete plan alone never blocks a stop. The native `.codex/hooks.json` route gained the PreCompact hook (`pre-compact.sh`) it was missing relative to the canonical SKILL.md, dormant on runtimes that never fire the event. The Pi extension gained a TypeScript integration suite covering all eight lifecycle handlers, the four runtime modes, and the attestation gate. `docs/perf-notes.md` documents the attestation SHA cache, corrected to the v3 `$XDG_CACHE_HOME/pwf-sha` location. Suite at 180 passed. |
@@ -147,7 +156,7 @@ When your context fills up and you run `/clear`, this skill **automatically reco
</details>
<details>
<summary><strong>🛠️ Supported IDEs (17+ Platforms)</strong></summary>
<summary><strong>🛠️ Supported IDEs (18+ Platforms)</strong></summary>
#### Enhanced Support (hooks + lifecycle automation)
@@ -176,6 +185,7 @@ These IDEs implement the [Agent Skills](https://agentskills.io) open specificati
| Continue | [Continue Setup](docs/continue.md) | `.continue/skills/` + [.prompt files](https://docs.continue.dev/customize/deep-dives/prompts) |
| Pi Agent | [Pi Agent Setup](docs/pi-agent.md) | `.pi/skills/` ([npm package](https://www.npmjs.com/package/@mariozechner/pi-coding-agent)) |
| OpenClaw | [OpenClaw Setup](docs/openclaw.md) | `.openclaw/skills/` ([docs](https://docs.openclaw.ai/tools/skills)) |
| Autohand Code | [Autohand Code Setup](docs/autohand.md) | `~/.autohand/skills/` or `.autohand/skills/` |
| Antigravity | [Antigravity Setup](docs/antigravity.md) | `.agent/skills/` ([docs](https://codelabs.developers.google.com/getting-started-with-antigravity-skills)) |
| Kilocode | [Kilocode Setup](docs/kilocode.md) | `.kilocode/skills/` ([docs](https://kilo.ai/docs/agent-behavior/skills)) |
| AdaL CLI (Sylph AI) | [AdaL Setup](docs/adal.md) | `.adal/skills/` ([docs](https://docs.sylph.ai/features/plugins-and-skills)) |
@@ -249,7 +259,7 @@ npx skills add OthmanAdi/planning-with-files --skill planning-with-files-zht -g
</details>
Works with Claude Code, Cursor, Codex, Gemini CLI, and 40+ agents supporting the [Agent Skills](https://agentskills.io) spec.
Works with Claude Code, Cursor, Codex, Gemini CLI, Autohand Code, and 40+ agents supporting the [Agent Skills](https://agentskills.io) spec.
<details>
<summary><strong>🔧 Claude Code Plugin (Advanced Features)</strong></summary>
@@ -270,6 +280,13 @@ That's it! Now use one of these commands in Claude Code:
| `/planning-with-files:plan` | Type `/plan` | Start planning session (v2.11.0+) |
| `/planning-with-files:status` | Type `/plan:status` | Show planning progress at a glance (v2.15.0+) |
| `/planning-with-files:start` | Type `/planning` | Original start command |
| `/planning-with-files:pwf` | Type `/pwf` | Short alias for `/plan`; supports `--autonomous` / `--gated` init (v3.0.0+) |
| `/planning-with-files:plan-goal` | Type `/plan-goal` | Run until the plan reports complete by composing with Claude Code `/goal` (v2.38.0+) |
| `/planning-with-files:plan-loop` | Type `/plan-loop` | Planning-aware cadence on Claude Code `/loop`, default 10m tick (v2.38.0+) |
| `/planning-with-files:plan-attest` | Type `/plan-attest` | Lock `task_plan.md` with a SHA-256 attestation; `--show` / `--clear` (v2.37.0+) |
| `/planning-with-files:plan-de` | Type `/plan-de` | Start planning in German (also `-ar`, `-es`, `-zh`, `-zht`) (v2.33.0+) |
Typing `/plan` prefix-matches every `plan*` command in autocomplete; `/planning-with-files:status` autocompletes as `/status` (the older `/plan:status` label predates the rename). On the Pi extension the status command is named `plan-status`.
**Alternative:** If you want `/planning-with-files` (without prefix), copy skills to your local folder:
@@ -285,6 +302,24 @@ Copy-Item -Recurse -Path "$env:USERPROFILE\.claude\plugins\cache\planning-with-f
See [docs/installation.md](docs/installation.md) for all installation methods.
### Pi extension commands
Install the Pi extension with `pi install npm:@tomxprime/planning-with-files`; it registers these commands, and on Pi they are typed with no `/planning-with-files:` prefix.
| Command | What it does | Version |
|---------|--------------|---------|
| `/plan-execute` | Pi only. Approve the active plan to ACTIVATE all Pi hooks; hooks stay passive until you run this; `reset` returns to passive review | v3.3.0+ |
| `/plan-status` | Active plan path, scope, and phase totals | v2.39.0+ |
| `/plan-goal <text\|default\|clear>` | Set or clear the goal string appended to auto-continue prompts | v2.39.0+ |
| `/plan-loop [interval] [prompt\|stop]` | Start or stop a planning tick (default 10m) that re-reads the plan and nudges progress | v2.39.0+ |
| `/plan-attest [--show\|--clear]` | Run the attest-plan helper; shares the `.attestation` file with Claude Code | v2.39.0+ |
On Pi there is no `/plan` command to create the files; the skill creates them, then `/plan-execute` approves and activates the hooks. Note that Pi `plan-goal`/`plan-loop` run their own logic, while the Claude Code commands of the same name forward to native `/goal` and `/loop`.
### Command names vs skill names
The command you TYPE differs by platform. Claude Code uses `/planning-with-files:<verb>` (for example `/plan`, `/plan-de`, `/plan-goal`, `/plan-attest`, `/pwf`, `/status`). Pi uses the bare form (`/plan-status`, `/plan-execute`, `/plan-goal`, `/plan-loop`, `/plan-attest`), no prefix. Continue.dev uses `/planning-with-files`. The underlying model-invocable SKILLS are named `planning-with-files:planning-with-files` (and `-de`, `-es`, `-ar`, `-zh`, `-zht`); that doubled form is the skill id, not a command you type. There is no `/pwf-de` and no `/planning-with-files:planning-with-files-goal`; `/pwf` is just a short alias for `/plan`.
## Why This Skill?
On December 29, 2025, [Meta acquired Manus for $2 billion](https://techcrunch.com/2025/12/29/meta-just-bought-manus-an-ai-startup-everyone-has-been-talking-about/). In just 8 months, Manus went from launch to $100M+ revenue. Their secret? **Context engineering**.
@@ -344,11 +379,46 @@ Once installed, the AI agent will:
Invoke with:
- `/planning-with-files:plan` - Type `/plan` to find in autocomplete (v2.11.0+)
- `/planning-with-files:pwf` - Type `/pwf`, short alias for `/plan` (v3.0.0+)
- `/planning-with-files:start` - Type `/planning` to find in autocomplete
- `/planning-with-files` - Only if you copied skills to `~/.claude/skills/`
For the goal, loop, attest, language, and Pi commands, see the command tables above.
See [docs/quickstart.md](docs/quickstart.md) for the full 5-step guide.
## v3 Long-Running Agent Features
The v3 line adds features aimed at long-running agentic runs. Each one is listed with the command or flag that turns it on. With no mode marker set, the hooks produce the same output as v2.43, so nothing changes for existing setups.
- **Autonomous mode** (`/pwf --autonomous`, or `init-session.sh --autonomous`): drops the per-tool-call plan recitation, keeps the turn-start injection, and turns attestation on by default.
- **Gated mode** (`--gated`): adds a Stop completion gate that blocks only when all completion conditions hold at once, so an incomplete plan alone never traps a session.
- **Auto-continue on Pi** (`agent_end` handler): re-prompts the agent up to a limit of 3 to keep an unfinished plan moving, plus an optional `/plan-goal` string appended to the prompt.
- **Pi approval gate** (`/plan-execute`): Pi hooks stay passive with a status line until you approve the active plan for the current session.
- **Session-catchup**: resumes work after `/clear` by re-reading the planning files from the active IDE's session store.
- **PreCompact progress flush** (`PreCompact` hook): surfaces a reminder to flush progress before compaction completes, and prints the active Plan-SHA256 when attested.
- **SHA-256 plan attestation** (`/plan-attest`): locks `task_plan.md`; a tampered plan body is refused at injection.
- **Run ledger**: an append-only JSONL record of phase transitions that replaces the raw `progress.md` tail in v3 modes with a fixed-shape summary.
- **Host capability tiers**: hard block on Claude Code, Codex, and Continue; follow-up injection on Cursor, Pi, and Kiro; notify-only elsewhere.
- **Per-invocation opt-out** (`PLANNING_DISABLED=1`, v3.4.0): a one-shot session that merely shares a cwd with an incomplete plan skips all plan reading at every hook entry point.
### Hooks and modes reference
| Platform | Lifecycle hooks | Where registered |
|----------|-----------------|------------------|
| Claude Code | 5: UserPromptSubmit, PreToolUse, PostToolUse, Stop, PreCompact | The skill's `SKILL.md` frontmatter (not `plugin.json`), so they ship with the bundled skill |
| Codex CLI | 7: SessionStart, UserPromptSubmit, PreToolUse, PermissionRequest, PostToolUse, PreCompact, Stop | `.codex/hooks.json`, Windows-safe via `commandWindows` since v3.4.1 |
| Pi | 8 lifecycle handlers in the bundled extension | The injection and recitation handlers stay passive until `/plan-execute` |
Pi runtime modes:
| Pi mode | Behavior |
|---------|----------|
| `auto` | Detects the model and picks `parity` or `cache-safe` |
| `parity` | Full plan injection, mirrors the Claude Code skill |
| `cache-safe` | A stable reminder instead of full injection, for KV-cache-sensitive models like DeepSeek |
| `notify` | Status-line only, no model injection |
## Benchmark Results
> **Methodology note:** the 96.7% figure comes from the v2.21.0 evaluation run on `claude-sonnet-4-6` (2026-03-06). It measures file-pattern fidelity (does the agent create and maintain the 3-file structure), not goal-drift over long autonomous runs. Newer models and the autonomous-mode work are not yet covered by this number. Full methodology, dataset, and assertion list: [docs/evals.md](docs/evals.md).
@@ -390,9 +460,16 @@ Formally evaluated using Anthropic's [skill-creator](https://github.com/anthropi
planning-with-files/
├── commands/ # Plugin commands
│ ├── plan.md # /planning-with-files:plan command (v2.11.0+)
│ ├── pwf.md # /planning-with-files:pwf short alias for /plan (v3.0.0+)
│ ├── status.md # /planning-with-files:status progress view (v2.15.0+)
│ ├── plan-goal.md # /planning-with-files:plan-goal, composes with /goal (v2.38.0+)
│ ├── plan-loop.md # /planning-with-files:plan-loop, composes with /loop (v2.38.0+)
│ ├── plan-attest.md # /planning-with-files:plan-attest SHA-256 lock (v2.37.0+)
│ ├── plan-ar.md # Arabic /plan command (v2.33.0+)
│ ├── plan-de.md # German /plan command (v2.33.0+)
│ ├── plan-es.md # Spanish /plan command (v2.33.0+)
│ ├── plan-zh.md # Chinese Simplified /plan command (v2.33.0+)
│ ├── plan-zht.md # Chinese Traditional /plan command (v2.33.0+)
│ └── start.md # /planning-with-files:start command
├── templates/ # Root-level templates (for CLAUDE_PLUGIN_ROOT)
├── scripts/ # Root-level scripts (for CLAUDE_PLUGIN_ROOT)
@@ -496,7 +573,15 @@ Context rot is the drift that sets in as the context window fills and earlier in
### Which coding agents does this work with?
Claude Code, OpenAI Codex CLI, Cursor, GitHub Copilot, Kiro, OpenCode, Continue, Pi, CodeBuddy, Factory, Mastra, and 60+ others via the SKILL.md open standard. Installation is one command; see [Quick Install](#quick-install) above.
Claude Code, OpenAI Codex CLI, Cursor, GitHub Copilot, Kiro, OpenCode, Continue, Pi, CodeBuddy, Factory, Mastra, and 70+ others via the SKILL.md open standard (the `npx skills` installer alone targets 71 agents, and tools like Zed, Amp, Warp, and Devin read the standard `.agents/skills/` layout natively). Installation is one command; see [Quick Install](#quick-install) above.
### How does this work with Claude Code's plan mode?
They are complementary stages, not alternatives. Plan mode designs the approach before execution and leaves an approved strategy in the transcript. planning-with-files persists execution state on disk while the work runs. The handoff is one step: after accepting a plan-mode plan, tell the agent to write the accepted plan into `task_plan.md` as phases (or invoke `/plan` and let the skill create the files from it), then execute in normal mode. From that point the hooks keep the phases in the attention window, and the files survive `/clear`, compaction, and session death, which transcript-bound plan-mode output does not.
### What happens to the plan files after a task is complete?
They are working memory, not a tracked deliverable. `task_plan.md`, `findings.md`, `progress.md`, and the `.planning/` directory are gitignored by default and are not archived automatically: the next task overwrites the root plan, and a slug directory just stops being active. Anything worth keeping should be promoted into code, a commit, or a doc. See [After Completion: What Happens to the Plan Files](docs/workflow.md#after-completion-what-happens-to-the-plan-files) for the full lifecycle and how to retain a completed plan. This is a deliberate default, not a missing feature; a completion-triggered archive step is a welcome opt-in extension.
## Documentation
+32
View File
@@ -0,0 +1,32 @@
# Security Policy
## Reporting a Vulnerability
Report suspected vulnerabilities privately through [GitHub Private Vulnerability Reporting](https://github.com/OthmanAdi/planning-with-files/security/advisories/new) (Security tab -> Report a vulnerability). This opens a private advisory visible only to the maintainer until a fix ships.
Include what you'd include in a normal bug report, plus:
- The affected hook, script, or file (e.g. `scripts/inject-plan.sh`, a specific IDE's `hooks/stop.sh`).
- The data flow: what untrusted input reaches what trusted context.
- Reproduction steps.
- Suggested mitigation, if you have one.
Do not open a public issue for a suspected vulnerability before it's been triaged privately.
## Scope
This repository ships markdown-based planning templates plus shell/PowerShell/Python hook scripts that several AI coding agents (Claude Code, Codex, Cursor, GitHub Copilot, and others) execute automatically during a session. Relevant security concerns include:
- Prompt injection via plan file content reaching model context (see the delimiter and attestation mechanisms in `scripts/inject-plan.sh` and `scripts/attest-plan.sh`).
- Path traversal or symlink escapes in plan directory resolution (`scripts/resolve-plan-dir.sh`, `scripts/inject-plan.sh`'s containment guard).
- Supply-chain concerns in any dependency, install script, or bin shim (this repo currently has none; flag if that changes).
Not in scope: vulnerabilities in the AI agents/IDEs themselves (Claude Code, Codex, etc.) — report those to their respective maintainers.
## Response
Expect an initial response within a few days. Confirmed fixes ship as a patch release with a CHANGELOG `### Security` entry; credit is given in `CONTRIBUTORS.md` unless you ask to stay anonymous.
## Supported Versions
Only the latest released version is supported. Update to the current release before reporting if you're on an older one.
+1 -1
View File
@@ -2,7 +2,7 @@
description: "بدء تخطيط الملفات بنمط Manus. إنشاء task_plan.md و findings.md و progress.md للمهام المعقدة."
---
استدعاء مهارة planning-with-files-ar:planning-with-files-ar وتنفيذ التعليمات بدقة.
استدعاء مهارة planning-with-files:planning-with-files-ar وتنفيذ التعليمات بدقة.
إذا لم تكن ملفات التخطيط الثلاثة موجودة في مجلد المشروع الحالي، قم بإنشائها:
- task_plan.md — للمراحل والتقدم والقرارات
+1 -1
View File
@@ -2,7 +2,7 @@
description: "Starte Manus-artige Dateiplanung. Erstelle task_plan.md, findings.md, progress.md für komplexe Aufgaben."
---
Rufe die planning-with-files-de:planning-with-files-de Skill auf und folge den Anweisungen genau.
Rufe die planning-with-files:planning-with-files-de Skill auf und folge den Anweisungen genau.
Wenn die drei Planungsdateien nicht im aktuellen Projektverzeichnis existieren, erstelle sie:
- task_plan.md — für Phasen, Fortschritt und Entscheidungen
+20
View File
@@ -0,0 +1,20 @@
---
description: "Self-check for the planning-with-files mechanisms that fail silently: plan resolution, hook injection, canonicalizer path shape, attestation state, install surfaces, and per-fire hook latency. Run it whenever hooks seem quiet or after installing on a new machine. Available since v3.6.0."
disable-model-invocation: true
allowed-tools: "Bash"
---
Run the planning-with-files self-check against the current project directory.
Steps:
1. Run the doctor script from the project root:
- Linux/macOS/Git Bash: `sh ${CLAUDE_PLUGIN_ROOT}/scripts/plan-doctor.sh`
- Windows without Git Bash on PATH: locate Git Bash via `git.exe` (its `usr\bin\sh.exe` sibling) and run the same script with it.
2. Report the PASS/WARN/FAIL lines to the user verbatim — they are designed to be read as-is.
3. If any line reports FAIL, explain the matching remediation:
- resolver FAIL → inspect `.planning/.active_plan` content and plan dir names.
- injection FAIL with a resolving plan → the hooks are silently dark; upgrading past v3.6.0 fixes the Windows-native-coreutils realpath cause. Also check `PLANNING_DISABLED`.
- tamper WARN → re-approve with `/plan-attest` if the plan edit was intentional.
4. Do not attempt automatic fixes; this command is diagnostic only.
Why this exists: the mechanisms this skill relies on (hook injection, plan resolution) exit 0 and stay silent by design when something is off, so a broken install looks identical to "no plan yet". The doctor makes the difference visible in one pass.
+1 -1
View File
@@ -2,7 +2,7 @@
description: "Iniciar planificación de archivos estilo Manus. Crear task_plan.md, findings.md, progress.md para tareas complejas."
---
Invocar la habilidad planning-with-files-es:planning-with-files-es y ejecutar sus instrucciones estrictamente.
Invocar la habilidad planning-with-files:planning-with-files-es y ejecutar sus instrucciones estrictamente.
Si los tres archivos de planificación no existen en el directorio del proyecto actual, créalos:
- task_plan.md — para fases, progreso y decisiones
+1 -1
View File
@@ -2,7 +2,7 @@
description: "启动 Manus 风格的文件规划。为复杂任务创建 task_plan.md、findings.md、progress.md。"
---
调用 planning-with-files-zh:planning-with-files-zh 技能,并严格按照其指示执行。
调用 planning-with-files:planning-with-files-zh 技能,并严格按照其指示执行。
如果当前项目目录中不存在以下三个规划文件,请创建它们:
- task_plan.md — 用于阶段、进度和决策
+12
View File
@@ -0,0 +1,12 @@
---
description: "啟動 Manus 風格的檔案規劃。為複雜任務建立 task_plan.md、findings.md、progress.md。"
---
調用 planning-with-files:planning-with-files-zht 技能,並嚴格按照其指示執行。
如果當前專案目錄中不存在以下三個規劃檔案,請建立它們:
- task_plan.md — 用於階段、進度和決策
- findings.md — 用於研究和發現
- progress.md — 用於工作階段日誌
然後引導使用者完成規劃工作流程。所有規劃檔案內容使用繁體中文。
+6 -3
View File
@@ -29,21 +29,24 @@ How to use planning-with-files with AdaL CLI (Sylph AI).
```bash
git clone https://github.com/OthmanAdi/planning-with-files.git
cp -r planning-with-files/.adal/skills/planning-with-files ~/.adal/skills/
mkdir -p ~/.adal/skills
cp -r planning-with-files/skills/planning-with-files ~/.adal/skills/
```
### Option 3: Copy to project skills directory
```bash
git clone https://github.com/OthmanAdi/planning-with-files.git
cp -r planning-with-files/.adal/skills/planning-with-files .adal/skills/
mkdir -p .adal/skills
cp -r planning-with-files/skills/planning-with-files .adal/skills/
```
### Option 4: Windows (PowerShell)
```powershell
git clone https://github.com/OthmanAdi/planning-with-files.git
Copy-Item -Recurse -Path "planning-with-files\.adal\skills\planning-with-files" -Destination "$env:USERPROFILE\.adal\skills\"
New-Item -ItemType Directory -Force -Path $env:USERPROFILE\.adal\skills
Copy-Item -Recurse -Path "planning-with-files\skills\planning-with-files" -Destination "$env:USERPROFILE\.adal\skills\"
```
---
+9 -8
View File
@@ -16,8 +16,9 @@ Share the skill with your entire team by adding it to your repository:
# In your project repository
git clone https://github.com/OthmanAdi/planning-with-files.git /tmp/planning-with-files
# Copy the Antigravity skill to your repo
cp -r /tmp/planning-with-files/.agent .
# Copy the planning-with-files skill to your repo
mkdir -p .agent/skills
cp -r /tmp/planning-with-files/skills/planning-with-files .agent/skills/
# Commit to share with team
git add .agent/
@@ -40,7 +41,7 @@ git clone https://github.com/OthmanAdi/planning-with-files.git /tmp/planning-wit
# Copy to your personal Antigravity skills folder
mkdir -p ~/.gemini/antigravity/skills
cp -r /tmp/planning-with-files/.agent/skills/planning-with-files ~/.gemini/antigravity/skills/
cp -r /tmp/planning-with-files/skills/planning-with-files ~/.gemini/antigravity/skills/
# Clean up
rm -rf /tmp/planning-with-files
@@ -97,7 +98,7 @@ Once activated, the skill creates:
### Templates
The skill includes starter templates in `.agent/skills/planning-with-files/references/`:
The skill includes starter templates in `.agent/skills/planning-with-files/templates/`:
- `task_plan.md` — Phase tracking template
- `findings.md` — Research storage template
- `progress.md` — Session logging template
@@ -208,9 +209,9 @@ Check:
### Need Templates?
Templates are in:
- **Workspace:** `.agent/skills/planning-with-files/references/`
- **Personal:** `~/.gemini/antigravity/skills/planning-with-files/references/`
Check:
- **Workspace:** `.agent/skills/planning-with-files/templates/`
- **Personal:** `~/.gemini/antigravity/skills/planning-with-files/templates/`
Copy them to your project root and customize.
@@ -229,7 +230,7 @@ Edit `.agent/skills/planning-with-files/SKILL.md` to customize:
Place custom templates in:
```
.agent/skills/planning-with-files/references/
.agent/skills/planning-with-files/templates/
```
Antigravity will reference them automatically.

Some files were not shown because too many files have changed in this diff Show More