Skip to content

Commit 77d8422

Browse files
authored
refactor: overhead-aware budgets, drop deps, limit tags (#44)
1 parent 13359a3 commit 77d8422

13 files changed

Lines changed: 58 additions & 41 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: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,26 @@
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)
20+
const sections = Math.max(1, sectionCount ?? 1)
21+
const perSection = Math.floor(remaining / sections)
1122
const scale = budgetScale(sectionCount)
12-
return Math.max(min, Math.round(max * scale))
23+
return Math.max(min, Math.min(Math.round(max * scale), perSection))
1324
}
1425

1526
/** 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
/**
@@ -171,7 +173,7 @@ function getSectionDef(section: SkillSection, ctx: SectionContext, customPrompt?
171173
switch (section) {
172174
case 'api-changes': return apiChangesSection(ctx)
173175
case 'best-practices': return bestPracticesSection(ctx)
174-
case 'custom': return customPrompt ? customSection(customPrompt, ctx.enabledSectionCount) : null
176+
case 'custom': return customPrompt ? customSection(customPrompt, ctx.enabledSectionCount, ctx.overheadLines) : null
175177
}
176178
}
177179

@@ -204,7 +206,7 @@ export function buildSectionPrompt(opts: BuildSkillPromptOptions & { section: Sk
204206
const m = f.match(/v\d+\.(\d+)\.(\d+)\.md$/)
205207
return m && (m[1] === '0' || m[2] === '0') // major (x.0.y) or minor (x.y.0)
206208
}).length
207-
const ctx: SectionContext = { packageName, version, hasIssues, hasDiscussions, hasReleases, hasChangelog, hasDocs, pkgFiles: opts.pkgFiles, features: opts.features, enabledSectionCount: opts.enabledSectionCount, releaseCount }
209+
const ctx: SectionContext = { packageName, version, hasIssues, hasDiscussions, hasReleases, hasChangelog, hasDocs, pkgFiles: opts.pkgFiles, features: opts.features, enabledSectionCount: opts.enabledSectionCount, releaseCount, overheadLines: opts.overheadLines }
208210
const sectionDef = getSectionDef(section, ctx, customPrompt)
209211
if (!sectionDef)
210212
return ''

src/agent/prompts/skill.ts

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ export interface SkillOptions {
1313
name: string
1414
version?: string
1515
releasedAt?: string
16-
/** Production dependencies with version specifiers */
17-
dependencies?: Record<string, string>
1816
/** npm dist-tags with version and release date */
1917
distTags?: Record<string, { version: string, releasedAt?: string }>
2018
globs?: string[]
@@ -70,7 +68,7 @@ function formatShortDate(isoDate: string): string {
7068
return `${months[date.getUTCMonth()]} ${date.getUTCFullYear()}`
7169
}
7270

73-
function generatePackageHeader({ name, description, version, releasedAt, dependencies, distTags, repoUrl, hasIssues, hasDiscussions, hasReleases, docsType, pkgFiles, packages, eject }: SkillOptions): string {
71+
function generatePackageHeader({ name, description, version, releasedAt, distTags, repoUrl, hasIssues, hasDiscussions, hasReleases, docsType, pkgFiles, packages, eject }: SkillOptions): string {
7472
let title = `# ${name}`
7573
if (repoUrl) {
7674
const url = repoUrl.startsWith('http') ? repoUrl : `https://github.com/${repoUrl}`
@@ -89,15 +87,10 @@ function generatePackageHeader({ name, description, version, releasedAt, depende
8987
lines.push('', `**Version:** ${versionStr}`)
9088
}
9189

92-
if (dependencies && Object.keys(dependencies).length > 0) {
93-
const deps = Object.entries(dependencies)
94-
.map(([n, v]) => `${n}@${v}`)
95-
.join(', ')
96-
lines.push(`**Deps:** ${deps}`)
97-
}
98-
9990
if (distTags && Object.keys(distTags).length > 0) {
10091
const tags = Object.entries(distTags)
92+
.sort(([, a], [, b]) => (b.releasedAt ?? '').localeCompare(a.releasedAt ?? ''))
93+
.slice(0, 3)
10194
.map(([tag, info]) => {
10295
const relDate = info.releasedAt ? ` (${formatShortDate(info.releasedAt)})` : ''
10396
return `${tag}: ${info.version}${relDate}`

src/commands/install.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -576,13 +576,11 @@ async function enhanceRegenerated(
576576
const cwd = process.cwd()
577577
const pkgPath = resolvePkgDir(pkgName, cwd, version)
578578
let description: string | undefined
579-
let dependencies: Record<string, string> | undefined
580579
if (pkgPath) {
581580
const pkgJsonPath = join(pkgPath, 'package.json')
582581
if (existsSync(pkgJsonPath)) {
583582
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'))
584583
description = pkg.description
585-
dependencies = pkg.dependencies
586584
}
587585
}
588586

@@ -600,7 +598,6 @@ async function enhanceRegenerated(
600598
name: pkgName,
601599
version,
602600
description,
603-
dependencies,
604601
body: optimized,
605602
relatedSkills: [],
606603
hasIssues,
@@ -659,13 +656,11 @@ function regenerateBaseSkillMd(
659656
// Read description + deps from local package.json
660657
const pkgPath = resolvePkgDir(pkgName, cwd, version)
661658
let description: string | undefined
662-
let dependencies: Record<string, string> | undefined
663659
if (pkgPath) {
664660
const pkgJsonPath = join(pkgPath, 'package.json')
665661
if (existsSync(pkgJsonPath)) {
666662
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf-8'))
667663
description = pkg.description
668-
dependencies = pkg.dependencies
669664
}
670665
}
671666

@@ -696,7 +691,6 @@ function regenerateBaseSkillMd(
696691
name: pkgName,
697692
version,
698693
description,
699-
dependencies,
700694
relatedSkills,
701695
hasIssues,
702696
hasDiscussions,

0 commit comments

Comments
 (0)