nexus-agents 3.6.13 → 3.6.15

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.
@@ -0,0 +1,15 @@
1
+ // src/utils/verdict-aggregation.ts
2
+ function allOf(items, predicate, whenEmpty) {
3
+ if (items.length === 0) return whenEmpty;
4
+ return items.every(predicate);
5
+ }
6
+ function anyOf(items, predicate, whenEmpty) {
7
+ if (items.length === 0) return whenEmpty;
8
+ return items.some(predicate);
9
+ }
10
+
11
+ export {
12
+ allOf,
13
+ anyOf
14
+ };
15
+ //# sourceMappingURL=chunk-OQB3XC5O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/verdict-aggregation.ts"],"sourcesContent":["/**\n * Aggregating a verdict over a collection that might be empty (#4580).\n *\n * `[].every(p)` is `true` in JavaScript. So `checks.every((c) => c.passed)`\n * reports **pass** when `checks` is empty — absence rendered as health, on\n * exactly the code paths where that is most dangerous.\n *\n * Confirmed instances of the shape:\n * - `verify_audit_chain` returns `ok: true` over zero events, so deleting\n * every audit log file makes a tamper-evident chain verify clean (#4579)\n * - a quality gate with zero checks reported `pass` (#4544)\n * - the QA pipeline stage reported `success` after reviewing zero tasks,\n * letting the graph advance as though implementations had been reviewed\n *\n * ## Why a helper rather than a lint or a type\n *\n * Measured before choosing: 64 non-test `.every()` calls, and most are fine —\n * `github-provider.ts:178` guards with `length > 0` and correctly leaves an\n * empty check set `pending`. A wide surface with sparse defects, so a blanket\n * lint would be mostly false positives, and false positives are what teach\n * people to bypass a gate.\n *\n * What was missing is not enforcement but a *decision point*: nothing made the\n * author say what empty means. `whenEmpty` is a required argument, so they\n * must. Chosen by a 7-voter `higher_order` panel at the supermajority bar.\n *\n * ## Choosing `whenEmpty`\n *\n * Ask what an empty collection is evidence OF. Usually nothing — in which case\n * the honest answer is the non-committal verdict (`pending`, `unmeasured`,\n * `skip`), not the optimistic one. Reserve `true` for cases where vacuous truth\n * is genuinely the contract, and say so at the call site.\n *\n * @module utils/verdict-aggregation\n * (Source: Issue #4580)\n */\n\n/**\n * `predicate` holds for every item — with the empty case named, not defaulted.\n *\n * @param items - The collection to judge.\n * @param predicate - Must hold for each item.\n * @param whenEmpty - The verdict when there is nothing to judge. Required.\n */\nexport function allOf<T>(\n items: readonly T[],\n predicate: (item: T) => boolean,\n whenEmpty: boolean\n): boolean {\n if (items.length === 0) return whenEmpty;\n return items.every(predicate);\n}\n\n/**\n * `predicate` holds for at least one item — with the empty case named.\n *\n * `[].some(p)` is already `false`, which is usually right, but not always: a\n * \"did anything fail?\" check over zero results should often be `unmeasured`\n * rather than a clean `false`. Naming it keeps the reasoning visible.\n *\n * @param items - The collection to judge.\n * @param predicate - Must hold for at least one item.\n * @param whenEmpty - The verdict when there is nothing to judge. Required.\n */\nexport function anyOf<T>(\n items: readonly T[],\n predicate: (item: T) => boolean,\n whenEmpty: boolean\n): boolean {\n if (items.length === 0) return whenEmpty;\n return items.some(predicate);\n}\n\n/**\n * Reduce a collection to an arbitrary verdict, with the empty case named.\n *\n * For verdicts richer than a boolean — a severity, a tri-state gate result —\n * where the empty case is usually `unmeasured` rather than the best value.\n *\n * @param items - The collection to judge.\n * @param aggregate - Folds a non-empty collection into a verdict.\n * @param whenEmpty - The verdict when there is nothing to judge. Required.\n */\nexport function verdictOver<T, V>(\n items: readonly T[],\n aggregate: (items: readonly T[]) => V,\n whenEmpty: V\n): V {\n if (items.length === 0) return whenEmpty;\n return aggregate(items);\n}\n"],"mappings":";AA4CO,SAAS,MACd,OACA,WACA,WACS;AACT,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO,MAAM,MAAM,SAAS;AAC9B;AAaO,SAAS,MACd,OACA,WACA,WACS;AACT,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO,MAAM,KAAK,SAAS;AAC7B;","names":[]}
@@ -8,7 +8,7 @@ import {
8
8
  checkSqlite,
9
9
  defaultConfig,
10
10
  initDataDirectories
11
- } from "./chunk-SQOTLTGX.js";
11
+ } from "./chunk-3TMJ34M4.js";
12
12
  import {
13
13
  BUILT_IN_EXPERTS
14
14
  } from "./chunk-YPNQPT2F.js";
@@ -2001,4 +2001,4 @@ export {
2001
2001
  setupCommand,
2002
2002
  setupCommandAsync
2003
2003
  };
2004
- //# sourceMappingURL=chunk-SYR23V25.js.map
2004
+ //# sourceMappingURL=chunk-RXXKAEZP.js.map
@@ -4,6 +4,9 @@ import {
4
4
  SEVERITY_EMOJI,
5
5
  SEVERITY_ORDER2 as SEVERITY_ORDER
6
6
  } from "./chunk-CQ77TF4Y.js";
7
+ import {
8
+ allOf
9
+ } from "./chunk-OQB3XC5O.js";
7
10
  import {
8
11
  assessReputation,
9
12
  gateWithReputation,
@@ -148,7 +151,7 @@ function determineApproval(findings) {
148
151
  function determineDecision(reviews, findings) {
149
152
  const hasCritical = findings.some((f) => f.severity === "critical");
150
153
  const hasHigh = findings.some((f) => f.severity === "high");
151
- const allApproved = reviews.every((r) => r.approved);
154
+ const allApproved = allOf(reviews, (r) => r.approved, false);
152
155
  if (hasCritical) return "request_changes";
153
156
  if (hasHigh && !allApproved) return "request_changes";
154
157
  if (findings.length > 0) return "comment";
@@ -288,4 +291,4 @@ export {
288
291
  formatReviewComment,
289
292
  createFailedReview
290
293
  };
291
- //# sourceMappingURL=chunk-MNQYHPWP.js.map
294
+ //# sourceMappingURL=chunk-UX7ARNQK.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 { Result } from '../core/index.js';\nimport { getTimeProvider, createLogger } 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 {\n assessReputation,\n gateWithReputation,\n resolveReputationGatingMode,\n} 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\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 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 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 * Assesses the PR author's reputation and applies the gating rollout mode\n * (#3123). Returns the gate decision (for the policy gate) and the assessment\n * surfaced on the result. A suppressed demotion (audit/off) is logged.\n */\nexport function gatePRAuthor(\n pr: PRMetadata,\n trustResult: ClassifyResult,\n accountAgeDays: number | undefined,\n cache: ReputationCache,\n enableReputation: boolean\n): { gateDecision: ReputationGateDecision; trustAssessment: PRTrustAssessment } {\n const reputation = assessPRReputation(pr, cache, enableReputation, accountAgeDays);\n const gateDecision = gateWithReputation(\n trustResult.trustTier,\n reputation,\n resolveReputationGatingMode()\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 {\n gateDecision,\n trustAssessment: buildPRTrustAssessment(trustResult, 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// =============================================================================\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 // 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.approved, false);\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: 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 = 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: 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\nfunction formatStatsSection(result: PRReviewDraft): 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":[]}
package/dist/cli.js CHANGED
@@ -13,12 +13,12 @@ import {
13
13
  generateSummary,
14
14
  parseFindings,
15
15
  sumFindings
16
- } from "./chunk-MNQYHPWP.js";
16
+ } from "./chunk-UX7ARNQK.js";
17
17
  import "./chunk-SAPRUA5H.js";
18
18
  import {
19
19
  setupCommandAsync,
20
20
  verifyCommand
21
- } from "./chunk-SYR23V25.js";
21
+ } from "./chunk-RXXKAEZP.js";
22
22
  import "./chunk-G54Y5OQU.js";
23
23
  import {
24
24
  AuthHandler,
@@ -139,7 +139,7 @@ import {
139
139
  validateCommand,
140
140
  validateWorkflow,
141
141
  wrapInMarkdownFence
142
- } from "./chunk-G45DZK5W.js";
142
+ } from "./chunk-BSGHDAEM.js";
143
143
  import "./chunk-DJLJGL34.js";
144
144
  import "./chunk-HFOQKCD2.js";
145
145
  import "./chunk-BQTMMLQQ.js";
@@ -166,7 +166,7 @@ import {
166
166
  loadConfig,
167
167
  runDoctor,
168
168
  validateNexusEnv
169
- } from "./chunk-SQOTLTGX.js";
169
+ } from "./chunk-3TMJ34M4.js";
170
170
  import {
171
171
  buildOpenAICompatAdapters,
172
172
  readOpenAICompatEnv
@@ -183,11 +183,15 @@ import {
183
183
  executeVoting,
184
184
  mapOutcomeToDecision,
185
185
  registerConsensusVoteTool
186
- } from "./chunk-EOIQRW7A.js";
186
+ } from "./chunk-5XSWNLKE.js";
187
187
  import {
188
188
  loadUsageEvents,
189
189
  rollupByModel
190
190
  } from "./chunk-6WRQIEKK.js";
191
+ import {
192
+ allOf,
193
+ anyOf
194
+ } from "./chunk-OQB3XC5O.js";
191
195
  import {
192
196
  synthesizeResearch
193
197
  } from "./chunk-CPEOXRCU.js";
@@ -4076,7 +4080,7 @@ ${file.patch}
4076
4080
  violations: policyResult.violations
4077
4081
  });
4078
4082
  }
4079
- const { formatReviewComment: formatReviewComment2 } = await import("./pr-reviewer-helpers-L3JGYIAM.js");
4083
+ const { formatReviewComment: formatReviewComment2 } = await import("./pr-reviewer-helpers-TZPWSNAU.js");
4080
4084
  const body = formatReviewComment2(result);
4081
4085
  const postResult = await provider.createReview(pr.prNumber, body, result.decision);
4082
4086
  if (!postResult.ok) {
@@ -4310,14 +4314,11 @@ async function validateToken(token) {
4310
4314
  }
4311
4315
  }
4312
4316
  async function runPreflightChecks(prUrl) {
4313
- const checks = [];
4314
4317
  const tokenCheck = await checkToken();
4315
- checks.push(tokenCheck);
4316
4318
  const urlCheck = checkPrUrl(prUrl);
4317
- checks.push(urlCheck);
4319
+ const checks = [tokenCheck, urlCheck];
4318
4320
  if (tokenCheck.passed) {
4319
- const scopeCheck = await checkTokenScopes();
4320
- checks.push(scopeCheck);
4321
+ checks.push(await checkTokenScopes());
4321
4322
  }
4322
4323
  const passed = checks.every((c) => c.passed);
4323
4324
  return { passed, checks };
@@ -14767,10 +14768,19 @@ async function validateSecurity(options) {
14767
14768
  });
14768
14769
  }
14769
14770
  } catch {
14771
+ findings.push({
14772
+ severity: "warning",
14773
+ category: "security",
14774
+ title: "Secret scan did not run",
14775
+ description: "The hardcoded-secret scan over recent commits failed to execute.",
14776
+ remediation: "Re-run with a full git history available, then review the output."
14777
+ });
14770
14778
  }
14771
14779
  return {
14772
14780
  expert: "security",
14773
- passed: !findings.some((f) => f.severity === "error"),
14781
+ // whenEmpty = false: with the failure above recorded as a finding, an empty
14782
+ // list now genuinely means "scanned, nothing found" (#4581).
14783
+ passed: !anyOf(findings, (f) => f.severity === "error", false),
14774
14784
  confidence: 0.85,
14775
14785
  findings,
14776
14786
  durationMs: Date.now() - startTime
@@ -15387,7 +15397,7 @@ async function runReleaseAnnounce(options) {
15387
15397
  }
15388
15398
  results.push(result);
15389
15399
  }
15390
- const allSuccess = results.every((r) => r.success);
15400
+ const allSuccess = allOf(results, (r) => r.success, false);
15391
15401
  return {
15392
15402
  success: allSuccess,
15393
15403
  version: opts.version,
@@ -15418,8 +15428,9 @@ function printReleaseAnnounceResult(result, verbose = false) {
15418
15428
  }
15419
15429
  console.log("");
15420
15430
  }
15421
- const allSuccess = result.channels.every((c) => c.success);
15422
- if (allSuccess) {
15431
+ if (result.channels.length === 0) {
15432
+ console.log(`${colors.yellow}${colors.bold}\u26A0 No announcements were generated${colors.reset}`);
15433
+ } else if (allOf(result.channels, (c) => c.success, false)) {
15423
15434
  console.log(`${colors.green}${colors.bold}\u2713 All announcements generated${colors.reset}`);
15424
15435
  } else {
15425
15436
  console.log(`${colors.yellow}${colors.bold}\u26A0 Some announcements failed${colors.reset}`);
@@ -15446,6 +15457,12 @@ async function releaseAnnounceCommand(args) {
15446
15457
  const channels = channelList.filter(
15447
15458
  (c) => c === "blog" || c === "bluesky"
15448
15459
  );
15460
+ if (channels.length === 0) {
15461
+ console.error(
15462
+ `${colors.red}Error: No known announcement channels in "${channelList.join(",")}" (known: blog, bluesky)${colors.reset}`
15463
+ );
15464
+ return 1;
15465
+ }
15449
15466
  const result = await runReleaseAnnounce({
15450
15467
  version,
15451
15468
  channels,
@@ -15454,7 +15471,7 @@ async function releaseAnnounceCommand(args) {
15454
15471
  ...args.options.releaseUrl !== void 0 && { releaseUrl: args.options.releaseUrl }
15455
15472
  });
15456
15473
  printReleaseAnnounceResult(result, args.options.verbose);
15457
- return result.channels.every((c) => c.success) ? 0 : 1;
15474
+ return allOf(result.channels, (c) => c.success, false) ? 0 : 1;
15458
15475
  }
15459
15476
 
15460
15477
  // src/cli/scaffold.ts
@@ -22662,7 +22679,7 @@ var ScenarioRunner = class {
22662
22679
  try {
22663
22680
  const stepResults = await this.executeSteps(scenario, testConfig);
22664
22681
  const validations = this.validateResults(stepResults, scenario.expectedOutputs);
22665
- const passed = validations.every((v) => v.passed);
22682
+ const passed = allOf(validations, (v) => v.passed, false);
22666
22683
  const durationMs = getTimeProvider().now() - startTime;
22667
22684
  this.log.info("Scenario completed", { scenarioId: scenario.id, passed, durationMs });
22668
22685
  return {