cloving 1.1.0 → 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 +121 -21
- 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 +25 -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 +1 -0
- package/dist/src/authority/reconciliation.js +18 -11
- package/dist/src/authority/transitions.d.ts +1 -1
- package/dist/src/authority/transitions.js +3 -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/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 +18 -0
- package/dist/src/chats/analysis-batches.js +15 -1
- package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
- package/dist/src/chats/analysis-checkpoint.js +31 -0
- package/dist/src/chats/analysis-input.d.ts +7 -1
- package/dist/src/chats/analysis-input.js +20 -6
- 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 +8 -0
- package/dist/src/chats/analysis-prompt.js +27 -1
- package/dist/src/chats/analysis-proposal.d.ts +2 -0
- package/dist/src/chats/analysis-proposal.js +9 -0
- package/dist/src/chats/analysis-shape.d.ts +1 -0
- package/dist/src/chats/analysis-shape.js +11 -4
- package/dist/src/chats/reference-mapping.d.ts +16 -0
- package/dist/src/chats/reference-mapping.js +153 -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/render.d.ts +1 -0
- package/dist/src/cli/render.js +60 -13
- package/dist/src/cli/usage.js +5 -2
- package/dist/src/cli/watch-board-view.d.ts +12 -1
- package/dist/src/cli/watch-board-view.js +57 -11
- package/dist/src/cli/watch-board.d.ts +7 -1
- package/dist/src/cli/watch-board.js +42 -10
- package/dist/src/cli/watch-client.d.ts +1 -1
- package/dist/src/cli/watch-client.js +7 -3
- package/dist/src/cli/watch-model.d.ts +10 -4
- package/dist/src/cli/watch-model.js +35 -58
- package/dist/src/cli/watch-page.js +21 -4
- package/dist/src/cli/watch-phases.js +3 -3
- package/dist/src/cli/watch-pr-command.d.ts +5 -2
- package/dist/src/cli/watch-pr-command.js +41 -4
- package/dist/src/cli/watch-pr-identity.d.ts +14 -2
- package/dist/src/cli/watch-pr-identity.js +25 -0
- package/dist/src/cli/watch-pr-observer.d.ts +1 -0
- package/dist/src/cli/watch-pr-observer.js +48 -6
- 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 +42 -20
- package/dist/src/cli/watch-render.d.ts +14 -1
- package/dist/src/cli/watch-render.js +2 -1
- package/dist/src/cli/watch-server.js +33 -2
- package/dist/src/cli/watch-turn.js +1 -1
- package/dist/src/cli/watch-view.d.ts +7 -0
- package/dist/src/cli/watch-view.js +51 -3
- package/dist/src/daemon/analysis-worker.d.ts +0 -2
- package/dist/src/daemon/analysis-worker.js +71 -19
- 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 +29 -2
- package/dist/src/daemon/chats-files.js +142 -3
- 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 +1 -0
- package/dist/src/daemon/chats-worker.js +52 -51
- package/dist/src/daemon/claude-turn.d.ts +2 -1
- package/dist/src/daemon/claude-turn.js +1 -1
- package/dist/src/daemon/claude-worker.d.ts +0 -2
- package/dist/src/daemon/claude-worker.js +6 -11
- package/dist/src/daemon/daemon.d.ts +5 -1
- package/dist/src/daemon/daemon.js +38 -10
- 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 +80 -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.js +27 -1
- 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 +1 -0
- package/dist/src/daemon/issue-queue.js +94 -42
- 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/retrying-turn.d.ts +3 -0
- package/dist/src/daemon/retrying-turn.js +55 -9
- 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 +1 -0
- package/dist/src/host/world-reconciliation.js +3 -0
- package/dist/src/host/world-runs.js +5 -0
- 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 +44 -12
- 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.js +25 -1
- 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.js +7 -4
- package/dist/src/token-prices.d.ts +2 -0
- package/dist/src/token-prices.js +41 -9
- package/dist/src/worker-usage.d.ts +9 -0
- package/dist/src/worker-usage.js +1 -1
- package/dist/src/workflow-admission.d.ts +1 -0
- package/dist/src/workflow-candidate.js +1 -0
- package/dist/src/workflow-contracts.d.ts +2 -0
- package/dist/src/workflow-contracts.js +8 -3
- package/dist/src/workflow-declaration.d.ts +11 -0
- package/dist/src/workflow-declaration.js +71 -15
- 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 +2 -1
- package/dist/src/workflows.js +1 -0
- package/dist/tools/admit-workflow.d.ts +13 -0
- package/dist/tools/admit-workflow.js +94 -0
- package/dist/tools/self-host-project.d.ts +17 -0
- package/dist/tools/self-host-project.js +177 -0
- package/dist/tools/self-host.js +27 -81
- 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 +6 -1
- package/dist/workflows/analyze-chats.declaration.json +14 -1
- package/dist/workflows/analyze-chats.js +188 -11
- package/dist/workflows/develop-prd.js +71 -3
- package/dist/workflows/develop-ticket.js +5 -0
- package/dist/workflows/ticket-prompts.js +12 -2
- package/package.json +21 -3
|
@@ -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,17 +1,21 @@
|
|
|
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";
|
|
3
5
|
import { closeIssue, partialIssue } from "./github-ticket-status.js";
|
|
4
6
|
import { merge } from "./github-merge.js";
|
|
5
7
|
import { createIssue, discard } from "./github-issue-methods.js";
|
|
6
8
|
import { existingTree, defaultBranch, changeChecked, field, githubTiming, repositoryPath, text, } from "./github-operation.js";
|
|
7
9
|
import { workTree } from "./work-tree.js";
|
|
8
10
|
const triggerText = "@greptileai";
|
|
11
|
+
const maximumReviewRequests = 3;
|
|
9
12
|
export const methodReasons = new Set([
|
|
10
13
|
"draft-not-available",
|
|
11
14
|
"invalid-draft",
|
|
12
15
|
"post-uncertain",
|
|
13
16
|
"private-texts-not-set",
|
|
14
17
|
"github-call-failed",
|
|
18
|
+
"github-network-failed",
|
|
15
19
|
"github-issue-conflict",
|
|
16
20
|
"git-call-failed",
|
|
17
21
|
"check-failed",
|
|
@@ -157,30 +161,90 @@ async function reviewed(operation, pull) {
|
|
|
157
161
|
const lines = await read(`pulls/${String(number)}/comments?per_page=100`);
|
|
158
162
|
return { number, commit, ...scored, findings: findingsOf(lines, commit) };
|
|
159
163
|
}
|
|
160
|
-
|
|
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) {
|
|
161
219
|
const pull = {
|
|
162
220
|
number: integer(operation, "number"),
|
|
163
221
|
commit: text(operation, "commit"),
|
|
164
222
|
};
|
|
165
223
|
const timing = operation.settings.timing ?? githubTiming;
|
|
166
|
-
const startedAt =
|
|
167
|
-
|
|
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
|
+
};
|
|
168
233
|
for (;;) {
|
|
169
|
-
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
|
+
});
|
|
170
239
|
if (review !== undefined)
|
|
171
240
|
return review;
|
|
172
|
-
const waited =
|
|
241
|
+
const waited = clock.now() - startedAt;
|
|
173
242
|
if (waited >= timing.limitMilliseconds)
|
|
174
|
-
throw new
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
await operation.
|
|
178
|
-
|
|
179
|
-
.catch(() => undefined);
|
|
180
|
-
}
|
|
181
|
-
await delay(timing.pollMilliseconds, undefined, {
|
|
182
|
-
signal: operation.signal,
|
|
183
|
-
});
|
|
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);
|
|
184
248
|
}
|
|
185
249
|
}
|
|
186
250
|
export const methods = new Map([
|
|
@@ -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>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { setTimeout as delay } from "node:timers/promises";
|
|
2
|
+
import { field, githubTiming } from "./github-operation.js";
|
|
3
|
+
function transient(error) {
|
|
4
|
+
if (!(error instanceof Error) || error.message !== "github-call-failed")
|
|
5
|
+
return false;
|
|
6
|
+
const detail = field(error, "detail");
|
|
7
|
+
if (typeof detail !== "string")
|
|
8
|
+
return false;
|
|
9
|
+
const status = field(error, "status");
|
|
10
|
+
if ([500, 502, 503, 504].includes(Number(status)))
|
|
11
|
+
return true;
|
|
12
|
+
return ((status === undefined ||
|
|
13
|
+
(typeof status === "number" && Number.isNaN(status))) &&
|
|
14
|
+
/TLS handshake timeout|connection reset|i\/o timeout|\bEOF\b/i.test(detail));
|
|
15
|
+
}
|
|
16
|
+
function retryFactor(error, retry) {
|
|
17
|
+
const network = transient(error);
|
|
18
|
+
if (!network && !retry.statuses.includes(Number(field(error, "status"))))
|
|
19
|
+
throw error;
|
|
20
|
+
if (retry.attempt < 2)
|
|
21
|
+
return network ? retry.attempt + 1 : 1;
|
|
22
|
+
if (!network)
|
|
23
|
+
throw error;
|
|
24
|
+
throw Object.assign(new Error("github-network-failed", { cause: error }), {
|
|
25
|
+
detail: field(error, "detail"),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function retryGithub(operation, retry) {
|
|
29
|
+
const timing = operation.settings.timing ?? githubTiming;
|
|
30
|
+
for (let attempt = 0;; attempt += 1) {
|
|
31
|
+
operation.signal.throwIfAborted();
|
|
32
|
+
try {
|
|
33
|
+
return await retry.action(attempt);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
operation.signal.throwIfAborted();
|
|
37
|
+
const factor = retryFactor(error, {
|
|
38
|
+
attempt,
|
|
39
|
+
statuses: retry.statuses ?? [],
|
|
40
|
+
});
|
|
41
|
+
await delay(timing.pollMilliseconds * factor, undefined, {
|
|
42
|
+
signal: operation.signal,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function githubRead(operation, parts) {
|
|
48
|
+
return retryGithub(operation, { action: () => operation.calls.gh(parts) });
|
|
49
|
+
}
|
|
@@ -87,12 +87,38 @@ function statusText(history) {
|
|
|
87
87
|
lines.push(`Completed in pull request #${String(history.completedPullRequest)}.`);
|
|
88
88
|
return `## Cloving status\n${lines.join("\n")}\n\n`;
|
|
89
89
|
}
|
|
90
|
+
function legacyFence(line) {
|
|
91
|
+
const marker = /^( {0,3}(?:[-+*]|\d{1,9}[.)])[ \t]+)? {0,3}(`{3,}|~{3,})/.exec(line);
|
|
92
|
+
if (marker === null || marker[2] === undefined)
|
|
93
|
+
return undefined;
|
|
94
|
+
const delimiter = marker[2];
|
|
95
|
+
if (delimiter.startsWith("`") && line.slice(marker[0].length).includes("`"))
|
|
96
|
+
return undefined;
|
|
97
|
+
const indentation = (marker[1]?.length ?? 0) + 3;
|
|
98
|
+
return new RegExp(`^ {0,${String(indentation)}}${delimiter.charAt(0)}{${String(delimiter.length)},}[ \\t]*\\r?\\n?$`);
|
|
99
|
+
}
|
|
100
|
+
function legacyStatusStart(body) {
|
|
101
|
+
let fence;
|
|
102
|
+
let offset = 0;
|
|
103
|
+
for (const line of body.split(/(?<=\n)/)) {
|
|
104
|
+
const start = offset;
|
|
105
|
+
offset += line.length;
|
|
106
|
+
if (fence !== undefined) {
|
|
107
|
+
fence = fence.test(line) ? undefined : fence;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (line.trimEnd() === "## Cloving status")
|
|
111
|
+
return start;
|
|
112
|
+
fence = legacyFence(line);
|
|
113
|
+
}
|
|
114
|
+
return body.length;
|
|
115
|
+
}
|
|
90
116
|
function statusSection(body) {
|
|
91
117
|
const empty = { version: 1, partialRuns: [] };
|
|
92
118
|
const marker = /^<!-- cloving-status(?=\s|-->|$)/gm;
|
|
93
119
|
const start = [...body.matchAll(marker)].at(-1)?.index;
|
|
94
120
|
if (start === undefined)
|
|
95
|
-
return { outside: body, history: empty };
|
|
121
|
+
return { outside: body.slice(0, legacyStatusStart(body)), history: empty };
|
|
96
122
|
const tail = body.slice(start);
|
|
97
123
|
const end = tail.indexOf("-->");
|
|
98
124
|
if (end !== -1 && tail.slice(end + 3).trim() !== "")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { calls } from "./github-calls.js";
|
|
4
|
-
import { methodReasons, methods, } from "./github-methods.js";
|
|
4
|
+
import { methodReasons, methods, ReviewTimeout, } from "./github-methods.js";
|
|
5
5
|
import { interrupted, located, readRecord, replied, reportOf, writeRecord, } from "./operation-journal.js";
|
|
6
6
|
const worker = "github";
|
|
7
7
|
const closing = "github-worker-closed";
|
|
@@ -62,6 +62,7 @@ async function outcome(parts, run) {
|
|
|
62
62
|
return {
|
|
63
63
|
...interrupted(methodReasons.has(reason) ? reason : "github-call-failed"),
|
|
64
64
|
...(typeof detail === "string" ? { detail } : {}),
|
|
65
|
+
...(error instanceof ReviewTimeout ? { result: error.detail } : {}),
|
|
65
66
|
};
|
|
66
67
|
}
|
|
67
68
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { EffectDelivery } from "./delivery-router.ts";
|
|
2
|
+
interface Settings {
|
|
3
|
+
readonly runtimeDirectory: string;
|
|
4
|
+
readonly journal: string;
|
|
5
|
+
readonly document: (digest: string) => Promise<string | undefined>;
|
|
6
|
+
readonly runStatus: (runId: string) => Promise<string | undefined>;
|
|
7
|
+
readonly retryMilliseconds?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function handoffWorker(settings: Settings): EffectDelivery;
|
|
10
|
+
export {};
|