fix(#2752): make tool_input.path authoritative over model-controlled file_path in gsd-phase-boundary.sh (#2860)

* test(#2752): path is authoritative over model-controlled file_path in phase-boundary hook

Rewrite the #2304 precedence test (which pinned the buggy file_path-wins
behavior with an impossible no-tool_name {file_path,path} payload) to assert
the correct precedence with realistic Kimi-shaped payloads: a real .planning/
write with a decoy file_path must still fire the reminder (suppression repro),
and a write elsewhere with a decoy .planning/ file_path must NOT fabricate one
(fabrication repro). Update the parity vocabulary alarm to the new expression.

* fix(#2752): make tool_input.path authoritative over model-controlled file_path in gsd-phase-boundary.sh

The hook consulted file_path first, path second. kimi-cli executes on path and
sends path only; file_path on a Kimi payload is always model-supplied. So a
model-supplied decoy file_path could suppress the reminder for a real .planning/
write or fabricate one for a file never touched. Flip the precedence so path is
authoritative and file_path is the fallback (Claude Code emits file_path and no
path, so the fallback must remain). Mirrors the #2595 JS-guard fix.

* chore(#2752): changeset fragment

* chore(#2752): clarify comment/changeset — JS guards use upstream normalization, shell hook applies precedence directly (review minor 1)

* chore(#2752): backfill changeset PR number (2860)

---------

Co-authored-by: Test <test@example.com>
This commit is contained in:
Tom Boucher
2026-07-30 12:32:00 -04:00
committed by GitHub
parent aa19e3478c
commit 97e9fd50bd
4 changed files with 56 additions and 14 deletions

View File

@@ -22,7 +22,15 @@ INPUT=$(cat)
# and its file tools name the field `path`, not `file_path` (kimi-cli
# src/kimi_cli/tools/file/write.py + replace.py) — fall back to tool_input.path
# when file_path is absent, mirroring normalizeKimiPayload in the JS guards.
FILE=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d).tool_input||{};process.stdout.write(i.file_path||(typeof i.path==='string'?i.path:'')||'')}catch{}})" 2>/dev/null)
# #2752: `path` is AUTHORITATIVE (kimi-cli executes on it; it sends `path` only,
# never `file_path`). `file_path` is model-controlled on Kimi, so consulting it
# first let a model-supplied decoy suppress/fabricate the reminder. `path` wins,
# `file_path` is the fallback (Claude Code emits `file_path` and no `path`, so the
# fallback must remain). The JS guards reach the same "path authoritative" outcome
# via an upstream normalizeKimiPayload step (copies path→file_path before any guard
# reads); this shell hook parses tool_input once, raw, so it applies the precedence
# directly at the read site.
FILE=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d).tool_input||{};process.stdout.write((typeof i.path==='string'&&i.path)||(typeof i.file_path==='string'&&i.file_path)||'')}catch{}})" 2>/dev/null)
# Emit a structured JSON envelope (#2974). additionalContext carries the
# user-visible reminder text; the typed `planning_modified` boolean and