Pass Project/Dispatch objects instead of project_root strings #657
Labels
No labels
P0
P1
P2
P3
bug
category/code-change
category/cross-repo
category/eval-run
category/investigation
category/no-test-target
category/owner-ruling
category/prose-artifact
category/recurring-chore
category/structure
category/umbrella
crc-refactor
create
delete
enhancement
filed-by/agent
filed-by/user
frozen
lint-rule
needs-info
needs-triage
next
plugin/cc-architect
plugin/os
plugin/os-adr
plugin/os-aidd-lint
plugin/os-backlog
plugin/os-context
plugin/os-doc-hygiene
plugin/os-sdlc
plugin/os-vault
project/cc-os
ready-for-agent
ready-for-human
recurring
review
rule-proposal
update
waiting
wayfinder:grilling
wayfinder:map
wayfinder:map
wayfinder:research
wayfinder:task
wayfinder:task
wontfix
worklist/deviations
worklist/lint-rule
worklist/new-implement-build
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
jared/cc-os#657
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Blocked by #655.
Pipeline classes pass the repository root as a
project_rootstring and build the sameProject, db, and ids again at each step. Inplugins/os-sdlc/lib,project_rootoccurs 54 times in 14 files (grep, 2026-09-23). Example:GateHandoffholds a liveDispatchandImplementation. It still handsViolationRecorderfour loose values (db,project_root:,dispatch_id:,implementation_id:).GateCommandsandGoalpostCheckeach load the project again from the string. The nameProject#rootis also ambiguous. It is the repository path, not a "root" of the target or the code. The user wants the namesproject.path,project.target.path, andproject.code.path.Run this ticket after #655. That ticket adds
project.target.path.Approach
Rename. The
Projectmemberrootbecomespath(absolute repository directory). Change theProject.new(root:)keyword,Project.load,Project.from_json,Config#project, andto_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, andlint/worklist_target.rb:24. The stored project_config JSON keeps no repository path. Itspathkey (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:)takesdispatch:, notdispatch:alone, because it also needs the repository path, andImplementation#project(root)needs a root argument and returns nil for an empty stored config: a dispatch alone cannot supply the repository path.dbcomes fromdispatch.dband the implementation fromdispatch.implementation.At the CLI/intake edge, where no target is known yet, introduce a small
Model::Repovalue (path:,db:).dispatch_commands.rbbuilds aCompletionIntake::Requestfrom aModel::Repo;completion_intake.rbopensModel::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:Dispatchis aSequel::Model(Model::Base = Class.new(Sequel::Model)).dispatch.dbtherefore gives the same database thatModel::Db.for(project_root)opened.GateCommandsusesproject.pathforAdapter::Git::Repo.newand for the existing-test check.BriefFactsdropsOptions#project_rootandOptions#target. It usesproject.pathfor the repo.These sites keep a path string. Each one is an edge that builds the models, or a pure path function:
dispatch_commands.rb:68subagent_stop(root, …)cwd.completion_intake.rb:12,21Request#project_rootcompletion_intake.rb:101,agent_transcript_path.rbgate_handoff.rb:8-9GateHandoff.call(project_root, dispatch_id)project_rootonly to buildprojectonce.gate_handoff.rb:50,82GateVerdict root:,ArtifactDir.newproject.path. Their own signatures do not change.source_snippet.rbbrief_lookup.rb@root,start_subagent.rb@rootlint/rubocop_report_fetcher.rb,lint/worklist_rubocop_config.rblint.rb:37andautocorrect_prepass/pass.rb:21call them with a config-source directory that need not hold.sdlc/project.yaml.The test-writer also updates
tests/support/violation_scenario.rbto the newViolationRecordersignature.Check the Retired tests list again at implementation-open: the earlier ticket may rename or retire some of these files first.
Required work:
Projectmemberroottopathand update its callers (proven by AC 1, AC 2)ViolationRecorderthe signature(project:, dispatch:), readingdbfromdispatch.dband the implementation fromdispatch.implementation, and build it fromGateHandoff(proven by AC 3)GateCommandsthe signature(project:, implementation:)and share one project inGateHandoff(proven by AC 4)Project(or nil) intoObjectionIntakeandGoalpostCheckfromCompletionIntake(proven by AC 5, AC 6)Project(or nil) intoBriefFactsfromBriefLookupandStartSubagent(proven by AC 7, AC 8)Model::Repo(path:,db:) and thread it throughdispatch_commands.rb→CompletionIntake::Request→completion_intake.rb'sModel::Db.for(request.project_root)(proven by AC 9)project_rootfrom 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.rbProject#rootcaller — 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/binbin/refresh-pluginsand append one History row todocs/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::Requestand the CLI verbs to take aProject. The hook and CLI get only a directory. Some paths have no project yet, and the intake must open the db first. Rejected: turnAgentTranscriptPathandSourceSnippetintoProjectmethods. They are pure path utilities with no project state.Acceptance criteria
.sdlc/project.yamlin directory D, whenProject.load(D)runs, thenproject.pathreturns D andprojectdoes not respond toroot.to_config_his stored and rebuilt withProject.from_json(D2, json), when the rebuilt project is read, thenproject.pathis D2 andproject.target.pathequals the original target path.lib/x.rb:3, whenViolationRecorder.new(project:, dispatch:).call(outcome, classification: :failed)runs, then one correction round is stored fordispatch.id, and its violation snippet is line 3 of<project.path>/lib/x.rb.GateCommands.new(project:, implementation:).to_hruns, then the diff-test command requires that test file, as it does today for the same fixture.tests/a_test.rb, and a project with no change to that file, whenObjectionIntake#record!runs with that project, then the dispatch is marked goalpost-moved.ObjectionIntake#record!runs withproject: nil, then the dispatch is not marked goalpost-moved.BriefFacts.new(implementation, tracker:, db:, project:).fact("code_changes")runs, then it returns a diff that contains that file.BriefFactsbuilt withproject: nil, whenfact("code_changes")andfact("test_changes")run, then both return nil.cwdD, whenDispatchCommands.subagent_stop_from_stdinruns, then it builds aModel::Repo.new(path: D, db:)andCompletionIntakeopens the same db instance thatModel::Db.for(D)returns.Owner sketch
Non-goals
WorklistTarget#root,GateVerdict root:,GateEvaluator root:, and thelsp_proberoot_pathnames. They are separate objects. Rename them in a later ticket if needed.CompletionIntake::Request#project_rootand 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
project_rootstring, not the liveProject/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)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.rbWork started on branch ticket-657 (session 557d47dd) via /os-sdlc:implement --target os-sdlc, after #655 merged (main
157da599) and #656 merged (3c48366d).Deviation: ticket open refused on a stale Retired tests entry
Problem.
implementation-openrefused the ticket:#655 deleted that file (the
bin_filesfallback 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 testsvia the Forgejo API; the other 17 entries exist on main3c48366d. Re-opened as implementation 20. No code change, no pipeline defect: the check worked as designed and the ticket predates #655's merge.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:
project_create_test.rbproject.root, renamed topathproject_nested_structural_followthrough_test.rb:rootchanges_target_path_test.rb(2 tests, from #655)GateCommands.new(project)positional, nowproject:keywordobjection_intake_scenario.rb(AC5/AC6 fixture)Objection::Entry.from_rawneeds{"objection","expected_action"}hashesThe programmer also kept a
root:keyword alias onProject#initializeso six old fixtures would still construct.Solution. A Sonnet agent fixed the four tests, converted the six
Project.new(root:)fixtures topath:, and removed the alias so the constructor takes onlypath:. Commitcd86cad4. 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 (noproject_rootin the five converted classes, noproject.rootcaller) 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.
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 main0fe1ee9a. 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.