@ccx-agent/opencode-ccx 0.1.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +135 -69
- package/dist/hooks/chat-message.d.ts +21 -0
- package/dist/hooks/chat-params.d.ts +18 -0
- package/dist/hooks/compaction.d.ts +6 -0
- package/dist/hooks/dynamic-system-prompt.d.ts +9 -0
- package/dist/hooks/message-transform.d.ts +16 -0
- package/dist/hooks/tool-definition.d.ts +6 -0
- package/dist/index.js +243 -9
- package/dist/plugin/interface.d.ts +1 -1
- package/package.json +5 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ccx-agent
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,48 +1,66 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">ccx</h1>
|
|
3
|
+
<p align="center"><strong>Coding Copilot, eXtended</strong></p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
An <a href="https://opencode.ai">OpenCode</a> plugin that makes your AI coding agent disciplined, verification-driven, and risk-aware.
|
|
6
|
+
</p>
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@ccx-agent/opencode-ccx"><img src="https://img.shields.io/npm/v/@ccx-agent/opencode-ccx?style=flat-square&color=blue" alt="npm version"></a>
|
|
9
|
+
<a href="https://github.com/Rejudge-F/ccx/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Rejudge-F/ccx?style=flat-square" alt="license"></a>
|
|
10
|
+
</p>
|
|
11
|
+
</p>
|
|
2
12
|
|
|
3
|
-
|
|
13
|
+
---
|
|
4
14
|
|
|
5
|
-
##
|
|
15
|
+
## The Problem
|
|
6
16
|
|
|
7
|
-
|
|
17
|
+
LLM coding agents tend to over-engineer, skip verification, and run destructive commands without thinking. They add abstractions nobody asked for, report "done" without testing, and `rm -rf` without blinking.
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
- **Verifies before reporting done** — an adversarial verification agent stress-tests implementations with real commands, not code reading
|
|
11
|
-
- **Asks before destroying** — a risk guard flags `rm -rf`, `git push --force`, and other irreversible actions before they execute
|
|
12
|
-
- **Delegates intelligently** — a coordinator agent decomposes complex tasks into research, implementation, and verification phases across parallel workers
|
|
19
|
+
## The Solution
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
ccx injects a complete behavioral framework into your OpenCode agent — system prompt sections that enforce coding discipline, specialized subagents that verify and plan, and safety hooks that catch dangerous operations before they execute.
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|-----------|-------------|
|
|
18
|
-
| **ccx** (primary agent) | Main agent with 7 composable system prompt sections governing coding discipline, risk awareness, tool usage, and output style |
|
|
19
|
-
| **ccx-explore** | Read-only codebase search specialist. Fast parallel file/content search across large repos |
|
|
20
|
-
| **ccx-plan** | Read-only software architect. Investigates code and produces step-by-step implementation plans with critical file lists |
|
|
21
|
-
| **ccx-verification** | Adversarial verifier with VERDICT protocol (PASS/FAIL/PARTIAL). Runs builds, tests, linters, then tries to break things with boundary values, concurrency probes, and idempotency checks |
|
|
22
|
-
| **ccx-general-purpose** | Multi-strategy task worker for anything that doesn't fit the specialists |
|
|
23
|
-
| **ccx-coordinator** | Multi-agent orchestrator. Decomposes work into Research → Synthesis → Implementation → Verification phases across parallel workers |
|
|
24
|
-
| **Risk Guard** | `tool.execute.before` hook that warns on destructive operations |
|
|
25
|
-
| **Verification Reminder** | `tool.execute.after` hook that nudges verification after N file edits |
|
|
23
|
+
**Before ccx:** Agent writes 200 lines, adds 3 helper files, says "done."
|
|
26
24
|
|
|
27
|
-
|
|
25
|
+
**After ccx:** Agent writes 40 lines, runs the tests, spawns a verification agent that tries to break it, then reports the VERDICT.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
opencode plugin @ccx-agent/opencode-ccx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That's it. Restart OpenCode, press `@`, select **ccx**.
|
|
36
|
+
|
|
37
|
+
For global install (applies to all projects):
|
|
28
38
|
|
|
29
39
|
```bash
|
|
30
|
-
|
|
40
|
+
opencode plugin @ccx-agent/opencode-ccx -g
|
|
31
41
|
```
|
|
32
42
|
|
|
33
|
-
|
|
43
|
+
### Manual Installation
|
|
44
|
+
|
|
45
|
+
If you prefer manual setup, add to your `opencode.json`:
|
|
34
46
|
|
|
35
47
|
```jsonc
|
|
36
48
|
// ~/.config/opencode/opencode.json
|
|
37
49
|
{
|
|
38
50
|
"plugin": [
|
|
39
|
-
"ccx"
|
|
40
|
-
// ... your other plugins
|
|
51
|
+
"@ccx-agent/opencode-ccx@0.1.0"
|
|
41
52
|
]
|
|
42
53
|
}
|
|
43
54
|
```
|
|
44
55
|
|
|
45
|
-
|
|
56
|
+
### Development / Local Build
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
git clone https://github.com/Rejudge-F/ccx.git
|
|
60
|
+
cd ccx && bun install && bun run build
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then in `opencode.json`:
|
|
46
64
|
|
|
47
65
|
```jsonc
|
|
48
66
|
{
|
|
@@ -52,9 +70,63 @@ Or use a local build:
|
|
|
52
70
|
}
|
|
53
71
|
```
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## What's Inside
|
|
76
|
+
|
|
77
|
+
### Primary Agent: `ccx`
|
|
78
|
+
|
|
79
|
+
The main agent comes with 7 composable system prompt sections:
|
|
80
|
+
|
|
81
|
+
| Section | What it enforces |
|
|
82
|
+
|---------|-----------------|
|
|
83
|
+
| **intro** | Agent identity and trust boundary for untrusted content |
|
|
84
|
+
| **system-rules** | Permission model, context compression, prompt injection awareness |
|
|
85
|
+
| **doing-tasks** | Scope control — no unrequested features, no speculative abstractions, duplicate code > premature abstraction |
|
|
86
|
+
| **actions** | Risk classification — reversible vs irreversible, local vs shared, ask before destroying |
|
|
87
|
+
| **using-tools** | Dedicated tools over Bash, parallel tool calls, structured progress tracking |
|
|
88
|
+
| **tone-style** | Concise output, no emojis, `file:line` references, `owner/repo#N` links |
|
|
89
|
+
| **output-efficiency** | Lead with the answer, skip filler, report at milestones not continuously |
|
|
90
|
+
|
|
91
|
+
### Subagents
|
|
92
|
+
|
|
93
|
+
| Agent | Mode | Purpose |
|
|
94
|
+
|-------|------|---------|
|
|
95
|
+
| **ccx-explore** | read-only | Fast codebase search — parallel glob/grep/read across large repos |
|
|
96
|
+
| **ccx-plan** | read-only | Software architect — produces step-by-step plans with critical file lists |
|
|
97
|
+
| **ccx-verification** | read-only | Adversarial verifier — VERDICT protocol (PASS/FAIL/PARTIAL), runs real commands, catches self-rationalizations |
|
|
98
|
+
| **ccx-general-purpose** | read-write | Multi-strategy worker for tasks outside other specialists |
|
|
99
|
+
| **ccx-coordinator** | orchestrator | Decomposes complex work into Research, Synthesis, Implementation, Verification across parallel workers |
|
|
100
|
+
|
|
101
|
+
### Safety Hooks
|
|
102
|
+
|
|
103
|
+
| Hook | Trigger | Behavior |
|
|
104
|
+
|------|---------|----------|
|
|
105
|
+
| **Risk Guard** | `tool.execute.before` | Warns on `rm -rf`, `git push --force`, `DROP TABLE`, and other irreversible commands |
|
|
106
|
+
| **Verification Reminder** | `tool.execute.after` | Nudges the agent to run `ccx-verification` after N file edits |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## The Verification Agent
|
|
111
|
+
|
|
112
|
+
The most opinionated part of ccx. Key design decisions:
|
|
113
|
+
|
|
114
|
+
- **Execution over reading** — every check must include a `Command run` block with real terminal output. "I reviewed the code and it looks correct" is rejected.
|
|
115
|
+
- **Self-rationalization awareness** — the prompt lists excuses the agent will reach for ("the code looks correct", "the tests already pass", "this would take too long") and instructs it to do the opposite.
|
|
116
|
+
- **Type-specific strategies** — different verification approaches for frontend, backend, CLI, infrastructure, database migrations, refactoring, mobile, data pipelines, and more.
|
|
117
|
+
- **Adversarial probes required** — before issuing PASS, at least one probe must be attempted: concurrency, boundary values, idempotency, or orphan operations.
|
|
118
|
+
|
|
119
|
+
Output format:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
VERDICT: PASS
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
or `FAIL` with reproduction steps, or `PARTIAL` with what couldn't be verified and why.
|
|
126
|
+
|
|
127
|
+
---
|
|
56
128
|
|
|
57
|
-
##
|
|
129
|
+
## Configuration
|
|
58
130
|
|
|
59
131
|
Create `~/.config/opencode/ccx.json` (global) or `.opencode/ccx.json` (project-level):
|
|
60
132
|
|
|
@@ -73,59 +145,53 @@ Create `~/.config/opencode/ccx.json` (global) or `.opencode/ccx.json` (project-l
|
|
|
73
145
|
|
|
74
146
|
| Field | Type | Default | Description |
|
|
75
147
|
|-------|------|---------|-------------|
|
|
76
|
-
| `enabled` | boolean | `true` | Master toggle |
|
|
77
|
-
| `disabled_sections` | string[] | `[]` |
|
|
78
|
-
| `disabled_hooks` | string[] | `[]` | Hooks to disable (e.g., `["risk-guard"]`) |
|
|
79
|
-
| `output_style` | string \| null | `null` | Custom output style name |
|
|
80
|
-
| `verification.auto_remind` | boolean | `true` |
|
|
81
|
-
| `verification.min_file_edits` | number | `3` |
|
|
82
|
-
|
|
83
|
-
## How It Works
|
|
84
|
-
|
|
85
|
-
ccx hooks into OpenCode's plugin lifecycle at two points:
|
|
86
|
-
|
|
87
|
-
**1. `config` hook** — Injects the `ccx` primary agent (with full system prompt) and 5 subagents into `config.agent`. The primary agent's prompt is composed from 7 independent sections + subagent orchestration guidance.
|
|
88
|
-
|
|
89
|
-
**2. `tool.execute.before/after` hooks** — Risk Guard scans commands for destructive patterns pre-execution; Verification Reminder tracks file edit count post-execution.
|
|
90
|
-
|
|
91
|
-
### System Prompt Sections
|
|
92
|
-
|
|
93
|
-
The primary agent's system prompt is assembled from these composable sections:
|
|
148
|
+
| `enabled` | `boolean` | `true` | Master toggle for the entire plugin |
|
|
149
|
+
| `disabled_sections` | `string[]` | `[]` | Prompt sections to skip (e.g., `["tone-style"]`) |
|
|
150
|
+
| `disabled_hooks` | `string[]` | `[]` | Hooks to disable (e.g., `["risk-guard"]`) |
|
|
151
|
+
| `output_style` | `string \| null` | `null` | Custom output style name |
|
|
152
|
+
| `verification.auto_remind` | `boolean` | `true` | Auto-nudge verification after edits |
|
|
153
|
+
| `verification.min_file_edits` | `number` | `3` | File edit threshold before nudge |
|
|
94
154
|
|
|
95
|
-
|
|
96
|
-
|---------|---------|
|
|
97
|
-
| `intro` | Agent identity and trust boundary |
|
|
98
|
-
| `system-rules` | Permission model, context compression, prompt injection awareness |
|
|
99
|
-
| `doing-tasks` | Coding discipline — scope control, minimal abstraction, honest reporting |
|
|
100
|
-
| `actions` | Risk classification for reversible vs irreversible operations |
|
|
101
|
-
| `using-tools` | Prefer dedicated tools over Bash, parallel tool call strategy |
|
|
102
|
-
| `tone-style` | No emojis, concise output, code reference formatting |
|
|
103
|
-
| `output-efficiency` | Lead with answers, skip filler, milestone-based updates |
|
|
104
|
-
|
|
105
|
-
### Verification Agent
|
|
106
|
-
|
|
107
|
-
The verification agent is the most opinionated component. Key behaviors:
|
|
108
|
-
|
|
109
|
-
- **Runs commands, not reads code** — every check must include a `Command run` block with actual terminal output
|
|
110
|
-
- **Catches its own rationalizations** — the prompt explicitly lists excuses the agent will reach for ("the code looks correct", "the tests already pass") and instructs it to do the opposite
|
|
111
|
-
- **Adapts by change type** — different verification strategies for frontend, backend, CLI, infrastructure, database migrations, refactoring, etc.
|
|
112
|
-
- **Requires adversarial probes** — at least one concurrency, boundary value, or idempotency test before issuing PASS
|
|
155
|
+
---
|
|
113
156
|
|
|
114
157
|
## Project Structure
|
|
115
158
|
|
|
116
159
|
```
|
|
117
160
|
ccx/
|
|
118
161
|
├── src/
|
|
119
|
-
│ ├── index.ts
|
|
120
|
-
│ ├── prompts/
|
|
121
|
-
│ ├── agents/
|
|
122
|
-
│ ├── hooks/
|
|
123
|
-
│ ├── config/
|
|
124
|
-
│ └── plugin/
|
|
162
|
+
│ ├── index.ts # Plugin entry — composes everything
|
|
163
|
+
│ ├── prompts/ # 7 system prompt sections + composer + environment detector
|
|
164
|
+
│ ├── agents/ # 5 subagent definitions with tailored prompts
|
|
165
|
+
│ ├── hooks/ # config-handler, risk-guard, verification-reminder
|
|
166
|
+
│ ├── config/ # Zod schema + JSONC config loader
|
|
167
|
+
│ └── plugin/ # OpenCode plugin interface + agent tool registry
|
|
168
|
+
├── .github/workflows/
|
|
169
|
+
│ └── publish.yml # Auto-publish to npm on tag push
|
|
125
170
|
├── package.json
|
|
126
171
|
└── tsconfig.json
|
|
127
172
|
```
|
|
128
173
|
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Contributing
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
git clone https://github.com/Rejudge-F/ccx.git
|
|
180
|
+
cd ccx
|
|
181
|
+
bun install
|
|
182
|
+
bun run typecheck # type check
|
|
183
|
+
bun run build # build to dist/
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
To publish a new version:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# bump version in package.json, then:
|
|
190
|
+
git add -A && git commit -m "chore: bump to x.y.z"
|
|
191
|
+
git tag vx.y.z && git push origin main --tags
|
|
192
|
+
# GitHub Actions auto-publishes to npm
|
|
193
|
+
```
|
|
194
|
+
|
|
129
195
|
## License
|
|
130
196
|
|
|
131
197
|
MIT
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { OhMyCCAgentConfig } from "../config/schema";
|
|
2
|
+
type ChatMessageInput = {
|
|
3
|
+
sessionID: string;
|
|
4
|
+
agent?: string;
|
|
5
|
+
model?: {
|
|
6
|
+
providerID: string;
|
|
7
|
+
modelID: string;
|
|
8
|
+
};
|
|
9
|
+
messageID?: string;
|
|
10
|
+
};
|
|
11
|
+
type ChatMessageOutput = {
|
|
12
|
+
message: Record<string, unknown>;
|
|
13
|
+
parts: Array<{
|
|
14
|
+
type: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}>;
|
|
18
|
+
};
|
|
19
|
+
export declare function recordToolCall(sessionID: string, tool: string, file?: string): void;
|
|
20
|
+
export declare function createChatMessageHook(config: OhMyCCAgentConfig): (input: ChatMessageInput, output: ChatMessageOutput) => Promise<void>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
type ChatParamsInput = {
|
|
2
|
+
sessionID: string;
|
|
3
|
+
agent: string;
|
|
4
|
+
model: {
|
|
5
|
+
modelID?: string;
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
};
|
|
8
|
+
provider: unknown;
|
|
9
|
+
message: unknown;
|
|
10
|
+
};
|
|
11
|
+
type ChatParamsOutput = {
|
|
12
|
+
temperature: number;
|
|
13
|
+
topP: number;
|
|
14
|
+
topK: number;
|
|
15
|
+
options: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
export declare function createChatParamsHook(): (input: ChatParamsInput, output: ChatParamsOutput) => Promise<void>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OhMyCCAgentConfig } from "../config/schema";
|
|
2
|
+
export declare function trackFileEdit(sessionID: string, filePath: string): void;
|
|
3
|
+
export declare function markVerificationTriggered(sessionID: string): void;
|
|
4
|
+
export declare function createDynamicSystemPrompt(config: OhMyCCAgentConfig, directory: string): (input: {
|
|
5
|
+
sessionID?: string;
|
|
6
|
+
model: unknown;
|
|
7
|
+
}, output: {
|
|
8
|
+
system: string[];
|
|
9
|
+
}) => Promise<void>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type MessageInfo = {
|
|
2
|
+
info: {
|
|
3
|
+
role: string;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
};
|
|
6
|
+
parts: Array<{
|
|
7
|
+
type: string;
|
|
8
|
+
text?: string;
|
|
9
|
+
output?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}>;
|
|
12
|
+
};
|
|
13
|
+
export declare function createMessageTransformHook(): (_input: Record<string, unknown>, output: {
|
|
14
|
+
messages: MessageInfo[];
|
|
15
|
+
}) => Promise<void>;
|
|
16
|
+
export {};
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, {
|
|
6
10
|
get: all[name],
|
|
7
11
|
enumerable: true,
|
|
8
12
|
configurable: true,
|
|
9
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
10
14
|
});
|
|
11
15
|
};
|
|
12
16
|
|
|
@@ -13634,6 +13638,141 @@ function loadConfig(directory) {
|
|
|
13634
13638
|
return readConfigFile(projectConfigPath) ?? readConfigFile(userConfigPath) ?? defaults;
|
|
13635
13639
|
}
|
|
13636
13640
|
|
|
13641
|
+
// src/hooks/dynamic-system-prompt.ts
|
|
13642
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
13643
|
+
import { join as join2 } from "path";
|
|
13644
|
+
var sessionStates = new Map;
|
|
13645
|
+
function getState(sessionID) {
|
|
13646
|
+
const existing = sessionStates.get(sessionID);
|
|
13647
|
+
if (existing)
|
|
13648
|
+
return existing;
|
|
13649
|
+
const state = {
|
|
13650
|
+
editedFiles: new Set,
|
|
13651
|
+
verificationTriggered: false,
|
|
13652
|
+
turnCount: 0
|
|
13653
|
+
};
|
|
13654
|
+
sessionStates.set(sessionID, state);
|
|
13655
|
+
return state;
|
|
13656
|
+
}
|
|
13657
|
+
function trackFileEdit(sessionID, filePath) {
|
|
13658
|
+
getState(sessionID).editedFiles.add(filePath);
|
|
13659
|
+
}
|
|
13660
|
+
function loadProjectInstructions(directory) {
|
|
13661
|
+
const candidates = ["CLAUDE.md", ".claude/instructions.md", "AGENTS.md"];
|
|
13662
|
+
for (const name of candidates) {
|
|
13663
|
+
const fullPath = join2(directory, name);
|
|
13664
|
+
if (existsSync2(fullPath)) {
|
|
13665
|
+
try {
|
|
13666
|
+
const content = readFileSync2(fullPath, "utf-8").trim();
|
|
13667
|
+
if (content)
|
|
13668
|
+
return content;
|
|
13669
|
+
} catch {
|
|
13670
|
+
continue;
|
|
13671
|
+
}
|
|
13672
|
+
}
|
|
13673
|
+
}
|
|
13674
|
+
return null;
|
|
13675
|
+
}
|
|
13676
|
+
function createDynamicSystemPrompt(config2, directory) {
|
|
13677
|
+
const projectInstructions = loadProjectInstructions(directory);
|
|
13678
|
+
return async (input, output) => {
|
|
13679
|
+
if (!config2.enabled)
|
|
13680
|
+
return;
|
|
13681
|
+
const sessionID = input.sessionID;
|
|
13682
|
+
if (projectInstructions) {
|
|
13683
|
+
output.system.push(`# Project Instructions
|
|
13684
|
+
|
|
13685
|
+
The following instructions were found in the project root and MUST be followed:
|
|
13686
|
+
|
|
13687
|
+
${projectInstructions}`);
|
|
13688
|
+
}
|
|
13689
|
+
if (sessionID) {
|
|
13690
|
+
const state = getState(sessionID);
|
|
13691
|
+
state.turnCount++;
|
|
13692
|
+
if (state.editedFiles.size > 0) {
|
|
13693
|
+
const fileList = [...state.editedFiles].slice(-20).join(", ");
|
|
13694
|
+
output.system.push(`# Session Context
|
|
13695
|
+
|
|
13696
|
+
Files edited in this session (${state.editedFiles.size} total): ${fileList}`);
|
|
13697
|
+
}
|
|
13698
|
+
if (config2.verification.auto_remind && state.editedFiles.size >= config2.verification.min_file_edits && !state.verificationTriggered) {
|
|
13699
|
+
output.system.push(`# Verification Reminder
|
|
13700
|
+
|
|
13701
|
+
You have edited ${state.editedFiles.size} files in this session. You MUST run verification (via ccx-verification subagent) before declaring the task complete. This is not optional.`);
|
|
13702
|
+
}
|
|
13703
|
+
}
|
|
13704
|
+
};
|
|
13705
|
+
}
|
|
13706
|
+
|
|
13707
|
+
// src/hooks/chat-message.ts
|
|
13708
|
+
var EDIT_TOOLS = new Set(["edit", "write"]);
|
|
13709
|
+
var recentToolCalls = new Map;
|
|
13710
|
+
function recordToolCall(sessionID, tool, file2) {
|
|
13711
|
+
const calls = recentToolCalls.get(sessionID) ?? [];
|
|
13712
|
+
calls.push({ tool, file: file2 });
|
|
13713
|
+
if (calls.length > 50)
|
|
13714
|
+
calls.shift();
|
|
13715
|
+
recentToolCalls.set(sessionID, calls);
|
|
13716
|
+
}
|
|
13717
|
+
function createChatMessageHook(config2) {
|
|
13718
|
+
return async (input, output) => {
|
|
13719
|
+
if (!config2.enabled)
|
|
13720
|
+
return;
|
|
13721
|
+
const { sessionID, agent } = input;
|
|
13722
|
+
if (!sessionID)
|
|
13723
|
+
return;
|
|
13724
|
+
const calls = recentToolCalls.get(sessionID);
|
|
13725
|
+
if (calls && calls.length > 0) {
|
|
13726
|
+
const editedInRecent = calls.filter((c) => EDIT_TOOLS.has(c.tool) && c.file).map((c) => c.file);
|
|
13727
|
+
for (const file2 of editedInRecent) {
|
|
13728
|
+
trackFileEdit(sessionID, file2);
|
|
13729
|
+
}
|
|
13730
|
+
}
|
|
13731
|
+
if (agent && !agent.startsWith("ccx"))
|
|
13732
|
+
return;
|
|
13733
|
+
const reminders = [];
|
|
13734
|
+
if (config2.verification.auto_remind && calls && calls.filter((c) => EDIT_TOOLS.has(c.tool)).length >= config2.verification.min_file_edits) {
|
|
13735
|
+
reminders.push(`<system-reminder>You have made file edits. Remember the verification contract: run ccx-verification before declaring completion if you have 3+ file edits, backend/API changes, or infrastructure changes.</system-reminder>`);
|
|
13736
|
+
}
|
|
13737
|
+
if (reminders.length > 0) {
|
|
13738
|
+
output.parts.push({
|
|
13739
|
+
type: "text",
|
|
13740
|
+
text: reminders.join(`
|
|
13741
|
+
`)
|
|
13742
|
+
});
|
|
13743
|
+
}
|
|
13744
|
+
};
|
|
13745
|
+
}
|
|
13746
|
+
|
|
13747
|
+
// src/hooks/chat-params.ts
|
|
13748
|
+
var AGENT_PARAMS = {
|
|
13749
|
+
"ccx-plan": { temperature: 0.3, topP: 0.9 },
|
|
13750
|
+
"ccx-verification": { temperature: 0.2, topP: 0.85 },
|
|
13751
|
+
"ccx-explore": { temperature: 0.5, topP: 0.95 },
|
|
13752
|
+
"ccx-coordinator": { temperature: 0.4, topP: 0.9 },
|
|
13753
|
+
"ccx-general-purpose": { temperature: 0.5, topP: 0.95 }
|
|
13754
|
+
};
|
|
13755
|
+
function createChatParamsHook() {
|
|
13756
|
+
return async (input, output) => {
|
|
13757
|
+
const agentParams = AGENT_PARAMS[input.agent];
|
|
13758
|
+
if (!agentParams)
|
|
13759
|
+
return;
|
|
13760
|
+
if (agentParams.temperature !== undefined)
|
|
13761
|
+
output.temperature = agentParams.temperature;
|
|
13762
|
+
if (agentParams.topP !== undefined)
|
|
13763
|
+
output.topP = agentParams.topP;
|
|
13764
|
+
if (agentParams.topK !== undefined)
|
|
13765
|
+
output.topK = agentParams.topK;
|
|
13766
|
+
};
|
|
13767
|
+
}
|
|
13768
|
+
|
|
13769
|
+
// src/hooks/compaction.ts
|
|
13770
|
+
function createCompactionHook() {
|
|
13771
|
+
return async (input, output) => {
|
|
13772
|
+
output.context.push("IMPORTANT: When summarizing this conversation, you MUST preserve the following:", "1. The original user task/request and any refinements", "2. The implementation plan if one was created", "3. All file paths that were edited, created, or deleted", "4. The current verification status (PASS/FAIL/PARTIAL) if verification was run", "5. Any unresolved issues, blockers, or pending decisions", "6. Key architectural decisions and their rationale", "7. The names of any subagents that were dispatched and their outcomes", "Do NOT summarize tool outputs verbatim \u2014 capture only the conclusions and any error messages.");
|
|
13773
|
+
};
|
|
13774
|
+
}
|
|
13775
|
+
|
|
13637
13776
|
// src/prompts/actions.ts
|
|
13638
13777
|
function getActionsSection() {
|
|
13639
13778
|
return `# Executing actions with care
|
|
@@ -13688,8 +13827,8 @@ function getDoingTasksSection() {
|
|
|
13688
13827
|
}
|
|
13689
13828
|
|
|
13690
13829
|
// src/prompts/environment.ts
|
|
13691
|
-
import { existsSync as
|
|
13692
|
-
import { join as
|
|
13830
|
+
import { existsSync as existsSync3 } from "fs";
|
|
13831
|
+
import { join as join3 } from "path";
|
|
13693
13832
|
import { type as osType, release as osRelease } from "os";
|
|
13694
13833
|
function normalizeShell(shell) {
|
|
13695
13834
|
if (shell.includes("zsh"))
|
|
@@ -13715,7 +13854,7 @@ function getOsVersion() {
|
|
|
13715
13854
|
}
|
|
13716
13855
|
function detectGit(cwd) {
|
|
13717
13856
|
try {
|
|
13718
|
-
return
|
|
13857
|
+
return existsSync3(join3(cwd, ".git"));
|
|
13719
13858
|
} catch {
|
|
13720
13859
|
return false;
|
|
13721
13860
|
}
|
|
@@ -14348,8 +14487,8 @@ function createConfigHook(config2, directory) {
|
|
|
14348
14487
|
}
|
|
14349
14488
|
|
|
14350
14489
|
// src/hooks/environment-context.ts
|
|
14351
|
-
import { existsSync as
|
|
14352
|
-
import { join as
|
|
14490
|
+
import { existsSync as existsSync4 } from "fs";
|
|
14491
|
+
import { join as join4 } from "path";
|
|
14353
14492
|
var environmentBySession = new Map;
|
|
14354
14493
|
function isRecord(value) {
|
|
14355
14494
|
return typeof value === "object" && value !== null;
|
|
@@ -14367,7 +14506,7 @@ function createEnvironmentContext(directory) {
|
|
|
14367
14506
|
const sessionID = typeof event.sessionID === "string" ? event.sessionID : typeof event.sessionId === "string" ? event.sessionId : undefined;
|
|
14368
14507
|
const environment = {
|
|
14369
14508
|
cwd: directory,
|
|
14370
|
-
isGit:
|
|
14509
|
+
isGit: existsSync4(join4(directory, ".git")),
|
|
14371
14510
|
platform: process.platform,
|
|
14372
14511
|
shell: process.env.SHELL ?? "unknown"
|
|
14373
14512
|
};
|
|
@@ -14380,6 +14519,51 @@ function createEnvironmentContext(directory) {
|
|
|
14380
14519
|
};
|
|
14381
14520
|
}
|
|
14382
14521
|
|
|
14522
|
+
// src/hooks/message-transform.ts
|
|
14523
|
+
var MAX_TOOL_OUTPUT_LENGTH = 8000;
|
|
14524
|
+
var TOOL_OUTPUT_TRIM_TO = 4000;
|
|
14525
|
+
function trimLongToolOutputs(messages) {
|
|
14526
|
+
for (const msg of messages) {
|
|
14527
|
+
if (msg.info.role !== "assistant")
|
|
14528
|
+
continue;
|
|
14529
|
+
for (const part of msg.parts) {
|
|
14530
|
+
if (part.type !== "tool")
|
|
14531
|
+
continue;
|
|
14532
|
+
const output = typeof part.output === "string" ? part.output : undefined;
|
|
14533
|
+
if (output && output.length > MAX_TOOL_OUTPUT_LENGTH) {
|
|
14534
|
+
const head = output.slice(0, TOOL_OUTPUT_TRIM_TO / 2);
|
|
14535
|
+
const tail = output.slice(-TOOL_OUTPUT_TRIM_TO / 2);
|
|
14536
|
+
const trimmed = output.length - TOOL_OUTPUT_TRIM_TO;
|
|
14537
|
+
part.output = `${head}
|
|
14538
|
+
|
|
14539
|
+
... [${trimmed} characters trimmed] ...
|
|
14540
|
+
|
|
14541
|
+
${tail}`;
|
|
14542
|
+
}
|
|
14543
|
+
}
|
|
14544
|
+
}
|
|
14545
|
+
}
|
|
14546
|
+
function collapseConsecutiveReasoningParts(messages) {
|
|
14547
|
+
for (const msg of messages) {
|
|
14548
|
+
if (msg.info.role !== "assistant")
|
|
14549
|
+
continue;
|
|
14550
|
+
const collapsed = [];
|
|
14551
|
+
for (const part of msg.parts) {
|
|
14552
|
+
if (part.type === "reasoning" && collapsed.length > 0 && collapsed[collapsed.length - 1].type === "reasoning") {
|
|
14553
|
+
continue;
|
|
14554
|
+
}
|
|
14555
|
+
collapsed.push(part);
|
|
14556
|
+
}
|
|
14557
|
+
msg.parts = collapsed;
|
|
14558
|
+
}
|
|
14559
|
+
}
|
|
14560
|
+
function createMessageTransformHook() {
|
|
14561
|
+
return async (_input, output) => {
|
|
14562
|
+
trimLongToolOutputs(output.messages);
|
|
14563
|
+
collapseConsecutiveReasoningParts(output.messages);
|
|
14564
|
+
};
|
|
14565
|
+
}
|
|
14566
|
+
|
|
14383
14567
|
// src/hooks/risk-guard.ts
|
|
14384
14568
|
var RISK_PATTERNS = [
|
|
14385
14569
|
/rm\s+-rf\b/i,
|
|
@@ -14427,6 +14611,35 @@ function createRiskGuard() {
|
|
|
14427
14611
|
};
|
|
14428
14612
|
}
|
|
14429
14613
|
|
|
14614
|
+
// src/hooks/tool-definition.ts
|
|
14615
|
+
var TOOL_SAFETY_HINTS = {
|
|
14616
|
+
bash: "SAFETY: Before executing, verify the command is non-destructive. Never run rm -rf, git push --force, git reset --hard, DROP TABLE, or TRUNCATE TABLE without explicit user approval. Prefer --dry-run flags when available. Quote all file paths containing spaces.",
|
|
14617
|
+
edit: "SAFETY: Always read the file first before editing. Preserve existing indentation. Do not modify files you have not examined. Never edit .env files or credentials.",
|
|
14618
|
+
write: "SAFETY: Prefer editing existing files over creating new ones. Never write to .env or credential files. Avoid creating files in the workspace root unless they are permanent source code."
|
|
14619
|
+
};
|
|
14620
|
+
var TOOL_CONTEXT_HINTS = {
|
|
14621
|
+
glob: "Use this for finding files by name pattern. Prefer this over bash find commands.",
|
|
14622
|
+
grep: "Use this for searching file contents by regex. Prefer this over bash grep/rg commands.",
|
|
14623
|
+
read: "Use this for reading file contents. Prefer this over bash cat/head/tail commands."
|
|
14624
|
+
};
|
|
14625
|
+
function createToolDefinitionHook() {
|
|
14626
|
+
return async (input, output) => {
|
|
14627
|
+
const toolName = input.toolID.toLowerCase();
|
|
14628
|
+
const safetyHint = TOOL_SAFETY_HINTS[toolName];
|
|
14629
|
+
if (safetyHint) {
|
|
14630
|
+
output.description = `${output.description}
|
|
14631
|
+
|
|
14632
|
+
${safetyHint}`;
|
|
14633
|
+
}
|
|
14634
|
+
const contextHint = TOOL_CONTEXT_HINTS[toolName];
|
|
14635
|
+
if (contextHint) {
|
|
14636
|
+
output.description = `${output.description}
|
|
14637
|
+
|
|
14638
|
+
${contextHint}`;
|
|
14639
|
+
}
|
|
14640
|
+
};
|
|
14641
|
+
}
|
|
14642
|
+
|
|
14430
14643
|
// src/hooks/verification-reminder.ts
|
|
14431
14644
|
var sessionStateById = new Map;
|
|
14432
14645
|
var EDIT_TOOL_NAMES = new Set(["edit", "write"]);
|
|
@@ -26848,14 +27061,35 @@ function createPluginInterface(args) {
|
|
|
26848
27061
|
const riskGuard = createRiskGuard();
|
|
26849
27062
|
const verificationReminder = createVerificationReminder(config3);
|
|
26850
27063
|
const environmentContext = createEnvironmentContext(ctx.directory);
|
|
27064
|
+
const dynamicSystemPrompt = createDynamicSystemPrompt(config3, ctx.directory);
|
|
27065
|
+
const chatMessageHook = createChatMessageHook(config3);
|
|
27066
|
+
const chatParamsHook = createChatParamsHook();
|
|
27067
|
+
const compactionHook = createCompactionHook();
|
|
27068
|
+
const toolDefinitionHook = createToolDefinitionHook();
|
|
27069
|
+
const messageTransformHook = createMessageTransformHook();
|
|
26851
27070
|
return {
|
|
26852
27071
|
config: configHook,
|
|
26853
27072
|
tool: createAgentTools(),
|
|
26854
27073
|
"tool.execute.before": riskGuard,
|
|
26855
|
-
"tool.execute.after":
|
|
27074
|
+
"tool.execute.after": async (input, output) => {
|
|
27075
|
+
await verificationReminder(input, output);
|
|
27076
|
+
const sessionID = typeof input.sessionID === "string" ? input.sessionID : undefined;
|
|
27077
|
+
const tool3 = typeof input.tool === "string" ? input.tool.toLowerCase() : undefined;
|
|
27078
|
+
if (sessionID && tool3) {
|
|
27079
|
+
const args2 = input.args;
|
|
27080
|
+
const file3 = typeof args2?.filePath === "string" ? args2.filePath : typeof args2?.path === "string" ? args2.path : undefined;
|
|
27081
|
+
recordToolCall(sessionID, tool3, file3);
|
|
27082
|
+
}
|
|
27083
|
+
},
|
|
26856
27084
|
event: async (input) => {
|
|
26857
27085
|
await environmentContext(input, {});
|
|
26858
|
-
}
|
|
27086
|
+
},
|
|
27087
|
+
"chat.message": chatMessageHook,
|
|
27088
|
+
"chat.params": chatParamsHook,
|
|
27089
|
+
"tool.definition": toolDefinitionHook,
|
|
27090
|
+
"experimental.chat.system.transform": dynamicSystemPrompt,
|
|
27091
|
+
"experimental.chat.messages.transform": messageTransformHook,
|
|
27092
|
+
"experimental.session.compacting": compactionHook
|
|
26859
27093
|
};
|
|
26860
27094
|
}
|
|
26861
27095
|
|
|
@@ -2,7 +2,7 @@ import type { Plugin } from "@opencode-ai/plugin";
|
|
|
2
2
|
import type { OhMyCCAgentConfig } from "../config/schema";
|
|
3
3
|
type PluginContext = Parameters<Plugin>[0];
|
|
4
4
|
type PluginInstance = Awaited<ReturnType<Plugin>>;
|
|
5
|
-
export type PluginInterface = Pick<PluginInstance, "config" | "tool" | "tool.execute.before" | "tool.execute.after" | "event">;
|
|
5
|
+
export type PluginInterface = Pick<PluginInstance, "config" | "tool" | "tool.execute.before" | "tool.execute.after" | "event" | "chat.message" | "chat.params" | "tool.definition" | "experimental.chat.system.transform" | "experimental.chat.messages.transform" | "experimental.session.compacting">;
|
|
6
6
|
export declare function createPluginInterface(args: {
|
|
7
7
|
ctx: PluginContext;
|
|
8
8
|
config: OhMyCCAgentConfig;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ccx-agent/opencode-ccx",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Production-grade prompt engineering for OpenCode — disciplined coding, risk-aware actions, adversarial verification",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"typecheck": "tsc --noEmit",
|
|
21
21
|
"prepublishOnly": "bun run clean && bun run build"
|
|
22
22
|
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/Rejudge-F/ccx.git"
|
|
26
|
+
},
|
|
23
27
|
"keywords": [
|
|
24
28
|
"opencode",
|
|
25
29
|
"plugin",
|