Skip to content

PR D: Split CommandExecutor into a dispatching composite with injectable BuiltinExecutor / ProjectExecutor / OverriddenExecutor strategies (exec_into vs run_waiting) - #122

Merged
JPDuchesne merged 8 commits into
mainfrom
ai/118-pr-d-split-commandexecutor-into-a-dispat
Aug 19, 2026

Conversation

@d3mlabs-ai-flow

Copy link
Copy Markdown
Contributor

Implements #118.

Requested by @JPDuchesne.

Closes #118

…ite with injectable BuiltinExecutor / ProjectExecutor / OverriddenExecutor strategies (exec_into vs run_waiting)

Co-authored-by: JPDuchesne <2636122+JPDuchesne@users.noreply.github.com>
@codecov

codecov Bot commented Aug 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@JPDuchesne JPDuchesne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/build let's fix the fake classes, put them within the test class

Comment thread test/dev/builtin_executor_test.rb Outdated
Comment on lines +9 to +24
# Builtin fake whose body records its invocations, so the test can assert
# the in-process delegation passed args and context through untouched.
class BuiltinExecutorFakeBuiltin < Dev::BuiltinCommand
attr_reader :calls

def initialize
@calls = []
super()
end

def desc = "a builtin"

def call(args:, context:)
@calls << [args, context]
end
end unless defined?(BuiltinExecutorFakeBuiltin)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's move the fake to be inside Dev::BuiltinExecutorTest

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the fake into Dev::BuiltinExecutorTest as a nested FakeBuiltin class, dropping the unless defined? guard and the BuiltinExecutor name prefix — both were only needed while the constant lived at file top level.

Addressed in 2e05625.

Comment thread test/dev/overridden_executor_test.rb Outdated
Comment on lines +11 to +22
class OverriddenExecutorFakeBuiltin < Dev::BuiltinCommand
def initialize(stamps:)
@stamps = stamps
super()
end

def desc = "a builtin"

def stamps? = @stamps

def call(args:, context:); end
end unless defined?(OverriddenExecutorFakeBuiltin)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same fix in Dev::OverriddenExecutorTest: the stamping-trait fake is now a nested FakeBuiltin, guard and prefix removed.

Addressed in 2e05625.

…bin/test.rb runner

Co-authored-by: Cursor <cursoragent@cursor.com>
@JPDuchesne

JPDuchesne commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

/build let's fix the fake classes, put them within the test class

(Re-firing the review-submitted command from #122 (review) — GitHub creates no workflow run for review-surface events while a PR is merge-conflicted, and this PR was conflicted with main between 17:47 and 18:33 UTC. The conflict is now resolved.)

/build — committed 2e05625.

Nested all four top-level fakes this PR introduced inside their test classes — the two threads plus the same pattern in test/dev/command_executor_test.rb and test/dev/command_service_test.rb, since the instruction covered "the fake classes" generally. Nesting under an rspock transform!-ed class is safe: the transformation only rewrites test blocks, so plain nested classes are untouched. Each fake is now simply FakeBuiltin, namespaced by its test class, with no collision guard. Verified with the full suite (896 tests: the only failure, Dev::BinDevTest#test_the_scrub_list_covers_every_key_the_running_bundler_exports, reproduces without my changes and is runner-environment-specific; dev tc's unparser-RBI errors are likewise pre-existing and unrelated — the touched files are typed: false) and RuboCop (no offenses). Captured a learning, testing/nested-test-fakes, recording the convention and why it composes safely with the rspock transformation.

🧠 drafted 1 learning in a learning proposal PR: #124

  • nested-test-fakes

⚙️ workflow run · model: claude-fable-5-high

…class

Co-authored-by: JPDuchesne <2636122+JPDuchesne@users.noreply.github.com>
JPDuchesne and others added 5 commits August 19, 2026 10:16
Every command declares which usage section it lists under (Lifecycle,
Development flow, or project commands). Abstract rather than defaulted so
no builtin lands in a section silently; OverriddenCommand takes the slot's
category, consistent with its guard/stamp trait delegation.

Co-authored-by: Cursor <cursoragent@cursor.com>
dev help renders the usage listing as a first-class command. It lists the
catalog that contains it, so the listing arrives as a commands provider
resolved at call time; the composition root closes that self-reference.
Staleness-exempt: help is how the remediation commands get discovered.

Co-authored-by: Cursor <cursoragent@cursor.com>
Bare dev, --help, and -h now route to the help builtin like any command
(dev help works too, and a project help: override composes via the usual
slot mechanics). UsagePrinter renders the listing as Category sections —
project commands, Lifecycle, Development flow — alphabetized so the
output is deterministic regardless of registration order.

With help inside the command path, the --help lazy-toolchain deferral is
dropped: the toolchain pass over dependencies.rb runs once per invocation,
unconditionally, and the composition root collapses to a single pass in
Runner#run (kept there, not the constructor, so dependencies.rb errors
still flow through the exit_for mapping). ui/out are per-process
collaborators and move to Runner's constructor; the help builtin's
self-referential listing is closed at the root with a call-time provider.

Co-authored-by: Cursor <cursoragent@cursor.com>
CommandRunner's wait: constructor flag was a per-call mode hiding in
construction; it becomes two public messages, exec_into and run_waiting,
mirroring ProjectExecutor's seam (the flag survives only on the private
run_child boundary). With the toolchain pass eager, the runner's
collaborators are all known at the composition root, so it is built once
from the run's ExecutionContext and injected into ProjectExecutor — the
project-side executor messages stop carrying a context they never used.

Co-authored-by: Cursor <cursoragent@cursor.com>
with_toolchain now runs once per invocation (help included); the lazy
--help carve-out is gone, so the comment states the real property:
dependencies.rb is a declaration file and must stay cheap.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JPDuchesne
JPDuchesne merged commit cb68d60 into main Aug 19, 2026
8 checks passed
@JPDuchesne
JPDuchesne deleted the ai/118-pr-d-split-commandexecutor-into-a-dispat branch August 19, 2026 14:42
JPDuchesne added a commit that referenced this pull request Aug 19, 2026
916fe89 Merge pull request #128 from d3mlabs/jpd/skill-installer-ephemeral-guard
b9ea143 Move the ephemeral-source guard to SkillInstaller, the seam all links share
36803f7 Merge pull request #127 from d3mlabs/jpd/capture-learning-root-cause-gate
d6e081e Name the wide-angle goal, not one command: an exact git-log depth invites checkbox compliance
f876dbe capture-learning: gate workaround learnings on root cause, add wide angle
7249198 Merge pull request #123 from d3mlabs/ai/119-pr-b-typed-child-process-failure-taxonom
e71063a Merge pull request #126 from d3mlabs/jpd/hermetic-scrub-guard
f911e38 Make the scrub-list guard hermetic: construct the bundler launch it measures
6c398b8 ai-flow /build: let's resolve conflicts
cb68d60 Merge pull request #122 from d3mlabs/ai/118-pr-d-split-commandexecutor-into-a-dispat
4477c6b Update the manifest-loader contract note for the eager toolchain pass
3ad03c3 Constructor-inject CommandRunner; two messages replace the wait flag
2ac941a Route help through the command path; group and eager-load usage
a78ba14 Add the help builtin
c7ae57a Add Category trait to the Command hierarchy
2e05625 ai-flow /build: let's fix the fake classes, put them within the test class
a29b5e6 Merge main: sealed-module Command hierarchy, super() convention, and bin/test.rb runner
5d9c57b Merge pull request #121 from d3mlabs/ai/117-pr-a-close-the-sealed-command-hierarchy
b8ed631 Call super() in every initializer that derives from the Command hierarchy
5bcc76f Rework the seal: Command becomes a sealed module, BuiltinCommand the abstract open-edge class
5c68c85 Merge pull request #120 from d3mlabs/ai/116-pr-c-bin-test-rb-tee-suite-output-to-a-s
c62efc8 ai-flow /build: PR B: Typed child-process failure taxonomy in CommandRunner (CommandFailedError / CommandKilledError / CommandSpawnError) mapped to exit codes in Runner#exit_for
f09f845 ai-flow /build: PR D: Split CommandExecutor into a dispatching composite with injectable BuiltinExecutor / ProjectExecutor / OverriddenExecutor strategies (exec_into vs run_waiting)
0775515 ai-flow /build: PR A: Close the sealed Command hierarchy honestly — BuiltinBody interface, final BuiltinCommand holding a body, delete the sorbet-runtime ivar pokes, un-private CommandRepository
5a5fb41 ai-flow /build: PR C: bin/test.rb — tee suite output to a stable log artifact and pass file args through to rake TEST
4c89408 Merge pull request #115 from d3mlabs/ai/37-layer-the-dev-runner-application-service
04d461c Add the simplecov-cobertura gem RBI
81677f6 Upload cobertura to codecov instead of SimpleCov JSON
0a741f0 Cover the default factories, image credential providers, and nocov the sealed absurd arm
fe7c94e ai-flow /build: Layer the dev Runner (application service + boundary coercion)
d782b1a Merge pull request #107 from d3mlabs/ai/101-dev-clone-host-global-builtin-cloning-vi
f305ac9 ai-flow /build: codecov coverage missing
fac96ee ai-flow /build: dev clone: host-global builtin cloning via gh auth to the canonical $DEV_CD_ROOT path
d16b757 Merge pull request #100 from d3mlabs/jpd/99-pin-homebrew-installer
2ad614e Pin the Homebrew installer to a commit SHA (dev#99)
53e3616 Merge pull request #90 from d3mlabs/ai/89-gemskilllinker-links-minted-under-a-sand
95ee372 Merge pull request #97 from d3mlabs/ai/learn-promote-rbenv-libruby-rpath-hijack
f7edc33 chore: nudge origin-firing after ai-flow#57 (removal diffs skip green)
646f189 Merge pull request #98 from d3mlabs/jpd/proposal-checks-edited
50e913a proposal-checks: re-verify on PR body edits (ai-flow#54)
59a3146 ai-flow /learn: drop rbenv-libruby-rpath-hijack (promoted to the org tier)
f119987 Merge pull request #96 from d3mlabs/jpd/ai-flow-knowledge-repo
b0e7131 ai-flow config: opt dev into org-tier learning promotion
d17b2ff Merge pull request #95 from d3mlabs/jpd/94-self-defending-entrypoint
29b2e16 Test readability: one aliased scrub list, one property per test
f7197ac Drift guard: the unset list must cover what the running bundler exports
049bbc8 Probe the shim scrub with a stub ruby instead of a full dev command run
88fa953 bin/dev: scrub foreign bundler activation before Ruby boots
9cf868a Merge pull request #92 from d3mlabs/ai/60-plan-pull-mangles-files-with-an-empty-fr
6783469 Merge pull request #93 from d3mlabs/ai/learn-issue-60
991d46d ai-flow /build: capture learnings from the build pass
fe64507 ai-flow /build: Plan pull mangles files with an empty frontmatter block above the real one (double frontmatter)
d8db57d ai-flow /build: GemSkillLinker: links minted under a sandboxed session point into ephemeral sandbox cache paths
b4526ea Merge pull request #88 from d3mlabs/jpd/ast-transform-3.1.1
de9beaf Bump ast_transform to 3.1.1 and drop the heredoc-emission workaround
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant