Skip to content

Align the Cmd integration with shell APIs - #4278

Merged
thomhurst merged 4 commits into
mainfrom
issue-4234-cmd-parity
Aug 29, 2026
Merged

Align the Cmd integration with shell APIs#4278
thomhurst merged 4 commits into
mainfrom
issue-4234-cmd-parity

Conversation

@thomhurst

@thomhurst thomhurst commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • rename ICmd to ICmdContext and document context.Tools.Cmd
  • move Cmd options into ModularPipelines.Options
  • add typed RunFileAsync parity while preserving direct batch-file execution

Validation

  • strict core build: 0 warnings, 0 errors
  • focused TUnit tests: 23 passed
  • changed-file analyzer format gate: passed
  • public API baselines: 61 package projects verified

Closes #4234

Summary by CodeRabbit

  • New Features

    • Added Windows Command Prompt support through context.Tools.Cmd.
    • Added asynchronous execution for inline scripts and batch files.
    • Added strongly typed options for scripts, batch files, echo, Unicode, ANSI output, execution stopping, and AutoRun behavior.
    • Added configurable batch-file execution, including tool and file path overrides.
  • Documentation

    • Updated Command Prompt examples, APIs, and usage guidance.
    • Added ModularPipelines v4 compatibility and migration notes.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-29T01:07:42.734554Z ed7147e New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a73d5f51-10ea-464d-b6e0-0c97bec0fbcc

📥 Commits

Reviewing files that changed from the base of the PR and between 1107de3 and ed7147e.

📒 Files selected for processing (1)
  • RELEASE_NOTES_V4.md

Included review availability: Your plan provides up to 4 included reviews per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The Cmd integration now exposes ICmdContext through context.Tools.Cmd, adds typed command and file options, updates dependency injection and tests, and documents the v4 API and usage patterns.

Changes

Cmd v4 integration

Layer / File(s) Summary
Cmd contracts and option records
src/ModularPipelines.Cmd/ICmd.cs, src/ModularPipelines.Cmd/ICmdContext.cs, src/ModularPipelines.Cmd/Options/*, src/ModularPipelines.Cmd/PublicAPI.*
ICmd was replaced by ICmdContext. CmdScriptOptions moved to ModularPipelines.Options. CmdOptions and CmdFileOptions were added.
Cmd execution and registration
src/ModularPipelines.Cmd/CmdContext.cs, src/ModularPipelines.Cmd/Extensions/CmdExtensions.cs, test/ModularPipelines.UnitTests/*
Registration and resolution now use ICmdContext. File execution accepts CmdFileOptions and passes it to shell execution. Tests cover string paths and explicit tools.
Cmd usage documentation
RELEASE_NOTES_V4.md, docs/docs/mp-packages/cmd.md
The documentation describes context.Tools.Cmd, asynchronous execution, typed options, CommandExecutionOptions, and related v4 API changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to ed714

This PR aligns the Cmd APIs, relocates options, and adds typed file execution while preserving direct batch-file execution; the supplied build, test, formatting, and API checks pass, leaving no actionable merge-blocking risk.

Poem

A rabbit checks the Cmd contract
Typed options follow every track
Context sends the script ahead
Shell returns the result thread
V4 records are neatly spread

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.10% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 9 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: aligning the Cmd integration with the APIs used by other shell integrations.
Linked Issues check ✅ Passed The changes satisfy issue #4234. They rename ICmd to ICmdContext, expose Cmd through context.Tools.Cmd, move Cmd options to ModularPipelines.Options, add typed RunFileAsync support, accept CommandExec…
Out of Scope Changes check ✅ Passed The release notes, documentation, implementation, public API metadata, and focused tests all support the linked Cmd API parity objectives. No unrelated code changes are evident.
Full details: Linked Issues check

Explanation

The changes satisfy issue #4234. They rename ICmd to ICmdContext, expose Cmd through context.Tools.Cmd, move Cmd options to ModularPipelines.Options, add typed RunFileAsync support, accept CommandExecutionOptions, and update documentation and XML comments.

Full details: Docstring Coverage

Explanation

Docstring coverage is 38.10% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 21 functions across 9 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-4234-cmd-parity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown

Greptile Summary

The PR aligns the Cmd integration with the shared shell API conventions and documents the v4 migration.

  • Renames the public context contract from ICmd to ICmdContext and updates dependency injection and generated tool access.
  • Moves Cmd option records into ModularPipelines.Options.
  • Adds typed batch-file execution through CmdFileOptions while retaining the string overload.
  • Updates public API baselines, documentation, release notes, and focused tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
src/ModularPipelines.Cmd/CmdContext.cs Renames the Cmd service implementation and adds typed batch-file execution while preserving the string overload.
src/ModularPipelines.Cmd/ICmdContext.cs Defines the renamed public Cmd context with typed and string overloads for scripts and batch files.
src/ModularPipelines.Cmd/Extensions/CmdExtensions.cs Updates dependency-injection registration and context resolution to consistently use ICmdContext.
src/ModularPipelines.Cmd/Options/CmdFileOptions.cs Introduces the strongly typed options record for direct batch-file execution.
src/ModularPipelines.Cmd/Options/CmdOptions.cs Extracts common Command Prompt flags into a shared options base record.
src/ModularPipelines.Cmd/Options/CmdScriptOptions.cs Moves inline script options to ModularPipelines.Options and derives them from the shared Cmd options.
test/ModularPipelines.UnitTests/Helpers/CmdTests.cs Adds coverage for typed and string batch-file overloads and explicit Tool preservation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    M[Pipeline module] --> T[context.Tools.Cmd]
    T --> C[ICmdContext]
    C -->|RunAsync| S[CmdScriptOptions]
    C -->|RunFileAsync| F[CmdFileOptions]
    S --> E[Shared shell execution]
    F --> E
    E --> R[CommandResult]
Loading

Reviews (6): Last reviewed commit: "fix(cmd): preserve file tool overrides" | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: abe65160bc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/ModularPipelines.Cmd/Options/CmdFileOptions.cs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Summary of changes: renames ICmdICmdContext / CmdCmdContext, moves the Cmd options out of ModularPipelines.Cmd.Models into ModularPipelines.Options (splitting into CmdOptions, CmdScriptOptions, CmdFileOptions), and adds a typed RunFileAsync(CmdFileOptions, ...) overload for parity with IBashContext/IPowerShellContext. PublicAPI.Shipped/Unshipped.txt, tests, docs, and release notes are all updated consistently with the new shape, and the rename itself is clean — no leftover references to the old ICmd/Cmd/ModularPipelines.Cmd.Models.CmdScriptOptions names anywhere in src or test.

Finding: CmdFileOptions.FilePath can silently desync from the executable that actually runs

src/ModularPipelines.Cmd/Options/CmdFileOptions.cs:10

public record CmdFileOptions(string FilePath) : CommandLineToolOptions(FilePath);

FilePath is a normal record positional property — a store separate from the base Tool property, which is what ToolResolver.ResolveTool (src/ModularPipelines/Helpers/Internal/ToolResolver.cs:32, options.Tool ?? ResolveTool(options.GetType())) and CommandLineBuilder.Build (src/ModularPipelines/Context/CommandLineBuilder.cs:57) actually read to pick the executable that gets launched. The constructor happens to set both to the same value, but nothing keeps them in sync afterward.

Because CmdFileOptions is an immutable record, the idiomatic way to change the path is options with { FilePath = "b.cmd" } — and that's exactly where it breaks: the record copy constructor carries the old Tool value forward untouched, so the resulting record's FilePath property reports "b.cmd" while the process that actually launches is still "a.cmd".

Compare with BashFileOptions/PowerShellFileOptions elsewhere in the codebase, where FilePath is a real [CliArgument]-attributed argument that the command builder re-reads each time, so with-mutations stay correct. CmdFileOptions diverges from that established pattern by piggy-backing on Tool directly (understandable, since a batch file is invoked directly rather than via a wrapping cmd/bash tool), but that's what introduces the duplicate, unsynchronized state.

Since this PR's own docs/release-notes changes are actively pointing users at these typed option records for their with-friendly ergonomics, this is a realistic footgun rather than a theoretical one — an automated review bot (Codex) also independently flagged this exact issue, and tracing it through ToolResolver/CommandLineBuilder confirms it's real.

Suggested fix: don't let Tool and FilePath drift independently. The smallest, self-contained fix is to resolve the executable from the current FilePath at the call site in CmdContext.RunFileAsync, right before forwarding to the shell:

public virtual Task<CommandResult> RunFileAsync(
    CmdFileOptions options,
    CommandExecutionOptions? executionOptions = null,
    CancellationToken cancellationToken = default)
{
    return _context.Shell.RunAsync(options with { Tool = options.FilePath }, executionOptions, cancellationToken);
}

This keeps the current public API/property shape intact (no breaking change) while guaranteeing whatever FilePath currently holds — including after any with mutation — is what actually executes.

@thomhurst
thomhurst force-pushed the issue-4234-cmd-parity branch from 1275f66 to 8b9b79b Compare August 28, 2026 23:21
@thomhurst

Copy link
Copy Markdown
Owner Author

@claude review

Comment thread src/ModularPipelines.Cmd/CmdContext.cs Outdated
Comment thread test/ModularPipelines.UnitTests/Helpers/CmdTests.cs

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: Align the Cmd integration with shell APIs

Reviewed the full diff (src/ModularPipelines.Cmd/*, docs/docs/mp-packages/cmd.md, RELEASE_NOTES_V4.md, and 3 test files). The ICmdICmdContext rename is complete and clean — traced every reference across the repo and found no stale usages. The CmdOptions/CmdScriptOptions split preserves the original [CliFlag] behavior exactly, and the existing RunAsync(CmdScriptOptions) code path is unchanged.

Two findings posted inline, both worth addressing before merge:

  1. src/ModularPipelines.Cmd/CmdContext.cs:50RunFileAsync(CmdFileOptions options, ...) unconditionally overwrites options.Tool with options.FilePath via options with { Tool = options.FilePath }. Since Tool is a public init property that ToolResolver.ResolveTool uses to pick the executable, any caller who explicitly sets Tool on the options (e.g. to run the file through cmd.exe rather than directly) has that value silently discarded. Either honor a caller-supplied Tool, or throw/document that it's not supported.

  2. test/ModularPipelines.UnitTests/Helpers/CmdTests.cs:34 — The test for file execution was migrated from the plain RunFileAsync(string path, ...) overload to the new RunFileAsync(CmdFileOptions options, ...) overload. Good coverage of the new API, but the original string-path overload is still public on ICmdContext and now has no test coverage anywhere in the repo. Consider keeping (or adding back) a test for that overload so it isn't shipped untested.

No architectural concerns beyond these — the rename and options split are a reasonable, low-risk refactor.

@thomhurst
thomhurst force-pushed the issue-4234-cmd-parity branch from 9db570f to 558f7d6 Compare August 29, 2026 00:21
@thomhurst

Copy link
Copy Markdown
Owner Author

@claude please review current head 558f7d6.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Summary: This PR renames ICmdICmdContext/CmdCmdContext, moves the Cmd option records from ModularPipelines.Cmd.Models into ModularPipelines.Options (splitting into CmdOptions/CmdScriptOptions/CmdFileOptions), and adds a typed RunFileAsync(CmdFileOptions, ...) overload for parity with IBashContext/IPowerShellContext. Docs, release notes, PublicAPI.* baselines, and tests are updated consistently, and I traced the rename across src/test — no stale ICmd/Cmd/ModularPipelines.Cmd.Models.CmdScriptOptions references remain.

Comparing against the prior review rounds on this PR:

  • Round 1 (abe65160) flagged that CmdFileOptions.FilePath could desync from the Tool actually executed after a with { FilePath = ... } mutation. That was fixed in 9d29c2fc by recomputing Tool from FilePath at the RunFileAsync call site.
  • Round 2 flagged that this fix has a side effect: it unconditionally discards a caller-supplied Tool. That finding is still present on the current head (558f7d6f) — the only commit since round 2 (558f7d6f) is a whitespace-only test change.

Still open: RunFileAsync silently discards a caller-supplied Tool

src/ModularPipelines.Cmd/CmdContext.cs:44-53

public virtual Task<CommandResult> RunFileAsync(
    CmdFileOptions options,
    CommandExecutionOptions? executionOptions = null,
    CancellationToken cancellationToken = default)
{
    return _context.Shell.RunAsync(
        options with { Tool = options.FilePath },
        executionOptions,
        cancellationToken);
}

Tool (src/ModularPipelines/Options/CommandLineToolOptions.cs:30) is documented as a general-purpose init override that every other CommandLineToolOptions-derived record honors when a caller sets it (e.g. to route execution through a different executable). This override unconditionally replaces whatever Tool the caller set with options.FilePath, so a caller who does new CmdFileOptions(path) with { Tool = "cmd.exe" } (e.g. to run the batch file through cmd.exe /c instead of launching it directly — useful for paths cmd needs to resolve via PATHEXT/PATH, or to combine with the CmdOptions flags) has that choice silently thrown away. That's a real inconsistency: every other options type in this codebase treats Tool as "last write wins" via with; CmdFileOptions alone makes it unwritable through the public API.

Since Tool is only ever meant to default to FilePath here (batch files are launched directly rather than through a wrapping shell, unlike Bash/PowerShell), the fix is to only fill it in when the caller hasn't already set one, preserving with-style overrides for everything else:

return _context.Shell.RunAsync(
    options.Tool is null ? options with { Tool = options.FilePath } : options,
    executionOptions,
    cancellationToken);

This keeps the round-1 desync fix (no explicit Tool ⇒ default to the current FilePath) while restoring the override contract that the rest of the options hierarchy relies on.

Still open: no coverage for the RunFileAsync(string path, ...) overload

test/ModularPipelines.UnitTests/Helpers/CmdTests.cs:25-36

The file-execution test was migrated to exercise RunFileAsync(CmdFileOptions, ...) only; the plain RunFileAsync(string path, ...) overload (still public on ICmdContext, still forwards into the code above via RunFileAsync(new CmdFileOptions(path), ...)) has no test anywhere in the repo. This was raised in round 2 and is still true on 558f7d6f. Worth adding a small test (or parameterizing the existing one) so that overload doesn't ship without any coverage.

No other concerns — the rename, options split, and doc/release-notes updates are a clean, low-risk refactor.

@thomhurst

Copy link
Copy Markdown
Owner Author

@claude please review current head 1107de3.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Summary: This PR renames ICmdICmdContext/CmdCmdContext, moves the Cmd option records into ModularPipelines.Options (split into CmdOptions/CmdScriptOptions/CmdFileOptions), and adds a typed RunFileAsync(CmdFileOptions, ...) overload for parity with IBashContext/IPowerShellContext.

Comparing against prior review rounds: the only commit since the last review (558f7d6f) is 1107de39 ("fix(cmd): preserve file tool overrides"), which addresses both outstanding findings:

  1. Tool/FilePath desync (round 1) — previously fixed by computing Tool from FilePath at the RunFileAsync call site.
  2. Caller-supplied Tool silently discarded (round 2) — now fixed properly: CmdFileOptions (src/ModularPipelines.Cmd/Options/CmdFileOptions.cs) no longer forwards FilePath into the base Tool via the constructor (it derives from the parameterless CommandLineToolOptions ctor, so Tool defaults to null), and CmdContext.RunFileAsync (src/ModularPipelines.Cmd/CmdContext.cs:50) now only defaults Tool from FilePath when the caller hasn't already set one: options.Tool is null ? options with { Tool = options.FilePath } : options. This restores the "last write wins" with-override contract that every other CommandLineToolOptions-derived record in the codebase honors, while keeping the round-1 desync fix intact.
  3. Missing coverage for the RunFileAsync(string path, ...) overload (round 2) — now covered by File_String_Overload_Produces_Expected_Output (CmdStringFileModule). A new test, File_Options_Preserve_Explicit_Tool (CmdToolOverrideFileModule), also directly exercises the override-preservation fix by setting Tool explicitly to a valid file path while FilePath points at a nonexistent file, asserting the explicit Tool wins.

I re-traced the rename across src/test again and confirmed no stale ICmd/Cmd/ModularPipelines.Cmd.Models.CmdScriptOptions references remain, and the docs/RELEASE_NOTES_V4.md don't reference the Tool/FilePath internals so nothing there needed updating for this fix.

No further concerns — the rename, options split, and the RunFileAsync fix are consistent with the established CommandLineToolOptions conventions elsewhere in the codebase.

Rename the context contract, move typed options into the shared options namespace, and add file-option parity while preserving direct batch-file execution.
@thomhurst
thomhurst force-pushed the issue-4234-cmd-parity branch from 1107de3 to ed7147e Compare August 29, 2026 01:04
@thomhurst

Copy link
Copy Markdown
Owner Author

@claude please review current head ed7147e.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review

Summary: No new content to review. I diffed the current head (ed7147ebd) against the previously-reviewed CLEAR commit (1107de39, itself equal in content to the prior head) restricted to every file this PR actually touches (src/ModularPipelines.Cmd/**, test/ModularPipelines.UnitTests/Helpers/CmdTests.cs, docs/docs/mp-packages/cmd.md, and the Cmd section of RELEASE_NOTES_V4.md) — the diff is empty. The only change since the last review is a rebase onto a newer main that picked up unrelated merged PRs (hashing/ZIP API unification, module-result metadata renames, installer flattening, etc.); the Cmd section of RELEASE_NOTES_V4.md is byte-identical, just relocated further down the file.

Recap of this PR's history for context: the ICmdICmdContext rename, the CmdOptions/CmdScriptOptions/CmdFileOptions split into ModularPipelines.Options, and the typed RunFileAsync overload were reviewed across four rounds. Two real issues were found and fixed along the way — a Tool/FilePath desync after with { FilePath = ... } mutation, and RunFileAsync silently discarding a caller-supplied Tool override — both resolved in 1107de39 ("fix(cmd): preserve file tool overrides"), which also added test coverage for the previously-untested RunFileAsync(string path, ...) overload. That commit's content carries forward unchanged to the current head.

No new findings; nothing further to address before merge.

@thomhurst
thomhurst merged commit 6b3a426 into main Aug 29, 2026
16 checks passed
@thomhurst
thomhurst deleted the issue-4234-cmd-parity branch August 29, 2026 01:57
Sign up for free to 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.

v4: ModularPipelines.Cmd parity with Bash/PowerShell — reachable as a shell property, options namespace, docs

1 participant