Skip to content

Commit 132cfdf

Browse files
oritwoenharlan-zw
andauthored
refactor: centralize generated skill writes (#74)
Co-authored-by: Harlan Wilton <harlan@harlanzw.com>
1 parent d7dfb1b commit 132cfdf

11 files changed

Lines changed: 77 additions & 41 deletions

File tree

src/agent/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export { detectInstalledAgents, detectProjectAgents, detectTargetAgent, getAgent
2424
export { computeSkillDirName, installSkillForAgents, linkSkillToAgents, sanitizeName, unlinkSkillFromAgents } from './install.ts'
2525

2626
// Skill generation
27-
export { extractMarkedSections, generateSkillMd, getSectionValidator, portabilizePrompt, wrapSection } from './prompts/index.ts'
27+
export { extractMarkedSections, generateSkillMd, getSectionValidator, portabilizePrompt, wrapSection, writeGeneratedSkillMd, writeSkillMd } from './prompts/index.ts'
2828

2929
export type { SkillOptions } from './prompts/index.ts'
3030
// Registry

src/agent/prompts/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export type { CustomPrompt, SectionValidationWarning } from './optional/index.ts'
22
export { buildAllSectionPrompts, buildSectionPrompt, extractMarkedSections, getSectionValidator, portabilizePrompt, SECTION_MERGE_ORDER, SECTION_OUTPUT_FILES, wrapSection } from './prompt.ts'
33
export type { BuildSkillPromptOptions, SkillSection } from './prompt.ts'
4-
export { generateSkillMd } from './skill.ts'
4+
export { generateSkillMd, writeGeneratedSkillMd, writeSkillMd } from './skill.ts'
55
export type { SkillOptions } from './skill.ts'

src/agent/prompts/skill.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
*/
44

55
import type { FeaturesConfig } from '../../core/config.ts'
6+
import { writeFileSync } from 'node:fs'
7+
import { join } from 'pathe'
68
import { repairMarkdown, sanitizeMarkdown } from '../../core/sanitize.ts'
79
import { resolveSkilldCommand } from '../../core/shared.ts'
810
import { yamlEscape } from '../../core/yaml.ts'
@@ -42,6 +44,16 @@ export interface SkillOptions {
4244
eject?: boolean
4345
}
4446

47+
export function writeSkillMd(skillDir: string, content: string): void {
48+
writeFileSync(join(skillDir, 'SKILL.md'), content)
49+
}
50+
51+
export function writeGeneratedSkillMd(skillDir: string, opts: SkillOptions): string {
52+
const content = generateSkillMd(opts)
53+
writeSkillMd(skillDir, content)
54+
return content
55+
}
56+
4557
export function generateSkillMd(opts: SkillOptions): string {
4658
const header = generatePackageHeader(opts)
4759
const search = !opts.eject && opts.features?.search !== false ? generateSearchBlock(opts.name) : ''

src/commands/author.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import type { OptimizeModel } from '../agent/index.ts'
22
import type { FeaturesConfig } from '../core/config.ts'
33
import type { LlmConfig } from './sync-shared.ts'
4-
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
4+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs'
55
import * as p from '@clack/prompts'
66
import { defineCommand } from 'citty'
77
import { join, relative, resolve } from 'pathe'
88
import {
99
computeSkillDirName,
10-
generateSkillMd,
1110
getModelLabel,
11+
writeGeneratedSkillMd,
1212
} from '../agent/index.ts'
1313
import {
1414
ensureCacheDir,
@@ -421,7 +421,7 @@ async function authorSinglePackage(opts: {
421421
const hasReleases = existsSync(join(cacheDir, 'releases'))
422422

423423
// Generate base SKILL.md
424-
const baseSkillMd = generateSkillMd({
424+
writeGeneratedSkillMd(outDir, {
425425
name: packageName,
426426
version,
427427
description: opts.description,
@@ -438,7 +438,6 @@ async function authorSinglePackage(opts: {
438438
features,
439439
eject: true,
440440
})
441-
writeFileSync(join(outDir, 'SKILL.md'), baseSkillMd)
442441
p.log.success(`Created base skill: ${relative(packageDir, outDir)}`)
443442

444443
// LLM enhancement (config resolved by caller)

src/commands/install.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import * as p from '@clack/prompts'
1818
import { defineCommand } from 'citty'
1919
import { dirname, join } from 'pathe'
2020
import { agents, createToolProgress, getModelLabel, linkSkillToAgents, optimizeDocs } from '../agent/index.ts'
21-
import { generateSkillMd } from '../agent/prompts/skill.ts'
21+
import { writeGeneratedSkillMd, writeSkillMd } from '../agent/prompts/skill.ts'
2222
import {
2323
hasShippedDocs as checkShippedDocs,
2424
ensureCacheDir,
@@ -172,7 +172,7 @@ export async function installCommand(opts: InstallOptions): Promise<void> {
172172
if (match) {
173173
const skillDir = join(skillsDir, name)
174174
mkdirSync(skillDir, { recursive: true })
175-
writeFileSync(join(skillDir, 'SKILL.md'), sanitizeMarkdown(match.content))
175+
writeSkillMd(skillDir, sanitizeMarkdown(match.content))
176176
for (const f of match.files) {
177177
const filePath = join(skillDir, f.path)
178178
mkdirSync(dirname(filePath), { recursive: true })
@@ -586,7 +586,7 @@ async function enhanceRegenerated(
586586
const dirName = skillDir.split('/').pop()
587587

588588
const allPackages = parsePackageNames(packages)
589-
const skillMd = generateSkillMd({
589+
writeGeneratedSkillMd(skillDir, {
590590
name: pkgName,
591591
version,
592592
description,
@@ -602,7 +602,6 @@ async function enhanceRegenerated(
602602
packages: allPackages.length > 1 ? allPackages : undefined,
603603
features,
604604
})
605-
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
606605
}
607606
else {
608607
llmLog.message('Enhancement skipped')
@@ -674,7 +673,8 @@ function regenerateBaseSkillMd(
674673
// Build multi-package list from lockfile packages field
675674
const allPackages = parsePackageNames(packages)
676675

677-
const content = generateSkillMd({
676+
mkdirSync(skillDir, { recursive: true })
677+
writeGeneratedSkillMd(skillDir, {
678678
name: pkgName,
679679
version,
680680
description,
@@ -690,8 +690,6 @@ function regenerateBaseSkillMd(
690690
features: readConfig().features ?? defaultFeatures,
691691
})
692692

693-
mkdirSync(skillDir, { recursive: true })
694-
writeFileSync(skillMdPath, content)
695693
return true
696694
}
697695

src/commands/sync-git.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ import * as p from '@clack/prompts'
1010
import { dirname, join, relative } from 'pathe'
1111
import {
1212
agents,
13-
generateSkillMd,
1413
getModelLabel,
1514
linkSkillToAgents,
1615
sanitizeName,
16+
writeGeneratedSkillMd,
17+
writeSkillMd,
1718
} from '../agent/index.ts'
1819
import {
1920
CACHE_DIR,
@@ -134,7 +135,7 @@ export async function syncGitSkills(opts: GitSyncOptions): Promise<void> {
134135
mkdirSync(skillDir, { recursive: true })
135136

136137
// Sanitize and write SKILL.md
137-
writeFileSync(join(skillDir, 'SKILL.md'), sanitizeMarkdown(skill.content))
138+
writeSkillMd(skillDir, sanitizeMarkdown(skill.content))
138139

139140
// Write supporting files directly in skill dir (not under .skilld/)
140141
// so SKILL.md relative paths like ./references/docs/guide.md resolve correctly
@@ -275,7 +276,7 @@ async function syncGitHubRepo(opts: GitSyncOptions): Promise<void> {
275276
})
276277

277278
// Write base SKILL.md
278-
const baseSkillMd = generateSkillMd({
279+
writeGeneratedSkillMd(skillDir, {
279280
name: packageName,
280281
version,
281282
releasedAt: resolved.releasedAt,
@@ -292,7 +293,6 @@ async function syncGitHubRepo(opts: GitSyncOptions): Promise<void> {
292293
repoUrl,
293294
features,
294295
})
295-
writeFileSync(join(skillDir, 'SKILL.md'), baseSkillMd)
296296

297297
p.log.success(`Created base skill: ${relative(cwd, skillDir)}`)
298298

src/commands/sync-parallel.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { AgentType, CustomPrompt, OptimizeModel, SkillSection } from '../agent/index.ts'
22
import type { FeaturesConfig } from '../core/config.ts'
33
import type { ResolvedPackage } from '../sources/index.ts'
4-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
4+
import { existsSync, mkdirSync, readFileSync } from 'node:fs'
55
import * as p from '@clack/prompts'
66
import logUpdate from 'log-update'
77
import pLimit from 'p-limit'
@@ -10,13 +10,13 @@ import {
1010
agents,
1111
computeSkillDirName,
1212

13-
generateSkillMd,
1413
getModelLabel,
1514
linkSkillToAgents,
1615
optimizeDocs,
1716
SECTION_MERGE_ORDER,
1817
SECTION_OUTPUT_FILES,
1918
wrapSection,
19+
writeGeneratedSkillMd,
2020

2121
} from '../agent/index.ts'
2222
import {
@@ -263,7 +263,7 @@ export async function syncPackagesParallel(config: ParallelSyncConfig): Promise<
263263
}
264264
const cachedBody = cachedParts.join('\n\n')
265265

266-
const skillMd = generateSkillMd({
266+
writeGeneratedSkillMd(skillDir, {
267267
name: resolvedName,
268268
version: data.version,
269269
releasedAt: data.resolved.releasedAt,
@@ -284,7 +284,6 @@ export async function syncPackagesParallel(config: ParallelSyncConfig): Promise<
284284
repoUrl: data.resolved.repoUrl,
285285
features: data.features,
286286
})
287-
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
288287
cachedPkgs.push(pkg)
289288
}
290289
}
@@ -575,7 +574,7 @@ async function syncBaseSkill(
575574
const updatedLock = readLock(baseDir)?.skills[skillDirName]
576575
const allPackages = parsePackageNames(updatedLock?.packages)
577576

578-
const skillMd = generateSkillMd({
577+
const skillMd = writeGeneratedSkillMd(skillDir, {
579578
name: packageName,
580579
version,
581580
releasedAt: resolved.releasedAt,
@@ -595,7 +594,6 @@ async function syncBaseSkill(
595594
repoUrl: resolved.repoUrl,
596595
features,
597596
})
598-
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
599597
const overheadLines = skillMd.split('\n').length
600598

601599
// Link shared dir to per-agent dirs
@@ -683,7 +681,7 @@ async function enhanceWithLLM(
683681
}
684682

685683
if (wasOptimized) {
686-
const skillMd = generateSkillMd({
684+
writeGeneratedSkillMd(skillDir, {
687685
name: packageName,
688686
version: data.version,
689687
releasedAt: data.resolved.releasedAt,
@@ -702,7 +700,6 @@ async function enhanceWithLLM(
702700
repoUrl: data.resolved.repoUrl,
703701
features: data.features,
704702
})
705-
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
706703
}
707704

708705
update(packageName, 'done', 'Skill optimized', versionKey)

src/commands/sync-registry.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@
99

1010
import type { AgentType } from '../agent/index.ts'
1111
import type { RegistrySkill } from '../registry/client.ts'
12-
import { mkdirSync, writeFileSync } from 'node:fs'
12+
import { mkdirSync } from 'node:fs'
1313
import { join } from 'pathe'
1414
import { linkSkillToAgents } from '../agent/install.ts'
15+
import { writeSkillMd } from '../agent/prompts/skill.ts'
1516
import { writeLock } from '../core/lockfile.ts'
1617
import { SHARED_SKILLS_DIR } from '../core/shared.ts'
1718
import { fetchRegistrySkill } from '../registry/client.ts'
@@ -38,7 +39,7 @@ export async function syncRegistrySkill(opts: SyncRegistryOptions): Promise<Regi
3839
const sharedDir = join(cwd, SHARED_SKILLS_DIR)
3940
const skillDir = join(sharedDir, skill.name)
4041
mkdirSync(skillDir, { recursive: true })
41-
writeFileSync(join(skillDir, 'SKILL.md'), skill.content)
42+
writeSkillMd(skillDir, skill.content)
4243

4344
// Update lockfile
4445
const baseDir = join(cwd, '.claude', 'skills')

src/commands/sync-shared.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import {
88
agents,
99
buildAllSectionPrompts,
1010
createToolProgress,
11-
generateSkillMd,
1211
getAvailableModels,
1312
getModelLabel,
1413
getModelName,
1514
optimizeDocs,
1615
SECTION_OUTPUT_FILES,
16+
writeGeneratedSkillMd,
1717
} from '../agent/index.ts'
1818
import { maxItems, maxLines } from '../agent/prompts/optional/budget.ts'
1919
import {
@@ -1430,7 +1430,7 @@ export async function enhanceSkillWithLLM(opts: EnhanceOptions): Promise<void> {
14301430
for (const w of warnings)
14311431
p.log.warn(`\x1B[33m${w}\x1B[0m`)
14321432
}
1433-
const skillMd = generateSkillMd({
1433+
writeGeneratedSkillMd(skillDir, {
14341434
name: packageName,
14351435
version,
14361436
releasedAt: resolved.releasedAt,
@@ -1452,7 +1452,6 @@ export async function enhanceSkillWithLLM(opts: EnhanceOptions): Promise<void> {
14521452
features,
14531453
eject,
14541454
})
1455-
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
14561455
}
14571456
else {
14581457
if (error && /\b429\b|rate.?limit|exhausted.*capacity|quota.*reset/i.test(error))

src/commands/sync.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {
1111
buildAllSectionPrompts,
1212
computeSkillDirName,
1313
detectImportedPackages,
14-
generateSkillMd,
1514
getAvailableModels,
1615
getModelLabel,
1716
linkSkillToAgents,
@@ -20,6 +19,7 @@ import {
2019
SECTION_MERGE_ORDER,
2120
SECTION_OUTPUT_FILES,
2221
wrapSection,
22+
writeGeneratedSkillMd,
2323
} from '../agent/index.ts'
2424
import {
2525
ensureCacheDir,
@@ -480,7 +480,7 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
480480
const shippedDocs = hasShippedDocs(existingStorageName, cwd, existingLock.version)
481481

482482
const mergeFeatures = readConfig().features ?? defaultFeatures
483-
const skillMd = generateSkillMd({
483+
writeGeneratedSkillMd(skillDir, {
484484
name: existingLock.packageName!,
485485
version: existingLock.version,
486486
relatedSkills,
@@ -494,7 +494,6 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
494494
packages: allPackages,
495495
features: mergeFeatures,
496496
})
497-
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
498497

499498
const mergeShared = !config.global && getSharedSkillsDir(cwd)
500499
if (mergeShared)
@@ -605,7 +604,7 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
605604
const allPackages = parsePackageNames(updatedLock?.packages)
606605

607606
const isEject = !!config.eject
608-
const baseSkillMd = generateSkillMd({
607+
const baseSkillMd = writeGeneratedSkillMd(skillDir, {
609608
name: identityPackageName,
610609
version,
611610
releasedAt: resolved.releasedAt,
@@ -626,7 +625,6 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
626625
features,
627626
eject: isEject,
628627
})
629-
writeFileSync(join(skillDir, 'SKILL.md'), baseSkillMd)
630628
const overheadLines = baseSkillMd.split('\n').length
631629

632630
p.log.success(config.mode === 'update' ? `Updated skill: ${relative(cwd, skillDir)}` : `Created base skill: ${relative(cwd, skillDir)}`)
@@ -650,7 +648,7 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
650648
}
651649
const cachedBody = cachedParts.join('\n\n')
652650

653-
const skillMd = generateSkillMd({
651+
writeGeneratedSkillMd(skillDir, {
654652
name: identityPackageName,
655653
version,
656654
releasedAt: resolved.releasedAt,
@@ -673,7 +671,6 @@ async function syncSinglePackage(packageSpec: string, config: SyncConfig): Promi
673671
features,
674672
eject: isEject,
675673
})
676-
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
677674
p.log.success('Applied cached SKILL.md sections')
678675
}
679676

@@ -1231,7 +1228,7 @@ export async function exportPortablePrompts(packageSpec: string, opts: {
12311228

12321229
// Generate SKILL.md (ejected — uses ./references/ paths)
12331230
const relatedSkills = await findRelatedSkills(packageName, join(skillDir, '..'))
1234-
const skillMd = generateSkillMd({
1231+
writeGeneratedSkillMd(skillDir, {
12351232
name: packageName,
12361233
version,
12371234
releasedAt: resolved.releasedAt,
@@ -1250,7 +1247,6 @@ export async function exportPortablePrompts(packageSpec: string, opts: {
12501247
features,
12511248
eject: true,
12521249
})
1253-
writeFileSync(join(skillDir, 'SKILL.md'), skillMd)
12541250

12551251
// Write lockfile so skilld list/update/assemble can discover this skill
12561252
const repoSlug = parseGitHubRepoSlug(resolved.repoUrl)

0 commit comments

Comments
 (0)