@shiyuphan/follow-me 0.4.1 → 0.5.1
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/dist/src/cli.js +171 -14
- package/dist/src/comments.js +62 -9
- package/dist/src/evolve.js +146 -26
- package/dist/src/harness.js +4 -1
- package/dist/src/host.js +29 -4
- package/dist/src/install.js +1 -1
- package/dist/src/plan.js +14 -0
- package/dist/src/prompt.js +270 -178
- package/dist/src/server.js +106 -29
- package/dist/src/session.js +23 -0
- package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-byISxePP.js → abnfDiagram-WEYOIA3C-8UsLPznO.js} +1 -1
- package/dist/ui/dist/assets/{arc-CBzsAsP2.js → arc-vovjGalz.js} +1 -1
- package/dist/ui/dist/assets/{architecture-WOLXFQ4H-0OSkIwdB.js → architecture-WOLXFQ4H-BBMK-PUu.js} +1 -1
- package/dist/ui/dist/assets/{architectureDiagram-ZHVYKT53-ixHybNqO.js → architectureDiagram-ZHVYKT53-Bw_ehaQk.js} +1 -1
- package/dist/ui/dist/assets/{blockDiagram-RJAE7QA3-1F8XL_Ou.js → blockDiagram-RJAE7QA3-CAmD0wKf.js} +1 -1
- package/dist/ui/dist/assets/{c4Diagram-XDPPPE54-D9PFBXnc.js → c4Diagram-XDPPPE54-DCP-3zgt.js} +1 -1
- package/dist/ui/dist/assets/channel-B7CT_oIp.js +1 -0
- package/dist/ui/dist/assets/{chunk-2E7WHVMD-zZdNfMkj.js → chunk-2E7WHVMD-BiHSeJMu.js} +1 -1
- package/dist/ui/dist/assets/{chunk-4EMNKR2T-CTKoxSSj.js → chunk-4EMNKR2T-CR3lHKf_.js} +1 -1
- package/dist/ui/dist/assets/{chunk-55XCU76K-ifFnDlcS.js → chunk-55XCU76K-CtaoloTT.js} +1 -1
- package/dist/ui/dist/assets/{chunk-7S6XSTFO-BCMVdi_1.js → chunk-7S6XSTFO-eABdc7fu.js} +1 -1
- package/dist/ui/dist/assets/{chunk-AQDTSKZB-DC-5Whju.js → chunk-AQDTSKZB-CPlbJWi-.js} +1 -1
- package/dist/ui/dist/assets/{chunk-DSNLSHJZ-DASbe_gb.js → chunk-DSNLSHJZ-YiMuxsb-.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HAGFOTED-CArytDjj.js → chunk-HAGFOTED-1VcdL78a.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HNE4Q36P-Dnl-Wtud.js → chunk-HNE4Q36P-u-sr_0Qu.js} +1 -1
- package/dist/ui/dist/assets/{chunk-NQOKRVIF-wvSLaeaK.js → chunk-NQOKRVIF-C_QlWSze.js} +1 -1
- package/dist/ui/dist/assets/{chunk-UHW7PFBP-CckaOGiQ.js → chunk-UHW7PFBP-CNuZhsbU.js} +1 -1
- package/dist/ui/dist/assets/{chunk-VQ5W62QI-kQBMDJ7V.js → chunk-VQ5W62QI-DAHWbLqU.js} +1 -1
- package/dist/ui/dist/assets/{chunk-WQQL4CDK-CMg6bbLU.js → chunk-WQQL4CDK-DWQnxFCd.js} +1 -1
- package/dist/ui/dist/assets/{chunk-XXDRQBXY-CYlFr19K.js → chunk-XXDRQBXY-B0lGgSOR.js} +1 -1
- package/dist/ui/dist/assets/{chunk-YZO67HFG-CqkG1vVk.js → chunk-YZO67HFG-BDw7efkN.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZAUX2LTX-BUNEdx-e.js → chunk-ZAUX2LTX-B5wfHsZv.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZCHMQSOR-CFTbvcrm.js → chunk-ZCHMQSOR--rWtYtAG.js} +1 -1
- package/dist/ui/dist/assets/{classDiagram-v2-4MYWR3UA-t2OdJ0LA.js → classDiagram-v2-4MYWR3UA-BKuQ_xoS.js} +1 -1
- package/dist/ui/dist/assets/{cose-bilkent-JH36ORCC-FGm030gc.js → cose-bilkent-JH36ORCC-Dk5fIpU0.js} +1 -1
- package/dist/ui/dist/assets/{cynefin-EF2NZ3EQ-BtU33Lsu.js → cynefin-EF2NZ3EQ-DIykJb2g.js} +1 -1
- package/dist/ui/dist/assets/{cynefinDiagram-WF24UDRP-DUr-I7hS.js → cynefinDiagram-WF24UDRP-Cg8tBqUb.js} +1 -1
- package/dist/ui/dist/assets/{dagre-NK4GIZVX-BWTHwJKt.js → dagre-NK4GIZVX-BC_tnnjz.js} +1 -1
- package/dist/ui/dist/assets/{diagram-CBK3MWTC-PcHrsEBn.js → diagram-CBK3MWTC-B_MQGixU.js} +1 -1
- package/dist/ui/dist/assets/{diagram-I5XIIV2D-CkGtKjYj.js → diagram-I5XIIV2D-DO681pZ5.js} +1 -1
- package/dist/ui/dist/assets/{diagram-ODIL2UCW-B_HdBB1w.js → diagram-ODIL2UCW-D14jLzU6.js} +1 -1
- package/dist/ui/dist/assets/{diagram-PFBEJUXE-DL9zayE6.js → diagram-PFBEJUXE-cML48gUS.js} +1 -1
- package/dist/ui/dist/assets/{diagram-RJA3I4VV-By5wl8nK.js → diagram-RJA3I4VV-DSoq8jFk.js} +1 -1
- package/dist/ui/dist/assets/{diagram-TYUNFSNR-ziWPVayL.js → diagram-TYUNFSNR-CaJ4pvnl.js} +1 -1
- package/dist/ui/dist/assets/{dist-NDVqCM7k.js → dist-D3j-DODP.js} +1 -1
- package/dist/ui/dist/assets/{ebnfDiagram-V4P3TTPT-DH4YGkPc.js → ebnfDiagram-V4P3TTPT-CyRpjSix.js} +1 -1
- package/dist/ui/dist/assets/{elk-QONTBCTQ-BL2RlCEv.js → elk-QONTBCTQ-ICJdzVFL.js} +1 -1
- package/dist/ui/dist/assets/{erDiagram-KYQIGQAA-BKbTMaRp.js → erDiagram-KYQIGQAA-B57hNTfw.js} +1 -1
- package/dist/ui/dist/assets/{eventmodeling-K75KTNOO-Dk5RCWc-.js → eventmodeling-K75KTNOO-BG8tUVIQ.js} +1 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU--HPNoVQJ.js +1 -0
- package/dist/ui/dist/assets/{ganttDiagram-R7GPNTJY-3vM9gxeT.js → ganttDiagram-R7GPNTJY-Czt6Klik.js} +1 -1
- package/dist/ui/dist/assets/{gitGraph-VSP46ZUC-CXGFhz54.js → gitGraph-VSP46ZUC-De_ZWJLj.js} +1 -1
- package/dist/ui/dist/assets/{gitGraphDiagram-PDYL7XHY-Cl4fwUuA.js → gitGraphDiagram-PDYL7XHY-BMCJoOnT.js} +1 -1
- package/dist/ui/dist/assets/index-CYKbk4QS.js +134 -0
- package/dist/ui/dist/assets/{info-OHQRW6UA-DfSsZ8ib.js → info-OHQRW6UA-D75H9kUq.js} +1 -1
- package/dist/ui/dist/assets/{infoDiagram-MVDRKAX5-B_7tyrL5.js → infoDiagram-MVDRKAX5-Bsm_I9dt.js} +1 -1
- package/dist/ui/dist/assets/{ishikawaDiagram-HPEHV2GE-DUvHljpg.js → ishikawaDiagram-HPEHV2GE-98xZAJ9y.js} +1 -1
- package/dist/ui/dist/assets/{journeyDiagram-BOSHW7O7-BCgqzrb8.js → journeyDiagram-BOSHW7O7-BWVGuc0P.js} +1 -1
- package/dist/ui/dist/assets/{kanban-definition-FNSA4HC5-DOzZ3NmE.js → kanban-definition-FNSA4HC5-DgUE_KDd.js} +1 -1
- package/dist/ui/dist/assets/{line-B-mPFVll.js → line-i__8Rvas.js} +1 -1
- package/dist/ui/dist/assets/{linear-k7o0jout.js → linear-BXKj0Fip.js} +1 -1
- package/dist/ui/dist/assets/{mermaid-parser.core-CAyLSQpH.js → mermaid-parser.core-DxvaorTa.js} +3 -3
- package/dist/ui/dist/assets/{mermaid.core-vfVMjgx2.js → mermaid.core-ujYTsRZz.js} +4 -4
- package/dist/ui/dist/assets/{mindmap-definition-6OY57UG3-B6y-B_r4.js → mindmap-definition-6OY57UG3-B7QD6sjk.js} +1 -1
- package/dist/ui/dist/assets/{packet-JDAUHWVQ-C_r4ed30.js → packet-JDAUHWVQ-B6aIwfD1.js} +1 -1
- package/dist/ui/dist/assets/{pegDiagram-ECIUJNBX-B8VUm3Bh.js → pegDiagram-ECIUJNBX-_RStHj-a.js} +1 -1
- package/dist/ui/dist/assets/{pie-XZMESJXO-DcwtbH9y.js → pie-XZMESJXO-CNLC-pTe.js} +1 -1
- package/dist/ui/dist/assets/{pieDiagram-GNMPY74T-CvUNBi2F.js → pieDiagram-GNMPY74T-0uT7MT-u.js} +1 -1
- package/dist/ui/dist/assets/{prism-core-_2ju85Ya.js → prism-core-DtxziK1i.js} +1 -1
- package/dist/ui/dist/assets/{quadrantDiagram-D57K4MC3-Dg2eVS3j.js → quadrantDiagram-D57K4MC3-CTMKb5hl.js} +1 -1
- package/dist/ui/dist/assets/{radar-ABXABTNO-xCNTcZE9.js → radar-ABXABTNO-D53h6I0N.js} +1 -1
- package/dist/ui/dist/assets/{railroad-I3PHUGI6-BI-sIL2Q.js → railroad-I3PHUGI6-CwjhW4mc.js} +1 -1
- package/dist/ui/dist/assets/{railroad-abnf-LNEFI6M7-Ddvk40Wx.js → railroad-abnf-LNEFI6M7-DDbQ3RmB.js} +1 -1
- package/dist/ui/dist/assets/{railroad-ebnf-DWYD2UWJ-h35gE183.js → railroad-ebnf-DWYD2UWJ-Am7YJAqO.js} +1 -1
- package/dist/ui/dist/assets/{railroad-peg-JR7BVNR7-Zim9iPdp.js → railroad-peg-JR7BVNR7-Ci32KoVW.js} +1 -1
- package/dist/ui/dist/assets/{railroadDiagram-Y72NRBL7-Cz6fdmOQ.js → railroadDiagram-Y72NRBL7-BPU5A6XW.js} +1 -1
- package/dist/ui/dist/assets/{requirementDiagram-EQYSW5RA-DTJkCrcD.js → requirementDiagram-EQYSW5RA-_0Pq4QdV.js} +1 -1
- package/dist/ui/dist/assets/{sankeyDiagram-SY24JHO5-Bg1iiVla.js → sankeyDiagram-SY24JHO5-c9h-7uf8.js} +1 -1
- package/dist/ui/dist/assets/{sequenceDiagram-BLEHNHQU-9XfetsRA.js → sequenceDiagram-BLEHNHQU-B9-S4Rc9.js} +1 -1
- package/dist/ui/dist/assets/{src-Dym5afQ5.js → src-BZ3kG7lO.js} +1 -1
- package/dist/ui/dist/assets/{stateDiagram-v2-A6IESKWZ-CMmcMV8z.js → stateDiagram-v2-A6IESKWZ-py1smSen.js} +1 -1
- package/dist/ui/dist/assets/{swimlanes-B3ENRKFQ-B2PcshTY.js → swimlanes-B3ENRKFQ-C1E90aH4.js} +1 -1
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-I98Q8AqP.js +8 -0
- package/dist/ui/dist/assets/{timeline-definition-JXYKGLGB-Cr1hCNtW.js → timeline-definition-JXYKGLGB-DNe1To6B.js} +1 -1
- package/dist/ui/dist/assets/{treeView-D4JQ5SDB-jH5ksKnh.js → treeView-D4JQ5SDB-D6MG89i6.js} +1 -1
- package/dist/ui/dist/assets/{treemap-SAJKECNS-BV90e8QL.js → treemap-SAJKECNS-CZpt7Mlh.js} +1 -1
- package/dist/ui/dist/assets/{usecaseDiagram-WPOLBNNX-BNwq5aJV.js → usecaseDiagram-WPOLBNNX-CKsv-03M.js} +1 -1
- package/dist/ui/dist/assets/{vennDiagram-Q3Y3IDCW-D-lLyZCL.js → vennDiagram-Q3Y3IDCW-DGJ1qlyJ.js} +1 -1
- package/dist/ui/dist/assets/{wardley-7MLQ67FV-CWfHQUGt.js → wardley-7MLQ67FV-ClOzBemC.js} +1 -1
- package/dist/ui/dist/assets/{wardleyDiagram-IVAIDKWA-BL_qNKVd.js → wardleyDiagram-IVAIDKWA-C68ahPyF.js} +1 -1
- package/dist/ui/dist/assets/{xychartDiagram-4URNHDU4-BHPK5Snp.js → xychartDiagram-4URNHDU4-DWDamCoH.js} +1 -1
- package/dist/ui/dist/index.html +1 -1
- package/package.json +1 -1
- package/skills/follow-me/SKILL.md +69 -7
- package/skills/follow-me/porting.md +7 -0
- package/dist/ui/dist/assets/channel-B-fwsHf1.js +0 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-Dkjb3PCK.js +0 -1
- package/dist/ui/dist/assets/index-CBbx_pJk.js +0 -134
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DfB3XTTn.js +0 -8
package/dist/src/prompt.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { tipStartOf } from "./diff.js";
|
|
2
|
+
import { anchorOn, concernOn } from "./evolve.js";
|
|
2
3
|
/** Consecutive runs collapse: [0,1,2,3,7] becomes "0-3,7". */
|
|
3
4
|
function formatRanges(changes) {
|
|
4
5
|
const sorted = [...new Set(changes)].sort((a, b) => a - b);
|
|
@@ -22,11 +23,13 @@ function formatSelection(unit, select) {
|
|
|
22
23
|
function stripEol(text) {
|
|
23
24
|
return text.replace(/\r?\n$/, "");
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
+
/** Under a "### path" heading the path is left out of each hunk's own line. */
|
|
27
|
+
function renderHunk(hunk, withPath = true) {
|
|
26
28
|
const lines = hunk.body.map((line) => line.op === " "
|
|
27
29
|
? ` ${stripEol(line.text)}`
|
|
28
30
|
: `[${line.change}]${line.op} ${stripEol(line.text)}`);
|
|
29
|
-
|
|
31
|
+
const where = withPath ? ` ${hunk.path}` : "";
|
|
32
|
+
return [`${hunk.id}${where} (${hunk.changeCount} change lines)`, ...lines].join("\n");
|
|
30
33
|
}
|
|
31
34
|
function renderOtherUnit(unit) {
|
|
32
35
|
if (unit.kind === "mode") {
|
|
@@ -41,7 +44,7 @@ function renderUnits(diff) {
|
|
|
41
44
|
byPath.set(unit.path, [...(byPath.get(unit.path) ?? []), unit]);
|
|
42
45
|
}
|
|
43
46
|
const files = [...byPath].map(([path, units]) => {
|
|
44
|
-
const rendered = units.map((unit) => unit.kind === "hunk" ? renderHunk(unit) : renderOtherUnit(unit));
|
|
47
|
+
const rendered = units.map((unit) => unit.kind === "hunk" ? renderHunk(unit, false) : renderOtherUnit(unit));
|
|
45
48
|
return [`### ${path}`, ...rendered].join("\n\n");
|
|
46
49
|
});
|
|
47
50
|
return files.join("\n\n");
|
|
@@ -162,8 +165,6 @@ function pickRows(ctx, anchor) {
|
|
|
162
165
|
touchesContext: count(" ") > 0,
|
|
163
166
|
};
|
|
164
167
|
}
|
|
165
|
-
/** Said once wherever comments reach the agent, with the same words. */
|
|
166
|
-
const CONTEXT_RULE = "A comment on a context row (marked `>` with no + or -) is about code this branch leaves alone or depends on. The plan cannot move it, so do not move lines because of it. Answer a question with an explanation, or point at the commit that last touched those lines. Answer a change request as a code issue, not by reordering.";
|
|
167
168
|
function renderComment(comment, ctx) {
|
|
168
169
|
const lines = [`- ${comment.id} on ${describeAnchor(comment.anchor, ctx)}`];
|
|
169
170
|
const rows = anchorRows(ctx, comment.anchor);
|
|
@@ -188,165 +189,136 @@ function renderComment(comment, ctx) {
|
|
|
188
189
|
}
|
|
189
190
|
return lines.join("\n");
|
|
190
191
|
}
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
A concern is \`{ "severity": "blocker" | "major" | "minor", "kind": "bug" | "missing-test" | "naming" | "risk", "text": "...", "lines": [0, 1] }\`. \`lines\` holds change indices of that unit and is allowed only on a unit ref. Write \`"concerns": [ ... ]\` on a commit, or on a unit written as an object: \`{ "unit": "h_x:0-3", "concerns": [ ... ] }\`. Never write an id; follow-me derives one.`;
|
|
195
|
-
const ORDERING = `Order the commits so the story reads naturally:
|
|
196
|
-
- Scaffolding and types come before the code that uses them.
|
|
197
|
-
- A failing test comes before the fix that makes it pass.
|
|
198
|
-
- Renames and moves come before the edits that build on them.
|
|
199
|
-
- One idea per commit. Prefer many small commits over a few large ones.
|
|
200
|
-
- Commits do not need to compile or pass tests on their own.
|
|
201
|
-
- The last commit must bring the tree to exactly the tip, which follows from covering every unit.
|
|
202
|
-
|
|
203
|
-
Write each commit message as a short imperative title, then a blank line and a body that
|
|
204
|
-
says why. Put the why in the body first, before anything else. Keep the title to 50
|
|
205
|
-
characters or fewer; put detail in the body.`;
|
|
206
|
-
const GOALS = {
|
|
207
|
-
split: {
|
|
208
|
-
title: "plan the commits",
|
|
209
|
-
goal: `You are reordering a finished branch into a series of commits that a reader can follow
|
|
210
|
-
as the author's thought process. The final tree is fixed. You only decide how its change
|
|
211
|
-
lines are grouped and ordered.
|
|
212
|
-
|
|
213
|
-
${ORDERING}`,
|
|
214
|
-
},
|
|
215
|
-
walk: {
|
|
216
|
-
title: "tell the story of this branch",
|
|
217
|
-
goal: `The reader is a reviewer meeting this branch for the first time. They will read it commit
|
|
218
|
-
by commit and comment on the code itself. The final tree is fixed. You decide how its change
|
|
219
|
-
lines are grouped and ordered, and why each step is there.
|
|
220
|
-
|
|
221
|
-
${ORDERING}
|
|
222
|
-
|
|
223
|
-
Add a \`narration\` to a commit, or to a unit, only when it says something the commit message
|
|
224
|
-
body does not. The page shows it as your note. A note is one plain paragraph on why, in plain
|
|
225
|
-
text with no markdown. Say why, not what the lines already show. Leave it out when the body
|
|
226
|
-
already says it.`,
|
|
227
|
-
},
|
|
192
|
+
const TITLES = {
|
|
193
|
+
split: "plan the commits",
|
|
194
|
+
walk: "tell the story of this branch",
|
|
228
195
|
};
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
"responses": [
|
|
244
|
-
{ "comment": "c_abc234", "status": "addressed", "note": "Split the hunk and moved it earlier." }
|
|
245
|
-
]
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
\`narration\` is optional on a commit and on a unit, and the page shows it as your note. A unit
|
|
249
|
-
with a note is written as \`{ "unit": "<ref>", "narration": "..." }\` instead of the plain ref string.
|
|
250
|
-
|
|
251
|
-
\`diagram\` is optional on a commit and on a unit, written the same way:
|
|
252
|
-
\`{ "unit": "<ref>", "diagram": "..." }\`. It is Mermaid source that the review page draws beside
|
|
253
|
-
the code: a commit's diagram under its commit, a unit's under its hunk. Add one only where a picture says what the code and prose cannot: data flowing between
|
|
254
|
-
modules, a state machine, a call graph, or the before and after shape of a structure. Most commits
|
|
255
|
-
need none. The first line names the diagram type, for example:
|
|
256
|
-
|
|
257
|
-
"diagram": "flowchart LR\\n parse --> validate --> apply"
|
|
258
|
-
|
|
259
|
-
The note stays plain text even when a diagram is present.
|
|
260
|
-
\`responses\` is [] when there are no review comments. \`status\` is one of "addressed",
|
|
261
|
-
"declined", "deferred" or, for a question, "answered", and \`note\` says what you did or why not.
|
|
262
|
-
An "answered" response also carries \`"at": { "commit": 3, "unit": "h_x" }\`: the commit, counted from 1,
|
|
263
|
-
and optionally the unit that holds the explanation.`;
|
|
264
|
-
const RULES = (checkCommand) => `- A unit is "h_…" (a text hunk), "f_…" (a whole file) or "m_…" (a mode change).
|
|
265
|
-
- "h_x:0-3,7" takes change lines 0, 1, 2, 3 and 7 of hunk h_x. Indices are the [n] shown below.
|
|
266
|
-
- A bare "h_x" takes every change line of that hunk that no other commit claimed.
|
|
267
|
-
- Every change line must be used exactly once across all commits. Never repeat a line.
|
|
268
|
-
- Use the unit ids exactly as printed. Do not invent ids.
|
|
269
|
-
- "f_…" and "m_…" units are taken whole, never with a line list.
|
|
270
|
-
- Every commit needs a non-empty message and at least one unit.
|
|
271
|
-
- Keep every file syntactically whole in every commit: a function with its closing brace, a block with both ends, an import before or with its first use. Commits need not build or pass tests, but each file a commit writes must parse.
|
|
272
|
-
- Check your work with \`${checkCommand}\` and fix every issue it prints, syntax errors included.`;
|
|
196
|
+
/** Where the fixed instructions live: the skill a harness loads once, not every prompt. */
|
|
197
|
+
function pointer(input) {
|
|
198
|
+
const mode = input.mode === "split" ? "author mode" : "reviewer mode";
|
|
199
|
+
const where = input.skillPath === undefined ? "" : ` If it is not loaded, read ${input.skillPath}.`;
|
|
200
|
+
return `Write the draft as "Writing the draft" in the follow-me skill says, in ${mode}.${where}`;
|
|
201
|
+
}
|
|
202
|
+
function sessionLines(input) {
|
|
203
|
+
return [
|
|
204
|
+
`- draft: ${input.draftPath}`,
|
|
205
|
+
`- base: ${input.diff.base}`,
|
|
206
|
+
`- tip: ${input.diff.tip}`,
|
|
207
|
+
`- check: ${input.checkCommand}`,
|
|
208
|
+
].join("\n");
|
|
209
|
+
}
|
|
273
210
|
function renderUnit(unit) {
|
|
274
211
|
return unit.kind === "hunk" ? renderHunk(unit) : renderOtherUnit(unit);
|
|
275
212
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
213
|
+
const QUOTE_LINES = 6;
|
|
214
|
+
const QUOTE_WIDTH = 160;
|
|
215
|
+
/**
|
|
216
|
+
* At most six rows around the picked ones (marked ">"), each cut to 160 characters: enough to
|
|
217
|
+
* recognise the code without a file read, short enough to send with every comment.
|
|
218
|
+
*/
|
|
219
|
+
function shortQuote(rows) {
|
|
220
|
+
const picked = rows.flatMap((r, i) => (r.startsWith(">") ? [i] : []));
|
|
221
|
+
const first = picked[0] ?? 0;
|
|
222
|
+
const last = picked.at(-1) ?? 0;
|
|
223
|
+
const before = Math.max(0, Math.min(2, QUOTE_LINES - (last - first + 1)));
|
|
224
|
+
const start = Math.max(0, first - before);
|
|
225
|
+
return rows
|
|
226
|
+
.slice(start, start + QUOTE_LINES)
|
|
227
|
+
.map((r) => (r.length > QUOTE_WIDTH ? `${r.slice(0, QUOTE_WIDTH - 1)}…` : r));
|
|
228
|
+
}
|
|
229
|
+
/** One comment in a line or two, with its quote only when the agent cannot see the hunk now. */
|
|
230
|
+
function renderOpen(c, ctx, quote) {
|
|
231
|
+
const what = c.kind === "question" ? "question" : (c.severity ?? "comment");
|
|
232
|
+
const lines = [
|
|
233
|
+
`- ${c.id} ${what} on ${describeAnchor(c.anchor, ctx)}: ${JSON.stringify(c.body)}`,
|
|
234
|
+
];
|
|
235
|
+
if (c.follows)
|
|
236
|
+
lines.push(` asks again after ${c.follows}`);
|
|
237
|
+
for (const reply of c.replies ?? []) {
|
|
238
|
+
lines.push(` ${reply.author} replied: ${JSON.stringify(reply.body)}`);
|
|
239
|
+
}
|
|
240
|
+
if (quote) {
|
|
241
|
+
const rows = anchorRows(ctx, c.anchor);
|
|
242
|
+
const quoted = rows.length > 0 ? rows : (c.context.lines ?? []).map((l) => `> ${l}`);
|
|
243
|
+
lines.push(...shortQuote(quoted).map((r) => ` ${r}`));
|
|
244
|
+
}
|
|
245
|
+
return lines.join("\n");
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* The prompt after the branch moved. The agent wrote the plan and read every comment already, so
|
|
249
|
+
* only new units carry bodies; the rest are one line each, and a comment is quoted only when the
|
|
250
|
+
* hunk it was on changed.
|
|
251
|
+
*/
|
|
252
|
+
function renderMoved(input, changes, head) {
|
|
253
|
+
const { since, vanished, added, draft } = changes;
|
|
254
|
+
const { units } = input.diff;
|
|
255
|
+
const gone = new Map(vanished.map((v) => [v.unit.id, v.unit]));
|
|
256
|
+
const sections = [
|
|
257
|
+
head,
|
|
258
|
+
`## Changes since v${since}\n\nThe branch moved after plan v${since}. The draft holds that plan on the new tip.`,
|
|
285
259
|
];
|
|
286
260
|
if (vanished.length > 0) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.join(", ");
|
|
291
|
-
|
|
292
|
-
|
|
261
|
+
sections.push([
|
|
262
|
+
`## Units of v${since} that changed, so their ids are gone\n`,
|
|
263
|
+
...vanished.map(({ unit, commits }) => {
|
|
264
|
+
const was = commits.map((c) => c.index + 1).join(", ");
|
|
265
|
+
return `- ${unit.id} ${unit.path} was in commit ${was}`;
|
|
266
|
+
}),
|
|
267
|
+
].join("\n"));
|
|
293
268
|
}
|
|
294
269
|
if (added.length > 0) {
|
|
295
|
-
|
|
270
|
+
sections.push(`## New units, in no commit yet\n\n${added.map(renderUnit).join("\n\n")}`);
|
|
296
271
|
}
|
|
297
|
-
|
|
298
|
-
|
|
272
|
+
const kept = draft.commits.flatMap((commit, k) => commit.units.map((ref) => {
|
|
273
|
+
const path = units.get(ref.unit)?.path ?? "";
|
|
274
|
+
return `- ${formatSelection(ref.unit, ref.select)} ${path} commit ${k + 1}`;
|
|
275
|
+
}));
|
|
276
|
+
if (kept.length > 0) {
|
|
277
|
+
sections.push(`## Unchanged units, in the draft's commits\n\n${kept.join("\n")}`);
|
|
299
278
|
}
|
|
300
|
-
|
|
279
|
+
const review = input.mode === "split" ? input.review : null;
|
|
280
|
+
const theirs = review?.comments.filter((c) => c.author !== "agent") ?? [];
|
|
281
|
+
if (review !== null && theirs.length > 0) {
|
|
282
|
+
const ctx = { commits: [], units: new Map([...units, ...gone]) };
|
|
283
|
+
const changed = (c) => c.anchor.kind === "hunk" && gone.has(c.anchor.unit);
|
|
284
|
+
sections.push([
|
|
285
|
+
`## Open comments, on plan v${review.planVersion}\n`,
|
|
286
|
+
...theirs.map((c) => renderOpen(c, ctx, changed(c))),
|
|
287
|
+
].join("\n"));
|
|
288
|
+
}
|
|
289
|
+
return `${sections.join("\n\n")}\n`;
|
|
301
290
|
}
|
|
302
291
|
/** The planner prompt for a coding agent. Pure and deterministic for the same input. */
|
|
303
292
|
export function renderPrompt(input) {
|
|
304
|
-
const { diff
|
|
305
|
-
const { title, goal } = GOALS[input.mode];
|
|
293
|
+
const { diff } = input;
|
|
306
294
|
const head = [
|
|
307
|
-
`# follow-me: ${
|
|
308
|
-
`## Output format\n\n${FORMAT(draftPath, diff.base, diff.tip)}`,
|
|
309
|
-
`## Rules\n\n${RULES(input.checkCommand)}`,
|
|
295
|
+
`# follow-me: ${TITLES[input.mode]}\n\n${pointer(input)}\n\n${sessionLines(input)}`,
|
|
310
296
|
];
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
? []
|
|
317
|
-
: [renderChanges(input.changes, input, { commits: [], units: diff.units })]),
|
|
318
|
-
`## Concerns\n\n${CONCERNS}`,
|
|
319
|
-
`## Questions from the reviewer\n\n${QUESTIONS}\n\n${CONTEXT_RULE}`,
|
|
320
|
-
units,
|
|
321
|
-
];
|
|
322
|
-
return `${sections.join("\n\n")}\n`;
|
|
323
|
-
}
|
|
297
|
+
if (input.changes !== undefined)
|
|
298
|
+
return renderMoved(input, input.changes, head.join(""));
|
|
299
|
+
const units = `## Units\n\n${renderUnits(diff)}`;
|
|
300
|
+
if (input.mode === "walk")
|
|
301
|
+
return `${[...head, units].join("\n\n")}\n`;
|
|
324
302
|
const ctx = {
|
|
325
303
|
commits: input.previousCommits ?? [],
|
|
326
304
|
units: diff.units,
|
|
327
305
|
};
|
|
328
|
-
const sections = [
|
|
329
|
-
...head,
|
|
330
|
-
...(input.changes === undefined ? [] : [renderChanges(input.changes, input, ctx)]),
|
|
331
|
-
units,
|
|
332
|
-
];
|
|
306
|
+
const sections = [...head, units];
|
|
333
307
|
const { previousPlan, review } = input;
|
|
334
|
-
if (previousPlan)
|
|
335
|
-
sections.push(`## Previous plan\n\
|
|
336
|
-
}
|
|
308
|
+
if (previousPlan)
|
|
309
|
+
sections.push(`## Previous plan\n\n${renderPreviousPlan(previousPlan)}`);
|
|
337
310
|
const theirs = review?.comments.filter((c) => c.author !== "agent") ?? [];
|
|
338
311
|
const asked = theirs.filter((c) => c.kind === "question");
|
|
339
312
|
const changes = theirs.filter((c) => c.kind !== "question");
|
|
340
|
-
const questions = [`## Questions from the reviewer\n`, QUESTIONS, `\n${CONTEXT_RULE}`];
|
|
341
313
|
if (review && asked.length > 0) {
|
|
342
|
-
|
|
314
|
+
sections.push([
|
|
315
|
+
`## Questions from the reviewer, on plan v${review.planVersion}\n`,
|
|
316
|
+
asked.map((c) => renderComment(c, ctx)).join("\n"),
|
|
317
|
+
].join("\n"));
|
|
343
318
|
}
|
|
344
|
-
sections.push(questions.join("\n"));
|
|
345
319
|
if (review && changes.length > 0) {
|
|
346
320
|
sections.push([
|
|
347
|
-
`## Open review comments\n`,
|
|
348
|
-
`A reviewer commented on plan v${review.planVersion}. Commits are numbered from 1.`,
|
|
349
|
-
"Answer every comment below with one entry in `responses`, changing the plan where you agree.\n",
|
|
321
|
+
`## Open review comments, on plan v${review.planVersion}\n`,
|
|
350
322
|
changes.map((c) => renderComment(c, ctx)).join("\n"),
|
|
351
323
|
].join("\n"));
|
|
352
324
|
}
|
|
@@ -412,10 +384,23 @@ function quoteHunk(units, commits, unit, k, picked) {
|
|
|
412
384
|
return { line, text: lines.map((l) => l.text) };
|
|
413
385
|
}
|
|
414
386
|
const SEVERITY_RANK = { blocker: 0, major: 1, minor: 2 };
|
|
387
|
+
/** Marks the label of what was said before its code changed, and places what lost its code. */
|
|
388
|
+
function marked(ctx, id, where, quote) {
|
|
389
|
+
const mark = ctx.marks?.get(id);
|
|
390
|
+
if (mark === undefined)
|
|
391
|
+
return where;
|
|
392
|
+
if (mark.gone) {
|
|
393
|
+
return { path: mark.path, line: null, label: `the code is gone after v${mark.since}`, quote };
|
|
394
|
+
}
|
|
395
|
+
return { ...where, label: `${where.label} · the code changed after v${mark.since}` };
|
|
396
|
+
}
|
|
415
397
|
function lineLabel(line, fallback) {
|
|
416
398
|
return line === null ? fallback : `line ${line}`;
|
|
417
399
|
}
|
|
418
400
|
function whereComment(ctx, c) {
|
|
401
|
+
return marked(ctx, c.id, whereOnTip(ctx, c), c.context.lines ?? []);
|
|
402
|
+
}
|
|
403
|
+
function whereOnTip(ctx, c) {
|
|
419
404
|
const { anchor } = c;
|
|
420
405
|
switch (anchor.kind) {
|
|
421
406
|
case "plan":
|
|
@@ -458,6 +443,9 @@ function whereComment(ctx, c) {
|
|
|
458
443
|
}
|
|
459
444
|
}
|
|
460
445
|
function whereConcern(ctx, kept) {
|
|
446
|
+
return marked(ctx, kept.concern.id, concernOnTip(ctx, kept), []);
|
|
447
|
+
}
|
|
448
|
+
function concernOnTip(ctx, kept) {
|
|
461
449
|
const { concern, commit } = kept;
|
|
462
450
|
if (concern.unit === undefined) {
|
|
463
451
|
return { path: null, line: null, label: `commit ${commit + 1}`, quote: [] };
|
|
@@ -756,8 +744,12 @@ function threadOf(c) {
|
|
|
756
744
|
function withSeverity(severity, text) {
|
|
757
745
|
return severity === null ? text : `**${severity}**\n\n${text}`;
|
|
758
746
|
}
|
|
759
|
-
function
|
|
760
|
-
const
|
|
747
|
+
function changedOf(ctx, id) {
|
|
748
|
+
const mark = ctx.marks?.get(id);
|
|
749
|
+
return mark === undefined ? {} : { changed: { since: mark.since, gone: mark.gone } };
|
|
750
|
+
}
|
|
751
|
+
function collectItems(review, ctx, responses) {
|
|
752
|
+
const { commits, units } = ctx;
|
|
761
753
|
const fromReviewer = review.comments.filter((c) => c.author !== "agent");
|
|
762
754
|
const walk = review.mode === "walk";
|
|
763
755
|
const rest = fromReviewer.filter((c) => c.kind !== "question");
|
|
@@ -777,6 +769,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
777
769
|
return {
|
|
778
770
|
where: it.where,
|
|
779
771
|
item: {
|
|
772
|
+
id: kept.concern.id,
|
|
780
773
|
...concernSpan(ctx, kept.concern),
|
|
781
774
|
path: it.where.path,
|
|
782
775
|
oldPath: it.where.path,
|
|
@@ -786,6 +779,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
786
779
|
thread,
|
|
787
780
|
resolved: false,
|
|
788
781
|
excluded: noConcerns.has(kept.concern.id),
|
|
782
|
+
...changedOf(ctx, kept.concern.id),
|
|
789
783
|
},
|
|
790
784
|
};
|
|
791
785
|
}
|
|
@@ -794,6 +788,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
794
788
|
return {
|
|
795
789
|
where: it.where,
|
|
796
790
|
item: {
|
|
791
|
+
id: first.id,
|
|
797
792
|
...commentSpan(ctx, first),
|
|
798
793
|
path: it.where.path,
|
|
799
794
|
oldPath: it.where.path,
|
|
@@ -803,6 +798,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
803
798
|
thread: threadOf(first),
|
|
804
799
|
resolved: resolved.has(first.id),
|
|
805
800
|
excluded: noComments.has(first.id),
|
|
801
|
+
...changedOf(ctx, first.id),
|
|
806
802
|
},
|
|
807
803
|
};
|
|
808
804
|
});
|
|
@@ -815,6 +811,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
815
811
|
out.push({
|
|
816
812
|
where,
|
|
817
813
|
item: {
|
|
814
|
+
id: q.id,
|
|
818
815
|
...commentSpan(ctx, q),
|
|
819
816
|
path: where.path,
|
|
820
817
|
oldPath: where.path,
|
|
@@ -824,6 +821,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
824
821
|
thread: threadOf(q),
|
|
825
822
|
resolved: resolved.has(q.id),
|
|
826
823
|
excluded: noComments.has(q.id),
|
|
824
|
+
...changedOf(ctx, q.id),
|
|
827
825
|
},
|
|
828
826
|
});
|
|
829
827
|
}
|
|
@@ -834,7 +832,8 @@ function collectItems(review, commits, units, responses) {
|
|
|
834
832
|
* Excluded ones are present and marked; whoever posts them skips those.
|
|
835
833
|
*/
|
|
836
834
|
export function reportItems(review, commits, units, opts) {
|
|
837
|
-
|
|
835
|
+
const session = folded(review, commits, units, opts.earlier);
|
|
836
|
+
return collectItems(session.review, session.ctx, opts.responses).map((c) => c.item);
|
|
838
837
|
}
|
|
839
838
|
/** A short list of what the reviewer left out, so nothing vanishes without a trace. */
|
|
840
839
|
function renderExcluded(collected) {
|
|
@@ -850,6 +849,64 @@ function renderExcluded(collected) {
|
|
|
850
849
|
out.push("");
|
|
851
850
|
return out;
|
|
852
851
|
}
|
|
852
|
+
/**
|
|
853
|
+
* The review of the whole session on this version's diff: the earlier versions' comments on
|
|
854
|
+
* their carried anchors, the concerns kept there that this plan lost added to the commit now
|
|
855
|
+
* taking their unit, and a mark on each whose code changed. Each id appears once; this
|
|
856
|
+
* version's own copy wins.
|
|
857
|
+
*/
|
|
858
|
+
function folded(review, commits, units, earlier) {
|
|
859
|
+
if (earlier === undefined)
|
|
860
|
+
return { review, ctx: { commits, units } };
|
|
861
|
+
const marks = new Map();
|
|
862
|
+
const mark = (id, at) => {
|
|
863
|
+
if (at.since !== null)
|
|
864
|
+
marks.set(id, { since: at.since, gone: at.unit === null, path: at.path });
|
|
865
|
+
};
|
|
866
|
+
const owner = (unit, fallback) => {
|
|
867
|
+
const k = commits.findIndex((c) => c.refs.some((r) => r.unit === unit));
|
|
868
|
+
return k === -1 ? Math.min(fallback, Math.max(commits.length - 1, 0)) : k;
|
|
869
|
+
};
|
|
870
|
+
const own = new Set(review.comments.map((c) => c.id));
|
|
871
|
+
const comments = earlier.comments
|
|
872
|
+
.filter((c) => !own.has(c.comment.id))
|
|
873
|
+
.map((c) => {
|
|
874
|
+
mark(c.comment.id, c);
|
|
875
|
+
const { anchor } = c.comment;
|
|
876
|
+
if (anchor.kind !== "hunk")
|
|
877
|
+
return c.comment;
|
|
878
|
+
const moved = anchorOn(c, units, owner(c.unit, anchor.commit));
|
|
879
|
+
return moved === null ? c.comment : { ...c.comment, anchor: moved };
|
|
880
|
+
});
|
|
881
|
+
const inPlan = new Set(commits.flatMap((c) => (c.concerns ?? []).map((k) => k.id)));
|
|
882
|
+
const lost = earlier.concerns.filter((c) => !inPlan.has(c.concern.id));
|
|
883
|
+
for (const c of lost)
|
|
884
|
+
mark(c.concern.id, c);
|
|
885
|
+
const decided = new Set((review.concerns ?? []).map((d) => d.concern));
|
|
886
|
+
return {
|
|
887
|
+
review: {
|
|
888
|
+
...review,
|
|
889
|
+
comments: [...comments, ...review.comments],
|
|
890
|
+
concerns: [
|
|
891
|
+
...lost.filter((c) => !decided.has(c.concern.id)).map((c) => c.decision),
|
|
892
|
+
...(review.concerns ?? []),
|
|
893
|
+
],
|
|
894
|
+
},
|
|
895
|
+
ctx: {
|
|
896
|
+
units,
|
|
897
|
+
marks,
|
|
898
|
+
commits: commits.map((commit, k) => {
|
|
899
|
+
const mine = lost.filter((c) => owner(c.unit, c.commit) === k);
|
|
900
|
+
if (mine.length === 0)
|
|
901
|
+
return commit;
|
|
902
|
+
return {
|
|
903
|
+
...commit,
|
|
904
|
+
concerns: [...(commit.concerns ?? []), ...mine.map((c) => concernOn(c, units))],
|
|
905
|
+
};
|
|
906
|
+
}),
|
|
907
|
+
},
|
|
908
|
+
};
|
|
909
|
+
}
|
|
853
910
|
/**
|
|
854
911
|
* The consolidated report of a review, in the voices of both the reviewer and the agent: the
|
|
855
912
|
* reviewer's comments and the agent's concerns the reviewer kept, grouped by file and ordered by
|
|
@@ -858,22 +915,24 @@ function renderExcluded(collected) {
|
|
|
858
915
|
* Concerns the reviewer has not decided are left out, as are those dismissed.
|
|
859
916
|
*/
|
|
860
917
|
export function renderReport(full, commits, units, opts) {
|
|
861
|
-
const
|
|
918
|
+
const session = folded(full, commits, units, opts.earlier);
|
|
919
|
+
const { ctx } = session;
|
|
920
|
+
const excluded = session.review.excluded;
|
|
862
921
|
if (excluded === undefined)
|
|
863
|
-
return renderIncluded(
|
|
922
|
+
return renderIncluded(session.review, ctx, opts);
|
|
864
923
|
const noComments = new Set(excluded.comments);
|
|
865
924
|
const noConcerns = new Set(excluded.concerns);
|
|
866
925
|
const review = {
|
|
867
|
-
...
|
|
868
|
-
comments:
|
|
869
|
-
concerns: (
|
|
926
|
+
...session.review,
|
|
927
|
+
comments: session.review.comments.filter((c) => !noComments.has(c.id)),
|
|
928
|
+
concerns: (session.review.concerns ?? []).filter((d) => !noConcerns.has(d.concern)),
|
|
870
929
|
};
|
|
871
|
-
const tail = renderExcluded(collectItems(
|
|
872
|
-
const main = renderIncluded(review,
|
|
930
|
+
const tail = renderExcluded(collectItems(session.review, ctx, opts.responses));
|
|
931
|
+
const main = renderIncluded(review, ctx, opts);
|
|
873
932
|
return tail.length === 0 ? main : `${main}${main.endsWith("\n") ? "" : "\n"}${tail.join("\n")}`;
|
|
874
933
|
}
|
|
875
|
-
function renderIncluded(review,
|
|
876
|
-
const
|
|
934
|
+
function renderIncluded(review, ctx, opts) {
|
|
935
|
+
const { commits, units } = ctx;
|
|
877
936
|
const walk = review.mode === "walk";
|
|
878
937
|
const fromReviewer = review.comments.filter((c) => c.author !== "agent");
|
|
879
938
|
// A thread the agent started matters to it only once the reviewer has answered.
|
|
@@ -914,47 +973,49 @@ function renderIncluded(review, commits, units, opts) {
|
|
|
914
973
|
if (!walk && (feedback.length > 0 || questions.length > 0)) {
|
|
915
974
|
out.push("Answer every comment id above in `responses` of the next draft, then run `follow-me check` and `follow-me apply`.", "");
|
|
916
975
|
}
|
|
917
|
-
const onContext = review.comments.some((c) => pickRows(ctx, c.anchor)?.touchesContext);
|
|
918
|
-
if (onContext)
|
|
919
|
-
out.push(CONTEXT_RULE, "");
|
|
920
976
|
if (!walk && items.length > 0) {
|
|
921
|
-
out.push("Fix the code issues on the original branch, then run `follow-me
|
|
977
|
+
out.push("Fix the code issues on the original branch, then run `follow-me` again.", "");
|
|
922
978
|
}
|
|
923
979
|
return out.join("\n");
|
|
924
980
|
}
|
|
981
|
+
function indented(body) {
|
|
982
|
+
return ` ${body.split("\n").join("\n ")}`;
|
|
983
|
+
}
|
|
984
|
+
/** "- head: body" for a one-line body, else the body indented under the head. */
|
|
985
|
+
function said(head, body) {
|
|
986
|
+
return body.includes("\n") ? [`${head}:`, indented(body)] : [`${head}: ${body}`];
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* A new comment carries its short quote, since the agent has not seen it; everything else is one
|
|
990
|
+
* line naming its id, because the agent already has the comment it refers to.
|
|
991
|
+
*/
|
|
925
992
|
function renderEvent(event, ctx) {
|
|
926
993
|
switch (event.type) {
|
|
927
|
-
case "comment":
|
|
928
|
-
case "comment-updated": {
|
|
994
|
+
case "comment": {
|
|
929
995
|
const { comment: c } = event;
|
|
930
|
-
const what = c.kind === "question" ? "question" : "comment";
|
|
931
|
-
const
|
|
932
|
-
const lines = [`- ${verb} ${what} ${c.id} on ${describeAnchor(c.anchor, ctx)}`];
|
|
933
|
-
const rows = anchorRows(ctx, c.anchor);
|
|
934
|
-
if (rows.length > 0)
|
|
935
|
-
lines.push(...rows.map((r) => ` ${r}`));
|
|
936
|
-
if (c.context.path)
|
|
937
|
-
lines.push(` - file: ${c.context.path}`);
|
|
938
|
-
if (c.severity)
|
|
939
|
-
lines.push(` - severity: ${c.severity}`);
|
|
996
|
+
const what = c.kind === "question" ? "question" : (c.severity ?? "comment");
|
|
997
|
+
const lines = [`- ${c.id} ${what} on ${describeAnchor(c.anchor, ctx)}`];
|
|
940
998
|
if (c.follows)
|
|
941
|
-
lines.push(`
|
|
942
|
-
lines.push(
|
|
999
|
+
lines.push(` asks again after ${c.follows}`);
|
|
1000
|
+
lines.push(...shortQuote(anchorRows(ctx, c.anchor)).map((r) => ` ${r}`));
|
|
1001
|
+
lines.push(indented(c.body));
|
|
943
1002
|
return lines;
|
|
944
1003
|
}
|
|
1004
|
+
case "comment-updated":
|
|
1005
|
+
return said(`- ${event.comment.id} edited`, event.comment.body);
|
|
945
1006
|
case "comment-deleted":
|
|
946
|
-
return [`-
|
|
1007
|
+
return [`- ${event.id} deleted`];
|
|
947
1008
|
case "reply":
|
|
948
|
-
return
|
|
1009
|
+
return said(`- ${event.commentId} ${event.reply.author} replied`, event.reply.body);
|
|
949
1010
|
case "concern": {
|
|
950
1011
|
const severity = event.severity === undefined ? "" : ` as ${event.severity}`;
|
|
951
1012
|
const note = event.note === undefined ? "" : `: ${event.note}`;
|
|
952
|
-
return [`-
|
|
1013
|
+
return [`- ${event.concernId} ${event.decision}${severity}${note}`];
|
|
953
1014
|
}
|
|
954
1015
|
case "resolved":
|
|
955
|
-
return [`-
|
|
1016
|
+
return [`- ${event.commentId} ${event.resolved ? "resolved" : "reopened"}`];
|
|
956
1017
|
case "version":
|
|
957
|
-
return [`- plan v${event.version} is
|
|
1018
|
+
return [`- plan v${event.version} is open`];
|
|
958
1019
|
case "submitted":
|
|
959
1020
|
return ["- the reviewer submitted the review"];
|
|
960
1021
|
case "ended":
|
|
@@ -965,11 +1026,42 @@ function renderEvent(event, ctx) {
|
|
|
965
1026
|
}
|
|
966
1027
|
}
|
|
967
1028
|
}
|
|
968
|
-
/**
|
|
1029
|
+
/** What happened on the review page, for the entry and `follow-me watch`. */
|
|
969
1030
|
export function renderEvents(events, ctx = null) {
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
1031
|
+
return `${events.flatMap((event) => renderEvent(event, ctx)).join("\n")}\n`;
|
|
1032
|
+
}
|
|
1033
|
+
/** A unit as prompt.md prints it, and which commits of the plan take its lines. */
|
|
1034
|
+
export function renderShownUnit(unit, commits) {
|
|
1035
|
+
const taken = commits.flatMap((commit, k) => commit.refs
|
|
1036
|
+
.filter((ref) => ref.unit === unit.id)
|
|
1037
|
+
.map((ref) => `commit ${k + 1} takes ${formatSelection(ref.unit, ref.select)}`));
|
|
1038
|
+
const where = taken.length === 0 ? "in no commit of the plan" : taken.join("; ");
|
|
1039
|
+
return `${renderUnit(unit)}\n${where}\n`;
|
|
1040
|
+
}
|
|
1041
|
+
/** A comment with every row it was made on, its words and the whole thread under it. */
|
|
1042
|
+
export function renderShownComment(c, ctx, version) {
|
|
1043
|
+
const what = c.kind === "question" ? "question" : (c.severity ?? "comment");
|
|
1044
|
+
const lines = [`${c.id} ${what} on plan v${version}, on ${describeAnchor(c.anchor, ctx)}`];
|
|
1045
|
+
if (c.follows)
|
|
1046
|
+
lines.push(`asks again after ${c.follows}`);
|
|
1047
|
+
const rows = anchorRows(ctx, c.anchor);
|
|
1048
|
+
const quoted = rows.length > 0 ? rows : (c.context.lines ?? []).map((l) => `> ${l}`);
|
|
1049
|
+
lines.push(...quoted.map((r) => ` ${r}`));
|
|
1050
|
+
lines.push(...said(c.author === "agent" ? "agent" : "reviewer", c.body));
|
|
1051
|
+
for (const reply of c.replies ?? []) {
|
|
1052
|
+
const diagram = reply.diagram === undefined ? "" : " (with a diagram)";
|
|
1053
|
+
lines.push(...said(`${reply.author} replied${diagram}`, reply.body));
|
|
1054
|
+
}
|
|
1055
|
+
return `${lines.join("\n")}\n`;
|
|
1056
|
+
}
|
|
1057
|
+
/** A concern of the plan with the lines it is pinned to, as its commit writes them. */
|
|
1058
|
+
export function renderShownConcern(concern, commit, ctx) {
|
|
1059
|
+
const where = whereConcern(ctx, { concern, commit, severity: concern.severity, note: null });
|
|
1060
|
+
const place = where.path === null ? where.label : `${where.path} ${where.label}`;
|
|
1061
|
+
return [
|
|
1062
|
+
`${concern.id} ${concern.severity} ${concern.kind} in commit ${commit + 1}, ${place}`,
|
|
1063
|
+
...where.quote.map((r) => ` ${r}`),
|
|
1064
|
+
concern.text,
|
|
1065
|
+
"",
|
|
1066
|
+
].join("\n");
|
|
975
1067
|
}
|