nexus-agents 2.125.1 → 2.125.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6431,7 +6431,6 @@ export {
6431
6431
  createUnifiedRegistry,
6432
6432
  getGlobalRegistry,
6433
6433
  resetGlobalRegistry,
6434
- PersistentOutcomeStore,
6435
6434
  JobStatusSchema,
6436
6435
  writeJobPending,
6437
6436
  writeJobComplete,
@@ -6446,6 +6445,7 @@ export {
6446
6445
  suggestRetryAfterMs,
6447
6446
  DEFAULT_VOTE_TIMEOUT_MS,
6448
6447
  isRateLimitError,
6448
+ PersistentOutcomeStore,
6449
6449
  VOTER_ROLES,
6450
6450
  NoAdapterError,
6451
6451
  collectRealVotes,
@@ -6503,4 +6503,4 @@ export {
6503
6503
  CONSENSUS_VOTE_OUTPUT_SCHEMA,
6504
6504
  registerConsensusVoteTool
6505
6505
  };
6506
- //# sourceMappingURL=chunk-NPRLDYPV.js.map
6506
+ //# sourceMappingURL=chunk-5VIJQRZ4.js.map
@@ -810,9 +810,17 @@ var INFRA_FAILURE_CATEGORIES = /* @__PURE__ */ new Set([
810
810
  "adapter_unavailable",
811
811
  "parse"
812
812
  ]);
813
+ var UNATTRIBUTED_VALUES = /* @__PURE__ */ new Set([
814
+ "",
815
+ "unknown",
816
+ "expert",
817
+ "heuristic",
818
+ "default"
819
+ ]);
813
820
  function accumulateQualityBuckets(outcomes) {
814
821
  const buckets = /* @__PURE__ */ new Map();
815
822
  for (const o of outcomes) {
823
+ if (UNATTRIBUTED_VALUES.has(o.cli) || UNATTRIBUTED_VALUES.has(o.model)) continue;
816
824
  const key = `${o.cli}::${o.category}`;
817
825
  const b = buckets.get(key) ?? { cli: o.cli, category: o.category, ok: 0, total: 0, infra: 0 };
818
826
  if (!o.success && INFRA_FAILURE_CATEGORIES.has(o.failureCategory ?? "")) {
@@ -1248,4 +1256,4 @@ export {
1248
1256
  runImprovementReview,
1249
1257
  registerImprovementReviewTool
1250
1258
  };
1251
- //# sourceMappingURL=chunk-MCVM6UWK.js.map
1259
+ //# sourceMappingURL=chunk-6MFQJEFC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/mcp/tools/improvement-review.ts","../src/mcp/middleware/tool-prerequisites.ts","../src/governance/fitness-score.ts","../src/mcp/tools/improvement-review-signals.ts","../src/mcp/tools/improvement-remediation.ts","../src/mcp/tools/improvement-remediation-shadow.ts"],"sourcesContent":["/**\n * nexus-agents/mcp - Improvement Review Tool\n *\n * Periodic, threshold-gated observability-driven improvement loop.\n *\n * Reads from existing observability primitives (OutcomeStore, weather-report,\n * fitness-audit, audit-chain) and surfaces patterns that cross documented\n * thresholds as candidate GitHub issues. Never auto-merges; humans or\n * `consensus_vote` decide what to implement.\n *\n * Replaces the deleted `src/workflows/self-development/` engine, which never\n * wired up to consume any of these signals.\n *\n * @module mcp/tools/improvement-review\n * (Source: Issue #2402)\n */\n\nimport { execFile } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { promisify } from 'node:util';\n/* eslint-disable max-lines */\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { createLogger, formatZodError, getErrorMessage, type ILogger } from '../../core/index.js';\nimport { wrapToolWithTimeout, toSdkCallback, getToolTimeout } from '../middleware/tool-wrapper.js';\nimport { createSecureHandler, type HandlerContext } from '../middleware/secure-handler.js';\nimport { withPrerequisite } from '../middleware/tool-prerequisites.js';\nimport {\n toolStructuredError,\n toolSuccessStructured,\n type ToolResult,\n type BaseMcpToolDeps,\n} from './tool-result.js';\nimport { getOutcomeStore } from '../../orchestration/outcomes/outcome-store.js';\nimport type { TaskOutcome } from '../../orchestration/outcomes/outcome-types.js';\nimport { calculateFitnessScore, type FitnessAudit } from '../../governance/fitness-score.js';\nimport { getPipelineEventBus } from '../../pipeline/event-bus.js';\nimport type { VoteRejectedSignalEvent } from '../../pipeline/event-types.js';\nimport { REJECTION_CATEGORIES } from '../../consensus/types-core.js';\nimport { emitFitnessDeclinedSignal } from './improvement-review-signals.js';\nimport { improvementSignalsToTasks } from './improvement-remediation.js';\nimport { recordRemediationShadow } from './improvement-remediation-shadow.js';\nimport type { PipelineTask } from '../../pipeline/dev-pipeline.js';\nimport { getToolAnnotations } from '../tool-annotations.js';\n\nconst execFileAsync = promisify(execFile);\n\n// ============================================================================\n// Schemas\n// ============================================================================\n\nexport const ImprovementReviewInputSchema = z.object({\n lookbackDays: z\n .number()\n .int()\n .min(1)\n .max(90)\n .optional()\n .default(7)\n .describe('Lookback window for outcome data, in days. Default 7.'),\n fileIssues: z\n .boolean()\n .optional()\n .default(false)\n .describe(\n 'When true, file candidate issues via `gh issue create` for crossed thresholds (rate-limited to 5 per run, deduped against open issues). When false (default), return signals only.'\n ),\n minSampleSize: z\n .number()\n .int()\n .min(1)\n .max(1000)\n .optional()\n .default(5)\n .describe('Minimum sample size before a CLI/category signal can fire.'),\n fitnessFloor: z\n .number()\n .int()\n .min(0)\n .max(100)\n .optional()\n .default(90)\n .describe('Fitness score below this threshold triggers a tech-debt signal.'),\n selfEvalReportPath: z\n .string()\n .optional()\n .describe(\n 'Optional path to a self-eval JSON report (from `self-eval --json`). When set, ' +\n 'high-confidence unanimous deprecate/refactor findings are surfaced as tech-debt ' +\n 'signals through the same deduped/rate-limited issue path (#3224). Unreadable/malformed ' +\n 'reports are skipped (no signal). Absent → no self-eval signals.'\n ),\n});\n\nexport type ImprovementReviewInput = z.infer<typeof ImprovementReviewInputSchema>;\n\nexport type SignalCategory = 'routing' | 'tech-debt' | 'bug' | 'security' | 'consensus';\n\nexport interface ImprovementSignal {\n readonly category: SignalCategory;\n /** Stable key used for dedup against existing issues. */\n readonly signalKey: string;\n /** Severity per CVSS-aligned scale (security uses critical; others use warning/info). */\n readonly severity: 'info' | 'warning' | 'critical';\n /** One-line title suitable for a GitHub issue. */\n readonly title: string;\n /** Multi-line body with evidence (sample counts, time windows, observed values). */\n readonly body: string;\n /** Linkable evidence the signal is grounded in observability data, not intuition. */\n readonly evidence: {\n readonly samples?: number;\n readonly window?: string;\n readonly observedValue?: number;\n readonly threshold?: number;\n };\n}\n\nexport interface ImprovementReviewResponse {\n readonly window: string;\n readonly totalOutcomes: number;\n readonly signals: readonly ImprovementSignal[];\n /**\n * Remediation tasks derived from {@link signals} (#3540 capability-loop\n * increment 1) — SUGGEST-ONLY: structured tasks for a reviewer to consider\n * routing through the dev-pipeline. Nothing here is executed or auto-invoked.\n */\n readonly remediationTasks: readonly PipelineTask[];\n readonly issuesFiled: readonly { readonly signalKey: string; readonly issueUrl: string }[];\n readonly issuesSkipped: readonly { readonly signalKey: string; readonly reason: string }[];\n}\n\n// ============================================================================\n// Pure threshold logic (testable without fs / network)\n// ============================================================================\n\nconst MAX_ISSUES_PER_RUN = 5;\n\nconst HOUR_MS = 60 * 60 * 1000;\nconst DAY_MS = 24 * HOUR_MS;\n\n/** Filter outcomes to a lookback window. Outcome timestamps are ISO strings. */\nexport function filterByLookback(\n outcomes: readonly TaskOutcome[],\n lookbackDays: number,\n now: number\n): readonly TaskOutcome[] {\n const cutoff = now - lookbackDays * DAY_MS;\n return outcomes.filter((o) => {\n const t = Date.parse(o.timestamp);\n return Number.isFinite(t) && t >= cutoff;\n });\n}\n\n/**\n * Infrastructure/transport failure categories — NOT model reasoning quality\n * (#3620). These are excluded from the CLI performance-floor so the routing\n * signal measures whether the MODEL does the task, not whether the adapter was\n * reachable. Adapter outages / empty responses still surface separately via\n * {@link detectFailureCategoryConcentration}, so excluding them here doesn't hide\n * them — it just stops them being mislabeled as a CLI quality regression.\n */\nconst INFRA_FAILURE_CATEGORIES: ReadonlySet<string> = new Set([\n 'timeout',\n 'authentication',\n 'rate_limit',\n 'connection',\n 'adapter_unavailable',\n 'parse',\n]);\n\n/**\n * Detect CLI × category pairs whose MODEL-QUALITY success rate has fallen below\n * the performance floor with at least minSamples observations.\n *\n * Threshold: quality success rate < 60% AND quality-samples >= minSamples.\n * Infra/transport failures (adapter_unavailable, parse/empty-response, auth,\n * rate-limit, timeout, connection) are excluded from the rate (#3620) — they are\n * availability problems, not model quality, and surface via the failure-category\n * concentration detector instead.\n */\ninterface QualityBucket {\n cli: string;\n category: string;\n ok: number;\n total: number;\n infra: number;\n}\n\n/**\n * Models/clis that don't identify a real executor (#3624) — outcomes with these\n * can't be attributed to a CLI's quality, so they're excluded from the floor\n * (else the skew just moves from a real CLI onto an `unknown`/placeholder one).\n */\nconst UNATTRIBUTED_VALUES: ReadonlySet<string> = new Set([\n '',\n 'unknown',\n 'expert',\n 'heuristic',\n 'default',\n]);\n\n/** Bucket outcomes by cli×category, separating infra failures from quality ones. */\nfunction accumulateQualityBuckets(outcomes: readonly TaskOutcome[]): Map<string, QualityBucket> {\n const buckets = new Map<string, QualityBucket>();\n for (const o of outcomes) {\n // Skip outcomes that can't attribute a real executing CLI (#3624).\n if (UNATTRIBUTED_VALUES.has(o.cli) || UNATTRIBUTED_VALUES.has(o.model)) continue;\n const key = `${o.cli}::${o.category}`;\n const b = buckets.get(key) ?? { cli: o.cli, category: o.category, ok: 0, total: 0, infra: 0 };\n if (!o.success && INFRA_FAILURE_CATEGORIES.has(o.failureCategory ?? '')) {\n b.infra += 1; // infra/transport failure — excluded from the quality rate\n } else {\n b.total += 1;\n if (o.success) b.ok += 1;\n }\n buckets.set(key, b);\n }\n return buckets;\n}\n\n/** Build a performance-floor signal for a below-floor quality bucket. */\nfunction floorSignalFromBucket(\n b: QualityBucket,\n minSamples: number,\n windowLabel: string\n): ImprovementSignal {\n const rate = b.ok / b.total;\n const ratePct = Math.round(rate * 100);\n const infraNote =\n b.infra > 0\n ? ` (${String(b.infra)} infra/transport failures excluded — see failure-concentration signals)`\n : '';\n return {\n category: 'routing',\n signalKey: `routing:cli-floor:${b.cli}:${b.category}`,\n severity: rate < 0.4 ? 'critical' : 'warning',\n title: `routing: ${b.cli} model-quality success ${String(ratePct)}% on ${b.category} (${windowLabel})`,\n body: [\n `Observed model-quality performance floor breach in the ${windowLabel} window.`,\n '',\n `- CLI: \\`${b.cli}\\``,\n `- Category: \\`${b.category}\\``,\n `- Quality success rate: ${String(ratePct)}% (${String(b.ok)}/${String(b.total)})${infraNote}`,\n `- Threshold: 60% with ≥${String(minSamples)} quality samples`,\n '',\n 'Quality failures only (infra/transport excluded). Consider routing this category away from this CLI, or investigating the failure pattern via `weather_report` and the OutcomeStore.',\n ].join('\\n'),\n evidence: { samples: b.total, window: windowLabel, observedValue: rate, threshold: 0.6 },\n };\n}\n\nexport function detectCliPerformanceFloor(\n outcomes: readonly TaskOutcome[],\n minSamples: number,\n windowLabel: string\n): readonly ImprovementSignal[] {\n const signals: ImprovementSignal[] = [];\n for (const b of accumulateQualityBuckets(outcomes).values()) {\n if (b.total < minSamples) continue;\n if (b.ok / b.total >= 0.6) continue;\n signals.push(floorSignalFromBucket(b, minSamples, windowLabel));\n }\n return signals;\n}\n\n/**\n * Minimum number of rejected plans citing the same ADR-0016 rule before a\n * recurring-rejection pattern is worth surfacing. Two is coincidence; three is\n * a systemic planning gap (mirrors the DRY \"third occurrence\" rule).\n */\nconst MIN_REJECTION_PATTERN = 3;\n\n/**\n * Detect recurring consensus-rejection patterns (#3259): a single ADR-0016\n * rejection rule (`DRY_VIOLATION`, `OVER_ENGINEERING`, `SCOPE_CREEP`, …) cited\n * across ≥{@link MIN_REJECTION_PATTERN} rejected plans in the window. The\n * `signal.vote_rejected` events are produced by `consensus_vote` on rejection\n * (consensus-vote-signals.ts) and buffered on the pipeline event bus; this\n * detector closes the loop the system review flagged as missing — recurring\n * rejection for the same reason means the planner keeps making the same class\n * of mistake, which the next improvement cycle should name explicitly.\n *\n * Events with no `rejectionRules` (un-categorized rejections) contribute no\n * pattern signal — there is nothing actionable to aggregate on.\n */\nexport function detectConsensusRejectionSignals(\n events: readonly VoteRejectedSignalEvent[],\n windowLabel: string\n): readonly ImprovementSignal[] {\n if (events.length === 0) return [];\n\n // Defense-in-depth allowlist: the only producer (consensus-vote-signals.ts)\n // sources rules from the Zod-validated ADR-0016 enum, so a free-form/poisoned\n // rule cannot reach here today. Re-validating against REJECTION_CATEGORIES\n // makes that safety local — an unexpected rule string never reaches an issue\n // title/body — instead of relying on cross-file inference (#3259 review).\n const allowed = new Set<string>(REJECTION_CATEGORIES);\n const byRule = new Map<string, number>();\n for (const e of events) {\n for (const rule of e.rejectionRules ?? []) {\n if (!allowed.has(rule)) continue;\n byRule.set(rule, (byRule.get(rule) ?? 0) + 1);\n }\n }\n\n const signals: ImprovementSignal[] = [];\n for (const [rule, count] of byRule) {\n if (count < MIN_REJECTION_PATTERN) continue;\n signals.push({\n category: 'consensus',\n signalKey: `consensus:rejection-pattern:${rule}`,\n severity: count >= MIN_REJECTION_PATTERN * 2 ? 'warning' : 'info',\n title: `consensus: ${String(count)} plans rejected for \\`${rule}\\` in ${windowLabel}`,\n body: [\n `Recurring consensus-rejection pattern in the ${windowLabel} window.`,\n '',\n `- Rejection rule: \\`${rule}\\` (ADR-0016 category)`,\n `- Occurrences: ${String(count)} rejected plans`,\n `- Threshold: ≥${String(MIN_REJECTION_PATTERN)} plans citing the same rule`,\n '',\n 'The planner keeps producing plans that voters reject for the same reason. ' +\n 'Feed this back into plan generation (e.g. a planning guardrail or a ' +\n 'targeted prompt note) rather than rejecting plan-by-plan. Inspect the ' +\n 'rejected proposals via `query_trace` / the consensus audit chain.',\n ].join('\\n'),\n evidence: {\n samples: count,\n window: windowLabel,\n observedValue: count,\n threshold: MIN_REJECTION_PATTERN,\n },\n });\n }\n return signals;\n}\n\n/**\n * Detect failure-category concentration: a single failure category accounts\n * for > 50% of all failures with at least 10 failures observed.\n */\nexport function detectFailureCategoryConcentration(\n outcomes: readonly TaskOutcome[],\n windowLabel: string\n): readonly ImprovementSignal[] {\n const failures = outcomes.filter((o) => !o.success);\n if (failures.length < 10) return [];\n\n const byCategory = new Map<string, number>();\n for (const f of failures) {\n const cat = f.failureCategory ?? 'unknown';\n byCategory.set(cat, (byCategory.get(cat) ?? 0) + 1);\n }\n\n const signals: ImprovementSignal[] = [];\n for (const [cat, count] of byCategory) {\n const share = count / failures.length;\n if (share <= 0.5) continue;\n const sharePct = Math.round(share * 100);\n signals.push({\n category: 'bug',\n signalKey: `bug:failure-concentration:${cat}`,\n severity: 'warning',\n title: `bug: ${String(sharePct)}% of failures in ${windowLabel} share category \\`${cat}\\``,\n body: [\n `Failure-category concentration breach in the ${windowLabel} window.`,\n '',\n `- Category: \\`${cat}\\``,\n `- Share: ${String(sharePct)}% (${String(count)}/${String(failures.length)} failures)`,\n `- Threshold: > 50% with ≥10 failures`,\n '',\n 'A single failure mode dominating the error budget usually means a systemic bug or routing miss. Investigate via `query_trace` and the OutcomeStore.',\n ].join('\\n'),\n evidence: {\n samples: failures.length,\n window: windowLabel,\n observedValue: share,\n threshold: 0.5,\n },\n });\n }\n return signals;\n}\n\nfunction buildFloorSignal(audit: FitnessAudit, fitnessFloor: number): ImprovementSignal {\n return {\n category: 'tech-debt',\n signalKey: `tech-debt:fitness-below-floor`,\n severity: audit.score < 70 ? 'critical' : 'warning',\n title: `tech-debt: fitness score ${String(audit.score)}/100 below floor ${String(fitnessFloor)}`,\n body: [\n `Code fitness score has dropped below the governance floor.`,\n '',\n `- Score: ${String(audit.score)} / 100`,\n `- Floor: ${String(fitnessFloor)} (governance threshold per CLAUDE.md)`,\n `- Findings: ${String(audit.findings.length)} total`,\n '',\n 'Run `nexus-agents fitness-audit` for the full breakdown. Critical findings:',\n ...audit.findings\n .filter((f) => f.severity === 'critical')\n .slice(0, 5)\n .map((f) => `- ${f.dimension}: ${f.description}`),\n ].join('\\n'),\n evidence: { observedValue: audit.score, threshold: fitnessFloor },\n };\n}\n\nfunction buildCriticalFindingSignal(finding: FitnessAudit['findings'][number]): ImprovementSignal {\n return {\n category: 'tech-debt',\n signalKey: `tech-debt:fitness-critical:${finding.dimension}`,\n severity: 'critical',\n title: `tech-debt: critical fitness finding in ${finding.dimension}`,\n body: [\n `Fitness audit returned a CRITICAL finding.`,\n '',\n `- Dimension: \\`${finding.dimension}\\``,\n `- Description: ${finding.description}`,\n `- Points deducted: ${String(finding.pointsDeducted)}`,\n finding.location !== undefined ? `- Location: ${finding.location}` : '',\n finding.suggestion !== undefined ? `- Suggestion: ${finding.suggestion}` : '',\n ]\n .filter((line) => line.length > 0)\n .join('\\n'),\n evidence: { observedValue: -finding.pointsDeducted },\n };\n}\n\n/** Detect fitness signals: score below floor OR critical findings. */\nexport function detectFitnessSignals(\n audit: FitnessAudit,\n fitnessFloor: number\n): readonly ImprovementSignal[] {\n const signals: ImprovementSignal[] = [];\n // #3621: a non-auditable result (e.g. run from the global npm install) has a\n // meaningless score of 0 — it is \"could not audit\", not \"fitness is low\". Do\n // not emit a spurious below-floor tech-debt signal for it.\n if (audit.auditable === false) return signals;\n if (audit.score < fitnessFloor) signals.push(buildFloorSignal(audit, fitnessFloor));\n for (const finding of audit.findings) {\n if (finding.severity === 'critical') signals.push(buildCriticalFindingSignal(finding));\n }\n return signals;\n}\n\n// ============================================================================\n// Self-eval findings → tech-debt signals (#3224)\n// ============================================================================\n\n/** Minimum confidence for a self-eval finding to surface as a signal. */\nconst SELF_EVAL_CONFIDENCE_FLOOR = 0.8;\n\n/**\n * Minimal, defensive schema for the `self-eval --json` report. We only read the\n * fields needed to surface a finding; unknown extras are ignored so the parse\n * tolerates schema drift in the (externally produced) artifact.\n */\nconst SelfEvalReportSchema = z.object({\n results: z.array(\n z.object({\n component: z.string(),\n finalRecommendation: z.string(),\n confidence: z.number(),\n dissent: z.array(z.unknown()).optional().default([]),\n evidenceQuality: z.number().optional(),\n })\n ),\n});\n\n/**\n * Convert a parsed self-eval report into tech-debt `ImprovementSignal`s.\n *\n * Only **actionable, high-confidence, unanimous** findings surface (#3224): a\n * `deprecate`/`refactor` recommendation with NO dissent and confidence at/above\n * {@link SELF_EVAL_CONFIDENCE_FLOOR}. This is a pure transform — it surfaces a\n * human decision point (a candidate issue), never an automatic routing change.\n */\nexport function detectSelfEvalSignals(\n report: z.infer<typeof SelfEvalReportSchema>,\n windowLabel: string\n): readonly ImprovementSignal[] {\n const signals: ImprovementSignal[] = [];\n for (const r of report.results) {\n const actionable =\n r.finalRecommendation === 'deprecate' || r.finalRecommendation === 'refactor';\n if (!actionable || r.dissent.length > 0 || r.confidence < SELF_EVAL_CONFIDENCE_FLOOR) continue;\n signals.push({\n category: 'tech-debt',\n signalKey: `tech-debt:self-eval:${r.component}:${r.finalRecommendation}`,\n severity: r.finalRecommendation === 'deprecate' ? 'warning' : 'info',\n title: `tech-debt: self-eval recommends ${r.finalRecommendation} for ${r.component}`,\n body: [\n `All self-eval evaluators agreed (no dissent) on **${r.finalRecommendation}** for \\`${r.component}\\``,\n `with confidence ${(r.confidence * 100).toFixed(0)}%${r.evidenceQuality !== undefined ? ` (evidence quality ${(r.evidenceQuality * 100).toFixed(0)}%)` : ''}.`,\n '',\n 'This is a RECOMMENDATION surfaced for human review — not an automatic change.',\n ].join('\\n'),\n evidence: {\n observedValue: r.confidence,\n threshold: SELF_EVAL_CONFIDENCE_FLOOR,\n window: windowLabel,\n },\n });\n }\n return signals;\n}\n\n/**\n * Read + parse a self-eval JSON report and convert it to signals. Fail-soft:\n * an unreadable or malformed report yields NO signals (logged at warn) rather\n * than breaking the review.\n */\nexport async function loadSelfEvalSignals(\n reportPath: string,\n windowLabel: string,\n logger: ReturnType<typeof createLogger>\n): Promise<readonly ImprovementSignal[]> {\n try {\n const raw = await readFile(reportPath, 'utf8');\n const parsed = SelfEvalReportSchema.safeParse(JSON.parse(raw));\n if (!parsed.success) {\n logger.warn('Self-eval report ignored — schema mismatch', {\n reportPath,\n error: formatZodError(parsed.error),\n });\n return [];\n }\n return detectSelfEvalSignals(parsed.data, windowLabel);\n } catch (error) {\n logger.warn('Self-eval report ignored — unreadable/invalid JSON', {\n reportPath,\n error: getErrorMessage(error),\n });\n return [];\n }\n}\n\n// ============================================================================\n// Issue filing (gated, dedup-checked, command-injection-safe)\n// ============================================================================\n\n/**\n * Check whether an existing OPEN issue already covers this signal key.\n * Uses `gh issue list --search` with the signal key as a literal phrase.\n * The signal key appears in our filed-issue body so this dedup is reliable.\n */\nasync function existingIssueForSignal(signalKey: string): Promise<string | null> {\n try {\n // Strip double-quotes from the search term so a quote in the signalKey\n // (e.g. an oddly-named self-eval component path, #3224) can't break the\n // `\"...\" in:body` phrase query and silently defeat dedup → refiling.\n const searchTerm = signalKey.replace(/\"/g, '');\n const { stdout } = await execFileAsync('gh', [\n 'issue',\n 'list',\n '--state',\n 'open',\n '--search',\n `\"${searchTerm}\" in:body`,\n '--json',\n 'number,url',\n '--limit',\n '5',\n ]);\n const parsed = JSON.parse(stdout) as readonly { url?: string }[];\n if (parsed.length > 0 && typeof parsed[0]?.url === 'string') {\n return parsed[0].url;\n }\n return null;\n } catch {\n // gh failure → conservatively treat as \"no dup\" but log upstream.\n return null;\n }\n}\n\n/**\n * File an issue via `gh issue create` using execFile (no shell, no\n * command-injection risk on errorMessage / title / body content).\n */\nasync function fileIssueForSignal(\n signal: ImprovementSignal\n): Promise<{ ok: true; url: string } | { ok: false; error: string }> {\n // Embed the signal key in the body so dedup is reliable on subsequent runs.\n const body = `${signal.body}\\n\\n---\\n\\n_Signal key (do not edit): \\`${signal.signalKey}\\` · Generated by \\`improvement_review\\` (#2402) · Severity: ${signal.severity}_`;\n const labels = signal.category === 'security' ? 'security' : signal.category;\n\n try {\n const { stdout } = await execFileAsync('gh', [\n 'issue',\n 'create',\n '--title',\n signal.title,\n '--body',\n body,\n '--label',\n labels,\n ]);\n const url = stdout.trim();\n if (!url.startsWith('https://')) {\n return { ok: false, error: `gh returned unexpected output: ${stdout.slice(0, 200)}` };\n }\n return { ok: true, url };\n } catch (caught) {\n const message = caught instanceof Error ? caught.message : String(caught);\n return { ok: false, error: message };\n }\n}\n\n// ============================================================================\n// Handler\n// ============================================================================\n\nfunction safeFitnessAudit(now: number, ctx: HandlerContext): FitnessAudit {\n try {\n return calculateFitnessScore('improvement-review');\n } catch (caught) {\n ctx.logger.warn('fitness audit failed; skipping fitness signals', {\n error: caught instanceof Error ? caught.message : String(caught),\n });\n return {\n score: 100,\n dimensions: {} as FitnessAudit['dimensions'],\n findings: [],\n timestamp: new Date(now).toISOString(),\n version: 'improvement-review-fallback',\n };\n }\n}\n\nconst SEVERITY_ORDER: Record<ImprovementSignal['severity'], number> = {\n critical: 0,\n warning: 1,\n info: 2,\n};\n\nasync function fileSignalsAsIssues(\n signals: readonly ImprovementSignal[],\n ctx: HandlerContext\n): Promise<{\n issuesFiled: { signalKey: string; issueUrl: string }[];\n issuesSkipped: { signalKey: string; reason: string }[];\n}> {\n const issuesFiled: { signalKey: string; issueUrl: string }[] = [];\n const issuesSkipped: { signalKey: string; reason: string }[] = [];\n\n for (const signal of signals) {\n if (issuesFiled.length >= MAX_ISSUES_PER_RUN) {\n issuesSkipped.push({ signalKey: signal.signalKey, reason: 'rate-limit' });\n continue;\n }\n const existing = await existingIssueForSignal(signal.signalKey);\n if (existing !== null) {\n issuesSkipped.push({ signalKey: signal.signalKey, reason: `dup:${existing}` });\n continue;\n }\n const result = await fileIssueForSignal(signal);\n if (result.ok) {\n issuesFiled.push({ signalKey: signal.signalKey, issueUrl: result.url });\n ctx.logger.info('improvement signal filed', {\n signalKey: signal.signalKey,\n url: result.url,\n });\n } else {\n issuesSkipped.push({ signalKey: signal.signalKey, reason: `error:${result.error}` });\n }\n }\n\n return { issuesFiled, issuesSkipped };\n}\n\n/**\n * Read `signal.vote_rejected` events from the pipeline bus's buffered history,\n * narrowed to the lookback window. Returns `[]` if the bus is empty or every\n * event predates the window. Fail-soft: never throws into the review run.\n *\n * The bus is a per-process module singleton (event-bus.ts), so this only sees\n * rejections emitted by `consensus_vote` *in the same process* — i.e. a\n * long-lived MCP server where both tools share the buffer. In separate\n * CLI invocations the buffer starts empty and this correctly yields no signals\n * (the loop degrades to \"no consensus data\" rather than misreporting).\n */\nfunction readBufferedVoteRejections(\n now: number,\n lookbackDays: number\n): readonly VoteRejectedSignalEvent[] {\n const cutoff = now - lookbackDays * DAY_MS;\n return getPipelineEventBus()\n .query({ type: 'signal.vote_rejected' })\n .filter((e): e is VoteRejectedSignalEvent => e.type === 'signal.vote_rejected')\n .filter((e) => e.timestamp >= cutoff);\n}\n\n/**\n * Context-free runner exposed for both the MCP handler and the\n * `nexus-agents improvement-review` CLI subcommand (#2444). Pure dependencies\n * — pass a logger and an OutcomeStore-query result if you want to inject test\n * data; defaults read the global store and a no-op logger.\n */\nexport async function runImprovementReview(\n input: ImprovementReviewInput,\n deps: { readonly logger?: ReturnType<typeof createLogger> } = {}\n): Promise<ImprovementReviewResponse> {\n const logger = deps.logger ?? createLogger({ component: 'improvement_review' });\n const { lookbackDays, fileIssues, minSampleSize, fitnessFloor, selfEvalReportPath } = input;\n const now = Date.now();\n const windowLabel = `${String(lookbackDays)}d`;\n\n const allOutcomes = getOutcomeStore().query();\n const windowed = filterByLookback(allOutcomes, lookbackDays, now);\n const audit = safeFitnessAudit(now, { logger } as HandlerContext);\n\n // Surface high-confidence unanimous self-eval findings as tech-debt signals\n // (#3224) — opt-in via selfEvalReportPath, fail-soft (no path / bad file → none).\n const selfEvalSignals =\n selfEvalReportPath !== undefined\n ? await loadSelfEvalSignals(selfEvalReportPath, windowLabel, logger)\n : [];\n\n // Recurring consensus-rejection patterns (#3259). `consensus_vote` buffers a\n // `signal.vote_rejected` event per rejected plan on the pipeline bus; read the\n // buffered history (not a live subscription — this is a one-shot tool) and\n // window-filter by event timestamp before aggregating.\n const rejectionEvents = readBufferedVoteRejections(now, lookbackDays);\n\n const signals: ImprovementSignal[] = [\n ...detectCliPerformanceFloor(windowed, minSampleSize, windowLabel),\n ...detectFailureCategoryConcentration(windowed, windowLabel),\n ...detectFitnessSignals(audit, fitnessFloor),\n ...detectConsensusRejectionSignals(rejectionEvents, windowLabel),\n ...selfEvalSignals,\n ];\n signals.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);\n\n // Close the self-tuning loop: a below-floor fitness audit emits\n // signal.fitness_declined onto the typed pipeline bus for the shadow\n // TuneStage (#3147; #3289 Option 2 — observability signals route through bus A).\n emitFitnessDeclinedSignal(audit, fitnessFloor, getPipelineEventBus(), logger);\n\n // Shadow-mode auto-remediation selector (#3540 inc.2a / #3611): record the\n // would-auto-remediate decision per signal. Logs only — executes nothing.\n shadowRecordRemediations(signals, logger);\n\n const { issuesFiled, issuesSkipped } = fileIssues\n ? await fileSignalsAsIssues(signals, { logger } as HandlerContext)\n : { issuesFiled: [], issuesSkipped: [] };\n\n return {\n window: windowLabel,\n totalOutcomes: windowed.length,\n signals,\n // #3540 increment 1: surface remediation tasks derived from the signals\n // (suggest-only — composes existing detection, no auto-invocation).\n remediationTasks: improvementSignalsToTasks(signals),\n issuesFiled,\n issuesSkipped,\n };\n}\n\n/**\n * Best-effort shadow logging of would-auto-remediate decisions (#3611). Never\n * throws — observability must not break the review tool.\n */\nfunction shadowRecordRemediations(signals: readonly ImprovementSignal[], logger: ILogger): void {\n try {\n const shadow = recordRemediationShadow(signals);\n if (shadow.length === 0) return;\n const wouldRemediate = shadow.filter((r) => r.wouldAutoRemediate).length;\n logger.info('Auto-remediation shadow recorded (#3611 — nothing executed)', {\n signals: shadow.length,\n wouldAutoRemediate: wouldRemediate,\n humanGated: shadow.length - wouldRemediate,\n });\n } catch (err) {\n logger.warn('Auto-remediation shadow logging failed (non-fatal)', {\n error: getErrorMessage(err),\n });\n }\n}\n\nasync function reviewHandler(args: unknown, ctx: HandlerContext): Promise<ToolResult> {\n const parsed = ImprovementReviewInputSchema.safeParse(args);\n if (!parsed.success) {\n return toolStructuredError({\n errorCategory: 'validation',\n message: `Validation error: ${formatZodError(parsed.error)}`,\n });\n }\n const response = await runImprovementReview(parsed.data, { logger: ctx.logger });\n return toolSuccessStructured(response as unknown as Record<string, unknown>);\n}\n\n// ============================================================================\n// Registration\n// ============================================================================\n\nexport type ImprovementReviewDeps = BaseMcpToolDeps;\n\nconst description =\n 'Periodic threshold-gated observability-driven improvement loop. Reads OutcomeStore + ' +\n 'fitness audit, surfaces patterns crossing documented thresholds as candidate findings. ' +\n 'When fileIssues=true, files candidate GitHub issues via `gh issue create` (rate-limited ' +\n 'to 5 per run, deduped against open issues). Never auto-merges. Replaces the deleted ' +\n 'self-development engine (#2402).';\n\nconst TOOL_INPUT_SCHEMA = {\n lookbackDays: z\n .number()\n .int()\n .min(1)\n .max(90)\n .optional()\n .describe('Lookback window for outcome data, in days. Default 7.'),\n fileIssues: z\n .boolean()\n .optional()\n .describe(\n 'When true, file candidate issues for crossed thresholds (default false — return signals only)'\n ),\n minSampleSize: z\n .number()\n .int()\n .min(1)\n .max(1000)\n .optional()\n .describe('Minimum sample size before a CLI/category signal fires (default 5).'),\n fitnessFloor: z\n .number()\n .int()\n .min(0)\n .max(100)\n .optional()\n .describe('Fitness score below this threshold triggers a tech-debt signal (default 90).'),\n selfEvalReportPath: z\n .string()\n .optional()\n .describe(\n 'Optional path to a self-eval JSON report. High-confidence unanimous ' +\n 'deprecate/refactor findings surface as tech-debt signals (#3224).'\n ),\n};\n\n/** @category MCP */\nexport function registerImprovementReviewTool(\n server: McpServer,\n deps: ImprovementReviewDeps\n): void {\n const logger = deps.logger ?? createLogger({ tool: 'improvement_review' });\n const secureHandler = createSecureHandler(reviewHandler, {\n toolName: 'improvement_review',\n rateLimiter: deps.rateLimiter,\n logger,\n });\n const guardedHandler = withPrerequisite('improvement_review', secureHandler);\n const timeoutMs = getToolTimeout('improvement_review', deps.security);\n const wrappedHandler = wrapToolWithTimeout('improvement_review', guardedHandler, {\n timeoutMs,\n logger,\n });\n server.registerTool(\n 'improvement_review',\n {\n description,\n inputSchema: TOOL_INPUT_SCHEMA,\n annotations: getToolAnnotations('improvement_review'),\n },\n toSdkCallback(wrappedHandler)\n );\n logger.info('Registered improvement_review tool');\n}\n","/**\n * nexus-agents/mcp - Tool Prerequisite Gates\n *\n * Blocks sensitive MCP tools from running unless a **world-state\n * precondition** holds at call time (Issue #2652, Epic B).\n *\n * DESIGN — predicate, not session-ordering. A prerequisite is a predicate\n * over observable world state (is `gh` installed? is the data dir\n * writable?), evaluated on every invocation. It is NOT \"tool A must have\n * been called before tool B\" — MCP `tools/call` invocations are\n * independent, and a session-ordering gate is satisfied by an LLM calling\n * the prior tool pointlessly without making the precondition true. If a\n * requirement can only be expressed as \"call X first,\" it is not a\n * prerequisite — it is the tool's own internal responsibility (e.g.\n * untrusted-input trust-tier classification stays inside `issue_triage`\n * per `.rules/untrusted-input.md`, it is not gated here).\n *\n * Fail-closed: a predicate that throws blocks the tool.\n *\n * @module mcp/middleware/tool-prerequisites\n * @see Issue #2652\n */\n\nimport { execFile } from 'node:child_process';\nimport { access, constants } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { promisify } from 'node:util';\nimport { getErrorMessage } from '../../core/index.js';\nimport { getNexusDataDir } from '../../config/nexus-data-dir.js';\nimport { toolStructuredError, type ToolResult } from '../tools/tool-result.js';\nimport type { ContextAwareToolHandler, ToolHandler } from './middleware-chain.js';\n\nconst execFileAsync = promisify(execFile);\n\n/** Outcome of evaluating a prerequisite predicate. */\nexport interface PrerequisiteResult {\n ok: boolean;\n /** When `!ok`: what would make the predicate pass. Surfaced to the caller. */\n remediation?: string;\n}\n\n/** A world-state precondition a guarded tool requires at call time. */\nexport interface ToolPrerequisite {\n /** Stable identifier, e.g. `data-dir-writable`. */\n name: string;\n /** Why the tool needs it — for `.rules/tool-prerequisites.md` + the block envelope. */\n rationale: string;\n /** World-state predicate. MUST be cheap and side-effect-free. */\n check: () => Promise<PrerequisiteResult> | PrerequisiteResult;\n}\n\n// ============================================================================\n// Predicates\n// ============================================================================\n\n/** The `gh` CLI is on PATH — required by tools that file/read GitHub state. */\nasync function ghCliAvailable(): Promise<PrerequisiteResult> {\n try {\n await execFileAsync('gh', ['--version'], { timeout: 5000 });\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n remediation: `the 'gh' CLI is not available (${getErrorMessage(err)}) — install GitHub CLI and authenticate (\\`gh auth login\\`)`,\n };\n }\n}\n\n/**\n * The runtime data directory is writable, or — if it does not exist yet —\n * creatable (its parent is writable). Memory/registry backends create the\n * dir on demand, so \"not yet created\" is not a failure; an unwritable\n * parent is.\n */\nasync function dataDirWritable(): Promise<PrerequisiteResult> {\n const dir = getNexusDataDir();\n try {\n await access(dir, constants.W_OK);\n return { ok: true };\n } catch {\n // Dir may not exist yet — backends create it on demand. Treat a\n // writable parent (creatable) as satisfying the precondition.\n try {\n await access(dirname(dir), constants.W_OK);\n return { ok: true };\n } catch (err) {\n return {\n ok: false,\n remediation: `NEXUS_DATA_DIR (${dir}) is not writable or creatable (${getErrorMessage(err)}) — fix its permissions; run \\`nexus-agents doctor\\` to diagnose`,\n };\n }\n }\n}\n\n// ============================================================================\n// Registry\n// ============================================================================\n\n/**\n * Tools guarded by a call-time prerequisite. Keep this in sync with\n * `.rules/tool-prerequisites.md` — the `check:tool-prerequisites` CI gate\n * verifies every non-read-only tool is either listed here or in\n * `NO_PREREQUISITE` with a reason.\n */\nexport const TOOL_PREREQUISITES: Record<string, ToolPrerequisite> = {\n improvement_review: {\n name: 'gh-cli-available',\n rationale:\n \"improvement_review's fileIssues mode shells out to `gh` to file candidate issues; without it the write path fails mid-operation\",\n check: ghCliAvailable,\n },\n memory_write: {\n name: 'data-dir-writable',\n rationale:\n 'memory_write persists entries under NEXUS_DATA_DIR — an unwritable data dir fails the write confusingly mid-operation',\n check: dataDirWritable,\n },\n registry_import: {\n name: 'data-dir-writable',\n rationale:\n 'registry_import persists a draft registry entry under NEXUS_DATA_DIR when not in dryRun mode',\n check: dataDirWritable,\n },\n};\n\n/**\n * Non-read-only tools that deliberately have NO call-time prerequisite,\n * with the reason. The `check:tool-prerequisites` gate requires every\n * non-read-only tool to appear here or in `TOOL_PREREQUISITES`, so a newly\n * added sensitive tool can't ship ungated by omission.\n */\nexport const NO_PREREQUISITE: Record<string, string> = {\n orchestrate: 'orchestration is self-contained; sub-tool calls carry their own gates',\n create_expert: 'in-memory expert creation; no external precondition',\n execute_expert:\n 'adapter availability is handled by the resilient-adapter circuit breaker, not a pre-gate',\n run_workflow: 'workflow execution is self-contained; step tools carry their own gates',\n run_graph_workflow: 'graph execution is self-contained; node tools carry their own gates',\n run_pipeline: 'pipeline execution is self-contained; stage tools carry their own gates',\n run_dev_pipeline: 'pipeline execution is self-contained; stage tools carry their own gates',\n execute_spec: 'spec execution is self-contained; stage tools carry their own gates',\n consensus_vote: 'voter-CLI availability is handled by per-voter fallback, not a pre-gate',\n ci_health_check:\n 'reads upstream CI status + appends a telemetry event (#3530); no world-state precondition to gate',\n supply_chain_tradeoff_panel: 'wraps consensus_vote; same per-voter fallback applies',\n pr_review: 'wraps consensus_vote; same per-voter fallback applies',\n research_add:\n 'arXiv fetch failures surface as a transient envelope from the handler; no useful pre-gate',\n research_add_source:\n 'GitHub metadata fetch is best-effort with graceful fallback; no useful pre-gate',\n research_catalog_review: 'operates on local catalog state already loaded by the handler',\n issue_triage:\n 'untrusted-input safety (trust-tier classification, Rule of Two) is internal-handler logic per .rules/untrusted-input.md — not a call-time world-state predicate',\n cancel_job:\n 'reads + writes the local sidecar file only; cancel-after-complete is an idempotent no-op (first-wins) so misuse is bounded — no pre-gate needed',\n run_quality_gate:\n 'stateless QA gate over the given projectDir; a missing toolchain (tsc/eslint/vitest/build) surfaces as a check failure in the verdict, not a call-time world-state precondition — no pre-gate needed',\n};\n\n// ============================================================================\n// Middleware\n// ============================================================================\n\n/** Either MCP handler shape, both structurally callable with `(args, ctx?)`. */\ntype AnyToolHandler = ContextAwareToolHandler | ToolHandler;\n/** Concrete callable accepting an optional context — what the gate produces. */\ntype GuardedHandler = (args: unknown, ctx?: unknown) => Promise<ToolResult>;\n\n/**\n * Wrap `handler` so `prereq` is evaluated before it runs. A failing or\n * throwing predicate returns a structured `permission` error envelope\n * (#2649) carrying the failed prerequisite + a remediation hint in\n * `detail` — so the caller knows how to recover, not just that it was\n * blocked. Exported for testing; production code uses `withPrerequisite`.\n */\nexport function applyPrerequisite(\n toolName: string,\n prereq: ToolPrerequisite,\n handler: AnyToolHandler\n): GuardedHandler {\n return async (args: unknown, ctx?: unknown): Promise<ToolResult> => {\n let result: PrerequisiteResult;\n try {\n result = await prereq.check();\n } catch (err) {\n // Fail closed — a predicate that throws blocks the tool.\n result = { ok: false, remediation: `prerequisite check threw: ${getErrorMessage(err)}` };\n }\n if (!result.ok) {\n return toolStructuredError({\n errorCategory: 'permission',\n message: `${toolName} blocked: prerequisite '${prereq.name}' not satisfied`,\n detail: {\n failedPrerequisite: prereq.name,\n rationale: prereq.rationale,\n remediation: result.remediation ?? 'see .rules/tool-prerequisites.md',\n },\n });\n }\n return (handler as GuardedHandler)(args, ctx);\n };\n}\n\n/**\n * Wrap a handler so its tool's prerequisite (if any) is evaluated before\n * the handler runs. Tools with no entry in `TOOL_PREREQUISITES` pass\n * through untouched.\n */\nexport function withPrerequisite(toolName: string, handler: AnyToolHandler): AnyToolHandler {\n const prereq = TOOL_PREREQUISITES[toolName];\n if (prereq === undefined) return handler;\n return applyPrerequisite(toolName, prereq, handler);\n}\n","/**\n * CLI Orchestration Fitness Score — real filesystem analysis.\n * Penalizes duplicate paths, hidden behavior, non-determinism, poor\n * observability, config sprawl, cross-layer coupling. Rewards canonical\n * paths, determinism, telemetry, CLI ergonomics, governance.\n * @module governance/fitness-score\n */\n\n/* eslint-disable max-lines -- cohesive fitness calculator (governance allows 400-600) */\n\nimport { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { createLogger, type ILogger } from '../core/index.js';\n\n/** Find package root by walking up from current dir to find package.json with our name */\nfunction findPkgRoot(): string {\n let dir = import.meta.dirname;\n for (let i = 0; i < 10; i++) {\n const pkgPath = join(dir, 'package.json');\n if (existsSync(pkgPath)) {\n const content = readFileSync(pkgPath, 'utf-8');\n if (content.includes('\"nexus-agents\"')) return dir;\n }\n dir = join(dir, '..');\n }\n return join(import.meta.dirname, '../..');\n}\n\nconst PKG_ROOT = findPkgRoot();\nconst SRC_ROOT = join(PKG_ROOT, 'src');\nconst REPO_ROOT = join(PKG_ROOT, '../..');\nconst DOCS_ROOT = join(REPO_ROOT, 'docs');\n\nconst DETERMINISM_EXCLUDES: RegExp[] = [\n /\\.test\\.ts$/,\n /\\.spec\\.ts$/,\n /random-provider\\.ts$/,\n /time-provider\\.ts$/,\n];\n\n// =========================================================================\n// Filesystem utility methods (inlined from scripts/fitness-utils.ts)\n// =========================================================================\n\nfunction countFiles(dir: string, pattern: RegExp): number {\n if (!existsSync(dir)) return 0;\n let count = 0;\n for (const entry of readdirSync(dir)) {\n const fullPath = join(dir, entry);\n const stat = statSync(fullPath);\n if (stat.isDirectory() && !entry.startsWith('.')) {\n count += countFiles(fullPath, pattern);\n } else if (pattern.test(entry)) {\n count++;\n }\n }\n return count;\n}\n\nfunction fileContains(filePath: string, pattern: RegExp): boolean {\n if (!existsSync(filePath)) return false;\n return pattern.test(readFileSync(filePath, 'utf-8'));\n}\n\nfunction isExcluded(entry: string, excludePatterns?: RegExp[]): boolean {\n return excludePatterns?.some((p) => p.test(entry)) ?? false;\n}\n\nfunction countMatchesInFile(fullPath: string, contentPattern: RegExp): number {\n const matches = readFileSync(fullPath, 'utf-8').match(contentPattern);\n return matches?.length ?? 0;\n}\n\nfunction countPatternInDir(\n dir: string,\n filePattern: RegExp,\n contentPattern: RegExp,\n excludePatterns?: RegExp[]\n): number {\n if (!existsSync(dir)) return 0;\n let count = 0;\n for (const entry of readdirSync(dir)) {\n const fullPath = join(dir, entry);\n const stat = statSync(fullPath);\n if (stat.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {\n count += countPatternInDir(fullPath, filePattern, contentPattern, excludePatterns);\n } else if (filePattern.test(entry) && !isExcluded(entry, excludePatterns)) {\n count += countMatchesInFile(fullPath, contentPattern);\n }\n }\n return count;\n}\n\n// =========================================================================\n// Public types\n// =========================================================================\n\n/**\n * Individual fitness dimension scores.\n */\nexport interface FitnessDimensions {\n /** Penalty: duplicate paths to same workflow (0-20, higher = better) */\n readonly canonicalPaths: number;\n /** Penalty: hidden/magic behavior (0-15, higher = better) */\n readonly explicitBehavior: number;\n /** Reward: deterministic execution (0-15, higher = better) */\n readonly determinism: number;\n /** Reward: observability coverage (0-15, higher = better) */\n readonly observability: number;\n /** Penalty: config surface area (0-10, higher = better) */\n readonly configSimplicity: number;\n /** Penalty: cross-layer coupling (0-10, higher = better) */\n readonly layerSeparation: number;\n /** Reward: CLI ergonomics (0-10, higher = better) */\n readonly operatorErgonomics: number;\n /** Reward: governance injection (0-5, higher = better) */\n readonly governanceIntegration: number;\n}\n\n/**\n * Detailed fitness audit result.\n */\nexport interface FitnessAudit {\n /** Overall score (0-100) */\n readonly score: number;\n /** Individual dimension scores */\n readonly dimensions: FitnessDimensions;\n /** Specific findings */\n readonly findings: readonly FitnessFinding[];\n /** Timestamp of audit */\n readonly timestamp: string;\n /** Version/commit reference */\n readonly version: string;\n /**\n * Whether the audit actually ran against the nexus-agents source tree (#3621).\n * `false` is the \"could not audit from here\" sentinel (e.g. run from the global\n * npm install, where the bundled `src/` lacks `cli-adapters/`): the score is a\n * meaningless 0, NOT a real low-fitness result. Consumers (e.g.\n * improvement_review) MUST NOT treat a `false` audit's score as a signal.\n * Absent/`true` → a genuine audit.\n */\n readonly auditable?: boolean;\n}\n\n/**\n * Individual finding from audit.\n */\nexport interface FitnessFinding {\n /** Dimension affected */\n readonly dimension: keyof FitnessDimensions;\n /** Issue severity: 'info' | 'warning' | 'critical' */\n readonly severity: 'info' | 'warning' | 'critical';\n /** Description */\n readonly description: string;\n /** Points deducted */\n readonly pointsDeducted: number;\n /** Location in code (optional) */\n readonly location?: string;\n /** Suggested fix (optional) */\n readonly suggestion?: string;\n}\n\n/**\n * Fitness check definition.\n */\ninterface FitnessCheck {\n readonly dimension: keyof FitnessDimensions;\n readonly maxPoints: number;\n readonly name: string;\n readonly check: () => FitnessCheckResult;\n}\n\n/**\n * Result of a single fitness check.\n */\ninterface FitnessCheckResult {\n readonly score: number;\n readonly findings: FitnessFinding[];\n}\n\n/** Clamp score to [0, max]. */\nfunction clamp(score: number, max: number): number {\n return Math.max(0, Math.min(max, score));\n}\n\n/**\n * CLI Orchestration Fitness Score calculator.\n */\nexport class FitnessScoreCalculator {\n private readonly logger: ILogger;\n private readonly checks: FitnessCheck[] = [];\n\n constructor(logger?: ILogger) {\n this.logger = logger ?? createLogger({ component: 'FitnessScoreCalculator' });\n this.registerDefaultChecks();\n }\n\n /** Register default fitness checks. */\n private registerDefaultChecks(): void {\n const reg = (\n dimension: keyof FitnessDimensions,\n maxPoints: number,\n name: string,\n check: () => FitnessCheckResult\n ): void => {\n this.checks.push({ dimension, maxPoints, name, check });\n };\n reg('canonicalPaths', 20, 'Canonical Paths', () => this.checkCanonicalPaths());\n reg('explicitBehavior', 15, 'Explicit Behavior', () => this.checkExplicitBehavior());\n reg('determinism', 15, 'Determinism', () => this.checkDeterminism());\n reg('observability', 15, 'Observability', () => this.checkObservability());\n reg('configSimplicity', 10, 'Config Simplicity', () => this.checkConfigSimplicity());\n reg('layerSeparation', 10, 'Layer Separation', () => this.checkLayerSeparation());\n reg('operatorErgonomics', 10, 'Operator Ergonomics', () => this.checkOperatorErgonomics());\n reg('governanceIntegration', 5, 'Governance Integration', () =>\n this.checkGovernanceIntegration()\n );\n }\n\n /** Run full fitness audit. */\n audit(version: string): FitnessAudit {\n const findings: FitnessFinding[] = [];\n const dimensions: Record<string, number> = {};\n\n // #2716 — the audit's existsSync checks look at SRC_ROOT-relative paths\n // (cli-adapters/composite-router.ts, cli/doctor.ts, etc). When run from\n // an `npm install -g nexus-agents` copy, SRC_ROOT points at the installed\n // package's `src/` which only contains workflow templates — not the full\n // source tree. Result: every existsSync returns false and the audit\n // emits a parade of fictional \"missing\" findings against a healthy\n // source tree. (`npx nexus-agents fitness-audit` from a repo root hits\n // this path because npx resolves to the GLOBAL bin, not the local\n // workspace bundle.) Bail with a clear single-finding result instead.\n const cliAdaptersDir = join(SRC_ROOT, 'cli-adapters');\n if (!existsSync(cliAdaptersDir)) {\n return this.notSourceRepoResult(version);\n }\n\n for (const check of this.checks) {\n this.logger.debug(`Running fitness check: ${check.name}`);\n const result = check.check();\n dimensions[check.dimension] = result.score;\n findings.push(...result.findings);\n }\n\n const score = Object.values(dimensions).reduce((sum, val) => sum + val, 0);\n this.logger.info('Fitness audit complete', { score, version });\n\n // Build a strictly-typed FitnessDimensions instead of `as unknown as` cast\n // (#1913 Class G). If a check skipped a dimension it defaults to 0,\n // making it explicit rather than UB.\n const safeDim = (k: keyof FitnessDimensions): number => dimensions[k] ?? 0;\n const typedDimensions: FitnessDimensions = {\n canonicalPaths: safeDim('canonicalPaths'),\n explicitBehavior: safeDim('explicitBehavior'),\n determinism: safeDim('determinism'),\n observability: safeDim('observability'),\n configSimplicity: safeDim('configSimplicity'),\n layerSeparation: safeDim('layerSeparation'),\n operatorErgonomics: safeDim('operatorErgonomics'),\n governanceIntegration: safeDim('governanceIntegration'),\n };\n\n return {\n score,\n dimensions: typedDimensions,\n findings,\n timestamp: new Date().toISOString(),\n version,\n auditable: true,\n };\n }\n\n /**\n * Return when the runtime SRC_ROOT doesn't look like the nexus-agents\n * source tree (#2716). One finding, score 0 — distinct from \"the source\n * tree is broken\" because every existsSync would falsely report missing.\n */\n private notSourceRepoResult(version: string): FitnessAudit {\n const finding: FitnessFinding = {\n dimension: 'governanceIntegration',\n severity: 'info',\n description:\n 'fitness-audit must run against the nexus-agents source repo, ' +\n `but ${SRC_ROOT}/cli-adapters does not exist. You are likely ` +\n 'running the installed npm package via `npx nexus-agents`, which ' +\n 'ships only the bundled dist/. Clone the source repo and run ' +\n '`node packages/nexus-agents/dist/cli.js fitness-audit` (or invoke ' +\n '`pnpm fitness-audit` from the workspace root).',\n pointsDeducted: 0,\n };\n return {\n score: 0,\n dimensions: {\n canonicalPaths: 0,\n explicitBehavior: 0,\n determinism: 0,\n observability: 0,\n configSimplicity: 0,\n layerSeparation: 0,\n operatorErgonomics: 0,\n governanceIntegration: 0,\n },\n findings: [finding],\n timestamp: new Date().toISOString(),\n version,\n auditable: false,\n };\n }\n\n // =========================================================================\n // Individual Checks — real filesystem analysis\n // =========================================================================\n\n /**\n * Check canonical paths: penalize duplicate router implementations.\n *\n * The current minimum is 6 (raised from 5 in #2063 after an audit):\n * 1. composite-router — pipeline orchestrator\n * 2. budget-router — budget/cost filtering\n * 3. zero-router — hard-constraint exclusion\n * 4. preference-router — user/task preference application\n * 5. topsis-router — TOPSIS multi-criteria scoring\n * 6. agreement-cascade-router — agreement-based cascade retry\n *\n * Each stage is distinct per CLAUDE.md's documented pipeline:\n * Task → BudgetRouter → ZeroRouter → PreferenceRouter → TopsisRouter → Agreement → Model\n */\n private checkCanonicalPaths(): FitnessCheckResult {\n const findings: FitnessFinding[] = [];\n let score = 20;\n\n const routerCount = countFiles(join(SRC_ROOT, 'cli-adapters'), /router\\.ts$/);\n const ROUTER_COUNT_THRESHOLD = 6;\n if (routerCount > ROUTER_COUNT_THRESHOLD) {\n const excess = routerCount - ROUTER_COUNT_THRESHOLD;\n const deduction = Math.min(5, excess);\n score -= deduction;\n findings.push(\n this.finding(\n 'canonicalPaths',\n 'warning',\n `${String(routerCount)} router implementations found (target: <=${String(ROUTER_COUNT_THRESHOLD)})`,\n deduction,\n 'Consolidate duplicate routers into CompositeRouter'\n )\n );\n }\n\n if (existsSync(join(SRC_ROOT, 'cli-adapters/composite-router.ts'))) {\n score += 2;\n } else {\n score -= 3;\n findings.push(\n this.finding(\n 'canonicalPaths',\n 'critical',\n 'CompositeRouter missing — no unified routing entry point',\n 3\n )\n );\n }\n\n score = this.checkOrchestratorInterface(score, findings);\n\n return { score: clamp(score, 20), findings };\n }\n\n /** Sub-check for IOrchestrator interface and adapter wiring. */\n private checkOrchestratorInterface(score: number, findings: FitnessFinding[]): number {\n const orchPath = join(SRC_ROOT, 'core/types/orchestrator.ts');\n if (existsSync(orchPath) && fileContains(orchPath, /interface IOrchestrator/)) {\n score += 3;\n } else {\n score -= 2;\n findings.push(\n this.finding('canonicalPaths', 'warning', 'No IOrchestrator interface in core/types', 2)\n );\n }\n\n const adapterPath = join(SRC_ROOT, 'orchestration/orchestrator-adapters.ts');\n if (existsSync(adapterPath) && fileContains(adapterPath, /TechLeadAdapter|PuppeteerAdapter/)) {\n score += 2;\n }\n\n return score;\n }\n\n /**\n * Check explicit behavior: penalize hidden/magic behavior.\n *\n * Implementation is intentionally filesystem-signal-based (no AST parse).\n * The `NEXUS_ALLOW_MOCK_ORCHESTRATION` guard and magic-routing pattern\n * grep capture the observable failure modes that historically slipped\n * past review. AST-based detection of implicit fallbacks was considered\n * but not pursued — it would significantly widen this function's\n * footprint and the filesystem signals already catch the recurring\n * regressions. Revisit only if a new class of hidden-behavior bug\n * surfaces that this grep-over-source approach can't catch.\n */\n private checkExplicitBehavior(): FitnessCheckResult {\n const findings: FitnessFinding[] = [];\n let score = 15;\n\n // Check: mock orchestration requires explicit env var opt-in\n const mockGuardCount = countPatternInDir(\n SRC_ROOT,\n /\\.ts$/,\n /NEXUS_ALLOW_MOCK_ORCHESTRATION/g,\n DETERMINISM_EXCLUDES\n );\n if (mockGuardCount === 0) {\n score -= 3;\n findings.push(\n this.finding(\n 'explicitBehavior',\n 'warning',\n 'No NEXUS_ALLOW_MOCK_ORCHESTRATION guard found — mock fallback may be implicit',\n 3,\n 'Require explicit env var for mock orchestration'\n )\n );\n }\n\n // Check: magic routing patterns (delegate without explicit capability match)\n const magicRouting = countPatternInDir(\n SRC_ROOT,\n /\\.ts$/,\n /fallback.*=.*true|implicitRoute/g,\n DETERMINISM_EXCLUDES\n );\n if (magicRouting > 5) {\n const deduction = Math.min(3, Math.floor(magicRouting / 3));\n score -= deduction;\n findings.push(\n this.finding(\n 'explicitBehavior',\n 'info',\n `${String(magicRouting)} implicit fallback/routing patterns detected`,\n deduction\n )\n );\n }\n\n return { score: clamp(score, 15), findings };\n }\n\n /** Check determinism: penalize unseeded random and raw Date.now(). */\n private checkDeterminism(): FitnessCheckResult {\n const findings: FitnessFinding[] = [];\n let score = 15;\n\n score = this.checkRandomDeterminism(score, findings);\n score = this.checkTimeDeterminism(score, findings);\n\n return { score: clamp(score, 15), findings };\n }\n\n /** Sub-check for Math.random() and injectable random provider. */\n private checkRandomDeterminism(score: number, findings: FitnessFinding[]): number {\n const randomCount = countPatternInDir(\n SRC_ROOT,\n /\\.ts$/,\n /Math\\.random\\(\\)/g,\n DETERMINISM_EXCLUDES\n );\n if (randomCount > 10) {\n const deduction = Math.min(5, Math.floor(randomCount / 5));\n score -= deduction;\n findings.push(\n this.finding(\n 'determinism',\n 'warning',\n `${String(randomCount)} unseeded Math.random() calls in production code`,\n deduction,\n 'Use getRandomProvider() for injectable randomness'\n )\n );\n } else if (randomCount === 0) {\n score += 2;\n }\n\n if (existsSync(join(SRC_ROOT, 'core/random-provider.ts'))) {\n score += 1;\n }\n const randomUsage = countPatternInDir(\n SRC_ROOT,\n /\\.ts$/,\n /getRandomProvider\\(\\)/g,\n DETERMINISM_EXCLUDES\n );\n if (randomUsage > 5) {\n score += 1;\n }\n\n return score;\n }\n\n /** Sub-check for Date.now() and injectable time provider. */\n private checkTimeDeterminism(score: number, findings: FitnessFinding[]): number {\n const dateNowCount = countPatternInDir(\n SRC_ROOT,\n /\\.ts$/,\n /Date\\.now\\(\\)/g,\n DETERMINISM_EXCLUDES\n );\n if (dateNowCount > 50) {\n score -= 2;\n findings.push(\n this.finding(\n 'determinism',\n 'info',\n `${String(dateNowCount)} Date.now() calls in production code`,\n 2,\n 'Use getTimeProvider() for injectable time'\n )\n );\n }\n\n if (existsSync(join(SRC_ROOT, 'core/time-provider.ts'))) {\n score += 1;\n }\n const timeUsage = countPatternInDir(\n SRC_ROOT,\n /\\.ts$/,\n /getTimeProvider\\(\\)/g,\n DETERMINISM_EXCLUDES\n );\n if (timeUsage > 10) {\n score += 1;\n }\n\n return score;\n }\n\n /** Check observability: reward tracing, logging, and audit coverage. */\n private checkObservability(): FitnessCheckResult {\n const findings: FitnessFinding[] = [];\n let score = 15;\n\n if (existsSync(join(SRC_ROOT, 'observability/swarm-observer.ts'))) {\n score += 3;\n } else {\n score -= 3;\n findings.push(\n this.finding(\n 'observability',\n 'warning',\n 'No SwarmObserver found',\n 3,\n 'Add observability/swarm-observer.ts'\n )\n );\n }\n\n if (existsSync(join(SRC_ROOT, 'core/trace.ts'))) {\n score += 2;\n }\n\n const loggerCount = countPatternInDir(SRC_ROOT, /\\.ts$/, /createLogger\\(/g);\n if (loggerCount > 50) {\n score += 2;\n } else {\n findings.push(\n this.finding(\n 'observability',\n 'info',\n `Only ${String(loggerCount)} createLogger() calls (target: >50)`,\n 0\n )\n );\n }\n\n if (existsSync(join(SRC_ROOT, 'audit'))) {\n score += 2;\n }\n\n return { score: clamp(score, 15), findings };\n }\n\n /** Check config simplicity: penalize excessive schema sprawl. */\n private checkConfigSimplicity(): FitnessCheckResult {\n const findings: FitnessFinding[] = [];\n let score = 10;\n\n const schemaCount = countFiles(join(SRC_ROOT, 'config'), /schema.*\\.ts$/);\n if (schemaCount > 10) {\n score -= 2;\n findings.push(\n this.finding(\n 'configSimplicity',\n 'info',\n `${String(schemaCount)} config schemas (target: <=10)`,\n 2,\n 'Consolidate related schemas'\n )\n );\n } else {\n score += 1;\n }\n\n if (existsSync(join(SRC_ROOT, 'config/config-loader.ts'))) {\n score += 2;\n }\n if (existsSync(join(SRC_ROOT, 'config/config-manager.ts'))) {\n score += 1;\n }\n\n return { score: clamp(score, 10), findings };\n }\n\n /** Check layer separation: penalize cross-layer imports. */\n private checkLayerSeparation(): FitnessCheckResult {\n const findings: FitnessFinding[] = [];\n let score = 10;\n\n const adapterAgentImports = countPatternInDir(\n join(SRC_ROOT, 'adapters'),\n /\\.ts$/,\n /from ['\"]\\.\\.\\/agents\\//g\n );\n if (adapterAgentImports > 0) {\n const deduction = Math.min(5, adapterAgentImports);\n score -= deduction;\n findings.push(\n this.finding(\n 'layerSeparation',\n 'warning',\n `${String(adapterAgentImports)} adapter->agent import violations`,\n deduction,\n 'Adapters should not import from agents layer'\n )\n );\n } else {\n score += 2;\n }\n\n const coreMcpImports = countPatternInDir(\n join(SRC_ROOT, 'core'),\n /\\.ts$/,\n /from ['\"]\\.\\.\\/mcp\\//g\n );\n if (coreMcpImports > 0) {\n const deduction = Math.min(3, coreMcpImports);\n score -= deduction;\n findings.push(\n this.finding(\n 'layerSeparation',\n 'critical',\n `${String(coreMcpImports)} core->MCP import violations`,\n deduction,\n 'Core must not depend on MCP layer'\n )\n );\n } else {\n score += 1;\n }\n\n return { score: clamp(score, 10), findings };\n }\n\n /** Check operator ergonomics: reward rich CLI commands. */\n private checkOperatorErgonomics(): FitnessCheckResult {\n const findings: FitnessFinding[] = [];\n let score = 10;\n\n const commandCount = countFiles(join(SRC_ROOT, 'cli'), /\\.ts$/);\n if (commandCount >= 20) {\n score += 3;\n } else {\n findings.push(\n this.finding(\n 'operatorErgonomics',\n 'info',\n `${String(commandCount)} CLI commands (target: >=20)`,\n 0\n )\n );\n }\n\n score = this.checkCliCommands(score, findings);\n\n return { score: clamp(score, 10), findings };\n }\n\n /** Sub-check for essential CLI commands (doctor, setup, demo, config). */\n private checkCliCommands(score: number, findings: FitnessFinding[]): number {\n const commands: Array<[string, string, number]> = [\n ['cli/doctor.ts', 'Doctor command', 2],\n ['cli/setup-command.ts', 'Setup wizard', 2],\n ['cli/demo-command.ts', 'Demo command', 1],\n ['cli/config-command.ts', 'Config command', 1],\n ];\n\n for (const [path, name, bonus] of commands) {\n if (existsSync(join(SRC_ROOT, path))) {\n score += bonus;\n } else {\n findings.push(this.finding('operatorErgonomics', 'info', `Missing ${name} (${path})`, 0));\n }\n }\n\n return score;\n }\n\n /** Check governance integration: policy firewall, rate limiter, docs. */\n private checkGovernanceIntegration(): FitnessCheckResult {\n const findings: FitnessFinding[] = [];\n let score = 5;\n\n score = this.checkGovernanceDocs(score, findings);\n score = this.checkGovernanceInfra(score, findings);\n\n return { score: clamp(score, 5), findings };\n }\n\n /** Sub-check for governance documentation artifacts. */\n private checkGovernanceDocs(score: number, findings: FitnessFinding[]): number {\n if (!existsSync(join(REPO_ROOT, 'CLAUDE.md'))) {\n score -= 3;\n findings.push(\n this.finding('governanceIntegration', 'critical', 'No CLAUDE.md governance document', 3)\n );\n } else {\n score += 1;\n }\n\n if (existsSync(join(DOCS_ROOT, 'architecture/wiring-graph.json'))) {\n score += 1;\n }\n\n if (existsSync(join(DOCS_ROOT, 'adr'))) {\n score += 1;\n } else {\n findings.push(this.finding('governanceIntegration', 'info', 'No ADR directory', 0));\n }\n\n return score;\n }\n\n /** Sub-check for governance runtime infrastructure. */\n private checkGovernanceInfra(score: number, findings: FitnessFinding[]): number {\n const hasPolicyFirewall =\n countPatternInDir(join(SRC_ROOT, 'security'), /\\.ts$/, /PolicyGate|policyFirewall/g) > 0;\n if (hasPolicyFirewall) {\n score += 1;\n } else {\n findings.push(\n this.finding(\n 'governanceIntegration',\n 'warning',\n 'No policy firewall detected in security layer',\n 0\n )\n );\n }\n\n const hasRateLimiter = countPatternInDir(SRC_ROOT, /\\.ts$/, /RateLimiter|rateLimiter/g) > 0;\n if (hasRateLimiter) {\n score += 1;\n }\n\n return score;\n }\n\n /** Helper to create a FitnessFinding with defaults. */\n private finding(\n dimension: keyof FitnessDimensions,\n severity: FitnessFinding['severity'],\n description: string,\n pointsDeducted: number,\n suggestion?: string\n ): FitnessFinding {\n const base: FitnessFinding = { dimension, severity, description, pointsDeducted };\n if (suggestion !== undefined) {\n return { ...base, suggestion };\n }\n return base;\n }\n}\n\n/**\n * Create a fitness score calculator.\n */\nexport function createFitnessScoreCalculator(logger?: ILogger): FitnessScoreCalculator {\n return new FitnessScoreCalculator(logger);\n}\n\n/**\n * Quick function to get current fitness score.\n */\nexport function calculateFitnessScore(version: string): FitnessAudit {\n const calculator = createFitnessScoreCalculator();\n return calculator.audit(version);\n}\n","/**\n * improvement_review → pipeline-bus signal emitter (#3147, epic #3143 P2).\n *\n * Emits `signal.fitness_declined` onto the typed pipeline event bus when the\n * fitness audit score falls below the governance floor. Runs in the\n * `improvement_review` MCP tool (server context), so the live shadow TuneStage\n * consumes it. Lives at the MCP layer to keep `governance/fitness-score`\n * decoupled from the pipeline bus, preserving the `A = observability /\n * B = messaging` boundary adopted for #3289 (scope Option 2).\n *\n * @module mcp/tools/improvement-review-signals\n */\n\nimport { getErrorMessage, getTimeProvider } from '../../core/index.js';\nimport type { ILogger } from '../../core/index.js';\nimport type { FitnessAudit } from '../../governance/fitness-score.js';\nimport type { IEventBus } from '../../pipeline/event-types.js';\n\n/** The dimension of the finding that deducted the most points, if any. */\nfunction worstDimension(audit: FitnessAudit): string | undefined {\n let worst: { dimension: string; pointsDeducted: number } | undefined;\n for (const finding of audit.findings) {\n if (worst === undefined || finding.pointsDeducted > worst.pointsDeducted) {\n worst = { dimension: finding.dimension, pointsDeducted: finding.pointsDeducted };\n }\n }\n return worst?.dimension;\n}\n\n/**\n * Emit `signal.fitness_declined` onto `bus` when `audit.score < fitnessFloor`.\n * No-op at or above floor. Emission errors are swallowed and logged — signalling\n * must never break the improvement-review path.\n */\nexport function emitFitnessDeclinedSignal(\n audit: FitnessAudit,\n fitnessFloor: number,\n bus: IEventBus,\n logger: ILogger\n): void {\n if (audit.score >= fitnessFloor) return;\n try {\n const dimension = worstDimension(audit);\n bus.emit({\n type: 'signal.fitness_declined',\n timestamp: getTimeProvider().now(),\n score: audit.score,\n floor: fitnessFloor,\n ...(dimension !== undefined ? { dimension } : {}),\n });\n } catch (error) {\n logger.warn('Failed to emit signal.fitness_declined', { error: getErrorMessage(error) });\n }\n}\n","/**\n * Improvement-signal → remediation-task bridge (#3540, capability-loop increment 1).\n *\n * The \"safe plumbing\" half of the capability loop: turn the observability\n * signals `improvement_review` already detects (fitness decline, CLI-floor,\n * failure-category concentration, consensus rejection, self-eval findings) into\n * structured remediation {@link PipelineTask}s, surfaced for human review.\n *\n * SUGGEST-ONLY by construction: this is a pure mapping. It executes nothing,\n * files nothing, and auto-invokes no pipeline — the safety-critical auto-invoke\n * gate is a deliberately separate, later increment (still owner-gated). A\n * reviewer (or a future human-gated path) decides whether to route a task\n * through the dev-pipeline.\n *\n * @module mcp/tools/improvement-remediation\n */\n\nimport type { PipelineTask, PipelineRole } from '../../pipeline/dev-pipeline.js';\nimport type { ImprovementSignal, SignalCategory } from './improvement-review.js';\n\n/**\n * Which pipeline role should own a remediation, by signal category. The\n * dev-pipeline re-plans from research anyway, so this is the seed owner, not a\n * final assignment.\n */\nconst ROLE_BY_CATEGORY: Readonly<Record<SignalCategory, PipelineRole>> = {\n security: 'security',\n bug: 'coder',\n 'tech-debt': 'coder',\n routing: 'researcher',\n consensus: 'researcher',\n};\n\n/** Stable, dedup-friendly task id for a signal (mirrors the signalKey). */\nexport function remediationTaskId(signal: ImprovementSignal): string {\n return `improvement-${signal.signalKey}`;\n}\n\n/** Maps one improvement signal to a suggest-only remediation task. */\nexport function improvementSignalToTask(signal: ImprovementSignal): PipelineTask {\n return {\n id: remediationTaskId(signal),\n title: signal.title,\n description:\n `Auto-suggested remediation from improvement_review (#3540 — SUGGEST-ONLY, nothing executed). ` +\n `Category: ${signal.category}; severity: ${signal.severity}.\\n\\n${signal.body}\\n\\n` +\n `If accepted, route this through the dev-pipeline (research → plan → vote → implement → QA → ` +\n `security gate). Auto-invocation is a separate, owner-gated step.`,\n assignedTo: ROLE_BY_CATEGORY[signal.category],\n status: 'pending',\n };\n}\n\n/**\n * Maps detected improvement signals to remediation tasks for review. Preserves\n * input order (signals arrive severity-sorted) and dedups by task id against\n * `existingTaskIds` when provided.\n */\nexport function improvementSignalsToTasks(\n signals: readonly ImprovementSignal[],\n existingTaskIds?: ReadonlySet<string>\n): PipelineTask[] {\n const tasks: PipelineTask[] = [];\n for (const signal of signals) {\n const task = improvementSignalToTask(signal);\n if (existingTaskIds?.has(task.id) === true) continue;\n tasks.push(task);\n }\n return tasks;\n}\n","/**\n * Shadow-mode auto-remediation selector (#3540 increment 2a / #3611).\n *\n * The safe, zero-blast-radius first step of the capability loop's auto-invoke\n * gate. For each remediation task derived from improvement_review signals\n * (#3609), it records the decision the *future* gate WOULD make — \"would this\n * signal be auto-routed through the dev-pipeline?\" — WITHOUT executing anything.\n * No pipeline is invoked, no PR opened, no issue filed.\n *\n * This accumulates the selection data that #3612 (the quantified shadow→enforce\n * exit criterion) evaluates before any enforcement (#3618) is ever enabled —\n * mirroring the learned-selection shadow tier (#3551) and tune-loop (#3147).\n *\n * The one hard exclusion encoded here is security-category signals (always\n * human-gated, never auto-remediated) — the fail-closed classifier hardening is\n * #3615; the rate-limit / Rule-of-Two / runaway bounds land with their own\n * issues. This module decides nothing operational: it only observes.\n *\n * @module mcp/tools/improvement-remediation-shadow\n */\n\nimport { getTimeProvider } from '../../core/index.js';\nimport type { ImprovementSignal } from './improvement-review.js';\nimport { remediationTaskId } from './improvement-remediation.js';\n\n/** One shadow decision: would the gate auto-route this remediation? (Logged only.) */\nexport interface RemediationShadowRecord {\n readonly timestamp: string;\n /** The improvement signal's stable key. */\n readonly signalKey: string;\n /** The remediation task id (from the #3609 bridge). */\n readonly taskId: string;\n readonly category: ImprovementSignal['category'];\n readonly severity: ImprovementSignal['severity'];\n /** Whether the gate WOULD auto-route this to the dev-pipeline (shadow — not executed). */\n readonly wouldAutoRemediate: boolean;\n /** Human-readable explanation of the shadow decision. */\n readonly reason: string;\n}\n\n/**\n * The shadow decision for one signal. Security-category signals are always\n * human-gated (never auto-remediated), even in shadow — so the accumulated data\n * reflects the real gate's hard exclusion. Everything else is a shadow\n * would-remediate=true (no execution happens regardless).\n */\nexport function evaluateRemediationShadow(signal: ImprovementSignal): RemediationShadowRecord {\n const isSecurity = signal.category === 'security';\n return {\n timestamp: new Date(getTimeProvider().now()).toISOString(),\n signalKey: signal.signalKey,\n taskId: remediationTaskId(signal),\n category: signal.category,\n severity: signal.severity,\n wouldAutoRemediate: !isSecurity,\n reason: isSecurity\n ? 'security-category — always human-gated, never auto-remediated'\n : 'shadow: would route to the dev-pipeline for remediation (not executed)',\n };\n}\n\n/** Offline-evaluation summary over shadow records (consumed by #3612). */\nexport interface RemediationShadowSummary {\n readonly total: number;\n /** How many the gate would have auto-routed (non-security). */\n readonly wouldAutoRemediate: number;\n /** How many were held for a human (security-category). */\n readonly humanGated: number;\n readonly byCategory: Readonly<Record<string, number>>;\n}\n\n/** A sink that records every shadow decision. Must not throw. */\nexport interface RemediationShadowSink {\n record(record: RemediationShadowRecord): void;\n}\n\n/** A {@link RemediationShadowSink} that also exposes its buffered records. */\nexport interface IRecordingRemediationShadowSink extends RemediationShadowSink {\n getRecords(): readonly RemediationShadowRecord[];\n}\n\n/** Default cap for the in-memory recording sink (matches the other shadow sinks). */\nconst DEFAULT_MAX_RECORDS = 200;\n\n/** Creates an in-memory shadow sink with a bounded buffer (oldest evicted). */\nexport function createRemediationShadowSink(\n maxRecords = DEFAULT_MAX_RECORDS\n): IRecordingRemediationShadowSink {\n const records: RemediationShadowRecord[] = [];\n return {\n record(record: RemediationShadowRecord): void {\n records.push(record);\n if (records.length > maxRecords) {\n records.splice(0, records.length - maxRecords);\n }\n },\n getRecords(): readonly RemediationShadowRecord[] {\n return records;\n },\n };\n}\n\n/** Summarizes shadow records for offline policy evaluation (#3612). */\nexport function summarizeRemediationShadow(\n records: readonly RemediationShadowRecord[]\n): RemediationShadowSummary {\n const byCategory: Record<string, number> = {};\n let wouldAutoRemediate = 0;\n for (const r of records) {\n if (r.wouldAutoRemediate) wouldAutoRemediate++;\n byCategory[r.category] = (byCategory[r.category] ?? 0) + 1;\n }\n return {\n total: records.length,\n wouldAutoRemediate,\n humanGated: records.length - wouldAutoRemediate,\n byCategory,\n };\n}\n\nlet singletonSink: IRecordingRemediationShadowSink | undefined;\n\n/** Process-scoped shadow sink — accumulates would-remediate decisions across runs. */\nexport function getRemediationShadowSink(): IRecordingRemediationShadowSink {\n singletonSink ??= createRemediationShadowSink();\n return singletonSink;\n}\n\n/**\n * Records shadow would-remediate decisions for a set of detected signals — the\n * default-on, zero-blast-radius observability path (#3611). Returns the records\n * it logged. Executes nothing; never throws is the caller's contract (wrap in\n * try/catch at the call site so observability can't break the tool).\n *\n * `tasks` is accepted to assert the bridge produced a task per non-excluded\n * signal, keeping the shadow log aligned with what would actually be routed.\n */\nexport function recordRemediationShadow(\n signals: readonly ImprovementSignal[],\n sink: RemediationShadowSink = getRemediationShadowSink()\n): readonly RemediationShadowRecord[] {\n const records = signals.map(evaluateRemediationShadow);\n for (const record of records) sink.record(record);\n return records;\n}\n"],"mappings":"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","names":["execFile","promisify","description","execFileAsync","promisify","execFile"]}
@@ -32,7 +32,7 @@ import {
32
32
  writeJobComplete,
33
33
  writeJobFailed,
34
34
  writeJobPending
35
- } from "./chunk-NPRLDYPV.js";
35
+ } from "./chunk-5VIJQRZ4.js";
36
36
  import {
37
37
  normalizeTopicToCanonical,
38
38
  synthesizeResearch
@@ -48,7 +48,7 @@ import {
48
48
  } from "./chunk-YXRDBU3Y.js";
49
49
  import {
50
50
  withPrerequisite
51
- } from "./chunk-MCVM6UWK.js";
51
+ } from "./chunk-6MFQJEFC.js";
52
52
  import {
53
53
  NOOP_NOTIFIER,
54
54
  RateLimiter,
@@ -123,7 +123,7 @@ import {
123
123
  DEFAULT_TASK_TTL_MS,
124
124
  DEFAULT_TOOL_RATE_LIMITS,
125
125
  clampTaskTtl
126
- } from "./chunk-RBR4P7Y7.js";
126
+ } from "./chunk-U67JEWQ5.js";
127
127
  import {
128
128
  resolveInsideRoot
129
129
  } from "./chunk-NUBSJGQZ.js";
@@ -25112,31 +25112,6 @@ function recordExpertError(role, errorMessage2) {
25112
25112
  logger14.warn("Failed to record expert error", { error: getErrorMessage(error) });
25113
25113
  }
25114
25114
  }
25115
- function resolveCategory(role) {
25116
- return ROLE_TO_TASK_CATEGORY[role] ?? "code_generation";
25117
- }
25118
- function recordExpertOutcome(role, success, durationMs, error) {
25119
- try {
25120
- const store = getOutcomeStore();
25121
- const errorMsg = error ?? "expert creation failed";
25122
- store.append({
25123
- id: `expert-${String(getTimeProvider().now())}-${getRandomProvider().random().toString(36).slice(2, 8)}`,
25124
- cli: DEFAULT_CLI,
25125
- category: resolveCategory(role),
25126
- model: "expert",
25127
- success,
25128
- durationMs,
25129
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
25130
- source: "manual",
25131
- ...!success ? {
25132
- failureCategory: categorizeOutcomeErrorMessage(errorMsg),
25133
- errorMessage: errorMsg.slice(0, 500)
25134
- } : {}
25135
- });
25136
- } catch (error2) {
25137
- logger14.debug("Best-effort expert outcome recording failed", { error: getErrorMessage(error2) });
25138
- }
25139
- }
25140
25115
 
25141
25116
  // src/mcp/tools/create-expert.ts
25142
25117
  var CreateExpertInputSchema = z43.object({
@@ -25243,19 +25218,15 @@ function createCreateExpertHandler(deps) {
25243
25218
  message: `Validation error: ${formatZodError(validationResult.error)}`
25244
25219
  });
25245
25220
  }
25246
- const startMs = Date.now();
25247
25221
  const result = await handleCreateExpert(deps, validationResult.data);
25248
- const durationMs = Date.now() - startMs;
25249
25222
  if (!result.ok) {
25250
25223
  recordExpertError(validationResult.data.role, result.error);
25251
- recordExpertOutcome(validationResult.data.role, false, durationMs, result.error);
25252
25224
  return toolStructuredError({
25253
25225
  errorCategory: "internal",
25254
25226
  message: `Failed to create expert: ${result.error}`
25255
25227
  });
25256
25228
  }
25257
25229
  recordExpertCreated(result.value.role, result.value.expertId);
25258
- recordExpertOutcome(result.value.role, true, durationMs);
25259
25230
  ctx.logger.info("Expert created", {
25260
25231
  expertId: result.value.expertId,
25261
25232
  role: result.value.role,
@@ -35434,7 +35405,7 @@ function resolveExpertCategory(opts) {
35434
35405
  if (roleCategory !== void 0) return roleCategory;
35435
35406
  return detectTaskCategory(opts.task)?.category ?? "exploration";
35436
35407
  }
35437
- function recordExpertOutcome2(opts) {
35408
+ function recordExpertOutcome(opts) {
35438
35409
  try {
35439
35410
  const match = detectTaskCategory(opts.task);
35440
35411
  getOutcomeStore().append({
@@ -35508,7 +35479,7 @@ function autoCatalogScan(output2, expertId, logger56) {
35508
35479
  function handleExpertFailure(task, expert, errorMsg, durationMs) {
35509
35480
  recordExpertError2(expert.expertId, expert.role, errorMsg);
35510
35481
  const fc = categorizeOutcomeErrorMessage(errorMsg);
35511
- recordExpertOutcome2({
35482
+ recordExpertOutcome({
35512
35483
  task,
35513
35484
  role: expert.role,
35514
35485
  success: false,
@@ -35527,7 +35498,7 @@ function handleExpertFailure(task, expert, errorMsg, durationMs) {
35527
35498
  }
35528
35499
  function handleExpertSuccess(task, expert, durationMs) {
35529
35500
  recordExpertSuccess(expert.expertId, expert.role, durationMs);
35530
- recordExpertOutcome2({
35501
+ recordExpertOutcome({
35531
35502
  task,
35532
35503
  role: expert.role,
35533
35504
  success: true,
@@ -42760,7 +42731,7 @@ ${contextBlock}`;
42760
42731
  const strategy = config.votingStrategy ?? "higher_order";
42761
42732
  await postProgress(config, "Vote", `Running consensus with ${strategy} strategy...`);
42762
42733
  try {
42763
- const { executeVoting } = await import("./consensus-vote-Y6LJI44O.js");
42734
+ const { executeVoting } = await import("./consensus-vote-2SZ62Q7V.js");
42764
42735
  const votingResult = await executeVoting(
42765
42736
  {
42766
42737
  proposal: buildVoteProposal(plan, research),
@@ -49800,4 +49771,4 @@ export {
49800
49771
  shutdownFeedbackSubscriber,
49801
49772
  createEventBusBridge
49802
49773
  };
49803
- //# sourceMappingURL=chunk-LCRVK5SA.js.map
49774
+ //# sourceMappingURL=chunk-IJNTGDD6.js.map