phase 05: change permissions default to none
This commit is contained in:
@@ -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.
|
||||||
@@ -181,5 +181,57 @@ If existing `opencode.json` is malformed JSON, warn and write plugin-only config
|
|||||||
## Alternatives Considered
|
## Alternatives Considered
|
||||||
|
|
||||||
1. Plugin wins on conflict - Rejected: would overwrite user data
|
1. Plugin wins on conflict - Rejected: would overwrite user data
|
||||||
2. Merge and combine arrays - Rejected: MCP servers are keyed objects, not array
|
2. Merge and combine arrays - Rejected: MCP servers are keyed object, not array
|
||||||
3. Fail on conflict - Rejected: breaks installation workflow
|
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
|
||||||
@@ -48,8 +48,8 @@ export default defineCommand({
|
|||||||
},
|
},
|
||||||
permissions: {
|
permissions: {
|
||||||
type: "string",
|
type: "string",
|
||||||
default: "broad",
|
default: "none", // Default is "none" -- writing global permissions to opencode.json pollutes user config. See ADR-003.
|
||||||
description: "Permission mapping: none | broad | from-commands",
|
description: "Permission mapping written to opencode.json: none (default) | broad | from-command",
|
||||||
},
|
},
|
||||||
agentMode: {
|
agentMode: {
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -426,4 +426,82 @@ describe("CLI", () => {
|
|||||||
expect(await exists(path.join(piRoot, "prompts", "workflows-review.md"))).toBe(true)
|
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)
|
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()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user