Skip to content

Exclude untracked dotfiles (e.g. .claude/) from generated patches - #36

Open
juanmaguitar wants to merge 12 commits into
trunkfrom
juanmaguitar/issue-19-claude-directory-picked
Open

Exclude untracked dotfiles (e.g. .claude/) from generated patches#36
juanmaguitar wants to merge 12 commits into
trunkfrom
juanmaguitar/issue-19-claude-directory-picked

Conversation

@juanmaguitar

@juanmaguitar juanmaguitar commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #19. Agent-tool config directories like .claude/ were showing up in patches generated by the app, because untracked files are staged and diffed regardless of what they are.

Per review feedback from @bacoords, exclusion is now gitignore-based rather than hardcoded: the app writes a default rule (.*, i.e. untracked dotfiles/dotdirs) into each site's .git/info/exclude, which isomorphic-git honors when building the status matrix.

A global gitignore wouldn't work here — isomorphic-git never reads core.excludesFile from ~/.gitconfig, and the app's target audience (first-time contributors) has no Git installed or configured at all. The per-repo exclude file gets the same effect with two advantages: advanced users can edit the rules per site (the app never rewrites the file once its marker line is present), and the file itself never shows up in generated patches.

Changes

  • Extracted patch generation from src/main.js into src/patch.js (no Electron dependency, so it's unit-testable).
  • Added ensureDefaultExcludes(): writes the default exclusion rules to .git/info/exclude at clone time and before each patch generation. Skipped once the marker line exists, so user edits survive.
  • Dotfiles already staged into the index by older app versions are unstaged (if ignored), so the exclusion applies to pre-existing sites too.
  • Tracked dotfiles (e.g. .gitignore) are unaffected — ignore rules only apply to untracked files.
  • Behavior change: deletions of tracked files now appear in generated patches. Previously a deleted file diffed as identical to itself and was silently dropped.
  • Added test/patch.test.cjs covering exclusion, tracked-dotfile edits, deletions, legacy staged dotfiles, and user overrides of the exclude rules.

Test plan

Automated

  • npm test passes (19/19, including 6 new tests in test/patch.test.cjs)
  • node --check src/main.js && node --check src/patch.js

Full end-to-end in the app

  • Verified on macOS against a real wordpress-develop clone.
  1. npm start
  2. Create a new site through the wizard, pointing it at an empty directory.
  3. Confirm the exclude file was written at clone time:
    cat <site>/.git/info/exclude
    
    Expect the # wp-contributor-toolkit defaults marker line followed by .*.
  4. Simulate a contributor's working directory — real work plus agent-tool droppings:
    mkdir -p <site>/.claude && echo '{}' > <site>/.claude/settings.local.json
    mkdir -p <site>/.cursor && echo 'rules' > <site>/.cursor/rules.md
    echo '<!-- test -->' >> <site>/README.md
    
  5. Generate a patch from the app. Expected: the patch contains only the README.md change; neither .claude/ nor .cursor/ appears.
  6. Advanced-user override: edit <site>/.git/info/exclude and replace .* with .claude/, keeping the marker line. Regenerate the patch. Expected: .cursor/rules.md now appears, .claude/ still doesn't — confirming the rules are user-controllable rather than hardcoded.
  7. Pre-existing sites (sites cloned by an older app version, where .claude/ was already staged into the index): generate a patch and confirm .claude/ is gone from it. To simulate on a fresh site, stage it manually first:
    node -e "const git=require('isomorphic-git');git.add({fs:require('fs'),dir:'<site>',filepath:'.claude/settings.local.json'})"
    

Note that steps 3-7 don't require the "Install npm dependencies" step to have succeeded — patch generation only needs the clone. That step is currently broken on trunk for an unrelated reason (Electron 32 bundles Node v20.18.1, while wordpress-develop's .npmrc sets engine-strict = true and its transitive dep @eslint/compat requires Node ^20.19.0 || ^22.13.0 || >=24). That's being tracked separately and is out of scope here.

Introduces createMinimalPatchForDir, which diffs the working tree
against origin/trunk via isomorphic-git and filters out untracked
dotfiles/dotdirs (e.g. .claude/, .cursor/) so agent tooling artifacts
never end up in a contributor's exported patch.
@juanmaguitar juanmaguitar changed the title Issue 19 claude directory picked Exclude untracked dotfiles (e.g. .claude/) from generated patches Jul 24, 2026
@juanmaguitar
juanmaguitar requested a review from Copilot July 24, 2026 10:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the app’s generated patch logic to avoid including untracked dotfiles/dot-directories (e.g. agent tool config like .claude/) while keeping already-tracked dotfiles (e.g. .gitignore) included. It also refactors patch generation into a standalone, unit-testable module and adds coverage for the new exclusion behavior.

Changes:

  • Extracted patch generation into src/patch.js and updated the Electron main process to call it.
  • Added isDotPath() filtering to skip untracked dotfile/dotdir paths during staging and when building the diff list.
  • Added test/patch.test.cjs to validate dotpath exclusion and tracked-dotfile modifications.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
src/patch.js New module containing the extracted patch generation logic plus dotpath filtering.
src/main.js Switches patch generation to use the new src/patch.js module.
test/patch.test.cjs Adds unit tests for dotpath exclusion and tracked dotfile modifications.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/patch.js
Comment thread test/patch.test.cjs
juanmaguitar and others added 2 commits July 24, 2026 11:26
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

test/patch.test.cjs:78

  • This test creates a temporary repo directory but doesn't clean it up afterwards. Wrapping the body in a try/finally and removing the directory helps avoid accumulating temp dirs over many test runs.
test('createMinimalPatchForDir still includes modifications to already-tracked dotfiles', async () => {
    const dir = await makeRepoWithTrunkCommit();
    fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules/\n');
    await git.add({ fs, dir, filepath: '.gitignore' });
    await git.commit({
        fs,
        dir,
        message: 'add .gitignore',
        author: { name: 'Test', email: 'test@example.com' },
    });
    await git.writeRef({
        fs,
        dir,
        ref: 'refs/remotes/origin/trunk',
        value: await git.resolveRef({ fs, dir, ref: 'HEAD' }),
        force: true,
    });

    fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules/\nbuild/\n');

    const patch = await createMinimalPatchForDir(dir);

    assert.match(patch, /\.gitignore/);
    assert.match(patch, /build\//);
});

test/patch.test.cjs:106

  • This test creates a temporary git repo but doesn't remove it after the assertions run. Cleaning up in a finally block keeps the test suite from leaving behind many patch-test-* directories in the temp folder.
test('createMinimalPatchForDir includes deletions of tracked files', async () => {
    const dir = await makeRepoWithTrunkCommit();

    // Create and commit a tracked file, and point origin/trunk at it.
    fs.writeFileSync(path.join(dir, 'delete-me.txt'), 'bye\n');
    await git.add({ fs, dir, filepath: 'delete-me.txt' });
    await git.commit({
        fs,
        dir,
        message: 'add file to delete',
        author: { name: 'Test', email: 'test@example.com' },
    });
    await git.writeRef({
        fs,
        dir,
        ref: 'refs/remotes/origin/trunk',
        value: await git.resolveRef({ fs, dir, ref: 'HEAD' }),
        force: true,
    });

    fs.rmSync(path.join(dir, 'delete-me.txt'));

    const patch = await createMinimalPatchForDir(dir);

    assert.match(patch, /delete-me\.txt/);
    assert.match(patch, /-bye/);
});

Comment thread test/patch.test.cjs
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

test/patch.test.cjs:60

  • This test creates a temporary repo directory but never removes it, which can leak temp files and cause flaky/slow CI over time. Consider registering cleanup via node:test's t.after() so the temp dir is removed even if assertions fail.
test('createMinimalPatchForDir still includes modifications to already-tracked dotfiles', async () => {
    const dir = await makeRepoWithTrunkCommit();
    fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules/\n');

test/patch.test.cjs:86

  • This test creates a temporary repo directory but never removes it, which can leak temp files and cause flaky/slow CI over time. Register cleanup via node:test's t.after() so the temp dir is removed even if the test throws or an assertion fails.
test('createMinimalPatchForDir includes deletions of tracked files', async () => {
    const dir = await makeRepoWithTrunkCommit();

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

Comment thread src/patch.js Outdated
Comment thread src/patch.js Outdated
Comment thread test/patch.test.cjs Outdated
juanmaguitar and others added 3 commits July 25, 2026 09:54
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

test/patch.test.cjs:65

  • This test creates a temp repo directory but never removes it. That can leak temp folders across test runs and eventually cause failures on CI machines with limited disk space.
test('createMinimalPatchForDir still includes modifications to already-tracked dotfiles', async () => {
    const dir = await makeRepoWithTrunkCommit();
    fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules/\n');
    await git.add({ fs, dir, filepath: '.gitignore' });
    await git.commit({

test/patch.test.cjs:89

  • This test creates a temp repo directory but doesn't clean it up. Using t.after() is a low-impact way to ensure cleanup even if an assertion fails.
test('createMinimalPatchForDir includes deletions of tracked files', async () => {
    const dir = await makeRepoWithTrunkCommit();

Comment thread src/patch.js Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

test/patch.test.cjs:63

  • This test creates a temp repo directory but never removes it, which can leak many patch-test-* directories across test runs. Add cleanup (e.g. t.after(...) or a try/finally) like the earlier test does.
test('createMinimalPatchForDir still includes modifications to already-tracked dotfiles', async () => {
    const dir = await makeRepoWithTrunkCommit();
    fs.writeFileSync(path.join(dir, '.gitignore'), 'node_modules/\n');

test/patch.test.cjs:89

  • This test creates a temp repo directory but never removes it, which can leak many patch-test-* directories across test runs. Add cleanup (e.g. t.after(...) or a try/finally) like the first test does.
test('createMinimalPatchForDir includes deletions of tracked files', async () => {
    const dir = await makeRepoWithTrunkCommit();

Comment thread src/patch.js Outdated
juanmaguitar and others added 2 commits July 27, 2026 10:20
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
A non-dot directory named claude/ is a real change and belongs in the
patch; only dotfiles/dotdirs are agent-tool config.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per review feedback, exclusion rules now live in each site's
.git/info/exclude (written at clone time and ensured before patch
generation), which isomorphic-git honors when walking the status
matrix. Advanced users can edit the rules per site; the app never
rewrites the file once its marker line is present. Dotfiles staged
into the index by older app versions are unstaged so the exclude
rules apply to existing sites too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Explains that new hidden files are left out of generated patches, at the
point in the walkthrough where the user generates one, and adds a
technical section on editing the rules per site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Claude directory picked up in diff

2 participants