diff --git a/src/core/output/outputGenerate.ts b/src/core/output/outputGenerate.ts index 32caa6666..a7fea5155 100644 --- a/src/core/output/outputGenerate.ts +++ b/src/core/output/outputGenerate.ts @@ -53,30 +53,67 @@ const getCompiledTemplate = (style: string): Handlebars.TemplateDelegate => { }; const calculateMarkdownDelimiter = (files: ReadonlyArray): string => { - const maxBackticks = files - .flatMap((file) => file.content.match(/`+/g) ?? []) - .reduce((max, match) => Math.max(max, match.length), 0); - return '`'.repeat(Math.max(3, maxBackticks + 1)); + // Single-pass character scan: avoid regex/flatMap overhead and intermediate array allocation + let maxLen = 0; + for (const file of files) { + let currentLen = 0; + for (let i = 0; i < file.content.length; i++) { + if (file.content[i] === '`') { + currentLen++; + if (currentLen > maxLen) { + maxLen = currentLen; + } + } else { + currentLen = 0; + } + } + } + return '`'.repeat(Math.max(3, maxLen + 1)); +}; + +const countNewlines = (content: string): number => { + let count = 0; + let pos = content.indexOf('\n'); + while (pos !== -1) { + count++; + pos = content.indexOf('\n', pos + 1); + } + return count; }; const calculateFileLineCounts = (processedFiles: ProcessedFile[]): Record => { const lineCounts: Record = {}; for (const file of processedFiles) { - // Count lines: empty files have 0 lines, otherwise count newlines + 1 - // (unless the content ends with a newline, in which case the last "line" is empty) const content = file.content; if (content.length === 0) { lineCounts[file.path] = 0; } else { - // Count actual lines (text editor style: number of \n + 1, but trailing \n doesn't add extra line) - const newlineCount = (content.match(/\n/g) || []).length; + const newlineCount = countNewlines(content); lineCounts[file.path] = content.endsWith('\n') ? newlineCount : newlineCount + 1; } } return lineCounts; }; -export const createRenderContext = (outputGeneratorContext: OutputGeneratorContext): RenderContext => { +export interface CreateRenderContextOptions { + // When true, compute all fields regardless of output style (needed by skill path) + forceAll?: boolean; +} + +export const createRenderContext = ( + outputGeneratorContext: OutputGeneratorContext, + options: CreateRenderContextOptions = {}, +): RenderContext => { + const style = outputGeneratorContext.config.output.style; + + // fileLineCounts: not referenced by any Handlebars template or parsable output generator. + // Only the skill path (packSkill.ts) uses it, so skip the full-content scan for regular output. + const needsLineCounts = !!options.forceAll; + + // markdownCodeBlockDelimiter: only used by the markdown template and skill generators. + // For XML, plain, JSON, and parsable-XML output, skip the full-content regex scan. + const needsMarkdownDelimiter = options.forceAll || style === 'markdown'; + return { generationHeader: generateHeader(outputGeneratorContext.config, outputGeneratorContext.generationDate), summaryPurpose: generateSummaryPurpose(outputGeneratorContext.config), @@ -90,12 +127,14 @@ export const createRenderContext = (outputGeneratorContext: OutputGeneratorConte instruction: outputGeneratorContext.instruction, treeString: outputGeneratorContext.treeString, processedFiles: outputGeneratorContext.processedFiles, - fileLineCounts: calculateFileLineCounts(outputGeneratorContext.processedFiles), + fileLineCounts: needsLineCounts ? calculateFileLineCounts(outputGeneratorContext.processedFiles) : {}, fileSummaryEnabled: outputGeneratorContext.config.output.fileSummary, directoryStructureEnabled: outputGeneratorContext.config.output.directoryStructure, filesEnabled: outputGeneratorContext.config.output.files, escapeFileContent: outputGeneratorContext.config.output.parsableStyle, - markdownCodeBlockDelimiter: calculateMarkdownDelimiter(outputGeneratorContext.processedFiles), + markdownCodeBlockDelimiter: needsMarkdownDelimiter + ? calculateMarkdownDelimiter(outputGeneratorContext.processedFiles) + : '```', gitDiffEnabled: outputGeneratorContext.config.output.git?.includeDiffs, gitDiffWorkTree: outputGeneratorContext.gitDiffResult?.workTreeDiffContent, gitDiffStaged: outputGeneratorContext.gitDiffResult?.stagedDiffContent, diff --git a/src/core/skill/packSkill.ts b/src/core/skill/packSkill.ts index 41575b984..11b34512b 100644 --- a/src/core/skill/packSkill.ts +++ b/src/core/skill/packSkill.ts @@ -111,7 +111,7 @@ export const generateSkillReferences = async ( gitDiffResult, gitLogResult, ); - const renderContext = createRenderContext(outputGeneratorContext); + const renderContext = createRenderContext(outputGeneratorContext, { forceAll: true }); // Calculate statistics const statistics = calculateStatistics(sortedProcessedFiles, renderContext.fileLineCounts);