os-aidd-lint: move bin/aidd-lint logic into AiddLint::CLI with an Invocation; bin tests run in-process #639

Closed
opened 2026-09-23 13:04:38 +00:00 by jared · 3 comments
Owner

Map: #631
Blocked by #638

Problem

Child of #631. Uses the seam shape that #638 records in its ADR. The plugins have no shared load path, so os-aidd-lint defines its own Invocation value.

Observed

All of the CLI logic lives in plugins/os-aidd-lint/bin/aidd-lint as top-level methods (main, fix, tier2_prepare, tier2_collect, 193 lines). All 20 tests in plugins/os-aidd-lint/tests/aidd_lint_bin_test.rb fork BIN (13 Open3.capture3 sites, lines 22-374). The file takes 2.8 s alone (2026-09-23).

Reproduce

cd plugins/os-aidd-lint && ruby tests/aidd_lint_bin_test.rb

Expected

One prose sentence: AiddLint::CLI.new(invocation).run(argv) returns the exit status, writes only to invocation.stdout/stderr, and bin/aidd-lint only builds the Invocation and calls it.

Origin

  • Trigger: decomposition of #631, 2026-09-23 (user-decided split)
  • Improvised this session: none
  • Chain: 13 forks per suite run ← CLI logic lives in bin/ as top-level methods (DESIGN, bin/aidd-lint)
  • Root candidate: this ticket
  • Where: plugins/os-aidd-lint/bin/aidd-lint; plugins/os-aidd-lint/tests/aidd_lint_bin_test.rb
  • Session: 5bb052ff-ddf2-4d47-a283-d00d071e9b79
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/5bb052ff-ddf2-4d47-a283-d00d071e9b79.jsonl

Owner sketch

module AiddLint
  Invocation = Data.define(:cwd, :stdin, :stdout, :stderr, :env)   # lib/aidd_lint/invocation.rb
  class CLI                                                          # lib/aidd_lint/cli.rb
    def initialize(invocation)
    def run(argv)
  end
end

Acceptance criteria

  1. Given a SKILL.md with a failing offense, when AiddLint::CLI.new(invocation).run([path]) runs, then the offense line is on invocation.stdout and the status is 1.
  2. Given --fix on a correctable file, when run in-process, then the file is rewritten and fixed <path>: <cops> is on invocation.stdout.
  3. Given --tier2-prepare on two files, when run in-process, then a workdir under the plugin's tmp/ holds manifest.json and one NN.prompt.md per subject, and workdir <dir> is on stdout.
  4. Given --tier2-collect without --tier2-workdir, when run in-process, then the message is on invocation.stderr and the status is 3.
  5. Given no path arguments, when run in-process, then the usage line is on invocation.stderr and the status is 3.
  6. Given the real bin/aidd-lint, when it runs as a subprocess on a clean markdown file, then it exits 0.
  7. Given tests/aidd_lint_bin_test.rb, when searched for Open3, then only the smoke test matches.

Approach

  • Move the bin methods into AiddLint::CLI unchanged except for I/O and exit: return a status, and let bin call exit(AiddLint::CLI.new(AiddLint::Invocation.new(...process...)).run(ARGV)). Keep the existing $LOAD_PATH/require lines in bin.
  • The tier-2 workdir parent must stay the plugin's tmp/ (bin used File.expand_path('../tmp', __dir__); from lib/aidd_lint/cli.rb that is '../../tmp'). Move the existing comment that explains the per-prepare workdir with the code. It records an external constraint.
  • Convert the existing 20 tests in place through one private run_cli(*args) helper. Do not rename them.
  • ADRs to respect: ADR-0079 (manifest.json in the workdir; collect takes no paths), ADR-0078 (no --tier2 flag; FindingsFileInvoker only), ADR-0064/0070 (prepare/collect protocol), ADR-0195 (one constant per file), ADR-0025 (require_relative manifest in lib/aidd_lint.rb), ADR-0123 (thin bin). Adopting Thor is out of scope.
    Required work:
  1. Move the on-bin CLI logic into AiddLint::CLI behind an AiddLint::Invocation, leaving bin/aidd-lint to build the Invocation and call exit(...). (check: ! grep -nE '^def (main|fix|tier2_prepare|tier2_collect)' plugins/os-aidd-lint/bin/aidd-lint)
  2. Keep the tier-2 workdir parent under the plugin's tmp/, adjusted for the new lib path. (proven by AC 3)
  3. Convert the 20 existing tests in place to call AiddLint::CLI in-process through one shared helper. (proven by AC 7)
  4. Keep the real bin/aidd-lint runnable as a subprocess that exits 0 on a clean file. (proven by AC 6)

Structural follow-through

  • One History row in docs/implementation-status/os-aidd-lint.md.
Map: #631 Blocked by #638 ## Problem Child of #631. Uses the seam shape that #638 records in its ADR. The plugins have no shared load path, so os-aidd-lint defines its own Invocation value. ## Observed All of the CLI logic lives in plugins/os-aidd-lint/bin/aidd-lint as top-level methods (`main`, `fix`, `tier2_prepare`, `tier2_collect`, 193 lines). All 20 tests in plugins/os-aidd-lint/tests/aidd_lint_bin_test.rb fork `BIN` (13 `Open3.capture3` sites, lines 22-374). The file takes 2.8 s alone (2026-09-23). ## Reproduce ``` cd plugins/os-aidd-lint && ruby tests/aidd_lint_bin_test.rb ``` ## Expected One prose sentence: `AiddLint::CLI.new(invocation).run(argv)` returns the exit status, writes only to `invocation.stdout`/`stderr`, and bin/aidd-lint only builds the Invocation and calls it. ## Origin - Trigger: decomposition of #631, 2026-09-23 (user-decided split) - Improvised this session: none - Chain: 13 forks per suite run ← CLI logic lives in bin/ as top-level methods (DESIGN, bin/aidd-lint) - Root candidate: this ticket - Where: plugins/os-aidd-lint/bin/aidd-lint; plugins/os-aidd-lint/tests/aidd_lint_bin_test.rb - Session: 5bb052ff-ddf2-4d47-a283-d00d071e9b79 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/5bb052ff-ddf2-4d47-a283-d00d071e9b79.jsonl ## Owner sketch ```ruby module AiddLint Invocation = Data.define(:cwd, :stdin, :stdout, :stderr, :env) # lib/aidd_lint/invocation.rb class CLI # lib/aidd_lint/cli.rb def initialize(invocation) def run(argv) end end ``` ## Acceptance criteria 1. Given a SKILL.md with a failing offense, when `AiddLint::CLI.new(invocation).run([path])` runs, then the offense line is on `invocation.stdout` and the status is 1. 2. Given `--fix` on a correctable file, when run in-process, then the file is rewritten and `fixed <path>: <cops>` is on `invocation.stdout`. 3. Given `--tier2-prepare` on two files, when run in-process, then a workdir under the plugin's tmp/ holds manifest.json and one NN.prompt.md per subject, and `workdir <dir>` is on stdout. 4. Given `--tier2-collect` without `--tier2-workdir`, when run in-process, then the message is on `invocation.stderr` and the status is 3. 5. Given no path arguments, when run in-process, then the usage line is on `invocation.stderr` and the status is 3. 6. Given the real bin/aidd-lint, when it runs as a subprocess on a clean markdown file, then it exits 0. 7. Given tests/aidd_lint_bin_test.rb, when searched for `Open3`, then only the smoke test matches. ## Approach - Move the bin methods into AiddLint::CLI unchanged except for I/O and exit: return a status, and let bin call `exit(AiddLint::CLI.new(AiddLint::Invocation.new(...process...)).run(ARGV))`. Keep the existing `$LOAD_PATH`/require lines in bin. - The tier-2 workdir parent must stay the plugin's tmp/ (bin used `File.expand_path('../tmp', __dir__)`; from lib/aidd_lint/cli.rb that is `'../../tmp'`). Move the existing comment that explains the per-prepare workdir with the code. It records an external constraint. - Convert the existing 20 tests in place through one private `run_cli(*args)` helper. Do not rename them. - ADRs to respect: ADR-0079 (manifest.json in the workdir; collect takes no paths), ADR-0078 (no --tier2 flag; FindingsFileInvoker only), ADR-0064/0070 (prepare/collect protocol), ADR-0195 (one constant per file), ADR-0025 (require_relative manifest in lib/aidd_lint.rb), ADR-0123 (thin bin). Adopting Thor is out of scope. Required work: 1. Move the on-bin CLI logic into `AiddLint::CLI` behind an `AiddLint::Invocation`, leaving bin/aidd-lint to build the Invocation and call `exit(...)`. (check: `! grep -nE '^def (main|fix|tier2_prepare|tier2_collect)' plugins/os-aidd-lint/bin/aidd-lint`) 2. Keep the tier-2 workdir parent under the plugin's tmp/, adjusted for the new lib path. (proven by AC 3) 3. Convert the 20 existing tests in place to call `AiddLint::CLI` in-process through one shared helper. (proven by AC 7) 4. Keep the real bin/aidd-lint runnable as a subprocess that exits 0 on a clean file. (proven by AC 6) ## Structural follow-through - One History row in docs/implementation-status/os-aidd-lint.md.
Author
Owner

Body before Required-work migration, 2026-09-23

Map: #631
Blocked by #638

Problem

Child of #631. Uses the seam shape that #638 records in its ADR. The plugins have no shared load path, so os-aidd-lint defines its own Invocation value.

Observed

All of the CLI logic lives in plugins/os-aidd-lint/bin/aidd-lint as top-level methods (main, fix, tier2_prepare, tier2_collect, 193 lines). All 20 tests in plugins/os-aidd-lint/tests/aidd_lint_bin_test.rb fork BIN (13 Open3.capture3 sites, lines 22-374). The file takes 2.8 s alone (2026-09-23).

Reproduce

cd plugins/os-aidd-lint && ruby tests/aidd_lint_bin_test.rb

Expected

One prose sentence: AiddLint::CLI.new(invocation).run(argv) returns the exit status, writes only to invocation.stdout/stderr, and bin/aidd-lint only builds the Invocation and calls it.

Origin

  • Trigger: decomposition of #631, 2026-09-23 (user-decided split)
  • Improvised this session: none
  • Chain: 13 forks per suite run ← CLI logic lives in bin/ as top-level methods (DESIGN, bin/aidd-lint)
  • Root candidate: this ticket
  • Where: plugins/os-aidd-lint/bin/aidd-lint; plugins/os-aidd-lint/tests/aidd_lint_bin_test.rb
  • Session: 5bb052ff-ddf2-4d47-a283-d00d071e9b79
  • Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/5bb052ff-ddf2-4d47-a283-d00d071e9b79.jsonl

Owner sketch

module AiddLint
  Invocation = Data.define(:cwd, :stdin, :stdout, :stderr, :env)   # lib/aidd_lint/invocation.rb
  class CLI                                                          # lib/aidd_lint/cli.rb
    def initialize(invocation)
    def run(argv)
  end
end

Acceptance criteria

  1. Given a SKILL.md with a failing offense, when AiddLint::CLI.new(invocation).run([path]) runs, then the offense line is on invocation.stdout and the status is 1.
  2. Given --fix on a correctable file, when run in-process, then the file is rewritten and fixed <path>: <cops> is on invocation.stdout.
  3. Given --tier2-prepare on two files, when run in-process, then a workdir under the plugin's tmp/ holds manifest.json and one NN.prompt.md per subject, and workdir <dir> is on stdout.
  4. Given --tier2-collect without --tier2-workdir, when run in-process, then the message is on invocation.stderr and the status is 3.
  5. Given no path arguments, when run in-process, then the usage line is on invocation.stderr and the status is 3.
  6. Given the real bin/aidd-lint, when it runs as a subprocess on a clean markdown file, then it exits 0.
  7. Given tests/aidd_lint_bin_test.rb, when searched for Open3, then only the smoke test matches.

Approach

  • Move the bin methods into AiddLint::CLI unchanged except for I/O and exit: return a status, and let bin call exit(AiddLint::CLI.new(AiddLint::Invocation.new(...process...)).run(ARGV)). Keep the existing $LOAD_PATH/require lines in bin.
  • The tier-2 workdir parent must stay the plugin's tmp/ (bin used File.expand_path('../tmp', __dir__); from lib/aidd_lint/cli.rb that is '../../tmp'). Move the existing comment that explains the per-prepare workdir with the code. It records an external constraint.
  • Convert the existing 20 tests in place through one private run_cli(*args) helper. Do not rename them.
  • ADRs to respect: ADR-0079 (manifest.json in the workdir; collect takes no paths), ADR-0078 (no --tier2 flag; FindingsFileInvoker only), ADR-0064/0070 (prepare/collect protocol), ADR-0195 (one constant per file), ADR-0025 (require_relative manifest in lib/aidd_lint.rb), ADR-0123 (thin bin). Adopting Thor is out of scope.

Structural follow-through

  • One History row in docs/implementation-status/os-aidd-lint.md.
Body before Required-work migration, 2026-09-23 Map: #631 Blocked by #638 ## Problem Child of #631. Uses the seam shape that #638 records in its ADR. The plugins have no shared load path, so os-aidd-lint defines its own Invocation value. ## Observed All of the CLI logic lives in plugins/os-aidd-lint/bin/aidd-lint as top-level methods (`main`, `fix`, `tier2_prepare`, `tier2_collect`, 193 lines). All 20 tests in plugins/os-aidd-lint/tests/aidd_lint_bin_test.rb fork `BIN` (13 `Open3.capture3` sites, lines 22-374). The file takes 2.8 s alone (2026-09-23). ## Reproduce ``` cd plugins/os-aidd-lint && ruby tests/aidd_lint_bin_test.rb ``` ## Expected One prose sentence: `AiddLint::CLI.new(invocation).run(argv)` returns the exit status, writes only to `invocation.stdout`/`stderr`, and bin/aidd-lint only builds the Invocation and calls it. ## Origin - Trigger: decomposition of #631, 2026-09-23 (user-decided split) - Improvised this session: none - Chain: 13 forks per suite run ← CLI logic lives in bin/ as top-level methods (DESIGN, bin/aidd-lint) - Root candidate: this ticket - Where: plugins/os-aidd-lint/bin/aidd-lint; plugins/os-aidd-lint/tests/aidd_lint_bin_test.rb - Session: 5bb052ff-ddf2-4d47-a283-d00d071e9b79 - Transcript: /home/jared/.claude/projects/-home-jared-dev-cc-os/5bb052ff-ddf2-4d47-a283-d00d071e9b79.jsonl ## Owner sketch ```ruby module AiddLint Invocation = Data.define(:cwd, :stdin, :stdout, :stderr, :env) # lib/aidd_lint/invocation.rb class CLI # lib/aidd_lint/cli.rb def initialize(invocation) def run(argv) end end ``` ## Acceptance criteria 1. Given a SKILL.md with a failing offense, when `AiddLint::CLI.new(invocation).run([path])` runs, then the offense line is on `invocation.stdout` and the status is 1. 2. Given `--fix` on a correctable file, when run in-process, then the file is rewritten and `fixed <path>: <cops>` is on `invocation.stdout`. 3. Given `--tier2-prepare` on two files, when run in-process, then a workdir under the plugin's tmp/ holds manifest.json and one NN.prompt.md per subject, and `workdir <dir>` is on stdout. 4. Given `--tier2-collect` without `--tier2-workdir`, when run in-process, then the message is on `invocation.stderr` and the status is 3. 5. Given no path arguments, when run in-process, then the usage line is on `invocation.stderr` and the status is 3. 6. Given the real bin/aidd-lint, when it runs as a subprocess on a clean markdown file, then it exits 0. 7. Given tests/aidd_lint_bin_test.rb, when searched for `Open3`, then only the smoke test matches. ## Approach - Move the bin methods into AiddLint::CLI unchanged except for I/O and exit: return a status, and let bin call `exit(AiddLint::CLI.new(AiddLint::Invocation.new(...process...)).run(ARGV))`. Keep the existing `$LOAD_PATH`/require lines in bin. - The tier-2 workdir parent must stay the plugin's tmp/ (bin used `File.expand_path('../tmp', __dir__)`; from lib/aidd_lint/cli.rb that is `'../../tmp'`). Move the existing comment that explains the per-prepare workdir with the code. It records an external constraint. - Convert the existing 20 tests in place through one private `run_cli(*args)` helper. Do not rename them. - ADRs to respect: ADR-0079 (manifest.json in the workdir; collect takes no paths), ADR-0078 (no --tier2 flag; FindingsFileInvoker only), ADR-0064/0070 (prepare/collect protocol), ADR-0195 (one constant per file), ADR-0025 (require_relative manifest in lib/aidd_lint.rb), ADR-0123 (thin bin). Adopting Thor is out of scope. ## Structural follow-through - One History row in docs/implementation-status/os-aidd-lint.md.
Author
Owner

Work started via /os-sdlc:implement on branch ticket-639 (worktree .claude/worktrees/ticket-639).

Work started via /os-sdlc:implement on branch ticket-639 (worktree .claude/worktrees/ticket-639).
Author
Owner

Resolution

Done: AiddLint::CLI.new(invocation).run(argv) returns the exit status and writes only to invocation.stdout/stderr; bin/aidd-lint only builds the Invocation and calls it; aidd_lint_bin_test.rb runs in-process with no Open3 forks.

Evidence: Branch ticket-639 commit 9df1d511 via os-sdlc implementation 16 (dispatches 181-188, all pass). os-aidd-lint suite: 502 runs, 1196 assertions, 0 failures. Two earlier implementations (14, 15) terminated on a charter defect: judgment charters forbade the ADR-0206 brief fetch over the hook cap; fixed in fc8a9686 and 53697f26 on the same branch.

Follow-ups: #662 (ticket-skeptic: root cause, charter/ADR-0206 mismatch; audit remaining charters and amend ADR-0206).

## Resolution **Done:** AiddLint::CLI.new(invocation).run(argv) returns the exit status and writes only to invocation.stdout/stderr; bin/aidd-lint only builds the Invocation and calls it; aidd_lint_bin_test.rb runs in-process with no Open3 forks. **Evidence:** Branch ticket-639 commit 9df1d511 via os-sdlc implementation 16 (dispatches 181-188, all pass). os-aidd-lint suite: 502 runs, 1196 assertions, 0 failures. Two earlier implementations (14, 15) terminated on a charter defect: judgment charters forbade the ADR-0206 brief fetch over the hook cap; fixed in fc8a9686 and 53697f26 on the same branch. **Follow-ups:** #662 (ticket-skeptic: root cause, charter/ADR-0206 mismatch; audit remaining charters and amend ADR-0206).
jared closed this issue 2026-09-24 01:48:51 +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#639
No description provided.