Collapsed the duplicate gitignore negations an older CLI left behind - #30
Merged
Merged
Conversation
0.12.1 stopped `.gitignore` growing by one negation chain per alignment, but nothing removed the chains already written: a project initialized under 0.12.0 or earlier keeps one copy per run it made. Identical ignore lines decide nothing — Git applies the last match — so this is residue, and clearing it is safe by construction. Alignment now keeps only the last occurrence of each line of the chain, and only below the policy's own header: a line the project wrote before Intelligence touched the file is never rewritten, and the probe path is re-checked afterwards so a collapse that somehow changed what Git ignores is rolled back instead of committed. The rewrite is plain Bash rather than awk, which reads in text mode on Windows and would have rewritten a CRLF file as LF. Lines it does not remove keep their bytes, including a missing final newline.
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved CRLF handling, macOS test portability, and stale test-suite documentation findings remain.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR removes legacy duplicate .gitignore negation chains during CLI alignment, adds regression coverage, and synchronizes release artifacts for 0.16.1.
Changes:
- Added header-scoped duplicate collapsing with ignore-state verification.
- Added unit and end-to-end coverage.
- Updated versions, locks, examples, and changelog.
File summaries
| File | Summary / final review note |
|---|---|
intelligence.yaml |
Updated schema and sync package versions. |
intelligence.lock |
Updated engine and package pins. |
examples/with-remote-skills/intelligence.yaml |
Updated schema and sync pins. |
examples/platform-with-submodules/intelligence.yaml |
Updated schema and sync pins. |
examples/platform-with-submodules/intelligence.lock |
Updated engine and package pins. |
examples/go-api/intelligence.yaml |
Updated schema and sync pins. |
examples/go-api/intelligence.lock |
Updated engine and package pins. |
examples/go-api-with-pi-and-codex/intelligence.yaml |
Updated schema and sync pins. |
examples/go-api-with-pi-and-codex/intelligence.lock |
Updated engine and package pins. |
examples/go-api-with-opencode/intelligence.yaml |
Updated schema and sync pins. |
examples/go-api-with-opencode/intelligence.lock |
Updated engine and package pins. |
examples/go-api-with-antigravity/intelligence.yaml |
Updated schema and sync pins. |
examples/go-api-with-antigravity/intelligence.lock |
Updated engine and package pins. |
examples/dotnet-api-with-react-frontend/intelligence.yaml |
Updated schema and sync pins. |
examples/dotnet-api-with-react-frontend/intelligence.lock |
Updated engine and package pins. |
examples/cli-project/intelligence.yaml |
Updated schema and sync pins. |
engine/VERSION |
Bumped the release version to 0.16.1. |
cli/tests/verify.sh |
Registered the new unit suite. Nit (1 vote): docs/cli.md:460 has a stale test-suite list. |
cli/tests/unit-gitignore.sh |
Added .gitignore collapse, CRLF, and newline-preservation coverage. |
cli/tests/e2e-negative.sh |
Added legacy-residue alignment coverage. Critical (3 votes): the GNU-specific sed insertion fails with macOS BSD sed. |
cli/lib/gitignore.sh |
Added duplicate collapse and rollback logic. Moderate (3 votes): the public path can append LF copies to CRLF files instead of preserving line endings. |
CHANGELOG.md |
Documented the fix in 0.16.1. |
Review details
Suppressed comments (1)
cli/tests/verify.sh:24
- Adding
unit-gitignoreto the runner makesdocs/cli.md:460stale: its enumeratedtestsscope still claims the runner executes a list that omits this suite. Please update that documented suite list in the same change so the new verification gate is discoverable and the documentation matches the runner.
SUITES=(unit-semver unit-manifest unit-gitignore unit-release unit-fetch unit-upgrade e2e-sources e2e-packages e2e-lifecycle e2e-negative e2e-lock-validation e2e-compat)
- Files reviewed: 15/22 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
The new residue scenario used `sed -i.bak '1i <text>'`, which BSD sed on macOS rejects — the hand-written line was never added there, so CI asserted against a file the test had not actually built and reported the collapse as the failure. This file already had the portable pattern a few hundred lines up; it now uses it, and shell.md records the BSD userland trap next to the Bash 3.2 one.
A CR belongs to the line Git matches, and presence was tested with it attached: on Linux and macOS every existing entry looked absent, so alignment appended an LF copy of it on every run and a CRLF file grew forever — the growth 0.12.1 was supposed to have ended. MSYS hid it, because its grep reads in text mode and never sees a CR. Presence now matches with or without the CR, an append uses the ending the file already has, and the tail comparison ignores it. The header write and the publisher ignore files go through the same helpers. Line endings are held in variables rather than written as $'\r' where they are used: bash does not expand that form inside a command substitution, so the same expression silently stops stripping the CR depending on the caller. Two CR assertions in the new suite were false-green for exactly that reason and now count in bash, which is also the only way to see a CR under MSYS.
…where `grep -Fxc` answered differently per platform for the same file: under MSYS it reads in text mode and never sees a CR, while on Linux and macOS the CR is part of the line, so the CRLF assertions passed locally and reported 0 on both CI legs. The behaviour under test was already correct — only the measurement was platform-dependent. Counting now strips the CR the way the policy itself does, and shell.md records that anything deciding on a CR belongs in Bash rather than a text-mode tool.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #24.
0.12.1stopped.gitignoregrowing by one negation chain per alignment, but nothing removed the chains the defect had already written: a project initialized under0.12.0or earlier keeps one copy of!<dir>/per run it made. The issue measured five copies surviving three further alignments untouched.Clearing them is safe by construction — Git applies the last matching rule, so identical earlier copies of a line decide nothing. Alignment now keeps only the last occurrence of each line of the chain, and only below the policy's own header: a line the project wrote before Intelligence touched the file is never rewritten. Afterwards the include path is re-probed, and a collapse that somehow changed what Git ignores is rolled back rather than committed.
Measured on the issue's fixture (five
!.claude/, plus a hand-written copy in the project's own section above the header):The rewrite is plain Bash rather than awk. On Windows awk reads in text mode and handed back every line stripped of its CR, which would have rewritten a CRLF
.gitignoreas LF; a CR is significant to Git. Lines the collapse does not remove keep their bytes, a missing final newline included.Type of change
Public CLI check
init,sync,update,upgrade,package,source,adapter,status,registryintelligence syncVerification
bash cli/tests/verify.shreportsverify okand skipped nothing this change neededintelligence status --checksucceeds in the relevant fixture/projectintelligence syncproduces no unexpected diffVersioned artifacts
CHANGELOG.mdversion; there is no[Unreleased]section or duplicated release dateengine/VERSIONrelease state was checked: append while pending, or select the next SemVer after publicationschema_versionand exact@ainova-systems/syncpin changed with itNotes for reviewers
Version.
v0.16.0is published, so this selects0.16.1and movesengine/VERSION, every example'sschema_versionand every exact sync pin with it.Why the header is the boundary. Everything this policy appends lands after its header line, so that line is also the limit of what it may rewrite. A hand-written
!.claude/above it stays exactly where the project put it — the fixture in both suites asserts this.Why not awk. The first draft used a two-pass awk and turned a CRLF fixture into LF. Bash
read/printfround-trips the bytes, stays inside the Bash 3.2 floorshell.mdrecords (indexed arrays and arithmeticfor, no associative arrays), and the file is small enough that the loop costs nothing measurable.Coverage. New
cli/tests/unit-gitignore.sh(registered inSUITES) covers the collapse itself: residue onto the last occurrence, the hand-written line above the header, a file with nothing to collapse left byte-identical, a file without the header left alone, CRLF endings, a missing final newline, and the full repair converging throughgitignore_add_effective_include.e2e-negativegains the issue's own scenario end to end throughinit --no-sync, next to the existing idempotence assertions. Againstmainthat repair case leaves 3 copies where the fix leaves 1.No adapter changes, and no behavior change for a project that never ran
0.12.0— its file has one copy already, and the collapse then writes nothing.Deployment notes
None.