Skip to content

Commit 5eb459c

Browse files
committed
feat(scaffold): Cursor nudge-uncommitted hook
Cursor 1.7+ ships a hooks system at .cursor/hooks.json. The afterFileEdit event fires after Cursor's edit/write tools and accepts a top-level additional_context field (snake_case) to inject a soft reminder into the agent's context. Same threshold + message as the Claude Code and Gemini CLI hooks. Skipped on main/master and outside a git work tree. Doc reference: https://cursor.com/docs/hooks
1 parent 4c21aa8 commit 5eb459c

3 files changed

Lines changed: 53 additions & 0 deletions

File tree

packages/cli/lib/create.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ export async function scaffoldApp(name, cwd, opts = {}) {
272272
// Gemini CLI config + hooks
273273
'.gemini/settings.json',
274274
'.gemini/hooks/nudge-uncommitted.sh',
275+
// Cursor config + hooks
276+
'.cursor/hooks.json',
277+
'.cursor/hooks/nudge-uncommitted.sh',
275278
// Cross-agent config files
276279
'.cursorrules',
277280
'.windsurfrules',
@@ -299,6 +302,10 @@ export async function scaffoldApp(name, cwd, opts = {}) {
299302
const hookPath = join(appDir, '.gemini', 'hooks', hook);
300303
if (existsSync(hookPath)) await chmod(hookPath, 0o755);
301304
}
305+
for (const hook of ['nudge-uncommitted.sh']) {
306+
const hookPath = join(appDir, '.cursor', 'hooks', hook);
307+
if (existsSync(hookPath)) await chmod(hookPath, 0o755);
308+
}
302309
// Make git pre-commit hook executable
303310
const preCommitPath = join(appDir, '.hooks', 'pre-commit');
304311
if (existsSync(preCommitPath)) await chmod(preCommitPath, 0o755);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"version": 1,
3+
"hooks": {
4+
"afterFileEdit": [
5+
{ "command": ".cursor/hooks/nudge-uncommitted.sh" }
6+
]
7+
}
8+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
#
3+
# Cursor afterFileEdit hook.
4+
#
5+
# Counterpart of .claude/hooks/nudge-uncommitted.sh. After each
6+
# file edit, counts uncommitted changes in the working tree.
7+
# When the count crosses a threshold (default 4, override with
8+
# WEBJS_COMMIT_NUDGE_THRESHOLD), injects a reminder via the
9+
# top-level additional_context field (snake_case, unlike Claude
10+
# Code's nested hookSpecificOutput.additionalContext).
11+
#
12+
# Soft nudge. Exit 0 always. Skipped on main/master and outside
13+
# a git work tree.
14+
15+
set -e
16+
17+
THRESHOLD="${WEBJS_COMMIT_NUDGE_THRESHOLD:-4}"
18+
19+
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
20+
exit 0
21+
fi
22+
23+
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
24+
if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
25+
exit 0
26+
fi
27+
28+
cat /dev/stdin >/dev/null 2>&1 || true
29+
30+
CHANGED=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
31+
32+
if [ -z "$CHANGED" ] || [ "$CHANGED" -lt "$THRESHOLD" ]; then
33+
exit 0
34+
fi
35+
36+
REASON="You have ${CHANGED} uncommitted changes on '${BRANCH}'. The webjs convention is small, focused commits per logical unit (one feature, one fix, one rename, one doc rewrite). Before continuing with more edits, group the current changes into a meaningful commit. See AGENTS.md \"Git workflow\" for the rule and the rationale. To raise the threshold for this hook in long-running tasks, set WEBJS_COMMIT_NUDGE_THRESHOLD."
37+
38+
jq -n --arg ctx "$REASON" '{ additional_context: $ctx }'

0 commit comments

Comments
 (0)