@revisium/orchestrator 0.0.0
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/LICENSE +21 -0
- package/README.md +20 -0
- package/bin/revo.js +2 -0
- package/control-plane/bootstrap.config.json +931 -0
- package/dist/app.module.js +28 -0
- package/dist/app.module.js.map +1 -0
- package/dist/cli/commands/bootstrap.js +39 -0
- package/dist/cli/commands/bootstrap.js.map +1 -0
- package/dist/cli/commands/dev.js +143 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/inbox.js +288 -0
- package/dist/cli/commands/inbox.js.map +1 -0
- package/dist/cli/commands/mcp.js +16 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/playbook.js +74 -0
- package/dist/cli/commands/playbook.js.map +1 -0
- package/dist/cli/commands/poll-workflow-state.js +144 -0
- package/dist/cli/commands/poll-workflow-state.js.map +1 -0
- package/dist/cli/commands/revisium-context.js +23 -0
- package/dist/cli/commands/revisium-context.js.map +1 -0
- package/dist/cli/commands/revisium-helpers.js +61 -0
- package/dist/cli/commands/revisium-helpers.js.map +1 -0
- package/dist/cli/commands/revisium.js +89 -0
- package/dist/cli/commands/revisium.js.map +1 -0
- package/dist/cli/commands/run.js +426 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/work.js +149 -0
- package/dist/cli/commands/work.js.map +1 -0
- package/dist/cli/config.js +2 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.js +32 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/live-guard.js +49 -0
- package/dist/cli/live-guard.js.map +1 -0
- package/dist/cli/needs-host.js +78 -0
- package/dist/cli/needs-host.js.map +1 -0
- package/dist/cli/program.js +37 -0
- package/dist/cli/program.js.map +1 -0
- package/dist/config.js +102 -0
- package/dist/config.js.map +1 -0
- package/dist/control-plane/client-transport.js +146 -0
- package/dist/control-plane/client-transport.js.map +1 -0
- package/dist/control-plane/data-access.js +99 -0
- package/dist/control-plane/data-access.js.map +1 -0
- package/dist/control-plane/definitions.js +120 -0
- package/dist/control-plane/definitions.js.map +1 -0
- package/dist/control-plane/errors.js +13 -0
- package/dist/control-plane/errors.js.map +1 -0
- package/dist/control-plane/inbox.js +261 -0
- package/dist/control-plane/inbox.js.map +1 -0
- package/dist/control-plane/index.js +6 -0
- package/dist/control-plane/index.js.map +1 -0
- package/dist/control-plane/json-fields.js +68 -0
- package/dist/control-plane/json-fields.js.map +1 -0
- package/dist/control-plane/resolve-cwd.js +75 -0
- package/dist/control-plane/resolve-cwd.js.map +1 -0
- package/dist/control-plane/steps.js +381 -0
- package/dist/control-plane/steps.js.map +1 -0
- package/dist/control-plane/tables.js +13 -0
- package/dist/control-plane/tables.js.map +1 -0
- package/dist/control-plane/versioned-meaning.js +53 -0
- package/dist/control-plane/versioned-meaning.js.map +1 -0
- package/dist/engine/dbos.module.js +18 -0
- package/dist/engine/dbos.module.js.map +1 -0
- package/dist/engine/dbos.service.js +296 -0
- package/dist/engine/dbos.service.js.map +1 -0
- package/dist/engine/ensure-postgres.js +80 -0
- package/dist/engine/ensure-postgres.js.map +1 -0
- package/dist/engine/types.js +2 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/host/ensure-revisium.js +269 -0
- package/dist/host/ensure-revisium.js.map +1 -0
- package/dist/host/host.lifecycle.js +75 -0
- package/dist/host/host.lifecycle.js.map +1 -0
- package/dist/mcp/mcp-capabilities.js +47 -0
- package/dist/mcp/mcp-capabilities.js.map +1 -0
- package/dist/mcp/mcp-facade.service.js +130 -0
- package/dist/mcp/mcp-facade.service.js.map +1 -0
- package/dist/mcp/mcp-stdio.service.js +48 -0
- package/dist/mcp/mcp-stdio.service.js.map +1 -0
- package/dist/mcp/mcp-tools.js +264 -0
- package/dist/mcp/mcp-tools.js.map +1 -0
- package/dist/mcp/mcp.module.js +21 -0
- package/dist/mcp/mcp.module.js.map +1 -0
- package/dist/package-info.js +10 -0
- package/dist/package-info.js.map +1 -0
- package/dist/pipeline/await-human.js +62 -0
- package/dist/pipeline/await-human.js.map +1 -0
- package/dist/pipeline/develop-task.workflow.js +497 -0
- package/dist/pipeline/develop-task.workflow.js.map +1 -0
- package/dist/pipeline/pipeline.module.js +34 -0
- package/dist/pipeline/pipeline.module.js.map +1 -0
- package/dist/playbook/catalog-loader.js +145 -0
- package/dist/playbook/catalog-loader.js.map +1 -0
- package/dist/playbook/errors.js +14 -0
- package/dist/playbook/errors.js.map +1 -0
- package/dist/playbook/import-mapper.js +186 -0
- package/dist/playbook/import-mapper.js.map +1 -0
- package/dist/playbook/manifest.js +60 -0
- package/dist/playbook/manifest.js.map +1 -0
- package/dist/playbook/playbook-installer.js +51 -0
- package/dist/playbook/playbook-installer.js.map +1 -0
- package/dist/playbook/prompt-composer.js +34 -0
- package/dist/playbook/prompt-composer.js.map +1 -0
- package/dist/playbook/source-resolver.js +105 -0
- package/dist/playbook/source-resolver.js.map +1 -0
- package/dist/poller/pr-readiness-core.js +656 -0
- package/dist/poller/pr-readiness-core.js.map +1 -0
- package/dist/poller/pr-readiness.js +91 -0
- package/dist/poller/pr-readiness.js.map +1 -0
- package/dist/revisium/inbox.service.js +67 -0
- package/dist/revisium/inbox.service.js.map +1 -0
- package/dist/revisium/playbooks.service.js +99 -0
- package/dist/revisium/playbooks.service.js.map +1 -0
- package/dist/revisium/revisium.module.js +55 -0
- package/dist/revisium/revisium.module.js.map +1 -0
- package/dist/revisium/roles.service.js +79 -0
- package/dist/revisium/roles.service.js.map +1 -0
- package/dist/revisium/run.service.js +182 -0
- package/dist/revisium/run.service.js.map +1 -0
- package/dist/revisium/tokens.js +7 -0
- package/dist/revisium/tokens.js.map +1 -0
- package/dist/run/append-event.js +112 -0
- package/dist/run/append-event.js.map +1 -0
- package/dist/run/cancel-run.js +47 -0
- package/dist/run/cancel-run.js.map +1 -0
- package/dist/run/create-run.js +208 -0
- package/dist/run/create-run.js.map +1 -0
- package/dist/run/fail-run.js +20 -0
- package/dist/run/fail-run.js.map +1 -0
- package/dist/run/inspect-run.js +287 -0
- package/dist/run/inspect-run.js.map +1 -0
- package/dist/run/terminal-run-status.js +45 -0
- package/dist/run/terminal-run-status.js.map +1 -0
- package/dist/runners/claude-code.service.js +48 -0
- package/dist/runners/claude-code.service.js.map +1 -0
- package/dist/runners/gh-identity.js +108 -0
- package/dist/runners/gh-identity.js.map +1 -0
- package/dist/runners/integrator.js +417 -0
- package/dist/runners/integrator.js.map +1 -0
- package/dist/runners/runner.module.js +40 -0
- package/dist/runners/runner.module.js.map +1 -0
- package/dist/runners/tokens.js +7 -0
- package/dist/runners/tokens.js.map +1 -0
- package/dist/task-control-plane/pr-readiness.service.js +33 -0
- package/dist/task-control-plane/pr-readiness.service.js.map +1 -0
- package/dist/task-control-plane/task-control-plane-api.service.js +440 -0
- package/dist/task-control-plane/task-control-plane-api.service.js.map +1 -0
- package/dist/task-control-plane/task-control-plane.module.js +23 -0
- package/dist/task-control-plane/task-control-plane.module.js.map +1 -0
- package/dist/worker/build-context.js +39 -0
- package/dist/worker/build-context.js.map +1 -0
- package/dist/worker/claude-code-runner.js +135 -0
- package/dist/worker/claude-code-runner.js.map +1 -0
- package/dist/worker/git-worktree-manager.js +84 -0
- package/dist/worker/git-worktree-manager.js.map +1 -0
- package/dist/worker/loop.js +122 -0
- package/dist/worker/loop.js.map +1 -0
- package/dist/worker/process-executor.js +62 -0
- package/dist/worker/process-executor.js.map +1 -0
- package/dist/worker/result-envelope.js +134 -0
- package/dist/worker/result-envelope.js.map +1 -0
- package/dist/worker/runner-dispatch.js +20 -0
- package/dist/worker/runner-dispatch.js.map +1 -0
- package/dist/worker/runner.js +2 -0
- package/dist/worker/runner.js.map +1 -0
- package/dist/worker/script-runner.js +28 -0
- package/dist/worker/script-runner.js.map +1 -0
- package/dist/worker/stub-runner.js +59 -0
- package/dist/worker/stub-runner.js.map +1 -0
- package/dist/worker/worker-id.js +18 -0
- package/dist/worker/worker-id.js.map +1 -0
- package/dist/worker/worktree-manager.js +7 -0
- package/dist/worker/worktree-manager.js.map +1 -0
- package/package.json +66 -0
- package/revisium.config.json +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-resolver.js","sourceRoot":"","sources":["../../src/playbook/source-resolver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { accessSync, constants } from 'node:fs';
|
|
3
|
+
const GH_EXECUTABLE_CANDIDATES = ['/opt/homebrew/bin/gh', '/usr/local/bin/gh', '/usr/bin/gh'];
|
|
4
|
+
function resolveGhExecutable() {
|
|
5
|
+
for (const candidate of GH_EXECUTABLE_CANDIDATES) {
|
|
6
|
+
try {
|
|
7
|
+
accessSync(candidate, constants.X_OK);
|
|
8
|
+
return candidate;
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Try the next fixed system location.
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
throw new Error(`gh executable not found in fixed locations: ${GH_EXECUTABLE_CANDIDATES.join(', ')}`);
|
|
15
|
+
}
|
|
16
|
+
export function defaultExecGh(args) {
|
|
17
|
+
return execFileSync(resolveGhExecutable(), args, { encoding: 'utf8', timeout: 60_000, maxBuffer: 10 * 1024 * 1024 });
|
|
18
|
+
}
|
|
19
|
+
function pickLine(top, tRange) {
|
|
20
|
+
if (typeof top === 'number')
|
|
21
|
+
return top;
|
|
22
|
+
const s = tRange?.['startLine'];
|
|
23
|
+
return typeof s === 'number' ? s : undefined;
|
|
24
|
+
}
|
|
25
|
+
function asStr(v, fallback = '') {
|
|
26
|
+
return typeof v === 'string' ? v : fallback;
|
|
27
|
+
}
|
|
28
|
+
function mapSonarIssue(i) {
|
|
29
|
+
const tRange = i['textRange'];
|
|
30
|
+
const rule = asStr(i['rule']);
|
|
31
|
+
return {
|
|
32
|
+
severity: asStr(i['severity'], 'UNKNOWN'),
|
|
33
|
+
message: asStr(i['message']),
|
|
34
|
+
component: asStr(i['component']),
|
|
35
|
+
rule: rule || undefined,
|
|
36
|
+
line: pickLine(i['line'], tRange),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function mapSonarHotspot(h) {
|
|
40
|
+
const tRange = h['textRange'];
|
|
41
|
+
const securityCategory = asStr(h['securityCategory']);
|
|
42
|
+
const vulnerabilityProbability = asStr(h['vulnerabilityProbability']);
|
|
43
|
+
return {
|
|
44
|
+
message: asStr(h['message']),
|
|
45
|
+
component: asStr(h['component']),
|
|
46
|
+
line: pickLine(h['line'], tRange),
|
|
47
|
+
securityCategory: securityCategory || undefined,
|
|
48
|
+
vulnerabilityProbability: vulnerabilityProbability || undefined,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function defaultFetchSonar(projectKey, prNumber) {
|
|
52
|
+
const token = process.env['SONAR_TOKEN'];
|
|
53
|
+
if (!token)
|
|
54
|
+
return { issues: [], hotspots: [], unavailable: true };
|
|
55
|
+
const host = process.env['SONAR_HOST_URL'] ?? 'https://sonarcloud.io';
|
|
56
|
+
const auth = 'Basic ' + Buffer.from(`${token}:`).toString('base64');
|
|
57
|
+
try {
|
|
58
|
+
const issuesUrl = `${host}/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}` +
|
|
59
|
+
`&pullRequest=${prNumber}&statuses=OPEN,CONFIRMED,REOPENED&ps=500`;
|
|
60
|
+
const hotspotsUrl = `${host}/api/hotspots/search?projectKey=${encodeURIComponent(projectKey)}` +
|
|
61
|
+
`&pullRequest=${prNumber}&status=TO_REVIEW&ps=500`;
|
|
62
|
+
const [issuesRes, hotspotsRes] = await Promise.all([
|
|
63
|
+
fetch(issuesUrl, { headers: { Authorization: auth }, signal: AbortSignal.timeout(30_000) }),
|
|
64
|
+
fetch(hotspotsUrl, { headers: { Authorization: auth }, signal: AbortSignal.timeout(30_000) }),
|
|
65
|
+
]);
|
|
66
|
+
if (!issuesRes.ok || !hotspotsRes.ok) {
|
|
67
|
+
const failed = issuesRes.ok ? `hotspots ${hotspotsRes.status}` : `issues ${issuesRes.status}`;
|
|
68
|
+
console.warn(`[sonar] fetch degraded: ${failed}`);
|
|
69
|
+
return { issues: [], hotspots: [], unavailable: true };
|
|
70
|
+
}
|
|
71
|
+
const issuesJson = (await issuesRes.json());
|
|
72
|
+
const hotspotsJson = (await hotspotsRes.json());
|
|
73
|
+
return {
|
|
74
|
+
issues: (issuesJson.issues ?? []).map(mapSonarIssue),
|
|
75
|
+
hotspots: (hotspotsJson.hotspots ?? []).map(mapSonarHotspot),
|
|
76
|
+
unavailable: false,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
console.warn(`[sonar] fetch degraded: ${String(err)}`);
|
|
81
|
+
return { issues: [], hotspots: [], unavailable: true };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function toFinitePositive(value, defaultValue) {
|
|
85
|
+
const n = Number(value);
|
|
86
|
+
return Number.isFinite(n) && n > 0 ? n : defaultValue;
|
|
87
|
+
}
|
|
88
|
+
function parseGhJson(raw, label) {
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(raw);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
throw new Error(`gh returned non-JSON for ${label}: ${raw.slice(0, 200)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const NOT_FOUND_RE = /could not resolve|could not find|no pull requests? found|not found/i;
|
|
97
|
+
function resolvePrByBranch(repo, headBranch, baseBranch, execGh) {
|
|
98
|
+
const raw = execGh([
|
|
99
|
+
'pr', 'list', '--repo', repo, '--head', headBranch, '--state', 'open',
|
|
100
|
+
'--json', 'number,baseRefName,state',
|
|
101
|
+
]);
|
|
102
|
+
const prs = parseGhJson(raw, `pr list --head ${headBranch}`);
|
|
103
|
+
const onBase = prs.filter((p) => p.baseRefName === baseBranch);
|
|
104
|
+
if (onBase.length === 0) {
|
|
105
|
+
return { needsHuman: true, lesson: `No open PR for head branch "${headBranch}" with base "${baseBranch}" in ${repo} - manual review needed` };
|
|
106
|
+
}
|
|
107
|
+
if (onBase.length === 1)
|
|
108
|
+
return { pr_number: onBase[0].number };
|
|
109
|
+
const candidates = onBase.map((p) => p.number).join(', ');
|
|
110
|
+
return {
|
|
111
|
+
needsHuman: true,
|
|
112
|
+
lesson: `Ambiguous: ${onBase.length} open PRs for head branch "${headBranch}" targeting base "${baseBranch}" - candidates #${candidates} - manual review needed`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function fetchPrView(prNumber, repo, execGh) {
|
|
116
|
+
const raw = execGh([
|
|
117
|
+
'pr', 'view', String(prNumber), '--repo', repo,
|
|
118
|
+
'--json', 'number,url,state,isDraft,baseRefName,headRefName,headRefOid,statusCheckRollup,mergeStateStatus,reviewDecision,mergeable',
|
|
119
|
+
]);
|
|
120
|
+
return parseGhJson(raw, `pr view #${prNumber}`);
|
|
121
|
+
}
|
|
122
|
+
function handleClosedPr(prNumber, resolvedFromBranch, input, baseBranch, execGh, prView) {
|
|
123
|
+
if (!input.headBranch || resolvedFromBranch) {
|
|
124
|
+
return { kind: 'needsHuman', verdict: 'closed', lesson: `PR #${prNumber} was closed without merging - manual review needed`, prNumber, prView };
|
|
125
|
+
}
|
|
126
|
+
const r = resolvePrByBranch(input.repo, input.headBranch, baseBranch, execGh);
|
|
127
|
+
if ('needsHuman' in r) {
|
|
128
|
+
return { kind: 'needsHuman', verdict: 'closed', lesson: `PR #${prNumber} was closed; ${r.lesson}`, prNumber, prView };
|
|
129
|
+
}
|
|
130
|
+
const newPrView = fetchPrView(r.pr_number, input.repo, execGh);
|
|
131
|
+
if (newPrView.state === 'MERGED')
|
|
132
|
+
return { kind: 'merged', prNumber: r.pr_number, prView: newPrView };
|
|
133
|
+
if (newPrView.state === 'CLOSED') {
|
|
134
|
+
return { kind: 'needsHuman', verdict: 'closed', lesson: `PR #${r.pr_number} (recovered via "${input.headBranch}") is also closed - manual review needed`, prNumber: r.pr_number, prView: newPrView };
|
|
135
|
+
}
|
|
136
|
+
return { kind: 'open', prNumber: r.pr_number, prView: newPrView };
|
|
137
|
+
}
|
|
138
|
+
function resolveOpenPr(input, baseBranch, execGh) {
|
|
139
|
+
let prNumber = input.prNumber;
|
|
140
|
+
let resolvedFromBranch = false;
|
|
141
|
+
if (!prNumber) {
|
|
142
|
+
if (!input.headBranch) {
|
|
143
|
+
return { kind: 'needsHuman', verdict: 'unresolved', lesson: 'ci-poller step has neither pr_number nor head_branch - cannot identify a PR to watch' };
|
|
144
|
+
}
|
|
145
|
+
const r = resolvePrByBranch(input.repo, input.headBranch, baseBranch, execGh);
|
|
146
|
+
if ('needsHuman' in r)
|
|
147
|
+
return { kind: 'needsHuman', verdict: 'unresolved', lesson: r.lesson };
|
|
148
|
+
prNumber = r.pr_number;
|
|
149
|
+
resolvedFromBranch = true;
|
|
150
|
+
}
|
|
151
|
+
let prView;
|
|
152
|
+
try {
|
|
153
|
+
prView = fetchPrView(prNumber, input.repo, execGh);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
if (!input.headBranch || resolvedFromBranch || !NOT_FOUND_RE.test(String(err))) {
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
const r = resolvePrByBranch(input.repo, input.headBranch, baseBranch, execGh);
|
|
160
|
+
if ('needsHuman' in r)
|
|
161
|
+
return { kind: 'needsHuman', verdict: 'unresolved', lesson: r.lesson };
|
|
162
|
+
prNumber = r.pr_number;
|
|
163
|
+
resolvedFromBranch = true;
|
|
164
|
+
prView = fetchPrView(prNumber, input.repo, execGh);
|
|
165
|
+
}
|
|
166
|
+
if (prView.state === 'MERGED')
|
|
167
|
+
return { kind: 'merged', prNumber, prView };
|
|
168
|
+
if (prView.state === 'CLOSED')
|
|
169
|
+
return handleClosedPr(prNumber, resolvedFromBranch, input, baseBranch, execGh, prView);
|
|
170
|
+
return { kind: 'open', prNumber, prView };
|
|
171
|
+
}
|
|
172
|
+
function isTerminal(item) {
|
|
173
|
+
if (item.__typename === 'CheckRun')
|
|
174
|
+
return item.status === 'COMPLETED';
|
|
175
|
+
return item.state !== 'PENDING';
|
|
176
|
+
}
|
|
177
|
+
function isPassed(item) {
|
|
178
|
+
if (item.__typename === 'CheckRun') {
|
|
179
|
+
return ['SUCCESS', 'SKIPPED', 'NEUTRAL'].includes(item.conclusion ?? '');
|
|
180
|
+
}
|
|
181
|
+
return item.state === 'SUCCESS';
|
|
182
|
+
}
|
|
183
|
+
function checkName(item) {
|
|
184
|
+
if (item.__typename === 'CheckRun')
|
|
185
|
+
return item.name ?? 'unknown';
|
|
186
|
+
return item.context ?? item.name ?? 'unknown';
|
|
187
|
+
}
|
|
188
|
+
function checkResult(item) {
|
|
189
|
+
if (item.__typename === 'CheckRun') {
|
|
190
|
+
return item.status === 'COMPLETED' ? (item.conclusion ?? 'unknown') : (item.status ?? 'unknown');
|
|
191
|
+
}
|
|
192
|
+
return item.state ?? 'unknown';
|
|
193
|
+
}
|
|
194
|
+
function isBot(user) {
|
|
195
|
+
return user?.type === 'Bot';
|
|
196
|
+
}
|
|
197
|
+
export function collectCiChecks(items) {
|
|
198
|
+
const pending = items.length === 0 || items.some((item) => !isTerminal(item));
|
|
199
|
+
const ci_passed = !pending && items.every((item) => isPassed(item));
|
|
200
|
+
const pendingNames = items
|
|
201
|
+
.filter((item) => !isTerminal(item))
|
|
202
|
+
.map(checkName);
|
|
203
|
+
const checks = items.map((item) => ({ name: checkName(item), result: checkResult(item) }));
|
|
204
|
+
return { pending, ci_passed, checks, pendingNames };
|
|
205
|
+
}
|
|
206
|
+
function compactBody(body) {
|
|
207
|
+
return body.replace(/\s+/g, ' ').trim().slice(0, 280);
|
|
208
|
+
}
|
|
209
|
+
function asRecord(value) {
|
|
210
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
211
|
+
return null;
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
function mapReviewThreads(raw) {
|
|
215
|
+
const root = asRecord(raw);
|
|
216
|
+
const repository = asRecord(root?.['repository']);
|
|
217
|
+
const pullRequest = asRecord(repository?.['pullRequest']);
|
|
218
|
+
const reviewThreads = asRecord(pullRequest?.['reviewThreads']);
|
|
219
|
+
const nodes = Array.isArray(reviewThreads?.['nodes']) ? reviewThreads.nodes : [];
|
|
220
|
+
return nodes.flatMap((node) => {
|
|
221
|
+
const thread = asRecord(node);
|
|
222
|
+
if (!thread)
|
|
223
|
+
return [];
|
|
224
|
+
const comments = asRecord(thread.comments);
|
|
225
|
+
const firstComment = Array.isArray(comments?.['nodes']) ? asRecord(comments.nodes[0]) : null;
|
|
226
|
+
const author = asRecord(firstComment?.['author']);
|
|
227
|
+
return [{
|
|
228
|
+
id: asStr(thread.id),
|
|
229
|
+
isResolved: Boolean(thread.isResolved),
|
|
230
|
+
isOutdated: Boolean(thread.isOutdated),
|
|
231
|
+
path: asStr(thread.path) || undefined,
|
|
232
|
+
line: typeof thread.line === 'number' ? thread.line : undefined,
|
|
233
|
+
author: asStr(author?.['login']) || undefined,
|
|
234
|
+
body: compactBody(asStr(firstComment?.['body'])),
|
|
235
|
+
url: asStr(firstComment?.['url']) || undefined,
|
|
236
|
+
}];
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function splitRepo(repo) {
|
|
240
|
+
const [owner, name] = repo.split('/');
|
|
241
|
+
if (!owner || !name)
|
|
242
|
+
throw new Error(`repo must use owner/name format: ${repo}`);
|
|
243
|
+
return { owner, name };
|
|
244
|
+
}
|
|
245
|
+
function fetchReviewThreads(repo, prNumber, execGh) {
|
|
246
|
+
const { owner, name } = splitRepo(repo);
|
|
247
|
+
const raw = execGh([
|
|
248
|
+
'api', 'graphql',
|
|
249
|
+
'-f', 'query=query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){reviewThreads(first:100){nodes{id,isResolved,isOutdated,path,line,comments(first:1){nodes{body,url,author{login}}}}}}}}',
|
|
250
|
+
'-f', `owner=${owner}`,
|
|
251
|
+
'-f', `name=${name}`,
|
|
252
|
+
'-F', `number=${prNumber}`,
|
|
253
|
+
]);
|
|
254
|
+
return mapReviewThreads(parseGhJson(raw, `review-threads #${prNumber}`));
|
|
255
|
+
}
|
|
256
|
+
function collectReviewThreads(input, prNumber, execGh) {
|
|
257
|
+
const threads = input.includeReviewThreads === false ? [] : fetchReviewThreads(input.repo, prNumber, execGh);
|
|
258
|
+
const unresolved = threads.filter((thread) => !thread.isResolved && !thread.isOutdated);
|
|
259
|
+
return {
|
|
260
|
+
included: input.includeReviewThreads !== false,
|
|
261
|
+
unresolvedCount: unresolved.length,
|
|
262
|
+
items: unresolved.slice(0, 20),
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
function fetchComments(input, prNumber, execGh) {
|
|
266
|
+
if (input.includeComments === false) {
|
|
267
|
+
return { human_reviews: [], human_comments: [], bot_comments: [] };
|
|
268
|
+
}
|
|
269
|
+
const reviewsRaw = execGh(['api', `repos/${input.repo}/pulls/${prNumber}/reviews`]);
|
|
270
|
+
const reviews = parseGhJson(reviewsRaw, `reviews #${prNumber}`);
|
|
271
|
+
const reviewCommentsRaw = execGh(['api', `repos/${input.repo}/pulls/${prNumber}/comments`]);
|
|
272
|
+
const reviewComments = parseGhJson(reviewCommentsRaw, `review-comments #${prNumber}`);
|
|
273
|
+
const issueCommentsRaw = execGh(['api', `repos/${input.repo}/issues/${prNumber}/comments`]);
|
|
274
|
+
const issueComments = parseGhJson(issueCommentsRaw, `issue-comments #${prNumber}`);
|
|
275
|
+
const allComments = [...reviewComments, ...issueComments];
|
|
276
|
+
const latestHumanReviewByAuthor = new Map();
|
|
277
|
+
for (const review of reviews) {
|
|
278
|
+
if (!review.user || isBot(review.user))
|
|
279
|
+
continue;
|
|
280
|
+
latestHumanReviewByAuthor.set(review.user.login, review);
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
human_reviews: [...latestHumanReviewByAuthor.values()],
|
|
284
|
+
human_comments: allComments.filter((comment) => !isBot(comment.user)),
|
|
285
|
+
bot_comments: allComments.filter((comment) => isBot(comment.user)),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function codeRabbitCommentReason(comments) {
|
|
289
|
+
for (const comment of comments) {
|
|
290
|
+
const login = comment.user?.login.toLowerCase() ?? '';
|
|
291
|
+
if (!login.includes('coderabbit'))
|
|
292
|
+
continue;
|
|
293
|
+
const body = comment.body.toLowerCase();
|
|
294
|
+
if (/rate.limit|provider.limit|quota|capacity|temporar/.test(body) && /did not start|could not start|not start|unable to start|paused/.test(body)) {
|
|
295
|
+
return 'provider_limit';
|
|
296
|
+
}
|
|
297
|
+
if (/review in progress|reviewing|processing/.test(body))
|
|
298
|
+
return 'review_in_progress';
|
|
299
|
+
if (/skipped|did not review|no files to review/.test(body))
|
|
300
|
+
return 'skipped';
|
|
301
|
+
if (/no actionable comments|no issues found|looks good/.test(body))
|
|
302
|
+
return 'no_actionable_comments';
|
|
303
|
+
}
|
|
304
|
+
return '';
|
|
305
|
+
}
|
|
306
|
+
function compactCheckLists(checks) {
|
|
307
|
+
const terminal = [];
|
|
308
|
+
const pending = [];
|
|
309
|
+
const pass = [];
|
|
310
|
+
const fail = [];
|
|
311
|
+
for (const check of checks) {
|
|
312
|
+
if (['QUEUED', 'IN_PROGRESS', 'PENDING'].includes(check.result)) {
|
|
313
|
+
pending.push(check.name);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
terminal.push(check.name);
|
|
317
|
+
if (['SUCCESS', 'SKIPPED', 'NEUTRAL'].includes(check.result))
|
|
318
|
+
pass.push(check.name);
|
|
319
|
+
else
|
|
320
|
+
fail.push(check.name);
|
|
321
|
+
}
|
|
322
|
+
return { terminal, pending, pass, fail, list: checks };
|
|
323
|
+
}
|
|
324
|
+
function providerState(checks, botComments) {
|
|
325
|
+
const codeRabbitCheck = checks.find((check) => check.name.toLowerCase().includes('coderabbit'));
|
|
326
|
+
const commentReason = codeRabbitCommentReason(botComments);
|
|
327
|
+
const statusSuccess = codeRabbitCheck?.result === 'SUCCESS';
|
|
328
|
+
if (commentReason === 'provider_limit') {
|
|
329
|
+
return {
|
|
330
|
+
codeRabbit: {
|
|
331
|
+
state: 'waiting',
|
|
332
|
+
reason: 'provider_limit',
|
|
333
|
+
statusContext: codeRabbitCheck?.result ?? '',
|
|
334
|
+
evidence: 'CodeRabbit status was green, but the top-level bot comment says review did not start because of provider or rate limits.',
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
if (commentReason) {
|
|
339
|
+
return {
|
|
340
|
+
codeRabbit: {
|
|
341
|
+
state: commentReason === 'no_actionable_comments' ? 'complete' : 'waiting',
|
|
342
|
+
reason: commentReason,
|
|
343
|
+
statusContext: codeRabbitCheck?.result ?? '',
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
if (codeRabbitCheck) {
|
|
348
|
+
return {
|
|
349
|
+
codeRabbit: {
|
|
350
|
+
state: statusSuccess ? 'complete' : 'waiting',
|
|
351
|
+
reason: statusSuccess ? 'status_success' : 'status_pending_or_failed',
|
|
352
|
+
statusContext: codeRabbitCheck.result,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
return {};
|
|
357
|
+
}
|
|
358
|
+
function locationOf(item) {
|
|
359
|
+
const path = item.path ?? item.component ?? '';
|
|
360
|
+
return item.line ? `${path}:${item.line}` : path;
|
|
361
|
+
}
|
|
362
|
+
function providerWaitFeedback(state) {
|
|
363
|
+
const codeRabbit = state.codeRabbit;
|
|
364
|
+
if (codeRabbit?.state !== 'waiting')
|
|
365
|
+
return [];
|
|
366
|
+
return [{ provider: 'CodeRabbit', reason: codeRabbit.reason, evidence: codeRabbit.evidence ?? codeRabbit.statusContext }];
|
|
367
|
+
}
|
|
368
|
+
function buildFeedback(input) {
|
|
369
|
+
const developerFixes = [
|
|
370
|
+
...input.checks.fail.map((name) => ({ source: 'ci', summary: `Fix failing check: ${name}`, evidence: name })),
|
|
371
|
+
...input.sonar.issues.map((issue) => ({
|
|
372
|
+
source: 'sonar',
|
|
373
|
+
summary: issue.message,
|
|
374
|
+
severity: issue.severity,
|
|
375
|
+
location: locationOf(issue),
|
|
376
|
+
evidence: issue.rule ?? issue.component,
|
|
377
|
+
})),
|
|
378
|
+
...input.sonar.hotspots.map((hotspot) => ({
|
|
379
|
+
source: 'sonar_hotspot',
|
|
380
|
+
summary: hotspot.message,
|
|
381
|
+
severity: hotspot.vulnerabilityProbability ?? '',
|
|
382
|
+
location: locationOf(hotspot),
|
|
383
|
+
evidence: hotspot.securityCategory ?? hotspot.component,
|
|
384
|
+
})),
|
|
385
|
+
...input.humanReviews
|
|
386
|
+
.filter((review) => review.state === 'CHANGES_REQUESTED')
|
|
387
|
+
.map((review) => ({
|
|
388
|
+
source: 'human_review',
|
|
389
|
+
summary: compactBody(review.body),
|
|
390
|
+
author: review.user?.login ?? '',
|
|
391
|
+
evidence: review.state,
|
|
392
|
+
})),
|
|
393
|
+
...input.reviewThreads.items.map((thread) => ({
|
|
394
|
+
source: 'review_thread',
|
|
395
|
+
summary: thread.body,
|
|
396
|
+
author: thread.author ?? '',
|
|
397
|
+
location: locationOf(thread),
|
|
398
|
+
evidence: thread.url ?? thread.id,
|
|
399
|
+
})),
|
|
400
|
+
];
|
|
401
|
+
const reviewerQuestions = input.humanComments
|
|
402
|
+
.filter((comment) => comment.body.includes('?'))
|
|
403
|
+
.map((comment) => ({
|
|
404
|
+
source: 'human_comment',
|
|
405
|
+
summary: compactBody(comment.body),
|
|
406
|
+
author: comment.user?.login ?? '',
|
|
407
|
+
location: locationOf(comment),
|
|
408
|
+
}));
|
|
409
|
+
const providerWait = providerWaitFeedback(input.providerState);
|
|
410
|
+
const humanDecisions = [
|
|
411
|
+
...(input.reviewDecision && input.reviewDecision !== 'APPROVED'
|
|
412
|
+
? [{ source: 'github_review_decision', summary: `Review decision is ${input.reviewDecision}` }]
|
|
413
|
+
: []),
|
|
414
|
+
...(input.sonar.unavailable ? [{ source: 'sonar', summary: 'Sonar was configured but unavailable.' }] : []),
|
|
415
|
+
];
|
|
416
|
+
const ignoredNoise = input.botComments
|
|
417
|
+
.filter((comment) => !comment.user?.login.toLowerCase().includes('coderabbit'))
|
|
418
|
+
.map((comment) => ({ source: comment.user?.login ?? 'bot', summary: compactBody(comment.body) }));
|
|
419
|
+
const residualRisks = [
|
|
420
|
+
...(input.reviewThreads.included ? [] : ['Review threads were not requested.']),
|
|
421
|
+
...(input.sonar.unavailable ? ['Sonar findings could not be fetched.'] : []),
|
|
422
|
+
];
|
|
423
|
+
return { developerFixes, reviewerQuestions, providerWait, humanDecisions, ignoredNoise, residualRisks };
|
|
424
|
+
}
|
|
425
|
+
function emptyPr(prNumber, state = '') {
|
|
426
|
+
return {
|
|
427
|
+
number: prNumber ?? null,
|
|
428
|
+
url: '',
|
|
429
|
+
state,
|
|
430
|
+
draft: false,
|
|
431
|
+
base: '',
|
|
432
|
+
head: '',
|
|
433
|
+
headSha: '',
|
|
434
|
+
mergeState: '',
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function prFromView(prNumber, view) {
|
|
438
|
+
return {
|
|
439
|
+
number: view?.number ?? prNumber,
|
|
440
|
+
url: view?.url ?? '',
|
|
441
|
+
state: view?.state ?? '',
|
|
442
|
+
draft: view?.isDraft === true,
|
|
443
|
+
base: view?.baseRefName ?? '',
|
|
444
|
+
head: view?.headRefName ?? '',
|
|
445
|
+
headSha: view?.headRefOid ?? '',
|
|
446
|
+
mergeState: view?.mergeStateStatus ?? '',
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function emptySonar(configured) {
|
|
450
|
+
return { configured, issues: [], hotspots: [], unavailable: false };
|
|
451
|
+
}
|
|
452
|
+
function emptyReviewThreads(included) {
|
|
453
|
+
return { included, unresolvedCount: 0, items: [] };
|
|
454
|
+
}
|
|
455
|
+
function emptyCiSummary(ciPassed) {
|
|
456
|
+
return { ci_passed: ciPassed, checks: [], sonar_issues: [], sonar_hotspots_to_review: [], human_reviews: [], human_comments: [], bot_comments: [] };
|
|
457
|
+
}
|
|
458
|
+
function buildEmptyFeedback(input) {
|
|
459
|
+
return buildFeedback({
|
|
460
|
+
checks: compactCheckLists([]),
|
|
461
|
+
providerState: {},
|
|
462
|
+
sonar: emptySonar(input.sonarConfigured),
|
|
463
|
+
reviewDecision: input.reviewDecision ?? '',
|
|
464
|
+
reviewThreads: input.reviewThreads ?? emptyReviewThreads(input.includeReviewThreads),
|
|
465
|
+
humanReviews: [],
|
|
466
|
+
humanComments: [],
|
|
467
|
+
botComments: [],
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
function buildMergedReadiness(input, resolved) {
|
|
471
|
+
const includeReviewThreads = input.includeReviewThreads !== false;
|
|
472
|
+
const sonarConfigured = Boolean(input.sonarProject);
|
|
473
|
+
return {
|
|
474
|
+
verdict: 'merged',
|
|
475
|
+
pr: prFromView(resolved.prNumber, resolved.prView),
|
|
476
|
+
checks: compactCheckLists([]),
|
|
477
|
+
reviewDecision: '',
|
|
478
|
+
reviewThreads: emptyReviewThreads(includeReviewThreads),
|
|
479
|
+
providerState: {},
|
|
480
|
+
sonar: emptySonar(sonarConfigured),
|
|
481
|
+
nextAction: 'none',
|
|
482
|
+
evidence: [`PR #${resolved.prNumber} is merged.`],
|
|
483
|
+
feedback: buildEmptyFeedback({ sonarConfigured, includeReviewThreads }),
|
|
484
|
+
ciSummary: emptyCiSummary(true),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
function buildNeedsHumanReadiness(input, resolved) {
|
|
488
|
+
const includeReviewThreads = input.includeReviewThreads !== false;
|
|
489
|
+
const sonarConfigured = Boolean(input.sonarProject);
|
|
490
|
+
const feedback = buildEmptyFeedback({ sonarConfigured, includeReviewThreads });
|
|
491
|
+
return {
|
|
492
|
+
verdict: resolved.verdict === 'closed' ? 'closed' : 'needs_human',
|
|
493
|
+
pr: resolved.prNumber ? prFromView(resolved.prNumber, resolved.prView) : emptyPr(input.prNumber, resolved.verdict),
|
|
494
|
+
checks: compactCheckLists([]),
|
|
495
|
+
reviewDecision: '',
|
|
496
|
+
reviewThreads: emptyReviewThreads(includeReviewThreads),
|
|
497
|
+
providerState: {},
|
|
498
|
+
sonar: emptySonar(sonarConfigured),
|
|
499
|
+
nextAction: 'human_decision',
|
|
500
|
+
evidence: [resolved.lesson],
|
|
501
|
+
feedback: { ...feedback, humanDecisions: [{ source: 'pr_resolution', summary: resolved.lesson }] },
|
|
502
|
+
ciSummary: emptyCiSummary(false),
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function buildWaitingReadiness(input) {
|
|
506
|
+
const feedback = buildFeedback({
|
|
507
|
+
checks: input.checkLists,
|
|
508
|
+
providerState: {},
|
|
509
|
+
sonar: emptySonar(input.sonarConfigured),
|
|
510
|
+
reviewDecision: input.prView.reviewDecision ?? '',
|
|
511
|
+
reviewThreads: input.reviewThreads,
|
|
512
|
+
humanReviews: [],
|
|
513
|
+
humanComments: [],
|
|
514
|
+
botComments: [],
|
|
515
|
+
});
|
|
516
|
+
return {
|
|
517
|
+
verdict: 'waiting',
|
|
518
|
+
pr: prFromView(input.prNumber, input.prView),
|
|
519
|
+
checks: input.checkLists,
|
|
520
|
+
reviewDecision: input.prView.reviewDecision ?? '',
|
|
521
|
+
reviewThreads: input.reviewThreads,
|
|
522
|
+
providerState: {},
|
|
523
|
+
sonar: emptySonar(input.sonarConfigured),
|
|
524
|
+
nextAction: 'watcher_wait',
|
|
525
|
+
evidence: input.evidence,
|
|
526
|
+
feedback,
|
|
527
|
+
ciSummary: {
|
|
528
|
+
ci_passed: false,
|
|
529
|
+
checks: input.ci.checks,
|
|
530
|
+
...(input.isDraft ? { isDraft: true } : {}),
|
|
531
|
+
sonar_issues: [],
|
|
532
|
+
sonar_hotspots_to_review: [],
|
|
533
|
+
human_reviews: [],
|
|
534
|
+
human_comments: [],
|
|
535
|
+
bot_comments: [],
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function readinessAction(input) {
|
|
540
|
+
if (input.providers.codeRabbit?.reason === 'provider_limit' || input.providers.codeRabbit?.reason === 'review_in_progress') {
|
|
541
|
+
return { verdict: 'waiting', nextAction: 'watcher_wait' };
|
|
542
|
+
}
|
|
543
|
+
if (!input.ci.ci_passed || input.feedback.developerFixes.length > 0) {
|
|
544
|
+
return { verdict: 'needs_work', nextAction: 'developer_fix' };
|
|
545
|
+
}
|
|
546
|
+
if (input.feedback.reviewerQuestions.length > 0) {
|
|
547
|
+
return { verdict: 'needs_human', nextAction: 'reviewer_triage' };
|
|
548
|
+
}
|
|
549
|
+
if (input.feedback.humanDecisions.length > 0) {
|
|
550
|
+
return { verdict: 'needs_human', nextAction: 'human_decision' };
|
|
551
|
+
}
|
|
552
|
+
return { verdict: 'ready', nextAction: 'ready_for_merge_gate' };
|
|
553
|
+
}
|
|
554
|
+
export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSonar = defaultFetchSonar) {
|
|
555
|
+
const baseBranch = input.baseBranch ?? 'master';
|
|
556
|
+
const resolved = resolveOpenPr(input, baseBranch, execGh);
|
|
557
|
+
if (resolved.kind === 'merged') {
|
|
558
|
+
return buildMergedReadiness(input, resolved);
|
|
559
|
+
}
|
|
560
|
+
if (resolved.kind === 'needsHuman') {
|
|
561
|
+
return buildNeedsHumanReadiness(input, resolved);
|
|
562
|
+
}
|
|
563
|
+
const { prNumber, prView } = resolved;
|
|
564
|
+
const checks = prView.statusCheckRollup ?? [];
|
|
565
|
+
const ci = collectCiChecks(checks);
|
|
566
|
+
const checkLists = compactCheckLists(ci.checks);
|
|
567
|
+
const reviewThreads = collectReviewThreads(input, prNumber, execGh);
|
|
568
|
+
const sonarConfigured = Boolean(input.sonarProject);
|
|
569
|
+
if (prView.isDraft === true) {
|
|
570
|
+
return buildWaitingReadiness({
|
|
571
|
+
prNumber,
|
|
572
|
+
prView,
|
|
573
|
+
checkLists,
|
|
574
|
+
ci,
|
|
575
|
+
reviewThreads,
|
|
576
|
+
sonarConfigured,
|
|
577
|
+
evidence: [`PR #${prNumber} is still draft.`],
|
|
578
|
+
isDraft: true,
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
if (ci.pending) {
|
|
582
|
+
return buildWaitingReadiness({
|
|
583
|
+
prNumber,
|
|
584
|
+
prView,
|
|
585
|
+
checkLists,
|
|
586
|
+
ci,
|
|
587
|
+
reviewThreads,
|
|
588
|
+
sonarConfigured,
|
|
589
|
+
evidence: ci.pendingNames.length > 0 ? [`Pending checks: ${ci.pendingNames.join(', ')}`] : ['No check rollup entries are registered yet.'],
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
const sonar = input.sonarProject
|
|
593
|
+
? await fetchSonar(input.sonarProject, prNumber)
|
|
594
|
+
: { issues: [], hotspots: [], unavailable: false };
|
|
595
|
+
const comments = fetchComments(input, prNumber, execGh);
|
|
596
|
+
const providers = providerState(ci.checks, comments.bot_comments);
|
|
597
|
+
const sonarSummary = {
|
|
598
|
+
configured: Boolean(input.sonarProject),
|
|
599
|
+
issues: sonar.issues,
|
|
600
|
+
hotspots: sonar.hotspots,
|
|
601
|
+
unavailable: sonar.unavailable,
|
|
602
|
+
};
|
|
603
|
+
const feedback = buildFeedback({
|
|
604
|
+
checks: checkLists,
|
|
605
|
+
providerState: providers,
|
|
606
|
+
sonar: sonarSummary,
|
|
607
|
+
reviewDecision: prView.reviewDecision ?? '',
|
|
608
|
+
reviewThreads,
|
|
609
|
+
humanReviews: comments.human_reviews,
|
|
610
|
+
humanComments: comments.human_comments,
|
|
611
|
+
botComments: comments.bot_comments,
|
|
612
|
+
});
|
|
613
|
+
const ciSummary = {
|
|
614
|
+
ci_passed: ci.ci_passed,
|
|
615
|
+
checks: ci.checks,
|
|
616
|
+
mergeStateStatus: prView.mergeStateStatus,
|
|
617
|
+
reviewDecision: prView.reviewDecision,
|
|
618
|
+
mergeable: prView.mergeable,
|
|
619
|
+
sonar_issues: sonar.issues,
|
|
620
|
+
sonar_hotspots_to_review: sonar.hotspots,
|
|
621
|
+
human_reviews: comments.human_reviews,
|
|
622
|
+
human_comments: comments.human_comments,
|
|
623
|
+
bot_comments: comments.bot_comments,
|
|
624
|
+
...(sonar.unavailable ? { sonar_unavailable: true } : {}),
|
|
625
|
+
};
|
|
626
|
+
const { verdict, nextAction } = readinessAction({ ci, feedback, providers });
|
|
627
|
+
return {
|
|
628
|
+
verdict,
|
|
629
|
+
pr: prFromView(prNumber, prView),
|
|
630
|
+
checks: checkLists,
|
|
631
|
+
reviewDecision: prView.reviewDecision ?? '',
|
|
632
|
+
reviewThreads,
|
|
633
|
+
providerState: providers,
|
|
634
|
+
sonar: sonarSummary,
|
|
635
|
+
nextAction,
|
|
636
|
+
evidence: [
|
|
637
|
+
`PR #${prNumber} state=${prView.state ?? 'unknown'} draft=${Boolean(prView.isDraft)}`,
|
|
638
|
+
`checks pass=${checkLists.pass.length} fail=${checkLists.fail.length} pending=${checkLists.pending.length}`,
|
|
639
|
+
...(providers.codeRabbit?.reason === 'provider_limit' ? ['CodeRabbit provider/rate limit comment overrides green status.'] : []),
|
|
640
|
+
],
|
|
641
|
+
feedback,
|
|
642
|
+
ciSummary,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
export function toReadinessInput(input) {
|
|
646
|
+
return {
|
|
647
|
+
repo: input.repo,
|
|
648
|
+
prNumber: input.pr_number,
|
|
649
|
+
headBranch: input.head_branch,
|
|
650
|
+
baseBranch: input.base_branch,
|
|
651
|
+
sonarProject: input.sonar_project,
|
|
652
|
+
includeComments: true,
|
|
653
|
+
includeReviewThreads: false,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
//# sourceMappingURL=pr-readiness-core.js.map
|