Lint lessons as YAML loaded into a Lesson object (ADR-0267, ADR-0268) #913

Closed
opened 2026-10-02 17:39:38 +00:00 by jared · 2 comments
Owner

Problem

ADR-0267 decides that lint lessons are YAML files loaded into a Lesson object.
Today a lesson is a Markdown file that Lesson::Library returns as a String.
A lesson with a missing section is not caught at load.
The later-encounter reminder is a fixed constant, not lesson content.

Approach

Stage 1 of ADR-0268 builds the shared YAML file loader here, and only Lesson::Library uses it.
Lesson files are plugins/os-sdlc/lint-lessons/<kebab-cop-name>.yml with the fields cop, signal, sign_of, why, decision, correction, example (before, after).
OsSdlc::Lesson is a concept root with data and no I/O, so it goes in model/lesson.rb.
The loader goes in adapter/yaml_file.rb (role: talks to a file) and its error in adapter/yaml_file/error.rb.

Required work:

  1. Add the loader OsSdlc::YamlFile, which reads a YAML file and returns the parsed mapping, with nil for an empty file — check: present class YamlFile in plugins/os-sdlc/lib/os_sdlc/adapter/yaml_file.rb
  2. Add OsSdlc::YamlFile::Error < HarnessError, raised for a file that does not parse and for content that is not a mapping, with a message that names the file and the cause — check: present HarnessError in plugins/os-sdlc/lib/os_sdlc/adapter/yaml_file/error.rb
  3. Add the Lesson value object built from the parsed data, with one reader per field, failing at load with a message that names the file and the missing field — check: present class Lesson in plugins/os-sdlc/lib/os_sdlc/model/lesson.rb
  4. Make Lesson::Library read the .yml file through YamlFile and return a Lesson, or nil when no file exists — check: present YamlFile in plugins/os-sdlc/lib/os_sdlc/adapter/lesson/library.rb
  5. Make Lesson::Relay render a Lesson to the Markdown block and use its signal as the later-encounter reminder — check: present signal in plugins/os-sdlc/lib/os_sdlc/presenter/lesson/relay.rb
  6. Remove Relay::REMINDER — check: absent REMINDER in plugins/os-sdlc/lib/os_sdlc/presenter/lesson/relay.rb
  7. Convert sdlc-sequel-dataset-result-compared.md to .yml with the same content — check: present signal: in plugins/os-sdlc/lint-lessons/sdlc-sequel-dataset-result-compared.yml
  8. Convert sdlc-sequel-ruby-filter-after-all.md to .yml with the same content — check: present signal: in plugins/os-sdlc/lint-lessons/sdlc-sequel-ruby-filter-after-all.yml
  9. Convert the sdlc-sequel-inline-dataset-query lesson that #911 adds to .yml with the same content — check: present signal: in plugins/os-sdlc/lint-lessons/sdlc-sequel-inline-dataset-query.yml
  10. Change the lesson step of the implement-cop reference to the YAML form and field list — check: present sign_of in plugins/os-sdlc/skills/mine-lints/references/implement-cop.md

Orchestrator work: delete the .md lesson files (no .md may remain in plugins/os-sdlc/lint-lessons/).

Runs after #911. Ticket B (the move of the three existing readers) depends on this ticket.

Rejected: Markdown files; Markdown with YAML front matter — ADR-0267.

Acceptance criteria

  1. Given a gate offense whose cop has a lesson file, when the offense appears for the first time in a dispatch, then the gate reply shows the rendered lesson.
  2. Given the same cop offense appears again in the same dispatch, when the gate replies, then the reply shows the lesson's signal line.
  3. Given a lesson file with a missing field, when the lesson loads, then loading fails with a message that names the file and the field.
  4. Given a lesson file that does not parse as YAML, when the lesson loads, then loading fails with a message that names the file.
  5. Given the lint-lessons directory, when it is listed, then it holds no .md file.

Owner sketch

module OsSdlc
  class YamlFile
    class Error < HarnessError; end
    def self.read(path); end
  end

  Lesson = Data.define(:cop, :signal, :sign_of, :why, :decision, :correction, :example)

  class Lesson
    class Library; def lesson(key); end; end
    class Relay; def render(key); end; end
  end
end

Origin

  • Trigger: user rulings 2026-10-02: lesson files are YAML; one shared YAML loader, staged.
  • Improvised this session: none
  • Chain: lessons are Markdown Strings ← ADR-0265 and ADR-0266 chose .md (DESIGN: docs/adr/0265-, docs/adr/0266-)
  • Root candidate: none
  • Where: plugins/os-sdlc/lib/os_sdlc/adapter/lesson/library.rb, presenter/lesson/relay.rb
  • Session: 9ee7805f-a4e4-470e-af50-5020e5c524a4
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/9ee7805f-a4e4-470e-af50-5020e5c524a4.jsonl
## Problem ADR-0267 decides that lint lessons are YAML files loaded into a `Lesson` object. Today a lesson is a Markdown file that `Lesson::Library` returns as a String. A lesson with a missing section is not caught at load. The later-encounter reminder is a fixed constant, not lesson content. ## Approach Stage 1 of ADR-0268 builds the shared YAML file loader here, and only `Lesson::Library` uses it. Lesson files are `plugins/os-sdlc/lint-lessons/<kebab-cop-name>.yml` with the fields `cop`, `signal`, `sign_of`, `why`, `decision`, `correction`, `example` (`before`, `after`). `OsSdlc::Lesson` is a concept root with data and no I/O, so it goes in `model/lesson.rb`. The loader goes in `adapter/yaml_file.rb` (role: talks to a file) and its error in `adapter/yaml_file/error.rb`. Required work: 1. Add the loader `OsSdlc::YamlFile`, which reads a YAML file and returns the parsed mapping, with nil for an empty file — check: present `class YamlFile` in `plugins/os-sdlc/lib/os_sdlc/adapter/yaml_file.rb` 2. Add `OsSdlc::YamlFile::Error < HarnessError`, raised for a file that does not parse and for content that is not a mapping, with a message that names the file and the cause — check: present `HarnessError` in `plugins/os-sdlc/lib/os_sdlc/adapter/yaml_file/error.rb` 3. Add the `Lesson` value object built from the parsed data, with one reader per field, failing at load with a message that names the file and the missing field — check: present `class Lesson` in `plugins/os-sdlc/lib/os_sdlc/model/lesson.rb` 4. Make `Lesson::Library` read the `.yml` file through `YamlFile` and return a `Lesson`, or nil when no file exists — check: present `YamlFile` in `plugins/os-sdlc/lib/os_sdlc/adapter/lesson/library.rb` 5. Make `Lesson::Relay` render a `Lesson` to the Markdown block and use its `signal` as the later-encounter reminder — check: present `signal` in `plugins/os-sdlc/lib/os_sdlc/presenter/lesson/relay.rb` 6. Remove `Relay::REMINDER` — check: absent `REMINDER` in `plugins/os-sdlc/lib/os_sdlc/presenter/lesson/relay.rb` 7. Convert `sdlc-sequel-dataset-result-compared.md` to `.yml` with the same content — check: present `signal:` in `plugins/os-sdlc/lint-lessons/sdlc-sequel-dataset-result-compared.yml` 8. Convert `sdlc-sequel-ruby-filter-after-all.md` to `.yml` with the same content — check: present `signal:` in `plugins/os-sdlc/lint-lessons/sdlc-sequel-ruby-filter-after-all.yml` 9. Convert the `sdlc-sequel-inline-dataset-query` lesson that #911 adds to `.yml` with the same content — check: present `signal:` in `plugins/os-sdlc/lint-lessons/sdlc-sequel-inline-dataset-query.yml` 10. Change the lesson step of the implement-cop reference to the YAML form and field list — check: present `sign_of` in `plugins/os-sdlc/skills/mine-lints/references/implement-cop.md` Orchestrator work: delete the `.md` lesson files (no `.md` may remain in `plugins/os-sdlc/lint-lessons/`). Runs after #911. Ticket B (the move of the three existing readers) depends on this ticket. Rejected: Markdown files; Markdown with YAML front matter — ADR-0267. ## Acceptance criteria 1. Given a gate offense whose cop has a lesson file, when the offense appears for the first time in a dispatch, then the gate reply shows the rendered lesson. 2. Given the same cop offense appears again in the same dispatch, when the gate replies, then the reply shows the lesson's `signal` line. 3. Given a lesson file with a missing field, when the lesson loads, then loading fails with a message that names the file and the field. 4. Given a lesson file that does not parse as YAML, when the lesson loads, then loading fails with a message that names the file. 5. Given the `lint-lessons` directory, when it is listed, then it holds no `.md` file. ## Owner sketch ```ruby module OsSdlc class YamlFile class Error < HarnessError; end def self.read(path); end end Lesson = Data.define(:cop, :signal, :sign_of, :why, :decision, :correction, :example) class Lesson class Library; def lesson(key); end; end class Relay; def render(key); end; end end end ``` ## Origin - Trigger: user rulings 2026-10-02: lesson files are YAML; one shared YAML loader, staged. - Improvised this session: none - Chain: lessons are Markdown Strings ← ADR-0265 and ADR-0266 chose .md (DESIGN: docs/adr/0265-*, docs/adr/0266-*) - Root candidate: none - Where: plugins/os-sdlc/lib/os_sdlc/adapter/lesson/library.rb, presenter/lesson/relay.rb - Session: 9ee7805f-a4e4-470e-af50-5020e5c524a4 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/9ee7805f-a4e4-470e-af50-5020e5c524a4.jsonl
Author
Owner

Work started on branch ticket-913 through the os-sdlc pipeline.

Work started on branch ticket-913 through the os-sdlc pipeline.
Author
Owner

Resolution

Done: Lint lessons are YAML loaded into OsSdlc::Lesson (model/lesson.rb). OsSdlc::YamlFile (adapter/yaml_file.rb) and YamlFile::Error are added and used by Lesson::Library. Lesson::Relay renders from the Lesson and uses its signal as the reminder. The three lessons are .yml; no .md remains. implement-cop.md describes the YAML form (ADR-0267, ADR-0268 stage 1).

Evidence: Branch ticket-913, commits abd8e57c and d9257a3a; os-sdlc implementation 18: 22 dispatches, 5 retries, one reroute after bound_exhausted at programmer-repair (the programmer role cannot delete files; the orchestrator removed the three .md files with git rm), required-work gate and full suite green, contract-auditor pass.

Follow-ups: #914 moves the three existing YAML readers to YamlFile (ADR-0268 stage 2). Lens line A2 on top-level YamlFile: auditor proposal NONE, clears in #914. Workflow defect to capture: programmer role cannot delete files and the Runner commit omits staged deletions (seen in #908 and #913). History row added after merge.

## Resolution **Done:** Lint lessons are YAML loaded into OsSdlc::Lesson (model/lesson.rb). OsSdlc::YamlFile (adapter/yaml_file.rb) and YamlFile::Error are added and used by Lesson::Library. Lesson::Relay renders from the Lesson and uses its signal as the reminder. The three lessons are .yml; no .md remains. implement-cop.md describes the YAML form (ADR-0267, ADR-0268 stage 1). **Evidence:** Branch ticket-913, commits abd8e57c and d9257a3a; os-sdlc implementation 18: 22 dispatches, 5 retries, one reroute after bound_exhausted at programmer-repair (the programmer role cannot delete files; the orchestrator removed the three .md files with git rm), required-work gate and full suite green, contract-auditor pass. **Follow-ups:** #914 moves the three existing YAML readers to YamlFile (ADR-0268 stage 2). Lens line A2 on top-level YamlFile: auditor proposal NONE, clears in #914. Workflow defect to capture: programmer role cannot delete files and the Runner commit omits staged deletions (seen in #908 and #913). History row added after merge.
jared closed this issue 2026-10-02 18:06:25 +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#913
No description provided.