Fix FastGate::ChangedFiles for a project root below the git top level #1689

Open
opened 2026-11-11 07:04:42 +00:00 by jared · 0 comments
Owner

Problem

fast-gate and its new-offense report give wrong results when the project root is a subdirectory of the git top level.
The Ruby class FastGate::ChangedFiles mixes two path bases: paths from the git top level and paths from the project root.
One form drops a tracked change without a message; the other form fails with HarnessError: git show lib/a.rb failed.
No test pins the subdirectory case.

Observed

The session ran both forms against a real temporary repository, with the project root in sub/ (transcript, piece 5 report, 2026-11-10):

Project root Form Tracked change base_source
Git top level either reported, same paths same content
Subdirectory tracked_from(git.found(root)) dropped silently: the top-relative path sub/lib/a.rb is joined to the root and no such file exists not reached
Subdirectory project.repo for both reported as lib/a.rb fails with HarnessError: git show lib/a.rb failed, because ref:path resolves from the top

Untracked files are reported in both forms.
The session wrote: "Both forms are defective for a subdirectory root, in different ways."
Both forms exist on main now:

  • plugins/os-sdlc/lib/os_sdlc/fast_gate/new_offense_report.rb:23: ChangedFiles.new(root, argv, options.base, git.at(root)).tracked_from(git.found(root)) (the first form).
  • plugins/os-sdlc/lib/os_sdlc/fast_gate/run.rb:29: ChangedFiles.new(root, target, base, @project.repo) (the second form; @top stays repo).
  • plugins/os-sdlc/lib/os_sdlc/fast_gate/changed_files.rb:22-25 (tracked_from), :33 (base_source reads @repo.read_at(base_commit, change.old_path)), :45-48 (tracked reads @top.changes(against: base_commit).tracked), and :29 (File.join(@root, change.path)).
    The session wrote: "no fast-gate test pins that case. This was already true with the shim."
    The pinning test on main is test_a_tracked_path_comes_from_the_repository_top_and_an_untracked_path_from_the_root; it covers only a root at the top.
    I read the code and the transcript table; I did not run the commands.

Reproduce

git init /tmp/fg && cd /tmp/fg && mkdir -p sub/lib && echo 'class A; end' > sub/lib/a.rb && git add -A && git commit -qm base && echo 'class A; def x; end; end' > sub/lib/a.rb
cd sub && /home/jared/dev/cc-os/plugins/os-sdlc/bin/fast-gate --target NAME lib/a.rb

I did not run this command form. Use a .sdlc/project.yaml under sub/ so that sub is the project root; the target name comes from that file.

Approach

Pick one path base for FastGate::ChangedFiles: convert every git path to a project-root-relative path in tracked, and convert it back to a git-top-relative path in base_source.
Remove tracked_from so that both callers use one form.

Required work:

  1. Remove the second path base — check: absent tracked_from in plugins/os-sdlc/lib/os_sdlc/fast_gate/*.rb

Acceptance criteria

  1. Given a project root in a subdirectory of the git top level and a tracked change to lib/a.rb, when fast-gate collects changes, then the change is reported as lib/a.rb.
  2. Given the same repository, when fast-gate reads the base source of that change, then it returns the content of sub/lib/a.rb at the base commit.
  3. Given a project root at the git top level, when fast-gate collects changes, then the paths and base sources are unchanged.

Owner sketch

class OsSdlc::FastGate::ChangedFiles
  def changes
  def base_source(change)
end

Origin

  • Trigger: the session compared the two ChangedFiles forms while it moved the git factory onto Project (commit 031244657).
  • Improvised this session: none
  • Chain: wrong changes from a subdirectory root ← ChangedFiles uses git-top paths for tracked and root paths for the rest (changed_files.rb:22-48) ← the shim form tracked_from kept a second base; no test covers a subdirectory root
  • Root candidate: none found (ticket-skeptic)
  • Where: plugins/os-sdlc/lib/os_sdlc/fast_gate/changed_files.rb
  • Session: a48658d5-b566-40ce-8257-ebeabf56f7e1
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os-plugins-os-sdlc/a48658d5-b566-40ce-8257-ebeabf56f7e1.jsonl

VERDICT: CREATE
REASON: The defect exists on main, was not caused by this session, and no test covers the case.

## Problem `fast-gate` and its new-offense report give wrong results when the project root is a subdirectory of the git top level. The Ruby class `FastGate::ChangedFiles` mixes two path bases: paths from the git top level and paths from the project root. One form drops a tracked change without a message; the other form fails with `HarnessError: git show lib/a.rb failed`. No test pins the subdirectory case. ## Observed The session ran both forms against a real temporary repository, with the project root in `sub/` (transcript, piece 5 report, 2026-11-10): | Project root | Form | Tracked change | `base_source` | |---|---|---|---| | Git top level | either | reported, same paths | same content | | Subdirectory | `tracked_from(git.found(root))` | dropped silently: the top-relative path `sub/lib/a.rb` is joined to the root and no such file exists | not reached | | Subdirectory | `project.repo` for both | reported as `lib/a.rb` | fails with `HarnessError: git show lib/a.rb failed`, because `ref:path` resolves from the top | Untracked files are reported in both forms. The session wrote: "Both forms are defective for a subdirectory root, in different ways." Both forms exist on `main` now: - `plugins/os-sdlc/lib/os_sdlc/fast_gate/new_offense_report.rb:23`: `ChangedFiles.new(root, argv, options.base, git.at(root)).tracked_from(git.found(root))` (the first form). - `plugins/os-sdlc/lib/os_sdlc/fast_gate/run.rb:29`: `ChangedFiles.new(root, target, base, @project.repo)` (the second form; `@top` stays `repo`). - `plugins/os-sdlc/lib/os_sdlc/fast_gate/changed_files.rb:22-25` (`tracked_from`), `:33` (`base_source` reads `@repo.read_at(base_commit, change.old_path)`), `:45-48` (`tracked` reads `@top.changes(against: base_commit).tracked`), and `:29` (`File.join(@root, change.path)`). The session wrote: "no fast-gate test pins that case. This was already true with the shim." The pinning test on `main` is `test_a_tracked_path_comes_from_the_repository_top_and_an_untracked_path_from_the_root`; it covers only a root at the top. I read the code and the transcript table; I did not run the commands. ## Reproduce ``` git init /tmp/fg && cd /tmp/fg && mkdir -p sub/lib && echo 'class A; end' > sub/lib/a.rb && git add -A && git commit -qm base && echo 'class A; def x; end; end' > sub/lib/a.rb cd sub && /home/jared/dev/cc-os/plugins/os-sdlc/bin/fast-gate --target NAME lib/a.rb ``` I did not run this command form. Use a `.sdlc/project.yaml` under `sub/` so that `sub` is the project root; the target name comes from that file. ## Approach Pick one path base for `FastGate::ChangedFiles`: convert every git path to a project-root-relative path in `tracked`, and convert it back to a git-top-relative path in `base_source`. Remove `tracked_from` so that both callers use one form. Required work: 1. Remove the second path base — check: absent `tracked_from` in `plugins/os-sdlc/lib/os_sdlc/fast_gate/*.rb` ## Acceptance criteria 1. Given a project root in a subdirectory of the git top level and a tracked change to `lib/a.rb`, when `fast-gate` collects changes, then the change is reported as `lib/a.rb`. 2. Given the same repository, when `fast-gate` reads the base source of that change, then it returns the content of `sub/lib/a.rb` at the base commit. 3. Given a project root at the git top level, when `fast-gate` collects changes, then the paths and base sources are unchanged. ## Owner sketch ```ruby class OsSdlc::FastGate::ChangedFiles def changes def base_source(change) end ``` ## Origin - Trigger: the session compared the two `ChangedFiles` forms while it moved the git factory onto `Project` (commit `031244657`). - Improvised this session: none - Chain: wrong changes from a subdirectory root ← `ChangedFiles` uses git-top paths for `tracked` and root paths for the rest (`changed_files.rb:22-48`) ← the shim form `tracked_from` kept a second base; no test covers a subdirectory root - Root candidate: none found (ticket-skeptic) - Where: plugins/os-sdlc/lib/os_sdlc/fast_gate/changed_files.rb - Session: a48658d5-b566-40ce-8257-ebeabf56f7e1 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os-plugins-os-sdlc/a48658d5-b566-40ce-8257-ebeabf56f7e1.jsonl VERDICT: CREATE REASON: The defect exists on main, was not caused by this session, and no test covers the case.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
jared/cc-os#1689
No description provided.