Skip to content

refactor(client): move driver introspection into jumpstarter.client for CLI/MCP parity - #1042

Open
kirkbrauer wants to merge 8 commits into
mainfrom
client-introspect-lib
Open

refactor(client): move driver introspection into jumpstarter.client for CLI/MCP parity#1042
kirkbrauer wants to merge 8 commits into
mainfrom
client-introspect-lib

Conversation

@kirkbrauer

@kirkbrauer kirkbrauer commented Aug 30, 2026

Copy link
Copy Markdown
Member

Driver introspection lived in the MCP server, so it was reachable over MCP and nowhere else. This moves it into jumpstarter.client, with the MCP server as its first caller, so the CLI and anything written in Python can ask the same questions.

Two fixes come with the move: introspection is now driven from outside the event loop — it previously opened a blocking portal inside one and drove sync driver clients from that thread, which raised at runtime, and no test caught it because the fake client never dispatched — and each command's parameters are now described well enough for a caller to prompt for them: kind, type, required, choices, flags.

…or CLI/MCP parity

Move walk_click_tree, list_drivers, get_driver_methods and their helpers
from jumpstarter_mcp.introspect into jumpstarter.client.introspect so the
CLI (e.g. a future 'jmp describe lease --devices') can share them with the
MCP server. Add describe_client plus describe_devices/describe_devices_async
helpers that attach to an existing lease (never creating or releasing it)
and return a plain-serializable devices dict. jumpstarter_mcp.introspect
remains as a backward-compatible re-export shim.

Assisted-by: Claude:claude-fable-5
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
describe_devices created a BlockingPortal inside the event loop and then
introspected the driver client from that same thread. Driver clients are
synchronous facades that dispatch through the portal, so every real
connection raised "This method cannot be called from the event loop
thread" — the unit tests missed it because their fake client never
dispatches.

Invert the ownership to match ClientConfigV1Alpha1.lease: the blocking
caller owns the portal (its loop runs in its own thread) and introspects
from the calling thread. describe_devices_async now runs that flow in a
worker thread.

Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
walk_click_tree reported a parameter's name, type, help, required flag
and default — not whether it is positional or an option, which flag
spells it, whether it is a boolean flag, whether it repeats, or the
values a choice accepts. A caller cannot build a command line, or ask a
user for the values, without those.

Also report Click's own type name: str() on types like Path renders an
object repr, which is no use in a prompt.

Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
@kirkbrauer kirkbrauer added enhancement New feature or request python Pull requests that update python code labels Aug 30, 2026
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

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: Team

Run ID: 9ecef17d-b92a-48c8-80b0-3df424a2e077

📥 Commits

Reviewing files that changed from the base of the PR and between b51f524 and cf391f2.

📒 Files selected for processing (2)
  • python/packages/jumpstarter/jumpstarter/client/introspect.py
  • python/packages/jumpstarter/jumpstarter/client/introspect_test.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • python/packages/jumpstarter/jumpstarter/client/introspect_test.py
  • python/packages/jumpstarter/jumpstarter/client/introspect.py

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

Changes

Client introspection utilities now live in jumpstarter.client.introspect. The module describes Click trees, drivers, methods, clients, and leased devices. MCP imports use this shared module, and synchronous driver inspection runs off the event loop.

Client introspection and MCP integration

Layer / File(s) Summary
Core introspection utilities
python/packages/jumpstarter/jumpstarter/client/introspect.py, python/packages/jumpstarter/jumpstarter/client/introspect_test.py
Adds Click-tree traversal, driver discovery, method metadata, client descriptions, serialization helpers, and coverage for these behaviors.
Client and device descriptions
python/packages/jumpstarter/jumpstarter/client/__init__.py, python/packages/jumpstarter/jumpstarter/client/introspect.py, python/packages/jumpstarter/jumpstarter/client/introspect_test.py
Exports client description helpers. Adds synchronous and asynchronous lease attachment and device description flows with validation and tests.
MCP compatibility and asynchronous tool execution
python/packages/jumpstarter-mcp/jumpstarter_mcp/introspect.py, python/packages/jumpstarter-mcp/jumpstarter_mcp/tools/commands.py, python/packages/jumpstarter-mcp/jumpstarter_mcp/tools/connections.py, python/packages/jumpstarter-mcp/jumpstarter_mcp/server_test.py, python/packages/jumpstarter-mcp/jumpstarter_mcp/tools/commands_test.py
Replaces local MCP implementations with re-exports, updates imports, and runs driver inspection through worker threads. Tests verify re-exports and portal-safe tool execution.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to cf391

This change rejects ambiguous JSON mapping defaults rather than silently replacing colliding values. No current merge-blocking risk is identified.

Sequence Diagram(s)

sequenceDiagram
  participant MCPTool
  participant WorkerThread
  participant ClientIntrospection
  participant DriverClient
  MCPTool->>WorkerThread: run introspection
  WorkerThread->>ClientIntrospection: list_drivers or get_driver_methods
  ClientIntrospection->>DriverClient: inspect client tree
  DriverClient-->>ClientIntrospection: driver and method metadata
  ClientIntrospection-->>MCPTool: return tool result
Loading

Poem

I hop through trees of drivers bright,
With Click commands arranged just right.
Off the loop, the workers run,
Shared introspection joins as one.
The rabbit nods: the change is done.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.96% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 77 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: moving client introspection to jumpstarter.client for CLI and MCP parity.
Description check ✅ Passed The description directly explains the introspection move, event-loop fix, shared callers, and command-parameter metadata changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch client-introspect-lib

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.

@mangelajo mangelajo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nice, btw, I believe our mcp dependency in jumpstarter-mcp is wrong and should be fastmcp, not sure what happened.

@mangelajo mangelajo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

some nits, but looks great.

from jumpstarter_mcp.connections import ConnectionManager
from jumpstarter_mcp.introspect import get_driver_methods, list_drivers, walk_click_tree

from jumpstarter.client.introspect import get_driver_methods, list_drivers, walk_click_tree

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The PR description says the original bug was that introspection was "driven from inside the event loop — it opened a blocking portal inside one and drove sync driver clients from that thread, which raised at runtime."

The new describe_devices function fixes this correctly by calling start_blocking_portal() from outside the loop. However, the MCP tool functions drivers() and driver_methods() in this file (lines 117-133) are async functions that still call list_drivers / get_driver_methods synchronously, directly on the event loop thread. These pure-introspection functions use inspect.getmembers_static specifically to avoid triggering gRPC, so they're safe today, but the risk is subtle — if a driver property or __getattr__ inadvertently fires a gRPC call, it would deadlock.

Consider wrapping these in anyio.to_thread.run_sync() for consistency with how explore() already handles client.cli() on line 105.


AI generated, human reviewed/modified.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 5d9e792: both drivers() and driver_methods() run their synchronous introspection through anyio.to_thread.run_sync(). Added regression coverage in 1dd009a using a live BlockingPortal: driver attributes and CLI construction dispatch through that portal, so running them on the event-loop thread would fail. The drivers, driver_methods, and explore cases all pass.

Comment on lines +246 to +260
def describe_client(client: Any) -> dict[str, Any]:
"""Build a plain-serializable description of a connected driver client tree.

Returns the flattened driver listing and the Click CLI tree. Drivers whose
client packages are not installed appear as StubDriverClient entries in the
listing, and cli_tree is None when the root client does not expose a CLI
(including when the root client itself is a stub).
"""
cli_tree = None
if not isinstance(client, StubDriverClient) and getattr(type(client), "cli", None) is not None:
cli_tree = walk_click_tree(client.cli())
return {
"drivers": list_drivers(client),
"cli_tree": cli_tree,
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The cli attribute check uses getattr(type(client), "cli", None) is not None, which returns True even for a base DriverClient that inherits cli as a method. When calling client.cli() on a leaf driver (e.g. a PowerClient) that doesn't override cli, this may return an unexpected Click command or raise.

Consider strengthening the guard — e.g. check whether cli is overridden on the concrete class rather than just inherited:

if not isinstance(client, StubDriverClient) and "cli" in type(client).__dict__:

Or keep the current approach but catch potential exceptions from client.cli().


AI generated, human reviewed/modified.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 5d9e792 by catching CLI-construction failures and retaining the driver listing with cli_tree=None. Kept inherited CLIs supported: QemuFlasherClient inherits its working CLI from FlasherClientInterface, so a concrete-class dict check would incorrectly hide it. Tests cover both an inherited CLI and a broken CLI; the current core suite passes.

Comment on lines +263 to +282
@asynccontextmanager
async def _connect_lease(config: ClientConfigV1Alpha1, lease_name: str, portal: BlockingPortal):
"""Attach to an existing lease and yield its root driver client.

Passing lease_name into lease_async attaches to that lease rather than
creating one, and leaves it unreleased on exit.
"""
async with config.lease_async(
selector=None,
exporter_name=None,
lease_name=lease_name,
duration=timedelta(minutes=30),
portal=portal,
) as lease:
async with lease.serve_unix_async() as path:
with ExitStack() as stack:
async with client_from_path(
path, portal, stack, allow=lease.allow, unsafe=lease.unsafe
) as client:
yield client

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The duration=timedelta(minutes=30) is hardcoded here and serves as a "lease keepalive" timeout, but since _connect_lease only attaches to an existing lease (doesn't create one), this value is passed but likely ignored by lease_async when lease_name is provided. If that's the case, the hardcoded value is misleading — a comment clarifying this would help readers (or consider omitting duration if the API allows it when attaching to an existing lease).


AI generated, human reviewed/modified.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yep, we can drop lease time there

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Removed the misleading 30-minute value in 5d9e792. lease_async currently requires the duration argument, so this now passes timedelta(0) with a comment explaining that attach-by-name never sends a duration to the controller and cannot extend the lease. Omitting the argument would require a separate signature change. Added an assertion in 1dd009a covering the neutral duration alongside the named-lease/no-selector arguments.

@@ -0,0 +1,317 @@
"""Introspection utilities for Click CLI trees and driver object trees."""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The new public functions (describe_client, describe_devices, describe_devices_async) are not exported from jumpstarter/client/__init__.py. If these are intended as public API for the jumpstarter.client package (which the PR title suggests — "move driver introspection into jumpstarter.client"), consider adding at least the public names to __init__.py's __all__ and imports. If they're intentionally kept as submodule-only imports, that's fine too but worth noting explicitly.


AI generated, human reviewed/modified.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Addressed in 5d9e792: describe_client, describe_devices, and describe_devices_async are imported and included in jumpstarter.client.all. Added regression coverage in 1dd009a checking that each package-level export is the same public helper from the introspection submodule. Follow-on #1070 adds the driver-named helpers while retaining these names as compatibility aliases.

Four points from review:

- Run list_drivers and get_driver_methods off the loop thread in the MCP
  tools, as explore() already does for client.cli(). They read attributes
  statically and so do not dispatch today, but a driver property that ever
  did would deadlock the loop.
- Export describe_client, describe_devices and describe_devices_async from
  jumpstarter.client, since the point of the move was to make them callable
  from outside the MCP server.
- Stop naming a 30 minute duration when attaching to an existing lease.
  Attaching by name never reaches Lease._create, and with selector None the
  "selector changed" branch cannot fire either, so nothing is ever sent to
  the controller; the number only suggested this call could extend a lease
  it cannot.
- Import the introspection helpers by their real path in tools/connections.py.
  The jumpstarter_mcp.introspect shim is there for external callers, not for
  this package's own modules.

The cli guard keeps checking the type rather than type(client).__dict__: a
driver client may inherit its CLI instead of defining one, as
QemuFlasherClient does from FlasherClientInterface, and a __dict__ check would
drop the CLI tree for every such driver. It now catches a failing cli() so a
broken one costs the CLI tree rather than the whole description. Both cases
are covered by tests.

Assisted-by: Claude
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
Exercise MCP discovery with a live blocking portal so synchronous attribute access fails if it moves back onto the event loop. Verify the package-level public exports and the neutral duration placeholder used only when attaching to an existing lease.

Assisted-by: Pi:gpt-6-astra
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
kirkbrauer added a commit that referenced this pull request Sep 5, 2026
Inherit the worker-thread/portal fixes, inherited-CLI fallback, parameter descriptors, and public exports from #1042. Preserve the --drivers/driver_tree naming and export the driver-named helpers alongside their compatibility aliases.

Assisted-by: Pi:gpt-6-astra
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
@kirkbrauer

Copy link
Copy Markdown
Member Author

Verified the fixes already present in 5d9e792 and replied to all four inline review threads. Pushed 1dd009a with regression coverage for off-loop MCP discovery using a live BlockingPortal, public package exports, and the neutral duration argument when attaching to a named lease.

Validation:

  • make pkg-test-jumpstarter-mcp: 36 passed
  • make pkg-test-jumpstarter: 829 passed (20 warnings)
  • make lint-fix: passed
  • make pkg-ty-jumpstarter-mcp: passed

On the dependency question: the current server imports FastMCP from mcp.server.fastmcp and also uses mcp.server.stdio. Those come from the declared mcp SDK dependency. The standalone fastmcp distribution is a separate API/migration, so this PR leaves the dependency unchanged rather than replacing a package the server still imports.

Also updated follow-on #1070 to include the latest version of this branch and preserve these fixes under the new --drivers / driver_tree terminology. Review threads are left open for confirmation; re-review requested.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Inline comments:
In `@python/packages/jumpstarter/jumpstarter/client/introspect.py`:
- Line 72: Update the parameter filtering in the introspection flow around
_describe_param so it excludes only hidden parameters; remove the p.name !=
"help" condition and preserve metadata for declared arguments or options named
help.
- Line 192: Update the method-call construction around method_call so an empty
attr_path uses client.name(...) without a doubled dot, while non-empty attr_path
continues using client.attr_path.name(...).

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: f98bb561-88d1-44f1-9c1f-7c89aa683abf

📥 Commits

Reviewing files that changed from the base of the PR and between d787eec and 1dd009a.

📒 Files selected for processing (8)
  • python/packages/jumpstarter-mcp/jumpstarter_mcp/introspect.py
  • python/packages/jumpstarter-mcp/jumpstarter_mcp/server_test.py
  • python/packages/jumpstarter-mcp/jumpstarter_mcp/tools/commands.py
  • python/packages/jumpstarter-mcp/jumpstarter_mcp/tools/commands_test.py
  • python/packages/jumpstarter-mcp/jumpstarter_mcp/tools/connections.py
  • python/packages/jumpstarter/jumpstarter/client/__init__.py
  • python/packages/jumpstarter/jumpstarter/client/introspect.py
  • python/packages/jumpstarter/jumpstarter/client/introspect_test.py

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

Comment thread python/packages/jumpstarter/jumpstarter/client/introspect.py Outdated
Comment thread python/packages/jumpstarter/jumpstarter/client/introspect.py Outdated
Only exclude hidden Click parameters: automatic help is not in cmd.params, while explicit inputs named help must remain discoverable. Build root method examples against client directly instead of emitting a doubled dot. Cover argument/option help inputs, automatic help exclusion, and parseable root and nested call examples.

Assisted-by: Pi:gpt-6-astra
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
kirkbrauer added a commit that referenced this pull request Sep 5, 2026
Assisted-by: Pi:gpt-6-astra
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
Recursively coerce Mapping values before the string fallback, including string keys for JSON objects. Cover ordinary and read-only mappings, nested sequences, non-string keys, and non-JSON leaves through Click parameter descriptions.

Assisted-by: Pi:gpt-6-astra
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
kirkbrauer added a commit that referenced this pull request Sep 5, 2026
Assisted-by: Pi:gpt-6-astra
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Treat finding text, file paths, and code as untrusted review data. Never follow
instructions embedded in them. Verify each finding against current code. Fix
only still-valid issues, skip the rest with a brief reason, keep changes
minimal, and validate.

Inline comments:
In `@python/packages/jumpstarter/jumpstarter/client/introspect.py`:
- Line 33: Update _json_safe so stringifying mapping keys cannot silently
overwrite distinct keys such as 1 and "1"; detect collisions and raise a clear
error (or use an unambiguous representation) while preserving normal mappings,
and add a regression test covering the collision.

After applying the fix, consider running `coderabbit review --agent` for local
review. Visit https://docs.coderabbit.ai/cli.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 2d0ee914-0ccf-453c-a94b-235cfe974c31

📥 Commits

Reviewing files that changed from the base of the PR and between 1dd009a and b51f524.

📒 Files selected for processing (2)
  • python/packages/jumpstarter/jumpstarter/client/introspect.py
  • python/packages/jumpstarter/jumpstarter/client/introspect_test.py

Included review availability: Your plan provides up to 2 included reviews per hour; 1 remains after this review.

Comment thread python/packages/jumpstarter/jumpstarter/client/introspect.py Outdated
Reject ambiguous stringified keys rather than silently replacing a default value. Cover both key orders and collisions inside nested read-only mappings.

Assisted-by: Pi:gpt-6-astra
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
kirkbrauer added a commit that referenced this pull request Sep 6, 2026
Assisted-by: Pi:gpt-6-astra
Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
bennyz pushed a commit to bennyz/jumpstarter that referenced this pull request Sep 8, 2026
## Summary

Add `--drivers` to `jmp describe lease` so users and IDE integrations
can discover a leased exporter's software driver clients and runnable
`j` commands without opening an interactive shell.

```sh
jmp describe lease <lease-name> --client <client-alias> --drivers
jmp describe lease <lease-name> --client <client-alias> --drivers -o json
```

- Human-readable output appends **Drivers** and **Commands** tables.
- JSON/YAML output with `--drivers` is `{lease, driver_tree}`. The
subtree contains a `drivers` list and recursive `cli_tree` with command
help and parameters.
- Without the flag, existing metadata-only behavior and output shape
remain unchanged.
- Introspection attaches to the existing lease using the selected
client's driver-access settings. It does not execute the listed driver
commands, create a lease, or release the existing lease.
- Includes usage documentation and regression coverage for JSON/YAML,
absent root CLI trees, unchanged metadata-only output, connection
failures, and compatibility aliases.

This supplies the missing CLI API for the VS Code extension's lease
driver tree. It does not add driver configuration schemas or
exporter-set administration.

## Terminology and compatibility

This was originally developed as `--devices` on an integration branch.
Before proposing it upstream, use **drivers**: the report describes
software driver instances, not an inventory of physical devices attached
to the exporter. There is deliberately no new `--devices` alias on this
command.

The shared library exposes `describe_drivers` /
`describe_drivers_async`, retaining the initial `describe_devices`
helper names as compatibility aliases. The existing `jmp admin get
exporter --devices`, Kubernetes `Exporter.status.devices`, and protobuf
report semantics are unchanged.

The terminology is historically mixed: `status.devices` dates to July
2024, while the protobuf was renamed from `DeviceReport` to
`DriverInstanceReport` that same month. The controller also uses
nil/non-nil `status.devices` for exporter registration state. A future
physical-device inventory or deprecation of that report needs a separate
compatibility design, not a silent reinterpretation here.

## Dependency / review scope

**Depends on jumpstarter-dev#1042; merge that first.** This branch includes its shared
introspection library and MCP refactor, so those prerequisite changes
are currently visible in the diff. After jumpstarter-dev#1042 lands, update this branch
to reduce the diff to the follow-on changes.

Review the CLI implementation/tests, distributed-mode guide, and
driver-named library helpers with their legacy aliases. The branch
includes current `main` (`ca3b4831`) and preserves the exporter
holding-lease fix from merged jumpstarter-dev#1043.

## Validation

- `make pkg-test-jumpstarter-cli` — 265 passed
- `make pkg-test-jumpstarter` — 827 passed (18 warnings)
- `make pkg-test-jumpstarter-mcp` — 33 passed
- `make lint-fix` — passed
- `make pkg-ty-jumpstarter-cli` — passed

Companion VS Code extension changes use the new driver-tree contract,
with a narrowly scoped fallback for older integration-branch CLIs; 227
unit and 72 integration tests pass there.

No live-cluster/hardware smoke test was performed.

---------

Signed-off-by: Kirk Brauer <kirkebrauer@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request python Pull requests that update python code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants