Skip to content

feat(cli): add plugin invocation protocol#155

Open
QMalcolm wants to merge 3 commits into
qmalcolm--feat-plugin-discoveryfrom
qmalcolm--feat-plugin-io-protocol
Open

feat(cli): add plugin invocation protocol#155
QMalcolm wants to merge 3 commits into
qmalcolm--feat-plugin-discoveryfrom
qmalcolm--feat-plugin-io-protocol

Conversation

@QMalcolm

@QMalcolm QMalcolm commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Stacked on #154

Summary

  • Adds Request, Response, and Issue JSON envelope types for the plugin stdin/stdout protocol
  • Adds Invoke(ctx, pluginDir, invoke, req, pluginStderr) — runs a plugin subprocess, pipes the request JSON to stdin, captures stdout as the response, and enforces a context timeout
  • A non-empty Issues slice in the response is not a Go error; the caller (defined in a later segment of work) will decide whether error-severity issues produce a non-zero exit code

@QMalcolm

Copy link
Copy Markdown
Contributor Author

@QMalcolm
QMalcolm force-pushed the qmalcolm--feat-plugin-discovery branch from 9b858f5 to 9811e05 Compare June 23, 2026 21:05
@QMalcolm
QMalcolm force-pushed the qmalcolm--feat-plugin-io-protocol branch from 37a0bc7 to fce4b4d Compare June 23, 2026 22:06
@QMalcolm
QMalcolm marked this pull request as ready for review June 23, 2026 22:22
QMalcolm added 3 commits June 24, 2026 16:27
C1 (osi convert base) needs a subprocess layer to pipe requests to
plugins and receive responses. This adds the Request/Response/Issue
envelope types and an Invoke() function that owns the full lifecycle:
marshal request to stdin, capture stdout, forward stderr, enforce
context timeout, and decode the JSON response.

Design decisions:
- Invoke takes pluginDir and invoke []string directly rather than
  *Plugin — decouples the invocation layer from the discovery types
  and makes the function independently testable
- cmd.Stdout assigned as *bytes.Buffer rather than using cmd.Output()
  — cmd.Output() is incompatible with a pre-assigned cmd.Stderr writer
  and would silently discard the caller's stderr destination
- ctx.Err() checked after cmd.Run() for timeout detection — exec
  kills the process with SIGKILL and returns "signal: killed", not
  context.DeadlineExceeded, so the context must be checked explicitly
- Issue.Path uses omitempty — an absent key is semantically distinct
  from an empty string; plugins that have no path omit the field
- A non-empty Issues slice is NOT a Go error — the caller (C1) is
  responsible for inspecting severities and determining exit code

Tests use the TestMain subprocess self-invocation pattern: the test
binary re-invokes itself with GO_TEST_PLUGIN=1, enters the fake
plugin branch in TestMain, and exits without running any tests.
This avoids compiling a separate helper binary and works portably.

Caveats:
- File collection, output writing, issue rendering, and wiring into
  cmd/convert.go are deferred to C1
A Request with nil Files marshals as {"files":null} rather than
{"files":{}}. Plugins that iterate over the files object may crash
on a null value. Guard added at the top of Invoke(); test added to
verify the wire format is always an object.
An empty invoke slice would panic with an index out of range on
invoke[0]. While F2 validation prevents this in normal usage, an
explicit check with a descriptive error is safer and makes the
failure mode obvious if the invariant is ever violated.
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.

1 participant