Skip to content

Layer the dev Runner (application service + boundary coercion) - #115

Merged
JPDuchesne merged 4 commits into
mainfrom
ai/37-layer-the-dev-runner-application-service
Aug 17, 2026
Merged

Layer the dev Runner (application service + boundary coercion)#115
JPDuchesne merged 4 commits into
mainfrom
ai/37-layer-the-dev-runner-application-service

Conversation

@JPDuchesne

Copy link
Copy Markdown
Contributor

Implements https://github.com/d3mlabs/plans/issues/37.

Requested by @JPDuchesne.

Closes d3mlabs/plans#37

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

Copy link
Copy Markdown
ContributorAuthor

Agent summary, recovered from the failed dispatch run (GitHub's 503 outage killed the run between the branch push and PR creation; this PR was created manually from the pushed branch with the body ai-flow would have posted):


RuboCop and srb tc are clean, and the suite runs 877 tests with 0 errors — the one remaining failure (Dev::BinDevTest#test_the_scrub_list_covers_every_key_the_running_bundler_exports) is a pre-existing environmental failure that was already failing before this work started: it asserts the test process itself was launched with BUNDLE_GEMFILE exported by bundler activation, which this harness doesn't do. It touches no file changed here.

What landed, per the issue's four PRs:

PR 1 — sealed hierarchy + service onion.src/dev/command.rb holds the sealed Command root with BuiltinCommand (abstract, open edge), ProjectCommand (renamed from ShellCommand), and OverriddenCommand; CommandRegistry, Config, and ConfigParser are deleted. Fourteen builtin classes live under src/dev/builtins/ with constructor-injected collaborators. CommandRepository (private constant, assembly only), CommandService (fetch → guard → execute → stamp), DependencyService (fronts Deps::Staleness with the warn-vs-CI-raise policy), and CommandExecutor (exhaustive case + T.absurd, wait-vs-exec derived from stamps?) form the onion; Runner shrank to usage check, context assembly, one service call, and the rescue-to-exit mapping. The any_instance purge is complete — zero occurrences remain in the suite; brew_integration_test.rb now stubs the instance in hand.

PR 2 — boundary coercion.ProjectManifest + ProjectManifestLoader: dev.yml parses once eagerly, dependencies.rb loads once lazily in run (a test proves dev --help never loads it, via a booby-trapped dependencies.rb), the removed ruby: key is rejected at parse time, and the rescue-to-nil fallback lives once in the loader with a warning test.

PR 3 — CLI shell.Cli::UsagePrinter and Cli::FlagParser extracted; flag parsing moved into CacheCommand/RunnerSetupCommand; exit-code mapping isolated in Runner#exit_for.

PR 4 — synchronizer.Synchronizer.for returns the real instance or UnconfiguredSynchronizer (sync no-op, sync! raises KnowledgeRepoNotConfiguredError); the constructor now requires its cache, and the nil-cache tri-state is gone.

One implementation note worth flagging for review: sorbet-runtime enforces sealing transitively at inheritance time (its inherited hook rides down to BuiltinCommand's subclasses and rejects them for lacking a sealed declaration), even though the static checker allows the issue's intended "sealed root, open abstract edge" shape. command.rb registers BuiltinCommand with an empty decl-file prefix to make the runtime match the static rule — commented in place, since it touches sorbet-runtime internals.

Tests mirror the structure: one file per builtin under test/dev/builtins/, plus command_service_test.rb, command_repository_test.rb (all three assembly branches + not-found), command_executor_test.rb (exhaustive dispatch, wait-vs-exec asserted at the Kernel.system/Kernel.exec boundary, including the dev#85 regression), project_manifest_loader_test.rb (absorbing the old config_parser_test.rb coverage), dependency_service_test.rb, the two CLI tests, and a runner_test.rb slimmed to usage wiring, context assembly, and error mapping. No commits were made, per the instructions.

@codecov

codecovBot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…e sealed absurd arm
codecov/patch flagged 8 lines: the builtins' default collaborator-factory
bodies (every test injects a fake), provide-image's lazy credential
providers (the resolution boundary is stubbed), and CommandExecutor's
T.absurd arm (unreachable by the sealed hierarchy). Each factory now has
one test executing its real composition — real objects over empty temp
projects where the path is inert, construction-boundary mocks where the
real run would hit docker or gh — and the absurd arm is :nocov:'d with
its unreachability argued in place.
Codecov mis-detects SimpleCov's JSON as its legacy V1 format, which
happens to parse until a `# :nocov:` line introduces the "ignored"
value — then processing fails wholesale (codecov/engineering-team#3592,
first hit here by command_executor's sealed absurd arm). Codecov's docs
recommend lcov/cobertura for SimpleCov, so the suite now emits cobertura
(ignored lines simply absent) and CI uploads coverage.xml; HTML stays
for local browsing.
@JPDuchesne
JPDuchesne merged commit 4c89408 into mainAug 17, 2026
5 checks passed
@JPDuchesne
JPDuchesne deleted the ai/37-layer-the-dev-runner-application-service branch August 17, 2026 19:12
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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@JPDuchesne