phase 05: change permissions default to none

This commit is contained in:
Adrian
2026-02-20 13:31:31 -05:00
parent 3914dfdebe
commit 27319bd85f
4 changed files with 169 additions and 4 deletions

View File

@@ -0,0 +1,35 @@
# Phase 5 Handoff: Change `--permissions` Default to `"none"`
## Summary
Changed the default value of `--permissions` from `"broad"` to `"none"` in the install command to prevent polluting user OpenCode config with global permissions.
## Changes Made
### 1. Code Change (`src/commands/install.ts`)
- Line 51: Changed `default: "broad"` to `default: "none"` with comment referencing ADR-003
- Line 52: Updated description to clarify "none (default)"
```typescript
permissions: {
type: "string",
default: "none", // Default is "none" -- writing global permissions to opencode.json pollutes user config. See ADR-003.
description: "Permission mapping written to opencode.json: none (default) | broad | from-command",
},
```
### 2. New Tests (`tests/cli.test.ts`)
Added two new tests:
1. `"install --to opencode uses permissions:none by default"` - Verifies no `permission` or `tools` keys in opencode.json when using default
2. `"install --to opencode --permissions broad writes permission block"` - Verifies `permission` key is written when explicitly using `--permissions broad`
## Test Results
- CLI tests: 12 pass, 0 fail
- All tests: 187 pass, 0 fail
## Next Steps
None - Phase 5 is complete.

View File

@@ -181,5 +181,57 @@ If existing `opencode.json` is malformed JSON, warn and write plugin-only config
## Alternatives Considered
1. Plugin wins on conflict - Rejected: would overwrite user data
2. Merge and combine arrays - Rejected: MCP servers are keyed objects, not array
3. Fail on conflict - Rejected: breaks installation workflow
2. Merge and combine arrays - Rejected: MCP servers are keyed object, not array
3. Fail on conflict - Rejected: breaks installation workflow
---
## Decision: ADR-003 - Permissions Default "none" for OpenCode Output
**Date:** 2026-02-20
**Status:** Implemented
## Context
When installing a Claude plugin to OpenCode format, the `--permissions` flag determines whether permission/tool mappings is written to `opencode.json`. The previous default was `"broad"`, which writes global permissions to the user's config file.
## Decision
Change the default value of `--permissions` from `"broad"` to `"none"` in the install command.
### Rationale
- **User safety:** Writing global permissions to `opencode.json` pollutes user config and may grant unintended access
- **Principle alignment:** Follows AGENTS.md "Do not delete or overwrite user data"
- **Explicit opt-in:** Users must explicitly request `--permissions broad` to write permissions to their config
- **Backward compatible:** Existing workflows using `--permissions broad` continues to work
### Implementation
In `src/commands/install.ts`:
```typescript
permissions: {
type: "string",
default: "none", // Default is "none" -- writing global permissions to opencode.json pollutes user config. See ADR-003.
description: "Permission mapping written to opencode.json: none (default) | broad | from-command",
},
```
### Test Coverage
Added two CLI tests cases:
1. `install --to opencode uses permissions:none by default` - Verifies no `permission` or `tools` key in output
2. `install --to opencode --permissions broad writes permission block` - Verifies `permission` key is written when explicitly requested
## Consequences
- **Positive:** User config remains clean by default
- **Positive:** Explicit opt-in required for permission writing
- **Negative:** Users migrating from older versions need to explicitly use `--permissions broad` if they want permissions
- **Migration path:** Document the change in migration notes
## Alternatives Considered
1. Keep "broad" as default - Rejected: pollutes user config
2. Prompt user interactively - Rejected: breaks CLI automation
3. Write to separate file - Rejected: OpenCode expects permissions in opencode.json

View File

@@ -48,8 +48,8 @@ export default defineCommand({
},
permissions: {
type: "string",
default: "broad",
description: "Permission mapping: none | broad | from-commands",
default: "none", // Default is "none" -- writing global permissions to opencode.json pollutes user config. See ADR-003.
description: "Permission mapping written to opencode.json: none (default) | broad | from-command",
},
agentMode: {
type: "string",

View File

@@ -426,4 +426,82 @@ describe("CLI", () => {
expect(await exists(path.join(piRoot, "prompts", "workflows-review.md"))).toBe(true)
expect(await exists(path.join(piRoot, "extensions", "compound-engineering-compat.ts"))).toBe(true)
})
test("install --to opencode uses permissions:none by default", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-perms-none-"))
const fixtureRoot = path.join(import.meta.dir, "fixtures", "sample-plugin")
const proc = Bun.spawn([
"bun",
"run",
"src/index.ts",
"install",
fixtureRoot,
"--to",
"opencode",
"--output",
tempRoot,
], {
cwd: path.join(import.meta.dir, ".."),
stdout: "pipe",
stderr: "pipe",
})
const exitCode = await proc.exited
const stdout = await new Response(proc.stdout).text()
const stderr = await new Response(proc.stderr).text()
if (exitCode !== 0) {
throw new Error(`CLI failed (exit ${exitCode}).\nstdout: ${stdout}\nstderr: ${stderr}`)
}
expect(stdout).toContain("Installed compound-engineering")
const opencodeJsonPath = path.join(tempRoot, "opencode.json")
const content = await fs.readFile(opencodeJsonPath, "utf-8")
const json = JSON.parse(content)
expect(json).not.toHaveProperty("permission")
expect(json).not.toHaveProperty("tools")
})
test("install --to opencode --permissions broad writes permission block", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "cli-perms-broad-"))
const fixtureRoot = path.join(import.meta.dir, "fixtures", "sample-plugin")
const proc = Bun.spawn([
"bun",
"run",
"src/index.ts",
"install",
fixtureRoot,
"--to",
"opencode",
"--permissions",
"broad",
"--output",
tempRoot,
], {
cwd: path.join(import.meta.dir, ".."),
stdout: "pipe",
stderr: "pipe",
})
const exitCode = await proc.exited
const stdout = await new Response(proc.stdout).text()
const stderr = await new Response(proc.stderr).text()
if (exitCode !== 0) {
throw new Error(`CLI failed (exit ${exitCode}).\nstdout: ${stdout}\nstderr: ${stderr}`)
}
expect(stdout).toContain("Installed compound-engineering")
const opencodeJsonPath = path.join(tempRoot, "opencode.json")
const content = await fs.readFile(opencodeJsonPath, "utf-8")
const json = JSON.parse(content)
expect(json).toHaveProperty("permission")
expect(json.permission).not.toBeNull()
})
})