Skip to content

feat: Implement shared IDE extension core module#30

Merged
Patrick-Ehimen merged 2 commits intomainfrom
feat/shared-extension-core
Oct 25, 2025
Merged

feat: Implement shared IDE extension core module#30
Patrick-Ehimen merged 2 commits intomainfrom
feat/shared-extension-core

Conversation

@Patrick-Ehimen
Copy link
Copy Markdown
Owner

@Patrick-Ehimen Patrick-Ehimen commented Oct 23, 2025

Overview

Implements the shared IDE extension core module that provides common business logic, workspace context management, AI command handling, and progress streaming for both VSCode and Cursor extensions.

Key Features

  • ExtensionCore: Main orchestrator managing all components
  • WorkspaceContextProvider: Comprehensive workspace info for AI agents (files, Git, datasets)
  • AICommandHandler: Built-in Lighthouse operations (upload, download, datasets)
  • CommandRegistry: Centralized command management with validation
  • ProgressStreamer: Real-time progress tracking
  • ConfigurationManager: Persistent settings with change notifications

Structure

packages/extension-core/
├── src/
│   ├── core/           # Extension orchestrator, commands, progress
│   ├── workspace/      # Context provider, file watcher, Git integration
│   ├── ai/            # AI command handler, context/session management
│   ├── config/        # Configuration and settings persistence
│   ├── events/        # Event system
│   └── types/         # TypeScript interfaces
└── __tests__/         # Unit tests

Usage

const core = createExtensionCore();
await core.initialize();

// Access components
const workspaceProvider = core.getWorkspaceContextProvider();
const aiHandler = core.getAICommandHandler();
const progressStreamer = core.getProgressStreamer();

Built-in AI Commands

  • lighthouse.upload.file - Upload files with encryption
  • lighthouse.download.file - Download by CID
  • lighthouse.create.dataset - Dataset management
  • lighthouse.workspace.context - Get workspace info
  • lighthouse.list.datasets - List active datasets

Summary by Sourcery

Implement a shared IDE extension core module with orchestrator, workspace context, AI commands, progress tracking, configuration management, and an event bus

New Features:

  • Add ExtensionCore orchestrator tying together commands, workspace context, AI handler, progress streamer, and configuration manager
  • Implement WorkspaceContextProvider that scans files, integrates Git info, manages datasets, and emits context updates
  • Provide AICommandHandler with built-in lighthouse commands for file upload, download, dataset creation, context retrieval, and workspace refresh
  • Introduce CommandRegistry for registering, validating, and executing commands with event emission
  • Add ProgressStreamer to manage and emit real-time operation progress events
  • Create ConfigurationManager for loading, saving, and watching persistent extension settings
  • Build an event system via ExtensionEventEmitter and extend event type definitions

Build:

  • Set up @lighthouse-tooling/extension-core package with TypeScript, Jest, ESLint, and project configs

Tests:

  • Add unit test scaffolds for command registry, extension core, and progress streamer

…ive extension core architecture with modular design - Implemented ExtensionCore class orchestrating all extension functionality - Built CommandRegistry with command registration, validation, and execution - Created ProgressStreamer for real-time progress updates with event streaming - Implemented WorkspaceContextProvider with file watching and Git integration - Added AICommandHandler with built-in Lighthouse commands (upload/download/datasets) - Built ConfigurationManager with persistent settings and change notifications - Created comprehensive event system with ExtensionEventEmitter - Added extensive unit tests covering all core functionality (20 tests passing) - Integrated with existing SDK wrapper and shared utilities - Proper TypeScript types and interfaces for all components
@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Oct 23, 2025

Reviewer's Guide

Implements a shared IDE extension core module by composing an orchestrator, command registry, workspace context provider, AI command handler, progress streaming, and configuration management into a cohesive TypeScript package with event-driven architecture.

Sequence diagram for AI command handling and progress streaming

sequenceDiagram
    participant AICommandHandlerImpl
    participant ProgressStreamerImpl
    participant WorkspaceContextProviderImpl
    participant LighthouseAISDK
    participant ExtensionEventEmitter
    actor IDEUser
    IDEUser->>AICommandHandlerImpl: Send AI command (e.g. upload file)
    AICommandHandlerImpl->>ProgressStreamerImpl: startProgress(operationId, title)
    AICommandHandlerImpl->>LighthouseAISDK: uploadFile(filePath)
    LighthouseAISDK-->>AICommandHandlerImpl: result
    AICommandHandlerImpl->>ProgressStreamerImpl: complete(result)
    AICommandHandlerImpl->>ExtensionEventEmitter: emit("ai.command.completed", ...)
    AICommandHandlerImpl-->>IDEUser: Return AICommandResult
Loading

Class diagram for the new shared IDE extension core module

classDiagram
    class ExtensionCoreImpl {
        -_initialized: boolean
        -_commandRegistry: CommandRegistry
        -_workspaceContextProvider: WorkspaceContextProvider
        -_aiCommandHandler: AICommandHandler
        -_progressStreamer: ProgressStreamer
        -_configurationManager: ConfigurationManager
        -_eventEmitter: ExtensionEventEmitter
        -_logger: Logger
        +initialize()
        +dispose()
        +getCommandRegistry()
        +getWorkspaceContextProvider()
        +getAICommandHandler()
        +getProgressStreamer()
        +getConfigurationManager()
        +isInitialized()
    }
    class CommandRegistryImpl {
        -_commands: Map
        -_logger: Logger
        -_eventEmitter: ExtensionEventEmitter
        +registerCommand(definition)
        +unregisterCommand(commandId)
        +executeCommand(commandId, ...args)
        +getCommands()
        +hasCommand(commandId)
    }
    class WorkspaceContextProviderImpl {
        -_workspacePath: string
        -_cachedContext: WorkspaceContext | null
        -_cacheTimestamp: number
        -_cacheTtl: number
        -_fileWatcher: FileWatcherImpl
        -_gitIntegration: GitIntegrationImpl
        -_eventEmitter: ExtensionEventEmitter
        -_configurationManager: ConfigurationManager
        -_logger: Logger
        +initialize()
        +dispose()
        +getContext()
        +refreshContext()
        +watchWorkspace(callback)
        +getWorkspaceFiles()
        +getLighthouseFiles()
        +getActiveDatasets()
    }
    class AICommandHandlerImpl {
        -_handlers: Map
        -_workspaceContextProvider: WorkspaceContextProvider
        -_progressStreamer: ProgressStreamer
        -_eventEmitter: ExtensionEventEmitter
        -_lighthouseSDK: LighthouseAISDK
        -_logger: Logger
        +initialize()
        +dispose()
        +handleCommand(command)
        +registerHandler(commandType, handler)
        +unregisterHandler(commandType)
        +getAvailableCommands()
    }
    class ProgressStreamerImpl {
        -_streams: Map
        -_eventEmitter: ExtensionEventEmitter
        -_logger: Logger
        +startProgress(operationId, title)
        +getProgress(operationId)
        +stopProgress(operationId)
        +getActiveStreams()
        +clearAllStreams()
    }
    class ConfigurationManagerImpl {
        -_configuration: ExtensionConfiguration
        -_settingsProvider: SettingsProviderImpl
        -_watchers: Set
        -_logger: Logger
        +initialize()
        +dispose()
        +getConfiguration()
        +updateConfiguration(config)
        +watchConfiguration(callback)
        +resetConfiguration()
    }
    class ExtensionEventEmitter {
        +emit(eventType, event)
        +on(eventType, listener)
        +once(eventType, listener)
        +getListenerCount(eventType)
        +getEventNames()
    }
    class FileWatcherImpl {
        -_workspacePath: string
        -_watchers: Map
        -_callbacks: Set
        -_logger: Logger
        -_debounceMap: Map
        -_debounceDelay: number
        +initialize()
        +dispose()
        +onFileChanged(callback)
        +offFileChanged(callback)
    }
    class GitIntegrationImpl {
        -_workspacePath: string
        -_logger: Logger
        -_isGitRepo: boolean
        +initialize()
        +dispose()
        +getGitInfo()
    }
    class SettingsProviderImpl {
        -_settingsPath: string
        -_logger: Logger
        +loadConfiguration()
        +saveConfiguration(config)
    }
    class ProgressStreamImpl {
        -_operationId: string
        -_title: string
        -_currentProgress: ProgressUpdate
        -_active: boolean
        -_eventEmitter: ExtensionEventEmitter
        -_logger: Logger
        +update(progress)
        +complete(result)
        +fail(error)
        +cancel()
        +getCurrentProgress()
        +isActive()
    }
    class AIContextManagerImpl {
        -_currentContext: AIContext | null
        -_logger: Logger
        +getCurrentContext()
        +setContext(context)
        +updateSession(sessionUpdate)
        +updatePreferences(preferences)
        +addHistoryEntry(entry)
        +clearContext()
    }
    class AISessionManagerImpl {
        -_activeSessions: Map
        -_logger: Logger
        +createSession(agentId, context)
        +getSession(sessionId)
        +updateSessionActivity(sessionId)
        +endSession(sessionId)
        +getActiveSessions()
        +cleanupOldSessions(maxAge)
    }
    ExtensionCoreImpl --> CommandRegistryImpl
    ExtensionCoreImpl --> WorkspaceContextProviderImpl
    ExtensionCoreImpl --> AICommandHandlerImpl
    ExtensionCoreImpl --> ProgressStreamerImpl
    ExtensionCoreImpl --> ConfigurationManagerImpl
    ExtensionCoreImpl --> ExtensionEventEmitter
    WorkspaceContextProviderImpl --> FileWatcherImpl
    WorkspaceContextProviderImpl --> GitIntegrationImpl
    WorkspaceContextProviderImpl --> ExtensionEventEmitter
    WorkspaceContextProviderImpl --> ConfigurationManagerImpl
    AICommandHandlerImpl --> WorkspaceContextProviderImpl
    AICommandHandlerImpl --> ProgressStreamerImpl
    AICommandHandlerImpl --> ExtensionEventEmitter
    ProgressStreamerImpl --> ProgressStreamImpl
    ProgressStreamerImpl --> ExtensionEventEmitter
    ConfigurationManagerImpl --> SettingsProviderImpl
    CommandRegistryImpl --> ExtensionEventEmitter
    FileWatcherImpl --> Logger
    GitIntegrationImpl --> Logger
    SettingsProviderImpl --> Logger
    ProgressStreamImpl --> ExtensionEventEmitter
    ProgressStreamImpl --> Logger
    AIContextManagerImpl --> Logger
    AISessionManagerImpl --> Logger
Loading

File-Level Changes

Change Details Files
Add ExtensionCore orchestrator
  • Initialize components in correct order
  • Register core commands and event listeners
  • Expose getters for sub-components
packages/extension-core/src/core/extension-core.ts
packages/extension-core/src/index.ts
Implement CommandRegistry
  • Validate and wrap command definitions with logging
  • Manage register/unregister flows
  • Emit execution and error events
packages/extension-core/src/core/command-registry.ts
Implement ProgressStreamer
  • Create progress streams with update/complete/fail/cancel methods
  • Emit progress events and periodic cleanup
  • Manage active streams map
packages/extension-core/src/core/progress-streamer.ts
Implement WorkspaceContextProvider
  • Scan and cache workspace files with TTL
  • Provide Git integration and metadata extraction
  • Watch workspace changes and invoke callbacks
packages/extension-core/src/workspace/context-provider.ts
packages/extension-core/src/workspace/file-watcher.ts
packages/extension-core/src/workspace/git-integration.ts
Implement AICommandHandler
  • Register built-in Lighthouse AI commands
  • Integrate with LighthouseAISDK for upload/download
  • Emit command lifecycle events via event emitter
packages/extension-core/src/ai/ai-command-handler.ts
Implement ConfigurationManager
  • Define default extension configuration
  • Persist settings via SettingsProvider
  • Support watchers for change notifications
packages/extension-core/src/config/configuration-manager.ts
packages/extension-core/src/config/settings-provider.ts
Add event system and types
  • Extend Node EventEmitter for debug and error-handling
  • Define all extension event types constants
  • Provide strongly-typed interfaces for events
packages/extension-core/src/events/event-emitter.ts
packages/extension-core/src/events/event-types.ts
packages/extension-core/src/types/index.ts
Add AI session and context managers
  • Manage AI session lifecycle and activity tracking
  • Maintain AI context and preferences
  • Cap history entries and support context clearing
packages/extension-core/src/ai/ai-session-manager.ts
packages/extension-core/src/ai/ai-context-manager.ts
Add scaffolding and configuration
  • Introduce package.json and build/test scripts
  • Provide tsconfig, eslint, and jest configurations
  • Include initial test files stubs
packages/extension-core/package.json
packages/extension-core/tsconfig.json
packages/extension-core/jest.config.js
packages/extension-core/eslint.config.mjs
packages/extension-core/__tests__/*

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey there - I've reviewed your changes - here's some feedback:

  • ProgressStreamerImpl starts a setInterval for cleanup but never clears it on dispose—consider adding a dispose method to clear that timer to avoid orphaned background tasks.
  • GitIntegrationImpl uses execSync for all Git commands, which will block the event loop—switch to an async child_process API or a promise‐based Git library to keep operations non-blocking.
  • FileWatcherImpl relies on fs.watch with recursive option and custom ignore logic—consider adopting a battle-tested watcher like chokidar to improve cross-platform reliability and more flexible ignore patterns.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- ProgressStreamerImpl starts a setInterval for cleanup but never clears it on dispose—consider adding a dispose method to clear that timer to avoid orphaned background tasks.
- GitIntegrationImpl uses execSync for all Git commands, which will block the event loop—switch to an async child_process API or a promise‐based Git library to keep operations non-blocking.
- FileWatcherImpl relies on fs.watch with recursive option and custom ignore logic—consider adopting a battle-tested watcher like chokidar to improve cross-platform reliability and more flexible ignore patterns.

## Individual Comments

### Comment 1
<location> `packages/extension-core/src/workspace/context-provider.ts:672-678` </location>
<code_context>
+    const frontendFrameworks = frameworks.filter((f) => f.type === "frontend");
+    const backendFrameworks = frameworks.filter((f) => f.type === "backend");
+
+    if (frontendFrameworks.length > 0 && backendFrameworks.length > 0) {
+      return "web_app";
+    } else if (frontendFrameworks.length > 0) {
+      return "web_app";
</code_context>

<issue_to_address>
**suggestion:** Workspace type detection may be too broad.

Current logic does not differentiate between full-stack and frontend-only apps. Please update type detection to provide more specific classifications.

```suggestion
    if (frontendFrameworks.length > 0 && backendFrameworks.length > 0) {
      return "web_fullstack_app";
    } else if (frontendFrameworks.length > 0) {
      return "web_frontend_app";
    } else if (backendFrameworks.length > 0) {
      return "web_backend_app";
    }
```
</issue_to_address>

### Comment 2
<location> `packages/extension-core/src/core/progress-streamer.ts:191` </location>
<code_context>
+    this._logger = new Logger({ level: "info", component: "ProgressStreamer" });
+
+    // Clean up completed streams periodically
+    setInterval(() => this._cleanupCompletedStreams(), 60000); // Every minute
+  }
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Unmanaged setInterval may cause resource leaks.

Store the interval ID and clear it in a dispose method to prevent leaks when ProgressStreamerImpl is disposed and recreated.
</issue_to_address>

### Comment 3
<location> `packages/extension-core/src/core/command-registry.ts:220-222` </location>
<code_context>
+  ): void {
+    const requiredParams = parameters.filter((p) => p.required);
+
+    if (args.length < requiredParams.length) {
+      throw new Error(
+        `Command ${commandId} requires ${requiredParams.length} arguments, got ${args.length}`,
+      );
+    }
</code_context>

<issue_to_address>
**issue:** Argument validation may not handle optional parameters correctly.

Validating only by required parameter count may fail if optional parameters come before required ones. Please update the logic to account for parameter order and required status.
</issue_to_address>

### Comment 4
<location> `packages/extension-core/src/events/event-emitter.ts:27` </location>
<code_context>
+  /**
+   * Emit an extension event
+   */
+  emit(eventType: string, event: ExtensionEvent | any): boolean {
+    this._logger.debug(`Emitting event: ${eventType}`, event);
+    return super.emit(eventType, event);
</code_context>

<issue_to_address>
**suggestion:** Event type is not strictly validated.

Validate eventType against the predefined set of allowed event types to prevent typos and incorrect usage.

Suggested implementation:

```typescript
  /**
   * Allowed event types for extension events
   */
  private static readonly ALLOWED_EVENT_TYPES = [
    "extensionLoaded",
    "extensionUnloaded",
    "extensionError",
    // Add other allowed event types here
  ];

  /**
   * Emit an extension event
   */
  emit(eventType: string, event: ExtensionEvent | any): boolean {
    if (!ExtensionEventEmitter.ALLOWED_EVENT_TYPES.includes(eventType)) {
      this._logger.error(`Invalid event type: ${eventType}. Allowed types: ${ExtensionEventEmitter.ALLOWED_EVENT_TYPES.join(", ")}`);
      return false;
    }
    this._logger.debug(`Emitting event: ${eventType}`, event);
    return super.emit(eventType, event);
  }

```

- You should update the `ALLOWED_EVENT_TYPES` array to include all valid event types used in your codebase.
- If event types are defined elsewhere (e.g., as an enum or constant), consider importing and using that definition for consistency.
</issue_to_address>

### Comment 5
<location> `packages/extension-core/__tests__/command-registry.test.ts:5` </location>
<code_context>
+ * Command registry tests
+ * @fileoverview Tests for the command registry functionality
+ */
+
+import { CommandRegistryImpl } from "../src/core/command-registry";
+import { ExtensionEventEmitter } from "../src/events/event-emitter";
</code_context>

<issue_to_address>
**suggestion (testing):** Missing test coverage for command argument validation and error handling.

Add tests for argument validation failures (missing, incorrect types, custom validator errors) and error handling during command execution (disabled or unregistered commands).
</issue_to_address>

### Comment 6
<location> `packages/extension-core/__tests__/extension-core.test.ts:5` </location>
<code_context>
+ * Command registry tests
+ * @fileoverview Tests for the command registry functionality
+ */
+
+import { CommandRegistryImpl } from "../src/core/command-registry";
+import { ExtensionEventEmitter } from "../src/events/event-emitter";
</code_context>

<issue_to_address>
**suggestion (testing):** Missing tests for error scenarios during initialization and disposal.

Add tests that cover error handling during ExtensionCore initialization and disposal, ensuring proper logging and propagation.
</issue_to_address>

### Comment 7
<location> `packages/extension-core/__tests__/progress-streamer.test.ts:5` </location>
<code_context>
+ * Command registry tests
+ * @fileoverview Tests for the command registry functionality
+ */
+
+import { CommandRegistryImpl } from "../src/core/command-registry";
+import { ExtensionEventEmitter } from "../src/events/event-emitter";
</code_context>

<issue_to_address>
**suggestion (testing):** Missing tests for cleanup and edge cases in progress streaming.

Add tests to cover cleanup of completed, failed, and cancelled progress streams, ensuring they are removed as expected. Also include cases for starting streams with duplicate operationIds and stopping streams that do not exist.
</issue_to_address>

### Comment 8
<location> `packages/extension-core/src/core/command-registry.ts:236-240` </location>
<code_context>
      if (arg !== undefined && arg !== null && param.validator) {
        if (!param.validator(arg)) {
          throw new Error(`Command ${commandId} parameter ${param.name} validation failed`);
        }
      }

</code_context>

<issue_to_address>
**suggestion (code-quality):** Merge nested if conditions ([`merge-nested-ifs`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/merge-nested-ifs))

```suggestion
      if (arg !== undefined && arg !== null && param.validator && !param.validator(arg)) {
            throw new Error(`Command ${commandId} parameter ${param.name} validation failed`);
      }

```

<br/><details><summary>Explanation</summary>Reading deeply nested conditional code is confusing, since you have to keep track of which
conditions relate to which levels. We therefore strive to reduce nesting where
possible, and the situation where two `if` conditions can be combined using
`and` is an easy win.
</details>
</issue_to_address>

### Comment 9
<location> `packages/extension-core/src/core/extension-core.ts:204-210` </location>
<code_context>
        const status = {
          initialized: this._initialized,
          activeStreams: this._progressStreamer.getActiveStreams().length,
          registeredCommands: this._commandRegistry.getCommands().length,
          workspaceContext: await this._workspaceContextProvider.getContext(),
        };
        return status;

</code_context>

<issue_to_address>
**suggestion (code-quality):** Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/inline-immediately-returned-variable))

```suggestion
        return {
                  initialized: this._initialized,
                  activeStreams: this._progressStreamer.getActiveStreams().length,
                  registeredCommands: this._commandRegistry.getCommands().length,
                  workspaceContext: await this._workspaceContextProvider.getContext(),
                };

```

<br/><details><summary>Explanation</summary>Something that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
</details>
</issue_to_address>

### Comment 10
<location> `packages/extension-core/src/workspace/context-provider.ts:199-200` </location>
<code_context>
      const files = await this._scanDirectory(this._workspacePath);
      return files;

</code_context>

<issue_to_address>
**suggestion (code-quality):** Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/TypeScript/Default-Rules/inline-immediately-returned-variable))

```suggestion
      return await this._scanDirectory(this._workspacePath);

```

<br/><details><summary>Explanation</summary>Something that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.
</details>
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +672 to +678
if (frontendFrameworks.length > 0 && backendFrameworks.length > 0) {
return "web_app";
} else if (frontendFrameworks.length > 0) {
return "web_app";
} else if (backendFrameworks.length > 0) {
return "web_app";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Workspace type detection may be too broad.

Current logic does not differentiate between full-stack and frontend-only apps. Please update type detection to provide more specific classifications.

Suggested change
if (frontendFrameworks.length > 0 && backendFrameworks.length > 0) {
return "web_app";
} else if (frontendFrameworks.length > 0) {
return "web_app";
} else if (backendFrameworks.length > 0) {
return "web_app";
}
if (frontendFrameworks.length > 0 && backendFrameworks.length > 0) {
return "web_fullstack_app";
} else if (frontendFrameworks.length > 0) {
return "web_frontend_app";
} else if (backendFrameworks.length > 0) {
return "web_backend_app";
}

this._logger = new Logger({ level: "info", component: "ProgressStreamer" });

// Clean up completed streams periodically
setInterval(() => this._cleanupCompletedStreams(), 60000); // Every minute
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): Unmanaged setInterval may cause resource leaks.

Store the interval ID and clear it in a dispose method to prevent leaks when ProgressStreamerImpl is disposed and recreated.

Comment on lines +220 to +222
if (args.length < requiredParams.length) {
throw new Error(
`Command ${commandId} requires ${requiredParams.length} arguments, got ${args.length}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue: Argument validation may not handle optional parameters correctly.

Validating only by required parameter count may fail if optional parameters come before required ones. Please update the logic to account for parameter order and required status.

/**
* Emit an extension event
*/
emit(eventType: string, event: ExtensionEvent | any): boolean {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion: Event type is not strictly validated.

Validate eventType against the predefined set of allowed event types to prevent typos and incorrect usage.

Suggested implementation:

  /**
   * Allowed event types for extension events
   */
  private static readonly ALLOWED_EVENT_TYPES = [
    "extensionLoaded",
    "extensionUnloaded",
    "extensionError",
    // Add other allowed event types here
  ];

  /**
   * Emit an extension event
   */
  emit(eventType: string, event: ExtensionEvent | any): boolean {
    if (!ExtensionEventEmitter.ALLOWED_EVENT_TYPES.includes(eventType)) {
      this._logger.error(`Invalid event type: ${eventType}. Allowed types: ${ExtensionEventEmitter.ALLOWED_EVENT_TYPES.join(", ")}`);
      return false;
    }
    this._logger.debug(`Emitting event: ${eventType}`, event);
    return super.emit(eventType, event);
  }
  • You should update the ALLOWED_EVENT_TYPES array to include all valid event types used in your codebase.
  • If event types are defined elsewhere (e.g., as an enum or constant), consider importing and using that definition for consistency.

* Extension core tests
* @fileoverview Tests for the main extension core functionality
*/

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (testing): Missing tests for error scenarios during initialization and disposal.

Add tests that cover error handling during ExtensionCore initialization and disposal, ensuring proper logging and propagation.

Comment on lines +204 to +210
const status = {
initialized: this._initialized,
activeStreams: this._progressStreamer.getActiveStreams().length,
registeredCommands: this._commandRegistry.getCommands().length,
workspaceContext: await this._workspaceContextProvider.getContext(),
};
return status;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const status = {
initialized: this._initialized,
activeStreams: this._progressStreamer.getActiveStreams().length,
registeredCommands: this._commandRegistry.getCommands().length,
workspaceContext: await this._workspaceContextProvider.getContext(),
};
return status;
return {
initialized: this._initialized,
activeStreams: this._progressStreamer.getActiveStreams().length,
registeredCommands: this._commandRegistry.getCommands().length,
workspaceContext: await this._workspaceContextProvider.getContext(),
};


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

Comment on lines +199 to +200
const files = await this._scanDirectory(this._workspacePath);
return files;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const files = await this._scanDirectory(this._workspacePath);
return files;
return await this._scanDirectory(this._workspacePath);


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

@Patrick-Ehimen Patrick-Ehimen merged commit 7d1475d into main Oct 25, 2025
1 check passed
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