@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
|
@@ -207,6 +207,48 @@ function scoreForemanCompletion(ev) {
|
|
|
207
207
|
detail: `succeeded=${succeeded}/${total}`,
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* qc_repair_loop: Observe QC repair loop outcomes.
|
|
212
|
+
*
|
|
213
|
+
* Scores repair loop terminal states without treating provider findings as
|
|
214
|
+
* ground truth. Best outcome = loop passed or no repairable findings.
|
|
215
|
+
* Worst = all providers failed, operator review required, or Medic referral.
|
|
216
|
+
* Skipped when QC is not configured or no repair loop ran.
|
|
217
|
+
*/
|
|
218
|
+
function scoreForemanQcRepairLoop(ev) {
|
|
219
|
+
if (ev.qc.availability === "future" || ev.qc.availability === "unavailable") {
|
|
220
|
+
return skipped("qc_repair_loop", `QC evidence availability=${ev.qc.availability}`);
|
|
221
|
+
}
|
|
222
|
+
const loop = ev.qc.repair_loop;
|
|
223
|
+
if (!loop) {
|
|
224
|
+
return skipped("qc_repair_loop", "no QC repair loop data available");
|
|
225
|
+
}
|
|
226
|
+
const { status, rounds_completed, max_rounds, packets_compiled, packets_failed, rerun_outcome } = loop;
|
|
227
|
+
switch (status) {
|
|
228
|
+
case "passed":
|
|
229
|
+
case "repaired":
|
|
230
|
+
case "no-repairable":
|
|
231
|
+
return dim("qc_repair_loop", 1.0, "high", {
|
|
232
|
+
detail: `status=${status}, rounds=${rounds_completed}/${max_rounds}, packets=${packets_compiled}, rerun=${rerun_outcome ?? "n/a"}`,
|
|
233
|
+
});
|
|
234
|
+
case "max-rounds":
|
|
235
|
+
return dim("qc_repair_loop", 0.5, "high", {
|
|
236
|
+
detail: `status=${status}, rounds=${rounds_completed}/${max_rounds}, packets=${packets_compiled}`,
|
|
237
|
+
});
|
|
238
|
+
case "all-providers-failed":
|
|
239
|
+
case "operator-review":
|
|
240
|
+
case "medic-referral":
|
|
241
|
+
return dim("qc_repair_loop", 0.0, "high", {
|
|
242
|
+
detail: `status=${status}, failed_packets=${packets_failed}, rerun=${rerun_outcome ?? "n/a"}`,
|
|
243
|
+
});
|
|
244
|
+
case "not-run":
|
|
245
|
+
case "not-configured":
|
|
246
|
+
case "in-progress":
|
|
247
|
+
case "unknown":
|
|
248
|
+
default:
|
|
249
|
+
return skipped("qc_repair_loop", `QC repair loop status=${status}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
210
252
|
/**
|
|
211
253
|
* recovery: State repair detection.
|
|
212
254
|
* Score = 1.0 when no medic/repair artifacts detected; 0.0 when repair required.
|
|
@@ -388,10 +430,11 @@ function computeForemanScore(ev) {
|
|
|
388
430
|
const scopeDim = scoreForemanScope(ev);
|
|
389
431
|
const completionDim = scoreForemanCompletion(ev);
|
|
390
432
|
const recoveryDim = scoreForemanRecovery(ev);
|
|
433
|
+
const qcRepairLoopDim = scoreForemanQcRepairLoop(ev);
|
|
391
434
|
const dims = [
|
|
392
435
|
tokenDim, durationDim, interventionDim, preAnalysisDim,
|
|
393
436
|
dependencyDim, dispatchDim, evidenceValidationDim, scopeDim,
|
|
394
|
-
completionDim, recoveryDim,
|
|
437
|
+
completionDim, recoveryDim, qcRepairLoopDim,
|
|
395
438
|
];
|
|
396
439
|
const { score: composite_score, confidence: composite_confidence } = composite(dims);
|
|
397
440
|
return {
|
|
@@ -407,6 +450,7 @@ function computeForemanScore(ev) {
|
|
|
407
450
|
scope: scopeDim,
|
|
408
451
|
completion: completionDim,
|
|
409
452
|
recovery: recoveryDim,
|
|
453
|
+
qc_repair_loop: qcRepairLoopDim,
|
|
410
454
|
};
|
|
411
455
|
}
|
|
412
456
|
/**
|
package/dist/cli/autoresearch.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createAutoresearchCommand = void 0;
|
|
4
4
|
exports.createSolCommand = createSolCommand;
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
|
+
const node_fs_1 = require("node:fs");
|
|
6
7
|
const commander_1 = require("commander");
|
|
7
8
|
const dev_gate_js_1 = require("../autoresearch/dev-gate.js");
|
|
8
9
|
const score_js_1 = require("../autoresearch/score.js");
|
|
@@ -12,6 +13,9 @@ const sol_evidence_loader_js_1 = require("../autoresearch/sol-evidence-loader.js
|
|
|
12
13
|
const sol_scorer_js_1 = require("../autoresearch/sol-scorer.js");
|
|
13
14
|
const sol_history_js_1 = require("../autoresearch/sol-history.js");
|
|
14
15
|
const sol_report_js_1 = require("../autoresearch/sol-report.js");
|
|
16
|
+
const sol_scorecard_calculator_js_1 = require("../autoresearch/sol-scorecard-calculator.js");
|
|
17
|
+
const sol_evaluation_writer_js_1 = require("../autoresearch/sol-evaluation-writer.js");
|
|
18
|
+
const sol_report_renderer_js_1 = require("../autoresearch/sol-report-renderer.js");
|
|
15
19
|
const sol_recommendations_js_1 = require("../autoresearch/sol-recommendations.js");
|
|
16
20
|
function createSolCommand(options) {
|
|
17
21
|
const repoRoot = options.repoRoot;
|
|
@@ -74,6 +78,79 @@ function createSolCommand(options) {
|
|
|
74
78
|
process.exit(1);
|
|
75
79
|
}
|
|
76
80
|
});
|
|
81
|
+
sol
|
|
82
|
+
.command("report <run-id>")
|
|
83
|
+
.description("Generate SOL evaluation artifacts and a human-readable SmartDocs report for a run")
|
|
84
|
+
.option("-r, --repo-root <path>", "Repository root", repoRoot)
|
|
85
|
+
.option("--format <mode>", "Output format: markdown or json", "markdown")
|
|
86
|
+
.option("--json", "Output raw JSON (same as --format json)")
|
|
87
|
+
.option("--no-write", "Skip writing artifact files")
|
|
88
|
+
.action((runId, cmdOptions) => {
|
|
89
|
+
const root = (0, node_path_1.resolve)(cmdOptions.repoRoot ?? repoRoot);
|
|
90
|
+
if (cmdOptions.format !== "markdown" && cmdOptions.format !== "json") {
|
|
91
|
+
process.stderr.write(`sol report error: invalid --format "${cmdOptions.format}" (expected "markdown" or "json")\n`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
(0, dev_gate_js_1.assertPolarisDevContext)(root);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
let artifacts;
|
|
102
|
+
let evidence;
|
|
103
|
+
try {
|
|
104
|
+
artifacts = (0, score_js_1.loadRunArtifacts)(root, runId);
|
|
105
|
+
evidence = (0, sol_evidence_loader_js_1.aggregateSolEvidence)(artifacts);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
process.stderr.write(`sol report error: cannot load run artifacts: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const report = (0, sol_scorer_js_1.computeSolScoreReport)(evidence);
|
|
113
|
+
const scorecards = (0, sol_scorecard_calculator_js_1.computeAllScorecards)(evidence);
|
|
114
|
+
const qcRecommendations = (0, sol_recommendations_js_1.generateQcRecommendations)(evidence);
|
|
115
|
+
let evaluationPath;
|
|
116
|
+
let scorecardPaths;
|
|
117
|
+
let markdownPath;
|
|
118
|
+
const evaluationRecord = (0, sol_evaluation_writer_js_1.buildEvaluationRecord)(report);
|
|
119
|
+
const rendered = (0, sol_report_renderer_js_1.renderSolMarkdown)(evaluationRecord, scorecards, qcRecommendations);
|
|
120
|
+
if (cmdOptions.write) {
|
|
121
|
+
// Persist the same evaluationRecord object that was rendered, rather than rebuilding internally
|
|
122
|
+
const evaluationDir = (0, sol_evaluation_writer_js_1.getSolEvaluationsDir)(root);
|
|
123
|
+
if (!(0, node_fs_1.existsSync)(evaluationDir))
|
|
124
|
+
(0, node_fs_1.mkdirSync)(evaluationDir, { recursive: true });
|
|
125
|
+
evaluationPath = (0, sol_evaluation_writer_js_1.getEvaluationRecordPath)(root, report.run_id);
|
|
126
|
+
(0, node_fs_1.writeFileSync)(evaluationPath, JSON.stringify(evaluationRecord, null, 2) + "\n", "utf-8");
|
|
127
|
+
scorecardPaths = (0, sol_evaluation_writer_js_1.writeScorecardSet)(root, scorecards);
|
|
128
|
+
markdownPath = (0, sol_evaluation_writer_js_1.writeSolMarkdownReport)(root, runId, rendered.markdown);
|
|
129
|
+
}
|
|
130
|
+
const outputFormat = cmdOptions.json || cmdOptions.format === "json" ? "json" : "markdown";
|
|
131
|
+
if (outputFormat === "json") {
|
|
132
|
+
const output = JSON.stringify({
|
|
133
|
+
run_id: runId,
|
|
134
|
+
evaluation: evaluationRecord,
|
|
135
|
+
scorecards,
|
|
136
|
+
qc_recommendations: qcRecommendations,
|
|
137
|
+
artifacts: {
|
|
138
|
+
evaluation: evaluationPath,
|
|
139
|
+
scorecards: scorecardPaths,
|
|
140
|
+
markdown: markdownPath,
|
|
141
|
+
},
|
|
142
|
+
}, null, 2);
|
|
143
|
+
process.stdout.write(`${output}\n`);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
process.stdout.write(rendered.markdown);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
process.stderr.write(`sol report error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
77
154
|
sol
|
|
78
155
|
.command("propose <diagnosis-file>")
|
|
79
156
|
.description("File Linear improvement proposals from a diagnosis report (dev-gated — never auto-applied)")
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.recordQcRun = exports.initializeClusterState = exports.writeClusterStateSync = exports.writeClusterState = exports.readClusterStateSync = exports.readClusterState = void 0;
|
|
37
37
|
exports.pruneExpiredClaims = pruneExpiredClaims;
|
|
38
|
+
exports.pruneMissingQcRunPointers = pruneMissingQcRunPointers;
|
|
38
39
|
const fs_1 = require("fs");
|
|
39
40
|
const path = __importStar(require("path"));
|
|
40
41
|
const local_graph_1 = require("../tracker/local-graph");
|
|
@@ -350,12 +351,25 @@ const recordQcRun = async (clusterId, result, repoRoot) => {
|
|
|
350
351
|
if (!currentState) {
|
|
351
352
|
throw new Error(`Cluster ${clusterId} state not found; cannot record QC run ${result.qcRunId}.`);
|
|
352
353
|
}
|
|
354
|
+
const rawArtifactPaths = result.rawArtifactPaths ?? [];
|
|
355
|
+
const providerAttemptPath = result.providerAttempt?.rawOutputArtifactPath;
|
|
356
|
+
const auditArtifactPaths = providerAttemptPath
|
|
357
|
+
? [...rawArtifactPaths, providerAttemptPath]
|
|
358
|
+
: rawArtifactPaths;
|
|
359
|
+
const failedRun = result.status === "failed" || result.status === "blocked" || result.allProvidersFailed === true;
|
|
360
|
+
let availability = "available";
|
|
361
|
+
if (failedRun && auditArtifactPaths.some((p) => !(0, fs_1.existsSync)(p))) {
|
|
362
|
+
availability = "unavailable";
|
|
363
|
+
}
|
|
353
364
|
const pointer = {
|
|
354
365
|
artifact_path: artifactPath,
|
|
355
366
|
status: result.status,
|
|
356
367
|
provider: result.provider,
|
|
357
368
|
started_at: result.startedAt,
|
|
358
369
|
completed_at: result.completedAt,
|
|
370
|
+
availability,
|
|
371
|
+
raw_artifact_paths: rawArtifactPaths.length > 0 ? rawArtifactPaths : undefined,
|
|
372
|
+
provider_attempt_artifact_path: providerAttemptPath,
|
|
359
373
|
};
|
|
360
374
|
const nextState = {
|
|
361
375
|
...currentState,
|
|
@@ -369,3 +383,45 @@ const recordQcRun = async (clusterId, result, repoRoot) => {
|
|
|
369
383
|
return { artifactPath, state: nextState };
|
|
370
384
|
};
|
|
371
385
|
exports.recordQcRun = recordQcRun;
|
|
386
|
+
/**
|
|
387
|
+
* Remove QC run pointers whose primary artifacts are missing and mark
|
|
388
|
+
* pointers with missing raw audit artifacts as unavailable. Returns a new
|
|
389
|
+
* state object; callers must persist the result if they want the cleanup to
|
|
390
|
+
* be durable.
|
|
391
|
+
*/
|
|
392
|
+
function pruneMissingQcRunPointers(state) {
|
|
393
|
+
const warnings = [];
|
|
394
|
+
const pruned = [];
|
|
395
|
+
if (!state.qc_runs) {
|
|
396
|
+
return { state, pruned, warnings };
|
|
397
|
+
}
|
|
398
|
+
const validation = (0, artifacts_js_1.validateQcArtifactPointers)(state.qc_runs);
|
|
399
|
+
if (validation.ok && validation.unavailable.length === 0) {
|
|
400
|
+
return { state, pruned, warnings };
|
|
401
|
+
}
|
|
402
|
+
const nextQcRuns = {};
|
|
403
|
+
for (const [runId, pointer] of Object.entries(state.qc_runs)) {
|
|
404
|
+
if (validation.missing.includes(pointer.artifact_path)) {
|
|
405
|
+
pruned.push(runId);
|
|
406
|
+
warnings.push(`QC run ${runId} pointer pruned: artifact missing ${pointer.artifact_path}`);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const updated = { ...pointer };
|
|
410
|
+
const hasMissingRaw = (pointer.raw_artifact_paths ?? []).some((p) => validation.unavailable.includes(p)) ||
|
|
411
|
+
(pointer.provider_attempt_artifact_path &&
|
|
412
|
+
validation.unavailable.includes(pointer.provider_attempt_artifact_path));
|
|
413
|
+
if (hasMissingRaw && updated.availability === "available") {
|
|
414
|
+
updated.availability = "unavailable";
|
|
415
|
+
warnings.push(`QC run ${runId} raw audit artifacts missing; marked unavailable`);
|
|
416
|
+
}
|
|
417
|
+
nextQcRuns[runId] = updated;
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
state: {
|
|
421
|
+
...state,
|
|
422
|
+
qc_runs: nextQcRuns,
|
|
423
|
+
},
|
|
424
|
+
pruned,
|
|
425
|
+
warnings,
|
|
426
|
+
};
|
|
427
|
+
}
|
package/dist/config/defaults.js
CHANGED
package/dist/config/validator.js
CHANGED
|
@@ -10,6 +10,9 @@ function isNumber(value) {
|
|
|
10
10
|
function isPositiveInteger(value) {
|
|
11
11
|
return Number.isInteger(value) && Number(value) > 0;
|
|
12
12
|
}
|
|
13
|
+
function isNonNegativeInteger(value) {
|
|
14
|
+
return Number.isInteger(value) && Number(value) >= 0;
|
|
15
|
+
}
|
|
13
16
|
function isString(value) {
|
|
14
17
|
return typeof value === "string";
|
|
15
18
|
}
|
|
@@ -91,6 +94,8 @@ const SUPPORTED_QC_PROVIDER_CAPABILITIES = [
|
|
|
91
94
|
"auto-fix",
|
|
92
95
|
"metrics-import",
|
|
93
96
|
];
|
|
97
|
+
const SUPPORTED_QC_OUTPUT_FORMATS = ["json", "jsonl", "sarif", "generic"];
|
|
98
|
+
const SUPPORTED_QC_FAILURE_ACTIONS = ["fail", "fallback", "ignore", "block"];
|
|
94
99
|
const SEVERITY_ORDER = ["info", "low", "medium", "high", "critical"];
|
|
95
100
|
function severityIndex(severity) {
|
|
96
101
|
return SEVERITY_ORDER.indexOf(severity);
|
|
@@ -908,6 +913,158 @@ function validateConfig(config) {
|
|
|
908
913
|
}
|
|
909
914
|
}
|
|
910
915
|
}
|
|
916
|
+
if ("enabled" in providerConfig && providerConfig.enabled !== undefined && !isBoolean(providerConfig.enabled)) {
|
|
917
|
+
result.valid = false;
|
|
918
|
+
result.errors.push(`qc.providers.${providerName}.enabled must be a boolean`);
|
|
919
|
+
}
|
|
920
|
+
if ("execution" in providerConfig && providerConfig.execution !== undefined) {
|
|
921
|
+
if (!isPlainObject(providerConfig.execution)) {
|
|
922
|
+
result.valid = false;
|
|
923
|
+
result.errors.push(`qc.providers.${providerName}.execution must be a plain object`);
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
const execution = providerConfig.execution;
|
|
927
|
+
if ("command" in execution && execution.command !== undefined && !isString(execution.command)) {
|
|
928
|
+
result.valid = false;
|
|
929
|
+
result.errors.push(`qc.providers.${providerName}.execution.command must be a string`);
|
|
930
|
+
}
|
|
931
|
+
if ("args" in execution && execution.args !== undefined && !isStringArray(execution.args)) {
|
|
932
|
+
result.valid = false;
|
|
933
|
+
result.errors.push(`qc.providers.${providerName}.execution.args must be an array of strings`);
|
|
934
|
+
}
|
|
935
|
+
if ("output" in execution && execution.output !== undefined) {
|
|
936
|
+
if (!isPlainObject(execution.output)) {
|
|
937
|
+
result.valid = false;
|
|
938
|
+
result.errors.push(`qc.providers.${providerName}.execution.output must be a plain object`);
|
|
939
|
+
}
|
|
940
|
+
else {
|
|
941
|
+
if ("format" in execution.output &&
|
|
942
|
+
execution.output.format !== undefined &&
|
|
943
|
+
(!isString(execution.output.format) ||
|
|
944
|
+
!SUPPORTED_QC_OUTPUT_FORMATS.includes(execution.output.format))) {
|
|
945
|
+
result.valid = false;
|
|
946
|
+
result.errors.push(`qc.providers.${providerName}.execution.output.format must be one of json, jsonl, sarif, generic`);
|
|
947
|
+
}
|
|
948
|
+
if ("parser" in execution.output &&
|
|
949
|
+
execution.output.parser !== undefined &&
|
|
950
|
+
!isString(execution.output.parser)) {
|
|
951
|
+
result.valid = false;
|
|
952
|
+
result.errors.push(`qc.providers.${providerName}.execution.output.parser must be a string`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if ("configPath" in execution &&
|
|
957
|
+
execution.configPath !== undefined &&
|
|
958
|
+
!isString(execution.configPath)) {
|
|
959
|
+
result.valid = false;
|
|
960
|
+
result.errors.push(`qc.providers.${providerName}.execution.configPath must be a string`);
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
if ("timeoutMs" in providerConfig && providerConfig.timeoutMs !== undefined && !isPositiveInteger(providerConfig.timeoutMs)) {
|
|
965
|
+
result.valid = false;
|
|
966
|
+
result.errors.push(`qc.providers.${providerName}.timeoutMs must be a positive integer`);
|
|
967
|
+
}
|
|
968
|
+
if ("primary" in providerConfig && providerConfig.primary !== undefined && !isBoolean(providerConfig.primary)) {
|
|
969
|
+
result.valid = false;
|
|
970
|
+
result.errors.push(`qc.providers.${providerName}.primary must be a boolean`);
|
|
971
|
+
}
|
|
972
|
+
if ("fallback" in providerConfig && providerConfig.fallback !== undefined && !isStringArray(providerConfig.fallback)) {
|
|
973
|
+
result.valid = false;
|
|
974
|
+
result.errors.push(`qc.providers.${providerName}.fallback must be an array of strings`);
|
|
975
|
+
}
|
|
976
|
+
if ("failurePolicy" in providerConfig && providerConfig.failurePolicy !== undefined) {
|
|
977
|
+
if (!isPlainObject(providerConfig.failurePolicy)) {
|
|
978
|
+
result.valid = false;
|
|
979
|
+
result.errors.push(`qc.providers.${providerName}.failurePolicy must be a plain object`);
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
for (const key of ["timeout", "parseFailure", "allProvidersFailed"]) {
|
|
983
|
+
const value = providerConfig.failurePolicy[key];
|
|
984
|
+
if (value !== undefined && (!isString(value) || !SUPPORTED_QC_FAILURE_ACTIONS.includes(value))) {
|
|
985
|
+
result.valid = false;
|
|
986
|
+
result.errors.push(`qc.providers.${providerName}.failurePolicy.${key} must be one of fail, fallback, ignore, block`);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if ("rateLimit" in providerConfig && providerConfig.rateLimit !== undefined) {
|
|
992
|
+
if (!isPlainObject(providerConfig.rateLimit)) {
|
|
993
|
+
result.valid = false;
|
|
994
|
+
result.errors.push(`qc.providers.${providerName}.rateLimit must be a plain object`);
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
if ("requestsPerMinute" in providerConfig.rateLimit &&
|
|
998
|
+
providerConfig.rateLimit.requestsPerMinute !== undefined &&
|
|
999
|
+
!isPositiveInteger(providerConfig.rateLimit.requestsPerMinute)) {
|
|
1000
|
+
result.valid = false;
|
|
1001
|
+
result.errors.push(`qc.providers.${providerName}.rateLimit.requestsPerMinute must be a positive integer`);
|
|
1002
|
+
}
|
|
1003
|
+
if ("maxConcurrent" in providerConfig.rateLimit &&
|
|
1004
|
+
providerConfig.rateLimit.maxConcurrent !== undefined &&
|
|
1005
|
+
!isPositiveInteger(providerConfig.rateLimit.maxConcurrent)) {
|
|
1006
|
+
result.valid = false;
|
|
1007
|
+
result.errors.push(`qc.providers.${providerName}.rateLimit.maxConcurrent must be a positive integer`);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if ("retry" in providerConfig && providerConfig.retry !== undefined) {
|
|
1012
|
+
if (!isPlainObject(providerConfig.retry)) {
|
|
1013
|
+
result.valid = false;
|
|
1014
|
+
result.errors.push(`qc.providers.${providerName}.retry must be a plain object`);
|
|
1015
|
+
}
|
|
1016
|
+
else {
|
|
1017
|
+
if ("maxRetries" in providerConfig.retry &&
|
|
1018
|
+
providerConfig.retry.maxRetries !== undefined &&
|
|
1019
|
+
!isNonNegativeInteger(providerConfig.retry.maxRetries)) {
|
|
1020
|
+
result.valid = false;
|
|
1021
|
+
result.errors.push(`qc.providers.${providerName}.retry.maxRetries must be a non-negative integer`);
|
|
1022
|
+
}
|
|
1023
|
+
if ("backoffMs" in providerConfig.retry &&
|
|
1024
|
+
providerConfig.retry.backoffMs !== undefined &&
|
|
1025
|
+
!isNonNegativeInteger(providerConfig.retry.backoffMs)) {
|
|
1026
|
+
result.valid = false;
|
|
1027
|
+
result.errors.push(`qc.providers.${providerName}.retry.backoffMs must be a non-negative integer`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if ("artifactPolicy" in providerConfig && providerConfig.artifactPolicy !== undefined) {
|
|
1032
|
+
if (!isPlainObject(providerConfig.artifactPolicy)) {
|
|
1033
|
+
result.valid = false;
|
|
1034
|
+
result.errors.push(`qc.providers.${providerName}.artifactPolicy must be a plain object`);
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
if ("retainRawOutput" in providerConfig.artifactPolicy &&
|
|
1038
|
+
providerConfig.artifactPolicy.retainRawOutput !== undefined &&
|
|
1039
|
+
!isBoolean(providerConfig.artifactPolicy.retainRawOutput)) {
|
|
1040
|
+
result.valid = false;
|
|
1041
|
+
result.errors.push(`qc.providers.${providerName}.artifactPolicy.retainRawOutput must be a boolean`);
|
|
1042
|
+
}
|
|
1043
|
+
if ("outputDir" in providerConfig.artifactPolicy &&
|
|
1044
|
+
providerConfig.artifactPolicy.outputDir !== undefined &&
|
|
1045
|
+
!isString(providerConfig.artifactPolicy.outputDir)) {
|
|
1046
|
+
result.valid = false;
|
|
1047
|
+
result.errors.push(`qc.providers.${providerName}.artifactPolicy.outputDir must be a string`);
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
// Validate fallback references after all provider keys are known.
|
|
1053
|
+
if (providers) {
|
|
1054
|
+
const providerKeys = new Set(Object.keys(config.qc.providers));
|
|
1055
|
+
for (const [providerName, providerConfig] of Object.entries(config.qc.providers)) {
|
|
1056
|
+
if (!isPlainObject(providerConfig))
|
|
1057
|
+
continue;
|
|
1058
|
+
const fallback = providerConfig.fallback;
|
|
1059
|
+
if (Array.isArray(fallback)) {
|
|
1060
|
+
for (const fallbackName of fallback) {
|
|
1061
|
+
if (isString(fallbackName) && !providerKeys.has(fallbackName)) {
|
|
1062
|
+
result.valid = false;
|
|
1063
|
+
result.errors.push(`qc.providers.${providerName}.fallback contains unknown provider: ${fallbackName}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
911
1068
|
}
|
|
912
1069
|
}
|
|
913
1070
|
}
|
|
@@ -37,6 +37,7 @@ function isPromotedClusterArtifact(relativePath, activeClusterId) {
|
|
|
37
37
|
const suffix = relativePath.slice(activeClusterPrefix.length);
|
|
38
38
|
return (suffix === "clusters.json"
|
|
39
39
|
|| suffix === "cluster-state.json"
|
|
40
|
+
|| suffix === "state.json"
|
|
40
41
|
|| suffix.startsWith("packets/")
|
|
41
42
|
|| suffix.startsWith("results/")
|
|
42
43
|
|| suffix.startsWith("qc/"));
|
|
@@ -131,6 +132,7 @@ function getArtifactPromotionPolicy(activeClusterId) {
|
|
|
131
132
|
promoted: [
|
|
132
133
|
`${activeClusterPrefix}clusters.json`,
|
|
133
134
|
`${activeClusterPrefix}cluster-state.json`,
|
|
135
|
+
`${activeClusterPrefix}state.json`,
|
|
134
136
|
`${activeClusterPrefix}packets/**`,
|
|
135
137
|
`${activeClusterPrefix}results/**`,
|
|
136
138
|
`${activeClusterPrefix}qc/**`,
|
package/dist/finalize/github.js
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildPrBody = buildPrBody;
|
|
3
4
|
exports.createDraftPr = createDraftPr;
|
|
4
5
|
const node_child_process_1 = require("node:child_process");
|
|
6
|
+
function buildPrBody(state, branch, authoritativeChildCount) {
|
|
7
|
+
return [
|
|
8
|
+
`**Cluster ID:** ${state.cluster_id}`,
|
|
9
|
+
`**Run ID:** ${state.run_id}`,
|
|
10
|
+
`**Branch:** ${branch}`,
|
|
11
|
+
`**Children completed:** ${authoritativeChildCount ?? state.completed_children.length}`,
|
|
12
|
+
``,
|
|
13
|
+
`_Generated by polaris finalize_`,
|
|
14
|
+
].join("\n");
|
|
15
|
+
}
|
|
5
16
|
function createDraftPr(options) {
|
|
6
|
-
const { repoRoot, branch, state, draft } = options;
|
|
17
|
+
const { repoRoot, branch, state, draft, authoritativeChildCount } = options;
|
|
7
18
|
if (!state.cluster_id) {
|
|
8
19
|
throw new Error(`createDraftPr: state.cluster_id is empty — cannot create PR without a cluster identifier`);
|
|
9
20
|
}
|
|
@@ -16,14 +27,7 @@ function createDraftPr(options) {
|
|
|
16
27
|
`branch/cluster mismatch, aborting PR creation`);
|
|
17
28
|
}
|
|
18
29
|
const title = `polaris finalize: ${state.cluster_id} (${state.run_id})`;
|
|
19
|
-
const body =
|
|
20
|
-
`**Cluster ID:** ${state.cluster_id}`,
|
|
21
|
-
`**Run ID:** ${state.run_id}`,
|
|
22
|
-
`**Branch:** ${branch}`,
|
|
23
|
-
`**Children completed:** ${state.completed_children.length}`,
|
|
24
|
-
``,
|
|
25
|
-
`_Generated by polaris finalize_`,
|
|
26
|
-
].join("\n");
|
|
30
|
+
const body = buildPrBody(state, branch, authoritativeChildCount);
|
|
27
31
|
const args = [
|
|
28
32
|
"pr", "create",
|
|
29
33
|
"--title", title,
|