@revisium/orchestrator 0.2.0-alpha.2 → 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 +405 -1065
- 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/inbox/inputs/resolve-gate.input.js +46 -0
- package/dist/api/graphql-api/inbox/inputs/resolve-gate.input.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/audit-validation-helpers.js +31 -0
- package/dist/control-plane/audit-validation-helpers.js.map +1 -0
- 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 +499 -78
- 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/manual-adoption-audit.js +3 -24
- package/dist/control-plane/manual-adoption-audit.js.map +1 -1
- package/dist/control-plane/merge-override-audit.js +44 -0
- package/dist/control-plane/merge-override-audit.js.map +1 -0
- 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 +172 -40
- 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/inbox/commands/impl/resolve-gate.command.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 +12 -11
- package/dist/mcp/mcp-capabilities.js.map +1 -1
- package/dist/mcp/mcp-facade.service.js +45 -5
- package/dist/mcp/mcp-facade.service.js.map +1 -1
- package/dist/mcp/mcp-tools.js +59 -5
- package/dist/mcp/mcp-tools.js.map +1 -1
- package/dist/mcp/monitoring-directive.js +43 -0
- package/dist/mcp/monitoring-directive.js.map +1 -0
- package/dist/pipeline/data-driven-task.workflow.js +393 -182
- 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 +280 -94
- 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 +234 -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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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(', ');
|
|
107
|
+
return {
|
|
108
|
+
needsHuman: true,
|
|
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);
|
|
109
135
|
return {
|
|
110
136
|
needsHuman: true,
|
|
111
|
-
lesson: `
|
|
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,30 +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
|
+
}
|
|
274
|
+
// gh api graphql wraps responses in {"data":{...}}; unwrap tolerantly so unit fixtures still work.
|
|
275
|
+
function unwrapGraphqlData(raw) {
|
|
276
|
+
const top = asRecord(raw);
|
|
277
|
+
const data = top ? asRecord(top['data']) : null;
|
|
278
|
+
return data ?? raw;
|
|
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
|
+
}
|
|
213
291
|
function mapReviewThreads(raw) {
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
const
|
|
217
|
-
const
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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 };
|
|
237
339
|
}
|
|
238
340
|
function splitRepo(repo) {
|
|
239
341
|
const [owner, name] = repo.split('/');
|
|
@@ -245,7 +347,7 @@ function fetchReviewThreads(repo, prNumber, execGh) {
|
|
|
245
347
|
const { owner, name } = splitRepo(repo);
|
|
246
348
|
const raw = execGh([
|
|
247
349
|
'api', 'graphql',
|
|
248
|
-
'-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}}}}}}}}',
|
|
249
351
|
'-f', `owner=${owner}`,
|
|
250
352
|
'-f', `name=${name}`,
|
|
251
353
|
'-F', `number=${prNumber}`,
|
|
@@ -253,12 +355,15 @@ function fetchReviewThreads(repo, prNumber, execGh) {
|
|
|
253
355
|
return mapReviewThreads(parseGhJson(raw, `review-threads #${prNumber}`));
|
|
254
356
|
}
|
|
255
357
|
function mapRequiredCheckNames(raw) {
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
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');
|
|
262
367
|
const required = new Set();
|
|
263
368
|
for (const node of nodes) {
|
|
264
369
|
const ctx = asRecord(node);
|
|
@@ -281,13 +386,56 @@ export function fetchRequiredCheckNames(repo, prNumber, execGh) {
|
|
|
281
386
|
]);
|
|
282
387
|
return mapRequiredCheckNames(parseGhJson(raw, `required-checks #${prNumber}`));
|
|
283
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
|
+
}
|
|
284
419
|
function collectReviewThreads(input, prNumber, execGh) {
|
|
285
|
-
const
|
|
286
|
-
|
|
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);
|
|
287
427
|
return {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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),
|
|
291
439
|
};
|
|
292
440
|
}
|
|
293
441
|
function fetchComments(input, prNumber, execGh) {
|
|
@@ -413,7 +561,7 @@ function compactCheckLists(checks) {
|
|
|
413
561
|
const pass = [];
|
|
414
562
|
const fail = [];
|
|
415
563
|
for (const check of checks) {
|
|
416
|
-
if (['QUEUED', 'IN_PROGRESS', 'PENDING'].includes(check.result)) {
|
|
564
|
+
if (['EXPECTED', 'QUEUED', 'IN_PROGRESS', 'PENDING'].includes(check.result)) {
|
|
417
565
|
pending.push(check.name);
|
|
418
566
|
continue;
|
|
419
567
|
}
|
|
@@ -461,7 +609,7 @@ function providerState(checks, botComments) {
|
|
|
461
609
|
}
|
|
462
610
|
function locationOf(item) {
|
|
463
611
|
const path = item.path ?? item.component ?? '';
|
|
464
|
-
return item.line ? `${path}:${item.line}` : path;
|
|
612
|
+
return typeof item.line === 'number' ? `${path}:${item.line}` : path;
|
|
465
613
|
}
|
|
466
614
|
function providerWaitFeedback(state, botComments) {
|
|
467
615
|
const codeRabbit = state.codeRabbit;
|
|
@@ -476,9 +624,6 @@ function providerWaitFeedback(state, botComments) {
|
|
|
476
624
|
resumeAfter: extractResumeHint(botComments),
|
|
477
625
|
}];
|
|
478
626
|
}
|
|
479
|
-
function escapeRegExp(value) {
|
|
480
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
481
|
-
}
|
|
482
627
|
function issueRefTitleTag(issueRef, repo) {
|
|
483
628
|
return issueRef.repo.toLowerCase() === repo.toLowerCase()
|
|
484
629
|
? `#${issueRef.number}`
|
|
@@ -487,25 +632,15 @@ function issueRefTitleTag(issueRef, repo) {
|
|
|
487
632
|
function hasIssueBranchToken(head, issueRef) {
|
|
488
633
|
return new RegExp(`(?:^|[/-])issue-${issueRef.number}(?:-|$)`).test(head ?? '');
|
|
489
634
|
}
|
|
490
|
-
function
|
|
491
|
-
|
|
492
|
-
const boundaryAfter = String.raw `(?=$|[^A-Za-z0-9_-])`;
|
|
493
|
-
const qualified = new RegExp(`${boundaryBefore}${escapeRegExp(`${issueRef.repo}#${issueRef.number}`)}${boundaryAfter}`, 'i');
|
|
494
|
-
if (qualified.test(title ?? ''))
|
|
495
|
-
return true;
|
|
496
|
-
if (issueRef.repo.toLowerCase() !== repo.toLowerCase())
|
|
497
|
-
return false;
|
|
498
|
-
return new RegExp(`${boundaryBefore}#${issueRef.number}${boundaryAfter}`).test(title ?? '');
|
|
499
|
-
}
|
|
500
|
-
function issueLinkageFeedback(issueRef, repo, pr) {
|
|
501
|
-
if (!issueRef)
|
|
635
|
+
function issueLinkageFeedback(issueRef, issueAction, repo, pr) {
|
|
636
|
+
if (!issueRef || issueAction === 'none')
|
|
502
637
|
return [];
|
|
503
638
|
const issueBranch = `issue-${issueRef.number}`;
|
|
504
639
|
const issueTag = issueRefTitleTag(issueRef, repo);
|
|
505
640
|
const missing = [];
|
|
506
641
|
if (!hasIssueBranchToken(pr.head, issueRef))
|
|
507
642
|
missing.push(`branch missing ${issueBranch}`);
|
|
508
|
-
if (!
|
|
643
|
+
if (!hasIssueRefToken(pr.title, issueRef, repo))
|
|
509
644
|
missing.push(`title missing ${issueTag}`);
|
|
510
645
|
if (missing.length === 0)
|
|
511
646
|
return [];
|
|
@@ -515,6 +650,24 @@ function issueLinkageFeedback(issueRef, repo, pr) {
|
|
|
515
650
|
evidence: `${missing.join('; ')} (head=${pr.head ?? ''}, title=${pr.title ?? ''})`,
|
|
516
651
|
}];
|
|
517
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
|
+
}
|
|
663
|
+
// Bot logins whose comments are purely informational and safe to suppress.
|
|
664
|
+
// Anything not in this list surfaces in developerFixes to avoid silent suppression.
|
|
665
|
+
const KNOWN_INFORMATIONAL_BOTS = new Set([
|
|
666
|
+
'sonarqubecloud[bot]',
|
|
667
|
+
'cursor[bot]',
|
|
668
|
+
'linear-app[bot]',
|
|
669
|
+
'deepsource-autofix[bot]',
|
|
670
|
+
]);
|
|
518
671
|
function buildFeedback(input) {
|
|
519
672
|
const developerFixes = [
|
|
520
673
|
...input.checks.fail.map((name) => ({ source: 'ci', summary: `Fix failing check: ${name}`, evidence: name })),
|
|
@@ -540,6 +693,13 @@ function buildFeedback(input) {
|
|
|
540
693
|
author: review.user?.login ?? '',
|
|
541
694
|
evidence: review.state,
|
|
542
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
|
+
: []),
|
|
543
703
|
...input.reviewThreads.items.map((thread) => ({
|
|
544
704
|
source: 'review_thread',
|
|
545
705
|
summary: thread.body,
|
|
@@ -548,6 +708,15 @@ function buildFeedback(input) {
|
|
|
548
708
|
evidence: thread.url ?? thread.id,
|
|
549
709
|
})),
|
|
550
710
|
...codeRabbitActionableFeedback(input.coderabbitReviews ?? [], input.botComments, new Set(input.reviewThreads.items.map((thread) => locationOf(thread)).filter((loc) => loc !== ''))),
|
|
711
|
+
...input.botComments
|
|
712
|
+
.filter((comment) => !isCodeRabbit(comment.user) && !KNOWN_INFORMATIONAL_BOTS.has(comment.user?.login ?? ''))
|
|
713
|
+
.filter((comment) => !isStaleThreadFeedback(comment, input.staleThreadEvidence))
|
|
714
|
+
.map((comment) => ({
|
|
715
|
+
source: 'bot_comment',
|
|
716
|
+
summary: compactBody(comment.body),
|
|
717
|
+
author: comment.user?.login ?? 'bot',
|
|
718
|
+
evidence: comment.user?.login ?? 'bot',
|
|
719
|
+
})),
|
|
551
720
|
];
|
|
552
721
|
const reviewerQuestions = input.humanComments
|
|
553
722
|
.filter((comment) => comment.body.includes('?'))
|
|
@@ -563,10 +732,11 @@ function buildFeedback(input) {
|
|
|
563
732
|
? [{ source: 'github_review_decision', summary: `Review decision is ${input.reviewDecision}` }]
|
|
564
733
|
: []),
|
|
565
734
|
...(input.sonar.unavailable ? [{ source: 'sonar', summary: 'Sonar was configured but unavailable.' }] : []),
|
|
566
|
-
...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),
|
|
567
737
|
];
|
|
568
738
|
const ignoredNoise = input.botComments
|
|
569
|
-
.filter((comment) => !comment.user
|
|
739
|
+
.filter((comment) => !isCodeRabbit(comment.user) && KNOWN_INFORMATIONAL_BOTS.has(comment.user?.login ?? ''))
|
|
570
740
|
.map((comment) => ({ source: comment.user?.login ?? 'bot', summary: compactBody(comment.body) }));
|
|
571
741
|
const residualRisks = [
|
|
572
742
|
...(input.reviewThreads.included ? [] : ['Review threads were not requested.']),
|
|
@@ -620,7 +790,9 @@ function buildEmptyFeedback(input) {
|
|
|
620
790
|
humanComments: [],
|
|
621
791
|
botComments: [],
|
|
622
792
|
issueRef: input.issueRef,
|
|
623
|
-
|
|
793
|
+
issueAction: input.issueAction,
|
|
794
|
+
repo: input.repo ?? '',
|
|
795
|
+
prView: input.prView,
|
|
624
796
|
});
|
|
625
797
|
}
|
|
626
798
|
function buildMergedReadiness(input, resolved) {
|
|
@@ -636,7 +808,7 @@ function buildMergedReadiness(input, resolved) {
|
|
|
636
808
|
sonar: emptySonar(sonarConfigured),
|
|
637
809
|
nextAction: 'none',
|
|
638
810
|
evidence: [`PR #${resolved.prNumber} is merged.`],
|
|
639
|
-
feedback: buildEmptyFeedback({ sonarConfigured, includeReviewThreads }),
|
|
811
|
+
feedback: buildEmptyFeedback({ sonarConfigured, includeReviewThreads, issueRef: input.issueRef, issueAction: input.issueAction, repo: input.repo, prView: resolved.prView }),
|
|
640
812
|
ciSummary: { ...emptyCiSummary(true), ...(input.issueRef ? { issueRef: input.issueRef } : {}) },
|
|
641
813
|
...(input.issueRef ? { issueRef: input.issueRef } : {}),
|
|
642
814
|
};
|
|
@@ -644,7 +816,7 @@ function buildMergedReadiness(input, resolved) {
|
|
|
644
816
|
function buildNeedsHumanReadiness(input, resolved) {
|
|
645
817
|
const includeReviewThreads = input.includeReviewThreads !== false;
|
|
646
818
|
const sonarConfigured = Boolean(input.sonarProject);
|
|
647
|
-
const feedback = buildEmptyFeedback({ sonarConfigured, includeReviewThreads });
|
|
819
|
+
const feedback = buildEmptyFeedback({ sonarConfigured, includeReviewThreads, issueRef: input.issueRef, issueAction: input.issueAction, repo: input.repo, prView: resolved.prView });
|
|
648
820
|
return {
|
|
649
821
|
verdict: resolved.verdict === 'closed' ? 'closed' : 'needs_human',
|
|
650
822
|
pr: resolved.prNumber ? prFromView(resolved.prNumber, resolved.prView) : emptyPr(input.prNumber, resolved.verdict),
|
|
@@ -661,6 +833,9 @@ function buildNeedsHumanReadiness(input, resolved) {
|
|
|
661
833
|
};
|
|
662
834
|
}
|
|
663
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;
|
|
664
839
|
const pendingCodeRabbit = input.ci.pendingNames.find((name) => name.toLowerCase().includes('coderabbit'));
|
|
665
840
|
const pendingProviderState = pendingCodeRabbit
|
|
666
841
|
? {
|
|
@@ -673,7 +848,7 @@ function buildWaitingReadiness(input) {
|
|
|
673
848
|
}
|
|
674
849
|
: {};
|
|
675
850
|
const feedback = buildFeedback({
|
|
676
|
-
checks:
|
|
851
|
+
checks: checkLists,
|
|
677
852
|
providerState: pendingProviderState,
|
|
678
853
|
sonar: emptySonar(input.sonarConfigured),
|
|
679
854
|
reviewDecision: input.prView.reviewDecision ?? '',
|
|
@@ -682,8 +857,10 @@ function buildWaitingReadiness(input) {
|
|
|
682
857
|
humanComments: [],
|
|
683
858
|
botComments: [],
|
|
684
859
|
issueRef: input.issueRef,
|
|
860
|
+
issueAction: input.issueAction,
|
|
685
861
|
repo: input.repo,
|
|
686
862
|
pr: { head: input.prView.headRefName, title: input.prView.title },
|
|
863
|
+
prView: input.prView,
|
|
687
864
|
});
|
|
688
865
|
const providerWait = pendingCodeRabbit
|
|
689
866
|
? [{
|
|
@@ -698,7 +875,7 @@ function buildWaitingReadiness(input) {
|
|
|
698
875
|
return {
|
|
699
876
|
verdict: 'waiting',
|
|
700
877
|
pr: prFromView(input.prNumber, input.prView),
|
|
701
|
-
checks:
|
|
878
|
+
checks: checkLists,
|
|
702
879
|
reviewDecision: input.prView.reviewDecision ?? '',
|
|
703
880
|
reviewThreads: input.reviewThreads,
|
|
704
881
|
providerState: pendingProviderState,
|
|
@@ -733,6 +910,7 @@ function readinessAction(input) {
|
|
|
733
910
|
return { verdict: 'ready', nextAction: 'ready_for_merge_gate' };
|
|
734
911
|
}
|
|
735
912
|
export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSonar = defaultFetchSonar) {
|
|
913
|
+
input = { ...input, issueAction: input.issueAction ?? defaultIssueAction(input.issueRef) };
|
|
736
914
|
const baseBranch = input.baseBranch ?? 'master';
|
|
737
915
|
const resolved = resolveOpenPr(input, baseBranch, execGh);
|
|
738
916
|
if (resolved.kind === 'merged') {
|
|
@@ -743,9 +921,9 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
743
921
|
}
|
|
744
922
|
const { prNumber, prView } = resolved;
|
|
745
923
|
const checks = prView.statusCheckRollup ?? [];
|
|
746
|
-
const ci = collectCiChecks(checks);
|
|
924
|
+
const ci = collectCiChecks(checks, { emptyIsPending: prView.statusCheckRollup === null });
|
|
747
925
|
const checkLists = compactCheckLists(ci.checks);
|
|
748
|
-
const reviewThreads = collectReviewThreads(input, prNumber, execGh);
|
|
926
|
+
const { reviewThreads, staleThreadEvidence } = collectReviewThreads(input, prNumber, execGh);
|
|
749
927
|
const sonarConfigured = Boolean(input.sonarProject);
|
|
750
928
|
if (prView.isDraft === true) {
|
|
751
929
|
return buildWaitingReadiness({
|
|
@@ -757,6 +935,7 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
757
935
|
reviewThreads,
|
|
758
936
|
sonarConfigured,
|
|
759
937
|
issueRef: input.issueRef,
|
|
938
|
+
issueAction: input.issueAction,
|
|
760
939
|
evidence: [`PR #${prNumber} is still draft.`],
|
|
761
940
|
isDraft: true,
|
|
762
941
|
});
|
|
@@ -771,6 +950,7 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
771
950
|
reviewThreads,
|
|
772
951
|
sonarConfigured,
|
|
773
952
|
issueRef: input.issueRef,
|
|
953
|
+
issueAction: input.issueAction,
|
|
774
954
|
evidence: ci.pendingNames.length > 0 ? [`Pending checks: ${ci.pendingNames.join(', ')}`] : ['No check rollup entries are registered yet.'],
|
|
775
955
|
});
|
|
776
956
|
}
|
|
@@ -794,10 +974,13 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
794
974
|
humanReviews: comments.human_reviews,
|
|
795
975
|
humanComments: comments.human_comments,
|
|
796
976
|
botComments: comments.bot_comments,
|
|
977
|
+
staleThreadEvidence,
|
|
797
978
|
coderabbitReviews: comments.coderabbit_reviews,
|
|
798
979
|
issueRef: input.issueRef,
|
|
980
|
+
issueAction: input.issueAction,
|
|
799
981
|
repo: input.repo,
|
|
800
982
|
pr: { head: prView.headRefName, title: prView.title },
|
|
983
|
+
prView,
|
|
801
984
|
});
|
|
802
985
|
const ciSummary = {
|
|
803
986
|
ci_passed: ci.ci_passed,
|
|
@@ -826,7 +1009,9 @@ export async function collectPrReadiness(input, execGh = defaultExecGh, fetchSon
|
|
|
826
1009
|
nextAction,
|
|
827
1010
|
evidence: [
|
|
828
1011
|
`PR #${prNumber} state=${prView.state ?? 'unknown'} draft=${Boolean(prView.isDraft)}`,
|
|
829
|
-
|
|
1012
|
+
checkLists.list.length === 0
|
|
1013
|
+
? 'checks: none registered'
|
|
1014
|
+
: `checks pass=${checkLists.pass.length} fail=${checkLists.fail.length} pending=${checkLists.pending.length}`,
|
|
830
1015
|
...(input.issueRef ? [`issueRef ${issueRefTitleTag(input.issueRef, input.repo)} expected in branch and title`] : []),
|
|
831
1016
|
...(providers.codeRabbit?.reason === 'provider_limit' ? ['CodeRabbit provider/rate limit comment detected.'] : []),
|
|
832
1017
|
...(verdict === 'ready' && hasInformationalProviderWait
|
|
@@ -846,6 +1031,7 @@ export function toReadinessInput(input) {
|
|
|
846
1031
|
baseBranch: input.base_branch,
|
|
847
1032
|
sonarProject: input.sonar_project,
|
|
848
1033
|
issueRef: input.issue_ref,
|
|
1034
|
+
issueAction: input.issue_action,
|
|
849
1035
|
includeComments: true,
|
|
850
1036
|
includeReviewThreads: false,
|
|
851
1037
|
};
|