Skip to content

Refactor/fix jscpd duplicates#50

Merged
kenryu42 merged 13 commits into
mainfrom
refactor/fix-jscpd-duplicates
May 8, 2026
Merged

Refactor/fix jscpd duplicates#50
kenryu42 merged 13 commits into
mainfrom
refactor/fix-jscpd-duplicates

Conversation

@kenryu42
Copy link
Copy Markdown
Owner

@kenryu42 kenryu42 commented May 8, 2026

Summary

  • Eliminate copy-paste code duplication across source and test files, bringing the codebase below jscpd thresholds
  • Extract shared modules: src/bin/hooks/common.ts (hook I/O + command analysis), src/core/analyze/child-command.ts (child command normalization), and tests/bin/hooks/hook-helpers.ts (hook test utilities)
  • Add jscpd duplicate detection to the check / check:ci scripts to prevent future regressions

Changes

N/A

New shared modules

  • src/bin/hooks/common.ts — Consolidates readHookInput, parseHookJson, and handleBlockedHookCommand that were previously duplicated across all three hook handlers (Claude Code, Copilot CLI, Gemini CLI)
  • src/core/analyze/child-command.ts — Extracts normalizeChildCommand (wrapper stripping, busybox handling, env merging) and collectCommandTemplate that were duplicated between parallel.ts and xargs.ts

Source refactors

  • src/bin/hooks/{claude-code,copilot-cli,gemini-cli}.ts — Replaced duplicated stdin reading, JSON parsing, env loading, config loading, and command analysis with calls to common.ts helpers
  • src/bin/doctor/format.ts — Extracted a shared formatAsciiTable function, replacing 5 nearly-identical inline table rendering blocks (hooks, rules, config, environment, activity, update tables)
  • src/core/analyze/parallel.ts — Replaced inline wrapper stripping + busybox handling with normalizeChildCommand
  • src/core/analyze/xargs.ts — Same refactor as parallel.ts
  • src/core/analyze/analyze-command.ts — Extracted exportTrackedGitContextEnvName to deduplicate two identical export-handling blocks
  • src/core/rules-git.ts — Replaced local findDotGitPath with shared findDotGitInAncestors from worktree.ts
  • src/core/worktree.ts — Exported findDotGitInAncestors for reuse by rules-git.ts

Test refactors

  • tests/bin/hooks/hook-helpers.ts — New shared helper with runHook, expectNoHookOutput, input builders (copilotBashInput, geminiShellInput, claudeCodeBashInput), and platform-specific hook runners
  • tests/helpers.ts — Added shared utilities: withTempDir, runSafetyNetCli, withStdoutColor, getTraceSteps, withLinkedWorktreeFixture, FakeGitFileFixture
  • Deduplicated helpers across 17 test files (see --stat below)

Tooling

  • Added jscpd as a devDependency
  • Added check-duplicates script (bunx jscpd src tests --exitCode 1 --reporters ai)
  • Integrated check-duplicates into both check and check:ci scripts

Screenshots

N/A — internal refactor, no UI changes.

Testing

bun run check

This now includes the new check-duplicates step, so it validates both correctness and that no new duplicates are introduced.

Related Issue

N/A

PR Checklist

  • I have read the CONTRIBUTING.md
  • Code follows project conventions (type hints, naming, etc.)
  • bun run check passes (lint, types, dead code, rules, tests)
  • Tests added for new rules (minimum 90% coverage required)
  • Tested locally with Claude Code, OpenCode, Gemini CLI, GitHub Copilot CLI or Codex
  • Updated documentation if needed (README, AGENTS.md)
  • No version changes in package.json

Summary by CodeRabbit

  • New Features

    • Added a check-duplicates npm script to detect duplicate code patterns.
  • Chores

    • Added jscpd to development dependencies.
  • Refactor

    • Consolidated shared utilities to reduce duplication and standardize formatting, hook handling, and command analysis.
  • Tests

    • Overhauled test suites with shared helpers, temp-dir management, and a unified CLI/test runner for clearer, more maintainable tests.

kenryu42 added 9 commits May 8, 2026 17:46
Extract shared CLI, hook, environment, color, trace, and fixture helpers used across the test suite. Refactor repeated test setup and assertions so the tests pass the normal jscpd duplicate scan without changing product behavior.

Also add jscpd as a dev dependency.
Integrate jscpd via a new 'check-duplicates' script and wire it into
both 'check' and 'check:ci' so duplicate code is caught in CI.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 8, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extracts duplicated code patterns across hooks, command analysis, output formatting, and tests into centralized shared helpers. It establishes new infrastructure modules (hook utilities, child command normalization, worktree helpers, expanded test utilities) and refactors multiple subsystems to use these shared functions, reducing maintenance burden and improving consistency across the codebase.

Changes

Shared Utilities and Infrastructure

Layer / File(s) Summary
Hook Input and Command Analysis Utilities
src/bin/hooks/common.ts
New module exports readHookInput, parseHookJson, and handleBlockedHookCommand to centralize hook input reading from stdin, strict-mode JSON parsing with conditional denial, and blocked-command analysis workflow.
Child Command Normalization Module
src/core/analyze/child-command.ts
New module introduces ChildCommandContext interface and exports normalizeChildCommand to unwrap shell wrappers, merge environment assignments, resolve working directory; also exports collectCommandTemplate for template extraction.
Worktree and .git Resolution
src/core/worktree.ts, src/core/rules-git.ts
Extracts ancestor-walking logic into new exported findDotGitInAncestors helper; updates rules-git.ts to use shared helper instead of local findDotGitPath implementation.
Doctor ASCII Table Formatter
src/bin/doctor/format.ts
Introduces formatAsciiTable helper that consolidates column-width calculation, header/separator rendering, and row formatting; doctor sections now call this shared function.
Test Helper Utilities
tests/helpers.ts, tests/bin/hooks/hook-helpers.ts
Expands test helpers with withTempDir, runSafetyNetCli, withStdoutColor, getTraceSteps, withLinkedWorktreeFixture; adds hook-specific input generators (copilotBashInput, geminiShellInput, claudeCodeBashInput) and expectNoHookOutput assertion helper.
Package Configuration
package.json
Adds check-duplicates script invoking jscpd over src and tests; adds jscpd as devDependency version ^4.0.9.

Hook System Refactoring

Layer / File(s) Summary
Hook Module Refactoring
src/bin/hooks/claude-code.ts, src/bin/hooks/copilot-cli.ts, src/bin/hooks/gemini-cli.ts
Three hook implementations delegate input parsing and blocked-command handling to shared helpers, removing inline JSON parsing with SAFETY_NET_STRICT handling and inline loadConfig/analyzeCommand/writeAuditLog flows.
Hook Test Refactoring
tests/bin/hooks/claude-code-hook.test.ts, tests/bin/hooks/copilot-cli-hook.test.ts, tests/bin/hooks/gemini-cli-hook.test.ts
Hook tests use input generator helpers and expectNoHookOutput instead of manually constructing payloads and checking stdout/exitCode.

Command Analysis and Core Refactoring

Layer / File(s) Summary
Analysis Helper Extraction
src/core/analyze/analyze-command.ts
Adds internal helper to centralize repeated export-tracking logic.
Parallel and Xargs Analysis Refactoring
src/core/analyze/parallel.ts, src/core/analyze/xargs.ts
Both modules now use normalizeChildCommand and collectCommandTemplate helpers instead of manually handling shell wrappers and token normalization; adds explicit find handling branches.

Doctor Subsystem Tests

Layer / File(s) Summary
Doctor Tests & Helpers
tests/bin/doctor/activity.test.ts, tests/bin/doctor/config.test.ts, tests/bin/doctor/format.test.ts, tests/bin/doctor/system-info.test.ts
Tests use withTempDir, withStdoutColor, and local helpers (createLogsDir, createDoctorReport, Copilot deferred fetcher) to reduce setup duplication.

Test Infrastructure and Refactoring

Layer / File(s) Summary
Explain CLI and Command Tests
tests/bin/explain/cli.test.ts, tests/bin/explain/command.test.ts
Tests refactored to use explainJson helper, getTraceSteps for step extraction, and local assertion helpers consolidating recursion limits, fallback scans, and worktree behavior expectations.
Explain Format and Redaction Tests
tests/bin/explain/format.test.ts, tests/bin/explain/redact.test.ts
Tests use getTraceSteps and mockExplainResult helper to centralize trace construction and step extraction.
Core Module Tests
tests/core/audit.test.ts, tests/core/config.test.ts, tests/core/rules-custom*.test.ts
Tests extract shared fixtures (configWithRule, blockGitAddAllRule, expectAuditLogStayedInLogsDir) to eliminate duplication.
Miscellaneous Test Refactoring
tests/bin/cli-statusline.test.ts, tests/bin/colors.test.ts, tests/bin/help.test.ts, tests/scripts/generate-changelog.test.ts
Tests refactored to use runSafetyNetCli, withStdoutColor, captureOutput, and local helper generators for mocked data.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through code with tiny paws,

Shared helpers stitched duplicate flaws.
Hooks read once, analysis stands tall,
Tests now tidy, no more sprawl,
A rabbit’s cheer for simpler laws.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.69% 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
Title check ✅ Passed Title clearly describes the main refactoring effort to eliminate jscpd code duplication and add duplicate detection tooling.
Description check ✅ Passed Description is comprehensive with clear sections covering summary, changes, new modules, refactors, test improvements, and tooling additions. All PR checklist items are marked complete.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 refactor/fix-jscpd-duplicates

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 8, 2026

Codecov Report

❌ Patch coverage is 99.48187% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 98.54%. Comparing base (0f8be53) to head (4c684a8).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
tests/helpers.ts 98.11% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #50      +/-   ##
==========================================
+ Coverage   98.53%   98.54%   +0.01%     
==========================================
  Files          52       53       +1     
  Lines        6975     6959      -16     
==========================================
- Hits         6873     6858      -15     
+ Misses        102      101       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 8, 2026

Greptile Summary

This PR is a clean internal refactoring with no issues found. All extracted helpers preserve the original semantics, the one deliberate behavioral change in xargs.ts is a security improvement, and the new jscpd gate prevents future regressions.

Confidence Score: 5/5

Pure internal refactor with no public API or runtime behaviour changes.

Every extracted helper was verified to preserve the original logic exactly. The one deliberate behavioural change (xargs now treats replacement tokens in env-var values as dynamic) is a security improvement covered by a new test.

No files require special attention.

Important Files Changed

Filename Overview
src/bin/hooks/common.ts New shared module consolidating readHookInput, parseHookJson, and handleBlockedHookCommand; behavior is semantically equivalent to the three per-hook implementations it replaces.
src/core/analyze/child-command.ts New module extracting normalizeChildCommand and collectCommandTemplate from parallel.ts and xargs.ts; logic is identical to the original inline blocks.
src/core/analyze/xargs.ts Uses normalizeChildCommand; extends hasDynamicReplacement to check env-var values for the replacement token — a security improvement covered by the new test in rules-git.test.ts.
src/bin/doctor/format.ts Six nearly-identical table-rendering blocks replaced by a single formatAsciiTable; column-width calculations and ANSI-aware padding are equivalent in all call sites.
src/core/worktree.ts findDotGitInAncestors extracted and exported; findDotGit now calls it after realpathSync, preserving the symlink-resolution step.
tests/bin/hooks/hook-helpers.ts New shared test helpers; copilotRawToolArgsInput is correctly consumed in copilot-cli-hook.test.ts.
tests/helpers.ts Added withTempDir, runSafetyNetCli, withStdoutColor, getTraceSteps, and withLinkedWorktreeFixture; cleanup paths use finally blocks correctly.
package.json Adds jscpd devDependency and check-duplicates script integrated into check and check:ci.

Reviews (2): Last reviewed commit: "refactor(tests): simplify test utilities..." | Re-trigger Greptile

Comment thread tests/bin/hooks/hook-helpers.ts Outdated
Copy link
Copy Markdown

@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 (3)
tests/bin/doctor/system-info.test.ts (1)

99-101: ⚡ Quick win

Inline the single-use fetcher variable.

fetcher is assigned from probes.fetcher and used exactly once on line 101. Per coding guidelines, values used only once should be inlined to reduce variable count.

♻️ Proposed fix
-    const probes = createCopilotDeferredFetcher();
-    const fetcher = probes.fetcher;
-    const sysInfoPromise = getSystemInfo(fetcher);
+    const probes = createCopilotDeferredFetcher();
+    const sysInfoPromise = getSystemInfo(probes.fetcher);

As per coding guidelines, "Reduce total variable count by inlining when a value is only used once."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bin/doctor/system-info.test.ts` around lines 99 - 101, The local
variable fetcher is assigned from probes.fetcher and only used once in the call
to getSystemInfo; inline it by passing probes.fetcher directly to getSystemInfo
and remove the unnecessary fetcher binding. Update the snippet that calls
createCopilotDeferredFetcher() so you keep const probes =
createCopilotDeferredFetcher(); and change getSystemInfo(fetcher) to
getSystemInfo(probes.fetcher), eliminating the single-use variable.
tests/bin/help.test.ts (1)

135-138: ⚡ Quick win

Prefer immutable result capture in these tests.

These two tests introduce mutable let result + reassignment. Returning both output and callback result from captureOutput keeps this immutable and simpler.

♻️ Suggested refactor
-function captureOutput(fn: () => void): string {
+function captureOutput<T>(fn: () => T): { output: string; result: T } {
   const originalLog = console.log;
   let output = '';
   console.log = (...args: unknown[]) => {
     output += `${args.map(String).join(' ')}\n`;
   };
   try {
-    fn();
+    const result = fn();
+    return { output, result };
   } finally {
     console.log = originalLog;
   }
-  return output;
 }
-      let result = false;
-      const output = captureOutput(() => {
-        result = showCommandHelp('doctor');
-      });
+      const { output, result } = captureOutput(() => showCommandHelp('doctor'));
-      let result = false;
-      const output = captureOutput(() => {
-        result = showCommandHelp('-cc');
-      });
+      const { output, result } = captureOutput(() => showCommandHelp('-cc'));
#!/bin/bash
# Verify mutable capture pattern occurrences in this test file.
rg -nP -C2 'let\s+result\s*=\s*false;|result\s*=\s*showCommandHelp\(' tests/bin/help.test.ts

As per coding guidelines, "Prefer const over let. Use ternaries or early returns instead of reassignment." and "Reduce total variable count by inlining when a value is only used once."

Also applies to: 145-148

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bin/help.test.ts` around lines 135 - 138, Replace the mutable pattern
(let result = false; result = showCommandHelp('doctor')) by updating
captureOutput to return both the captured output and the callback return value
and then use const to destructure them (e.g., const { output, result } =
captureOutput(() => showCommandHelp('doctor')) or const [output, result] =
captureOutput(() => showCommandHelp('doctor'))); apply the same change for the
other occurrence using showCommandHelp and captureOutput so tests use immutable
const bindings and no reassignment.
tests/bin/cli-statusline.test.ts (1)

105-112: ⚡ Quick win

Use functional iteration for mode-based test registration

Line 105 uses a for...of loop; this repo’s TS/JS guideline prefers functional array methods. Converting to forEach keeps behavior identical and aligns style.

Suggested change
-  for (const mode of modes) {
-    test(`shows ${mode.name}`, async () => {
-      await expectStatusline(
-        { CLAUDE_SETTINGS_PATH: enabledSettingsPath, ...mode.env },
-        mode.output,
-      );
-    });
-  }
+  modes.forEach((mode) => {
+    test(`shows ${mode.name}`, async () => {
+      await expectStatusline(
+        { CLAUDE_SETTINGS_PATH: enabledSettingsPath, ...mode.env },
+        mode.output,
+      );
+    });
+  });

As per coding guidelines, "Prefer functional array methods (flatMap, filter, map) over for loops; use type guards on filter to maintain type inference downstream".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bin/cli-statusline.test.ts` around lines 105 - 112, The test
registration uses a for...of loop over modes; replace it with a functional
iteration such as modes.forEach(mode => { test(`shows ${mode.name}`, async () =>
{ await expectStatusline({ CLAUDE_SETTINGS_PATH: enabledSettingsPath,
...mode.env }, mode.output); }); }); to comply with the repo guideline
preferring functional array methods—ensure you keep the async test callback and
the call to expectStatusline unchanged and reference the modes array, the test
registration, and expectStatusline function when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/analyze/xargs.ts`:
- Around line 65-74: The worktreeMode decision currently uses
hasDynamicReplacement which only checks childTokens, missing cases where the
replacementToken appears in childCommand.envAssignments (e.g., after
normalizeChildCommand); update the check before calling analyzeGit so that
hasDynamicReplacement also inspects childCommand.envAssignments (or compute a
new flag like hasDynamicReplacementInEnv) and pass worktreeMode: false whenever
replacementToken is found in either childTokens or childCommand.envAssignments,
referencing childCommand, childTokens, replacementToken, hasDynamicReplacement,
childCommand.envAssignments, and analyzeGit.

In `@tests/bin/explain/command.test.ts`:
- Around line 399-403: The test currently allows passing if only 'recurse' trace
steps exist, which can hide regressions in recursion-limit signaling; update the
assertions to directly check recursionLimitErrorStep rather than summing
'recurse' counts. Replace the combined assertion that uses
getTraceSteps(explainCommand(deepNested)) and a recurse-count sum with a direct
assertion like expect(recursionLimitErrorStep(deepNested)).toBeTruthy() (and the
analogous negative case where the limit should not trigger use toBeFalsy()) so
the tests assert the intended recursion-limit behavior explicitly; update both
the block using getTraceSteps/explainCommand/deepNested and the second
occurrence around lines 409-412 accordingly.

In `@tests/helpers.ts`:
- Around line 67-74: The withTempDir fixture currently cleans up in the finally
block immediately after returning fn(dir), which causes premature deletion when
fn is async; change withTempDir to be async (returning Promise<T>) and await the
callback result inside the try (e.g., const result = await fn(dir); return
result;) so cleanup in the finally runs after the awaited work completes; apply
the same change to the other fixture wrapper at the block around lines 205-212
(the analogous temp-file/dir helper) so both handle Promise-returning callbacks
correctly.

---

Nitpick comments:
In `@tests/bin/cli-statusline.test.ts`:
- Around line 105-112: The test registration uses a for...of loop over modes;
replace it with a functional iteration such as modes.forEach(mode => {
test(`shows ${mode.name}`, async () => { await expectStatusline({
CLAUDE_SETTINGS_PATH: enabledSettingsPath, ...mode.env }, mode.output); }); });
to comply with the repo guideline preferring functional array methods—ensure you
keep the async test callback and the call to expectStatusline unchanged and
reference the modes array, the test registration, and expectStatusline function
when making the change.

In `@tests/bin/doctor/system-info.test.ts`:
- Around line 99-101: The local variable fetcher is assigned from probes.fetcher
and only used once in the call to getSystemInfo; inline it by passing
probes.fetcher directly to getSystemInfo and remove the unnecessary fetcher
binding. Update the snippet that calls createCopilotDeferredFetcher() so you
keep const probes = createCopilotDeferredFetcher(); and change
getSystemInfo(fetcher) to getSystemInfo(probes.fetcher), eliminating the
single-use variable.

In `@tests/bin/help.test.ts`:
- Around line 135-138: Replace the mutable pattern (let result = false; result =
showCommandHelp('doctor')) by updating captureOutput to return both the captured
output and the callback return value and then use const to destructure them
(e.g., const { output, result } = captureOutput(() => showCommandHelp('doctor'))
or const [output, result] = captureOutput(() => showCommandHelp('doctor')));
apply the same change for the other occurrence using showCommandHelp and
captureOutput so tests use immutable const bindings and no reassignment.
🪄 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: dd151320-a61c-4b4f-880f-3ed618f22151

📥 Commits

Reviewing files that changed from the base of the PR and between 6ae71cb and be9c357.

⛔ Files ignored due to path filters (6)
  • bun.lock is excluded by !**/*.lock
  • dist/bin/cc-safety-net.js is excluded by !**/dist/**
  • dist/bin/hooks/common.d.ts is excluded by !**/dist/**
  • dist/core/analyze/child-command.d.ts is excluded by !**/dist/**
  • dist/core/worktree.d.ts is excluded by !**/dist/**
  • dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (34)
  • package.json
  • src/bin/doctor/format.ts
  • src/bin/hooks/claude-code.ts
  • src/bin/hooks/common.ts
  • src/bin/hooks/copilot-cli.ts
  • src/bin/hooks/gemini-cli.ts
  • src/core/analyze/analyze-command.ts
  • src/core/analyze/child-command.ts
  • src/core/analyze/parallel.ts
  • src/core/analyze/xargs.ts
  • src/core/rules-git.ts
  • src/core/worktree.ts
  • tests/bin/cli-statusline.test.ts
  • tests/bin/colors.test.ts
  • tests/bin/doctor/activity.test.ts
  • tests/bin/doctor/config.test.ts
  • tests/bin/doctor/format.test.ts
  • tests/bin/doctor/system-info.test.ts
  • tests/bin/explain/cli.test.ts
  • tests/bin/explain/command.test.ts
  • tests/bin/explain/format.test.ts
  • tests/bin/explain/redact.test.ts
  • tests/bin/help.test.ts
  • tests/bin/hooks/claude-code-hook.test.ts
  • tests/bin/hooks/copilot-cli-hook.test.ts
  • tests/bin/hooks/gemini-cli-hook.test.ts
  • tests/bin/hooks/hook-helpers.ts
  • tests/core/analyze/analyze-coverage.test.ts
  • tests/core/audit.test.ts
  • tests/core/config.test.ts
  • tests/core/rules-custom-integration.test.ts
  • tests/core/rules-custom.test.ts
  • tests/helpers.ts
  • tests/scripts/generate-changelog.test.ts

Comment thread src/core/analyze/xargs.ts
Comment thread tests/bin/explain/command.test.ts Outdated
Comment thread tests/helpers.ts Outdated
kenryu42 added 4 commits May 8, 2026 21:56
The hasDynamicReplacement check now also looks at envAssignments for the
replacement token, catching cases like
where the token appears in an environment variable value rather than a
direct argument.
Convert test helpers to properly await async callbacks before cleanup.
This prevents race conditions where temp directories or worktrees are
cleaned up before async operations complete. Adds dedicated tests
validating cleanup waits for pending promises.
- Use forEach instead of for...of in statusline test loop
- Inline fetcher variable in system-info test
- Simplify captureOutput to return both output and result via object
  destructuring, eliminating mutable let bindings in callers
Copy link
Copy Markdown

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/bin/hooks/hook-helpers.ts`:
- Around line 85-93: The helper expectNoHookOutput currently only asserts stdout
and exitCode, allowing hooks to emit stderr unnoticed; update the function
(expectNoHookOutput) to also destructure and assert that stderr is an empty
string (e.g., expect(stderr).toBe('')) after calling run(input, env) so the
helper truly enforces "no output" across stdout and stderr while keeping the
existing exitCode assertion.
🪄 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: 21d735e4-7461-4686-81c9-9632c480964f

📥 Commits

Reviewing files that changed from the base of the PR and between be9c357 and 4c684a8.

⛔ Files ignored due to path filters (2)
  • dist/bin/cc-safety-net.js is excluded by !**/dist/**
  • dist/index.js is excluded by !**/dist/**
📒 Files selected for processing (11)
  • src/core/analyze/xargs.ts
  • tests/bin/cli-statusline.test.ts
  • tests/bin/doctor/config.test.ts
  • tests/bin/doctor/system-info.test.ts
  • tests/bin/explain/command.test.ts
  • tests/bin/help.test.ts
  • tests/bin/hooks/hook-helpers.ts
  • tests/core/analyze/analyze-coverage.test.ts
  • tests/core/rules-git.test.ts
  • tests/helpers.test.ts
  • tests/helpers.ts
✅ Files skipped from review due to trivial changes (2)
  • tests/helpers.test.ts
  • tests/core/rules-git.test.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/core/analyze/xargs.ts
  • tests/bin/doctor/system-info.test.ts
  • tests/helpers.ts
  • tests/core/analyze/analyze-coverage.test.ts
  • tests/bin/doctor/config.test.ts
  • tests/bin/cli-statusline.test.ts
  • tests/bin/explain/command.test.ts

Comment on lines +85 to +93
export async function expectNoHookOutput(
run: (input: object | string, env?: Record<string, string>) => Promise<HookResult>,
input: object | string,
env?: Record<string, string>,
): Promise<void> {
const { stdout, exitCode } = await run(input, env);
expect(stdout).toBe('');
expect(exitCode).toBe(0);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

expectNoHookOutput should also assert empty stderr.

Right now a hook can emit stderr and still pass this helper, which conflicts with the helper name and can hide regressions in “no output” scenarios.

Suggested patch
 export async function expectNoHookOutput(
   run: (input: object | string, env?: Record<string, string>) => Promise<HookResult>,
   input: object | string,
   env?: Record<string, string>,
 ): Promise<void> {
-  const { stdout, exitCode } = await run(input, env);
+  const { stdout, stderr, exitCode } = await run(input, env);
   expect(stdout).toBe('');
+  expect(stderr).toBe('');
   expect(exitCode).toBe(0);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/bin/hooks/hook-helpers.ts` around lines 85 - 93, The helper
expectNoHookOutput currently only asserts stdout and exitCode, allowing hooks to
emit stderr unnoticed; update the function (expectNoHookOutput) to also
destructure and assert that stderr is an empty string (e.g.,
expect(stderr).toBe('')) after calling run(input, env) so the helper truly
enforces "no output" across stdout and stderr while keeping the existing
exitCode assertion.

@kenryu42
Copy link
Copy Markdown
Owner Author

kenryu42 commented May 8, 2026

@greptileai review

@kenryu42 kenryu42 merged commit f37126b into main May 8, 2026
9 checks passed
@kenryu42 kenryu42 deleted the refactor/fix-jscpd-duplicates branch May 8, 2026 13:45
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