Skip to content

Feat/extension package configuration#3

Merged
ayagmar merged 4 commits intomasterfrom
feat/extension-package-configuration
Feb 23, 2026
Merged

Feat/extension package configuration#3
ayagmar merged 4 commits intomasterfrom
feat/extension-package-configuration

Conversation

@ayagmar
Copy link
Owner

@ayagmar ayagmar commented Feb 23, 2026

Closes #2

Summary by CodeRabbit

  • New Features

    • Package extension configuration panel to enable/disable individual extensions per installed package; changes persist to settings and can prompt a restart.
    • New package actions: Configure / Update / Remove / Details; keybinding "c" to open Configure.
    • Improved manager shortcuts and action labels (toggle/save wording).
  • Documentation

    • README and in-app help updated with configure shortcut, restart guidance, and notes on persisted staged changes.

@ayagmar
Copy link
Owner Author

ayagmar commented Feb 23, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

The PR adds a UI-driven package extension configuration panel, refactors extension entrypoint discovery to be manifest-driven and asynchronous (with npm root resolution), removes the separate package-extension item type from the unified list, adds a new 'c' key binding to configure package extensions, and updates types, UI flows, and tests accordingly.

Changes

Cohort / File(s) Summary
Documentation
README.md
Adds Node.js >=22.5.0 requirement, documents new "c" configure key, updates features/tips and restart/persistence guidance.
Extension Entrypoint Resolution
src/packages/extensions.ts
Async npm root resolution + caching; new resolveManifestExtensionEntrypoints and comprehensive glob/directory entry resolution; toPackageRoot now async; removed toProjectRelativePath export.
Installation Integration
src/packages/install.ts
Replaced inline manifest parsing with resolveManifestExtensionEntrypoints in hasStandaloneEntrypoint; removed obsolete helpers.
Package Management
src/packages/management.ts
Removed interactive showPackageActions per-package menu and its returned reload flag flow.
Type Definitions
src/types/index.ts
Removed "package-extension" from UnifiedItem.type, narrowed scope, removed packageSource/extensionPath, and added "configure" to UnifiedAction.
UI Footer & Help
src/ui/footer.ts, src/ui/help.ts
Footer no longer counts package-extension toggles; added "c Configure" shortcut and help entry for configure action.
Package Configuration UI
src/ui/package-config.ts
New comprehensive interactive panel: builds rows, validates file existence, stages changes, applies +/- markers into settings.json, and optionally prompts/restarts UI; exports builder/apply/configure functions.
Unified Items & Actions
src/ui/unified.ts
Dropped package-extension handling from buildUnifiedItems signature and logic; introduced promptPackageActionSelection and package-level actions (configure/update/remove/details) routed via new prompt flow.
Tests — new
test/package-config.test.ts, test/package-extensions.test.ts
Extensive tests for manifest resolution, glob/dir expansion, settings persistence, error handling, and setPackageExtensionState semantics.
Tests — updated
test/unified-items.test.ts
Migrated tests to unified local+package model, replaced package-extension fixtures with local extension entries, updated assertions and call signatures.
Package manifest
package.json
Bumped Node.js engine requirement to >=22.5.0.

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as UI/Command Handler
    participant Discovery as Extension Discovery
    participant Config as Config Panel
    participant Settings as Settings Manager
    participant Restart as Restart Handler

    User->>UI: Press 'c' to configure package
    activate UI
    UI->>Discovery: resolveManifestExtensionEntrypoints(packageRoot)
    activate Discovery
    Discovery->>Discovery: Parse package.json pi.extensions
    Discovery->>Discovery: Resolve globs & directories
    Discovery-->>UI: Return entrypoint list
    deactivate Discovery

    UI->>Config: configurePackageExtensions(pkg, ctx, pi)
    activate Config
    Config->>Config: buildPackageConfigRows(entries)
    Config->>Config: Check file existence and build UI rows
    Config->>Config: Show interactive selection panel

    User->>Config: Toggle entries (stage)
    Config->>Config: Track staged changes

    User->>Config: Save changes
    Config->>Settings: applyPackageExtensionChanges(rows, staged, ...)
    activate Settings
    Settings->>Settings: Read settings.json
    Settings->>Settings: Apply +/- markers to pi.extensions
    Settings->>Settings: Write updated settings.json
    Settings-->>Config: Confirm changes applied
    deactivate Settings

    Config->>Restart: promptRestartForPackageConfig()
    activate Restart
    User->>Restart: Confirm restart
    Restart->>UI: Trigger UI reload (ctx.reload)
    deactivate Restart
    Config-->>UI: Return changed count & reload status
    deactivate Config
    deactivate UI
    UI-->>User: Configuration complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A twitch and a hop
I found your package's hidden doors,
Files and globs and manifest scores.
Press 'c' — toggle, stage, and save,
Settings sing, the rabbit's glad and brave. 🎩

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly refers to the main feature added: package extension configuration UI with enable/disable functionality.
Linked Issues check ✅ Passed The PR implements the requested feature from issue #2: a UI for managing individual extension enable/disable within packages, replacing manual settings.json editing.
Out of Scope Changes check ✅ Passed All changes support the core objective of package extension configuration. The removal of showPackageActions and package-extension items are in-scope refactoring for the new unified configuration workflow.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/extension-package-configuration

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

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
src/ui/unified.ts (1)

273-309: ⚠️ Potential issue | 🟡 Minor

Normalize path separators before duplicate detection.
On Windows, localPath.startsWith(pkgResolvedLower + "/") can fail when localPath contains backslashes, letting duplicate package rows through. Normalize both sides to forward slashes before comparisons.

🔧 Suggested fix
-    localPaths.add(entry.activePath.toLowerCase());
+    localPaths.add(entry.activePath.replace(/\\/g, "/").toLowerCase());
     items.push({
       type: "local",
       id: entry.id,
@@
-    const pkgResolvedLower = pkg.resolvedPath?.toLowerCase() ?? "";
+    const pkgResolvedLower = pkg.resolvedPath?.replace(/\\/g, "/").toLowerCase() ?? "";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/unified.ts` around lines 273 - 309, The duplicate-detection fails on
Windows because comparisons mix backslashes and forward slashes; normalize path
separators to forward slashes before any comparisons. When building localPaths
(and when computing pkgSourceLower/pkgResolvedLower inside the installedPackages
loop) replace backslashes with "/" and lowercase the result, then use those
normalizedLocalPath and normalizedPkgResolved strings for equality, startsWith,
and localDir comparisons (referencing localPaths, localPath, installedPackages,
pkgSourceLower, pkgResolvedLower). Ensure the localPaths Set stores normalized
paths and that all comparisons use the normalized variables to reliably detect
duplicates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/packages/extensions.ts`:
- Around line 366-423: The directory token normalization step can leave a
trailing slash (e.g., "src/") which later produces double slashes and prevents
matches; inside resolveManifestExtensionEntries after computing pattern (the
result of normalizeRelativePath) trim any trailing path separator(s) from
pattern (e.g., remove a trailing "/" or "\\") before using isSafeRelativePath,
hasGlobMagic, selectDirectoryFiles, and isExtensionEntrypointPath so directory
lookups and pattern matching work correctly.
- Around line 209-219: The Node.js-only API used by matchesFilterPattern
(matchesGlob) may be missing on Node <22.5.0; update by adding a safe fallback:
in the module that defines/uses matchesGlob, detect availability (typeof
matchesGlob === 'function' or try importing path.matchesGlob) and if missing,
import a small dependency (picomatch or minimatch) and provide a local
matchesGlob fallback function used by matchesFilterPattern; alternatively,
tighten package.json engine to ">=22.5.0" and document the change—ensure you
reference and update the matchesFilterPattern function to call the safe
matchesGlob wrapper instead of relying on the built-in directly.

---

Outside diff comments:
In `@src/ui/unified.ts`:
- Around line 273-309: The duplicate-detection fails on Windows because
comparisons mix backslashes and forward slashes; normalize path separators to
forward slashes before any comparisons. When building localPaths (and when
computing pkgSourceLower/pkgResolvedLower inside the installedPackages loop)
replace backslashes with "/" and lowercase the result, then use those
normalizedLocalPath and normalizedPkgResolved strings for equality, startsWith,
and localDir comparisons (referencing localPaths, localPath, installedPackages,
pkgSourceLower, pkgResolvedLower). Ensure the localPaths Set stores normalized
paths and that all comparisons use the normalized variables to reliably detect
duplicates.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 869d556 and 9ef3f20.

📒 Files selected for processing (12)
  • README.md
  • src/packages/extensions.ts
  • src/packages/install.ts
  • src/packages/management.ts
  • src/types/index.ts
  • src/ui/footer.ts
  • src/ui/help.ts
  • src/ui/package-config.ts
  • src/ui/unified.ts
  • test/package-config.test.ts
  • test/package-extensions.test.ts
  • test/unified-items.test.ts

@ayagmar ayagmar merged commit 6d3da96 into master Feb 23, 2026
1 of 2 checks passed
Copy link

@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

Caution

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

⚠️ Outside diff range comments (2)
test/package-extensions.test.ts (2)

113-152: ⚠️ Potential issue | 🟡 Minor

Missing try/finally — temp directory not cleaned up on failure.

cwd is created with mkdtemp but there is no try/finally block. If any assertion throws, the directory is leaked. All other new tests in this file use try/finally for cleanup.

🔧 Suggested fix
 void test("discoverPackageExtensions reads manifest entrypoints and project filter state", async () => {
   const cwd = await mkdtemp(join(tmpdir(), "pi-extmgr-cwd-"));
   const pkgRoot = join(cwd, "vendor", "demo");

+  try {
   await mkdir(pkgRoot, { recursive: true });
   // ... rest of setup and assertions ...
   const discovered = await discoverPackageExtensions(installed, cwd);
   assert.equal(discovered.length, 1);
   assert.equal(discovered[0]?.extensionPath, "index.ts");
   assert.equal(discovered[0]?.state, "disabled");
+  } finally {
+    await rm(cwd, { recursive: true, force: true });
+  }
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/package-extensions.test.ts` around lines 113 - 152, Wrap the test
"discoverPackageExtensions reads manifest entrypoints and project filter state"
body in a try/finally: create cwd with mkdtemp as before, then move all setup,
calls to discoverPackageExtensions and assertions into the try block, and in the
finally block remove the temporary directory (cwd) recursively (e.g., using rm
or rmdir with recursive option) to ensure cleanup on failure; reference the test
name, the cwd variable, mkdtemp call, and the final call to
discoverPackageExtensions so you can find where to add the try/finally and the
recursive deletion.

311-364: ⚠️ Potential issue | 🟡 Minor

finally block only restores the env var — cwd and agentDir are never cleaned up.

Both temp directories are created via mkdtemp but neither is removed in the finally block. On test failure they are permanently leaked.

🔧 Suggested fix
   } finally {
     if (oldAgentDir === undefined) {
       delete process.env.PI_CODING_AGENT_DIR;
     } else {
       process.env.PI_CODING_AGENT_DIR = oldAgentDir;
     }
+    await rm(cwd, { recursive: true, force: true });
+    await rm(agentDir, { recursive: true, force: true });
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/package-extensions.test.ts` around lines 311 - 364, The finally block
for the test "setPackageExtensionState converts string package entries and keeps
latest marker" only restores process.env.PI_CODING_AGENT_DIR but does not remove
the temporary directories created with mkdtemp (cwd and agentDir), leaking temp
files on failure; update the finally block to also remove both temp dirs (cwd
and agentDir) using an async recursive rm/rmdir (e.g., fs.promises.rm with {
recursive: true, force: true } or equivalent) awaited before exiting the
finally, while keeping the existing logic that restores or deletes
process.env.PI_CODING_AGENT_DIR and preserving the order so env restoration
happens after cleanup.
♻️ Duplicate comments (1)
package.json (1)

59-61: Correct engine bump for path.matchesGlob.

>=22.5.0 is the exact minimum needed for path.matchesGlob on the v22.x line.

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

In `@package.json` around lines 59 - 61, The package.json engines.node field must
require Node >=22.5.0 to guarantee availability of path.matchesGlob; update the
"engines" -> "node" value to ">=22.5.0" (edit the package.json engines.entry) so
the project explicitly enforces the correct minimum Node version.
🧹 Nitpick comments (1)
src/ui/unified.ts (1)

758-779: Consider adding a default guard to the selection switch.

If the TypeScript types ever widen (or a new PackageActionKey is added without a corresponding case), execution silently falls through to applyToggleChangesFromManager and applies local toggle changes — incorrect for a package action context.

🛡️ Proposed defensive default
      case "details": {
        ...
        return false;
      }
+     default:
+       return false;
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/unified.ts` around lines 758 - 779, The switch on selection in the
package-action handling block (inside unified.ts where
configurePackageExtensions, updatePackageWithOutcome, removePackageWithOutcome
and the "details" branch are used) lacks a default guard; add a default case
that either throws an explicit error or returns false (and logs) to prevent
accidental fall-through to applyToggleChangesFromManager when selection widens;
implement an exhaustive-check pattern (e.g., assertUnreachable(selection)) or a
clear default return to ensure any unhandled PackageActionKey is caught at
runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/packages/extensions.ts`:
- Around line 52-56: The execFileAsync call that invokes npm (the invocation of
execFileAsync("npm", ["root", "-g"], { timeout: 2_000, windowsHide: true }))
will silently fail on Windows when only npm.cmd exists; update that call (in
src/packages/extensions.ts where execFileAsync is used) to add shell: true to
the options object so .cmd/.bat invocations work cross-platform, preserving the
existing timeout and windowsHide settings.

---

Outside diff comments:
In `@test/package-extensions.test.ts`:
- Around line 113-152: Wrap the test "discoverPackageExtensions reads manifest
entrypoints and project filter state" body in a try/finally: create cwd with
mkdtemp as before, then move all setup, calls to discoverPackageExtensions and
assertions into the try block, and in the finally block remove the temporary
directory (cwd) recursively (e.g., using rm or rmdir with recursive option) to
ensure cleanup on failure; reference the test name, the cwd variable, mkdtemp
call, and the final call to discoverPackageExtensions so you can find where to
add the try/finally and the recursive deletion.
- Around line 311-364: The finally block for the test "setPackageExtensionState
converts string package entries and keeps latest marker" only restores
process.env.PI_CODING_AGENT_DIR but does not remove the temporary directories
created with mkdtemp (cwd and agentDir), leaking temp files on failure; update
the finally block to also remove both temp dirs (cwd and agentDir) using an
async recursive rm/rmdir (e.g., fs.promises.rm with { recursive: true, force:
true } or equivalent) awaited before exiting the finally, while keeping the
existing logic that restores or deletes process.env.PI_CODING_AGENT_DIR and
preserving the order so env restoration happens after cleanup.

---

Duplicate comments:
In `@package.json`:
- Around line 59-61: The package.json engines.node field must require Node
>=22.5.0 to guarantee availability of path.matchesGlob; update the "engines" ->
"node" value to ">=22.5.0" (edit the package.json engines.entry) so the project
explicitly enforces the correct minimum Node version.

---

Nitpick comments:
In `@src/ui/unified.ts`:
- Around line 758-779: The switch on selection in the package-action handling
block (inside unified.ts where configurePackageExtensions,
updatePackageWithOutcome, removePackageWithOutcome and the "details" branch are
used) lacks a default guard; add a default case that either throws an explicit
error or returns false (and logs) to prevent accidental fall-through to
applyToggleChangesFromManager when selection widens; implement an
exhaustive-check pattern (e.g., assertUnreachable(selection)) or a clear default
return to ensure any unhandled PackageActionKey is caught at runtime.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ef3f20 and d32ae58.

📒 Files selected for processing (6)
  • README.md
  • package.json
  • src/packages/extensions.ts
  • src/ui/unified.ts
  • test/package-extensions.test.ts
  • test/unified-items.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • README.md

Comment on lines +52 to +56
try {
const { stdout } = await execFileAsync("npm", ["root", "-g"], {
timeout: 2_000,
windowsHide: true,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

execFile("npm", ...) will silently fail on Windows where only npm.cmd is in PATH.

On Windows, .bat and .cmd files are not executable on their own without a terminal, and therefore cannot be launched using child_process.execFile(). npm on many Windows installations (e.g., Chocolatey, older official installers) ships only as npm.cmd, making this call fail. The error is caught and globalNpmRootCache is set to null, so global npm root discovery is silently disabled on those setups, falling back to PI_PACKAGE_DIR-based resolution only.

Add shell: true to cover .cmd-only environments:

🔧 Suggested fix
     const { stdout } = await execFileAsync("npm", ["root", "-g"], {
       timeout: 2_000,
       windowsHide: true,
+      shell: process.platform === "win32",
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/packages/extensions.ts` around lines 52 - 56, The execFileAsync call that
invokes npm (the invocation of execFileAsync("npm", ["root", "-g"], { timeout:
2_000, windowsHide: true })) will silently fail on Windows when only npm.cmd
exists; update that call (in src/packages/extensions.ts where execFileAsync is
used) to add shell: true to the options object so .cmd/.bat invocations work
cross-platform, preserving the existing timeout and windowsHide settings.

@coderabbitai coderabbitai bot mentioned this pull request Feb 24, 2026
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.

Enable/disable extensions inside of one package

1 participant