Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/clear-boats-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nanocollective/nanocoder": minor
---

Added support for a .nanocoderignore file, letting you hide tracked-but-unwanted files (lockfiles, .env, generated fixtures) from the AI even when .gitignore doesn't cover them. Thanks to @A-S-Manoj. Closes #755.
2 changes: 2 additions & 0 deletions docs/features/file-explorer.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,5 @@ Sometimes you want to browse your project and pick files visually rather than ty
```

The explorer respects your `.gitignore`, so you won't see `node_modules`, `dist`, or other ignored directories.

It also respects an optional `.nanocoderignore`. Use this for files that are tracked in git (and so aren't covered by `.gitignore`) but that you don't want the AI to read (large lockfiles like `package-lock.json`, generated fixtures, or sensitive files like `.env`). Patterns in `.nanocoderignore` use the same syntax as `.gitignore`.
59 changes: 59 additions & 0 deletions source/utils/gitignore-loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,65 @@ test.serial('loadGitignore works without .gitignore file', async t => {
}
});

test.serial('loadGitignore loads .nanocoderignore patterns', async t => {
const testDir = join(process.cwd(), 'test-nanocoderignore-temp');

try {
mkdirSync(testDir, {recursive: true});
writeFileSync(join(testDir, '.nanocoderignore'), '*.secret\ndata/\n');

const ig = loadGitignore(testDir);

t.true(ig.ignores('api.secret'), 'Should ignore .secret files');
t.true(ig.ignores('data/dump.csv'), 'Should ignore data/ directory');
t.false(ig.ignores('file.ts'), 'Should not ignore unrelated files');
} finally {
rmSync(testDir, {recursive: true, force: true});
}
});

test.serial(
'loadGitignore merges .gitignore and .nanocoderignore patterns',
async t => {
const testDir = join(process.cwd(), 'test-both-ignores-temp');

try {
mkdirSync(testDir, {recursive: true});
writeFileSync(join(testDir, '.gitignore'), '*.log\n');
writeFileSync(join(testDir, '.nanocoderignore'), '.env\npackage-lock.json\n');

const ig = loadGitignore(testDir);

t.true(ig.ignores('file.log'), 'Should ignore .gitignore patterns');
t.true(ig.ignores('.env'), 'Should ignore .nanocoderignore patterns');
t.true(
ig.ignores('package-lock.json'),
'Should ignore committed files listed in .nanocoderignore',
);
t.false(ig.ignores('file.ts'), 'Should not ignore unrelated files');
} finally {
rmSync(testDir, {recursive: true, force: true});
}
},
);

test.serial('loadGitignore works without .nanocoderignore file', async t => {
const testDir = join(process.cwd(), 'test-no-nanocoderignore-temp');

try {
mkdirSync(testDir, {recursive: true});
// No .nanocoderignore file

const ig = loadGitignore(testDir);

// Should still have default ignores and not throw
t.true(ig.ignores('node_modules/file.js'));
t.false(ig.ignores('src/file.ts'));
} finally {
rmSync(testDir, {recursive: true, force: true});
}
});
Comment on lines +129 to +144

test('loadGitignore ignores all language-specific directories', t => {
const ig = loadGitignore(process.cwd());

Expand Down
23 changes: 21 additions & 2 deletions source/utils/gitignore-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,22 @@ const DEFAULT_IGNORE_DIRS = [
];

/**
* Load and parse .gitignore file, returns an ignore instance.
* Load and parse .gitignore and .nanocoderignore files, returns an ignore instance.
* Always includes default ignore patterns for common directories.
*
* @param cwd - The current working directory to load .gitignore from
* .nanocoderignore is an additional, nanocoder-specific ignore file. It is useful
* for hiding files from the AI (to save tokens / avoid context bloat) even when
* those files are tracked in git and therefore not covered by .gitignore
* (e.g. package-lock.json, large fixtures, or sensitive files like .env that are
* intentionally committed).
*
* @param cwd - The current working directory to load .gitignore / .nanocoderignore from
* @returns An ignore instance configured with patterns
*/
export function loadGitignore(cwd: string): ReturnType<typeof ignore> {
const ig = ignore();
const gitignorePath = join(cwd, '.gitignore');
const nanocoderignorePath = join(cwd, '.nanocoderignore');
Comment on lines +50 to +56

// Always ignore common directories
ig.add(DEFAULT_IGNORE_DIRS);
Expand All @@ -62,6 +69,18 @@ export function loadGitignore(cwd: string): ReturnType<typeof ignore> {
}
}

// Load .nanocoderignore if it exists. Patterns are additive on top of
// .gitignore and the default ignores, not a replacement for them.
if (existsSync(nanocoderignorePath)) {
try {
const nanocoderignoreContent = readFileSync(nanocoderignorePath, 'utf-8');
ig.add(nanocoderignoreContent);
} catch {
// Silently fail if we can't read .nanocoderignore
// The gitignore + hardcoded ignores above will still apply
}
}

return ig;
}

Expand Down
Loading