Skip to content

fix: v5.2.8 — auto-untrack Kit artifacts on init/update#32

Merged
emredursun merged 4 commits into
mainfrom
claude/objective-liskov
Apr 11, 2026
Merged

fix: v5.2.8 — auto-untrack Kit artifacts on init/update#32
emredursun merged 4 commits into
mainfrom
claude/objective-liskov

Conversation

@emredursun
Copy link
Copy Markdown
Contributor

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/, and dev/null/ got committed to branch claude/mystifying-cohen.

Root cause

An agent or contributor runs git add -A before 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 --cached manually. v5.2.8 closes the loop by doing it automatically.

Changes

  • New untrackKitArtifacts(projectRoot) in lib/io.js — removes known Kit paths from the git index while keeping working-tree files
  • New KIT_TRACKED_ARTIFACTS frozen constant — single source of truth for artifact paths, including dev/null/ for the Windows literal-path bug
  • kit init Step 4 — replaces two passive "warn if tracked" branches with a single auto-untrack call
  • kit update Step 4 — new step between gitignore add and worktree regeneration
  • 10 new tests in tests/unit/untrack-artifacts.test.js

Why include dev/null/?

On Windows, running git config core.hooksPath dev/null (without the leading slash — easy typo when trying to disable hooks) and then git lfs install creates a literal dev/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

  • 1028 tests passing (54 files)
  • 10 new tests cover: not-a-git-repo, no-op, .cursor/commands/, .agent/, dev/null/, full-sweep, non-Kit-file preservation, idempotency, absolute-path guard, frozen constant
  • After merge: publish 5.2.8 to npm
  • After publish: run npx @devran-ai/kit@5.2.8 init --force on deelmarkt main repo + every affected worktree to finalize cleanup

Related

  • Reported against deelmarkt PR (file tree screenshot showed .claude/, .cursor/commands/, dev/null/ tracked)
  • Complements v5.2.7's gitignore pipeline by handling the "already tracked" case

- 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).
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

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.

Comment thread lib/updater.js Outdated
Comment on lines +280 to +293
// 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}`);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread lib/io.js Outdated
Comment on lines +341 to +348
'.windsurf/workflows/',
'.github/prompts/',
// IDE config files
'.cursor/rules/kit-governance.mdc',
'.cursor/rules/',
'.opencode/opencode.json',
'.opencode/',
'.codex/',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Suggested change
'.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',

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment thread lib/io.js Outdated
Comment on lines +392 to +393
for (const artifact of KIT_TRACKED_ARTIFACTS) {
// Ask git which files at this path are tracked (if any)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

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.

Suggested change
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)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

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
@emredursun
Copy link
Copy Markdown
Contributor Author

Gemini review — all 3 comments addressed in 4c40231

Severity Comment Fix
HIGH applyUpdate breaks kit init --shared workflow New isSharedMode(projectRoot) helper in lib/io.js; lib/updater.js short-circuits Steps 1–4 (narrow → cleanup → add → untrack) when .agent/ is tracked but not gitignored. report.sharedMode surfaces the decision.
MED Directory-level untrack of .cursor/rules/, .opencode/, .codex/ too aggressive KIT_TRACKED_ARTIFACTS now lists only specific Kit-written paths: .cursor/rules/kit-governance.mdc, .opencode/opencode.json, .codex/instructions.md. Directory entries retained only where Kit fully owns every file (.agent/, .*/commands/, .windsurf/workflows/, .github/prompts/).
MED Must verify gitignore status before untracking untrackKitArtifacts now runs git check-ignore --no-index -q -- <path> as the first gate per artifact. --no-index is load-bearing — default behavior is to skip tracked paths regardless of .gitignore.

Extra hardening (unrelated to Gemini but landed together):

  • untrackKitArtifacts + isSharedMode use execFileSync with argument arrays and -- pathspec terminators. Zero shell interpolation, zero injection surface even for future artifact list changes.

Tests: 1028 → 1037 (+9)

  • untrackKitArtifacts: check-ignore gate skips non-ignored paths, specific-path gate leaves user .cursor/rules/*.mdc alone, trimmed KIT_TRACKED_ARTIFACTS export invariants
  • isSharedMode: non-git, untracked, bug scenario (tracked+ignored → false), shared mode (tracked+not-ignored → true), non-absolute path
  • Updater: end-to-end shared-mode short-circuit — real git repo with .agent/ tracked and absent from .gitignore, verifies .gitignore is untouched and .agent/ stays in the index after applyUpdate

Full suite: 1037/1037 passing locally.

@emredursun emredursun merged commit 1f08c37 into main Apr 11, 2026
4 checks passed
@emredursun emredursun deleted the claude/objective-liskov branch April 11, 2026 01:37
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.

1 participant