Skip to content

feat(hooks/rules/skills): deterministic hooks, git blast-radius guard, design rules#48

Merged
rohitg00 merged 3 commits into
mainfrom
fix/issue-47-deterministic-hooks
Apr 17, 2026
Merged

feat(hooks/rules/skills): deterministic hooks, git blast-radius guard, design rules#48
rohitg00 merged 3 commits into
mainfrom
fix/issue-47-deterministic-hooks

Conversation

@rohitg00
Copy link
Copy Markdown
Owner

@rohitg00 rohitg00 commented Apr 17, 2026

Fixes #47. Also adds defensive git hook, two design-discipline rules, and three new skills.

Fix for #47 — deterministic hooks

hooks/hooks.json hardcoded "model": "haiku" for two prompt hooks, which no longer resolves in current Claude Code → hooks fired silently.

  • scripts/commit-validate.js — regex conventional-commits validator. Parses -m flag and HEREDOC bodies, 11 allowed types, 72-char summary limit.
  • scripts/secret-scan.js — regex patterns for AWS / GitHub (classic + fine-grained PAT) / OpenAI / Anthropic / Slack / Google / Stripe / PEM / Bearer / generic password= / api_key=. Allowlists process.env.*, placeholders, <REDACTED> style tokens. Refuses writes to .env, .pem, .key, secrets/ paths.

[LEARN] capture is already a command-type hook (regex → SQLite); no change needed.

New: scripts/git-blast-radius.js

PreToolUse Bash hook that blocks destructive git ops before execution. Patterns covered: force push, hard reset, clean -f[d], branch -D, checkout . / restore ., interactive rebase on protected branches (main/master/trunk/release/*), filter-branch, reflog expire, update-ref -d, stash drop|clear.

  • Warns (does not block) push --force-with-lease.
  • Non-git commands pass through untouched.
  • Escape hatch: export PRO_WORKFLOW_ALLOW_UNSAFE_GIT=1 in the specific shell.

New: design rules

  • rules/incremental-verify.mdc — one test, one implementation, one commit. Guards against horizontal test authoring that asserts on imagined rather than observed behavior.
  • rules/module-shape.mdc — narrow surface, thick depth. Inject dependencies at the boundary. Mock only at system edges. Endpoint-shaped mocks over generic fetchers.

New skills

  • skills/plan-interrogate/ — walks a plan's decision tree one question at a time. Every question carries a recommended answer. Exits when zero open nodes remain.
  • skills/module-map/ — one-screen orientation map for unfamiliar areas. Entry points → core modules → data flow → callers → hidden coupling. Fifteen-second read target.
  • skills/bug-capture/ — durable GitHub issue capture. Minimal clarifying questions, parallel codebase exploration for domain language, gh issue list --search duplicate check, dependency-ordered breakdowns, no file paths or line numbers in issue bodies.

Doctor updates

commands/doctor.md gains sanity-check commands for all three deterministic hook scripts, plus documentation for the git safety override.

Test plan

  • commit-validate.js accepts feat: x, rejects bad message (exit 2)
  • secret-scan.js flags sk-ant-*, allows api_key=process.env.FOO
  • git-blast-radius.js blocks force push, hard reset, clean -fd, branch -D, checkout ., interactive rebase on main
  • git-blast-radius.js warns on --force-with-lease, allows safe git push origin main, passes through non-git commands
  • PRO_WORKFLOW_ALLOW_UNSAFE_GIT=1 override works
  • hooks.json parses as valid JSON
  • Manual: attempt a blocked git command with the plugin installed — hook blocks
  • Manual: /pro-workflow:doctor prints the new sanity-check output

Summary by CodeRabbit

  • New Features

    • Deterministic command-based commit, secret and git-safety checks added, including an automated “git blast radius” safety check and a caution pathway.
  • Bug Fixes

    • Commit message and secret validations now run deterministically with strict exit-status semantics (replacing prior interactive prompts).
    • Unsafe Git operations are detected and blocked by default; a documented per-shell override allows intentional bypass.
  • Documentation

    • Added doctor guidance for deterministic hook sanity and git safety override.
    • New rules: Incremental Verification and Module Shape.
    • New skills: bug capture, module mapping, and plan interrogation.

Bare "model": "haiku" no longer resolves in current Claude Code, so the
conventional-commits validator and secret scanner fired silently with no
effect. Replace both prompt hooks with deterministic command scripts:

- scripts/commit-validate.js: regex validator for conventional commits
  (type/scope/summary, 72-char limit, parses -m flag and HEREDOC bodies).
- scripts/secret-scan.js: regex patterns for AWS/GitHub/OpenAI/Anthropic/
  Slack/Google/Stripe/private keys/Bearer/generic password-token
  assignments, with env-reference allowlist.

Update commands/doctor.md with a sanity check that runs both scripts
without an LLM. No new dependencies; works offline.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

Replaces LLM-based prompt hooks with deterministic command hooks, adding three Node.js validators (commit-validate.js, secret-scan.js, git-blast-radius.js), updates hooks/hooks.json, extends commands/doctor.md with hook-status checks, and adds multiple rules and skill docs.

Changes

Cohort / File(s) Summary
Hook configuration
hooks/hooks.json
Replaced prompt-based hooks with command hooks: added PreToolUse command to run scripts/git-blast-radius.js, switched git-commit validation to scripts/commit-validate.js, broadened secret-scan matcher and switched to scripts/secret-scan.js; updated descriptions to deterministic regex-based checks.
Deterministic validators (new scripts)
scripts/commit-validate.js, scripts/secret-scan.js, scripts/git-blast-radius.js
Added three executable Node.js CLIs: commit-message validator (Conventional Commits regex + 72-char summary limit, exit 0/2), secret scanner (path-based denylist + regexes, exit 0/2, allowlist suppressions), and unsafe-git detector (regexes for destructive git ops, exit 0/2, honor PRO_WORKFLOW_ALLOW_UNSAFE_GIT=1).
Doctor docs
commands/doctor.md
Added two “Hooks Status” subsections documenting deterministic hook sanity checks and a per-shell PRO_WORKFLOW_ALLOW_UNSAFE_GIT=1 override with sample shell invocations.
New rules & skills documentation
rules/incremental-verify.mdc, rules/module-shape.mdc, skills/bug-capture/SKILL.md, skills/module-map/SKILL.md, skills/plan-interrogate/SKILL.md
Added several guidance and skill markdown files (Incremental Verification, Module Shape, and three new skill workflows) with checklists and templates.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ToolRunner as Hook Runner
    participant Script as Validator Script
    participant Git as Git/Target

    User->>ToolRunner: invoke tool (e.g., `git commit` / edit / write)
    ToolRunner->>Script: run appropriate script (commit-validate / secret-scan / git-blast-radius) with JSON stdin
    Script-->>ToolRunner: exit code 0 (allow) or 2 (block) + diagnostics on stderr
    alt exit 0
        ToolRunner->>Git: proceed with tool action
    else exit 2
        ToolRunner->>User: surface blocked message and diagnostics
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I nibbled at prompts at dawn,
Swapped haiku for regex, tidy and drawn.
Scanned for secrets, trimmed commit fur,
Guarded the branches where wild things were.
Hop safe, push slow — carrots all around.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title precisely identifies the main changes: deterministic hooks implementation, git safety guard, and new design rules and skills documentation.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #47: replaces prompt-based hooks with deterministic implementations for commit validation and secret scanning [#47], adds git-blast-radius guard [#47], supports configurable models and feature-detect degradation [#47], and surfaces diagnostics in doctor commands [#47].
Out of Scope Changes check ✅ Passed All changes align with issue #47 requirements: the three scripts implement requested deterministic validators, design rules establish practices for the codebase, and skills provide supporting workflows; no unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-47-deterministic-hooks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
hooks/hooks.json (1)

62-62: ⚠️ Potential issue | 🟡 Minor

Stale description: "LLM commit validation" is no longer accurate.

After this PR the Bash(git commit*) hook runs deterministic commit-validate.js, not an LLM. Update the description to reflect reality:

-        "description": "Git operation guards: quality gates before commit, LLM commit validation, wrap-up before push"
+        "description": "Git operation guards: quality gates before commit, deterministic commit-message validation, wrap-up before push"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/hooks.json` at line 62, Update the stale hooks.json description string
that currently says "LLM commit validation" to accurately reflect that the
Bash(git commit*) hook now runs the deterministic commit-validate.js script;
edit the value for the "description" key to mention deterministic commit
validation via commit-validate.js (and remove any reference to an LLM) so the
text correctly describes the Git operation guards and wrap-up before push.
♻️ Duplicate comments (1)
hooks/hooks.json (1)

65-72: ⚠️ Potential issue | 🟠 Major

Consider widening matcher to Edit as well.

The content scanner already supports tool_input.new_string, so extending the matcher closes the Edit-tool bypass (see detailed comment on scripts/secret-scan.js):

-        "matcher": "Write",
+        "matcher": "tool == \"Edit\" || tool == \"Write\"",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@hooks/hooks.json` around lines 65 - 72, Update the hooks.json matcher so the
secret-scanner runs not just on "Write" events but also on "Edit" events to
prevent the Edit-tool bypass; modify the "matcher" entry for the secret-scan
hook (the block that currently has matcher "Write" and runs
scripts/secret-scan.js) to include both Write and Edit (e.g., as a combined
matcher or list) so the scanner executes on edits as well as writes.
🧹 Nitpick comments (4)
scripts/commit-validate.js (1)

2-3: Type list diverges from rules/atomic-commits.mdc.

The rule file documents 9 types (feat, fix, refactor, test, docs, chore, perf, ci, style); this script adds build and revert. Both directions are reasonable (build/revert are standard Conventional Commits types), but the rule and validator should agree or developers will hit drift between documentation and enforcement. Either extend the rule, or drop build/revert here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/commit-validate.js` around lines 2 - 3, The TYPES array in
commit-validate.js (const TYPES) includes 'build' and 'revert' which diverges
from the documented list in rules/atomic-commits.mdc; update the validator so it
matches the rule: either remove 'build' and 'revert' from the TYPES array or add
them to the documented rule, and then regenerate the PATTERN (const PATTERN) if
needed; locate and edit the const TYPES declaration to make the list identical
to the atomic-commits rule to prevent enforcement/documentation drift.
scripts/secret-scan.js (3)

2-16: Duplicated secret-pattern logic with scripts/post-edit-check.js.

post-edit-check.js (lines 43-47 per context) carries its own api[_-]?key|secret|password|token regex. Now that secret-scan.js is the canonical detector, consider exporting PATTERNS/ALLOWLIST from a shared module and consuming them from both sites so rules don't drift.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/secret-scan.js` around lines 2 - 16, The PATTERNS array in
scripts/secret-scan.js is duplicated by the inline regex in
scripts/post-edit-check.js; extract PATTERNS (and any ALLOWLIST used by
post-edit-check.js) into a new shared module (e.g., scripts/secret-patterns.js)
and update both scripts to import that export instead of defining their own
regexes so rules stay canonical; specifically move the PATTERNS constant (and
any allowlist logic) out of scripts/secret-scan.js, export it, then replace the
inline pattern/regex usage in scripts/post-edit-check.js (the
api[_-]?key|secret|password|token logic) to import and use the shared
PATTERNS/ALLOWLIST.

51-55: Blanket refusal on .env*/*.pem/*.key/secrets/ paths may break legitimate flows.

Anchored on $, this correctly passes .env.example/.env.sample, which is good. But it still hard-blocks any write to .env, .env.local, id_rsa.pem, keys/foo.key, etc. — including the agent legitimately scaffolding a fresh local .env with only placeholders. Since the content scanner + allowlist already handles placeholders, consider downgrading this to "scan content strictly" rather than unconditionally refusing, or at least allow through if the content is 100% allowlisted (process.env.* references only, <REDACTED>, etc.).

Not blocking, but worth a conscious decision before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/secret-scan.js` around lines 51 - 55, The current check in
secret-scan.js that tests the path variable with the regex and immediately calls
process.exit(2) is too aggressive; change it so matching secret-like paths do
not unconditionally abort: if input?.tool_input?.content exists, run a strict
allowlist check against that content (e.g., only allow fully placeholder files
like process.env.* references, "<REDACTED>", "REDACTED", or other approved
patterns) and if the content passes the allowlist then continue normally;
otherwise invoke the existing content scanner/deny path and only then call
process.exit(2). Keep the original path regex but replace the immediate exit
with this conditional content check using input?.tool_input?.content and the
same process.exit(2) only for non-allowlisted content.

2-16: Pattern definitions are overly broad; official gitleaks patterns are far more specific.

The patterns in lines 2-16 lack specificity compared to gitleaks' official detectors:

  • OpenAI API Key: Your pattern /\bsk-(?:proj-)?(?!ant-)[A-Za-z0-9_\-]{20,}\b/ will match any sk- token. Official gitleaks requires the base64-encoded marker T3BlbkFJ plus exact length constraints (e.g., sk-[a-zA-Z0-9]{20}T3BlbkFJ[a-zA-Z0-9]{20} for legacy tokens), drastically reducing false positives.
  • Anthropic API Key: Your pattern /\bsk-ant-[A-Za-z0-9_\-]{20,}\b/ is also broad. Official patterns require specific prefixes like api03- or admin01-, exactly 93 random characters, and the suffix AA (e.g., sk-ant-api03-[a-zA-Z0-9_\-]{93}AA).
  • Stripe vs. OpenAI: Your Stripe pattern /\bsk_live_[0-9a-zA-Z]{24,}\b/ (underscore) doesn't conflict with OpenAI's hyphenated prefix—this concern is resolved by design.
  • Generic Bearer Token: /\bBearer\s+[A-Za-z0-9_\-.=]{30,}/ remains overly permissive and will match placeholder strings like Bearer ${process.env.TOKEN} once they exceed 30 characters.

Consider tightening these patterns to match official gitleaks definitions to reduce noise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/secret-scan.js` around lines 2 - 16, The PATTERNS array uses
overly-broad regexes that generate false positives; tighten each detector by
replacing the generic entries in PATTERNS (notably the 'OpenAI API Key',
'Anthropic API Key', and 'Generic Bearer Token' objects) with stricter
gitleaks-style patterns: for 'OpenAI API Key' require the known base64 marker
(e.g., include T3BlbkFJ) and exact length constraints for legacy/modern forms,
for 'Anthropic API Key' match the documented prefixes (e.g., api03-, admin01-)
plus the exact 93-char body and AA suffix, and for 'Generic Bearer Token'
disallow template/env placeholders like ${...} and require a fixed minimum
length and allowed charset; update the regex literals in the PATTERNS array
accordingly to mirror gitleaks official patterns to reduce noise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@commands/doctor.md`:
- Around line 28-35: Update the Deterministic Hook Sanity section to include one
negative test per script so failures are detected: add a failing commit case
invoking scripts/commit-validate.js with a non-conventional message (e.g., "not
conventional") and a failing secret case invoking scripts/secret-scan.js with a
fake key string; run each with stderr suppressed (2>/dev/null) and use
conditional &&/|| to print "BROKEN (should have failed)" vs "reject: OK" so a
script that always exits 0 will be flagged. Reference the existing invocations
of scripts/commit-validate.js and scripts/secret-scan.js and mirror the
stdout/stderr handling and success/failure echoing used in the current
happy-path lines.

In `@scripts/commit-validate.js`:
- Around line 24-35: The validate function currently returns { ok: true } when
msg is falsy which silently skips validation; change validate(msg) so that when
msg is null/undefined it does not silently succeed but instead writes a
non-blocking stderr warning (e.g. console.error) indicating validation was
skipped for an unparsable commit message and return a distinct result (e.g. {
ok: true, skipped: true } or include a reason) so callers can differentiate
skipped vs validated passes; keep the existing PATTERN/TYPES/MAX_SUMMARY checks
for real messages and only return a hard pass { ok: true } when a parsed
firstLine is present and validated.
- Around line 15-22: extractMessage currently misses --message/--message=, the
-F file flag, and heredocs that use terminators other than EOF; update
extractMessage to also match --message(?:=|\s+)(?:"..."|'...'|[^ \t]+) and the
-F\s+(\S+) flag (for -F return a sentinel or null but ensure caller emits a
warning), and replace the hardcoded EOF heredoc regex with one that captures any
terminator token (e.g. /<<'?([A-Za-z0-9_]+)'?\s*\n([\s\S]*?)\n\1/) so opening
and closing tags must match; finally, adjust validate (the code path that
currently treats extractMessage null as { ok: true }) so that a missing parsed
message either returns { ok: false, reason: "no message parsed" } or logs a
visible warning when a heredoc was present but unrecognized, preventing silent
passes. Ensure you modify references to extractMessage and the validate return
path accordingly.

In `@scripts/secret-scan.js`:
- Around line 46-60: Update the hook matcher so secret-scan runs for edits as
well as writes: change the matcher expression in hooks/hooks.json from matching
only Write to include Edit (e.g. use tool == "Edit" || tool == "Write"); no code
changes needed in the script (it already reads tool_input.new_string into
content and uses file_path via the path variable and the existing secret-path
regex), just broaden the matcher to prevent Edit-based bypasses.
- Around line 33-44: The allowlist is currently applied only to the matched
token (snippet) so entries like /process\.env/ never match short tokens (e.g.
AKIA...). In scan(), compute the full surrounding line for the match using
m.index and m[0].length (e.g. find prior newline and next newline to slice the
full line), then replace ALLOWLIST.some(a => a.test(snippet)) with
ALLOWLIST.some(a => a.test(lineText)). Keep returning the short snippet
(snippet.slice(0,40)) and the line number as before; update references in scan()
where snippet and m are used.

---

Outside diff comments:
In `@hooks/hooks.json`:
- Line 62: Update the stale hooks.json description string that currently says
"LLM commit validation" to accurately reflect that the Bash(git commit*) hook
now runs the deterministic commit-validate.js script; edit the value for the
"description" key to mention deterministic commit validation via
commit-validate.js (and remove any reference to an LLM) so the text correctly
describes the Git operation guards and wrap-up before push.

---

Duplicate comments:
In `@hooks/hooks.json`:
- Around line 65-72: Update the hooks.json matcher so the secret-scanner runs
not just on "Write" events but also on "Edit" events to prevent the Edit-tool
bypass; modify the "matcher" entry for the secret-scan hook (the block that
currently has matcher "Write" and runs scripts/secret-scan.js) to include both
Write and Edit (e.g., as a combined matcher or list) so the scanner executes on
edits as well as writes.

---

Nitpick comments:
In `@scripts/commit-validate.js`:
- Around line 2-3: The TYPES array in commit-validate.js (const TYPES) includes
'build' and 'revert' which diverges from the documented list in
rules/atomic-commits.mdc; update the validator so it matches the rule: either
remove 'build' and 'revert' from the TYPES array or add them to the documented
rule, and then regenerate the PATTERN (const PATTERN) if needed; locate and edit
the const TYPES declaration to make the list identical to the atomic-commits
rule to prevent enforcement/documentation drift.

In `@scripts/secret-scan.js`:
- Around line 2-16: The PATTERNS array in scripts/secret-scan.js is duplicated
by the inline regex in scripts/post-edit-check.js; extract PATTERNS (and any
ALLOWLIST used by post-edit-check.js) into a new shared module (e.g.,
scripts/secret-patterns.js) and update both scripts to import that export
instead of defining their own regexes so rules stay canonical; specifically move
the PATTERNS constant (and any allowlist logic) out of scripts/secret-scan.js,
export it, then replace the inline pattern/regex usage in
scripts/post-edit-check.js (the api[_-]?key|secret|password|token logic) to
import and use the shared PATTERNS/ALLOWLIST.
- Around line 51-55: The current check in secret-scan.js that tests the path
variable with the regex and immediately calls process.exit(2) is too aggressive;
change it so matching secret-like paths do not unconditionally abort: if
input?.tool_input?.content exists, run a strict allowlist check against that
content (e.g., only allow fully placeholder files like process.env.* references,
"<REDACTED>", "REDACTED", or other approved patterns) and if the content passes
the allowlist then continue normally; otherwise invoke the existing content
scanner/deny path and only then call process.exit(2). Keep the original path
regex but replace the immediate exit with this conditional content check using
input?.tool_input?.content and the same process.exit(2) only for non-allowlisted
content.
- Around line 2-16: The PATTERNS array uses overly-broad regexes that generate
false positives; tighten each detector by replacing the generic entries in
PATTERNS (notably the 'OpenAI API Key', 'Anthropic API Key', and 'Generic Bearer
Token' objects) with stricter gitleaks-style patterns: for 'OpenAI API Key'
require the known base64 marker (e.g., include T3BlbkFJ) and exact length
constraints for legacy/modern forms, for 'Anthropic API Key' match the
documented prefixes (e.g., api03-, admin01-) plus the exact 93-char body and AA
suffix, and for 'Generic Bearer Token' disallow template/env placeholders like
${...} and require a fixed minimum length and allowed charset; update the regex
literals in the PATTERNS array accordingly to mirror gitleaks official patterns
to reduce noise.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cc438aae-cb84-42f9-aabf-9b58db69c92f

📥 Commits

Reviewing files that changed from the base of the PR and between 0b21f18 and a80b4bf.

📒 Files selected for processing (4)
  • commands/doctor.md
  • hooks/hooks.json
  • scripts/commit-validate.js
  • scripts/secret-scan.js

Comment thread commands/doctor.md
Comment thread scripts/commit-validate.js
Comment thread scripts/commit-validate.js
Comment thread scripts/secret-scan.js
Comment thread scripts/secret-scan.js
Adds a deterministic PreToolUse Bash hook that blocks destructive git
operations before execution, two design-discipline rules, and three
new skills for planning, orientation, and durable defect capture.

Hook:
- scripts/git-blast-radius.js: blocks force push, hard reset, clean -f,
  branch -D, checkout/restore '.', interactive rebase on protected
  branches, filter-branch, reflog expire, update-ref -d, stash drop.
  Warns (but allows) push --force-with-lease. Override via
  PRO_WORKFLOW_ALLOW_UNSAFE_GIT=1. Non-git commands pass through.

Rules:
- rules/incremental-verify.mdc: one test, one implementation, one
  commit. Guards against horizontal test authoring that asserts
  on imagined rather than observed behavior.
- rules/module-shape.mdc: narrow surface + thick depth. Inject
  dependencies at the boundary. Mock only at system edges.

Skills:
- skills/plan-interrogate/: walks a plan's decision tree one question
  at a time, every question carrying a recommended answer. Exits when
  zero open nodes remain.
- skills/module-map/: one-screen orientation map for unfamiliar
  codebase areas. Entry points, core modules, data flow, callers,
  hidden coupling. Fifteen-second read target.
- skills/bug-capture/: durable GitHub issue capture. Minimal
  clarifying questions, parallel codebase exploration for domain
  language, duplicate check, dependency-ordered breakdowns, no file
  paths or line numbers in issue bodies.

Doctor:
- commands/doctor.md: new sanity check for all deterministic hook
  scripts plus documentation for the git safety override.
@rohitg00 rohitg00 changed the title fix(hooks): replace haiku prompt hooks with deterministic scripts feat(hooks/rules/skills): deterministic hooks, git blast-radius guard, design rules Apr 17, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
skills/plan-interrogate/SKILL.md (1)

15-17: Align node state terminology with output format.

Step 2 introduces three node states: open, pinned, and inferred (lines 16-17). However, step 7's output format (line 28) specifies sources as user / inferred: path, which doesn't explicitly mention "pinned."

While the mapping is intuitive (pinned → user), explicitly stating this connection would improve clarity and prevent confusion when following the workflow.

📝 Suggested clarification

Option 1: Update line 28 to reference all three states:

 7. Exit only when zero nodes are open. Print the resolved tree as a flat
-   list: "Decision — Choice — Source (user / inferred: path)".
+   list: "Decision — Choice — Source (user for pinned nodes / inferred: path)".

Option 2: Update lines 16-17 to use "user-pinned" to match output terminology:

 2. Extract the decision tree. Every branch point becomes a node. Mark
-   nodes as **open** (not decided), **pinned** (decided by the user), or
+   nodes as **open** (not decided), **user-pinned** (decided by the user), or
    **inferred** (derivable from the codebase or existing constraints).

Also applies to: 28-28

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@skills/plan-interrogate/SKILL.md` around lines 15 - 17, Step 2 defines node
states as "open", "pinned", and "inferred" but Step 7's output format uses "user
/ inferred: path" and omits "pinned", causing ambiguity; update the
documentation so terminology is consistent by either (A) changing Step 7's
output format text to explicitly list all three states (e.g., "user-pinned /
inferred: path" or "pinned / inferred: path") or (B) renaming "pinned" in Step 2
to "user-pinned" (or "user") to match the output; apply the same change wherever
node states are referenced to keep "nodes", "open", "pinned"/"user-pinned", and
"inferred" aligned across the doc.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/git-blast-radius.js`:
- Line 39: The console.error call prints the raw command (variable command)
which can include credentials; create and call a sanitizer (e.g.,
sanitizeCommand) before logging or log only the matched subcommand/flag (e.g.,
name and the specific matched token) instead of the full command. Implement
sanitizeCommand to strip userinfo from any URL patterns (remove
username:password@ or username@ in https?://... and git+ssh urls) and return a
redacted string, then replace console.error(`[pro-workflow] git-blast-radius:
blocked "${name}". Command: ${command}`) with a call that uses the sanitized
value (or just `${name} ${matchedFlag}`) to avoid leaking credentials. Ensure
references to command and name remain correct and unit-test the regex against
examples like https://USER:TOKEN@host/... and https://USER@host/... to confirm
redaction.
- Around line 2-14: The BLOCK regexes in scripts/git-blast-radius.js assume the
subcommand immediately follows "git", so commands with global options (e.g. git
-c ... reset --hard), refspec force pushes (git push origin +main) and
push-based ref deletions (git push origin :feature) slip through; update the
matching by first normalizing the command string (remove leading "git" and any
global options like -c KEY=VAL or --no-pager / --<flag>) before testing against
BLOCK, and strengthen these specific entries: adjust the "force push" entry to
also detect refspec-based forcing (look for "\s+\+\S" or refspec patterns),
extend "ref deletion" to catch push-based deletions (":\S" in push args), and
make "interactive rebase on protected branch" tolerate "-i" appearing after the
branch token as well as before; reference the BLOCK array and the entries named
'force push', 'ref deletion', and 'interactive rebase on protected branch' when
applying fixes.

In `@skills/plan-interrogate/SKILL.md`:
- Around line 1-4: The SKILL frontmatter for plan-interrogate should include
user-invocable: true and a shorter description; update the YAML block in
SKILL.md for the skill named "plan-interrogate" to add user-invocable: true and
replace the long description with a concise one such as "Stress-test a plan by
walking its decision tree one question at a time." so the skill is discoverable
and succinct.

---

Nitpick comments:
In `@skills/plan-interrogate/SKILL.md`:
- Around line 15-17: Step 2 defines node states as "open", "pinned", and
"inferred" but Step 7's output format uses "user / inferred: path" and omits
"pinned", causing ambiguity; update the documentation so terminology is
consistent by either (A) changing Step 7's output format text to explicitly list
all three states (e.g., "user-pinned / inferred: path" or "pinned / inferred:
path") or (B) renaming "pinned" in Step 2 to "user-pinned" (or "user") to match
the output; apply the same change wherever node states are referenced to keep
"nodes", "open", "pinned"/"user-pinned", and "inferred" aligned across the doc.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65e5a6a1-c900-4446-a93a-7b3c582bbc59

📥 Commits

Reviewing files that changed from the base of the PR and between a80b4bf and abcf98a.

📒 Files selected for processing (8)
  • commands/doctor.md
  • hooks/hooks.json
  • rules/incremental-verify.mdc
  • rules/module-shape.mdc
  • scripts/git-blast-radius.js
  • skills/bug-capture/SKILL.md
  • skills/module-map/SKILL.md
  • skills/plan-interrogate/SKILL.md
✅ Files skipped from review due to trivial changes (4)
  • skills/module-map/SKILL.md
  • rules/module-shape.mdc
  • skills/bug-capture/SKILL.md
  • rules/incremental-verify.mdc
🚧 Files skipped from review as they are similar to previous changes (1)
  • hooks/hooks.json

Comment thread scripts/git-blast-radius.js
Comment thread scripts/git-blast-radius.js Outdated
Comment thread skills/plan-interrogate/SKILL.md
commit-validate.js:
- Parse --message flag (space and = forms) in addition to -m.
- Detect heredoc with any tag, not only EOF, including <<- style.
- Treat -F/--file and editor-mode commit (no -m/-F/--amend) as
  pass-through instead of silent ok. For unrecognized forms, emit
  a non-blocking stderr warning so skipped validation is visible.
- Return structured {msg, form} from extractMessage so the driver
  can branch on intent.

secret-scan.js:
- Allowlist now tests the full surrounding line, not just the
  matched token. Fixes the AWS/GitHub/private-key classes where
  the snippet itself cannot contain the allowlist signal.
- Hook matcher in hooks.json broadened from Write to (Edit || Write)
  so secrets introduced via Edit into existing files are also caught.

git-blast-radius.js:
- Tolerate global options between git and the subcommand
  (git -c foo=bar, git --no-pager, git -C dir) via a shared
  GIT_PREFIX regex builder.
- Add patterns for refspec force push (push origin +branch) and
  remote branch deletion (push origin :ref, push --delete).
- Redact credentials embedded in URLs (https://user:token@host/...)
  before echoing the blocked command to stderr.

plan-interrogate/SKILL.md:
- Shorten frontmatter description; add user-invocable: true.
- Collapse node-state terminology from three states (open/pinned/
  inferred) to two (open/resolved) with a source tag (user|inferred)
  to match the final output format.

commands/doctor.md:
- Replace happy-path-only sanity checks with paired positive and
  negative cases for each hook script, so a script that always
  exits 0 fails the doctor instead of passing silently.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/commit-validate.js`:
- Around line 37-42: The regex that computes hasExplicitFlag in
scripts/commit-validate.js incorrectly treats --amend as a message-providing
flag, causing the branch in the git commit detection (the if block testing
/\bgit\s+(?:-[^\s]+\s+)*commit\b/) to return 'unknown' for `git commit --amend`;
update the hasExplicitFlag logic to only consider flags that actually supply a
message (e.g., -m, --message, -F, --file) and exclude --amend (or invert the
logic so only message flags set hasExplicitFlag), so that when --amend appears
without a message flag the function returns { msg: null, form: 'editor' }
instead of 'unknown'.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 81490812-21cf-4553-b167-4ac86ccfa868

📥 Commits

Reviewing files that changed from the base of the PR and between abcf98a and 9239a5b.

📒 Files selected for processing (6)
  • commands/doctor.md
  • hooks/hooks.json
  • scripts/commit-validate.js
  • scripts/git-blast-radius.js
  • scripts/secret-scan.js
  • skills/plan-interrogate/SKILL.md
✅ Files skipped from review due to trivial changes (1)
  • commands/doctor.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • hooks/hooks.json
  • scripts/secret-scan.js
  • scripts/git-blast-radius.js

Comment on lines +37 to +42
if (/\bgit\s+(?:-[^\s]+\s+)*commit\b/.test(command)) {
const afterCommit = command.split(/\bcommit\b/)[1] || '';
const hasExplicitFlag = /(?:-m|--message|-F|--file|--amend)\b/.test(afterCommit);
if (!hasExplicitFlag) return { msg: null, form: 'editor' };
return { msg: null, form: 'unknown' };
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

--amend without -m is misclassified as unknown instead of editor.

git commit --amend (no message flag) opens the editor, same as a bare git commit. With the current logic, --amend sets hasExplicitFlag = true, so it falls into the unknown branch and prints a "could not parse commit message" warning, even though this is a perfectly normal editor-mode amend. Consider excluding --amend from the explicit-flag set, or only treating -m/--message/-F/--file as message-providing flags:

Proposed diff
-    const hasExplicitFlag = /(?:-m|--message|-F|--file|--amend)\b/.test(afterCommit);
+    const hasExplicitFlag = /(?:-m|--message|-F|--file)\b/.test(afterCommit);
     if (!hasExplicitFlag) return { msg: null, form: 'editor' };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/commit-validate.js` around lines 37 - 42, The regex that computes
hasExplicitFlag in scripts/commit-validate.js incorrectly treats --amend as a
message-providing flag, causing the branch in the git commit detection (the if
block testing /\bgit\s+(?:-[^\s]+\s+)*commit\b/) to return 'unknown' for `git
commit --amend`; update the hasExplicitFlag logic to only consider flags that
actually supply a message (e.g., -m, --message, -F, --file) and exclude --amend
(or invert the logic so only message flags set hasExplicitFlag), so that when
--amend appears without a message flag the function returns { msg: null, form:
'editor' } instead of 'unknown'.

@rohitg00 rohitg00 merged commit f9e421e into main Apr 17, 2026
5 checks passed
@rohitg00 rohitg00 deleted the fix/issue-47-deterministic-hooks branch April 17, 2026 15:10
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.

LLM-powered hooks fail silently because "model": "haiku" isn't resolvable in current Claude Code

1 participant