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.
Files changed (112) hide show
  1. package/dist/adaptive-memory-5VP5WWTE.js +15 -0
  2. package/dist/chunk-5COIDGQJ.js +1585 -0
  3. package/dist/chunk-5COIDGQJ.js.map +1 -0
  4. package/dist/{chunk-HWDBNDUX.js → chunk-63AJLNKU.js} +2 -2
  5. package/dist/chunk-66NNHMVB.js +195 -0
  6. package/dist/chunk-66NNHMVB.js.map +1 -0
  7. package/dist/chunk-AP2FD37C.js +127 -0
  8. package/dist/chunk-AP2FD37C.js.map +1 -0
  9. package/dist/chunk-BC3M4VLP.js +359 -0
  10. package/dist/chunk-BC3M4VLP.js.map +1 -0
  11. package/dist/chunk-BQ4YXGGQ.js +127 -0
  12. package/dist/chunk-BQ4YXGGQ.js.map +1 -0
  13. package/dist/{chunk-ZBZJHXRT.js → chunk-CW2Z773T.js} +19 -347
  14. package/dist/chunk-CW2Z773T.js.map +1 -0
  15. package/dist/chunk-DDQGAVQA.js +944 -0
  16. package/dist/chunk-DDQGAVQA.js.map +1 -0
  17. package/dist/chunk-ED6VQWNG.js +63 -0
  18. package/dist/chunk-ED6VQWNG.js.map +1 -0
  19. package/dist/{chunk-7F6HYUIY.js → chunk-EPMBGZQX.js} +16 -97
  20. package/dist/chunk-EPMBGZQX.js.map +1 -0
  21. package/dist/chunk-GX436VRU.js +931 -0
  22. package/dist/chunk-GX436VRU.js.map +1 -0
  23. package/dist/{chunk-IMWYKX4H.js → chunk-HSOPD265.js} +444 -399
  24. package/dist/chunk-HSOPD265.js.map +1 -0
  25. package/dist/{chunk-S3BKWNST.js → chunk-J245RJGW.js} +680 -1436
  26. package/dist/chunk-J245RJGW.js.map +1 -0
  27. package/dist/{chunk-I6YDS23R.js → chunk-KQIDTE52.js} +2 -2
  28. package/dist/{chunk-POBO4G2P.js → chunk-LDIN2PLV.js} +250 -110
  29. package/dist/chunk-LDIN2PLV.js.map +1 -0
  30. package/dist/{chunk-KGDG6PWZ.js → chunk-LKDHAJJB.js} +2 -2
  31. package/dist/{chunk-T7PU3NPQ.js → chunk-NKGTEJYU.js} +7 -5
  32. package/dist/{chunk-T7PU3NPQ.js.map → chunk-NKGTEJYU.js.map} +1 -1
  33. package/dist/chunk-QGODFK36.js +122 -0
  34. package/dist/chunk-QGODFK36.js.map +1 -0
  35. package/dist/{chunk-DAMRMAM2.js → chunk-QSNAFOE6.js} +12369 -14499
  36. package/dist/chunk-QSNAFOE6.js.map +1 -0
  37. package/dist/chunk-TL2GJMJ5.js +700 -0
  38. package/dist/chunk-TL2GJMJ5.js.map +1 -0
  39. package/dist/{chunk-WSK4VSXP.js → chunk-V6MSPUQF.js} +2 -2
  40. package/dist/chunk-VZ2YOQWU.js +90 -0
  41. package/dist/chunk-VZ2YOQWU.js.map +1 -0
  42. package/dist/{chunk-5VZLXMO7.js → chunk-WSYJN7BI.js} +7 -6
  43. package/dist/chunk-WSYJN7BI.js.map +1 -0
  44. package/dist/chunk-Y477EGI4.js +356 -0
  45. package/dist/chunk-Y477EGI4.js.map +1 -0
  46. package/dist/{chunk-HH5LVGEE.js → chunk-Z4OZ25VS.js} +4 -4
  47. package/dist/cli-circuit-breaker-6EJO3PPU.js +13 -0
  48. package/dist/cli.js +123 -68
  49. package/dist/cli.js.map +1 -1
  50. package/dist/codebase-search-CZUA37RU.js +9 -0
  51. package/dist/{composite-router-YPRWVTRB.js → composite-router-JD7URTC2.js} +2 -2
  52. package/dist/{consensus-vote-DBE6RNZG.js → consensus-vote-COW34Q2Y.js} +7 -5
  53. package/dist/{dist-7PQR2BQB.js → dist-CV74KUT7.js} +1302 -805
  54. package/dist/dist-CV74KUT7.js.map +1 -0
  55. package/dist/{doctor-deep-AWE7SRU6.js → doctor-deep-4A4X5X6U.js} +3 -3
  56. package/dist/expert-bridge-J36C7VES.js +10 -0
  57. package/dist/{expert-config-FHNBQRX2.js → expert-config-MQ5OJE3U.js} +2 -2
  58. package/dist/{factory-O5C7ZBZO.js → factory-4Z4RSUYE.js} +5 -4
  59. package/dist/{factory-PCHGQ3ZG.js → factory-NHORX63J.js} +4 -3
  60. package/dist/index.d.ts +507 -42
  61. package/dist/index.js +331 -78
  62. package/dist/index.js.map +1 -1
  63. package/dist/issue-triage-TIG3RKXF.js +15 -0
  64. package/dist/{mcp-config-AUZQPUBY.js → mcp-config-ETY7GFGW.js} +3 -3
  65. package/dist/mobimem-5PAAMVFR.js +13 -0
  66. package/dist/mobimem-5PAAMVFR.js.map +1 -0
  67. package/dist/repo-analyze-HWMXSK5C.js +24 -0
  68. package/dist/repo-analyze-HWMXSK5C.js.map +1 -0
  69. package/dist/repo-security-plan-KQB3ZJTE.js +17 -0
  70. package/dist/repo-security-plan-KQB3ZJTE.js.map +1 -0
  71. package/dist/research-helpers-synthesize-ZMERZZ5B.js +10 -0
  72. package/dist/research-helpers-synthesize-ZMERZZ5B.js.map +1 -0
  73. package/dist/{routing-memory-QY3XMU2R.js → routing-memory-3ES3OHLM.js} +2 -2
  74. package/dist/routing-memory-3ES3OHLM.js.map +1 -0
  75. package/dist/{session-memory-3MBCE5KS.js → session-memory-E2OE2CYR.js} +3 -3
  76. package/dist/session-memory-E2OE2CYR.js.map +1 -0
  77. package/dist/{setup-command-IQ4MD3FT.js → setup-command-CMCQRBJF.js} +7 -6
  78. package/dist/setup-command-CMCQRBJF.js.map +1 -0
  79. package/dist/{setup-config-5YUPLDXF.js → setup-config-KITOPV7V.js} +3 -3
  80. package/dist/setup-config-KITOPV7V.js.map +1 -0
  81. package/dist/shared-memory-AEO2HJLC.js +8 -0
  82. package/dist/shared-memory-AEO2HJLC.js.map +1 -0
  83. package/dist/symbol-extractor-UEBANFSN.js +10 -0
  84. package/dist/symbol-extractor-UEBANFSN.js.map +1 -0
  85. package/dist/{weather-report-CC2C4KAX.js → weather-report-KUSVNXDZ.js} +2 -2
  86. package/dist/weather-report-KUSVNXDZ.js.map +1 -0
  87. package/package.json +14 -13
  88. package/dist/chunk-5VZLXMO7.js.map +0 -1
  89. package/dist/chunk-7F6HYUIY.js.map +0 -1
  90. package/dist/chunk-DAMRMAM2.js.map +0 -1
  91. package/dist/chunk-IMWYKX4H.js.map +0 -1
  92. package/dist/chunk-POBO4G2P.js.map +0 -1
  93. package/dist/chunk-S3BKWNST.js.map +0 -1
  94. package/dist/chunk-ZBZJHXRT.js.map +0 -1
  95. package/dist/dist-7PQR2BQB.js.map +0 -1
  96. /package/dist/{composite-router-YPRWVTRB.js.map → adaptive-memory-5VP5WWTE.js.map} +0 -0
  97. /package/dist/{chunk-HWDBNDUX.js.map → chunk-63AJLNKU.js.map} +0 -0
  98. /package/dist/{chunk-I6YDS23R.js.map → chunk-KQIDTE52.js.map} +0 -0
  99. /package/dist/{chunk-KGDG6PWZ.js.map → chunk-LKDHAJJB.js.map} +0 -0
  100. /package/dist/{chunk-WSK4VSXP.js.map → chunk-V6MSPUQF.js.map} +0 -0
  101. /package/dist/{chunk-HH5LVGEE.js.map → chunk-Z4OZ25VS.js.map} +0 -0
  102. /package/dist/{consensus-vote-DBE6RNZG.js.map → cli-circuit-breaker-6EJO3PPU.js.map} +0 -0
  103. /package/dist/{doctor-deep-AWE7SRU6.js.map → codebase-search-CZUA37RU.js.map} +0 -0
  104. /package/dist/{expert-config-FHNBQRX2.js.map → composite-router-JD7URTC2.js.map} +0 -0
  105. /package/dist/{factory-O5C7ZBZO.js.map → consensus-vote-COW34Q2Y.js.map} +0 -0
  106. /package/dist/{factory-PCHGQ3ZG.js.map → doctor-deep-4A4X5X6U.js.map} +0 -0
  107. /package/dist/{mcp-config-AUZQPUBY.js.map → expert-bridge-J36C7VES.js.map} +0 -0
  108. /package/dist/{routing-memory-QY3XMU2R.js.map → expert-config-MQ5OJE3U.js.map} +0 -0
  109. /package/dist/{session-memory-3MBCE5KS.js.map → factory-4Z4RSUYE.js.map} +0 -0
  110. /package/dist/{setup-command-IQ4MD3FT.js.map → factory-NHORX63J.js.map} +0 -0
  111. /package/dist/{setup-config-5YUPLDXF.js.map → issue-triage-TIG3RKXF.js.map} +0 -0
  112. /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