fix(skills): gitignore .env.local on the link/create path

SS
2mo ago
+130-42

Closes #289.

ai setup already gitignores .env.local through ensureLocalEnvIgnored(). link and create write credentials to the same file and never call it. This routes them through the same helper instead of adding a second implementation.

What changed

ensureLocalEnvIgnored and its isLocalEnvFile companion move from src/commands/ai/setup.ts into src/lib/env-writer.ts, next to upsertEnvFile. That pairs the two halves of the same job: upsertEnvFile writes the secret, ensureLocalEnvIgnored keeps it out of git.

The move is what makes the reuse possible. src/lib/ imports from src/commands/ nowhere in the tree today, so calling the helper from skills.ts at its old address would have inverted the layering. setup.ts imports it from the new location and re-exports it, so ./setup.js stays a valid import path and setup.test.ts needs no changes.

updateGitignore() in src/lib/skills.ts then calls ensureLocalEnvIgnored(process.cwd(), '.env.local') after its existing GITIGNORE_ENTRIES append. Two details there, both easy to get wrong:

  • The early return when no agent entries are missing became an if block. Without that, a project that already had all thirteen agent directories in .gitignore would skip the env check entirely, which is exactly the repeat-link case.
  • The two writes stay separate, under their own headers (# InsForge & AI agent skills and # Local environment secrets). That matches what a user who runs both link and ai setup already ends up with today.

updateGitignore is now exported. It was internal, and the behavior above is worth testing directly rather than through installSkills, which shells out to npx.

Why reuse instead of adding entries to GITIGNORE_ENTRIES

Adding .env.local to that array looked like the one-line version, and it is worse in two ways. updateGitignore matches with lines.has(entry), an exact match on trimmed lines, so a project whose .gitignore already says .env* or .env.* would get a redundant .env.local appended. And it would leave two different answers in the tree to the same question. ensureLocalEnvIgnored already checks .env*, .env.*, .env*.local, and .env.local before writing, and already refuses paths that resolve outside cwd.

Scope

Only .env*.local, which is what the helper already writes and what this CLI actually creates. Bare .env is left alone: some projects commit a non-secret one deliberately, and that call belongs to you rather than to this PR. Say the word if you want it included and I will add it here.

Tests

npm run lint passes: 830 tests, eslint clean, and npm run build succeeds.

The existing ensureLocalEnvIgnored coverage in src/commands/ai/setup.test.ts is untouched and still passing, which is the signal that the move is behavior-preserving. Four cases added to src/lib/skills.test.ts cover the new path:

  • .env.local is ignored alongside the agent directories
  • no second env pattern is added when .env* already covers the file
  • .env.local is still ignored when every agent entry is already present, the case the early return used to swallow
  • repeated runs are idempotent

Summary by cubic

Closes #289. link and create write credentials to .env.local but never gitignore it, unlike ai setup. This routes them through the same ensureLocalEnvIgnored helper so the file stays out of git.

  • Moves ensureLocalEnvIgnored and isLocalEnvFile to src/lib/env-writer.ts, next to the writer that creates the file; setup.ts re-exports them to keep imports valid.
  • updateGitignore() now calls the helper after appending agent entries, and its early return became an if so the env check runs even when all agent entries are already present — the repeat-link case.
  • Reuses the helper instead of adding .env.local to the entries list: the exact-match check would append a redundant entry when .env* or .env.* already covers it.
  • Leaves bare .env alone; only .env*.local files are handled.
  • Exports updateGitignore so the behavior is testable directly; tests verify the ignore rule, dedup, idempotency, and the all-agent-entries-present case.

Summary by CodeRabbit

  • Bug Fixes

    • Local environment files are now consistently protected from accidental Git commits.
    • Gitignore updates remain safe to run repeatedly without creating duplicate entries.
    • Environment-file protections are added even when other required Gitignore entries already exist.
  • Tests

    • Added coverage for Gitignore updates, including existing entries, missing entries, and repeated runs.