nexus-agents 2.90.0 → 2.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/{chunk-EUR32RSL.js → chunk-HPV2D4HN.js} +7 -5
  2. package/dist/{chunk-EUR32RSL.js.map → chunk-HPV2D4HN.js.map} +1 -1
  3. package/dist/{chunk-L3S7HOAS.js → chunk-L6JZCAFH.js} +146 -620
  4. package/dist/chunk-L6JZCAFH.js.map +1 -0
  5. package/dist/{chunk-IBRZK662.js → chunk-LOBVTRQQ.js} +3 -3
  6. package/dist/chunk-NR4NSTJH.js +547 -0
  7. package/dist/chunk-NR4NSTJH.js.map +1 -0
  8. package/dist/{chunk-3RZWLQSC.js → chunk-RS6RGSYG.js} +31 -2
  9. package/dist/chunk-RS6RGSYG.js.map +1 -0
  10. package/dist/{chunk-X2M7OF27.js → chunk-S4UF577T.js} +3 -1
  11. package/dist/chunk-S4UF577T.js.map +1 -0
  12. package/dist/{chunk-3NNVITJF.js → chunk-VW2LBC7B.js} +2 -2
  13. package/dist/cli.js +58 -18
  14. package/dist/cli.js.map +1 -1
  15. package/dist/index.js +19 -17
  16. package/dist/index.js.map +1 -1
  17. package/dist/{issue-triage-J2MUXK6R.js → issue-triage-FVZOVIRX.js} +3 -2
  18. package/dist/{pr-reviewer-helpers-WYPUYQ2U.js → pr-reviewer-helpers-7D2W5HTO.js} +10 -3
  19. package/dist/{setup-command-Z7TRZUDI.js → setup-command-UN3XDJ2H.js} +3 -3
  20. package/package.json +1 -1
  21. package/dist/chunk-3RZWLQSC.js.map +0 -1
  22. package/dist/chunk-L3S7HOAS.js.map +0 -1
  23. package/dist/chunk-X2M7OF27.js.map +0 -1
  24. /package/dist/{chunk-IBRZK662.js.map → chunk-LOBVTRQQ.js.map} +0 -0
  25. /package/dist/{chunk-3NNVITJF.js.map → chunk-VW2LBC7B.js.map} +0 -0
  26. /package/dist/{issue-triage-J2MUXK6R.js.map → issue-triage-FVZOVIRX.js.map} +0 -0
  27. /package/dist/{pr-reviewer-helpers-WYPUYQ2U.js.map → pr-reviewer-helpers-7D2W5HTO.js.map} +0 -0
  28. /package/dist/{setup-command-Z7TRZUDI.js.map → setup-command-UN3XDJ2H.js.map} +0 -0
@@ -3,10 +3,37 @@ import {
3
3
  DECISION_EMOJI,
4
4
  SEVERITY_EMOJI,
5
5
  SEVERITY_ORDER
6
- } from "./chunk-X2M7OF27.js";
6
+ } from "./chunk-S4UF577T.js";
7
+ import {
8
+ assessReputation,
9
+ sanitizeInput
10
+ } from "./chunk-NR4NSTJH.js";
7
11
 
8
12
  // src/dogfooding/pr-reviewer-helpers.ts
9
13
  import { randomUUID } from "crypto";
14
+ function assessPRReputation(pr, cache, enableReputation) {
15
+ if (!enableReputation) return void 0;
16
+ const sanitizeResult = sanitizeInput(pr.body, "unknown", pr.author);
17
+ const metadata = {
18
+ username: pr.author,
19
+ authorAssociation: pr.authorAssociation,
20
+ injectionFlags: sanitizeResult.injectionFlags
21
+ };
22
+ return assessReputation(metadata, cache);
23
+ }
24
+ function buildPRTrustAssessment(trustResult, reputation, gateDecision) {
25
+ const isTier1 = trustResult.trustTier === "1";
26
+ return {
27
+ trustTier: trustResult.trustTier,
28
+ userRole: trustResult.userRole,
29
+ isAllowlisted: trustResult.isAllowlisted,
30
+ reputationScore: reputation?.reputationScore,
31
+ isSuspicious: isTier1 ? false : reputation?.isSuspicious ?? false,
32
+ enforcedTrustTier: gateDecision.enforcedTier,
33
+ reputationReconciledTier: gateDecision.reconciledTier,
34
+ gatingMode: gateDecision.mode
35
+ };
36
+ }
10
37
  function parseSeverity(value) {
11
38
  if (typeof value === "string") {
12
39
  const lower = value.toLowerCase();
@@ -201,6 +228,8 @@ function createFailedReview(expertId, category, durationMs, error) {
201
228
  }
202
229
 
203
230
  export {
231
+ assessPRReputation,
232
+ buildPRTrustAssessment,
204
233
  parseSeverity,
205
234
  parseCategory,
206
235
  extractSummary,
@@ -216,4 +245,4 @@ export {
216
245
  formatReviewComment,
217
246
  createFailedReview
218
247
  };
219
- //# sourceMappingURL=chunk-3RZWLQSC.js.map
248
+ //# sourceMappingURL=chunk-RS6RGSYG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dogfooding/pr-reviewer-helpers.ts"],"sourcesContent":["/**\n * nexus-agents/dogfooding - PR Reviewer Helpers\n *\n * Helper functions for PR review formatting and aggregation.\n *\n * @module dogfooding/pr-reviewer-helpers\n * (Source: Issue #161, Alignment Roadmap Phase 3)\n */\n\nimport { randomUUID } from 'node:crypto';\nimport type {\n PRMetadata,\n PRReviewResult,\n PRTrustAssessment,\n ExpertReviewResult,\n ReviewFinding,\n ReviewCategory,\n ReviewSeverity,\n ReviewDecision,\n} from './pr-review-types.js';\nimport {\n SEVERITY_ORDER,\n CATEGORY_DISPLAY_NAMES,\n SEVERITY_EMOJI,\n DECISION_EMOJI,\n} from './pr-review-types.js';\nimport { sanitizeInput } from '../security/input-sanitizer.js';\nimport { assessReputation } from '../security/reputation-model.js';\nimport type {\n ReputationCache,\n ReputationGateDecision,\n ReputationAssessment,\n GitHubUserMetadata,\n} from '../security/reputation-model.js';\nimport type { ClassifyResult } from '../security/trust-classifier.js';\n\n// =============================================================================\n// Reputation Gating Helpers (#3123, epic #3118 Phase 5)\n// =============================================================================\n\n/**\n * Assesses the PR author's reputation from the signals available in the PR\n * event: author association + injection flags from the (sanitized) PR body.\n * Account-age / contribution signals are NOT fetched here yet — OMITTED, never\n * fabricated, so the engine skips those signals. Returns undefined when\n * reputation is disabled.\n */\nexport function assessPRReputation(\n pr: PRMetadata,\n cache: ReputationCache,\n enableReputation: boolean\n): ReputationAssessment | undefined {\n if (!enableReputation) return undefined;\n // Only `injectionFlags` is consumed here, and injection detection is\n // role-independent — the userRole arg ('unknown') and the sanitizer's own\n // trustTier output are intentionally irrelevant to this call.\n const sanitizeResult = sanitizeInput(pr.body, 'unknown', pr.author);\n const metadata: GitHubUserMetadata = {\n username: pr.author,\n authorAssociation: pr.authorAssociation,\n injectionFlags: sanitizeResult.injectionFlags,\n };\n return assessReputation(metadata, cache);\n}\n\n/** Builds the observability assessment surfaced on the review result (#3123). */\nexport function buildPRTrustAssessment(\n trustResult: ClassifyResult,\n reputation: ReputationAssessment | undefined,\n gateDecision: ReputationGateDecision\n): PRTrustAssessment {\n // Tier-1 (owner/allowlisted) authors cannot be suspicious.\n const isTier1 = trustResult.trustTier === '1';\n return {\n trustTier: trustResult.trustTier,\n userRole: trustResult.userRole,\n isAllowlisted: trustResult.isAllowlisted,\n reputationScore: reputation?.reputationScore,\n isSuspicious: isTier1 ? false : (reputation?.isSuspicious ?? false),\n enforcedTrustTier: gateDecision.enforcedTier,\n reputationReconciledTier: gateDecision.reconciledTier,\n gatingMode: gateDecision.mode,\n };\n}\n\n// =============================================================================\n// Parsing Helpers\n// =============================================================================\n\nexport function parseSeverity(value: unknown): ReviewSeverity {\n if (typeof value === 'string') {\n const lower = value.toLowerCase();\n if (lower in SEVERITY_ORDER) return lower as ReviewSeverity;\n }\n return 'medium';\n}\n\nexport function parseCategory(value: unknown): ReviewCategory {\n if (typeof value === 'string') {\n const lower = value.toLowerCase();\n if (lower in CATEGORY_DISPLAY_NAMES) return lower as ReviewCategory;\n }\n return 'code_quality';\n}\n\nexport function extractSummary(output: Record<string, unknown>): string {\n if (typeof output.summary === 'string') return output.summary;\n if (typeof output.content === 'string') return output.content;\n if (typeof output.message === 'string') return output.message;\n return 'Review completed';\n}\n\nexport function extractStringField(\n record: Record<string, unknown>,\n ...keys: string[]\n): string | undefined {\n for (const key of keys) {\n const value = record[key];\n if (typeof value === 'string') return value;\n }\n return undefined;\n}\n\n// =============================================================================\n// Finding Parsing\n// =============================================================================\n\nexport function parseFindings(\n output: Record<string, unknown>,\n expertId: string,\n minSeverity: ReviewSeverity\n): ReviewFinding[] {\n const minOrder = SEVERITY_ORDER[minSeverity];\n const sources = collectSources(output);\n\n const findings: ReviewFinding[] = [];\n for (const source of sources) {\n if (!Array.isArray(source)) continue;\n for (const item of source) {\n const finding = parseOneFinding(item, expertId, minOrder);\n if (finding !== null) findings.push(finding);\n }\n }\n return findings;\n}\n\nfunction collectSources(output: Record<string, unknown>): unknown[] {\n return [\n output.findings,\n output.vulnerabilities,\n output.issues,\n (output as { content?: { findings?: unknown } }).content,\n ];\n}\n\nfunction parseOneFinding(item: unknown, expertId: string, minOrder: number): ReviewFinding | null {\n if (typeof item !== 'object' || item === null) return null;\n\n const record = item as Record<string, unknown>;\n const severity = parseSeverity(record.severity);\n if (SEVERITY_ORDER[severity] < minOrder) return null;\n\n return {\n id: randomUUID(),\n category: parseCategory(record.category),\n severity,\n title: extractStringField(record, 'title', 'name') ?? 'Finding',\n description: extractStringField(record, 'description', 'message') ?? '',\n file: typeof record.file === 'string' ? record.file : undefined,\n line: typeof record.line === 'number' ? record.line : undefined,\n suggestion: typeof record.suggestion === 'string' ? record.suggestion : undefined,\n expertId,\n confidence: typeof record.confidence === 'number' ? record.confidence : 0.7,\n };\n}\n\n// =============================================================================\n// Decision Helpers\n// =============================================================================\n\nexport function determineApproval(findings: ReviewFinding[]): boolean {\n const hasBlocking = findings.some((f) => f.severity === 'critical' || f.severity === 'high');\n return !hasBlocking;\n}\n\nexport function determineDecision(\n reviews: ExpertReviewResult[],\n findings: ReviewFinding[]\n): ReviewDecision {\n const hasCritical = findings.some((f) => f.severity === 'critical');\n const hasHigh = findings.some((f) => f.severity === 'high');\n const allApproved = reviews.every((r) => r.approved);\n\n if (hasCritical) return 'request_changes';\n if (hasHigh && !allApproved) return 'request_changes';\n if (findings.length > 0) return 'comment';\n return 'approve';\n}\n\nexport function calculateConsensus(reviews: ExpertReviewResult[]): number {\n if (reviews.length === 0) return 1;\n const approvals = reviews.filter((r) => r.approved).length;\n return approvals / reviews.length;\n}\n\n// =============================================================================\n// Counting Helpers\n// =============================================================================\n\nexport function countBySeverity(findings: ReviewFinding[]): Record<ReviewSeverity, number> {\n const counts: Record<ReviewSeverity, number> = {\n critical: 0,\n high: 0,\n medium: 0,\n low: 0,\n info: 0,\n };\n\n for (const f of findings) {\n counts[f.severity]++;\n }\n\n return counts;\n}\n\nexport function countByCategory(findings: ReviewFinding[]): Record<ReviewCategory, number> {\n const counts: Record<ReviewCategory, number> = {\n security: 0,\n performance: 0,\n code_quality: 0,\n testing: 0,\n documentation: 0,\n architecture: 0,\n };\n\n for (const f of findings) {\n counts[f.category]++;\n }\n\n return counts;\n}\n\nexport function sumFindings(counts: Record<ReviewSeverity, number>): number {\n return Object.values(counts).reduce((a, b) => a + b, 0);\n}\n\n// =============================================================================\n// Summary Generation\n// =============================================================================\n\nexport function generateSummary(\n pr: PRMetadata,\n reviews: ExpertReviewResult[],\n decision: ReviewDecision\n): string {\n const expertSummaries = reviews\n .map((r) => `- **${CATEGORY_DISPLAY_NAMES[r.expertType as ReviewCategory]}**: ${r.summary}`)\n .join('\\n');\n\n return `Reviewed PR #${String(pr.number)}: ${pr.title}\n\n**Decision:** ${decision.replaceAll('_', ' ')}\n**Experts consulted:** ${String(reviews.length)}\n\n${expertSummaries}`;\n}\n\n// =============================================================================\n// GitHub Comment Formatting\n// =============================================================================\n\n/**\n * Formats the review result as a GitHub comment.\n */\nexport function formatReviewComment(result: PRReviewResult): string {\n const emoji = DECISION_EMOJI[result.decision];\n const decisionText = result.decision.replaceAll('_', ' ').toUpperCase();\n\n const findingsSection = formatFindingsSection(result);\n const statsSection = formatStatsSection(result);\n\n return `## ${emoji} Nexus Agents Review: ${decisionText}\n\n${result.summary}\n\n${findingsSection}\n\n${statsSection}\n\n---\n*Reviewed by [nexus-agents](https://github.com/nexus-substrate/nexus-agents) in ${String(result.totalDurationMs)}ms*`;\n}\n\nfunction formatFindingsSection(result: PRReviewResult): string {\n const allFindings = result.expertReviews.flatMap((r) => r.findings);\n\n if (allFindings.length === 0) {\n return '_No issues found._';\n }\n\n const sorted = [...allFindings].sort(\n (a, b) => SEVERITY_ORDER[b.severity] - SEVERITY_ORDER[a.severity]\n );\n\n const lines = ['### Findings', ''];\n\n for (const f of sorted) {\n const emoji = SEVERITY_EMOJI[f.severity];\n const loc =\n f.file !== undefined\n ? ` (\\`${f.file}${f.line !== undefined ? `:${String(f.line)}` : ''}\\`)`\n : '';\n lines.push(`${emoji} **${f.title}**${loc}`);\n lines.push(`> ${f.description}`);\n if (f.suggestion !== undefined) {\n lines.push(`> 💡 ${f.suggestion}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\nfunction formatStatsSection(result: PRReviewResult): string {\n const { findingsBySeverity } = result;\n const total = sumFindings(findingsBySeverity);\n\n const parts: string[] = [];\n for (const severity of ['critical', 'high', 'medium', 'low', 'info'] as ReviewSeverity[]) {\n const count = findingsBySeverity[severity];\n if (count > 0) {\n parts.push(`${SEVERITY_EMOJI[severity]} ${String(count)} ${severity}`);\n }\n }\n\n return `<details>\n<summary>Review Statistics (${String(total)} findings)</summary>\n\n- Experts: ${String(result.expertCount)}\n- Consensus: ${(result.consensusScore * 100).toFixed(0)}%\n- Duration: ${String(result.totalDurationMs)}ms\n- Findings: ${parts.join(', ') || 'none'}\n\n</details>`;\n}\n\n// =============================================================================\n// Failed Review Factory\n// =============================================================================\n\nexport function createFailedReview(\n expertId: string,\n category: ReviewCategory,\n durationMs: number,\n error: string\n): ExpertReviewResult {\n return {\n expertId,\n expertType: category,\n approved: true, // Don't block on failures\n summary: `Review failed: ${error}`,\n findings: [],\n durationMs,\n confidence: 0,\n };\n}\n"],"mappings":"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","names":[]}
@@ -7,6 +7,7 @@ var DEFAULT_PR_REVIEW_CONFIG = {
7
7
  minSeverity: "low",
8
8
  enableInlineComments: true,
9
9
  dryRun: false,
10
+ enableReputation: true,
10
11
  modelConfig: {
11
12
  temperature: 0.3,
12
13
  maxTokens: 8192
@@ -28,6 +29,7 @@ var PRReviewConfigSchema = z.object({
28
29
  minSeverity: z.enum(["critical", "high", "medium", "low", "info"]).default("low"),
29
30
  enableInlineComments: z.boolean().default(true),
30
31
  dryRun: z.boolean().default(false),
32
+ enableReputation: z.boolean().default(true),
31
33
  githubToken: z.string().optional(),
32
34
  modelConfig: z.object({
33
35
  temperature: z.number().min(0).max(2).default(0.3),
@@ -69,4 +71,4 @@ export {
69
71
  SEVERITY_EMOJI,
70
72
  DECISION_EMOJI
71
73
  };
72
- //# sourceMappingURL=chunk-X2M7OF27.js.map
74
+ //# sourceMappingURL=chunk-S4UF577T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dogfooding/pr-review-types.ts"],"sourcesContent":["/**\n * nexus-agents/dogfooding - PR Review Types\n *\n * Type definitions for automated pull request review using\n * multi-agent collaboration.\n *\n * @module dogfooding/pr-review-types\n * (Source: Issue #161, Alignment Roadmap Phase 3)\n */\n\nimport { z } from 'zod';\n\n/**\n * Pull request file change information.\n */\nexport interface PRFileChange {\n /** File path */\n readonly filename: string;\n /** Change status */\n readonly status: 'added' | 'removed' | 'modified' | 'renamed' | 'copied';\n /** Number of additions */\n readonly additions: number;\n /** Number of deletions */\n readonly deletions: number;\n /** File patch/diff content */\n readonly patch?: string | undefined;\n /** Previous filename if renamed */\n readonly previousFilename?: string | undefined;\n}\n\n/**\n * Pull request metadata from GitHub API.\n */\nexport interface PRMetadata {\n /** PR number */\n readonly number: number;\n /** PR title */\n readonly title: string;\n /** PR description/body */\n readonly body: string;\n /** Author username */\n readonly author: string;\n /** GitHub API author_association (e.g., 'OWNER', 'COLLABORATOR', 'NONE') */\n readonly authorAssociation: string;\n /** Base branch */\n readonly base: string;\n /** Head branch */\n readonly head: string;\n /** Repository owner */\n readonly owner: string;\n /** Repository name */\n readonly repo: string;\n /** PR URL */\n readonly url: string;\n /** Whether PR is draft */\n readonly draft: boolean;\n /** PR labels */\n readonly labels: readonly string[];\n /** Changed files */\n readonly files: readonly PRFileChange[];\n /** Total additions */\n readonly additions: number;\n /** Total deletions */\n readonly deletions: number;\n /** Commit SHA of head */\n readonly headSha: string;\n}\n\n/**\n * Review severity levels.\n */\nexport type ReviewSeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';\n\n/**\n * Review category for organizing findings.\n */\nexport type ReviewCategory =\n | 'security'\n | 'performance'\n | 'code_quality'\n | 'testing'\n | 'documentation'\n | 'architecture';\n\n/**\n * Individual review finding from an expert.\n */\nexport interface ReviewFinding {\n /** Unique finding ID */\n readonly id: string;\n /** Category of finding */\n readonly category: ReviewCategory;\n /** Severity level */\n readonly severity: ReviewSeverity;\n /** Finding title */\n readonly title: string;\n /** Detailed description */\n readonly description: string;\n /** Affected file (if applicable) */\n readonly file?: string | undefined;\n /** Line number (if applicable) */\n readonly line?: number | undefined;\n /** Suggested fix */\n readonly suggestion?: string | undefined;\n /** Expert that found this issue */\n readonly expertId: string;\n /** Confidence score (0-1) */\n readonly confidence: number;\n}\n\n/**\n * Review result from a single expert agent.\n */\nexport interface ExpertReviewResult {\n /** Expert ID */\n readonly expertId: string;\n /** Expert role/type */\n readonly expertType: string;\n /** Overall approval */\n readonly approved: boolean;\n /** Review summary */\n readonly summary: string;\n /** Individual findings */\n readonly findings: readonly ReviewFinding[];\n /** Execution time in ms */\n readonly durationMs: number;\n /** Confidence in review (0-1) */\n readonly confidence: number;\n}\n\n/**\n * Aggregated review decision after multi-agent debate.\n */\nexport type ReviewDecision = 'approve' | 'request_changes' | 'comment';\n\n/**\n * Complete PR review result from multi-agent collaboration.\n */\n/**\n * Trust + reputation assessment of the PR author, surfaced for observability\n * (#3123, epic #3118 Phase 5). Mirrors `issue_triage`'s assessment.\n */\nexport interface PRTrustAssessment {\n /** Classifier trust tier (1-4) from author association. */\n readonly trustTier: string;\n /** Author's GitHub role. */\n readonly userRole: string;\n /** Whether the author is on the maintainer allowlist. */\n readonly isAllowlisted: boolean;\n /** Reputation score (0-100) when reputation is enabled. */\n readonly reputationScore?: number | undefined;\n /** Whether the author is flagged as suspicious. */\n readonly isSuspicious: boolean;\n /** Tier the policy gate ACTUALLY enforced (== trustTier under audit/off). */\n readonly enforcedTrustTier?: string | undefined;\n /** Tier reputation reconciliation computed — what enforce mode WOULD gate on. */\n readonly reputationReconciledTier?: string | undefined;\n /** Reputation-gating rollout mode applied: `off` | `audit` | `enforce`. */\n readonly gatingMode?: string | undefined;\n}\n\nexport interface PRReviewResult {\n /** Pull request number */\n readonly prNumber: number;\n /** Repository full name (owner/repo) */\n readonly repository: string;\n /** Overall decision */\n readonly decision: ReviewDecision;\n /** Executive summary */\n readonly summary: string;\n /** Individual expert reviews */\n readonly expertReviews: readonly ExpertReviewResult[];\n /** Aggregated findings by severity */\n readonly findingsBySeverity: Readonly<Record<ReviewSeverity, number>>;\n /** Aggregated findings by category */\n readonly findingsByCategory: Readonly<Record<ReviewCategory, number>>;\n /** Total execution time in ms */\n readonly totalDurationMs: number;\n /** Number of experts that participated */\n readonly expertCount: number;\n /** Consensus score (0-1) */\n readonly consensusScore: number;\n /** Debate rounds completed */\n readonly debateRounds: number;\n /** Timestamp of review */\n readonly timestamp: string;\n /** Author trust + reputation assessment (#3123). */\n readonly trustAssessment: PRTrustAssessment;\n}\n\n/**\n * Configuration for PR review.\n */\nexport interface PRReviewConfig {\n /** Experts to include in review */\n readonly experts: readonly ReviewCategory[];\n /** Maximum debate rounds */\n readonly maxDebateRounds: number;\n /** Consensus threshold (0-1) */\n readonly consensusThreshold: number;\n /** Minimum severity to report */\n readonly minSeverity: ReviewSeverity;\n /** Whether to post inline comments */\n readonly enableInlineComments: boolean;\n /** Whether to run in dry-run mode (no GitHub posting) */\n readonly dryRun: boolean;\n /** Whether to assess author reputation and gate on it (#3123). */\n readonly enableReputation: boolean;\n /** GitHub token for API access */\n readonly githubToken?: string | undefined;\n /** Model adapter configuration */\n readonly modelConfig?: {\n readonly temperature?: number;\n readonly maxTokens?: number;\n };\n}\n\n/**\n * Default PR review configuration.\n */\nexport const DEFAULT_PR_REVIEW_CONFIG: PRReviewConfig = {\n experts: ['security', 'code_quality', 'testing'],\n maxDebateRounds: 3,\n consensusThreshold: 0.7,\n minSeverity: 'low',\n enableInlineComments: true,\n dryRun: false,\n enableReputation: true,\n modelConfig: {\n temperature: 0.3,\n maxTokens: 8192,\n },\n};\n\n/**\n * Zod schema for PR review configuration.\n */\nexport const PRReviewConfigSchema = z.object({\n experts: z\n .array(\n z.enum([\n 'security',\n 'performance',\n 'code_quality',\n 'testing',\n 'documentation',\n 'architecture',\n ])\n )\n .default(['security', 'code_quality', 'testing']),\n maxDebateRounds: z.number().int().min(1).max(10).default(3),\n consensusThreshold: z.number().min(0).max(1).default(0.7),\n minSeverity: z.enum(['critical', 'high', 'medium', 'low', 'info']).default('low'),\n enableInlineComments: z.boolean().default(true),\n dryRun: z.boolean().default(false),\n enableReputation: z.boolean().default(true),\n githubToken: z.string().optional(),\n modelConfig: z\n .object({\n temperature: z.number().min(0).max(2).default(0.3),\n maxTokens: z.number().int().positive().default(8192),\n })\n .optional(),\n});\n\n/**\n * Severity order for comparison.\n */\nexport const SEVERITY_ORDER: Record<ReviewSeverity, number> = {\n critical: 5,\n high: 4,\n medium: 3,\n low: 2,\n info: 1,\n};\n\n/**\n * Category display names.\n */\nexport const CATEGORY_DISPLAY_NAMES: Record<ReviewCategory, string> = {\n security: 'Security',\n performance: 'Performance',\n code_quality: 'Code Quality',\n testing: 'Testing',\n documentation: 'Documentation',\n architecture: 'Architecture',\n};\n\n/**\n * Severity emoji for GitHub comments.\n */\nexport const SEVERITY_EMOJI: Record<ReviewSeverity, string> = {\n critical: ':rotating_light:',\n high: ':warning:',\n medium: ':yellow_circle:',\n low: ':large_blue_circle:',\n info: ':information_source:',\n};\n\n/**\n * Decision emoji for GitHub comments.\n */\nexport const DECISION_EMOJI: Record<ReviewDecision, string> = {\n approve: ':white_check_mark:',\n request_changes: ':x:',\n comment: ':speech_balloon:',\n};\n"],"mappings":";AAUA,SAAS,SAAS;AAkNX,IAAM,2BAA2C;AAAA,EACtD,SAAS,CAAC,YAAY,gBAAgB,SAAS;AAAA,EAC/C,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,aAAa;AAAA,EACb,sBAAsB;AAAA,EACtB,QAAQ;AAAA,EACR,kBAAkB;AAAA,EAClB,aAAa;AAAA,IACX,aAAa;AAAA,IACb,WAAW;AAAA,EACb;AACF;AAKO,IAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,SAAS,EACN;AAAA,IACC,EAAE,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH,EACC,QAAQ,CAAC,YAAY,gBAAgB,SAAS,CAAC;AAAA,EAClD,iBAAiB,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,QAAQ,CAAC;AAAA,EAC1D,oBAAoB,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG;AAAA,EACxD,aAAa,EAAE,KAAK,CAAC,YAAY,QAAQ,UAAU,OAAO,MAAM,CAAC,EAAE,QAAQ,KAAK;AAAA,EAChF,sBAAsB,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,EAC9C,QAAQ,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACjC,kBAAkB,EAAE,QAAQ,EAAE,QAAQ,IAAI;AAAA,EAC1C,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACjC,aAAa,EACV,OAAO;AAAA,IACN,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG;AAAA,IACjD,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,IAAI;AAAA,EACrD,CAAC,EACA,SAAS;AACd,CAAC;AAKM,IAAM,iBAAiD;AAAA,EAC5D,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,MAAM;AACR;AAKO,IAAM,yBAAyD;AAAA,EACpE,UAAU;AAAA,EACV,aAAa;AAAA,EACb,cAAc;AAAA,EACd,SAAS;AAAA,EACT,eAAe;AAAA,EACf,cAAc;AAChB;AAKO,IAAM,iBAAiD;AAAA,EAC5D,UAAU;AAAA,EACV,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,KAAK;AAAA,EACL,MAAM;AACR;AAKO,IAAM,iBAAiD;AAAA,EAC5D,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,SAAS;AACX;","names":[]}
@@ -8,7 +8,7 @@ import {
8
8
  checkSqlite,
9
9
  defaultConfig,
10
10
  initDataDirectories
11
- } from "./chunk-IBRZK662.js";
11
+ } from "./chunk-LOBVTRQQ.js";
12
12
  import {
13
13
  probeAllClis
14
14
  } from "./chunk-X5BQF6HM.js";
@@ -1987,4 +1987,4 @@ export {
1987
1987
  setupCommand,
1988
1988
  setupCommandAsync
1989
1989
  };
1990
- //# sourceMappingURL=chunk-3NNVITJF.js.map
1990
+ //# sourceMappingURL=chunk-VW2LBC7B.js.map
package/dist/cli.js CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ assessPRReputation,
4
+ buildPRTrustAssessment,
3
5
  calculateConsensus,
4
6
  countByCategory,
5
7
  countBySeverity,
@@ -11,7 +13,7 @@ import {
11
13
  generateSummary,
12
14
  parseFindings,
13
15
  sumFindings
14
- } from "./chunk-3RZWLQSC.js";
16
+ } from "./chunk-RS6RGSYG.js";
15
17
  import "./chunk-AQTHLUCP.js";
16
18
  import {
17
19
  buildOpenAICompatAdapters,
@@ -22,7 +24,7 @@ import {
22
24
  import {
23
25
  setupCommandAsync,
24
26
  verifyCommand
25
- } from "./chunk-3NNVITJF.js";
27
+ } from "./chunk-VW2LBC7B.js";
26
28
  import "./chunk-2SMTCNXP.js";
27
29
  import {
28
30
  AuthHandler,
@@ -162,14 +164,14 @@ import {
162
164
  validateCommand,
163
165
  validateWorkflow,
164
166
  wrapInMarkdownFence
165
- } from "./chunk-EUR32RSL.js";
167
+ } from "./chunk-HPV2D4HN.js";
166
168
  import {
167
169
  resolveToken
168
170
  } from "./chunk-EAT4GTMT.js";
169
171
  import {
170
172
  CATEGORY_DISPLAY_NAMES,
171
173
  DEFAULT_PR_REVIEW_CONFIG
172
- } from "./chunk-X2M7OF27.js";
174
+ } from "./chunk-S4UF577T.js";
173
175
  import "./chunk-E6W4Z6GN.js";
174
176
  import "./chunk-3LKW4IEW.js";
175
177
  import {
@@ -205,10 +207,15 @@ import {
205
207
  classifyTrust,
206
208
  createFullGitHubProvider,
207
209
  evaluatePolicy,
208
- parsePRUrl,
209
- sanitizeInput
210
- } from "./chunk-L3S7HOAS.js";
210
+ parsePRUrl
211
+ } from "./chunk-L6JZCAFH.js";
211
212
  import "./chunk-EFHZHCF2.js";
213
+ import {
214
+ ReputationCache,
215
+ gateWithReputation,
216
+ resolveReputationGatingMode,
217
+ sanitizeInput
218
+ } from "./chunk-NR4NSTJH.js";
212
219
  import "./chunk-57HMLBYN.js";
213
220
  import "./chunk-HFOQKCD2.js";
214
221
  import "./chunk-3ACDP4E6.js";
@@ -235,7 +242,7 @@ import {
235
242
  loadConfig,
236
243
  runDoctor,
237
244
  validateNexusEnv
238
- } from "./chunk-IBRZK662.js";
245
+ } from "./chunk-LOBVTRQQ.js";
239
246
  import "./chunk-SFZESA5O.js";
240
247
  import "./chunk-6Q33PBXC.js";
241
248
  import {
@@ -2840,10 +2847,12 @@ var PRReviewer = class {
2840
2847
  config;
2841
2848
  observer;
2842
2849
  adapter;
2850
+ reputationCache;
2843
2851
  constructor(config, adapter) {
2844
2852
  this.config = { ...DEFAULT_PR_REVIEW_CONFIG, ...config };
2845
2853
  this.observer = new SwarmObserver();
2846
2854
  this.adapter = adapter ?? void 0;
2855
+ this.reputationCache = new ReputationCache();
2847
2856
  }
2848
2857
  /**
2849
2858
  * Reviews a pull request using multi-agent collaboration.
@@ -2866,10 +2875,11 @@ var PRReviewer = class {
2866
2875
  userRole: trustResult.userRole,
2867
2876
  isAllowlisted: trustResult.isAllowlisted
2868
2877
  });
2878
+ const { gateDecision, trustAssessment } = this.gatePRAuthor(prMetadata, trustResult);
2869
2879
  const expertReviews = await this.runExpertReviews(prMetadata, traceId);
2870
- const result = this.aggregateReviews(prMetadata, expertReviews, startTime);
2880
+ const result = this.aggregateReviews(prMetadata, expertReviews, startTime, trustAssessment);
2871
2881
  if (!this.config.dryRun) {
2872
- await this.postReviewToGitHub(provider, parseResult.value, result, trustResult);
2882
+ await this.postReviewToGitHub(provider, parseResult.value, result, gateDecision);
2873
2883
  }
2874
2884
  logger2.info("PR review completed", {
2875
2885
  prNumber,
@@ -2958,6 +2968,32 @@ var PRReviewer = class {
2958
2968
  authorAssociation: pr.authorAssociation
2959
2969
  });
2960
2970
  }
2971
+ /**
2972
+ * Assesses the PR author's reputation and applies the gating rollout mode
2973
+ * (#3123). Returns the gate decision (for the policy gate) and the assessment
2974
+ * surfaced on the result. A suppressed demotion (audit/off) is logged.
2975
+ */
2976
+ gatePRAuthor(pr, trustResult) {
2977
+ const reputation = assessPRReputation(pr, this.reputationCache, this.config.enableReputation);
2978
+ const gateDecision = gateWithReputation(
2979
+ trustResult.trustTier,
2980
+ reputation,
2981
+ resolveReputationGatingMode()
2982
+ );
2983
+ if (gateDecision.demotionSuppressed) {
2984
+ logger2.warn("Reputation demotion suppressed by gating mode (would block under enforce)", {
2985
+ prNumber: pr.number,
2986
+ author: pr.author,
2987
+ mode: gateDecision.mode,
2988
+ classifierTier: trustResult.trustTier,
2989
+ reconciledTier: gateDecision.reconciledTier
2990
+ });
2991
+ }
2992
+ return {
2993
+ gateDecision,
2994
+ trustAssessment: buildPRTrustAssessment(trustResult, reputation, gateDecision)
2995
+ };
2996
+ }
2961
2997
  /**
2962
2998
  * Sanitizes untrusted PR content (body/title) before embedding in expert tasks.
2963
2999
  * Uses the security/input-sanitizer pipeline to strip injection vectors.
@@ -3053,7 +3089,7 @@ ${file.patch}
3053
3089
  /**
3054
3090
  * Aggregates individual expert reviews into final result.
3055
3091
  */
3056
- aggregateReviews(pr, reviews, startTime) {
3092
+ aggregateReviews(pr, reviews, startTime, trustAssessment) {
3057
3093
  const allFindings = reviews.flatMap((r) => r.findings);
3058
3094
  const decision = determineDecision(reviews, allFindings);
3059
3095
  const consensusScore = calculateConsensus(reviews);
@@ -3069,7 +3105,8 @@ ${file.patch}
3069
3105
  expertCount: reviews.length,
3070
3106
  consensusScore,
3071
3107
  debateRounds: 1,
3072
- timestamp: getTimeProvider().nowIso()
3108
+ timestamp: getTimeProvider().nowIso(),
3109
+ trustAssessment
3073
3110
  };
3074
3111
  }
3075
3112
  /**
@@ -3079,8 +3116,8 @@ ${file.patch}
3079
3116
  * from the untrusted PR author.
3080
3117
  * (Source: Issue #828 — Wire policy gate into production pipeline)
3081
3118
  */
3082
- async postReviewToGitHub(provider, pr, result, trustResult) {
3083
- const policyResult = this.auditReviewAction(trustResult);
3119
+ async postReviewToGitHub(provider, pr, result, gateDecision) {
3120
+ const policyResult = this.auditReviewAction(gateDecision.enforcedTier);
3084
3121
  if (policyResult.hasRuleOfTwoViolation) {
3085
3122
  logger2.warn("Rule of Two: review posting blocked", {
3086
3123
  prNumber: pr.prNumber,
@@ -3094,17 +3131,20 @@ ${file.patch}
3094
3131
  violations: policyResult.violations
3095
3132
  });
3096
3133
  }
3097
- const { formatReviewComment: formatReviewComment2 } = await import("./pr-reviewer-helpers-WYPUYQ2U.js");
3134
+ const { formatReviewComment: formatReviewComment2 } = await import("./pr-reviewer-helpers-7D2W5HTO.js");
3098
3135
  const body = formatReviewComment2(result);
3099
3136
  const postResult = await provider.createReview(pr.prNumber, body, result.decision);
3100
3137
  if (!postResult.ok) {
3101
3138
  logger2.error("Failed to post review", postResult.error);
3102
3139
  }
3103
3140
  }
3104
- /** Audits review posting against the policy gate for logging. */
3105
- auditReviewAction(trustResult) {
3141
+ /**
3142
+ * Audits review posting against the policy gate. Gates on the reputation-
3143
+ * reconciled `enforcedTier` (#3123) rather than the raw classifier tier.
3144
+ */
3145
+ auditReviewAction(enforcedTier) {
3106
3146
  const context = {
3107
- inputTrustTier: trustResult.trustTier,
3147
+ inputTrustTier: enforcedTier,
3108
3148
  hasWriteAccess: true,
3109
3149
  hasSecretAccess: true
3110
3150
  };