@lsctech/polaris 0.4.9 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/autoresearch/gates.js +47 -0
  2. package/dist/autoresearch/proposal.js +183 -1
  3. package/dist/autoresearch/score.js +276 -0
  4. package/dist/cli/init.js +17 -0
  5. package/dist/cluster-state/store.js +70 -1
  6. package/dist/config/defaults.js +25 -0
  7. package/dist/config/validator.js +390 -0
  8. package/dist/finalize/artifact-policy.js +3 -1
  9. package/dist/finalize/index.js +73 -17
  10. package/dist/finalize/linear.js +45 -0
  11. package/dist/finalize/run-report.js +68 -12
  12. package/dist/loop/adapters/agent-subtask.js +19 -0
  13. package/dist/loop/adapters/cli-subtask-bridge.js +0 -1
  14. package/dist/loop/adapters/terminal-cli.js +194 -14
  15. package/dist/loop/checkpoint.js +40 -0
  16. package/dist/loop/dispatch-boundary.js +29 -0
  17. package/dist/loop/dispatch.js +193 -22
  18. package/dist/loop/orphan-recovery.js +56 -0
  19. package/dist/loop/parent.js +197 -22
  20. package/dist/loop/router/engine.js +229 -0
  21. package/dist/loop/router/index.js +5 -0
  22. package/dist/loop/router/types.js +2 -0
  23. package/dist/loop/worker-packet.js +16 -0
  24. package/dist/qc/artifacts.js +117 -0
  25. package/dist/qc/attribution.js +266 -0
  26. package/dist/qc/autofix.js +77 -0
  27. package/dist/qc/index.js +30 -0
  28. package/dist/qc/orchestration.js +150 -0
  29. package/dist/qc/policy.js +70 -0
  30. package/dist/qc/provider.js +28 -0
  31. package/dist/qc/providers/coderabbit.js +214 -0
  32. package/dist/qc/providers/index.js +5 -0
  33. package/dist/qc/registry.js +16 -0
  34. package/dist/qc/routing.js +55 -0
  35. package/dist/qc/runner.js +137 -0
  36. package/dist/qc/schemas.js +110 -0
  37. package/dist/qc/security-category.js +11 -0
  38. package/dist/qc/severity.js +78 -0
  39. package/dist/qc/triggers.js +92 -0
  40. package/dist/qc/types.js +9 -0
  41. package/dist/runtime/scheduling/child-selector.js +81 -0
  42. package/package.json +1 -1
@@ -195,6 +195,10 @@ function compileStartupPacket(input) {
195
195
  prompt_mode: 'full',
196
196
  prompt_metrics: { mode: 'full', char_count: 0, estimated_tokens: 0 },
197
197
  role_context: roleContextForWorkerRole('startup'),
198
+ routing_context: {
199
+ task_type: "startup",
200
+ required_capabilities: ["orchestration"],
201
+ },
198
202
  result_file_contract: {
199
203
  result_file: input.resultFile,
200
204
  result_required_fields: Object.fromEntries([
@@ -287,6 +291,10 @@ function compileImplPacket(input) {
287
291
  prompt_mode: promptMode,
288
292
  prompt_metrics: promptResult.metrics,
289
293
  role_context: roleContextForWorkerRole('impl'),
294
+ routing_context: {
295
+ task_type: "impl",
296
+ required_capabilities: ["implementation"],
297
+ },
290
298
  prohibited_write_paths: exports.WORKER_PROHIBITED_WRITE_PATHS,
291
299
  result_file_contract: {
292
300
  result_file: input.resultFile,
@@ -341,6 +349,10 @@ function compileFinalizePacket(input) {
341
349
  prompt_mode: 'full',
342
350
  prompt_metrics: { mode: 'full', char_count: 0, estimated_tokens: 0 },
343
351
  role_context: roleContextForWorkerRole('finalize'),
352
+ routing_context: {
353
+ task_type: "finalize",
354
+ required_capabilities: ["finalization"],
355
+ },
344
356
  result_file_contract: {
345
357
  result_file: input.resultFile,
346
358
  result_required_fields: Object.fromEntries([
@@ -390,6 +402,10 @@ function compilePreflightPacket(input) {
390
402
  prompt_mode: 'full',
391
403
  prompt_metrics: { mode: 'full', char_count: 0, estimated_tokens: 0 },
392
404
  role_context: roleContextForWorkerRole('preflight'),
405
+ routing_context: {
406
+ task_type: "startup",
407
+ required_capabilities: ["orchestration"],
408
+ },
393
409
  result_file_contract: {
394
410
  result_file: input.resultFile,
395
411
  result_required_fields: Object.fromEntries([
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getQcArtifactDir = getQcArtifactDir;
37
+ exports.writeQcArtifact = writeQcArtifact;
38
+ exports.readQcArtifact = readQcArtifact;
39
+ exports.listQcArtifactIds = listQcArtifactIds;
40
+ const node_fs_1 = require("node:fs");
41
+ const path = __importStar(require("node:path"));
42
+ const schemas_js_1 = require("./schemas.js");
43
+ /**
44
+ * Returns the cluster-scoped QC evidence directory.
45
+ * Artifacts live under `.polaris/clusters/<cluster-id>/qc/` so that finalize's
46
+ * artifact policy can promote them as durable evidence.
47
+ */
48
+ function getQcArtifactDir(clusterId, repoRoot) {
49
+ return path.join(repoRoot || process.cwd(), ".polaris", "clusters", clusterId, "qc");
50
+ }
51
+ function getArtifactPath(clusterId, qcRunId, repoRoot) {
52
+ return path.join(getQcArtifactDir(clusterId, repoRoot), `${qcRunId}.json`);
53
+ }
54
+ /**
55
+ * Write a normalized QC result under the active cluster's evidence surface.
56
+ * The file is written atomically via a temp file + rename.
57
+ * Returns the absolute artifact path.
58
+ */
59
+ function writeQcArtifact(clusterId, result, repoRoot) {
60
+ const artifactDir = getQcArtifactDir(clusterId, repoRoot);
61
+ (0, node_fs_1.mkdirSync)(artifactDir, { recursive: true });
62
+ const artifactPath = getArtifactPath(clusterId, result.qcRunId, repoRoot);
63
+ const tempPath = `${artifactPath}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
64
+ try {
65
+ (0, node_fs_1.writeFileSync)(tempPath, JSON.stringify(result, null, 2), "utf-8");
66
+ (0, node_fs_1.renameSync)(tempPath, artifactPath);
67
+ }
68
+ catch (error) {
69
+ try {
70
+ (0, node_fs_1.unlinkSync)(tempPath);
71
+ }
72
+ catch {
73
+ // Ignore cleanup failure.
74
+ }
75
+ throw error;
76
+ }
77
+ return artifactPath;
78
+ }
79
+ /**
80
+ * Read a QC artifact by its run id.
81
+ * Returns null if the file does not exist or fails validation.
82
+ */
83
+ function readQcArtifact(clusterId, qcRunId, repoRoot) {
84
+ const artifactPath = getArtifactPath(clusterId, qcRunId, repoRoot);
85
+ try {
86
+ const data = (0, node_fs_1.readFileSync)(artifactPath, "utf-8");
87
+ const parsed = JSON.parse(data);
88
+ const validation = (0, schemas_js_1.validateQcResult)(parsed);
89
+ if (!validation.success) {
90
+ return null;
91
+ }
92
+ return validation.result;
93
+ }
94
+ catch (error) {
95
+ if (error.code === "ENOENT") {
96
+ return null;
97
+ }
98
+ throw error;
99
+ }
100
+ }
101
+ /**
102
+ * List persisted QC run ids for a cluster.
103
+ */
104
+ function listQcArtifactIds(clusterId, repoRoot) {
105
+ const artifactDir = getQcArtifactDir(clusterId, repoRoot);
106
+ try {
107
+ return (0, node_fs_1.readdirSync)(artifactDir, "utf-8")
108
+ .filter((name) => name.endsWith(".json"))
109
+ .map((name) => name.slice(0, -5));
110
+ }
111
+ catch (error) {
112
+ if (error.code === "ENOENT") {
113
+ return [];
114
+ }
115
+ throw error;
116
+ }
117
+ }
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ /**
3
+ * QC attribution resolver.
4
+ *
5
+ * Correlates normalized QC findings to the child/worker most likely responsible
6
+ * for the changed code. It uses durable evidence only: result packet pointers,
7
+ * child commits, dispatch records, optional PR review metadata, and (as a
8
+ * fallback) git history.
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.resolveAttribution = resolveAttribution;
45
+ exports.buildChangedFileOwnership = buildChangedFileOwnership;
46
+ exports.resolveAttributionWithOwnership = resolveAttributionWithOwnership;
47
+ const path = __importStar(require("node:path"));
48
+ const node_child_process_1 = require("node:child_process");
49
+ function normalizeFilePath(filePath, repoRoot) {
50
+ if (!filePath)
51
+ return null;
52
+ const absolute = path.isAbsolute(filePath) ? filePath : path.join(repoRoot ?? process.cwd(), filePath);
53
+ return path.relative(repoRoot ?? process.cwd(), absolute).replace(/\\/g, "/");
54
+ }
55
+ function collectResultChangedFiles(result) {
56
+ if (result.changed_files && result.changed_files.length > 0) {
57
+ return result.changed_files;
58
+ }
59
+ const fromData = result.result_data?.changed_files;
60
+ if (Array.isArray(fromData) && fromData.every((f) => typeof f === "string")) {
61
+ return fromData;
62
+ }
63
+ return [];
64
+ }
65
+ function collectChangedFilesByChild(context) {
66
+ if (context.changedFilesByChild) {
67
+ return context.changedFilesByChild;
68
+ }
69
+ const ownership = {};
70
+ if (context.completedResults) {
71
+ for (const [childId, result] of Object.entries(context.completedResults)) {
72
+ const files = collectResultChangedFiles(result);
73
+ if (files.length > 0) {
74
+ ownership[childId] = files.map((f) => normalizeFilePath(f, context.repoRoot)).filter((f) => f !== null);
75
+ }
76
+ }
77
+ }
78
+ if (Object.keys(ownership).length > 0) {
79
+ return ownership;
80
+ }
81
+ if (context.clusterState?.commits) {
82
+ const base = context.baseBranch ?? "main";
83
+ for (const [childId, commitSha] of Object.entries(context.clusterState.commits)) {
84
+ const files = diffFilesForCommit(context.repoRoot, base, commitSha);
85
+ if (files.length > 0) {
86
+ ownership[childId] = files.map((f) => normalizeFilePath(f, context.repoRoot)).filter((f) => f !== null);
87
+ }
88
+ }
89
+ }
90
+ return ownership;
91
+ }
92
+ function diffFilesForCommit(repoRoot, baseBranch, commitSha) {
93
+ if (!repoRoot)
94
+ return [];
95
+ try {
96
+ const output = (0, node_child_process_1.execFileSync)("git", ["diff", "--name-only", `${baseBranch}..${commitSha}`], { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
97
+ return output
98
+ .trim()
99
+ .split("\n")
100
+ .filter((line) => line.length > 0);
101
+ }
102
+ catch {
103
+ return [];
104
+ }
105
+ }
106
+ function ownersForFile(filePath, ownership) {
107
+ const owners = [];
108
+ for (const [childId, files] of Object.entries(ownership)) {
109
+ if (files.includes(filePath)) {
110
+ owners.push(childId);
111
+ }
112
+ }
113
+ return owners;
114
+ }
115
+ function resolveCommitSha(finding) {
116
+ return finding.commitSha ?? finding.attribution?.commitSha;
117
+ }
118
+ function isKnownChildCommit(commitSha, context) {
119
+ if (!commitSha)
120
+ return false;
121
+ const commits = Object.values(context.clusterState?.commits ?? {});
122
+ if (commits.includes(commitSha))
123
+ return true;
124
+ if (context.completedResults) {
125
+ return Object.values(context.completedResults).some((r) => r.commit === commitSha);
126
+ }
127
+ return false;
128
+ }
129
+ function isPreExistingFile(filePath, context) {
130
+ if (!context.repoRoot)
131
+ return false;
132
+ try {
133
+ (0, node_child_process_1.execFileSync)("git", ["cat-file", "-e", `HEAD:${filePath}`], {
134
+ cwd: context.repoRoot,
135
+ stdio: ["pipe", "pipe", "pipe"],
136
+ });
137
+ return true;
138
+ }
139
+ catch {
140
+ return false;
141
+ }
142
+ }
143
+ /**
144
+ * Resolve the most likely attribution for a finding.
145
+ *
146
+ * The resolver never fabricates confidence. When evidence is weak it falls
147
+ * back to explicitly named reason codes so downstream routing can treat the
148
+ * finding conservatively.
149
+ */
150
+ function resolveAttribution(finding, context) {
151
+ const threshold = context.providerConfidenceThreshold ?? 0.5;
152
+ const filePath = normalizeFilePath(finding.filePath, context.repoRoot);
153
+ const ownership = filePath ? collectChangedFilesByChild(context) : {};
154
+ const owners = filePath ? ownersForFile(filePath, ownership) : [];
155
+ const commitSha = resolveCommitSha(finding);
156
+ if (finding.confidence !== undefined && finding.confidence < threshold) {
157
+ const childId = owners.length === 1 ? owners[0] : undefined;
158
+ return {
159
+ confidence: "low",
160
+ reason: "provider-uncertain",
161
+ ...(childId ? { childId } : {}),
162
+ filePath: filePath ?? undefined,
163
+ commitSha,
164
+ };
165
+ }
166
+ if (!filePath) {
167
+ return {
168
+ confidence: "unattributed",
169
+ reason: "provider-uncertain",
170
+ commitSha,
171
+ };
172
+ }
173
+ const commitKnown = isKnownChildCommit(commitSha, context);
174
+ const commitMatchesOwner = commitSha && owners.some((childId) => {
175
+ const childCommit = context.clusterState?.commits?.[childId] ?? context.completedResults?.[childId]?.commit;
176
+ return childCommit === commitSha;
177
+ });
178
+ if (owners.length === 1) {
179
+ const childId = owners[0];
180
+ if (commitSha && !commitMatchesOwner) {
181
+ return {
182
+ confidence: "low",
183
+ reason: "provider-uncertain",
184
+ childId,
185
+ filePath,
186
+ commitSha,
187
+ };
188
+ }
189
+ if (commitMatchesOwner) {
190
+ return {
191
+ confidence: "high",
192
+ reason: "commit-line-match",
193
+ childId,
194
+ filePath,
195
+ commitSha,
196
+ };
197
+ }
198
+ return {
199
+ confidence: "high",
200
+ reason: "changed-file-owner",
201
+ childId,
202
+ filePath,
203
+ commitSha,
204
+ };
205
+ }
206
+ if (owners.length > 1) {
207
+ return {
208
+ confidence: "low",
209
+ reason: "shared-file",
210
+ childId: owners[0],
211
+ filePath,
212
+ commitSha,
213
+ };
214
+ }
215
+ // File is not owned by any child. Use PR review metadata as a weak signal.
216
+ const reviewerFiles = Object.values(context.prReviewMetadata?.changedFilesByReviewer ?? {}).flat();
217
+ if (reviewerFiles.includes(filePath)) {
218
+ return {
219
+ confidence: "low",
220
+ reason: "child-scope-match",
221
+ filePath,
222
+ commitSha,
223
+ };
224
+ }
225
+ if (commitSha) {
226
+ if (commitKnown) {
227
+ return {
228
+ confidence: "medium",
229
+ reason: "commit-line-match",
230
+ filePath,
231
+ commitSha,
232
+ };
233
+ }
234
+ return {
235
+ confidence: "low",
236
+ reason: "provider-uncertain",
237
+ filePath,
238
+ commitSha,
239
+ };
240
+ }
241
+ if (isPreExistingFile(filePath, context)) {
242
+ return {
243
+ confidence: "low",
244
+ reason: "pre-existing",
245
+ filePath,
246
+ };
247
+ }
248
+ return {
249
+ confidence: "unattributed",
250
+ reason: "unattributed",
251
+ filePath,
252
+ };
253
+ }
254
+ /**
255
+ * Expose the ownership map built by the resolver. Useful for tests and for
256
+ * callers that want to cache the map across many findings.
257
+ */
258
+ function buildChangedFileOwnership(context) {
259
+ return collectChangedFilesByChild(context);
260
+ }
261
+ /**
262
+ * Re-resolve attribution using a pre-built ownership map.
263
+ */
264
+ function resolveAttributionWithOwnership(finding, context, ownership) {
265
+ return resolveAttribution(finding, { ...context, changedFilesByChild: ownership });
266
+ }
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * QC auto-fix gating.
4
+ *
5
+ * Determines whether a normalized finding is allowed to attempt an unattended
6
+ * auto-fix. Gating is intentionally conservative: auto-fix is allowed only for
7
+ * explicitly eligible providers, low/medium severities, non-security categories,
8
+ * clean branches, and fix modes that are known to be safe.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.QC_SAFE_FIX_MODES = void 0;
12
+ exports.isAutofixEligible = isAutofixEligible;
13
+ const security_category_js_1 = require("./security-category.js");
14
+ const severity_js_1 = require("./severity.js");
15
+ /** Fix modes considered safe for unattended application. */
16
+ exports.QC_SAFE_FIX_MODES = [
17
+ "refactor",
18
+ "style",
19
+ "format",
20
+ "typo",
21
+ "lint-fix",
22
+ "apply-suggestion",
23
+ "safe",
24
+ ];
25
+ function providerConfig(config, provider) {
26
+ if (!provider)
27
+ return undefined;
28
+ return config.providers?.[provider];
29
+ }
30
+ function isSafeFixMode(suggestedAction) {
31
+ if (!suggestedAction?.trim())
32
+ return false;
33
+ const normalized = suggestedAction.trim().toLowerCase();
34
+ if (exports.QC_SAFE_FIX_MODES.includes(normalized))
35
+ return true;
36
+ // A provider may describe a safe fix with a sentence that starts with a safe mode keyword.
37
+ return exports.QC_SAFE_FIX_MODES.some((mode) => normalized.startsWith(mode));
38
+ }
39
+ /**
40
+ * Check whether a finding may attempt an unattended auto-fix.
41
+ *
42
+ * Returns an object so callers can log why a fix was blocked.
43
+ */
44
+ function isAutofixEligible(finding, config, context = {}) {
45
+ const globalPolicy = config.autoFix ?? "disabled";
46
+ if (globalPolicy === "disabled") {
47
+ return { eligible: false, reason: "auto-fix disabled globally" };
48
+ }
49
+ const routePolicy = context.routeName ? config.routes?.[context.routeName]?.autoFix : undefined;
50
+ if (routePolicy === "disabled") {
51
+ return { eligible: false, reason: "auto-fix disabled for route" };
52
+ }
53
+ const pCfg = providerConfig(config, context.provider);
54
+ if (!pCfg?.autoFixEligible) {
55
+ return { eligible: false, reason: "provider not auto-fix eligible" };
56
+ }
57
+ const maxAutofixSeverity = "medium";
58
+ if ((0, severity_js_1.compareSeverity)(finding.severity, maxAutofixSeverity) > 0) {
59
+ return { eligible: false, reason: `severity ${finding.severity} exceeds auto-fix threshold` };
60
+ }
61
+ if ((0, security_category_js_1.isSecurityCategory)(finding.category)) {
62
+ return { eligible: false, reason: "security-sensitive finding" };
63
+ }
64
+ if (context.branchDirty) {
65
+ return { eligible: false, reason: "branch has uncommitted changes" };
66
+ }
67
+ if (!finding.fixAvailable) {
68
+ return { eligible: false, reason: "no fix available" };
69
+ }
70
+ if (!isSafeFixMode(finding.suggestedAction)) {
71
+ return { eligible: false, reason: `fix mode "${finding.suggestedAction}" is not in safe list` };
72
+ }
73
+ if (globalPolicy === "dry-run") {
74
+ return { eligible: true, reason: "dry-run mode: fix may be generated but not applied" };
75
+ }
76
+ return { eligible: true, reason: "auto-fix eligible" };
77
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types.js"), exports);
18
+ __exportStar(require("./provider.js"), exports);
19
+ __exportStar(require("./providers/index.js"), exports);
20
+ __exportStar(require("./severity.js"), exports);
21
+ __exportStar(require("./schemas.js"), exports);
22
+ __exportStar(require("./artifacts.js"), exports);
23
+ __exportStar(require("./triggers.js"), exports);
24
+ __exportStar(require("./policy.js"), exports);
25
+ __exportStar(require("./runner.js"), exports);
26
+ __exportStar(require("./orchestration.js"), exports);
27
+ __exportStar(require("./registry.js"), exports);
28
+ __exportStar(require("./attribution.js"), exports);
29
+ __exportStar(require("./autofix.js"), exports);
30
+ __exportStar(require("./routing.js"), exports);
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ /**
3
+ * QC trigger orchestration.
4
+ *
5
+ * Wires QC providers into the Polaris lifecycle at:
6
+ * - completed-cluster (after all children done, before final delivery)
7
+ * - pr (after a PR is created, for providers that require a PR URL)
8
+ * - child (selected at dispatch time for high-risk scopes)
9
+ *
10
+ * The orchestrator is passive/dry-run by default; only configured providers
11
+ * are invoked, and only blocking policy stops the loop/finalize flow.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.runQcAtTrigger = runQcAtTrigger;
15
+ const store_js_1 = require("../cluster-state/store.js");
16
+ const policy_js_1 = require("./policy.js");
17
+ const runner_js_1 = require("./runner.js");
18
+ const triggers_js_1 = require("./triggers.js");
19
+ const attribution_js_1 = require("./attribution.js");
20
+ const autofix_js_1 = require("./autofix.js");
21
+ const routing_js_1 = require("./routing.js");
22
+ function buildAttributionContext(options) {
23
+ const { repoRoot, clusterId, branch, state } = options;
24
+ const dispatchRecords = {};
25
+ if (state?.open_children_meta) {
26
+ for (const [childId, meta] of Object.entries(state.open_children_meta)) {
27
+ if (meta?.dispatch_record) {
28
+ dispatchRecords[childId] = meta.dispatch_record;
29
+ }
30
+ }
31
+ }
32
+ return {
33
+ repoRoot,
34
+ baseBranch: branch ?? state?.branch ?? "main",
35
+ completedResults: state?.completed_children_results,
36
+ dispatchRecords,
37
+ clusterState: repoRoot ? ((0, store_js_1.readClusterStateSync)(clusterId, repoRoot) ?? undefined) : undefined,
38
+ };
39
+ }
40
+ function applyAttributionAndRouting(result, config, context, ownership, routeName) {
41
+ const updatedFindings = result.findings.map((finding) => {
42
+ const attribution = (0, attribution_js_1.resolveAttributionWithOwnership)(finding, context, ownership);
43
+ const autofix = (0, autofix_js_1.isAutofixEligible)(finding, config, {
44
+ provider: result.provider,
45
+ routeName,
46
+ });
47
+ const routing = (0, routing_js_1.decideRepairRouting)({ ...finding, attribution, autofixEligible: autofix.eligible }, config, autofix.eligible, { routeName });
48
+ return {
49
+ ...finding,
50
+ attribution,
51
+ autofixEligible: autofix.eligible,
52
+ routingDecision: routing,
53
+ };
54
+ });
55
+ const updatedResult = {
56
+ ...result,
57
+ findings: updatedFindings,
58
+ policyDecision: (0, policy_js_1.computeQcPolicyDecision)({ ...result, findings: updatedFindings }, config),
59
+ };
60
+ return updatedResult;
61
+ }
62
+ /**
63
+ * Run QC for the given lifecycle trigger when configured.
64
+ *
65
+ * Returns a no-op "pass" result when QC is disabled, no providers are configured
66
+ * for the trigger, or all providers run successfully without findings that
67
+ * match a blocking/follow-up policy.
68
+ */
69
+ async function runQcAtTrigger(options) {
70
+ const { config, registry, trigger, prUrl, repoRoot, runId, clusterId, branch, telemetryFile, timeoutMs, state, routeName, } = options;
71
+ if (!config.enabled) {
72
+ return {
73
+ trigger,
74
+ results: [],
75
+ action: "pass",
76
+ summary: "QC disabled by configuration",
77
+ };
78
+ }
79
+ const providers = (0, triggers_js_1.activeProvidersForTrigger)(config, trigger);
80
+ if (providers.length === 0) {
81
+ return {
82
+ trigger,
83
+ results: [],
84
+ action: "pass",
85
+ summary: `No QC providers configured for trigger "${trigger}"`,
86
+ };
87
+ }
88
+ const attributionContext = buildAttributionContext(options);
89
+ const ownership = (0, attribution_js_1.buildChangedFileOwnership)(attributionContext);
90
+ const results = [];
91
+ const errors = [];
92
+ for (const [name, providerConfig] of providers) {
93
+ const provider = registry.get(name);
94
+ if (!provider) {
95
+ errors.push(`Unknown QC provider "${name}"`);
96
+ continue;
97
+ }
98
+ const scope = {
99
+ clusterId,
100
+ runId,
101
+ ...(trigger === "pr" ? { prUrl } : { branch }),
102
+ };
103
+ try {
104
+ const rawResult = await (0, runner_js_1.executeQcProvider)(provider, scope, {
105
+ repoRoot,
106
+ runId,
107
+ clusterId,
108
+ branch,
109
+ telemetryFile,
110
+ timeoutMs,
111
+ });
112
+ const result = applyAttributionAndRouting(rawResult, config, attributionContext, ownership, routeName);
113
+ try {
114
+ await (0, store_js_1.recordQcRun)(clusterId, result, repoRoot);
115
+ }
116
+ catch (err) {
117
+ errors.push(`Failed to record QC run ${result.qcRunId}: ${err instanceof Error ? err.message : String(err)}`);
118
+ }
119
+ results.push(result);
120
+ }
121
+ catch (err) {
122
+ errors.push(`QC provider "${name}" execution error: ${err instanceof Error ? err.message : String(err)}`);
123
+ }
124
+ }
125
+ let action = "pass";
126
+ for (const result of results) {
127
+ const a = (0, policy_js_1.decideQcAction)(result, config);
128
+ if (a === "block") {
129
+ action = "block";
130
+ break;
131
+ }
132
+ if (a === "follow-up" && action === "pass") {
133
+ action = "follow-up";
134
+ }
135
+ }
136
+ const successfulRuns = results.filter((result) => result.status !== "failed");
137
+ if (action === "pass" && successfulRuns.length === 0 && (results.length > 0 || errors.length > 0)) {
138
+ action = "block";
139
+ }
140
+ const summaryParts = results.map((r) => `${r.provider}: ${r.status} (${r.findings.length} findings)`);
141
+ if (errors.length > 0) {
142
+ summaryParts.push(`errors: ${errors.join(", ")}`);
143
+ }
144
+ return {
145
+ trigger,
146
+ results,
147
+ action,
148
+ summary: summaryParts.join("; ") || "No QC providers produced results",
149
+ };
150
+ }