fix: v5.2.8 — auto-untrack Kit artifacts on init/update#32
Conversation
- Add missing v5.2.4 CHANGELOG entry (squash-merge gap from PR #25) - Fix architecture.md module count math: "+ 14 more" → "+ 34 more" (Gemini finding) - Fix updater test: create .cursor dir for IDE detection, assert .cursor/commands/ (Gemini finding) - Update provenance header examples in ide-support.md: v5.2.0 → v5.2.7 - Remove hardcoded version from cross-ide-setup.md bridge description - Add getting-started.md sections: Gitignore Management, Worktree Support, Cross-IDE Bridge Generation 1018 tests passing (53 files).
Addresses a persistent class of failures where Kit files end up tracked in user repos despite being gitignored. Root cause: an agent or contributor runs `git add -A` before Kit's gitignore entries are in place, so bridge files (.cursor/commands/, .agent/, etc.) get committed. Once tracked, gitignore has no effect — previous Kit versions could only print a warning and ask the user to run `git rm -r --cached` manually. v5.2.8 closes that loop: - New `untrackKitArtifacts()` in lib/io.js actively removes known Kit paths from the git index while preserving working-tree files - `kit init` Step 4 replaces the two passive "warn if tracked" branches with a single auto-untrack call - `kit update` gains Step 4 (auto-untrack) between gitignore add and worktree regeneration - `KIT_TRACKED_ARTIFACTS` is the frozen source of truth for which paths are always bogus if tracked The artifact list also includes `dev/null/` — a Windows-specific literal path artifact created when someone runs `git config core.hooksPath dev/null` (without leading slash) and then `git lfs install`, producing a real `dev/null/` directory containing LFS hooks. These are never Kit files but they're always bogus and get cleaned up the same way. 10 new tests covering not-a-git-repo, no-op, single-category, full-sweep, non-Kit-file preservation, idempotency, and dev/null/ case. 1028 tests passing (54 files).
There was a problem hiding this comment.
Code Review
This pull request updates the Devran AI Kit to version 5.2.8, introducing a mechanism to automatically untrack Kit-generated artifacts that may have been accidentally committed to a user's git repository. The changes include a new untrackKitArtifacts function in lib/io.js, integration into kit init and kit update, and comprehensive unit tests. Feedback indicates that the untracking logic is currently too broad and could inadvertently remove user-created files or break "shared" mode configurations. It is recommended to refine the list of targeted artifacts and verify that files are explicitly ignored by git before attempting to untrack them.
| // Step 4: Auto-untrack Kit artifacts that were accidentally committed | ||
| // (e.g. via `git add -A` before gitignore was configured). Gitignore has | ||
| // no effect on already-tracked files, so we actively remove them from | ||
| // the git index here while keeping the working-tree copies. | ||
| try { | ||
| const { untrackKitArtifacts } = require('./io'); | ||
| const untrackResult = untrackKitArtifacts(targetRoot); | ||
| if (untrackResult.untracked.length > 0) { | ||
| report.untracked = untrackResult.untracked; | ||
| } | ||
| } catch (err) { | ||
| report.warnings = report.warnings || []; | ||
| report.warnings.push(`Kit artifact untrack failed: ${err.message}`); | ||
| } |
There was a problem hiding this comment.
The applyUpdate function calls untrackKitArtifacts (and addToGitignore previously) without checking if the project is in "shared" mode. For users who have intentionally opted into tracking the .agent/ directory (using kit init --shared), running kit update will now force these files into .gitignore and subsequently untrack them from the git index. This breaks the shared workflow during framework upgrades. The updater should detect if the project is in shared mode (e.g., by checking if .agent/ is currently tracked and not ignored) before attempting to modify the gitignore or untrack artifacts.
There was a problem hiding this comment.
Addressed in 4c40231. lib/updater.js now wraps Steps 1–4 (narrow → cleanup → addToGitignore → untrack) in an isSharedMode(targetRoot) check. The helper (new lib/io.js:isSharedMode) returns true when .agent/ is currently tracked AND not gitignored (requires git check-ignore --no-index so tracked paths aren't silently skipped). In shared mode the entire gitignore pipeline is a no-op and report.sharedMode is set instead. Verified end-to-end by the new CLI Updater — Shared Mode test in tests/unit/updater.test.js, which sets up a real git repo with .agent/ tracked + absent from .gitignore, runs applyUpdate, and asserts .gitignore is untouched and .agent/ files stay in the index.
| '.windsurf/workflows/', | ||
| '.github/prompts/', | ||
| // IDE config files | ||
| '.cursor/rules/kit-governance.mdc', | ||
| '.cursor/rules/', | ||
| '.opencode/opencode.json', | ||
| '.opencode/', | ||
| '.codex/', |
There was a problem hiding this comment.
Untracking entire directories such as .cursor/rules/, .github/prompts/, and .opencode/ is overly aggressive. These are standard directories used by their respective IDEs and may contain user-created configurations or prompts that are not managed by Kit. Automatically untracking the entire directory will remove these user files from the git index, forcing users to manually re-add them after every Kit update. It is safer to only untrack the specific files that Kit generates (e.g., .cursor/rules/kit-governance.mdc, .opencode/opencode.json) or to verify that the path is actually ignored by git before untracking.
| '.windsurf/workflows/', | |
| '.github/prompts/', | |
| // IDE config files | |
| '.cursor/rules/kit-governance.mdc', | |
| '.cursor/rules/', | |
| '.opencode/opencode.json', | |
| '.opencode/', | |
| '.codex/', | |
| '.windsurf/workflows/', | |
| // IDE config files | |
| '.cursor/rules/kit-governance.mdc', | |
| '.opencode/opencode.json', | |
| '.codex/config.toml', |
There was a problem hiding this comment.
Addressed in 4c40231. KIT_TRACKED_ARTIFACTS now lists only the specific Kit-written paths for IDE configs — .cursor/rules/kit-governance.mdc, .opencode/opencode.json, .codex/instructions.md — instead of the parent directories. Removed: .cursor/rules/, .opencode/, .codex/. Directory-level entries are retained only for dirs Kit fully owns (.agent/, .claude/commands/, .cursor/commands/, .opencode/commands/, .windsurf/workflows/, .github/prompts/) where every file is a regenerated bridge. New regression test leaves user-authored files under .cursor/rules/ alone (specific-path gate) verifies a user's my-custom-rule.mdc survives while kit-governance.mdc is untracked.
| for (const artifact of KIT_TRACKED_ARTIFACTS) { | ||
| // Ask git which files at this path are tracked (if any) |
There was a problem hiding this comment.
To prevent accidental untracking of intentionally tracked files (such as in shared mode or user-customized IDE configs), untrackKitArtifacts should verify that an artifact is actually ignored by git before attempting to remove it from the index. This ensures that Kit only cleans up files that the user (or Kit) has already marked as local-only via .gitignore.
| for (const artifact of KIT_TRACKED_ARTIFACTS) { | |
| // Ask git which files at this path are tracked (if any) | |
| for (const artifact of KIT_TRACKED_ARTIFACTS) { | |
| // Skip if not ignored (intentional tracking/shared mode) | |
| try { | |
| execSync(`git check-ignore --quiet -- "${artifact}"`, { | |
| cwd: projectRoot, stdio: ['ignore', 'pipe', 'ignore'], | |
| }); | |
| } catch { | |
| skipped.push(artifact); | |
| continue; | |
| } | |
| // Ask git which files at this path are tracked (if any) |
There was a problem hiding this comment.
Addressed in 4c40231. untrackKitArtifacts now runs git check-ignore --no-index -q -- <path> as the first gate for every artifact. If the path is not ignored, it goes into skipped[] and nothing is touched. The --no-index flag is load-bearing: without it git's default behavior is to skip any path already present in the index and return exit 1 regardless of .gitignore contents, which would have broken the untrack loop entirely (no tracked path would ever look "ignored"). Also switched the whole function from shell execSync to execFileSync with argument arrays and -- pathspec terminators while I was in there, so the artifact list has no injection surface even if it grows in the future. New test skips artifacts that are NOT listed in .gitignore (check-ignore gate) covers the shared-mode-like scenario where .agent/ is tracked without being gitignored.
…bjective-liskov
Closes Gemini review feedback on #32: 1. [HIGH] `kit update` now detects "shared mode" (user ran `kit init --shared`) by checking if `.agent/` is tracked but NOT gitignored. In shared mode, the entire gitignore pipeline (narrow → cleanup → add → untrack) is skipped so team workflows survive framework upgrades. New `isSharedMode(projectRoot)` helper in lib/io.js, gated short-circuit in lib/updater.js. 2. [MED] Trimmed KIT_TRACKED_ARTIFACTS: removed `.cursor/rules/`, `.opencode/`, `.codex/` directory entries that would have untracked user-authored configs. Now lists only the specific Kit-written paths: `.cursor/rules/kit-governance.mdc`, `.opencode/opencode.json`, `.codex/instructions.md`. 3. [MED] `untrackKitArtifacts` now gates each path on `git check-ignore --no-index -q` before touching it. Artifacts that are not explicitly gitignored are skipped — belt-and-suspenders protection for shared mode and any user-tracked files that happen to sit in a bridge directory. `--no-index` is required because git's default behavior is to skip tracked paths in check-ignore. Hardening (unrelated to Gemini but worth landing together): - Switched untrackKitArtifacts from shell `execSync` to `execFileSync` with argument arrays and `--` pathspec terminators, eliminating any command-injection surface for future artifact list changes. Tests: 1028 → 1037 (+9). New coverage: - untrackKitArtifacts: check-ignore gate skips non-ignored paths, specific-path gate leaves user `.cursor/rules/*.mdc` alone, trimmed KIT_TRACKED_ARTIFACTS export - isSharedMode: non-git, untracked, bug-scenario (tracked+ignored), shared-mode (tracked+not-ignored), non-absolute path - updater: shared-mode short-circuit verified end-to-end
Gemini review — all 3 comments addressed in 4c40231
Extra hardening (unrelated to Gemini but landed together):
Tests: 1028 → 1037 (+9)
Full suite: 1037/1037 passing locally. |
Summary
Closes a persistent class of failures where Kit files end up tracked in user repos despite being gitignored. Addresses the exact issue reported against deelmarkt on 2026-04-11 where
.cursor/commands/*.md,.claude/, anddev/null/got committed to branchclaude/mystifying-cohen.Root cause
An agent or contributor runs
git add -Abefore Kit's gitignore is configured → bridge files and.agent/get committed → gitignore later added → gitignore has no effect on already-tracked files → Kit files pollute the user's tracked tree forever.Previous Kit versions detected this and printed a warning asking the user to run
git rm -r --cachedmanually. v5.2.8 closes the loop by doing it automatically.Changes
untrackKitArtifacts(projectRoot)inlib/io.js— removes known Kit paths from the git index while keeping working-tree filesKIT_TRACKED_ARTIFACTSfrozen constant — single source of truth for artifact paths, includingdev/null/for the Windows literal-path bugkit initStep 4 — replaces two passive "warn if tracked" branches with a single auto-untrack callkit updateStep 4 — new step between gitignore add and worktree regenerationtests/unit/untrack-artifacts.test.jsWhy include
dev/null/?On Windows, running
git config core.hooksPath dev/null(without the leading slash — easy typo when trying to disable hooks) and thengit lfs installcreates a literaldev/null/directory containing the 4 LFS hook files. These are never Kit files but they're always bogus. Including them in the auto-untrack list means Kit now cleans up this Windows-specific artifact wherever it's found.Test plan
.cursor/commands/,.agent/,dev/null/, full-sweep, non-Kit-file preservation, idempotency, absolute-path guard, frozen constantnpx @devran-ai/kit@5.2.8 init --forceon deelmarkt main repo + every affected worktree to finalize cleanupRelated
.claude/,.cursor/commands/,dev/null/tracked)