Skip to content

feat: add file browser component for PRD file selection#236

Closed
esumerfd wants to merge 30 commits intosubsy:mainfrom
esumerfd:main
Closed

feat: add file browser component for PRD file selection#236
esumerfd wants to merge 30 commits intosubsy:mainfrom
esumerfd:main

Conversation

@esumerfd
Copy link
Copy Markdown
Contributor

@esumerfd esumerfd commented Jan 28, 2026

Summary

Replace manual text input with a keyboard-navigable file browser when selecting prd.json files. Features include:

  • Add keyboard-navigable file browser for selecting prd.json files
  • Auto-scroll to keep selection visible in viewport
  • Fix schema inconsistency in ralph-tui-create-json skill that caused invalid prd.json generation
ralph-tui-file-browser

Features

  • Directory navigation with j/k, Enter, Backspace, ~, and arrow keys
  • File filtering to show only prd*.json files
  • Editable path field (press / to type a path directly)
  • Hidden files toggle (press .)
  • Visual hint showing typical prd.json location
  • Auto-scroll when selection moves out of visible area
  • Inconsistent prd.json generation.

Files Changed

  • src/tui/components/FileBrowser.tsx - New file browser component
  • src/tui/components/EpicLoaderOverlay.tsx - Integrate file browser
  • src/tui/components/index.ts - Export new component
  • src/utils/files.ts - File utilities
  • Missing required field: "name" (string) - the project/feature name
  • Fixing a lot of tests. 87 failures down to 0.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Keyboard-driven File Browser for directory navigation, path editing, filtering and file selection; available as an alternate file-prompt mode and exported for reuse.
  • Refactor

    • Epic loader overlay split into two modes (list and file-prompt); path input delegated to the File Browser with simplified keyboard behaviour and a consistent "Load Epic" header and footer hints.
  • New API

    • Directory-listing utility returning filtered, sorted entries.
  • Tests

    • Many tests reworked to apply mocks in lifecycle hooks with dynamic imports for improved isolation.
  • Documentation

    • Example prd.json updated to use the revised root field name.

✏️ Tip: You can customize this high-level summary in your review settings.

Replace manual text input with a keyboard-navigable file browser when
selecting prd.json files. Features include:

- Directory navigation with j/k, Enter, Backspace, ~, and arrow keys
- File filtering to show only prd*.json files
- Editable path field (press / to type a path directly)
- Hidden files toggle (press .)
- Visual hint showing typical prd.json location
- Auto-scroll to keep selection visible in viewport

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Jan 28, 2026

@esumerfield is attempting to deploy a commit to the plgeek Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 28, 2026

Walkthrough

Adds a keyboard-driven FileBrowser and listDirectory utility; refactors EpicLoaderOverlay into a discriminated-union API with 'list' and 'file-prompt' modes (file-prompt delegates to FileBrowser); RunApp selects overlay mode; exports FileBrowser; many tests switched to lifecycle-scoped mocks and dynamic imports.

Changes

Cohort / File(s) Summary
Epic loader refactor
src/tui/components/EpicLoaderOverlay.tsx
Replaced single-mode props with a discriminated-union (`mode: 'list'
New file browser
src/tui/components/FileBrowser.tsx
Added a keyboard-driven modal FileBrowser component with props (visible, initialPath, fileExtension, filenamePrefix, trackerLabel, filePatternHint, onSelect, onCancel); supports navigation, path editing, show-hidden toggle, extension/prefix filtering and directory listing via listDirectory.
Component barrel exports
src/tui/components/index.ts
Exported FileBrowser and FileBrowserProps from the components barrel.
Filesystem utilities
src/utils/files.ts
Added DirectoryEntry interface and listDirectory(dirPath, options) to read and return directories-first sorted entries, with options to show hidden items, filter by extension and filename prefix.
Run loop integration
src/tui/components/RunApp.tsx
Conditional rendering for EpicLoaderOverlay: selects mode="file-prompt" or mode="list" based on epicLoaderMode; wires onFilePath for file-prompt and onSelect for list mode with mode-specific error handling and visibility behaviour.
Many test files: mock isolation & dynamic imports
src/.../*.test.ts, tests/.../*.test.ts
Moved module mocks into lifecycle hooks (beforeAll/afterAll), used dynamic/dedicated import (test-reload) to load modules after mocks are applied, introduced typed local bindings and Bun.spawn-based test variants for process-heavy suites.
New/adjusted test suites
tests/.../*.test.ts, src/.../*.test.ts
Added Bun-based and isolated test suites (e.g., sandbox detect, base-execute), removed or simplified some heavy integration tests (info.test).
Docs/example update
skills/ralph-tui-create-json/SKILL.md
Updated prd.json example: replaced root project field with name.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant RunApp
    participant EpicLoaderOverlay
    participant FileBrowser
    participant listDirectory

    User->>RunApp: open "Load Epic"
    RunApp->>EpicLoaderOverlay: render(mode="file-prompt" or "list")
    alt file-prompt
        EpicLoaderOverlay->>FileBrowser: mount(visible=true, initialPath, filters)
        User->>FileBrowser: navigate / edit path / select
        FileBrowser->>listDirectory: listDirectory(path, options)
        listDirectory-->>FileBrowser: DirectoryEntry[]
        alt select directory
            FileBrowser->>FileBrowser: update currentPath & refresh entries
        else select file
            FileBrowser->>EpicLoaderOverlay: onSelect(filePath)
            EpicLoaderOverlay->>RunApp: onFilePath(filePath)
        end
        User->>FileBrowser: Esc
        FileBrowser->>EpicLoaderOverlay: onCancel()
    else list
        EpicLoaderOverlay->>User: show epics list
        User->>EpicLoaderOverlay: navigate / select epic
        EpicLoaderOverlay->>RunApp: onSelect(epic)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introducing a file browser component for PRD file selection, which is the primary feature across multiple files.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

🧪 Unit Test Generation v2 is now available!

We have significantly improved our unit test generation capabilities.

To enable: Add this to your .coderabbit.yaml configuration:

reviews:
  finishing_touches:
    unit_tests:
      enabled: true

Try it out by using the @coderabbitai generate unit tests command on your code files or under ✨ Finishing Touches on the walkthrough!

Have feedback? Share your thoughts on our Discord thread!


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 Jan 28, 2026

Codecov Report

❌ Patch coverage is 8.69565% with 42 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.48%. Comparing base (046a6eb) to head (a1078ce).
⚠️ Report is 73 commits behind head on main.

Files with missing lines Patch % Lines
src/utils/files.ts 2.43% 40 Missing ⚠️
src/plugins/trackers/builtin/beads/index.ts 60.00% 2 Missing ⚠️

❌ Your patch status has failed because the patch coverage (8.69%) is below the target coverage (50.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #236      +/-   ##
==========================================
- Coverage   45.45%   43.48%   -1.97%     
==========================================
  Files          84       84              
  Lines       24396    24553     +157     
==========================================
- Hits        11088    10678     -410     
- Misses      13308    13875     +567     
Files with missing lines Coverage Δ
src/plugins/trackers/builtin/beads/index.ts 51.44% <60.00%> (+0.98%) ⬆️
src/utils/files.ts 59.17% <2.43%> (-18.18%) ⬇️

... and 9 files with indirect coverage changes

🚀 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/tui/components/FileBrowser.tsx`:
- Around line 44-49: The formatPath function uses homedir() and currently checks
path.startsWith(home), which can falsely match siblings (e.g., /home/alice2);
update formatPath to only substitute with '~' when the match is on a
path-separator boundary by verifying that either the path equals home or the
character after home is path.sep (or '/' on POSIX) before returning '~' +
path.slice(home.length); keep the rest of the function and use the existing
homedir() and formatPath symbols to locate the change.
- Around line 224-227: The default branch is appending raw key.sequence into
editedPath (via setEditedPath) which allows control/escape sequences
(arrow/function keys) to be inserted; update the handler that uses setEditedPath
in the FileBrowser component so it only appends printable characters — e.g.,
validate key.sequence/key.name before calling setEditedPath by ensuring the
sequence is a single printable character (ASCII printable range or matches a
printable regex) and ignore entries where key.name indicates control keys (like
'up','down','left','right','escape','f1' etc.) or where key.sequence contains
non-printable characters.
🧹 Nitpick comments (2)
src/tui/components/FileBrowser.tsx (1)

349-359: Consider making the hint text configurable.

The hard‑coded “Pattern: prd*.json” hint will be misleading if this component is reused with different filters. Exposing a prop (or deriving it from fileExtension/filenamePrefix) would keep the UI accurate.

src/tui/components/EpicLoaderOverlay.tsx (1)

22-51: Make onFilePath required in file-prompt mode to prevent silent no-ops.

When mode='file-prompt', the component delegates to FileBrowser and calls onFilePath?.(path) using optional chaining. If onFilePath is omitted, file selection silently fails. A discriminated union type would enforce this contract at compile time, ensuring that onFilePath is always provided whenever mode='file-prompt' is set.

🔧 Example type-safe shape
+type EpicLoaderOverlayBaseProps = {
+  visible: boolean;
+  trackerName: string;
+  onCancel: () => void;
+};
+
-export interface EpicLoaderOverlayProps {
-  /** Whether the overlay is visible */
-  visible: boolean;
-  /** Mode: 'list' for beads-style selection, 'file-prompt' for json-style */
-  mode: EpicLoaderMode;
-  /** Available epics (for list mode) */
-  epics: TrackerTask[];
-  /** Whether epics are loading */
-  loading: boolean;
-  /** Error message if loading failed */
-  error?: string;
-  /** Tracker name for display */
-  trackerName: string;
-  /** Current epic ID (for highlighting) */
-  currentEpicId?: string;
-  /** Callback when an epic is selected */
-  onSelect: (epic: TrackerTask) => void;
-  /** Callback when user cancels (Escape) */
-  onCancel: () => void;
-  /** Callback when file path is submitted (file-prompt mode) */
-  onFilePath?: (path: string) => void;
-}
+export type EpicLoaderOverlayProps =
+  | (EpicLoaderOverlayBaseProps & {
+      mode: 'list';
+      epics: TrackerTask[];
+      loading: boolean;
+      error?: string;
+      currentEpicId?: string;
+      onSelect: (epic: TrackerTask) => void;
+      onFilePath?: never;
+    })
+  | (EpicLoaderOverlayBaseProps & {
+      mode: 'file-prompt';
+      onFilePath: (path: string) => void;
+      onSelect?: never;
+      epics?: never;
+      loading?: never;
+      error?: string;
+      currentEpicId?: string;
+    });

esumerfield and others added 2 commits January 28, 2026 16:01
The example prd.json output was using "project" instead of "name",
causing Claude to sometimes generate invalid JSON that failed
ralph-tui validation with "Missing required field: name".

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix formatPath to check path separator boundary, preventing false ~
  substitution on sibling directories (e.g., /home/alice2)
- Fix setEditedPath to only accept printable characters, preventing
  control/escape sequences from being inserted
- Add filePatternHint prop to FileBrowser for configurable hint text
- Refactor EpicLoaderOverlayProps to use discriminated union type for
  type-safe mode handling
- Update RunApp.tsx to use conditional rendering for proper type narrowing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/tui/components/RunApp.tsx`:
- Around line 2573-2578: Wrap the onEpicSwitch call inside the onSelect handler
in a try/catch/finally: call await onEpicSwitch(epic) inside try, catch any
thrown error and set the component's epic error state (the same state used by
the onFilePath handler, e.g., setEpicError or setErrorMessage) so the user sees
feedback, and ensure setShowEpicLoader(false) runs in finally so the loader is
always hidden. Locate the onSelect callback in RunApp.tsx and update it to
mirror the error handling pattern used by onFilePath.
🧹 Nitpick comments (2)
src/tui/components/FileBrowser.tsx (1)

168-178: Consider user feedback on invalid paths.

When navigateToPath receives an invalid path that causes isDirectory to return false (including when the path doesn't exist), the error message says "Not a directory" which may be misleading for non-existent paths.

💡 Optional improvement for clearer error messaging
 const navigateToPath = useCallback(async (path: string) => {
   const expanded = expandPath(path);
-  const isDir = await isDirectory(expanded);
-  if (isDir) {
-    setCurrentPath(expanded);
-    setEditingPath(false);
-    setEditedPath('');
-  } else {
-    setError(`Not a directory: ${path}`);
+  try {
+    const isDir = await isDirectory(expanded);
+    if (isDir) {
+      setCurrentPath(expanded);
+      setEditingPath(false);
+      setEditedPath('');
+    } else {
+      setError(`Not a directory or does not exist: ${path}`);
+    }
+  } catch {
+    setError(`Cannot access: ${path}`);
   }
 }, [expandPath]);
src/tui/components/EpicLoaderOverlay.tsx (1)

180-192: Simplify callback wrapper.

The arrow function wrapper (path) => onFilePath(path) is redundant since onFilePath already has the correct signature.

♻️ Simplify to direct reference
     return (
       <FileBrowser
         visible={visible}
         fileExtension=".json"
         filenamePrefix="prd"
         trackerLabel={trackerName}
-        onSelect={(path) => onFilePath(path)}
+        onSelect={onFilePath}
         onCancel={onCancel}
       />
     );

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@src/plugins/trackers/builtin/beads-rust/index.test.ts`:
- Around line 92-95: The inline comment "Now import the module - it will get the
mocked versions" uses temporal wording; replace it with an evergreen phrasing
such as "Import the module so it uses the mocked implementations" to describe
intent without referencing time; update the comment immediately above the import
in index.test.ts (the lines that import './index.js' and assign
BeadsRustTrackerPlugin) to the new wording.

In `@src/setup/skill-installer-spawn.test.ts`:
- Around line 53-55: The inline comment above the dynamic import should be
reworded to remove temporal phrasing ("Now ..."); update the comment that
precedes the import of './skill-installer.js' and the assignment to
installViaAddSkill so it is evergreen (e.g., "Import the module to use the
mocked spawn" or "Import module to obtain installViaAddSkill with mocked spawn")
while keeping the rest of the test unchanged.

In `@src/setup/skill-installer.test.ts`:
- Around line 5-8: Update the header comment to match the actual import query
string used in this test file: replace the mention of "?reload" with
"?test-reload" (or alternatively change the import to use "?reload" if that was
intended); ensure the header text describing the reason for the reload
explicitly references the same query string ("?test-reload") so future readers
aren't misled.

In `@tests/utils/process.test.ts`:
- Around line 72-73: The regex uses the unescaped testDir variable in
expect(...).toMatch(new RegExp(`^(/private)?${testDir}$`)), which can
misinterpret path characters as regex metacharacters; fix by escaping regex
metacharacters in testDir before creating the RegExp (e.g., const escapedTestDir
= testDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') and then use new
RegExp(`^(/private)?${escapedTestDir}$`) in the expectation).

@subsy
Copy link
Copy Markdown
Owner

subsy commented Jan 29, 2026

Thanks @esumerfd - mind taking a look at the outstanding feedback from CR ?

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@src/commands/skills.test.ts`:
- Around line 10-54: Add an afterAll hook to restore Bun's global module mocks
created by mock.module(...) so the pass-through mock for
'../setup/skill-installer.js' does not leak to other suites; specifically add
afterAll(() => { mock.restore(); }) (or the appropriate mock.restore call from
bun:test) in src/commands/skills.test.ts after the beforeAll block to undo the
mock.module registration used when importing the skills module (affecting the
mock object used around
executeSkillsCommand/printSkillsHelp/parseInstallArgs/buildAddSkillArgs/parseAddSkillOutput).

In `@src/tui/components/FileBrowser.tsx`:
- Around line 184-186: The catch block in navigateToPath declares an unused
variable err causing an ESLint no-unused-vars error; update the catch to either
remove the parameter or use err in the error message—e.g., change catch (err) to
catch (err) and call setError(`Cannot access path: ${path} - ${String(err)}`) or
simply use catch { setError(`Cannot access path: ${path}`) } so the linter stops
complaining; locate this in the navigateToPath function where setError is
invoked.

In `@src/tui/components/RunApp.tsx`:
- Around line 2552-2560: The onFilePath handler currently awaits
onFilePathSwitch(path) but doesn't catch rejections, so failures can leave the
overlay open; wrap the call to onFilePathSwitch(path) in a try/catch inside the
onFilePath async callback (the same block that calls setShowEpicLoader and
setEpicLoaderError), setShowEpicLoader(false) on success,
setEpicLoaderError(`Failed to load file: ${path}`) on failure, and ensure the
loader is hidden in the catch path (and if needed also clear or set error when
onFilePathSwitch returns false) so all failure modes surface an error and close
the loading overlay.

In `@tests/commands/info.test.ts`:
- Around line 50-61: The imports in the beforeAll hook should use cache-busting
query params and TS-ignore comments to avoid module cache pollution: update the
three dynamic imports that provide AgentRegistry, registerBuiltinAgents, and
collectSystemInfo to append "?test-reload" to their module specifiers and add
the corresponding `@ts-expect-error` comment above each import so the test loads
fresh modules when calling AgentRegistry.resetInstance(),
registerBuiltinAgents(), and assigning collectSystemInfo.

In `@tests/templates/engine.test.ts`:
- Line 6: Remove the temporal NOTE and add afterAll to the test imports (so the
import line includes afterAll), then ensure any mock.module() call created in
beforeAll is restored in an afterAll inside the same describe block;
specifically update the describe block for installBuiltinTemplates to call
mock.module().restore() (or the appropriate mock restore API) in afterAll to
undo the module mock created by beforeAll, and delete the NOTE lines that use
temporal language; target the mock.module(), beforeAll, and
installBuiltinTemplates describe block to scope and cleanup the mock.
- Around line 738-750: The test calls freshInstallBuiltinTemplates(false) but
doesn't sandbox the user config dir, so update the test to ensure
getUserConfigDir is redirected to a temp testDir: either set
process.env.XDG_CONFIG_HOME (or process.env.HOME) to testDir in a beforeEach and
restore in afterEach, or change the mock.module() factory used for the fresh
import to return a mocked getUserConfigDir that resolves to testDir; make sure
to apply the sandbox before importing the fresh module and restore
environment/mocks after the test so freshInstallBuiltinTemplates and any code
that reads getUserConfigDir write to the isolated testDir instead of the real
~/.config directory.
🧹 Nitpick comments (4)
src/sandbox/detect.test.ts (1)

14-21: Consider renaming the describe block or testing actual module exports.

The test suite is named "sandbox detection constants" but only verifies that Node.js's platform() function returns expected values. This tests stdlib behaviour rather than any code from the sandbox detection module itself. If the intent is to have a placeholder test suite, consider either:

  1. Renaming to something more accurate (e.g., "platform utility behaviour")
  2. Importing and testing actual constants or exports from the sandbox detection module

Given the header comment explains this file is intentionally minimal, this is a minor clarity improvement.

💡 Suggested rename for clarity
-describe('sandbox detection constants', () => {
-  test('platform returns expected values', () => {
+describe('platform utility', () => {
+  test('returns a known OS identifier', () => {
     const currentPlatform = platform();
-    // platform() should return a known value
     expect(['darwin', 'linux', 'win32', 'freebsd', 'openbsd', 'sunos', 'aix']).toContain(
       currentPlatform
     );
   });
 });
tests/sandbox/detect.test.ts (2)

18-36: Consider adding a comment about keeping this in sync with production code.

The local implementation mirrors src/sandbox/detect.ts but uses Bun.spawn to avoid mock pollution. This creates a maintenance risk if the production logic changes.

📝 Suggested documentation addition
 /**
  * Local implementation of commandExists using Bun.spawn to bypass mock pollution.
  * Mirrors the logic in src/sandbox/detect.ts but uses Bun's native spawn.
+ *
+ * IMPORTANT: Keep this implementation in sync with src/sandbox/detect.ts.
+ * If the production logic changes, update this function accordingly.
  *
  * NOTE: Uses process.platform instead of platform() from node:os to avoid
  * mock pollution from other tests that mock the node:os module.
  */

45-53: Use test.skipIf() for clearer platform-specific test skipping.

Early return; statements in conditional blocks mask issues in test reporting. Bun's test runner supports test.skipIf() to make the intent explicit and improve test output clarity.

♻️ Example refactor
-  test('returns true for which command', async () => {
-    // 'which' doesn't exist on Windows (uses 'where' instead)
-    if (process.platform === 'win32') {
-      return; // Skip on Windows
-    }
-    // 'which' should exist on Linux/macOS
-    const result = await commandExists('which');
-    expect(result).toBe(true);
-  });
+  test.skipIf(process.platform === 'win32')('returns true for which command', async () => {
+    // 'which' should exist on Linux/macOS (Windows uses 'where' instead)
+    const result = await commandExists('which');
+    expect(result).toBe(true);
+  });

Also applies to: lines 131–144, 146–159, 161–168

tests/plugins/agents/base-execute.test.ts (1)

458-600: Guard env mutations with try/finally to prevent leakage between tests.
If a spawn/expectation throws before the restore block, process.env changes can bleed into later tests. A small helper keeps cleanup consistent.

♻️ Example helper to guarantee env restore
+async function withEnv(
+  key: string,
+  value: string,
+  run: () => Promise<void>
+): Promise<void> {
+  const original = process.env[key];
+  process.env[key] = value;
+  try {
+    await run();
+  } finally {
+    if (original === undefined) {
+      delete process.env[key];
+    } else {
+      process.env[key] = original;
+    }
+  }
+}
@@
-      const originalValue = process.env.TEST_KEPT_VAR;
-      process.env.TEST_KEPT_VAR = 'should_remain';
-
-      let stdout = '';
-      const handle = agent.execute('TEST_KEPT_VAR', [], {
-        onStdout: (data) => {
-          stdout += data;
-        },
-      });
-
-      await handle.promise;
-
-      if (originalValue === undefined) {
-        delete process.env.TEST_KEPT_VAR;
-      } else {
-        process.env.TEST_KEPT_VAR = originalValue;
-      }
-
-      await agent.dispose();
+      let stdout = '';
+      await withEnv('TEST_KEPT_VAR', 'should_remain', async () => {
+        const handle = agent.execute('TEST_KEPT_VAR', [], {
+          onStdout: (data) => {
+            stdout += data;
+          },
+        });
+
+        await handle.promise;
+      });
+
+      await agent.dispose();

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@tests/templates/engine.test.ts`:
- Around line 720-723: The inline comment above the dynamic import for
realTemplateEngine uses temporal wording ("first"); update the comment to be
evergreen by removing temporal cues and clarifying intent—for example, change
the comment that begins "CRITICAL: Get the REAL template engine module first,
bypassing any cached mock" to something like "CRITICAL: Load the real template
engine module, bypassing any cached mock" so it clearly documents why the import
with ?test-reload-install is used; locate the comment around the
realTemplateEngine variable and the import of
'../../src/templates/engine.js?test-reload-install' and replace the wording
accordingly.
- Around line 714-716: The afterEach teardown currently restores HOME by
assigning process.env.HOME = originalHome which writes the string "undefined" if
originalHome was not set; change the restore logic in the afterEach block so it
checks originalHome (the saved value) and if it is undefined deletes
process.env.HOME instead of assigning, otherwise reassigns it, keeping the
existing await cleanupTestDir(testDir) call; update references in the afterEach
closure where originalHome and process.env.HOME are used to implement this
conditional restore.
🧹 Nitpick comments (2)
src/commands/skills.test.ts (1)

24-44: Reduce maintenance drift in the pass-through mock.
Manually listing every export is easy to forget when new exports are added. Consider spreading the real module and override only when needed.

♻️ Proposed refactor
-  mock.module('../setup/skill-installer.js', () => ({
-    listBundledSkills: realSkillInstaller.listBundledSkills,
-    isSkillInstalledAt: realSkillInstaller.isSkillInstalledAt,
-    resolveSkillsPath: realSkillInstaller.resolveSkillsPath,
-    installViaAddSkill: realSkillInstaller.installViaAddSkill,
-    resolveAddSkillAgentId: realSkillInstaller.resolveAddSkillAgentId,
-    buildAddSkillInstallArgs: realSkillInstaller.buildAddSkillInstallArgs,
-    expandTilde: realSkillInstaller.expandTilde,
-    computeSkillsPath: realSkillInstaller.computeSkillsPath,
-    getBundledSkillsDir: realSkillInstaller.getBundledSkillsDir,
-    isEloopOnlyFailure: realSkillInstaller.isEloopOnlyFailure,
-    getSkillStatusForAgent: realSkillInstaller.getSkillStatusForAgent,
-    AGENT_ID_MAP: realSkillInstaller.AGENT_ID_MAP,
-  }));
+  mock.module('../setup/skill-installer.js', () => ({
+    ...realSkillInstaller,
+  }));
src/tui/components/FileBrowser.tsx (1)

158-188: Resolve typed relative paths against the current directory for more intuitive navigation.

Right now, relative inputs resolve against process CWD; using currentPath is more consistent with the browser context and matches user expectations when typing paths relative to the currently displayed directory.

♻️ Proposed change
  const expandPath = useCallback((path: string): string => {
    let expanded = path.trim();
    if (expanded.startsWith('~')) {
      expanded = homedir() + expanded.slice(1);
    }
-   return resolve(expanded);
-  }, []);
+   return resolve(currentPath, expanded);
+  }, [currentPath]);

@esumerfd
Copy link
Copy Markdown
Contributor Author

esumerfd commented Feb 3, 2026

dude, what happened, we were so close? Did I need to do something else?

@subsy
Copy link
Copy Markdown
Owner

subsy commented Feb 3, 2026

dude, what happened, we were so close? Did I need to do something else?

@esumerfd fear not - all the great work is in main via #263

After the massive parallel execution branch was merged it was just much easier for me to pick your commits into a new PR, combining with @bigs fuzzy search changes.

0.7.0 should go out tonight with all the changes

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.

3 participants