Skip to content

Commit 281e39c

Browse files
Merge remote-tracking branch 'origin/cowork/improve-devforge-cli' into cowork/improve-devforge-cli
2 parents 6766e94 + 4a75601 commit 281e39c

3 files changed

Lines changed: 40 additions & 6 deletions

File tree

.github/workflows/cowork-auto-pr.yml

100755100644
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ jobs:
1212
ensure-pr:
1313
runs-on: ubuntu-latest
1414
steps:
15+
# gh pr create requires a local git checkout to diff head against base;
16+
# without this step every run failed with "not a git repository" and no
17+
# PR was ever opened (fleet-wide defect: 11/11 seeded copies lacked it).
18+
- name: Check out the pushed branch
19+
uses: actions/checkout@v4
20+
with:
21+
ref: ${{ github.ref_name }}
22+
fetch-depth: 0
1523
- name: Open PR for this branch if none exists
1624
env:
1725
GH_TOKEN: ${{ github.token }}

src/devforge/cli.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,7 @@ def _make_dispatch(tool_name: str):
153153
"""Create a typer command that dispatches to the underlying tool CLI."""
154154
pkg = TOOLS[tool_name]["package"]
155155

156-
def dispatch(
157-
args: list[str] = typer.Argument(None, help="Arguments to pass to the tool."), # noqa: B008
158-
):
156+
def dispatch(ctx: typer.Context):
159157
info = TOOLS.get(tool_name)
160158
if not info:
161159
console.print(f"[red]Unknown tool: {tool_name}[/red]")
@@ -170,8 +168,12 @@ def dispatch(
170168
)
171169
raise typer.Exit(code=1)
172170

171+
# `ignore_unknown_options` + `allow_extra_args` let tool flags (e.g.
172+
# `--config file.yaml`) reach the underlying CLI instead of being
173+
# rejected by typer as "No such option".
174+
forwarded = list(ctx.args)
173175
result = subprocess.run(
174-
[sys.executable, "-m", module_name] + (args or []),
176+
[sys.executable, "-m", module_name] + forwarded,
175177
capture_output=True,
176178
text=True,
177179
)
@@ -183,11 +185,14 @@ def dispatch(
183185

184186
dispatch.__name__ = tool_name
185187
dispatch.__doc__ = f"Run `{pkg}` commands via the {tool_name} subcommand."
186-
return dispatch
188+
return app.command(
189+
name=tool_name,
190+
context_settings={"ignore_unknown_options": True, "allow_extra_args": True},
191+
)(dispatch)
187192

188193

189194
for cmd_name in TOOLS:
190-
app.command(name=cmd_name)(_make_dispatch(cmd_name))
195+
_make_dispatch(cmd_name)
191196

192197

193198
def main():

tests/test_cli.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,27 @@ def test_dispatch_installed_tool_runs(self, mock_run, _mock_installed):
134134
cmd = mock_run.call_args[0][0]
135135
assert "api_contract_guardian" in cmd
136136

137+
@mock.patch("devforge.cli._is_tool_installed", return_value=True)
138+
@mock.patch("devforge.cli.subprocess.run")
139+
def test_dispatch_forwards_tool_flags(self, mock_run, _mock_installed):
140+
"""Tool flags (e.g. `--config file.yaml`) must reach the underlying CLI.
141+
142+
Regression guard for the silent-failure trap where typer rejected any
143+
argument beginning with `-` as 'No such option' before the tool ran.
144+
With ignore_unknown_options/allow_extra_args, such flags are forwarded
145+
via ctx.args.
146+
"""
147+
mock_run.return_value = mock.MagicMock(returncode=0)
148+
with mock.patch("devforge.cli.sys.exit"):
149+
runner.invoke(app, ["guard", "--config", "x.yaml", "--verbose"])
150+
mock_run.assert_called_once()
151+
cmd = mock_run.call_args[0][0]
152+
# Underlying module is launched...
153+
assert "api_contract_guardian" in cmd
154+
# ...and the tool flags are forwarded, not swallowed by typer.
155+
assert "--config" in cmd
156+
assert "x.yaml" in cmd
157+
assert "--verbose" in cmd
137158

138159
@mock.patch("devforge.cli._is_tool_installed", return_value=False)
139160
def test_dispatch_install_hint_escapes_extra_brackets(self, _mock):

0 commit comments

Comments
 (0)