Skip to content

Commit 153b18e

Browse files
committed
feat: overhead-aware section budgets, raise total target to 500 lines
Section budgets now account for actual SKILL.md overhead (frontmatter, header, search block, footer). The base SKILL.md line count is passed through to budget functions so sections fill available space rather than using fixed estimates. Total target raised from 300 to 500 lines. Per-section limits bumped proportionally: best-practices 100/250 lines 6/15 items, api-changes 60/130 lines 8/18 items.
1 parent 5b5a901 commit 153b18e

11 files changed

Lines changed: 47 additions & 18 deletions

File tree

src/agent/clis/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -561,7 +561,7 @@ function optimizeSection(opts: OptimizeSectionOptions): Promise<SectionResult> {
561561
// ── Main orchestrator ────────────────────────────────────────────────
562562

563563
export async function optimizeDocs(opts: OptimizeDocsOptions): Promise<OptimizeResult> {
564-
const { packageName, skillDir, model = 'sonnet', version, hasGithub, hasReleases, hasChangelog, docFiles, docsType, hasShippedDocs, onProgress, timeout = 180000, debug, noCache, sections, customPrompt, features, pkgFiles } = opts
564+
const { packageName, skillDir, model = 'sonnet', version, hasGithub, hasReleases, hasChangelog, docFiles, docsType, hasShippedDocs, onProgress, timeout = 180000, debug, noCache, sections, customPrompt, features, pkgFiles, overheadLines } = opts
565565

566566
const selectedSections = sections ?? ['api-changes', 'best-practices'] as SkillSection[]
567567

@@ -580,6 +580,7 @@ export async function optimizeDocs(opts: OptimizeDocsOptions): Promise<OptimizeR
580580
customPrompt,
581581
features,
582582
pkgFiles,
583+
overheadLines,
583584
sections: selectedSections,
584585
})
585586

src/agent/clis/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ export interface OptimizeDocsOptions {
8282
features?: FeaturesConfig
8383
/** Key files from the package (e.g., dist/pkg.d.ts) */
8484
pkgFiles?: string[]
85+
/** Lines consumed by SKILL.md overhead (frontmatter + header + search + footer) */
86+
overheadLines?: number
8587
}
8688

8789
export interface OptimizeResult {

src/agent/prompts/optional/api-changes.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { resolveSkilldCommand } from '../../../core/shared.ts'
33
import { maxItems, maxLines, releaseBoost } from './budget.ts'
44
import { checkAbsolutePaths, checkLineCount, checkSourceCoverage, checkSourcePaths, checkSparseness } from './validate.ts'
55

6-
export function apiChangesSection({ packageName, version, hasReleases, hasChangelog, hasDocs, hasIssues, hasDiscussions, pkgFiles, features, enabledSectionCount, releaseCount }: SectionContext): PromptSection {
6+
export function apiChangesSection({ packageName, version, hasReleases, hasChangelog, hasDocs, hasIssues, hasDiscussions, pkgFiles, features, enabledSectionCount, releaseCount, overheadLines }: SectionContext): PromptSection {
77
const [, major, minor] = version?.match(/^(\d+)\.(\d+)/) ?? []
88
const boost = releaseBoost(releaseCount, minor ? Number(minor) : undefined)
99

@@ -69,7 +69,7 @@ export function apiChangesSection({ packageName, version, hasReleases, hasChange
6969
The "Older" column means ≤ v${Number(major) - 2}.x — these changes are NOT useful because anyone on v${major}.x already migrated past them.`
7070
: ''
7171

72-
const apiChangesMaxLines = maxLines(50, Math.round(80 * boost), enabledSectionCount)
72+
const apiChangesMaxLines = maxLines(60, Math.round(130 * boost), enabledSectionCount, overheadLines)
7373

7474
return {
7575
referenceWeights,
@@ -123,7 +123,7 @@ Each item: BREAKING/DEPRECATED/NEW label + API name + what changed + source link
123123
**Tiered format:** Top-scoring items get full detailed entries. Remaining relevant items go in a compact "**Also changed:**" line at the end — API name + brief label, separated by \` · \`. This surfaces more changes without bloating the section.`,
124124

125125
rules: [
126-
`- **API Changes:** ${maxItems(6, Math.round(12 * boost), enabledSectionCount)} detailed items + compact "Also changed" line for remaining, MAX ${apiChangesMaxLines} lines`,
126+
`- **API Changes:** ${maxItems(8, Math.round(18 * boost), enabledSectionCount)} detailed items + compact "Also changed" line for remaining, MAX ${apiChangesMaxLines} lines`,
127127
'- **Every detailed item MUST have a `[source](./.skilld/...#section)` link** with a section anchor (`#heading-slug`) or line reference (`:L<line>` or `:L<start>:<end>`). If you cannot cite a specific location in a release, changelog entry, or migration doc, do NOT include the item',
128128
'- **Recency:** Only include changes from the current major version and the previous→current migration. Exclude changes from older major versions entirely — users already migrated past them',
129129
'- Focus on APIs that CHANGED, not general conventions or gotchas',

src/agent/prompts/optional/best-practices.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { resolveSkilldCommand } from '../../../core/shared.ts'
33
import { maxItems, maxLines, releaseBoost } from './budget.ts'
44
import { checkAbsolutePaths, checkLineCount, checkSourceCoverage, checkSourcePaths, checkSparseness } from './validate.ts'
55

6-
export function bestPracticesSection({ packageName, hasIssues, hasDiscussions, hasReleases, hasChangelog, hasDocs, pkgFiles, features, enabledSectionCount, releaseCount, version }: SectionContext): PromptSection {
6+
export function bestPracticesSection({ packageName, hasIssues, hasDiscussions, hasReleases, hasChangelog, hasDocs, pkgFiles, features, enabledSectionCount, releaseCount, version, overheadLines }: SectionContext): PromptSection {
77
const [,, minor] = version?.match(/^(\d+)\.(\d+)/) ?? []
88
// Dampened boost — best practices are less directly tied to releases than API changes
99
const rawBoost = releaseBoost(releaseCount, minor ? Number(minor) : undefined)
@@ -35,7 +35,7 @@ export function bestPracticesSection({ packageName, hasIssues, hasDiscussions, h
3535
referenceWeights.push({ name: 'Changelog', path: `./.skilld/${hasChangelog}`, score: 3, useFor: 'Only for new patterns introduced in recent versions' })
3636
}
3737

38-
const bpMaxLines = maxLines(80, Math.round(150 * boost), enabledSectionCount)
38+
const bpMaxLines = maxLines(100, Math.round(250 * boost), enabledSectionCount, overheadLines)
3939

4040
return {
4141
referenceWeights,
@@ -86,7 +86,7 @@ const client = createX({ retryDelay: attempt => Math.min(1000 * 2 ** attempt, 30
8686
Each item: markdown list item (-) + ${packageName}-specific pattern + why it's preferred + \`[source](./.skilld/...#section)\` link. **Prefer concise descriptions over inline code** — the source link points the agent to full examples in the docs. Only add a code block when the pattern genuinely cannot be understood from the description alone (e.g., non-obvious syntax, multi-step wiring). Most items should be description + source link only. All source links MUST use \`./.skilld/\` prefix and include a **section anchor** (\`#heading-slug\`) or **line reference** (\`:L<line>\` or \`:L<start>:<end>\`) to pinpoint the exact location. Do NOT use emoji — use plain text markers only.`,
8787

8888
rules: [
89-
`- **${maxItems(4, Math.round(10 * boost), enabledSectionCount)} best practice items**`,
89+
`- **${maxItems(6, Math.round(15 * boost), enabledSectionCount)} best practice items**`,
9090
`- **MAX ${bpMaxLines} lines** for best practices section`,
9191
'- **Every item MUST have a `[source](./.skilld/...#section)` link** with a section anchor (`#heading-slug`) or line reference (`:L<line>` or `:L<start>:<end>`). If you cannot cite a specific location in a reference file, do NOT include the item — unsourced items risk hallucination and will be rejected',
9292
'- **Minimize inline code.** Most items should be description + source link only. The source file contains full examples the agent can read. Only add a code block when the pattern is unintuitable from the description (non-obvious syntax, surprising argument order, multi-step wiring). Aim for at most 1 in 4 items having a code block',

src/agent/prompts/optional/budget.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
/**
22
* Dynamic budget allocation for skill sections.
33
*
4-
* Total SKILL.md body should stay under ~300 lines (≈5,000 words per Agent Skills guide).
5-
* When more sections are enabled, each gets proportionally less space.
6-
* When a package has many releases, API changes budget scales up to capture more churn.
4+
* Total SKILL.md target is ~500 lines. Overhead (frontmatter, header, search, footer)
5+
* is subtracted to get the available body budget, which is divided among enabled sections.
6+
* When a package has many releases, budgets scale up.
77
*/
88

9-
/** Scale max lines based on enabled section count. Solo sections get full budget, 4 sections ~60%. */
10-
export function maxLines(min: number, max: number, sectionCount?: number): number {
9+
const TOTAL_TARGET = 500
10+
const DEFAULT_OVERHEAD = 30
11+
12+
/** Available body lines after overhead is subtracted */
13+
function remainingLines(overheadLines?: number): number {
14+
return TOTAL_TARGET - (overheadLines ?? DEFAULT_OVERHEAD)
15+
}
16+
17+
/** Scale max lines based on enabled section count and available remaining space. */
18+
export function maxLines(min: number, max: number, sectionCount?: number, overheadLines?: number): number {
19+
const remaining = remainingLines(overheadLines)
1120
const scale = budgetScale(sectionCount)
12-
return Math.max(min, Math.round(max * scale))
21+
return Math.max(min, Math.min(Math.round(max * scale), remaining))
1322
}
1423

1524
/** Scale item count based on enabled section count. */

src/agent/prompts/optional/custom.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type { CustomPrompt, PromptSection, SectionValidationWarning } from './ty
22
import { maxLines } from './budget.ts'
33
import { checkAbsolutePaths, checkLineCount, checkSourceCoverage, checkSourcePaths, checkSparseness } from './validate.ts'
44

5-
export function customSection({ heading, body }: CustomPrompt, enabledSectionCount?: number): PromptSection {
6-
const customMaxLines = maxLines(50, 80, enabledSectionCount)
5+
export function customSection({ heading, body }: CustomPrompt, enabledSectionCount?: number, overheadLines?: number): PromptSection {
6+
const customMaxLines = maxLines(50, 80, enabledSectionCount, overheadLines)
77

88
return {
99
validate(content: string): SectionValidationWarning[] {

src/agent/prompts/optional/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export interface SectionContext {
3939
enabledSectionCount?: number
4040
/** Number of release files — used for adaptive API changes budget */
4141
releaseCount?: number
42+
/** Lines consumed by frontmatter + header + search + footer */
43+
overheadLines?: number
4244
}
4345

4446
export interface CustomPrompt {

src/agent/prompts/prompt.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ export interface BuildSkillPromptOptions {
6868
enabledSectionCount?: number
6969
/** Key files from the package (e.g., dist/pkg.d.ts) — surfaced in prompt for tool hints */
7070
pkgFiles?: string[]
71+
/** Lines consumed by SKILL.md overhead (frontmatter + header + search + footer) */
72+
overheadLines?: number
7173
}
7274

7375
/**
@@ -178,7 +180,7 @@ function getSectionDef(section: SkillSection, ctx: SectionContext, customPrompt?
178180
switch (section) {
179181
case 'api-changes': return apiChangesSection(ctx)
180182
case 'best-practices': return bestPracticesSection(ctx)
181-
case 'custom': return customPrompt ? customSection(customPrompt, ctx.enabledSectionCount) : null
183+
case 'custom': return customPrompt ? customSection(customPrompt, ctx.enabledSectionCount, ctx.overheadLines) : null
182184
}
183185
}
184186

@@ -211,7 +213,7 @@ export function buildSectionPrompt(opts: BuildSkillPromptOptions & { section: Sk
211213
const m = f.match(/v\d+\.(\d+)\.(\d+)\.md$/)
212214
return m && (m[1] === '0' || m[2] === '0') // major (x.0.y) or minor (x.y.0)
213215
}).length
214-
const ctx: SectionContext = { packageName, version, hasIssues, hasDiscussions, hasReleases, hasChangelog, hasDocs, pkgFiles: opts.pkgFiles, features: opts.features, enabledSectionCount: opts.enabledSectionCount, releaseCount }
216+
const ctx: SectionContext = { packageName, version, hasIssues, hasDiscussions, hasReleases, hasChangelog, hasDocs, pkgFiles: opts.pkgFiles, features: opts.features, enabledSectionCount: opts.enabledSectionCount, releaseCount, overheadLines: opts.overheadLines }
215217
const sectionDef = getSectionDef(section, ctx, customPrompt)
216218
if (!sectionDef)
217219
return ''

src/commands/sync-parallel.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ interface BaseSkillData {
130130
/** Whether the existing SKILL.md had LLM-generated content */
131131
wasEnhanced?: boolean
132132
usedCache: boolean
133+
/** Lines consumed by SKILL.md overhead */
134+
overheadLines?: number
133135
}
134136

135137
export async function syncPackagesParallel(config: ParallelSyncConfig): Promise<void> {
@@ -350,6 +352,7 @@ export async function syncPackagesParallel(config: ParallelSyncConfig): Promise<
350352
sections: llmConfig.sections,
351353
customPrompt: llmConfig.customPrompt,
352354
features: data.features,
355+
overheadLines: data.overheadLines,
353356
})
354357
}
355358
}
@@ -570,6 +573,7 @@ async function syncBaseSkill(
570573
features,
571574
})
572575
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
576+
const overheadLines = skillMd.split('\n').length
573577

574578
// Link shared dir to per-agent dirs
575579
const shared = !config.global && getSharedSkillsDir(cwd)
@@ -601,6 +605,7 @@ async function syncBaseSkill(
601605
oldVersion: preLock?.version,
602606
oldSyncedAt: preLock?.syncedAt,
603607
wasEnhanced: preEnhanced,
608+
overheadLines,
604609
}
605610
}
606611

@@ -639,6 +644,7 @@ async function enhanceWithLLM(
639644
customPrompt,
640645
features: data.features,
641646
pkgFiles: data.pkgFiles,
647+
overheadLines: data.overheadLines,
642648
onProgress: (progress) => {
643649
const isReasoning = progress.type === 'reasoning'
644650
const status = isReasoning ? 'exploring' : 'generating'

src/commands/sync-shared.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1376,10 +1376,11 @@ export interface EnhanceOptions {
13761376
packages?: Array<{ name: string }>
13771377
features?: FeaturesConfig
13781378
eject?: boolean
1379+
overheadLines?: number
13791380
}
13801381

13811382
export async function enhanceSkillWithLLM(opts: EnhanceOptions): Promise<void> {
1382-
const { packageName, version, skillDir, dirName, model, resolved, relatedSkills, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs: shippedDocs, pkgFiles, force, debug, sections, customPrompt, packages, features, eject } = opts
1383+
const { packageName, version, skillDir, dirName, model, resolved, relatedSkills, hasIssues, hasDiscussions, hasReleases, hasChangelog, docsType, hasShippedDocs: shippedDocs, pkgFiles, force, debug, sections, customPrompt, packages, features, eject, overheadLines } = opts
13831384

13841385
const effectiveFeatures = features
13851386

@@ -1403,6 +1404,7 @@ export async function enhanceSkillWithLLM(opts: EnhanceOptions): Promise<void> {
14031404
customPrompt,
14041405
features: effectiveFeatures,
14051406
pkgFiles,
1407+
overheadLines,
14061408
onProgress: createToolProgress(llmLog),
14071409
})
14081410

@@ -1467,6 +1469,7 @@ export interface WritePromptFilesOptions {
14671469
sections: SkillSection[]
14681470
customPrompt?: CustomPrompt
14691471
features?: FeaturesConfig
1472+
overheadLines?: number
14701473
}
14711474

14721475
/**
@@ -1490,6 +1493,7 @@ export function writePromptFiles(opts: WritePromptFilesOptions): SkillSection[]
14901493
pkgFiles: opts.pkgFiles,
14911494
customPrompt,
14921495
features,
1496+
overheadLines: opts.overheadLines,
14931497
sections,
14941498
})
14951499

0 commit comments

Comments
 (0)