Compare commits

...
2 Commits
Author SHA1 Message Date
OthmanAdi 746f57a7c5 release: v3.18.3 2026-09-16 13:23:11 +02:00
OthmanAdi 0d41eeeddd fix: silence completed-plan Stop notices 2026-09-16 13:21:35 +02:00
45 changed files with 275 additions and 79 deletions
+2 -2
View File
@@ -28,7 +28,7 @@ hooks:
- type: command
command: "SH=\"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\"; [ -f \"$SH\" ] || SH=$(ls \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\" 2>/dev/null | head -1); [ -n \"$SH\" ] && [ -f \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
# Planning with Files
@@ -365,7 +365,7 @@ The mode is set by writing a `.mode` file next to the plan (`.planning/<id>/.mod
### The legacy invariant (promise)
With no `.mode` file and no other v3 marker, the hooks produce byte-identical output to v2.43, including the raw `progress.md` tail and the `===BEGIN PLAN DATA===` / `===END PLAN DATA===` delimiters. Every v3 behavior is additive and opt-in. No existing workflow changes.
With no `.mode` file and no other v3 marker, plan injection preserves the v2.43 output, including the raw `progress.md` tail and the `===BEGIN PLAN DATA===` / `===END PLAN DATA===` delimiters. Autonomous and gated behavior remains opt-in. Since v3.18.3, completed plans are silent through the shared Stop gate and Codex Stop hook. Explicit `check-complete.sh` or `check-complete.ps1` calls without the gate flag still report completion; incomplete-plan notices and gate decisions are unchanged.
### What each mode does
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
+1 -1
View File
@@ -10,7 +10,7 @@
"name": "planning-with-files",
"source": "./",
"description": "Persistent project planning files with lifecycle hooks that inject selected plan context. Automatic recovery uses project files only. Explicit catchup modes may read same-project local session records to emit aggregate counts or bounded nonce-framed excerpts. Optional gated mode can request continuation on capable hosts and never runs commands declared in Markdown. No network upload path. Installs across 60+ agents.",
"version": "3.18.2"
"version": "3.18.3"
}
]
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "planning-with-files",
"version": "3.18.2",
"version": "3.18.3",
"description": "Persistent project planning files with lifecycle hooks that inject selected plan context. Automatic recovery uses project files only. Explicit catchup modes may read same-project local session records to emit aggregate counts or bounded nonce-framed excerpts. Optional gated mode can request continuation on capable hosts and never runs commands declared in Markdown. No network upload path. Works across 60+ agents and includes Arabic, German, Spanish, Simplified Chinese, and Traditional Chinese.",
"author": {
"name": "OthmanAdi",
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"${CODEBUDDY_PLUGIN_ROOT}/scripts/skill-hook.sh\" \"$HOME/.codebuddy/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "planning-with-files",
"version": "3.18.2",
"version": "3.18.3",
"description": "Persistent project planning for Codex. Opt-in lifecycle hooks inject selected plan context; automatic recovery uses project files only. Explicit catchup modes may read same-project local session records to emit aggregate counts or bounded nonce-framed excerpts. Optional gated mode can request continuation on capable hosts and never runs Markdown-declared commands. No network upload path.",
"author": {
"name": "OthmanAdi",
+2 -3
View File
@@ -23,8 +23,8 @@ fi
# Codex supports a native Stop continuation decision. Delegate the decision to
# the existing v3 gate oracle so Codex and skill-frontmatter installs share the
# same opt-in mode, in_progress, recursion, cap, and stall guards. Outside a
# gated block the oracle is advisory, and the legacy message below remains the
# public output for backward compatibility.
# gated block the oracle is advisory. Incomplete plans retain their notice;
# completed plans need no followup message.
CHECK_COMPLETE="${HOOK_DIR}/../skills/planning-with-files/scripts/check-complete.sh"
if [ "${1:-}" != "--stop-hook-active" ] && [ -f "${CHECK_COMPLETE}" ]; then
GATE_OUTPUT="$(sh "${CHECK_COMPLETE}" --gate "${PLAN_FILE}")"
@@ -59,7 +59,6 @@ if [ "$TOTAL" -eq 0 ]; then
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
fi
+1 -1
View File
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.codex/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
+1 -1
View File
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.cursor/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
+1 -1
View File
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.factory/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
# Planning with Files
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
+1 -1
View File
@@ -2,7 +2,7 @@
name: planning-with-files
description: "Persistent file-based planning for multi-step AI-agent work. Keeps task_plan.md, findings.md, and progress.md on disk; lifecycle hooks inject selected project planning context. Automatic recovery reads project planning files only. Explicit session-catchup.py --metadata reads same-project local agent session records and emits aggregate counts only; --replay may emit bounded nonce-framed excerpts. Optional gated mode can request continuation only when the host supports it and never runs commands declared in Markdown. The skill has no network upload path. Use for research or work needing 5+ tool calls."
metadata:
version: "3.18.2"
version: "3.18.3"
hermes:
tags: [planning, long-running-tasks, context-engineering, workflow]
---
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.mastracode/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.config/opencode/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.opencode/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
+1 -1
View File
@@ -366,7 +366,7 @@ The mode is set by writing a `.mode` file next to the plan (`.planning/<id>/.mod
### The legacy invariant (promise)
With no `.mode` file and no other v3 marker, the hooks produce byte-identical output to v2.43, including the raw `progress.md` tail and the `===BEGIN PLAN DATA===` / `===END PLAN DATA===` delimiters. Every v3 behavior is additive and opt-in. No existing workflow changes.
With no `.mode` file and no other v3 marker, plan injection preserves the v2.43 output, including the raw `progress.md` tail and the `===BEGIN PLAN DATA===` / `===END PLAN DATA===` delimiters. Autonomous and gated behavior remains opt-in. Since v3.18.3, completed plans are silent through the shared Stop gate and Codex Stop hook. Explicit `check-complete.sh` or `check-complete.ps1` calls without the gate flag still report completion; incomplete-plan notices and gate decisions are unchanged.
### What each mode does
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "planning-with-files",
"version": "3.18.2",
"version": "3.18.3",
"description": "Persistent project planning with selected context injection. Automatic recovery uses project files only; explicit catchup modes read same-project local session records for aggregate counts or bounded replay. The host-aware gate never runs Markdown-declared commands. No network upload path. Ships the skill plus a Pi Coding Agent extension.",
"keywords": [
"pi-package",
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
+6
View File
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
## [3.18.3] - 2026-09-16
### Fixed
- Completed plans no longer emit routine completion notices through the shared Stop gate or Codex Stop hook. Claude Code plugin and standalone skill hooks inherit this behavior from the canonical shell checker.
- Preserve completion reports from explicit checker commands without the gate flag. Incomplete-plan notices, gate decisions, recursion protection, block caps, stall diagnostics and plan selection safeguards are unchanged. Shell and PowerShell fixes are synchronized across maintained mirrors.
## [3.18.2] - 2026-09-16
### Fixed
+1 -1
View File
@@ -7,7 +7,7 @@ authors:
- family-names: Adi
given-names: Ahmad Othman Ammar
alias: OthmanAdi
version: "3.18.2"
version: "3.18.3"
date-released: "2026-09-16"
license: MIT
url: "https://github.com/OthmanAdi/planning-with-files"
+1
View File
@@ -356,6 +356,7 @@ One hook fire measures 289ms wall-clock since the v3.6.0 optimization, down from
| Version | Highlights |
|---------|------------|
| **v3.18.3** | Silences completed-plan notices in shared Stop gates and Codex while preserving explicit reports and gate safeguards. |
| **v3.18.2** | Isolates Python in Codex, Gemini, and Copilot shell adapters and makes IDE sync verification fail on missing canonical sources (#244, #245). |
| **v3.18.1** | Fixes active-plan display and listing for UTF-8 BOM-prefixed pointers from Windows editors and PowerShell workflows across the canonical shell helpers (#243). |
| **v3.18.0** | Lists saved plans and phase counts with `--list` or PowerShell `-List` (#242). Supports the shipped translated templates, checks project containment, and delivers the canonical helpers across IDE bundles. |
+4 -2
View File
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
+6 -3
View File
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
+1 -1
View File
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files-ar/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
# نظام تخطيط الملفات
+1 -1
View File
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files-de/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
# Dateiplanungssystem
+1 -1
View File
@@ -36,7 +36,7 @@ hooks:
- type: command
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files-es/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
# Sistema de Planificación con Archivos
+1 -1
View File
@@ -37,7 +37,7 @@ hooks:
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files-zh/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
+1 -1
View File
@@ -37,7 +37,7 @@ hooks:
command: "SH=\"\"; for c in \"${PWF_SCRIPT_DIR}/skill-hook.sh\" \"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files-zht/scripts/skill-hook.sh\" \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\"; do [ -f \"$c\" ] && { SH=\"$c\"; break; }; done; [ -n \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
+2 -2
View File
@@ -28,7 +28,7 @@ hooks:
- type: command
command: "[ -n \"${CLAUDE_PLUGIN_ROOT:-}\" ] && exit 0; SH=\"${CLAUDE_SKILL_DIR}/scripts/skill-hook.sh\"; [ -f \"$SH\" ] || SH=$(ls \"$HOME/.claude/skills/planning-with-files/scripts/skill-hook.sh\" \"$HOME/.claude/plugins/marketplaces/planning-with-files/scripts/skill-hook.sh\" 2>/dev/null | head -1); [ -n \"$SH\" ] && [ -f \"$SH\" ] && sh \"$SH\" --event=precompact; exit 0"
metadata:
version: "3.18.2"
version: "3.18.3"
---
# Planning with Files
@@ -368,7 +368,7 @@ The mode is set by writing a `.mode` file next to the plan (`.planning/<id>/.mod
### The legacy invariant (promise)
With no `.mode` file and no other v3 marker, the hooks produce byte-identical output to v2.43, including the raw `progress.md` tail and the `===BEGIN PLAN DATA===` / `===END PLAN DATA===` delimiters. Every v3 behavior is additive and opt-in. No existing workflow changes.
With no `.mode` file and no other v3 marker, plan injection preserves the v2.43 output, including the raw `progress.md` tail and the `===BEGIN PLAN DATA===` / `===END PLAN DATA===` delimiters. Autonomous and gated behavior remains opt-in. Since v3.18.3, completed plans are silent through the shared Stop gate and Codex Stop hook. Explicit `check-complete.sh` or `check-complete.ps1` calls without the gate flag still report completion; incomplete-plan notices and gate decisions are unchanged.
### What each mode does
@@ -10,7 +10,8 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall -> allow stop)
# When all hold, emits a single-line block-decision JSON on stdout and exits 0.
# Otherwise advisory output and exit 0. Without -Gate, byte-equivalent to v2.43.
# Otherwise reports incomplete plans and exits 0; completed plans stay silent
# with -Gate, including legacy plans without .mode. Explicit reports are unchanged.
#
# Stdin: read only when input is redirected ([Console]::IsInputRedirected), so an
# interactive console never blocks. Hook-piped JSON is EOF-terminated.
@@ -85,9 +86,10 @@ if ($TOTAL -eq 0) {
exit 0
}
# advisory_report: the v2.43 status echo.
# Keep explicit reports, but omit routine success from automatic gate checks.
function Write-AdvisoryReport {
if ($COMPLETE -eq $TOTAL -and $TOTAL -gt 0) {
if ($Gate) { return }
Write-Host ('[planning-with-files] ALL PHASES COMPLETE (' + $COMPLETE + '/' + $TOTAL + '). If the user has additional work, add new phases to task_plan.md before starting.')
} else {
Write-Host ('[planning-with-files] Task in progress (' + $COMPLETE + '/' + $TOTAL + ' phases complete). Update progress.md before stopping.')
@@ -21,8 +21,9 @@
# 4. the block counter (<plan-dir>/.stop_blocks) is below cap (PWF_GATE_CAP, default 20)
# 5. the ledger advanced since the last block (stall → allow stop)
# When all hold, it emits a single-line block-decision JSON on stdout and
# exits 0. Otherwise it falls back to advisory output and exits 0.
# Without --gate, or in non-gated mode, behavior is byte-equivalent to v2.43.
# exits 0. Otherwise it reports incomplete plans and exits 0; completed
# plans stay silent in --gate mode, including legacy plans without .mode.
# Without --gate, the explicit advisory report is unchanged.
#
# Stdin handling: the Claude Code Stop hook pipes a JSON payload on stdin. To
# avoid hanging when nothing is piped, stdin is read ONLY when fd 0 is not a
@@ -119,9 +120,11 @@ if [ "$TOTAL" -eq 0 ]; then
exit 0
fi
# advisory_report: the v2.43 status echo. Always exit 0 after calling.
# Explicit status reports retain completion text. Automatic gate checks have
# nothing to report on success; keep evaluating all gate guards before here.
advisory_report() {
if [ "$COMPLETE" -eq "$TOTAL" ] && [ "$TOTAL" -gt 0 ]; then
[ "$GATE" -eq 1 ] && return 0
echo "[planning-with-files] ALL PHASES COMPLETE ($COMPLETE/$TOTAL). If the user has additional work, add new phases to task_plan.md before starting."
else
echo "[planning-with-files] Task in progress ($COMPLETE/$TOTAL phases complete). Update progress.md before stopping."
+2 -4
View File
@@ -174,16 +174,14 @@ class CodexPluginOperationsTests(unittest.TestCase):
self.assertNotIn("decision", payload)
self.assertIn("Task in progress", payload["systemMessage"])
def test_complete_gated_plan_allows_stop_with_advisory(self) -> None:
def test_complete_gated_plan_allows_stop_silently(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
root = Path(tmpdir)
self.write_plan(root, "complete", gated=True)
result = self.run_stop(root, stop_hook_active=False)
self.assertEqual(0, result.returncode, result.stderr)
payload = json.loads(result.stdout)
self.assertNotIn("decision", payload)
self.assertIn("ALL PHASES COMPLETE", payload["systemMessage"])
self.assertEqual("", result.stdout.strip())
def test_stop_is_silent_without_plan(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
+142
View File
@@ -0,0 +1,142 @@
"""Completed plans stay quiet in automatic Stop checks, not explicit reports."""
from __future__ import annotations
import json
import os
from pathlib import Path
import runpy
import shutil
import subprocess
import sys
import pytest
REPO = Path(__file__).resolve().parents[1]
SCRIPTS = REPO / "skills" / "planning-with-files" / "scripts"
CHECKERS = ("sh-checker", "ps-checker")
HOOKS = ("claude-plugin", "standalone-skill", "codex")
@pytest.fixture
def project(tmp_path):
root = tmp_path / "project with spaces"
root.mkdir()
env = {
key: value for key, value in os.environ.items()
if not key.startswith(("PWF_", "CLAUDE_", "CODEX_"))
and key not in {
"PLAN_ID", "PLANNING_DISABLED", "BASH_ENV", "ENV", "CDPATH",
"PYTHONPATH", "PYTHONHOME",
}
}
for variable, directory in {
"HOME": "home", "USERPROFILE": "home", "XDG_CACHE_HOME": "cache",
"XDG_CONFIG_HOME": "config", "TMPDIR": "tmp", "TMP": "tmp", "TEMP": "tmp",
}.items():
path = tmp_path / directory
path.mkdir(exist_ok=True)
env[variable] = str(path)
env.update({
"PWF_TRUSTED_PYTHON": str(Path(sys.executable).resolve()),
"CLAUDE_PLUGIN_ROOT": str(REPO),
"PYTHONUTF8": "1",
"PYTHONIOENCODING": "utf-8",
})
return root, env
def write_plan(root, *, gated, completed=True):
first = "complete" if completed else "in_progress"
(root / "task_plan.md").write_text(
f"# Task Plan\n### Phase 1: Work\n**Status:** {first}\n"
"### Phase 2: Verify\n**Status:** complete\n",
encoding="utf-8",
)
if gated:
(root / ".mode").write_text("autonomous gate\n", encoding="utf-8")
(root / "ledger-main.jsonl").write_text(
'{"tick":1,"event":"progress","summary":"verified"}\n', encoding="utf-8"
)
def file_state(root):
return {
path.relative_to(root).as_posix(): (path.read_bytes(), path.stat().st_mtime_ns)
for path in root.rglob("*") if path.is_file()
}
def command(route, env, *, gate=True):
if route == "ps-checker":
powershell = shutil.which("powershell.exe") or shutil.which("pwsh")
if not powershell:
pytest.skip("PowerShell is unavailable")
return [powershell, "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
"-File", str(SCRIPTS / "check-complete.ps1"), *(["-Gate"] if gate else [])]
if os.name == "nt":
adapter = runpy.run_path(str(REPO / ".codex" / "hooks" / "codex_hook_adapter.py"))
shell, path_dirs = adapter["_windows_git_bash"]()
env["PATH"] = os.pathsep.join([*path_dirs, env.get("PATH", "")])
else:
shell = shutil.which("sh")
if not shell:
pytest.skip("POSIX sh or Git for Windows sh.exe is unavailable")
scripts = {
"sh-checker": [str(SCRIPTS / "check-complete.sh"), *(["--gate"] if gate else [])],
"claude-plugin": [str(REPO / "hooks" / "claude-hook.sh"), "stop"],
"standalone-skill": [str(SCRIPTS / "skill-hook.sh"), "--event=stop"],
"codex": [str(REPO / ".codex" / "hooks" / "stop.sh")],
}
return [shell, *scripts[route]]
def invoke(route, project, *, gate=True):
root, environment = project
env = environment.copy()
result = subprocess.run(
command(route, env, gate=gate), cwd=root, env=env,
input=json.dumps({"session_id": "quiet-completion-test", "stop_hook_active": False}),
capture_output=True, text=True, encoding="utf-8", timeout=60, check=False,
)
assert result.returncode == 0, result.stderr
assert result.stderr == "", result.stderr
return result.stdout
@pytest.mark.parametrize("route", CHECKERS + HOOKS)
@pytest.mark.parametrize("gated", [False, True], ids=["legacy", "gated"])
def test_completed_automatic_checks_are_silent_and_preserve_state(project, route, gated):
root, _ = project
write_plan(root, gated=gated)
# A finished plan can retain earlier gate bookkeeping. Repeated Stop
# events must neither reset that state nor append new ledger activity.
(root / ".stop_blocks").write_text("2\n", encoding="utf-8")
(root / ".gate_last_ledger").write_text("1\n", encoding="utf-8")
before = file_state(root)
outputs = [invoke(route, project), invoke(route, project)]
assert file_state(root) == before
assert outputs == ["", ""]
@pytest.mark.parametrize("route", CHECKERS)
def test_explicit_completion_report_remains_available(project, route):
root, _ = project
write_plan(root, gated=True)
before = file_state(root)
output = invoke(route, project, gate=False)
assert "ALL PHASES COMPLETE (2/2)" in output
assert file_state(root) == before
@pytest.mark.parametrize("route", CHECKERS + HOOKS)
def test_incomplete_gated_plan_still_blocks_automatic_stop(project, route):
root, _ = project
write_plan(root, gated=True, completed=False)
ledger_before = (root / "ledger-main.jsonl").read_bytes()
output = json.loads(invoke(route, project))
assert output["decision"] == "block"
assert "Phase 1: Work" in output["reason"]
assert (root / ".stop_blocks").read_text(encoding="utf-8-sig").strip() == "1"
assert (root / "ledger-main.jsonl").read_bytes() == ledger_before