nexus-agents 8.19.0 → 8.19.1

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 (31) hide show
  1. package/dist/{chunk-3W4L63ML.js → chunk-2G2ZXAD7.js} +1 -1
  2. package/dist/chunk-2G2ZXAD7.js.map +1 -0
  3. package/dist/chunk-CKTLFPT3.js +7 -0
  4. package/dist/{chunk-6B7E372N.js → chunk-DRD2GXBT.js} +2 -2
  5. package/dist/{chunk-2WAF3ZFK.js → chunk-KRCRVSGX.js} +3 -3
  6. package/dist/{chunk-KJN5RWXY.js → chunk-PODRYCRR.js} +41 -24
  7. package/dist/chunk-PODRYCRR.js.map +1 -0
  8. package/dist/{chunk-5LAPFTNG.js → chunk-PY6BGMNJ.js} +7 -7
  9. package/dist/{chunk-LB7DDJOG.js → chunk-UDYR3ME6.js} +3 -3
  10. package/dist/{chunk-ZTB4Z23O.js → chunk-XLNP4ODH.js} +2 -2
  11. package/dist/cli.js +18 -17
  12. package/dist/cli.js.map +1 -1
  13. package/dist/{consensus-vote-QS72DIWI.js → consensus-vote-QOWW7RVO.js} +4 -4
  14. package/dist/{improvement-review-JRUO6SOJ.js → improvement-review-MVM36CQM.js} +4 -4
  15. package/dist/index.js +8 -8
  16. package/dist/{pr-reviewer-helpers-BINKSJSV.js → pr-reviewer-helpers-7CDQLZCS.js} +3 -3
  17. package/dist/{setup-data-dir-4AUIRLO4.js → setup-data-dir-TKS6JEYR.js} +3 -3
  18. package/package.json +1 -1
  19. package/dist/chunk-3W4L63ML.js.map +0 -1
  20. package/dist/chunk-KJN5RWXY.js.map +0 -1
  21. package/dist/chunk-XFDT265H.js +0 -7
  22. /package/dist/{chunk-XFDT265H.js.map → chunk-CKTLFPT3.js.map} +0 -0
  23. /package/dist/{chunk-6B7E372N.js.map → chunk-DRD2GXBT.js.map} +0 -0
  24. /package/dist/{chunk-2WAF3ZFK.js.map → chunk-KRCRVSGX.js.map} +0 -0
  25. /package/dist/{chunk-5LAPFTNG.js.map → chunk-PY6BGMNJ.js.map} +0 -0
  26. /package/dist/{chunk-LB7DDJOG.js.map → chunk-UDYR3ME6.js.map} +0 -0
  27. /package/dist/{chunk-ZTB4Z23O.js.map → chunk-XLNP4ODH.js.map} +0 -0
  28. /package/dist/{consensus-vote-QS72DIWI.js.map → consensus-vote-QOWW7RVO.js.map} +0 -0
  29. /package/dist/{improvement-review-JRUO6SOJ.js.map → improvement-review-MVM36CQM.js.map} +0 -0
  30. /package/dist/{pr-reviewer-helpers-BINKSJSV.js.map → pr-reviewer-helpers-7CDQLZCS.js.map} +0 -0
  31. /package/dist/{setup-data-dir-4AUIRLO4.js.map → setup-data-dir-TKS6JEYR.js.map} +0 -0
@@ -112,4 +112,4 @@ export {
112
112
  SEVERITY_EMOJI,
113
113
  DECISION_EMOJI
114
114
  };
115
- //# sourceMappingURL=chunk-3W4L63ML.js.map
115
+ //# sourceMappingURL=chunk-2G2ZXAD7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dogfooding/pr-review-types.ts","../src/security/sarif-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';\nimport type { FullCapableProvider } from '../scm/types.js';\nimport { FINDING_SEVERITY_LEVELS } from '../security/sarif-types.js';\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 = (typeof FINDING_SEVERITY_LEVELS)[number];\n\n/**\n * Review category for organizing findings.\n */\nexport type ReviewCategory =\n 'security' | 'performance' | 'code_quality' | 'testing' | 'documentation' | '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 * The expert never produced a verdict — its adapter failed or returned\n * nothing (#5012).\n *\n * `approved` cannot express this. A failed expert used to be recorded as\n * `approved: true` (\"don't block on failures\"), so a review in which every\n * expert failed resolved to `approve` at 100% consensus and was posted to\n * GitHub as a real APPROVE. Absence of a review is not approval — a consumer\n * must be able to tell them apart, and `confidence: 0` was already present\n * and already ignored.\n */\n readonly errored?: boolean;\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 /**\n * Whether the author is on the maintainer allowlist — present only when an\n * allowlist was consulted (#4992). Absent means \"no allowlist was consulted\",\n * which is the state on every live path today; it was previously recorded\n * as `false` without being measured.\n */\n readonly isAllowlisted?: boolean;\n /**\n * Whether a durable `AuditLogger` was configured for this process (#4992\n * review). `configured` — the trust event was handed to it; delivery to the\n * hash chain is subject to the logger's own severity filter (trust events\n * are `info`), queue and fail-loud flush, and is not confirmed per call.\n * `none` — no audit logger (audit disabled, or the CLI path).\n */\n readonly auditSink: 'configured' | 'none';\n /** Reputation score (0-100) when reputation is enabled. */\n readonly reputationScore?: number | undefined;\n /** Suspicious signals detected (e.g. `new_account`, `injection_patterns_detected`). */\n readonly suspiciousSignals: readonly string[];\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\n/**\n * Result of fetching PR data plus best-effort author signals (#3133).\n */\nexport interface PRFetchData {\n readonly metadata: PRMetadata;\n readonly provider: FullCapableProvider;\n /** Author's real account age in days, when the lookup succeeded (#3133). */\n readonly accountAgeDays?: number;\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 /** Total number of changed files reported by the SCM provider. */\n readonly totalFiles: number;\n /** Number of changed files for which the SCM provider returned a patch. */\n readonly filesWithPatch: number;\n /** Number of file patches supplied to at least one non-errored expert. */\n readonly filesReviewed: number;\n /** Whether the successful expert panel covered all, some, or no changed files. */\n readonly reviewCoverage: 'full' | 'partial' | 'none';\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 * What actually happened when the review was posted to GitHub (#4354).\n *\n * The review previously reported success whether or not the post landed: a\n * `createReview` rejection was logged and discarded, and a Rule-of-Two block\n * returned early, both leaving the CLI to print \"Review posted to GitHub.\"\n * over a review that does not exist. Callers must consult this rather than\n * inferring a successful post from a successful review.\n */\n readonly postOutcome: ReviewPostOutcome;\n}\n\n/**\n * A completed review before the posting step has run (#4354).\n *\n * Everything the reviewer aggregates is known before the post is attempted; the\n * outcome is stamped on afterwards. Keeping the two apart in the types is what\n * stops `postOutcome` from being defaulted to a hopeful value.\n */\nexport type PRReviewDraft = Omit<PRReviewResult, 'postOutcome'>;\n\n/** Terminal state of the GitHub review-posting step (#4354). */\nexport type ReviewPostOutcome =\n /** The review was created on GitHub. */\n | { readonly status: 'posted' }\n /** Posting was deliberately not attempted (dry-run, or a policy gate blocked it). */\n | { readonly status: 'skipped'; readonly reason: string }\n /** Posting was attempted and GitHub rejected it. */\n | { readonly status: 'failed'; readonly error: string };\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(FINDING_SEVERITY_LEVELS).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. INVERTED vs sarif-types' SEVERITY_ORDER by\n * design (higher = more severe here, for descending sort); only the KEY SET is\n * shared. Derived from the canonical {@link FINDING_SEVERITY_LEVELS} so the keys\n * can't drift (#3570): critical→5 … info→1.\n */\nexport const SEVERITY_ORDER: Record<ReviewSeverity, number> = Object.fromEntries(\n FINDING_SEVERITY_LEVELS.map((level, index) => [level, FINDING_SEVERITY_LEVELS.length - index])\n) as Record<ReviewSeverity, number>;\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","/**\n * SARIF Types and Security Finding Schema (#1682)\n *\n * Types for parsing SARIF (Static Analysis Results Interchange Format)\n * output from security scanners (Semgrep, CodeQL, Bandit, ESLint).\n * Normalizes findings into a unified schema for MCP tool consumption.\n *\n * @module security/sarif-types\n * (Source: Issue #1681, #1682 — Proactive Defensive Security)\n */\n\nimport { z } from 'zod';\n\n// ============================================================================\n// Unified Security Finding Schema\n// ============================================================================\n\n/**\n * Canonical 5-value finding-severity vocabulary (CVSS-aligned), most→least\n * severe. THE single source of truth — every 5-value finding-severity zod enum\n * (severity-consensus, finding-triage, agents/output-schemas, expert-types\n * VulnerabilitySeverity) and both SEVERITY_ORDER maps derive from this (#3570).\n * Tuple order is authoritative: SEVERITY_ORDER below maps it to ascending ranks.\n */\nexport const FINDING_SEVERITY_LEVELS = ['critical', 'high', 'medium', 'low', 'info'] as const;\n\n/** Severity levels aligned with CVSS and common scanner output. */\nexport const FindingSeveritySchema = z.enum(FINDING_SEVERITY_LEVELS);\nexport type FindingSeverity = z.infer<typeof FindingSeveritySchema>;\n\n/** A normalized security finding from any SARIF-compatible scanner. */\nexport const SecurityFindingSchema = z.object({\n /** Unique finding ID (scanner-specific rule ID). */\n id: z.string().min(1),\n /** Scanner that produced this finding. */\n scanner: z.string().min(1),\n /** Rule identifier (e.g., 'javascript.lang.security.detect-eval'). */\n rule: z.string().min(1),\n /** Normalized severity. */\n severity: FindingSeveritySchema,\n /** Human-readable description of the finding. */\n message: z.string().min(1).max(2000),\n /** File path where the finding was detected. */\n file: z.string().min(1),\n /** Start line number (1-based). */\n startLine: z.number().int().min(1),\n /** End line number (1-based, optional). */\n endLine: z.number().int().min(1).optional(),\n /** CWE identifiers (e.g., ['CWE-79', 'CWE-89']). */\n cweIds: z.array(z.string()).default([]),\n /** Scanner confidence (0-1). */\n confidence: z.number().min(0).max(1).default(0.5),\n /** Code snippet around the finding (optional). */\n snippet: z.string().max(500).optional(),\n /** Help URL for remediation guidance. */\n helpUrl: z.string().optional(),\n});\n\nexport type SecurityFinding = z.infer<typeof SecurityFindingSchema>;\n\n/** Result of parsing a SARIF file. */\nexport interface SarifParseResult {\n /** Scanner name extracted from SARIF. */\n readonly scanner: string;\n /** Total findings parsed. */\n readonly totalFindings: number;\n /** Findings sorted by severity (critical first). */\n readonly findings: readonly SecurityFinding[];\n /** Parsing errors (non-fatal). */\n readonly errors: readonly string[];\n}\n\n// ============================================================================\n// SARIF Input Types (subset of the SARIF 2.1.0 spec we need)\n// ============================================================================\n\n/** SARIF result level → our severity mapping. */\nexport const SARIF_LEVEL_MAP: Readonly<Record<string, FindingSeverity>> = {\n error: 'high',\n warning: 'medium',\n note: 'low',\n none: 'info',\n};\n\n/**\n * Severity order for sorting (lower = more severe). Derived from the canonical\n * tuple so it can never drift from {@link FINDING_SEVERITY_LEVELS} (#3570).\n */\nexport const SEVERITY_ORDER: Readonly<Record<FindingSeverity, number>> = Object.fromEntries(\n FINDING_SEVERITY_LEVELS.map((level, index) => [level, index])\n) as Record<FindingSeverity, number>;\n"],"mappings":"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","names":["z","z","SEVERITY_ORDER"]}
@@ -0,0 +1,7 @@
1
+ // src/version.ts
2
+ var VERSION = true ? "8.19.1" : "dev";
3
+
4
+ export {
5
+ VERSION
6
+ };
7
+ //# sourceMappingURL=chunk-CKTLFPT3.js.map
@@ -7,7 +7,7 @@ import {
7
7
  toolStructuredError,
8
8
  toolSuccessStructured,
9
9
  wrapToolWithTimeout
10
- } from "./chunk-ZTB4Z23O.js";
10
+ } from "./chunk-XLNP4ODH.js";
11
11
  import {
12
12
  JsonlStore,
13
13
  SECURITY_KEYWORDS,
@@ -2159,4 +2159,4 @@ export {
2159
2159
  runImprovementReview,
2160
2160
  registerImprovementReviewTool
2161
2161
  };
2162
- //# sourceMappingURL=chunk-6B7E372N.js.map
2162
+ //# sourceMappingURL=chunk-DRD2GXBT.js.map
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-OQB3XC5O.js";
7
7
  import {
8
8
  VERSION
9
- } from "./chunk-XFDT265H.js";
9
+ } from "./chunk-CKTLFPT3.js";
10
10
  import {
11
11
  capitalize
12
12
  } from "./chunk-7LP6VJRP.js";
@@ -1220,7 +1220,7 @@ async function runDoctorFix(result) {
1220
1220
  writeLine2("\u2500".repeat(40));
1221
1221
  let fixCount = 0;
1222
1222
  if (!result.dataDirectory.rootExists || result.dataDirectory.subdirectories.some((d) => !d.exists || !d.writable)) {
1223
- const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-4AUIRLO4.js");
1223
+ const { initDataDirectories: initDataDirectories2, describeDataDirFix: describeDataDirFix2 } = await import("./setup-data-dir-TKS6JEYR.js");
1224
1224
  const outcome = describeDataDirFix2(initDataDirectories2());
1225
1225
  writeLine2(outcome.line);
1226
1226
  if (outcome.counted) fixCount++;
@@ -1322,4 +1322,4 @@ export {
1322
1322
  startStdioServer,
1323
1323
  closeServer
1324
1324
  };
1325
- //# sourceMappingURL=chunk-2WAF3ZFK.js.map
1325
+ //# sourceMappingURL=chunk-KRCRVSGX.js.map
@@ -3,7 +3,7 @@ import {
3
3
  DECISION_EMOJI,
4
4
  SEVERITY_EMOJI,
5
5
  SEVERITY_ORDER2 as SEVERITY_ORDER
6
- } from "./chunk-3W4L63ML.js";
6
+ } from "./chunk-2G2ZXAD7.js";
7
7
  import {
8
8
  allOf
9
9
  } from "./chunk-OQB3XC5O.js";
@@ -20,6 +20,39 @@ import {
20
20
 
21
21
  // src/dogfooding/pr-reviewer-helpers.ts
22
22
  import { randomUUID } from "node:crypto";
23
+
24
+ // src/dogfooding/pr-review-stats.ts
25
+ function getFileReviewCoverage(pr, reviews, filesIncluded) {
26
+ const totalFiles = pr.files.length;
27
+ const filesWithPatch = pr.files.filter((file) => file.patch !== void 0).length;
28
+ const hasSuccessfulReview = reviews.some((review) => review.errored !== true);
29
+ const filesReviewed = hasSuccessfulReview ? filesIncluded : 0;
30
+ const reviewCoverage = filesReviewed === 0 ? "none" : filesReviewed === totalFiles ? "full" : "partial";
31
+ return { totalFiles, filesWithPatch, filesReviewed, reviewCoverage };
32
+ }
33
+ function formatFileReviewCoverage(result) {
34
+ return `${String(result.filesReviewed)} of ${String(result.totalFiles)} files reviewed (${result.reviewCoverage})`;
35
+ }
36
+ function formatReviewStats(result) {
37
+ const total = Object.values(result.findingsBySeverity).reduce((a, b) => a + b, 0);
38
+ const parts = [];
39
+ for (const severity of ["critical", "high", "medium", "low", "info"]) {
40
+ const count = result.findingsBySeverity[severity];
41
+ if (count > 0) parts.push(`${SEVERITY_EMOJI[severity]} ${String(count)} ${severity}`);
42
+ }
43
+ return `<details>
44
+ <summary>Review Statistics (${String(total)} findings)</summary>
45
+
46
+ - Experts: ${String(result.expertCount)}
47
+ - Files: ${formatFileReviewCoverage(result)}
48
+ - Consensus: ${(result.consensusScore * 100).toFixed(0)}%
49
+ - Duration: ${String(result.totalDurationMs)}ms
50
+ - Findings: ${parts.join(", ") || "none"}
51
+
52
+ </details>`;
53
+ }
54
+
55
+ // src/dogfooding/pr-reviewer-helpers.ts
23
56
  var repLogger = createLogger({ component: "PRReviewer.reputation" });
24
57
  async function fetchAccountAgeDays(provider, username) {
25
58
  try {
@@ -110,6 +143,7 @@ function extractStringField(record, ...keys) {
110
143
  function formatDiffs(pr) {
111
144
  const maxDiffLength = 2e3;
112
145
  let totalLength = 0;
146
+ let filesIncluded = 0;
113
147
  const diffs = [];
114
148
  for (const file of pr.files) {
115
149
  if (file.patch === void 0) continue;
@@ -123,9 +157,10 @@ ${file.patch}
123
157
  } else {
124
158
  diffs.push(diff);
125
159
  totalLength += diff.length;
160
+ filesIncluded++;
126
161
  }
127
162
  }
128
- return diffs.join("\n\n");
163
+ return { text: diffs.join("\n\n"), filesIncluded };
129
164
  }
130
165
  function parseFindings(output, expertId, minSeverity) {
131
166
  const minOrder = SEVERITY_ORDER[minSeverity];
@@ -268,7 +303,7 @@ function formatReviewComment(result) {
268
303
  const emoji = DECISION_EMOJI[result.decision];
269
304
  const decisionText = result.decision.replaceAll("_", " ").toUpperCase();
270
305
  const findingsSection = formatFindingsSection(result);
271
- const statsSection = formatStatsSection(result);
306
+ const statsSection = formatReviewStats(result);
272
307
  return `## ${emoji} Nexus Agents Review: ${decisionText}
273
308
 
274
309
  ${result.summary}
@@ -301,26 +336,6 @@ function formatFindingsSection(result) {
301
336
  }
302
337
  return lines.join("\n");
303
338
  }
304
- function formatStatsSection(result) {
305
- const { findingsBySeverity } = result;
306
- const total = sumFindings(findingsBySeverity);
307
- const parts = [];
308
- for (const severity of ["critical", "high", "medium", "low", "info"]) {
309
- const count = findingsBySeverity[severity];
310
- if (count > 0) {
311
- parts.push(`${SEVERITY_EMOJI[severity]} ${String(count)} ${severity}`);
312
- }
313
- }
314
- return `<details>
315
- <summary>Review Statistics (${String(total)} findings)</summary>
316
-
317
- - Experts: ${String(result.expertCount)}
318
- - Consensus: ${(result.consensusScore * 100).toFixed(0)}%
319
- - Duration: ${String(result.totalDurationMs)}ms
320
- - Findings: ${parts.join(", ") || "none"}
321
-
322
- </details>`;
323
- }
324
339
  function createFailedReview(expertId, category, durationMs, error) {
325
340
  return {
326
341
  expertId,
@@ -344,6 +359,8 @@ function reviewPostingBlock(verdict) {
344
359
  }
345
360
 
346
361
  export {
362
+ getFileReviewCoverage,
363
+ formatFileReviewCoverage,
347
364
  fetchAccountAgeDays,
348
365
  assessPRReputation,
349
366
  buildPRTrustAssessment,
@@ -366,4 +383,4 @@ export {
366
383
  createFailedReview,
367
384
  reviewPostingBlock
368
385
  };
369
- //# sourceMappingURL=chunk-KJN5RWXY.js.map
386
+ //# sourceMappingURL=chunk-PODRYCRR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dogfooding/pr-reviewer-helpers.ts","../src/dogfooding/pr-review-stats.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 { Result } from '../core/index.js';\nimport { getTimeProvider, createLogger, ok, err } from '../core/index.js';\nimport type { ScmUserMetadata } from '../scm/types.js';\nimport type {\n PRMetadata,\n PRReviewDraft,\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 { allOf } from '../utils/verdict-aggregation.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 { FirewallResult } from '../security/firewall/firewall-pipeline.js';\nimport { formatReviewStats } from './pr-review-stats.js';\n\nconst repLogger = createLogger({ component: 'PRReviewer.reputation' });\n\n// =============================================================================\n// Reputation Gating Helpers (#3123, epic #3118 Phase 5)\n// =============================================================================\n\n/**\n * Best-effort account-age lookup for a PR author (#3133, Phase-3 equivalent for\n * the PR path). Returns the author's real account age in days via the provider's\n * `fetchUserMetadata`, or `undefined` on any failure (err Result, unparseable\n * `createdAt`, or an unexpected rejection) — never fabricated, never throws, so\n * the review is never blocked by this lookup.\n */\nexport async function fetchAccountAgeDays(\n provider: { fetchUserMetadata: (u: string) => Promise<Result<ScmUserMetadata, Error>> },\n username: string\n): Promise<number | undefined> {\n try {\n const result = await provider.fetchUserMetadata(username);\n if (!result.ok) return undefined;\n const createdMs = Date.parse(result.value.createdAt);\n if (!Number.isFinite(createdMs)) return undefined;\n return Math.floor((getTimeProvider().now() - createdMs) / 86_400_000);\n } catch {\n return undefined;\n }\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, plus\n * the author's real account age when it was fetched (#3133). `accountAgeDays` is\n * OMITTED when the lookup failed — never fabricated, so the engine skips the\n * `new_account` signal. Returns undefined when reputation is disabled.\n */\nexport function assessPRReputation(\n pr: PRMetadata,\n cache: ReputationCache,\n enableReputation: boolean,\n accountAgeDays: number | undefined\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 ...(accountAgeDays !== undefined ? { accountAgeDays } : {}),\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 firewall: Pick<FirewallResult, 'trust' | 'isAllowlisted' | 'auditSink'>,\n reputation: ReputationAssessment | undefined,\n gateDecision: ReputationGateDecision\n): PRTrustAssessment {\n const trustResult = firewall.trust;\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 // Measured or absent (#4992): never the classifier's default `false`.\n ...(firewall.isAllowlisted !== undefined ? { isAllowlisted: firewall.isAllowlisted } : {}),\n auditSink: firewall.auditSink,\n reputationScore: reputation?.reputationScore,\n suspiciousSignals: isTier1 ? [] : (reputation?.suspiciousSignals ?? []),\n isSuspicious: isTier1 ? false : (reputation?.isSuspicious ?? false),\n enforcedTrustTier: gateDecision.enforcedTier,\n reputationReconciledTier: gateDecision.reconciledTier,\n gatingMode: gateDecision.mode,\n };\n}\n\n/**\n * Reads the reputation gate decision the firewall computed for this PR (#3123\n * gating; #4992 review: ONE gate, shared with the firewall's Rule-of-Two\n * signal) and builds the assessment surfaced on the result. A suppressed\n * demotion (audit/off) is logged. Fails closed when the firewall returned no\n * gate — structurally unreachable, since the caller always supplies the\n * reputation option, but the classifier tier must never stand in for it.\n */\nexport function gatePRAuthor(\n pr: PRMetadata,\n firewall: Pick<FirewallResult, 'trust' | 'isAllowlisted' | 'auditSink' | 'reputationGate'>,\n reputation: ReputationAssessment | undefined\n): Result<{ gateDecision: ReputationGateDecision; trustAssessment: PRTrustAssessment }, Error> {\n const trustResult = firewall.trust;\n const gateDecision = firewall.reputationGate;\n if (gateDecision === undefined) {\n return err(new Error('Untrusted-input firewall returned no reputation gate decision'));\n }\n if (gateDecision.demotionSuppressed) {\n repLogger.warn('Reputation demotion suppressed by gating mode (would block under enforce)', {\n prNumber: pr.number,\n author: pr.author,\n mode: gateDecision.mode,\n classifierTier: trustResult.trustTier,\n reconciledTier: gateDecision.reconciledTier,\n });\n }\n return ok({\n gateDecision,\n trustAssessment: buildPRTrustAssessment(firewall, reputation, gateDecision),\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/** Formats fetched patches and reports how many were supplied without truncation. */\nexport function formatDiffs(pr: PRMetadata): {\n readonly text: string;\n readonly filesIncluded: number;\n} {\n const maxDiffLength = 2000;\n let totalLength = 0;\n let filesIncluded = 0;\n const diffs: string[] = [];\n for (const file of pr.files) {\n if (file.patch === undefined) continue;\n const diff = `\\`\\`\\`diff\\n# ${file.filename}\\n${file.patch}\\n\\`\\`\\``;\n if (totalLength + diff.length > maxDiffLength * pr.files.length) {\n diffs.push(`# ${file.filename}\\n(diff truncated)`);\n } else {\n diffs.push(diff);\n totalLength += diff.length;\n filesIncluded++;\n }\n }\n return { text: diffs.join('\\n\\n'), filesIncluded };\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 for (const warning of parseWarnings(output.warnings)) {\n const finding = parseWarningFinding(warning, expertId, minOrder);\n if (finding !== null) findings.push(finding);\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\nfunction parseWarnings(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((warning): warning is string => typeof warning === 'string');\n}\n\nfunction parseWarningFinding(\n warning: string,\n expertId: string,\n minOrder: number\n): ReviewFinding | null {\n const severity = parseSeverity(\n warning.match(/\\b(critical|high|medium|low|info)-?severity\\b/i)?.[1]\n );\n return parseOneFinding(\n {\n title: 'Expert warning',\n description: warning,\n severity,\n category: parseCategory(expertId.replace(/-expert$/, '')),\n },\n expertId,\n minOrder\n );\n}\n\ntype ExpertVerdict = 'approved' | 'changes_requested' | 'findings' | 'errored';\n\ninterface ParsedExpertReview {\n readonly findings: ReviewFinding[];\n readonly verdict: ExpertVerdict;\n}\n\nfunction parseVerdictMarker(\n value: unknown\n): Exclude<ExpertVerdict, 'findings' | 'errored'> | undefined {\n if (typeof value !== 'string') return undefined;\n const normalized = value.toUpperCase().replaceAll('-', '_').replaceAll(' ', '_');\n if (/\\b(?:CHANGES_REQUESTED|REQUEST_CHANGES)\\b/.test(normalized)) return 'changes_requested';\n if (/\\b(?:APPROVED|APPROVE)\\b/.test(normalized)) return 'approved';\n return undefined;\n}\n\n/** Parses one expert output into a single, validated review verdict. */\nexport function parseExpertReview(\n output: Record<string, unknown>,\n expertId: string,\n minSeverity: ReviewSeverity\n): ParsedExpertReview {\n const findings = parseFindings(output, expertId, minSeverity);\n const verdict = parseVerdictMarker(output.verdict) ?? parseVerdictMarker(output.content);\n if (verdict !== undefined) return { findings, verdict };\n const hasFindingShape = collectSources(output).some((source) => Array.isArray(source));\n const hasWarningShape = parseWarnings(output.warnings).length > 0;\n return { findings, verdict: hasFindingShape || hasWarningShape ? 'findings' : 'errored' };\n}\n\n// =============================================================================\n// Decision Helpers\n// =============================================================================\n\nexport function determineApproval(\n findings: ReviewFinding[],\n verdict: ExpertVerdict = 'findings'\n): boolean {\n if (verdict === 'errored' || verdict === 'changes_requested') return false;\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 // #5012: an expert that errored produced no verdict, so it can neither\n // approve nor object. `allOf(reviews, …, false)` already refuses to call\n // ZERO reviews unanimous approval (#4581); feeding it synthetic approvals\n // for failed experts defeated that guard by making the list non-empty.\n const completedReviews = reviews.filter((r) => r.errored !== true);\n const hasCritical = findings.some((f) => f.severity === 'critical');\n const hasHigh = findings.some((f) => f.severity === 'high');\n // Zero expert reviews is not unanimous approval (#4581): with `true` here the\n // `hasHigh && !allApproved` branch could never fire on an unreviewed PR, so a\n // HIGH finding silently downgraded from request_changes to comment.\n const allApproved = allOf(reviews, (r) => r.errored !== true && r.approved, false);\n\n if (hasCritical) return 'request_changes';\n if (hasHigh && !allApproved) return 'request_changes';\n if (findings.length > 0) return 'comment';\n // Nothing read the diff, so there is nothing to approve. `comment` posts the\n // failure summaries without asserting the change is fine.\n if (completedReviews.length === 0 || !allApproved) 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: PRReviewDraft): string {\n const emoji = DECISION_EMOJI[result.decision];\n const decisionText = result.decision.replaceAll('_', ' ').toUpperCase();\n\n const findingsSection = formatFindingsSection(result);\n const statsSection = formatReviewStats(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: PRReviewDraft): 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\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 // #5012: NOT `approved: true`. \"Don't block on failures\" made an expert\n // that never ran indistinguishable from one that read the diff and had no\n // objection, and `determineDecision` turned three of those into an APPROVE\n // posted to GitHub.\n approved: false,\n errored: true,\n summary: `Review failed: ${error}`,\n findings: [],\n durationMs,\n confidence: 0,\n };\n}\n\n/** The policy gate's verdict on posting a review, as the caller needs it. */\nexport interface ReviewPostingVerdict {\n readonly allowed: boolean;\n readonly hasRuleOfTwoViolation: boolean;\n readonly violations: readonly { rule: string; message?: string }[];\n}\n\n/**\n * Decide whether review posting is blocked, and how to describe it.\n *\n * Consumes the gate's own `allowed` rather than re-deriving a narrower\n * condition. The caller previously blocked on `hasRuleOfTwoViolation` alone\n * while `evaluatePolicy` had already computed `allowed`.\n *\n * For the action `auditReviewAction` builds, the two are currently EQUIVALENT:\n * tiers 1-2 yield no violations, tiers 3-4 yield INSUFFICIENT_TRUST +\n * UNTRUSTED_INFLUENCE + RULE_OF_TWO together. So this changes no behaviour\n * today. It makes the equivalence guaranteed rather than accidental — it holds\n * only because that context hardcodes `hasWriteAccess` and `hasSecretAccess`\n * to true, which is what makes `checkRuleOfTwo` fire at tier 3+. Make either\n * conditional and RULE_OF_TWO stops firing while the other two blocking rules\n * still do, and a review would post against `allowed: false`.\n *\n * Returns `undefined` when posting may proceed. Rule of Two keeps its own\n * label: it is the distinctive condition (untrusted input + write access +\n * secrets at once) and reads very differently from a trust-tier block.\n */\nexport function reviewPostingBlock(\n verdict: ReviewPostingVerdict\n): { label: string; reason: string } | undefined {\n if (verdict.allowed) return undefined;\n const label = verdict.hasRuleOfTwoViolation ? 'Rule of Two' : 'Policy gate';\n return { label, reason: `${label}: ${verdict.violations.map((v) => v.rule).join(', ')}` };\n}\n","/**\n * Review coverage calculation and statistics rendering.\n *\n * @module dogfooding/pr-review-stats\n */\n\nimport type {\n ExpertReviewResult,\n PRMetadata,\n PRReviewDraft,\n ReviewSeverity,\n} from './pr-review-types.js';\nimport { SEVERITY_EMOJI } from './pr-review-types.js';\n\n/** Calculates how much of the fetched PR was supplied to a successful expert. */\nexport function getFileReviewCoverage(\n pr: PRMetadata,\n reviews: readonly ExpertReviewResult[],\n filesIncluded: number\n): Pick<PRReviewDraft, 'totalFiles' | 'filesWithPatch' | 'filesReviewed' | 'reviewCoverage'> {\n const totalFiles = pr.files.length;\n const filesWithPatch = pr.files.filter((file) => file.patch !== undefined).length;\n const hasSuccessfulReview = reviews.some((review) => review.errored !== true);\n const filesReviewed = hasSuccessfulReview ? filesIncluded : 0;\n const reviewCoverage =\n filesReviewed === 0 ? 'none' : filesReviewed === totalFiles ? 'full' : 'partial';\n return { totalFiles, filesWithPatch, filesReviewed, reviewCoverage };\n}\n\n/** Formats the measured file-review coverage without losing the PR total. */\nexport function formatFileReviewCoverage(result: PRReviewDraft): string {\n return `${String(result.filesReviewed)} of ${String(result.totalFiles)} files reviewed (${result.reviewCoverage})`;\n}\n\n/** Renders the collapsible statistics section in a GitHub review comment. */\nexport function formatReviewStats(result: PRReviewDraft): string {\n const total = Object.values(result.findingsBySeverity).reduce((a, b) => a + b, 0);\n const parts: string[] = [];\n for (const severity of ['critical', 'high', 'medium', 'low', 'info'] as ReviewSeverity[]) {\n const count = result.findingsBySeverity[severity];\n if (count > 0) parts.push(`${SEVERITY_EMOJI[severity]} ${String(count)} ${severity}`);\n }\n\n return `<details>\n<summary>Review Statistics (${String(total)} findings)</summary>\n\n- Experts: ${String(result.expertCount)}\n- Files: ${formatFileReviewCoverage(result)}\n- Consensus: ${(result.consensusScore * 100).toFixed(0)}%\n- Duration: ${String(result.totalDurationMs)}ms\n- Findings: ${parts.join(', ') || 'none'}\n\n</details>`;\n}\n"],"mappings":"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","names":[]}
@@ -17,11 +17,11 @@ import {
17
17
  FINDING_SEVERITY_LEVELS,
18
18
  SARIF_LEVEL_MAP,
19
19
  SEVERITY_ORDER
20
- } from "./chunk-3W4L63ML.js";
20
+ } from "./chunk-2G2ZXAD7.js";
21
21
  import {
22
22
  DEFAULT_TASK_TTL_MS,
23
23
  clampTaskTtl
24
- } from "./chunk-2WAF3ZFK.js";
24
+ } from "./chunk-KRCRVSGX.js";
25
25
  import {
26
26
  executeExpert
27
27
  } from "./chunk-C7CSLODO.js";
@@ -48,7 +48,7 @@ import {
48
48
  simulationDeniedResult,
49
49
  toJobSummary,
50
50
  writeJobCancelled
51
- } from "./chunk-LB7DDJOG.js";
51
+ } from "./chunk-UDYR3ME6.js";
52
52
  import {
53
53
  allOf
54
54
  } from "./chunk-OQB3XC5O.js";
@@ -67,7 +67,7 @@ import {
67
67
  } from "./chunk-ZUK5BG3Q.js";
68
68
  import {
69
69
  withPrerequisite
70
- } from "./chunk-6B7E372N.js";
70
+ } from "./chunk-DRD2GXBT.js";
71
71
  import {
72
72
  NOOP_NOTIFIER,
73
73
  RateLimiter,
@@ -92,7 +92,7 @@ import {
92
92
  withAuditTrail,
93
93
  withProgressHeartbeat,
94
94
  wrapToolWithTimeout
95
- } from "./chunk-ZTB4Z23O.js";
95
+ } from "./chunk-XLNP4ODH.js";
96
96
  import {
97
97
  createAuditTrail,
98
98
  createGraphAuditBridge,
@@ -47461,7 +47461,7 @@ ${contextBlock}`;
47461
47461
  const strategy = config.votingStrategy ?? "higher_order";
47462
47462
  await postProgress(config, "Vote", `Running consensus with ${strategy} strategy...`);
47463
47463
  try {
47464
- const { executeVoting } = await import("./consensus-vote-QS72DIWI.js");
47464
+ const { executeVoting } = await import("./consensus-vote-QOWW7RVO.js");
47465
47465
  const votingResult = await executeVoting(
47466
47466
  {
47467
47467
  proposal: buildVoteProposal(plan, research),
@@ -56125,4 +56125,4 @@ export {
56125
56125
  shutdownFeedbackSubscriber,
56126
56126
  createEventBusBridge
56127
56127
  };
56128
- //# sourceMappingURL=chunk-5LAPFTNG.js.map
56128
+ //# sourceMappingURL=chunk-PY6BGMNJ.js.map
@@ -23,10 +23,10 @@ import {
23
23
  toolSuccessStructured,
24
24
  withProgressHeartbeat,
25
25
  wrapToolWithTimeout
26
- } from "./chunk-ZTB4Z23O.js";
26
+ } from "./chunk-XLNP4ODH.js";
27
27
  import {
28
28
  VERSION
29
- } from "./chunk-XFDT265H.js";
29
+ } from "./chunk-CKTLFPT3.js";
30
30
  import {
31
31
  assessPanelIndependence,
32
32
  countDistinctModels,
@@ -7205,4 +7205,4 @@ export {
7205
7205
  CONSENSUS_VOTE_TOOL_SCHEMA,
7206
7206
  registerConsensusVoteTool
7207
7207
  };
7208
- //# sourceMappingURL=chunk-LB7DDJOG.js.map
7208
+ //# sourceMappingURL=chunk-UDYR3ME6.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  VERSION
3
- } from "./chunk-XFDT265H.js";
3
+ } from "./chunk-CKTLFPT3.js";
4
4
  import {
5
5
  emitClawGuardViolation
6
6
  } from "./chunk-TCTKOFNL.js";
@@ -2246,4 +2246,4 @@ export {
2246
2246
  VOTING_THRESHOLDS,
2247
2247
  ConsensusMetricsSchema
2248
2248
  };
2249
- //# sourceMappingURL=chunk-ZTB4Z23O.js.map
2249
+ //# sourceMappingURL=chunk-XLNP4ODH.js.map
package/dist/cli.js CHANGED
@@ -15,13 +15,15 @@ import {
15
15
  extractSummary,
16
16
  fetchAccountAgeDays,
17
17
  formatDiffs,
18
+ formatFileReviewCoverage,
18
19
  formatReviewComment,
19
20
  gatePRAuthor,
20
21
  generateSummary,
22
+ getFileReviewCoverage,
21
23
  parseExpertReview,
22
24
  reviewPostingBlock,
23
25
  sumFindings
24
- } from "./chunk-KJN5RWXY.js";
26
+ } from "./chunk-PODRYCRR.js";
25
27
  import "./chunk-UCIRVMOZ.js";
26
28
  import {
27
29
  runConfigInitSync
@@ -155,7 +157,7 @@ import {
155
157
  validateNexusEnv,
156
158
  validateWorkflow,
157
159
  wrapInMarkdownFence
158
- } from "./chunk-5LAPFTNG.js";
160
+ } from "./chunk-PY6BGMNJ.js";
159
161
  import "./chunk-VLNPDWIW.js";
160
162
  import "./chunk-DGWT7DXX.js";
161
163
  import "./chunk-64GMIUM5.js";
@@ -163,7 +165,7 @@ import "./chunk-WGYETY6P.js";
163
165
  import {
164
166
  CATEGORY_DISPLAY_NAMES,
165
167
  DEFAULT_PR_REVIEW_CONFIG
166
- } from "./chunk-3W4L63ML.js";
168
+ } from "./chunk-2G2ZXAD7.js";
167
169
  import {
168
170
  DATA_SUBDIRECTORIES,
169
171
  checkApiKeys,
@@ -175,7 +177,7 @@ import {
175
177
  doctorCommand,
176
178
  initDataDirectories,
177
179
  runDoctor
178
- } from "./chunk-2WAF3ZFK.js";
180
+ } from "./chunk-KRCRVSGX.js";
179
181
  import {
180
182
  buildOpenAICompatAdapters,
181
183
  readOpenAICompatEnv
@@ -194,7 +196,7 @@ import {
194
196
  recordAuthenticVote,
195
197
  registerConsensusVoteTool,
196
198
  toRecordDecision
197
- } from "./chunk-LB7DDJOG.js";
199
+ } from "./chunk-UDYR3ME6.js";
198
200
  import {
199
201
  loadUsageEvents,
200
202
  rollupByModel
@@ -234,15 +236,15 @@ import {
234
236
  registerImprovementReviewTool,
235
237
  runImprovementReview,
236
238
  scanForSecrets
237
- } from "./chunk-6B7E372N.js";
239
+ } from "./chunk-DRD2GXBT.js";
238
240
  import {
239
241
  createDefaultPolicyFirewall,
240
242
  setGlobalPolicyFirewall,
241
243
  stagePolicyFirewallForRollout
242
- } from "./chunk-ZTB4Z23O.js";
244
+ } from "./chunk-XLNP4ODH.js";
243
245
  import {
244
246
  VERSION
245
- } from "./chunk-XFDT265H.js";
247
+ } from "./chunk-CKTLFPT3.js";
246
248
  import "./chunk-TCTKOFNL.js";
247
249
  import {
248
250
  createUnifiedRegistry,
@@ -4369,7 +4371,7 @@ ${safeBody || "No description provided."}
4369
4371
  ${filesSummary}
4370
4372
 
4371
4373
  ### File Diffs
4372
- ${formatDiffs(pr)}
4374
+ ${formatDiffs(pr).text}
4373
4375
 
4374
4376
  Provide a structured review with:
4375
4377
  1. Overall approval (APPROVED/CHANGES_REQUESTED)
@@ -4408,7 +4410,6 @@ Provide a structured review with:
4408
4410
  aggregateReviews(pr, reviews, startTime, trustAssessment) {
4409
4411
  const allFindings = reviews.flatMap((r) => r.findings);
4410
4412
  const decision = determineDecision(reviews, allFindings);
4411
- const consensusScore = calculateConsensus(reviews);
4412
4413
  return {
4413
4414
  prNumber: pr.number,
4414
4415
  repository: `${pr.owner}/${pr.repo}`,
@@ -4419,8 +4420,8 @@ Provide a structured review with:
4419
4420
  findingsByCategory: countByCategory(allFindings),
4420
4421
  totalDurationMs: getTimeProvider().now() - startTime,
4421
4422
  expertCount: reviews.length,
4422
- filesReviewed: pr.files.length,
4423
- consensusScore,
4423
+ ...getFileReviewCoverage(pr, reviews, formatDiffs(pr).filesIncluded),
4424
+ consensusScore: calculateConsensus(reviews),
4424
4425
  debateRounds: 1,
4425
4426
  timestamp: getTimeProvider().nowIso(),
4426
4427
  trustAssessment
@@ -4450,7 +4451,7 @@ Provide a structured review with:
4450
4451
  * (Source: Issue #828 — Wire policy gate into production pipeline)
4451
4452
  */
4452
4453
  async postReviewToGitHub(provider, pr, result, gateDecision) {
4453
- const { formatReviewComment: formatReviewComment2 } = await import("./pr-reviewer-helpers-BINKSJSV.js");
4454
+ const { formatReviewComment: formatReviewComment2 } = await import("./pr-reviewer-helpers-7CDQLZCS.js");
4454
4455
  const formattedBody = formatReviewComment2(result);
4455
4456
  const body = truncateText(formattedBody, DRAFT_REPLY_BODY_MAX_LENGTH, "\u2026[review truncated]");
4456
4457
  const sources = pr.files.slice(0, 20).map((file) => ({ type: "repoFile", path: file.filename }));
@@ -5088,9 +5089,7 @@ function updateAllStepsCompleted(steps, review, totalDurationMs) {
5088
5089
  function getStepMessage(index, review) {
5089
5090
  const msgMap = {
5090
5091
  0: "OK",
5091
- // #4350: this printed `expertReviews.length` — the EXPERT count — under a
5092
- // "files" label, so a 7-file PR reported "3 files".
5093
- 1: `${String(review.filesReviewed)} files`,
5092
+ 1: formatFileReviewCoverage(review),
5094
5093
  2: getExpertMessage(review, "security"),
5095
5094
  3: getExpertMessage(review, "code_quality"),
5096
5095
  4: getExpertMessage(review, "testing"),
@@ -5140,6 +5139,8 @@ function printSummary(review) {
5140
5139
  process.stdout.write(`Decision: ${review.decision.replaceAll("_", " ").toUpperCase()}
5141
5140
  `);
5142
5141
  process.stdout.write(`Experts: ${String(review.expertCount)}
5142
+ `);
5143
+ process.stdout.write(`Files: ${formatFileReviewCoverage(review)}
5143
5144
  `);
5144
5145
  process.stdout.write(`Consensus: ${formatPercentage(review.consensusScore)}
5145
5146
  `);
@@ -19671,7 +19672,7 @@ function startImprovementReviewScheduler(options) {
19671
19672
  let running = false;
19672
19673
  const runOnce = async () => {
19673
19674
  try {
19674
- const { runImprovementReview: runImprovementReview2, ImprovementReviewInputSchema: ImprovementReviewInputSchema2 } = await import("./improvement-review-JRUO6SOJ.js");
19675
+ const { runImprovementReview: runImprovementReview2, ImprovementReviewInputSchema: ImprovementReviewInputSchema2 } = await import("./improvement-review-MVM36CQM.js");
19675
19676
  const input = ImprovementReviewInputSchema2.parse({ fileIssues });
19676
19677
  const result = await runImprovementReview2(input, { logger: logger22 });
19677
19678
  logger22.info("Scheduled improvement_review complete", {