From 3b2448dbb400f177e3a67cc6f4c63e2080e3feec Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Thu, 26 Feb 2026 18:40:31 -0800 Subject: [PATCH] fix(hooks): extract doc-warning hook to external script to fix CI The inline JS in the Write PreToolUse hook had a multi-layer escaping bug: the regex [\\/\\] collapsed to [\/\] after the validator's unescape chain, producing an invalid regex (Unmatched ')'). Fix: move the doc-file-warning hook to scripts/hooks/pre-write-doc-warn.js, eliminating the inline escaping problem entirely. All 992 tests now pass. Closes the 991/992 CI failure on main. --- hooks/hooks.json | 2 +- scripts/hooks/pre-write-doc-warn.js | 61 +++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 scripts/hooks/pre-write-doc-warn.js diff --git a/hooks/hooks.json b/hooks/hooks.json index 33423181..26c8e6d7 100644 --- a/hooks/hooks.json +++ b/hooks/hooks.json @@ -37,7 +37,7 @@ "hooks": [ { "type": "command", - "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const i=JSON.parse(d);const p=i.tool_input?.file_path||'';if(/\\.(md|txt)$/.test(p)&&!/(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL)\\.md$/i.test(p)&&!/\\.claude[\\/\\\\]plans[\\/\\\\]/.test(p)&&!/(^|[\\/\\\\])(docs|skills)[\\/\\\\]/.test(p)){console.error('[Hook] WARNING: Non-standard documentation file detected');console.error('[Hook] File: '+p);console.error('[Hook] Consider consolidating into README.md or docs/ directory')}}catch{}console.log(d)})\"" + "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/pre-write-doc-warn.js\"" } ], "description": "Doc file warning: warn about non-standard documentation files (exit code 0; warns only)" diff --git a/scripts/hooks/pre-write-doc-warn.js b/scripts/hooks/pre-write-doc-warn.js new file mode 100644 index 00000000..886258dd --- /dev/null +++ b/scripts/hooks/pre-write-doc-warn.js @@ -0,0 +1,61 @@ +#!/usr/bin/env node +/** + * PreToolUse Hook: Warn about non-standard documentation files + * + * Cross-platform (Windows, macOS, Linux) + * + * Runs before Write tool use. If the file is a .md or .txt file that isn't + * a standard documentation file (README, CLAUDE, AGENTS, etc.) or in an + * expected directory (docs/, skills/, .claude/plans/), warns the user. + * + * Exit code 0 — warn only, does not block. + */ + +const path = require('path'); + +const MAX_STDIN = 1024 * 1024; // 1MB limit +let data = ''; +process.stdin.setEncoding('utf8'); + +process.stdin.on('data', chunk => { + if (data.length < MAX_STDIN) { + const remaining = MAX_STDIN - data.length; + data += chunk.length > remaining ? chunk.slice(0, remaining) : chunk; + } +}); + +process.stdin.on('end', () => { + try { + const input = JSON.parse(data); + const filePath = input.tool_input?.file_path || ''; + + // Only check .md and .txt files + if (!/\.(md|txt)$/.test(filePath)) { + process.stdout.write(data); + return; + } + + // Allow standard documentation files + const basename = path.basename(filePath); + if (/^(README|CLAUDE|AGENTS|CONTRIBUTING|CHANGELOG|LICENSE|SKILL)\.md$/i.test(basename)) { + process.stdout.write(data); + return; + } + + // Allow files in .claude/plans/, docs/, and skills/ directories + const normalized = filePath.replace(/\\/g, '/'); + if (/\.claude\/plans\//.test(normalized) || /(^|\/)(docs|skills)\//.test(normalized)) { + process.stdout.write(data); + return; + } + + // Warn about non-standard documentation files + console.error('[Hook] WARNING: Non-standard documentation file detected'); + console.error('[Hook] File: ' + filePath); + console.error('[Hook] Consider consolidating into README.md or docs/ directory'); + } catch { + // Parse error — pass through + } + + process.stdout.write(data); +});