Skip to content

Commit 2ee5e15

Browse files
committed
feat(scaffold): OpenCode commit-nudge plugin
OpenCode does not have shell-script hooks; it uses TS plugin modules loaded as-is by Bun. This plugin is the counterpart of the .claude/, .gemini/, .cursor/ hooks already in the scaffold. The plugin hooks tool.execute.after, filters to edit/write tools, runs 'git status --porcelain' via the injected Bun shell, and when uncommitted file count crosses WEBJS_COMMIT_NUDGE_THRESHOLD (default 4), appends a nudge to output.output so the agent sees it on its next turn. Same threshold and same message text as the other agents' hooks. Skipped on main/master (different guards cover that) and outside a git work tree. Auto-discovered by OpenCode from .opencode/plugins/ at startup. No opencode.json registration needed. lib/create.js scaffold copy list extended so new apps ship the plugin file alongside the other hook configs. Doc reference: https://opencode.ai/docs/plugins/
1 parent 0b382f9 commit 2ee5e15

2 files changed

Lines changed: 64 additions & 0 deletions

File tree

packages/cli/lib/create.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ export async function scaffoldApp(name, cwd, opts = {}) {
275275
// Cursor config + hooks
276276
'.cursor/hooks.json',
277277
'.cursor/hooks/nudge-uncommitted.sh',
278+
// OpenCode plugins (loaded as TS by Bun at runtime)
279+
'.opencode/plugins/nudge-uncommitted.ts',
278280
// Cross-agent config files
279281
'.cursorrules',
280282
'.windsurfrules',
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* OpenCode commit-frequency nudge plugin.
3+
*
4+
* Counterpart of the Claude Code, Gemini CLI, and Cursor hooks in
5+
* `.claude/hooks/`, `.gemini/hooks/`, and `.cursor/hooks/`. After
6+
* each edit/write tool call, counts uncommitted changes in the
7+
* working tree. When the count crosses a threshold (default 4,
8+
* override with the WEBJS_COMMIT_NUDGE_THRESHOLD env var), appends
9+
* a reminder to the tool result so the agent sees it on the next
10+
* turn.
11+
*
12+
* Soft nudge by design. Does NOT block the edit. The goal is to
13+
* keep the agent honest about the "commit per logical unit" rule,
14+
* not to interrupt valid work.
15+
*
16+
* Skipped on main/master (different guard rules cover that) and
17+
* outside a git work tree.
18+
*
19+
* Auto-discovered by OpenCode at startup. No opencode.json entry
20+
* needed. Lives in .opencode/plugins/ at the project root.
21+
*
22+
* Docs: https://opencode.ai/docs/plugins/
23+
*/
24+
import type { Plugin } from "@opencode-ai/plugin";
25+
26+
export const NudgeUncommitted: Plugin = async ({ $ }) => {
27+
const THRESHOLD = Number(process.env.WEBJS_COMMIT_NUDGE_THRESHOLD ?? 4);
28+
29+
return {
30+
"tool.execute.after": async (input, output) => {
31+
if (input.tool !== "edit" && input.tool !== "write") return;
32+
33+
let branch = "";
34+
try {
35+
branch = (await $`git symbolic-ref --short HEAD`.text()).trim();
36+
} catch {
37+
return; // not in a git work tree
38+
}
39+
if (branch === "main" || branch === "master") return;
40+
41+
let changed = 0;
42+
try {
43+
const out = (await $`git status --porcelain`.text()).trim();
44+
changed = out === "" ? 0 : out.split("\n").length;
45+
} catch {
46+
return;
47+
}
48+
if (changed < THRESHOLD) return;
49+
50+
const reason =
51+
`[webjs] You have ${changed} uncommitted changes on '${branch}'. ` +
52+
`The webjs convention is small, focused commits per logical unit ` +
53+
`(one feature, one fix, one rename, one doc rewrite). Before ` +
54+
`continuing with more edits, group the current changes into a ` +
55+
`meaningful commit. See AGENTS.md "Git workflow" for the rule ` +
56+
`and the rationale. To raise the threshold for this hook in ` +
57+
`long-running tasks, set WEBJS_COMMIT_NUDGE_THRESHOLD.`;
58+
59+
output.output = output.output ? `${output.output}\n\n${reason}` : reason;
60+
},
61+
};
62+
};

0 commit comments

Comments
 (0)