@lsctech/polaris 0.5.5 → 0.5.7

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.
@@ -11,7 +11,7 @@
11
11
  * accept BootstrapPacket transparently accept WorkerPacket.
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.STARTUP_RETURN_CONTRACT = exports.PREFLIGHT_RETURN_CONTRACT = exports.WORKER_PROHIBITED_WRITE_PATHS = exports.FINALIZE_RETURN_CONTRACT = exports.IMPL_RETURN_CONTRACT = void 0;
14
+ exports.REPAIR_RETURN_CONTRACT = exports.STARTUP_RETURN_CONTRACT = exports.PREFLIGHT_RETURN_CONTRACT = exports.WORKER_PROHIBITED_WRITE_PATHS = exports.FINALIZE_RETURN_CONTRACT = exports.IMPL_RETURN_CONTRACT = void 0;
15
15
  exports.roleContextForWorkerRole = roleContextForWorkerRole;
16
16
  exports.isWorkerPacket = isWorkerPacket;
17
17
  exports.getWorkerCommitPolicy = getWorkerCommitPolicy;
@@ -19,6 +19,7 @@ exports.compileStartupPacket = compileStartupPacket;
19
19
  exports.compileImplPacket = compileImplPacket;
20
20
  exports.compileFinalizePacket = compileFinalizePacket;
21
21
  exports.compilePreflightPacket = compilePreflightPacket;
22
+ exports.compileRepairWorkerPacket = compileRepairWorkerPacket;
22
23
  const worker_prompt_js_1 = require("./worker-prompt.js");
23
24
  const body_parser_js_1 = require("./body-parser.js");
24
25
  const WORKER_ROLE_CONTEXT = {
@@ -421,3 +422,76 @@ function compilePreflightPacket(input) {
421
422
  },
422
423
  };
423
424
  }
425
+ /** Return contract for repair workers (same shape as impl). */
426
+ exports.REPAIR_RETURN_CONTRACT = [
427
+ 'child_id',
428
+ 'status',
429
+ 'commit',
430
+ 'validation',
431
+ 'next_recommended_action',
432
+ ];
433
+ /**
434
+ * Build a compiled repair worker packet.
435
+ *
436
+ * Repair workers use `worker_role: "repair"` and the same sealed packet/result
437
+ * contracts as impl workers, but with tightly scoped allowed/prohibited paths
438
+ * derived from the compiled repair packet manifest.
439
+ *
440
+ * The parent/orchestrator MUST use this function and MUST NOT implement
441
+ * repair work inline.
442
+ */
443
+ function compileRepairWorkerPacket(input) {
444
+ const steps = [
445
+ `Confirm QC repair scope: allowed=${JSON.stringify(input.allowedScope)}, prohibited=${JSON.stringify(input.prohibitedScope)}.`,
446
+ `Review root-cause hint: ${input.rootCauseHint}`,
447
+ `Apply minimal repair to files within allowed scope only. Do NOT touch prohibited paths.`,
448
+ `Run validation commands and confirm all pass.`,
449
+ `Create exactly ONE git commit: [REPAIR] ${input.clusterId} round-${input.round} packet-${input.packetId}.`,
450
+ `Write compact return JSON to stdout (fields: ${exports.REPAIR_RETURN_CONTRACT.join(', ')}). next_recommended_action MUST be "continue" on success, "stop" on unresolvable blocker.`,
451
+ `TERMINATE SESSION IMMEDIATELY.`,
452
+ ];
453
+ return {
454
+ schema_version: '2.1',
455
+ worker_role: 'repair',
456
+ run_id: input.runId,
457
+ cluster_id: input.clusterId,
458
+ active_child: input.packetId,
459
+ state_file: input.stateFile,
460
+ telemetry_file: input.telemetryFile,
461
+ instructions: {
462
+ primary_goal: `QC repair worker for cluster ${input.clusterId}, round ${input.round}, packet ${input.packetId}. ` +
463
+ `Apply minimal targeted repairs to findings within the allowed scope. Root cause: ${input.rootCauseHint}`,
464
+ steps,
465
+ allowed_scope: input.allowedScope,
466
+ validation_commands: input.validationCommands,
467
+ },
468
+ lifecycle: defaultLifecycle(input.maxConcurrentWorkers ?? 1, 'commit-and-exit'),
469
+ return_contract: exports.REPAIR_RETURN_CONTRACT,
470
+ prompt_mode: 'full',
471
+ prompt_metrics: { mode: 'full', char_count: 0, estimated_tokens: 0 },
472
+ role_context: roleContextForWorkerRole('repair'),
473
+ routing_context: {
474
+ task_type: "repair",
475
+ required_capabilities: ["repair"],
476
+ },
477
+ prohibited_write_paths: [
478
+ ...exports.WORKER_PROHIBITED_WRITE_PATHS,
479
+ ...input.prohibitedScope,
480
+ ],
481
+ result_file_contract: {
482
+ result_file: input.resultFile,
483
+ result_required_fields: Object.fromEntries([
484
+ ['run_id', input.runId],
485
+ ['cluster_id', input.clusterId],
486
+ ['child_id', input.packetId],
487
+ ...exports.REPAIR_RETURN_CONTRACT.filter((key) => key !== 'child_id').map((key) => [key, `<${key}>`]),
488
+ ]),
489
+ },
490
+ context: {
491
+ branch: input.branch,
492
+ worker_role: 'repair',
493
+ repair_round: input.round,
494
+ repair_packet_id: input.packetId,
495
+ },
496
+ };
497
+ }
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_TEST_CONFIG = void 0;
4
+ exports.makeFinding = makeFinding;
5
+ exports.makeResult = makeResult;
6
+ exports.makeSameFileFindings = makeSameFileFindings;
7
+ exports.makeCrossFileSubsystemFindings = makeCrossFileSubsystemFindings;
8
+ exports.makeOverlappingScopeFindings = makeOverlappingScopeFindings;
9
+ exports.makeHighRiskFindings = makeHighRiskFindings;
10
+ exports.makeLowConfidenceBroadFindings = makeLowConfidenceBroadFindings;
11
+ exports.DEFAULT_TEST_CONFIG = {
12
+ enabled: true,
13
+ severityThresholds: { block: "high", repair: "medium", followUp: "low" },
14
+ };
15
+ function makeFinding(overrides = {}) {
16
+ return {
17
+ findingId: `f-${Math.random().toString(36).slice(2, 8)}`,
18
+ severity: "medium",
19
+ category: "style",
20
+ title: "Test finding",
21
+ fixAvailable: true,
22
+ autofixEligible: false,
23
+ attribution: { confidence: "high", reason: "changed-file-owner", childId: "POL-472" },
24
+ status: "open",
25
+ ...overrides,
26
+ };
27
+ }
28
+ function makeResult(overrides = {}) {
29
+ const now = new Date().toISOString();
30
+ return {
31
+ schemaVersion: "1.0",
32
+ qcRunId: `qc-run-${Math.random().toString(36).slice(2, 8)}`,
33
+ runId: "run-1",
34
+ clusterId: "POL-TEST",
35
+ trigger: "completed-cluster",
36
+ provider: "coderabbit",
37
+ providerMode: "local",
38
+ startedAt: now,
39
+ completedAt: now,
40
+ status: "findings",
41
+ findings: [],
42
+ rawArtifactPaths: [],
43
+ parserVersion: "coderabbit-1.0",
44
+ policyDecision: {
45
+ blocksDelivery: false,
46
+ requiresOperatorReview: false,
47
+ routedToRepair: false,
48
+ summary: "ok",
49
+ },
50
+ ...overrides,
51
+ };
52
+ }
53
+ function makeSameFileFindings() {
54
+ return [
55
+ makeFinding({
56
+ findingId: "f-same-1",
57
+ filePath: "src/auth/login.ts",
58
+ category: "style",
59
+ severity: "medium",
60
+ range: { startLine: 10, endLine: 15 },
61
+ }),
62
+ makeFinding({
63
+ findingId: "f-same-2",
64
+ filePath: "src/auth/login.ts",
65
+ category: "type-safety",
66
+ severity: "medium",
67
+ range: { startLine: 30, endLine: 35 },
68
+ }),
69
+ makeFinding({
70
+ findingId: "f-same-3",
71
+ filePath: "src/auth/login.ts",
72
+ category: "style",
73
+ severity: "medium",
74
+ range: { startLine: 12, endLine: 16 },
75
+ }),
76
+ ];
77
+ }
78
+ function makeCrossFileSubsystemFindings() {
79
+ return [
80
+ makeFinding({
81
+ findingId: "f-sub-a",
82
+ filePath: "src/qc/compiler.ts",
83
+ category: "error-handling",
84
+ severity: "medium",
85
+ range: { startLine: 5, endLine: 8 },
86
+ }),
87
+ makeFinding({
88
+ findingId: "f-sub-b",
89
+ filePath: "src/qc/runner.ts",
90
+ category: "error-handling",
91
+ severity: "medium",
92
+ range: { startLine: 20, endLine: 24 },
93
+ }),
94
+ makeFinding({
95
+ findingId: "f-sub-c",
96
+ filePath: "src/loop/worker.ts",
97
+ category: "error-handling",
98
+ severity: "medium",
99
+ range: { startLine: 7, endLine: 11 },
100
+ }),
101
+ ];
102
+ }
103
+ function makeOverlappingScopeFindings() {
104
+ return [
105
+ makeFinding({
106
+ findingId: "f-overlap-1",
107
+ filePath: "src/core/state.ts",
108
+ category: "race-condition",
109
+ severity: "high",
110
+ range: { startLine: 40, endLine: 45 },
111
+ }),
112
+ makeFinding({
113
+ findingId: "f-overlap-2",
114
+ filePath: "src/core/state.ts",
115
+ category: "race-condition",
116
+ severity: "high",
117
+ range: { startLine: 42, endLine: 48 },
118
+ }),
119
+ ];
120
+ }
121
+ function makeHighRiskFindings() {
122
+ return [
123
+ makeFinding({
124
+ findingId: "f-security-1",
125
+ filePath: "src/auth/token.ts",
126
+ category: "security",
127
+ severity: "high",
128
+ range: { startLine: 1, endLine: 5 },
129
+ }),
130
+ makeFinding({
131
+ findingId: "f-security-2",
132
+ filePath: "src/auth/token.ts",
133
+ category: "auth",
134
+ severity: "medium",
135
+ range: { startLine: 10, endLine: 14 },
136
+ }),
137
+ makeFinding({
138
+ findingId: "f-migration-1",
139
+ filePath: "src/db/migrate.ts",
140
+ category: "migration",
141
+ severity: "medium",
142
+ range: { startLine: 3, endLine: 7 },
143
+ }),
144
+ ];
145
+ }
146
+ function makeLowConfidenceBroadFindings() {
147
+ return [
148
+ makeFinding({
149
+ findingId: "f-broad-1",
150
+ filePath: undefined,
151
+ category: "architecture",
152
+ severity: "medium",
153
+ attribution: { confidence: "low", reason: "provider-uncertain" },
154
+ }),
155
+ makeFinding({
156
+ findingId: "f-broad-2",
157
+ filePath: undefined,
158
+ category: "architecture",
159
+ severity: "low",
160
+ attribution: { confidence: "unattributed", reason: "unattributed" },
161
+ }),
162
+ makeFinding({
163
+ findingId: "f-preexisting-1",
164
+ filePath: "src/legacy/old.ts",
165
+ category: "tech-debt",
166
+ severity: "low",
167
+ attribution: { confidence: "medium", reason: "pre-existing" },
168
+ }),
169
+ ];
170
+ }
package/dist/qc/index.js CHANGED
@@ -28,3 +28,5 @@ __exportStar(require("./registry.js"), exports);
28
28
  __exportStar(require("./attribution.js"), exports);
29
29
  __exportStar(require("./autofix.js"), exports);
30
30
  __exportStar(require("./routing.js"), exports);
31
+ __exportStar(require("./repair-packets.js"), exports);
32
+ __exportStar(require("./repair-loop.js"), exports);
@@ -108,6 +108,8 @@ async function runQcAtTrigger(options) {
108
108
  branch,
109
109
  telemetryFile,
110
110
  timeoutMs,
111
+ config,
112
+ registry,
111
113
  });
112
114
  const result = applyAttributionAndRouting(rawResult, config, attributionContext, ownership, routeName);
113
115
  try {
package/dist/qc/policy.js CHANGED
@@ -9,6 +9,7 @@
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
11
  exports.decideQcAction = decideQcAction;
12
+ exports.decideProviderFailureAction = decideProviderFailureAction;
12
13
  exports.computeQcPolicyDecision = computeQcPolicyDecision;
13
14
  const severity_js_1 = require("./severity.js");
14
15
  /**
@@ -16,6 +17,7 @@ const severity_js_1 = require("./severity.js");
16
17
  *
17
18
  * Rules:
18
19
  * - passed / skipped -> pass (no effect on delivery)
20
+ * - allProvidersFailed -> block
19
21
  * - repairRouting: "block" -> block finalize/delivery
20
22
  * - repairRouting: "log" -> pass (passive report only)
21
23
  * - repairRouting: "route" / "follow-up" -> create follow-up work, but high/critical
@@ -27,6 +29,9 @@ function decideQcAction(result, config) {
27
29
  if (result.status === "passed" || result.status === "skipped") {
28
30
  return "pass";
29
31
  }
32
+ if (result.allProvidersFailed) {
33
+ return "block";
34
+ }
30
35
  const routing = config.repairRouting ?? "route";
31
36
  if (routing === "block") {
32
37
  return "block";
@@ -41,19 +46,41 @@ function decideQcAction(result, config) {
41
46
  }
42
47
  return "follow-up";
43
48
  }
49
+ /**
50
+ * Decide what the runtime should do with a classified provider failure.
51
+ *
52
+ * Maps a failure reason to the per-provider failure policy from configuration.
53
+ * Reasons without a dedicated policy key default to "fail" so behavior is
54
+ * deterministic even for newly-classified failures.
55
+ */
56
+ function decideProviderFailureAction(reason, policy) {
57
+ switch (reason) {
58
+ case "timeout":
59
+ return policy?.timeout ?? "fail";
60
+ case "parse-failed":
61
+ return policy?.parseFailure ?? "fail";
62
+ default:
63
+ return "fail";
64
+ }
65
+ }
44
66
  /**
45
67
  * Compute the detailed policy decision for a QC result after attribution and
46
68
  * repair routing have been applied.
47
69
  */
48
70
  function computeQcPolicyDecision(result, config) {
49
71
  const blockThreshold = config.severityThresholds?.block ?? "high";
50
- const blocksDelivery = result.findings.some((f) => (0, severity_js_1.compareSeverity)(f.severity, blockThreshold) >= 0 &&
51
- (f.routingDecision === "operator-review" || f.routingDecision === undefined));
52
- const requiresOperatorReview = result.status === "failed" ||
72
+ const blocksDelivery = result.allProvidersFailed ||
73
+ result.findings.some((f) => (0, severity_js_1.compareSeverity)(f.severity, blockThreshold) >= 0 &&
74
+ (f.routingDecision === "operator-review" || f.routingDecision === undefined));
75
+ const requiresOperatorReview = result.allProvidersFailed ||
76
+ result.status === "failed" ||
53
77
  result.status === "blocked" ||
54
78
  result.findings.some((f) => f.routingDecision === "operator-review");
55
79
  const routedToRepair = result.findings.some((f) => f.routingDecision === "original-worker" || f.routingDecision === "repair-worker");
56
80
  const summaryParts = [];
81
+ if (result.allProvidersFailed) {
82
+ summaryParts.push("all providers failed");
83
+ }
57
84
  summaryParts.push(`${result.findings.length} findings`);
58
85
  if (requiresOperatorReview) {
59
86
  summaryParts.push("operator review required");
@@ -53,7 +53,13 @@ function parseFindingsFromPayload(payload) {
53
53
  }
54
54
  return [];
55
55
  }
56
- function parseReport(output) {
56
+ function parseReport(output, format, parser) {
57
+ if (parser && parser !== "coderabbit") {
58
+ throw new Error(`Unsupported parser for CodeRabbit provider: ${parser}`);
59
+ }
60
+ if (format === "sarif") {
61
+ throw new Error("SARIF output format is not supported by the CodeRabbit provider");
62
+ }
57
63
  const text = "stdout" in output && typeof output.stdout === "string" ? output.stdout : "";
58
64
  const data = "data" in output ? output.data : undefined;
59
65
  if (data !== undefined && typeof data === "object" && data !== null) {
@@ -64,7 +70,10 @@ function parseReport(output) {
64
70
  ...(typeof record.prUrl === "string" ? { prUrl: record.prUrl } : {}),
65
71
  };
66
72
  }
67
- if (text.trim().startsWith("{") || text.trim().startsWith("[")) {
73
+ if (text.trim().length === 0) {
74
+ return null;
75
+ }
76
+ if (format === "json" || text.trim().startsWith("{") || text.trim().startsWith("[")) {
68
77
  try {
69
78
  const parsed = JSON.parse(text);
70
79
  if (Array.isArray(parsed)) {
@@ -77,11 +86,15 @@ function parseReport(output) {
77
86
  : {}),
78
87
  };
79
88
  }
80
- catch {
81
- // Fall through to line scanning.
89
+ catch (jsonError) {
90
+ // When the format is explicitly JSON, a parse error is a real failure.
91
+ // Otherwise, treat the text as JSONL and fall through to line scanning.
92
+ if (format === "json") {
93
+ throw jsonError;
94
+ }
82
95
  }
83
96
  }
84
- // Try JSONL: one finding per line
97
+ // JSONL or generic line scanning: one finding per line
85
98
  const lines = text.split("\n").filter((line) => line.trim().length > 0);
86
99
  const lineFindings = [];
87
100
  for (const line of lines) {
@@ -96,7 +109,7 @@ function parseReport(output) {
96
109
  if (lineFindings.length > 0) {
97
110
  return { findings: lineFindings };
98
111
  }
99
- return null;
112
+ throw new Error("CodeRabbit output could not be parsed as JSON, JSONL, or metrics payload");
100
113
  }
101
114
  function normalizeFinding(raw, index) {
102
115
  const severityLabel = pickString(raw.severity, raw.level) ?? "info";
@@ -179,6 +192,7 @@ function buildResultFromReport(report, output, mode, providerFailed = false) {
179
192
  * No external network calls are made here.
180
193
  */
181
194
  class CodeRabbitQcProvider {
195
+ config;
182
196
  name = "coderabbit";
183
197
  supportedModes = ["local", "pr", "metrics-import"];
184
198
  capabilities = [
@@ -188,12 +202,29 @@ class CodeRabbitQcProvider {
188
202
  "auto-fix",
189
203
  "metrics-import",
190
204
  ];
205
+ constructor(config) {
206
+ this.config = config;
207
+ }
191
208
  canReview(scope) {
192
209
  if (scope.prUrl)
193
210
  return true;
194
211
  return Boolean(scope.branch);
195
212
  }
196
213
  buildReviewCommand(scope) {
214
+ const execution = this.config?.execution;
215
+ if (execution) {
216
+ const args = execution.args ? [...execution.args] : [];
217
+ if (execution.configPath) {
218
+ args.push("--config", execution.configPath);
219
+ }
220
+ if (scope.prUrl) {
221
+ args.push("--pr-url", scope.prUrl);
222
+ }
223
+ else {
224
+ args.push("--branch", scope.branch ?? "HEAD");
225
+ }
226
+ return { command: execution.command, args };
227
+ }
197
228
  if (scope.prUrl) {
198
229
  return { command: "coderabbit", args: ["review", "--agent", "--pr-url", scope.prUrl] };
199
230
  }
@@ -203,7 +234,8 @@ class CodeRabbitQcProvider {
203
234
  };
204
235
  }
205
236
  parse(output) {
206
- const report = parseReport(output);
237
+ const outputConfig = this.config?.execution?.output;
238
+ const report = parseReport(output, outputConfig?.format, outputConfig?.parser);
207
239
  return buildResultFromReport(report, output, "local", output.exitCode !== 0);
208
240
  }
209
241
  importMetrics(payload) {
@@ -1,16 +1,49 @@
1
1
  "use strict";
2
2
  /**
3
- * Default QC provider registry.
3
+ * QC provider registry builders.
4
4
  *
5
- * Registers all built-in QC adapters. In the future this can be extended to
6
- * discover providers from configuration or installed plugins.
5
+ * `createDefaultQcRegistry` returns the static built-in registry for backward
6
+ * compatibility. `createQcRegistry` builds a registry from polaris.config.json
7
+ * qc.providers, wiring provider-agnostic command/output config into the
8
+ * matching built-in adapters.
7
9
  */
8
10
  Object.defineProperty(exports, "__esModule", { value: true });
9
11
  exports.createDefaultQcRegistry = createDefaultQcRegistry;
12
+ exports.createQcRegistry = createQcRegistry;
10
13
  const provider_js_1 = require("./provider.js");
11
14
  const coderabbit_js_1 = require("./providers/coderabbit.js");
15
+ function createProvider(name, config) {
16
+ // Only CodeRabbit is supported as a concrete provider in this child.
17
+ // Future providers can be registered here by name.
18
+ if (name === "coderabbit") {
19
+ return new coderabbit_js_1.CodeRabbitQcProvider(config);
20
+ }
21
+ return undefined;
22
+ }
12
23
  function createDefaultQcRegistry() {
13
24
  const registry = new provider_js_1.QcProviderRegistry();
14
25
  registry.register(new coderabbit_js_1.CodeRabbitQcProvider());
15
26
  return registry;
16
27
  }
28
+ /**
29
+ * Build a registry from configured QC providers.
30
+ *
31
+ * Disabled providers (enabled: false) are skipped. Unknown provider names are
32
+ * skipped; callers should validate config before dispatch and may report
33
+ * unknown providers as blockers at runtime.
34
+ */
35
+ function createQcRegistry(config) {
36
+ const registry = new provider_js_1.QcProviderRegistry();
37
+ if (!config?.enabled) {
38
+ return registry;
39
+ }
40
+ for (const [name, providerConfig] of Object.entries(config.providers ?? {})) {
41
+ if (providerConfig.enabled === false)
42
+ continue;
43
+ const provider = createProvider(name, providerConfig);
44
+ if (provider) {
45
+ registry.register(provider);
46
+ }
47
+ }
48
+ return registry;
49
+ }