Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ coverage/
# Repopack output
repopack-output.txt
repopack-output.xml
repopack-output.md

# ESLint cache
.eslintcache
Expand Down
43 changes: 41 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,14 +223,53 @@ https://docs.anthropic.com/en/docs/build-with-claude/prompt-engineering/use-xml-

This means that the XML output from Repopack is not just a different format, but potentially a more effective way to feed your codebase into AI systems for analysis, code review, or other tasks.

#### Markdown Format

To generate output in Markdown format, use the `--style markdown` option:
```bash
repopack --style markdown
```

The Markdown format structures the content in a hierarchical manner:

````markdown
This file is a merged representation of the entire codebase, combining all repository files into a single document.

# File Summary
(Metadata and usage AI instructions)

# Repository Structure
```
src/
cli/
cliOutput.ts
index.ts
```
(...remaining directories)

# Repository Files

## File: src/index.js
```
// File contents here
```

(...remaining files)

# Instruction
(Custom instructions from `output.instructionFilePath`)
````

This format provides a clean, readable structure that is both human-friendly and easily parseable by AI systems.

### Command Line Options

- `-v, --version`: Show tool version
- `-o, --output <file>`: Specify the output file name
- `--include <patterns>`: List of include patterns (comma-separated)
- `-i, --ignore <patterns>`: Additional ignore patterns (comma-separated)
- `-c, --config <path>`: Path to a custom config file
- `--style <style>`: Specify the output style (`plain` or `xml`)
- `--style <style>`: Specify the output style (`plain`, `xml`, `markdown`)
- `--top-files-len <number>`: Number of top files to display in the summary
- `--output-show-line-numbers`: Show line numbers in the output
- `--remote <url>`: Process a remote Git repository
Expand Down Expand Up @@ -290,7 +329,7 @@ Here's an explanation of the configuration options:
| Option | Description | Default |
|--------|-------------|---------|
|`output.filePath`| The name of the output file | `"repopack-output.txt"` |
|`output.style`| The style of the output (`plain`, `xml`) |`"plain"`|
|`output.style`| The style of the output (`plain`, `xml`, `markdown`) |`"plain"`|
|`output.headerText`| Custom text to include in the file header |`null`|
|`output.instructionFilePath`| Path to a file containing detailed custom instructions |`null`|
|`output.removeComments`| Whether to remove comments from supported file types | `false` |
Expand Down
2 changes: 1 addition & 1 deletion src/cli/cliRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function run() {
.option('-c, --config <path>', 'path to a custom config file')
.option('--top-files-len <number>', 'specify the number of top files to display', Number.parseInt)
.option('--output-show-line-numbers', 'add line numbers to each line in the output')
.option('--style <type>', 'specify the output style (plain or xml)')
.option('--style <type>', 'specify the output style (plain, xml, markdown)')
.option('--verbose', 'enable verbose logging for detailed output')
.option('--init', 'initialize a new repopack.config.json file')
.option('--global', 'use global configuration (only applicable with --init)')
Expand Down
2 changes: 1 addition & 1 deletion src/config/configTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export type RepopackOutputStyle = 'plain' | 'xml';
export type RepopackOutputStyle = 'plain' | 'xml' | 'markdown';

interface RepopackConfigBase {
output?: {
Expand Down
81 changes: 81 additions & 0 deletions src/core/output/markdownStyleGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import Handlebars from 'handlebars';
import type { OutputGeneratorContext } from './outputGeneratorTypes.js';
import {
generateHeader,
generateSummaryAdditionalInfo,
generateSummaryFileFormat,
generateSummaryNotes,
generateSummaryPurpose,
generateSummaryUsageGuidelines,
} from './outputStyleDecorator.js';

export const generateMarkdownStyle = (outputGeneratorContext: OutputGeneratorContext) => {
const template = Handlebars.compile(markdownTemplate);

const renderContext = {
generationHeader: generateHeader(outputGeneratorContext.generationDate),
summaryPurpose: generateSummaryPurpose(),
summaryFileFormat: generateSummaryFileFormat(),
summaryUsageGuidelines: generateSummaryUsageGuidelines(
outputGeneratorContext.config,
outputGeneratorContext.instruction,
),
summaryNotes: generateSummaryNotes(outputGeneratorContext.config),
summaryAdditionalInfo: generateSummaryAdditionalInfo(),
headerText: outputGeneratorContext.config.output.headerText,
instruction: outputGeneratorContext.instruction,
treeString: outputGeneratorContext.treeString,
processedFiles: outputGeneratorContext.processedFiles,
};

return `${template(renderContext).trim()}\n`;
};

const markdownTemplate = /* md */ `
{{{generationHeader}}}

# File Summary

## Purpose
{{{summaryPurpose}}}

## File Format
{{{summaryFileFormat}}}
4. Multiple file entries, each consisting of:
a. A header with the file path (## File: path/to/file)
b. The full contents of the file in a code block

## Usage Guidelines
{{{summaryUsageGuidelines}}}

## Notes
{{{summaryNotes}}}

## Additional Info
{{#if headerText}}
### User Provided Header
{{{headerText}}}
{{/if}}

{{{summaryAdditionalInfo}}}

# Repository Structure
\`\`\`
{{{treeString}}}
\`\`\`

# Repository Files

{{#each processedFiles}}
## File: {{{this.path}}}
\`\`\`
{{{this.content}}}
\`\`\`

{{/each}}

{{#if instruction}}
# Instruction
{{{instruction}}}
{{/if}}
`;
4 changes: 4 additions & 0 deletions src/core/output/outputGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { RepopackConfigMerged } from '../../config/configTypes.js';
import { RepopackError } from '../../shared/errorHandler.js';
import { generateTreeString } from '../file/fileTreeGenerator.js';
import type { ProcessedFile } from '../file/fileTypes.js';
import { generateMarkdownStyle } from './markdownStyleGenerator.js';
import type { OutputGeneratorContext } from './outputGeneratorTypes.js';
import { generatePlainStyle } from './plainStyleGenerator.js';
import { generateXmlStyle } from './xmlStyleGenerator.js';
Expand All @@ -21,6 +22,9 @@ export const generateOutput = async (
case 'xml':
output = generateXmlStyle(outputGeneratorContext);
break;
case 'markdown':
output = generateMarkdownStyle(outputGeneratorContext);
break;
default:
output = generatePlainStyle(outputGeneratorContext);
}
Expand Down
28 changes: 13 additions & 15 deletions src/core/output/plainStyleGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,11 @@ import {
generateSummaryUsageGuidelines,
} from './outputStyleDecorator.js';

const PLAIN_SEPARATOR = '='.repeat(16);
const PLAIN_LONG_SEPARATOR = '='.repeat(64);

export const generatePlainStyle = (outputGeneratorContext: OutputGeneratorContext) => {
const template = Handlebars.compile(plainTemplate);

const renderContext = {
generationHeader: generateHeader(outputGeneratorContext.generationDate),
plainSeparator: PLAIN_SEPARATOR,
plainLongSeparator: PLAIN_LONG_SEPARATOR,
summaryPurpose: generateSummaryPurpose(),
summaryFileFormat: generateSummaryFileFormat(),
summaryUsageGuidelines: generateSummaryUsageGuidelines(
Expand All @@ -36,12 +31,15 @@ export const generatePlainStyle = (outputGeneratorContext: OutputGeneratorContex
return `${template(renderContext).trim()}\n`;
};

const PLAIN_SEPARATOR = '='.repeat(16);
const PLAIN_LONG_SEPARATOR = '='.repeat(64);

const plainTemplate = `
{{{generationHeader}}}

{{{plainLongSeparator}}}
${PLAIN_LONG_SEPARATOR}
File Summary
{{{plainLongSeparator}}}
${PLAIN_LONG_SEPARATOR}

Purpose:
--------
Expand Down Expand Up @@ -75,27 +73,27 @@ User Provided Header:

{{{summaryAdditionalInfo}}}

{{{plainLongSeparator}}}
${PLAIN_LONG_SEPARATOR}
Repository Structure
{{{plainLongSeparator}}}
${PLAIN_LONG_SEPARATOR}
{{{treeString}}}

{{{plainLongSeparator}}}
${PLAIN_LONG_SEPARATOR}
Repository Files
{{{plainLongSeparator}}}
${PLAIN_LONG_SEPARATOR}

{{#each processedFiles}}
{{{../plainSeparator}}}
${PLAIN_SEPARATOR}
File: {{{this.path}}}
{{{../plainSeparator}}}
${PLAIN_SEPARATOR}
{{{this.content}}}

{{/each}}

{{#if instruction}}
{{{plainLongSeparator}}}
${PLAIN_LONG_SEPARATOR}
Instruction
{{{plainLongSeparator}}}
${PLAIN_LONG_SEPARATOR}
{{{instruction}}}
{{/if}}

Expand Down
2 changes: 1 addition & 1 deletion src/core/output/xmlStyleGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const generateXmlStyle = (outputGeneratorContext: OutputGeneratorContext)
return `${template(renderContext).trim()}\n`;
};

const xmlTemplate = `
const xmlTemplate = /* xml */ `
{{{generationHeader}}}

<file_summary>
Expand Down
34 changes: 34 additions & 0 deletions tests/core/output/markdownStyleGenerator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import process from 'node:process';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { generateMarkdownStyle } from '../../../src/core/output/markdownStyleGenerator.js';
import { buildOutputGeneratorContext } from '../../../src/core/output/outputGenerator.js';
import { createMockConfig } from '../../testing/testUtils.js';

vi.mock('fs/promises');

describe('outputGenerator', () => {
beforeEach(() => {
vi.resetAllMocks();
});

test('generateMarkdownOutput should include user-provided header text', async () => {
const mockConfig = createMockConfig({
output: {
filePath: 'output.md',
style: 'markdown',
headerText: 'Custom header text',
topFilesLength: 2,
showLineNumbers: false,
removeComments: false,
removeEmptyLines: false,
},
});

const context = await buildOutputGeneratorContext(process.cwd(), mockConfig, [], []);
const output = await generateMarkdownStyle(context);

expect(output).toContain('# File Summary');
expect(output).toContain('# Repository Structure');
expect(output).toContain('# Repository Files');
});
});