@ossdeveloper/github-compliance 1.0.2 → 1.3.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 CHANGED
@@ -10,14 +10,34 @@ Deep dive into the GitHub Compliance Plugin design.
10
10
  4. **Audit Trail**: Complete history of all operations for debugging
11
11
  5. **Centralized Schema**: Single source of truth for all database definitions
12
12
 
13
+ ## MCP Tool Handling
14
+
15
+ The plugin must handle two types of GitHub tools differently:
16
+
17
+ ### Non-MCP Tools (e.g., custom tools you create)
18
+ - `tool.execute.before` has access to `output.args`
19
+ - Can validate AND block before execution
20
+ - `tool.execute.after` logs success
21
+
22
+ ### MCP Tools (GitHub MCP - prefixed with `github_`)
23
+ - `tool.execute.before` does NOT have access to `output.args` (OpenCode limitation)
24
+ - Cannot validate before execution
25
+ - Must defer validation to `tool.execute.after` where `input.args` IS available
26
+ - Validation happens post-execution (for audit purposes)
27
+
28
+ **Important**: For MCP tools, if compliance is invalid, the tool has already executed.
29
+ The plugin logs the violation for audit but cannot block the operation.
30
+
13
31
  ## Component Flow
14
32
 
33
+ ### Non-MCP Tools (Standard Flow)
34
+
15
35
  ```
16
36
  ┌──────────────────────────────────────────────────────────────────────────┐
17
37
  │ tool.execute.before │
18
38
  │ │
19
39
  │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
20
- │ │ isWriteTool? │────▶│ extractInfo │────▶│ getComplianceRecord │ │
40
+ │ │ isWriteTool? │────▶│ extractInfo │────▶│ getComplianceRecord │ │
21
41
  │ └──────────────┘ └──────────────┘ └──────────────────────────┘ │
22
42
  │ │ │
23
43
  │ ▼ │
@@ -30,13 +50,109 @@ Deep dive into the GitHub Compliance Plugin design.
30
50
  │ ▼ ▼ │ │
31
51
  │ ┌──────────────────┐ ┌──────────────────────┐ │ │
32
52
  │ │ valid: false │ │ valid: true │ │ │
33
- │ │ logFailedAttempt │ │ markRecordUsed │ │ │
34
- │ │ throw BlockedErr │ │ attach metadata │ │ │
53
+ │ │ logFailedAttempt│ │ markRecordUsed │ │ │
54
+ │ │ throw BlockedErr│ │ attach metadata │ │ │
35
55
  │ └──────────────────┘ └──────────────────────┘ │ │
36
56
  │ │
37
57
  └──────────────────────────────────────────────────────────────────────────┘
38
58
  ```
39
59
 
60
+ ### MCP Tools (Deferred Validation Flow)
61
+
62
+ ```
63
+ ┌──────────────────────────────────────────────────────────────────────────┐
64
+ │ tool.execute.before │
65
+ │ │
66
+ │ ┌──────────────┐ ┌──────────────┐ │
67
+ │ │ isWriteTool? │────▶│ isMcpTool? │ │
68
+ │ └──────────────┘ └──────────────┘ │
69
+ │ │ │
70
+ │ │ (yes) │
71
+ │ ▼ │
72
+ │ ┌──────────────────┐ │
73
+ │ │ Skip validation │ │
74
+ │ │ Set __needsMcpValidation │ │
75
+ │ │ marker on output │ │
76
+ │ └──────────────────┘ │
77
+ │ │
78
+ └──────────────────────────────────────────────────────────────────────────┘
79
+
80
+ │ Tool executes
81
+
82
+ ┌──────────────────────────────────────────────────────────────────────────┐
83
+ │ tool.execute.after │
84
+ │ │
85
+ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────────┐ │
86
+ │ │ needsMcpVal? │────▶│ extractInfo │────▶│ getComplianceRecord │ │
87
+ │ └──────────────┘ │ from input.args └──────────────────────────┘ │
88
+ │ └──────────────┘ │
89
+ │ │ │
90
+ │ ▼ │
91
+ │ ┌────────────────────────┐ │
92
+ │ │ validateCompliance │ │
93
+ │ └────────────────────────┘ │
94
+ │ │ │
95
+ │ ┌─────────────────────┼─────────────────────┐ │
96
+ │ │ │ │ │
97
+ │ ▼ ▼ ▼ │
98
+ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
99
+ │ │ valid: false │ │ valid: true │ │ extraction err │ │
100
+ │ │ log violation │ │ log success │ │ log error │ │
101
+ │ │ (can't block) │ │ │ │ │ │
102
+ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
103
+ │ │
104
+ └──────────────────────────────────────────────────────────────────────────┘
105
+ ```
106
+
107
+ ## OpenCode Plugin API Notes
108
+
109
+ The OpenCode SDK defines hooks as:
110
+
111
+ ```typescript
112
+ "tool.execute.before"?: (input: {
113
+ tool: string;
114
+ sessionID: string;
115
+ callID: string;
116
+ }, output: {
117
+ args: any; // NOTE: NOT populated for MCP tools!
118
+ }) => Promise<void>;
119
+
120
+ "tool.execute.after"?: (input: {
121
+ tool: string;
122
+ sessionID: string;
123
+ callID: string;
124
+ args: any; // IS populated for MCP tools
125
+ }, output: {
126
+ title: string;
127
+ output: string;
128
+ metadata: any;
129
+ }) => Promise<void>;
130
+ ```
131
+
132
+ This is why MCP tools require deferred validation.
133
+
134
+ ## Centralized Contracts (contracts.ts)
135
+
136
+ All content hashing and tool name normalization is centralized in `contracts.ts`:
137
+
138
+ ```typescript
139
+ // Tool name normalization - handles github_ prefix
140
+ export function normalizeToolName(toolName: string): string {
141
+ return TOOL_PREFIX_NORMALIZE[toolName] || toolName;
142
+ }
143
+
144
+ // Content hash - used by both tools.ts and plugin.ts
145
+ export function computeContentHash(title: string | null, body: string | null): string {
146
+ const content = JSON.stringify({ title: title || "", body: body || "" });
147
+ return "sha256:" + createHash("sha256").update(content).digest("hex");
148
+ }
149
+
150
+ // Field extraction - unified interface for compliance lookups
151
+ export function extractRecordFields(toolName: string, args: Record<string, unknown> | null): ComplianceRecordFields;
152
+ ```
153
+
154
+ **Key invariant**: `computeContentHash` is called with DECODED (not base64) values, both when creating records and looking them up.
155
+
40
156
  ## Schema Design (schema.ts)
41
157
 
42
158
  All database definitions are centralized in `schema.ts`:
@@ -63,7 +179,7 @@ export const SCHEMA = {
63
179
  export const RECORD_STATUS = {
64
180
  PENDING: "pending",
65
181
  APPROVED: "approved",
66
- EXP IRED: "expired",
182
+ EXPIRED: "expired",
67
183
  USED: "used"
68
184
  }
69
185
  ```
@@ -88,7 +204,7 @@ pending ────▶ approved ────▶ used
88
204
  ### Content Hash Calculation
89
205
 
90
206
  ```typescript
91
- // In compliance.ts
207
+ // In contracts.ts
92
208
  const content = JSON.stringify({
93
209
  title: title || "",
94
210
  body: body || ""
@@ -112,15 +228,15 @@ CREATE INDEX idx_compliance_expires ON compliance_records(expires_at);
112
228
 
113
229
  1. **tool.execute.before**
114
230
  - Runs BEFORE tool execution
115
- - Can modify output (attach metadata)
116
- - Can throw to block execution
231
+ - For non-MCP: Can validate and block
232
+ - For MCP: Cannot validate (no args), marks for post-validation
117
233
 
118
234
  2. **Tool executes** (if before hook allowed)
119
235
 
120
236
  3. **tool.execute.after**
121
237
  - Runs AFTER tool execution
122
- - Cannot block, only for logging
123
- - Can access output.result
238
+ - For MCP: Performs validation, logs success or violation
239
+ - Cannot block execution (already happened)
124
240
 
125
241
  ## Custom Tool Execution
126
242
 
@@ -138,59 +254,19 @@ Custom tools (from `tools.ts`) do NOT go through the `tool.execute.before` hook.
138
254
 
139
255
  All error reasons are defined in `ERROR_REASONS` enum in `schema.ts`.
140
256
 
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
257
  ## Security Considerations
185
258
 
186
259
  1. **No Fabrication**: LLM cannot fake compliance records - they must actually call `create_compliance_record`
187
260
  2. **No Bypass**: Plugin hook cannot be circumvented by LLM
188
261
  3. **Content Integrity**: Hash prevents silent content changes
189
262
  4. **Expiration**: 30-minute window limits abuse window
263
+ 5. **Audit Trail**: All violations are logged for review
264
+
265
+ **Limitation for MCP Tools**: Since MCP tools execute before compliance can be validated, a determined LLM could potentially execute a write operation before the plugin detects the violation. The violation IS logged and auditable, but the operation completes. Consider this when designing high-security workflows.
190
266
 
191
267
  ## Testing Considerations
192
268
 
193
- ### Happy Path
269
+ ### Happy Path (Non-MCP)
194
270
  1. Load skill
195
271
  2. Read CONTRIBUTING.md
196
272
  3. Search duplicates
@@ -201,16 +277,30 @@ To add a new table:
201
277
  8. Call issue_write
202
278
  9. Success logged
203
279
 
280
+ ### Happy Path (MCP)
281
+ 1. Load skill
282
+ 2. Read CONTRIBUTING.md
283
+ 3. Search duplicates
284
+ 4. Draft content
285
+ 5. Present to user
286
+ 6. Get approval
287
+ 7. Call create_compliance_record
288
+ 8. Call github_issue_write
289
+ 9. Tool executes (before hook can't validate)
290
+ 10. After hook validates, finds valid record
291
+ 11. Success logged
292
+
204
293
  ### Rejection Paths
205
- - No record → Blocked with NO_RECORD
206
- - Expired record → Blocked with EXPIRED
294
+ - No record → Blocked with NO_RECORD (or logged if MCP)
295
+ - Expired record → Blocked with EXPIRED (or logged if MCP)
207
296
  - Content changed → Blocked (hash mismatch)
208
297
  - Record already used → Blocked with ALREADY_USED
209
298
 
210
299
  ## Future Improvements
211
300
 
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
301
+ 1. **Pre-validation for MCP**: Work with OpenCode to enable args in before hook for MCP tools
302
+ 2. **Cleanup Job**: Cron-like cleanup of old records
303
+ 3. **Metrics**: Track compliance pass/fail rates
304
+ 4. **Policy Fetching**: Pull compliance policy from repo
305
+ 5. **Multi-Client Support**: Share compliance records across clients
306
+ 6. **Signature Verification**: Cryptographic signing of records
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to the GitHub Compliance Plugin.
4
4
 
5
+ ## [1.3.0] - 2026-03-21
6
+
7
+ ### Fixed
8
+
9
+ - **MCP Tool Argument Extraction Bug**: Fixed critical bug where MCP tools (prefixed with `github_`) could not be validated because `output.args` is not populated in `tool.execute.before` hook
10
+ - MCP tools now use deferred validation: `tool.execute.before` skips validation and marks tool for post-execution validation
11
+ - `tool.execute.after` performs validation when `input.args` IS available
12
+
13
+ ### Changed
14
+
15
+ - Rewrote `plugin.ts` to use centralized `extractRecordFields` from `contracts.ts`
16
+ - Added helper functions `isMcpTool()`, `extractArgs()`, `validateAndProcess()`, `logSuccess()`
17
+ - Separated validation logic for non-MCP (pre-execution blocking) and MCP (post-execution audit) flows
18
+ - Refactored error handling to be more robust
19
+
20
+ ### Architecture Updates
21
+
22
+ - Updated ARCHITECTURE.md with new MCP tool handling flow
23
+ - Added OpenCode Plugin API notes explaining the args availability difference between hooks
24
+ - Added security consideration for MCP tool limitation
25
+
26
+ ### Benefits
27
+
28
+ - MCP tools can now be properly audited for compliance
29
+ - Non-MCP tools continue to work with pre-execution blocking
30
+ - Centralized contracts ensure consistent field extraction everywhere
31
+
5
32
  ## [1.2.0] - 2026-03-21
6
33
 
7
34
  ### Added
@@ -97,4 +124,4 @@ All notable changes to the GitHub Compliance Plugin.
97
124
 
98
125
  ### Database Location
99
126
 
100
- `~/.opencode/github-compliance.db`
127
+ `~/.opencode/github-compliance.db`
package/README.md CHANGED
@@ -13,28 +13,53 @@ This plugin intercepts GitHub write operations and blocks them unless a valid co
13
13
  │ OpenCode │
14
14
  │ ┌───────────┐ ┌──────────────────────────────────────────┐ │
15
15
  │ │ LLM │───▶│ Plugin (tool.execute.before) │ │
16
- │ └───────────┘ │ - Checks SQLite for compliance record │ │
17
- │ │ - Validates status, expiration │ │
18
- - Blocks if invalid, allows if valid
16
+ │ └───────────┘ │ - MCP tools: Skip (no args), mark for after│ │
17
+ │ │ - Other tools: Check SQLite, block/allow │ │
18
+ └──────────────────────────────────────────┘
19
+ │ │ │
20
+ │ ▼ (if allowed) │
21
+ │ ┌──────────────────────────────────────────┐ │
22
+ │ │ GitHub MCP executes │ │
23
+ │ └──────────────────────────────────────────┘ │
24
+ │ │ │
25
+ │ ▼ │
26
+ │ ┌───────────┐ ┌──────────────────────────────────────────┐ │
27
+ │ │ LLM │◀───│ Plugin (tool.execute.after) │ │
28
+ │ └───────────┘ │ - MCP tools: Validate now, log result │ │
29
+ │ │ - Other tools: Log success │ │
19
30
  │ └──────────────────────────────────────────┘ │
20
31
  │ │ │
21
32
  │ ▼ │
22
33
  │ ┌──────────────────────────────────────────┐ │
23
- │ │ Custom Tools (LLM calls these) │ │
34
+ │ │ Custom Tools (LLM calls these) │ │
24
35
  │ │ - create_compliance_record │ │
25
36
  │ │ - get_compliance_status │ │
26
37
  │ └──────────────────────────────────────────┘ │
27
38
  │ │ │
28
39
  └────────────────────────────────────┼────────────────────────────┘
29
-
30
-
31
- ┌─────────────────────┐
32
- │ SQLite Database │
33
- │ ~/.opencode/ │
34
- │ github-compliance.db│
35
- └─────────────────────┘
40
+
41
+
42
+ ┌─────────────────────┐
43
+ │ SQLite Database │
44
+ │ ~/.opencode/ │
45
+ │ github-compliance.db│
46
+ └─────────────────────┘
36
47
  ```
37
48
 
49
+ ## MCP Tool Handling
50
+
51
+ **Important**: MCP tools (prefixed with `github_`) behave differently than other tools:
52
+
53
+ | Aspect | Non-MCP Tools | MCP Tools (github_*) |
54
+ |--------|---------------|----------------------|
55
+ | Args in `before` hook | Available in `output.args` | NOT available |
56
+ | Can block before execution | Yes | No (no args) |
57
+ | Args in `after` hook | Available in `input.args` | Available |
58
+ | Validation timing | Pre-execution | Post-execution |
59
+ | Can prevent execution | Yes | No (already executed) |
60
+
61
+ For MCP tools, the plugin validates after execution and logs violations for audit, but cannot block the operation.
62
+
38
63
  ## Components
39
64
 
40
65
  | File | Purpose |
@@ -42,6 +67,7 @@ This plugin intercepts GitHub write operations and blocks them unless a valid co
42
67
  | `schema.ts` | Centralized constants for table/column names, enums |
43
68
  | `database.ts` | SQLite operations, schema, CRUD |
44
69
  | `compliance.ts` | Tool identification, content hashing, validation logic |
70
+ | `contracts.ts` | Centralized field extraction, hash computation, tool name normalization |
45
71
  | `errors.ts` | Error message generation |
46
72
  | `tools.ts` | Custom tools exposed to LLM |
47
73
  | `plugin.ts` | Main entry, hooks into tool.execute.before/after |
@@ -112,8 +138,10 @@ Log of rejected operations with reasons.
112
138
 
113
139
  ## Workflow
114
140
 
141
+ ### For Non-MCP Tools
142
+
115
143
  ```
116
- 1. LLM attempts github_issue_write
144
+ 1. LLM attempts issue_write
117
145
 
118
146
  2. plugin.tool.execute.before intercepts
119
147
 
@@ -122,7 +150,28 @@ Log of rejected operations with reasons.
122
150
  4. No record → BLOCK with error message
123
151
  Record valid → Mark as used, allow execution
124
152
 
125
- 5. If allowed, plugin.tool.execute.after logs success
153
+ 5. plugin.tool.execute.after logs success
154
+ ```
155
+
156
+ ### For MCP Tools (github_*)
157
+
158
+ ```
159
+ 1. LLM attempts github_issue_write
160
+
161
+ 2. plugin.tool.execute.before intercepts
162
+
163
+ 3. Detects MCP tool (no args available)
164
+
165
+ 4. Marks tool for post-execution validation
166
+
167
+ 5. GitHub MCP executes the operation
168
+
169
+ 6. plugin.tool.execute.after intercepts
170
+
171
+ 7. Now has args, validates compliance
172
+
173
+ 8. Valid record → Log success
174
+ Invalid/no record → Log violation (cannot block)
126
175
  ```
127
176
 
128
177
  ## Custom Tools
@@ -268,6 +317,6 @@ Hash is computed from `title` and `body` only. Do not include:
268
317
 
269
318
  ## Version
270
319
 
271
- Current: 1.2.0
320
+ Current: 1.3.0
272
321
 
273
- See [CHANGELOG.md](./CHANGELOG.md) for version history.
322
+ See [CHANGELOG.md](./CHANGELOG.md) for version history.
package/compliance.ts CHANGED
@@ -1,11 +1,18 @@
1
1
  /**
2
- * Compliance validation logic for GitHub write tools
2
+ * Compliance validation logic for GitHub Compliance Plugin
3
3
  *
4
4
  * Handles tool identification, content hashing, and result parsing.
5
+ *
6
+ * IMPORTANT: All content hashing uses centralized functions from contracts.ts
5
7
  */
6
8
 
7
- import { createHash } from "crypto";
8
9
  import { SCHEMA, RECORD_STATUS } from "./schema";
10
+ import {
11
+ extractRecordFields,
12
+ computeContentHash,
13
+ normalizeToolName,
14
+ type ComplianceRecordFields
15
+ } from "./contracts";
9
16
 
10
17
  /**
11
18
  * List of GitHub write tools that require compliance validation
@@ -63,50 +70,28 @@ export function isGitHubWriteTool(toolName: string): boolean {
63
70
  }
64
71
 
65
72
  /**
66
- * Extract compliance-relevant information from a tool call
73
+ * Extract compliance-relevant information from a tool call.
67
74
  *
68
- * Different GitHub tools have different arguments, but they all share
69
- * owner/repo and typically have title/body for content.
75
+ * This function delegates to the centralized extractRecordFields from contracts.ts
76
+ * to ensure consistent field extraction and content hashing.
70
77
  *
71
78
  * @param toolName - Name of the tool being called
72
- * @param args - Arguments passed to the tool
79
+ * @param args - Arguments passed to the tool (can be null/undefined)
73
80
  * @returns Extracted ToolInfo with content hash
74
81
  */
75
- export function extractToolInfo(toolName: string, args: Record<string, unknown>): ToolInfo {
76
- const owner = (args.owner as string) || "";
77
- const repo = (args.repo as string) || "";
78
- const title = (args.title as string) || null;
79
- const body = (args.body as string) || null;
80
-
82
+ export function extractToolInfo(toolName: string, args: Record<string, unknown> | null | undefined): ToolInfo {
83
+ const fields: ComplianceRecordFields = extractRecordFields(toolName, args);
81
84
  return {
82
- toolName,
83
- owner,
84
- repo,
85
- title,
86
- body,
87
- contentHash: computeContentHash(title, body)
85
+ toolName: fields.toolName,
86
+ owner: fields.owner,
87
+ repo: fields.repo,
88
+ title: fields.title,
89
+ body: fields.body,
90
+ contentHash: fields.contentHash
88
91
  };
89
92
  }
90
93
 
91
- /**
92
- * Compute a SHA256 hash of the content for integrity verification
93
- *
94
- * The hash is computed over a normalized JSON string containing
95
- * the content-determining fields. This ensures:
96
- * - Same content = same hash
97
- * - Different content = different hash
98
- *
99
- * @param title - Issue/PR title (or null)
100
- * @param body - Issue/PR body (or null)
101
- * @returns Hash string in format "sha256:..."
102
- */
103
- export function computeContentHash(title: string | null, body: string | null): string {
104
- const content = JSON.stringify({
105
- title: title || "",
106
- body: body || ""
107
- });
108
- return "sha256:" + createHash("sha256").update(content).digest("hex");
109
- }
94
+ export { computeContentHash, normalizeToolName } from "./contracts";
110
95
 
111
96
  /**
112
97
  * Extract GitHub issue/PR ID from a tool execution result
package/contracts.ts ADDED
@@ -0,0 +1,137 @@
1
+ /**
2
+ * GitHub Compliance Plugin - Centralized Contracts
3
+ *
4
+ * This module defines the contract structures and normalization functions
5
+ * used across the plugin to ensure consistency between:
6
+ * - Skill documentation (what LLM uses)
7
+ * - create_compliance_record tool (what gets stored)
8
+ * - tool.execute.before hook (what gets looked up)
9
+ *
10
+ * All content hashing, tool name normalization, and field extraction
11
+ * should go through functions in this module.
12
+ */
13
+
14
+ import { createHash } from "crypto";
15
+ import { SCHEMA } from "./schema";
16
+
17
+ /**
18
+ * GitHub MCP tools use a `github_` prefix (e.g., github_issue_write).
19
+ * But the skill and compliance records use unprefixed names (e.g., issue_write).
20
+ * This map allows normalization between the two.
21
+ */
22
+ const TOOL_PREFIX_NORMALIZE: Record<string, string> = {
23
+ github_issue_write: "issue_write",
24
+ github_issue_update: "issue_update",
25
+ github_pull_request_write: "pull_request_write",
26
+ github_pull_request_update: "pull_request_update",
27
+ github_add_issue_comment: "add_issue_comment",
28
+ github_add_pull_request_comment: "add_pull_request_comment",
29
+ github_pull_request_review: "pull_request_review",
30
+ github_pull_request_review_comment: "pull_request_review_comment",
31
+ github_create_pull_request: "create_pull_request",
32
+ github_update_issue: "update_issue",
33
+ github_update_pull_request: "update_pull_request",
34
+ github_push_files: "push_files",
35
+ github_create_branch: "create_branch",
36
+ github_create_or_update_file: "create_or_update_file",
37
+ github_delete_file: "delete_file",
38
+ github_create_repository: "create_repository",
39
+ github_create_pull_request_with_copilot: "create_pull_request_with_copilot",
40
+ github_update_pull_request_branch: "update_pull_request_branch",
41
+ github_reply_to_pull_request_comment: "reply_to_pull_request_comment",
42
+ github_add_reply_to_pull_request_comment: "add_reply_to_pull_request_comment"
43
+ };
44
+
45
+ /**
46
+ * Normalize a tool name to its unprefixed form.
47
+ * Both "issue_write" and "github_issue_write" return "issue_write".
48
+ *
49
+ * @param toolName - The tool name to normalize
50
+ * @returns Normalized tool name (unprefixed)
51
+ */
52
+ export function normalizeToolName(toolName: string): string {
53
+ return TOOL_PREFIX_NORMALIZE[toolName] || toolName;
54
+ }
55
+
56
+ /**
57
+ * Compute a SHA256 hash of the content for integrity verification.
58
+ *
59
+ * This is the CENTRALIZED content hash computation.
60
+ * Used by BOTH:
61
+ * - tools.ts when creating compliance records
62
+ * - plugin.ts when looking up compliance records
63
+ *
64
+ * IMPORTANT: The skill instructs LLM to base64 encode title/body when
65
+ * calling create_compliance_record. tools.ts decodes them before passing
66
+ * to createComplianceRecord(). So the values stored in DB are DECODED.
67
+ *
68
+ * When the plugin looks up records, it receives the RAW (decoded) values
69
+ * from the tool arguments. So we compute hash from the same decoded values.
70
+ *
71
+ * @param title - Issue/PR title (or null)
72
+ * @param body - Issue/PR body (or null)
73
+ * @returns Hash string in format "sha256:..."
74
+ */
75
+ export function computeContentHash(title: string | null, body: string | null): string {
76
+ const content = JSON.stringify({
77
+ title: title || "",
78
+ body: body || ""
79
+ });
80
+ return "sha256:" + createHash("sha256").update(content).digest("hex");
81
+ }
82
+
83
+ /**
84
+ * Fields required for compliance record creation and lookup.
85
+ * This interface represents the minimal set of fields needed to:
86
+ * 1. Create a compliance record (tools.ts)
87
+ * 2. Look up a compliance record (plugin.ts)
88
+ */
89
+ export interface ComplianceRecordFields {
90
+ toolName: string;
91
+ owner: string;
92
+ repo: string;
93
+ title: string | null;
94
+ body: string | null;
95
+ contentHash: string;
96
+ }
97
+
98
+ /**
99
+ * Extract compliance-relevant fields from tool arguments.
100
+ *
101
+ * This function handles:
102
+ * - Null/undefined args
103
+ * - Tool name normalization (github_ prefix)
104
+ * - Content hash computation
105
+ *
106
+ * @param toolName - Name of the tool being called (can be prefixed or unprefixed)
107
+ * @param args - Arguments passed to the tool (can be null/undefined)
108
+ * @returns Extracted fields with computed content hash
109
+ */
110
+ export function extractRecordFields(toolName: string, args: Record<string, unknown> | null | undefined): ComplianceRecordFields {
111
+ const normalizedToolName = normalizeToolName(toolName);
112
+
113
+ if (!args || typeof args !== 'object') {
114
+ return {
115
+ toolName: normalizedToolName,
116
+ owner: "",
117
+ repo: "",
118
+ title: null,
119
+ body: null,
120
+ contentHash: computeContentHash(null, null)
121
+ };
122
+ }
123
+
124
+ const owner = (args.owner as string) || "";
125
+ const repo = (args.repo as string) || "";
126
+ const title = (args.title as string) || null;
127
+ const body = (args.body as string) || null;
128
+
129
+ return {
130
+ toolName: normalizedToolName,
131
+ owner,
132
+ repo,
133
+ title,
134
+ body,
135
+ contentHash: computeContentHash(title, body)
136
+ };
137
+ }