Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copilot Instructions

This is an [OpenCode](https://opencode.ai) plugin written in TypeScript. It delegates tasks to GitHub Copilot CLI (`copilot -p`) as background subprocesses and reports results back through OpenCode's tool system.

## Project Conventions

Follow AGENTS.md for architecture, coding standards, and module boundaries.

## Critical Rules

- **No type suppression**: never use `as any`, `@ts-ignore`, or `@ts-expect-error`
- **No implicit any**: all function parameters and return types must be inferrable or annotated
- **Discriminated unions over optional properties**: prefer `{ type: 'success', data } | { type: 'error', message }` over `{ data?: T, error?: string }`
- **Parse, don't validate**: use Zod schemas for external input; trust internal types
- **Structured errors**: tools return `{ error: string }` objects, never throw

## Stack

- **Runtime**: Bun
- **Language**: TypeScript (strict mode)
- **Linting/formatting**: Biome (not ESLint/Prettier)
- **Test framework**: `bun:test`
- **Build**: `bun build` + `tsc --emitDeclarationOnly`
- **Dependencies**: `fkill` for process cleanup; `@opencode-ai/plugin` and `@opencode-ai/sdk` as peer dependencies

## Verification Commands

Run all four before considering work complete:

```bash
bun test # Unit + integration tests
bun run typecheck # tsc --noEmit (strict)
bun run lint # biome check .
bun run build # Bundle + declaration emit
```

## Module Boundaries

| Module | Owns | Must Not |
|--------|------|----------|
| `src/tools/` | Tool definitions, schema, execute functions | Import from other tools |
| `src/runtime/` | Subprocess management, task registry, JSONL parsing, notifications | Reference OpenCode plugin/tool APIs directly |
| `src/discovery/` | Agent file discovery, description builder | Depend on runtime or tools |
| `src/index.ts` | Plugin entrypoint, wiring | Contain business logic |

## Changeset Policy

This package uses `0.x` unstable versioning. User-visible changes require a `.changeset/*.md` entry with a `minor` bump (not `patch`).
31 changes: 31 additions & 0 deletions .github/workflows/copilot-setup-steps.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
name: Copilot Setup Steps

on:
workflow_dispatch:
push:
paths: [.github/workflows/copilot-setup-steps.yaml]
pull_request:
paths: [.github/workflows/copilot-setup-steps.yaml]

permissions:
contents: read

jobs:
copilot-setup-steps:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Build
run: bun run build
87 changes: 87 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# AGENTS.md

## What This Project Is

`opencode-copilot-delegate` is an OpenCode plugin that spawns GitHub Copilot CLI (`copilot -p`) as background subprocesses. It exposes three tools to OpenCode sessions:

- **`copilot_delegate`** — Spawn a Copilot CLI subprocess with a prompt and optional agent/model
- **`copilot_output`** — Retrieve results from a running or completed delegation (supports blocking with timeout)
- **`copilot_cancel`** — Cancel a running delegation and kill its process tree

## Architecture

```
src/
├── index.ts # Plugin entrypoint — wires tools to runtime
├── tools/
│ ├── delegate.ts # copilot_delegate tool
│ ├── output.ts # copilot_output tool
│ └── cancel.ts # copilot_cancel tool
├── runtime/
│ ├── subprocess.ts # Spawns copilot CLI, streams JSONL stdout
│ ├── task-registry.ts # In-memory task state (create/get/update/delete/cleanup)
│ ├── jsonl-parser.ts # Single-line JSONL parser for Copilot CLI output
│ ├── envelope.ts # Builds structured output envelopes from parsed events
│ └── notify.ts # Injects completion notifications into OpenCode sessions
├── discovery/
│ ├── agents.ts # Discovers .agent.md files from Copilot agent directories
│ └── description.ts # Builds copilot_delegate tool description from discovered agents
tests/
├── jsonl-parser.test.ts # Parser unit tests
├── envelope.test.ts # Envelope builder tests
├── subprocess.test.ts # Subprocess wrapper tests (fake copilot binary)
├── discovery.test.ts # Agent discovery tests (temp fixture dirs)
├── notify.test.ts # Notification injection tests
└── tools.test.ts # Tool integration tests (full plugin lifecycle)
tests/fixtures/
└── jsonl/ # Real Copilot CLI JSONL captures (PII-scrubbed)
```

## Design Decisions

- **Peer dependencies**: `@opencode-ai/plugin` and `@opencode-ai/sdk` are peers — the host OpenCode install provides them
- **Single-line JSONL parser**: `parseJsonlLine` handles one line at a time and returns `{ type: 'unknown' }` for malformed input. Stream-level multiline accumulation belongs in the subprocess wrapper
- **Task IDs**: prefixed with `cpl_` to distinguish from OpenCode-native task IDs
- **Process cleanup**: uses `fkill` with `{ force: false, forceTimeout: 2000, waitForExit: 5000 }` and `.catch()` guards on all `killProcessTree` calls in abort handlers
- **Notification safety**: in-flight counter is decremented synchronously (before any `await`) in close handlers; `isShuttingDown` checks gate `prompt` calls; counter map entries are deleted at zero to prevent memory leaks
- **Agent discovery**: builtin agents (bundled with Copilot CLI) cannot be overridden by user or repo agents
- **Structured errors**: tools return `{ error: string }` objects, never throw exceptions

## Coding Standards

### TypeScript

- Strict mode (`strict: true` in tsconfig)
- No `as any`, `@ts-ignore`, or `@ts-expect-error`
- Prefer `satisfies` over type annotations when you want inference
- Discriminated unions over optional properties
- `const` assertions for literal types
- ESM imports only (this is a `"type": "module"` package)

### Formatting and Linting

Biome handles both. Configuration in `biome.json`:
- 2-space indent, single quotes, no semicolons (ASI)
- Recommended lint rules enabled

Run `bun run lint` to check, `bun run fix` to auto-fix.

### Testing

- Framework: `bun:test`
- Pattern: arrange-act-assert with real filesystem fixtures (temp dirs)
- Subprocess tests use a fake `copilot` shell script that emits JSONL
- No mocking libraries — use plain functions and temp dirs
- Tests must be deterministic: no wall-clock timing assertions, use injected timestamps

### Commits

- Format: `feat(scope): description`, `fix(scope): description`, `chore(scope): description`
- Scopes: `runtime`, `tools`, `discovery`, `ci`, `docs`
- User-visible changes require a `.changeset/*.md` with `minor` bump (unstable `0.x` series)

## Security Constraints

- No secrets, PATs, tokens, or PII in tool return values or log output
- Copilot CLI prompts are visible in `ps` output (upstream limitation) — avoid delegating prompts containing secrets
- Process environment is inherited from the parent OpenCode process (trusted local chain)