code.path is the target path; no lib/ default #655
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#655
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
A target has two directory keys:
code_path(defaultlib) androot_path(ADR-0208, defaultFile.dirname(code_path)).code_pathnow only anchors test mirroring.root_pathsets the read scope. Two keys for one idea confuse agents and users. The Ruby-only rule inCodebase#code?also hides non-Ruby edits from the behavior-verifier. On #627 implementation 12 the verifier failed the increment becausecode_changeshad no hunk forplugins/os-sdlc/agents/wiring.md(#629).User decision (planning session d1aa78d1, reconfirmed 2026-09-23): a target's code directory IS the target directory.
project.code.pathis an alias of a newproject.target.path, for exampleplugins/os-sdlc, or.for a single-target project. There is nolib/default. Every pipeline role sees all code in the target, includingbin/and non-Ruby files. This supersedes ADR-0208 and closes #629.Approach
Record the decision first. Run
/os-adr:createfor an ADR that supersedes ADR-0208. It records: onepathper target,code_pathas a load-time alias,root_pathandCode#rootremoved, non-Ruby target files incode_changes, and the mirroring rule below.Model. Add
Project::Target = Data.define(:path)with default".".Projectholdstargetin place ofcode.Project#codereturnsProject::Code.new(path: target.path).Codelosesroot.Code#include?andCode#relativetreat"."as the whole repository (no"./"prefix).Config keys. The loader reads only the target key
path.code_pathandroot_pathare not read at all: unknown keys are ignored, with no alias, no conflict error, and no removal error.project.code.pathstays as a Ruby-only alias method ofproject.target.path. A flat, single-target.sdlc/project.yaml(notargets:map) defaultspathto"."when the key is absent. A named target undertargets:is different: if it has nopath:key,Project.loadstill defaults it to"."(so bare project loading never raises), butValidationtreats a named target's missingpath:as a problem, and implementation-open surfaces it as a hard failure before any pipeline role runs:target "<name>" has no path: key; add path: <target directory> to .sdlc/project.yaml (see /os-sdlc:setup-project). This is deliberately louder than the missing-directory problem (AC 18), because a named target with nopath:was previously silently scoped to the whole repository — the exact defect ADR-0208 introduced and this ticket removes. The stored implementation config blob (the JSON snapshot on an open implementation) also readspathonly; a blob without it loads with path"."and is not re-validated — this widens scope for in-flight implementations only, which is acceptable.Existing values. This change migrates the cc-os
.sdlc/project.yaml: every target getspath: plugins/<x>, and thecode_path/root_pathlines are removed. Other repos are out of scope: they keep whatever keys they have, and a multi-target repo there with nopath:keys loads every target with path"."(wider scope, no failure). Cross-project follow-up issues will be filed for cc-plugins and cc-plugins/api-wrapper.Classification.
Codebase#code?(path)is true for any file under the target that is not undertest_pathand has no segment inEXCLUDED_SEGMENTS. The Ruby check moves out ofcode?.Changes#code_pathsandChanges#code_difftherefore includeagents/*.md,skills/**,hooks/*, andbin/*.Changes#unmapped_code_pathskeeps only Ruby files (RubyFile.ruby?). A non-Ruby edit therefore appears incode_changesbut does not widen diff-test to the full suite. This keeps ADR-0208 D6's cost limit.Codebase#tests_forreturnsnilfor a non-Ruby file.Test mapping: S2, longest suffix plus scenario siblings. For a changed Ruby production file under the target, glob
<test_path>/**/<basename>_test.rband<test_path>/**/<basename>_*_test.rb. Only regular files count; a directory that happens to share the basename (for exampletests/evidence) never matches. For each hit, take its path relative totest_path, remove the_testsuffix, and for a scenario hit (the_*_test.rbglob) also remove the_<scenario>part, so the hit's directory plus<basename>.rbis what gets compared. Keep the hit only if that relative path is a path-segment suffix of the production file's path. Among the surviving hits, keep the exact hit or hits with the longest matching suffix, plus any scenario hits that sit in that same directory. No hit at all means unmapped, and the file falls back to the Ruby full-suite trigger (ADR-0208 D4).Measured 2026-09-23 over 9 targets in 4 repos: this rule never selects a different test than today's lib/app/src-strip rule, only supersets (the scenario siblings). On os-sdlc it maps 149 of 428 production files, against 142 today. Median 1 test per file, p90 2, max 20.
hooks/*.rbfiles now map to their real hook tests instead of falling back unmapped. Glob cost is about 0.5 ms per file; no index is needed.Lint worklist.
WorklistTarget#lint_pathis[File.join(root, target.path), *configured_lint_paths]. For"."it is[root, *configured_lint_paths].bin_files,conventional_test_dirs, androot_subtreeare deleted, because the target directory already contains them. RuboCop's own file discovery includes ruby-shebang files.Surface.
to_flat_h,to_config_h, andstatus_lineemitpath(path=plugins/os-sdlc) in place ofcode_pathandroot_path.Yaml::Document::FLAT_KEYSreads onlypath;code_pathandroot_pathare removed from the flat-key list, so a yaml file that still has them loads with those lines simply ignored.project-configaccepts--path PATHonly;--code-pathand--root-pathare removed.Validationreports a missing target directory aspath "<value>" does not exist for target <name>and dropsroot_path_problem.Prose. The charters and skills that name
root_pathorcode_pathsay "the target path (pathin.sdlc/project.yaml)". These are change-auditor, reviewer, seam-designer, test-writer, programmer, setup-project, responsibility-audit, code-probe SKILL.md, and cheatsheet.Required work:
/os-adr:create; set ADR-0208superseded-by— check:grep -l "supersedes: \[\?\"\?0208" /home/jared/dev/cc-os/docs/adr/*.mdProject::Target, removeCode#root, and makeProject#codealias the target path (proven by AC 1, AC 2)path(default"."for a flat project or a named target at load time), ignoringcode_pathandroot_path(proven by AC 3, AC 4, AC 5)pathloadable at"."(proven by AC 6)Codebase#code?to every file under the target; restrict the unmapped fallback to Ruby files (proven by AC 7, AC 8, AC 9)Test#covering(proven by AC 10, AC 11, AC 12, AC 13, AC 14)WorklistTarget#lint_pathto the target directory pluslint_paths(proven by AC 15)pathinto_flat_h,status_line, andproject-config --show; accept--path; drop--code-pathand--root-path(proven by AC 16, AC 17)pathkey (proven by AC 18)path:key, with the messagetarget "<name>" has no path: key; add path: <target directory> to .sdlc/project.yaml (see /os-sdlc:setup-project); a flat single-target project with nopath:key still opens, defaulted to"."(proven by AC 19, AC 20).sdlc/project.yamltopath:per target — check:cd /home/jared/dev/cc-os && ! grep -n "code_path\|root_path" .sdlc/project.yaml && plugins/os-sdlc/bin/os-sdlc project-config --show --target os-sdlc | grep -q "path=plugins/os-sdlc "root_path/code_pathwording in charters and skills — check:! grep -rn "root_path\|code_path" /home/jared/dev/cc-os/plugins/os-sdlc/agents /home/jared/dev/cc-os/plugins/os-sdlc/skillsbin/refresh-pluginsand append one History row about the target path todocs/implementation-status/os-sdlc.md— check:tail -n 1 /home/jared/dev/cc-os/docs/implementation-status/os-sdlc.md | grep -qi "target path"Rejected: keep
root_pathnext tocode_path(ADR-0208) — the user decided one directory per target. Rejected: make every target file a fallback trigger (ADR-0208 D6-B) — each.mdedit would run the full suite.Acceptance criteria
os-sdlcwithpath: plugins/os-sdlc, when the project loads, thenproject.target.pathandproject.code.pathboth returnplugins/os-sdlc..sdlc/project.yamlwith nopathkey, when the project loads, thenproject.code.pathreturns.andproject.code.include?("lib/widget.rb")is true.code_path: plugins/x/liband nopathkey, when the project loads, thenproject.target.pathreturns.(thecode_pathkey is ignored).path: plugins/xandcode_path: plugins/x/lib, when the project loads, then it loads without error andproject.target.pathreturnsplugins/x(thecode_pathkey is ignored).root_path: plugins/xand nopathkey, when the project loads, then it loads without error andproject.target.pathreturns.(theroot_pathkey is ignored).pathkey, whenimplementation.project(root)runs, then it returns a project withcode.path.and does not raise.plugins/os-sdlcand a changedplugins/os-sdlc/agents/wiring.md, when the behavior-verifier brief resolvescode_changes, then the diff contains a hunk forplugins/os-sdlc/agents/wiring.md.plugins/os-sdlcand only a changedplugins/os-sdlc/agents/wiring.md, when the diff-test gate command is built, then it is not the full-suite command.plugins/os-sdlcand a changedplugins/os-sdlc/bin/os-sdlc-runnerwith a ruby shebang, when the diff-test gate command is built, then it is the full-suite command.plugins/os-sdlcwith test pathplugins/os-sdlc/tests, whentests_for("plugins/os-sdlc/lib/os_sdlc/model/project/changes.rb")runs, then it returns["plugins/os-sdlc/tests/model/project/changes_test.rb"].plugins/os-backlogwith test pathplugins/os-backlog/tests, whentests_for("plugins/os-backlog/lib/backlog/checkout.rb")runs, then it returns["plugins/os-backlog/tests/checkout_test.rb"].plugins/oswith test pathplugins/os/tests, whentests_for("plugins/os/lib/worktree/finish_command.rb")runs, then it returnsplugins/os/tests/worktree/finish_command_test.rbplus everyplugins/os/tests/worktree/finish_command_*_test.rbscenario file in that directory.plugins/os-sdlcwith test pathplugins/os-sdlc/testsand two fixture tests<test_path>/hook/widget_test.rband<test_path>/hooks/widget_test.rbfor a production file at<target>/hooks/widget.rb, whentests_forruns, then it returns only<test_path>/hooks/widget_test.rb(thehook/candidate does not suffix-match, sincehookis nothooks).plugins/os-sdlcwith test pathplugins/os-sdlc/tests, whentests_for("plugins/os-sdlc/bin/evidence")runs, then it is unmapped, even thoughplugins/os-sdlc/tests/evidence/exists as a directory; and given two fixture tests<dir>/widget_test.rband<dir>/sub/widget_test.rbfor a production file at<dir>/sub/widget.rb,tests_forreturns only<dir>/sub/widget_test.rb, the longer suffix.plugins/os-sdlcandlint_paths: [extra], whenWorklistTarget#lint_pathruns, then it returns the absolute target directory followed by the absoluteextrapath, and nothing else.plugins/os-sdlc, whenproject-config --show --target os-sdlcruns, then the output containspath=plugins/os-sdlcand contains neithercode_path=norroot_path=.project-config --path appruns, then the written.sdlc/project.yamlhaspath: app;project-config --code-pathandproject-config --root-pathare not recognized flags.pathdirectory does not exist, when implementation-open validates the project, then the problem line starts withpath "<value>" does not exist for target <name>.targets:map with a named targetxthat has nopath:key, when implementation-open validates the project, then it fails and the problem line is exactlytarget "x" has no path: key; add path: <target directory> to .sdlc/project.yaml (see /os-sdlc:setup-project)..sdlc/project.yamlwith notargets:map and nopath:key, when implementation-open validates the project, then it succeeds and the opened implementation's project hastarget.path..Owner sketch
Non-goals
.claude/settings.jsonfrom #627 case 7. They stay out ofcode_changes.bin/<name>→<name>_bin_test.rbmapping (ADR-0208 D4-B follow-up)..sdlc/project.yamlfiles. A flat single-target project elsewhere with nopath:key keeps working, defaulted to".". A repo with atargets:map whose named targets have nopath:key now fails loudly at implementation-open with thetarget "<name>" has no path: key...message (AC 19) instead of silently widening scope — this is the intended tightening, not a regression to work around here.cc-pluginsandcc-plugins/api-wrapperare both named-target projects withoutpath:keys today, so they will fail this way until migrated; cross-project follow-up issues will be filed for each.Project#root(see #657).Retired tests
plugins/os-sdlc/tests/model/project/code_root_test.rb
plugins/os-sdlc/tests/model/project/validation_root_path_test.rb
plugins/os-sdlc/tests/lint_worklist_target_root_path_test.rb
plugins/os-sdlc/tests/lint_worklist_target_path_test.rb
plugins/os-sdlc/tests/model/project/codebase_code_test.rb#test_readme_is_not_code
plugins/os-sdlc/tests/cli_project_config_test.rb#test_project_config_updates_an_existing_project_yaml
plugins/os-sdlc/tests/model/project_defaults_test.rb#test_load_applies_plain_gem_defaults_when_keys_absent
plugins/os-sdlc/tests/model/project_defaults_test.rb#test_new_without_the_new_keys_applies_the_same_defaults
Origin
agents/wiring.mdhunk (#629)code_changesdrops non-Ruby files (Codebase#code?,model/project/codebase.rb:22-24) ← code scope is split acrosscode_pathandroot_path← DESIGN (ADR-0208 D1, D6)plugins/os-sdlc/lib/os_sdlc/model/project/code.rb,codebase.rb,test.rb,flat_attrs.rb,lint/worklist_target.rbWork started on branch ticket-655 (session 557d47dd) via /os-sdlc:implement --target os-sdlc. Step 1: superseding ADR for ADR-0208, then implementation-open.
Deviation 1: stale tests outside the Retired list (#665)
Problem. After test-reviewer accepted the tests (round 4) and programmer implemented ADR-0210, the diff-test gate stayed red through two programmer-repair rounds (dispatches 223, 224). Every red was in a pre-existing test file that asserts the removed API:
code_path:fixtures,Code#root,--root-path,bin_filesandconventional_test_dirslint paths, andtests_forraising instead of returning nil. The ticket's Retired tests list named three files; the change invalidated about twenty. The map routes a red diff-test only to programmer, who may not edit tests, so the pipeline could not converge.Solution. A Sonnet agent updated only test files in the worktree: three files deleted (behavior removed outright), the rest rewritten from
code_path:topath:and fromproject.codetoproject.target, shared fixtures intests/support/updated once. Commite38e71b1. Suite after: 1419 runs, 3221 assertions, 0 failures. The pipeline re-enters at programmer-repair so the gate, verifier, and auditors still run through the map.Pipeline defect. #665 already covers the missing route. A second symptom surfaced here: the ticket-open
Retired testscheck verifies that listed entries exist but cannot detect entries that are missing.Deviation 2: gate output cut at 4000 characters (#670)
Problem. In test-reviewer rounds 1 to 3 (dispatches 216, 218, 220) the brief's diff-test output stopped at 4000 of 9000 to 14000 characters. The reviewer could not see which new tests were red and recorded a GAP item each round instead of a verdict it could stand behind.
Solution. #670 filed against
BriefFacts::MAX_GATE_OUTPUT_CHARS. For round 4 the reviewer was told it may run a single test file directly to observe a red reason the brief does not show; it then passed the set.Resolution
Done: One path per target (ADR-0210 supersedes ADR-0208): Project::Target with default '.'; project.code.path aliases target.path; Code#root removed; loader reads only path: (code_path/root_path ignored); stored blob without path loads at '.'; Codebase#code? covers every non-test file under the target while unmapped fallback stays Ruby-only; S2 test mapping (longest suffix plus scenario siblings) in Test::Candidates; WorklistTarget#lint_path = [target, *lint_paths]; project-config emits path= and accepts --path only; missing-directory and missing-path: validation messages per AC 18-20; cc-os .sdlc/project.yaml migrated; charters and skills reworded; plugins refreshed; History row added.
Evidence: Pipeline implementation 19, 19 dispatches, implementation_complete (change-auditor, seam-designer, 4 test-writer/test-reviewer rounds, programmer + 3 repair, behavior-verifier pass, suite-check pass x2, contract-auditor -> refactorer -> pass). Commits
f01d5984(ADR-0210),e38e71b1(hand-finished stale tests, #665),753d1123(implementation), merge commit on main157da599. os-sdlc suite after merge: 1419 runs, 3219 assertions, 0 failures, 0 errors, 40.8 s. Ticket checks 11 and 12 pass (no code_path/root_path in .sdlc/project.yaml, agents, or skills). Deviation write-up: #655 (comment)Follow-ups: Pipeline defects cited: #665 (no repair route for red test-file diffs; hit again here on ~20 stale tests outside the Retired list), #670 (gate output cut at 4000 chars blinds test-reviewer; filed this session). Cross-project: jaredswanson/cc-plugins#74 (migrate named targets to path:). Retired-tests check cannot detect missing entries: dropped, absorbed into #665's route discussion. #629 closes with this ticket per its Problem statement.