nexus-agents 2.29.0 → 2.29.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/dist/adaptive-memory-5VP5WWTE.js +15 -0
- package/dist/chunk-5COIDGQJ.js +1585 -0
- package/dist/chunk-5COIDGQJ.js.map +1 -0
- package/dist/{chunk-HWDBNDUX.js → chunk-63AJLNKU.js} +2 -2
- package/dist/chunk-66NNHMVB.js +195 -0
- package/dist/chunk-66NNHMVB.js.map +1 -0
- package/dist/chunk-AP2FD37C.js +127 -0
- package/dist/chunk-AP2FD37C.js.map +1 -0
- package/dist/chunk-BC3M4VLP.js +359 -0
- package/dist/chunk-BC3M4VLP.js.map +1 -0
- package/dist/chunk-BQ4YXGGQ.js +127 -0
- package/dist/chunk-BQ4YXGGQ.js.map +1 -0
- package/dist/{chunk-ZBZJHXRT.js → chunk-CW2Z773T.js} +19 -347
- package/dist/chunk-CW2Z773T.js.map +1 -0
- package/dist/chunk-DDQGAVQA.js +944 -0
- package/dist/chunk-DDQGAVQA.js.map +1 -0
- package/dist/chunk-ED6VQWNG.js +63 -0
- package/dist/chunk-ED6VQWNG.js.map +1 -0
- package/dist/{chunk-7F6HYUIY.js → chunk-EPMBGZQX.js} +16 -97
- package/dist/chunk-EPMBGZQX.js.map +1 -0
- package/dist/chunk-GX436VRU.js +931 -0
- package/dist/chunk-GX436VRU.js.map +1 -0
- package/dist/{chunk-IMWYKX4H.js → chunk-HSOPD265.js} +444 -399
- package/dist/chunk-HSOPD265.js.map +1 -0
- package/dist/{chunk-S3BKWNST.js → chunk-J245RJGW.js} +680 -1436
- package/dist/chunk-J245RJGW.js.map +1 -0
- package/dist/{chunk-I6YDS23R.js → chunk-KQIDTE52.js} +2 -2
- package/dist/{chunk-POBO4G2P.js → chunk-LDIN2PLV.js} +250 -110
- package/dist/chunk-LDIN2PLV.js.map +1 -0
- package/dist/{chunk-KGDG6PWZ.js → chunk-LKDHAJJB.js} +2 -2
- package/dist/{chunk-T7PU3NPQ.js → chunk-NKGTEJYU.js} +7 -5
- package/dist/{chunk-T7PU3NPQ.js.map → chunk-NKGTEJYU.js.map} +1 -1
- package/dist/chunk-QGODFK36.js +122 -0
- package/dist/chunk-QGODFK36.js.map +1 -0
- package/dist/{chunk-DAMRMAM2.js → chunk-QSNAFOE6.js} +12369 -14499
- package/dist/chunk-QSNAFOE6.js.map +1 -0
- package/dist/chunk-TL2GJMJ5.js +700 -0
- package/dist/chunk-TL2GJMJ5.js.map +1 -0
- package/dist/{chunk-WSK4VSXP.js → chunk-V6MSPUQF.js} +2 -2
- package/dist/chunk-VZ2YOQWU.js +90 -0
- package/dist/chunk-VZ2YOQWU.js.map +1 -0
- package/dist/{chunk-5VZLXMO7.js → chunk-WSYJN7BI.js} +7 -6
- package/dist/chunk-WSYJN7BI.js.map +1 -0
- package/dist/chunk-Y477EGI4.js +356 -0
- package/dist/chunk-Y477EGI4.js.map +1 -0
- package/dist/{chunk-HH5LVGEE.js → chunk-Z4OZ25VS.js} +4 -4
- package/dist/cli-circuit-breaker-6EJO3PPU.js +13 -0
- package/dist/cli.js +123 -68
- package/dist/cli.js.map +1 -1
- package/dist/codebase-search-CZUA37RU.js +9 -0
- package/dist/{composite-router-YPRWVTRB.js → composite-router-JD7URTC2.js} +2 -2
- package/dist/{consensus-vote-DBE6RNZG.js → consensus-vote-COW34Q2Y.js} +7 -5
- package/dist/{dist-7PQR2BQB.js → dist-CV74KUT7.js} +1302 -805
- package/dist/dist-CV74KUT7.js.map +1 -0
- package/dist/{doctor-deep-AWE7SRU6.js → doctor-deep-4A4X5X6U.js} +3 -3
- package/dist/expert-bridge-J36C7VES.js +10 -0
- package/dist/{expert-config-FHNBQRX2.js → expert-config-MQ5OJE3U.js} +2 -2
- package/dist/{factory-O5C7ZBZO.js → factory-4Z4RSUYE.js} +5 -4
- package/dist/{factory-PCHGQ3ZG.js → factory-NHORX63J.js} +4 -3
- package/dist/index.d.ts +507 -42
- package/dist/index.js +331 -78
- package/dist/index.js.map +1 -1
- package/dist/issue-triage-TIG3RKXF.js +15 -0
- package/dist/{mcp-config-AUZQPUBY.js → mcp-config-ETY7GFGW.js} +3 -3
- package/dist/mobimem-5PAAMVFR.js +13 -0
- package/dist/mobimem-5PAAMVFR.js.map +1 -0
- package/dist/repo-analyze-HWMXSK5C.js +24 -0
- package/dist/repo-analyze-HWMXSK5C.js.map +1 -0
- package/dist/repo-security-plan-KQB3ZJTE.js +17 -0
- package/dist/repo-security-plan-KQB3ZJTE.js.map +1 -0
- package/dist/research-helpers-synthesize-ZMERZZ5B.js +10 -0
- package/dist/research-helpers-synthesize-ZMERZZ5B.js.map +1 -0
- package/dist/{routing-memory-QY3XMU2R.js → routing-memory-3ES3OHLM.js} +2 -2
- package/dist/routing-memory-3ES3OHLM.js.map +1 -0
- package/dist/{session-memory-3MBCE5KS.js → session-memory-E2OE2CYR.js} +3 -3
- package/dist/session-memory-E2OE2CYR.js.map +1 -0
- package/dist/{setup-command-IQ4MD3FT.js → setup-command-CMCQRBJF.js} +7 -6
- package/dist/setup-command-CMCQRBJF.js.map +1 -0
- package/dist/{setup-config-5YUPLDXF.js → setup-config-KITOPV7V.js} +3 -3
- package/dist/setup-config-KITOPV7V.js.map +1 -0
- package/dist/shared-memory-AEO2HJLC.js +8 -0
- package/dist/shared-memory-AEO2HJLC.js.map +1 -0
- package/dist/symbol-extractor-UEBANFSN.js +10 -0
- package/dist/symbol-extractor-UEBANFSN.js.map +1 -0
- package/dist/{weather-report-CC2C4KAX.js → weather-report-KUSVNXDZ.js} +2 -2
- package/dist/weather-report-KUSVNXDZ.js.map +1 -0
- package/package.json +14 -13
- package/dist/chunk-5VZLXMO7.js.map +0 -1
- package/dist/chunk-7F6HYUIY.js.map +0 -1
- package/dist/chunk-DAMRMAM2.js.map +0 -1
- package/dist/chunk-IMWYKX4H.js.map +0 -1
- package/dist/chunk-POBO4G2P.js.map +0 -1
- package/dist/chunk-S3BKWNST.js.map +0 -1
- package/dist/chunk-ZBZJHXRT.js.map +0 -1
- package/dist/dist-7PQR2BQB.js.map +0 -1
- /package/dist/{composite-router-YPRWVTRB.js.map → adaptive-memory-5VP5WWTE.js.map} +0 -0
- /package/dist/{chunk-HWDBNDUX.js.map → chunk-63AJLNKU.js.map} +0 -0
- /package/dist/{chunk-I6YDS23R.js.map → chunk-KQIDTE52.js.map} +0 -0
- /package/dist/{chunk-KGDG6PWZ.js.map → chunk-LKDHAJJB.js.map} +0 -0
- /package/dist/{chunk-WSK4VSXP.js.map → chunk-V6MSPUQF.js.map} +0 -0
- /package/dist/{chunk-HH5LVGEE.js.map → chunk-Z4OZ25VS.js.map} +0 -0
- /package/dist/{consensus-vote-DBE6RNZG.js.map → cli-circuit-breaker-6EJO3PPU.js.map} +0 -0
- /package/dist/{doctor-deep-AWE7SRU6.js.map → codebase-search-CZUA37RU.js.map} +0 -0
- /package/dist/{expert-config-FHNBQRX2.js.map → composite-router-JD7URTC2.js.map} +0 -0
- /package/dist/{factory-O5C7ZBZO.js.map → consensus-vote-COW34Q2Y.js.map} +0 -0
- /package/dist/{factory-PCHGQ3ZG.js.map → doctor-deep-4A4X5X6U.js.map} +0 -0
- /package/dist/{mcp-config-AUZQPUBY.js.map → expert-bridge-J36C7VES.js.map} +0 -0
- /package/dist/{routing-memory-QY3XMU2R.js.map → expert-config-MQ5OJE3U.js.map} +0 -0
- /package/dist/{session-memory-3MBCE5KS.js.map → factory-4Z4RSUYE.js.map} +0 -0
- /package/dist/{setup-command-IQ4MD3FT.js.map → factory-NHORX63J.js.map} +0 -0
- /package/dist/{setup-config-5YUPLDXF.js.map → issue-triage-TIG3RKXF.js.map} +0 -0
- /package/dist/{weather-report-CC2C4KAX.js.map → mcp-config-ETY7GFGW.js.map} +0 -0
|
@@ -0,0 +1,1585 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GitHubProvider,
|
|
3
|
+
ScmError
|
|
4
|
+
} from "./chunk-EPMBGZQX.js";
|
|
5
|
+
import {
|
|
6
|
+
CACHE_TIMEOUTS,
|
|
7
|
+
createLogger,
|
|
8
|
+
err,
|
|
9
|
+
getTimeProvider,
|
|
10
|
+
ok
|
|
11
|
+
} from "./chunk-HSOPD265.js";
|
|
12
|
+
|
|
13
|
+
// src/security/trust-types.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var TrustTierSchema = z.enum(["1", "2", "3", "4"]);
|
|
16
|
+
var TRUST_TIER_NUMERIC = {
|
|
17
|
+
"1": 1,
|
|
18
|
+
"2": 2,
|
|
19
|
+
"3": 3,
|
|
20
|
+
"4": 4
|
|
21
|
+
};
|
|
22
|
+
var GitHubUserRoleSchema = z.enum([
|
|
23
|
+
"owner",
|
|
24
|
+
"maintainer",
|
|
25
|
+
"collaborator",
|
|
26
|
+
"contributor",
|
|
27
|
+
"member",
|
|
28
|
+
"unknown"
|
|
29
|
+
]);
|
|
30
|
+
var ROLE_DEFAULT_TRUST = {
|
|
31
|
+
owner: "1",
|
|
32
|
+
maintainer: "1",
|
|
33
|
+
collaborator: "2",
|
|
34
|
+
contributor: "2",
|
|
35
|
+
member: "3",
|
|
36
|
+
unknown: "3"
|
|
37
|
+
};
|
|
38
|
+
var InjectionFlagSchema = z.enum([
|
|
39
|
+
"authority_claim",
|
|
40
|
+
"instruction_pattern",
|
|
41
|
+
"system_prompt_manipulation",
|
|
42
|
+
"hidden_content",
|
|
43
|
+
"urgency_manipulation",
|
|
44
|
+
"fake_conversation",
|
|
45
|
+
"base64_encoded",
|
|
46
|
+
"external_link_instruction"
|
|
47
|
+
]);
|
|
48
|
+
var StrippedElementSchema = z.object({
|
|
49
|
+
/** Type of element stripped. */
|
|
50
|
+
tag: z.string().min(1),
|
|
51
|
+
/** Reason for stripping. */
|
|
52
|
+
reason: z.string().min(1),
|
|
53
|
+
/** Start index in original content. */
|
|
54
|
+
startIndex: z.number().int().nonnegative(),
|
|
55
|
+
/** Length of stripped content. */
|
|
56
|
+
length: z.number().int().positive()
|
|
57
|
+
});
|
|
58
|
+
var SanitizedInputSchema = z.object({
|
|
59
|
+
/** Sanitized content with dangerous elements removed. */
|
|
60
|
+
content: z.string(),
|
|
61
|
+
/** Original content before sanitization (for audit). */
|
|
62
|
+
originalLength: z.number().int().nonnegative(),
|
|
63
|
+
/** Assigned trust tier based on user role and content analysis. */
|
|
64
|
+
trustTier: TrustTierSchema,
|
|
65
|
+
/** GitHub user role of the input source. */
|
|
66
|
+
userRole: GitHubUserRoleSchema,
|
|
67
|
+
/** Injection patterns detected in content. */
|
|
68
|
+
injectionFlags: z.array(InjectionFlagSchema),
|
|
69
|
+
/** Elements stripped during sanitization (audit trail). */
|
|
70
|
+
strippedElements: z.array(StrippedElementSchema),
|
|
71
|
+
/** Whether any dangerous content was detected and stripped. */
|
|
72
|
+
wasModified: z.boolean(),
|
|
73
|
+
/** Timestamp of sanitization (ISO 8601). */
|
|
74
|
+
sanitizedAt: z.iso.datetime()
|
|
75
|
+
});
|
|
76
|
+
var SanitizerConfigSchema = z.object({
|
|
77
|
+
/** GitHub usernames that are always Tier 1 (allowlisted maintainers). */
|
|
78
|
+
allowlistedMaintainers: z.array(z.string().min(1)).default([]),
|
|
79
|
+
/** Whether to fail open (log only) or fail closed (block). Phase 1 = open. */
|
|
80
|
+
failOpen: z.boolean().default(true),
|
|
81
|
+
/** Maximum input length before truncation. */
|
|
82
|
+
maxInputLength: z.number().int().positive().default(5e4)
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// src/security/input-sanitizer.ts
|
|
86
|
+
var DANGEROUS_HTML_PATTERN = /<(picture|source|img)\b[^>]*>[\s\S]*?<\/\1>|<(picture|source|img)\b[^>]*\/?>/gi;
|
|
87
|
+
var XML_INJECTION_PATTERN = /<\/?(system|human|assistant|instructions|user|prompt|context|tool_use|tool_result)\b[^>]*>/gi;
|
|
88
|
+
var HTML_COMMENT_PATTERN = /<!--[\s\S]*?-->/g;
|
|
89
|
+
var INJECTION_PATTERNS = [
|
|
90
|
+
{
|
|
91
|
+
flag: "authority_claim",
|
|
92
|
+
pattern: /\b(as (?:a|the) (?:maintainer|admin|owner|security lead|repo owner|developer))\b/i
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
flag: "authority_claim",
|
|
96
|
+
pattern: /\b(i(?:'m| am) the (?:repo |project )?(?:owner|maintainer|admin))\b/i
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
flag: "instruction_pattern",
|
|
100
|
+
pattern: /\b(please (?:close|merge|label|mark|apply|delete|remove|approve|reject))\b/i
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
flag: "instruction_pattern",
|
|
104
|
+
pattern: /\b(you (?:should|must|need to) (?:close|merge|label|apply|delete))\b/i
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
flag: "system_prompt_manipulation",
|
|
108
|
+
pattern: /\b(ignore (?:all )?previous (?:instructions|rules|prompts))\b/i
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
flag: "system_prompt_manipulation",
|
|
112
|
+
pattern: /\b(forget (?:your |all )?(?:instructions|rules|safety))\b/i
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
flag: "system_prompt_manipulation",
|
|
116
|
+
pattern: /\b(new (?:instructions|rules|system prompt|directives))\b/i
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
flag: "urgency_manipulation",
|
|
120
|
+
pattern: /\b(critical|emergency|urgent|must act now|immediately|time[- ]?sensitive)\b/i
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
flag: "fake_conversation",
|
|
124
|
+
pattern: /<(?:assistant|human|user|system)>/i
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
flag: "base64_encoded",
|
|
128
|
+
// Requires at least one base64-discriminating char (g-z/G-Z or +, /, =)
|
|
129
|
+
// to avoid false positives on SHA-1 / SHA-256 hex hashes (#1811).
|
|
130
|
+
pattern: /(?=[A-Za-z0-9+/]*[g-zG-Z+/=])[A-Za-z0-9+/]{40,}={0,2}/
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
flag: "external_link_instruction",
|
|
134
|
+
pattern: /(?:apply|run|execute|install)\s+(?:this\s+)?(?:from\s+)?https?:\/\//i
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
function stripDangerousHtml(content) {
|
|
138
|
+
const stripped = [];
|
|
139
|
+
let cleaned = content;
|
|
140
|
+
const MAX_PASSES = 5;
|
|
141
|
+
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
142
|
+
DANGEROUS_HTML_PATTERN.lastIndex = 0;
|
|
143
|
+
if (!DANGEROUS_HTML_PATTERN.test(cleaned)) break;
|
|
144
|
+
cleaned = cleaned.replace(DANGEROUS_HTML_PATTERN, (match, _g1, _g2, offset) => {
|
|
145
|
+
stripped.push({
|
|
146
|
+
tag: match.slice(0, 30) + (match.length > 30 ? "..." : ""),
|
|
147
|
+
reason: "Dangerous HTML tag (Trail of Bits injection vector)",
|
|
148
|
+
startIndex: offset,
|
|
149
|
+
length: match.length
|
|
150
|
+
});
|
|
151
|
+
return "";
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return { cleaned, stripped };
|
|
155
|
+
}
|
|
156
|
+
function stripXmlTags(content) {
|
|
157
|
+
const stripped = [];
|
|
158
|
+
let cleaned = content;
|
|
159
|
+
const MAX_PASSES = 5;
|
|
160
|
+
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
161
|
+
XML_INJECTION_PATTERN.lastIndex = 0;
|
|
162
|
+
if (!XML_INJECTION_PATTERN.test(cleaned)) break;
|
|
163
|
+
cleaned = cleaned.replace(XML_INJECTION_PATTERN, (match, _g1, offset) => {
|
|
164
|
+
stripped.push({
|
|
165
|
+
tag: match,
|
|
166
|
+
reason: "XML-like conversation injection tag",
|
|
167
|
+
startIndex: offset,
|
|
168
|
+
length: match.length
|
|
169
|
+
});
|
|
170
|
+
return "";
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return { cleaned, stripped };
|
|
174
|
+
}
|
|
175
|
+
function stripHtmlComments(content) {
|
|
176
|
+
const stripped = [];
|
|
177
|
+
let cleaned = content;
|
|
178
|
+
const MAX_PASSES = 5;
|
|
179
|
+
for (let pass = 0; pass < MAX_PASSES; pass++) {
|
|
180
|
+
HTML_COMMENT_PATTERN.lastIndex = 0;
|
|
181
|
+
const prevLength = cleaned.length;
|
|
182
|
+
cleaned = cleaned.replace(HTML_COMMENT_PATTERN, (match, offset) => {
|
|
183
|
+
const hasInstruction = /\b(ignore|execute|close|merge|delete|apply)\b/i.test(match);
|
|
184
|
+
if (!hasInstruction) return match;
|
|
185
|
+
stripped.push({
|
|
186
|
+
tag: "<!-- ... -->",
|
|
187
|
+
reason: "HTML comment with instruction-like content",
|
|
188
|
+
startIndex: offset,
|
|
189
|
+
length: match.length
|
|
190
|
+
});
|
|
191
|
+
return "";
|
|
192
|
+
});
|
|
193
|
+
if (cleaned.length === prevLength) break;
|
|
194
|
+
}
|
|
195
|
+
let searchFrom = 0;
|
|
196
|
+
while (searchFrom < cleaned.length) {
|
|
197
|
+
const openIdx = cleaned.indexOf("<!--", searchFrom);
|
|
198
|
+
if (openIdx === -1) break;
|
|
199
|
+
const closeIdx = cleaned.indexOf("-->", openIdx + 4);
|
|
200
|
+
if (closeIdx === -1) {
|
|
201
|
+
stripped.push({
|
|
202
|
+
tag: "<!--",
|
|
203
|
+
reason: "Unclosed HTML comment (potential injection vector)",
|
|
204
|
+
startIndex: openIdx,
|
|
205
|
+
length: cleaned.length - openIdx
|
|
206
|
+
});
|
|
207
|
+
cleaned = cleaned.slice(0, openIdx);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
searchFrom = closeIdx + 3;
|
|
211
|
+
}
|
|
212
|
+
return { cleaned, stripped };
|
|
213
|
+
}
|
|
214
|
+
function detectInjectionPatterns(content) {
|
|
215
|
+
const flags = /* @__PURE__ */ new Set();
|
|
216
|
+
for (const { flag, pattern } of INJECTION_PATTERNS) {
|
|
217
|
+
pattern.lastIndex = 0;
|
|
218
|
+
if (pattern.test(content)) {
|
|
219
|
+
flags.add(flag);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return Array.from(flags);
|
|
223
|
+
}
|
|
224
|
+
function assignTrustTier(userRole, injectionFlags, allowlisted) {
|
|
225
|
+
if (allowlisted) return "1";
|
|
226
|
+
const baseTier = ROLE_DEFAULT_TRUST[userRole];
|
|
227
|
+
const hostileFlags = ["system_prompt_manipulation", "fake_conversation"];
|
|
228
|
+
if (injectionFlags.some((f) => hostileFlags.includes(f))) return "4";
|
|
229
|
+
if (injectionFlags.includes("authority_claim") && userRole !== "owner" && userRole !== "maintainer") {
|
|
230
|
+
return "4";
|
|
231
|
+
}
|
|
232
|
+
return baseTier;
|
|
233
|
+
}
|
|
234
|
+
function sanitizeInput(content, userRole, username, config) {
|
|
235
|
+
const cfg = SanitizerConfigSchema.parse(config ?? {});
|
|
236
|
+
const truncated = content.slice(0, cfg.maxInputLength);
|
|
237
|
+
const allowlisted = cfg.allowlistedMaintainers.includes(username);
|
|
238
|
+
const html = stripDangerousHtml(truncated);
|
|
239
|
+
const xml = stripXmlTags(html.cleaned);
|
|
240
|
+
const comments = stripHtmlComments(xml.cleaned);
|
|
241
|
+
const allStripped = [...html.stripped, ...xml.stripped, ...comments.stripped];
|
|
242
|
+
const injectionFlags = detectInjectionPatterns(truncated);
|
|
243
|
+
const trustTier = assignTrustTier(userRole, injectionFlags, allowlisted);
|
|
244
|
+
return {
|
|
245
|
+
content: comments.cleaned,
|
|
246
|
+
originalLength: content.length,
|
|
247
|
+
trustTier,
|
|
248
|
+
userRole,
|
|
249
|
+
injectionFlags,
|
|
250
|
+
strippedElements: allStripped,
|
|
251
|
+
wasModified: allStripped.length > 0,
|
|
252
|
+
sanitizedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/security/trust-classifier.ts
|
|
257
|
+
function mapAuthorAssociation(association) {
|
|
258
|
+
switch (association.toUpperCase()) {
|
|
259
|
+
case "OWNER":
|
|
260
|
+
return "owner";
|
|
261
|
+
case "MEMBER":
|
|
262
|
+
return "member";
|
|
263
|
+
case "COLLABORATOR":
|
|
264
|
+
return "collaborator";
|
|
265
|
+
case "CONTRIBUTOR":
|
|
266
|
+
return "contributor";
|
|
267
|
+
case "FIRST_TIMER":
|
|
268
|
+
case "FIRST_TIME_CONTRIBUTOR":
|
|
269
|
+
case "NONE":
|
|
270
|
+
return "unknown";
|
|
271
|
+
case "MANNEQUIN":
|
|
272
|
+
return "unknown";
|
|
273
|
+
default:
|
|
274
|
+
return "unknown";
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function classifyTrust(input) {
|
|
278
|
+
const allowlistedMaintainers = input.config?.allowlistedMaintainers ?? [];
|
|
279
|
+
const isAllowlisted = allowlistedMaintainers.includes(input.username);
|
|
280
|
+
const userRole = mapAuthorAssociation(input.authorAssociation);
|
|
281
|
+
if (isAllowlisted) {
|
|
282
|
+
return {
|
|
283
|
+
trustTier: "1",
|
|
284
|
+
userRole,
|
|
285
|
+
isAllowlisted: true,
|
|
286
|
+
wasDowngraded: false,
|
|
287
|
+
reason: `User ${input.username} is on the maintainer allowlist`
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const baseTier = ROLE_DEFAULT_TRUST[userRole];
|
|
291
|
+
if (input.sanitizedInput !== void 0) {
|
|
292
|
+
const contentTier = input.sanitizedInput.trustTier;
|
|
293
|
+
const downgraded = TRUST_TIER_NUMERIC[contentTier] > TRUST_TIER_NUMERIC[baseTier];
|
|
294
|
+
return {
|
|
295
|
+
trustTier: downgraded ? contentTier : baseTier,
|
|
296
|
+
userRole,
|
|
297
|
+
isAllowlisted: false,
|
|
298
|
+
wasDowngraded: downgraded,
|
|
299
|
+
reason: downgraded ? `Downgraded from Tier ${baseTier} to ${contentTier}: injection patterns detected` : `Role ${userRole} \u2192 Tier ${baseTier}`
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
trustTier: baseTier,
|
|
304
|
+
userRole,
|
|
305
|
+
isAllowlisted: false,
|
|
306
|
+
wasDowngraded: false,
|
|
307
|
+
reason: `Role ${userRole} \u2192 Tier ${baseTier}`
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function canInfluenceDecisions(tier) {
|
|
311
|
+
return TRUST_TIER_NUMERIC[tier] <= 2;
|
|
312
|
+
}
|
|
313
|
+
function requiresCorroboration(tier) {
|
|
314
|
+
return tier === "2";
|
|
315
|
+
}
|
|
316
|
+
function getRequiredTrustTier(actionType) {
|
|
317
|
+
switch (actionType) {
|
|
318
|
+
case "GeneratePatchPlan":
|
|
319
|
+
return "1";
|
|
320
|
+
// Requires maintainer-level trust
|
|
321
|
+
case "DraftReply":
|
|
322
|
+
case "ProposeLabels":
|
|
323
|
+
return "2";
|
|
324
|
+
// Requires at least collaborator-level trust
|
|
325
|
+
case "SummarizeIssue":
|
|
326
|
+
case "ClassifyIssue":
|
|
327
|
+
case "IdentifyDuplicates":
|
|
328
|
+
return "3";
|
|
329
|
+
// Read-only, can use any input
|
|
330
|
+
case "RequestHumanApproval":
|
|
331
|
+
case "RefuseAction":
|
|
332
|
+
return "4";
|
|
333
|
+
// Always allowed (safety actions)
|
|
334
|
+
default:
|
|
335
|
+
return "1";
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// src/security/policy-gate.ts
|
|
340
|
+
import { z as z3 } from "zod";
|
|
341
|
+
|
|
342
|
+
// src/security/action-schema.ts
|
|
343
|
+
import { z as z2 } from "zod";
|
|
344
|
+
var RepoFileSource = z2.object({
|
|
345
|
+
type: z2.literal("repoFile"),
|
|
346
|
+
path: z2.string().min(1),
|
|
347
|
+
line: z2.number().int().positive().optional(),
|
|
348
|
+
commit: z2.string().regex(/^[a-f0-9]{7,40}$/).optional()
|
|
349
|
+
});
|
|
350
|
+
var IssueCommentSource = z2.object({
|
|
351
|
+
type: z2.literal("issueComment"),
|
|
352
|
+
issueNumber: z2.number().int().positive(),
|
|
353
|
+
commentId: z2.number().int().positive(),
|
|
354
|
+
author: z2.string().min(1),
|
|
355
|
+
authorTrustTier: TrustTierSchema
|
|
356
|
+
});
|
|
357
|
+
var CIResultSource = z2.object({
|
|
358
|
+
type: z2.literal("ciResult"),
|
|
359
|
+
runId: z2.number().int().positive(),
|
|
360
|
+
status: z2.enum(["pass", "fail"]),
|
|
361
|
+
job: z2.string().min(1)
|
|
362
|
+
});
|
|
363
|
+
var PolicyDocSource = z2.object({
|
|
364
|
+
type: z2.literal("policyDoc"),
|
|
365
|
+
path: z2.string().min(1),
|
|
366
|
+
section: z2.string().min(1)
|
|
367
|
+
});
|
|
368
|
+
var MaintainerCommandSource = z2.object({
|
|
369
|
+
type: z2.literal("maintainerCommand"),
|
|
370
|
+
username: z2.string().min(1),
|
|
371
|
+
commentId: z2.number().int().positive()
|
|
372
|
+
});
|
|
373
|
+
var SourceCitationSchema = z2.discriminatedUnion("type", [
|
|
374
|
+
RepoFileSource,
|
|
375
|
+
IssueCommentSource,
|
|
376
|
+
CIResultSource,
|
|
377
|
+
PolicyDocSource,
|
|
378
|
+
MaintainerCommandSource
|
|
379
|
+
]);
|
|
380
|
+
var SummarizeIssueAction = z2.object({
|
|
381
|
+
type: z2.literal("SummarizeIssue"),
|
|
382
|
+
summary: z2.string().min(10).max(2e3),
|
|
383
|
+
sources: z2.array(SourceCitationSchema).min(1).max(20)
|
|
384
|
+
});
|
|
385
|
+
var ProposeLabelsAction = z2.object({
|
|
386
|
+
type: z2.literal("ProposeLabels"),
|
|
387
|
+
labels: z2.array(z2.string()).min(1).max(5),
|
|
388
|
+
reason: z2.string().min(10).max(500),
|
|
389
|
+
sources: z2.array(SourceCitationSchema).min(1).max(20)
|
|
390
|
+
});
|
|
391
|
+
var DraftReplyAction = z2.object({
|
|
392
|
+
type: z2.literal("DraftReply"),
|
|
393
|
+
body: z2.string().min(10).max(2e3),
|
|
394
|
+
requiresApproval: z2.literal(true),
|
|
395
|
+
sources: z2.array(SourceCitationSchema).min(1).max(20)
|
|
396
|
+
});
|
|
397
|
+
var RequestHumanApprovalAction = z2.object({
|
|
398
|
+
type: z2.literal("RequestHumanApproval"),
|
|
399
|
+
reason: z2.string().min(10).max(500),
|
|
400
|
+
context: z2.string().min(10).max(2e3)
|
|
401
|
+
});
|
|
402
|
+
var GeneratePatchPlanAction = z2.object({
|
|
403
|
+
type: z2.literal("GeneratePatchPlan"),
|
|
404
|
+
files: z2.array(
|
|
405
|
+
z2.object({
|
|
406
|
+
path: z2.string().min(1),
|
|
407
|
+
operation: z2.enum(["modify", "create", "delete"]),
|
|
408
|
+
description: z2.string().min(10).max(500)
|
|
409
|
+
})
|
|
410
|
+
).min(1).max(10),
|
|
411
|
+
rationale: z2.string().min(10).max(1e3),
|
|
412
|
+
requiresApproval: z2.literal(true),
|
|
413
|
+
sources: z2.array(SourceCitationSchema).min(2).max(20)
|
|
414
|
+
});
|
|
415
|
+
var ClassifyIssueAction = z2.object({
|
|
416
|
+
type: z2.literal("ClassifyIssue"),
|
|
417
|
+
category: z2.enum(["bug", "feature", "question", "documentation", "security", "performance"]),
|
|
418
|
+
confidence: z2.number().min(0).max(1),
|
|
419
|
+
sources: z2.array(SourceCitationSchema).min(1).max(20)
|
|
420
|
+
});
|
|
421
|
+
var IdentifyDuplicatesAction = z2.object({
|
|
422
|
+
type: z2.literal("IdentifyDuplicates"),
|
|
423
|
+
candidates: z2.array(z2.number().int().positive()).min(1).max(10),
|
|
424
|
+
similarity: z2.array(z2.number().min(0).max(1)),
|
|
425
|
+
sources: z2.array(SourceCitationSchema).min(1).max(20)
|
|
426
|
+
});
|
|
427
|
+
var RefuseActionAction = z2.object({
|
|
428
|
+
type: z2.literal("RefuseAction"),
|
|
429
|
+
reason: z2.string().min(10).max(500),
|
|
430
|
+
escalateTo: z2.enum(["maintainer", "security"])
|
|
431
|
+
});
|
|
432
|
+
var HandoffMessageAction = z2.object({
|
|
433
|
+
type: z2.literal("HandoffMessage"),
|
|
434
|
+
targetCapability: z2.string().min(1).max(100),
|
|
435
|
+
reason: z2.string().min(5).max(500),
|
|
436
|
+
inputTrustTier: z2.enum(["1", "2", "3", "4"]),
|
|
437
|
+
sources: z2.array(SourceCitationSchema).min(1).max(20)
|
|
438
|
+
});
|
|
439
|
+
var AgentActionSchema = z2.discriminatedUnion("type", [
|
|
440
|
+
SummarizeIssueAction,
|
|
441
|
+
ProposeLabelsAction,
|
|
442
|
+
DraftReplyAction,
|
|
443
|
+
RequestHumanApprovalAction,
|
|
444
|
+
GeneratePatchPlanAction,
|
|
445
|
+
ClassifyIssueAction,
|
|
446
|
+
IdentifyDuplicatesAction,
|
|
447
|
+
RefuseActionAction,
|
|
448
|
+
HandoffMessageAction
|
|
449
|
+
]);
|
|
450
|
+
var READ_ONLY_ACTIONS = /* @__PURE__ */ new Set([
|
|
451
|
+
"SummarizeIssue",
|
|
452
|
+
"ClassifyIssue",
|
|
453
|
+
"IdentifyDuplicates",
|
|
454
|
+
"RefuseAction",
|
|
455
|
+
"RequestHumanApproval",
|
|
456
|
+
"HandoffMessage"
|
|
457
|
+
]);
|
|
458
|
+
var MUTATING_ACTIONS = /* @__PURE__ */ new Set([
|
|
459
|
+
"ProposeLabels",
|
|
460
|
+
"DraftReply",
|
|
461
|
+
"GeneratePatchPlan"
|
|
462
|
+
]);
|
|
463
|
+
var CITATION_REQUIRED_ACTIONS = /* @__PURE__ */ new Set([
|
|
464
|
+
"SummarizeIssue",
|
|
465
|
+
"ProposeLabels",
|
|
466
|
+
"DraftReply",
|
|
467
|
+
"GeneratePatchPlan",
|
|
468
|
+
"ClassifyIssue",
|
|
469
|
+
"IdentifyDuplicates",
|
|
470
|
+
"HandoffMessage"
|
|
471
|
+
]);
|
|
472
|
+
function validateAgentAction(input) {
|
|
473
|
+
const result = AgentActionSchema.safeParse(input);
|
|
474
|
+
if (result.success) {
|
|
475
|
+
return { ok: true, value: result.data };
|
|
476
|
+
}
|
|
477
|
+
const messages = result.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
|
|
478
|
+
return { ok: false, error: `Action validation failed: ${messages.join("; ")}` };
|
|
479
|
+
}
|
|
480
|
+
function isReadOnlyAction(actionType) {
|
|
481
|
+
return READ_ONLY_ACTIONS.has(actionType);
|
|
482
|
+
}
|
|
483
|
+
function isMutatingAction(actionType) {
|
|
484
|
+
return MUTATING_ACTIONS.has(actionType);
|
|
485
|
+
}
|
|
486
|
+
function requiresCitation(actionType) {
|
|
487
|
+
return CITATION_REQUIRED_ACTIONS.has(actionType);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// src/security/policy-gate.ts
|
|
491
|
+
var ViolationSchema = z3.object({
|
|
492
|
+
/** Machine-readable rule identifier. */
|
|
493
|
+
rule: z3.string().min(1),
|
|
494
|
+
/** Human-readable description of the violation. */
|
|
495
|
+
message: z3.string().min(1),
|
|
496
|
+
/** Severity: 'block' prevents execution, 'warn' logs only. */
|
|
497
|
+
severity: z3.enum(["block", "warn"])
|
|
498
|
+
});
|
|
499
|
+
function getActionSources(action) {
|
|
500
|
+
if ("sources" in action) {
|
|
501
|
+
return action.sources;
|
|
502
|
+
}
|
|
503
|
+
return [];
|
|
504
|
+
}
|
|
505
|
+
function checkCitationRequirement(action) {
|
|
506
|
+
if (!requiresCitation(action.type)) return void 0;
|
|
507
|
+
const sources = getActionSources(action);
|
|
508
|
+
if (sources.length === 0) {
|
|
509
|
+
return {
|
|
510
|
+
rule: "REQUIRE_CITATION",
|
|
511
|
+
message: `Action '${action.type}' requires at least one source citation`,
|
|
512
|
+
severity: "block"
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return void 0;
|
|
516
|
+
}
|
|
517
|
+
function checkTrustRequirement(action, context) {
|
|
518
|
+
const requiredTier = getRequiredTrustTier(action.type);
|
|
519
|
+
const requiredNumeric = TRUST_TIER_NUMERIC[requiredTier];
|
|
520
|
+
const inputNumeric = TRUST_TIER_NUMERIC[context.inputTrustTier];
|
|
521
|
+
if (inputNumeric > requiredNumeric) {
|
|
522
|
+
return {
|
|
523
|
+
rule: "INSUFFICIENT_TRUST",
|
|
524
|
+
message: `Action '${action.type}' requires Tier ${requiredTier} but input is Tier ${context.inputTrustTier}`,
|
|
525
|
+
severity: "block"
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
return void 0;
|
|
529
|
+
}
|
|
530
|
+
function checkInfluenceBlock(action, context) {
|
|
531
|
+
if (!canInfluenceDecisions(context.inputTrustTier) && isMutatingAction(action.type)) {
|
|
532
|
+
return {
|
|
533
|
+
rule: "UNTRUSTED_INFLUENCE",
|
|
534
|
+
message: `Tier ${context.inputTrustTier} input cannot drive mutating action '${action.type}'`,
|
|
535
|
+
severity: "block"
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
return void 0;
|
|
539
|
+
}
|
|
540
|
+
function checkRuleOfTwo(context) {
|
|
541
|
+
const isUntrusted = TRUST_TIER_NUMERIC[context.inputTrustTier] >= 3;
|
|
542
|
+
if (isUntrusted && context.hasWriteAccess && context.hasSecretAccess) {
|
|
543
|
+
return {
|
|
544
|
+
rule: "RULE_OF_TWO",
|
|
545
|
+
message: "Rule of Two violation: agent simultaneously processes untrusted input, has write access, and accesses secrets",
|
|
546
|
+
severity: "block"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return void 0;
|
|
550
|
+
}
|
|
551
|
+
function checkLabelValidity(action, context) {
|
|
552
|
+
if (action.type !== "ProposeLabels") return void 0;
|
|
553
|
+
const labels = context.existingLabels;
|
|
554
|
+
if (labels === void 0) return void 0;
|
|
555
|
+
const invalid = action.labels.filter((l) => !labels.has(l));
|
|
556
|
+
if (invalid.length > 0) {
|
|
557
|
+
return {
|
|
558
|
+
rule: "INVALID_LABELS",
|
|
559
|
+
message: `Proposed labels not in repository: ${invalid.join(", ")}`,
|
|
560
|
+
severity: "block"
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
return void 0;
|
|
564
|
+
}
|
|
565
|
+
function checkSourceTrustTiers(action) {
|
|
566
|
+
const sources = getActionSources(action);
|
|
567
|
+
if (sources.length === 0) return void 0;
|
|
568
|
+
const requiredTier = getRequiredTrustTier(action.type);
|
|
569
|
+
const requiredNumeric = TRUST_TIER_NUMERIC[requiredTier];
|
|
570
|
+
for (const source of sources) {
|
|
571
|
+
if (source.type === "issueComment") {
|
|
572
|
+
const sourceTierNumeric = TRUST_TIER_NUMERIC[source.authorTrustTier];
|
|
573
|
+
if (sourceTierNumeric > requiredNumeric) {
|
|
574
|
+
return {
|
|
575
|
+
rule: "SOURCE_TRUST_MISMATCH",
|
|
576
|
+
message: `Source from '${source.author}' (Tier ${source.authorTrustTier}) insufficient for action requiring Tier ${requiredTier}`,
|
|
577
|
+
severity: "warn"
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return void 0;
|
|
583
|
+
}
|
|
584
|
+
function evaluatePolicy(action, context) {
|
|
585
|
+
const violations = [];
|
|
586
|
+
const checks = [
|
|
587
|
+
checkCitationRequirement(action),
|
|
588
|
+
checkTrustRequirement(action, context),
|
|
589
|
+
checkInfluenceBlock(action, context),
|
|
590
|
+
checkRuleOfTwo(context),
|
|
591
|
+
checkLabelValidity(action, context),
|
|
592
|
+
checkSourceTrustTiers(action)
|
|
593
|
+
];
|
|
594
|
+
for (const violation of checks) {
|
|
595
|
+
if (violation !== void 0) {
|
|
596
|
+
violations.push(violation);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const hasBlockingViolation = violations.some((v) => v.severity === "block");
|
|
600
|
+
const needsApproval = !hasBlockingViolation && isMutatingAction(action.type);
|
|
601
|
+
return {
|
|
602
|
+
allowed: !hasBlockingViolation,
|
|
603
|
+
requiresApproval: needsApproval,
|
|
604
|
+
violations,
|
|
605
|
+
evaluatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function canProceed(actionType, inputTrustTier) {
|
|
609
|
+
if (isReadOnlyAction(actionType)) {
|
|
610
|
+
const requiredTier = getRequiredTrustTier(actionType);
|
|
611
|
+
return TRUST_TIER_NUMERIC[inputTrustTier] <= TRUST_TIER_NUMERIC[requiredTier];
|
|
612
|
+
}
|
|
613
|
+
return canInfluenceDecisions(inputTrustTier);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/security/corroboration-validator.ts
|
|
617
|
+
function hasCIPass(sources) {
|
|
618
|
+
return sources.some((s) => s.type === "ciResult" && s.status === "pass");
|
|
619
|
+
}
|
|
620
|
+
function hasMaintainerCommand(sources) {
|
|
621
|
+
return sources.some((s) => s.type === "maintainerCommand");
|
|
622
|
+
}
|
|
623
|
+
function hasTier1Comment(sources) {
|
|
624
|
+
return sources.some((s) => s.type === "issueComment" && s.authorTrustTier === "1");
|
|
625
|
+
}
|
|
626
|
+
function hasRepoFileRef(sources) {
|
|
627
|
+
return sources.some((s) => s.type === "repoFile");
|
|
628
|
+
}
|
|
629
|
+
function hasCodeLevelEvidence(sources) {
|
|
630
|
+
return sources.some((s) => s.type === "repoFile" && s.line !== void 0);
|
|
631
|
+
}
|
|
632
|
+
function hasPolicyDocRef(sources) {
|
|
633
|
+
return sources.some((s) => s.type === "policyDoc");
|
|
634
|
+
}
|
|
635
|
+
function hasSourceAtTier(sources, maxTier) {
|
|
636
|
+
const maxNumeric = TRUST_TIER_NUMERIC[maxTier];
|
|
637
|
+
return sources.some((s) => {
|
|
638
|
+
if (s.type === "issueComment") {
|
|
639
|
+
return TRUST_TIER_NUMERIC[s.authorTrustTier] <= maxNumeric;
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
var ACTION_CORROBORATION_RULES = {
|
|
645
|
+
SummarizeIssue: [
|
|
646
|
+
{
|
|
647
|
+
description: "At least one Tier 1/2 source",
|
|
648
|
+
isSatisfied: (s) => hasSourceAtTier(s, "2")
|
|
649
|
+
}
|
|
650
|
+
],
|
|
651
|
+
ProposeLabels: [
|
|
652
|
+
{
|
|
653
|
+
description: "Keyword match in issue body (repo file) OR maintainer instruction",
|
|
654
|
+
isSatisfied: (s) => hasRepoFileRef(s) || hasMaintainerCommand(s) || hasPolicyDocRef(s)
|
|
655
|
+
}
|
|
656
|
+
],
|
|
657
|
+
DraftReply: [
|
|
658
|
+
{
|
|
659
|
+
description: "At least one Tier 1 source citation",
|
|
660
|
+
isSatisfied: (s) => hasRepoFileRef(s) || hasCIPass(s) || hasMaintainerCommand(s) || hasPolicyDocRef(s) || hasTier1Comment(s)
|
|
661
|
+
}
|
|
662
|
+
],
|
|
663
|
+
GeneratePatchPlan: [
|
|
664
|
+
{
|
|
665
|
+
description: "Failing test OR bug reproduction steps (code-level evidence)",
|
|
666
|
+
isSatisfied: (s) => hasCodeLevelEvidence(s) || hasCIPass(s)
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
description: "Maintainer corroboration",
|
|
670
|
+
isSatisfied: (s) => hasMaintainerCommand(s) || hasTier1Comment(s)
|
|
671
|
+
}
|
|
672
|
+
],
|
|
673
|
+
ClassifyIssue: [
|
|
674
|
+
{
|
|
675
|
+
description: "At least one source citation",
|
|
676
|
+
isSatisfied: (s) => s.length > 0
|
|
677
|
+
}
|
|
678
|
+
],
|
|
679
|
+
IdentifyDuplicates: [
|
|
680
|
+
{
|
|
681
|
+
description: "At least one source citation",
|
|
682
|
+
isSatisfied: (s) => s.length > 0
|
|
683
|
+
}
|
|
684
|
+
],
|
|
685
|
+
RequestHumanApproval: [],
|
|
686
|
+
RefuseAction: [],
|
|
687
|
+
HandoffMessage: [
|
|
688
|
+
{
|
|
689
|
+
description: "At least one source citation for trust tier propagation",
|
|
690
|
+
isSatisfied: (s) => s.length > 0
|
|
691
|
+
}
|
|
692
|
+
]
|
|
693
|
+
};
|
|
694
|
+
function validateCorroboration(action) {
|
|
695
|
+
const rules = ACTION_CORROBORATION_RULES[action.type];
|
|
696
|
+
const sources = "sources" in action ? action.sources : [];
|
|
697
|
+
if (rules.length === 0) {
|
|
698
|
+
return {
|
|
699
|
+
satisfied: true,
|
|
700
|
+
corroboratingSources: sources,
|
|
701
|
+
missing: [],
|
|
702
|
+
actionType: action.type
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const missing = [];
|
|
706
|
+
const corroborating = [];
|
|
707
|
+
for (const rule of rules) {
|
|
708
|
+
if (rule.isSatisfied(sources)) {
|
|
709
|
+
for (const s of sources) {
|
|
710
|
+
if (!corroborating.includes(s)) {
|
|
711
|
+
corroborating.push(s);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
missing.push(rule.description);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return {
|
|
719
|
+
satisfied: missing.length === 0,
|
|
720
|
+
corroboratingSources: corroborating,
|
|
721
|
+
missing,
|
|
722
|
+
actionType: action.type
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
function getCorroborationRules(actionType) {
|
|
726
|
+
return ACTION_CORROBORATION_RULES[actionType];
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/security/reputation-model.ts
|
|
730
|
+
import { z as z4 } from "zod";
|
|
731
|
+
var SuspiciousSignalSchema = z4.enum([
|
|
732
|
+
"new_account",
|
|
733
|
+
"no_prior_contributions",
|
|
734
|
+
"injection_patterns_detected",
|
|
735
|
+
"rapid_comments",
|
|
736
|
+
"mismatched_authority_claim"
|
|
737
|
+
]);
|
|
738
|
+
var DAYS_PER_YEAR_APPROX = 36.5;
|
|
739
|
+
var SUSPICIOUS_THRESHOLDS = {
|
|
740
|
+
/** Account younger than this (days) is flagged. */
|
|
741
|
+
newAccountDays: 30,
|
|
742
|
+
/** Fewer contributions than this is flagged. */
|
|
743
|
+
minContributions: 1,
|
|
744
|
+
/** More comments than this in the window triggers rapid-comment flag. */
|
|
745
|
+
rapidCommentThreshold: 5,
|
|
746
|
+
/** Time window (minutes) for rapid comment detection. */
|
|
747
|
+
rapidCommentWindowMinutes: 10
|
|
748
|
+
};
|
|
749
|
+
var DEFAULT_TTL_MS = CACHE_TIMEOUTS.reputationTtlMs;
|
|
750
|
+
var DEFAULT_MAX_SIZE = 1e3;
|
|
751
|
+
var ReputationCache = class {
|
|
752
|
+
cache = /* @__PURE__ */ new Map();
|
|
753
|
+
ttlMs;
|
|
754
|
+
maxSize;
|
|
755
|
+
constructor(ttlMs = DEFAULT_TTL_MS, maxSize = DEFAULT_MAX_SIZE) {
|
|
756
|
+
this.ttlMs = ttlMs;
|
|
757
|
+
this.maxSize = maxSize;
|
|
758
|
+
}
|
|
759
|
+
get(username) {
|
|
760
|
+
const entry = this.cache.get(username);
|
|
761
|
+
if (entry === void 0) return void 0;
|
|
762
|
+
if (Date.now() > entry.expiresAt) {
|
|
763
|
+
this.cache.delete(username);
|
|
764
|
+
return void 0;
|
|
765
|
+
}
|
|
766
|
+
return entry.assessment;
|
|
767
|
+
}
|
|
768
|
+
set(username, assessment) {
|
|
769
|
+
if (this.cache.size >= this.maxSize && !this.cache.has(username)) {
|
|
770
|
+
this.evictOldest();
|
|
771
|
+
}
|
|
772
|
+
this.cache.set(username, {
|
|
773
|
+
assessment,
|
|
774
|
+
expiresAt: Date.now() + this.ttlMs
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
/** Evict a batch of oldest entries (10% of maxSize, minimum 1). */
|
|
778
|
+
evictOldest() {
|
|
779
|
+
const batchSize = Math.max(1, Math.floor(this.maxSize * 0.1));
|
|
780
|
+
const keys = this.cache.keys();
|
|
781
|
+
for (let i = 0; i < batchSize; i++) {
|
|
782
|
+
const next = keys.next();
|
|
783
|
+
if (next.done === true) break;
|
|
784
|
+
this.cache.delete(next.value);
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
clear() {
|
|
788
|
+
this.cache.clear();
|
|
789
|
+
}
|
|
790
|
+
get size() {
|
|
791
|
+
return this.cache.size;
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
function detectSuspiciousSignals(metadata) {
|
|
795
|
+
const signals = [];
|
|
796
|
+
if (metadata.accountAgeDays < SUSPICIOUS_THRESHOLDS.newAccountDays) {
|
|
797
|
+
signals.push("new_account");
|
|
798
|
+
}
|
|
799
|
+
if (metadata.priorContributions < SUSPICIOUS_THRESHOLDS.minContributions) {
|
|
800
|
+
signals.push("no_prior_contributions");
|
|
801
|
+
}
|
|
802
|
+
const hostileInjectionFlags = [
|
|
803
|
+
"system_prompt_manipulation",
|
|
804
|
+
"fake_conversation",
|
|
805
|
+
"authority_claim",
|
|
806
|
+
"hidden_content"
|
|
807
|
+
];
|
|
808
|
+
const hasHostileFlags = metadata.injectionFlags.some((f) => hostileInjectionFlags.includes(f));
|
|
809
|
+
if (hasHostileFlags) {
|
|
810
|
+
signals.push("injection_patterns_detected");
|
|
811
|
+
}
|
|
812
|
+
if (metadata.recentCommentCount > SUSPICIOUS_THRESHOLDS.rapidCommentThreshold && metadata.recentCommentWindowMinutes <= SUSPICIOUS_THRESHOLDS.rapidCommentWindowMinutes) {
|
|
813
|
+
signals.push("rapid_comments");
|
|
814
|
+
}
|
|
815
|
+
const hasAuthorityClaim = metadata.injectionFlags.includes("authority_claim");
|
|
816
|
+
const association = metadata.authorAssociation.toUpperCase();
|
|
817
|
+
const isAuthoritative = association === "OWNER" || association === "MEMBER";
|
|
818
|
+
if (hasAuthorityClaim && !isAuthoritative) {
|
|
819
|
+
signals.push("mismatched_authority_claim");
|
|
820
|
+
}
|
|
821
|
+
return signals;
|
|
822
|
+
}
|
|
823
|
+
function calculateReputationScore(metadata, signals, userRole) {
|
|
824
|
+
let score = 50;
|
|
825
|
+
const roleBonus = {
|
|
826
|
+
owner: 40,
|
|
827
|
+
maintainer: 35,
|
|
828
|
+
collaborator: 25,
|
|
829
|
+
contributor: 15,
|
|
830
|
+
member: 5,
|
|
831
|
+
unknown: 0
|
|
832
|
+
};
|
|
833
|
+
score += roleBonus[userRole];
|
|
834
|
+
score += Math.min(metadata.accountAgeDays / DAYS_PER_YEAR_APPROX, 10);
|
|
835
|
+
score += Math.min(metadata.priorContributions, 10);
|
|
836
|
+
const signalPenalty = {
|
|
837
|
+
new_account: -15,
|
|
838
|
+
no_prior_contributions: -10,
|
|
839
|
+
injection_patterns_detected: -25,
|
|
840
|
+
rapid_comments: -20,
|
|
841
|
+
mismatched_authority_claim: -30
|
|
842
|
+
};
|
|
843
|
+
for (const signal of signals) {
|
|
844
|
+
score += signalPenalty[signal];
|
|
845
|
+
}
|
|
846
|
+
return Math.max(0, Math.min(100, Math.round(score)));
|
|
847
|
+
}
|
|
848
|
+
function mapRole(association) {
|
|
849
|
+
switch (association.toUpperCase()) {
|
|
850
|
+
case "OWNER":
|
|
851
|
+
return "owner";
|
|
852
|
+
case "MEMBER":
|
|
853
|
+
return "member";
|
|
854
|
+
case "COLLABORATOR":
|
|
855
|
+
return "collaborator";
|
|
856
|
+
case "CONTRIBUTOR":
|
|
857
|
+
return "contributor";
|
|
858
|
+
default:
|
|
859
|
+
return "unknown";
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
function determineEffectiveTier(userRole, signals) {
|
|
863
|
+
const baseTier = ROLE_DEFAULT_TRUST[userRole];
|
|
864
|
+
const hostileSignals = [
|
|
865
|
+
"injection_patterns_detected",
|
|
866
|
+
"mismatched_authority_claim"
|
|
867
|
+
];
|
|
868
|
+
if (signals.some((s) => hostileSignals.includes(s))) return "4";
|
|
869
|
+
if (signals.length >= 2) {
|
|
870
|
+
const baseNumeric = TRUST_TIER_NUMERIC[baseTier];
|
|
871
|
+
const downgraded = Math.min(baseNumeric + 1, 4);
|
|
872
|
+
return String(downgraded);
|
|
873
|
+
}
|
|
874
|
+
return baseTier;
|
|
875
|
+
}
|
|
876
|
+
function assessReputation(metadata, cache) {
|
|
877
|
+
const cached = cache?.get(metadata.username);
|
|
878
|
+
if (cached !== void 0) return cached;
|
|
879
|
+
const userRole = mapRole(metadata.authorAssociation);
|
|
880
|
+
const signals = detectSuspiciousSignals(metadata);
|
|
881
|
+
const score = calculateReputationScore(metadata, signals, userRole);
|
|
882
|
+
const effectiveTier = determineEffectiveTier(userRole, signals);
|
|
883
|
+
const assessment = {
|
|
884
|
+
username: metadata.username,
|
|
885
|
+
userRole,
|
|
886
|
+
suspiciousSignals: signals,
|
|
887
|
+
isSuspicious: signals.length > 0,
|
|
888
|
+
effectiveTrustTier: effectiveTier,
|
|
889
|
+
reputationScore: score,
|
|
890
|
+
reason: buildReason(userRole, signals, effectiveTier),
|
|
891
|
+
assessedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
892
|
+
};
|
|
893
|
+
cache?.set(metadata.username, assessment);
|
|
894
|
+
return assessment;
|
|
895
|
+
}
|
|
896
|
+
function buildReason(role, signals, tier) {
|
|
897
|
+
if (signals.length === 0) {
|
|
898
|
+
return `Role ${role} \u2192 Tier ${tier} (no suspicious signals)`;
|
|
899
|
+
}
|
|
900
|
+
const signalList = signals.join(", ");
|
|
901
|
+
return `Role ${role} \u2192 Tier ${tier} (signals: ${signalList})`;
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// src/dogfooding/github-client.ts
|
|
905
|
+
import { execFileSync } from "child_process";
|
|
906
|
+
var logger = createLogger({ component: "GitHubClient" });
|
|
907
|
+
function parsePRUrl(url) {
|
|
908
|
+
const httpPattern = /github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/;
|
|
909
|
+
const shortPattern = /^([^/]+)\/([^/#]+)(?:#|\/pull\/)(\d+)$/;
|
|
910
|
+
const match = httpPattern.exec(url) ?? shortPattern.exec(url);
|
|
911
|
+
if (match === null) {
|
|
912
|
+
return err(new Error(`Invalid PR URL format: ${url}`));
|
|
913
|
+
}
|
|
914
|
+
const owner = match[1];
|
|
915
|
+
const repo = match[2];
|
|
916
|
+
const numberStr = match[3];
|
|
917
|
+
if (owner === void 0 || repo === void 0 || numberStr === void 0) {
|
|
918
|
+
return err(new Error(`Invalid PR URL format: ${url}`));
|
|
919
|
+
}
|
|
920
|
+
const prNumber = parseInt(numberStr, 10);
|
|
921
|
+
if (isNaN(prNumber)) {
|
|
922
|
+
return err(new Error(`Invalid PR URL format: ${url}`));
|
|
923
|
+
}
|
|
924
|
+
return ok({ owner, repo, prNumber });
|
|
925
|
+
}
|
|
926
|
+
function parseIssueUrl(url) {
|
|
927
|
+
const httpPattern = /github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/;
|
|
928
|
+
const shortPattern = /^([^/]+)\/([^/#]+)#(\d+)$/;
|
|
929
|
+
const match = httpPattern.exec(url) ?? shortPattern.exec(url);
|
|
930
|
+
if (match === null) {
|
|
931
|
+
return err(new Error(`Invalid issue URL format: ${url}`));
|
|
932
|
+
}
|
|
933
|
+
const owner = match[1];
|
|
934
|
+
const repo = match[2];
|
|
935
|
+
const numberStr = match[3];
|
|
936
|
+
if (owner === void 0 || repo === void 0 || numberStr === void 0) {
|
|
937
|
+
return err(new Error(`Invalid issue URL format: ${url}`));
|
|
938
|
+
}
|
|
939
|
+
const issueNumber = parseInt(numberStr, 10);
|
|
940
|
+
if (isNaN(issueNumber)) {
|
|
941
|
+
return err(new Error(`Invalid issue URL format: ${url}`));
|
|
942
|
+
}
|
|
943
|
+
return ok({ owner, repo, issueNumber });
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
// src/scm/github-provider-traits.ts
|
|
947
|
+
var logger2 = createLogger({ component: "GitHubProviderTraits" });
|
|
948
|
+
var cachedGhToken = null;
|
|
949
|
+
var ghTokenPromise;
|
|
950
|
+
async function resolveGhToken() {
|
|
951
|
+
if (cachedGhToken !== null) return cachedGhToken;
|
|
952
|
+
ghTokenPromise ??= resolveGhTokenImpl().finally(() => {
|
|
953
|
+
ghTokenPromise = void 0;
|
|
954
|
+
});
|
|
955
|
+
return ghTokenPromise;
|
|
956
|
+
}
|
|
957
|
+
async function resolveGhTokenImpl() {
|
|
958
|
+
const envToken = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
|
|
959
|
+
if (envToken !== void 0 && envToken.length > 0) {
|
|
960
|
+
cachedGhToken = envToken;
|
|
961
|
+
return envToken;
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
const { execFile } = await import("child_process");
|
|
965
|
+
const { promisify } = await import("util");
|
|
966
|
+
const exec = promisify(execFile);
|
|
967
|
+
const { stdout } = await exec("gh", ["auth", "token"], { timeout: 5e3 });
|
|
968
|
+
const token = stdout.trim();
|
|
969
|
+
cachedGhToken = token.length > 0 ? token : void 0;
|
|
970
|
+
} catch {
|
|
971
|
+
cachedGhToken = void 0;
|
|
972
|
+
}
|
|
973
|
+
return cachedGhToken;
|
|
974
|
+
}
|
|
975
|
+
async function execGhApi(endpoint, method) {
|
|
976
|
+
const { execFile } = await import("child_process");
|
|
977
|
+
const { promisify } = await import("util");
|
|
978
|
+
const exec = promisify(execFile);
|
|
979
|
+
const args = ["api", endpoint];
|
|
980
|
+
if (method !== void 0) args.push("--method", method);
|
|
981
|
+
const token = await resolveGhToken();
|
|
982
|
+
const env = token !== void 0 ? { ...process.env, GH_TOKEN: token } : void 0;
|
|
983
|
+
try {
|
|
984
|
+
const { stdout } = await exec("gh", args, {
|
|
985
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
986
|
+
timeout: 3e4,
|
|
987
|
+
...env !== void 0 ? { env } : {}
|
|
988
|
+
});
|
|
989
|
+
return ok(stdout.trim());
|
|
990
|
+
} catch (error) {
|
|
991
|
+
const execError = error;
|
|
992
|
+
return err(
|
|
993
|
+
new ScmError(`gh api failed: ${execError.message}`, "github", void 0, {
|
|
994
|
+
endpoint,
|
|
995
|
+
stderr: execError.stderr
|
|
996
|
+
})
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function mapFileChange(raw) {
|
|
1001
|
+
const statusMap = {
|
|
1002
|
+
added: "added",
|
|
1003
|
+
removed: "removed",
|
|
1004
|
+
modified: "modified",
|
|
1005
|
+
renamed: "renamed",
|
|
1006
|
+
copied: "copied"
|
|
1007
|
+
};
|
|
1008
|
+
return {
|
|
1009
|
+
filename: raw.filename,
|
|
1010
|
+
status: statusMap[raw.status] ?? "modified",
|
|
1011
|
+
additions: raw.additions,
|
|
1012
|
+
deletions: raw.deletions,
|
|
1013
|
+
...raw.patch !== void 0 ? { patch: raw.patch } : {},
|
|
1014
|
+
...raw.previous_filename !== void 0 ? { previousFilename: raw.previous_filename } : {}
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
var GitHubReviewer = class {
|
|
1018
|
+
constructor(provider) {
|
|
1019
|
+
this.provider = provider;
|
|
1020
|
+
}
|
|
1021
|
+
async getPullRequestDetail(prNumber) {
|
|
1022
|
+
const repo = this.provider.repo;
|
|
1023
|
+
logger2.debug("Getting PR detail", { repo, prNumber });
|
|
1024
|
+
const prResult = await execGhApi(`repos/${repo}/pulls/${String(prNumber)}`);
|
|
1025
|
+
if (!prResult.ok) return prResult;
|
|
1026
|
+
const filesResult = await execGhApi(`repos/${repo}/pulls/${String(prNumber)}/files`);
|
|
1027
|
+
if (!filesResult.ok) return filesResult;
|
|
1028
|
+
try {
|
|
1029
|
+
const pr = JSON.parse(prResult.value);
|
|
1030
|
+
const files = JSON.parse(filesResult.value);
|
|
1031
|
+
return ok({
|
|
1032
|
+
number: pr.number,
|
|
1033
|
+
title: pr.title,
|
|
1034
|
+
body: pr.body ?? "",
|
|
1035
|
+
author: pr.user.login,
|
|
1036
|
+
base: pr.base.ref,
|
|
1037
|
+
head: pr.head.ref,
|
|
1038
|
+
url: pr.html_url,
|
|
1039
|
+
draft: pr.draft,
|
|
1040
|
+
authorAssociation: pr.author_association,
|
|
1041
|
+
labels: pr.labels.map((l) => l.name),
|
|
1042
|
+
files: files.map(mapFileChange),
|
|
1043
|
+
additions: pr.additions,
|
|
1044
|
+
deletions: pr.deletions,
|
|
1045
|
+
headSha: pr.head.sha
|
|
1046
|
+
});
|
|
1047
|
+
} catch {
|
|
1048
|
+
return err(new ScmError("Failed to parse PR detail JSON", "github"));
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
async createReview(prNumber, body, decision) {
|
|
1052
|
+
const repo = this.provider.repo;
|
|
1053
|
+
const eventMap = {
|
|
1054
|
+
approve: "APPROVE",
|
|
1055
|
+
request_changes: "REQUEST_CHANGES",
|
|
1056
|
+
comment: "COMMENT"
|
|
1057
|
+
};
|
|
1058
|
+
logger2.info("Creating review", { repo, prNumber, decision });
|
|
1059
|
+
const { execFile } = await import("child_process");
|
|
1060
|
+
const { promisify } = await import("util");
|
|
1061
|
+
const exec = promisify(execFile);
|
|
1062
|
+
try {
|
|
1063
|
+
await exec(
|
|
1064
|
+
"gh",
|
|
1065
|
+
[
|
|
1066
|
+
"api",
|
|
1067
|
+
`repos/${repo}/pulls/${String(prNumber)}/reviews`,
|
|
1068
|
+
"--method",
|
|
1069
|
+
"POST",
|
|
1070
|
+
"-f",
|
|
1071
|
+
`body=${body}`,
|
|
1072
|
+
"-f",
|
|
1073
|
+
`event=${eventMap[decision]}`
|
|
1074
|
+
],
|
|
1075
|
+
{ maxBuffer: 10 * 1024 * 1024, timeout: 3e4 }
|
|
1076
|
+
);
|
|
1077
|
+
return ok(void 0);
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
const execError = error;
|
|
1080
|
+
return err(new ScmError(`Failed to create review: ${execError.message}`, "github"));
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
async getIssueDetail(issueNumber) {
|
|
1084
|
+
const repo = this.provider.repo;
|
|
1085
|
+
logger2.debug("Getting issue detail", { repo, issueNumber });
|
|
1086
|
+
const result = await execGhApi(`repos/${repo}/issues/${String(issueNumber)}`);
|
|
1087
|
+
if (!result.ok) return result;
|
|
1088
|
+
try {
|
|
1089
|
+
const raw = JSON.parse(result.value);
|
|
1090
|
+
return ok({
|
|
1091
|
+
number: raw.number,
|
|
1092
|
+
title: raw.title,
|
|
1093
|
+
body: raw.body ?? "",
|
|
1094
|
+
labels: raw.labels.map((l) => l.name),
|
|
1095
|
+
author: raw.user.login,
|
|
1096
|
+
createdAt: raw.created_at,
|
|
1097
|
+
authorAssociation: raw.author_association,
|
|
1098
|
+
state: raw.state,
|
|
1099
|
+
url: raw.html_url
|
|
1100
|
+
});
|
|
1101
|
+
} catch {
|
|
1102
|
+
return err(new ScmError("Failed to parse issue detail JSON", "github"));
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
async listCommentDetails(issueNumber) {
|
|
1106
|
+
const repo = this.provider.repo;
|
|
1107
|
+
logger2.debug("Listing comment details", { repo, issueNumber });
|
|
1108
|
+
const result = await execGhApi(`repos/${repo}/issues/${String(issueNumber)}/comments`);
|
|
1109
|
+
if (!result.ok) return result;
|
|
1110
|
+
try {
|
|
1111
|
+
const comments = JSON.parse(result.value);
|
|
1112
|
+
return ok(
|
|
1113
|
+
comments.map((c) => ({
|
|
1114
|
+
id: c.id,
|
|
1115
|
+
body: c.body,
|
|
1116
|
+
author: c.user.login,
|
|
1117
|
+
createdAt: c.created_at,
|
|
1118
|
+
authorAssociation: c.author_association
|
|
1119
|
+
}))
|
|
1120
|
+
);
|
|
1121
|
+
} catch {
|
|
1122
|
+
return err(new ScmError("Failed to parse comment details JSON", "github"));
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
var GitHubUserInfo = class {
|
|
1127
|
+
async fetchUserMetadata(username) {
|
|
1128
|
+
logger2.debug("Fetching user metadata", { username });
|
|
1129
|
+
const result = await execGhApi(`users/${username}`);
|
|
1130
|
+
if (!result.ok) return result;
|
|
1131
|
+
try {
|
|
1132
|
+
const raw = JSON.parse(result.value);
|
|
1133
|
+
return ok({
|
|
1134
|
+
login: raw.login,
|
|
1135
|
+
name: raw.name,
|
|
1136
|
+
company: raw.company,
|
|
1137
|
+
followers: raw.followers,
|
|
1138
|
+
following: raw.following,
|
|
1139
|
+
publicRepos: raw.public_repos,
|
|
1140
|
+
createdAt: raw.created_at
|
|
1141
|
+
});
|
|
1142
|
+
} catch {
|
|
1143
|
+
return err(new ScmError("Failed to parse user metadata JSON", "github"));
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
function createFullGitHubProvider(repo) {
|
|
1148
|
+
const base = new GitHubProvider(repo);
|
|
1149
|
+
const reviewer = new GitHubReviewer(base);
|
|
1150
|
+
const userInfo = new GitHubUserInfo();
|
|
1151
|
+
return Object.assign(base, {
|
|
1152
|
+
getPullRequestDetail: reviewer.getPullRequestDetail.bind(reviewer),
|
|
1153
|
+
createReview: reviewer.createReview.bind(reviewer),
|
|
1154
|
+
getIssueDetail: reviewer.getIssueDetail.bind(reviewer),
|
|
1155
|
+
listCommentDetails: reviewer.listCommentDetails.bind(reviewer),
|
|
1156
|
+
fetchUserMetadata: userInfo.fetchUserMetadata.bind(userInfo)
|
|
1157
|
+
});
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// src/dogfooding/issue-triage-types.ts
|
|
1161
|
+
import { z as z5 } from "zod";
|
|
1162
|
+
var CATEGORY_DISPLAY_NAMES = {
|
|
1163
|
+
bug: "Bug Report",
|
|
1164
|
+
feature: "Feature Request",
|
|
1165
|
+
question: "Question",
|
|
1166
|
+
documentation: "Documentation",
|
|
1167
|
+
security: "Security",
|
|
1168
|
+
performance: "Performance"
|
|
1169
|
+
};
|
|
1170
|
+
var CATEGORY_EMOJI = {
|
|
1171
|
+
bug: ":bug:",
|
|
1172
|
+
feature: ":sparkles:",
|
|
1173
|
+
question: ":question:",
|
|
1174
|
+
documentation: ":books:",
|
|
1175
|
+
security: ":lock:",
|
|
1176
|
+
performance: ":zap:"
|
|
1177
|
+
};
|
|
1178
|
+
var DEFAULT_ISSUE_TRIAGE_CONFIG = {
|
|
1179
|
+
dryRun: true,
|
|
1180
|
+
maxComments: 50,
|
|
1181
|
+
enableReputation: true
|
|
1182
|
+
};
|
|
1183
|
+
var IssueTriageConfigSchema = z5.object({
|
|
1184
|
+
dryRun: z5.boolean().default(true),
|
|
1185
|
+
githubToken: z5.string().optional(),
|
|
1186
|
+
maxComments: z5.number().int().min(1).max(100).default(50),
|
|
1187
|
+
enableReputation: z5.boolean().default(true)
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
// src/dogfooding/issue-triage-helpers.ts
|
|
1191
|
+
var CATEGORY_KEYWORDS = {
|
|
1192
|
+
bug: ["bug", "error", "crash", "broken", "fix", "fail", "issue", "wrong", "unexpected"],
|
|
1193
|
+
feature: ["feature", "request", "enhancement", "proposal", "add", "support", "implement"],
|
|
1194
|
+
question: ["question", "how to", "help", "confused", "explain", "documentation"],
|
|
1195
|
+
documentation: ["docs", "documentation", "readme", "typo", "example", "guide"],
|
|
1196
|
+
security: ["security", "vulnerability", "cve", "exploit", "injection", "xss", "csrf"],
|
|
1197
|
+
performance: ["performance", "slow", "memory", "leak", "optimize", "latency", "timeout"]
|
|
1198
|
+
};
|
|
1199
|
+
function categorizeIssue(title, body) {
|
|
1200
|
+
const text = `${title} ${body}`.toLowerCase();
|
|
1201
|
+
const scores = {
|
|
1202
|
+
bug: 0,
|
|
1203
|
+
feature: 0,
|
|
1204
|
+
question: 0,
|
|
1205
|
+
documentation: 0,
|
|
1206
|
+
security: 0,
|
|
1207
|
+
performance: 0
|
|
1208
|
+
};
|
|
1209
|
+
let totalMatches = 0;
|
|
1210
|
+
for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
|
|
1211
|
+
for (const keyword of keywords) {
|
|
1212
|
+
if (text.includes(keyword)) {
|
|
1213
|
+
scores[category]++;
|
|
1214
|
+
totalMatches++;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
let bestCategory = "bug";
|
|
1219
|
+
let bestScore = 0;
|
|
1220
|
+
for (const [category, score] of Object.entries(scores)) {
|
|
1221
|
+
if (score > bestScore) {
|
|
1222
|
+
bestScore = score;
|
|
1223
|
+
bestCategory = category;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const confidence = totalMatches > 0 ? Math.min(bestScore / totalMatches, 1) : 0.1;
|
|
1227
|
+
return [bestCategory, Math.round(confidence * 100) / 100];
|
|
1228
|
+
}
|
|
1229
|
+
var LABEL_HINTS = /* @__PURE__ */ new Map([
|
|
1230
|
+
["bug", "bug"],
|
|
1231
|
+
["feature request", "enhancement"],
|
|
1232
|
+
["enhancement", "enhancement"],
|
|
1233
|
+
["breaking change", "breaking-change"],
|
|
1234
|
+
["documentation", "documentation"],
|
|
1235
|
+
["help wanted", "help wanted"],
|
|
1236
|
+
["good first issue", "good first issue"],
|
|
1237
|
+
["security", "security"],
|
|
1238
|
+
["performance", "performance"],
|
|
1239
|
+
["regression", "regression"]
|
|
1240
|
+
]);
|
|
1241
|
+
function extractLabelsFromBody(title, body) {
|
|
1242
|
+
const text = `${title} ${body}`.toLowerCase();
|
|
1243
|
+
const labels = [];
|
|
1244
|
+
for (const [pattern, label] of LABEL_HINTS) {
|
|
1245
|
+
if (text.includes(pattern) && !labels.includes(label)) {
|
|
1246
|
+
labels.push(label);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
return labels.slice(0, 5);
|
|
1250
|
+
}
|
|
1251
|
+
function formatTriageComment(result) {
|
|
1252
|
+
const emoji = CATEGORY_EMOJI[result.category];
|
|
1253
|
+
const categoryName = CATEGORY_DISPLAY_NAMES[result.category];
|
|
1254
|
+
const lines = [];
|
|
1255
|
+
lines.push(`## ${emoji} Issue Triage: ${categoryName}`);
|
|
1256
|
+
lines.push("");
|
|
1257
|
+
lines.push(
|
|
1258
|
+
`**Category:** ${categoryName} (${String(Math.round(result.categoryConfidence * 100))}% confidence)`
|
|
1259
|
+
);
|
|
1260
|
+
lines.push(
|
|
1261
|
+
`**Trust Tier:** ${result.trustAssessment.trustTier} (${result.trustAssessment.userRole})`
|
|
1262
|
+
);
|
|
1263
|
+
if (result.trustAssessment.reputationScore !== void 0) {
|
|
1264
|
+
lines.push(`**Reputation Score:** ${String(result.trustAssessment.reputationScore)}/100`);
|
|
1265
|
+
}
|
|
1266
|
+
if (result.trustAssessment.isSuspicious) {
|
|
1267
|
+
lines.push("");
|
|
1268
|
+
lines.push(":warning: **Suspicious signals detected:**");
|
|
1269
|
+
for (const signal of result.trustAssessment.suspiciousSignals) {
|
|
1270
|
+
lines.push(`- ${signal}`);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
if (result.proposedActions.length > 0) {
|
|
1274
|
+
lines.push("");
|
|
1275
|
+
lines.push("### Proposed Actions");
|
|
1276
|
+
lines.push("");
|
|
1277
|
+
for (const action of result.proposedActions) {
|
|
1278
|
+
const status = formatActionStatus(action);
|
|
1279
|
+
lines.push(`- ${status} **${action.type}**: ${action.description}`);
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
lines.push("");
|
|
1283
|
+
lines.push(`---`);
|
|
1284
|
+
lines.push(`_Triage completed in ${String(result.totalDurationMs)}ms_`);
|
|
1285
|
+
return lines.join("\n");
|
|
1286
|
+
}
|
|
1287
|
+
function formatActionStatus(action) {
|
|
1288
|
+
if (action.policyApproved && action.corroborated) return ":white_check_mark:";
|
|
1289
|
+
if (action.policyApproved && !action.corroborated) return ":yellow_circle:";
|
|
1290
|
+
return ":no_entry:";
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// src/dogfooding/issue-triage.ts
|
|
1294
|
+
var logger3 = createLogger({ component: "IssueTriage" });
|
|
1295
|
+
var IssueTriage = class {
|
|
1296
|
+
config;
|
|
1297
|
+
reputationCache;
|
|
1298
|
+
constructor(config) {
|
|
1299
|
+
this.config = { ...DEFAULT_ISSUE_TRIAGE_CONFIG, ...config };
|
|
1300
|
+
this.reputationCache = new ReputationCache();
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Triages a GitHub issue through the full security pipeline.
|
|
1304
|
+
*/
|
|
1305
|
+
async triageIssue(issueUrl) {
|
|
1306
|
+
const startTime = getTimeProvider().now();
|
|
1307
|
+
logger3.info("Starting issue triage", { issueUrl });
|
|
1308
|
+
const parseResult = parseIssueUrl(issueUrl);
|
|
1309
|
+
if (!parseResult.ok) return parseResult;
|
|
1310
|
+
const { owner, repo, issueNumber } = parseResult.value;
|
|
1311
|
+
const fetchResult = await this.fetchIssueData(owner, repo, issueNumber);
|
|
1312
|
+
if (!fetchResult.ok) return fetchResult;
|
|
1313
|
+
const { issue: issueResult, comments } = fetchResult.value;
|
|
1314
|
+
const safeTitle = this.sanitizeContent(issueResult.title, issueResult.author);
|
|
1315
|
+
const safeBody = this.sanitizeContent(issueResult.body, issueResult.author);
|
|
1316
|
+
const trustResult = this.classifyAuthor(issueResult);
|
|
1317
|
+
const reputation = this.assessAuthorReputation(issueResult, comments);
|
|
1318
|
+
const actions = this.generateActions(safeTitle, safeBody, issueResult, trustResult);
|
|
1319
|
+
const validatedActions = this.validateActions(actions, trustResult);
|
|
1320
|
+
const result = this.buildResult({
|
|
1321
|
+
issue: issueResult,
|
|
1322
|
+
actions: validatedActions,
|
|
1323
|
+
trustResult,
|
|
1324
|
+
reputation,
|
|
1325
|
+
safeContent: { title: safeTitle, body: safeBody },
|
|
1326
|
+
startTime
|
|
1327
|
+
});
|
|
1328
|
+
logger3.info("Issue triage completed", {
|
|
1329
|
+
issueNumber,
|
|
1330
|
+
category: result.category,
|
|
1331
|
+
actionCount: result.proposedActions.length,
|
|
1332
|
+
durationMs: result.totalDurationMs
|
|
1333
|
+
});
|
|
1334
|
+
return ok(result);
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Sanitizes untrusted issue content before processing.
|
|
1338
|
+
* (Source: Issue #828 — input-sanitizer wiring)
|
|
1339
|
+
*/
|
|
1340
|
+
sanitizeContent(text, author) {
|
|
1341
|
+
if (text.length === 0) return text;
|
|
1342
|
+
const result = sanitizeInput(text, "unknown", author);
|
|
1343
|
+
if (result.wasModified) {
|
|
1344
|
+
logger3.warn("Issue content sanitized", {
|
|
1345
|
+
author,
|
|
1346
|
+
strippedCount: result.strippedElements.length,
|
|
1347
|
+
injectionFlags: result.injectionFlags
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
return result.content;
|
|
1351
|
+
}
|
|
1352
|
+
/**
|
|
1353
|
+
* Classifies the issue author's trust tier.
|
|
1354
|
+
* (Source: Issue #828 — trust-classifier wiring)
|
|
1355
|
+
*/
|
|
1356
|
+
classifyAuthor(issue) {
|
|
1357
|
+
return classifyTrust({
|
|
1358
|
+
username: issue.author,
|
|
1359
|
+
authorAssociation: issue.authorAssociation
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Assesses the issue author's reputation using the reputation model.
|
|
1364
|
+
* This is one of the two NEW security module wirings completing #828.
|
|
1365
|
+
* (Source: Issue #828 — reputation-model wiring)
|
|
1366
|
+
*/
|
|
1367
|
+
assessAuthorReputation(issue, comments) {
|
|
1368
|
+
if (!this.config.enableReputation) return void 0;
|
|
1369
|
+
const sanitizeResult = sanitizeInput(issue.body, "unknown", issue.author);
|
|
1370
|
+
const metadata = {
|
|
1371
|
+
username: issue.author,
|
|
1372
|
+
accountAgeDays: estimateAccountAge(issue.createdAt),
|
|
1373
|
+
priorContributions: countAuthorComments(issue.author, comments),
|
|
1374
|
+
recentCommentCount: countRecentComments(issue.author, comments),
|
|
1375
|
+
recentCommentWindowMinutes: 10,
|
|
1376
|
+
authorAssociation: issue.authorAssociation,
|
|
1377
|
+
injectionFlags: sanitizeResult.injectionFlags
|
|
1378
|
+
};
|
|
1379
|
+
return assessReputation(metadata, this.reputationCache);
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Generates typed agent actions based on issue analysis.
|
|
1383
|
+
*/
|
|
1384
|
+
generateActions(safeTitle, safeBody, issue, trustResult) {
|
|
1385
|
+
const actions = [];
|
|
1386
|
+
const source = this.createRepoSource(issue);
|
|
1387
|
+
const [category, confidence] = categorizeIssue(safeTitle, safeBody);
|
|
1388
|
+
actions.push({
|
|
1389
|
+
type: "ClassifyIssue",
|
|
1390
|
+
category,
|
|
1391
|
+
confidence,
|
|
1392
|
+
sources: [source]
|
|
1393
|
+
});
|
|
1394
|
+
const labels = extractLabelsFromBody(safeTitle, safeBody);
|
|
1395
|
+
if (labels.length > 0) {
|
|
1396
|
+
actions.push({
|
|
1397
|
+
type: "ProposeLabels",
|
|
1398
|
+
labels,
|
|
1399
|
+
reason: `Keyword-based label extraction from issue #${String(issue.number)}`,
|
|
1400
|
+
sources: [source]
|
|
1401
|
+
});
|
|
1402
|
+
}
|
|
1403
|
+
if (trustResult.trustTier === "1" || trustResult.trustTier === "2") {
|
|
1404
|
+
const summary = `Issue #${String(issue.number)} "${safeTitle}" by ${issue.author} (${trustResult.userRole})`;
|
|
1405
|
+
actions.push({
|
|
1406
|
+
type: "SummarizeIssue",
|
|
1407
|
+
summary: summary.length >= 10 ? summary : `${summary} \u2014 awaiting further analysis`,
|
|
1408
|
+
sources: [source]
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
return actions;
|
|
1412
|
+
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Validates all actions through policy gate and corroboration validator.
|
|
1415
|
+
* This completes the #828 wiring by integrating corroboration-validator.
|
|
1416
|
+
* (Source: Issue #828 — policy-gate + corroboration-validator wiring)
|
|
1417
|
+
*/
|
|
1418
|
+
validateActions(actions, trustResult) {
|
|
1419
|
+
const context = {
|
|
1420
|
+
inputTrustTier: trustResult.trustTier,
|
|
1421
|
+
hasWriteAccess: !this.config.dryRun,
|
|
1422
|
+
hasSecretAccess: false
|
|
1423
|
+
};
|
|
1424
|
+
return actions.map((action) => {
|
|
1425
|
+
const policyDecision = evaluatePolicy(action, context);
|
|
1426
|
+
const corrobResult = this.validateActionCorroboration(action);
|
|
1427
|
+
return {
|
|
1428
|
+
type: action.type,
|
|
1429
|
+
description: describeAction(action),
|
|
1430
|
+
policyApproved: policyDecision.allowed,
|
|
1431
|
+
corroborated: corrobResult.satisfied,
|
|
1432
|
+
details: buildActionDetails(action, policyDecision, corrobResult)
|
|
1433
|
+
};
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Validates corroboration for a single action.
|
|
1438
|
+
* This is the second NEW security module wiring completing #828.
|
|
1439
|
+
* (Source: Issue #828 — corroboration-validator wiring)
|
|
1440
|
+
*/
|
|
1441
|
+
validateActionCorroboration(action) {
|
|
1442
|
+
return validateCorroboration(action);
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Builds the final triage result.
|
|
1446
|
+
*/
|
|
1447
|
+
buildResult(opts) {
|
|
1448
|
+
const { issue, actions, trustResult, reputation, safeContent, startTime } = opts;
|
|
1449
|
+
const [category, confidence] = categorizeIssue(safeContent.title, safeContent.body);
|
|
1450
|
+
const isTier1 = trustResult.trustTier === "1";
|
|
1451
|
+
const trustAssessment = {
|
|
1452
|
+
trustTier: trustResult.trustTier,
|
|
1453
|
+
userRole: trustResult.userRole,
|
|
1454
|
+
isAllowlisted: trustResult.isAllowlisted,
|
|
1455
|
+
reputationScore: reputation?.reputationScore,
|
|
1456
|
+
suspiciousSignals: isTier1 ? [] : reputation?.suspiciousSignals ?? [],
|
|
1457
|
+
isSuspicious: isTier1 ? false : reputation?.isSuspicious ?? false
|
|
1458
|
+
};
|
|
1459
|
+
return {
|
|
1460
|
+
issueNumber: issue.number,
|
|
1461
|
+
repository: `${issue.owner}/${issue.repo}`,
|
|
1462
|
+
proposedActions: actions,
|
|
1463
|
+
trustAssessment,
|
|
1464
|
+
category,
|
|
1465
|
+
categoryConfidence: confidence,
|
|
1466
|
+
totalDurationMs: getTimeProvider().now() - startTime,
|
|
1467
|
+
timestamp: getTimeProvider().nowIso()
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Creates a repo file source citation for the triage.
|
|
1472
|
+
*/
|
|
1473
|
+
createRepoSource(issue) {
|
|
1474
|
+
return {
|
|
1475
|
+
type: "repoFile",
|
|
1476
|
+
path: `issues/${String(issue.number)}`
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Fetches issue data from the SCM provider and maps to dogfooding types.
|
|
1481
|
+
*/
|
|
1482
|
+
async fetchIssueData(owner, repo, issueNumber) {
|
|
1483
|
+
const provider = createFullGitHubProvider(`${owner}/${repo}`);
|
|
1484
|
+
const detailResult = await provider.getIssueDetail(issueNumber);
|
|
1485
|
+
if (!detailResult.ok) return err(detailResult.error);
|
|
1486
|
+
const detail = detailResult.value;
|
|
1487
|
+
const issue = {
|
|
1488
|
+
number: detail.number,
|
|
1489
|
+
title: detail.title,
|
|
1490
|
+
body: detail.body,
|
|
1491
|
+
author: detail.author,
|
|
1492
|
+
authorAssociation: detail.authorAssociation,
|
|
1493
|
+
owner,
|
|
1494
|
+
repo,
|
|
1495
|
+
url: detail.url,
|
|
1496
|
+
state: detail.state,
|
|
1497
|
+
labels: [...detail.labels],
|
|
1498
|
+
createdAt: detail.createdAt
|
|
1499
|
+
};
|
|
1500
|
+
const commentsResult = await provider.listCommentDetails(issueNumber);
|
|
1501
|
+
const comments = commentsResult.ok ? commentsResult.value.map((c) => ({
|
|
1502
|
+
id: c.id,
|
|
1503
|
+
body: c.body,
|
|
1504
|
+
author: c.author,
|
|
1505
|
+
authorAssociation: c.authorAssociation,
|
|
1506
|
+
createdAt: c.createdAt
|
|
1507
|
+
})) : [];
|
|
1508
|
+
return ok({ issue, comments });
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
var DEFAULT_ACCOUNT_AGE_DAYS = 365;
|
|
1512
|
+
function estimateAccountAge(_createdAt) {
|
|
1513
|
+
return DEFAULT_ACCOUNT_AGE_DAYS;
|
|
1514
|
+
}
|
|
1515
|
+
function countAuthorComments(author, comments) {
|
|
1516
|
+
return comments.filter((c) => c.author === author).length;
|
|
1517
|
+
}
|
|
1518
|
+
function countRecentComments(author, comments) {
|
|
1519
|
+
const tenMinutesAgo = Date.now() - 10 * 60 * 1e3;
|
|
1520
|
+
return comments.filter(
|
|
1521
|
+
(c) => c.author === author && new Date(c.createdAt).getTime() > tenMinutesAgo
|
|
1522
|
+
).length;
|
|
1523
|
+
}
|
|
1524
|
+
function describeAction(action) {
|
|
1525
|
+
switch (action.type) {
|
|
1526
|
+
case "ClassifyIssue":
|
|
1527
|
+
return `Classified as ${action.category} (${String(Math.round(action.confidence * 100))}% confidence)`;
|
|
1528
|
+
case "ProposeLabels":
|
|
1529
|
+
return `Suggest labels: ${action.labels.join(", ")}`;
|
|
1530
|
+
case "SummarizeIssue":
|
|
1531
|
+
return action.summary.slice(0, 100);
|
|
1532
|
+
default:
|
|
1533
|
+
return `${action.type} action`;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
function buildActionDetails(action, policy, corrob) {
|
|
1537
|
+
return {
|
|
1538
|
+
policyViolations: policy.violations.map((v) => v.rule),
|
|
1539
|
+
missingCorroboration: corrob.missing,
|
|
1540
|
+
...action.type === "ClassifyIssue" && { category: action.category },
|
|
1541
|
+
...action.type === "ProposeLabels" && { labels: action.labels }
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
function createIssueTriage(config) {
|
|
1545
|
+
return new IssueTriage(config);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
export {
|
|
1549
|
+
TrustTierSchema,
|
|
1550
|
+
TRUST_TIER_NUMERIC,
|
|
1551
|
+
GitHubUserRoleSchema,
|
|
1552
|
+
ROLE_DEFAULT_TRUST,
|
|
1553
|
+
InjectionFlagSchema,
|
|
1554
|
+
StrippedElementSchema,
|
|
1555
|
+
SanitizedInputSchema,
|
|
1556
|
+
SanitizerConfigSchema,
|
|
1557
|
+
sanitizeInput,
|
|
1558
|
+
mapAuthorAssociation,
|
|
1559
|
+
classifyTrust,
|
|
1560
|
+
canInfluenceDecisions,
|
|
1561
|
+
requiresCorroboration,
|
|
1562
|
+
getRequiredTrustTier,
|
|
1563
|
+
SourceCitationSchema,
|
|
1564
|
+
AgentActionSchema,
|
|
1565
|
+
validateAgentAction,
|
|
1566
|
+
isReadOnlyAction,
|
|
1567
|
+
isMutatingAction,
|
|
1568
|
+
requiresCitation,
|
|
1569
|
+
ViolationSchema,
|
|
1570
|
+
evaluatePolicy,
|
|
1571
|
+
canProceed,
|
|
1572
|
+
validateCorroboration,
|
|
1573
|
+
getCorroborationRules,
|
|
1574
|
+
SuspiciousSignalSchema,
|
|
1575
|
+
ReputationCache,
|
|
1576
|
+
assessReputation,
|
|
1577
|
+
parsePRUrl,
|
|
1578
|
+
GitHubReviewer,
|
|
1579
|
+
GitHubUserInfo,
|
|
1580
|
+
createFullGitHubProvider,
|
|
1581
|
+
formatTriageComment,
|
|
1582
|
+
IssueTriage,
|
|
1583
|
+
createIssueTriage
|
|
1584
|
+
};
|
|
1585
|
+
//# sourceMappingURL=chunk-5COIDGQJ.js.map
|