@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.
@@ -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.