Add cop ConceptCycle that reports the reference that closes a concept loop #1742
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#1742
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
Concepts can depend on each other in a loop. A concept is the first namespace segment below the library root. A loop makes all its concepts one unit of change: a change to one member can break each member, and no member can move out alone.
Lint::ConceptCycleCheckfinds a loop, prints it, and always exits 0. Nothing fails when a new reference closes a loop, and the report does not name the reference that closed it.Observed
CLI,Dispatch,Gate,Implementation,Lint,MutationTest,Project,StepOutcome,Tracker. The loop has 27 edges and 93 reference sites.lib/os_sdlc/lint/concept_cycle_check.rb:7-12printscycle: ...and returns 0.docs/research/lint-lessons/2026-11-12-domain-boundary-cops/smell-2-domain-cycle.md.Reproduce
n/a: feature ticket.
Approach
Add the cop
Sdlc/Structural/ConceptCycleand its lint lesson. The cop reports the reference, at its line, that is an edge of a loop.The value rule:
Consequences of the rule, with no special case in the code:
Constraints:
Lint::ConceptCycleCheck::References(lib/os_sdlc/lint/concept_cycle_check/references.rb). Give that class a public message for the resolution if the cop needs one. Do not write a second resolver and do not add a new parser call in the cop.OsSdlc. Sdlc cops run in every target project (ADR-0082). Get the root namespace and the library directory from the loader of the target, aslib/os_sdlc/detector/cycle_edges/signals.rb:12does. When no library is found for the file, report nothing.external_dependency_checksum, so the RuboCop result cache does not keep an old answer.Offense message:
Lesson file
lint-lessons/sdlc-structural-concept-cycle.yml(fields as inlib/os_sdlc/lesson.rb:4; modellint-lessons/sdlc-structural-inward-dependency.yml):Enable the cop in the sweep tier only (
.rubocop-sweep.yml, asSdlc/Structural/InwardDependencyat:7, with noExclude). One loop exists today, so the cop does not go in.rubocop.ymlin this ticket.Required work:
Sdlc/Structural/ConceptCycleinplugins/os-sdlc/lint-lessons/sdlc-structural-concept-cycle.ymlSdlc/Structural/ConceptCycleinplugins/os-sdlc/.rubocop-sweep.ymlexternal_dependency_checksuminplugins/os-sdlc/lib/os_sdlc/cops/structural/concept_cycle.rbOrchestrator work: none.
Rejected:
bin/lint-concept-cyclesexits 1 and a new gate step runs it — it fails each run until the existing loop is cut, and it does not name the reference. Rejected: a recorded loop size — a recorded number is a baseline. Rejected: a list of exempt entry concepts — an entry that nothing names is never on a loop.Acceptance criteria
B, and conceptA.Cin the path.Owner sketch
Non-goals
.rubocop.yml.bin/lint-concept-cycles.Origin
Lint::ConceptCycleCheckprints a loop and returns 0, and no gate reads it ← DESIGN (plugins/os-sdlc/lib/os_sdlc/lint/concept_cycle_check.rb:7-12)VERDICT: CREATE (ticket-skeptic, 2026-11-12, backlog search only; Jared asked for this ticket): no open ticket adds a cop that fires on a loop-closing reference.
Work started on branch ticket-1742 through the os-sdlc implement pipeline.
Removed Required work item 4 (absent
Prismin the cop file). implementation-open refuses an absent check whose glob matches no file, and the cop file does not exist before the build. The constraint stays in the Approach text; the orchestrator checks it by hand after the build.Follow-up dropped (ticket-skeptic, DROP, failed the need-to-exist test): 'A cop that needs a gem the cops loader does not load passes its tests and fails in RuboCop'. This is one occurrence, corrected by hand in
2f58591f4(require "os_git"incops/structural/concept_cycle/library.rb). The sibling cops of #1743 and #1744 did not have the defect. Note for a later cop: run the new cop withbundle exec rubocop -d --only <Cop>on the real tree before sign-off, and read stderr. File a ticket only if a second cop fails the same way.Resolution
Done: Added the cop Sdlc/Structural/ConceptCycle, its collaborators (Reference, Library), its lint lesson, and its tests. Lint::ConceptCycleCheck::References has a public resolve message, and ConceptCycleCheck has route, concept, and concept_edge?. The cop reports each reference that is an edge of a concept loop and names the path back. It is enabled in the sweep tier only.
Evidence: Implementation 290 ended implementation_complete; commits
5d827b7d3and2f58591f4on branch ticket-1742 (not merged yet). Full suite after the last commit: 5390 runs, 0 failures. Sweep run on the real tree: 93 offenses in 58 lib files on 27 edges in 9 concepts, the same counts as the probe; none in test, bin, or hooks; 0 cop errors on stderr. The cop files do not contain Prism. rubocop --show-cops under .rubocop.yml shows Enabled: false. Deviations: Required work item 4 removed from the ticket (absent check on a file that did not exist); two no-work pieces removed by hand at escalation 130, then a reroute; require os_git added by hand after the run ended, because the cop raised NameError in a real RuboCop run.Follow-ups: #1745 (repair the offenses and move the three boundary cops to the gate; verdict CREATE). #1746 (superclass reference source in ConceptCycleCheck::References; verdict CREATE). Dropped by ticket-skeptic: cop load-state gap, reason in the comment above. bin/lint-concept-cycles with no arguments raises TypeError: not captured, the script needs two arguments; named in the session handoff.