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 @@ -26,6 +26,7 @@ coverage/
repomix-output.txt
repomix-output.xml
repomix-output.md
repomix-output.json

# ESLint cache
.eslintcache
Expand Down
84 changes: 81 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,76 @@ src/

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

#### JSON Format

To generate output in JSON format, use the `--style json` option:

```bash
repomix --style json
```

The JSON format structures the content as a hierarchical JSON object with camelCase property names:

```json
{
"fileSummary": {
"generationHeader": "This file is a merged representation of the entire codebase, combined into a single document by Repomix.",
"purpose": "This file contains a packed representation of the entire repository's contents...",
"fileFormat": "The content is organized as follows...",
"usageGuidelines": "- This file should be treated as read-only...",
"notes": "- Some files may have been excluded based on .gitignore rules..."
},
"userProvidedHeader": "Custom header text if specified",
"directoryStructure": "src/\n cli/\n cliOutput.ts\n index.ts\n config/\n configLoader.ts",
"files": {
"src/index.js": "// File contents here",
"src/utils.js": "// File contents here"
},
"instruction": "Custom instructions from instructionFilePath"
}
```

This format is ideal for:
- **Programmatic processing**: Easy to parse and manipulate with JSON libraries
- **API integration**: Direct consumption by web services and applications
- **AI tool compatibility**: Structured format for machine learning and AI systems
- **Data analysis**: Straightforward extraction of specific information using tools like `jq`

##### Working with JSON Output Using `jq`

The JSON format makes it easy to extract specific information programmatically:

```bash
# List all file paths
cat repomix-output.json | jq -r '.files | keys[]'

# Count total number of files
cat repomix-output.json | jq '.files | keys | length'

# Extract specific file content
cat repomix-output.json | jq -r '.files["README.md"]'
cat repomix-output.json | jq -r '.files["src/index.js"]'

# Find files by extension
cat repomix-output.json | jq -r '.files | keys[] | select(endswith(".ts"))'

# Get files containing specific text
cat repomix-output.json | jq -r '.files | to_entries[] | select(.value | contains("function")) | .key'

# Extract directory structure
cat repomix-output.json | jq -r '.directoryStructure'

# Get file summary information
cat repomix-output.json | jq '.fileSummary.purpose'
cat repomix-output.json | jq -r '.fileSummary.generationHeader'

# Extract user-provided header (if exists)
cat repomix-output.json | jq -r '.userProvidedHeader // "No header provided"'

# Create a file list with sizes
cat repomix-output.json | jq -r '.files | to_entries[] | "\(.key): \(.value | length) characters"'
```

#### Plain Text Format

To generate output in plain text format, use the `--style plain` option:
Expand Down Expand Up @@ -544,7 +614,7 @@ Instruction

#### Repomix Output Options
- `-o, --output <file>`: Output file path (default: repomix-output.xml, use "-" for stdout)
- `--style <style>`: Output format: xml, markdown, or plain (default: xml)
- `--style <style>`: Output format: xml, markdown, json, or plain (default: xml)
- `--parsable-style`: Escape special characters to ensure valid XML/Markdown (needed when output contains code that breaks formatting)
- `--compress`: Extract essential code structure (classes, functions, interfaces) using Tree-sitter parsing
- `--output-show-line-numbers`: Prefix each line with its line number in the output
Expand Down Expand Up @@ -937,7 +1007,7 @@ Here's an explanation of the configuration options:
|----------------------------------|------------------------------------------------------------------------------------------------------------------------------|------------------------|
| `input.maxFileSize` | Maximum file size in bytes to process. Files larger than this will be skipped | `50000000` |
| `output.filePath` | The name of the output file | `"repomix-output.xml"` |
| `output.style` | The style of the output (`xml`, `markdown`, `plain`) | `"xml"` |
| `output.style` | The style of the output (`xml`, `markdown`, `json`, `plain`) | `"xml"` |
| `output.parsableStyle` | Whether to escape the output based on the chosen style schema. Note that this can increase token count. | `false` |
| `output.compress` | Whether to perform intelligent code extraction to reduce token count | `false` |
| `output.headerText` | Custom text to include in the file header | `null` |
Expand Down Expand Up @@ -1203,6 +1273,14 @@ Use `--style` to generate output in different formats:
style: markdown
```

```yaml
- name: Pack repository with Repomix (JSON format)
uses: yamadashy/repomix/.github/actions/repomix@main
with:
output: repomix-output.json
style: json
```

Pack specific directories with compression:

```yaml
Expand Down Expand Up @@ -1276,7 +1354,7 @@ See the complete workflow example [here](https://github.com/yamadashy/repomix/bl
| `ignore` | Comma-separated glob patterns to ignore files (e.g., `**/*.test.ts,**/node_modules/**`) | `""` |
| `output` | Relative path for the packed file (extension determines format: `.txt`, `.md`, `.xml`) | `repomix-output.xml` |
| `compress` | Enable smart compression to reduce output size by pruning implementation details | `true` |
| `style` | Output style (`xml`, `markdown`, `plain`) | `xml` |
| `style` | Output style (`xml`, `markdown`, `json`, `plain`) | `xml` |
| `additional-args` | Extra raw arguments for the repomix CLI (e.g., `--no-file-summary --no-security-check`) | `""` |
| `repomix-version` | Version of the npm package to install (supports semver ranges, tags, or specific versions like `0.2.25`) | `latest` |

Expand Down
1 change: 1 addition & 0 deletions src/cli/actions/initAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const createConfigFile = async (rootDir: string, isGlobal: boolean): Prom
options: [
{ value: 'xml', label: 'XML', hint: 'Structured XML format' },
{ value: 'markdown', label: 'Markdown', hint: 'Markdown format' },
{ value: 'json', label: 'JSON', hint: 'Machine-readable JSON format' },
{ value: 'plain', label: 'Plain', hint: 'Simple text format' },
],
initialValue: defaultConfig.output.style,
Expand Down
2 changes: 1 addition & 1 deletion src/cli/cliRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export const run = async () => {
// Repomix Output Options
.optionsGroup('Repomix Output Options')
.option('-o, --output <file>', 'Output file path (default: repomix-output.xml, use "-" for stdout)')
.option('--style <type>', 'Output format: xml, markdown, or plain (default: xml)')
.option('--style <type>', 'Output format: xml, markdown, json, or plain (default: xml)')
.option(
'--parsable-style',
'Escape special characters to ensure valid XML/Markdown (needed when output contains code that breaks formatting)',
Expand Down
3 changes: 2 additions & 1 deletion src/config/configSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ import type { TiktokenEncoding } from 'tiktoken';
import { z } from 'zod';

// Output style enum
export const repomixOutputStyleSchema = z.enum(['xml', 'markdown', 'plain']);
export const repomixOutputStyleSchema = z.enum(['xml', 'markdown', 'json', 'plain']);
export type RepomixOutputStyle = z.infer<typeof repomixOutputStyleSchema>;

// Default values map
export const defaultFilePathMap: Record<RepomixOutputStyle, string> = {
xml: 'repomix-output.xml',
markdown: 'repomix-output.md',
plain: 'repomix-output.txt',
json: 'repomix-output.json',
} as const;

// Base config schema
Expand Down
68 changes: 64 additions & 4 deletions src/core/output/outputGenerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { sortOutputFiles } from './outputSort.js';
import {
generateHeader,
generateSummaryFileFormat,
generateSummaryFileFormatJson,
generateSummaryNotes,
generateSummaryPurpose,
generateSummaryUsageGuidelines,
Expand Down Expand Up @@ -112,6 +113,60 @@ const generateParsableXmlOutput = async (renderContext: RenderContext): Promise<
}
};

const generateParsableJsonOutput = async (renderContext: RenderContext): Promise<string> => {
const jsonDocument = {
...(renderContext.fileSummaryEnabled && {
fileSummary: {
generationHeader: renderContext.generationHeader,
purpose: renderContext.summaryPurpose,
fileFormat: generateSummaryFileFormatJson(),
usageGuidelines: renderContext.summaryUsageGuidelines,
notes: renderContext.summaryNotes,
},
}),
...(renderContext.headerText && {
userProvidedHeader: renderContext.headerText,
}),
...(renderContext.directoryStructureEnabled && {
directoryStructure: renderContext.treeString,
}),
...(renderContext.filesEnabled && {
files: renderContext.processedFiles.reduce(
(acc, file) => {
acc[file.path] = file.content;
return acc;
},
{} as Record<string, string>,
),
}),
...(renderContext.gitDiffEnabled && {
gitDiffs: {
workTree: renderContext.gitDiffWorkTree,
staged: renderContext.gitDiffStaged,
},
}),
...(renderContext.gitLogEnabled && {
gitLogs: renderContext.gitLogCommits?.map((commit) => ({
date: commit.date,
message: commit.message,
files: commit.files,
})),
}),
...(renderContext.instruction && {
instruction: renderContext.instruction,
}),
};

try {
return JSON.stringify(jsonDocument, null, 2);
} catch (error) {
throw new RepomixError(
`Failed to generate JSON output: ${error instanceof Error ? error.message : 'Unknown error'}`,
error instanceof Error ? { cause: error } : undefined,
);
}
};

const generateHandlebarOutput = async (
config: RepomixConfigMerged,
renderContext: RenderContext,
Expand All @@ -129,7 +184,7 @@ const generateHandlebarOutput = async (
template = getPlainTemplate();
break;
default:
throw new RepomixError(`Unknown output style: ${config.output.style}`);
throw new RepomixError(`Unsupported output style for handlebars template: ${config.output.style}`);
}

try {
Expand Down Expand Up @@ -174,6 +229,7 @@ export const generateOutput = async (
buildOutputGeneratorContext,
generateHandlebarOutput,
generateParsableXmlOutput,
generateParsableJsonOutput,
sortOutputFiles,
},
): Promise<string> => {
Expand All @@ -190,14 +246,18 @@ export const generateOutput = async (
);
const renderContext = createRenderContext(outputGeneratorContext);

if (!config.output.parsableStyle) return deps.generateHandlebarOutput(config, renderContext, sortedProcessedFiles);
switch (config.output.style) {
case 'xml':
return deps.generateParsableXmlOutput(renderContext);
return config.output.parsableStyle
? deps.generateParsableXmlOutput(renderContext)
: deps.generateHandlebarOutput(config, renderContext, sortedProcessedFiles);
case 'json':
return deps.generateParsableJsonOutput(renderContext);
case 'markdown':
case 'plain':
return deps.generateHandlebarOutput(config, renderContext, sortedProcessedFiles);
default:
return deps.generateHandlebarOutput(config, renderContext, sortedProcessedFiles);
throw new RepomixError(`Unsupported output style: ${config.output.style}`);
}
};

Expand Down
12 changes: 12 additions & 0 deletions src/core/output/outputStyleDecorate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,18 @@ The content is organized as follows:
`.trim();
};

export const generateSummaryFileFormatJson = (): string => {
return `
The content is organized as follows:
1. This summary section
2. Repository information
3. Directory structure
4. Repository files, each consisting of:
- File path as a key
- Full contents of the file as the value
`.trim();
};

export const generateSummaryUsageGuidelines = (config: RepomixConfigMerged, repositoryInstruction: string): string => {
return `
- This file should be treated as read-only. Any changes should be made to the
Expand Down
2 changes: 2 additions & 0 deletions tests/core/output/diffsInOutput.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ index 123..456 100644
buildOutputGeneratorContext: mockBuildOutputGeneratorContext,
generateHandlebarOutput: mockGenerateHandlebarOutput,
generateParsableXmlOutput: mockGenerateParsableXmlOutput,
generateParsableJsonOutput: vi.fn(),
sortOutputFiles: mockSortOutputFiles,
});

Expand Down Expand Up @@ -190,6 +191,7 @@ index 123..456 100644
buildOutputGeneratorContext: mockBuildOutputGeneratorContext,
generateHandlebarOutput: mockGenerateHandlebarOutput,
generateParsableXmlOutput: mockGenerateParsableXmlOutput,
generateParsableJsonOutput: vi.fn(),
sortOutputFiles: mockSortOutputFiles,
});

Expand Down
1 change: 1 addition & 0 deletions tests/core/output/outputGenerate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ describe('outputGenerate', () => {
buildOutputGeneratorContext: vi.fn(),
generateHandlebarOutput: vi.fn(),
generateParsableXmlOutput: vi.fn(),
generateParsableJsonOutput: vi.fn(),
sortOutputFiles: vi.fn(),
};
test('generateOutput should use sortOutputFiles before generating content', async () => {
Expand Down
1 change: 1 addition & 0 deletions tests/core/output/outputGenerateDiffs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ describe('Output Generation with Diffs', () => {
})),
generateHandlebarOutput: vi.fn(),
generateParsableXmlOutput: vi.fn(),
generateParsableJsonOutput: vi.fn(),
sortOutputFiles: vi.fn().mockResolvedValue(mockProcessedFiles),
};

Expand Down
Loading
Loading