openpetswithchatandmcp/docs/phases/phase-19c-opencode-cli-project-setup.md
2026-05-10 13:30:48 +02:00

274 lines
12 KiB
Markdown

# Phase 19C — OpenCode CLI Project Setup
## Goal
Extend the OpenPets CLI so users can configure a project for OpenCode with one command:
```bash
npx @open-pets/cli configure --agent opencode --pet fixer
```
This writes project-local OpenCode config for OpenPets MCP, OpenPets instructions, and the OpenPets OpenCode plugin.
## Non-goals
- No Desktop Integrations OpenCode UI.
- No global OpenCode setup from desktop.
- No project directory picker UI.
- No OpenCode runtime plugin changes beyond using Phase 19B's plugin package/export.
- No new public MCP tools.
- No OpenCode source changes under `v1/opencode/`.
- No real user config writes in tests.
## User-visible/manual outcome
From a project directory, users can run:
```bash
npx @open-pets/cli configure --agent opencode --pet fixer
```
Expected result:
- OpenCode project config contains `mcp.openpets`.
- OpenCode project config includes OpenPets instruction file `.opencode/openpets.md`.
- OpenCode project config includes OpenPets plugin spec/options targeting `fixer`.
- Starting OpenCode from that project gives OpenPets MCP tools and plugin-driven reactions for `fixer`.
If `--pet` is omitted, the CLI uses the same installed-pet picker as Claude setup and therefore requires OpenPets desktop to be running.
## Acceptance criteria
- `parseConfigureArgs` accepts `--agent opencode` and still accepts/keeps `--agent claude` behavior unchanged.
- Unsupported agents still fail clearly.
- `openpets configure --agent opencode --pet <id> --cwd <project>` runs without requiring OpenPets desktop.
- `openpets configure --agent opencode` without `--pet` uses local IPC pet listing/picker like Claude.
- OpenCode setup does not require `opencode` binary on `PATH`; if detection is added, it is warning-only.
- Project path validation rejects symlinked project roots.
- OpenCode config writes use Phase 19A helpers:
- project config candidate selection;
- all existing project config candidate scanning before writing;
- JSON/JSONC parsing/updating;
- backup/temp/atomic write safety;
- symlink and escape rejection.
- If no project OpenCode config exists, create `.opencode/opencode.jsonc`.
- Existing unrelated OpenCode config keys are preserved.
- Existing non-OpenPets MCP/plugin/instruction entries are preserved.
- Existing matching OpenPets entries are idempotent.
- Existing stale managed OpenPets entries are updated.
- Custom/foreign `mcp.openpets` or OpenPets-like plugin/instruction entries must not be overwritten. In this phase, fail clearly and tell the user to edit/remove the custom entry manually.
- `--force` may replace stale managed OpenPets entries, but must not overwrite custom/foreign entries.
- Written project config uses published mode by default:
```jsonc
{
"mcp": {
"openpets": {
"type": "local",
"command": ["npx", "-y", "@open-pets/cli@0.0.0", "mcp", "--pet", "fixer"],
"enabled": true
}
},
"instructions": [".opencode/openpets.md"],
"plugin": [["@open-pets/opencode@0.0.0", { "pet": "fixer" }]]
}
```
- With `--local-dev`, generated MCP config may use `node <current cli dist/index.js> mcp --pet fixer`, but the plugin spec should remain package-based unless a safe local plugin file path policy is implemented in a later phase.
- The package-based plugin spec must be version-pinned to the same package version used for generated MCP commands, e.g. `@open-pets/opencode@<version>`.
- The managed instruction file `.opencode/openpets.md` is written with OpenPets managed markers.
- Instruction file writes must be safe:
- reject symlinked `.opencode/openpets.md`;
- reject oversized instruction files;
- preserve user content outside managed markers;
- upsert the managed OpenPets block if the expected path exists without a managed block;
- backup before destructive update;
- temp-file + rename atomic write;
- no instruction write if config planning fails.
- CLI output prints:
- configured agent (`OpenCode`);
- target project path;
- selected pet id/name;
- config file path changed;
- instruction file path changed;
- a warning that `.opencode/opencode.jsonc` and `.opencode/openpets.md` can be committed and contain the selected pet id;
- restart guidance for OpenCode.
- Tests cover offline explicit-pet setup, idempotency, preserving unrelated config, custom/foreign conflict refusal, symlink rejection, and Claude regression.
## Proposed files/directories
Likely changed files:
- `packages/cli/package.json`
- `packages/cli/src/index.ts`
- `packages/cli/src/check-cli-contract.ts`
- `packages/opencode/src/opencode-config.ts`
- `packages/opencode/src/opencode-previews.ts`
- `packages/opencode/src/opencode-status.ts`
- `packages/opencode/src/check-opencode-foundation.ts`
Possible new file:
- `packages/opencode/src/opencode-project-setup.ts`
## Technical approach
### CLI flow
Keep existing Claude flow intact and branch in `configureProject` by `options.agent`:
```ts
if (options.agent === "claude") return configureClaudeProject(options)
if (options.agent === "opencode") return configureOpenCodeProject(options)
```
Update `ConfigureOptions.agent` to `"claude" | "opencode"`.
### OpenCode project setup helper
Prefer putting most OpenCode-specific write logic in `packages/opencode`, not in the CLI, so desktop Phase 19D can reuse the same primitives.
Potential API:
```ts
prepareOpenCodeProjectSetup({
projectDir,
petId,
cliVersion,
commandMode,
cliEntryPath,
}): PreparedOpenCodeProjectSetup
writePreparedOpenCodeProjectSetup(prepared): OpenCodeProjectSetupResult
```
The helper should:
1. Validate project root.
2. Select config write target.
3. Read and classify **all existing project config candidates** because OpenCode can merge top-level and `.opencode` configs.
4. Fail on custom/foreign/conflicting OpenPets entries anywhere in those candidates.
5. Read the selected write target or `{}`.
6. Classify selected-target OpenPets entries for idempotent updates.
7. Add/update:
- `mcp.openpets`;
- `instructions` containing `.opencode/openpets.md` once;
- `plugin` containing version-pinned `@open-pets/opencode@<version>` once with `{ pet }`.
8. Plan `.opencode/openpets.md` managed block upsert.
9. Plan safe config writes via Phase 19A helpers.
10. Execute writes only after all config and instruction write plans have succeeded.
The setup must be two-phase: validate/classify/plan all writes first, then execute. If any plan fails, write nothing.
### Instruction file content
Use the same guidance as Claude memory, adapted for OpenCode:
- OpenPets MCP tools may be available.
- Use `openpets_say` for meaningful short status/personality messages.
- Keep messages brief, user-facing, and non-sensitive.
- Do not include code, logs, secrets, URLs, or file paths.
- Use `openpets_react` for visual feedback.
- Use `openpets_status` only when checking availability or target pet.
- Do not spam every internal step.
### Conflict policy
Status helpers from Phase 19A distinguish `installed`, `needs_update`, `custom`, and `conflict`.
For Phase 19C:
- `installed`: leave as-is unless generated content differs only in managed block, then refresh instruction block.
- `needs_update`: update managed entries.
- `not_installed`: install entries.
- `custom` / `conflict`: fail clearly and do not write config.
This avoids overwriting user-owned `openpets` entries.
If the expected instruction path is present but lacks the managed block, treat it as an instruction `needs_update`: upsert the managed block while preserving existing file content outside managed markers.
### Offline behavior
Reuse existing `resolveConfiguredPet` behavior:
- explicit `--pet` validates syntax only and does not require desktop;
- omitted `--pet` queries installed pets through local IPC.
## Risks and tradeoffs
- Project `.opencode/opencode.jsonc` and `.opencode/openpets.md` can be committed. CLI must warn clearly.
- Direct JSONC editing risks data loss. Use existing parse guards, backups, temp writes, and no-write-on-error policy.
- Package-based plugin spec assumes `@open-pets/opencode` is published alongside CLI. This is correct for published mode; local plugin path setup is deferred.
- `--local-dev` only affects MCP command in this phase. Plugin local-dev path is deferred to avoid unsafe path/config churn.
## Security/privacy notes
- Do not write outside the project root.
- Reject symlinked project roots and unsafe config paths.
- Preserve unrelated OpenCode config.
- Do not overwrite custom/foreign OpenPets-like entries.
- Do not perform partial writes; if any plan fails, no config or instruction file should be written.
- Do not expose prompts, code, logs, URLs, paths, or secrets in instruction text beyond generic warnings.
- Tests must use temp directories only.
## Test/check plan
- `pnpm --filter @open-pets/opencode check`
- `pnpm --filter @open-pets/cli check`
- `pnpm --filter @open-pets/claude check`
- `pnpm check` after implementation review fixes.
Specific tests:
- `parseConfigureArgs(["--agent", "opencode", "--pet", "fixer"])` works.
- Unsupported agent still throws.
- Offline explicit-pet OpenCode setup writes config without calling local IPC.
- Missing `--pet` still calls pet picker/listing.
- New project creates `.opencode/opencode.jsonc` and `.opencode/openpets.md`.
- Existing `opencode.json` is preferred over `.opencode/opencode.jsonc` as write target.
- Existing unrelated config keys/MCP/plugin/instructions are preserved.
- Conflicts across multiple project config candidate files are detected before writing.
- Re-running setup is idempotent.
- Stale managed OpenPets entries are updated.
- Custom `mcp.openpets` refuses without writing.
- Custom OpenPets-like plugin/instruction refuses without writing.
- Existing `.opencode/openpets.md` without managed block gets managed block added while preserving user text.
- Instruction symlink/oversized file is rejected without config writes.
- Symlink project/config paths are rejected.
- Claude CLI tests still pass.
## Manual verification guide
After implementation and review:
1. Run `pnpm --filter @open-pets/cli check`.
2. Run `pnpm check`.
3. In a temporary project, run:
```bash
node /path/to/packages/cli/dist/index.js configure --agent opencode --pet fixer --cwd /tmp/openpets-opencode-test --local-dev
```
4. Confirm `.opencode/opencode.jsonc` contains `mcp.openpets`, `.opencode/openpets.md` instruction path, and `@open-pets/opencode` plugin spec.
5. Confirm `.opencode/openpets.md` contains OpenPets managed markers.
6. Re-run the command and confirm config remains idempotent.
7. Add a custom `mcp.openpets` entry and confirm setup refuses without overwriting.
8. Confirm real user OpenCode config was not touched.
## Oracle plan review
Oracle reviewed the initial Phase 19C spec and found blockers:
- Must scan all project config candidates before writing, not only selected write target.
- Instruction file write safety was under-specified.
- Partial-write/data-loss sequencing was undefined.
- Published plugin spec should be version-pinned.
- Existing expected instruction path without managed block needed a preserve-and-upsert policy.
## Oracle feedback disposition
- **Fixed:** Required scanning/classifying all existing project config candidates before selecting a write target.
- **Fixed:** Added safe instruction file write requirements: symlink/size rejection, preserve user content, backup, temp+rename, no write if config planning fails.
- **Fixed:** Required two-phase plan-all-then-execute sequencing to avoid partial writes.
- **Fixed:** Required version-pinned `@open-pets/opencode@<version>` plugin spec.
- **Fixed:** Clarified expected instruction path without managed block is `needs_update` and should upsert the managed block while preserving existing content.