singularity-forge/src/resources/extensions/gsd/summary-distiller.ts
Jeremy McSpadden 39b3daee6f feat: add token optimization suite for prompt caching, compression, and smart context selection
Introduces six new modules that work together to reduce token usage across
the dispatch pipeline while preserving semantic content quality:

- Provider-aware token counting with per-provider char/token ratios
- Prompt cache optimizer for maximizing Anthropic/OpenAI cache hit rates
- Structured data formatter (compact notation for decisions/requirements/tasks)
- Deterministic prompt compressor (light/moderate/aggressive levels)
- Semantic chunker with TF-IDF relevance scoring for context selection
- Summary distiller for condensed dependency summaries

Integration points:
- inlineDependencySummaries uses distillation before truncation (3+ deps)
- inlineDecisionsFromDb/inlineRequirementsFromDb use compact format at non-full levels
- buildExecuteTaskPrompt compresses carry-forward when it exceeds 40% of budget
- context-budget.reduceToFit combines compression with section-boundary truncation
- computeBudgets accepts optional provider for accurate char/token ratios

All existing 1475 unit tests + 30 integration tests pass with zero regressions.
157 new tests cover all optimization modules.
2026-03-17 22:02:27 -05:00

258 lines
7.5 KiB
TypeScript

/**
* Summary distiller — extracts essential structured data from SUMMARY.md files,
* dropping verbose prose to save context budget.
*/
export interface DistillationResult {
content: string;
summaryCount: number;
savingsPercent: number;
originalChars: number;
distilledChars: number;
}
interface ParsedFrontmatter {
id: string;
provides: string[];
requires: string[];
key_files: string[];
key_decisions: string[];
patterns_established: string[];
}
interface DistilledEntry {
id: string;
oneLiner: string;
provides: string[];
requires: string[];
key_files: string[];
key_decisions: string[];
patterns: string[];
}
// ─── Frontmatter parsing ─────────────────────────────────────────────────────
function parseFrontmatter(raw: string): ParsedFrontmatter {
const result: ParsedFrontmatter = {
id: "",
provides: [],
requires: [],
key_files: [],
key_decisions: [],
patterns_established: [],
};
// Extract frontmatter block between --- markers
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!fmMatch) return result;
const fmBlock = fmMatch[1];
const lines = fmBlock.split(/\r?\n/);
let currentKey: string | null = null;
for (const line of lines) {
// Scalar value: key: value
const scalarMatch = line.match(/^(\w[\w_]*):\s*(.+)$/);
if (scalarMatch) {
const [, key, value] = scalarMatch;
currentKey = key;
setScalar(result, key, value.trim());
continue;
}
// Array-start key with empty value: key:\n or key: []\n
const arrayStartMatch = line.match(/^(\w[\w_]*):\s*(\[\])?\s*$/);
if (arrayStartMatch) {
currentKey = arrayStartMatch[1];
continue;
}
// Array item: - value
const itemMatch = line.match(/^\s+-\s+(.+)$/);
if (itemMatch && currentKey) {
pushItem(result, currentKey, itemMatch[1].trim());
continue;
}
}
return result;
}
function setScalar(fm: ParsedFrontmatter, key: string, value: string): void {
if (key === "id") fm.id = value;
}
function pushItem(fm: ParsedFrontmatter, key: string, value: string): void {
switch (key) {
case "provides": fm.provides.push(value); break;
case "requires": fm.requires.push(value); break;
case "key_files": fm.key_files.push(value); break;
case "key_decisions": fm.key_decisions.push(value); break;
case "patterns_established": fm.patterns_established.push(value); break;
}
}
// ─── Body parsing ────────────────────────────────────────────────────────────
function extractTitleAndOneLiner(body: string): { id: string; oneLiner: string } {
const lines = body.split(/\r?\n/);
let titleId = "";
let oneLiner = "";
let foundTitle = false;
for (const line of lines) {
const titleMatch = line.match(/^#\s+(\S+):\s*(.*)$/);
if (titleMatch && !foundTitle) {
titleId = titleMatch[1];
// If the title line itself has text after "S01: ", use that as a fallback
if (titleMatch[2].trim()) {
oneLiner = titleMatch[2].trim();
}
foundTitle = true;
continue;
}
// First non-empty line after the title is the one-liner
if (foundTitle && !oneLiner && line.trim() && !line.startsWith("#")) {
oneLiner = line.trim();
break;
}
}
return { id: titleId, oneLiner };
}
function getBodyAfterFrontmatter(raw: string): string {
const fmMatch = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
if (fmMatch) {
return raw.slice(fmMatch[0].length);
}
return raw;
}
// ─── Public API ──────────────────────────────────────────────────────────────
/**
* Distill a single SUMMARY.md content string into a compact structured block.
*/
export function distillSingle(summary: string): string {
const fm = parseFrontmatter(summary);
const body = getBodyAfterFrontmatter(summary);
const { id: titleId, oneLiner } = extractTitleAndOneLiner(body);
const id = fm.id || titleId || "???";
return formatEntry({
id,
oneLiner,
provides: fm.provides,
requires: fm.requires,
key_files: fm.key_files,
key_decisions: fm.key_decisions,
patterns: fm.patterns_established,
});
}
function formatEntry(entry: DistilledEntry): string {
return formatEntryWithDropLevel(entry, 0);
}
/**
* Format an entry, progressively dropping fields based on dropLevel:
* 0 = full output
* 1 = drop patterns
* 2 = drop patterns + key_decisions
* 3 = drop patterns + key_decisions + key_files
*/
function formatEntryWithDropLevel(entry: DistilledEntry, dropLevel: number): string {
const lines: string[] = [];
lines.push(`## ${entry.id}: ${entry.oneLiner}`);
if (entry.provides.length > 0) {
lines.push(`provides: ${entry.provides.join(", ")}`);
}
if (entry.requires.length > 0) {
lines.push(`requires: ${entry.requires.join(", ")}`);
}
if (dropLevel < 3 && entry.key_files.length > 0) {
lines.push(`key_files: ${entry.key_files.join(", ")}`);
}
if (dropLevel < 2 && entry.key_decisions.length > 0) {
lines.push(`key_decisions: ${entry.key_decisions.join(", ")}`);
}
if (dropLevel < 1 && entry.patterns.length > 0) {
lines.push(`patterns: ${entry.patterns.join(", ")}`);
}
return lines.join("\n");
}
/**
* Distill multiple SUMMARY.md contents into a budget-constrained output.
*/
export function distillSummaries(summaries: string[], budgetChars: number): DistillationResult {
const originalChars = summaries.reduce((sum, s) => sum + s.length, 0);
if (summaries.length === 0) {
return {
content: "",
summaryCount: 0,
savingsPercent: 0,
originalChars: 0,
distilledChars: 0,
};
}
// Parse all entries up front
const entries: DistilledEntry[] = summaries.map((summary) => {
const fm = parseFrontmatter(summary);
const body = getBodyAfterFrontmatter(summary);
const { id: titleId, oneLiner } = extractTitleAndOneLiner(body);
return {
id: fm.id || titleId || "???",
oneLiner,
provides: fm.provides,
requires: fm.requires,
key_files: fm.key_files,
key_decisions: fm.key_decisions,
patterns: fm.patterns_established,
};
});
// Try progressively more aggressive dropping until it fits
for (let dropLevel = 0; dropLevel <= 3; dropLevel++) {
const blocks = entries.map((e) => formatEntryWithDropLevel(e, dropLevel));
const content = blocks.join("\n\n");
if (content.length <= budgetChars) {
const distilledChars = content.length;
return {
content,
summaryCount: summaries.length,
savingsPercent: originalChars > 0
? Math.round((1 - distilledChars / originalChars) * 100)
: 0,
originalChars,
distilledChars,
};
}
}
// Even at max drop level it doesn't fit — truncate
const blocks = entries.map((e) => formatEntryWithDropLevel(e, 3));
let content = blocks.join("\n\n");
if (content.length > budgetChars) {
content = content.slice(0, Math.max(0, budgetChars - 15)) + "\n[...truncated]";
}
const distilledChars = content.length;
return {
content,
summaryCount: summaries.length,
savingsPercent: originalChars > 0
? Math.round((1 - distilledChars / originalChars) * 100)
: 0,
originalChars,
distilledChars,
};
}