Skip to content
Open
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ To exclude specific files or directories:
repomix --ignore "**/*.log,tmp/"
```

To force include files (overriding ignore patterns):

```bash
repomix --force-include "coverage/summary.json,test-results/junit.xml"
```

To pack a remote repository:

```bash
Expand Down
3 changes: 3 additions & 0 deletions src/cli/actions/defaultAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export const buildCliConfig = (options: CliOptions): RepomixConfigCli => {
if (options.include) {
cliConfig.include = splitPatterns(options.include);
}
if (options.forceInclude) {
cliConfig.forceInclude = options.forceInclude.split(',').map((pattern) => pattern.trim());
}
if (options.ignore) {
cliConfig.ignore = { customPatterns: splitPatterns(options.ignore) };
}
Expand Down
1 change: 1 addition & 0 deletions src/cli/cliRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const run = async () => {
// Filter Options
.optionsGroup('Filter Options')
.option('--include <patterns>', 'list of include patterns (comma-separated)')
.option('--force-include <patterns>', 'list of patterns to include, overriding ignores (comma-separated)')
.option('-i, --ignore <patterns>', 'additional ignore patterns (comma-separated)')
.option('--no-gitignore', 'disable .gitignore file usage')
.option('--no-default-patterns', 'disable default patterns')
Expand Down
1 change: 1 addition & 0 deletions src/cli/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface CliOptions extends OptionValues {

// Filter Options
include?: string;
forceInclude?: string;
ignore?: string;
gitignore?: boolean;
defaultPatterns?: boolean;
Expand Down
5 changes: 5 additions & 0 deletions src/config/configLoad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ export const mergeConfigs = (
...cliConfig.output,
},
include: [...(baseConfig.include || []), ...(fileConfig.include || []), ...(cliConfig.include || [])],
forceInclude: [
...(baseConfig.forceInclude || []),
...(fileConfig.forceInclude || []),
...(cliConfig.forceInclude || []),
],
ignore: {
...baseConfig.ignore,
...fileConfig.ignore,
Expand Down
2 changes: 2 additions & 0 deletions src/config/configSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const repomixConfigBaseSchema = z.object({
})
.optional(),
include: z.array(z.string()).optional(),
forceInclude: z.array(z.string()).optional(),
ignore: z
.object({
useGitignore: z.boolean().optional(),
Expand Down Expand Up @@ -104,6 +105,7 @@ export const repomixConfigDefaultSchema = z.object({
})
.default({}),
include: z.array(z.string()).default([]),
forceInclude: z.array(z.string()).default([]),
ignore: z
.object({
useGitignore: z.boolean().default(true),
Expand Down
32 changes: 29 additions & 3 deletions src/core/file/fileSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged):
logger.trace('Include patterns:', includePatterns);
logger.trace('Ignore patterns:', normalizedIgnorePatterns);
logger.trace('Ignore file patterns:', ignoreFilePatterns);
logger.trace('Force include patterns:', config.forceInclude);

// Check if .git is a worktree reference
const gitPath = path.join(rootDir, '.git');
Expand All @@ -171,7 +172,8 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged):
}
}

const filePaths = await globby(includePatterns, {
// Step 1: Get initial files with normal include/ignore patterns
const initialFilePaths = await globby(includePatterns, {
cwd: rootDir,
ignore: [...adjustedIgnorePatterns],
ignoreFiles: [...ignoreFilePatterns],
Expand All @@ -190,6 +192,28 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged):
throw error;
});

// Step 2: If forceInclude is specified, get additional files without applying ignore patterns
let forcedFilePaths: string[] = [];
if (config.forceInclude.length > 0) {
const forceIncludePatterns = config.forceInclude.map((pattern) => escapeGlobPattern(pattern));

logger.trace('Getting forced include files with patterns:', forceIncludePatterns);

forcedFilePaths = await globby(forceIncludePatterns, {
cwd: rootDir,
onlyFiles: true,
absolute: false,
dot: true,
followSymbolicLinks: false,
// No ignore or ignoreFiles specified to override ignore settings
});

logger.trace('Found forced include files:', forcedFilePaths);
}

// Step 3: Combine initialFilePaths and forcedFilePaths, removing duplicates
const combinedFilePaths = Array.from(new Set([...initialFilePaths, ...forcedFilePaths]));

let emptyDirPaths: string[] = [];
if (config.output.includeEmptyDirectories) {
const directories = await globby(includePatterns, {
Expand All @@ -205,10 +229,12 @@ export const searchFiles = async (rootDir: string, config: RepomixConfigMerged):
emptyDirPaths = await findEmptyDirectories(rootDir, directories, adjustedIgnorePatterns);
}

logger.trace(`Filtered ${filePaths.length} files`);
logger.trace(
`Filtered ${combinedFilePaths.length} files (including ${forcedFilePaths.length} forced include files)`,
);

return {
filePaths: sortPaths(filePaths),
filePaths: sortPaths(combinedFilePaths),
emptyDirPaths: sortPaths(emptyDirPaths),
};
} catch (error: unknown) {
Expand Down
19 changes: 19 additions & 0 deletions tests/cli/actions/defaultAction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('defaultAction', () => {
customPatterns: [],
},
include: [],
forceInclude: [],
security: {
enableSecurityCheck: true,
},
Expand Down Expand Up @@ -530,15 +531,33 @@ describe('defaultAction', () => {
});
});

it('should handle custom force include patterns', async () => {
const options: CliOptions = {
forceInclude: 'coverage/*.json,test-results/*.xml',
};

await runDefaultAction(['.'], process.cwd(), options);

expect(configLoader.mergeConfigs).toHaveBeenCalledWith(
process.cwd(),
expect.anything(),
expect.objectContaining({
forceInclude: ['coverage/*.json', 'test-results/*.xml'],
}),
);
});

it('should properly trim whitespace from comma-separated patterns', () => {
const options = {
include: 'src/**/*, tests/**/*, examples/**/*',
ignore: 'node_modules/**, dist/**, coverage/**',
forceInclude: 'coverage/*.json, test-results/*.xml',
};
const config = buildCliConfig(options);

expect(config.include).toEqual(['src/**/*', 'tests/**/*', 'examples/**/*']);
expect(config.ignore?.customPatterns).toEqual(['node_modules/**', 'dist/**', 'coverage/**']);
expect(config.forceInclude).toEqual(['coverage/*.json', 'test-results/*.xml']);
});

describe('files flag', () => {
Expand Down
2 changes: 2 additions & 0 deletions tests/cli/cliRun.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ describe('cliRun', () => {
},
},
include: [],
forceInclude: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down Expand Up @@ -126,6 +127,7 @@ describe('cliRun', () => {
},
},
include: [],
forceInclude: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down
2 changes: 2 additions & 0 deletions tests/config/configSchema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ describe('configSchema', () => {
},
},
include: [],
forceInclude: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down Expand Up @@ -177,6 +178,7 @@ describe('configSchema', () => {
},
},
include: ['**/*.js', '**/*.ts'],
forceInclude: ['**/*.js', '**/*.ts', '*.log'],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down
6 changes: 4 additions & 2 deletions tests/core/file/fileSearch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,11 +334,13 @@ node_modules
'root/subdir/ignored.js',
];

vi.mocked(globby).mockResolvedValue(mockFileStructure);
// Update mock implementation to handle both the initial call and forceInclude call
vi.mocked(globby).mockImplementation(() => Promise.resolve(mockFileStructure));

const result = await searchFiles('/mock/root', mockConfig);

expect(result.filePaths).toEqual(mockFileStructure);
// Sort both arrays to ensure order doesn't matter for the test
expect(new Set(result.filePaths)).toEqual(new Set(mockFileStructure));
expect(result.filePaths).toContain('root/subdir/ignored.js');
expect(result.emptyDirPaths).toEqual([]);
});
Expand Down
3 changes: 3 additions & 0 deletions tests/core/metrics/diffTokenCount.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ index 123..456 100644
},
},
include: [],
forceInclude: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down Expand Up @@ -158,6 +159,7 @@ index 123..456 100644
},
},
include: [],
forceInclude: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down Expand Up @@ -235,6 +237,7 @@ index 123..456 100644
},
},
include: [],
forceInclude: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down
1 change: 1 addition & 0 deletions tests/core/treeSitter/parseFile.solidity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('Solidity File Parsing', () => {
},
},
include: [],
forceInclude: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down
1 change: 1 addition & 0 deletions tests/mcp/tools/packCodebaseTool.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ describe('PackCodebaseTool', () => {
},
cwd,
include: Array.isArray(opts.include) ? opts.include : opts.include ? [opts.include] : [],
forceInclude: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
Expand Down
1 change: 1 addition & 0 deletions tests/testing/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const createMockConfig = (config: DeepPartial<RepomixConfigMerged> = {}):
customPatterns: [...(defaultConfig.ignore.customPatterns || []), ...(config.ignore?.customPatterns || [])],
},
include: [...(defaultConfig.include || []), ...(config.include || [])],
forceInclude: [...(defaultConfig.forceInclude || []), ...(config.forceInclude || [])],
security: {
...defaultConfig.security,
...config.security,
Expand Down
4 changes: 4 additions & 0 deletions website/client/src/en/guide/command-line-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

## Filter Options
- `--include <patterns>`: Include patterns (comma-separated)
- `--force-include <patterns>`: Force include patterns that override ignore rules (comma-separated)
- `-i, --ignore <patterns>`: Ignore patterns (comma-separated)
- `--no-gitignore`: Disable .gitignore file usage
- `--no-default-patterns`: Disable default patterns
Expand Down Expand Up @@ -69,6 +70,9 @@ repomix --compress
# Process specific files
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# Force include specific files even if they're ignored
repomix --force-include "coverage/summary.json,test-results/junit.xml"

# Remote repository with branch
repomix --remote https://github.com/user/repo/tree/main

Expand Down
10 changes: 6 additions & 4 deletions website/client/src/en/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Here's an example of a complete configuration file (`repomix.config.json`):
}
},
"include": ["**/*"],
"forceInclude": ["coverage/summary.json", "test-results/junit.xml"],
"ignore": {
"useGitignore": true,
"useDefaultPatterns": true,
Expand Down Expand Up @@ -131,10 +132,11 @@ Command-line options take precedence over configuration file settings.

Repomix provides multiple ways to specify which files should be ignored. The patterns are processed in the following priority order:

1. CLI options (`--ignore`)
2. `.repomixignore` file in the project directory
3. `.gitignore` and `.git/info/exclude` (if `ignore.useGitignore` is true)
4. Default patterns (if `ignore.useDefaultPatterns` is true)
1. Force include patterns (`--force-include`)
2. CLI options (`--ignore`)
3. `.repomixignore` file in the project directory
4. `.gitignore` and `.git/info/exclude` (if `ignore.useGitignore` is true)
5. Default patterns (if `ignore.useDefaultPatterns` is true)

Example of `.repomixignore`:
```text
Expand Down
4 changes: 4 additions & 0 deletions website/client/src/ja/guide/command-line-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

## フィルターオプション
- `--include <patterns>`: 含めるパターン(カンマ区切り)
- `--force-include <patterns>`: 除外ルールを上書きして強制的に含めるパターン(カンマ区切り)
- `-i, --ignore <patterns>`: 除外パターン(カンマ区切り)
- `--no-gitignore`: .gitignoreファイルの使用を無効化
- `--no-default-patterns`: デフォルトパターンを無効化
Expand Down Expand Up @@ -69,6 +70,9 @@ repomix --compress
# 特定のファイルを処理
repomix --include "src/**/*.ts" --ignore "**/*.test.ts"

# 除外ルールを無視して特定のファイルを強制的に含める
repomix --force-include "coverage/summary.json,test-results/junit.xml"

# ブランチを指定したリモートリポジトリ
repomix --remote https://github.com/user/repo/tree/main

Expand Down
12 changes: 7 additions & 5 deletions website/client/src/ja/guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ repomix --init --global
}
},
"include": ["**/*"],
"forceInclude": ["coverage/summary.json", "test-results/junit.xml"],
"ignore": {
"useGitignore": true,
"useDefaultPatterns": true,
Expand Down Expand Up @@ -131,10 +132,11 @@ Repomixは以下の順序で設定ファイルを探します:

Repomixは複数の方法でファイルの除外を指定できます。パターンは以下の優先順位で処理されます:

1. CLIオプション(`--ignore`)
2. プロジェクトディレクトリの`.repomixignore`ファイル
3. `.gitignore`および`.git/info/exclude`(`ignore.useGitignore`がtrueの場合)
4. デフォルトパターン(`ignore.useDefaultPatterns`がtrueの場合)
1. 強制的に含めるパターン(`--force-include`)
2. CLIオプション(`--ignore`)
3. プロジェクトディレクトリの`.repomixignore`ファイル
4. `.gitignore`および`.git/info/exclude`(`ignore.useGitignore`がtrueの場合)
5. デフォルトパターン(`ignore.useDefaultPatterns`がtrueの場合)

`.repomixignore`の例:
```text
Expand Down Expand Up @@ -216,4 +218,4 @@ dist/**
- トークン数を削減したい場合
- コードの構造とロジックに集中したい場合

サポートされている言語と詳細な例については[コメント削除ガイド](comment-removal)をご覧ください。
サポートされている言語と詳細な例については[コメント削除ガイド](comment-removal)をご覧ください。