mirror of
https://github.com/affaan-m/ECC.git
synced 2026-08-17 21:15:40 +02:00
* fix(hooks,lib): fix hook detection and parsing edge cases
- auto-tmux-dev: dev\b -> dev(?![\w-]) so one-shot dev-build/dev-docs scripts
are not detached into tmux; align command shapes (yarn run dev, bun dev) with
pre-bash-dev-server-block.js DEV_PATTERN.
- pre-bash-commit-quality: skip obvious non-secret placeholders (env refs,
${...}, <...>, whitelisted tokens) in the api-key rule without suppressing
real high-entropy secrets; make -m message extraction quote- and
escaped-quote-aware so `-m "fix: \"x\""` / apostrophes are not truncated.
- pre-compact: annotate the CURRENT worktree's session (match **Worktree:** /
legacy **Project:**) instead of the newest *-session.tmp across all projects,
layered onto the LLM-summary flow from #2388; a present-but-blank Worktree
header is treated as non-legacy (no foreign project fallback).
- shell-substitution: stop double-appending a trailing backslash in an
unterminated backtick span.
- utils readStdinJson: on overflow, settle and resolve {} immediately (clear
timer + listeners) instead of waiting for end/timeout and parsing a partial
prefix; surface the overflow on stderr.
Regression tests added/extended (new tests/hooks/pre-compact.test.js).
Addresses review feedback on #2405. The earlier block-no-verify change was
dropped: its message-value skip on merge/cherry-pick/am/rebase would let
`git rebase -m --no-verify` bypass the hook (rebase's -m is the boolean
--merge), a false-negative worse than the contrived false-positive it fixed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ci): align hook fixtures and drain oversized stdin
---------
Co-authored-by: djpjronline-netizen <276112803+djpjronline-netizen@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
117 lines
4.9 KiB
JavaScript
Executable File
117 lines
4.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* Auto-Tmux Dev Hook - Start dev servers in tmux/cmd automatically
|
|
*
|
|
* macOS/Linux: Runs dev server in a named tmux session (non-blocking).
|
|
* Falls back to original command if tmux is not installed.
|
|
* Windows: Opens dev server in a new cmd window (non-blocking).
|
|
*
|
|
* Runs before Bash tool use. If command is a dev server (npm run dev, pnpm dev, yarn dev, bun run dev),
|
|
* transforms it to run in a detached session.
|
|
*
|
|
* Benefits:
|
|
* - Dev server runs detached (doesn't block Claude Code)
|
|
* - Session persists (can run `tmux capture-pane -t <session> -p` to see logs on Unix)
|
|
* - Session name matches project directory (allows multiple projects simultaneously)
|
|
*
|
|
* Session management (Unix):
|
|
* - Checks tmux availability before transforming
|
|
* - Kills any existing session with the same name (clean restart)
|
|
* - Creates new detached session
|
|
* - Reports session name and how to view logs
|
|
*
|
|
* Session management (Windows):
|
|
* - Opens new cmd window with descriptive title
|
|
* - Allows multiple dev servers to run simultaneously
|
|
*/
|
|
|
|
const path = require('path');
|
|
const { spawnSync } = require('child_process');
|
|
|
|
const MAX_STDIN = 1024 * 1024; // 1MB limit
|
|
let data = '';
|
|
|
|
function run(rawInput) {
|
|
try {
|
|
const input = typeof rawInput === 'string' ? JSON.parse(rawInput) : rawInput;
|
|
const cmd = input.tool_input?.command || '';
|
|
|
|
// Detect dev server commands: npm run dev, pnpm (run) dev, yarn (run) dev,
|
|
// bun (run) dev. Trailing (?![\w-]) rather than \b: \b treats a hyphen as a
|
|
// word boundary, so `dev\b` matches the `dev` prefix of distinct scripts
|
|
// like `dev-build` / `dev-docs` and would wrongly detach those one-shot
|
|
// scripts into tmux. The lookahead still matches the dev server (`dev`,
|
|
// `dev:ssr`, ...) but not a `dev-<suffix>` script. The optional `run` on
|
|
// yarn/bun mirrors the command shapes in pre-bash-dev-server-block.js
|
|
// DEV_PATTERN so the two hooks agree on what counts as a dev server.
|
|
// Flexible whitespace (\s+) and leading \b make this byte-identical to
|
|
// pre-bash-dev-server-block.js DEV_PATTERN, so a tabbed/multi-space command
|
|
// the blocker catches is also detached here (they agree exactly).
|
|
const devServerRegex = /\b(npm\s+run\s+dev|pnpm(?:\s+run)?\s+dev|yarn(?:\s+run)?\s+dev|bun(?:\s+run)?\s+dev)(?![\w-])/;
|
|
|
|
if (devServerRegex.test(cmd)) {
|
|
// Get session name from current directory basename, sanitize for shell safety
|
|
// e.g., /home/user/Portfolio → "Portfolio", /home/user/my-app-v2 → "my-app-v2"
|
|
const rawName = path.basename(process.cwd());
|
|
// Replace non-alphanumeric characters (except - and _) with underscore to prevent shell injection
|
|
const sessionName = rawName.replace(/[^a-zA-Z0-9_-]/g, '_') || 'dev';
|
|
|
|
if (process.platform === 'win32') {
|
|
// Windows: open in a new cmd window (non-blocking)
|
|
// Escape double quotes in cmd for cmd /k syntax
|
|
const escapedCmd = cmd.replace(/"/g, '""');
|
|
return JSON.stringify({
|
|
...input,
|
|
tool_input: {
|
|
...input.tool_input,
|
|
command: `start "DevServer-${sessionName}" cmd /k "${escapedCmd}"`,
|
|
},
|
|
});
|
|
} else {
|
|
// Unix (macOS/Linux): Check tmux is available before transforming
|
|
const tmuxCheck = spawnSync('which', ['tmux'], { encoding: 'utf8' });
|
|
if (tmuxCheck.status === 0) {
|
|
// Escape single quotes for shell safety: 'text' -> 'text'\''text'
|
|
const escapedCmd = cmd.replace(/'/g, "'\\''");
|
|
|
|
// Build the transformed command:
|
|
// 1. Kill existing session (silent if doesn't exist)
|
|
// 2. Create new detached session with the dev command
|
|
// 3. Echo confirmation message with instructions for viewing logs
|
|
const transformedCmd = `SESSION="${sessionName}"; tmux kill-session -t "$SESSION" 2>/dev/null || true; tmux new-session -d -s "$SESSION" '${escapedCmd}' && echo "[Hook] Dev server started in tmux session '${sessionName}'. View logs: tmux capture-pane -t ${sessionName} -p -S -100"`;
|
|
return JSON.stringify({
|
|
...input,
|
|
tool_input: {
|
|
...input.tool_input,
|
|
command: transformedCmd,
|
|
},
|
|
});
|
|
}
|
|
// else: tmux not found, pass through original command unchanged
|
|
}
|
|
}
|
|
|
|
return JSON.stringify(input);
|
|
} catch {
|
|
// Invalid input — pass through original data unchanged
|
|
return typeof rawInput === 'string' ? rawInput : JSON.stringify(rawInput);
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
process.stdin.setEncoding('utf8');
|
|
process.stdin.on('data', chunk => {
|
|
if (data.length < MAX_STDIN) {
|
|
const remaining = MAX_STDIN - data.length;
|
|
data += chunk.substring(0, remaining);
|
|
}
|
|
});
|
|
|
|
process.stdin.on('end', () => {
|
|
process.stdout.write(run(data));
|
|
process.exit(0);
|
|
});
|
|
}
|
|
|
|
module.exports = { run };
|