@ossdeveloper/github-compliance 1.0.0
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/ARCHITECTURE.md +216 -0
- package/CHANGELOG.md +100 -0
- package/README.md +273 -0
- package/compliance.ts +159 -0
- package/database.ts +563 -0
- package/dist/plugin.js +12949 -0
- package/errors.ts +149 -0
- package/package.json +14 -0
- package/plugin.ts +177 -0
- package/schema.ts +88 -0
- package/tools.ts +95 -0
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
Deep dive into the GitHub Compliance Plugin design.
|
|
4
|
+
|
|
5
|
+
## Design Goals
|
|
6
|
+
|
|
7
|
+
1. **External Enforcement**: Compliance is enforced by external code, not LLM reasoning
|
|
8
|
+
2. **No Server-Side Storage**: All state in local SQLite, no external infrastructure
|
|
9
|
+
3. **Content Integrity**: Hash-based verification prevents content tampering
|
|
10
|
+
4. **Audit Trail**: Complete history of all operations for debugging
|
|
11
|
+
5. **Centralized Schema**: Single source of truth for all database definitions
|
|
12
|
+
|
|
13
|
+
## Component Flow
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
17
|
+
│ tool.execute.before │
|
|
18
|
+
│ │
|
|
19
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
|
|
20
|
+
│ │ isWriteTool? │────▶│ extractInfo │────▶│ getComplianceRecord │ │
|
|
21
|
+
│ └──────────────┘ └──────────────┘ └──────────────────────────┘ │
|
|
22
|
+
│ │ │
|
|
23
|
+
│ ▼ │
|
|
24
|
+
│ ┌──────────────────┐ │
|
|
25
|
+
│ │ validateCompliance│ │
|
|
26
|
+
│ └──────────────────┘ │
|
|
27
|
+
│ │ │
|
|
28
|
+
│ ┌──────────────────────────┼───────────────┐ │
|
|
29
|
+
│ │ │ │ │
|
|
30
|
+
│ ▼ ▼ │ │
|
|
31
|
+
│ ┌──────────────────┐ ┌──────────────────────┐ │ │
|
|
32
|
+
│ │ valid: false │ │ valid: true │ │ │
|
|
33
|
+
│ │ logFailedAttempt │ │ markRecordUsed │ │ │
|
|
34
|
+
│ │ throw BlockedErr │ │ attach metadata │ │ │
|
|
35
|
+
│ └──────────────────┘ └──────────────────────┘ │ │
|
|
36
|
+
│ │
|
|
37
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Schema Design (schema.ts)
|
|
41
|
+
|
|
42
|
+
All database definitions are centralized in `schema.ts`:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
export const SCHEMA = {
|
|
46
|
+
tables: {
|
|
47
|
+
COMPLIANCE_RECORDS: "compliance_records",
|
|
48
|
+
COMPLIANCE_CHECKS: "compliance_checks",
|
|
49
|
+
AUDIT_LOG: "audit_log",
|
|
50
|
+
FAILED_ATTEMPTS: "failed_attempts"
|
|
51
|
+
},
|
|
52
|
+
columns: {
|
|
53
|
+
ID: "id",
|
|
54
|
+
TOOL_NAME: "tool_name",
|
|
55
|
+
// ... all columns
|
|
56
|
+
},
|
|
57
|
+
indexes: {
|
|
58
|
+
COMPLIANCE_LOOKUP: "idx_compliance_lookup",
|
|
59
|
+
COMPLIANCE_EXPIRES: "idx_compliance_expires"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const RECORD_STATUS = {
|
|
64
|
+
PENDING: "pending",
|
|
65
|
+
APPROVED: "approved",
|
|
66
|
+
EXP IRED: "expired",
|
|
67
|
+
USED: "used"
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Why centralized schema matters:**
|
|
72
|
+
|
|
73
|
+
1. **Single Source of Truth**: Column names defined once, used everywhere
|
|
74
|
+
2. **No Typos**: `SCHEMA.columns.STATUS` vs manually typing "status"
|
|
75
|
+
3. **Refactoring**: Change a name in schema.ts, all SQL updates automatically
|
|
76
|
+
4. **IDE Support**: Autocomplete works for all table/column names
|
|
77
|
+
|
|
78
|
+
## Database Design
|
|
79
|
+
|
|
80
|
+
### Compliance Record Lifecycle
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
pending ────▶ approved ────▶ used
|
|
84
|
+
│
|
|
85
|
+
└──▶ expired (automatic after 30 min)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Content Hash Calculation
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// In compliance.ts
|
|
92
|
+
const content = JSON.stringify({
|
|
93
|
+
title: title || "",
|
|
94
|
+
body: body || ""
|
|
95
|
+
});
|
|
96
|
+
const hash = "sha256:" + createHash("sha256").update(content).digest("hex");
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Why this matters**: The hash ensures the content the LLM drafted is the same content that gets executed. If the LLM modifies content between approval and execution, the hash won't match and the operation will be rejected.
|
|
100
|
+
|
|
101
|
+
### Indexes
|
|
102
|
+
|
|
103
|
+
```sql
|
|
104
|
+
CREATE INDEX idx_compliance_lookup ON compliance_records(tool_name, owner, repo, status);
|
|
105
|
+
CREATE INDEX idx_compliance_expires ON compliance_records(expires_at);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
- `idx_compliance_lookup`: Fast lookups for validation
|
|
109
|
+
- `idx_compliance_expires`: Fast cleanup of expired records
|
|
110
|
+
|
|
111
|
+
## Hook Execution Order
|
|
112
|
+
|
|
113
|
+
1. **tool.execute.before**
|
|
114
|
+
- Runs BEFORE tool execution
|
|
115
|
+
- Can modify output (attach metadata)
|
|
116
|
+
- Can throw to block execution
|
|
117
|
+
|
|
118
|
+
2. **Tool executes** (if before hook allowed)
|
|
119
|
+
|
|
120
|
+
3. **tool.execute.after**
|
|
121
|
+
- Runs AFTER tool execution
|
|
122
|
+
- Cannot block, only for logging
|
|
123
|
+
- Can access output.result
|
|
124
|
+
|
|
125
|
+
## Custom Tool Execution
|
|
126
|
+
|
|
127
|
+
Custom tools (from `tools.ts`) do NOT go through the `tool.execute.before` hook. They execute directly. This is intentional - the custom tools are how the LLM creates compliance records, so they must always be allowed.
|
|
128
|
+
|
|
129
|
+
## Error Handling Strategy
|
|
130
|
+
|
|
131
|
+
| Error Type | Handling |
|
|
132
|
+
|------------|----------|
|
|
133
|
+
| NO_RECORD | Clear message to load skill and follow workflow |
|
|
134
|
+
| NOT_APPROVED | Tell LLM to present draft and get approval |
|
|
135
|
+
| ALREADY_USED | Each write needs its own record |
|
|
136
|
+
| EXPIRED | Re-approval required |
|
|
137
|
+
| DATABASE_ERROR | Log and suggest retry |
|
|
138
|
+
|
|
139
|
+
All error reasons are defined in `ERROR_REASONS` enum in `schema.ts`.
|
|
140
|
+
|
|
141
|
+
## Extensibility Points
|
|
142
|
+
|
|
143
|
+
### 1. New Check Types
|
|
144
|
+
|
|
145
|
+
Add to `CHECK_TYPES` in `schema.ts`:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
export const CHECK_TYPES = {
|
|
149
|
+
// ... existing ...
|
|
150
|
+
VOUCH_VERIFIED: "vouch_verified"
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
LLM will automatically include them when calling `create_compliance_record`.
|
|
155
|
+
|
|
156
|
+
### 2. New Validation Rules
|
|
157
|
+
|
|
158
|
+
Add to `validateCompliance()` in `database.ts`:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
if (record.status === RECORD_STATUS.PENDING) {
|
|
162
|
+
return { valid: false, reason: ERROR_REASONS.NOT_APPROVED, ... };
|
|
163
|
+
}
|
|
164
|
+
// Add new validation here
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 3. New Columns
|
|
168
|
+
|
|
169
|
+
To add a new column:
|
|
170
|
+
|
|
171
|
+
1. Add to `SCHEMA.columns` in `schema.ts`
|
|
172
|
+
2. Add to `CREATE TABLE` in `initDatabase()` in `database.ts`
|
|
173
|
+
3. Add to `ComplianceRecord` interface in `database.ts`
|
|
174
|
+
|
|
175
|
+
### 4. New Table
|
|
176
|
+
|
|
177
|
+
To add a new table:
|
|
178
|
+
|
|
179
|
+
1. Add table name to `SCHEMA.tables` in `schema.ts`
|
|
180
|
+
2. Add column constants to `SCHEMA.columns` in `schema.ts`
|
|
181
|
+
3. Add `CREATE TABLE` in `initDatabase()` in `database.ts`
|
|
182
|
+
4. Create CRUD functions in `database.ts`
|
|
183
|
+
|
|
184
|
+
## Security Considerations
|
|
185
|
+
|
|
186
|
+
1. **No Fabrication**: LLM cannot fake compliance records - they must actually call `create_compliance_record`
|
|
187
|
+
2. **No Bypass**: Plugin hook cannot be circumvented by LLM
|
|
188
|
+
3. **Content Integrity**: Hash prevents silent content changes
|
|
189
|
+
4. **Expiration**: 30-minute window limits abuse window
|
|
190
|
+
|
|
191
|
+
## Testing Considerations
|
|
192
|
+
|
|
193
|
+
### Happy Path
|
|
194
|
+
1. Load skill
|
|
195
|
+
2. Read CONTRIBUTING.md
|
|
196
|
+
3. Search duplicates
|
|
197
|
+
4. Draft content
|
|
198
|
+
5. Present to user
|
|
199
|
+
6. Get approval
|
|
200
|
+
7. Call create_compliance_record
|
|
201
|
+
8. Call issue_write
|
|
202
|
+
9. Success logged
|
|
203
|
+
|
|
204
|
+
### Rejection Paths
|
|
205
|
+
- No record → Blocked with NO_RECORD
|
|
206
|
+
- Expired record → Blocked with EXPIRED
|
|
207
|
+
- Content changed → Blocked (hash mismatch)
|
|
208
|
+
- Record already used → Blocked with ALREADY_USED
|
|
209
|
+
|
|
210
|
+
## Future Improvements
|
|
211
|
+
|
|
212
|
+
1. **Cleanup Job**: Cron-like cleanup of old records
|
|
213
|
+
2. **Metrics**: Track compliance pass/fail rates
|
|
214
|
+
3. **Policy Fetching**: Pull compliance policy from repo
|
|
215
|
+
4. **Multi-Client Support**: Share compliance records across clients
|
|
216
|
+
5. **Signature Verification**: Cryptographic signing of records
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the GitHub Compliance Plugin.
|
|
4
|
+
|
|
5
|
+
## [1.2.0] - 2026-03-21
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Prefix/suffix variation support - all GitHub write tools now have both `tool_name` and `github_tool_name` variants (e.g., `issue_write` and `github_issue_write`)
|
|
10
|
+
- Additional GitHub write tools intercepted:
|
|
11
|
+
- `push_files` / `github_push_files`
|
|
12
|
+
- `create_branch` / `github_create_branch`
|
|
13
|
+
- `create_or_update_file` / `github_create_or_update_file`
|
|
14
|
+
- `delete_file` / `github_delete_file`
|
|
15
|
+
- `create_repository` / `github_create_repository`
|
|
16
|
+
- `create_pull_request_with_copilot` / `github_create_pull_request_with_copilot`
|
|
17
|
+
- `update_pull_request_branch` / `github_update_pull_request_branch`
|
|
18
|
+
- `reply_to_pull_request_comment` / `github_reply_to_pull_request_comment`
|
|
19
|
+
- `add_reply_to_pull_request_comment` / `github_add_reply_to_pull_request_comment`
|
|
20
|
+
|
|
21
|
+
### Benefits
|
|
22
|
+
|
|
23
|
+
- Ensures no GitHub write operation can escape interception regardless of MCP tool naming convention
|
|
24
|
+
- Comprehensive coverage of all GitHub MCP write tools
|
|
25
|
+
|
|
26
|
+
## [1.1.0] - 2026-03-21
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
|
|
30
|
+
- `schema.ts` - Centralized constants for all table names, column names, enums
|
|
31
|
+
- `SCHEMA.tables` - Table name constants
|
|
32
|
+
- `SCHEMA.columns` - All column name constants
|
|
33
|
+
- `SCHEMA.indexes` - Index name constants
|
|
34
|
+
- `RECORD_STATUS` enum - Record status values
|
|
35
|
+
- `CHECK_TYPES` enum - Check type constants
|
|
36
|
+
- `ERROR_REASONS` enum - Error reason constants
|
|
37
|
+
- `RECORD_TTL_MS` - Record expiration time constant
|
|
38
|
+
- `RECORD_CLEANUP_AGE_MS` - Cleanup age constant
|
|
39
|
+
- `CONTENT_PREVIEW_MAX_LENGTH` - Content preview truncation limit
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Refactored all SQL queries to use centralized schema constants
|
|
44
|
+
- All `CREATE TABLE` statements now use `SCHEMA.tables.*` and `SCHEMA.columns.*`
|
|
45
|
+
- All `INSERT` statements now use centralized constants
|
|
46
|
+
- All `SELECT`, `UPDATE`, `DELETE` statements now use centralized constants
|
|
47
|
+
- `validateCompliance()` now uses `RECORD_STATUS` enum
|
|
48
|
+
- Error handling now uses `ERROR_REASONS` enum
|
|
49
|
+
- Plugin hook now uses `ERROR_REASONS` enum
|
|
50
|
+
|
|
51
|
+
### Benefits
|
|
52
|
+
|
|
53
|
+
- Single source of truth for all schema definitions
|
|
54
|
+
- Change a column name in one place, propagates everywhere
|
|
55
|
+
- Eliminates typos between schema definition and queries
|
|
56
|
+
- IDE autocomplete works for table/column names
|
|
57
|
+
- Self-documenting SQL
|
|
58
|
+
|
|
59
|
+
## [1.0.0] - 2026-03-20
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- Initial implementation
|
|
64
|
+
- SQLite database with 4 tables:
|
|
65
|
+
- `compliance_records` - Main compliance records
|
|
66
|
+
- `compliance_checks` - Individual checks per record
|
|
67
|
+
- `audit_log` - Successful operation log
|
|
68
|
+
- `failed_attempts` - Rejected operation log
|
|
69
|
+
- Plugin hooks:
|
|
70
|
+
- `tool.execute.before` - Intercepts and validates GitHub write tools
|
|
71
|
+
- `tool.execute.after` - Logs successful executions
|
|
72
|
+
- Custom tools for LLM:
|
|
73
|
+
- `create_compliance_record` - Create record after user approval
|
|
74
|
+
- `get_compliance_status` - Check existing record status
|
|
75
|
+
- GitHub write tool interception (11 tools):
|
|
76
|
+
- issue_write, issue_update
|
|
77
|
+
- pull_request_write, pull_request_update
|
|
78
|
+
- add_issue_comment, add_pull_request_comment
|
|
79
|
+
- pull_request_review, pull_request_review_comment
|
|
80
|
+
- create_pull_request, update_issue, update_pull_request
|
|
81
|
+
- Content hash integrity verification
|
|
82
|
+
- 30-minute expiration on compliance records
|
|
83
|
+
- Comprehensive error messages with workflow instructions
|
|
84
|
+
- JSDoc documentation throughout
|
|
85
|
+
|
|
86
|
+
### Configuration
|
|
87
|
+
|
|
88
|
+
- Plugin configured in `opencode.json`:
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"plugin": ["github-compliance"],
|
|
92
|
+
"permission": {
|
|
93
|
+
"github_*": "ask"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Database Location
|
|
99
|
+
|
|
100
|
+
`~/.opencode/github-compliance.db`
|
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# GitHub Compliance Plugin
|
|
2
|
+
|
|
3
|
+
Enforces mandatory human approval for GitHub write operations in OpenCode.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This plugin intercepts GitHub write operations and blocks them unless a valid compliance record exists in SQLite. The LLM must follow the compliance workflow to create a record before any write operation is allowed.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
13
|
+
│ OpenCode │
|
|
14
|
+
│ ┌───────────┐ ┌──────────────────────────────────────────┐ │
|
|
15
|
+
│ │ LLM │───▶│ Plugin (tool.execute.before) │ │
|
|
16
|
+
│ └───────────┘ │ - Checks SQLite for compliance record │ │
|
|
17
|
+
│ │ - Validates status, expiration │ │
|
|
18
|
+
│ │ - Blocks if invalid, allows if valid │ │
|
|
19
|
+
│ └──────────────────────────────────────────┘ │
|
|
20
|
+
│ │ │
|
|
21
|
+
│ ▼ │
|
|
22
|
+
│ ┌──────────────────────────────────────────┐ │
|
|
23
|
+
│ │ Custom Tools (LLM calls these) │ │
|
|
24
|
+
│ │ - create_compliance_record │ │
|
|
25
|
+
│ │ - get_compliance_status │ │
|
|
26
|
+
│ └──────────────────────────────────────────┘ │
|
|
27
|
+
│ │ │
|
|
28
|
+
└────────────────────────────────────┼────────────────────────────┘
|
|
29
|
+
│
|
|
30
|
+
▼
|
|
31
|
+
┌─────────────────────┐
|
|
32
|
+
│ SQLite Database │
|
|
33
|
+
│ ~/.opencode/ │
|
|
34
|
+
│ github-compliance.db│
|
|
35
|
+
└─────────────────────┘
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Components
|
|
39
|
+
|
|
40
|
+
| File | Purpose |
|
|
41
|
+
|------|---------|
|
|
42
|
+
| `schema.ts` | Centralized constants for table/column names, enums |
|
|
43
|
+
| `database.ts` | SQLite operations, schema, CRUD |
|
|
44
|
+
| `compliance.ts` | Tool identification, content hashing, validation logic |
|
|
45
|
+
| `errors.ts` | Error message generation |
|
|
46
|
+
| `tools.ts` | Custom tools exposed to LLM |
|
|
47
|
+
| `plugin.ts` | Main entry, hooks into tool.execute.before/after |
|
|
48
|
+
|
|
49
|
+
## Schema (schema.ts)
|
|
50
|
+
|
|
51
|
+
All table and column names are centralized in `schema.ts`:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
export const SCHEMA = {
|
|
55
|
+
tables: {
|
|
56
|
+
COMPLIANCE_RECORDS: "compliance_records",
|
|
57
|
+
COMPLIANCE_CHECKS: "compliance_checks",
|
|
58
|
+
AUDIT_LOG: "audit_log",
|
|
59
|
+
FAILED_ATTEMPTS: "failed_attempts"
|
|
60
|
+
},
|
|
61
|
+
columns: { ID, TOOL_NAME, OWNER, REPO, ... },
|
|
62
|
+
indexes: { COMPLIANCE_LOOKUP, COMPLIANCE_EXPIRES }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const RECORD_STATUS = { PENDING, APPROVED, EXPIRED, USED }
|
|
66
|
+
export const CHECK_TYPES = { CONTRIBUTING_MD_READ, DUPLICATE_SEARCHED, ... }
|
|
67
|
+
export const ERROR_REASONS = { NO_RECORD, NOT_APPROVED, ALREADY_USED, ... }
|
|
68
|
+
export const RECORD_TTL_MS = 30 * 60 * 1000
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Installation
|
|
72
|
+
|
|
73
|
+
1. Copy plugin to `~/.config/opencode/plugins/github-compliance/`
|
|
74
|
+
2. Add to `opencode.json`:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"plugin": ["github-compliance"],
|
|
79
|
+
"permission": {
|
|
80
|
+
"github_*": "ask"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Database Schema
|
|
86
|
+
|
|
87
|
+
### compliance_records
|
|
88
|
+
|
|
89
|
+
| Column | Type | Description |
|
|
90
|
+
|--------|------|-------------|
|
|
91
|
+
| id | TEXT | UUID primary key |
|
|
92
|
+
| tool_name | TEXT | GitHub tool name |
|
|
93
|
+
| owner | TEXT | Repository owner |
|
|
94
|
+
| repo | TEXT | Repository name |
|
|
95
|
+
| content_hash | TEXT | SHA256 of content |
|
|
96
|
+
| github_username | TEXT | Approver's GitHub username |
|
|
97
|
+
| approved_at | TEXT | ISO8601 timestamp |
|
|
98
|
+
| expires_at | TEXT | ISO8601 expiration |
|
|
99
|
+
| status | TEXT | pending/approved/expired/used |
|
|
100
|
+
|
|
101
|
+
### compliance_checks
|
|
102
|
+
|
|
103
|
+
Stores individual checks performed for each record.
|
|
104
|
+
|
|
105
|
+
### audit_log
|
|
106
|
+
|
|
107
|
+
Immutable log of successful operations.
|
|
108
|
+
|
|
109
|
+
### failed_attempts
|
|
110
|
+
|
|
111
|
+
Log of rejected operations with reasons.
|
|
112
|
+
|
|
113
|
+
## Workflow
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
1. LLM attempts github_issue_write
|
|
117
|
+
↓
|
|
118
|
+
2. plugin.tool.execute.before intercepts
|
|
119
|
+
↓
|
|
120
|
+
3. Checks SQLite for compliance record
|
|
121
|
+
↓
|
|
122
|
+
4. No record → BLOCK with error message
|
|
123
|
+
Record valid → Mark as used, allow execution
|
|
124
|
+
↓
|
|
125
|
+
5. If allowed, plugin.tool.execute.after logs success
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Custom Tools
|
|
129
|
+
|
|
130
|
+
### create_compliance_record
|
|
131
|
+
|
|
132
|
+
Called by LLM after user approval.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
create_compliance_record({
|
|
136
|
+
tool_name: "issue_write",
|
|
137
|
+
owner: "owner",
|
|
138
|
+
repo: "repo",
|
|
139
|
+
title: "Issue title",
|
|
140
|
+
body: "Issue body",
|
|
141
|
+
github_username: "user",
|
|
142
|
+
approved_at: "2026-03-20T15:30:00Z",
|
|
143
|
+
checks: [
|
|
144
|
+
{ check_type: "contributing_md_read", passed: true },
|
|
145
|
+
{ check_type: "duplicate_searched", passed: true },
|
|
146
|
+
{ check_type: "human_approval_obtained", passed: true }
|
|
147
|
+
]
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### get_compliance_status
|
|
152
|
+
|
|
153
|
+
Check if valid record exists.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
get_compliance_status({
|
|
157
|
+
tool_name: "issue_write",
|
|
158
|
+
owner: "owner",
|
|
159
|
+
repo: "repo",
|
|
160
|
+
content_hash: "sha256:abc123..."
|
|
161
|
+
})
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Extending
|
|
165
|
+
|
|
166
|
+
### Adding New Write Tools
|
|
167
|
+
|
|
168
|
+
Edit `GITHUB_WRITE_TOOLS_UNPREFIXED` in `compliance.ts`. The prefixed variants (`github_` prefix) are automatically generated:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
const GITHUB_WRITE_TOOLS_UNPREFIXED = [
|
|
172
|
+
// ... existing tools ...
|
|
173
|
+
"new_write_tool"
|
|
174
|
+
] as const;
|
|
175
|
+
|
|
176
|
+
const GITHUB_WRITE_TOOLS_PREFIXED = GITHUB_WRITE_TOOLS_UNPREFIXED.map(t => `github_${t}`);
|
|
177
|
+
|
|
178
|
+
export const GITHUB_WRITE_TOOLS = [...GITHUB_WRITE_TOOLS_UNPREFIXED, ...GITHUB_WRITE_TOOLS_PREFIXED] as const;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Note: Both `tool_name` and `github_tool_name` variants will be intercepted.
|
|
182
|
+
|
|
183
|
+
### Currently Intercepted Tools
|
|
184
|
+
|
|
185
|
+
The plugin intercepts all GitHub write tools in both prefixed and unprefixed forms:
|
|
186
|
+
|
|
187
|
+
- Issue operations: `issue_write`, `issue_update`, `update_issue`
|
|
188
|
+
- Pull request operations: `pull_request_write`, `pull_request_update`, `create_pull_request`, `update_pull_request`, `pull_request_review`, `update_pull_request_branch`
|
|
189
|
+
- Comments: `add_issue_comment`, `add_pull_request_comment`, `pull_request_review_comment`, `reply_to_pull_request_comment`, `add_reply_to_pull_request_comment`
|
|
190
|
+
- Repository operations: `push_files`, `create_branch`, `create_or_update_file`, `delete_file`, `create_repository`
|
|
191
|
+
- Advanced: `create_pull_request_with_copilot`
|
|
192
|
+
|
|
193
|
+
### Adding New Check Types
|
|
194
|
+
|
|
195
|
+
1. Add to `CHECK_TYPES` in `schema.ts`
|
|
196
|
+
2. LLM includes in `checks` array when calling `create_compliance_record`
|
|
197
|
+
3. Check is stored in `compliance_checks` table
|
|
198
|
+
|
|
199
|
+
### Adding New Validation Rules
|
|
200
|
+
|
|
201
|
+
Edit `validateCompliance()` in `database.ts`:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
if (record.status === RECORD_STATUS.PENDING) {
|
|
205
|
+
return { valid: false, reason: ERROR_REASONS.NOT_APPROVED, ... };
|
|
206
|
+
}
|
|
207
|
+
// Add new validation here
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Adding New Columns
|
|
211
|
+
|
|
212
|
+
1. Add column constant to `SCHEMA.columns` in `schema.ts`
|
|
213
|
+
2. Update `CREATE TABLE` statements in `initDatabase()` in `database.ts`
|
|
214
|
+
3. Update type interfaces in `database.ts`
|
|
215
|
+
|
|
216
|
+
## Compliance JSON Format
|
|
217
|
+
|
|
218
|
+
Issues/PRs should include this JSON in the body:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"mcp_compliance": {
|
|
223
|
+
"version": "1.0",
|
|
224
|
+
"client": {
|
|
225
|
+
"name": "opencode",
|
|
226
|
+
"client_issue_id": "<record_uuid>"
|
|
227
|
+
},
|
|
228
|
+
"human_approval": {
|
|
229
|
+
"github_username": "<user>",
|
|
230
|
+
"approved_at": "<timestamp>"
|
|
231
|
+
},
|
|
232
|
+
"checks_performed": [
|
|
233
|
+
"contributing_md_read",
|
|
234
|
+
"duplicate_searched",
|
|
235
|
+
"human_approval_obtained"
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Error Codes
|
|
242
|
+
|
|
243
|
+
| Code | Meaning |
|
|
244
|
+
|------|---------|
|
|
245
|
+
| NO_RECORD | No compliance record found |
|
|
246
|
+
| NOT_APPROVED | Record exists but not approved |
|
|
247
|
+
| ALREADY_USED | Record already consumed |
|
|
248
|
+
| EXPIRED | Record older than 30 minutes |
|
|
249
|
+
| DATABASE_ERROR | SQLite error |
|
|
250
|
+
|
|
251
|
+
## Maintenance
|
|
252
|
+
|
|
253
|
+
### Cleanup Expired Records
|
|
254
|
+
|
|
255
|
+
Called automatically on plugin init:
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
await cleanupExpiredRecords(); // Marks expired as 'expired'
|
|
259
|
+
await cleanupOldRecords(); // Deletes records > 7 days old
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Content Hash Stability
|
|
263
|
+
|
|
264
|
+
Hash is computed from `title` and `body` only. Do not include:
|
|
265
|
+
- Timestamps
|
|
266
|
+
- Dynamic content
|
|
267
|
+
- Template auto-fill that changes
|
|
268
|
+
|
|
269
|
+
## Version
|
|
270
|
+
|
|
271
|
+
Current: 1.2.0
|
|
272
|
+
|
|
273
|
+
See [CHANGELOG.md](./CHANGELOG.md) for version history.
|