@lsctech/polaris 0.5.6 → 0.5.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/autoresearch/index.js +30 -1
  2. package/dist/autoresearch/proposal.js +5 -0
  3. package/dist/autoresearch/score.js +174 -2
  4. package/dist/autoresearch/sol-evaluation-writer.js +135 -0
  5. package/dist/autoresearch/sol-evidence-loader.js +40 -3
  6. package/dist/autoresearch/sol-evidence-normalizer.js +337 -0
  7. package/dist/autoresearch/sol-recommendations.js +212 -0
  8. package/dist/autoresearch/sol-report-renderer.js +282 -0
  9. package/dist/autoresearch/sol-report.js +44 -0
  10. package/dist/autoresearch/sol-scorecard-calculator.js +865 -0
  11. package/dist/autoresearch/sol-scorer.js +45 -1
  12. package/dist/cli/autoresearch.js +77 -0
  13. package/dist/cluster-state/store.js +56 -0
  14. package/dist/config/defaults.js +1 -0
  15. package/dist/config/validator.js +157 -0
  16. package/dist/finalize/artifact-policy.js +2 -0
  17. package/dist/finalize/github.js +13 -9
  18. package/dist/finalize/index.js +198 -4
  19. package/dist/finalize/linear.js +8 -4
  20. package/dist/finalize/steps/08-create-pr.js +2 -2
  21. package/dist/finalize/steps/11-update-linear.js +2 -2
  22. package/dist/loop/parent.js +189 -33
  23. package/dist/loop/worker-packet.js +75 -1
  24. package/dist/qc/artifacts.js +27 -0
  25. package/dist/qc/fixtures/repair-packets.js +170 -0
  26. package/dist/qc/index.js +2 -0
  27. package/dist/qc/orchestration.js +5 -2
  28. package/dist/qc/policy.js +30 -3
  29. package/dist/qc/providers/coderabbit.js +152 -17
  30. package/dist/qc/registry.js +36 -3
  31. package/dist/qc/repair-loop.js +458 -0
  32. package/dist/qc/repair-packets.js +420 -0
  33. package/dist/qc/runner.js +328 -37
  34. package/dist/qc/schemas.js +79 -1
  35. package/dist/types/sol-metrics.js +108 -0
  36. package/dist/types/sol-scorecard.js +148 -0
  37. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatRecommendationsCli = exports.recommendationToProposal = exports.recommendationsToProposals = exports.generateRecommendations = exports.formatReportCli = exports.generateReport = exports.getHistoryFilePath = exports.buildSnapshot = exports.loadSnapshots = exports.appendSnapshot = exports.computeSolScoreReport = exports.computeWorkerScore = exports.computeForemanScore = exports.aggregateSolEvidence = exports.routeProposals = exports.validateDiagnosisReport = exports.loadDiagnosisReport = exports.buildProposals = exports.FIX_ZONE_MAP = exports.ALL_GATES = exports.buildDiagnosisHints = exports.computeScore = exports.loadRunArtifacts = exports.scoreRun = exports.assertPolarisDevContext = exports.isPolarisDevContext = void 0;
3
+ exports.scorecardToRecommendationSummary = exports.formatRecommendationsCli = exports.recommendationToProposal = exports.recommendationsToProposals = exports.generateRecommendations = exports.renderSolMarkdown = exports.getSolMarkdownReportPath = exports.getScorecardPath = exports.getEvaluationRecordPath = exports.getSolReportsDir = exports.getSolScorecardsDir = exports.getSolEvaluationsDir = exports.writeSolMarkdownReport = exports.writeScorecardSet = exports.writeScorecard = exports.writeEvaluationRecord = exports.buildEvaluationRecord = exports.formatReportCli = exports.generateReport = exports.getHistoryFilePath = exports.buildSnapshot = exports.loadSnapshots = exports.appendSnapshot = exports.buildRoutingRawMetrics = exports.buildModelRawMetrics = exports.buildProviderRawMetrics = exports.buildWorkerRawMetrics = exports.buildForemanRawMetrics = exports.computeAllScorecards = exports.computeRoutingScorecard = exports.computeModelScorecard = exports.computeProviderScorecard = exports.computeWorkerScorecard = exports.computeForemanScorecard = exports.computeSolScoreReport = exports.computeWorkerScore = exports.computeForemanScore = exports.aggregateSolEvidence = exports.routeProposals = exports.validateDiagnosisReport = exports.loadDiagnosisReport = exports.buildProposals = exports.FIX_ZONE_MAP = exports.ALL_GATES = exports.buildDiagnosisHints = exports.computeScore = exports.loadRunArtifacts = exports.scoreRun = exports.assertPolarisDevContext = exports.isPolarisDevContext = void 0;
4
+ exports.scorecardsToRecommendationSummaries = void 0;
4
5
  var dev_gate_js_1 = require("./dev-gate.js");
5
6
  Object.defineProperty(exports, "isPolarisDevContext", { enumerable: true, get: function () { return dev_gate_js_1.isPolarisDevContext; } });
6
7
  Object.defineProperty(exports, "assertPolarisDevContext", { enumerable: true, get: function () { return dev_gate_js_1.assertPolarisDevContext; } });
@@ -24,6 +25,18 @@ var sol_scorer_js_1 = require("./sol-scorer.js");
24
25
  Object.defineProperty(exports, "computeForemanScore", { enumerable: true, get: function () { return sol_scorer_js_1.computeForemanScore; } });
25
26
  Object.defineProperty(exports, "computeWorkerScore", { enumerable: true, get: function () { return sol_scorer_js_1.computeWorkerScore; } });
26
27
  Object.defineProperty(exports, "computeSolScoreReport", { enumerable: true, get: function () { return sol_scorer_js_1.computeSolScoreReport; } });
28
+ var sol_scorecard_calculator_js_1 = require("./sol-scorecard-calculator.js");
29
+ Object.defineProperty(exports, "computeForemanScorecard", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.computeForemanScorecard; } });
30
+ Object.defineProperty(exports, "computeWorkerScorecard", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.computeWorkerScorecard; } });
31
+ Object.defineProperty(exports, "computeProviderScorecard", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.computeProviderScorecard; } });
32
+ Object.defineProperty(exports, "computeModelScorecard", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.computeModelScorecard; } });
33
+ Object.defineProperty(exports, "computeRoutingScorecard", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.computeRoutingScorecard; } });
34
+ Object.defineProperty(exports, "computeAllScorecards", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.computeAllScorecards; } });
35
+ Object.defineProperty(exports, "buildForemanRawMetrics", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.buildForemanRawMetrics; } });
36
+ Object.defineProperty(exports, "buildWorkerRawMetrics", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.buildWorkerRawMetrics; } });
37
+ Object.defineProperty(exports, "buildProviderRawMetrics", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.buildProviderRawMetrics; } });
38
+ Object.defineProperty(exports, "buildModelRawMetrics", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.buildModelRawMetrics; } });
39
+ Object.defineProperty(exports, "buildRoutingRawMetrics", { enumerable: true, get: function () { return sol_scorecard_calculator_js_1.buildRoutingRawMetrics; } });
27
40
  var sol_history_js_1 = require("./sol-history.js");
28
41
  Object.defineProperty(exports, "appendSnapshot", { enumerable: true, get: function () { return sol_history_js_1.appendSnapshot; } });
29
42
  Object.defineProperty(exports, "loadSnapshots", { enumerable: true, get: function () { return sol_history_js_1.loadSnapshots; } });
@@ -32,8 +45,24 @@ Object.defineProperty(exports, "getHistoryFilePath", { enumerable: true, get: fu
32
45
  var sol_report_js_1 = require("./sol-report.js");
33
46
  Object.defineProperty(exports, "generateReport", { enumerable: true, get: function () { return sol_report_js_1.generateReport; } });
34
47
  Object.defineProperty(exports, "formatReportCli", { enumerable: true, get: function () { return sol_report_js_1.formatReportCli; } });
48
+ var sol_evaluation_writer_js_1 = require("./sol-evaluation-writer.js");
49
+ Object.defineProperty(exports, "buildEvaluationRecord", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.buildEvaluationRecord; } });
50
+ Object.defineProperty(exports, "writeEvaluationRecord", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.writeEvaluationRecord; } });
51
+ Object.defineProperty(exports, "writeScorecard", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.writeScorecard; } });
52
+ Object.defineProperty(exports, "writeScorecardSet", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.writeScorecardSet; } });
53
+ Object.defineProperty(exports, "writeSolMarkdownReport", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.writeSolMarkdownReport; } });
54
+ Object.defineProperty(exports, "getSolEvaluationsDir", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.getSolEvaluationsDir; } });
55
+ Object.defineProperty(exports, "getSolScorecardsDir", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.getSolScorecardsDir; } });
56
+ Object.defineProperty(exports, "getSolReportsDir", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.getSolReportsDir; } });
57
+ Object.defineProperty(exports, "getEvaluationRecordPath", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.getEvaluationRecordPath; } });
58
+ Object.defineProperty(exports, "getScorecardPath", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.getScorecardPath; } });
59
+ Object.defineProperty(exports, "getSolMarkdownReportPath", { enumerable: true, get: function () { return sol_evaluation_writer_js_1.getSolMarkdownReportPath; } });
60
+ var sol_report_renderer_js_1 = require("./sol-report-renderer.js");
61
+ Object.defineProperty(exports, "renderSolMarkdown", { enumerable: true, get: function () { return sol_report_renderer_js_1.renderSolMarkdown; } });
35
62
  var sol_recommendations_js_1 = require("./sol-recommendations.js");
36
63
  Object.defineProperty(exports, "generateRecommendations", { enumerable: true, get: function () { return sol_recommendations_js_1.generateRecommendations; } });
37
64
  Object.defineProperty(exports, "recommendationsToProposals", { enumerable: true, get: function () { return sol_recommendations_js_1.recommendationsToProposals; } });
38
65
  Object.defineProperty(exports, "recommendationToProposal", { enumerable: true, get: function () { return sol_recommendations_js_1.recommendationToProposal; } });
39
66
  Object.defineProperty(exports, "formatRecommendationsCli", { enumerable: true, get: function () { return sol_recommendations_js_1.formatRecommendationsCli; } });
67
+ Object.defineProperty(exports, "scorecardToRecommendationSummary", { enumerable: true, get: function () { return sol_recommendations_js_1.scorecardToRecommendationSummary; } });
68
+ Object.defineProperty(exports, "scorecardsToRecommendationSummaries", { enumerable: true, get: function () { return sol_recommendations_js_1.scorecardsToRecommendationSummaries; } });
@@ -253,6 +253,11 @@ function normalizeQcScoreSummary(raw) {
253
253
  category_breakdown: q.category_breakdown ?? {},
254
254
  recurring_child_signals: Array.isArray(q.recurring_child_signals) ? q.recurring_child_signals : [],
255
255
  recurring_provider_signals: Array.isArray(q.recurring_provider_signals) ? q.recurring_provider_signals : [],
256
+ repair_loop: q.repair_loop ?? null,
257
+ noisy_providers: Array.isArray(q.noisy_providers) ? q.noisy_providers : [],
258
+ has_repair_failures: q.has_repair_failures ?? false,
259
+ unresolved_high_severity: q.unresolved_high_severity ?? 0,
260
+ max_round_exhausted: q.max_round_exhausted ?? false,
256
261
  };
257
262
  }
258
263
  /**
@@ -25,6 +25,7 @@ exports.scoreRun = scoreRun;
25
25
  const node_fs_1 = require("node:fs");
26
26
  const node_path_1 = require("node:path");
27
27
  const artifacts_js_1 = require("../qc/artifacts.js");
28
+ const store_js_1 = require("../cluster-state/store.js");
28
29
  const gates_js_1 = require("./gates.js");
29
30
  // ──────────────────────────────────────────────
30
31
  // Diagnosis hints table
@@ -196,6 +197,7 @@ function loadRunArtifacts(repoRoot, runId) {
196
197
  workerResultContracts = extractWorkerResultContracts(resultPackets);
197
198
  }
198
199
  const qcResults = loadQcArtifacts(clusterId, repoRoot);
200
+ const clusterState = clusterId ? (0, store_js_1.readClusterStateSync)(clusterId, repoRoot) : null;
199
201
  return {
200
202
  runId,
201
203
  runDir,
@@ -206,6 +208,7 @@ function loadRunArtifacts(repoRoot, runId) {
206
208
  workerResultContracts,
207
209
  telemetryEvents,
208
210
  qcResults,
211
+ clusterState,
209
212
  };
210
213
  }
211
214
  // ──────────────────────────────────────────────
@@ -247,6 +250,159 @@ function computeQcPenalty(weightedOpenScore) {
247
250
  // Soft penalty that grows with weighted severity but does not dominate the score.
248
251
  return weightedOpenScore === 0 ? 0 : weightedOpenScore / (weightedOpenScore + 20);
249
252
  }
253
+ function zeroProviderAttemptSummary() {
254
+ return { total: 0, success: 0, failure: 0, fallback: 0, skipped: 0, all_providers_failed: false };
255
+ }
256
+ function zeroRepairLoopSummary() {
257
+ return {
258
+ status: "not-configured",
259
+ rounds_completed: 0,
260
+ max_rounds: 0,
261
+ packets_compiled: 0,
262
+ packets_completed: 0,
263
+ packets_failed: 0,
264
+ rerun_outcome: null,
265
+ provider_attempts: zeroProviderAttemptSummary(),
266
+ };
267
+ }
268
+ function mapTerminalOutcome(outcome) {
269
+ switch (outcome) {
270
+ case "pass":
271
+ return "passed";
272
+ case "no-repairable":
273
+ return "no-repairable";
274
+ case "max-rounds":
275
+ return "max-rounds";
276
+ case "all-providers-failed":
277
+ return "all-providers-failed";
278
+ case "operator-review":
279
+ return "operator-review";
280
+ case "medic-referral":
281
+ return "medic-referral";
282
+ case "qc-disabled":
283
+ return "not-configured";
284
+ default:
285
+ return "unknown";
286
+ }
287
+ }
288
+ function providerAttemptStatusFromResult(result) {
289
+ if (result.providerAttempt)
290
+ return result.providerAttempt.status;
291
+ if (result.allProvidersFailed || result.status === "failed")
292
+ return "failure";
293
+ if (result.status === "skipped")
294
+ return "skipped";
295
+ if (result.findings.length > 0 || result.status === "findings" || result.status === "blocked")
296
+ return "success";
297
+ if (result.status === "passed")
298
+ return "success";
299
+ return undefined;
300
+ }
301
+ function computeProviderAttemptSummary(qcResults, telemetryEvents) {
302
+ const summary = zeroProviderAttemptSummary();
303
+ for (const result of qcResults) {
304
+ const status = providerAttemptStatusFromResult(result);
305
+ if (status) {
306
+ summary.total += 1;
307
+ if (status === "success")
308
+ summary.success += 1;
309
+ else if (status === "failure")
310
+ summary.failure += 1;
311
+ else if (status === "fallback")
312
+ summary.fallback += 1;
313
+ else if (status === "skipped")
314
+ summary.skipped += 1;
315
+ }
316
+ if (result.allProvidersFailed)
317
+ summary.all_providers_failed = true;
318
+ }
319
+ // Telemetry fallback events augment the fallback count when providerAttempt was not written.
320
+ for (const ev of telemetryEvents) {
321
+ const rec = asRecord(ev);
322
+ if (rec?.["event"] === "provider-fallback-attempted")
323
+ summary.fallback += 1;
324
+ }
325
+ return summary;
326
+ }
327
+ function computeQcRepairLoopSummary(qcResults, clusterState, telemetryEvents, currentState) {
328
+ if (qcResults.length === 0)
329
+ return zeroRepairLoopSummary();
330
+ const currentStateRec = asRecord(currentState);
331
+ const loopState = currentStateRec?.["qc_repair_loop"];
332
+ const terminalOutcome = clusterState?.qc_repair_outcome ??
333
+ (loopState?.terminal_outcome ? String(loopState.terminal_outcome) : null);
334
+ const terminalEvents = telemetryEvents
335
+ .map(asRecord)
336
+ .filter((e) => e !== undefined && e["event"] === "qc-repair-loop-terminal");
337
+ const lastTerminal = terminalEvents[terminalEvents.length - 1];
338
+ const manifestEvents = telemetryEvents
339
+ .map(asRecord)
340
+ .filter((e) => e !== undefined && e["event"] === "qc-repair-manifest-compiled");
341
+ const failureEvents = telemetryEvents
342
+ .map(asRecord)
343
+ .filter((e) => e !== undefined && e["event"] === "qc-repair-worker-failures");
344
+ const failedPacketIds = new Set();
345
+ for (const ev of failureEvents) {
346
+ const ids = ev["failed_packet_ids"];
347
+ if (Array.isArray(ids)) {
348
+ for (const id of ids) {
349
+ if (typeof id === "string")
350
+ failedPacketIds.add(id);
351
+ }
352
+ }
353
+ }
354
+ const rerunEvents = telemetryEvents
355
+ .map(asRecord)
356
+ .filter((e) => e !== undefined && e["event"] === "qc-repair-rerun-complete");
357
+ const lastRerun = rerunEvents[rerunEvents.length - 1];
358
+ let status;
359
+ if (terminalOutcome) {
360
+ status = mapTerminalOutcome(terminalOutcome);
361
+ }
362
+ else if (lastTerminal) {
363
+ status = mapTerminalOutcome(asString(lastTerminal["outcome"]));
364
+ }
365
+ else if (loopState) {
366
+ status = "in-progress";
367
+ }
368
+ else {
369
+ status = "not-run";
370
+ }
371
+ const roundsCompleted = asNumber(lastTerminal?.["rounds_completed"]) ??
372
+ (loopState?.current_round ?? 0);
373
+ const maxRounds = asNumber(lastTerminal?.["max_rounds"]) ??
374
+ (loopState?.max_rounds ?? 0);
375
+ const packetsCompiled = manifestEvents.reduce((sum, e) => {
376
+ const n = asNumber(e["packet_count"]);
377
+ return sum + (n ?? 0);
378
+ }, 0);
379
+ const packetsFailed = failedPacketIds.size;
380
+ const packetsCompleted = status === "not-run" || status === "not-configured"
381
+ ? 0
382
+ : Math.max(0, packetsCompiled - packetsFailed);
383
+ let rerunOutcome = null;
384
+ if (lastRerun) {
385
+ const action = asString(lastRerun["action"]);
386
+ if (action === "pass")
387
+ rerunOutcome = "pass";
388
+ else if (action === "findings")
389
+ rerunOutcome = "findings";
390
+ else if (action === "failure" || action === "error")
391
+ rerunOutcome = "failed";
392
+ else if (action === "skipped")
393
+ rerunOutcome = "skipped";
394
+ }
395
+ return {
396
+ status,
397
+ rounds_completed: roundsCompleted,
398
+ max_rounds: maxRounds,
399
+ packets_compiled: packetsCompiled,
400
+ packets_completed: packetsCompleted,
401
+ packets_failed: packetsFailed,
402
+ rerun_outcome: rerunOutcome,
403
+ provider_attempts: computeProviderAttemptSummary(qcResults, telemetryEvents),
404
+ };
405
+ }
250
406
  /**
251
407
  * Computes a QcScoreSummary from the QC results loaded for this run.
252
408
  * Returns null when no QC results are present.
@@ -263,7 +419,7 @@ function computeQcPenalty(weightedOpenScore) {
263
419
  * a critical/high-confidence finding hurts SOL scoring more than a low-severity
264
420
  * or uncertain finding.
265
421
  */
266
- function computeQcSummary(qcResults) {
422
+ function computeQcSummary(qcResults, clusterState = null, telemetryEvents = [], currentState = null) {
267
423
  if (qcResults.length === 0)
268
424
  return null;
269
425
  let total = 0;
@@ -362,6 +518,11 @@ function computeQcSummary(qcResults) {
362
518
  .map(([provider, value]) => ({ provider, ...value }))
363
519
  .filter((s) => s.weighted_score > 0)
364
520
  .sort((a, b) => b.weighted_score - a.weighted_score || a.provider.localeCompare(b.provider));
521
+ const repairLoop = computeQcRepairLoopSummary(qcResults, clusterState, telemetryEvents, currentState);
522
+ const noisyProviders = Array.from(providerBreakdown.entries())
523
+ .filter(([, counts]) => counts.total >= 2 && counts.unvalidated / counts.total >= 0.5)
524
+ .map(([provider]) => provider)
525
+ .sort();
365
526
  return {
366
527
  total_findings: total,
367
528
  blocking_findings: blocking,
@@ -379,6 +540,11 @@ function computeQcSummary(qcResults) {
379
540
  category_breakdown: Object.fromEntries(categoryBreakdown),
380
541
  recurring_child_signals: recurringChildSignals,
381
542
  recurring_provider_signals: recurringProviderSignals,
543
+ repair_loop: repairLoop,
544
+ noisy_providers: noisyProviders,
545
+ has_repair_failures: repairLoop.packets_failed > 0 || repairLoop.status === "medic-referral",
546
+ unresolved_high_severity: openBySeverity.critical + openBySeverity.high,
547
+ max_round_exhausted: repairLoop.status === "max-rounds",
382
548
  };
383
549
  }
384
550
  function asRecord(value) {
@@ -386,6 +552,12 @@ function asRecord(value) {
386
552
  ? value
387
553
  : undefined;
388
554
  }
555
+ function asString(value) {
556
+ return typeof value === "string" ? value : null;
557
+ }
558
+ function asNumber(value) {
559
+ return typeof value === "number" ? value : null;
560
+ }
389
561
  function asStringArray(value) {
390
562
  if (!Array.isArray(value))
391
563
  return [];
@@ -485,7 +657,7 @@ function scoreRun(repoRoot, runId) {
485
657
  const score = computeScore(gateResults);
486
658
  const diagnosisHints = buildDiagnosisHints(failedGates);
487
659
  const routerOutcomes = summarizeRouterOutcomes(artifacts);
488
- const qcSummary = computeQcSummary(artifacts.qcResults);
660
+ const qcSummary = computeQcSummary(artifacts.qcResults, artifacts.clusterState, artifacts.telemetryEvents, artifacts.currentState);
489
661
  const clusterId = artifacts.currentState &&
490
662
  typeof artifacts.currentState === "object" &&
491
663
  !Array.isArray(artifacts.currentState)
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * SOL evaluation artifact writer.
4
+ *
5
+ * Persists machine-readable SOL evaluation records and scorecard snapshots
6
+ * to deterministic local paths. All writes are append-only / generation-safe:
7
+ * existing source run evidence is never read or rewritten.
8
+ *
9
+ * Output locations:
10
+ * - Evaluations: .polaris/sol/evaluations/<run-id>.json
11
+ * - Scorecards: .polaris/sol/scorecards/<subject>/<scorecard-id>.json
12
+ * - Reports: smartdocs/reports/sol/<run-id>-evaluation-report.md
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.getSolEvaluationsDir = getSolEvaluationsDir;
16
+ exports.getSolScorecardsDir = getSolScorecardsDir;
17
+ exports.getSolReportsDir = getSolReportsDir;
18
+ exports.buildEvaluationRecord = buildEvaluationRecord;
19
+ exports.getEvaluationRecordPath = getEvaluationRecordPath;
20
+ exports.writeEvaluationRecord = writeEvaluationRecord;
21
+ exports.getScorecardPath = getScorecardPath;
22
+ exports.writeScorecard = writeScorecard;
23
+ exports.writeScorecardSet = writeScorecardSet;
24
+ exports.getSolMarkdownReportPath = getSolMarkdownReportPath;
25
+ exports.writeSolMarkdownReport = writeSolMarkdownReport;
26
+ const node_fs_1 = require("node:fs");
27
+ const node_path_1 = require("node:path");
28
+ // ──────────────────────────────────────────────
29
+ // Paths
30
+ // ──────────────────────────────────────────────
31
+ const EVALUATIONS_DIR = ".polaris/sol/evaluations";
32
+ const SCORECARDS_DIR = ".polaris/sol/scorecards";
33
+ const REPORTS_DIR = "smartdocs/reports/sol";
34
+ function getSolEvaluationsDir(repoRoot) {
35
+ return (0, node_path_1.join)(repoRoot, EVALUATIONS_DIR);
36
+ }
37
+ function getSolScorecardsDir(repoRoot) {
38
+ return (0, node_path_1.join)(repoRoot, SCORECARDS_DIR);
39
+ }
40
+ function getSolReportsDir(repoRoot) {
41
+ return (0, node_path_1.join)(repoRoot, REPORTS_DIR);
42
+ }
43
+ // ──────────────────────────────────────────────
44
+ // Filename safety
45
+ // ──────────────────────────────────────────────
46
+ function safeFilename(value) {
47
+ // Prevent path traversal and reserved characters while keeping readability.
48
+ const sanitized = value
49
+ .replace(/[\\/]+/g, "-")
50
+ .replace(/[:\?*"<>|]+/g, "-")
51
+ .replace(/\.{2,}/g, "-")
52
+ .replace(/^[.]+/, "")
53
+ .trim();
54
+ return sanitized.length > 0 ? sanitized : "unnamed";
55
+ }
56
+ function buildEvaluationRecord(report) {
57
+ return {
58
+ schema_version: "1.0",
59
+ record_type: "sol-evaluation",
60
+ run_id: report.run_id,
61
+ cluster_id: report.cluster_id,
62
+ scored_at: report.scored_at,
63
+ report,
64
+ generated_at: new Date().toISOString(),
65
+ };
66
+ }
67
+ function getEvaluationRecordPath(repoRoot, runId) {
68
+ return (0, node_path_1.join)(getSolEvaluationsDir(repoRoot), `${safeFilename(runId)}.json`);
69
+ }
70
+ /**
71
+ * Write a SOL evaluation record under .polaris/sol/evaluations/.
72
+ * Creates directories as needed. Overwrites only the derived record file.
73
+ */
74
+ function writeEvaluationRecord(repoRoot, report) {
75
+ const record = buildEvaluationRecord(report);
76
+ const dir = getSolEvaluationsDir(repoRoot);
77
+ if (!(0, node_fs_1.existsSync)(dir))
78
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
79
+ const filePath = getEvaluationRecordPath(repoRoot, report.run_id);
80
+ (0, node_fs_1.writeFileSync)(filePath, JSON.stringify(record, null, 2) + "\n", "utf-8");
81
+ return { record, path: filePath };
82
+ }
83
+ // ──────────────────────────────────────────────
84
+ // Scorecard snapshots
85
+ // ──────────────────────────────────────────────
86
+ function scorecardSubjectDir(subject) {
87
+ return subject;
88
+ }
89
+ function getScorecardPath(repoRoot, scorecard) {
90
+ return (0, node_path_1.join)(getSolScorecardsDir(repoRoot), scorecardSubjectDir(scorecard.subject), `${safeFilename(scorecard.scorecard_id)}.json`);
91
+ }
92
+ /**
93
+ * Write a single scorecard snapshot under .polaris/sol/scorecards/<subject>/.
94
+ */
95
+ function writeScorecard(repoRoot, scorecard) {
96
+ const dir = (0, node_path_1.join)(getSolScorecardsDir(repoRoot), scorecardSubjectDir(scorecard.subject));
97
+ if (!(0, node_fs_1.existsSync)(dir))
98
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
99
+ const filePath = getScorecardPath(repoRoot, scorecard);
100
+ (0, node_fs_1.writeFileSync)(filePath, JSON.stringify(scorecard, null, 2) + "\n", "utf-8");
101
+ return filePath;
102
+ }
103
+ /**
104
+ * Write all scorecards in a SolScorecardSet. Returns the paths written.
105
+ */
106
+ function writeScorecardSet(repoRoot, scorecardSet) {
107
+ const paths = [];
108
+ paths.push(writeScorecard(repoRoot, scorecardSet.foreman));
109
+ for (const s of scorecardSet.workers)
110
+ paths.push(writeScorecard(repoRoot, s));
111
+ for (const s of scorecardSet.providers)
112
+ paths.push(writeScorecard(repoRoot, s));
113
+ for (const s of scorecardSet.models)
114
+ paths.push(writeScorecard(repoRoot, s));
115
+ for (const s of scorecardSet.routing)
116
+ paths.push(writeScorecard(repoRoot, s));
117
+ return paths;
118
+ }
119
+ // ──────────────────────────────────────────────
120
+ // Human-readable SmartDocs report
121
+ // ──────────────────────────────────────────────
122
+ function getSolMarkdownReportPath(repoRoot, runId) {
123
+ return (0, node_path_1.join)(getSolReportsDir(repoRoot), `${safeFilename(runId)}-evaluation-report.md`);
124
+ }
125
+ /**
126
+ * Write a rendered Markdown SOL report to smartdocs/reports/sol/.
127
+ */
128
+ function writeSolMarkdownReport(repoRoot, runId, markdown) {
129
+ const dir = getSolReportsDir(repoRoot);
130
+ if (!(0, node_fs_1.existsSync)(dir))
131
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
132
+ const filePath = getSolMarkdownReportPath(repoRoot, runId);
133
+ (0, node_fs_1.writeFileSync)(filePath, markdown, "utf-8");
134
+ return filePath;
135
+ }
@@ -293,9 +293,11 @@ function buildRouterEvidence(artifacts) {
293
293
  // QC evidence
294
294
  // ──────────────────────────────────────────────
295
295
  function buildQcEvidence(artifacts) {
296
+ const disabledOutcome = artifacts.clusterState?.qc_repair_outcome;
297
+ const isDisabled = disabledOutcome === "qc-disabled";
296
298
  if (artifacts.qcResults.length === 0) {
297
- return {
298
- availability: "future",
299
+ const base = {
300
+ availability: isDisabled ? "unavailable" : "future",
299
301
  qc_run_count: 0,
300
302
  total_findings: 0,
301
303
  blocking_findings: 0,
@@ -307,9 +309,32 @@ function buildQcEvidence(artifacts) {
307
309
  qc_penalty: 0,
308
310
  blocks_delivery: false,
309
311
  open_by_severity: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
312
+ provider_breakdown: {},
313
+ repair_loop: {
314
+ status: isDisabled ? "not-configured" : "not-run",
315
+ rounds_completed: 0,
316
+ max_rounds: 0,
317
+ packets_compiled: 0,
318
+ packets_completed: 0,
319
+ packets_failed: 0,
320
+ rerun_outcome: null,
321
+ provider_attempts: {
322
+ total: 0,
323
+ success: 0,
324
+ failure: 0,
325
+ fallback: 0,
326
+ skipped: 0,
327
+ all_providers_failed: false,
328
+ },
329
+ },
330
+ noisy_providers: [],
331
+ has_repair_failures: false,
332
+ unresolved_high_severity: 0,
333
+ max_round_exhausted: false,
310
334
  };
335
+ return base;
311
336
  }
312
- const summary = (0, score_js_1.computeQcSummary)(artifacts.qcResults);
337
+ const summary = (0, score_js_1.computeQcSummary)(artifacts.qcResults, artifacts.clusterState, artifacts.telemetryEvents, artifacts.currentState);
313
338
  if (!summary) {
314
339
  // computeQcSummary only returns null when qcResults is empty; handled above.
315
340
  // ponytail: defensive branch to satisfy type narrowing
@@ -326,6 +351,12 @@ function buildQcEvidence(artifacts) {
326
351
  qc_penalty: 0,
327
352
  blocks_delivery: false,
328
353
  open_by_severity: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
354
+ provider_breakdown: {},
355
+ repair_loop: null,
356
+ noisy_providers: [],
357
+ has_repair_failures: false,
358
+ unresolved_high_severity: 0,
359
+ max_round_exhausted: false,
329
360
  };
330
361
  }
331
362
  return {
@@ -341,6 +372,12 @@ function buildQcEvidence(artifacts) {
341
372
  qc_penalty: summary.qc_penalty,
342
373
  blocks_delivery: summary.blocks_delivery,
343
374
  open_by_severity: { ...summary.open_by_severity },
375
+ provider_breakdown: { ...summary.provider_breakdown },
376
+ repair_loop: summary.repair_loop,
377
+ noisy_providers: [...summary.noisy_providers],
378
+ has_repair_failures: summary.has_repair_failures,
379
+ unresolved_high_severity: summary.unresolved_high_severity,
380
+ max_round_exhausted: summary.max_round_exhausted,
344
381
  };
345
382
  }
346
383
  // ──────────────────────────────────────────────