Pass Project/Dispatch objects instead of project_root strings #657

Closed
opened 2026-09-23 21:37:09 +00:00 by jared · 4 comments
Owner

Problem

Blocked by #655.

Pipeline classes pass the repository root as a project_root string and build the same Project, db, and ids again at each step. In plugins/os-sdlc/lib, project_root occurs 54 times in 14 files (grep, 2026-09-23). Example: GateHandoff holds a live Dispatch and Implementation. It still hands ViolationRecorder four loose values (db, project_root:, dispatch_id:, implementation_id:). GateCommands and GoalpostCheck each load the project again from the string. The name Project#root is also ambiguous. It is the repository path, not a "root" of the target or the code. The user wants the names project.path, project.target.path, and project.code.path.

Run this ticket after #655. That ticket adds project.target.path.

Approach

Rename. The Project member root becomes path (absolute repository directory). Change the Project.new(root:) keyword, Project.load, Project.from_json, Config#project, and to_config_h (except(:path, …)). Update the callers: adapter/issue_source.rb:25, adapter/issue_source/tracker_resolver.rb:6,31, cli/project_config_command.rb:90, and lint/worklist_target.rb:24. The stored project_config JSON keeps no repository path. Its path key (from the earlier ticket) is the target path. Config#project(path) still takes the repository path as an argument, as it does today.

ViolationRecorder.new(project:, dispatch:) takes dispatch:, not dispatch: alone, because it also needs the repository path, and Implementation#project(root) needs a root argument and returns nil for an empty stored config: a dispatch alone cannot supply the repository path. db comes from dispatch.db and the implementation from dispatch.implementation.

At the CLI/intake edge, where no target is known yet, introduce a small Model::Repo value (path:, db:). dispatch_commands.rb builds a CompletionIntake::Request from a Model::Repo; completion_intake.rb opens Model::Db.for(request.project_root) from it. The dependency order there is: repository path → db → dispatch → implementation → target config → Project.

Sites with a live dispatch or implementation take project:/dispatch: directly, since the object is already live or built one step earlier:

 # gate_handoff.rb — builds the project once
def project = @project ||= Model::Project::Source.for(implementation, target: implementation.target).project(project_root)
GateCommands.new(project: project, implementation: implementation)
ViolationRecorder.new(project: project, dispatch: dispatch)

 # violation_recorder.rb
def initialize(project:, dispatch:)       # db = dispatch.db; implementation = dispatch.implementation
SourceSnippet.read(project.path, file, line)

 # completion_intake.rb — builds the project once from request.project_root
ObjectionIntake.new(dispatch:, node:, content:, project: project)   # project may be nil
GoalpostCheck.new(implementation, project)                          # nil project → never moved

 # brief_lookup.rb, start_subagent.rb
BriefFacts.new(implementation, tracker:, db:, project: project)     # nil project → code/test facts nil

Dispatch is a Sequel::Model (Model::Base = Class.new(Sequel::Model)). dispatch.db therefore gives the same database that Model::Db.for(project_root) opened. GateCommands uses project.path for Adapter::Git::Repo.new and for the existing-test check. BriefFacts drops Options#project_root and Options#target. It uses project.path for the repo.

These sites keep a path string. Each one is an edge that builds the models, or a pure path function:

Site Why it keeps a string
dispatch_commands.rb:68 subagent_stop(root, …) CLI edge. The hook event gives only cwd.
completion_intake.rb:12,21 Request#project_root Intake boundary. It opens the db and builds the project from the string.
completion_intake.rb:101, agent_transcript_path.rb Pure path function. It encodes a directory name as Claude Code's transcript folder.
gate_handoff.rb:8-9 GateHandoff.call(project_root, dispatch_id) CLI edge. It opens the db and fetches the dispatch. The instance keeps project_root only to build project once.
gate_handoff.rb:50,82 GateVerdict root:, ArtifactDir.new They take project.path. Their own signatures do not change.
source_snippet.rb Pure file-read utility.
brief_lookup.rb @root, start_subagent.rb @root CLI edge objects. They open the db from the directory and now also build the project.
lint/rubocop_report_fetcher.rb, lint/worklist_rubocop_config.rb Lint slice. lint.rb:37 and autocorrect_prepass/pass.rb:21 call them with a config-source directory that need not hold .sdlc/project.yaml.

The test-writer also updates tests/support/violation_scenario.rb to the new ViolationRecorder signature.

Check the Retired tests list again at implementation-open: the earlier ticket may rename or retire some of these files first.

Required work:

  1. Rename the Project member root to path and update its callers (proven by AC 1, AC 2)
  2. Give ViolationRecorder the signature (project:, dispatch:), reading db from dispatch.db and the implementation from dispatch.implementation, and build it from GateHandoff (proven by AC 3)
  3. Give GateCommands the signature (project:, implementation:) and share one project in GateHandoff (proven by AC 4)
  4. Pass a Project (or nil) into ObjectionIntake and GoalpostCheck from CompletionIntake (proven by AC 5, AC 6)
  5. Pass a Project (or nil) into BriefFacts from BriefLookup and StartSubagent (proven by AC 7, AC 8)
  6. Add Model::Repo (path:, db:) and thread it through dispatch_commands.rb → CompletionIntake::Request → completion_intake.rb's Model::Db.for(request.project_root) (proven by AC 9)
  7. Remove project_root from the converted classes — check: cd /home/jared/dev/cc-os/plugins/os-sdlc/lib/os_sdlc/pipeline && ! grep -n "project_root" violation_recorder.rb gate_commands.rb goalpost_check.rb objection_intake.rb brief_facts.rb
  8. Leave no Project#root caller — check: ! grep -rn "project\.root\b\|@project\.root\b" /home/jared/dev/cc-os/plugins/os-sdlc/lib /home/jared/dev/cc-os/plugins/os-sdlc/bin
  9. Run bin/refresh-plugins and append one History row to docs/implementation-status/os-sdlc.md — check: tail -n 1 /home/jared/dev/cc-os/docs/implementation-status/os-sdlc.md | grep -q "project.path"

Rejected: convert CompletionIntake::Request and the CLI verbs to take a Project. The hook and CLI get only a directory. Some paths have no project yet, and the intake must open the db first. Rejected: turn AgentTranscriptPath and SourceSnippet into Project methods. They are pure path utilities with no project state.

Acceptance criteria

  1. Given a .sdlc/project.yaml in directory D, when Project.load(D) runs, then project.path returns D and project does not respond to root.
  2. Given a project whose to_config_h is stored and rebuilt with Project.from_json(D2, json), when the rebuilt project is read, then project.path is D2 and project.target.path equals the original target path.
  3. Given a dispatch whose lint gate fails with one offense at lib/x.rb:3, when ViolationRecorder.new(project:, dispatch:).call(outcome, classification: :failed) runs, then one correction round is stored for dispatch.id, and its violation snippet is line 3 of <project.path>/lib/x.rb.
  4. Given a project with one changed code file and its mapped test, when GateCommands.new(project:, implementation:).to_h runs, then the diff-test command requires that test file, as it does today for the same fixture.
  5. Given a test-reviewer objection that names only tests/a_test.rb, and a project with no change to that file, when ObjectionIntake#record! runs with that project, then the dispatch is marked goalpost-moved.
  6. Given the same objection, when ObjectionIntake#record! runs with project: nil, then the dispatch is not marked goalpost-moved.
  7. Given an implementation with a changed code file, when BriefFacts.new(implementation, tracker:, db:, project:).fact("code_changes") runs, then it returns a diff that contains that file.
  8. Given BriefFacts built with project: nil, when fact("code_changes") and fact("test_changes") run, then both return nil.
  9. Given a SubagentStop hook payload with cwd D, when DispatchCommands.subagent_stop_from_stdin runs, then it builds a Model::Repo.new(path: D, db:) and CompletionIntake opens the same db instance that Model::Db.for(D) returns.

Owner sketch

class OsSdlc::Model::Project            # Data member :path (was :root)
class OsSdlc::Model::Repo               # Data.define(:path, :db) — CLI/intake edge, no target known yet
class OsSdlc::Pipeline::ViolationRecorder
  def initialize(project:, dispatch:)
end
class OsSdlc::Pipeline::GateCommands
  def initialize(project:, implementation: nil)
end
class OsSdlc::Pipeline::GoalpostCheck
  def initialize(implementation, project)
end
class OsSdlc::Pipeline::ObjectionIntake
  def initialize(dispatch:, node:, content:, project: nil)
end
class OsSdlc::Pipeline::BriefFacts
  def initialize(implementation, tracker:, db: nil, project: nil)
end

Non-goals

  • WorklistTarget#root, GateVerdict root:, GateEvaluator root:, and the lsp_probe root_path names. They are separate objects. Rename them in a later ticket if needed.
  • CompletionIntake::Request#project_root and every CLI verb signature.

Retired tests

plugins/os-sdlc/tests/pipeline/gate_commands_test.rb
plugins/os-sdlc/tests/pipeline/gate_commands_diff_test_quoting_test.rb
plugins/os-sdlc/tests/pipeline/gate_commands_filters_deleted_test_path_test.rb
plugins/os-sdlc/tests/pipeline/gate_commands_implementation_project_test.rb
plugins/os-sdlc/tests/pipeline/gate_commands_retired_fallback_test.rb
plugins/os-sdlc/tests/pipeline/goalpost_check_test.rb
plugins/os-sdlc/tests/pipeline/objection_intake_test.rb
plugins/os-sdlc/tests/pipeline/brief_facts_bin_file_test.rb
plugins/os-sdlc/tests/pipeline/brief_facts_diff_facts_never_truncated_test.rb
plugins/os-sdlc/tests/pipeline/brief_facts_enrichment_facts_test.rb
plugins/os-sdlc/tests/pipeline/brief_facts_multi_target_test.rb
plugins/os-sdlc/tests/pipeline/brief_facts_project_source_test.rb
plugins/os-sdlc/tests/pipeline/brief_facts_unmapped_code_files_test.rb
plugins/os-sdlc/tests/model/project_load_test.rb#test_load_reads_name_and_derives_root_from_project_yaml_location
plugins/os-sdlc/tests/model/project_load_test.rb#test_load_walks_up_from_a_subdirectory_to_find_project_yaml
plugins/os-sdlc/tests/model/project_create_test.rb#test_save_tracker_rewrites_project_yaml_and_returns_an_updated_project
plugins/os-sdlc/tests/model/project_json_round_trip_test.rb#test_from_json_ignores_a_root_key_embedded_in_the_payload

Origin

  • Trigger: planning session d1aa78d1, follow-up to #655
  • Improvised this session: none
  • Chain: each pipeline step loads the project again ← steps receive a project_root string, not the live Project/Dispatch (gate_handoff.rb:54,64, goalpost_check.rb:30-34, brief_facts.rb:94-98) ← DESIGN (string-typed boundaries kept past the CLI edge)
  • Root candidate: this ticket
  • Where: plugins/os-sdlc/lib/os_sdlc/pipeline/gate_handoff.rb, violation_recorder.rb, gate_commands.rb, objection_intake.rb, goalpost_check.rb, brief_facts.rb, model/project.rb
  • Session: a5f43be9-5476-418e-99f0-2c4d0e5fa57d
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/a5f43be9-5476-418e-99f0-2c4d0e5fa57d.jsonl
## Problem Blocked by #655. Pipeline classes pass the repository root as a `project_root` string and build the same `Project`, db, and ids again at each step. In `plugins/os-sdlc/lib`, `project_root` occurs 54 times in 14 files (grep, 2026-09-23). Example: `GateHandoff` holds a live `Dispatch` and `Implementation`. It still hands `ViolationRecorder` four loose values (`db`, `project_root:`, `dispatch_id:`, `implementation_id:`). `GateCommands` and `GoalpostCheck` each load the project again from the string. The name `Project#root` is also ambiguous. It is the repository path, not a "root" of the target or the code. The user wants the names `project.path`, `project.target.path`, and `project.code.path`. Run this ticket after #655. That ticket adds `project.target.path`. ## Approach Rename. The `Project` member `root` becomes `path` (absolute repository directory). Change the `Project.new(root:)` keyword, `Project.load`, `Project.from_json`, `Config#project`, and `to_config_h` (`except(:path, …)`). Update the callers: `adapter/issue_source.rb:25`, `adapter/issue_source/tracker_resolver.rb:6,31`, `cli/project_config_command.rb:90`, and `lint/worklist_target.rb:24`. The stored project_config JSON keeps no repository path. Its `path` key (from the earlier ticket) is the target path. `Config#project(path)` still takes the repository path as an argument, as it does today. `ViolationRecorder.new(project:, dispatch:)` takes `dispatch:`, not `dispatch:` alone, because it also needs the repository path, and `Implementation#project(root)` needs a root argument and returns nil for an empty stored config: a dispatch alone cannot supply the repository path. `db` comes from `dispatch.db` and the implementation from `dispatch.implementation`. At the CLI/intake edge, where no target is known yet, introduce a small `Model::Repo` value (`path:`, `db:`). `dispatch_commands.rb` builds a `CompletionIntake::Request` from a `Model::Repo`; `completion_intake.rb` opens `Model::Db.for(request.project_root)` from it. The dependency order there is: repository path → db → dispatch → implementation → target config → `Project`. Sites with a live dispatch or implementation take `project:`/`dispatch:` directly, since the object is already live or built one step earlier: ```ruby # gate_handoff.rb — builds the project once def project = @project ||= Model::Project::Source.for(implementation, target: implementation.target).project(project_root) GateCommands.new(project: project, implementation: implementation) ViolationRecorder.new(project: project, dispatch: dispatch) # violation_recorder.rb def initialize(project:, dispatch:) # db = dispatch.db; implementation = dispatch.implementation SourceSnippet.read(project.path, file, line) # completion_intake.rb — builds the project once from request.project_root ObjectionIntake.new(dispatch:, node:, content:, project: project) # project may be nil GoalpostCheck.new(implementation, project) # nil project → never moved # brief_lookup.rb, start_subagent.rb BriefFacts.new(implementation, tracker:, db:, project: project) # nil project → code/test facts nil ``` `Dispatch` is a `Sequel::Model` (`Model::Base = Class.new(Sequel::Model)`). `dispatch.db` therefore gives the same database that `Model::Db.for(project_root)` opened. `GateCommands` uses `project.path` for `Adapter::Git::Repo.new` and for the existing-test check. `BriefFacts` drops `Options#project_root` and `Options#target`. It uses `project.path` for the repo. These sites keep a path string. Each one is an edge that builds the models, or a pure path function: | Site | Why it keeps a string | |---|---| | `dispatch_commands.rb:68` `subagent_stop(root, …)` | CLI edge. The hook event gives only `cwd`. | | `completion_intake.rb:12,21` `Request#project_root` | Intake boundary. It opens the db and builds the project from the string. | | `completion_intake.rb:101`, `agent_transcript_path.rb` | Pure path function. It encodes a directory name as Claude Code's transcript folder. | | `gate_handoff.rb:8-9` `GateHandoff.call(project_root, dispatch_id)` | CLI edge. It opens the db and fetches the dispatch. The instance keeps `project_root` only to build `project` once. | | `gate_handoff.rb:50,82` `GateVerdict root:`, `ArtifactDir.new` | They take `project.path`. Their own signatures do not change. | | `source_snippet.rb` | Pure file-read utility. | | `brief_lookup.rb` `@root`, `start_subagent.rb` `@root` | CLI edge objects. They open the db from the directory and now also build the project. | | `lint/rubocop_report_fetcher.rb`, `lint/worklist_rubocop_config.rb` | Lint slice. `lint.rb:37` and `autocorrect_prepass/pass.rb:21` call them with a config-source directory that need not hold `.sdlc/project.yaml`. | The test-writer also updates `tests/support/violation_scenario.rb` to the new `ViolationRecorder` signature. Check the Retired tests list again at implementation-open: the earlier ticket may rename or retire some of these files first. Required work: 1. Rename the `Project` member `root` to `path` and update its callers (proven by AC 1, AC 2) 2. Give `ViolationRecorder` the signature `(project:, dispatch:)`, reading `db` from `dispatch.db` and the implementation from `dispatch.implementation`, and build it from `GateHandoff` (proven by AC 3) 3. Give `GateCommands` the signature `(project:, implementation:)` and share one project in `GateHandoff` (proven by AC 4) 4. Pass a `Project` (or nil) into `ObjectionIntake` and `GoalpostCheck` from `CompletionIntake` (proven by AC 5, AC 6) 5. Pass a `Project` (or nil) into `BriefFacts` from `BriefLookup` and `StartSubagent` (proven by AC 7, AC 8) 6. Add `Model::Repo` (`path:`, `db:`) and thread it through `dispatch_commands.rb` → `CompletionIntake::Request` → `completion_intake.rb`'s `Model::Db.for(request.project_root)` (proven by AC 9) 7. Remove `project_root` from the converted classes — check: `cd /home/jared/dev/cc-os/plugins/os-sdlc/lib/os_sdlc/pipeline && ! grep -n "project_root" violation_recorder.rb gate_commands.rb goalpost_check.rb objection_intake.rb brief_facts.rb` 8. Leave no `Project#root` caller — check: `! grep -rn "project\.root\b\|@project\.root\b" /home/jared/dev/cc-os/plugins/os-sdlc/lib /home/jared/dev/cc-os/plugins/os-sdlc/bin` 9. Run `bin/refresh-plugins` and append one History row to `docs/implementation-status/os-sdlc.md` — check: `tail -n 1 /home/jared/dev/cc-os/docs/implementation-status/os-sdlc.md | grep -q "project.path"` Rejected: convert `CompletionIntake::Request` and the CLI verbs to take a `Project`. The hook and CLI get only a directory. Some paths have no project yet, and the intake must open the db first. Rejected: turn `AgentTranscriptPath` and `SourceSnippet` into `Project` methods. They are pure path utilities with no project state. ## Acceptance criteria 1. Given a `.sdlc/project.yaml` in directory D, when `Project.load(D)` runs, then `project.path` returns D and `project` does not respond to `root`. 2. Given a project whose `to_config_h` is stored and rebuilt with `Project.from_json(D2, json)`, when the rebuilt project is read, then `project.path` is D2 and `project.target.path` equals the original target path. 3. Given a dispatch whose lint gate fails with one offense at `lib/x.rb:3`, when `ViolationRecorder.new(project:, dispatch:).call(outcome, classification: :failed)` runs, then one correction round is stored for `dispatch.id`, and its violation snippet is line 3 of `<project.path>/lib/x.rb`. 4. Given a project with one changed code file and its mapped test, when `GateCommands.new(project:, implementation:).to_h` runs, then the diff-test command requires that test file, as it does today for the same fixture. 5. Given a test-reviewer objection that names only `tests/a_test.rb`, and a project with no change to that file, when `ObjectionIntake#record!` runs with that project, then the dispatch is marked goalpost-moved. 6. Given the same objection, when `ObjectionIntake#record!` runs with `project: nil`, then the dispatch is not marked goalpost-moved. 7. Given an implementation with a changed code file, when `BriefFacts.new(implementation, tracker:, db:, project:).fact("code_changes")` runs, then it returns a diff that contains that file. 8. Given `BriefFacts` built with `project: nil`, when `fact("code_changes")` and `fact("test_changes")` run, then both return nil. 9. Given a SubagentStop hook payload with `cwd` D, when `DispatchCommands.subagent_stop_from_stdin` runs, then it builds a `Model::Repo.new(path: D, db:)` and `CompletionIntake` opens the same db instance that `Model::Db.for(D)` returns. ## Owner sketch ```ruby class OsSdlc::Model::Project # Data member :path (was :root) class OsSdlc::Model::Repo # Data.define(:path, :db) — CLI/intake edge, no target known yet class OsSdlc::Pipeline::ViolationRecorder def initialize(project:, dispatch:) end class OsSdlc::Pipeline::GateCommands def initialize(project:, implementation: nil) end class OsSdlc::Pipeline::GoalpostCheck def initialize(implementation, project) end class OsSdlc::Pipeline::ObjectionIntake def initialize(dispatch:, node:, content:, project: nil) end class OsSdlc::Pipeline::BriefFacts def initialize(implementation, tracker:, db: nil, project: nil) end ``` ## Non-goals - `WorklistTarget#root`, `GateVerdict root:`, `GateEvaluator root:`, and the `lsp_probe` `root_path` names. They are separate objects. Rename them in a later ticket if needed. - `CompletionIntake::Request#project_root` and every CLI verb signature. ## Retired tests plugins/os-sdlc/tests/pipeline/gate_commands_test.rb plugins/os-sdlc/tests/pipeline/gate_commands_diff_test_quoting_test.rb plugins/os-sdlc/tests/pipeline/gate_commands_filters_deleted_test_path_test.rb plugins/os-sdlc/tests/pipeline/gate_commands_implementation_project_test.rb plugins/os-sdlc/tests/pipeline/gate_commands_retired_fallback_test.rb plugins/os-sdlc/tests/pipeline/goalpost_check_test.rb plugins/os-sdlc/tests/pipeline/objection_intake_test.rb plugins/os-sdlc/tests/pipeline/brief_facts_bin_file_test.rb plugins/os-sdlc/tests/pipeline/brief_facts_diff_facts_never_truncated_test.rb plugins/os-sdlc/tests/pipeline/brief_facts_enrichment_facts_test.rb plugins/os-sdlc/tests/pipeline/brief_facts_multi_target_test.rb plugins/os-sdlc/tests/pipeline/brief_facts_project_source_test.rb plugins/os-sdlc/tests/pipeline/brief_facts_unmapped_code_files_test.rb plugins/os-sdlc/tests/model/project_load_test.rb#test_load_reads_name_and_derives_root_from_project_yaml_location plugins/os-sdlc/tests/model/project_load_test.rb#test_load_walks_up_from_a_subdirectory_to_find_project_yaml plugins/os-sdlc/tests/model/project_create_test.rb#test_save_tracker_rewrites_project_yaml_and_returns_an_updated_project plugins/os-sdlc/tests/model/project_json_round_trip_test.rb#test_from_json_ignores_a_root_key_embedded_in_the_payload ## Origin - Trigger: planning session d1aa78d1, follow-up to #655 - Improvised this session: none - Chain: each pipeline step loads the project again ← steps receive a `project_root` string, not the live `Project`/`Dispatch` (`gate_handoff.rb:54,64`, `goalpost_check.rb:30-34`, `brief_facts.rb:94-98`) ← DESIGN (string-typed boundaries kept past the CLI edge) - Root candidate: this ticket - Where: `plugins/os-sdlc/lib/os_sdlc/pipeline/gate_handoff.rb`, `violation_recorder.rb`, `gate_commands.rb`, `objection_intake.rb`, `goalpost_check.rb`, `brief_facts.rb`, `model/project.rb` - Session: a5f43be9-5476-418e-99f0-2c4d0e5fa57d - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/a5f43be9-5476-418e-99f0-2c4d0e5fa57d.jsonl
Author
Owner

Work started on branch ticket-657 (session 557d47dd) via /os-sdlc:implement --target os-sdlc, after #655 merged (main 157da599) and #656 merged (3c48366d).

Work started on branch ticket-657 (session 557d47dd) via /os-sdlc:implement --target os-sdlc, after #655 merged (main 157da599) and #656 merged (3c48366d).
Author
Owner

Deviation: ticket open refused on a stale Retired tests entry

Problem. implementation-open refused the ticket:

ticket body missing: Retired tests (not found: plugins/os-sdlc/tests/pipeline/gate_commands_bin_fallback_test.rb)

#655 deleted that file (the bin_files fallback it tested no longer exists). The ticket's own Approach anticipated this: "Check the Retired tests list again at implementation-open."

Solution. Removed the one line from ## Retired tests via the Forgejo API; the other 17 entries exist on main 3c48366d. Re-opened as implementation 20. No code change, no pipeline defect: the check worked as designed and the ticket predates #655's merge.

## Deviation: ticket open refused on a stale Retired tests entry **Problem.** `implementation-open` refused the ticket: ``` ticket body missing: Retired tests (not found: plugins/os-sdlc/tests/pipeline/gate_commands_bin_fallback_test.rb) ``` #655 deleted that file (the `bin_files` fallback it tested no longer exists). The ticket's own Approach anticipated this: "Check the Retired tests list again at implementation-open." **Solution.** Removed the one line from `## Retired tests` via the Forgejo API; the other 17 entries exist on main 3c48366d. Re-opened as implementation 20. No code change, no pipeline defect: the check worked as designed and the ticket predates #655's merge.
Author
Owner

Deviation 2: stale tests outside the Retired list (#665)

Problem. After test-reviewer accepted the tests (round 3) and programmer implemented the rename, the diff-test gate stayed red on four items the programmer may not fix, because each is a test edit:

Test Why red
project_create_test.rb calls project.root, renamed to path
project_nested_structural_followthrough_test.rb asserts Data members include :root
changes_target_path_test.rb (2 tests, from #655) GateCommands.new(project) positional, now project: keyword
objection_intake_scenario.rb (AC5/AC6 fixture) builds objections as plain strings; Objection::Entry.from_raw needs {"objection","expected_action"} hashes

The programmer also kept a root: keyword alias on Project#initialize so six old fixtures would still construct.

Solution. A Sonnet agent fixed the four tests, converted the six Project.new(root:) fixtures to path:, and removed the alias so the constructor takes only path:. Commit cd86cad4. Suite after: 1382 runs, 3134 assertions, 0 failures (count is lower than 1419 because the ticket retired 17 tests of the old signatures). Both ticket grep checks (no project_root in the five converted classes, no project.root caller) pass. The pipeline re-enters at programmer-repair.

Pipeline defect. #665, as on #641 and #655. The fixture-format item is a latent defect the new tests exposed, not a pipeline defect.

## Deviation 2: stale tests outside the Retired list (#665) **Problem.** After test-reviewer accepted the tests (round 3) and programmer implemented the rename, the diff-test gate stayed red on four items the programmer may not fix, because each is a test edit: | Test | Why red | |---|---| | `project_create_test.rb` | calls `project.root`, renamed to `path` | | `project_nested_structural_followthrough_test.rb` | asserts Data members include `:root` | | `changes_target_path_test.rb` (2 tests, from #655) | `GateCommands.new(project)` positional, now `project:` keyword | | `objection_intake_scenario.rb` (AC5/AC6 fixture) | builds objections as plain strings; `Objection::Entry.from_raw` needs `{"objection","expected_action"}` hashes | The programmer also kept a `root:` keyword alias on `Project#initialize` so six old fixtures would still construct. **Solution.** A Sonnet agent fixed the four tests, converted the six `Project.new(root:)` fixtures to `path:`, and removed the alias so the constructor takes only `path:`. Commit cd86cad4. Suite after: 1382 runs, 3134 assertions, 0 failures (count is lower than 1419 because the ticket retired 17 tests of the old signatures). Both ticket grep checks (no `project_root` in the five converted classes, no `project.root` caller) pass. The pipeline re-enters at programmer-repair. **Pipeline defect.** #665, as on #641 and #655. The fixture-format item is a latent defect the new tests exposed, not a pipeline defect.
Author
Owner

Resolution

Done: Project member root renamed to path (Project.new(path:), load, from_json, Config#project, to_config_h) with callers updated; ViolationRecorder.new(project:, dispatch:) reads db and implementation from the dispatch; GateCommands.new(project:, implementation:) and GateHandoff share one project; CompletionIntake passes a Project or nil into ObjectionIntake and GoalpostCheck; BriefLookup and StartSubagent pass project: into BriefFacts; Model::Repo(path:, db:) threads from dispatch_commands.rb through CompletionIntake::Request; no project_root in the five converted classes and no Project#root caller under lib or bin; History row added; plugins refreshed.

Evidence: Pipeline implementation 20, 16 dispatches, implementation_complete (change-auditor, seam-designer, 3 test-writer/test-reviewer rounds, programmer + repair, behavior-verifier pass on all 9 ACs, suite-check pass x2, contract-auditor -> refactorer -> pass). Commits cd86cad4 (hand-finished stale tests, #665), 7c37c87c (implementation), merge commit on main 0fe1ee9a. Suites on main after merge: os-sdlc 1383 runs, 3137 assertions, 0 failures, 0 errors; os-backlog 244 runs, 632 assertions, 0 failures. Both ticket grep checks pass. Deviation write-ups: #657 (comment) (stale Retired tests entry at open) and #657 (comment) (stale tests outside the Retired list).

Follow-ups: Pipeline defect cited: #665 (no repair route for red test-file diffs; four stale tests plus six root: fixtures hand-finished here). HandRolledFetchTest#test_autocorrecting_an_in_memory_source_writes_no_file_to_disk is order-dependent (rubocop config loader chdir races a parallel Dir.pwd read; seen once after the merge, green on rerun): dropped, pre-existing flake unrelated to this ticket and already described in test_helper.rb's chdir lock note; no ticket-skeptic verdict sought. Non-goals in the ticket (WorklistTarget#root, GateVerdict root:, lsp_probe root_path) stay as named.

## Resolution **Done:** Project member root renamed to path (Project.new(path:), load, from_json, Config#project, to_config_h) with callers updated; ViolationRecorder.new(project:, dispatch:) reads db and implementation from the dispatch; GateCommands.new(project:, implementation:) and GateHandoff share one project; CompletionIntake passes a Project or nil into ObjectionIntake and GoalpostCheck; BriefLookup and StartSubagent pass project: into BriefFacts; Model::Repo(path:, db:) threads from dispatch_commands.rb through CompletionIntake::Request; no project_root in the five converted classes and no Project#root caller under lib or bin; History row added; plugins refreshed. **Evidence:** Pipeline implementation 20, 16 dispatches, implementation_complete (change-auditor, seam-designer, 3 test-writer/test-reviewer rounds, programmer + repair, behavior-verifier pass on all 9 ACs, suite-check pass x2, contract-auditor -> refactorer -> pass). Commits cd86cad4 (hand-finished stale tests, #665), 7c37c87c (implementation), merge commit on main 0fe1ee9a. Suites on main after merge: os-sdlc 1383 runs, 3137 assertions, 0 failures, 0 errors; os-backlog 244 runs, 632 assertions, 0 failures. Both ticket grep checks pass. Deviation write-ups: https://forgejo.swansoncloud.com/jared/cc-os/issues/657#issuecomment-7219 (stale Retired tests entry at open) and https://forgejo.swansoncloud.com/jared/cc-os/issues/657#issuecomment-7221 (stale tests outside the Retired list). **Follow-ups:** Pipeline defect cited: #665 (no repair route for red test-file diffs; four stale tests plus six root: fixtures hand-finished here). HandRolledFetchTest#test_autocorrecting_an_in_memory_source_writes_no_file_to_disk is order-dependent (rubocop config loader chdir races a parallel Dir.pwd read; seen once after the merge, green on rerun): dropped, pre-existing flake unrelated to this ticket and already described in test_helper.rb's chdir lock note; no ticket-skeptic verdict sought. Non-goals in the ticket (WorklistTarget#root, GateVerdict root:, lsp_probe root_path) stay as named.
jared closed this issue 2026-09-24 15:44:01 +00:00
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#657
No description provided.