@lsctech/polaris 0.5.7 → 0.5.9
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/sol-evaluation-writer.js +135 -0
- package/dist/autoresearch/sol-evidence-normalizer.js +337 -0
- package/dist/autoresearch/sol-recommendations.js +130 -0
- package/dist/autoresearch/sol-report-renderer.js +282 -0
- package/dist/autoresearch/sol-run-health-bridge.js +246 -0
- package/dist/autoresearch/sol-scorecard-calculator.js +865 -0
- package/dist/cli/autoresearch.js +77 -0
- package/dist/cli/medic.js +65 -0
- package/dist/cluster-state/store.js +56 -0
- package/dist/config/defaults.js +3 -0
- package/dist/config/validator.js +141 -0
- package/dist/finalize/artifact-policy.js +2 -0
- package/dist/finalize/github.js +13 -9
- package/dist/finalize/index.js +231 -5
- package/dist/finalize/linear.js +8 -4
- package/dist/finalize/medic-gate.js +42 -0
- 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 +234 -28
- package/dist/loop/worker-packet.js +19 -1
- package/dist/medic/run-health-consult.js +381 -0
- package/dist/medic/treatment-packets.js +169 -0
- package/dist/qc/artifacts.js +27 -0
- package/dist/qc/orchestration.js +3 -2
- package/dist/qc/providers/coderabbit.js +114 -11
- package/dist/qc/repair-loop.js +49 -14
- package/dist/qc/runner.js +10 -2
- package/dist/qc/schemas.js +1 -0
- package/dist/run-health/foreman-symptoms.js +100 -0
- package/dist/run-health/index.js +301 -0
- package/dist/run-health/qc-escalation.js +155 -0
- package/dist/run-health/schema.js +123 -0
- package/dist/types/sol-metrics.js +108 -0
- package/dist/types/sol-scorecard.js +148 -0
- package/package.json +1 -1
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)")
|
package/dist/cli/medic.js
CHANGED
|
@@ -5,6 +5,10 @@ const commander_1 = require("commander");
|
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const node_fs_1 = require("node:fs");
|
|
7
7
|
const chart_id_js_1 = require("../medic/chart-id.js");
|
|
8
|
+
const loader_js_1 = require("../config/loader.js");
|
|
9
|
+
const terminal_cli_js_1 = require("../loop/adapters/terminal-cli.js");
|
|
10
|
+
const run_health_consult_js_1 = require("../medic/run-health-consult.js");
|
|
11
|
+
const treatment_packets_js_1 = require("../medic/treatment-packets.js");
|
|
8
12
|
function createMedicCommand(options = {}) {
|
|
9
13
|
const repoRootDefault = options.repoRoot ?? (0, node_path_1.resolve)(process.cwd());
|
|
10
14
|
const medic = new commander_1.Command("medic")
|
|
@@ -34,6 +38,67 @@ function createMedicCommand(options = {}) {
|
|
|
34
38
|
process.exit(1);
|
|
35
39
|
}
|
|
36
40
|
}));
|
|
41
|
+
medic
|
|
42
|
+
.command("run-health-consult")
|
|
43
|
+
.description("Run a Medic run-health consult from a packet file")
|
|
44
|
+
.requiredOption("--packet-file <path>", "Path to MedicRunHealthPacket JSON")
|
|
45
|
+
.option("-r, --repo-root <path>", "Repository root", repoRootDefault)
|
|
46
|
+
.action(async (cmdOptions) => {
|
|
47
|
+
const repoRoot = cmdOptions.repoRoot;
|
|
48
|
+
let packet;
|
|
49
|
+
try {
|
|
50
|
+
packet = JSON.parse((0, node_fs_1.readFileSync)(cmdOptions.packetFile, "utf-8"));
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
process.stderr.write(`medic run-health-consult error: cannot read packet: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (packet.role !== "medic-run-health") {
|
|
58
|
+
process.stderr.write(`medic run-health-consult error: packet role must be \"medic-run-health\", got \"${packet.role}\"\n`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const config = (0, loader_js_1.loadConfig)(repoRoot);
|
|
63
|
+
const adapter = new terminal_cli_js_1.TerminalCliAdapter(config.execution);
|
|
64
|
+
// Use shared provider resolution logic: prefer rotation, fall back to first provider
|
|
65
|
+
const providerName = (config.execution.rotation && config.execution.rotation.length > 0)
|
|
66
|
+
? config.execution.rotation[0]
|
|
67
|
+
: Object.keys(config.execution.providers ?? {})[0] ?? "terminal-cli";
|
|
68
|
+
// Get current branch from git
|
|
69
|
+
let branch = "main";
|
|
70
|
+
try {
|
|
71
|
+
const { execFileSync } = require("node:child_process");
|
|
72
|
+
branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
73
|
+
cwd: repoRoot,
|
|
74
|
+
encoding: "utf-8",
|
|
75
|
+
}).trim();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// Fall back to main if git fails
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const result = await (0, run_health_consult_js_1.runMedicRunHealthConsult)({
|
|
82
|
+
packet,
|
|
83
|
+
repoRoot,
|
|
84
|
+
stateFile: packet.cluster_state_path,
|
|
85
|
+
telemetryFile: packet.telemetry_path,
|
|
86
|
+
branch,
|
|
87
|
+
dryRun: false,
|
|
88
|
+
dispatchTreatmentWorkerFn: (input) => (0, treatment_packets_js_1.dispatchTreatmentWorker)({
|
|
89
|
+
...input,
|
|
90
|
+
repoRoot,
|
|
91
|
+
dispatch: (workerPacket) => adapter.dispatch(workerPacket, { provider: providerName }),
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
(0, node_fs_1.writeFileSync)(packet.result_path, JSON.stringify(result, null, 2), "utf-8");
|
|
95
|
+
process.stdout.write(`Medic run-health consult result written to ${packet.result_path}\n`);
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
process.stderr.write(`medic run-health-consult error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
37
102
|
return medic;
|
|
38
103
|
}
|
|
39
104
|
function createChart(options) {
|
|
@@ -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
|
@@ -548,6 +548,20 @@ function validateConfig(config) {
|
|
|
548
548
|
result.errors.push("finalize.archiveRunSnapshot must be a boolean");
|
|
549
549
|
}
|
|
550
550
|
}
|
|
551
|
+
if ("medic" in config.finalize && config.finalize.medic !== undefined) {
|
|
552
|
+
if (!isPlainObject(config.finalize.medic)) {
|
|
553
|
+
result.valid = false;
|
|
554
|
+
result.errors.push("finalize.medic must be an object");
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
if ("bypassPolicy" in config.finalize.medic && config.finalize.medic.bypassPolicy !== undefined) {
|
|
558
|
+
if (!isString(config.finalize.medic.bypassPolicy) || !["none", "cli"].includes(config.finalize.medic.bypassPolicy)) {
|
|
559
|
+
result.valid = false;
|
|
560
|
+
result.errors.push('finalize.medic.bypassPolicy must be one of "none" or "cli"');
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
551
565
|
}
|
|
552
566
|
}
|
|
553
567
|
// tracker
|
|
@@ -1197,6 +1211,128 @@ function validateConfig(config) {
|
|
|
1197
1211
|
}
|
|
1198
1212
|
}
|
|
1199
1213
|
}
|
|
1214
|
+
// run_health
|
|
1215
|
+
if ("run_health" in config && config.run_health !== undefined) {
|
|
1216
|
+
if (!isPlainObject(config.run_health)) {
|
|
1217
|
+
result.valid = false;
|
|
1218
|
+
result.errors.push("run_health must be an object");
|
|
1219
|
+
}
|
|
1220
|
+
else {
|
|
1221
|
+
if ("foreman_symptoms" in config.run_health && config.run_health.foreman_symptoms !== undefined) {
|
|
1222
|
+
if (!isPlainObject(config.run_health.foreman_symptoms)) {
|
|
1223
|
+
result.valid = false;
|
|
1224
|
+
result.errors.push("run_health.foreman_symptoms must be an object");
|
|
1225
|
+
}
|
|
1226
|
+
else {
|
|
1227
|
+
if ("enabled" in config.run_health.foreman_symptoms && config.run_health.foreman_symptoms.enabled !== undefined) {
|
|
1228
|
+
if (!isBoolean(config.run_health.foreman_symptoms.enabled)) {
|
|
1229
|
+
result.valid = false;
|
|
1230
|
+
result.errors.push("run_health.foreman_symptoms.enabled must be a boolean");
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
// sol
|
|
1238
|
+
if ("sol" in config && config.sol !== undefined) {
|
|
1239
|
+
if (!isPlainObject(config.sol)) {
|
|
1240
|
+
result.valid = false;
|
|
1241
|
+
result.errors.push("sol must be an object");
|
|
1242
|
+
}
|
|
1243
|
+
else {
|
|
1244
|
+
if ("history" in config.sol && config.sol.history !== undefined) {
|
|
1245
|
+
if (!isPlainObject(config.sol.history)) {
|
|
1246
|
+
result.valid = false;
|
|
1247
|
+
result.errors.push("sol.history must be an object");
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
if ("enabled" in config.sol.history && config.sol.history.enabled !== undefined) {
|
|
1251
|
+
if (!isBoolean(config.sol.history.enabled)) {
|
|
1252
|
+
result.valid = false;
|
|
1253
|
+
result.errors.push("sol.history.enabled must be a boolean");
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
if ("path" in config.sol.history && config.sol.history.path !== undefined) {
|
|
1257
|
+
if (!isString(config.sol.history.path)) {
|
|
1258
|
+
result.valid = false;
|
|
1259
|
+
result.errors.push("sol.history.path must be a string");
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
if ("thresholds" in config.sol && config.sol.thresholds !== undefined) {
|
|
1265
|
+
if (!isPlainObject(config.sol.thresholds)) {
|
|
1266
|
+
result.valid = false;
|
|
1267
|
+
result.errors.push("sol.thresholds must be an object");
|
|
1268
|
+
}
|
|
1269
|
+
else {
|
|
1270
|
+
if ("enabled" in config.sol.thresholds && config.sol.thresholds.enabled !== undefined) {
|
|
1271
|
+
if (!isBoolean(config.sol.thresholds.enabled)) {
|
|
1272
|
+
result.valid = false;
|
|
1273
|
+
result.errors.push("sol.thresholds.enabled must be a boolean");
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
if ("policy" in config.sol.thresholds && config.sol.thresholds.policy !== undefined) {
|
|
1277
|
+
if (!isPlainObject(config.sol.thresholds.policy)) {
|
|
1278
|
+
result.valid = false;
|
|
1279
|
+
result.errors.push("sol.thresholds.policy must be an object");
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
if ("createRunHealthReport" in config.sol.thresholds.policy && config.sol.thresholds.policy.createRunHealthReport !== undefined) {
|
|
1283
|
+
if (!isBoolean(config.sol.thresholds.policy.createRunHealthReport)) {
|
|
1284
|
+
result.valid = false;
|
|
1285
|
+
result.errors.push("sol.thresholds.policy.createRunHealthReport must be a boolean");
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
if ("requireMedic" in config.sol.thresholds.policy && config.sol.thresholds.policy.requireMedic !== undefined) {
|
|
1289
|
+
if (!isBoolean(config.sol.thresholds.policy.requireMedic)) {
|
|
1290
|
+
result.valid = false;
|
|
1291
|
+
result.errors.push("sol.thresholds.policy.requireMedic must be a boolean");
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
if ("low_composite_score" in config.sol.thresholds && config.sol.thresholds.low_composite_score !== undefined) {
|
|
1297
|
+
if (!isNumber(config.sol.thresholds.low_composite_score) || !inRange(config.sol.thresholds.low_composite_score, 0, 1)) {
|
|
1298
|
+
result.valid = false;
|
|
1299
|
+
result.errors.push("sol.thresholds.low_composite_score must be a number between 0 and 1");
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if ("qc_repair_loop_failure_statuses" in config.sol.thresholds && config.sol.thresholds.qc_repair_loop_failure_statuses !== undefined) {
|
|
1303
|
+
if (!isStringArray(config.sol.thresholds.qc_repair_loop_failure_statuses)) {
|
|
1304
|
+
result.valid = false;
|
|
1305
|
+
result.errors.push("sol.thresholds.qc_repair_loop_failure_statuses must be an array of strings");
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
if ("repeated_provider_failures" in config.sol.thresholds && config.sol.thresholds.repeated_provider_failures !== undefined) {
|
|
1309
|
+
if (!isPositiveInteger(config.sol.thresholds.repeated_provider_failures)) {
|
|
1310
|
+
result.valid = false;
|
|
1311
|
+
result.errors.push("sol.thresholds.repeated_provider_failures must be a positive integer");
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
if ("foreman_intervention_count" in config.sol.thresholds && config.sol.thresholds.foreman_intervention_count !== undefined) {
|
|
1315
|
+
if (!isNonNegativeInteger(config.sol.thresholds.foreman_intervention_count)) {
|
|
1316
|
+
result.valid = false;
|
|
1317
|
+
result.errors.push("sol.thresholds.foreman_intervention_count must be a non-negative integer");
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
if ("stale_wrong_run_telemetry" in config.sol.thresholds && config.sol.thresholds.stale_wrong_run_telemetry !== undefined) {
|
|
1321
|
+
if (!isBoolean(config.sol.thresholds.stale_wrong_run_telemetry)) {
|
|
1322
|
+
result.valid = false;
|
|
1323
|
+
result.errors.push("sol.thresholds.stale_wrong_run_telemetry must be a boolean");
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
if ("validation_failures" in config.sol.thresholds && config.sol.thresholds.validation_failures !== undefined) {
|
|
1327
|
+
if (!isNonNegativeInteger(config.sol.thresholds.validation_failures)) {
|
|
1328
|
+
result.valid = false;
|
|
1329
|
+
result.errors.push("sol.thresholds.validation_failures must be a non-negative integer");
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1200
1336
|
// unknown top-level fields -> warnings
|
|
1201
1337
|
const knownKeys = new Set([
|
|
1202
1338
|
"version",
|
|
@@ -1213,6 +1349,11 @@ function validateConfig(config) {
|
|
|
1213
1349
|
"budget",
|
|
1214
1350
|
"compact",
|
|
1215
1351
|
"qc",
|
|
1352
|
+
"sol",
|
|
1353
|
+
"run_health",
|
|
1354
|
+
"orchestration",
|
|
1355
|
+
"skill_packet",
|
|
1356
|
+
"simplicity",
|
|
1216
1357
|
]);
|
|
1217
1358
|
for (const key of Object.keys(config)) {
|
|
1218
1359
|
if (!knownKeys.has(key)) {
|
|
@@ -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,
|