cloving 1.0.1 → 1.1.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.
Files changed (177) hide show
  1. package/README.md +145 -14
  2. package/dist/src/authority/commands.js +10 -2
  3. package/dist/src/authority/reconciliation.d.ts +2 -0
  4. package/dist/src/authority/reconciliation.js +10 -1
  5. package/dist/src/authority/transitions.d.ts +1 -1
  6. package/dist/src/authority/transitions.js +2 -0
  7. package/dist/src/broker/actor/authority-session.d.ts +2 -0
  8. package/dist/src/broker/actor/authority-session.js +3 -4
  9. package/dist/src/chats/analysis-batches.d.ts +24 -0
  10. package/dist/src/chats/analysis-batches.js +83 -0
  11. package/dist/src/chats/analysis-contract.d.ts +80 -0
  12. package/dist/src/chats/analysis-contract.js +16 -0
  13. package/dist/src/chats/analysis-data.d.ts +3 -0
  14. package/dist/src/chats/analysis-data.js +85 -0
  15. package/dist/src/chats/analysis-input.d.ts +12 -0
  16. package/dist/src/chats/analysis-input.js +83 -0
  17. package/dist/src/chats/analysis-links.d.ts +2 -0
  18. package/dist/src/chats/analysis-links.js +25 -0
  19. package/dist/src/chats/analysis-prompt.d.ts +10 -0
  20. package/dist/src/chats/analysis-prompt.js +16 -0
  21. package/dist/src/chats/analysis-proposal.d.ts +2 -0
  22. package/dist/src/chats/analysis-proposal.js +32 -0
  23. package/dist/src/chats/analysis-provenance.d.ts +4 -0
  24. package/dist/src/chats/analysis-provenance.js +99 -0
  25. package/dist/src/chats/analysis-shape.d.ts +3 -0
  26. package/dist/src/chats/analysis-shape.js +228 -0
  27. package/dist/src/chats/analysis.d.ts +3 -0
  28. package/dist/src/chats/analysis.js +42 -0
  29. package/dist/src/chats/corpus-input.d.ts +5 -0
  30. package/dist/src/chats/corpus-input.js +288 -0
  31. package/dist/src/chats/destination.js +3 -1
  32. package/dist/src/chats/import.d.ts +8 -6
  33. package/dist/src/chats/import.js +195 -23
  34. package/dist/src/chats/input-file.d.ts +3 -0
  35. package/dist/src/chats/input-file.js +32 -0
  36. package/dist/src/chats/normalize.d.ts +11 -2
  37. package/dist/src/chats/normalize.js +18 -2
  38. package/dist/src/chats/previous-import.d.ts +5 -0
  39. package/dist/src/chats/previous-import.js +34 -0
  40. package/dist/src/chats/reader.d.ts +7 -1
  41. package/dist/src/chats/reader.js +46 -15
  42. package/dist/src/chats/redact-import.d.ts +14 -0
  43. package/dist/src/chats/redact-import.js +141 -0
  44. package/dist/src/chats/redaction.d.ts +41 -0
  45. package/dist/src/chats/redaction.js +161 -0
  46. package/dist/src/chats/revision-input.d.ts +3 -0
  47. package/dist/src/chats/revision-input.js +180 -0
  48. package/dist/src/chats/revision.d.ts +48 -0
  49. package/dist/src/chats/revision.js +94 -0
  50. package/dist/src/chats/selection.d.ts +25 -0
  51. package/dist/src/chats/selection.js +109 -0
  52. package/dist/src/chats/source.d.ts +23 -0
  53. package/dist/src/chats/source.js +135 -0
  54. package/dist/src/chats/structure.d.ts +57 -0
  55. package/dist/src/chats/structure.js +216 -0
  56. package/dist/src/cli/arguments.d.ts +8 -0
  57. package/dist/src/cli/arguments.js +94 -13
  58. package/dist/src/cli/client.d.ts +1 -0
  59. package/dist/src/cli/client.js +38 -1
  60. package/dist/src/cli/commands.d.ts +3 -0
  61. package/dist/src/cli/commands.js +29 -11
  62. package/dist/src/cli/main.d.ts +2 -1
  63. package/dist/src/cli/main.js +61 -14
  64. package/dist/src/cli/render.d.ts +2 -0
  65. package/dist/src/cli/render.js +70 -2
  66. package/dist/src/cli/usage-render.d.ts +3 -0
  67. package/dist/src/cli/usage-render.js +28 -0
  68. package/dist/src/cli/usage.d.ts +3 -2
  69. package/dist/src/cli/usage.js +38 -6
  70. package/dist/src/cli/watch-actions.d.ts +10 -0
  71. package/dist/src/cli/watch-actions.js +58 -0
  72. package/dist/src/cli/watch-board-view.d.ts +32 -0
  73. package/dist/src/cli/watch-board-view.js +88 -15
  74. package/dist/src/cli/watch-board.js +6 -1
  75. package/dist/src/cli/watch-card-client.d.ts +1 -0
  76. package/dist/src/cli/watch-card-client.js +44 -0
  77. package/dist/src/cli/watch-card-forms.d.ts +2 -0
  78. package/dist/src/cli/watch-card-forms.js +33 -0
  79. package/dist/src/cli/watch-card-reply.d.ts +5 -0
  80. package/dist/src/cli/watch-card-reply.js +34 -0
  81. package/dist/src/cli/watch-card-view.d.ts +40 -0
  82. package/dist/src/cli/watch-card-view.js +86 -0
  83. package/dist/src/cli/watch-client.d.ts +1 -0
  84. package/dist/src/cli/watch-client.js +118 -0
  85. package/dist/src/cli/watch-model.js +31 -14
  86. package/dist/src/cli/watch-page.d.ts +1 -1
  87. package/dist/src/cli/watch-page.js +71 -85
  88. package/dist/src/cli/watch-pr-board.d.ts +3 -0
  89. package/dist/src/cli/watch-pr-board.js +26 -0
  90. package/dist/src/cli/watch-pr-command.d.ts +15 -0
  91. package/dist/src/cli/watch-pr-command.js +37 -0
  92. package/dist/src/cli/watch-pr-identity.d.ts +20 -0
  93. package/dist/src/cli/watch-pr-identity.js +62 -0
  94. package/dist/src/cli/watch-pr-observer.d.ts +19 -0
  95. package/dist/src/cli/watch-pr-observer.js +108 -0
  96. package/dist/src/cli/watch-pr-settings.d.ts +8 -0
  97. package/dist/src/cli/watch-pr-settings.js +47 -0
  98. package/dist/src/cli/watch-reads.js +4 -0
  99. package/dist/src/cli/watch-render.d.ts +16 -2
  100. package/dist/src/cli/watch-render.js +10 -3
  101. package/dist/src/cli/watch-run-input.d.ts +14 -0
  102. package/dist/src/cli/watch-run-input.js +49 -0
  103. package/dist/src/cli/watch-server.d.ts +3 -0
  104. package/dist/src/cli/watch-server.js +44 -6
  105. package/dist/src/cli/watch-start-view.d.ts +18 -0
  106. package/dist/src/cli/watch-start-view.js +77 -0
  107. package/dist/src/cli/watch-turn.d.ts +1 -0
  108. package/dist/src/cli/watch-turn.js +7 -2
  109. package/dist/src/cli/watch-usage.d.ts +6 -0
  110. package/dist/src/cli/watch-usage.js +65 -0
  111. package/dist/src/cli/watch-view.d.ts +15 -0
  112. package/dist/src/cli/watch-view.js +61 -17
  113. package/dist/src/cli.js +48 -6
  114. package/dist/src/daemon/analysis-launch.d.ts +5 -0
  115. package/dist/src/daemon/analysis-launch.js +81 -0
  116. package/dist/src/daemon/analysis-token.d.ts +6 -0
  117. package/dist/src/daemon/analysis-token.js +88 -0
  118. package/dist/src/daemon/analysis-worker.d.ts +22 -0
  119. package/dist/src/daemon/analysis-worker.js +219 -0
  120. package/dist/src/daemon/chats-files.d.ts +15 -0
  121. package/dist/src/daemon/chats-files.js +113 -0
  122. package/dist/src/daemon/chats-worker.d.ts +12 -0
  123. package/dist/src/daemon/chats-worker.js +200 -0
  124. package/dist/src/daemon/claude-turn.d.ts +10 -0
  125. package/dist/src/daemon/claude-turn.js +96 -15
  126. package/dist/src/daemon/claude-worker.d.ts +4 -1
  127. package/dist/src/daemon/claude-worker.js +99 -47
  128. package/dist/src/daemon/codex-stream.js +16 -0
  129. package/dist/src/daemon/daemon.d.ts +3 -0
  130. package/dist/src/daemon/daemon.js +16 -7
  131. package/dist/src/daemon/github-methods.js +4 -0
  132. package/dist/src/daemon/github-ticket-status.d.ts +3 -0
  133. package/dist/src/daemon/github-ticket-status.js +268 -0
  134. package/dist/src/daemon/issue-queue.d.ts +1 -0
  135. package/dist/src/daemon/issue-queue.js +115 -24
  136. package/dist/src/daemon/operation-journal.d.ts +1 -0
  137. package/dist/src/daemon/operation-journal.js +4 -2
  138. package/dist/src/daemon/retrying-turn.d.ts +9 -0
  139. package/dist/src/daemon/retrying-turn.js +185 -0
  140. package/dist/src/host/world-reconciliation.d.ts +2 -0
  141. package/dist/src/host/world-reconciliation.js +6 -0
  142. package/dist/src/host/world-runs.js +9 -1
  143. package/dist/src/service/run-handlers.js +15 -23
  144. package/dist/src/service/worker-totals.d.ts +3 -0
  145. package/dist/src/service/worker-totals.js +28 -0
  146. package/dist/src/ticket-status.d.ts +8 -0
  147. package/dist/src/ticket-status.js +45 -0
  148. package/dist/src/token-prices.d.ts +19 -0
  149. package/dist/src/token-prices.js +76 -0
  150. package/dist/src/worker-usage.d.ts +22 -0
  151. package/dist/src/worker-usage.js +44 -0
  152. package/dist/src/workflow-candidate.d.ts +29 -0
  153. package/dist/src/workflow-candidate.js +59 -0
  154. package/dist/src/workflow-contracts.d.ts +2 -0
  155. package/dist/src/workflow-contracts.js +27 -5
  156. package/dist/src/workflow-declaration.d.ts +1 -1
  157. package/dist/src/workflow-declaration.js +3 -1
  158. package/dist/src/workflows.d.ts +1 -0
  159. package/dist/src/workflows.js +1 -0
  160. package/dist/tools/self-host-processes.js +7 -2
  161. package/dist/tools/self-host-workflows.d.ts +1 -1
  162. package/dist/tools/self-host-workflows.js +2 -2
  163. package/dist/tools/self-host.js +6 -6
  164. package/dist/tools/self-update-host.d.ts +7 -0
  165. package/dist/tools/self-update-host.js +48 -1
  166. package/dist/tools/self-update.d.ts +1 -0
  167. package/dist/tools/self-update.js +85 -13
  168. package/dist/workflows/analyze-chats.d.ts +10 -0
  169. package/dist/workflows/analyze-chats.declaration.json +9 -0
  170. package/dist/workflows/analyze-chats.js +73 -0
  171. package/dist/workflows/develop-ticket.d.ts +1 -7
  172. package/dist/workflows/develop-ticket.js +42 -141
  173. package/dist/workflows/issue-labels.d.ts +3 -1
  174. package/dist/workflows/issue-labels.js +34 -0
  175. package/dist/workflows/ticket-prompts.d.ts +35 -0
  176. package/dist/workflows/ticket-prompts.js +133 -0
  177. package/package.json +5 -3
@@ -1,77 +1,11 @@
1
1
  import { decisionGate } from "cloving/workflows/steps/decision-gate";
2
2
  import { protectedEffect } from "cloving/workflows/steps/protected-effect";
3
3
  import { finishIssue, labelIssue } from "./issue-labels.js";
4
- const lastRound = 3;
4
+ import { fields, part, lastRound, review, prompt, turnPrompt, pullTitle, pullBody, } from "./ticket-prompts.js";
5
5
  const lastFix = 3;
6
- const publicRule = "The branch becomes a public pull request. Do not write local file paths, such as the path of your home folder, in files or in commit messages.";
7
- function instructions(repository) {
8
- return [
9
- "You work on one ticket of this repository. Your current directory is a git work tree on its own branch.",
10
- `The main repository is at ${repository}. Do not change files there.`,
11
- `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`,
12
- "Follow the instructions of the repository (AGENTS.md and CLAUDE.md).",
13
- "Use test-driven development: write a test that fails, see it fail, then write the code.",
14
- "Run the checks of the repository before you finish.",
15
- "Commit your work on the current branch. Do not push. Do not change a different branch.",
16
- "End with a short report: what you changed, the checks that you ran with their results, and what is not done.",
17
- ].join("\n");
18
- }
19
- function named(input) {
20
- return input.ticketId === undefined
21
- ? "this ticket"
22
- : `the ticket ${input.ticketId}`;
23
- }
24
- function fields(text) {
25
- const value = JSON.parse(text ?? "{}");
26
- return typeof value === "object" && value !== null ? { ...value } : {};
27
- }
28
- function part(turn, name) {
29
- const value = turn[name];
30
- if (Array.isArray(value))
31
- return value.join("\n");
32
- if (typeof value === "number")
33
- return value.toFixed(2);
34
- return typeof value === "string" ? value : "";
35
- }
36
- function denial(round) {
37
- return round < lastRound
38
- ? "Deny with a feedback text to send the work back to the worker for one more round. Deny with no text to reject the work."
39
- : "Deny to reject the work. This is the last round.";
40
- }
41
- function review(input, turnResult) {
42
- const turn = fields(turnResult.result);
43
- return [
44
- `Review the work of the Claude worker for ${named(input)}.`,
45
- `Round: ${String(turnResult.round)} of ${String(lastRound)}`,
46
- `Branch: ${part(turn, "branch")}`,
47
- `Work tree: ${part(turn, "worktree")}`,
48
- `Cost (USD): ${part(turn, "costUsd")}`,
49
- "",
50
- "Commits:",
51
- part(turn, "commits"),
52
- "",
53
- "Changed files:",
54
- part(turn, "files"),
55
- "",
56
- "Not committed:",
57
- part(turn, "uncommitted"),
58
- "",
59
- "Report of the worker:",
60
- part(turn, "summary"),
61
- "",
62
- "Approve to accept the branch, then merge the branch yourself.",
63
- denial(turnResult.round),
64
- ].join("\n");
65
- }
66
6
  function blocked(cycleId, cause) {
67
7
  return { kind: "blocked", cycleId, ...cause };
68
8
  }
69
- function prompt(input, round) {
70
- const ticket = `${instructions(input.repository)}\n\nThe text of ${named(input)}:\n\n${input.ticket}`;
71
- if (round.feedback === null)
72
- return ticket;
73
- return `${ticket}\n\nThis is round ${String(round.number)}. The branch has the commits of the earlier rounds. The operator denied the work of round ${String(round.number - 1)} with this feedback:\n\n${round.feedback}`;
74
- }
75
9
  function decided(cycleId, decision) {
76
10
  const number = Number(decision.gateId.slice("review-".length));
77
11
  if (decision.decision === "approved")
@@ -92,61 +26,6 @@ function decided(cycleId, decision) {
92
26
  });
93
27
  return { number: number + 1, feedback: decision.answer };
94
28
  }
95
- function pullPrompt(input) {
96
- return `${instructions(input.repository)}\n${publicRule}\n\nThe text of ${named(input)}:\n\n${input.ticket}`;
97
- }
98
- function findingLines(findings) {
99
- if (!Array.isArray(findings))
100
- return "";
101
- return findings
102
- .map((item) => {
103
- const finding = fields(JSON.stringify(item));
104
- const line = finding["line"];
105
- const place = typeof line === "number" ? `:${String(line)}` : "";
106
- return `- ${part(finding, "path")}${place}: ${part(finding, "body")}`;
107
- })
108
- .join("\n");
109
- }
110
- function turnPrompt(input, fix) {
111
- if (fix === null)
112
- return pullPrompt(input);
113
- const { round, pull, review } = fix;
114
- if (typeof review["reason"] === "string")
115
- return [
116
- pullPrompt(input),
117
- "",
118
- `This is round ${String(round)}. The merge of the pull request ${pull.url} needs a fix: ${review["reason"]}.`,
119
- review["reason"] === "merge-conflict"
120
- ? "Merge the default branch of origin into the branch, resolve each conflict, run the tests, and commit."
121
- : "The check failed on the tree of the merge of the branch with the newest commit of the default branch. Fix the cause and commit the fix.",
122
- "",
123
- part(review, "output"),
124
- ].join("\n");
125
- return [
126
- pullPrompt(input),
127
- "",
128
- `This is round ${String(round)}. The branch has the commits of the earlier rounds. Greptile reviewed the pull request ${pull.url} at the commit ${pull.commit.slice(0, 7)} and gave the confidence score ${String(review["score"])}/5. Fix each problem of the review, then commit the fixes. Cloving merges the pull request when the score is 5/5.`,
129
- "",
130
- "Summary of the review:",
131
- "",
132
- part(review, "summary"),
133
- "",
134
- "Findings on lines:",
135
- "",
136
- findingLines(review["findings"]),
137
- ].join("\n");
138
- }
139
- function pullTitle(input) {
140
- const [first = ""] = input.ticket.split("\n", 1);
141
- return first.startsWith("# ")
142
- ? first.slice(2).trim().slice(0, 200)
143
- : `Cloving task ${input.cycleId}`;
144
- }
145
- function pullBody(input) {
146
- const issue = /^GitHub issue: ([\w.-]+[/][\w.-]+#\d+)/m.exec(input.ticket);
147
- const about = `Cloving made this change for ${named(input)}. Greptile reviews the pull request, and Cloving merges it when the confidence score is 5/5.`;
148
- return issue === null ? about : `Closes ${String(issue[1])}\n\n${about}`;
149
- }
150
29
  async function stepped(input, call) {
151
30
  const { phase, operation, round, capability, method, payload } = call;
152
31
  const done = await protectedEffect({
@@ -202,7 +81,30 @@ async function merged(input, done) {
202
81
  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}`,
203
82
  evidenceId: "github-merge",
204
83
  });
205
- return { round: done.round + 1, pull: done.pull, review: merge.result };
84
+ return {
85
+ round: done.round + 1,
86
+ pull: done.pull,
87
+ review: merge.result,
88
+ report: done.report,
89
+ };
90
+ }
91
+ async function reviewedPull(input, round) {
92
+ const opened = await github(input, {
93
+ method: "open-pr",
94
+ round,
95
+ payload: { title: pullTitle(input), body: pullBody(input) },
96
+ });
97
+ if (opened.kind !== "done")
98
+ return opened;
99
+ const pull = pullOf(opened.result);
100
+ const reviewed = await github(input, {
101
+ method: "wait-review",
102
+ round,
103
+ payload: { number: pull.number, commit: pull.commit },
104
+ });
105
+ return reviewed.kind === "done"
106
+ ? { kind: "reviewed", pull, review: reviewed.result }
107
+ : reviewed;
206
108
  }
207
109
  async function pullRequest(input) {
208
110
  const prepared = await github(input, {
@@ -225,26 +127,20 @@ async function pullRequest(input) {
225
127
  });
226
128
  if (turn.kind !== "done")
227
129
  return finishIssue(input, { round, outcome: turn });
228
- const opened = await github(input, {
229
- method: "open-pr",
230
- round,
231
- payload: { title: pullTitle(input), body: pullBody(input) },
232
- });
233
- if (opened.kind !== "done")
234
- return finishIssue(input, { round, outcome: opened });
235
- const pull = pullOf(opened.result);
236
- const reviewed = await github(input, {
237
- method: "wait-review",
238
- round,
239
- payload: { number: pull.number, commit: pull.commit },
240
- });
241
- if (reviewed.kind !== "done")
130
+ const reviewed = await reviewedPull(input, round);
131
+ if (reviewed.kind !== "reviewed")
242
132
  return finishIssue(input, { round, outcome: reviewed });
243
- const score = reviewed.result["score"];
133
+ const { pull } = reviewed;
134
+ const score = reviewed.review["score"];
244
135
  if (score === 5) {
245
- const result = await merged(input, { round, pull });
136
+ const result = await merged(input, { round, pull, report: turn.result });
246
137
  if ("kind" in result)
247
- return finishIssue(input, { round, outcome: result });
138
+ return finishIssue(input, {
139
+ round,
140
+ outcome: result,
141
+ summary: part(turn.result, "summary"),
142
+ status: turn.result["ticketStatus"],
143
+ });
248
144
  fix = result;
249
145
  continue;
250
146
  }
@@ -256,7 +152,12 @@ async function pullRequest(input) {
256
152
  evidenceId: "github-wait-review",
257
153
  }),
258
154
  });
259
- fix = { round: round + 1, pull, review: reviewed.result };
155
+ fix = {
156
+ round: round + 1,
157
+ pull,
158
+ review: reviewed.review,
159
+ report: turn.result,
160
+ };
260
161
  }
261
162
  }
262
163
  export async function developTicket(input, decision) {
@@ -1,4 +1,4 @@
1
- import type { CycleOutcome } from "cloving/workflows";
1
+ import { type CycleOutcome } from "cloving/workflows";
2
2
  interface IssueTicket {
3
3
  readonly cycleId: string;
4
4
  readonly ticket: string;
@@ -10,5 +10,7 @@ export declare function labelIssue(input: IssueTicket, state: {
10
10
  export declare function finishIssue(input: IssueTicket, ending: {
11
11
  readonly round: number;
12
12
  readonly outcome: CycleOutcome;
13
+ readonly summary?: string;
14
+ readonly status?: unknown;
13
15
  }): Promise<CycleOutcome>;
14
16
  export {};
@@ -1,3 +1,4 @@
1
+ import { ticketStatus } from "cloving/workflows";
1
2
  import { protectedEffect } from "cloving/workflows/steps/protected-effect";
2
3
  export async function labelIssue(input, state) {
3
4
  const issue = /^GitHub issue: ([\w.-]+)\/([\w.-]+)#(\d+)/m.exec(input.ticket);
@@ -28,9 +29,42 @@ export async function labelIssue(input, state) {
28
29
  });
29
30
  }
30
31
  export async function finishIssue(input, ending) {
32
+ if (ending.outcome.kind === "handoff_requested")
33
+ return finishMergedIssue(input, ending);
31
34
  await labelIssue(input, {
32
35
  round: ending.round,
33
36
  key: ending.outcome.kind === "blocked" ? "blocked" : "done",
34
37
  });
35
38
  return ending.outcome;
36
39
  }
40
+ async function finishMergedIssue(input, ending) {
41
+ if (ending.outcome.kind !== "handoff_requested")
42
+ return ending.outcome;
43
+ const status = ticketStatus(ending.summary ?? "", ending.status);
44
+ const method = status.kind === "complete" ? "close-issue" : "partial-issue";
45
+ const issue = /^GitHub issue: ([\w.-]+)\/([\w.-]+)#(\d+)/m.exec(input.ticket);
46
+ const outcome = { ...ending.outcome, ticketStatus: status.kind };
47
+ if (issue === null)
48
+ return outcome;
49
+ const update = await protectedEffect({
50
+ scope: {
51
+ phase: "pull-request",
52
+ operation: `github-${method}`,
53
+ round: ending.round,
54
+ },
55
+ capability: "github",
56
+ method,
57
+ target: input.cycleId,
58
+ units: 1,
59
+ payload: JSON.stringify({
60
+ owner: issue[1],
61
+ repository: issue[2],
62
+ number: Number(issue[3]),
63
+ pullRequest: Number(ending.outcome.promotionId.slice("pull-request-".length)),
64
+ ...(status.kind === "partial" ? { remaining: status.remaining } : {}),
65
+ }),
66
+ });
67
+ if (update.kind === "completed")
68
+ return outcome;
69
+ return { ...outcome, issueUpdate: "failed" };
70
+ }
@@ -0,0 +1,35 @@
1
+ export interface Ticket {
2
+ readonly cycleId: string;
3
+ readonly ticket: string;
4
+ readonly ticketId?: string;
5
+ readonly repository: string;
6
+ readonly delivery?: string;
7
+ }
8
+ export interface Round {
9
+ readonly number: number;
10
+ readonly feedback: string | null;
11
+ }
12
+ export interface Pull {
13
+ readonly number: number;
14
+ readonly url: string;
15
+ readonly commit: string;
16
+ }
17
+ export interface Fix {
18
+ readonly round: number;
19
+ readonly pull: Pull;
20
+ readonly review: Fields;
21
+ readonly report: Fields;
22
+ }
23
+ export declare const lastRound = 3;
24
+ type Fields = Readonly<Record<string, unknown>>;
25
+ export declare function fields(text: string | null): Readonly<Record<string, unknown>>;
26
+ export declare function part(turn: Readonly<Record<string, unknown>>, name: string): string;
27
+ export declare function review(input: Ticket, turnResult: {
28
+ readonly round: number;
29
+ readonly result: string | null;
30
+ }): string;
31
+ export declare function prompt(input: Ticket, round: Round): string;
32
+ export declare function turnPrompt(input: Ticket, fix: Fix | null): string;
33
+ export declare function pullTitle(input: Ticket): string;
34
+ export declare function pullBody(input: Ticket): string;
35
+ export {};
@@ -0,0 +1,133 @@
1
+ import { ticketStatus } from "cloving/workflows";
2
+ export const lastRound = 3;
3
+ const publicRule = "The branch becomes a public pull request. Do not write local file paths, such as the path of your home folder, in files or in commit messages.";
4
+ function instructions(repository) {
5
+ return [
6
+ "You work on one ticket of this repository. Your current directory is a git work tree on its own branch.",
7
+ `The main repository is at ${repository}. Do not change files there.`,
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
+ "Follow the instructions of the repository (AGENTS.md and CLAUDE.md).",
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.",
12
+ "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
+ ].join("\n");
15
+ }
16
+ function named(input) {
17
+ return input.ticketId === undefined
18
+ ? "this ticket"
19
+ : `the ticket ${input.ticketId}`;
20
+ }
21
+ export function fields(text) {
22
+ const value = JSON.parse(text ?? "{}");
23
+ return typeof value === "object" && value !== null ? { ...value } : {};
24
+ }
25
+ export function part(turn, name) {
26
+ const value = turn[name];
27
+ if (Array.isArray(value))
28
+ return value.join("\n");
29
+ if (typeof value === "number")
30
+ return value.toFixed(2);
31
+ return typeof value === "string" ? value : "";
32
+ }
33
+ function denial(round) {
34
+ return round < lastRound
35
+ ? "Deny with a feedback text to send the work back to the worker for one more round. Deny with no text to reject the work."
36
+ : "Deny to reject the work. This is the last round.";
37
+ }
38
+ export function review(input, turnResult) {
39
+ const turn = fields(turnResult.result);
40
+ return [
41
+ `Review the work of the Claude worker for ${named(input)}.`,
42
+ `Round: ${String(turnResult.round)} of ${String(lastRound)}`,
43
+ `Branch: ${part(turn, "branch")}`,
44
+ `Work tree: ${part(turn, "worktree")}`,
45
+ `Cost (USD): ${part(turn, "costUsd")}`,
46
+ "",
47
+ "Commits:",
48
+ part(turn, "commits"),
49
+ "",
50
+ "Changed files:",
51
+ part(turn, "files"),
52
+ "",
53
+ "Not committed:",
54
+ part(turn, "uncommitted"),
55
+ "",
56
+ "Report of the worker:",
57
+ part(turn, "summary"),
58
+ "",
59
+ "Approve to accept the branch, then merge the branch yourself.",
60
+ denial(turnResult.round),
61
+ ].join("\n");
62
+ }
63
+ export function prompt(input, round) {
64
+ const ticket = `${instructions(input.repository)}\n\nThe text of ${named(input)}:\n\n${input.ticket}`;
65
+ if (round.feedback === null)
66
+ return ticket;
67
+ return `${ticket}\n\nThis is round ${String(round.number)}. The branch has the commits of the earlier rounds. The operator denied the work of round ${String(round.number - 1)} with this feedback:\n\n${round.feedback}`;
68
+ }
69
+ function pullPrompt(input) {
70
+ return `${instructions(input.repository)}\n${publicRule}\n\nThe text of ${named(input)}:\n\n${input.ticket}`;
71
+ }
72
+ function findingLines(findings) {
73
+ if (!Array.isArray(findings))
74
+ return "";
75
+ return findings
76
+ .map((item) => {
77
+ const finding = fields(JSON.stringify(item));
78
+ const line = finding["line"];
79
+ const place = typeof line === "number" ? `:${String(line)}` : "";
80
+ return `- ${part(finding, "path")}${place}: ${part(finding, "body")}`;
81
+ })
82
+ .join("\n");
83
+ }
84
+ export function turnPrompt(input, fix) {
85
+ if (fix === null)
86
+ return pullPrompt(input);
87
+ const { round, pull, review } = fix;
88
+ const status = ticketStatus(part(fix.report, "summary"), fix.report["ticketStatus"]);
89
+ const continuation = status.kind === "complete"
90
+ ? ""
91
+ : [
92
+ "",
93
+ "Remaining work from the previous worker report:",
94
+ ...status.remaining.map((item) => `- ${item}`),
95
+ "Reassess each remaining item before reporting Ticket status: complete. Keep every unfinished item in the next Remaining: list.",
96
+ ].join("\n");
97
+ const ticket = pullPrompt(input) + continuation;
98
+ if (typeof review["reason"] === "string")
99
+ return [
100
+ ticket,
101
+ "",
102
+ `This is round ${String(round)}. The merge of the pull request ${pull.url} needs a fix: ${review["reason"]}.`,
103
+ review["reason"] === "merge-conflict"
104
+ ? "Merge the default branch of origin into the branch, resolve each conflict, run the tests, and commit."
105
+ : "The check failed on the tree of the merge of the branch with the newest commit of the default branch. Fix the cause and commit the fix.",
106
+ "",
107
+ part(review, "output"),
108
+ ].join("\n");
109
+ return [
110
+ ticket,
111
+ "",
112
+ `This is round ${String(round)}. The branch has the commits of the earlier rounds. Greptile reviewed the pull request ${pull.url} at the commit ${pull.commit.slice(0, 7)} and gave the confidence score ${String(review["score"])}/5. Fix each problem of the review, then commit the fixes. Cloving merges the pull request when the score is 5/5.`,
113
+ "",
114
+ "Summary of the review:",
115
+ "",
116
+ part(review, "summary"),
117
+ "",
118
+ "Findings on lines:",
119
+ "",
120
+ findingLines(review["findings"]),
121
+ ].join("\n");
122
+ }
123
+ export function pullTitle(input) {
124
+ const [first = ""] = input.ticket.split("\n", 1);
125
+ return first.startsWith("# ")
126
+ ? first.slice(2).trim().slice(0, 200)
127
+ : `Cloving task ${input.cycleId}`;
128
+ }
129
+ export function pullBody(input) {
130
+ const issue = /^GitHub issue: ([\w.-]+[/][\w.-]+#\d+)/m.exec(input.ticket);
131
+ const about = `Cloving made this change for ${named(input)}. Greptile reviews the pull request, and Cloving merges it when the confidence score is 5/5.`;
132
+ return issue === null ? about : `Refs ${String(issue[1])}\n\n${about}`;
133
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloving",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Local durable development workflow service",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -46,10 +46,12 @@
46
46
  "npm": ">=11.19.1 <12"
47
47
  },
48
48
  "os": [
49
- "darwin"
49
+ "darwin",
50
+ "linux"
50
51
  ],
51
52
  "cpu": [
52
- "arm64"
53
+ "arm64",
54
+ "x64"
53
55
  ],
54
56
  "scripts": {
55
57
  "build": "tsc --project tsconfig.build.json && node tools/copy-workflow-declarations.ts workflows dist/workflows",