Skip to content

Commit d4e065c

Browse files
committed
feat: implement openspec plugin core logic
1 parent eb11c24 commit d4e065c

9 files changed

Lines changed: 240 additions & 1 deletion

File tree

DESIGN.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# OpenCode Plugin OpenSpec Design
2+
3+
## 1. Overview
4+
This plugin integrates OpenSpec into OpenCode. It is **context-aware**: it activates only when it detects an OpenSpec-initialized project. When active, it dynamically registers an `openspec-plan` agent via the `config` hook, tailored for architectural planning.
5+
6+
## 2. Activation Logic
7+
The plugin checks for OpenSpec presence at startup.
8+
9+
- **Condition**: Existence of `openspec/AGENTS.md` (or `AGENTS.md`).
10+
- **Action**: If detected, the plugin hooks into the configuration loading process to inject the OpenSpec agent.
11+
12+
## 3. Features (Active Mode)
13+
14+
### 3.1. Agent Injection (`config` hook)
15+
Instead of just relying on runtime hooks, we will use the `config` hook to modify the OpenCode configuration directly. This is how `oh-my-opencode` replaces the default plan agent.
16+
17+
**Implementation Strategy:**
18+
1. **Hook**: `config`
19+
2. **Logic**:
20+
- Check if `openspec/AGENTS.md` exists.
21+
- If yes, inject a new agent `openspec-plan` into the `agent` configuration object.
22+
- **Agent Configuration**:
23+
- `name`: "openspec-plan"
24+
- `mode`: "primary"
25+
- `description`: "OpenSpec Architect - Plan and specify software architecture."
26+
- `prompt`: (Custom System Prompt for OpenSpec)
27+
- `permission`:
28+
- Explicitly `allow` editing `**/*.spec.md`, `project.md`, `AGENTS.md`.
29+
- This avoids reliance on the global `permission.ask` hook for basic operations, though we can keep `permission.ask` as a fallback or for finer control.
30+
- **Optional**: Hide/Demote default `plan` or `sisyphus-plan` to reduce confusion.
31+
32+
### 3.2. Auto-Permission (Fallback)
33+
While the agent config handles most permissions, we can still use `permission.ask` for edge cases or to ensure a smooth experience if the static permission config isn't enough.
34+
35+
- **Scope**: `openspec/**/*.md`, `specs/**/*.md`.
36+
- **Logic**: Intercept `permission.ask` and return `allow` for these patterns.
37+
38+
## 4. Dependencies
39+
- `@opencode-ai/plugin`: ^1.1.1
40+
- `@opencode-ai/sdk`: ^1.1.1
41+
42+
## 5. Project Structure
43+
```
44+
opencode-plugin-openspec/
45+
├── package.json
46+
├── tsconfig.json
47+
├── README.md
48+
├── DESIGN.md
49+
└── src/
50+
├── index.ts # Plugin entry point
51+
├── config.ts # Config hook implementation (Agent injection)
52+
├── prompts.ts # System prompts
53+
└── utils/
54+
└── detection.ts # Detection logic
55+
```
56+
57+
## 6. Implementation Details
58+
59+
### 6.1. Config Hook (`src/config.ts`)
60+
```typescript
61+
export const configHook: Hooks["config"] = async (config, ctx) => {
62+
if (!await isOpenSpecProject(ctx)) return config;
63+
64+
// Define OpenSpec Plan Agent
65+
const openSpecAgent = {
66+
name: "openspec-plan",
67+
mode: "primary",
68+
description: "OpenSpec Architect",
69+
prompt: OPENSPEC_SYSTEM_PROMPT,
70+
permission: {
71+
edit: {
72+
"**/*.spec.md": "allow",
73+
"**/project.md": "allow",
74+
"**/AGENTS.md": "allow"
75+
}
76+
}
77+
};
78+
79+
// Inject into config
80+
return {
81+
...config,
82+
agent: {
83+
...(config.agent || {}),
84+
"openspec-plan": openSpecAgent
85+
}
86+
};
87+
};
88+
```
89+
90+
### 6.2. System Prompt
91+
The prompt will instruct the model to:
92+
- Act as an Architect.
93+
- Read `project.md` and `AGENTS.md` for context.
94+
- Create/Update `specs/*.spec.md` files.
95+
- **NOT** write implementation code.

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,12 @@
11
# opencode-plugin-openspec
2-
An OpenCode plugin that integrates OpenSpec, allowing Plan Mode to create and edit spec files.
2+
3+
An OpenCode plugin that integrates OpenSpec.
4+
5+
## Features
6+
7+
- **New Mode: `openspec-plan`**: A dedicated planning mode that allows creating and editing OpenSpec files (`*.spec.md`), while keeping the rest of the codebase read-only.
8+
- **Auto-Permission**: Automatically grants write permissions for openSpec files in `openspec-plan` mode.
9+
10+
## Installation
11+
12+
(Instructions to be added)

package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "opencode-plugin-openspec",
3+
"version": "0.1.0",
4+
"main": "src/index.ts",
5+
"peerDependencies": {
6+
"@opencode-ai/plugin": "^1.1.1",
7+
"@opencode-ai/sdk": "^1.1.1"
8+
},
9+
"devDependencies": {
10+
"bun-types": "latest",
11+
"typescript": "^5.0.0"
12+
}
13+
}

src/config.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Hooks } from "@opencode-ai/plugin";
2+
import { isOpenSpecProject } from "./utils/detection";
3+
import { OPENSPEC_SYSTEM_PROMPT } from "./prompts";
4+
5+
export function createConfigHook(ctx: { directory: string }): Hooks["config"] {
6+
return async (config) => {
7+
// 1. Check if this is an OpenSpec project
8+
const mockCtx = { directory: ctx.directory } as any;
9+
10+
if (!await isOpenSpecProject(mockCtx)) {
11+
return;
12+
}
13+
14+
// 2. Define the OpenSpec Plan Agent
15+
const openSpecAgent = {
16+
name: "openspec-plan",
17+
mode: "primary",
18+
description: "OpenSpec Architect - Plan and specify software architecture.",
19+
prompt: OPENSPEC_SYSTEM_PROMPT,
20+
permission: {
21+
edit: {
22+
"**/*.spec.md": "allow",
23+
"**/project.md": "allow",
24+
"**/AGENTS.md": "allow",
25+
// Allow creating new spec directories
26+
"specs/**": "allow",
27+
"openspec/**": "allow"
28+
}
29+
},
30+
color: "#FF6B6B" // Distinctive color for the agent
31+
};
32+
33+
// 3. Inject into configuration
34+
// We use 'any' cast here to bypass strict type checking on the config object structure
35+
// because we are dynamically extending it.
36+
const agentConfig = (config.agent || {}) as any;
37+
agentConfig["openspec-plan"] = openSpecAgent;
38+
config.agent = agentConfig;
39+
};
40+
}

src/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { Plugin } from "@opencode-ai/plugin";
2+
import { createConfigHook } from "./config";
3+
import { isOpenSpecProject } from "./utils/detection";
4+
5+
const OpenSpecPlugin: Plugin = async (ctx) => {
6+
const isActive = await isOpenSpecProject(ctx);
7+
8+
if (!isActive) {
9+
return {};
10+
}
11+
12+
return {
13+
config: createConfigHook(ctx),
14+
};
15+
};
16+
17+
export default OpenSpecPlugin;

src/prompts.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
export const OPENSPEC_SYSTEM_PROMPT = `
2+
<Role>
3+
You are the **OpenSpec Architect**.
4+
Your goal is to design, specify, and document software architecture using the OpenSpec standard.
5+
You work primarily with Markdown files in the \`openspec/\` and \`specs/\` directories.
6+
</Role>
7+
8+
<Context>
9+
This project follows the OpenSpec standard for documentation-driven development.
10+
Key files:
11+
- \`project.md\`: High-level project vision, goals, and scope.
12+
- \`openspec/AGENTS.md\` (or \`AGENTS.md\`): Definitions of agents and their roles.
13+
- \`specs/**/*.spec.md\`: Detailed specifications for components or features.
14+
</Context>
15+
16+
<Rules>
17+
1. **Focus on Specifications**: Your primary output should be modifications to \`*.spec.md\` files.
18+
2. **No Implementation**: Do NOT write implementation code (TypeScript, Python, etc.) unless explicitly requested to "implement" or "prototype". Your job is to *plan*, not to *build*.
19+
3. **Structure**:
20+
- When designing a new feature, create a new spec file in \`specs/<feature-name>/spec.md\`.
21+
- Link back to \`project.md\` to ensure alignment with high-level goals.
22+
4. **Format**: Follow the existing Markdown structure found in the project. Use clear headers, bullet points, and diagrams (Mermaid) where appropriate.
23+
</Rules>
24+
`.trim();

src/utils/detection.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { PluginInput } from "@opencode-ai/plugin";
2+
import { existsSync } from "node:fs";
3+
import { join } from "node:path";
4+
5+
/**
6+
* Checks if the current workspace is an OpenSpec project.
7+
*
8+
* Detection logic:
9+
* 1. Checks for `openspec/AGENTS.md` (Primary indicator)
10+
* 2. Checks for `AGENTS.md` in root (Secondary indicator)
11+
*/
12+
export async function isOpenSpecProject(ctx: PluginInput): Promise<boolean> {
13+
const openspecAgentsPath = join(ctx.directory, "openspec", "AGENTS.md");
14+
const rootAgentsPath = join(ctx.directory, "AGENTS.md");
15+
16+
return existsSync(openspecAgentsPath) || existsSync(rootAgentsPath);
17+
}

tsconfig.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"lib": ["ESNext"],
4+
"module": "esnext",
5+
"target": "esnext",
6+
"moduleResolution": "bundler",
7+
"moduleDetection": "force",
8+
"allowImportingTsExtensions": true,
9+
"noEmit": true,
10+
"composite": true,
11+
"strict": true,
12+
"downlevelIteration": true,
13+
"skipLibCheck": true,
14+
"jsx": "react-jsx",
15+
"allowSyntheticDefaultImports": true,
16+
"forceConsistentCasingInFileNames": true,
17+
"allowJs": true,
18+
"types": [
19+
"bun-types" // add Bun global
20+
]
21+
}
22+
}

tsconfig.tsbuildinfo

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)