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,8 +1,9 @@
|
|
|
1
|
+
import { decisionGate } from "cloving/workflows/steps/decision-gate";
|
|
1
2
|
import { protectedEffect } from "cloving/workflows/steps/protected-effect";
|
|
2
3
|
function request(importId, step) {
|
|
3
4
|
const { operation, payload } = step;
|
|
4
5
|
return {
|
|
5
|
-
scope: { phase: "analysis", operation, round: 1 },
|
|
6
|
+
scope: { phase: "analysis", operation, round: step.round ?? 1 },
|
|
6
7
|
capability: operation === "turn-analysis" ? "claude" : "chats",
|
|
7
8
|
method: operation === "turn-analysis" ? "run-turn:analyze-chats" : operation,
|
|
8
9
|
target: importId,
|
|
@@ -33,14 +34,72 @@ function fields(result) {
|
|
|
33
34
|
return {};
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
-
"
|
|
37
|
+
function usableDraft(cycleId, draft) {
|
|
38
|
+
return (draft["cycleId"] === cycleId &&
|
|
39
|
+
typeof draft["candidateDigest"] === "string" &&
|
|
40
|
+
/^[a-f0-9]{64}$/.test(draft["candidateDigest"]) &&
|
|
41
|
+
typeof draft["expiresAt"] === "string" &&
|
|
42
|
+
record(draft["question"]));
|
|
43
|
+
}
|
|
44
|
+
function draftAnswer(draft, decision) {
|
|
45
|
+
if (decision.decision === "expired")
|
|
46
|
+
return "analysis-answer-missing";
|
|
47
|
+
const question = draft["question"];
|
|
48
|
+
const choices = record(question) ? question["choices"] : undefined;
|
|
49
|
+
if (decision.gateId !== "analysis-question" ||
|
|
50
|
+
decision.decision !== "answered" ||
|
|
51
|
+
typeof decision.answer !== "string" ||
|
|
52
|
+
!Array.isArray(choices) ||
|
|
53
|
+
!choices.some((choice) => record(choice) && choice["id"] === decision.answer))
|
|
54
|
+
return "analysis-answer-invalid";
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
async function askDraft(cycleId, continuation) {
|
|
58
|
+
const { draft, decision } = continuation;
|
|
59
|
+
if (!usableDraft(cycleId, draft))
|
|
60
|
+
return {
|
|
61
|
+
kind: "blocked",
|
|
62
|
+
cycleId,
|
|
63
|
+
reason: "analysis-input-refused",
|
|
64
|
+
evidenceId: "save-analysis",
|
|
65
|
+
};
|
|
66
|
+
if (decision !== undefined) {
|
|
67
|
+
const reason = draftAnswer(draft, decision);
|
|
68
|
+
if (reason === null)
|
|
69
|
+
return { kind: "answered", answer: decision.answer ?? "" };
|
|
70
|
+
return {
|
|
71
|
+
kind: "blocked",
|
|
72
|
+
cycleId,
|
|
73
|
+
reason,
|
|
74
|
+
evidenceId: "analysis-question",
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const gate = await decisionGate({
|
|
78
|
+
gateId: "analysis-question",
|
|
79
|
+
kind: "question",
|
|
80
|
+
prompt: JSON.stringify({
|
|
81
|
+
candidateDigest: draft["candidateDigest"],
|
|
82
|
+
question: draft["question"],
|
|
83
|
+
}),
|
|
84
|
+
expiresAt: draft.expiresAt,
|
|
85
|
+
});
|
|
86
|
+
if (gate.kind !== "opened")
|
|
87
|
+
return {
|
|
88
|
+
kind: "blocked",
|
|
89
|
+
cycleId,
|
|
90
|
+
reason: gate.reason,
|
|
91
|
+
evidenceId: "analysis-question",
|
|
92
|
+
};
|
|
93
|
+
return { kind: "waiting_for_decision", cycleId, decisionId: gate.decisionId };
|
|
94
|
+
}
|
|
95
|
+
async function firstTurn(input) {
|
|
38
96
|
const { cycleId } = input;
|
|
39
97
|
const { importId } = input.input;
|
|
40
98
|
const read = await protectedEffect(request(importId, { operation: "read-analysis", payload: { importId } }));
|
|
41
99
|
if (read.kind !== "completed")
|
|
42
100
|
return blocked(cycleId, { operation: "read-analysis", result: read });
|
|
43
|
-
const
|
|
101
|
+
const captured = fields(read.result);
|
|
102
|
+
const snapshot = captured["snapshot"];
|
|
44
103
|
if (!record(snapshot) || snapshot["importId"] !== importId)
|
|
45
104
|
return {
|
|
46
105
|
kind: "blocked",
|
|
@@ -48,7 +107,29 @@ export async function analyzeChats(input) {
|
|
|
48
107
|
reason: "analysis-input-refused",
|
|
49
108
|
evidenceId: "read-analysis",
|
|
50
109
|
};
|
|
51
|
-
|
|
110
|
+
return runTurn(input, batchData(captured, snapshot));
|
|
111
|
+
}
|
|
112
|
+
function batchData(value, snapshot) {
|
|
113
|
+
const context = value["context"];
|
|
114
|
+
const batch = value["batch"];
|
|
115
|
+
return {
|
|
116
|
+
snapshot,
|
|
117
|
+
...(record(context) ? { context } : {}),
|
|
118
|
+
...(record(batch) ? { batch } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
async function runTurn(input, data) {
|
|
122
|
+
const { cycleId } = input;
|
|
123
|
+
const { snapshot, followUp, context } = data;
|
|
124
|
+
const turn = await protectedEffect(request(input.input.importId, {
|
|
125
|
+
operation: "turn-analysis",
|
|
126
|
+
payload: {
|
|
127
|
+
snapshot,
|
|
128
|
+
...(context === undefined ? {} : { context }),
|
|
129
|
+
...(followUp === undefined ? {} : { followUp }),
|
|
130
|
+
},
|
|
131
|
+
round: turnRound(data),
|
|
132
|
+
}));
|
|
52
133
|
if (turn.kind !== "completed")
|
|
53
134
|
return blocked(cycleId, { operation: "turn-analysis", result: turn });
|
|
54
135
|
const reply = fields(turn.result)["reply"];
|
|
@@ -59,15 +140,111 @@ export async function analyzeChats(input) {
|
|
|
59
140
|
reason: "analysis-turn-failed",
|
|
60
141
|
evidenceId: "turn-analysis",
|
|
61
142
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}));
|
|
66
|
-
if (save.kind !== "completed")
|
|
67
|
-
return blocked(cycleId, { operation: "save-analysis", result: save });
|
|
143
|
+
return { kind: "analyzed", data, reply };
|
|
144
|
+
}
|
|
145
|
+
function handoff(cycleId, importId) {
|
|
68
146
|
return {
|
|
69
147
|
kind: "handoff_requested",
|
|
70
148
|
cycleId,
|
|
71
149
|
promotionId: `analysis:${importId}:${cycleId}`,
|
|
72
150
|
};
|
|
73
151
|
}
|
|
152
|
+
function turnRound(data) {
|
|
153
|
+
if (data.followUp !== undefined)
|
|
154
|
+
return data.batch === undefined ? 2 : 5;
|
|
155
|
+
return Number(data.batch?.["batchIndex"] ?? 0) + 1;
|
|
156
|
+
}
|
|
157
|
+
async function saveTurn(input, turn) {
|
|
158
|
+
const { batch, followUp } = turn.data;
|
|
159
|
+
return protectedEffect(request(input.input.importId, {
|
|
160
|
+
operation: "save-analysis",
|
|
161
|
+
round: turnRound(turn.data),
|
|
162
|
+
payload: {
|
|
163
|
+
importId: input.input.importId,
|
|
164
|
+
cycleId: input.cycleId,
|
|
165
|
+
reply: turn.reply,
|
|
166
|
+
...(batch === undefined ? {} : { batch }),
|
|
167
|
+
...(followUp === undefined ? {} : { followUp }),
|
|
168
|
+
},
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
171
|
+
export async function analyzeChats(input, decision) {
|
|
172
|
+
"use workflow";
|
|
173
|
+
const { cycleId } = input;
|
|
174
|
+
const { importId } = input.input;
|
|
175
|
+
let analyzed = await firstTurn(input);
|
|
176
|
+
let answered = false;
|
|
177
|
+
while (analyzed.kind === "analyzed") {
|
|
178
|
+
const { data, reply } = analyzed;
|
|
179
|
+
const save = await saveTurn(input, analyzed);
|
|
180
|
+
if (save.kind !== "completed")
|
|
181
|
+
return blocked(cycleId, { operation: "save-analysis", result: save });
|
|
182
|
+
const saved = fields(save.result);
|
|
183
|
+
if (saved["kind"] === "analysis_draft") {
|
|
184
|
+
analyzed = await questionTurn(input, {
|
|
185
|
+
saved,
|
|
186
|
+
data,
|
|
187
|
+
reply,
|
|
188
|
+
decision,
|
|
189
|
+
answered,
|
|
190
|
+
});
|
|
191
|
+
answered = true;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (saved["kind"] !== "analysis_batch")
|
|
195
|
+
return finishBatch(input, {
|
|
196
|
+
saved,
|
|
197
|
+
decision: answered ? undefined : decision,
|
|
198
|
+
});
|
|
199
|
+
const nextSnapshot = saved["snapshot"];
|
|
200
|
+
if (!record(nextSnapshot) || nextSnapshot["importId"] !== importId)
|
|
201
|
+
return blocked(cycleId, {
|
|
202
|
+
operation: "save-analysis",
|
|
203
|
+
result: { kind: "refused", reason: "analysis-input-refused" },
|
|
204
|
+
});
|
|
205
|
+
analyzed = await runTurn(input, batchData(saved, nextSnapshot));
|
|
206
|
+
}
|
|
207
|
+
return analyzed;
|
|
208
|
+
}
|
|
209
|
+
async function questionTurn(input, continuation) {
|
|
210
|
+
const { saved, data, reply, decision, answered } = continuation;
|
|
211
|
+
if (answered)
|
|
212
|
+
return blocked(input.cycleId, {
|
|
213
|
+
operation: "save-analysis",
|
|
214
|
+
result: { kind: "refused", reason: "analysis-question-limit" },
|
|
215
|
+
});
|
|
216
|
+
const answer = await askDraft(input.cycleId, { draft: saved, decision });
|
|
217
|
+
if (answer.kind !== "answered")
|
|
218
|
+
return answer;
|
|
219
|
+
return runTurn(input, {
|
|
220
|
+
...data,
|
|
221
|
+
followUp: { draft: fields(reply), answer: answer.answer },
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
function finishBatch(input, result) {
|
|
225
|
+
const { cycleId } = input;
|
|
226
|
+
const { importId } = input.input;
|
|
227
|
+
const { saved, decision } = result;
|
|
228
|
+
if (saved["kind"] === "analysis_partial")
|
|
229
|
+
return {
|
|
230
|
+
kind: "blocked",
|
|
231
|
+
cycleId,
|
|
232
|
+
reason: String(saved["reason"]),
|
|
233
|
+
evidenceId: "save-analysis",
|
|
234
|
+
};
|
|
235
|
+
if (decision !== undefined)
|
|
236
|
+
return {
|
|
237
|
+
kind: "blocked",
|
|
238
|
+
cycleId,
|
|
239
|
+
reason: "analysis-answer-invalid",
|
|
240
|
+
evidenceId: "analysis-question",
|
|
241
|
+
};
|
|
242
|
+
if (saved["kind"] !== "handoff_requested")
|
|
243
|
+
return {
|
|
244
|
+
kind: "blocked",
|
|
245
|
+
cycleId,
|
|
246
|
+
reason: "analysis-input-refused",
|
|
247
|
+
evidenceId: "save-analysis",
|
|
248
|
+
};
|
|
249
|
+
return handoff(cycleId, importId);
|
|
250
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { protectedEffect } from "cloving/workflows/steps/protected-effect";
|
|
2
|
+
import { handoff } from "cloving/workflows/steps/handoff";
|
|
2
3
|
const draft = "cloving-prd-draft.md";
|
|
3
4
|
const noCommit = "Do not commit and do not push. Work only in this work tree.";
|
|
4
5
|
function issueNumber(result) {
|
|
@@ -67,9 +68,76 @@ function requests(input) {
|
|
|
67
68
|
units: 1,
|
|
68
69
|
}));
|
|
69
70
|
}
|
|
71
|
+
function readyIssue(input, issueResult) {
|
|
72
|
+
const { number, result } = issueResult;
|
|
73
|
+
const created = JSON.parse(result ?? "null");
|
|
74
|
+
const url = typeof created === "object" && created !== null && "url" in created
|
|
75
|
+
? created.url
|
|
76
|
+
: undefined;
|
|
77
|
+
const issue = typeof url === "string"
|
|
78
|
+
? /^https?:\/\/[^/\s]+\/([\w.-]+)\/([\w.-]+)\/issues\/([1-9]\d*)$/.exec(url)
|
|
79
|
+
: null;
|
|
80
|
+
if (issue === null || Number(issue[3]) !== number)
|
|
81
|
+
throw new Error("invalid-issue-result");
|
|
82
|
+
return {
|
|
83
|
+
scope: { phase: "prd", operation: "github-ready", round: 1 },
|
|
84
|
+
capability: "github",
|
|
85
|
+
method: "label",
|
|
86
|
+
target: input.cycleId,
|
|
87
|
+
units: 1,
|
|
88
|
+
payload: JSON.stringify({
|
|
89
|
+
owner: issue[1],
|
|
90
|
+
repository: issue[2],
|
|
91
|
+
number,
|
|
92
|
+
add: ["cloving: ready"],
|
|
93
|
+
remove: [],
|
|
94
|
+
}),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
async function handoffIssue(input, created) {
|
|
98
|
+
const { number } = created;
|
|
99
|
+
const outcome = {
|
|
100
|
+
kind: "handoff_requested",
|
|
101
|
+
cycleId: input.cycleId,
|
|
102
|
+
promotionId: `issue-${String(number)}`,
|
|
103
|
+
};
|
|
104
|
+
try {
|
|
105
|
+
await handoff({
|
|
106
|
+
workflowId: "develop-issue",
|
|
107
|
+
input: { ticket: String(number) },
|
|
108
|
+
});
|
|
109
|
+
return outcome;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
const reason = handoffReason(error);
|
|
113
|
+
const queued = await protectedEffect(readyIssue(input, created));
|
|
114
|
+
return {
|
|
115
|
+
...outcome,
|
|
116
|
+
reason,
|
|
117
|
+
...(queued.kind === "completed" ? {} : { issueUpdate: "failed" }),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function handoffReason(error) {
|
|
122
|
+
const cause = typeof error === "object" && error !== null && "cause" in error
|
|
123
|
+
? error.cause
|
|
124
|
+
: error;
|
|
125
|
+
if (typeof cause !== "object" ||
|
|
126
|
+
cause === null ||
|
|
127
|
+
!("message" in cause) ||
|
|
128
|
+
typeof cause.message !== "string")
|
|
129
|
+
return "handoff-failed";
|
|
130
|
+
return /^[a-z][a-z0-9-]{0,127}$/.test(cause.message)
|
|
131
|
+
? cause.message
|
|
132
|
+
: "handoff-failed";
|
|
133
|
+
}
|
|
70
134
|
export async function developPrd(input) {
|
|
71
135
|
"use workflow";
|
|
72
|
-
let
|
|
136
|
+
let outcome = {
|
|
137
|
+
kind: "handoff_requested",
|
|
138
|
+
cycleId: input.cycleId,
|
|
139
|
+
promotionId: "",
|
|
140
|
+
};
|
|
73
141
|
for (const request of requests(input)) {
|
|
74
142
|
const result = await protectedEffect(request);
|
|
75
143
|
if (result.kind !== "completed")
|
|
@@ -91,7 +159,7 @@ export async function developPrd(input) {
|
|
|
91
159
|
reason: "invalid-issue-result",
|
|
92
160
|
evidenceId: request.scope.operation,
|
|
93
161
|
};
|
|
94
|
-
|
|
162
|
+
outcome = await handoffIssue(input, { number, result: result.result });
|
|
95
163
|
}
|
|
96
|
-
return
|
|
164
|
+
return outcome;
|
|
97
165
|
}
|
|
@@ -76,6 +76,11 @@ async function merged(input, done) {
|
|
|
76
76
|
cycleId: input.cycleId,
|
|
77
77
|
promotionId: `pull-request-${String(number)}`,
|
|
78
78
|
};
|
|
79
|
+
if (merge.result["reason"] === "ci-pending")
|
|
80
|
+
return blocked(input.cycleId, {
|
|
81
|
+
reason: `The merge has the result ci-pending: ${part(merge.result, "output")}. The pull request stays open: ${done.pull.url}`,
|
|
82
|
+
evidenceId: "github-merge",
|
|
83
|
+
});
|
|
79
84
|
if (done.round > lastFix)
|
|
80
85
|
return blocked(input.cycleId, {
|
|
81
86
|
reason: `The merge has the result ${merge.result["reason"]} in round ${String(done.round)}, and a run has at most ${String(lastFix)} fix rounds. The pull request stays open: ${done.pull.url}`,
|
|
@@ -8,9 +8,10 @@ function instructions(repository) {
|
|
|
8
8
|
`If this work tree has no node_modules folder and the main repository has one, make a copy-on-write clone: cp -c -R ${repository}/node_modules node_modules`,
|
|
9
9
|
"Follow the instructions of the repository (AGENTS.md and CLAUDE.md).",
|
|
10
10
|
"Use test-driven development: write a test that fails, see it fail, then write the code.",
|
|
11
|
-
"Run the checks of the repository before you finish.",
|
|
11
|
+
"Run the checks of the repository before you finish. Commit your work before the report. If the repository provides npm run check:clean, run it after committing.",
|
|
12
|
+
"Treat a check time warning as work to do in the same ticket when your change made the check slower.",
|
|
12
13
|
"Commit your work on the current branch. Do not push. Do not change a different branch.",
|
|
13
|
-
"End with a short report: what you changed, the checks that you ran with their results, and what is not done. End the report with exactly one of the lines Ticket status: complete or Ticket status: partial. Before a partial status line, include Remaining: followed by a list with one item on each line, using a dash and a space before each item.",
|
|
14
|
+
"End with a short report: what you changed, the checks that you ran with their results, and what is not done. End the report with exactly one of the lines Ticket status: complete or Ticket status: partial. Before a partial status line, include Remaining: followed by a list with one item on each line, using a dash and a space before each item. Blank lines are allowed around and between list items.",
|
|
14
15
|
].join("\n");
|
|
15
16
|
}
|
|
16
17
|
function named(input) {
|
|
@@ -95,6 +96,15 @@ export function turnPrompt(input, fix) {
|
|
|
95
96
|
"Reassess each remaining item before reporting Ticket status: complete. Keep every unfinished item in the next Remaining: list.",
|
|
96
97
|
].join("\n");
|
|
97
98
|
const ticket = pullPrompt(input) + continuation;
|
|
99
|
+
if (review["reason"] === "ci-failed")
|
|
100
|
+
return [
|
|
101
|
+
ticket,
|
|
102
|
+
"",
|
|
103
|
+
`This is round ${String(round)}. The CI checks of the pull request ${pull.url} failed.`,
|
|
104
|
+
"Fix the failed CI checks, including failures that the pull request did not cause, and commit the fix.",
|
|
105
|
+
"",
|
|
106
|
+
part(review, "output"),
|
|
107
|
+
].join("\n");
|
|
98
108
|
if (typeof review["reason"] === "string")
|
|
99
109
|
return [
|
|
100
110
|
ticket,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloving",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Local durable development workflow service",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -34,12 +34,29 @@
|
|
|
34
34
|
"./workflows/steps/decision-gate": {
|
|
35
35
|
"types": "./dist/src/steps/decision-gate.d.ts",
|
|
36
36
|
"import": "./dist/src/steps/decision-gate.js"
|
|
37
|
+
},
|
|
38
|
+
"./workflows/steps/handoff": {
|
|
39
|
+
"types": "./dist/src/steps/handoff.d.ts",
|
|
40
|
+
"import": "./dist/src/steps/handoff.js"
|
|
37
41
|
}
|
|
38
42
|
},
|
|
39
43
|
"files": [
|
|
40
44
|
"dist/src",
|
|
41
45
|
"dist/tools",
|
|
42
|
-
"dist/workflows"
|
|
46
|
+
"dist/workflows",
|
|
47
|
+
"assets/watch-fonts/fira-code-latin-400-normal.woff2",
|
|
48
|
+
"assets/watch-fonts/fira-code-latin-500-normal.woff2",
|
|
49
|
+
"assets/watch-fonts/fira-code-OFL.md",
|
|
50
|
+
"assets/watch-fonts/heebo-latin-500-normal.woff2",
|
|
51
|
+
"assets/watch-fonts/heebo-latin-700-normal.woff2",
|
|
52
|
+
"assets/watch-fonts/heebo-latin-800-normal.woff2",
|
|
53
|
+
"assets/watch-fonts/heebo-OFL.md",
|
|
54
|
+
"assets/watch-fonts/space-grotesk-latin-400-normal.woff2",
|
|
55
|
+
"assets/watch-fonts/space-grotesk-latin-500-normal.woff2",
|
|
56
|
+
"assets/watch-fonts/space-grotesk-latin-600-normal.woff2",
|
|
57
|
+
"assets/watch-fonts/space-grotesk-latin-700-normal.woff2",
|
|
58
|
+
"assets/watch-fonts/space-grotesk-OFL.md",
|
|
59
|
+
"assets/watch-fonts/manifest.json"
|
|
43
60
|
],
|
|
44
61
|
"engines": {
|
|
45
62
|
"node": ">=26.9.0 <27",
|
|
@@ -58,6 +75,7 @@
|
|
|
58
75
|
"build:workflows": "node tools/compile-workflows.ts",
|
|
59
76
|
"check:setup": "tsc --project tsconfig.json --noEmit",
|
|
60
77
|
"check:quality": "node tools/check-quality.ts",
|
|
78
|
+
"check:clean": "node tools/check-clean.ts",
|
|
61
79
|
"check:lint": "eslint src tools quality test eslint.config.mjs --max-warnings 0",
|
|
62
80
|
"check:format": "prettier --check src tools quality test eslint.config.mjs package.json tsconfig.json tsconfig.build.json",
|
|
63
81
|
"test:quality": "node --test test/quality/*.test.ts",
|
|
@@ -76,7 +94,6 @@
|
|
|
76
94
|
"istanbul-lib-coverage": "3.2.2",
|
|
77
95
|
"jscpd": "5.4.0",
|
|
78
96
|
"prettier": "3.9.9",
|
|
79
|
-
"typescript": "6.0.3",
|
|
80
97
|
"typescript-eslint": "8.71.1"
|
|
81
98
|
},
|
|
82
99
|
"overrides": {
|
|
@@ -90,6 +107,7 @@
|
|
|
90
107
|
"entities": "8.1.0",
|
|
91
108
|
"preact": "11.0.0",
|
|
92
109
|
"solid-objects": "0.17.8",
|
|
110
|
+
"typescript": "6.0.3",
|
|
93
111
|
"workflow": "5.1.0"
|
|
94
112
|
}
|
|
95
113
|
}
|