Skip to content

Conversation

@jhaynie
Copy link
Member

@jhaynie jhaynie commented Jun 10, 2025

Summary by CodeRabbit

  • New Features
    • TypeScript type checking is now performed automatically during the build process if a TypeScript compiler is present.
  • Bug Fixes
    • Improved file and directory ignore handling, ensuring that common development files and directories (e.g., swap files, backup files, node_modules, virtual environments) are consistently excluded across all relevant features.
  • Refactor
    • Enhanced file watcher to use ignore rules for filtering, improving reliability and performance.
    • Streamlined development server restart logic for smoother and more predictable restarts.
    • Centralized ignore rules creation for project deployments to simplify configuration management.
  • Tests
    • Added comprehensive tests to verify that default ignore rules correctly exclude intended files and directories.
  • Chores
    • Removed unused debounce dependency to reduce unnecessary code.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jun 10, 2025

Walkthrough

The changes introduce centralized ignore rule handling, refactor the development server restart logic, and enhance file watching with improved ignore filtering and debouncing. TypeScript type checking is now enforced during JavaScript bundling. Default ignore patterns are expanded and made recursive, and corresponding tests are added to verify correct ignore behavior. Unused dependencies are removed.

Changes

File(s) Change Summary
cmd/cloud.go Extracted ignore rules logic into new createProjectIgnoreRules helper function.
cmd/dev.go Refactored server restart logic, removed debounce, improved teardown and logging.
go.mod Removed unused github.com/bep/debounce dependency.
internal/bundler/bundler.go Added runTypecheck to perform TypeScript type checking before bundling.
internal/dev/watcher.go Switched from pattern-based to ignore-rule-based file watching; added debouncing and improved event filtering.
internal/ignore/rules.go Expanded and made default ignore patterns recursive; added new ignore rules.
internal/ignore/rules_test.go Added tests to verify default ignore rules correctly filter various files and directories.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DevCmd
    participant Watcher
    participant Bundler
    participant IgnoreRules

    User->>DevCmd: Start development server
    DevCmd->>Watcher: Initialize with IgnoreRules
    Watcher->>IgnoreRules: Filter files/directories
    Watcher->>DevCmd: On file change (debounced)
    DevCmd->>Bundler: Rebuild project
    Bundler->>Bundler: runTypecheck (TypeScript)
    Bundler->>Bundler: Bundle JS if typecheck passes
    DevCmd->>DevCmd: Restart server if needed
Loading

Poem

🐇
In burrows deep, the code did sprawl,
Now rules ignore both big and small.
With type checks swift and watchers keen,
Restarts are smooth, the logs are clean.
Old patterns gone, new tests in place—
This rabbit hops with tidy grace!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c4b2a9f and 88ee926.

📒 Files selected for processing (1)
  • cmd/dev.go (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • cmd/dev.go
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test CLI Upgrade Path (windows-latest)
  • GitHub Check: Analyze (go)
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
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: 2

🔭 Outside diff range comments (1)
internal/ignore/rules.go (1)

131-169: ⚠️ Potential issue

Ignore panics when fi == nil

Several call-sites (e.g. new unit-test rules_test.go) pass a nil os.FileInfo.
Ignore dereferences fi unconditionally (fi.IsDir()), leading to a runtime panic.

 func (r *Rules) Ignore(path string, fi os.FileInfo) bool {
     ...
-    for _, p := range r.patterns {
+    for _, p := range r.patterns {
         ...
-        if p.negate {
-            if p.mustDir && !fi.IsDir() {
+        if p.negate {
+            if p.mustDir && (fi == nil || !fi.IsDir()) {
                 return true
             }
-            if !p.match(path, fi) {
+            if !p.match(path, fi) {
                 return true
             }
             continue
         }
         ...
-        if p.mustDir && !fi.IsDir() {
+        if p.mustDir && (fi == nil || !fi.IsDir()) {
             continue
         }

Fail fast on path == "" already exists, so this patch is safe.

🧹 Nitpick comments (6)
internal/ignore/rules.go (1)

80-86: Duplicate default patterns – minor cleanup

**/.cursor/** and **/.vscode/** are added twice. Removing duplicates avoids unnecessary matcher allocations.

internal/bundler/bundler.go (1)

120-139: runTypecheck emits nothing if Writer is nil

cmd.Stdout/Stderr = ctx.Writer will silently drop tsc output when Writer is nil (e.g. non-interactive deploy).
Consider defaulting to os.Stdout/os.Stderr or the logger:

 if ctx.Writer != nil {
     cmd.Stdout = ctx.Writer
     cmd.Stderr = ctx.Writer
 } else {
+    cmd.Stdout = os.Stdout
+    cmd.Stderr = os.Stderr
 }

This preserves useful diagnostics during CI builds.

cmd/dev.go (1)

252-257: Graceful shutdown – wait for watcher goroutine

After calling watcher.Close(log) you still have the goroutine spawned in runServer potentially blocking on Wait().
Join it (e.g. via sync.WaitGroup) or signal it to avoid leaking goroutines on exit.

cmd/cloud.go (1)

130-152: Minor shadowing – avoid r short-var inside the if

Using r, err := ... shadows the outer rules variable and then re-assigns. Not harmful, but slightly confusing. Consider:

if util.Exists(gitignore) {
    parsed, err := ignore.ParseFile(gitignore)
    ...
    rules = parsed
}
internal/dev/watcher.go (2)

42-45: Consider initializing ignore patterns in the Rules constructor

The ignore patterns are being added to the rules object after it's created. This could lead to inconsistency if multiple watchers are created with the same rules object.

Consider moving these default patterns to the ignore.Rules initialization or create a copy of the rules object before modifying it:

-	for _, pattern := range ignorePatterns {
-		rules.Add(pattern)
-	}
+	// Option 1: Clone the rules to avoid modifying the shared instance
+	localRules := rules.Clone()
+	for _, pattern := range ignorePatterns {
+		localRules.Add(pattern)
+	}
+	fw.ignore = localRules

Alternatively, these patterns could be added as defaults in the ignore.Rules.AddDefaults() method mentioned in the AI summary.


58-60: Consider using a debug logger method instead of conditional logging

The pattern of checking debugLogging before logging could be simplified by using a debug logger method that internally handles the conditional check.

Instead of:

if debugLogging {
    logger.Trace("Ignoring path: %s", path)
}

Consider implementing a Debug method on the logger that checks the flag internally, allowing cleaner code:

logger.Debug("Ignoring path: %s", path)

Also applies to: 63-63, 87-87, 100-100, 105-105

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f01ee7f and 64e4706.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • cmd/cloud.go (2 hunks)
  • cmd/dev.go (3 hunks)
  • go.mod (0 hunks)
  • internal/bundler/bundler.go (2 hunks)
  • internal/dev/watcher.go (2 hunks)
  • internal/ignore/rules.go (1 hunks)
  • internal/ignore/rules_test.go (1 hunks)
💤 Files with no reviewable changes (1)
  • go.mod
🧰 Additional context used
🧬 Code Graph Analysis (3)
cmd/cloud.go (5)
internal/project/project.go (2)
  • Project (145-154)
  • Bundler (481-490)
internal/ignore/rules.go (4)
  • Rules (46-48)
  • Ignore (40-40)
  • Empty (51-53)
  • ParseFile (95-102)
internal/util/io.go (1)
  • Exists (14-19)
internal/errsystem/errsystem.go (2)
  • New (33-63)
  • WithContextMessage (100-104)
internal/errsystem/errorcodes.go (1)
  • ErrInvalidConfiguration (25-28)
internal/ignore/rules_test.go (1)
internal/ignore/rules.go (2)
  • Empty (51-53)
  • Ignore (40-40)
internal/bundler/bundler.go (1)
internal/util/io.go (1)
  • Exists (14-19)
🪛 golangci-lint (1.64.8)
internal/ignore/rules.go

57-57: Error return value of r.parseRule is not checked

(errcheck)


58-58: Error return value of r.parseRule is not checked

(errcheck)


59-59: Error return value of r.parseRule is not checked

(errcheck)

🔇 Additional comments (4)
internal/ignore/rules_test.go (1)

12-38: Tests will panic – supply a fake os.FileInfo

Because Ignore currently dereferences fi, passing nil will panic (see previous comment).
Either adjust the production code to tolerate nil (recommended) or create a stub for the tests:

type fakeInfo struct{ os.FileInfo }

func (fakeInfo) IsDir() bool { return false }

fi := fakeInfo{}
assert.True(t, rules.Ignore("/path/to/.gitignore", fi))

After fixing Ignore as suggested, the current nil usage becomes valid.

internal/dev/watcher.go (3)

88-96: Good defensive programming with file existence checks

The addition of file existence and stat checks prevents potential panics from race conditions where files are deleted between event notification and processing.


24-34: ⚠️ Potential issue

Remove duplicate ignore pattern

The pattern "**/.agentuity" appears twice in the ignorePatterns slice (lines 24 and 33).

Apply this diff to remove the duplicate:

 var ignorePatterns = []string{
 	"**/.agentuity/**",
 	"**/package-lock.json",
 	"**/package.json",
 	"**/yarn.lock",
 	"**/pnpm-lock.yaml",
 	"**/bun.lock",
 	"**/bun.lockb",
 	"**/tsconfig.json",
 	"**/agentuity.yaml",
-	"**/.agentuity",
 }

Likely an incorrect or invalid review comment.


73-107: ⚠️ Potential issue

Potential race condition in debouncing implementation

The pending map is accessed from multiple goroutine contexts without synchronization. The ticker case reads and deletes from the map while the event case writes to it, which could lead to a race condition.

Consider using a mutex to protect access to the pending map:

+import "sync"

 func (fw *FileWatcher) watch(logger logger.Logger) {
 	t := time.NewTicker(250 * time.Millisecond) // how long to debounce changes
 	defer t.Stop()
 	pending := make(map[string]bool)
+	var mu sync.Mutex
 	for {
 		select {
 		case <-t.C:
+			mu.Lock()
 			for path := range pending {
 				fw.callback(path)
 				delete(pending, path)
 			}
+			mu.Unlock()
 		case event, ok := <-fw.watcher.Events:
 			// ... existing code ...
 			if (event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create) && !fw.ignore.Ignore(event.Name, fi) {
 				logger.Trace("Write detected for %s", event.Name)
+				mu.Lock()
 				pending[event.Name] = true
+				mu.Unlock()
 			}

Likely an incorrect or invalid review comment.

Copy link
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: 2

♻️ Duplicate comments (2)
cmd/dev.go (2)

170-190: ⚠️ Potential issue

Critical: Race condition with shared projectServerCmd variable.

The runServer function assigns to the shared projectServerCmd variable without proper synchronization, while the restart function accesses it with a different mutex. This creates a race condition where restart might access a nil or stale projectServerCmd reference.

The past review comment correctly identified this issue. Both functions must use the same mutex when accessing projectServerCmd.

 runServer := func() {
+   restartingLock.Lock()
    projectServerCmd, err = dev.CreateRunProjectCmd(processCtx, log, theproject, server, dir, orgId, port, os.Stdout, os.Stderr)
    if err != nil {
+     restartingLock.Unlock()
      errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage("Failed to run project")).ShowErrorAndExit()
    }
    if err := projectServerCmd.Start(); err != nil {
+     restartingLock.Unlock()
      errsystem.New(errsystem.ErrInvalidConfiguration, err, errsystem.WithContextMessage(fmt.Sprintf("Failed to start project: %s", err))).ShowErrorAndExit()
    }
    atomic.StoreInt32(&pid, int32(projectServerCmd.Process.Pid))
+   restartingLock.Unlock()
    log.Trace("restarted project server (pid: %d)", projectServerCmd.Process.Pid)
    log.Trace("waiting for project server to exit (pid: %d)", projectServerCmd.Process.Pid)
    if err := projectServerCmd.Wait(); err != nil {
      log.Error("project server (pid: %d) exited with error: %s", projectServerCmd.Process.Pid, err)
    }
    // ... rest unchanged
 }

199-208: ⚠️ Potential issue

Partial protection against race condition.

The mutex protects against concurrent restarts but doesn't synchronize access to projectServerCmd with the runServer function. The dev.KillProjectServer call on line 203 could access a nil or stale projectServerCmd if runServer is concurrently reassigning it.

This is part of the same race condition issue identified in the past review comment.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64e4706 and c4b2a9f.

📒 Files selected for processing (1)
  • cmd/dev.go (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Test CLI Upgrade Path (windows-latest)
  • GitHub Check: Analyze (go)
🔇 Additional comments (8)
cmd/dev.go (8)

10-11: LGTM: Appropriate imports for synchronization primitives.

The addition of sync and sync/atomic packages aligns with the new concurrent process management logic.


120-120: LGTM: Atomic-compatible type for PID.

Using int32 enables atomic operations for thread-safe PID access across goroutines.


197-197: LGTM: Mutex for preventing concurrent restarts.

The restartingLock mutex appropriately prevents multiple restart operations from running simultaneously.


215-215: LGTM: Simplified watcher callback.

The direct call to restart() removes external debouncing logic, which aligns with the summary indicating that debouncing is now handled internally by the watcher.


228-229: LGTM: Atomic PID storage for thread safety.

Using atomic.StoreInt32 ensures thread-safe PID updates that can be safely read from other goroutines.


253-253: LGTM: Clean interrupt character handling.

The escape sequence \b\b\033[K properly clears the "^C" characters displayed when Ctrl+C is pressed.


257-257: LGTM: Nice farewell message.

The friendly goodbye message improves user experience.


213-213:

Details

❌ Incorrect review comment

Verify ignore rules function exists.

The code references createProjectIgnoreRules(dir, theproject.Project) but this function isn't defined in the provided file.


🏁 Script executed:

#!/bin/bash
# Description: Verify that createProjectIgnoreRules function exists in the codebase
# Expected: Function definition should be found

ast-grep --pattern 'func createProjectIgnoreRules($_, $_) $_ {
  $$$
}'

Length of output: 1263


Function createProjectIgnoreRules Is Defined
The function createProjectIgnoreRules(dir string, theproject *project.Project) *ignore.Rules is implemented in cmd/cloud.go (lines 130–152), so the reference in cmd/dev.go is valid. No action required.

Likely an incorrect or invalid review comment.

This was linked to issues Jun 10, 2025
@jhaynie jhaynie merged commit 2097f3a into main Jun 10, 2025
14 checks passed
@jhaynie jhaynie deleted the hot-reload branch June 10, 2025 01:04
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.

Rebuild Build errors are not reported

2 participants