From 614012f38a588ca70a82d8f8069c0e25abc7f094 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 17 Mar 2026 17:27:34 -0400 Subject: [PATCH] fix: add git.manage_gitignore preference to opt out of .gitignore changes (#950) (#952) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When set to false in .gsd/preferences.md, GSD will not modify .gitignore at all — no baseline patterns added, no self-healing, no untracking. Usage in preferences.md: git: manage_gitignore: false Files changed: - git-service.ts: Add manage_gitignore to GitPreferences interface - gitignore.ts: Early return when manageGitignore is false - auto.ts: Pass manage_gitignore preference to ensureGitignore - preferences.ts: Parse and validate manage_gitignore in git config --- src/resources/extensions/gsd/auto.ts | 8 +++++--- src/resources/extensions/gsd/git-service.ts | 6 ++++++ src/resources/extensions/gsd/gitignore.ts | 5 ++++- src/resources/extensions/gsd/preferences.ts | 4 ++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/resources/extensions/gsd/auto.ts b/src/resources/extensions/gsd/auto.ts index a01cde834..e154e2b5f 100644 --- a/src/resources/extensions/gsd/auto.ts +++ b/src/resources/extensions/gsd/auto.ts @@ -688,9 +688,11 @@ export async function startAuto( } // Ensure .gitignore has baseline patterns - const commitDocs = loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs; - ensureGitignore(base, { commitDocs }); - untrackRuntimeFiles(base); + const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git; + const commitDocs = gitPrefs?.commit_docs; + const manageGitignore = gitPrefs?.manage_gitignore; + ensureGitignore(base, { commitDocs, manageGitignore }); + if (manageGitignore !== false) untrackRuntimeFiles(base); // Bootstrap .gsd/ if it doesn't exist const gsdDir = join(base, ".gsd"); diff --git a/src/resources/extensions/gsd/git-service.ts b/src/resources/extensions/gsd/git-service.ts index 8f30cc883..00c65c4dd 100644 --- a/src/resources/extensions/gsd/git-service.ts +++ b/src/resources/extensions/gsd/git-service.ts @@ -53,6 +53,12 @@ export interface GitPreferences { * Default: true (planning docs are tracked in git). */ commit_docs?: boolean; + /** When false, GSD will not modify .gitignore at all — no baseline patterns + * are added and no self-healing occurs. Use this if you manage your own + * .gitignore and don't want GSD touching it. + * Default: true (GSD ensures baseline patterns are present). + */ + manage_gitignore?: boolean; /** Script to run after a worktree is created (#597). * Receives SOURCE_DIR and WORKTREE_DIR as environment variables. * Can be an absolute path or relative to the project root. diff --git a/src/resources/extensions/gsd/gitignore.ts b/src/resources/extensions/gsd/gitignore.ts index f4ca898b3..1784365ae 100644 --- a/src/resources/extensions/gsd/gitignore.ts +++ b/src/resources/extensions/gsd/gitignore.ts @@ -85,7 +85,10 @@ const BASELINE_PATTERNS = [ * .gitignore instead of individual runtime patterns, keeping all GSD * artifacts local-only. */ -export function ensureGitignore(basePath: string, options?: { commitDocs?: boolean }): boolean { +export function ensureGitignore(basePath: string, options?: { commitDocs?: boolean; manageGitignore?: boolean }): boolean { + // If manage_gitignore is explicitly false, do not touch .gitignore at all + if (options?.manageGitignore === false) return false; + const gitignorePath = join(basePath, ".gitignore"); const commitDocs = options?.commitDocs !== false; // default true diff --git a/src/resources/extensions/gsd/preferences.ts b/src/resources/extensions/gsd/preferences.ts index 2d79976c5..a16819684 100644 --- a/src/resources/extensions/gsd/preferences.ts +++ b/src/resources/extensions/gsd/preferences.ts @@ -1329,6 +1329,10 @@ export function validatePreferences(preferences: GSDPreferences): { if (typeof g.commit_docs === "boolean") git.commit_docs = g.commit_docs; else errors.push("git.commit_docs must be a boolean"); } + if (g.manage_gitignore !== undefined) { + if (typeof g.manage_gitignore === "boolean") git.manage_gitignore = g.manage_gitignore; + else errors.push("git.manage_gitignore must be a boolean"); + } if (g.worktree_post_create !== undefined) { if (typeof g.worktree_post_create === "string" && g.worktree_post_create.trim()) { git.worktree_post_create = g.worktree_post_create.trim();