69 lines
2.3 KiB
Markdown
69 lines
2.3 KiB
Markdown
|
|
# Task ID: 79
|
||
|
|
|
||
|
|
**Title:** Implement Stage Registry and Dynamic Loader
|
||
|
|
|
||
|
|
**Status:** done
|
||
|
|
|
||
|
|
**Dependencies:** 71 ✓, 75 ✓, 76 ✓, 77 ✓
|
||
|
|
|
||
|
|
**Priority:** high
|
||
|
|
|
||
|
|
**Description:** Create the stage registry that resolves stage names to handlers, loading from ~/.mcpctl/stages/ for custom stages and falling back to built-ins.
|
||
|
|
|
||
|
|
**Details:**
|
||
|
|
|
||
|
|
Create `src/mcplocal/src/proxymodel/stage-registry.ts`:
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
import { readdir, stat } from 'fs/promises';
|
||
|
|
import { join } from 'path';
|
||
|
|
import type { StageHandler } from './types';
|
||
|
|
|
||
|
|
const STAGES_DIR = join(process.env.HOME ?? '', '.mcpctl', 'stages');
|
||
|
|
|
||
|
|
const builtInStages: Map<string, StageHandler> = new Map();
|
||
|
|
const customStages: Map<string, StageHandler> = new Map();
|
||
|
|
|
||
|
|
// Register built-ins at module load
|
||
|
|
import passthrough from './stages/passthrough';
|
||
|
|
import paginate from './stages/paginate';
|
||
|
|
import sectionSplit from './stages/section-split';
|
||
|
|
import summarizeTree from './stages/summarize-tree';
|
||
|
|
|
||
|
|
builtInStages.set('passthrough', passthrough);
|
||
|
|
builtInStages.set('paginate', paginate);
|
||
|
|
builtInStages.set('section-split', sectionSplit);
|
||
|
|
builtInStages.set('summarize-tree', summarizeTree);
|
||
|
|
|
||
|
|
export async function loadCustomStages(): Promise<void> {
|
||
|
|
customStages.clear();
|
||
|
|
try {
|
||
|
|
const files = await readdir(STAGES_DIR);
|
||
|
|
for (const file of files.filter(f => f.endsWith('.ts') || f.endsWith('.js'))) {
|
||
|
|
const name = file.replace(/\.(ts|js)$/, '');
|
||
|
|
const module = await import(join(STAGES_DIR, file));
|
||
|
|
customStages.set(name, module.default);
|
||
|
|
}
|
||
|
|
} catch { /* directory doesn't exist */ }
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getStage(name: string): StageHandler | null {
|
||
|
|
return customStages.get(name) ?? builtInStages.get(name) ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function listStages(): { name: string; source: 'built-in' | 'local' }[] {
|
||
|
|
const result: { name: string; source: 'built-in' | 'local' }[] = [];
|
||
|
|
for (const name of builtInStages.keys()) {
|
||
|
|
result.push({ name, source: customStages.has(name) ? 'local' : 'built-in' });
|
||
|
|
}
|
||
|
|
for (const name of customStages.keys()) {
|
||
|
|
if (!builtInStages.has(name)) result.push({ name, source: 'local' });
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Test Strategy:**
|
||
|
|
|
||
|
|
Unit tests: built-in stages are registered, getStage() returns correct handler, custom stages override built-ins, listStages() shows correct sources, missing stages return null. Integration test with actual stage files in temp directory.
|