feat: activity monitoring hooks with CodeRabbit review fixes#9
feat: activity monitoring hooks with CodeRabbit review fixes#9ellisjr wants to merge 20 commits intojrenaldi79:mainfrom
Conversation
Adds a skill that triggers after completing a feature/fix, offering to spawn a headless sidecar (Plan mode, read-only) to review changes for bugs, missed edge cases, and quality issues before claiming done. Key design decisions: - Dynamic model discovery via sidecar_guide (not hardcoded aliases) - Timeout omitted — uses sidecar platform default (15 min) - Polling cadence follows sidecar_status responses - Explicit terminal states: complete, timeout, crashed, error, aborted - Always unions staged + unstaged diffs before empty check - includeContext controls conversation history, not file access - Proof of failure required for each finding (reduces false positives) - git diff --stat for large diffs; Plan agent has read_file access - Briefing includes validation context and hardcoded-value checks - Dependency impact flagging for manifest/lockfile changes - Non-blocking: review runs in background while user gets completion msg Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ging Adds a skill that triggers after 5+ failed distinct approaches, offering an iterative brainstorming loop with a sidecar. The sidecar suggests ideas, Claude evaluates them against codebase knowledge, and feeds back what won't work — prompting deeper suggestions. Key design decisions: - Uses repeated sidecar_start (not sidecar_continue) to stay in Plan mode (headless sidecar_continue forces Build mode per mcp-server.js:275) - Dynamic model discovery via sidecar_guide (not hardcoded aliases) - Timeout omitted — uses sidecar platform default (15 min) - Polling cadence follows sidecar_status responses - Explicit terminal states: complete, timeout, crashed, error, aborted - Multi-model narrowing: all models round 1, best model only from round 2 - User check-in every 5 rounds to prevent theoretical spiraling - Rubber duck effect: abort sidecar if briefing prep solves the problem - Sidecar designs diagnostic probes, not just suggestions - Broadened context requests (dir listings, env vars, dep trees) - Token management guidance in follow-up briefings - Per-model task ID tracking for multi-model brainstorms - Max 20 iterations with clear termination conditions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a skill that triggers when user asks to commit/push/PR, offering
a headless sidecar security audit before changes enter the repository.
Sequential (blocks commit) to catch critical vulnerabilities first.
Key design decisions:
- Dynamic base branch resolution with full fallback chain
(origin/HEAD -> @{upstream} -> main -> master -> ask user)
- Audits union of staged + unstaged for commits; full branch diff for PRs
- Dynamic model discovery via sidecar_guide (not hardcoded aliases)
- Timeout omitted — uses sidecar platform default (15 min)
- Polling cadence follows sidecar_status responses
- Explicit terminal states: complete, timeout, crashed, error, aborted
- Attack vector required for each finding (reduces false positives)
- CI/CD files (.github/workflows/, etc.) in high-priority list
- Suspicious dependency detection (typosquatting, unmaintained packages)
- Security-relevant file prioritization for large diffs
- Critical/high findings block commit; medium findings are user's choice
- Does not fire on slash commands (/commit) — isolated context
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Updates postinstall.js to also install auto-review, auto-unblock, and auto-security skills to ~/.claude/skills/sidecar/<name>/SKILL.md alongside the main SKILL.md. Without this, npm install would not deploy the auto-skills to the user's skill directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of hardcoding the list of auto-skills, scan the skill/ directory for auto-* subdirectories at install time. This ensures new auto-skills are picked up without updating the script. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…acts Adds a new auto-skill that offers second-opinion sidecar reviews at natural BMAD-METHOD workflow checkpoints. Covers 9 artifact types (PRD, architecture, epics, stories, etc.) with sequential multi-model review where each model sees improvements from prior rounds. Includes bmad-workflow.md reference doc. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add sharded epics handling (concatenate shards with headers in briefing) - Remove Implementation Readiness from scope (gate, not persisted artifact) - Add partial pipeline failure handling (preserve changes, continue to next model) - Add explicit sleep 25 polling rule in Step 4a - Add prioritization guidance for large input document sets - Add scope rationale note explaining intentional artifact selectivity Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…catch - Add `text` language tags to bare code fences in bmad-workflow.md - Wrap AUTO_SKILLS readdirSync in try-catch for resilience Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds an "Auto-Skills" section to the main sidecar SKILL.md listing all four auto-skills with trigger conditions. Since the main skill appears in Claude's available skills list, this gives Claude passive awareness of auto-skill triggers without requiring new hook infrastructure. Also adds docs/auto-skill-invocation-research.md with findings on how Superpowers achieves reliable skill invocation and recommendations for improving sidecar auto-skill reliability (SessionStart hook, etc.). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Install auto-skills as top-level skill directories (~/.claude/skills/sidecar-auto-*/) instead of nested under sidecar/. This makes them appear in Claude Code's available skills list, enabling both auto-fire on trigger conditions AND manual invocation via slash commands (/sidecar-auto-review, /sidecar-auto-security, etc.). - Rename frontmatter `name` fields to sidecar-auto-* prefix - Update postinstall.js to install to top-level with cleanup of old path - Update main SKILL.md reference to new location Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reflects that Options B and D are now implemented. Updates the gap analysis table, adds verified skills list output, documents the nesting depth discovery, and refocuses remaining recommendations on SessionStart hook and config framework. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Claude Code hooks as a second triggering path for auto-skills, alongside existing description-matching. Phase 1 delivers shell hooks for auto-security (PreToolUse gate on git commit/push/PR) and auto-bmad-check (PostToolUse on _bmad-output/ writes), plus event collection for Phase 2 stuck-loop and review-completion detection. Includes: - autoSkills config namespace with master + per-skill enable/disable - `sidecar auto-skills` CLI command for managing auto-skill state - Shell hooks (pre-bash.sh, post-tool-use.sh, post-failure.sh) - Hook registration in postinstall with absolute path resolution - Cleanup on npm uninstall (preuninstall.js) - Step 0 config check in all 4 auto-skill SKILL.md files - Research doc analyzing 3 approaches (recommends Approach B) - 23 unit tests for config module Reviewed by Gemini Pro (9 findings) and ChatGPT (12 findings), all accepted fixes applied before commit. Builds on PR jrenaldi79#7 (feat/auto-sidecar-skills) which introduced the four auto-skills. Addresses jrenaldi79#8 (activity monitoring for auto-skill triggers). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use jq for safe JSON construction in BMAD trigger (prevent injection from filenames with quotes/backslashes) - Make mktemp failure non-fatal in all hooks (exit 0 instead of blocking git operations) - Fix CLAUDE.md auto-generated description for preuninstall.js - Update postinstall.js JSDoc to mention hook registration - Gate first-run notice on hooks actually being registered Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Split registerHooks into mergeHooks + registerHooks (both under 50 lines) - Deduplicate hooks by basename (not full path) to prevent accumulation on upgrades - Fix TOCTOU race: use umask for atomic file creation with restrictive permissions - Extract getUsage() to cli-usage.js (cli.js now 289 lines, under 300 limit) - Handle --key=value syntax in CLI arg parser - Sanitize SESSION_ID to prevent path traversal in event file paths - Fix jq slice syntax ([:500] -> [0:500]) for broader compatibility - Cap event JSONL files at 5MB to prevent unbounded growth - Add Phase 2 forward-reference comment for stop-hook.sh in preuninstall Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes 14 issues found during structured code review: High-confidence fixes (score 100): - Replace console.error with logger.error in start-helpers.js (lint failure) - Fix JSON injection in registerHooks via JSON.stringify escaping - Prevent mergeHooks from unconditionally rewriting settings.json - Scope basename dedup to claude-sidecar paths to protect user hooks Sub-threshold fixes (score 50-75): - Move cli-handlers.js no-console override to .eslintrc.js - Extract parseAutoSkillsArgs to bring handleAutoSkills under 50 lines - Decouple monitoring.enabled from auto-skill triggers in hooks - Wrap saveConfig in try/catch in setAutoSkillsEnabled - Consolidate double progress.json read into single parse - Remove phantom stop-hook.sh from preuninstall.js - Remove broken docs/plans/index.md link from CLAUDE.md - Add unit tests for start-helpers, cli-handlers, and mergeHooks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes 4 issues found by multi-model sidecar review: - Fix String.replace() $-interpolation in registerHooks by using replacer function (() => safeHooksDir) instead of string argument - Add typeof guard for h.command in mergeHooks dedup filter to prevent TypeError crash on malformed settings.json entries - Return boolean from setAutoSkillsEnabled so CLI can detect and report save failures instead of printing false success (logger.warn -> error) - Compare full matcher object in mergeHooks alreadyExists check so upgraded matchers (e.g. adding MultiEdit) are properly replaced Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Security: make remote debugging opt-in (setup-window), fix TOCTOU race conditions in hook event files (post-tool-use, post-failure), add request timeout to API key validation, handle Anthropic 5xx/429 as errors, add curl timeout to publish workflow, fix pre-push hook to block on test failure. Bugs: fix validateOpenRouterKey backwards-compat alias signature, add await to handleAutoSkills, add try-catch and result checking to IPC get-api-keys handler, guard against null m.name in model-validator, default unknown skills to disabled, validate skill names before saving, use smarter thinking level fallback for high/xhigh, handle first release in publish workflow. Tests: fix process.env restoration in config-null-alias, fix nested model id test case, remove silent-pass risk in setup-ui-model test, update setup-window and api-key-validation tests for new behavior. Docs: add lang/title to social card HTML, clarify publishing auth method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds an auto-skills system: config and CLI surfaces, activity-monitoring hooks and shell hook scripts, postinstall/preuninstall hook registration, several auto-skill specifications, supporting utilities/tests, and robustness fixes across sidecar components and tooling. (50 words) Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant Tool as Dev Tool (git/gh/other)
participant Hook as Hook Script
participant Config as ~/.config/sidecar/config.json
participant Sidecar as Sidecar CLI/Agent
participant Skill as Auto-Skill
User->>Tool: run command (commit/push/edit/other)
Tool->>Hook: emits JSON event (stdin)
Hook->>Config: read settings (monitoring/autoSkills)
alt monitoring enabled
Hook->>Hook: write event to session JSONL
Hook->>Sidecar: return additionalContext / trigger payload
Sidecar->>Skill: handleAutoSkills / spawn sidecar models
Skill-->>Sidecar: results
Sidecar-->>User: display suggestions / apply changes
else monitoring disabled
Hook-->>Tool: exit quietly
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/utils/model-validator.js (1)
93-101:⚠️ Potential issue | 🟠 MajorGuard sort key normalization too; current fallback is only partial.
Line 94-95 hardens filtering, but Line 100 still assumes
nameis always a string. If a provider model has missing/non-stringname,localeComparewill throw and abort validation.Suggested defensive fix
function filterRelevantModels(models, alias) { const term = (ALIAS_SEARCH_TERMS[alias] || alias).toLowerCase(); - let filtered = models.filter(m => - (m.id || '').toLowerCase().includes(term) || - (m.name || '').toLowerCase().includes(term) + let filtered = models.filter((m) => + String(m?.id ?? '').toLowerCase().includes(term) || + String(m?.name ?? '').toLowerCase().includes(term) ); if (filtered.length === 0) { filtered = models; } - filtered.sort((a, b) => a.name.localeCompare(b.name)); + filtered.sort((a, b) => + String(a?.name ?? a?.id ?? '').localeCompare(String(b?.name ?? b?.id ?? '')) + ); return filtered.slice(0, 15); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/model-validator.js` around lines 93 - 101, The sort assumes every model has a string name and calls a.name.localeCompare(b.name), which can throw for missing/non-string names; update the sorting in the models filtering block to normalize the sort keys to strings (e.g., coerce a.name and b.name to '' or String(...) and optionally lower-case) before calling localeCompare so filtered.sort(...) never receives undefined/non-string values and remains stable when model.name is absent or not a string.
🧹 Nitpick comments (6)
tests/sidecar/setup-window.test.js (1)
57-58: Avoid unit-testing Electron window configuration details in this test.This assertion checks launch argument wiring (
main.jsplacement), which should be covered by manual CDP verification instead of unit tests.As per coding guidelines,
tests/**/*.js: “In tests, do not write unit tests for ... Electron window configuration - these require manual CDP verification.”🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/sidecar/setup-window.test.js` around lines 57 - 58, Remove the unit assertion that inspects Electron launch args for window configuration (the expect(spawnArgs[1][0]).toContain('main.js') check) from tests/sidecar/setup-window.test.js; instead either delete that line or replace it with a non-Electron-window-related assertion (e.g., verifying spawn was called or that a fallback path exists) so the test no longer asserts on Electron window/launch arguments like main.js which must be validated manually via CDP.src/sidecar/setup-window.js (1)
18-81: RefactorlaunchSetupWindowinto smaller helpers to meet function-size limits.This function is over the 50-line limit; extracting arg construction and stdout parsing would keep it within policy and improve readability.
As per coding guidelines,
src/**/*.js: “Any function must not exceed 50 lines.”🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/sidecar/setup-window.js` around lines 18 - 81, The launchSetupWindow function exceeds the 50-line limit and should be split into smaller helpers; extract the argument construction and stdout parsing into two functions (e.g., buildElectronArgs(debugPort, mainPath) to return the args array and parseSetupStdout(stdout) to return the same result object currently produced inside the proc.on('close') handler), then update launchSetupWindow to call those helpers and keep only process setup, logging, and wiring; make sure launchSetupWindow still resolves the same objects ({success, error, default, keyCount}) and preserves the existing debugPort handling, env assembly, and stdio listeners.scripts/preuninstall.js (1)
23-23: Add JSDoc for exportedremoveHooks()API.This is a public export and should be documented like other public functions in the codebase.
As per coding guidelines
**/*.js: Write JSDoc comments for all public APIs.Also applies to: 65-65
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/preuninstall.js` at line 23, Add a JSDoc comment block above the exported function removeHooks() describing its purpose, parameters (if any) and return value, matching the project's public API style; include a short description, `@returns` annotation (or `@returns` {void} if it returns nothing), and any relevant `@example` or `@throws` notes used elsewhere in the codebase so the function is documented like other public APIs (ensure the comment is placed immediately above the removeHooks function declaration).src/utils/auto-skills-config.js (2)
44-49: Minor: Redundant check on line 48.When
skillis truthy (not null/undefined),skill.enabled !== falseis sufficient. The current logic is correct but the ternary could be simplified.♻️ Simplified check
function isSkillEnabled(skillName, config) { const as = getAutoSkillsConfig(config); if (!as.enabled) { return false; } const skill = as[skillName]; - return skill ? skill.enabled !== false : false; + return skill?.enabled !== false; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/auto-skills-config.js` around lines 44 - 49, In isSkillEnabled, simplify the redundant ternary by returning the boolean expression directly: after calling getAutoSkillsConfig(config) and verifying as.enabled, compute const skill = as[skillName]; then return skill && skill.enabled !== false; update the function (isSkillEnabled) to use this simplified check referencing getAutoSkillsConfig, as and skill.
75-78: Dynamic require of logger inside function.The
require('./logger')inside the function body avoids potential circular dependency issues but adds overhead on each call. If circular dependencies aren't a concern, consider hoisting to module level.♻️ Hoist logger import if no circular dependency
const { loadConfig, saveConfig } = require('./config'); +const { logger } = require('./logger'); // ... later in setAutoSkillsEnabled and error handling ... - const { logger } = require('./logger'); logger.warn(`Ignoring invalid skill name: ${name}`);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/auto-skills-config.js` around lines 75 - 78, The code dynamically requires the logger inside the loop (in the for...of over skillNames) which adds repeated overhead; move the logger import out of the loop to module scope by adding const { logger } = require('./logger'); (or an equivalent top-level import) and replace the inline require in the block so the loop uses the hoisted logger; if a circular dependency actually exists, instead create a single cached require before the loop (e.g., let logger; if (!logger) logger = require('./logger').logger) so the require happens once.src/utils/api-key-validation.js (1)
34-35: Consider adding JSDoc for the public API.The
validateApiKeyfunction is exported and used externally but lacks JSDoc documentation describing parameters and return type.📝 Suggested JSDoc
+/** + * Validate an API key by making a test request to the provider's API + * `@param` {string} provider - Provider name (openrouter, openai, anthropic, google, deepseek) + * `@param` {string} key - API key to validate + * `@returns` {Promise<{valid: boolean, error?: string}>} Validation result + */ function validateApiKey(provider, key) {As per coding guidelines: "Write JSDoc comments for all public APIs"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/utils/api-key-validation.js` around lines 34 - 35, Add a JSDoc block above the exported validateApiKey function describing its purpose, parameters and return value: document the provider parameter (type and expected shape or allowed strings), the key parameter (string), the async return type (Promise<boolean> indicating whether the key is valid) and any errors thrown (use `@throws` if the function can reject with an error). Ensure tags include `@param` for provider and key, `@returns` {Promise<boolean>} and a short description of behavior so external callers and editors get proper tooling/type hints for validateApiKey.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@hooks/post-failure.sh`:
- Around line 57-64: Add a size-cap check before appending to EVENT_FILE: define
or use a MAX_EVENT_FILE_BYTES (env or constant) and, after verifying FILE_OWNER
equals "$(id -u)", determine the current file size using a portable stat
fallback (e.g., stat -c '%s' || stat -f '%z') and only perform echo "$EVENT" >>
"$EVENT_FILE" if the file size is less than MAX_EVENT_FILE_BYTES; if the cap is
reached, skip the append (or log/handle per policy). Keep the existing atomic
creation and ownership checks (EVENT, EVENT_FILE, FILE_OWNER) and ensure the
size check is done immediately before the append to avoid race windows.
In `@hooks/post-tool-use.sh`:
- Line 69: Replace the current telemetry value that stores the raw Bash command
(the expression using .tool_name and .tool_input.command) so it never persists
full command lines; instead when .tool_name == "Bash" extract and record only
the executable name (e.g., basename of the first token) or a deterministic
redacted placeholder, and for non-Bash tools keep existing behavior, ensuring
the stored value never contains arguments, environment exports, or URLs/tokens;
update the expression referencing .tool_name and .tool_input.command accordingly
so telemetry writes a safe executable name or redacted string to the session
file.
- Around line 74-77: The FILE_OWNER lookup uses stat -f first (BSD) then stat -c
(GNU), which yields wrong output on Linux; update the fallback order in the
FILE_OWNER assignment so it attempts GNU-style stat -c '%u' first, then
BSD-style stat -f '%u' as a fallback (preserving the existing 2>/dev/null error
suppression and final echo ""), ensuring the subsequent ownership comparison
against $(id -u) in the if condition works correctly across GNU and BSD systems.
In `@hooks/pre-bash.sh`:
- Around line 49-55: The grep -E patterns that search COMMAND for 'git commit',
'git push', and 'gh pr create' use \s which isn't portable; update each grep
invocation that references COMMAND (the lines setting IS_COMMIT=true) to replace
\s and \s+ with POSIX whitespace classes like [[:space:]] and [[:space:]]+ (e.g.
change '(^|[;&|]+\s*)git\s+commit(\s|$)' to
'(^|[;&|]+[[:space:]]*)git[[:space:]]+commit([[:space:]]|$)' and do the
analogous replacements for the git push and gh pr create patterns) so the hook
works on BSD/macOS.
In `@package.json`:
- Line 49: The preuninstall lifecycle script ("preuninstall": "node
scripts/preuninstall.js") is not executed by npm v7+, so replace the reliance on
that lifecycle by exposing the cleanup logic as an explicit npm script and
documenting its use: remove or stop relying on "preuninstall" and add a script
entry like "cleanup-hooks": "node scripts/preuninstall.js" (or rename the script
file to scripts/cleanup.js and reference it) and update user-facing docs to
instruct users to run npm run cleanup-hooks (or the package CLI equivalent)
before uninstalling; optionally keep a legacy "preuninstall" entry for older npm
versions but ensure the canonical mechanism is the explicit "cleanup-hooks"
script and any package uninstall docs reference it.
In `@scripts/postinstall.js`:
- Around line 175-188: The current filter on settings.hooks[event] removes
entire matcher objects when any contained hook is a sidecar; instead iterate
over each matcher (existing) and, if existing.hooks is an array, replace
existing.hooks with a filtered array that removes only hooks where typeof
h.command === 'string' && (h.command === cmd || (path.basename(h.command) ===
basename && h.command.includes('claude-sidecar'))); then keep the matcher only
if it still has at least one hook (existing.hooks.length > 0) or if it
originally had no hooks array; use the same symbols (basename, cmd, existing, h,
matcher, settings.hooks[event]) so you only drop sidecar-owned hooks and
preserve user hooks in the same matcher object.
In `@scripts/preuninstall.js`:
- Around line 17-21: isSidecarHookCommand currently uses only path.basename to
detect sidecar-owned hooks, which can falsely match user hooks elsewhere; update
it to check the command's full path against the sidecar install locations
instead of only basename. Replace the basename-based check in
isSidecarHookCommand (and the duplicate logic around the 37-42 block) with a
test that the absolute command path is inside the known sidecar hook directories
(or startsWith the sidecar install prefix) and then verify the file name is one
of SIDECAR_HOOK_SCRIPTS; ensure you resolve symlinks/absolute paths before
comparing so only files under sidecar-managed directories are considered owned.
In `@skill/auto-security/SKILL.md`:
- Around line 69-71: The docs currently instruct combining `git diff` and `git
diff --cached` for commit audits; change the default behavior so commits audit
only staged changes by default (use `git diff --cached`) and make the combined
unstaged+staged audit (the `git diff` + `git diff --cached` approach) opt-in;
update the "Choose the right diff for the operation" section text to explicitly
state the default is `git diff --cached` for commits, describe how to opt into
broader working-tree scans, and keep the PR/push guidance (the `git merge-base
...` diff resolution) unchanged.
In `@src/cli-usage.js`:
- Around line 11-91: The getUsage() function is too large; split its static
sections into separate constants/functions and have getUsage() assemble them.
Extract the multi-line string into smaller pieces like USAGE_HEADER,
USAGE_COMMANDS, OPTIONS_START, OPTIONS_LIST, OPTIONS_READ, AGENT_TYPES, EXAMPLES
(or helper functions buildStartOptions(), buildCommonOptions(), etc.), ensure
each extracted constant/function is under 50 lines, then return a single
concatenation/template literal in getUsage() that composes those pieces
(referencing the existing getUsage identifier to wire them together).
In `@src/cli.js`:
- Around line 59-63: The parser currently treats --flag=value as true in the
boolean-flag branch (isBooleanFlag), which silently accepts inline values;
update the logic in the CLI parsing function so that when isBooleanFlag(key) is
true you check whether a value was provided (i.e., the token contained '=' or
next token is a non-flag) and, if so, throw or return a clear error/message
instead of setting result[key]=true; only set result[key]=true when no inline
value is present. Locate the boolean handling around isBooleanFlag and
result[key] to implement this validation and error path.
In `@src/sidecar/progress.js`:
- Around line 177-186: The code uses progress.updatedAt without validating it,
which can produce an invalid Date and NaN progressMs; update the logic in the
block handling progress.updatedAt (references: progress.updatedAt, progressTime,
computeLastActivity, progressMs, lastActivityMs, convStat) to first construct
progressTime and guard that progressTime.getTime() is a finite number (e.g.,
!isNaN(progressTime.getTime())) before calling computeLastActivity or computing
progressMs; if the date is invalid, skip updating lastActivity and
lastActivityMs so they cannot become NaN.
---
Outside diff comments:
In `@src/utils/model-validator.js`:
- Around line 93-101: The sort assumes every model has a string name and calls
a.name.localeCompare(b.name), which can throw for missing/non-string names;
update the sorting in the models filtering block to normalize the sort keys to
strings (e.g., coerce a.name and b.name to '' or String(...) and optionally
lower-case) before calling localeCompare so filtered.sort(...) never receives
undefined/non-string values and remains stable when model.name is absent or not
a string.
---
Nitpick comments:
In `@scripts/preuninstall.js`:
- Line 23: Add a JSDoc comment block above the exported function removeHooks()
describing its purpose, parameters (if any) and return value, matching the
project's public API style; include a short description, `@returns` annotation (or
`@returns` {void} if it returns nothing), and any relevant `@example` or `@throws`
notes used elsewhere in the codebase so the function is documented like other
public APIs (ensure the comment is placed immediately above the removeHooks
function declaration).
In `@src/sidecar/setup-window.js`:
- Around line 18-81: The launchSetupWindow function exceeds the 50-line limit
and should be split into smaller helpers; extract the argument construction and
stdout parsing into two functions (e.g., buildElectronArgs(debugPort, mainPath)
to return the args array and parseSetupStdout(stdout) to return the same result
object currently produced inside the proc.on('close') handler), then update
launchSetupWindow to call those helpers and keep only process setup, logging,
and wiring; make sure launchSetupWindow still resolves the same objects
({success, error, default, keyCount}) and preserves the existing debugPort
handling, env assembly, and stdio listeners.
In `@src/utils/api-key-validation.js`:
- Around line 34-35: Add a JSDoc block above the exported validateApiKey
function describing its purpose, parameters and return value: document the
provider parameter (type and expected shape or allowed strings), the key
parameter (string), the async return type (Promise<boolean> indicating whether
the key is valid) and any errors thrown (use `@throws` if the function can reject
with an error). Ensure tags include `@param` for provider and key, `@returns`
{Promise<boolean>} and a short description of behavior so external callers and
editors get proper tooling/type hints for validateApiKey.
In `@src/utils/auto-skills-config.js`:
- Around line 44-49: In isSkillEnabled, simplify the redundant ternary by
returning the boolean expression directly: after calling
getAutoSkillsConfig(config) and verifying as.enabled, compute const skill =
as[skillName]; then return skill && skill.enabled !== false; update the function
(isSkillEnabled) to use this simplified check referencing getAutoSkillsConfig,
as and skill.
- Around line 75-78: The code dynamically requires the logger inside the loop
(in the for...of over skillNames) which adds repeated overhead; move the logger
import out of the loop to module scope by adding const { logger } =
require('./logger'); (or an equivalent top-level import) and replace the inline
require in the block so the loop uses the hoisted logger; if a circular
dependency actually exists, instead create a single cached require before the
loop (e.g., let logger; if (!logger) logger = require('./logger').logger) so the
require happens once.
In `@tests/sidecar/setup-window.test.js`:
- Around line 57-58: Remove the unit assertion that inspects Electron launch
args for window configuration (the expect(spawnArgs[1][0]).toContain('main.js')
check) from tests/sidecar/setup-window.test.js; instead either delete that line
or replace it with a non-Electron-window-related assertion (e.g., verifying
spawn was called or that a fallback path exists) so the test no longer asserts
on Electron window/launch arguments like main.js which must be validated
manually via CDP.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a97bfdb6-1ef6-4f96-b78f-83b2d4bb53bb
📒 Files selected for processing (43)
.eslintrc.js.github/workflows/publish.yml.husky/pre-pushCLAUDE.mdbin/sidecar.jsdocs/activity-monitoring-research.mddocs/auto-skill-invocation-proposal.mddocs/bmad-workflow.mddocs/publishing.mdelectron/ipc-setup.jshooks/hooks.jsonhooks/post-failure.shhooks/post-tool-use.shhooks/pre-bash.shpackage.jsonscripts/generate-docs-helpers.jsscripts/postinstall.jsscripts/preuninstall.jssite/social-card-render.htmlskill/SKILL.mdskill/auto-bmad-method-check/SKILL.mdskill/auto-review/SKILL.mdskill/auto-security/SKILL.mdskill/auto-unblock/SKILL.mdsrc/cli-handlers.jssrc/cli-usage.jssrc/cli.jssrc/sidecar/progress.jssrc/sidecar/setup-window.jssrc/utils/api-key-validation.jssrc/utils/auto-skills-config.jssrc/utils/model-validator.jssrc/utils/start-helpers.jssrc/utils/thinking-validators.jstests/api-key-store-validation.test.jstests/auto-skills-config.test.jstests/cli-handlers.test.jstests/config-null-alias.test.jstests/model-validator-normalize.test.jstests/postinstall.test.jstests/setup-ui-model.test.jstests/sidecar/setup-window.test.jstests/start-helpers.test.js
| "posttest": "git rev-parse HEAD > .test-passed 2>/dev/null || true", | ||
| "lint": "eslint src/", | ||
| "postinstall": "node scripts/postinstall.js", | ||
| "preuninstall": "node scripts/preuninstall.js", |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Does npm v10 (and npm v7+) execute package.json "preuninstall" scripts during npm uninstall? Please cite the official npm lifecycle docs.
💡 Result:
No. In npm v7 and later (including npm v10), npm uninstall does not run uninstall lifecycle scripts—so preuninstall (and uninstall / postuninstall) will not execute.
The official npm lifecycle docs state: “While npm v6 had uninstall lifecycle scripts, npm v7 does not… uninstall lifecycle scripts are not implemented and will not function.” [1] (same note is present in the v8+ scripts docs as well). [2]
Sources (official npm docs):
[1] (docs.npmjs.com)
[2] (docs.npmjs.com)
Citations:
- 1: https://docs.npmjs.com/cli/v10/using-npm/scripts/?v=true
- 2: https://docs.npmjs.com/cli/v8/using-npm/scripts/
preuninstall script will not execute in npm v7+, leaving cleanup logic unrun.
The preuninstall script on line 49 relies on uninstall lifecycle execution, which is not supported in npm v7 and later. According to official npm documentation, uninstall lifecycle scripts (preuninstall, uninstall, postuninstall) are not implemented in npm v7+. This means user hook entries may remain after package removal.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` at line 49, The preuninstall lifecycle script ("preuninstall":
"node scripts/preuninstall.js") is not executed by npm v7+, so replace the
reliance on that lifecycle by exposing the cleanup logic as an explicit npm
script and documenting its use: remove or stop relying on "preuninstall" and add
a script entry like "cleanup-hooks": "node scripts/preuninstall.js" (or rename
the script file to scripts/cleanup.js and reference it) and update user-facing
docs to instruct users to run npm run cleanup-hooks (or the package CLI
equivalent) before uninstalling; optionally keep a legacy "preuninstall" entry
for older npm versions but ensure the canonical mechanism is the explicit
"cleanup-hooks" script and any package uninstall docs reference it.
| function isSidecarHookCommand(command) { | ||
| if (!command || typeof command !== 'string') { return false; } | ||
| const basename = path.basename(command); | ||
| return SIDECAR_HOOK_SCRIPTS.includes(basename); | ||
| } |
There was a problem hiding this comment.
Avoid basename-only hook ownership detection; it can delete user hooks.
Current matching treats any pre-bash.sh/post-tool-use.sh/post-failure.sh as sidecar-owned, even outside sidecar paths, which can remove unrelated user hooks during uninstall.
🔧 Proposed fix
function isSidecarHookCommand(command) {
if (!command || typeof command !== 'string') { return false; }
const basename = path.basename(command);
- return SIDECAR_HOOK_SCRIPTS.includes(basename);
+ return SIDECAR_HOOK_SCRIPTS.includes(basename) && command.includes('claude-sidecar');
}Also applies to: 37-42
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/preuninstall.js` around lines 17 - 21, isSidecarHookCommand currently
uses only path.basename to detect sidecar-owned hooks, which can falsely match
user hooks elsewhere; update it to check the command's full path against the
sidecar install locations instead of only basename. Replace the basename-based
check in isSidecarHookCommand (and the duplicate logic around the 37-42 block)
with a test that the absolute command path is inside the known sidecar hook
directories (or startsWith the sidecar install prefix) and then verify the file
name is one of SIDECAR_HOOK_SCRIPTS; ensure you resolve symlinks/absolute paths
before comparing so only files under sidecar-managed directories are considered
owned.
src/cli.js
Outdated
| // Boolean flags (no value expected) — ignore inline values (--on=x is invalid) | ||
| if (isBooleanFlag(key)) { | ||
| result[key] = true; | ||
| continue; | ||
| } |
There was a problem hiding this comment.
Don’t silently ignore inline values on boolean flags.
Lines [59-63] treat --flag=value as just true. For --on=security, that can unexpectedly activate master enable behavior instead of targeting a skill.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/cli.js` around lines 59 - 63, The parser currently treats --flag=value as
true in the boolean-flag branch (isBooleanFlag), which silently accepts inline
values; update the logic in the CLI parsing function so that when
isBooleanFlag(key) is true you check whether a value was provided (i.e., the
token contained '=' or next token is a non-flag) and, if so, throw or return a
clear error/message instead of setting result[key]=true; only set
result[key]=true when no inline value is present. Locate the boolean handling
around isBooleanFlag and result[key] to implement this validation and error
path.
- Redact bash commands in telemetry (truncate + strip long base64) - Fix stat fallback order to GNU first in post-tool-use.sh - Use POSIX [[:space:]] instead of \s in pre-bash.sh grep patterns - Fix postinstall mergeHooks to remove only sidecar hooks within matchers, not entire matcher objects with mixed ownership - Use path-based detection in preuninstall to avoid removing user hooks with same basename - Scope auto-security commit scans to staged changes only - Split getUsage() into sub-50-line helper functions - Guard model-validator sort against null name - Guard progress.js against invalid updatedAt dates - Warn when boolean CLI flags receive inline values - Add uninstall script as fallback for preuninstall lifecycle Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/cli.js (2)
40-92: 🛠️ Refactor suggestion | 🟠 MajorSplit
parseArgsto meet max function-length guideline.
parseArgsexceeds the 50-line cap; please extract boolean/value/array flag handlers into helpers.As per coding guidelines,
src/**/*.js: "Any function must not exceed 50 lines".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli.js` around lines 40 - 92, The parseArgs function is over the 50-line limit; extract the inline handlers into small helpers to reduce its length while preserving behavior: create e.g. handleBooleanFlag(key, inlineValue, result, logger) to set boolean flags and warn on inline values (used where isBooleanFlag(key) is true), handleArrayFlag(key, val, result) to accumulate values for the 'exclude-mcp' case (ensure it increments the caller's index when consumed), and handleOptionWithValue(key, inlineValue, next, result) to parse and assign values via parseValue or set true when no value is present; update parseArgs to call these helpers from the main loop and keep DEFAULTS merging and the positional-arguments push logic unchanged so semantics and i increments remain identical.
78-85:⚠️ Potential issue | 🟠 MajorReject missing values for value-flags instead of coercing to
true.At Line [84], non-boolean flags without values become
true. That can break downstream validation (e.g.,--modelwith no value can reachisValidModelFormatand fail when calling.spliton a boolean).🛠️ Proposed fix
+function requiresValueFlag(key) { + const valueFlags = new Set([ + 'prompt', 'model', 'cwd', 'session-id', 'context-turns', + 'context-max-tokens', 'timeout', 'opencode-port', 'summary-length', + 'context-since', 'thinking', 'mcp', 'mcp-config', 'client', 'session-dir' + ]); + return valueFlags.has(key); +} + function parseArgs(argv) { const result = { _: [], ...DEFAULTS }; @@ if (inlineValue !== undefined) { result[key] = parseValue(key, inlineValue); } else if (next && !next.startsWith('--')) { result[key] = parseValue(key, next); i++; + } else if (requiresValueFlag(key)) { + logger.warn(`Flag --${key} requires a value`); } else { result[key] = true; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli.js` around lines 78 - 85, The current CLI parsing loop sets result[key] = true when a non-boolean flag has no inline value and the next token is absent or is another flag, which causes downstream errors (e.g., parseValue expecting a string). Update the final else branch in the loop (the block handling inlineValue/next) to reject missing values for value-flags: instead of assigning true, detect a missing value for the flag identified by key and throw or return a clear error like "Missing value for flag --<key>" (or call your CLI error handler), so callers of parseValue(key, ...) never receive a boolean for a value-flag; keep parseValue, inlineValue, next and result as the referenced symbols to locate and modify the logic.
🧹 Nitpick comments (3)
skill/auto-security/SKILL.md (1)
141-142: Minor: Consider hyphenating "medium-severity" as a compound modifier.Static analysis suggests using "medium-severity issues" for grammatical correctness.
📝 Suggested wording
-- **If medium severity issues found:** Surface them as warnings +- **If medium-severity issues found:** Surface them as warnings🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@skill/auto-security/SKILL.md` around lines 141 - 142, Replace the unhyphenated phrase "medium severity" with the compound modifier "medium-severity" in the SKILL.md text (specifically where the lines mention "If medium severity issues found" and "Security scan flagged some medium-severity items" / "medium severity items") so the compound modifier is grammatically correct; update both occurrences to "medium-severity issues" or "medium-severity items" as appropriate, keeping the surrounding wording intact.src/cli-usage.js (1)
48-49: Minor: Inconsistent indentation in options list.Lines 48-49 have extra leading spaces compared to other options:
- --context-max-tokens <N> Max context tokens (default: 80000) - --summary-length <length> Summary verbosity: brief, normal (default), verbose + --context-max-tokens <N> Max context tokens (default: 80000) + --summary-length <length> Summary verbosity: brief, normal (default), verbose🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/cli-usage.js` around lines 48 - 49, Fix the inconsistent indentation in the CLI usage options list by aligning the two options shown to the same indentation as the other entries; specifically update the lines defining the "--context-max-tokens" and "--summary-length" options in the usage text so they do not have the extra leading spaces and match the indentation style used elsewhere in the options output.src/sidecar/progress.js (1)
114-199: Consider splittingreadProgress()to meet the 50-line function limit.The
readProgressfunction spans ~85 lines, exceeding the 50-line guideline. Consider extracting helpers such as:
readConversationEntries(convPath)for lines 121-136processProgressJson(progressPath, ...)for lines 156-193This would improve maintainability and align with the coding standards.
As per coding guidelines
src/**/*.js: "Any function must not exceed 50 lines".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/sidecar/progress.js` around lines 114 - 199, readProgress is >50 lines; split it into helpers to meet the guideline: extract the conversation-reading block into readConversationEntries(convPath) which returns {entries, convStat} and preserves skipping blank/malformed lines and setting convStat via fs.statSync, and extract the progress handling into processProgressJson(progressPath, {entries, convStat, messages, latest, lastActivity, lastActivityMs}) which reads progress.json safely, applies stage, progress.stageLabel, progress.latestTool fallback logic, messagesReceived override, updatedAt-based lastActivity/lastActivityMs adjustments, and ignores malformed files; then refactor readProgress to call these two helpers and return the same result object (including conditionally adding stage), while still using computeLastActivity and extractLatest as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/cli.js`:
- Around line 40-92: The parseArgs function is over the 50-line limit; extract
the inline handlers into small helpers to reduce its length while preserving
behavior: create e.g. handleBooleanFlag(key, inlineValue, result, logger) to set
boolean flags and warn on inline values (used where isBooleanFlag(key) is true),
handleArrayFlag(key, val, result) to accumulate values for the 'exclude-mcp'
case (ensure it increments the caller's index when consumed), and
handleOptionWithValue(key, inlineValue, next, result) to parse and assign values
via parseValue or set true when no value is present; update parseArgs to call
these helpers from the main loop and keep DEFAULTS merging and the
positional-arguments push logic unchanged so semantics and i increments remain
identical.
- Around line 78-85: The current CLI parsing loop sets result[key] = true when a
non-boolean flag has no inline value and the next token is absent or is another
flag, which causes downstream errors (e.g., parseValue expecting a string).
Update the final else branch in the loop (the block handling inlineValue/next)
to reject missing values for value-flags: instead of assigning true, detect a
missing value for the flag identified by key and throw or return a clear error
like "Missing value for flag --<key>" (or call your CLI error handler), so
callers of parseValue(key, ...) never receive a boolean for a value-flag; keep
parseValue, inlineValue, next and result as the referenced symbols to locate and
modify the logic.
---
Nitpick comments:
In `@skill/auto-security/SKILL.md`:
- Around line 141-142: Replace the unhyphenated phrase "medium severity" with
the compound modifier "medium-severity" in the SKILL.md text (specifically where
the lines mention "If medium severity issues found" and "Security scan flagged
some medium-severity items" / "medium severity items") so the compound modifier
is grammatically correct; update both occurrences to "medium-severity issues" or
"medium-severity items" as appropriate, keeping the surrounding wording intact.
In `@src/cli-usage.js`:
- Around line 48-49: Fix the inconsistent indentation in the CLI usage options
list by aligning the two options shown to the same indentation as the other
entries; specifically update the lines defining the "--context-max-tokens" and
"--summary-length" options in the usage text so they do not have the extra
leading spaces and match the indentation style used elsewhere in the options
output.
In `@src/sidecar/progress.js`:
- Around line 114-199: readProgress is >50 lines; split it into helpers to meet
the guideline: extract the conversation-reading block into
readConversationEntries(convPath) which returns {entries, convStat} and
preserves skipping blank/malformed lines and setting convStat via fs.statSync,
and extract the progress handling into processProgressJson(progressPath,
{entries, convStat, messages, latest, lastActivity, lastActivityMs}) which reads
progress.json safely, applies stage, progress.stageLabel, progress.latestTool
fallback logic, messagesReceived override, updatedAt-based
lastActivity/lastActivityMs adjustments, and ignores malformed files; then
refactor readProgress to call these two helpers and return the same result
object (including conditionally adding stage), while still using
computeLastActivity and extractLatest as before.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 837ef997-59a8-4c76-8aa2-de7d44c60f42
📒 Files selected for processing (12)
CLAUDE.mdhooks/post-failure.shhooks/post-tool-use.shhooks/pre-bash.shpackage.jsonscripts/postinstall.jsscripts/preuninstall.jsskill/auto-security/SKILL.mdsrc/cli-usage.jssrc/cli.jssrc/sidecar/progress.jssrc/utils/model-validator.js
🚧 Files skipped from review as they are similar to previous changes (3)
- hooks/pre-bash.sh
- hooks/post-failure.sh
- hooks/post-tool-use.sh
Summary
additionalContextwhen Write/Edit targets_bmad-output/.auto-review,auto-unblock,auto-security,auto-bmad-method-check) with master/per-skill enable/disable controls, CLI management viasidecar auto-skills, and config persistence.Key changes
Security
SIDECAR_DEBUG_PORT(was always-on port 9222)Bug fixes
validateOpenRouterKeybackwards-compat alias now correctly wraps with(key) => validateApiKey('openrouter', key)handleAutoSkillsnow properlyawaited in CLI dispatchget-api-keyshandler: try-catch added,saveApiKeyresult checked before reporting successfilterRelevantModelsguards against nullm.name|| exit 1)Tests
process.envrestoration, nested model ID test case, silent-pass risk in UI testTest plan
npm test— 1558 tests pass (70 suites)npm run lint— clean🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Bug Fixes / Reliability
Chores