Skip to content

Commit-frequency enforcement across agents#13

Merged
vivek7405 merged 11 commits into
mainfrom
feat/commit-frequency-enforcement
May 19, 2026
Merged

Commit-frequency enforcement across agents#13
vivek7405 merged 11 commits into
mainfrom
feat/commit-frequency-enforcement

Conversation

@vivek7405
Copy link
Copy Markdown
Collaborator

Summary

Adds programmatic enforcement of the "commit per logical unit" rule for AI agents that support hooks. Falls back to text rules for those that don't.

Hook coverage matrix

Agent Mechanism Path
Claude Code PostToolUse hook .claude/hooks/nudge-uncommitted.sh
Gemini CLI AfterTool hook .gemini/hooks/nudge-uncommitted.sh
Cursor 1.7+ afterFileEdit hook .cursor/hooks/nudge-uncommitted.sh
Windsurf text rule only, post-write hooks cannot inject context .windsurfrules
GitHub Copilot text rule only, no hooks API .github/copilot-instructions.md
OpenCode text rule only for now, TS plugin support planned AGENTS.md
Google Antigravity text rule only, no hooks API AGENTS.md

Tool-agnostic backstop

.hooks/pre-commit now also runs webjs test and webjs check. Fires on every commit regardless of which agent or human made it. Skipped if the CLI isn't installed yet, e.g. fresh clone before npm install.

Behavior

Each hook counts git status --porcelain after an edit. When the count is at least 4 (override via WEBJS_COMMIT_NUDGE_THRESHOLD), it injects a soft reminder into the agent's context. Output shape per tool:

  • Claude and Gemini share the nested hookSpecificOutput.additionalContext field.
  • Cursor uses a top-level snake_case additional_context field.

Hooks are soft, they exit 0 always and never block. Skipped on main/master since guard-branch-context.sh handles that case.

Commits in this branch

Nine commits, each a single logical unit. Highlights:

  • Framework-repo Claude hook
  • Scaffold-template Claude hook plus chmod plumbing in lib/create.js
  • Strengthened text rules across AGENTS.md, CONVENTIONS.md, .cursorrules, .windsurfrules, copilot-instructions.md
  • Pre-commit running webjs test plus webjs check
  • Gemini CLI hook
  • Cursor hook
  • Cross-agent enforcement matrix documentation

Test plan

  • All hooks smoke-tested with WEBJS_COMMIT_NUDGE_THRESHOLD=2 against a temp dirty repo. Each emits the JSON shape its tool expects.
  • npm test passes, 896 of 896.
  • Framework hook fired on me during this branch's own work at 4 uncommitted files, confirming the enforcement loop closes.
  • End-to-end test of a scaffolded app would require running webjs create and connecting Gemini or Cursor. Not done in this PR.

vivek7405 added 11 commits May 19, 2026 14:03
PostToolUse hook on Edit/Write/MultiEdit/NotebookEdit that
counts working-tree changes after each tool call. When the
count crosses a threshold (default 4, override via
WEBJS_COMMIT_NUDGE_THRESHOLD), injects a reminder into the
model's context via hookSpecificOutput.

Soft nudge by design. Does not block edits. Skipped on
main/master (guard-branch-context.sh handles that case) and
outside a git work tree. Threshold of 4 catches the common
batching failure mode without firing for trivial work.

Motivation: I just shipped PR #12 with too many changes in
too few commits. The text guidance in AGENTS.md is easy to
ignore. A hook is harder.
Mirrors the framework-repo hook into the scaffold templates so
end-user webjs apps inherit the same commit-frequency
enforcement out of the box. Three pieces:

* templates/.claude/hooks/nudge-uncommitted.sh: copy of the
  framework hook
* templates/.claude/settings.json: registers it on
  PostToolUse for Write/Edit/MultiEdit/NotebookEdit
* lib/create.js: adds the new hook to the chmod list so
  scaffolded apps get the executable bit (the templates
  directory itself does not preserve +x)
Replaces the soft 'commit and push freely' wording with explicit
directives: commit per logical unit, do not pile changes, push
after each commit, and respect the nudge-uncommitted hook
reminder at threshold 4. Reframes 'meaningful commit messages'
to say the body explains the reason rather than the diff.

Driven by the failure mode in PR #12, where 8 logical units
collapsed into 1 batched diff before being split at the end.
…NS/cursorrules/windsurfrules

Aligns the four scaffold-template agent rule files with the new
framework AGENTS.md wording:

* logical unit defined explicitly (one feature, one fix, one
  rename, one doc rewrite)
* hard limit of 5+ unstaged files spanning different concerns
  before commit is mandatory
* reference to the .claude/hooks/nudge-uncommitted.sh hook so
  Claude Code users see it cited in the prose they read, and
  Cursor/Windsurf users have an explicit self-enforce directive
  since their agents do not consume Claude Code hooks

Copilot's instructions file lands in the next commit (hook fired
mid-edit at threshold 4, respecting it).
…ons.md

Completes the cross-agent rollout from the previous commit.
Copilot has no hooks API; the rule self-enforces via prose.
Same logical-unit definition and 5-file hard limit as the other
agent rule files.
Tool-agnostic git-level enforcement. Fires on every commit
regardless of agent (Claude, Cursor, Windsurf, Copilot, human).
Blocks commits that fail tests or convention checks.

Skipped on fresh clones before npm install (when the CLI is
not yet on PATH). To bypass in emergencies: git commit
--no-verify.
Adds the same commit-frequency enforcement to scaffolded apps for
Gemini CLI users. The hook output shape (hookSpecificOutput.
additionalContext) is identical to Claude Code's, so the body is
nearly a copy of the Claude version.

Also fixes a small omission from the earlier 'scaffold nudge hook'
commit: '.claude/hooks/nudge-uncommitted.sh' was created in
templates/ but never added to the templateFiles copy list in
create.js, so it would not have shipped to scaffolded apps. Add
it to the list now alongside the Gemini files.

Doc reference: https://geminicli.com/docs/hooks/reference/
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
Scaffolded apps now ship hooks for Claude Code, Gemini CLI, and
Cursor 1.7+. Other agents (Windsurf, Copilot, OpenCode,
Antigravity) fall back to text rules. Records this matrix in
both templates/AGENTS.md (for end-user webjs apps) and root
AGENTS.md (for framework devs).

Notes the rationale for each fallback:
* Windsurf post-write hooks cannot inject context to the agent
* Copilot has no hooks API
* OpenCode has plugins (TS, planned for follow-up)
* Antigravity has no hooks API

Pre-commit (.hooks/pre-commit) is the tool-agnostic backstop:
runs webjs test + webjs check on every commit regardless of
which agent (or human) is making it.
…d app

Mirrors the framework-repo prose hook into scaffolded apps so end
users get the same enforcement of AGENTS.md invariant 11 (no
em-dashes, no hyphen-as-pause, no semicolon-as-pause, no
xyz():-then-prose). The rule is already documented in
templates/AGENTS.md and the per-agent rule files; the hook adds
programmatic enforcement for Claude Code users.

Three pieces:
* templates/.claude/hooks/block-prose-punctuation.sh: copy of
  the framework hook
* templates/.claude/settings.json: registers it on PreToolUse
  for Write/Edit/MultiEdit/NotebookEdit/Bash
* lib/create.js: extends templateFiles copy list and chmod loop
…ain locally

This hook prompted on every 'git merge' and every 'git push ... main'
through Claude's Bash tool. That matches the framework dev's preference
for a strict GitHub-PR-only workflow, but it is the wrong default for
end users. Scaffolded webjs apps may use GitLab, Bitbucket, plain git,
or just merge locally without a forge at all.

End-user enforcement against main is now layered as:
* .hooks/pre-commit (tool-agnostic) blocks direct commits to main
* .claude/hooks/guard-branch-context.sh prompts before editing on main

Both of those work with any git workflow including local merges and
direct pushes to main. The framework repo keeps its own copy of the
merge guard if the user wants to formalize their PR-only workflow
there.
@vivek7405 vivek7405 merged commit 4497523 into main May 19, 2026
@vivek7405 vivek7405 deleted the feat/commit-frequency-enforcement branch May 19, 2026 09:24
vivek7405 added a commit that referenced this pull request May 19, 2026
Mirrors what scaffolded webjs apps get, adapted for the framework
repo's own test runner. Two gates:
  1. Block direct commits to main/master (use a feature branch)
  2. Run npm test, refuse the commit if it fails

Adds a 'prepare' script to root package.json that sets
core.hooksPath to .hooks so the hook activates on npm install
for any contributor who clones the repo. To bypass in
emergencies: git commit --no-verify.

Driven by an audit gap: the scaffold templates have shipped a
pre-commit since PR #13 merged, but the framework repo itself
had no equivalent, so my own commits were not gated by tests.
vivek7405 added a commit that referenced this pull request May 21, 2026
Third slice. Wires the slot runtime into the WebComponent lifecycle and
refines render-client.js's slot bind so fallback content is captured
once at compile time and cloned freshly per instance.

component.js:

- Light-DOM activation now runs in two phases:
    Phase 1 (before _performRender): if not hydrating, call
      captureAuthoredChildren(host) to move authored children into the
      slot state's assignment table. The renderer's replaceChildren()
      call would otherwise destroy them on first render.
    Phase 2 (after _performRender, slots are now live DOM): if the
      host was hydrating, call adoptSSRAssignments(host) to record
      SSR-placed children so the first projection pass is a no-op.
      Then call attachSlotObservers(host) so future authored-child
      mutations and slot-name changes drive incremental projection.

- New __isHydrating() helper inspects this.firstChild for the
  framework's <!--webjs-hydrate--> marker and records the result
  on __hydratedAtActivate so phase two can branch correctly.

- disconnectedCallback now calls detachSlotObservers(host) for
  light-DOM hosts. The per-host slot state (assignment table,
  pending fragments, last snapshots) is preserved across disconnect,
  so a re-attached element picks up where it left off.

render-client.js:

- discoverSlots() now MOVES the slot's authored children into a
  fallbackTemplate DocumentFragment stored on the SLOT
  PartDescriptor. The cached templateEl's slot becomes empty, so
  every clone starts empty too. This eliminates the cloning-the-
  fallback-out-of-the-slot dance the previous bind step did.

- bindPart for slot now clones the descriptor's fallbackTemplate
  into a per-instance holding fragment and stamps it on the slot
  via SLOT_FALLBACK_FRAG for slot.js to swap in. The slot itself
  is left untouched at bind time, which makes hydration trivially
  correct (SSR-projected children stay in place; the slot-part
  just sets up its fallback supply for later transitions).

- PartDescriptor typedef gains an optional fallbackTemplate field.

Verified across 115 existing core unit tests (component, render-client,
render-server, directives, registry, css, html, context, task,
suspense, repeat, testing). All pass.

What remains: render-server.js injectDSD upgrade for SSR slot
substitution (Task #13), then the 62-case test suite (Task #14),
then docs (Task #15).
vivek7405 added a commit that referenced this pull request May 21, 2026
vivek7405 added a commit that referenced this pull request May 21, 2026
Mirrors what scaffolded webjs apps get, adapted for the framework
repo's own test runner. Two gates:
  1. Block direct commits to main/master (use a feature branch)
  2. Run npm test, refuse the commit if it fails

Adds a 'prepare' script to root package.json that sets
core.hooksPath to .hooks so the hook activates on npm install
for any contributor who clones the repo. To bypass in
emergencies: git commit --no-verify.

Driven by an audit gap: the scaffold templates have shipped a
pre-commit since PR #13 merged, but the framework repo itself
had no equivalent, so my own commits were not gated by tests.
vivek7405 added a commit that referenced this pull request May 21, 2026
For agents without a programmatic hooks API (Windsurf,
Copilot) and as a baseline source of truth for all agents:

* .cursorrules: Cursor's rule file
* .windsurfrules: Windsurf's rule file
* .github/copilot-instructions.md: GitHub Copilot's rule file
* .github/pull_request_template.md: PR template
* .editorconfig: editor settings (indent, line endings)

All copied verbatim from packages/cli/templates/, including
the strengthened commit-frequency wording and hook references
from PRs #12 and #13.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant