@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/errors.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error message builder for compliance violations
|
|
3
|
+
*
|
|
4
|
+
* Generates user-friendly error messages when compliance checks fail.
|
|
5
|
+
* These messages are shown to the LLM when a write operation is blocked.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ERROR_REASONS } from "./schema";
|
|
9
|
+
|
|
10
|
+
export interface ValidationResult {
|
|
11
|
+
valid: boolean;
|
|
12
|
+
reason: string;
|
|
13
|
+
message: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Build a detailed blocked error message for compliance violations
|
|
18
|
+
*
|
|
19
|
+
* The error message includes:
|
|
20
|
+
* - The reason for blocking
|
|
21
|
+
* - Instructions on how to create a valid compliance record
|
|
22
|
+
* - Reference to the skill documentation
|
|
23
|
+
*
|
|
24
|
+
* @param validation - Validation result with reason and message
|
|
25
|
+
* @returns Formatted error message string
|
|
26
|
+
*/
|
|
27
|
+
export function buildBlockedError(validation: ValidationResult): string {
|
|
28
|
+
const skillInstruction = `skill({ name: 'github-compliance' })`;
|
|
29
|
+
|
|
30
|
+
const baseError = `
|
|
31
|
+
[GITHUB COMPLIANCE BLOCKED]
|
|
32
|
+
|
|
33
|
+
Reason: ${validation.message}
|
|
34
|
+
|
|
35
|
+
To proceed:
|
|
36
|
+
1. Load the github-compliance skill:
|
|
37
|
+
${skillInstruction}
|
|
38
|
+
|
|
39
|
+
2. Follow the workflow to create a valid compliance record:
|
|
40
|
+
- Read CONTRIBUTING.md for the target repository
|
|
41
|
+
- Search for existing issues/PRs to avoid duplicates
|
|
42
|
+
- Draft your issue/PR content
|
|
43
|
+
- Present draft to user and wait for explicit approval
|
|
44
|
+
- Call create_compliance_record with approval details
|
|
45
|
+
|
|
46
|
+
3. After compliance record is created, retry this operation.
|
|
47
|
+
|
|
48
|
+
Skill location: ~/.config/opencode/skills/github-compliance/SKILL.md
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
if (validation.reason === ERROR_REASONS.NO_RECORD) {
|
|
52
|
+
return baseError;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (validation.reason === ERROR_REASONS.NOT_APPROVED) {
|
|
56
|
+
return `
|
|
57
|
+
[GITHUB COMPLIANCE BLOCKED]
|
|
58
|
+
|
|
59
|
+
Reason: Compliance record exists but has not been approved by user.
|
|
60
|
+
|
|
61
|
+
Your draft was created but not yet approved. Please:
|
|
62
|
+
1. Present the draft to the user
|
|
63
|
+
2. Wait for explicit approval ("yes", "proceed", etc.)
|
|
64
|
+
3. Call create_compliance_record with approved status
|
|
65
|
+
4. Retry this operation
|
|
66
|
+
|
|
67
|
+
${baseError}
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (validation.reason === ERROR_REASONS.ALREADY_USED) {
|
|
72
|
+
return `
|
|
73
|
+
[GITHUB COMPLIANCE BLOCKED]
|
|
74
|
+
|
|
75
|
+
Reason: This compliance record has already been used.
|
|
76
|
+
|
|
77
|
+
Each GitHub write operation requires its own compliance record.
|
|
78
|
+
Please create a new compliance record for this operation.
|
|
79
|
+
|
|
80
|
+
${baseError}
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (validation.reason === ERROR_REASONS.EXPIRED) {
|
|
85
|
+
return `
|
|
86
|
+
[GITHUB COMPLIANCE BLOCKED]
|
|
87
|
+
|
|
88
|
+
Reason: Your compliance record has expired (30 minute limit).
|
|
89
|
+
|
|
90
|
+
Please create a new compliance record by:
|
|
91
|
+
1. Loading the skill: ${skillInstruction}
|
|
92
|
+
2. Presenting draft to user and getting approval again
|
|
93
|
+
3. Creating a new compliance record
|
|
94
|
+
4. Retrying this operation
|
|
95
|
+
|
|
96
|
+
${baseError}
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (validation.reason === ERROR_REASONS.DATABASE_ERROR) {
|
|
101
|
+
return `
|
|
102
|
+
[GITHUB COMPLIANCE BLOCKED]
|
|
103
|
+
|
|
104
|
+
Reason: Compliance database error (${validation.message})
|
|
105
|
+
|
|
106
|
+
Please try again. If the problem persists, restart your OpenCode session.
|
|
107
|
+
|
|
108
|
+
${baseError}
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return baseError;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Build a compliance JSON snippet for embedding in issue/PR body
|
|
117
|
+
*
|
|
118
|
+
* This JSON is added to the end of issue/PR bodies to provide
|
|
119
|
+
* transparency about the compliance process that was followed.
|
|
120
|
+
*
|
|
121
|
+
* @param clientIssueId - UUID from the compliance record
|
|
122
|
+
* @param githubUsername - GitHub username of approver
|
|
123
|
+
* @param approvedAt - ISO8601 timestamp of approval
|
|
124
|
+
* @param checksPerformed - List of check types performed
|
|
125
|
+
* @returns Formatted markdown string with JSON compliance record
|
|
126
|
+
*/
|
|
127
|
+
export function buildComplianceJson(
|
|
128
|
+
clientIssueId: string,
|
|
129
|
+
githubUsername: string,
|
|
130
|
+
approvedAt: string,
|
|
131
|
+
checksPerformed: string[]
|
|
132
|
+
): string {
|
|
133
|
+
const compliance = {
|
|
134
|
+
mcp_compliance: {
|
|
135
|
+
version: "1.0",
|
|
136
|
+
client: {
|
|
137
|
+
name: "opencode",
|
|
138
|
+
client_issue_id: clientIssueId
|
|
139
|
+
},
|
|
140
|
+
human_approval: {
|
|
141
|
+
github_username: githubUsername,
|
|
142
|
+
approved_at: approvedAt
|
|
143
|
+
},
|
|
144
|
+
checks_performed: checksPerformed
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
return `\n\n---\n\n## MCP Compliance Record\n\n\`\`\`json\n${JSON.stringify(compliance, null, 2)}\n\`\`\`\n`;
|
|
149
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ossdeveloper/github-compliance",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "GitHub compliance plugin for OpenCode - enforces human approval for write operations",
|
|
5
|
+
"main": "dist/plugin.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "bun build plugin.ts --target=bun --outfile=dist/plugin.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"@opencode-ai/plugin": "^1.0.0"
|
|
13
|
+
}
|
|
14
|
+
}
|
package/plugin.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Compliance Plugin for OpenCode
|
|
3
|
+
*
|
|
4
|
+
* Enforces mandatory human approval for GitHub write operations.
|
|
5
|
+
* Blocks write operations without valid compliance records in SQLite.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
9
|
+
import {
|
|
10
|
+
initDatabase,
|
|
11
|
+
getComplianceRecord,
|
|
12
|
+
validateCompliance,
|
|
13
|
+
markRecordUsed,
|
|
14
|
+
logAudit,
|
|
15
|
+
logFailedAttempt,
|
|
16
|
+
type ComplianceRecord
|
|
17
|
+
} from "./database";
|
|
18
|
+
import {
|
|
19
|
+
isGitHubWriteTool,
|
|
20
|
+
extractToolInfo,
|
|
21
|
+
extractGitHubUrl,
|
|
22
|
+
extractGitHubId,
|
|
23
|
+
type ToolInfo
|
|
24
|
+
} from "./compliance";
|
|
25
|
+
import { buildBlockedError } from "./errors";
|
|
26
|
+
import { tools } from "./tools";
|
|
27
|
+
import { ERROR_REASONS } from "./schema";
|
|
28
|
+
|
|
29
|
+
export const GitHubCompliancePlugin: Plugin = async (_ctx) => {
|
|
30
|
+
await initDatabase();
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
/** Custom tools exposed to the LLM */
|
|
34
|
+
tool: tools,
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pre-execution hook for tool calls
|
|
38
|
+
*
|
|
39
|
+
* Intercepts GitHub write tools and validates compliance:
|
|
40
|
+
* - Checks for existing compliance record in SQLite
|
|
41
|
+
* - Validates record status (approved, not expired, not used)
|
|
42
|
+
* - Blocks tool execution if validation fails
|
|
43
|
+
* - Marks record as 'used' if validation succeeds
|
|
44
|
+
*/
|
|
45
|
+
"tool.execute.before": async (input, output) => {
|
|
46
|
+
// Only intercept GitHub write tools - let everything else pass through
|
|
47
|
+
if (!isGitHubWriteTool(input.tool)) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let toolInfo: ToolInfo;
|
|
52
|
+
let record: ComplianceRecord | null = null;
|
|
53
|
+
|
|
54
|
+
// Extract tool-specific information for compliance lookup
|
|
55
|
+
try {
|
|
56
|
+
toolInfo = extractToolInfo(input.tool, input.args as Record<string, unknown>);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Log extraction failures for debugging
|
|
59
|
+
await logFailedAttempt({
|
|
60
|
+
tool_name: input.tool,
|
|
61
|
+
owner: "unknown",
|
|
62
|
+
repo: "unknown",
|
|
63
|
+
reason: ERROR_REASONS.EXTRACTION_ERROR,
|
|
64
|
+
content_hash: null,
|
|
65
|
+
error_details: `Failed to extract tool info: ${error instanceof Error ? error.message : String(error)}`
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
throw new Error(buildBlockedError({
|
|
69
|
+
valid: false,
|
|
70
|
+
reason: ERROR_REASONS.DATABASE_ERROR,
|
|
71
|
+
message: `Failed to parse tool arguments: ${error instanceof Error ? error.message : String(error)}`
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Look up compliance record by tool, repo, and content hash
|
|
77
|
+
record = await getComplianceRecord(
|
|
78
|
+
toolInfo.toolName,
|
|
79
|
+
toolInfo.owner,
|
|
80
|
+
toolInfo.repo,
|
|
81
|
+
toolInfo.contentHash
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Validate the record (status, expiration, etc.)
|
|
85
|
+
const validation = await validateCompliance(record, toolInfo);
|
|
86
|
+
|
|
87
|
+
if (!validation.valid) {
|
|
88
|
+
// Log rejected attempt for audit
|
|
89
|
+
await logFailedAttempt({
|
|
90
|
+
tool_name: toolInfo.toolName,
|
|
91
|
+
owner: toolInfo.owner,
|
|
92
|
+
repo: toolInfo.repo,
|
|
93
|
+
reason: validation.reason,
|
|
94
|
+
content_hash: toolInfo.contentHash,
|
|
95
|
+
provided_record_id: record?.id || null,
|
|
96
|
+
error_details: validation.message
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Block the tool execution with detailed error message
|
|
100
|
+
throw new Error(buildBlockedError(validation));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Record is valid - mark as used to prevent replay
|
|
104
|
+
await markRecordUsed(record.id);
|
|
105
|
+
|
|
106
|
+
// Attach metadata for after hook
|
|
107
|
+
(output as Record<string, unknown>).__complianceRecordId = record.id;
|
|
108
|
+
(output as Record<string, unknown>).__toolInfo = toolInfo;
|
|
109
|
+
|
|
110
|
+
} catch (error) {
|
|
111
|
+
// Re-throw blocked errors as-is
|
|
112
|
+
if (error instanceof Error && error.message.includes("[GITHUB COMPLIANCE BLOCKED]")) {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Unexpected errors - log and block
|
|
117
|
+
await logFailedAttempt({
|
|
118
|
+
tool_name: toolInfo.toolName,
|
|
119
|
+
owner: toolInfo.owner,
|
|
120
|
+
repo: toolInfo.repo,
|
|
121
|
+
reason: ERROR_REASONS.UNEXPECTED_ERROR,
|
|
122
|
+
content_hash: toolInfo.contentHash,
|
|
123
|
+
provided_record_id: record?.id || null,
|
|
124
|
+
error_details: error instanceof Error ? error.message : String(error)
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
throw new Error(buildBlockedError({
|
|
128
|
+
valid: false,
|
|
129
|
+
reason: ERROR_REASONS.DATABASE_ERROR,
|
|
130
|
+
message: error instanceof Error ? error.message : String(error)
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Post-execution hook for successful tool calls
|
|
137
|
+
*
|
|
138
|
+
* Logs successful GitHub write operations to audit trail.
|
|
139
|
+
* Only logs for GitHub write tools that were validated.
|
|
140
|
+
*/
|
|
141
|
+
"tool.execute.after": async (input, output) => {
|
|
142
|
+
// Only process GitHub write tools we intercepted
|
|
143
|
+
if (!isGitHubWriteTool(input.tool)) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Skip if no compliance record was attached (shouldn't happen)
|
|
148
|
+
const complianceRecordId = (output as Record<string, unknown>).__complianceRecordId as string | undefined;
|
|
149
|
+
const toolInfo = (output as Record<string, unknown>).__toolInfo as ToolInfo | undefined;
|
|
150
|
+
|
|
151
|
+
if (!complianceRecordId || !toolInfo) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Extract GitHub response data for audit
|
|
156
|
+
const githubUrl = extractGitHubUrl(output.result);
|
|
157
|
+
const githubId = extractGitHubId(output.result);
|
|
158
|
+
|
|
159
|
+
// Log successful execution
|
|
160
|
+
await logAudit({
|
|
161
|
+
compliance_record_id: complianceRecordId,
|
|
162
|
+
tool_name: toolInfo.toolName,
|
|
163
|
+
owner: toolInfo.owner,
|
|
164
|
+
repo: toolInfo.repo,
|
|
165
|
+
github_username: "unknown",
|
|
166
|
+
status: "success",
|
|
167
|
+
error_message: null,
|
|
168
|
+
github_url: githubUrl,
|
|
169
|
+
github_id: githubId,
|
|
170
|
+
executed_at: new Date().toISOString(),
|
|
171
|
+
content_preview: toolInfo.body ? toolInfo.body.substring(0, 500) : null
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export default GitHubCompliancePlugin;
|
package/schema.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub Compliance Plugin for OpenCode
|
|
3
|
+
*
|
|
4
|
+
* Enforces mandatory human approval for GitHub write operations.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Centralized schema - single source of truth for all column/table names
|
|
8
|
+
export const SCHEMA = {
|
|
9
|
+
tables: {
|
|
10
|
+
COMPLIANCE_RECORDS: "compliance_records",
|
|
11
|
+
COMPLIANCE_CHECKS: "compliance_checks",
|
|
12
|
+
AUDIT_LOG: "audit_log",
|
|
13
|
+
FAILED_ATTEMPTS: "failed_attempts"
|
|
14
|
+
},
|
|
15
|
+
columns: {
|
|
16
|
+
// compliance_records
|
|
17
|
+
ID: "id",
|
|
18
|
+
TOOL_NAME: "tool_name",
|
|
19
|
+
OWNER: "owner",
|
|
20
|
+
REPO: "repo",
|
|
21
|
+
TITLE: "title",
|
|
22
|
+
CONTENT_HASH: "content_hash",
|
|
23
|
+
GITHUB_USERNAME: "github_username",
|
|
24
|
+
APPROVED_AT: "approved_at",
|
|
25
|
+
EXPIRES_AT: "expires_at",
|
|
26
|
+
STATUS: "status",
|
|
27
|
+
CREATED_AT: "created_at",
|
|
28
|
+
CLIENT_NAME: "client_name",
|
|
29
|
+
POLICY_VERSION: "policy_version",
|
|
30
|
+
// compliance_checks
|
|
31
|
+
COMPLIANCE_RECORD_ID: "compliance_record_id",
|
|
32
|
+
CHECK_TYPE: "check_type",
|
|
33
|
+
CHECK_PASSED: "check_passed",
|
|
34
|
+
DETAILS: "details",
|
|
35
|
+
// audit_log & failed_attempts
|
|
36
|
+
ERROR_MESSAGE: "error_message",
|
|
37
|
+
GITHUB_URL: "github_url",
|
|
38
|
+
GITHUB_ID: "github_id",
|
|
39
|
+
EXECUTED_AT: "executed_at",
|
|
40
|
+
CONTENT_PREVIEW: "content_preview",
|
|
41
|
+
// failed_attempts
|
|
42
|
+
PROVIDED_RECORD_ID: "provided_record_id",
|
|
43
|
+
ATTEMPTED_AT: "attempted_at",
|
|
44
|
+
ERROR_DETAILS: "error_details"
|
|
45
|
+
},
|
|
46
|
+
indexes: {
|
|
47
|
+
COMPLIANCE_LOOKUP: "idx_compliance_lookup",
|
|
48
|
+
COMPLIANCE_EXPIRES: "idx_compliance_expires"
|
|
49
|
+
}
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
// Record status enum
|
|
53
|
+
export const RECORD_STATUS = {
|
|
54
|
+
PENDING: "pending",
|
|
55
|
+
APPROVED: "approved",
|
|
56
|
+
EXPIRED: "expired",
|
|
57
|
+
USED: "used"
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
// Check types
|
|
61
|
+
export const CHECK_TYPES = {
|
|
62
|
+
CONTRIBUTING_MD_READ: "contributing_md_read",
|
|
63
|
+
DUPLICATE_SEARCHED: "duplicate_searched",
|
|
64
|
+
TEMPLATE_FOLLOWED: "template_followed",
|
|
65
|
+
HUMAN_APPROVAL_OBTAINED: "human_approval_obtained",
|
|
66
|
+
POLICY_ACKNOWLEDGED: "policy_acknowledged",
|
|
67
|
+
VOUCH_VERIFIED: "vouch_verified"
|
|
68
|
+
} as const;
|
|
69
|
+
|
|
70
|
+
// Error reasons
|
|
71
|
+
export const ERROR_REASONS = {
|
|
72
|
+
NO_RECORD: "NO_RECORD",
|
|
73
|
+
NOT_APPROVED: "NOT_APPROVED",
|
|
74
|
+
ALREADY_USED: "ALREADY_USED",
|
|
75
|
+
EXPIRED: "EXPIRED",
|
|
76
|
+
DATABASE_ERROR: "DATABASE_ERROR",
|
|
77
|
+
EXTRACTION_ERROR: "EXTRACTION_ERROR",
|
|
78
|
+
UNEXPECTED_ERROR: "UNEXPECTED_ERROR"
|
|
79
|
+
} as const;
|
|
80
|
+
|
|
81
|
+
// Record expiration time in milliseconds (30 minutes)
|
|
82
|
+
export const RECORD_TTL_MS = 30 * 60 * 1000;
|
|
83
|
+
|
|
84
|
+
// Record cleanup age in milliseconds (7 days)
|
|
85
|
+
export const RECORD_CLEANUP_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
86
|
+
|
|
87
|
+
// Content preview max length
|
|
88
|
+
export const CONTENT_PREVIEW_MAX_LENGTH = 500;
|
package/tools.ts
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom tools exposed to the LLM by the GitHub Compliance Plugin
|
|
3
|
+
*
|
|
4
|
+
* These tools allow the LLM to interact with the compliance system:
|
|
5
|
+
* - create_compliance_record: Create a record after user approval
|
|
6
|
+
* - get_compliance_status: Check if a valid record exists
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { tool } from "@opencode-ai/plugin";
|
|
10
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
11
|
+
import {
|
|
12
|
+
createComplianceRecord,
|
|
13
|
+
getComplianceStatus,
|
|
14
|
+
type CreateRecordInput
|
|
15
|
+
} from "./database";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a compliance record after user approval
|
|
19
|
+
*
|
|
20
|
+
* This tool MUST be called AFTER:
|
|
21
|
+
* 1. Reading CONTRIBUTING.md and other community files
|
|
22
|
+
* 2. Searching for duplicates
|
|
23
|
+
* 3. Drafting the issue/PR content
|
|
24
|
+
* 4. Presenting draft to user
|
|
25
|
+
* 5. Getting explicit user approval ("yes", "proceed", etc.)
|
|
26
|
+
*
|
|
27
|
+
* The tool records that human approval was obtained and creates
|
|
28
|
+
* a valid compliance record that will allow the GitHub write operation.
|
|
29
|
+
*/
|
|
30
|
+
export const createComplianceRecordTool = tool({
|
|
31
|
+
description: "Create a compliance record AFTER user approves the draft. MUST be called after explicit user approval. This records that human approval was obtained and allows the GitHub write operation to proceed.",
|
|
32
|
+
args: {
|
|
33
|
+
tool_name: tool.schema.string({ description: "GitHub tool name (e.g., issue_write, pull_request_write)" }),
|
|
34
|
+
owner: tool.schema.string({ description: "Repository owner (user or org)" }),
|
|
35
|
+
repo: tool.schema.string({ description: "Repository name" }),
|
|
36
|
+
title: tool.schema.string().optional({ description: "Issue/PR title" }),
|
|
37
|
+
body: tool.schema.string({ description: "Issue/PR body content" }),
|
|
38
|
+
github_username: tool.schema.string({ description: "GitHub username of human approver" }),
|
|
39
|
+
approved_at: tool.schema.string({ description: "ISO8601 timestamp when user approved" }),
|
|
40
|
+
checks: tool.schema.array(tool.schema.object({
|
|
41
|
+
check_type: tool.schema.string({ description: "Type of check performed" }),
|
|
42
|
+
passed: tool.schema.boolean({ description: "Whether check passed" }),
|
|
43
|
+
details: tool.schema.record(tool.schema.string(), tool.schema.any()).optional({ description: "Additional details" })
|
|
44
|
+
})).optional({ description: "List of compliance checks performed" })
|
|
45
|
+
},
|
|
46
|
+
async execute(args, _context) {
|
|
47
|
+
const input: CreateRecordInput = {
|
|
48
|
+
tool_name: args.tool_name,
|
|
49
|
+
owner: args.owner,
|
|
50
|
+
repo: args.repo,
|
|
51
|
+
title: args.title || null,
|
|
52
|
+
body: args.body,
|
|
53
|
+
github_username: args.github_username,
|
|
54
|
+
approved_at: args.approved_at,
|
|
55
|
+
checks: args.checks || []
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const recordId = await createComplianceRecord(input);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
record_id: recordId,
|
|
63
|
+
message: "Compliance record created. You may now execute the GitHub write operation.",
|
|
64
|
+
expires_at: new Date(Date.now() + 30 * 60 * 1000).toISOString()
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check compliance status for a planned operation
|
|
71
|
+
*
|
|
72
|
+
* Use this to verify if a valid compliance record already exists
|
|
73
|
+
* before attempting a GitHub write operation.
|
|
74
|
+
*/
|
|
75
|
+
export const getComplianceStatusTool = tool({
|
|
76
|
+
description: "Check if a valid compliance record exists for a planned GitHub write operation.",
|
|
77
|
+
args: {
|
|
78
|
+
tool_name: tool.schema.string({ description: "GitHub tool name" }),
|
|
79
|
+
owner: tool.schema.string({ description: "Repository owner" }),
|
|
80
|
+
repo: tool.schema.string({ description: "Repository name" }),
|
|
81
|
+
content_hash: tool.schema.string({ description: "SHA256 hash of the content (format: sha256:...)" })
|
|
82
|
+
},
|
|
83
|
+
async execute(args, _context) {
|
|
84
|
+
const status = await getComplianceStatus(args.tool_name, args.owner, args.repo, args.content_hash);
|
|
85
|
+
return status;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* All custom tools exported by this plugin
|
|
91
|
+
*/
|
|
92
|
+
export const tools = {
|
|
93
|
+
create_compliance_record: createComplianceRecordTool,
|
|
94
|
+
get_compliance_status: getComplianceStatusTool
|
|
95
|
+
};
|