Files
mcpctl/.taskmaster/tasks/task_076.md

85 lines
2.9 KiB
Markdown
Raw Normal View History

# Task ID: 76
**Title:** Implement summarize-tree Stage
**Status:** done
**Dependencies:** 71 ✓, 72 ✓, 73 ✓, 74 ✓
**Priority:** high
**Description:** Create the built-in summarize-tree stage that recursively summarizes sections, using structural summaries for programmatic content and LLM summaries for prose.
**Details:**
Create `src/mcplocal/src/proxymodel/stages/summarize-tree.ts`:
```typescript
import type { StageHandler, Section, StageContext } from '../types';
import { detectContentType } from '../content-detection';
const handler: StageHandler = async (content, ctx) => {
const maxTokens = (ctx.config.maxSummaryTokens as number) ?? 200;
const maxGroup = (ctx.config.maxGroupSize as number) ?? 5;
const maxDepth = (ctx.config.maxDepth as number) ?? 3;
// Parse sections from previous stage or create single section
const inputSections = parseSectionsFromContent(content);
const tree = await buildTree(inputSections, ctx, { maxTokens, maxGroup, maxDepth, depth: 0 });
const toc = tree.map(s =>
`[${s.id}] ${s.title} — ${s.metadata?.summary ?? ''}` +
(s.sections?.length ? `\n → ${s.sections.length} sub-sections` : '')
).join('\n');
return {
content: `${tree.length} sections:\n${toc}\n\nUse section parameter to read details.`,
sections: tree,
};
};
async function buildTree(
sections: Section[],
ctx: StageContext,
opts: { maxTokens: number; maxGroup: number; maxDepth: number; depth: number }
): Promise<Section[]> {
for (const section of sections) {
const contentType = detectContentType(section.content);
// Structural summary for programmatic content (no LLM needed)
if (contentType !== 'prose') {
section.metadata = { summary: generateStructuralSummary(section.content, contentType) };
} else {
// LLM summary for prose (cached)
const cacheKey = `summary:${ctx.cache.hash(section.content)}:${opts.maxTokens}`;
const summary = await ctx.cache.getOrCompute(cacheKey, () =>
ctx.llm.complete(
`Summarize in ${opts.maxTokens} tokens, preserve MUST/REQUIRED items:\n\n${section.content}`
)
);
section.metadata = { summary };
}
// Recurse if large and not at max depth
if (section.content.length > 5000 && opts.depth < opts.maxDepth) {
section.sections = await buildTree(
splitContent(section.content),
ctx,
{ ...opts, depth: opts.depth + 1 }
);
}
}
return sections;
}
function generateStructuralSummary(content: string, type: string): string {
// Generate summary from structure: key names, array lengths, types
// No LLM needed for JSON/YAML/XML/code
}
```
**Test Strategy:**
Unit tests: prose content gets LLM summary (mock LLM), JSON content gets structural summary without LLM call, recursive splitting triggers at 5000 chars, maxDepth is respected, cache is used for repeated content. Integration test with real LLM provider.