@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.
- package/dist/autoresearch/index.js +30 -1
- package/dist/autoresearch/proposal.js +5 -0
- package/dist/autoresearch/score.js +174 -2
- package/dist/autoresearch/sol-evaluation-writer.js +135 -0
- package/dist/autoresearch/sol-evidence-loader.js +40 -3
- package/dist/autoresearch/sol-evidence-normalizer.js +337 -0
- package/dist/autoresearch/sol-recommendations.js +212 -0
- package/dist/autoresearch/sol-report-renderer.js +282 -0
- package/dist/autoresearch/sol-report.js +44 -0
- package/dist/autoresearch/sol-scorecard-calculator.js +865 -0
- package/dist/autoresearch/sol-scorer.js +45 -1
- package/dist/cli/autoresearch.js +77 -0
- package/dist/cluster-state/store.js +56 -0
- package/dist/config/defaults.js +1 -0
- package/dist/config/validator.js +157 -0
- package/dist/finalize/artifact-policy.js +2 -0
- package/dist/finalize/github.js +13 -9
- package/dist/finalize/index.js +198 -4
- package/dist/finalize/linear.js +8 -4
- package/dist/finalize/steps/08-create-pr.js +2 -2
- package/dist/finalize/steps/11-update-linear.js +2 -2
- package/dist/loop/parent.js +189 -33
- package/dist/loop/worker-packet.js +75 -1
- package/dist/qc/artifacts.js +27 -0
- package/dist/qc/fixtures/repair-packets.js +170 -0
- package/dist/qc/index.js +2 -0
- package/dist/qc/orchestration.js +5 -2
- package/dist/qc/policy.js +30 -3
- package/dist/qc/providers/coderabbit.js +152 -17
- package/dist/qc/registry.js +36 -3
- package/dist/qc/repair-loop.js +458 -0
- package/dist/qc/repair-packets.js +420 -0
- package/dist/qc/runner.js +328 -37
- package/dist/qc/schemas.js +79 -1
- package/dist/types/sol-metrics.js +108 -0
- package/dist/types/sol-scorecard.js +148 -0
- 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
|
-
|
|
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
|
// ──────────────────────────────────────────────
|