cloving 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +251 -20
- package/assets/watch-fonts/fira-code-OFL.md +93 -0
- package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-OFL.md +93 -0
- package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
- package/assets/watch-fonts/manifest.json +112 -0
- package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
- package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
- package/dist/src/authority/commands.d.ts +1 -0
- package/dist/src/authority/commands.js +33 -4
- package/dist/src/authority/dispatch-decision.d.ts +1 -0
- package/dist/src/authority/dispatch-decision.js +7 -4
- package/dist/src/authority/events.js +2 -2
- package/dist/src/authority/handoff-chain.d.ts +24 -0
- package/dist/src/authority/handoff-chain.js +46 -0
- package/dist/src/authority/reconciliation.d.ts +3 -0
- package/dist/src/authority/reconciliation.js +21 -5
- package/dist/src/authority/transitions.d.ts +1 -1
- package/dist/src/authority/transitions.js +5 -0
- package/dist/src/broker/actor/authority-queries.d.ts +4 -0
- package/dist/src/broker/actor/authority-queries.js +51 -2
- package/dist/src/broker/actor/authority-session.d.ts +2 -0
- package/dist/src/broker/actor/authority-session.js +3 -4
- package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
- package/dist/src/broker/actor/effect-delivery.js +9 -0
- package/dist/src/broker/authority-admission.js +13 -5
- package/dist/src/chats/analysis-batches.d.ts +42 -0
- package/dist/src/chats/analysis-batches.js +97 -0
- package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
- package/dist/src/chats/analysis-checkpoint.js +31 -0
- package/dist/src/chats/analysis-contract.d.ts +80 -0
- package/dist/src/chats/analysis-contract.js +16 -0
- package/dist/src/chats/analysis-data.d.ts +3 -0
- package/dist/src/chats/analysis-data.js +85 -0
- package/dist/src/chats/analysis-input.d.ts +18 -0
- package/dist/src/chats/analysis-input.js +97 -0
- package/dist/src/chats/analysis-links.d.ts +2 -0
- package/dist/src/chats/analysis-links.js +25 -0
- package/dist/src/chats/analysis-merge.d.ts +16 -0
- package/dist/src/chats/analysis-merge.js +134 -0
- package/dist/src/chats/analysis-prompt.d.ts +18 -0
- package/dist/src/chats/analysis-prompt.js +42 -0
- package/dist/src/chats/analysis-proposal.d.ts +4 -0
- package/dist/src/chats/analysis-proposal.js +41 -0
- package/dist/src/chats/analysis-provenance.d.ts +4 -0
- package/dist/src/chats/analysis-provenance.js +99 -0
- package/dist/src/chats/analysis-shape.d.ts +4 -0
- package/dist/src/chats/analysis-shape.js +235 -0
- package/dist/src/chats/analysis.d.ts +3 -0
- package/dist/src/chats/analysis.js +42 -0
- package/dist/src/chats/corpus-input.d.ts +5 -0
- package/dist/src/chats/corpus-input.js +288 -0
- package/dist/src/chats/destination.js +3 -1
- package/dist/src/chats/import.d.ts +8 -6
- package/dist/src/chats/import.js +195 -23
- package/dist/src/chats/input-file.d.ts +3 -0
- package/dist/src/chats/input-file.js +32 -0
- package/dist/src/chats/normalize.d.ts +11 -2
- package/dist/src/chats/normalize.js +18 -2
- package/dist/src/chats/previous-import.d.ts +5 -0
- package/dist/src/chats/previous-import.js +34 -0
- package/dist/src/chats/reader.d.ts +7 -1
- package/dist/src/chats/reader.js +46 -15
- package/dist/src/chats/redact-import.d.ts +14 -0
- package/dist/src/chats/redact-import.js +141 -0
- package/dist/src/chats/redaction.d.ts +41 -0
- package/dist/src/chats/redaction.js +161 -0
- package/dist/src/chats/reference-mapping.d.ts +16 -0
- package/dist/src/chats/reference-mapping.js +153 -0
- package/dist/src/chats/revision-input.d.ts +3 -0
- package/dist/src/chats/revision-input.js +180 -0
- package/dist/src/chats/revision.d.ts +48 -0
- package/dist/src/chats/revision.js +94 -0
- package/dist/src/chats/selection.d.ts +25 -0
- package/dist/src/chats/selection.js +109 -0
- package/dist/src/chats/source.d.ts +23 -0
- package/dist/src/chats/source.js +135 -0
- package/dist/src/chats/structure.d.ts +57 -0
- package/dist/src/chats/structure.js +216 -0
- package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
- package/dist/src/chats/workflow-generation-projection.js +75 -0
- package/dist/src/chats/workflow-generation-source.d.ts +25 -0
- package/dist/src/chats/workflow-generation-source.js +124 -0
- package/dist/src/chats/workflow-generation-text.d.ts +23 -0
- package/dist/src/chats/workflow-generation-text.js +74 -0
- package/dist/src/chats/workflow-generation.d.ts +69 -0
- package/dist/src/chats/workflow-generation.js +142 -0
- package/dist/src/cli/arguments.d.ts +8 -0
- package/dist/src/cli/arguments.js +94 -13
- package/dist/src/cli/client.d.ts +1 -0
- package/dist/src/cli/client.js +38 -1
- package/dist/src/cli/commands.d.ts +3 -0
- package/dist/src/cli/commands.js +29 -11
- package/dist/src/cli/main.d.ts +2 -1
- package/dist/src/cli/main.js +61 -14
- package/dist/src/cli/render.d.ts +3 -0
- package/dist/src/cli/render.js +122 -7
- package/dist/src/cli/usage-render.d.ts +3 -0
- package/dist/src/cli/usage-render.js +28 -0
- package/dist/src/cli/usage.d.ts +3 -2
- package/dist/src/cli/usage.js +42 -7
- package/dist/src/cli/watch-actions.d.ts +10 -0
- package/dist/src/cli/watch-actions.js +58 -0
- package/dist/src/cli/watch-board-view.d.ts +44 -1
- package/dist/src/cli/watch-board-view.js +137 -18
- package/dist/src/cli/watch-board.d.ts +7 -1
- package/dist/src/cli/watch-board.js +48 -11
- package/dist/src/cli/watch-card-client.d.ts +1 -0
- package/dist/src/cli/watch-card-client.js +44 -0
- package/dist/src/cli/watch-card-forms.d.ts +2 -0
- package/dist/src/cli/watch-card-forms.js +33 -0
- package/dist/src/cli/watch-card-reply.d.ts +5 -0
- package/dist/src/cli/watch-card-reply.js +34 -0
- package/dist/src/cli/watch-card-view.d.ts +40 -0
- package/dist/src/cli/watch-card-view.js +86 -0
- package/dist/src/cli/watch-client.d.ts +1 -0
- package/dist/src/cli/watch-client.js +122 -0
- package/dist/src/cli/watch-model.d.ts +10 -4
- package/dist/src/cli/watch-model.js +61 -67
- package/dist/src/cli/watch-page.d.ts +1 -1
- package/dist/src/cli/watch-page.js +89 -86
- package/dist/src/cli/watch-phases.js +3 -3
- package/dist/src/cli/watch-pr-board.d.ts +3 -0
- package/dist/src/cli/watch-pr-board.js +26 -0
- package/dist/src/cli/watch-pr-command.d.ts +18 -0
- package/dist/src/cli/watch-pr-command.js +74 -0
- package/dist/src/cli/watch-pr-identity.d.ts +32 -0
- package/dist/src/cli/watch-pr-identity.js +87 -0
- package/dist/src/cli/watch-pr-observer.d.ts +20 -0
- package/dist/src/cli/watch-pr-observer.js +150 -0
- package/dist/src/cli/watch-pr-settings.d.ts +8 -0
- package/dist/src/cli/watch-pr-settings.js +47 -0
- package/dist/src/cli/watch-pull-request.d.ts +10 -0
- package/dist/src/cli/watch-pull-request.js +130 -0
- package/dist/src/cli/watch-reads.d.ts +7 -1
- package/dist/src/cli/watch-reads.js +44 -18
- package/dist/src/cli/watch-render.d.ts +29 -2
- package/dist/src/cli/watch-render.js +11 -3
- package/dist/src/cli/watch-run-input.d.ts +14 -0
- package/dist/src/cli/watch-run-input.js +49 -0
- package/dist/src/cli/watch-server.d.ts +3 -0
- package/dist/src/cli/watch-server.js +77 -8
- package/dist/src/cli/watch-start-view.d.ts +18 -0
- package/dist/src/cli/watch-start-view.js +77 -0
- package/dist/src/cli/watch-turn.d.ts +1 -0
- package/dist/src/cli/watch-turn.js +8 -3
- package/dist/src/cli/watch-usage.d.ts +6 -0
- package/dist/src/cli/watch-usage.js +65 -0
- package/dist/src/cli/watch-view.d.ts +22 -0
- package/dist/src/cli/watch-view.js +111 -19
- package/dist/src/cli.js +48 -6
- package/dist/src/daemon/analysis-launch.d.ts +5 -0
- package/dist/src/daemon/analysis-launch.js +81 -0
- package/dist/src/daemon/analysis-token.d.ts +6 -0
- package/dist/src/daemon/analysis-token.js +88 -0
- package/dist/src/daemon/analysis-worker.d.ts +20 -0
- package/dist/src/daemon/analysis-worker.js +271 -0
- package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
- package/dist/src/daemon/chats-batch-binding.js +99 -0
- package/dist/src/daemon/chats-capture.d.ts +18 -0
- package/dist/src/daemon/chats-capture.js +194 -0
- package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
- package/dist/src/daemon/chats-checkpoints.js +186 -0
- package/dist/src/daemon/chats-files.d.ts +42 -0
- package/dist/src/daemon/chats-files.js +252 -0
- package/dist/src/daemon/chats-follow-up.d.ts +38 -0
- package/dist/src/daemon/chats-follow-up.js +180 -0
- package/dist/src/daemon/chats-question.d.ts +15 -0
- package/dist/src/daemon/chats-question.js +93 -0
- package/dist/src/daemon/chats-worker.d.ts +13 -0
- package/dist/src/daemon/chats-worker.js +201 -0
- package/dist/src/daemon/claude-turn.d.ts +11 -0
- package/dist/src/daemon/claude-turn.js +96 -15
- package/dist/src/daemon/claude-worker.d.ts +2 -1
- package/dist/src/daemon/claude-worker.js +92 -45
- package/dist/src/daemon/codex-stream.js +16 -0
- package/dist/src/daemon/daemon.d.ts +8 -1
- package/dist/src/daemon/daemon.js +51 -14
- package/dist/src/daemon/delivery-router.js +6 -0
- package/dist/src/daemon/github-calls.d.ts +4 -0
- package/dist/src/daemon/github-calls.js +16 -7
- package/dist/src/daemon/github-ci.d.ts +6 -0
- package/dist/src/daemon/github-ci.js +170 -0
- package/dist/src/daemon/github-merge-call.d.ts +14 -0
- package/dist/src/daemon/github-merge-call.js +73 -0
- package/dist/src/daemon/github-merge.d.ts +3 -5
- package/dist/src/daemon/github-merge.js +52 -95
- package/dist/src/daemon/github-methods.d.ts +28 -0
- package/dist/src/daemon/github-methods.js +84 -16
- package/dist/src/daemon/github-operation.d.ts +2 -0
- package/dist/src/daemon/github-retry.d.ts +6 -0
- package/dist/src/daemon/github-retry.js +49 -0
- package/dist/src/daemon/github-ticket-status.d.ts +3 -0
- package/dist/src/daemon/github-ticket-status.js +294 -0
- package/dist/src/daemon/github-worker.js +2 -1
- package/dist/src/daemon/handoff-worker.d.ts +10 -0
- package/dist/src/daemon/handoff-worker.js +232 -0
- package/dist/src/daemon/issue-queue.d.ts +2 -0
- package/dist/src/daemon/issue-queue.js +186 -43
- package/dist/src/daemon/notify-journal.d.ts +18 -0
- package/dist/src/daemon/notify-journal.js +108 -0
- package/dist/src/daemon/notify-message.js +2 -1
- package/dist/src/daemon/notify.js +176 -37
- package/dist/src/daemon/operation-journal.d.ts +1 -0
- package/dist/src/daemon/operation-journal.js +4 -2
- package/dist/src/daemon/retrying-turn.d.ts +12 -0
- package/dist/src/daemon/retrying-turn.js +231 -0
- package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
- package/dist/src/daemon/ticket-dependency-waits.js +60 -0
- package/dist/src/daemon/ticket-registration.d.ts +8 -4
- package/dist/src/daemon/ticket-registration.js +76 -22
- package/dist/src/daemon/ticket-source.d.ts +4 -1
- package/dist/src/daemon/ticket-source.js +8 -2
- package/dist/src/decision-visibility.d.ts +2 -0
- package/dist/src/decision-visibility.js +10 -0
- package/dist/src/execution-pin.d.ts +4 -0
- package/dist/src/execution-pin.js +23 -2
- package/dist/src/host/start-delivery.d.ts +2 -1
- package/dist/src/host/start-delivery.js +1 -1
- package/dist/src/host/step-bridge-host.d.ts +1 -0
- package/dist/src/host/step-bridge-host.js +16 -0
- package/dist/src/host/world-reconciliation.d.ts +3 -0
- package/dist/src/host/world-reconciliation.js +9 -0
- package/dist/src/host/world-runs.js +14 -1
- package/dist/src/issue-identity.d.ts +6 -0
- package/dist/src/issue-identity.js +23 -0
- package/dist/src/service/queue-runs.d.ts +3 -0
- package/dist/src/service/queue-runs.js +39 -0
- package/dist/src/service/registry.d.ts +8 -2
- package/dist/src/service/registry.js +20 -8
- package/dist/src/service/replies.d.ts +2 -0
- package/dist/src/service/run-handlers.js +54 -30
- package/dist/src/service/service.d.ts +3 -0
- package/dist/src/service/service.js +3 -1
- package/dist/src/service/submission-handler.d.ts +1 -0
- package/dist/src/service/submission-handler.js +92 -13
- package/dist/src/service/worker-totals.d.ts +3 -0
- package/dist/src/service/worker-totals.js +52 -0
- package/dist/src/step-bridge.d.ts +9 -0
- package/dist/src/steps/handoff.d.ts +2 -0
- package/dist/src/steps/handoff.js +13 -0
- package/dist/src/ticket-dependencies.d.ts +1 -0
- package/dist/src/ticket-dependencies.js +15 -0
- package/dist/src/ticket-status.d.ts +8 -0
- package/dist/src/ticket-status.js +48 -0
- package/dist/src/token-prices.d.ts +21 -0
- package/dist/src/token-prices.js +108 -0
- package/dist/src/worker-usage.d.ts +31 -0
- package/dist/src/worker-usage.js +44 -0
- package/dist/src/workflow-admission.d.ts +1 -0
- package/dist/src/workflow-candidate.d.ts +29 -0
- package/dist/src/workflow-candidate.js +60 -0
- package/dist/src/workflow-contracts.d.ts +4 -0
- package/dist/src/workflow-contracts.js +32 -5
- package/dist/src/workflow-declaration.d.ts +12 -1
- package/dist/src/workflow-declaration.js +71 -13
- package/dist/src/workflow-discovery.d.ts +18 -0
- package/dist/src/workflow-discovery.js +33 -0
- package/dist/src/workflow-refusals.d.ts +6 -0
- package/dist/src/workflow-refusals.js +39 -0
- package/dist/src/workflows.d.ts +3 -1
- package/dist/src/workflows.js +2 -0
- package/dist/tools/admit-workflow.d.ts +13 -0
- package/dist/tools/admit-workflow.js +94 -0
- package/dist/tools/self-host-processes.js +7 -2
- package/dist/tools/self-host-project.d.ts +17 -0
- package/dist/tools/self-host-project.js +177 -0
- package/dist/tools/self-host-workflows.d.ts +1 -1
- package/dist/tools/self-host-workflows.js +2 -2
- package/dist/tools/self-host.js +30 -84
- package/dist/tools/self-update-host.d.ts +7 -0
- package/dist/tools/self-update-host.js +48 -1
- package/dist/tools/self-update.d.ts +1 -0
- package/dist/tools/self-update.js +85 -13
- package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
- package/dist/tools/workflow-bundle-dependencies.js +93 -0
- package/dist/tools/workflow-bundles.d.ts +20 -0
- package/dist/tools/workflow-bundles.js +173 -0
- package/dist/tools/workflow-candidate-counts.d.ts +8 -0
- package/dist/tools/workflow-candidate-counts.js +251 -0
- package/dist/tools/workflow-candidate-program.d.ts +11 -0
- package/dist/tools/workflow-candidate-program.js +78 -0
- package/dist/tools/workflow-candidate-source.d.ts +6 -0
- package/dist/tools/workflow-candidate-source.js +107 -0
- package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
- package/dist/tools/workflow-candidate-syntax.js +219 -0
- package/dist/tools/workflow-folder-capture.d.ts +17 -0
- package/dist/tools/workflow-folder-capture.js +216 -0
- package/dist/tools/workflow-folders.d.ts +33 -0
- package/dist/tools/workflow-folders.js +150 -0
- package/dist/tools/workflow-source-effects.d.ts +10 -0
- package/dist/tools/workflow-source-effects.js +213 -0
- package/dist/workflows/analyze-chats.d.ts +15 -0
- package/dist/workflows/analyze-chats.declaration.json +22 -0
- package/dist/workflows/analyze-chats.js +250 -0
- package/dist/workflows/develop-prd.js +71 -3
- package/dist/workflows/develop-ticket.d.ts +1 -7
- package/dist/workflows/develop-ticket.js +47 -141
- package/dist/workflows/issue-labels.d.ts +3 -1
- package/dist/workflows/issue-labels.js +34 -0
- package/dist/workflows/ticket-prompts.d.ts +35 -0
- package/dist/workflows/ticket-prompts.js +143 -0
- package/package.json +25 -5
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { githubText } from "./github-calls.js";
|
|
3
|
+
import { githubRead } from "./github-retry.js";
|
|
4
|
+
import { field, githubTiming, repositoryPath, } from "./github-operation.js";
|
|
5
|
+
function isCheckRun(value) {
|
|
6
|
+
const conclusion = field(value, "conclusion");
|
|
7
|
+
const details = field(value, "details_url");
|
|
8
|
+
const app = field(value, "app");
|
|
9
|
+
return (typeof field(value, "id") === "number" &&
|
|
10
|
+
typeof field(value, "name") === "string" &&
|
|
11
|
+
typeof field(value, "status") === "string" &&
|
|
12
|
+
(conclusion === null || typeof conclusion === "string") &&
|
|
13
|
+
(details === null || typeof details === "string") &&
|
|
14
|
+
(app === null || typeof field(app, "slug") === "string"));
|
|
15
|
+
}
|
|
16
|
+
const passing = new Set(["success", "neutral", "skipped"]);
|
|
17
|
+
function latestRuns(checks, required) {
|
|
18
|
+
const latest = new Map();
|
|
19
|
+
for (const check of checks) {
|
|
20
|
+
const name = check.name;
|
|
21
|
+
if (required === undefined
|
|
22
|
+
? check.app?.slug !== "github-actions"
|
|
23
|
+
: !required.has(name))
|
|
24
|
+
continue;
|
|
25
|
+
const previous = latest.get(name);
|
|
26
|
+
if (previous === undefined || check.id > previous.id)
|
|
27
|
+
latest.set(name, check);
|
|
28
|
+
}
|
|
29
|
+
return [...latest.values()];
|
|
30
|
+
}
|
|
31
|
+
function list(value) {
|
|
32
|
+
return Array.isArray(value) ? value.map((item) => item) : [];
|
|
33
|
+
}
|
|
34
|
+
async function checkRuns(operation, commit) {
|
|
35
|
+
const pages = JSON.parse(await githubRead(operation, [
|
|
36
|
+
"api",
|
|
37
|
+
"--paginate",
|
|
38
|
+
"--slurp",
|
|
39
|
+
`${repositoryPath}/commits/${commit}/check-runs?filter=all&per_page=100`,
|
|
40
|
+
]));
|
|
41
|
+
if (!Array.isArray(pages) || pages.length === 0)
|
|
42
|
+
throw new Error("github-call-failed");
|
|
43
|
+
return list(pages).flatMap((page) => {
|
|
44
|
+
const checks = field(page, "check_runs");
|
|
45
|
+
if (!Array.isArray(checks) || !checks.every(isCheckRun))
|
|
46
|
+
throw new Error("github-call-failed");
|
|
47
|
+
return checks;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function unavailable(operation, error) {
|
|
51
|
+
operation.signal.throwIfAborted();
|
|
52
|
+
const detail = field(error, "detail");
|
|
53
|
+
return `CI diagnostics unavailable: ${githubText(typeof detail === "string" ? detail : "GitHub logs could not be read", operation.settings)}`;
|
|
54
|
+
}
|
|
55
|
+
async function failedSteps(operation, place) {
|
|
56
|
+
try {
|
|
57
|
+
const view = JSON.parse(await operation.calls.gh(["run", "view", place.run, "--json", "jobs"]));
|
|
58
|
+
return list(field(view, "jobs"))
|
|
59
|
+
.filter((job) => String(field(job, "databaseId")) === place.job)
|
|
60
|
+
.flatMap((job) => list(field(job, "steps")))
|
|
61
|
+
.filter((step) => typeof field(step, "conclusion") === "string" &&
|
|
62
|
+
!passing.has(String(field(step, "conclusion"))))
|
|
63
|
+
.map((step) => `Failed step: ${String(field(step, "name"))}`)
|
|
64
|
+
.join("\n");
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return unavailable(operation, error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function failedCheck(operation, check) {
|
|
71
|
+
const summary = [
|
|
72
|
+
`Failed check: ${check.name} (${check.conclusion ?? "null"})`,
|
|
73
|
+
];
|
|
74
|
+
const details = check.details_url ?? "";
|
|
75
|
+
const run = /\/actions\/runs\/(\d+)/.exec(details)?.[1];
|
|
76
|
+
let log = "Failed log unavailable.";
|
|
77
|
+
if (run !== undefined) {
|
|
78
|
+
const job = /\/job\/(\d+)/.exec(details)?.[1];
|
|
79
|
+
summary.push(await failedSteps(operation, { run, job }));
|
|
80
|
+
log = await failedLog(operation, { run, job });
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
summary: githubText(summary.join("\n"), operation.settings),
|
|
84
|
+
log: githubText(log, operation.settings),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function failedLog(operation, place) {
|
|
88
|
+
try {
|
|
89
|
+
return await operation.calls.gh([
|
|
90
|
+
"run",
|
|
91
|
+
"view",
|
|
92
|
+
place.run,
|
|
93
|
+
"--log-failed",
|
|
94
|
+
...(place.job === undefined ? [] : ["--job", place.job]),
|
|
95
|
+
]);
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
operation.signal.throwIfAborted();
|
|
99
|
+
if (place.job === undefined)
|
|
100
|
+
return unavailable(operation, error);
|
|
101
|
+
return operation.calls
|
|
102
|
+
.gh(["api", `${repositoryPath}/actions/jobs/${place.job}/logs`])
|
|
103
|
+
.catch((failure) => unavailable(operation, failure));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function failedOutput(operation, checks) {
|
|
107
|
+
const reports = [];
|
|
108
|
+
for (const check of checks)
|
|
109
|
+
reports.push(await failedCheck(operation, check));
|
|
110
|
+
const summary = reports.map((report) => report.summary).join("\n");
|
|
111
|
+
const available = Math.max(0, 6_000 - summary.length - reports.length);
|
|
112
|
+
const allowance = Math.floor(available / reports.length);
|
|
113
|
+
return [
|
|
114
|
+
summary,
|
|
115
|
+
...reports.map((report) => allowance === 0 ? "" : report.log.slice(-allowance)),
|
|
116
|
+
]
|
|
117
|
+
.join("\n")
|
|
118
|
+
.slice(0, 6_000);
|
|
119
|
+
}
|
|
120
|
+
function requiredChecks(operation) {
|
|
121
|
+
const setting = operation.settings.environment?.["CLOVING_REQUIRED_CHECKS"] ??
|
|
122
|
+
process.env["CLOVING_REQUIRED_CHECKS"];
|
|
123
|
+
if (setting === undefined)
|
|
124
|
+
return undefined;
|
|
125
|
+
const names = JSON.parse(setting);
|
|
126
|
+
if (!Array.isArray(names) ||
|
|
127
|
+
!names.every((name) => typeof name === "string" && name.trim() !== ""))
|
|
128
|
+
throw new Error("invalid-payload");
|
|
129
|
+
return new Set(names);
|
|
130
|
+
}
|
|
131
|
+
function pendingChecks(checks, names) {
|
|
132
|
+
return [...names].filter((name) => !checks.some((check) => check.name === name && check.status === "completed"));
|
|
133
|
+
}
|
|
134
|
+
export async function waitForCi(operation, commit) {
|
|
135
|
+
const { pollMilliseconds: poll, ciGraceMilliseconds: grace, ciLimitMilliseconds: limit, } = {
|
|
136
|
+
...githubTiming,
|
|
137
|
+
ciGraceMilliseconds: 60_000,
|
|
138
|
+
ciLimitMilliseconds: 5_400_000,
|
|
139
|
+
...operation.settings.timing,
|
|
140
|
+
};
|
|
141
|
+
const started = Date.now();
|
|
142
|
+
const required = requiredChecks(operation);
|
|
143
|
+
const names = new Set(required);
|
|
144
|
+
for (;;) {
|
|
145
|
+
const checks = latestRuns(await checkRuns(operation, commit), required);
|
|
146
|
+
for (const check of checks)
|
|
147
|
+
names.add(check.name);
|
|
148
|
+
const failed = checks.filter((check) => check.status === "completed" && !passing.has(check.conclusion ?? ""));
|
|
149
|
+
if (failed.length > 0)
|
|
150
|
+
return {
|
|
151
|
+
reason: "ci-failed",
|
|
152
|
+
output: await failedOutput(operation, failed),
|
|
153
|
+
};
|
|
154
|
+
const pending = pendingChecks(checks, names);
|
|
155
|
+
if (pending.length === 0 &&
|
|
156
|
+
(required !== undefined ||
|
|
157
|
+
names.size > 0 ||
|
|
158
|
+
Date.now() - started >= grace))
|
|
159
|
+
return undefined;
|
|
160
|
+
const remaining = limit - (Date.now() - started);
|
|
161
|
+
if (remaining <= 0)
|
|
162
|
+
return {
|
|
163
|
+
reason: "ci-pending",
|
|
164
|
+
output: `CI wait limit reached for ${commit}. Pending checks: ${pending.join(", ") || "waiting for GitHub Actions checks"}`,
|
|
165
|
+
};
|
|
166
|
+
await delay(Math.min(poll, remaining), undefined, {
|
|
167
|
+
signal: operation.signal,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Operation } from "./github-operation.ts";
|
|
2
|
+
export interface ReviewedPull {
|
|
3
|
+
readonly number: number;
|
|
4
|
+
readonly commit: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function knownMerge(operation: Operation, expected: ReviewedPull & {
|
|
7
|
+
readonly updated: string;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
readonly head: string;
|
|
10
|
+
readonly merged: string | undefined;
|
|
11
|
+
}>;
|
|
12
|
+
export declare function mergePull(operation: Operation, pull: ReviewedPull & {
|
|
13
|
+
readonly updated: string;
|
|
14
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { field, githubTiming, repositoryPath, } from "./github-operation.js";
|
|
3
|
+
import { githubRead, retryGithub } from "./github-retry.js";
|
|
4
|
+
function commitOf(value, name) {
|
|
5
|
+
if (typeof value !== "object" || value === null)
|
|
6
|
+
throw new Error("github-call-failed");
|
|
7
|
+
const commit = Reflect.get(value, name);
|
|
8
|
+
if (typeof commit !== "string")
|
|
9
|
+
throw new Error("github-call-failed");
|
|
10
|
+
return commit;
|
|
11
|
+
}
|
|
12
|
+
export async function knownMerge(operation, expected) {
|
|
13
|
+
const pull = JSON.parse(await githubRead(operation, [
|
|
14
|
+
"api",
|
|
15
|
+
`${repositoryPath}/pulls/${String(expected.number)}`,
|
|
16
|
+
]));
|
|
17
|
+
const head = commitOf(field(pull, "head"), "sha");
|
|
18
|
+
if (field(pull, "merged") !== true)
|
|
19
|
+
return { head, merged: undefined };
|
|
20
|
+
if (head !== expected.commit && head !== expected.updated)
|
|
21
|
+
throw new Error("merged-other-commit");
|
|
22
|
+
return { head, merged: commitOf(pull, "merge_commit_sha") };
|
|
23
|
+
}
|
|
24
|
+
async function mergedNow(operation, pull) {
|
|
25
|
+
const reply = JSON.parse(await operation.calls.gh([
|
|
26
|
+
"api",
|
|
27
|
+
"-X",
|
|
28
|
+
"PUT",
|
|
29
|
+
`${repositoryPath}/pulls/${String(pull.number)}/merge`,
|
|
30
|
+
"-f",
|
|
31
|
+
"merge_method=merge",
|
|
32
|
+
"-f",
|
|
33
|
+
`sha=${pull.commit}`,
|
|
34
|
+
]));
|
|
35
|
+
return commitOf(reply, "sha");
|
|
36
|
+
}
|
|
37
|
+
async function waitForHead(operation, pull) {
|
|
38
|
+
const timing = operation.settings.timing ?? githubTiming;
|
|
39
|
+
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
40
|
+
if (attempt > 0)
|
|
41
|
+
await delay(timing.pollMilliseconds, undefined, {
|
|
42
|
+
signal: operation.signal,
|
|
43
|
+
});
|
|
44
|
+
const current = JSON.parse(await githubRead(operation, [
|
|
45
|
+
"api",
|
|
46
|
+
`${repositoryPath}/pulls/${String(pull.number)}`,
|
|
47
|
+
]));
|
|
48
|
+
if (commitOf(field(current, "head"), "sha") === pull.commit)
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
throw new Error("head-not-updated");
|
|
52
|
+
}
|
|
53
|
+
export function mergePull(operation, pull) {
|
|
54
|
+
return retryGithub(operation, {
|
|
55
|
+
action: async (attempt) => {
|
|
56
|
+
const known = attempt > 0 ? await knownMerge(operation, pull) : undefined;
|
|
57
|
+
if (known?.merged !== undefined)
|
|
58
|
+
return known.merged;
|
|
59
|
+
const updated = { number: pull.number, commit: pull.updated };
|
|
60
|
+
if (pull.updated !== pull.commit)
|
|
61
|
+
await waitForHead(operation, updated);
|
|
62
|
+
return mergedNow(operation, updated);
|
|
63
|
+
},
|
|
64
|
+
statuses: pull.updated === pull.commit ? [] : [405, 409],
|
|
65
|
+
}).catch(async (error) => {
|
|
66
|
+
if (!(error instanceof Error) || error.message !== "github-network-failed")
|
|
67
|
+
throw error;
|
|
68
|
+
const known = await knownMerge(operation, pull);
|
|
69
|
+
if (known.merged !== undefined)
|
|
70
|
+
return known.merged;
|
|
71
|
+
throw error;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { type Operation } from "./github-operation.ts";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
readonly commit: string;
|
|
5
|
-
}
|
|
2
|
+
import { type CiResult } from "./github-ci.ts";
|
|
3
|
+
import { type ReviewedPull } from "./github-merge-call.ts";
|
|
6
4
|
interface SuccessfulMerge {
|
|
7
5
|
readonly number: number;
|
|
8
6
|
readonly commit: string;
|
|
@@ -13,6 +11,6 @@ interface MergeRepair {
|
|
|
13
11
|
readonly reason: "check-failed" | "merge-conflict";
|
|
14
12
|
readonly output: string;
|
|
15
13
|
}
|
|
16
|
-
export type MergeResult = SuccessfulMerge | MergeRepair;
|
|
14
|
+
export type MergeResult = SuccessfulMerge | MergeRepair | CiResult;
|
|
17
15
|
export declare function merge(operation: Operation, pull: ReviewedPull): Promise<MergeResult>;
|
|
18
16
|
export {};
|
|
@@ -1,28 +1,10 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { setTimeout as delay } from "node:timers/promises";
|
|
3
2
|
import { readRecord, writeRecord } from "./operation-journal.js";
|
|
4
|
-
import { changeChecked, defaultBranch, existingTree, field,
|
|
3
|
+
import { changeChecked, defaultBranch, existingTree, field, removeWorktree, repositoryPath, } from "./github-operation.js";
|
|
5
4
|
import { treePlace } from "./work-tree.js";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const commit = Reflect.get(value, name);
|
|
10
|
-
if (typeof commit !== "string")
|
|
11
|
-
throw new Error("github-call-failed");
|
|
12
|
-
return commit;
|
|
13
|
-
}
|
|
14
|
-
async function knownMerge(operation, expected) {
|
|
15
|
-
const pull = JSON.parse(await operation.calls.gh([
|
|
16
|
-
"api",
|
|
17
|
-
`${repositoryPath}/pulls/${String(expected.number)}`,
|
|
18
|
-
]));
|
|
19
|
-
const head = commitOf(field(pull, "head"), "sha");
|
|
20
|
-
if (field(pull, "merged") !== true)
|
|
21
|
-
return { head, merged: undefined };
|
|
22
|
-
if (head !== expected.commit && head !== expected.updated)
|
|
23
|
-
throw new Error("merged-other-commit");
|
|
24
|
-
return { head, merged: commitOf(pull, "merge_commit_sha") };
|
|
25
|
-
}
|
|
5
|
+
import { waitForCi } from "./github-ci.js";
|
|
6
|
+
import { githubText } from "./github-calls.js";
|
|
7
|
+
import { knownMerge, mergePull, } from "./github-merge-call.js";
|
|
26
8
|
async function abortMerge(operation, tree) {
|
|
27
9
|
const path = (await operation.calls.git(tree.path, [
|
|
28
10
|
"rev-parse",
|
|
@@ -69,14 +51,19 @@ async function updatedTree(operation, place) {
|
|
|
69
51
|
place.base,
|
|
70
52
|
]);
|
|
71
53
|
}
|
|
72
|
-
catch {
|
|
54
|
+
catch (error) {
|
|
73
55
|
const conflicts = await calls.git(place.tree.path, [
|
|
74
56
|
"diff",
|
|
75
57
|
"--name-only",
|
|
76
58
|
"--diff-filter=U",
|
|
77
59
|
]);
|
|
78
60
|
await calls.git(place.tree.path, ["merge", "--abort"]);
|
|
79
|
-
|
|
61
|
+
const output = commandOutput(error instanceof Error ? error.cause : error);
|
|
62
|
+
throw Object.assign(new Error("merge-conflict", { cause: error }), {
|
|
63
|
+
output: githubText(output + conflicts, operation.settings)
|
|
64
|
+
.slice(-6_000)
|
|
65
|
+
.trim(),
|
|
66
|
+
});
|
|
80
67
|
}
|
|
81
68
|
}
|
|
82
69
|
async function checked(operation, place) {
|
|
@@ -115,52 +102,6 @@ async function refreshed(operation, place) {
|
|
|
115
102
|
}
|
|
116
103
|
throw new Error("base-moved");
|
|
117
104
|
}
|
|
118
|
-
async function mergedNow(operation, pull) {
|
|
119
|
-
const reply = JSON.parse(await operation.calls.gh([
|
|
120
|
-
"api",
|
|
121
|
-
"-X",
|
|
122
|
-
"PUT",
|
|
123
|
-
`${repositoryPath}/pulls/${String(pull.number)}/merge`,
|
|
124
|
-
"-f",
|
|
125
|
-
"merge_method=merge",
|
|
126
|
-
"-f",
|
|
127
|
-
`sha=${pull.commit}`,
|
|
128
|
-
]));
|
|
129
|
-
return commitOf(reply, "sha");
|
|
130
|
-
}
|
|
131
|
-
async function waitForHead(operation, pull) {
|
|
132
|
-
const timing = operation.settings.timing ?? githubTiming;
|
|
133
|
-
for (let attempt = 0; attempt < 30; attempt += 1) {
|
|
134
|
-
if (attempt > 0)
|
|
135
|
-
await delay(timing.pollMilliseconds, undefined, {
|
|
136
|
-
signal: operation.signal,
|
|
137
|
-
});
|
|
138
|
-
const current = JSON.parse(await operation.calls.gh([
|
|
139
|
-
"api",
|
|
140
|
-
`${repositoryPath}/pulls/${String(pull.number)}`,
|
|
141
|
-
]));
|
|
142
|
-
if (commitOf(field(current, "head"), "sha") === pull.commit)
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
throw new Error("head-not-updated");
|
|
146
|
-
}
|
|
147
|
-
async function mergeUpdated(operation, pull) {
|
|
148
|
-
const timing = operation.settings.timing ?? githubTiming;
|
|
149
|
-
for (let attempt = 0;; attempt += 1) {
|
|
150
|
-
await waitForHead(operation, pull);
|
|
151
|
-
try {
|
|
152
|
-
return await mergedNow(operation, pull);
|
|
153
|
-
}
|
|
154
|
-
catch (error) {
|
|
155
|
-
const status = field(error, "status");
|
|
156
|
-
if (attempt === 2 || (status !== 405 && status !== 409))
|
|
157
|
-
throw error;
|
|
158
|
-
await delay(timing.pollMilliseconds, undefined, {
|
|
159
|
-
signal: operation.signal,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
105
|
async function removed(operation, place) {
|
|
165
106
|
const { settings, calls } = operation;
|
|
166
107
|
await calls
|
|
@@ -191,35 +132,47 @@ async function recordedHead(operation, commit) {
|
|
|
191
132
|
}
|
|
192
133
|
async function readyHead(operation, pull) {
|
|
193
134
|
const tree = existingTree(operation);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (pull.recorded !== updated)
|
|
202
|
-
await writeRecord(operation.folder, {
|
|
203
|
-
name: "updated-head.json",
|
|
204
|
-
value: { commit: pull.commit, updated },
|
|
135
|
+
let recorded = pull.recorded;
|
|
136
|
+
let remote = pull.remote;
|
|
137
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
138
|
+
const updated = await refreshed(operation, {
|
|
139
|
+
tree,
|
|
140
|
+
commit: pull.commit,
|
|
141
|
+
recorded,
|
|
205
142
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
143
|
+
if (updated !== pull.commit && recorded !== updated)
|
|
144
|
+
await writeRecord(operation.folder, {
|
|
145
|
+
name: "updated-head.json",
|
|
146
|
+
value: { commit: pull.commit, updated },
|
|
147
|
+
});
|
|
148
|
+
if (updated !== pull.commit && remote !== updated)
|
|
149
|
+
await operation.calls.git(tree.path, [
|
|
150
|
+
"push",
|
|
151
|
+
"--quiet",
|
|
152
|
+
"origin",
|
|
153
|
+
`${updated}:refs/heads/${tree.branch}`,
|
|
154
|
+
]);
|
|
155
|
+
const ci = await waitForCi(operation, updated);
|
|
156
|
+
if (ci !== undefined)
|
|
157
|
+
return ci;
|
|
158
|
+
const newest = await fetched(operation, await defaultBranch(operation));
|
|
159
|
+
if ((await operation.calls.git(tree.path, ["merge-base", updated, newest])).trim() === newest)
|
|
160
|
+
return updated;
|
|
161
|
+
recorded = updated;
|
|
162
|
+
remote = updated;
|
|
163
|
+
}
|
|
164
|
+
throw new Error("base-moved");
|
|
214
165
|
}
|
|
215
166
|
function checkOutput(cause) {
|
|
167
|
+
return commandOutput(cause).slice(-6_000);
|
|
168
|
+
}
|
|
169
|
+
function commandOutput(cause) {
|
|
216
170
|
return ["stdout", "stderr"]
|
|
217
171
|
.map((name) => {
|
|
218
172
|
const value = field(cause, name);
|
|
219
173
|
return typeof value === "string" ? value : "";
|
|
220
174
|
})
|
|
221
|
-
.join("")
|
|
222
|
-
.slice(-6_000);
|
|
175
|
+
.join("");
|
|
223
176
|
}
|
|
224
177
|
export async function merge(operation, pull) {
|
|
225
178
|
try {
|
|
@@ -232,10 +185,13 @@ export async function merge(operation, pull) {
|
|
|
232
185
|
const reason = error.message;
|
|
233
186
|
if (reason !== "check-failed" && reason !== "merge-conflict")
|
|
234
187
|
throw error;
|
|
235
|
-
const
|
|
188
|
+
const detail = field(error, "output");
|
|
189
|
+
const conflicts = typeof detail === "string" ? detail : "";
|
|
236
190
|
return {
|
|
237
191
|
reason,
|
|
238
|
-
output: reason === "check-failed"
|
|
192
|
+
output: reason === "check-failed"
|
|
193
|
+
? checkOutput(error.cause)
|
|
194
|
+
: githubText(conflicts, operation.settings),
|
|
239
195
|
};
|
|
240
196
|
}
|
|
241
197
|
}
|
|
@@ -252,8 +208,9 @@ async function completedMerge(operation, pull) {
|
|
|
252
208
|
const updated = known.merged === undefined
|
|
253
209
|
? await readyHead(operation, { ...pull, recorded, remote: known.head })
|
|
254
210
|
: (recorded ?? pull.commit);
|
|
255
|
-
|
|
256
|
-
|
|
211
|
+
if (typeof updated !== "string")
|
|
212
|
+
return updated;
|
|
213
|
+
const merged = known.merged ?? (await mergePull(operation, { ...pull, updated }));
|
|
257
214
|
await removed(operation, place);
|
|
258
215
|
return { number: pull.number, commit: pull.commit, updated, merged };
|
|
259
216
|
}
|
|
@@ -1,5 +1,33 @@
|
|
|
1
|
+
import { type Finding, type Scored } from "./github-calls.ts";
|
|
1
2
|
import { type Operation } from "./github-operation.ts";
|
|
2
3
|
export type { GithubTiming, MethodSettings } from "./github-operation.ts";
|
|
3
4
|
export declare const methodReasons: ReadonlySet<string>;
|
|
5
|
+
export interface ReviewResult extends Scored {
|
|
6
|
+
readonly number: number;
|
|
7
|
+
readonly commit: string;
|
|
8
|
+
readonly findings: readonly Finding[];
|
|
9
|
+
}
|
|
10
|
+
interface ReviewFailure {
|
|
11
|
+
readonly call: "read-review" | "request-review";
|
|
12
|
+
readonly reason: "github-call-failed" | "invalid-review-response";
|
|
13
|
+
readonly status?: number;
|
|
14
|
+
}
|
|
15
|
+
interface ReviewClock {
|
|
16
|
+
readonly now: () => number;
|
|
17
|
+
readonly sleep: (milliseconds: number, signal: AbortSignal) => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
interface ReviewWait {
|
|
20
|
+
readonly clock: ReviewClock;
|
|
21
|
+
failedReads: number;
|
|
22
|
+
requests: number;
|
|
23
|
+
acceptedRequests: number;
|
|
24
|
+
requestedAt: number;
|
|
25
|
+
lastFailure: ReviewFailure | null;
|
|
26
|
+
}
|
|
27
|
+
export declare class ReviewTimeout extends Error {
|
|
28
|
+
readonly detail: string;
|
|
29
|
+
constructor(wait: ReviewWait);
|
|
30
|
+
}
|
|
31
|
+
export declare function waitReview(operation: Operation, clock?: ReviewClock): Promise<ReviewResult>;
|
|
4
32
|
export type Method = (operation: Operation) => Promise<unknown>;
|
|
5
33
|
export declare const methods: ReadonlyMap<string, Method>;
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
-
import {
|
|
2
|
+
import { appendFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { findingsOf, scoreOf, } from "./github-calls.js";
|
|
5
|
+
import { closeIssue, partialIssue } from "./github-ticket-status.js";
|
|
3
6
|
import { merge } from "./github-merge.js";
|
|
4
7
|
import { createIssue, discard } from "./github-issue-methods.js";
|
|
5
8
|
import { existingTree, defaultBranch, changeChecked, field, githubTiming, repositoryPath, text, } from "./github-operation.js";
|
|
6
9
|
import { workTree } from "./work-tree.js";
|
|
7
10
|
const triggerText = "@greptileai";
|
|
11
|
+
const maximumReviewRequests = 3;
|
|
8
12
|
export const methodReasons = new Set([
|
|
9
13
|
"draft-not-available",
|
|
10
14
|
"invalid-draft",
|
|
11
15
|
"post-uncertain",
|
|
12
16
|
"private-texts-not-set",
|
|
13
17
|
"github-call-failed",
|
|
18
|
+
"github-network-failed",
|
|
19
|
+
"github-issue-conflict",
|
|
14
20
|
"git-call-failed",
|
|
15
21
|
"check-failed",
|
|
16
22
|
"merge-conflict",
|
|
@@ -155,34 +161,96 @@ async function reviewed(operation, pull) {
|
|
|
155
161
|
const lines = await read(`pulls/${String(number)}/comments?per_page=100`);
|
|
156
162
|
return { number, commit, ...scored, findings: findingsOf(lines, commit) };
|
|
157
163
|
}
|
|
158
|
-
|
|
164
|
+
const reviewClock = {
|
|
165
|
+
now: () => Date.now(),
|
|
166
|
+
sleep: async (milliseconds, signal) => {
|
|
167
|
+
await delay(milliseconds, undefined, { signal });
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
export class ReviewTimeout extends Error {
|
|
171
|
+
detail;
|
|
172
|
+
constructor(wait) {
|
|
173
|
+
super("review-not-received");
|
|
174
|
+
this.detail = JSON.stringify({
|
|
175
|
+
failedReads: wait.failedReads,
|
|
176
|
+
requests: wait.requests,
|
|
177
|
+
acceptedRequests: wait.acceptedRequests,
|
|
178
|
+
lastFailure: wait.lastFailure,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
async function reviewFailure(operation, failed) {
|
|
183
|
+
const { wait, call, error } = failed;
|
|
184
|
+
const status = field(error, "status");
|
|
185
|
+
const failure = {
|
|
186
|
+
call,
|
|
187
|
+
reason: error instanceof SyntaxError
|
|
188
|
+
? "invalid-review-response"
|
|
189
|
+
: "github-call-failed",
|
|
190
|
+
...(typeof status === "number" &&
|
|
191
|
+
Number.isInteger(status) &&
|
|
192
|
+
status >= 400 &&
|
|
193
|
+
status <= 599
|
|
194
|
+
? { status }
|
|
195
|
+
: {}),
|
|
196
|
+
};
|
|
197
|
+
wait.lastFailure = failure;
|
|
198
|
+
await appendFile(join(operation.folder, "review-failures.jsonl"), JSON.stringify(failure) + "\n", { mode: 0o600 }).catch(() => undefined);
|
|
199
|
+
}
|
|
200
|
+
async function requestReview(operation, request) {
|
|
201
|
+
const { wait, number } = request;
|
|
202
|
+
wait.requests++;
|
|
203
|
+
try {
|
|
204
|
+
await operation.calls.gh([
|
|
205
|
+
"pr",
|
|
206
|
+
"comment",
|
|
207
|
+
String(number),
|
|
208
|
+
"--body",
|
|
209
|
+
triggerText,
|
|
210
|
+
]);
|
|
211
|
+
wait.acceptedRequests++;
|
|
212
|
+
wait.requestedAt = wait.clock.now();
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
await reviewFailure(operation, { wait, call: "request-review", error });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
export async function waitReview(operation, clock = reviewClock) {
|
|
159
219
|
const pull = {
|
|
160
220
|
number: integer(operation, "number"),
|
|
161
221
|
commit: text(operation, "commit"),
|
|
162
222
|
};
|
|
163
223
|
const timing = operation.settings.timing ?? githubTiming;
|
|
164
|
-
const startedAt =
|
|
165
|
-
|
|
224
|
+
const startedAt = clock.now();
|
|
225
|
+
const wait = {
|
|
226
|
+
clock,
|
|
227
|
+
failedReads: 0,
|
|
228
|
+
requests: 0,
|
|
229
|
+
acceptedRequests: 0,
|
|
230
|
+
requestedAt: startedAt,
|
|
231
|
+
lastFailure: null,
|
|
232
|
+
};
|
|
166
233
|
for (;;) {
|
|
167
|
-
const review = await reviewed(operation, pull).catch(() =>
|
|
234
|
+
const review = await reviewed(operation, pull).catch(async (error) => {
|
|
235
|
+
wait.failedReads++;
|
|
236
|
+
await reviewFailure(operation, { wait, call: "read-review", error });
|
|
237
|
+
return undefined;
|
|
238
|
+
});
|
|
168
239
|
if (review !== undefined)
|
|
169
240
|
return review;
|
|
170
|
-
const waited =
|
|
241
|
+
const waited = clock.now() - startedAt;
|
|
171
242
|
if (waited >= timing.limitMilliseconds)
|
|
172
|
-
throw new
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
await operation.
|
|
176
|
-
|
|
177
|
-
.catch(() => undefined);
|
|
178
|
-
}
|
|
179
|
-
await delay(timing.pollMilliseconds, undefined, {
|
|
180
|
-
signal: operation.signal,
|
|
181
|
-
});
|
|
243
|
+
throw new ReviewTimeout(wait);
|
|
244
|
+
if (wait.acceptedRequests < maximumReviewRequests &&
|
|
245
|
+
clock.now() - wait.requestedAt >= timing.triggerMilliseconds)
|
|
246
|
+
await requestReview(operation, { wait, number: pull.number });
|
|
247
|
+
await clock.sleep(timing.pollMilliseconds, operation.signal);
|
|
182
248
|
}
|
|
183
249
|
}
|
|
184
250
|
export const methods = new Map([
|
|
185
251
|
["prepare", prepare],
|
|
252
|
+
["close-issue", closeIssue],
|
|
253
|
+
["partial-issue", partialIssue],
|
|
186
254
|
["label", label],
|
|
187
255
|
["create-issue", createIssue],
|
|
188
256
|
["discard", discard],
|
|
@@ -3,6 +3,8 @@ export interface GithubTiming {
|
|
|
3
3
|
readonly pollMilliseconds: number;
|
|
4
4
|
readonly triggerMilliseconds: number;
|
|
5
5
|
readonly limitMilliseconds: number;
|
|
6
|
+
readonly ciLimitMilliseconds?: number;
|
|
7
|
+
readonly ciGraceMilliseconds?: number;
|
|
6
8
|
}
|
|
7
9
|
export declare const githubTiming: GithubTiming;
|
|
8
10
|
export interface MethodSettings extends CallSettings {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Operation } from "./github-operation.ts";
|
|
2
|
+
export declare function retryGithub<Result>(operation: Operation, retry: {
|
|
3
|
+
readonly action: (attempt: number) => Promise<Result>;
|
|
4
|
+
readonly statuses?: readonly number[];
|
|
5
|
+
}): Promise<Result>;
|
|
6
|
+
export declare function githubRead(operation: Operation, parts: readonly string[]): Promise<string>;
|