99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
import memoize from 'lodash-es/memoize.js'
|
|
import { basename } from 'path'
|
|
import type { OutputStyleConfig } from '../constants/outputStyles.js'
|
|
import { logForDebugging } from '../utils/debug.js'
|
|
import { coerceDescriptionToString } from '../utils/frontmatterParser.js'
|
|
import { logError } from '../utils/log.js'
|
|
import {
|
|
extractDescriptionFromMarkdown,
|
|
loadMarkdownFilesForSubdir,
|
|
} from '../utils/markdownConfigLoader.js'
|
|
import { clearPluginOutputStyleCache } from '../utils/plugins/loadPluginOutputStyles.js'
|
|
|
|
/**
|
|
* Loads markdown files from .claude/output-styles directories throughout the project
|
|
* and from ~/.claude/output-styles directory and converts them to output styles.
|
|
*
|
|
* Each filename becomes a style name, and the file content becomes the style prompt.
|
|
* The frontmatter provides name and description.
|
|
*
|
|
* Structure:
|
|
* - Project .claude/output-styles/*.md -> project styles
|
|
* - User ~/.claude/output-styles/*.md -> user styles (overridden by project styles)
|
|
*
|
|
* @param cwd Current working directory for project directory traversal
|
|
*/
|
|
export const getOutputStyleDirStyles = memoize(
|
|
async (cwd: string): Promise<OutputStyleConfig[]> => {
|
|
try {
|
|
const markdownFiles = await loadMarkdownFilesForSubdir(
|
|
'output-styles',
|
|
cwd,
|
|
)
|
|
|
|
const styles = markdownFiles
|
|
.map(({ filePath, frontmatter, content, source }) => {
|
|
try {
|
|
const fileName = basename(filePath)
|
|
const styleName = fileName.replace(/\.md$/, '')
|
|
|
|
// Get style configuration from frontmatter
|
|
const name = (frontmatter['name'] || styleName) as string
|
|
const description =
|
|
coerceDescriptionToString(
|
|
frontmatter['description'],
|
|
styleName,
|
|
) ??
|
|
extractDescriptionFromMarkdown(
|
|
content,
|
|
`Custom ${styleName} output style`,
|
|
)
|
|
|
|
// Parse keep-coding-instructions flag (supports both boolean and string values)
|
|
const keepCodingInstructionsRaw =
|
|
frontmatter['keep-coding-instructions']
|
|
const keepCodingInstructions =
|
|
keepCodingInstructionsRaw === true ||
|
|
keepCodingInstructionsRaw === 'true'
|
|
? true
|
|
: keepCodingInstructionsRaw === false ||
|
|
keepCodingInstructionsRaw === 'false'
|
|
? false
|
|
: undefined
|
|
|
|
// Warn if force-for-plugin is set on non-plugin output style
|
|
if (frontmatter['force-for-plugin'] !== undefined) {
|
|
logForDebugging(
|
|
`Output style "${name}" has force-for-plugin set, but this option only applies to plugin output styles. Ignoring.`,
|
|
{ level: 'warn' },
|
|
)
|
|
}
|
|
|
|
return {
|
|
name,
|
|
description,
|
|
prompt: content.trim(),
|
|
source,
|
|
keepCodingInstructions,
|
|
}
|
|
} catch (error) {
|
|
logError(error)
|
|
return null
|
|
}
|
|
})
|
|
.filter(style => style !== null)
|
|
|
|
return styles
|
|
} catch (error) {
|
|
logError(error)
|
|
return []
|
|
}
|
|
},
|
|
)
|
|
|
|
export function clearOutputStyleCaches(): void {
|
|
getOutputStyleDirStyles.cache?.clear?.()
|
|
loadMarkdownFilesForSubdir.cache?.clear?.()
|
|
clearPluginOutputStyleCache()
|
|
}
|