diff --git a/README.md b/README.md index ff46a6aa6..c43f5c342 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,8 @@ As you run commands, Impeccable writes working files under `.impeccable/`: criti .impeccable/hook.cache.json .impeccable/hook.pending.json .impeccable/*.png +.impeccable/review/ +.impeccable/questions/ .impeccable/live/server.json .impeccable/live/sessions/ .impeccable/live/previews/ diff --git a/scripts/test-suites.mjs b/scripts/test-suites.mjs index af5463804..58ba4a2e1 100644 --- a/scripts/test-suites.mjs +++ b/scripts/test-suites.mjs @@ -73,6 +73,7 @@ export const SUITES = { 'tests/doctor.test.mjs', 'tests/staleness.test.mjs', 'tests/skill-reference.test.mjs', + 'tests/readme-gitignore.test.mjs', 'tests/target-args.test.mjs', 'tests/surface-brief.test.mjs', 'tests/template-extensions.test.mjs', diff --git a/tests/readme-gitignore.test.mjs b/tests/readme-gitignore.test.mjs new file mode 100644 index 000000000..5b0c2ddee --- /dev/null +++ b/tests/readme-gitignore.test.mjs @@ -0,0 +1,42 @@ +import { describe, it, after } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; +import { execFileSync, spawnSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; + +const ROOT = fileURLToPath(new URL('..', import.meta.url)); + +describe('README gitignore block', () => { + let tmp; + + after(() => { + if (tmp) rmSync(tmp, { recursive: true, force: true }); + }); + + it('ignores ephemeral review and questions dirs while keeping shared artifacts tracked', () => { + const readme = readFileSync(join(ROOT, 'README.md'), 'utf-8').replace(/\r\n?/g, '\n'); + const match = readme.match(/```gitignore\n([\s\S]*?)```/); + assert.ok(match, 'README.md should contain a fenced gitignore block'); + const block = match[1]; + assert.match(block, /# impeccable-ignore-start/); + + tmp = mkdtempSync(join(tmpdir(), 'impeccable-readme-gitignore-')); + writeFileSync(join(tmp, '.gitignore'), block); + execFileSync('git', ['init'], { cwd: tmp }); + + const ignored = execFileSync('git', [ + 'check-ignore', + '.impeccable/review/desktop.png', + '.impeccable/questions/fb63f8a6.log', + ], { cwd: tmp, encoding: 'utf-8' }); + assert.match(ignored, /\.impeccable\/review\/desktop\.png/); + assert.match(ignored, /\.impeccable\/questions\/fb63f8a6\.log/); + + for (const rel of ['.impeccable/config.json', '.impeccable/critique/report.md']) { + const result = spawnSync('git', ['check-ignore', rel], { cwd: tmp }); + assert.notEqual(result.status, 0, `${rel} should not be ignored`); + } + }); +});