@revisium/orchestrator 0.2.0-alpha.3 → 0.2.0-alpha.4
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/README.md +3 -3
- package/control-plane/bootstrap.config.json +104 -550
- package/control-plane/default-playbook/catalog/pipelines.json +394 -1122
- package/control-plane/default-playbook/catalog/run-profiles.json +208 -0
- package/control-plane/default-playbook/package.json +1 -1
- package/control-plane/default-playbook/playbook.json +2 -1
- package/control-plane/default-playbook/prompts/developer.md +6 -6
- package/dist/__generated__/client/browser.js +18 -0
- package/dist/__generated__/client/browser.js.map +1 -0
- package/dist/__generated__/client/client.js +32 -0
- package/dist/__generated__/client/client.js.map +1 -0
- package/dist/__generated__/client/commonInputTypes.js +11 -0
- package/dist/__generated__/client/commonInputTypes.js.map +1 -0
- package/dist/__generated__/client/enums.js +19 -0
- package/dist/__generated__/client/enums.js.map +1 -0
- package/dist/__generated__/client/internal/class.js +53 -0
- package/dist/__generated__/client/internal/class.js.map +1 -0
- package/dist/__generated__/client/internal/prismaNamespace.js +451 -0
- package/dist/__generated__/client/internal/prismaNamespace.js.map +1 -0
- package/dist/__generated__/client/internal/prismaNamespaceBrowser.js +422 -0
- package/dist/__generated__/client/internal/prismaNamespaceBrowser.js.map +1 -0
- package/dist/__generated__/client/models/Branch.js +2 -0
- package/dist/__generated__/client/models/Branch.js.map +1 -0
- package/dist/__generated__/client/models/CostLedgerEntry.js +2 -0
- package/dist/__generated__/client/models/CostLedgerEntry.js.map +1 -0
- package/dist/__generated__/client/models/FileBlob.js +2 -0
- package/dist/__generated__/client/models/FileBlob.js.map +1 -0
- package/dist/__generated__/client/models/InboxItem.js +2 -0
- package/dist/__generated__/client/models/InboxItem.js.map +1 -0
- package/dist/__generated__/client/models/ProjectFileUsage.js +2 -0
- package/dist/__generated__/client/models/ProjectFileUsage.js.map +1 -0
- package/dist/__generated__/client/models/Revision.js +2 -0
- package/dist/__generated__/client/models/Revision.js.map +1 -0
- package/dist/__generated__/client/models/RevoProject.js +2 -0
- package/dist/__generated__/client/models/RevoProject.js.map +1 -0
- package/dist/__generated__/client/models/Row.js +2 -0
- package/dist/__generated__/client/models/Row.js.map +1 -0
- package/dist/__generated__/client/models/RunAttempt.js +2 -0
- package/dist/__generated__/client/models/RunAttempt.js.map +1 -0
- package/dist/__generated__/client/models/RunEvent.js +2 -0
- package/dist/__generated__/client/models/RunEvent.js.map +1 -0
- package/dist/__generated__/client/models/RunOutput.js +2 -0
- package/dist/__generated__/client/models/RunOutput.js.map +1 -0
- package/dist/__generated__/client/models/RunTask.js +2 -0
- package/dist/__generated__/client/models/RunTask.js.map +1 -0
- package/dist/__generated__/client/models/Table.js +2 -0
- package/dist/__generated__/client/models/Table.js.map +1 -0
- package/dist/__generated__/client/models/TableMigration.js +2 -0
- package/dist/__generated__/client/models/TableMigration.js.map +1 -0
- package/dist/__generated__/client/models/TaskRun.js +2 -0
- package/dist/__generated__/client/models/TaskRun.js.map +1 -0
- package/dist/__generated__/client/models.js +2 -0
- package/dist/__generated__/client/models.js.map +1 -0
- package/dist/api/graphql-api/graphql-ws/pubsub.module.js +2 -1
- package/dist/api/graphql-api/graphql-ws/pubsub.module.js.map +1 -1
- package/dist/api/graphql-api/graphql-ws/subscription-bridge.service.js +110 -28
- package/dist/api/graphql-api/graphql-ws/subscription-bridge.service.js.map +1 -1
- package/dist/api/graphql-api/graphql-ws/subscription-mappers.js +3 -1
- package/dist/api/graphql-api/graphql-ws/subscription-mappers.js.map +1 -1
- package/dist/api/graphql-api/pr/inputs/pr-readiness.input.js +6 -0
- package/dist/api/graphql-api/pr/inputs/pr-readiness.input.js.map +1 -1
- package/dist/api/graphql-api/registerGraphqlEnums.js +2 -0
- package/dist/api/graphql-api/registerGraphqlEnums.js.map +1 -1
- package/dist/api/graphql-api/runs/inputs/create-run.input.js +17 -0
- package/dist/api/graphql-api/runs/inputs/create-run.input.js.map +1 -1
- package/dist/api/graphql-api/runs/inputs/execution-profile.input.js +85 -0
- package/dist/api/graphql-api/runs/inputs/execution-profile.input.js.map +1 -0
- package/dist/api/graphql-api/runs/inputs/simulate-route.input.js +11 -0
- package/dist/api/graphql-api/runs/inputs/simulate-route.input.js.map +1 -1
- package/dist/api/graphql-api/runs/model/run-workflow.model.js +5 -0
- package/dist/api/graphql-api/runs/model/run-workflow.model.js.map +1 -1
- package/dist/api/graphql-api/share/model/issue-action.model.js +7 -0
- package/dist/api/graphql-api/share/model/issue-action.model.js.map +1 -0
- package/dist/cli/commands/doctor-report.js +1 -13
- package/dist/cli/commands/doctor-report.js.map +1 -1
- package/dist/cli/commands/lifecycle.js +50 -65
- package/dist/cli/commands/lifecycle.js.map +1 -1
- package/dist/cli/commands/{revisium-helpers.js → process-helpers.js} +1 -24
- package/dist/cli/commands/process-helpers.js.map +1 -0
- package/dist/config.js +17 -55
- package/dist/config.js.map +1 -1
- package/dist/control-plane/bootstrap-json-validator.js +65 -0
- package/dist/control-plane/bootstrap-json-validator.js.map +1 -0
- package/dist/control-plane/bootstrap.js +25 -38
- package/dist/control-plane/bootstrap.js.map +1 -1
- package/dist/control-plane/change-notifications.js +45 -14
- package/dist/control-plane/change-notifications.js.map +1 -1
- package/dist/control-plane/data-access.js +1 -106
- package/dist/control-plane/data-access.js.map +1 -1
- package/dist/control-plane/default-playbook-policy.js +497 -94
- package/dist/control-plane/default-playbook-policy.js.map +1 -1
- package/dist/control-plane/definitions.js +4 -5
- package/dist/control-plane/definitions.js.map +1 -1
- package/dist/control-plane/engine-transport.js +381 -0
- package/dist/control-plane/engine-transport.js.map +1 -0
- package/dist/control-plane/inbox.js +15 -7
- package/dist/control-plane/inbox.js.map +1 -1
- package/dist/control-plane/index.js +0 -1
- package/dist/control-plane/index.js.map +1 -1
- package/dist/control-plane/merge-override-audit.js +2 -2
- package/dist/control-plane/merge-override-audit.js.map +1 -1
- package/dist/control-plane/pipeline-coverage-registry.js +478 -0
- package/dist/control-plane/pipeline-coverage-registry.js.map +1 -0
- package/dist/control-plane/query-types.js +2 -0
- package/dist/control-plane/query-types.js.map +1 -0
- package/dist/control-plane/run-profiles.js +89 -0
- package/dist/control-plane/run-profiles.js.map +1 -0
- package/dist/control-plane/schema-migration.js +0 -35
- package/dist/control-plane/schema-migration.js.map +1 -1
- package/dist/control-plane/seed-default-playbook.js +2 -31
- package/dist/control-plane/seed-default-playbook.js.map +1 -1
- package/dist/control-plane/tables.js +8 -0
- package/dist/control-plane/tables.js.map +1 -1
- package/dist/control-plane/transport.js +16 -0
- package/dist/control-plane/transport.js.map +1 -0
- package/dist/control-plane/versioned-meaning.js +56 -14
- package/dist/control-plane/versioned-meaning.js.map +1 -1
- package/dist/e2e/kit/agents.js +60 -6
- package/dist/e2e/kit/agents.js.map +1 -1
- package/dist/e2e/kit/assertions.js +13 -1
- package/dist/e2e/kit/assertions.js.map +1 -1
- package/dist/e2e/kit/drive.js +46 -11
- package/dist/e2e/kit/drive.js.map +1 -1
- package/dist/e2e/kit/env.js +17 -1
- package/dist/e2e/kit/env.js.map +1 -1
- package/dist/e2e/kit/fake-integrator.js +25 -5
- package/dist/e2e/kit/fake-integrator.js.map +1 -1
- package/dist/e2e/kit/gh-emulator.js +158 -24
- package/dist/e2e/kit/gh-emulator.js.map +1 -1
- package/dist/e2e/kit/harness.js +44 -10
- package/dist/e2e/kit/harness.js.map +1 -1
- package/dist/e2e/kit/index.js +1 -0
- package/dist/e2e/kit/index.js.map +1 -1
- package/dist/e2e/kit/scenario.js +165 -0
- package/dist/e2e/kit/scenario.js.map +1 -0
- package/dist/e2e/kit/scenarios.js +12 -18
- package/dist/e2e/kit/scenarios.js.map +1 -1
- package/dist/engine/dbos.service.js +8 -1
- package/dist/engine/dbos.service.js.map +1 -1
- package/dist/features/pr/queries/impl/get-pr-readiness.query.js.map +1 -1
- package/dist/features/runs/commands/impl/create-run.command.js.map +1 -1
- package/dist/host/daemon.js +32 -8
- package/dist/host/daemon.js.map +1 -1
- package/dist/host/ensure-host.js +3 -3
- package/dist/host/ensure-host.js.map +1 -1
- package/dist/host/host-runtime.js.map +1 -1
- package/dist/host/host.lifecycle.js +3 -16
- package/dist/host/host.lifecycle.js.map +1 -1
- package/dist/http/graphql-host.js +17 -8
- package/dist/http/graphql-host.js.map +1 -1
- package/dist/mcp/mcp-capabilities.js +3 -2
- package/dist/mcp/mcp-capabilities.js.map +1 -1
- package/dist/mcp/mcp-facade.service.js +21 -0
- package/dist/mcp/mcp-facade.service.js.map +1 -1
- package/dist/mcp/mcp-tools.js +35 -1
- package/dist/mcp/mcp-tools.js.map +1 -1
- package/dist/pipeline/data-driven-task.workflow.js +276 -57
- package/dist/pipeline/data-driven-task.workflow.js.map +1 -1
- package/dist/pipeline/pipeline.service.js +27 -5
- package/dist/pipeline/pipeline.service.js.map +1 -1
- package/dist/pipeline/route-contract.js +123 -3
- package/dist/pipeline/route-contract.js.map +1 -1
- package/dist/pipeline-core/condition-scopes.js +15 -0
- package/dist/pipeline-core/condition-scopes.js.map +1 -0
- package/dist/pipeline-core/index.js +2 -0
- package/dist/pipeline-core/index.js.map +1 -1
- package/dist/pipeline-core/kit/fixtures.js +81 -44
- package/dist/pipeline-core/kit/fixtures.js.map +1 -1
- package/dist/pipeline-core/materialize.js +221 -0
- package/dist/pipeline-core/materialize.js.map +1 -0
- package/dist/pipeline-core/types.js +5 -0
- package/dist/pipeline-core/types.js.map +1 -1
- package/dist/pipeline-core/validate-loops.js +2 -15
- package/dist/pipeline-core/validate-loops.js.map +1 -1
- package/dist/pipeline-core/validate-resilience.js +257 -0
- package/dist/pipeline-core/validate-resilience.js.map +1 -0
- package/dist/pipeline-core/validate.js +2 -0
- package/dist/pipeline-core/validate.js.map +1 -1
- package/dist/playbook/catalog-loader.js +39 -2
- package/dist/playbook/catalog-loader.js.map +1 -1
- package/dist/playbook/catalog-schema-validator.js +438 -0
- package/dist/playbook/catalog-schema-validator.js.map +1 -0
- package/dist/playbook/import-mapper.js +41 -0
- package/dist/playbook/import-mapper.js.map +1 -1
- package/dist/playbook/manifest.js +5 -0
- package/dist/playbook/manifest.js.map +1 -1
- package/dist/playbook/playbook-installer.js +17 -1
- package/dist/playbook/playbook-installer.js.map +1 -1
- package/dist/poller/pr-readiness-core.js +257 -93
- package/dist/poller/pr-readiness-core.js.map +1 -1
- package/dist/poller/pr-readiness.js +2 -2
- package/dist/poller/pr-readiness.js.map +1 -1
- package/dist/revisium/inbox.service.js +8 -8
- package/dist/revisium/inbox.service.js.map +1 -1
- package/dist/revisium/playbooks.service.js +116 -21
- package/dist/revisium/playbooks.service.js.map +1 -1
- package/dist/revisium/revisium.module.js +8 -8
- package/dist/revisium/revisium.module.js.map +1 -1
- package/dist/revisium/roles.service.js +3 -1
- package/dist/revisium/roles.service.js.map +1 -1
- package/dist/revisium/run.service.js +9 -8
- package/dist/revisium/run.service.js.map +1 -1
- package/dist/revisium/tokens.js +0 -1
- package/dist/revisium/tokens.js.map +1 -1
- package/dist/run/create-run.js +1 -1
- package/dist/run/create-run.js.map +1 -1
- package/dist/run/inspect-run.js +32 -28
- package/dist/run/inspect-run.js.map +1 -1
- package/dist/run/issue-ref.js +97 -2
- package/dist/run/issue-ref.js.map +1 -1
- package/dist/run/prisma-runtime-data-access.js +619 -0
- package/dist/run/prisma-runtime-data-access.js.map +1 -0
- package/dist/run/run-outputs.js +39 -14
- package/dist/run/run-outputs.js.map +1 -1
- package/dist/run/terminal-run-status.js.map +1 -1
- package/dist/runners/integrator.js +657 -56
- package/dist/runners/integrator.js.map +1 -1
- package/dist/runners/worktree.service.js +10 -8
- package/dist/runners/worktree.service.js.map +1 -1
- package/dist/schema/ajv-errors.js +15 -0
- package/dist/schema/ajv-errors.js.map +1 -0
- package/dist/smoke/isolation.js +7 -16
- package/dist/smoke/isolation.js.map +1 -1
- package/dist/storage/ensure-storage.js +117 -0
- package/dist/storage/ensure-storage.js.map +1 -0
- package/dist/storage/revo-database.js +39 -0
- package/dist/storage/revo-database.js.map +1 -0
- package/dist/storage/revo-prisma.service.js +31 -0
- package/dist/storage/revo-prisma.service.js.map +1 -0
- package/dist/storage/revo-storage.module.js +18 -0
- package/dist/storage/revo-storage.module.js.map +1 -0
- package/dist/task-control-plane/pr-readiness.service.js +5 -2
- package/dist/task-control-plane/pr-readiness.service.js.map +1 -1
- package/dist/task-control-plane/run-watch.service.js +3 -6
- package/dist/task-control-plane/run-watch.service.js.map +1 -1
- package/dist/task-control-plane/task-control-plane-api.service.js +225 -43
- package/dist/task-control-plane/task-control-plane-api.service.js.map +1 -1
- package/dist/testing/runtime-data-access.js +175 -0
- package/dist/testing/runtime-data-access.js.map +1 -0
- package/dist/worker/build-context.js +178 -2
- package/dist/worker/build-context.js.map +1 -1
- package/dist/worker/claude-code-runner.js +22 -3
- package/dist/worker/claude-code-runner.js.map +1 -1
- package/dist/worker/codex-runner.js +6 -1
- package/dist/worker/codex-runner.js.map +1 -1
- package/package.json +20 -7
- package/prisma/migrations/20260707080500_initial_schema/migration.sql +259 -0
- package/prisma/migrations/20260707103000_prisma_runtime_tables/migration.sql +235 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +339 -0
- package/prisma.config.ts +11 -0
- package/dist/cli/commands/revisium-helpers.js.map +0 -1
- package/dist/control-plane/client-transport.js +0 -168
- package/dist/control-plane/client-transport.js.map +0 -1
- package/dist/engine/ensure-postgres.js +0 -46
- package/dist/engine/ensure-postgres.js.map +0 -1
- package/dist/host/ensure-revisium.js +0 -135
- package/dist/host/ensure-revisium.js.map +0 -1
- package/revisium.config.json +0 -10
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { accessSync, constants } from 'node:fs';
|
|
3
|
+
import { defaultIssueAction, hasClosingIssueReference, hasIssueRefToken } from '../run/issue-ref.js';
|
|
3
4
|
const GH_EXECUTABLE_CANDIDATES = ['/opt/homebrew/bin/gh', '/usr/local/bin/gh', '/usr/bin/gh'];
|
|
4
5
|
function resolveGhExecutable() {
|
|
5
6
|
for (const candidate of GH_EXECUTABLE_CANDIDATES) {
|
|
@@ -93,46 +94,96 @@ function parseGhJson(raw, label) {
|
|
|
93
94
|
}
|
|
94
95
|
}
|
|
95
96
|
const NOT_FOUND_RE = /could not resolve|could not find|no pull requests? found|not found/i;
|
|
96
|
-
function
|
|
97
|
+
function listPrsByBranch(repo, headBranch, state, execGh) {
|
|
97
98
|
const raw = execGh([
|
|
98
|
-
'pr', 'list', '--repo', repo, '--head', headBranch, '--state',
|
|
99
|
+
'pr', 'list', '--repo', repo, '--head', headBranch, '--state', state,
|
|
99
100
|
'--json', 'number,baseRefName,state',
|
|
100
101
|
]);
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (onBase.length === 1)
|
|
107
|
-
return { pr_number: onBase[0].number };
|
|
108
|
-
const candidates = onBase.map((p) => p.number).join(', ');
|
|
102
|
+
const stateLabel = state === 'all' ? ' --state all' : '';
|
|
103
|
+
return parseGhJson(raw, `pr list --head ${headBranch}${stateLabel}`);
|
|
104
|
+
}
|
|
105
|
+
function ambiguousBranchPrs(scope, matches, headBranch, baseBranch) {
|
|
106
|
+
const candidates = matches.map((p) => p.number).join(', ');
|
|
109
107
|
return {
|
|
110
108
|
needsHuman: true,
|
|
111
|
-
lesson: `Ambiguous: ${
|
|
109
|
+
lesson: `Ambiguous: ${matches.length} ${scope} PRs for head branch "${headBranch}" targeting base "${baseBranch}" - candidates #${candidates} - manual review needed`,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function resolvePrByBranch(repo, headBranch, baseBranch, execGh) {
|
|
113
|
+
const openPrs = listPrsByBranch(repo, headBranch, 'open', execGh);
|
|
114
|
+
const openOnBase = openPrs.filter((p) => p.baseRefName === baseBranch);
|
|
115
|
+
if (openOnBase.length === 1)
|
|
116
|
+
return { kind: 'open', prNumber: openOnBase[0].number };
|
|
117
|
+
if (openOnBase.length > 1)
|
|
118
|
+
return ambiguousBranchPrs('open', openOnBase, headBranch, baseBranch);
|
|
119
|
+
const allPrs = listPrsByBranch(repo, headBranch, 'all', execGh);
|
|
120
|
+
const terminalOnBase = allPrs.filter((p) => p.baseRefName === baseBranch && p.state.toUpperCase() !== 'OPEN');
|
|
121
|
+
if (terminalOnBase.length === 1) {
|
|
122
|
+
const pr = terminalOnBase[0];
|
|
123
|
+
const state = pr.state.toUpperCase();
|
|
124
|
+
if (state === 'MERGED')
|
|
125
|
+
return { kind: 'merged', prNumber: pr.number };
|
|
126
|
+
if (state === 'CLOSED')
|
|
127
|
+
return { kind: 'closed', prNumber: pr.number };
|
|
128
|
+
return {
|
|
129
|
+
needsHuman: true,
|
|
130
|
+
lesson: `Unexpected terminal PR state ${pr.state} for head branch "${headBranch}" targeting base "${baseBranch}" - manual review needed`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (terminalOnBase.length > 1)
|
|
134
|
+
return ambiguousBranchPrs('terminal', terminalOnBase, headBranch, baseBranch);
|
|
135
|
+
return {
|
|
136
|
+
needsHuman: true,
|
|
137
|
+
lesson: `No open PR for head branch "${headBranch}" with base "${baseBranch}" in ${repo} - manual review needed`,
|
|
112
138
|
};
|
|
113
139
|
}
|
|
114
140
|
function fetchPrView(prNumber, repo, execGh) {
|
|
115
141
|
const raw = execGh([
|
|
116
142
|
'pr', 'view', String(prNumber), '--repo', repo,
|
|
117
|
-
'--json', 'number,url,state,isDraft,baseRefName,headRefName,headRefOid,title,statusCheckRollup,mergeStateStatus,reviewDecision,mergeable',
|
|
143
|
+
'--json', 'number,url,state,isDraft,baseRefName,headRefName,headRefOid,title,statusCheckRollup,mergeStateStatus,reviewDecision,mergeable,closingIssuesReferences',
|
|
118
144
|
]);
|
|
119
145
|
return parseGhJson(raw, `pr view #${prNumber}`);
|
|
120
146
|
}
|
|
147
|
+
function terminalBranchResult(result, headBranch, prView) {
|
|
148
|
+
if (result.kind === 'merged')
|
|
149
|
+
return { kind: 'merged', prNumber: result.prNumber, prView };
|
|
150
|
+
return {
|
|
151
|
+
kind: 'needsHuman',
|
|
152
|
+
verdict: 'closed',
|
|
153
|
+
lesson: `pr_closed_externally: PR #${result.prNumber} for "${headBranch}" was closed without merging - manual review needed`,
|
|
154
|
+
prNumber: result.prNumber,
|
|
155
|
+
prView,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
function resolveBranchForOpenPr(input, headBranch, baseBranch, execGh) {
|
|
159
|
+
const result = resolvePrByBranch(input.repo, headBranch, baseBranch, execGh);
|
|
160
|
+
if ('needsHuman' in result) {
|
|
161
|
+
return { kind: 'needsHuman', verdict: 'unresolved', lesson: result.lesson };
|
|
162
|
+
}
|
|
163
|
+
if (result.kind === 'open')
|
|
164
|
+
return { kind: 'open', prNumber: result.prNumber };
|
|
165
|
+
return terminalBranchResult(result, headBranch, fetchPrView(result.prNumber, input.repo, execGh));
|
|
166
|
+
}
|
|
167
|
+
function canRecoverFromMissingPr(headBranch, resolvedFromBranch, err) {
|
|
168
|
+
return Boolean(headBranch && !resolvedFromBranch && NOT_FOUND_RE.test(String(err)));
|
|
169
|
+
}
|
|
121
170
|
function handleClosedPr(prNumber, resolvedFromBranch, input, baseBranch, execGh, prView) {
|
|
122
171
|
if (!input.headBranch || resolvedFromBranch) {
|
|
123
|
-
return { kind: 'needsHuman', verdict: 'closed', lesson: `PR #${prNumber} was closed without merging - manual review needed`, prNumber, prView };
|
|
172
|
+
return { kind: 'needsHuman', verdict: 'closed', lesson: `pr_closed_externally: PR #${prNumber} was closed without merging - manual review needed`, prNumber, prView };
|
|
124
173
|
}
|
|
125
174
|
const r = resolvePrByBranch(input.repo, input.headBranch, baseBranch, execGh);
|
|
126
175
|
if ('needsHuman' in r) {
|
|
127
|
-
return { kind: 'needsHuman', verdict: 'closed', lesson: `PR #${prNumber} was closed; ${r.lesson}`, prNumber, prView };
|
|
176
|
+
return { kind: 'needsHuman', verdict: 'closed', lesson: `pr_closed_externally: PR #${prNumber} was closed; ${r.lesson}`, prNumber, prView };
|
|
128
177
|
}
|
|
129
|
-
const newPrView = fetchPrView(r.
|
|
178
|
+
const newPrView = fetchPrView(r.prNumber, input.repo, execGh);
|
|
179
|
+
if (r.kind !== 'open')
|
|
180
|
+
return terminalBranchResult(r, input.headBranch, newPrView);
|
|
130
181
|
if (newPrView.state === 'MERGED')
|
|
131
|
-
return { kind: 'merged', prNumber: r.
|
|
182
|
+
return { kind: 'merged', prNumber: r.prNumber, prView: newPrView };
|
|
132
183
|
if (newPrView.state === 'CLOSED') {
|
|
133
|
-
return { kind: 'needsHuman', verdict: 'closed', lesson: `PR #${r.
|
|
184
|
+
return { kind: 'needsHuman', verdict: 'closed', lesson: `pr_closed_externally: PR #${r.prNumber} (recovered via "${input.headBranch}") is also closed - manual review needed`, prNumber: r.prNumber, prView: newPrView };
|
|
134
185
|
}
|
|
135
|
-
return { kind: 'open', prNumber: r.
|
|
186
|
+
return { kind: 'open', prNumber: r.prNumber, prView: newPrView };
|
|
136
187
|
}
|
|
137
188
|
function resolveOpenPr(input, baseBranch, execGh) {
|
|
138
189
|
let prNumber = input.prNumber;
|
|
@@ -141,10 +192,10 @@ function resolveOpenPr(input, baseBranch, execGh) {
|
|
|
141
192
|
if (!input.headBranch) {
|
|
142
193
|
return { kind: 'needsHuman', verdict: 'unresolved', lesson: 'ci-poller step has neither pr_number nor head_branch - cannot identify a PR to watch' };
|
|
143
194
|
}
|
|
144
|
-
const
|
|
145
|
-
if ('
|
|
146
|
-
return
|
|
147
|
-
prNumber =
|
|
195
|
+
const resolved = resolveBranchForOpenPr(input, input.headBranch, baseBranch, execGh);
|
|
196
|
+
if (resolved.kind !== 'open')
|
|
197
|
+
return resolved;
|
|
198
|
+
prNumber = resolved.prNumber;
|
|
148
199
|
resolvedFromBranch = true;
|
|
149
200
|
}
|
|
150
201
|
let prView;
|
|
@@ -152,13 +203,13 @@ function resolveOpenPr(input, baseBranch, execGh) {
|
|
|
152
203
|
prView = fetchPrView(prNumber, input.repo, execGh);
|
|
153
204
|
}
|
|
154
205
|
catch (err) {
|
|
155
|
-
|
|
206
|
+
const recoveryHeadBranch = input.headBranch;
|
|
207
|
+
if (!canRecoverFromMissingPr(recoveryHeadBranch, resolvedFromBranch, err))
|
|
156
208
|
throw err;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
prNumber = r.pr_number;
|
|
209
|
+
const resolved = resolveBranchForOpenPr(input, recoveryHeadBranch, baseBranch, execGh);
|
|
210
|
+
if (resolved.kind !== 'open')
|
|
211
|
+
return resolved;
|
|
212
|
+
prNumber = resolved.prNumber;
|
|
162
213
|
resolvedFromBranch = true;
|
|
163
214
|
prView = fetchPrView(prNumber, input.repo, execGh);
|
|
164
215
|
}
|
|
@@ -171,7 +222,7 @@ function resolveOpenPr(input, baseBranch, execGh) {
|
|
|
171
222
|
function isTerminal(item) {
|
|
172
223
|
if (item.__typename === 'CheckRun')
|
|
173
224
|
return item.status === 'COMPLETED';
|
|
174
|
-
return item.state !== 'PENDING';
|
|
225
|
+
return item.state !== 'EXPECTED' && item.state !== 'PENDING';
|
|
175
226
|
}
|
|
176
227
|
function isPassed(item) {
|
|
177
228
|
if (item.__typename === 'CheckRun') {
|
|
@@ -193,12 +244,15 @@ function checkResult(item) {
|
|
|
193
244
|
function isBot(user) {
|
|
194
245
|
return user?.type === 'Bot';
|
|
195
246
|
}
|
|
196
|
-
export
|
|
197
|
-
|
|
247
|
+
export const GITHUB_CHECK_ROLLUP_UNAVAILABLE = 'GitHub check rollup unavailable (re-polling for checks)';
|
|
248
|
+
export function collectCiChecks(items, opts = {}) {
|
|
249
|
+
const pending = (opts.emptyIsPending === true && items.length === 0) || items.some((item) => !isTerminal(item));
|
|
198
250
|
const ci_passed = !pending && items.every((item) => isPassed(item));
|
|
199
251
|
const pendingNames = items
|
|
200
252
|
.filter((item) => !isTerminal(item))
|
|
201
253
|
.map(checkName);
|
|
254
|
+
if (opts.emptyIsPending === true && items.length === 0)
|
|
255
|
+
pendingNames.push(GITHUB_CHECK_ROLLUP_UNAVAILABLE);
|
|
202
256
|
const checks = items.map((item) => ({ name: checkName(item), result: checkResult(item) }));
|
|
203
257
|
return { pending, ci_passed, checks, pendingNames };
|
|
204
258
|
}
|
|
@@ -210,36 +264,78 @@ function asRecord(value) {
|
|
|
210
264
|
return null;
|
|
211
265
|
return value;
|
|
212
266
|
}
|
|
267
|
+
function firstNumber(...values) {
|
|
268
|
+
for (const value of values) {
|
|
269
|
+
if (typeof value === 'number')
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
213
274
|
// gh api graphql wraps responses in {"data":{...}}; unwrap tolerantly so unit fixtures still work.
|
|
214
275
|
function unwrapGraphqlData(raw) {
|
|
215
276
|
const top = asRecord(raw);
|
|
216
277
|
const data = top ? asRecord(top['data']) : null;
|
|
217
278
|
return data ?? raw;
|
|
218
279
|
}
|
|
280
|
+
function requireGraphqlRecord(value, context, path) {
|
|
281
|
+
const record = asRecord(value);
|
|
282
|
+
if (!record)
|
|
283
|
+
throw new Error(`invalid GraphQL shape in ${context} response: missing ${path}`);
|
|
284
|
+
return record;
|
|
285
|
+
}
|
|
286
|
+
function requireGraphqlArray(value, context, path) {
|
|
287
|
+
if (!Array.isArray(value))
|
|
288
|
+
throw new Error(`invalid GraphQL shape in ${context} response: missing ${path}`);
|
|
289
|
+
return value;
|
|
290
|
+
}
|
|
219
291
|
function mapReviewThreads(raw) {
|
|
220
|
-
const
|
|
221
|
-
const
|
|
222
|
-
const
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
292
|
+
const context = 'reviewThreads';
|
|
293
|
+
const root = requireGraphqlRecord(unwrapGraphqlData(raw), context, 'root');
|
|
294
|
+
const repository = requireGraphqlRecord(root['repository'], context, 'repository');
|
|
295
|
+
const pullRequest = requireGraphqlRecord(repository['pullRequest'], context, 'repository.pullRequest');
|
|
296
|
+
const reviewThreads = requireGraphqlRecord(pullRequest['reviewThreads'], context, 'repository.pullRequest.reviewThreads');
|
|
297
|
+
const nodes = requireGraphqlArray(reviewThreads['nodes'], context, 'repository.pullRequest.reviewThreads.nodes');
|
|
298
|
+
const pageInfo = asRecord(reviewThreads['pageInfo']);
|
|
299
|
+
const hasNextPage = pageInfo?.['hasNextPage'] === true;
|
|
300
|
+
return { threads: nodes.flatMap((node) => {
|
|
301
|
+
const thread = asRecord(node);
|
|
302
|
+
if (!thread)
|
|
303
|
+
return [];
|
|
304
|
+
const comments = asRecord(thread.comments);
|
|
305
|
+
const commentNodes = Array.isArray(comments?.['nodes']) ? comments.nodes.flatMap((comment) => {
|
|
306
|
+
const record = asRecord(comment);
|
|
307
|
+
return record ? [record] : [];
|
|
308
|
+
}) : [];
|
|
309
|
+
const firstComment = commentNodes[0] ?? null;
|
|
310
|
+
const author = asRecord(firstComment?.['author']);
|
|
311
|
+
const path = asStr(thread.path) || undefined;
|
|
312
|
+
const line = firstNumber(thread.line, firstComment?.['line']);
|
|
313
|
+
const originalLine = firstNumber(thread.originalLine, firstComment?.['originalLine']);
|
|
314
|
+
return [{
|
|
315
|
+
item: {
|
|
316
|
+
id: asStr(thread.id),
|
|
317
|
+
isResolved: Boolean(thread.isResolved),
|
|
318
|
+
isOutdated: Boolean(thread.isOutdated),
|
|
319
|
+
path,
|
|
320
|
+
line,
|
|
321
|
+
originalLine,
|
|
322
|
+
author: asStr(author?.['login']) || undefined,
|
|
323
|
+
body: compactBody(asStr(firstComment?.['body'])),
|
|
324
|
+
url: asStr(firstComment?.['url']) || undefined,
|
|
325
|
+
},
|
|
326
|
+
comments: commentNodes.map((comment) => {
|
|
327
|
+
const commentAuthor = asRecord(comment['author']);
|
|
328
|
+
return {
|
|
329
|
+
path,
|
|
330
|
+
line: firstNumber(comment['line'], thread.line),
|
|
331
|
+
originalLine: firstNumber(comment['originalLine'], thread.originalLine),
|
|
332
|
+
author: asStr(commentAuthor?.['login']) || undefined,
|
|
333
|
+
body: asStr(comment['body']),
|
|
334
|
+
url: asStr(comment['url']) || undefined,
|
|
335
|
+
};
|
|
336
|
+
}),
|
|
337
|
+
}];
|
|
338
|
+
}), hasNextPage };
|
|
243
339
|
}
|
|
244
340
|
function splitRepo(repo) {
|
|
245
341
|
const [owner, name] = repo.split('/');
|
|
@@ -251,7 +347,7 @@ function fetchReviewThreads(repo, prNumber, execGh) {
|
|
|
251
347
|
const { owner, name } = splitRepo(repo);
|
|
252
348
|
const raw = execGh([
|
|
253
349
|
'api', 'graphql',
|
|
254
|
-
'-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:
|
|
350
|
+
'-f', 'query=query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){reviewThreads(first:100){pageInfo{hasNextPage}nodes{id,isResolved,isOutdated,path,line,originalLine,comments(first:100){nodes{body,url,line,originalLine,author{login}}}}}}}}',
|
|
255
351
|
'-f', `owner=${owner}`,
|
|
256
352
|
'-f', `name=${name}`,
|
|
257
353
|
'-F', `number=${prNumber}`,
|
|
@@ -259,12 +355,15 @@ function fetchReviewThreads(repo, prNumber, execGh) {
|
|
|
259
355
|
return mapReviewThreads(parseGhJson(raw, `review-threads #${prNumber}`));
|
|
260
356
|
}
|
|
261
357
|
function mapRequiredCheckNames(raw) {
|
|
262
|
-
const
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
358
|
+
const context = 'required-checks';
|
|
359
|
+
const root = requireGraphqlRecord(unwrapGraphqlData(raw), context, 'root');
|
|
360
|
+
const repository = requireGraphqlRecord(root['repository'], context, 'repository');
|
|
361
|
+
const pullRequest = requireGraphqlRecord(repository['pullRequest'], context, 'repository.pullRequest');
|
|
362
|
+
if (pullRequest['statusCheckRollup'] === null)
|
|
363
|
+
throw new Error(GITHUB_CHECK_ROLLUP_UNAVAILABLE);
|
|
364
|
+
const rollup = requireGraphqlRecord(pullRequest['statusCheckRollup'], context, 'repository.pullRequest.statusCheckRollup');
|
|
365
|
+
const contexts = requireGraphqlRecord(rollup['contexts'], context, 'repository.pullRequest.statusCheckRollup.contexts');
|
|
366
|
+
const nodes = requireGraphqlArray(contexts['nodes'], context, 'repository.pullRequest.statusCheckRollup.contexts.nodes');
|
|
268
367
|
const required = new Set();
|
|
269
368
|
for (const node of nodes) {
|
|
270
369
|
const ctx = asRecord(node);
|
|
@@ -287,13 +386,56 @@ export function fetchRequiredCheckNames(repo, prNumber, execGh) {
|
|
|
287
386
|
]);
|
|
288
387
|
return mapRequiredCheckNames(parseGhJson(raw, `required-checks #${prNumber}`));
|
|
289
388
|
}
|
|
389
|
+
function reviewLineOf(item) {
|
|
390
|
+
return firstNumber(item.line, item.originalLine, item.original_line);
|
|
391
|
+
}
|
|
392
|
+
function preciseReviewLocationOf(item) {
|
|
393
|
+
const line = reviewLineOf(item);
|
|
394
|
+
return item.path && typeof line === 'number' ? `${item.path}:${line}` : '';
|
|
395
|
+
}
|
|
396
|
+
function feedbackMatchEvidence(item) {
|
|
397
|
+
const location = preciseReviewLocationOf(item);
|
|
398
|
+
if (location === '')
|
|
399
|
+
return null;
|
|
400
|
+
const url = item.html_url ?? item.url ?? '';
|
|
401
|
+
const author = (item.author ?? item.user?.login ?? '').toLowerCase();
|
|
402
|
+
const body = compactBody(item.body);
|
|
403
|
+
const contentKey = author !== '' && body !== '' ? [author, body].join('\0') : '';
|
|
404
|
+
return { location, url, contentKey };
|
|
405
|
+
}
|
|
406
|
+
function isStaleThreadFeedback(comment, staleThreadEvidence) {
|
|
407
|
+
const commentEvidence = feedbackMatchEvidence(comment);
|
|
408
|
+
if (!commentEvidence || !staleThreadEvidence)
|
|
409
|
+
return false;
|
|
410
|
+
return staleThreadEvidence
|
|
411
|
+
.filter((staleEvidence) => staleEvidence.location === commentEvidence.location)
|
|
412
|
+
.some((staleEvidence) => {
|
|
413
|
+
if (commentEvidence.url !== '' && staleEvidence.url !== '') {
|
|
414
|
+
return commentEvidence.url === staleEvidence.url;
|
|
415
|
+
}
|
|
416
|
+
return commentEvidence.contentKey !== '' && commentEvidence.contentKey === staleEvidence.contentKey;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
290
419
|
function collectReviewThreads(input, prNumber, execGh) {
|
|
291
|
-
const
|
|
292
|
-
|
|
420
|
+
const fetched = input.includeReviewThreads === false
|
|
421
|
+
? { threads: [], hasNextPage: false }
|
|
422
|
+
: fetchReviewThreads(input.repo, prNumber, execGh);
|
|
423
|
+
const threads = fetched.threads;
|
|
424
|
+
const unresolved = threads
|
|
425
|
+
.filter((thread) => !thread.item.isResolved && !thread.item.isOutdated)
|
|
426
|
+
.map((thread) => thread.item);
|
|
293
427
|
return {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
428
|
+
reviewThreads: {
|
|
429
|
+
included: input.includeReviewThreads !== false,
|
|
430
|
+
unresolvedCount: unresolved.length,
|
|
431
|
+
items: unresolved,
|
|
432
|
+
...(fetched.hasNextPage ? { truncated: true } : {}),
|
|
433
|
+
},
|
|
434
|
+
staleThreadEvidence: threads
|
|
435
|
+
.filter((thread) => thread.item.isResolved || thread.item.isOutdated)
|
|
436
|
+
.flatMap((thread) => thread.comments)
|
|
437
|
+
.map(feedbackMatchEvidence)
|
|
438
|
+
.filter((evidence) => evidence !== null),
|
|
297
439
|
};
|
|
298
440
|
}
|
|
299
441
|
function fetchComments(input, prNumber, execGh) {
|
|
@@ -419,7 +561,7 @@ function compactCheckLists(checks) {
|
|
|
419
561
|
const pass = [];
|
|
420
562
|
const fail = [];
|
|
421
563
|
for (const check of checks) {
|
|
422
|
-
if (['QUEUED', 'IN_PROGRESS', 'PENDING'].includes(check.result)) {
|
|
564
|
+
if (['EXPECTED', 'QUEUED', 'IN_PROGRESS', 'PENDING'].includes(check.result)) {
|
|
423
565
|
pending.push(check.name);
|
|
424
566
|
continue;
|
|
425
567
|
}
|
|
@@ -467,7 +609,7 @@ function providerState(checks, botComments) {
|
|
|
467
609
|
}
|
|
468
610
|
function locationOf(item) {
|
|
469
611
|
const path = item.path ?? item.component ?? '';
|
|
470
|
-
return item.line ? `${path}:${item.line}` : path;
|
|
612
|
+
return typeof item.line === 'number' ? `${path}:${item.line}` : path;
|
|
471
613
|
}
|
|
472
614
|
function providerWaitFeedback(state, botComments) {
|
|
473
615
|
const codeRabbit = state.codeRabbit;
|
|
@@ -482,9 +624,6 @@ function providerWaitFeedback(state, botComments) {
|
|
|
482
624
|
resumeAfter: extractResumeHint(botComments),
|
|
483
625
|
}];
|
|
484
626
|
}
|
|
485
|
-
function escapeRegExp(value) {
|
|
486
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
487
|
-
}
|
|
488
627
|
function issueRefTitleTag(issueRef, repo) {
|
|
489
628
|
return issueRef.repo.toLowerCase() === repo.toLowerCase()
|
|
490
629
|
? `#${issueRef.number}`
|
|
@@ -493,25 +632,15 @@ function issueRefTitleTag(issueRef, repo) {
|
|
|
493
632
|
function hasIssueBranchToken(head, issueRef) {
|
|
494
633
|
return new RegExp(`(?:^|[/-])issue-${issueRef.number}(?:-|$)`).test(head ?? '');
|
|
495
634
|
}
|
|
496
|
-
function
|
|
497
|
-
|
|
498
|
-
const boundaryAfter = String.raw `(?=$|[^A-Za-z0-9_-])`;
|
|
499
|
-
const qualified = new RegExp(`${boundaryBefore}${escapeRegExp(`${issueRef.repo}#${issueRef.number}`)}${boundaryAfter}`, 'i');
|
|
500
|
-
if (qualified.test(title ?? ''))
|
|
501
|
-
return true;
|
|
502
|
-
if (issueRef.repo.toLowerCase() !== repo.toLowerCase())
|
|
503
|
-
return false;
|
|
504
|
-
return new RegExp(`${boundaryBefore}#${issueRef.number}${boundaryAfter}`).test(title ?? '');
|
|
505
|
-
}
|
|
506
|
-
function issueLinkageFeedback(issueRef, repo, pr) {
|
|
507
|
-
if (!issueRef)
|
|
635
|
+
function issueLinkageFeedback(issueRef, issueAction, repo, pr) {
|
|
636
|
+
if (!issueRef || issueAction === 'none')
|
|
508
637
|
return [];
|
|
509
638
|
const issueBranch = `issue-${issueRef.number}`;
|
|
510
639
|
const issueTag = issueRefTitleTag(issueRef, repo);
|
|
511
640
|
const missing = [];
|
|
512
641
|
if (!hasIssueBranchToken(pr.head, issueRef))
|
|
513
642
|
missing.push(`branch missing ${issueBranch}`);
|
|
514
|
-
if (!
|
|
643
|
+
if (!hasIssueRefToken(pr.title, issueRef, repo))
|
|
515
644
|
missing.push(`title missing ${issueTag}`);
|
|
516
645
|
if (missing.length === 0)
|
|
517
646
|
return [];
|
|
@@ -521,6 +650,16 @@ function issueLinkageFeedback(issueRef, repo, pr) {
|
|
|
521
650
|
evidence: `${missing.join('; ')} (head=${pr.head ?? ''}, title=${pr.title ?? ''})`,
|
|
522
651
|
}];
|
|
523
652
|
}
|
|
653
|
+
function issueCloseFeedback(issueRef, issueAction, repo, prView) {
|
|
654
|
+
if (issueAction !== 'close' || !issueRef || hasClosingIssueReference(prView?.closingIssuesReferences, issueRef))
|
|
655
|
+
return [];
|
|
656
|
+
const issueTag = issueRefTitleTag(issueRef, repo);
|
|
657
|
+
return [{
|
|
658
|
+
source: 'issue_auto_close',
|
|
659
|
+
summary: `PR does not auto-close ${issueTag}`,
|
|
660
|
+
evidence: `GitHub closingIssuesReferences does not include ${issueTag}`,
|
|
661
|
+
}];
|
|
662
|
+
}
|
|
524
663
|
// Bot logins whose comments are purely informational and safe to suppress.
|
|
525
664
|
// Anything not in this list surfaces in developerFixes to avoid silent suppression.
|
|
526
665
|
const KNOWN_INFORMATIONAL_BOTS = new Set([
|
|
@@ -554,6 +693,13 @@ function buildFeedback(input) {
|
|
|
554
693
|
author: review.user?.login ?? '',
|
|
555
694
|
evidence: review.state,
|
|
556
695
|
})),
|
|
696
|
+
...(input.reviewThreads.truncated
|
|
697
|
+
? [{
|
|
698
|
+
source: 'review_threads_truncated',
|
|
699
|
+
summary: 'Review thread data is incomplete; fetch all review thread pages before declaring the PR ready.',
|
|
700
|
+
evidence: 'GitHub reviewThreads pageInfo.hasNextPage=true',
|
|
701
|
+
}]
|
|
702
|
+
: []),
|
|
557
703
|
...input.reviewThreads.items.map((thread) => ({
|
|
558
704
|
source: 'review_thread',
|
|
559
705
|
summary: thread.body,
|
|
@@ -564,6 +710,7 @@ function buildFeedback(input) {
|
|
|
564
710
|
...codeRabbitActionableFeedback(input.coderabbitReviews ?? [], input.botComments, new Set(input.reviewThreads.items.map((thread) => locationOf(thread)).filter((loc) => loc !== ''))),
|
|
565
711
|
...input.botComments
|
|
566
712
|
.filter((comment) => !isCodeRabbit(comment.user) && !KNOWN_INFORMATIONAL_BOTS.has(comment.user?.login ?? ''))
|
|
713
|
+
.filter((comment) => !isStaleThreadFeedback(comment, input.staleThreadEvidence))
|
|
567
714
|
.map((comment) => ({
|
|
568
715
|
source: 'bot_comment',
|
|
569
716
|
summary: compactBody(comment.body),
|
|
@@ -585,7 +732,8 @@ function buildFeedback(input) {
|
|
|
585
732
|
? [{ source: 'github_review_decision', summary: `Review decision is ${input.reviewDecision}` }]
|
|
586
733
|
: []),
|
|
587
734
|
...(input.sonar.unavailable ? [{ source: 'sonar', summary: 'Sonar was configured but unavailable.' }] : []),
|
|
588
|
-
...issueLinkageFeedback(input.issueRef, input.repo, input.pr ?? {}),
|
|
735
|
+
...issueLinkageFeedback(input.issueRef, input.issueAction, input.repo, input.pr ?? {}),
|
|
736
|
+
...issueCloseFeedback(input.issueRef, input.issueAction, input.repo, input.prView),
|
|
589
737
|
];
|
|
590
738
|
const ignoredNoise = input.botComments
|
|
591
739
|
.filter((comment) => !isCodeRabbit(comment.user) && KNOWN_INFORMATIONAL_BOTS.has(comment.user?.login ?? ''))
|
|
@@ -642,7 +790,9 @@ function buildEmptyFeedback(input) {
|
|
|
642
790
|
humanComments: [],
|
|
643
791
|
botComments: [],
|
|
644
792
|
issueRef: input.issueRef,
|
|
645
|
-
|
|
793
|
+
issueAction: input.issueAction,
|
|
794
|
+
repo: input.repo ?? '',
|
|
795
|
+
prView: input.prView,
|
|
646
796
|
});
|
|
647
797
|
}
|
|
648
798
|
function buildMergedReadiness(input, resolved) {
|
|
@@ -658,7 +808,7 @@ function buildMergedReadiness(input, resolved) {
|
|
|
658
808
|
sonar: emptySonar(sonarConfigured),
|
|
659
809
|
nextAction: 'none',
|
|
660
810
|
evidence: [`PR #${resolved.prNumber} is merged.`],
|
|
661
|
-
feedback: buildEmptyFeedback({ sonarConfigured, includeReviewThreads }),
|
|
811
|
+
feedback: buildEmptyFeedback({ sonarConfigured, includeReviewThreads, issueRef: input.issueRef, issueAction: input.issueAction, repo: input.repo, prView: resolved.prView }),
|
|
662
812
|
ciSummary: { ...emptyCiSummary(true), ...(input.issueRef ? { issueRef: input.issueRef } : {}) },
|
|
663
813
|
...(input.issueRef ? { issueRef: input.issueRef } : {}),
|
|
664
814
|
};
|
|
@@ -666,7 +816,7 @@ function buildMergedReadiness(input, resolved) {
|
|
|
666
816
|
function buildNeedsHumanReadiness(input, resolved) {
|
|
667
817
|
const includeReviewThreads = input.includeReviewThreads !== false;
|
|
668
818
|
const sonarConfigured = Boolean(input.sonarProject);
|
|
669
|
-
const feedback = buildEmptyFeedback({ sonarConfigured, includeReviewThreads });
|
|
819
|
+
const feedback = buildEmptyFeedback({ sonarConfigured, includeReviewThreads, issueRef: input.issueRef, issueAction: input.issueAction, repo: input.repo, prView: resolved.prView });
|
|
670
820
|
return {
|
|
671
821
|
verdict: resolved.verdict === 'closed' ? 'closed' : 'needs_human',
|
|
672
822
|
pr: resolved.prNumber ? prFromView(resolved.prNumber, resolved.prView) : emptyPr(input.prNumber, resolved.verdict),
|
|
@@ -683,6 +833,9 @@ function buildNeedsHumanReadiness(input, resolved) {
|
|
|
683
833
|
};
|
|
684
834
|
}
|
|
685
835
|
function buildWaitingReadiness(input) {
|
|
836
|
+
const checkLists = input.checkLists.list.length === 0 && input.ci.pendingNames.length > 0
|
|
837
|
+
? { ...input.checkLists, pending: input.ci.pendingNames }
|
|
838
|
+
: input.checkLists;
|
|
686
839
|
const pendingCodeRabbit = input.ci.pendingNames.find((name) => name.toLowerCase().includes('coderabbit'));
|
|
687
840
|
const pendingProviderState = pendingCodeRabbit
|
|
688
841
|
? {
|
|
@@ -695,7 +848,7 @@ function buildWaitingReadiness(input) {
|
|
|
695
848
|
}
|
|
696
849
|
: {};
|
|
697
850
|
const feedback = buildFeedback({
|
|
698
|
-
checks:
|
|
851
|
+
checks: checkLists,
|
|
699
852
|
providerState: pendingProviderState,
|
|
700
853
|
sonar: emptySonar(input.sonarConfigured),
|
|
701
854
|
reviewDecision: input.prView.reviewDecision ?? '',
|
|
@@ -704,8 +857,10 @@ function buildWaitingReadiness(input) {
|
|
|
704
857
|
humanComments: [],
|
|
705
858
|
botComments: [],
|
|
706
859
|
issueRef: input.issueRef,
|
|
860
|
+
issueAction: input.issueAction,
|
|
707
861
|
repo: input.repo,
|
|
708
862
|
pr: { head: input.prView.headRefName, title: input.prView.title },
|
|
863
|
+
prView: input.prView,
|
|
709
864
|
});
|
|
710
865
|
const providerWait = pendingCodeRabbit
|
|
711
866
|
? [{
|
|
@@ -720,7 +875,7 @@ function buildWaitingReadiness(input) {
|
|
|
720
875
|
return {
|
|
721
876
|
verdict: 'waiting',
|
|
722
877
|
pr: prFromView(input.prNumber, input.prView),
|
|
723
|
-
checks:
|
|
878
|
+
checks: checkLists,
|
|
724
879
|
reviewDecision: input.prView.reviewDecision ?? '',
|
|
725
880
|
reviewThreads: input.reviewThreads,
|
|
726
881
|
providerState: pendingProviderState,
|
|
@@ -755,6 +910,7 @@ function readinessAction(input) {
|
|
|
755
910
|
return { verdict: 'ready', nextAction: 'ready_for_merge_gate' };
|
|
756
911
|
}
|
|
757
912
|
export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSonar = defaultFetchSonar) {
|
|
913
|
+
input = { ...input, issueAction: input.issueAction ?? defaultIssueAction(input.issueRef) };
|
|
758
914
|
const baseBranch = input.baseBranch ?? 'master';
|
|
759
915
|
const resolved = resolveOpenPr(input, baseBranch, execGh);
|
|
760
916
|
if (resolved.kind === 'merged') {
|
|
@@ -765,9 +921,9 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
765
921
|
}
|
|
766
922
|
const { prNumber, prView } = resolved;
|
|
767
923
|
const checks = prView.statusCheckRollup ?? [];
|
|
768
|
-
const ci = collectCiChecks(checks);
|
|
924
|
+
const ci = collectCiChecks(checks, { emptyIsPending: prView.statusCheckRollup === null });
|
|
769
925
|
const checkLists = compactCheckLists(ci.checks);
|
|
770
|
-
const reviewThreads = collectReviewThreads(input, prNumber, execGh);
|
|
926
|
+
const { reviewThreads, staleThreadEvidence } = collectReviewThreads(input, prNumber, execGh);
|
|
771
927
|
const sonarConfigured = Boolean(input.sonarProject);
|
|
772
928
|
if (prView.isDraft === true) {
|
|
773
929
|
return buildWaitingReadiness({
|
|
@@ -779,6 +935,7 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
779
935
|
reviewThreads,
|
|
780
936
|
sonarConfigured,
|
|
781
937
|
issueRef: input.issueRef,
|
|
938
|
+
issueAction: input.issueAction,
|
|
782
939
|
evidence: [`PR #${prNumber} is still draft.`],
|
|
783
940
|
isDraft: true,
|
|
784
941
|
});
|
|
@@ -793,6 +950,7 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
793
950
|
reviewThreads,
|
|
794
951
|
sonarConfigured,
|
|
795
952
|
issueRef: input.issueRef,
|
|
953
|
+
issueAction: input.issueAction,
|
|
796
954
|
evidence: ci.pendingNames.length > 0 ? [`Pending checks: ${ci.pendingNames.join(', ')}`] : ['No check rollup entries are registered yet.'],
|
|
797
955
|
});
|
|
798
956
|
}
|
|
@@ -816,10 +974,13 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
816
974
|
humanReviews: comments.human_reviews,
|
|
817
975
|
humanComments: comments.human_comments,
|
|
818
976
|
botComments: comments.bot_comments,
|
|
977
|
+
staleThreadEvidence,
|
|
819
978
|
coderabbitReviews: comments.coderabbit_reviews,
|
|
820
979
|
issueRef: input.issueRef,
|
|
980
|
+
issueAction: input.issueAction,
|
|
821
981
|
repo: input.repo,
|
|
822
982
|
pr: { head: prView.headRefName, title: prView.title },
|
|
983
|
+
prView,
|
|
823
984
|
});
|
|
824
985
|
const ciSummary = {
|
|
825
986
|
ci_passed: ci.ci_passed,
|
|
@@ -848,7 +1009,9 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
848
1009
|
nextAction,
|
|
849
1010
|
evidence: [
|
|
850
1011
|
`PR #${prNumber} state=${prView.state ?? 'unknown'} draft=${Boolean(prView.isDraft)}`,
|
|
851
|
-
|
|
1012
|
+
checkLists.list.length === 0
|
|
1013
|
+
? 'checks: none registered'
|
|
1014
|
+
: `checks pass=${checkLists.pass.length} fail=${checkLists.fail.length} pending=${checkLists.pending.length}`,
|
|
852
1015
|
...(input.issueRef ? [`issueRef ${issueRefTitleTag(input.issueRef, input.repo)} expected in branch and title`] : []),
|
|
853
1016
|
...(providers.codeRabbit?.reason === 'provider_limit' ? ['CodeRabbit provider/rate limit comment detected.'] : []),
|
|
854
1017
|
...(verdict === 'ready' && hasInformationalProviderWait
|
|
@@ -868,6 +1031,7 @@ export function toReadinessInput(input) {
|
|
|
868
1031
|
baseBranch: input.base_branch,
|
|
869
1032
|
sonarProject: input.sonar_project,
|
|
870
1033
|
issueRef: input.issue_ref,
|
|
1034
|
+
issueAction: input.issue_action,
|
|
871
1035
|
includeComments: true,
|
|
872
1036
|
includeReviewThreads: false,
|
|
873
1037
|
};
|