Skip to content

Add .gitignore seeding to init for ephemeral .impeccable output#314

Open
spa5k wants to merge 2 commits into
pbakaus:mainfrom
spa5k:feat/init-gitignore
Open

Add .gitignore seeding to init for ephemeral .impeccable output#314
spa5k wants to merge 2 commits into
pbakaus:mainfrom
spa5k:feat/init-gitignore

Conversation

@spa5k

@spa5k spa5k commented Jun 27, 2026

Copy link
Copy Markdown

What

/impeccable init now seeds the project's shared, committed .gitignore so ephemeral .impeccable/ output never pollutes git status or gets committed by accident.

Why

.impeccable/ accumulates a lot of junk: critique/polish screenshots, live/*.png, per-session live/{sessions,previews,annotations,cache}/, runtime server.json, edit logs, config.local.json, hook.cache.json. The existing runtime helpers (ensureHookGitExcludes, ensureLiveGitIgnores) write to machine-local .git/info/exclude lazily and never cover screenshots, so a fresh clone sees all of it untracked. Init is the team-wide entry point, so it now writes one marked block to the committed .gitignore up front. Shared artifacts (config.json, live/config.json, design.json, critique/*.md) stay tracked.

Changes

  • skill/scripts/ensure-gitignore.mjs (new): idempotent marked-block writer for the repo-root .gitignore, with --check mode and tracked reporting.
  • skill/reference/init.md: new Step 7 runs it for git repos; wrap-up renumbered to Step 8.
  • tests/ensure-gitignore.test.mjs (new, 13 tests).

Validation

bun run build green; bun test tests/docs-integrity.test.js tests/build.test.js tests/ensure-gitignore.test.mjs — 25/25 pass.


Note

Low Risk
Changes are limited to init docs, a new optional .gitignore writer, and tests; no runtime auth or app logic is modified.

Overview
Init gains a new Step 7 that runs ensure-gitignore.mjs in git repos to idempotently add a marked # impeccable-ignore-start / # impeccable-ignore-end block to the repo-root .gitignore. The block ignores ephemeral .impeccable/ paths (screenshots, live session/preview/cache dirs, hook caches, config.local.json, etc.) using unanchored patterns so nested monorepos match, while shared files like config.json, live/config.json, design.json, and critique/*.md stay trackable.

The new script also classifies committed paths via git ls-files into tracked vs needsUntrack, so init can suggest git rm --cached for previously committed junk. Wrap-up moves to Step 8 (Step 2’s follow-up reference updated accordingly). 13 tests cover idempotent writes, stale-block refresh, monorepo paths, and CLI/--check behavior.

Reviewed by Cursor Bugbot for commit 894f66a. Bugbot is set up for automated code reviews on this repo. Configure here.

Init now runs ensure-gitignore.mjs to write a marked block to the shared, committed .gitignore so screenshots, live session/preview/cache dirs, hook caches, and per-dev config.local.json never pollute git status across the team. Shared artifacts (config.json, live/config.json, design.json, critique/*.md) stay tracked. Unlike the existing hook/live runtime helpers, which write machine-local .git/info/exclude lazily, this targets .gitignore at init time so every clone is covered up front.
@spa5k spa5k requested a review from pbakaus as a code owner June 27, 2026 07:37

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2c101f6. Configure here.

Comment thread skill/scripts/ensure-gitignore.mjs Outdated
Comment thread skill/scripts/ensure-gitignore.mjs Outdated
Cursor Bugbot on PR pbakaus#314 flagged two issues. (1) Patterns were root-anchored (/.impeccable/...) so they missed a nested monorepo .impeccable (apps/web/.impeccable/...); dropped the leading slash to match HOOK_LOCAL_IGNORE_PATTERNS / LIVE_IGNORE_PATTERNS. (2) detectTrackedArtifacts used fs.existsSync, reporting untracked/ignored files as committed; replaced with git ls-files based analyzeTracked that returns gitAvailable, tracked (confirmed shared artifacts), and needsUntrack (committed ephemeral files -> git rm --cached candidates). init Step 7 wording updated to match.
@greptile-apps

greptile-apps Bot commented Jun 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR seeds the committed .gitignore during /impeccable init so ephemeral .impeccable/ output (screenshots, live-session dirs, runtime JSON, per-dev overrides) is never left untracked after a fresh clone. It introduces a new idempotent script, a documentation step, and 13 tests.

  • skill/scripts/ensure-gitignore.mjs: writes a clearly marked block to the repo-root .gitignore, handles monorepo subdirectory invocation, reports already-committed ephemeral files via git ls-files, and exposes a --check mode that reports staleness without writing.
  • skill/reference/init.md: inserts Step 7 for git repos (run ensure-gitignore.mjs) and renumbers the old wrap-up to Step 8, updating the one cross-reference in Step 2.
  • tests/ensure-gitignore.test.mjs: covers block creation, idempotency, stale-block replacement, root resolution from a subdirectory, --check mode, CLI JSON output, and the analyzeTracked git classifier.

Confidence Score: 3/5

Safe to merge after addressing the missing error-handling in checkImpeccableGitignore; the write path is solid, but the check path can crash with a raw exception where callers expect JSON.

checkImpeccableGitignore has no try/catch, so any I/O error on .gitignore produces an unhandled exception from the --check CLI path instead of the documented { ok: false } JSON. ensureImpeccableGitignore handles this correctly, making the asymmetry a present defect. The stale-block replacement also only covers the first marker pair. The init.md change is clean; the missing bun run test:skill-behavior run is the only process gap there.

skill/scripts/ensure-gitignore.mjs — specifically the checkImpeccableGitignore function, which needs the same try/catch wrapper as its write-path counterpart.

Important Files Changed

Filename Overview
skill/scripts/ensure-gitignore.mjs New idempotent .gitignore block writer; checkImpeccableGitignore lacks the try/catch that ensureImpeccableGitignore has, breaking its JSON output contract on I/O errors. Also missing global-flag coverage for duplicate marker blocks.
skill/reference/init.md Adds Step 7 for .gitignore seeding in git repos; renumbers old Step 7 to Step 8 and updates the cross-reference. Content is clear and consistent. The required bun run test:skill-behavior suite for init.md changes was not mentioned in the PR validation.
tests/ensure-gitignore.test.mjs 13 well-structured tests covering idempotency, blank-separator logic, stale-block replacement, monorepo root resolution, --check mode, CLI JSON output, and the analyzeTracked classifier. No issues found.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[node ensure-gitignore.mjs] --> B{--check flag?}
    B -- yes --> C[checkImpeccableGitignore]
    B -- no --> D[ensureImpeccableGitignore]

    C --> E[resolveRepoRoot]
    D --> E

    E --> F{.git found?}
    F -- yes --> G[repo root]
    F -- no --> H[fallback: cwd]

    D --> J{marker block present?}
    J -- yes --> K[replace block in-place]
    J -- no --> L[append block with blank separator]
    K --> M[writeFileSync if changed]
    L --> M

    C --> N{.gitignore exists?}
    N -- no --> O[present:false stale:false]
    N -- yes --> P[match marker regex]
    P --> Q[present / stale flags]

    D --> R[analyzeTracked via git ls-files]
    C --> R
    R --> S{git available?}
    S -- no --> T[gitAvailable:false empty lists]
    S -- yes --> U[classify .impeccable/ files]
    U --> V[tracked: shared artifacts
needsUntrack: ephemeral files]

    M --> W[JSON output]
    O --> W
    Q --> W
    T --> W
    V --> W
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[node ensure-gitignore.mjs] --> B{--check flag?}
    B -- yes --> C[checkImpeccableGitignore]
    B -- no --> D[ensureImpeccableGitignore]

    C --> E[resolveRepoRoot]
    D --> E

    E --> F{.git found?}
    F -- yes --> G[repo root]
    F -- no --> H[fallback: cwd]

    D --> J{marker block present?}
    J -- yes --> K[replace block in-place]
    J -- no --> L[append block with blank separator]
    K --> M[writeFileSync if changed]
    L --> M

    C --> N{.gitignore exists?}
    N -- no --> O[present:false stale:false]
    N -- yes --> P[match marker regex]
    P --> Q[present / stale flags]

    D --> R[analyzeTracked via git ls-files]
    C --> R
    R --> S{git available?}
    S -- no --> T[gitAvailable:false empty lists]
    S -- yes --> U[classify .impeccable/ files]
    U --> V[tracked: shared artifacts
needsUntrack: ephemeral files]

    M --> W[JSON output]
    O --> W
    Q --> W
    T --> W
    V --> W
Loading

Fix All in Codex Fix All in Claude Code

Reviews (2): Last reviewed commit: "Fix: unanchored patterns + git-aware tra..." | Re-trigger Greptile

Comment thread skill/scripts/ensure-gitignore.mjs
Comment thread skill/reference/init.md
Comment on lines +51 to +56
'.impeccable/*.png',
'.impeccable/live/server.json',
'.impeccable/live/sessions/',
'.impeccable/live/previews/',
'.impeccable/live/annotations/',
'.impeccable/live/cache/',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 TRACKED_ARTIFACTS missing .impeccable/critique/ coverage

init.md Step 7 lists .impeccable/critique/*.md as a shared artifact that stays tracked, right alongside config.json, design.json, and live/config.json. TRACKED_ARTIFACTS omits critique files entirely, so the tracked report will never include them even if several critique reports are present and committed. Because critique/ is a glob, you can't use a single existsSync call, but a quick fs.readdirSync check on .impeccable/critique/ filtered to *.md would cover the gap and keep the tracked output consistent with what init.md promises.

Fix in Codex Fix in Claude Code

Comment thread skill/scripts/ensure-gitignore.mjs
Comment on lines +237 to +259
export function checkImpeccableGitignore(cwd = process.cwd()) {
const repoRoot = resolveRepoRoot(cwd);
const targetPath = path.join(repoRoot, '.gitignore');
if (!fs.existsSync(targetPath)) {
return {
ok: true,
file: path.relative(path.resolve(cwd), targetPath).split(path.sep).join('/') || '.gitignore',
present: false,
stale: false,
patterns: [...GITIGNORE_PATTERNS],
};
}
const existing = fs.readFileSync(targetPath, 'utf-8');
const re = markerRegex();
const match = existing.match(re);
return {
ok: true,
file: path.relative(path.resolve(cwd), targetPath).split(path.sep).join('/') || '.gitignore',
present: !!match,
stale: !!match && match[0] !== blockText(),
patterns: [...GITIGNORE_PATTERNS],
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 checkImpeccableGitignore can throw instead of returning { ok: false }

ensureImpeccableGitignore wraps its entire body in a try/catch that returns { ok: false, error, ... } on any I/O failure. checkImpeccableGitignore has no such guard: if .gitignore exists but is unreadable (e.g. a permissions problem), fs.readFileSync on line 249 throws, the CLI's --check path crashes with an unhandled exception, and the caller receives a Node stack trace instead of the documented JSON output. The asymmetry is especially visible in the CLI block, which spreads both results together — the write path can never crash JSON output, but the check path can.

Fix in Codex Fix in Claude Code

Comment thread skill/reference/init.md
Comment on lines +154 to 172
## Step 7: Keep ephemeral Impeccable output out of git

If the project is a git repo (a `.git` entry exists at or above the project root), seed the shared, committed `.gitignore` so screenshots, runtime state, and per-dev overrides never pollute `git status` or get committed by accident. This is team-wide hygiene: every clone benefits, and it covers artifacts (critique/polish PNGs, live sessions) that the runtime `.git/info/exclude` writers in the hook and live mode do not reach and only add lazily on first run.

Run `node {{scripts_path}}/ensure-gitignore.mjs`. It is idempotent: it writes one marked block (between `# impeccable-ignore-start` / `# impeccable-ignore-end`) to the repo-root `.gitignore`, preserving everything else in the file. The block ignores only ephemeral and machine-local paths under `.impeccable/` (for example `*.png`, `live/sessions/`, `live/previews/`, `config.local.json`, `hook.cache.json`); shared artifacts stay tracked:

- `.impeccable/config.json`: unified shared config
- `.impeccable/live/config.json`: framework wiring (Step 6)
- `.impeccable/design.json`: shared design spec
- `.impeccable/critique/*.md`: review reports

The script also prints two lists derived from `git ls-files`: `tracked` (shared artifacts that are confirmed committed and will stay tracked), and `needsUntrack` (ephemeral files like screenshots or `config.local.json` that were committed earlier and now match the new block). **Adding a `.gitignore` rule does not untrack a file that is already committed.** For each path in `needsUntrack`, offer `git rm --cached <path>` to stop tracking it without deleting the working copy. If `needsUntrack` is empty, just report that the ignore block is in place and nothing needs untracking. This step needs no consent: writing `.gitignore` is harmless, reversible, and the block is clearly marked as owned by Impeccable.

Skip silently (and say nothing) for non-git projects; there is no `.gitignore` to write.

## Step 8: Recommend starting points, then wrap up

Summarize tersely:
- Register captured (brand / product)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 bun run test:skill-behavior not mentioned for init.md changes

AGENTS.md specifies: "For changes to … any Setup-touching reference file (init.md, …), also run bun run test:skill-behavior." The PR description records only bun test tests/docs-integrity.test.js tests/build.test.js tests/ensure-gitignore.test.mjs (25/25). The skill-behavior suite spawns real LLMs against the source SKILL.md and asserts on the tool-call trace; it is the only test that catches an LLM failing to execute the new Step 7 or incorrectly re-numbering downstream steps. Adding a new init step is exactly the scenario that suite is designed to guard.

Context Used: AGENTS.md (source)

Fix in Codex Fix in Claude Code

Comment on lines +207 to +208
if (re.test(existing)) {
updated = existing.replace(re, block);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Stale-block replacement leaves a second block if the file contains two marker pairs

markerRegex() has no g flag and String.prototype.replace without a global regex replaces only the first match. If a .gitignore ever ends up with two impeccable-ignore-start … impeccable-ignore-end blocks (e.g., from a manual edit or a merge conflict), the call replaces the first block correctly but silently leaves the second. The test suite only covers the single-stale-block case.

Fix in Codex Fix in Claude Code

@abdulwahabone

Copy link
Copy Markdown
Contributor

Thanks for the PR @spa5k.
But adding this step on init is an overkill. A readme section with a copy-paste .gitignore block for ephemeral .impeccable/ stuff should enough.

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.

2 participants