Skip to content

docs(local-paths): document the supported fork-CI route for the declaration file - #4154

Open
L4XB wants to merge 1 commit into
career-ops-hq:mainfrom
L4XB:fix/4128-document-fork-ci-local-paths
Open

docs(local-paths): document the supported fork-CI route for the declaration file#4154
L4XB wants to merge 1 commit into
career-ops-hq:mainfrom
L4XB:fix/4128-document-fork-ci-local-paths

Conversation

@L4XB

@L4XB L4XB commented Sep 13, 2026

Copy link
Copy Markdown

Fixes #4128.

The bind

A fork that runs the suite in CI is caught between two guards:

Committing it fails the second. Not committing it fails the first. DATA_CONTRACT.md (lines 62 and 79) and config/local-paths.example.txt both described the file as simply gitignored, so nothing pointed at a way out.

The route, and why it needs no behaviour change

There is a supported state that satisfies both, and it is the one the reporter found: append !config/local-paths.txt to the fork's own .gitignore and commit the file. It is then tracked and not ignored — so the coverage guard sees it, and the untracked guard has nothing to flag, because the condition it tests is "ignored and tracked".

That is now written down at each place a reader lands: the shipped example carries the recipe (including that the negation must come after upstream's rule, since Git applies the last matching pattern), DATA_CONTRACT.md stops claiming the file is always gitignored, and the #2991 assertion in the coverage validator names it.

The alternative is deliberately not taken. Exempting the file inside user-layer-untracked.test.mjs would accept exactly the ignored-and-tracked state that guard exists to catch, for a file that holds fork-local paths, and it would do so for every fork rather than the ones that opted in. The negation makes the fork's intent explicit in the fork's own config, where it belongs.

Evidence

The route is pinned by a fixture, not by prose alone — with a control, so it cannot pass by testing nothing:

$ node tests/user-layer-untracked.test.mjs
  ✅ parsed 46 user-layer paths from DATA_CONTRACT.md
  ✅ no ignored User Layer file remains tracked
  ✅ parses a User Layer section at end of file
  ✅ root wildcard finds tracked personal files without flagging a negated scaffold
  ✅ a fork that un-ignores config/local-paths.txt may commit it (#4128)
  ✅ committing it while it stays ignored is still flagged (#4128 control)

$ node test-all.mjs
📊 Results: 8726 passed, 1 failed, 16 warnings

Test count goes up by the two new assertions (8724 → 8726). The single failure (--status prerequisite/score handling wrong) is pre-existing and unrelated: main at bb641dc, in a clean worktree with the same dependencies, reports 8724 passed, 1 failed.

User impact

Forks that run CI can now commit config/local-paths.txt safely.

To pass validation:

  1. Add !config/local-paths.txt at the end of the fork’s .gitignore.
  2. Commit config/local-paths.txt.

The coverage guard can then inspect declared paths. The user-layer validation also accepts the file because it is tracked and not ignored.

The regression fixture verifies both the accepted configuration and the failure when the file remains ignored.

Files changed

  • DATA_CONTRACT.md:62,79: Documents the tracked and un-ignored configuration.
  • config/local-paths.example.txt:1-16: Documents the fork CI workflow.
  • validate-system-paths-coverage.mjs: self-test comment: Explains the required .gitignore rule and commit.
  • tests/user-layer-untracked.test.mjs: new fixture: Covers accepted and rejected configurations.

No validation logic changed.

The following requested system files were not changed: AGENTS.md, modes/, update-system.mjs, providers/, and .github/.

…ration file

A fork that runs the suite in CI was caught between two guards.
`validate-system-paths-coverage.mjs` needs `config/local-paths.txt` visible to
that checkout or every path declared in it is reported as an orphan (career-ops-hq#2991),
while `tests/user-layer-untracked.test.mjs` refuses a User Layer file that is
git-ignored AND tracked — the signature of a late ignore rule hiding an
already-committed personal file. Committing it fails the second; not committing
it fails the first. `DATA_CONTRACT.md` and the shipped example both described
the file as simply gitignored, so nothing pointed at a way out.

There is one, and it needs no behaviour change: a fork appends
`!config/local-paths.txt` to its own `.gitignore` and commits the file. It is
then tracked and NOT ignored, which both guards accept. That is now written down
where each guard sends the reader — the example file carries the recipe,
`DATA_CONTRACT.md` stops claiming the file is always gitignored, and the career-ops-hq#2991
assertion in the coverage validator names it.

Exempting the file inside `user-layer-untracked.test.mjs` was the alternative and
is deliberately not taken: the exemption would accept exactly the ignored-and-
tracked state the guard exists to catch, for a file that can hold fork-local
paths, and it would do so for every fork rather than the ones that opted in.

The route is pinned by a fixture rather than by prose alone: a repo that negates
the rule and commits the file reports no violation, and the same commit with the
negation removed is still flagged.

Fixes career-ops-hq#4128
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: fcb543ef-90f4-4878-9ca5-844c2b7823ac

📥 Commits

Reviewing files that changed from the base of the PR and between bb641dc and dfabbc9.

📒 Files selected for processing (4)
  • DATA_CONTRACT.md
  • config/local-paths.example.txt
  • tests/user-layer-untracked.test.mjs
  • validate-system-paths-coverage.mjs
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • career-ops-hq/career-ops-docs (manual)

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The PR documents a supported fork configuration for tracking config/local-paths.txt and adds a regression fixture for its ignore-state validation.

Changes

Local paths fork support

Layer / File(s) Summary
Declaration contract and fork guidance
DATA_CONTRACT.md, config/local-paths.example.txt, validate-system-paths-coverage.mjs
The contract and examples document the default ignore rule and the required trailing !config/local-paths.txt exception for tracked fork declarations. The self-test comment describes the same configuration.
Ignore-state regression fixture
tests/user-layer-untracked.test.mjs
A temporary Git repository test accepts a tracked, un-ignored declaration and rejects the same file when it remains ignored.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Other · Severity of issue fixed: Medium

Suggested reviewers: scott-emberson

Merge Risk: ⚪ Minimal · up to dfabb

The fork CI guidance and its regression coverage align with the existing validation contract; no actionable merge risk remains.

🚥 Pre-merge checks | ✅ 9
✅ Passed checks (9 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title uses the required docs prefix and local-paths scope. It accurately summarizes the documented fork-CI workflow for the declaration file.
Linked Issues check ✅ Passed Issue #4128 requires a supported fork-CI workflow. The documentation gives the trailing !config/local-paths.txt rule and commit steps in config/local-paths.example.txt:11-23. DATA_CONTRACT.md:69
Out of Scope Changes check ✅ Passed The changes stay within Issue #4128. They update the local-paths contract, add fork-CI guidance, document the workflow near the coverage assertion, and add focused regression coverage. The fixture and…
User Layer Untouched ✅ Passed PASS: The PR changes only DATA_CONTRACT.md, config/local-paths.example.txt, tests/user-layer-untracked.test.mjs, and validate-system-paths-coverage.mjs. None is one of the protected user-layer…
No Personal Data ✅ Passed PASS: The changed lines contain only technical documentation and synthetic test-fixture values. No real names paired with contact details, emails, phone numbers, or CV content appear in the diff. The …
Shipped File Registered ✅ Passed PASS: The reviewed range adds no new files. It modifies existing files only: DATA_CONTRACT.md:63, config/local-paths.example.txt:14-29, tests/user-layer-untracked.test.mjs:154-199, and `validate…
Provider Contract ✅ Passed No provider implementation changed: the authoritative PR diff contains only DATA_CONTRACT.md, config/local-paths.example.txt, tests/user-layer-untracked.test.mjs, and validate-system-paths-coverage.mj…
Agent-Operated Pr Disclosure ✅ Passed PASS: The PR is not identified as agent-operated. PR #4154 lists author L4XB and head branch fix/4128-document-fork-ci-local-paths, not app/copilot-swe-agent or copilot/*. Its labels are `🔴 co…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • 🛠️ register-shipped-file
  • 🛠️ provider-test-scaffold
  • 🛠️ sync-language-mode
🚀 Post-Merge Actions
  • localization drift report
  • documentation drift report

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Scott-Emberson Scott-Emberson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I reviewed the owned test tests/user-layer-untracked.test.mjs. The new fixture block is sound and mutation-sensitive, and it drives real code.

The added block (the #4128 fork-CI regression fixture) builds isolated real git repos in temp dirs (git init + git add) and runs the same parseUserLayerPaths / trackedIgnoredUserLayerFiles helpers the always-on block uses against the real DATA_CONTRACT.md User Layer table, no mocks or reconstruction. It asserts the invariant with literal expecteds: the negated case pins forkViolations.length === 0 (a fork may commit config/local-paths.txt), and the control case pins stillTracked.length === 1 && stillTracked[0] === 'config/local-paths.txt' (an ignored-and-tracked user-layer file is still flagged, which is the actual privacy-leak invariant). I mutation-checked both directions: dropping --ignored reddens the negated case, forcing an empty return reddens the #4128 control, green again on revert. No process.exit(, so test-all runs it, and the fixture is hermetic.

One thing worth knowing, and it is not introduced by this PR: the pre-existing always-on real-tree block in this same file is host-git-version dependent. On my machine (git 2.55.0.windows.3) it exits 1 flagging data/offers/.gitkeep and jds/.gitkeep, because git ls-files --ignored disagrees with git check-ignore on those directory-nested .gitkeep negations. Your CI is green on all three OSes and your new fixtures use single-file negation in isolated repos, so they are not affected. Flagging it only because it lives in the file you are touching: at some point the real-tree block would be more robust querying git check-ignore per candidate rather than leaning on ls-files --ignored, so it stops depending on the local git build.

Owned test sound, not a false-pass. The substance is DATA_CONTRACT.md and the coverage script, so the routing and merge decision is the maintainer's; I am speaking to the owned test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Committing config/local-paths.txt is required by the coverage guard (#2991) but fails user-layer-untracked

2 participants