os-sdlc: replace code_path with an explicit target root; every role sees the whole target #645

Closed
opened 2026-09-23 16:45:17 +00:00 by jared · 2 comments
Owner

Context

Found during #638 impl 13 root-cause analysis: the behavior-verifier reported no evidence for a bin/ change because bin/ is outside the target's code_path.

Observed

Each target has one code_path string (plugins/os-sdlc/lib/os_sdlc/model/project/code.rb:5-11, default "lib", include? = start_with?). Model::Project::Changes (changes.rb:8-21) drops every changed file outside it, so behavior-verifier code_changes (brief_facts.rb:86) and the diff-test gate (gate_commands.rb:35-44) never see bin/ or other code dirs (Rails: app/, config/, db/). Agent charters also limit reading to code_path with no stated reason: change-auditor.md:11 (--root <code_path>), reviewer.md:9, seam-designer.md:11, test-writer.md:25-26, programmer.md:24. Lint worklist already goes past code_path (lint/worklist_target.rb:29,51,59 adds bin/ and lint_paths).

Absorbs #629 (verifier misses bin/ changes; on #638 impl 13 the verifier said "code_changes has no hunk for plugins/os-sdlc/bin/os-sdlc-runner"). Also: a bin/-only change passes diff-test untested, because bin/ never enters code_paths and never trips the unmapped full-suite fallback (gate_commands.rb:37). Separate but related: #625 (fallback fires on every run because new files have no test yet).

Restrictions WITH a stated reason stay: refactorer/lint-fixer write scope (production only), test-reviewer tests only, responsibility-audit production only.

Reproduce

n/a: design/cross-cutting ticket, not a single reproducible bug

Expected

Every pipeline role sees the whole target root, not just code_path. code_changes and diff-test gates cover files like bin/os-sdlc-runner.

Approach (proposed, for discussion)

Replace code_path with an explicit per-target root key (no name→path object exists today: Project#root is the repo root, project_targets.rb has no directory concept; and cc-plugins/.sdlc/project.yaml target renderer has code_path api-wrapper/skills/api-wrapper/renderer/lib, so the target name cannot derive the path). Rule: code = under target root, not under test_path, not in an exclusion list.

Blast radius (from lsp-probe/ast-grep/grep):

  • lib: code.rb, codebase.rb (tests_for raises outside code_path, :13-19), changes.rb, flat_attrs.rb:7, yaml/document.rb:8-9, project.rb:44-46 (to_flat_h), gate_commands.rb, lint/worklist_target.rb (~9 files).
  • prose: 5 charters above, skills/responsibility-audit/SKILL.md:21, skills/setup-project/SKILL.md:17-43 (onboarding entry point, full rewrite).
  • tests pinning behavior (~15): project_tests_for_test.rb, changes_test.rb, changes_unmapped_code_paths_test.rb, brief_facts_unmapped_code_files_test.rb, gate_commands_test.rb, project_load_shared_test.rb, project_multi_target_test.rb, project_json_round_trip_test.rb, project_nested_test.rb, lint_worklist_target_test.rb, cli_project_config_test.rb, setup_test.rb, support fixtures (changed_project_fixture.rb, gate_commands_project_fixture.rb, project_attr_assertions.rb).
  • other repos using os-sdlc: cc-plugins (renderer, code_path 4 levels deep), cc-plugins/api-wrapper (code_path = templates subdir of skills/api-wrapper — root would widen scope), llf-schema (lib), ruby-gems herdr-cli/tally-api/pb-mirror/caldotcom-api/smartlead-api (no code_path; default lib).
  • ADRs: 0180 reversed; 0173, 0174 amended; 0100, 0102, 0103 re-anchor; 0088, 0074, 0192 text check.

Highest risks:

  1. test-name mapping — Test#covering (test.rb:14-18) mirrors the path after stripping code_path (lib/os_sdlc/x.rb → tests/os_sdlc/x_test.rb); stripping only the root gives tests/lib/os_sdlc/... which does not exist; needs an explicit rule.
  2. exclusion list: nested .sdlc/ dirs inside plugins, tmp/ (plugins/os-aidd-lint/tmp), generated dirs would count as code.
  3. the unmapped fallback must be redefined for files with no test mapping (bin/).

Related: #629, #625, #643, #639-#641 (all change bin/).

Origin

  • Trigger: #638 impl 13 behavior-verifier run
  • Improvised this session: none
  • Chain: verifier missed bin/ hunk ← code_changes filtered by code_path ← DESIGN (plugins/os-sdlc/lib/os_sdlc/model/project/code.rb:5-11, single code_path string per target)
  • Root candidate: this ticket
  • Where: plugins/os-sdlc/lib/os_sdlc/model/project/code.rb
  • Session: d1aa78d1-c840-471e-bdd0-2be657c7d950
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/d1aa78d1-c840-471e-bdd0-2be657c7d950.jsonl
## Context Found during #638 impl 13 root-cause analysis: the behavior-verifier reported no evidence for a bin/ change because bin/ is outside the target's code_path. ## Observed Each target has one `code_path` string (`plugins/os-sdlc/lib/os_sdlc/model/project/code.rb:5-11`, default "lib", `include?` = start_with?). `Model::Project::Changes` (`changes.rb:8-21`) drops every changed file outside it, so behavior-verifier `code_changes` (`brief_facts.rb:86`) and the diff-test gate (`gate_commands.rb:35-44`) never see `bin/` or other code dirs (Rails: app/, config/, db/). Agent charters also limit reading to code_path with no stated reason: change-auditor.md:11 (`--root <code_path>`), reviewer.md:9, seam-designer.md:11, test-writer.md:25-26, programmer.md:24. Lint worklist already goes past code_path (`lint/worklist_target.rb:29,51,59` adds bin/ and lint_paths). Absorbs #629 (verifier misses bin/ changes; on #638 impl 13 the verifier said "code_changes has no hunk for plugins/os-sdlc/bin/os-sdlc-runner"). Also: a bin/-only change passes diff-test untested, because bin/ never enters `code_paths` and never trips the unmapped full-suite fallback (`gate_commands.rb:37`). Separate but related: #625 (fallback fires on every run because new files have no test yet). Restrictions WITH a stated reason stay: refactorer/lint-fixer write scope (production only), test-reviewer tests only, responsibility-audit production only. ## Reproduce n/a: design/cross-cutting ticket, not a single reproducible bug ## Expected Every pipeline role sees the whole target root, not just code_path. code_changes and diff-test gates cover files like bin/os-sdlc-runner. ## Approach (proposed, for discussion) Replace `code_path` with an explicit per-target root key (no name→path object exists today: `Project#root` is the repo root, `project_targets.rb` has no directory concept; and `cc-plugins/.sdlc/project.yaml` target `renderer` has code_path `api-wrapper/skills/api-wrapper/renderer/lib`, so the target name cannot derive the path). Rule: code = under target root, not under test_path, not in an exclusion list. Blast radius (from lsp-probe/ast-grep/grep): - lib: code.rb, codebase.rb (`tests_for` raises outside code_path, :13-19), changes.rb, flat_attrs.rb:7, yaml/document.rb:8-9, project.rb:44-46 (`to_flat_h`), gate_commands.rb, lint/worklist_target.rb (~9 files). - prose: 5 charters above, skills/responsibility-audit/SKILL.md:21, skills/setup-project/SKILL.md:17-43 (onboarding entry point, full rewrite). - tests pinning behavior (~15): project_tests_for_test.rb, changes_test.rb, changes_unmapped_code_paths_test.rb, brief_facts_unmapped_code_files_test.rb, gate_commands_test.rb, project_load_shared_test.rb, project_multi_target_test.rb, project_json_round_trip_test.rb, project_nested_*_test.rb, lint_worklist_target_*_test.rb, cli_project_config_test.rb, setup_test.rb, support fixtures (changed_project_fixture.rb, gate_commands_project_fixture.rb, project_attr_assertions.rb). - other repos using os-sdlc: cc-plugins (renderer, code_path 4 levels deep), cc-plugins/api-wrapper (code_path = templates subdir of skills/api-wrapper — root would widen scope), llf-schema (lib), ruby-gems herdr-cli/tally-api/pb-mirror/caldotcom-api/smartlead-api (no code_path; default lib). - ADRs: 0180 reversed; 0173, 0174 amended; 0100, 0102, 0103 re-anchor; 0088, 0074, 0192 text check. Highest risks: 1. test-name mapping — `Test#covering` (test.rb:14-18) mirrors the path after stripping code_path (`lib/os_sdlc/x.rb` → `tests/os_sdlc/x_test.rb`); stripping only the root gives `tests/lib/os_sdlc/...` which does not exist; needs an explicit rule. 2. exclusion list: nested `.sdlc/` dirs inside plugins, `tmp/` (plugins/os-aidd-lint/tmp), generated dirs would count as code. 3. the unmapped fallback must be redefined for files with no test mapping (bin/). Related: #629, #625, #643, #639-#641 (all change bin/). ## Origin - Trigger: #638 impl 13 behavior-verifier run - Improvised this session: none - Chain: verifier missed bin/ hunk ← code_changes filtered by code_path ← DESIGN (plugins/os-sdlc/lib/os_sdlc/model/project/code.rb:5-11, single code_path string per target) - Root candidate: this ticket - Where: plugins/os-sdlc/lib/os_sdlc/model/project/code.rb - Session: d1aa78d1-c840-471e-bdd0-2be657c7d950 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/d1aa78d1-c840-471e-bdd0-2be657c7d950.jsonl
Author
Owner

Decision memo: open choices in #645 (design review claude-opus-5-5, 2026-09-23)

Two facts the body does not record:

  1. Widening the root alone does not fix #629. Changes#changed_paths uses the default glob "*.rb" (adapter/git/repo.rb:50), so bin/os-sdlc-runner (no extension) is dropped before any path check.
  2. Exclusion risk 2 is mostly handled by git: plugins/os-aidd-lint/tmp/ and plugins/os-sdlc/.sdlc/tmp/ are gitignored, and changed_paths uses ls-files --exclude-standard.

D1. Key: add root_path, keep code_path as the test-mirroring anchor

A (additive, chosen):

os-sdlc:
  root_path: plugins/os-sdlc      # optional; default File.dirname(code_path)
  code_path: plugins/os-sdlc/lib  # test-mirroring anchor only

B (replace): root: with code_path as a deprecated alias.
Recommendation: A, 75%. The default dirname(code_path) is the anchor ADR-0100/0103 already use, so every other repo's .sdlc/project.yaml works unchanged. root_path, not root, because Project#root already means the repo root.

D2. Test-name mapping: keep mirroring relative to code_path

A (chosen): tests_for(file) returns nil when file is outside code_path; mapping is unchanged.
B: strip the root and drop a leading lib|app|src segment.
Recommendation: A, 80%. All existing mappings stay byte-identical in every repo.

D3. Exclusions: git ignore rules plus a fixed segment constant, no config key

EXCLUDED_SEGMENTS = %w[.sdlc vendor node_modules tmp].freeze

Recommendation: A, 75%. ADR-0102: no exclude mechanism until a real need appears.

D4. Unmapped fallback: a code file with no resolvable test (bin/, hooks/) widens diff-test to the full suite

Recommendation: A, 75%. Closes "bin/-only change passes untested" with no new naming convention. A bin/<name> → <name>_bin_test.rb mapping is a possible follow-up.

D5. Charters: widen every read scope without a stated reason to root_path

change-auditor.md, reviewer.md, seam-designer.md, test-writer.md, programmer.md, code-probe SKILL.md + cheatsheet; responsibility-audit stays production-only ("root_path excluding test_path"); setup-project adds root_path.
Recommendation: A, 70%. Otherwise code-probe docs contradict the charters.

D6. File kind: code = *.rb or an extensionless file with a ruby shebang

B would treat every .md/.json edit as code and force full-suite runs.
Recommendation: A, 70%.

Taking all recommendations. Recorded as an ADR amending ADR-0180, 0173, 0174, 0100, 0103.

## Decision memo: open choices in #645 (design review claude-opus-5-5, 2026-09-23) Two facts the body does not record: 1. Widening the root alone does not fix #629. `Changes#changed_paths` uses the default glob `"*.rb"` (`adapter/git/repo.rb:50`), so `bin/os-sdlc-runner` (no extension) is dropped before any path check. 2. Exclusion risk 2 is mostly handled by git: `plugins/os-aidd-lint/tmp/` and `plugins/os-sdlc/.sdlc/tmp/` are gitignored, and `changed_paths` uses `ls-files --exclude-standard`. ### D1. Key: add `root_path`, keep `code_path` as the test-mirroring anchor A (additive, chosen): ```yaml os-sdlc: root_path: plugins/os-sdlc # optional; default File.dirname(code_path) code_path: plugins/os-sdlc/lib # test-mirroring anchor only ``` B (replace): `root:` with `code_path` as a deprecated alias. Recommendation: **A, 75%**. The default `dirname(code_path)` is the anchor ADR-0100/0103 already use, so every other repo's `.sdlc/project.yaml` works unchanged. `root_path`, not `root`, because `Project#root` already means the repo root. ### D2. Test-name mapping: keep mirroring relative to `code_path` A (chosen): `tests_for(file)` returns nil when `file` is outside `code_path`; mapping is unchanged. B: strip the root and drop a leading `lib|app|src` segment. Recommendation: **A, 80%**. All existing mappings stay byte-identical in every repo. ### D3. Exclusions: git ignore rules plus a fixed segment constant, no config key ```ruby EXCLUDED_SEGMENTS = %w[.sdlc vendor node_modules tmp].freeze ``` Recommendation: **A, 75%**. ADR-0102: no exclude mechanism until a real need appears. ### D4. Unmapped fallback: a code file with no resolvable test (bin/, hooks/) widens diff-test to the full suite Recommendation: **A, 75%**. Closes "bin/-only change passes untested" with no new naming convention. A `bin/<name>` → `<name>_bin_test.rb` mapping is a possible follow-up. ### D5. Charters: widen every read scope without a stated reason to `root_path` change-auditor.md, reviewer.md, seam-designer.md, test-writer.md, programmer.md, code-probe SKILL.md + cheatsheet; responsibility-audit stays production-only ("root_path excluding test_path"); setup-project adds `root_path`. Recommendation: **A, 70%**. Otherwise code-probe docs contradict the charters. ### D6. File kind: code = `*.rb` or an extensionless file with a ruby shebang B would treat every `.md`/`.json` edit as code and force full-suite runs. Recommendation: **A, 70%**. Taking all recommendations. Recorded as an ADR amending ADR-0180, 0173, 0174, 0100, 0103.
Author
Owner

Resolution

Done: Per-target root_path (default parent of code_path). Code = Ruby file (*.rb or ruby shebang) under root_path, not under test_path, not in .sdlc/vendor/node_modules/tmp. Changes reads every changed file, so bin/ reaches code_changes; a code file with no resolvable test widens diff-test to the full suite. tests_for returns nil outside code_path. Lint bin/ anchor = root_path. --root-path CLI flag. Charters and code-probe read root_path. ADR-0208 amends 0100/0103/0173/0174/0180. Other repos' .sdlc/project.yaml need no edit.

Evidence: main 1725c5bc (fast-forward from ticket-645). os-sdlc suite 1411 runs, 3211 assertions, 0 failures (48.8 s). New tests: code_root_test, codebase_code_test, changes_bin_files_test, gate_commands_bin_fallback_test, brief_facts_bin_file_test, validation_root_path_test, lint_worklist_target_root_path_test. Plugins refreshed.

Follow-ups: #629 stays open for non-Ruby files (charters, settings.json) in code_changes. New: LSP timing-test flake under load; worktree .cc-os ignore pattern. Rejected: bin/ test mapping (no observed cost).

## Resolution **Done:** Per-target root_path (default parent of code_path). Code = Ruby file (*.rb or ruby shebang) under root_path, not under test_path, not in .sdlc/vendor/node_modules/tmp. Changes reads every changed file, so bin/ reaches code_changes; a code file with no resolvable test widens diff-test to the full suite. tests_for returns nil outside code_path. Lint bin/ anchor = root_path. --root-path CLI flag. Charters and code-probe read root_path. ADR-0208 amends 0100/0103/0173/0174/0180. Other repos' .sdlc/project.yaml need no edit. **Evidence:** main 1725c5bc (fast-forward from ticket-645). os-sdlc suite 1411 runs, 3211 assertions, 0 failures (48.8 s). New tests: code_root_test, codebase_code_test, changes_bin_files_test, gate_commands_bin_fallback_test, brief_facts_bin_file_test, validation_root_path_test, lint_worklist_target_root_path_test. Plugins refreshed. **Follow-ups:** #629 stays open for non-Ruby files (charters, settings.json) in code_changes. New: LSP timing-test flake under load; worktree .cc-os ignore pattern. Rejected: bin/<name> test mapping (no observed cost).
jared closed this issue 2026-09-23 17:41:40 +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#645
No description provided.