Skip to content

feat(opencode): repo-local Impeccable design-hook plugin#191

Merged
marcusrbrown merged 2 commits into
mainfrom
feat/opencode-impeccable-plugin
Jul 10, 2026
Merged

feat(opencode): repo-local Impeccable design-hook plugin#191
marcusrbrown merged 2 commits into
mainfrom
feat/opencode-impeccable-plugin

Conversation

@marcusrbrown

Copy link
Copy Markdown
Collaborator

Adds a repo-local OpenCode plugin that runs Impeccable's design detector after file-mutating tool calls and surfaces any findings back to the agent in the same turn — giving OpenCode the in-session design feedback the Claude/Codex/Cursor/Copilot harnesses already get. Advisory only; it never blocks an edit.

How it works

The plugin bridges OpenCode's tool.execute.after hook into the same shared brain the Copilot hook uses (.agents/skills/impeccable/scripts/hook.mjs) rather than reimplementing detection. On a file-mutating tool it shapes the touched path into the event hook.mjs expects, runs it, and appends the human-readable findings to the tool result. Clean edits stay silent; a broken bridge fails loud instead of masquerading as "no findings"; detection is bounded by a ~5s timeout. Because it depends only on hook.mjs's stable contract and lives outside Impeccable's four managed harness targets, it keeps working across npx impeccable update.

Third runtime, isolated from the app

The plugin runs in OpenCode's Bun plugin host — a distinct runtime from the Node-24 strip-only server (src/) and the Vite/React client (web/). It never executes in the server, the web bundle, the Docker image, or the CI impeccable detect gate. To keep it cleanly separated:

  • .opencode/impeccable/ is excluded from the server's strip-only tsconfig.json and ESLint config and given its own .opencode/tsconfig.json (full TS is valid there).
  • Pure, framework-free logic lives in hook-bridge.ts (Node-testable); the Bun-$ runner and single-function plugin export live in plugin.ts.
  • @opencode-ai/plugin is added as a type-only devDependency.

Verification

Verified live in a real OpenCode session: a design issue surfaces clean feedback text in the tool result, a clean edit produces nothing and is never blocked, and the plugin files are byte-identical after npx impeccable update. Gates green: check-types (server + web + .opencode), lint, and the full test suite (31 plugin unit tests covering the bridge logic and state machine).

The only CI-facing surface is the config/lockfile changes; the plugin itself is dev tooling.

Bridges OpenCode's tool.execute.after into the shared Impeccable hook.mjs
brain so file-mutating edits get in-session design feedback, mirroring the
Copilot hook. Advisory-only, ~5s timeout, fail-loud on bridge errors. Pure
helpers and an injectable detector runner keep the logic Node-testable; the
Bun-$ runner is wired only in the exported plugin factory.
@fro-bot fro-bot added the agent: working Agent is currently working on this label Jul 10, 2026

@fro-bot fro-bot left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Reviewed the diff directly (no ce:* skills invoked). This PR adds a repo-local OpenCode plugin (.opencode/impeccable/) that bridges to the existing, unmodified hook.mjs design detector. Verified locally: vitest run .opencode/impeccable/plugin.test.ts → 31/31 pass; tsc --noEmit -p .opencode/tsconfig.json and root tsc --noEmit → clean; pnpm install --frozen-lockfile --dry-run → lockfile consistent, supply-chain policy passes.

Verdict: PASS

Blocking issues

None.

Non-blocking concerns

  • hook-bridge.ts withTimeout (lines 143-153): on timeout the underlying runDetector promise/subprocess is not cancelled or awaited — it keeps running detached. Bounded by the 5s timeout and low call frequency, so impact is minor, but worth a follow-up if tool-call volume ever gets high (e.g. rapid multi-file edits could pile up orphaned node hook.mjs processes).
  • plugin.ts createDefaultRunner (line 27) spreads the full process.env into the detector subprocess. Not a live risk here — hook.mjs is a trusted, unmodified in-repo script, this plugin only runs in a developer's local Bun/OpenCode host (never in the Node-24 server, CI impeccable detect gate, or the shipped app), and the script doesn't echo env back to stdout/stderr. Flagging only because the repo holds a GitHub App private key elsewhere — no action needed unless this pattern is later reused somewhere with a narrower trust boundary.
  • vitest.config.ts broadened exclude from 'node_modules' to '**/node_modules/**' — correct given the new .opencode/impeccable/**/*.test.ts include, but double-check it doesn't unintentionally widen matching elsewhere (looked fine on the dry run above).

Missing tests

None — plugin.test.ts covers the pure bridge logic thoroughly: tool-name matching (bare + MCP-suffixed), path extraction for edit/write/apply_patch, payload shaping, timeout degradation (no-op, no throw), fail-loud on nonzero exit and runner throw, single-warning-per-session dedup, and stdout parsing (JSON envelope, snake_case, plain text, empty/whitespace). The Bun-only plugin.ts shell (createDefaultRunner) is intentionally left untested under Node, which is reasonable since it's a thin, side-effect-only wrapper verified manually per the PR description.

Risk assessment: LOW

Fully isolated dev-only tooling: no changes to src/ (server) or web/ (client), correctly excluded from the server's strip-only tsconfig.json/ESLint (verified: eslint reports "ignored" for both new files, both tsc invocations pass clean), advisory-only (never blocks a tool call, fails loud instead of silently swallowing errors), and the only production-facing surface is the lockfile/config diff. @opencode-ai/plugin is added as type-only (confirmed import type usage) so it carries no runtime footprint. No auth, secrets, or API-contract surfaces touched.


Run Summary
Field Value
Event pull_request
Repository fro-bot/dashboard
Run ID 29129987797
Cache hit
Session ses_0b1acb22fffeX6ud3ZqZas0da1

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.

2 participants