Skip to content

Commit

Permalink
feat: Add accurate codebase scope description to output header
Browse files Browse the repository at this point in the history
  • Loading branch information
yamadashy committed Feb 1, 2025
1 parent 7ad395e commit 225712b
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 44 deletions.
21 changes: 3 additions & 18 deletions src/core/output/outputGenerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { RepomixError } from '../../shared/errorHandle.js';
import { searchFiles } from '../file/fileSearch.js';
import { generateTreeString } from '../file/fileTreeGenerate.js';
import type { ProcessedFile } from '../file/fileTypes.js';
import type { OutputGeneratorContext } from './outputGeneratorTypes.js';
import type { OutputGeneratorContext, RenderContext } from './outputGeneratorTypes.js';
import {
generateHeader,
generateSummaryFileFormat,
Expand All @@ -19,22 +19,6 @@ import { getMarkdownTemplate } from './outputStyles/markdownStyle.js';
import { getPlainTemplate } from './outputStyles/plainStyle.js';
import { getXmlTemplate } from './outputStyles/xmlStyle.js';

interface RenderContext {
readonly generationHeader: string;
readonly summaryPurpose: string;
readonly summaryFileFormat: string;
readonly summaryUsageGuidelines: string;
readonly summaryNotes: string;
readonly headerText: string | undefined;
readonly instruction: string;
readonly treeString: string;
readonly processedFiles: ReadonlyArray<ProcessedFile>;
readonly fileSummaryEnabled: boolean;
readonly directoryStructureEnabled: boolean;
readonly escapeFileContent: boolean;
readonly markdownCodeBlockDelimiter: string;
}

const calculateMarkdownDelimiter = (files: ReadonlyArray<ProcessedFile>): string => {
const maxBackticks = files
.flatMap((file) => file.content.match(/`+/g) ?? [])
Expand All @@ -44,7 +28,7 @@ const calculateMarkdownDelimiter = (files: ReadonlyArray<ProcessedFile>): string

const createRenderContext = (outputGeneratorContext: OutputGeneratorContext): RenderContext => {
return {
generationHeader: generateHeader(outputGeneratorContext.generationDate),
generationHeader: generateHeader(outputGeneratorContext.config, outputGeneratorContext.generationDate), // configを追加
summaryPurpose: generateSummaryPurpose(),
summaryFileFormat: generateSummaryFileFormat(),
summaryUsageGuidelines: generateSummaryUsageGuidelines(
Expand Down Expand Up @@ -175,6 +159,7 @@ export const buildOutputGeneratorContext = async (
}
}
}

return {
generationDate: new Date().toISOString(),
treeString: generateTreeString(allFilePaths, emptyDirPaths),
Expand Down
16 changes: 16 additions & 0 deletions src/core/output/outputGeneratorTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,19 @@ export interface OutputGeneratorContext {
config: RepomixConfigMerged;
instruction: string;
}

export interface RenderContext {
readonly generationHeader: string;
readonly summaryPurpose: string;
readonly summaryFileFormat: string;
readonly summaryUsageGuidelines: string;
readonly summaryNotes: string;
readonly headerText: string | undefined;
readonly instruction: string;
readonly treeString: string;
readonly processedFiles: ReadonlyArray<ProcessedFile>;
readonly fileSummaryEnabled: boolean;
readonly directoryStructureEnabled: boolean;
readonly escapeFileContent: boolean;
readonly markdownCodeBlockDelimiter: string;
}
129 changes: 115 additions & 14 deletions src/core/output/outputStyleDecorate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,82 @@
import type { RepomixConfigMerged } from '../../config/configSchema.js';

export const generateHeader = (generationDate: string): string => {
return `
This file is a merged representation of the entire codebase, combining all repository files into a single document.
Generated by Repomix on: ${generationDate}
`.trim();
interface ContentInfo {
selection: {
isEntireCodebase: boolean;
include?: boolean;
ignore?: boolean;
gitignore?: boolean;
defaultIgnore?: boolean;
};
processing: {
commentsRemoved: boolean;
emptyLinesRemoved: boolean;
securityCheckEnabled: boolean;
showLineNumbers: boolean;
parsableStyle: boolean;
};
}

export const analyzeContent = (config: RepomixConfigMerged): ContentInfo => {
return {
selection: {
isEntireCodebase: !config.include.length && !config.ignore.customPatterns.length,
include: config.include.length > 0,
ignore: config.ignore.customPatterns.length > 0,
gitignore: config.ignore.useGitignore,
defaultIgnore: config.ignore.useDefaultPatterns,
},
processing: {
commentsRemoved: config.output.removeComments,
emptyLinesRemoved: config.output.removeEmptyLines,
securityCheckEnabled: config.security.enableSecurityCheck,
showLineNumbers: config.output.showLineNumbers,
parsableStyle: config.output.parsableStyle,
},
};
};

export const generateHeader = (config: RepomixConfigMerged, generationDate: string): string => {
const info = analyzeContent(config);

// Generate selection description
let description: string;
if (info.selection.isEntireCodebase) {
description = 'This file is a merged representation of the entire codebase';
} else {
const parts = [];
if (info.selection.include) {
parts.push('specifically included files');
}
if (info.selection.ignore) {
parts.push('files not matching ignore patterns');
}

Check warning on line 53 in src/core/output/outputStyleDecorate.ts

View check run for this annotation

Codecov / codecov/patch

src/core/output/outputStyleDecorate.ts#L52-L53

Added lines #L52 - L53 were not covered by tests
description = `This file is a merged representation of a subset of the codebase, containing ${parts.join(' and ')}`;
}

// Add processing information
const processingNotes = [];
if (info.processing.commentsRemoved) {
processingNotes.push('comments have been removed');
}
if (info.processing.emptyLinesRemoved) {
processingNotes.push('empty lines have been removed');
}
if (info.processing.showLineNumbers) {
processingNotes.push('line numbers have been added');
}
if (info.processing.parsableStyle) {
processingNotes.push('content has been formatted for parsing');
}
if (!info.processing.securityCheckEnabled) {
processingNotes.push('security check has been disabled');
}

const processingInfo =
processingNotes.length > 0 ? ` The content has been processed where ${processingNotes.join(', ')}.` : '';

return `${description}, combined into a single document.${processingInfo}
Generated by Repomix on: ${generationDate}`;
};

export const generateSummaryPurpose = (): string => {
Expand Down Expand Up @@ -38,13 +110,42 @@ ${repositoryInstruction ? '- Pay special attention to the Repository Instruction
};

export const generateSummaryNotes = (config: RepomixConfigMerged): string => {
return `
- Some files may have been excluded based on .gitignore rules and Repomix's
configuration.
- Binary files are not included in this packed representation. Please refer to
the Repository Structure section for a complete list of file paths, including
binary files.
${config.output.removeComments ? '- Code comments have been removed.\n' : ''}
${config.output.showLineNumbers ? '- Line numbers have been added to the beginning of each line.\n' : ''}
`.trim();
const info = analyzeContent(config);
const notes = [
"- Some files may have been excluded based on .gitignore rules and Repomix's configuration",
'- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files',
];

// File selection notes
if (info.selection.include) {
notes.push(`- Only files matching these patterns are included: ${config.include.join(', ')}`);
}
if (info.selection.ignore) {
notes.push(`- Files matching these patterns are excluded: ${config.ignore.customPatterns.join(', ')}`);
}
if (info.selection.gitignore) {
notes.push('- Files matching patterns in .gitignore are excluded');
}
if (info.selection.defaultIgnore) {
notes.push('- Files matching default ignore patterns are excluded');
}

// Processing notes
if (info.processing.commentsRemoved) {
notes.push('- Code comments have been removed from supported file types');
}
if (info.processing.emptyLinesRemoved) {
notes.push('- Empty lines have been removed from all files');
}

Check warning on line 139 in src/core/output/outputStyleDecorate.ts

View check run for this annotation

Codecov / codecov/patch

src/core/output/outputStyleDecorate.ts#L138-L139

Added lines #L138 - L139 were not covered by tests
if (info.processing.showLineNumbers) {
notes.push('- Line numbers have been added to the beginning of each line');
}
if (info.processing.parsableStyle) {
notes.push(`- Content has been formatted for parsing in ${config.output.style} style`);
}
if (!info.processing.securityCheckEnabled) {
notes.push('- Security check has been disabled - content may contain sensitive information');
}

return notes.join('\n');
};
153 changes: 153 additions & 0 deletions tests/core/output/outputStyleDecorate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { describe, expect, it } from 'vitest';
import {
analyzeContent,
generateHeader,
generateSummaryNotes,
generateSummaryPurpose,
generateSummaryUsageGuidelines,
} from '../../../src/core/output/outputStyleDecorate.js';
import { createMockConfig } from '../../testing/testUtils.js';

describe('analyzeContent', () => {
it('should detect entire codebase when using default settings', () => {
const config = createMockConfig();
const result = analyzeContent(config);
expect(result.selection.isEntireCodebase).toBe(true);
});

it('should detect subset when using include patterns', () => {
const config = createMockConfig({
include: ['src/**/*.ts'],
});
const result = analyzeContent(config);
expect(result.selection.isEntireCodebase).toBe(false);
expect(result.selection.include).toBe(true);
});

it('should detect processing states', () => {
const config = createMockConfig({
output: {
removeComments: true,
removeEmptyLines: true,
},
});
const result = analyzeContent(config);
expect(result.processing.commentsRemoved).toBe(true);
expect(result.processing.emptyLinesRemoved).toBe(true);
});
});

describe('generateHeader', () => {
const mockDate = '2025-01-29T11:23:01.763Z';

it('should generate header for entire codebase', () => {
const config = createMockConfig();
const header = generateHeader(config, mockDate);
expect(header).toContain('entire codebase');
expect(header).not.toContain('subset');
});

it('should generate header for subset with processing', () => {
const config = createMockConfig({
include: ['src/**/*.ts'],
output: {
removeComments: true,
},
});
const header = generateHeader(config, mockDate);
expect(header).toContain('subset of the codebase');
expect(header).toContain('comments have been removed');
});

it('should include security check disabled warning', () => {
const config = createMockConfig({
security: {
enableSecurityCheck: false,
},
});
const header = generateHeader(config, mockDate);
expect(header).toContain('security check has been disabled');
});

it('should include multiple processing states', () => {
const config = createMockConfig({
output: {
removeComments: true,
removeEmptyLines: true,
showLineNumbers: true,
},
});
const header = generateHeader(config, mockDate);
expect(header).toContain('comments have been removed');
expect(header).toContain('empty lines have been removed');
expect(header).toContain('line numbers have been added');
});
});

describe('generateSummaryPurpose', () => {
it('should generate consistent purpose text', () => {
const purpose = generateSummaryPurpose();
expect(purpose).toContain('packed representation');
expect(purpose).toContain('AI systems');
expect(purpose).toContain('code review');
});
});

describe('generateSummaryUsageGuidelines', () => {
it('should include header text note when headerText is provided', () => {
const config = createMockConfig({
output: {
headerText: 'Custom header',
},
});
const guidelines = generateSummaryUsageGuidelines(config, '');
expect(guidelines).toContain('Repository Description');
});

it('should include instruction note when instruction is provided', () => {
const config = createMockConfig();
const guidelines = generateSummaryUsageGuidelines(config, 'Custom instruction');
expect(guidelines).toContain('Repository Instruction');
});
});

describe('generateSummaryNotes', () => {
it('should include selection information', () => {
const config = createMockConfig({
include: ['src/**/*.ts'],
ignore: {
customPatterns: ['*.test.ts'],
},
});
const notes = generateSummaryNotes(config);
expect(notes).toContain('Only files matching these patterns are included: src/**/*.ts');
expect(notes).toContain('Files matching these patterns are excluded: *.test.ts');
});

it('should include processing notes', () => {
const config = createMockConfig({
output: {
removeComments: true,
showLineNumbers: true,
style: 'xml',
parsableStyle: true,
},
security: {
enableSecurityCheck: false,
},
});
const notes = generateSummaryNotes(config);
expect(notes).toContain('Code comments have been removed');
expect(notes).toContain('Line numbers have been added');
expect(notes).toContain('Content has been formatted for parsing in xml style');
expect(notes).toContain('Security check has been disabled');
});

it('should handle case with minimal processing', () => {
const config = createMockConfig();
const notes = generateSummaryNotes(config);
expect(notes).toContain('Files matching patterns in .gitignore are excluded');
expect(notes).toContain('Files matching default ignore patterns are excluded');
expect(notes).not.toContain('Code comments have been removed');
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
This file is a merged representation of the entire codebase, combining all repository files into a single document.
This file is a merged representation of the entire codebase, combined into a single document.

================================================================
File Summary
Expand Down Expand Up @@ -35,11 +35,10 @@ Usage Guidelines:

Notes:
------
- Some files may have been excluded based on .gitignore rules and Repomix's
configuration.
- Binary files are not included in this packed representation. Please refer to
the Repository Structure section for a complete list of file paths, including
binary files.
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
- Files matching patterns in .gitignore are excluded
- Files matching default ignore patterns are excluded

Additional Info:
----------------
Expand Down
Loading

0 comments on commit 225712b

Please sign in to comment.