feat(sdk): reduce context prompt sizes with truncation and cache-friendly ordering (#1615)
* chore: add v1.31.0 npm known-issue notice to issue template config Adds a top-priority contact link to the issue template chooser so users are redirected to the Discussions announcement before opening a duplicate issue about v1.31.0 not being on npm. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(sdk): reduce context prompt sizes with truncation and cache-friendly ordering (#1614) - Reorder prompt assembly in PromptFactory to place stable content (role, workflow, phase instructions) before variable content (.planning/ files), enabling Anthropic prompt caching at 0.1x input cost on cache hits - Add markdown-aware truncation for oversized context files (headings + first paragraphs preserved, rest omitted with line counts) - Add ROADMAP.md milestone extraction to inject only the current milestone instead of the full roadmap - Export truncation utilities from SDK public API - 60 new + updated tests covering truncation, milestone extraction, cache-friendly ordering, and ContextEngine integration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,11 @@
|
||||
* only needs STATE.md + config.json (minimal). Research needs STATE.md +
|
||||
* ROADMAP.md + CONTEXT.md. Plan needs all files. Verify needs STATE.md +
|
||||
* ROADMAP.md + REQUIREMENTS.md + PLAN/SUMMARY files.
|
||||
*
|
||||
* Context reduction (issue #1614):
|
||||
* - Large files are truncated to keep prompts cache-friendly
|
||||
* - ROADMAP.md is narrowed to the current milestone when possible
|
||||
* - Truncation preserves headings + first paragraph per section
|
||||
*/
|
||||
|
||||
import { readFile, access } from 'node:fs/promises';
|
||||
@@ -14,6 +19,12 @@ import { constants } from 'node:fs';
|
||||
import type { ContextFiles } from './types.js';
|
||||
import { PhaseType } from './types.js';
|
||||
import type { GSDLogger } from './logger.js';
|
||||
import {
|
||||
truncateMarkdown,
|
||||
extractCurrentMilestone,
|
||||
DEFAULT_TRUNCATION_OPTIONS,
|
||||
type TruncationOptions,
|
||||
} from './context-truncation.js';
|
||||
|
||||
// ─── File manifest per phase ─────────────────────────────────────────────────
|
||||
|
||||
@@ -64,16 +75,21 @@ const PHASE_FILE_MANIFEST: Record<PhaseType, FileSpec[]> = {
|
||||
export class ContextEngine {
|
||||
private readonly planningDir: string;
|
||||
private readonly logger?: GSDLogger;
|
||||
private readonly truncation: TruncationOptions;
|
||||
|
||||
constructor(projectDir: string, logger?: GSDLogger) {
|
||||
constructor(projectDir: string, logger?: GSDLogger, truncation?: Partial<TruncationOptions>) {
|
||||
this.planningDir = join(projectDir, '.planning');
|
||||
this.logger = logger;
|
||||
this.truncation = { ...DEFAULT_TRUNCATION_OPTIONS, ...truncation };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve context files appropriate for the given phase type.
|
||||
* Reads each file defined in the phase manifest, returning undefined
|
||||
* for missing optional files and warning for missing required files.
|
||||
*
|
||||
* Files exceeding the truncation threshold are reduced to headings +
|
||||
* first paragraphs. ROADMAP.md is narrowed to the current milestone.
|
||||
*/
|
||||
async resolveContextFiles(phaseType: PhaseType): Promise<ContextFiles> {
|
||||
const manifest = PHASE_FILE_MANIFEST[phaseType];
|
||||
@@ -94,6 +110,40 @@ export class ContextEngine {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply context reduction: milestone extraction then truncation
|
||||
if (result.roadmap && result.state) {
|
||||
const before = result.roadmap.length;
|
||||
result.roadmap = extractCurrentMilestone(result.roadmap, result.state);
|
||||
if (result.roadmap.length < before) {
|
||||
this.logger?.debug?.('ROADMAP.md narrowed to current milestone', {
|
||||
before,
|
||||
after: result.roadmap.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate oversized files (skip config.json — structured data, not markdown)
|
||||
const truncatable: Array<{ key: keyof ContextFiles; filename: string }> = [
|
||||
{ key: 'roadmap', filename: 'ROADMAP.md' },
|
||||
{ key: 'context', filename: 'CONTEXT.md' },
|
||||
{ key: 'research', filename: 'RESEARCH.md' },
|
||||
{ key: 'requirements', filename: 'REQUIREMENTS.md' },
|
||||
{ key: 'plan', filename: 'PLAN.md' },
|
||||
{ key: 'summary', filename: 'SUMMARY.md' },
|
||||
];
|
||||
|
||||
for (const { key, filename } of truncatable) {
|
||||
const raw = result[key];
|
||||
if (raw && raw.length > this.truncation.maxContentLength) {
|
||||
const before = raw.length;
|
||||
result[key] = truncateMarkdown(raw, filename, this.truncation);
|
||||
this.logger?.debug?.(`${filename} truncated`, {
|
||||
before,
|
||||
after: result[key]!.length,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user