fix(hooks): state-line-guard ratchets against current size, not a flat gate
Advisor review caught this before the v0.23.0 tag landed: the guard compared the projected line count only against the fixed 60-line max, never against the file's current size, so trimming an already-oversized STATE.md (e.g. 156 -> 100 lines, still over 60 but smaller) was denied exactly like growing it would be. Verified against the real tree: 23 of the machine's STATE.md files are already over 60 lines today, one at 1405. Shipped as a flat gate, this hook would have made most of them un-editable except by a single write landing at <=60 in one shot -- backwards for a guard meant to make trimming possible. Fixed with a ratchet: deny only when the projection is over the max AND larger than the file's current line count (0 for a file that doesn't exist yet), for both Write and Edit. A compliant file still cannot grow past the limit and a new file still cannot be created oversized, but an oversized file can now be edited toward compliance one write at a time. state-line-guard-selftest.sh: 16 -> 21 checks (new section 8: shrink allows, same-size allows, grow-while-oversized still denies, new-oversized still denies). Suite total: 191 + 152 + 69 + 21 = 433. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0186kZGKddxfA9N84HqMLbb2
This commit is contained in:
parent
f39c0df929
commit
c1dabf109d
5 changed files with 154 additions and 10 deletions
|
|
@ -28,6 +28,17 @@
|
|||
// project confidently (file missing, old_string not found, fields of
|
||||
// the wrong type) is left to the real tool, which will give a clearer
|
||||
// error than a guess here would.
|
||||
// - RATCHET: denies only when the projected line count is BOTH over
|
||||
// MAX_LINES and larger than the file's CURRENT line count (0 for a file
|
||||
// that doesn't exist yet). A file already over the limit is the normal
|
||||
// starting point for a trim, not an edge case - measured on the real
|
||||
// tree 2026-08-14, 23 of the machine's STATE.md files were already over
|
||||
// 60 lines, one at 1405. Comparing only against MAX_LINES (no ratchet)
|
||||
// would deny every incremental trim of those files that doesn't land at
|
||||
// <=60 in one shot - the opposite of what a guard meant to make trimming
|
||||
// possible should do. The ratchet still blocks what the guard exists to
|
||||
// block: a compliant file growing past the limit, or a brand-new file
|
||||
// being created oversized.
|
||||
// - Block: stderr + exit 2
|
||||
// - Allow: exit 0, no output
|
||||
|
||||
|
|
@ -45,6 +56,14 @@ function countLines(text) {
|
|||
return matches ? matches.length : 0;
|
||||
}
|
||||
|
||||
function currentLineCountOf(path) {
|
||||
try {
|
||||
return countLines(readFileSync(path, 'utf-8'));
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
let input;
|
||||
try {
|
||||
input = JSON.parse(readFileSync(0, 'utf-8'));
|
||||
|
|
@ -65,9 +84,11 @@ if (
|
|||
}
|
||||
|
||||
let projected;
|
||||
let currentLines;
|
||||
if (toolName === 'Write') {
|
||||
if (typeof toolInput.content !== 'string') allow();
|
||||
projected = toolInput.content;
|
||||
currentLines = currentLineCountOf(filePath);
|
||||
} else {
|
||||
let current;
|
||||
try {
|
||||
|
|
@ -83,15 +104,17 @@ if (toolName === 'Write') {
|
|||
projected = toolInput.replace_all
|
||||
? current.split(oldStr).join(newStr)
|
||||
: current.replace(oldStr, newStr);
|
||||
currentLines = countLines(current);
|
||||
}
|
||||
|
||||
const lines = countLines(projected);
|
||||
if (lines > MAX_LINES) {
|
||||
if (lines > MAX_LINES && lines > currentLines) {
|
||||
process.stderr.write(
|
||||
`\n[repo-mailbox] STATE LINE GUARD: ${toolName} blocked\n` +
|
||||
` File: ${filePath}\n` +
|
||||
` Projected: ${lines} lines (max ${MAX_LINES} per the STATE.md convention)\n\n` +
|
||||
`Trim STATE.md before writing -- history belongs in git, not STATE.md.\n`
|
||||
` Projected: ${lines} lines (current: ${currentLines}, max ${MAX_LINES} per the STATE.md convention)\n\n` +
|
||||
`This would grow STATE.md further past the limit. Trim it instead -- ` +
|
||||
`any write that reduces the line count is allowed, even if still over ${MAX_LINES}.\n`
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue