This guide explains the core architectural flows in the Prompt Registry extension. Use this as a reference when contributing to or extending the codebase.
- Bundle Installation Pipeline
- Adapter Pattern for Registry Sources
- Authentication Flow
- VS Code Extension Integration
- Quick Reference
- Development Tips
The bundle installation pipeline handles the complete lifecycle from user initiating an install to files being available in both the registry and Copilot.
flowchart TD
START([User Action: UI/Command])
CMD[Command Handler<br/>BundleCommands]
RM[RegistryManager.installBundle]
START --> CMD
CMD --> RM
RM --> STEP1[1. Create Adapter for Source<br/>RepositoryAdapterFactory]
STEP1 --> STEP2[2. Download Bundle ZIP or Buffer<br/>adapter methods]
STEP2 --> STEP3[3. Extract to Temp Directory<br/>unzip]
STEP3 --> STEP4[4. Validate deployment-manifest.yml<br/>check structure]
STEP4 --> STEP5[5. Copy to Install Location<br/>extension storage]
STEP5 --> STEP6[6. Sync to Copilot Directory<br/>UserScopeService]
STEP6 --> STEP7[7. Create Symlinks/Copies<br/>platform-specific]
STEP7 --> STEP8[8. Update Installation Record<br/>RegistryStorage]
STEP8 --> STEP9[9. Fire Event<br/>onBundleInstalled]
style START fill:#4CAF50
style STEP9 fill:#2196F3
File: src/commands/BundleCommands.ts
Purpose: Handles user commands for bundle operations
// Example: Install command
async installBundle(bundleId: string): Promise<void> {
// Delegates to RegistryManager
await this.registryManager.installBundle(bundleId, options);
}File: src/services/RegistryManager.ts
Purpose: Orchestrates all registry operations
async installBundle(bundleId: string, options: InstallOptions): Promise<InstalledBundle> {
// Find bundle and source
// Create appropriate adapter
// Delegate to installer
// Update storage
// Fire events
}File: src/services/BundleInstaller.ts
Purpose: Handles two installation methods
// Method 1: URL-based installation
async install(bundle: Bundle, downloadUrl: string, options: InstallOptions)
// Method 2: Buffer-based installation
async installFromBuffer(bundle: Bundle, bundleBuffer: Buffer, options: InstallOptions)URL-based: Downloads pre-packaged ZIP from remote URL
Buffer-based: Accepts ZIP created dynamically in memory
File: src/services/UserScopeService.ts
Purpose: Syncs bundles to GitHub Copilot's native directories
// Gets OS-specific Copilot directory
getCopilotPromptsDirectory(): string
// Syncs bundle files
syncBundle(bundleId: string, installDir: string): Promise<void>URL-Based Installation - install(bundle, downloadUrl, options):
- Used by:
GitHubAdapter,GitLabAdapter,HttpAdapter - Downloads pre-packaged ZIP file from URL
- Flow: Download → Extract → Validate → Install
Buffer-Based Installation - installFromBuffer(bundle, buffer, options):
- Used by:
AwesomeCopilotAdapter,LocalAdapter - Receives ZIP created dynamically in memory
- Flow: Write buffer → Extract → Validate → Install
interface InstallOptions {
scope: 'user' | 'workspace' | 'repository'; // Installation scope
force?: boolean; // Overwrite existing
commitMode?: 'commit' | 'local-only'; // For repository scope
}For repository-scoped installations:
- Files are placed in
.github/directories (prompts, agents, instructions, skills) - MCP servers are merged into
.vscode/mcp.json - The lockfile (
prompt-registry.lock.json) tracks installed bundles - Local-only mode excludes files via
.git/info/exclude
See Installation Flow for detailed repository scope documentation.
The pipeline includes error handling for:
- Network failures: Connection errors, timeouts
- Authentication failures: 401, 403, token issues
- Validation failures: Missing manifest, corrupt ZIP
- Sync failures: Permission issues, disk space
All errors are logged to the Output panel under "Prompt Registry".
The adapter pattern allows the registry to support multiple source types (GitHub, GitLab, Local, HTTP, Awesome Copilot) through a unified interface.
flowchart TD
BI[BundleInstaller]
FACTORY[RepositoryAdapterFactory.create<br/>Factory Pattern]
BI --> FACTORY
FACTORY --> GHA[GitHubAdapter]
FACTORY --> GLA[GitLabAdapter]
FACTORY --> HA[HttpAdapter]
FACTORY --> LA[LocalAdapter]
FACTORY --> ACA[AwesomeCopilotAdapter]
FACTORY --> LACA[LocalAwesomeCopilotAdapter]
FACTORY --> APMA[ApmAdapter]
FACTORY --> LAPMA[LocalApmAdapter]
INTERFACE["<b>IRepositoryAdapter Interface</b><br/>- fetchBundles<br/>- downloadBundle<br/>- validate<br/>- requiresAuthentication"]
GHA -.implements.-> INTERFACE
LA -.implements.-> INTERFACE
ACA -.implements.-> INTERFACE
style FACTORY fill:#FFC107
style INTERFACE fill:#E3F2FD
style GHA fill:#4CAF50
style LA fill:#4CAF50
style ACA fill:#4CAF50
File: src/adapters/RepositoryAdapter.ts
Class: RepositoryAdapterFactory
// Creates appropriate adapter for source type
static create(source: RegistrySource): IRepositoryAdapter {
const AdapterClass = this.adapters.get(source.type);
return new AdapterClass(source);
}
// Register adapters
RepositoryAdapterFactory.register('github', GitHubAdapter);
RepositoryAdapterFactory.register('awesome-copilot', AwesomeCopilotAdapter);
// ... etcFile: src/services/RegistryManager.ts
Constructor: Registers all default adapters
RepositoryAdapterFactory.register('github', GitHubAdapter);
RepositoryAdapterFactory.register('gitlab', GitLabAdapter);
RepositoryAdapterFactory.register('http', HttpAdapter);
RepositoryAdapterFactory.register('local', LocalAdapter);
RepositoryAdapterFactory.register('awesome-copilot', AwesomeCopilotAdapter);
RepositoryAdapterFactory.register('local-awesome-copilot', LocalAwesomeCopilotAdapter);
RepositoryAdapterFactory.register('local-apm', LocalApmAdapter);
RepositoryAdapterFactory.register('apm', ApmAdapter);File: src/adapters/GitHubAdapter.ts
Purpose: Fetches bundles from GitHub releases
async fetchBundles(): Promise<Bundle[]> {
// Fetches releases from GitHub API
// Looks for deployment-manifest.yml in assets
}Features:
- GitHub API v3 integration
- Release asset scanning
- Authentication fallback chain (VSCode → gh CLI → explicit token)
- Bearer token authentication
File: src/adapters/AwesomeCopilotAdapter.ts
Purpose: Fetches awesome-copilot collections from GitHub
async fetchBundles(): Promise<Bundle[]> {
// Fetches collection YAML files from GitHub
// Parses YAML to build bundle metadata
}
async downloadBundle(bundle: Bundle): Promise<Buffer> {
// Dynamically creates ZIP archive
// Fetches individual files
// Returns ZIP buffer
}Features:
- GitHub raw content access
- YAML collection parsing
- Dynamic ZIP creation with archiver
- No release requirement
- Authentication support (added Nov 2025)
File: src/adapters/LocalAdapter.ts
Purpose: Handles local filesystem bundles
async fetchBundles(): Promise<Bundle[]> {
// Scans directory for deployment-manifest.yml files
}Features:
- Filesystem access
- Local development support
- file:// protocol support
- Fast iteration
Files: src/adapters/GitLabAdapter.ts, src/adapters/HttpAdapter.ts
Purpose: Support GitLab repositories and generic HTTP sources
Similar patterns to GitHubAdapter but for different platforms.
Files: src/adapters/LocalAwesomeCopilotAdapter.ts, src/adapters/LocalApmAdapter.ts
Purpose: Local filesystem variants of AwesomeCopilot and APM adapters
File: src/adapters/ApmAdapter.ts
Purpose: Fetches APM (AI Prompt Manager) packages from GitHub
File: src/adapters/RepositoryAdapter.ts
Interface: IRepositoryAdapter
export interface IRepositoryAdapter {
readonly type: string;
readonly source: RegistrySource;
fetchBundles(): Promise<Bundle[]>;
downloadBundle(bundle: Bundle): Promise<Buffer>;
fetchMetadata(): Promise<SourceMetadata>;
validate(): Promise<ValidationResult>;
requiresAuthentication(): boolean;
getManifestUrl(bundleId: string, version?: string): string;
getDownloadUrl(bundleId: string, version?: string): string;
forceAuthentication?(): Promise<void>;
}All adapters implement downloadBundle() directly. The base RepositoryAdapter class provides common functionality like requiresAuthentication() and getAuthToken().
To add support for a new registry source:
- Create Adapter Class:
// src/adapters/MyAdapter.ts
export class MyAdapter extends RepositoryAdapter {
readonly type = 'mytype';
async fetchBundles(): Promise<Bundle[]> {
// Your implementation
}
// Implement other IRepositoryAdapter methods
}- Register in Factory:
// src/services/RegistryManager.ts constructor
RepositoryAdapterFactory.register('mytype', MyAdapter);- Add Source Type:
// src/types/registry.ts
type SourceType = 'github' | 'gitlab' | 'http' | 'local' |
'awesome-copilot' | 'local-awesome-copilot' |
'apm' | 'local-apm' | 'mytype';- Write Tests:
// test/adapters/MyAdapter.test.ts
describe('MyAdapter', () => {
it('should fetch bundles', async () => {
// Test implementation
});
});Both GitHubAdapter and AwesomeCopilotAdapter support private GitHub repositories through a three-tier authentication fallback chain.
flowchart TD
START([Request Authentication])
START --> VSCODE{VSCode<br/>GitHub Auth?}
VSCODE -->|Available| USE_VS["Use Bearer Token<br/>✓ Authenticated"]
VSCODE -->|Not Available| GHCLI{gh CLI<br/>Installed?}
GHCLI -->|Available| USE_GH["Use CLI Token<br/>✓ Authenticated"]
GHCLI -->|Not Available| EXPLICIT{Explicit Token<br/>in Config?}
EXPLICIT -->|Available| USE_EX["Use Config Token<br/>✓ Authenticated"]
EXPLICIT -->|Not Available| NONE["No Authentication<br/>⚠️ Public Only"]
USE_VS --> CACHE[Cache Token]
USE_GH --> CACHE
USE_EX --> CACHE
style USE_VS fill:#4CAF50
style USE_GH fill:#4CAF50
style USE_EX fill:#4CAF50
style NONE fill:#FF9800
style CACHE fill:#2196F3
Files: src/adapters/GitHubAdapter.ts, src/adapters/AwesomeCopilotAdapter.ts
Method: getAuthenticationToken()
private async getAuthenticationToken(): Promise<string | undefined> {
// Return cached token if already retrieved
if (this.authToken !== undefined) {
return this.authToken;
}
// Try VSCode GitHub authentication
try {
const session = await vscode.authentication.getSession(
'github',
['repo'],
{ silent: true }
);
if (session) {
this.authToken = session.accessToken;
this.authMethod = 'vscode';
return this.authToken;
}
} catch (error) {
// Log and continue to next method
}
// Try gh CLI
try {
const { stdout } = await execAsync('gh auth token');
const token = stdout.trim();
if (token && token.length > 0) {
this.authToken = token;
this.authMethod = 'gh-cli';
return this.authToken;
}
} catch (error) {
// Log and continue to next method
}
// Try explicit token from source config
const explicitToken = this.getAuthToken();
if (explicitToken) {
this.authToken = explicitToken;
this.authMethod = 'explicit';
return this.authToken;
}
// No authentication available
this.authMethod = 'none';
return undefined;
}// Correct format for GitHub API
headers['Authorization'] = `Bearer ${token}`;
// NOT the deprecated format:
// headers['Authorization'] = `token ${token}`;Authentication attempts and results are logged:
[GitHubAdapter] Attempting authentication...
[GitHubAdapter] ✓ Using VSCode GitHub authentication
[GitHubAdapter] Token preview: gho_abc12...
[GitHubAdapter] Request to https://api.github.com/... with auth (method: vscode)
Errors are also logged:
[GitHubAdapter] ✗ No authentication available
[GitHubAdapter] HTTP 404: Not Found - Repository not found or not accessible
Tokens are cached after first successful retrieval to avoid repeated authentication attempts:
private authToken: string | undefined;
private authMethod: 'vscode' | 'gh-cli' | 'explicit' | 'none' = 'none';The extension integrates with VS Code through commands, WebView UI, and event handlers.
flowchart TD
HOST([VS Code Extension Host])
ACTIVATE[extension.ts:activate]
HOST --> ACTIVATE
INIT["Extension Initialization"]
CMD[Register Commands]
WV[Register WebView Provider]
SVC[Initialize Services]
EVT[Setup Event Listeners]
ACTIVATE --> INIT
INIT --> CMD
INIT --> WV
INIT --> SVC
INIT --> EVT
WV --> MVP[MarketplaceViewProvider]
COMM["WebView ↔ Extension Communication"]
UI_MSG[UI sends message]
RECV[onDidReceiveMessage]
EXEC[Execute command]
POST[postMessage to UI]
MVP --> COMM
COMM --> UI_MSG
UI_MSG --> RECV
RECV --> EXEC
EXEC --> POST
POST -.update.-> UI_MSG
style ACTIVATE fill:#4CAF50
style INIT fill:#2196F3
style COMM fill:#FFC107
File: src/extension.ts
Class: PromptRegistryExtension
public async activate(): Promise<void> {
// Initialize Registry Manager
await this.registryManager.initialize();
// Register commands
this.registerCommands();
// Initialize UI components
await this.initializeUI();
// Register TreeView and Marketplace
await this.registerTreeView();
await this.registerMarketplaceView();
// Initialize Copilot Integration
await this.initializeCopilot();
}File: src/extension.ts
Method: registerMarketplaceView()
const marketplaceProvider = new MarketplaceViewProvider(
this.context,
this.registryManager
);
this.context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
'promptregistry.marketplace',
marketplaceProvider
)
);File: src/ui/MarketplaceViewProvider.ts
Method: handleMessage()
private async handleMessage(message: WebviewMessage): Promise<void> {
switch (message.type) {
case 'refresh':
await this.loadBundles();
break;
case 'install':
await this.handleInstall(message.bundleId);
break;
case 'update':
await this.handleUpdate(message.bundleId);
break;
case 'uninstall':
await this.handleUninstall(message.bundleId);
break;
case 'openDetails':
await this.openBundleDetails(message.bundleId);
break;
case 'installVersion':
await this.handleInstallVersion(message.bundleId, message.version);
break;
case 'toggleAutoUpdate':
await this.handleToggleAutoUpdate(message.bundleId, message.enabled);
break;
// ... additional handlers
}
}File: src/ui/MarketplaceViewProvider.ts
Method: handleInstall()
private async handleInstall(bundleId: string): Promise<void> {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: `Installing bundle...`
}, async () => {
await this.registryManager.installBundle(bundleId, {
scope: 'user',
version: 'latest'
});
});
// Refresh marketplace to show installed status
await this.loadBundles();
}File: src/ui/MarketplaceViewProvider.ts
Method: loadBundles()
private async loadBundles(): Promise<void> {
const bundles = await this.registryManager.searchBundles({});
const installedBundles = await this.registryManager.listInstalledBundles();
// Enhance bundles with installed status
const enhancedBundles = bundles.map(bundle => ({
...bundle,
installed: installedBundles.some(ib => ib.bundleId === bundle.id)
}));
this._view?.webview.postMessage({
type: 'bundlesLoaded',
bundles: enhancedBundles
});
}From UI to Extension:
interface WebviewMessage {
type: 'refresh' | 'install' | 'update' | 'uninstall' | 'openDetails' |
'installVersion' | 'getVersions' | 'toggleAutoUpdate' |
'openSourceRepository' | 'openPromptFile';
bundleId?: string;
version?: string;
enabled?: boolean;
installPath?: string;
filePath?: string;
}From Extension to UI:
interface ExtensionMessage {
type: 'bundlesLoaded' | 'bundleDetails' | 'versionsLoaded' | 'error';
bundles?: Bundle[];
bundle?: Bundle;
versions?: string[];
error?: string;
}File: src/extension.ts
Method: registerCommands()
Commands are registered through command classes:
this.bundleCommands = new BundleCommands(this.context, this.registryManager);
this.sourceCommands = new SourceCommands(this.context, this.registryManager);
this.profileCommands = new ProfileCommands(this.context, this.registryManager);
// Commands are defined in package.json and implemented in command classesAvailable Commands:
promptRegistry.scaffoldProject- Scaffold new collectionpromptRegistry.validateCollections- Validate YAML filespromptRegistry.createCollection- Create new collectionpromptregistry.checkUpdates- Check for bundle updatespromptregistry.validateAccess- Test repository access
| Task | Entry Point | Key Files |
|---|---|---|
| Install Bundle | BundleCommands |
RegistryManager, BundleInstaller, UserScopeService |
| Fetch from GitHub | GitHubAdapter |
RepositoryAdapter, GitHubAdapter |
| Fetch Awesome Copilot | AwesomeCopilotAdapter |
RepositoryAdapter, AwesomeCopilotAdapter |
| UI Interaction | MarketplaceViewProvider |
MarketplaceViewProvider, extension.ts |
| Add Source Type | RepositoryAdapterFactory |
Create new adapter class, register |
Target Coverage by Component Type:
- Adapters: 80%+ (unit tests with mocking)
- Pure Utilities: 100% (no external dependencies)
- Services with VS Code dependencies: Integration test focused
- UI Components: Manual testing + integration tests
Run Current Coverage:
npm test -- --coverageTest Organization:
test/adapters/- Adapter unit teststest/services/- Service teststest/utils/- Utility teststest/fixtures/- Test data
Key Test Files:
GitHubAdapter.auth.test.ts- Authentication testsAwesomeCopilotAdapter.test.ts- Collection parsingcollectionValidator.test.ts- YAML validation
View detailed logs in the Output panel:
- Open Output:
View → Output - Select Prompt Registry from dropdown
- Watch authentication and installation logs
Relevant log messages:
[RegistryManager] Installing bundle: testing-automation
[GitHubAdapter] ✓ Using VSCode GitHub authentication
[BundleInstaller] Downloaded bundle to temp
[UserScopeService] Synced to Copilot directory
Run adapter tests:
npm run test:unitRun specific adapter tests:
npm run test:unit -- --grep "GitHubAdapter"
npm run test:unit -- --grep "AwesomeCopilotAdapter"Use test fixtures in test/fixtures/ for consistent test data.
Watch mode for rapid iteration:
npm run watchTest WebView messages from browser console:
vscode.postMessage({ type: 'test', data: 'hello' });- Define in
package.json:
{
"commands": [{
"command": "promptRegistry.myCommand",
"title": "My Command",
"category": "Prompt Registry"
}]
}- Implement in command class:
// src/commands/BundleCommands.ts
async myCommand(): Promise<void> {
// Implementation
}- Register in
extension.ts:
vscode.commands.registerCommand('promptRegistry.myCommand',
() => this.bundleCommands.myCommand())Monitor operations using the logger:
const startTime = Date.now();
// ... operation ...
logger.debug(`Operation completed in ${Date.now() - startTime}ms`);Key metrics to watch:
- Bundle download time (< 5s target)
- UI render time (< 100ms target)
- Adapter fetch time (< 2s target)
- Architecture - High-level architecture overview
- Getting Started - Quick start guide for users
- Testing - Comprehensive testing guide
- CONTRIBUTING.md - Contribution guidelines
Found an issue or have a suggestion?
- File an issue: GitHub Issues
- Submit a PR: GitHub Pull Requests
Happy Coding! 🚀
For detailed guidance on extending specific subsystems:
- Scaffolding: See architecture/scaffolding.md
- Validation: See architecture/validation.md
- Adapters: See architecture/adapters.md
| Extension Type | Key Files | Steps |
|---|---|---|
| New Adapter | src/adapters/, src/types/registry.ts |
Create adapter class, register in RegistryManager |
| New Scaffold | templates/scaffolds/, src/commands/ScaffoldCommand.ts |
Create template dir, add manifest.json, update enum |
| New Schema | schemas/, src/services/SchemaValidator.ts |
Create JSON schema, use SchemaValidator |
| New Command | src/commands/, package.json |
Define in package.json, implement handler, register |