@shiyuphan/follow-me 0.5.0 → 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 +17 -3
- package/dist/src/comments.js +62 -9
- package/dist/src/evolve.js +145 -29
- package/dist/src/host.js +12 -3
- package/dist/src/plan.js +14 -0
- package/dist/src/prompt.js +100 -12
- package/dist/src/server.js +106 -29
- package/dist/src/session.js +23 -0
- package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-BXkLs7mV.js → abnfDiagram-WEYOIA3C-8UsLPznO.js} +1 -1
- package/dist/ui/dist/assets/{arc-DNzjw7Ie.js → arc-vovjGalz.js} +1 -1
- package/dist/ui/dist/assets/{architecture-WOLXFQ4H-D66kgJJt.js → architecture-WOLXFQ4H-BBMK-PUu.js} +1 -1
- package/dist/ui/dist/assets/{architectureDiagram-ZHVYKT53-D8UWuhQr.js → architectureDiagram-ZHVYKT53-Bw_ehaQk.js} +1 -1
- package/dist/ui/dist/assets/{blockDiagram-RJAE7QA3-BLkmr62i.js → blockDiagram-RJAE7QA3-CAmD0wKf.js} +1 -1
- package/dist/ui/dist/assets/{c4Diagram-XDPPPE54-3rZPYwOc.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-MMnvAgpB.js → chunk-2E7WHVMD-BiHSeJMu.js} +1 -1
- package/dist/ui/dist/assets/{chunk-4EMNKR2T-D9FGWA3l.js → chunk-4EMNKR2T-CR3lHKf_.js} +1 -1
- package/dist/ui/dist/assets/{chunk-55XCU76K-DuOUaV8F.js → chunk-55XCU76K-CtaoloTT.js} +1 -1
- package/dist/ui/dist/assets/{chunk-7S6XSTFO-p3YWPv0m.js → chunk-7S6XSTFO-eABdc7fu.js} +1 -1
- package/dist/ui/dist/assets/{chunk-AQDTSKZB-DWfKYjwk.js → chunk-AQDTSKZB-CPlbJWi-.js} +1 -1
- package/dist/ui/dist/assets/{chunk-DSNLSHJZ-C3CIwpv0.js → chunk-DSNLSHJZ-YiMuxsb-.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HAGFOTED-Bwfl9gme.js → chunk-HAGFOTED-1VcdL78a.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HNE4Q36P-0_oeU0le.js → chunk-HNE4Q36P-u-sr_0Qu.js} +1 -1
- package/dist/ui/dist/assets/{chunk-NQOKRVIF-z_LPPPx5.js → chunk-NQOKRVIF-C_QlWSze.js} +1 -1
- package/dist/ui/dist/assets/{chunk-UHW7PFBP-BH28V2yu.js → chunk-UHW7PFBP-CNuZhsbU.js} +1 -1
- package/dist/ui/dist/assets/{chunk-VQ5W62QI-BXDajDf7.js → chunk-VQ5W62QI-DAHWbLqU.js} +1 -1
- package/dist/ui/dist/assets/{chunk-WQQL4CDK-BphBlfKj.js → chunk-WQQL4CDK-DWQnxFCd.js} +1 -1
- package/dist/ui/dist/assets/{chunk-XXDRQBXY-BiCT5NwV.js → chunk-XXDRQBXY-B0lGgSOR.js} +1 -1
- package/dist/ui/dist/assets/{chunk-YZO67HFG-Bg5Pu8BX.js → chunk-YZO67HFG-BDw7efkN.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZAUX2LTX-C_Tgd8tB.js → chunk-ZAUX2LTX-B5wfHsZv.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZCHMQSOR-MBRcMi8-.js → chunk-ZCHMQSOR--rWtYtAG.js} +1 -1
- package/dist/ui/dist/assets/{classDiagram-v2-4MYWR3UA-1wyeCe5y.js → classDiagram-v2-4MYWR3UA-BKuQ_xoS.js} +1 -1
- package/dist/ui/dist/assets/{cose-bilkent-JH36ORCC-BtOYclF6.js → cose-bilkent-JH36ORCC-Dk5fIpU0.js} +1 -1
- package/dist/ui/dist/assets/{cynefin-EF2NZ3EQ-dJmoOG8g.js → cynefin-EF2NZ3EQ-DIykJb2g.js} +1 -1
- package/dist/ui/dist/assets/{cynefinDiagram-WF24UDRP-AloyqbEN.js → cynefinDiagram-WF24UDRP-Cg8tBqUb.js} +1 -1
- package/dist/ui/dist/assets/{dagre-NK4GIZVX-NCZhdnfb.js → dagre-NK4GIZVX-BC_tnnjz.js} +1 -1
- package/dist/ui/dist/assets/{diagram-CBK3MWTC-D8w7x5Gb.js → diagram-CBK3MWTC-B_MQGixU.js} +1 -1
- package/dist/ui/dist/assets/{diagram-I5XIIV2D-BUFnCGlp.js → diagram-I5XIIV2D-DO681pZ5.js} +1 -1
- package/dist/ui/dist/assets/{diagram-ODIL2UCW-BULmHKaW.js → diagram-ODIL2UCW-D14jLzU6.js} +1 -1
- package/dist/ui/dist/assets/{diagram-PFBEJUXE-B5MqFo0r.js → diagram-PFBEJUXE-cML48gUS.js} +1 -1
- package/dist/ui/dist/assets/{diagram-RJA3I4VV-BIkDLL8E.js → diagram-RJA3I4VV-DSoq8jFk.js} +1 -1
- package/dist/ui/dist/assets/{diagram-TYUNFSNR-Ca4gsQV8.js → diagram-TYUNFSNR-CaJ4pvnl.js} +1 -1
- package/dist/ui/dist/assets/{dist-G85JKY5k.js → dist-D3j-DODP.js} +1 -1
- package/dist/ui/dist/assets/{ebnfDiagram-V4P3TTPT-rVjcFFtb.js → ebnfDiagram-V4P3TTPT-CyRpjSix.js} +1 -1
- package/dist/ui/dist/assets/{elk-QONTBCTQ-BWPdvULh.js → elk-QONTBCTQ-ICJdzVFL.js} +1 -1
- package/dist/ui/dist/assets/{erDiagram-KYQIGQAA-BPxJJkRl.js → erDiagram-KYQIGQAA-B57hNTfw.js} +1 -1
- package/dist/ui/dist/assets/{eventmodeling-K75KTNOO-BSB9WYV3.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-DKps530K.js → ganttDiagram-R7GPNTJY-Czt6Klik.js} +1 -1
- package/dist/ui/dist/assets/{gitGraph-VSP46ZUC-D1d4UAFV.js → gitGraph-VSP46ZUC-De_ZWJLj.js} +1 -1
- package/dist/ui/dist/assets/{gitGraphDiagram-PDYL7XHY-N55yl6hc.js → gitGraphDiagram-PDYL7XHY-BMCJoOnT.js} +1 -1
- package/dist/ui/dist/assets/index-CYKbk4QS.js +134 -0
- package/dist/ui/dist/assets/{info-OHQRW6UA-CgxE3gf9.js → info-OHQRW6UA-D75H9kUq.js} +1 -1
- package/dist/ui/dist/assets/{infoDiagram-MVDRKAX5-DOnUrnMf.js → infoDiagram-MVDRKAX5-Bsm_I9dt.js} +1 -1
- package/dist/ui/dist/assets/{ishikawaDiagram-HPEHV2GE-BIY0fFHB.js → ishikawaDiagram-HPEHV2GE-98xZAJ9y.js} +1 -1
- package/dist/ui/dist/assets/{journeyDiagram-BOSHW7O7-CqA2Ern0.js → journeyDiagram-BOSHW7O7-BWVGuc0P.js} +1 -1
- package/dist/ui/dist/assets/{kanban-definition-FNSA4HC5-vGrpQKIF.js → kanban-definition-FNSA4HC5-DgUE_KDd.js} +1 -1
- package/dist/ui/dist/assets/{line-Brzv3qA6.js → line-i__8Rvas.js} +1 -1
- package/dist/ui/dist/assets/{linear-D9uIlqyh.js → linear-BXKj0Fip.js} +1 -1
- package/dist/ui/dist/assets/{mermaid-parser.core-Bz9b-Thu.js → mermaid-parser.core-DxvaorTa.js} +3 -3
- package/dist/ui/dist/assets/{mermaid.core-BaZipu3b.js → mermaid.core-ujYTsRZz.js} +4 -4
- package/dist/ui/dist/assets/{mindmap-definition-6OY57UG3-D6aie7uH.js → mindmap-definition-6OY57UG3-B7QD6sjk.js} +1 -1
- package/dist/ui/dist/assets/{packet-JDAUHWVQ-28r3jVFY.js → packet-JDAUHWVQ-B6aIwfD1.js} +1 -1
- package/dist/ui/dist/assets/{pegDiagram-ECIUJNBX-CbGvD4qP.js → pegDiagram-ECIUJNBX-_RStHj-a.js} +1 -1
- package/dist/ui/dist/assets/{pie-XZMESJXO-DHpcGnZd.js → pie-XZMESJXO-CNLC-pTe.js} +1 -1
- package/dist/ui/dist/assets/{pieDiagram-GNMPY74T-DuhauwnY.js → pieDiagram-GNMPY74T-0uT7MT-u.js} +1 -1
- package/dist/ui/dist/assets/{prism-core-CbZMOvrb.js → prism-core-DtxziK1i.js} +1 -1
- package/dist/ui/dist/assets/{quadrantDiagram-D57K4MC3-BfyNmVca.js → quadrantDiagram-D57K4MC3-CTMKb5hl.js} +1 -1
- package/dist/ui/dist/assets/{radar-ABXABTNO-Xp1TYwaj.js → radar-ABXABTNO-D53h6I0N.js} +1 -1
- package/dist/ui/dist/assets/{railroad-I3PHUGI6-c5FY4wwa.js → railroad-I3PHUGI6-CwjhW4mc.js} +1 -1
- package/dist/ui/dist/assets/{railroad-abnf-LNEFI6M7-RDyWXqcL.js → railroad-abnf-LNEFI6M7-DDbQ3RmB.js} +1 -1
- package/dist/ui/dist/assets/{railroad-ebnf-DWYD2UWJ-CLD4jVou.js → railroad-ebnf-DWYD2UWJ-Am7YJAqO.js} +1 -1
- package/dist/ui/dist/assets/{railroad-peg-JR7BVNR7-17EYTA-w.js → railroad-peg-JR7BVNR7-Ci32KoVW.js} +1 -1
- package/dist/ui/dist/assets/{railroadDiagram-Y72NRBL7-C4p2wtKe.js → railroadDiagram-Y72NRBL7-BPU5A6XW.js} +1 -1
- package/dist/ui/dist/assets/{requirementDiagram-EQYSW5RA-DTYIFN8T.js → requirementDiagram-EQYSW5RA-_0Pq4QdV.js} +1 -1
- package/dist/ui/dist/assets/{sankeyDiagram-SY24JHO5-YDYhRyJU.js → sankeyDiagram-SY24JHO5-c9h-7uf8.js} +1 -1
- package/dist/ui/dist/assets/{sequenceDiagram-BLEHNHQU-Crvy6IlU.js → sequenceDiagram-BLEHNHQU-B9-S4Rc9.js} +1 -1
- package/dist/ui/dist/assets/{src-BPod-Ype.js → src-BZ3kG7lO.js} +1 -1
- package/dist/ui/dist/assets/{stateDiagram-v2-A6IESKWZ-BWr70YOQ.js → stateDiagram-v2-A6IESKWZ-py1smSen.js} +1 -1
- package/dist/ui/dist/assets/{swimlanes-B3ENRKFQ-DvbG4I9a.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-CR950Bzb.js → timeline-definition-JXYKGLGB-DNe1To6B.js} +1 -1
- package/dist/ui/dist/assets/{treeView-D4JQ5SDB-DSDDXGrk.js → treeView-D4JQ5SDB-D6MG89i6.js} +1 -1
- package/dist/ui/dist/assets/{treemap-SAJKECNS-Ct5w2_RR.js → treemap-SAJKECNS-CZpt7Mlh.js} +1 -1
- package/dist/ui/dist/assets/{usecaseDiagram-WPOLBNNX-_eP0371y.js → usecaseDiagram-WPOLBNNX-CKsv-03M.js} +1 -1
- package/dist/ui/dist/assets/{vennDiagram-Q3Y3IDCW-Cu-IA57L.js → vennDiagram-Q3Y3IDCW-DGJ1qlyJ.js} +1 -1
- package/dist/ui/dist/assets/{wardley-7MLQ67FV-r8yd6frN.js → wardley-7MLQ67FV-ClOzBemC.js} +1 -1
- package/dist/ui/dist/assets/{wardleyDiagram-IVAIDKWA-CBUUiRQm.js → wardleyDiagram-IVAIDKWA-C68ahPyF.js} +1 -1
- package/dist/ui/dist/assets/{xychartDiagram-4URNHDU4-DEVQjvgG.js → xychartDiagram-4URNHDU4-DWDamCoH.js} +1 -1
- package/dist/ui/dist/index.html +1 -1
- package/package.json +1 -1
- package/dist/ui/dist/assets/channel-B9JVdOBZ.js +0 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-N-Spyk2o.js +0 -1
- package/dist/ui/dist/assets/index-BKtWBS7Z.js +0 -134
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-D2wU-jW6.js +0 -8
package/dist/src/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { naiveDraft } from "./naive.js";
|
|
|
11
11
|
import { isCommentId, isConcernId, isFields, serializePlanFile, validatePlan, } from "./plan.js";
|
|
12
12
|
import { renderEvents, renderPrompt, renderReport, renderShownComment, renderShownConcern, renderShownUnit, reportItems, } from "./prompt.js";
|
|
13
13
|
import { RegistrationError, sessionIdOf } from "./server.js";
|
|
14
|
-
import { BranchMovedError, cleanSessions, extractSession, MissingDraftError, NotARepositoryError, openSession, SessionFileError, SessionLockedError, sinceLastPlan, } from "./session.js";
|
|
14
|
+
import { BranchMovedError, cleanSessions, extractSession, MissingDraftError, NotARepositoryError, openSession, SessionFileError, SessionLockedError, sessionComments, sinceLastPlan, } from "./session.js";
|
|
15
15
|
import { checkSyntax } from "./syntax.js";
|
|
16
16
|
class UsageError extends Error {
|
|
17
17
|
name = "UsageError";
|
|
@@ -909,7 +909,9 @@ function finished(session, stored) {
|
|
|
909
909
|
const text = renderStored(session, stored);
|
|
910
910
|
if (text === null)
|
|
911
911
|
return 1;
|
|
912
|
-
|
|
912
|
+
// A review that swapped versions holds what its earlier versions said too.
|
|
913
|
+
const said = stored.review.comments.length + (stored.review.carried?.length ?? 0);
|
|
914
|
+
say(`review v${stored.version} ended with ${plural(said, "comment")}`);
|
|
913
915
|
process.stdout.write(text);
|
|
914
916
|
say();
|
|
915
917
|
next("done; the user can port it with follow-me report --json");
|
|
@@ -1240,7 +1242,19 @@ function renderStored(session, stored, format = "markdown") {
|
|
|
1240
1242
|
.plans()
|
|
1241
1243
|
.filter((p) => p.version > stored.version)
|
|
1242
1244
|
.flatMap((p) => p.file.responses);
|
|
1243
|
-
const
|
|
1245
|
+
const { carried } = stored.review;
|
|
1246
|
+
const said = reviewsOf(session)
|
|
1247
|
+
.filter((r) => r.version <= stored.version)
|
|
1248
|
+
.map((r) => r.review);
|
|
1249
|
+
const earlier = carried === undefined
|
|
1250
|
+
? {}
|
|
1251
|
+
: {
|
|
1252
|
+
earlier: {
|
|
1253
|
+
comments: sessionComments(carried, said),
|
|
1254
|
+
concerns: stored.review.carriedConcerns ?? [],
|
|
1255
|
+
},
|
|
1256
|
+
};
|
|
1257
|
+
const opts = { branch: session.anchor.branch, responses, ...earlier };
|
|
1244
1258
|
if (format === "json") {
|
|
1245
1259
|
const items = reportItems(stored.review, verdict.plan.commits, session.diff.units, opts);
|
|
1246
1260
|
return `${JSON.stringify(items, null, 2)}\n`;
|
package/dist/src/comments.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isRepoPath, isUnitId } from "./diff.js";
|
|
2
|
-
import { diagramProblem, isArray, isCommentId, isConcernId, isFields, isPlanVersion, isSeverity, } from "./plan.js";
|
|
2
|
+
import { diagramProblem, isArray, isCommentId, isConcernId, isFields, isPlanVersion, isSeverity, parsePlacedConcern, } from "./plan.js";
|
|
3
3
|
/**
|
|
4
4
|
* The wire formats of review state, parsed once at the boundary: comments, replies, concern
|
|
5
5
|
* decisions, the review file, its autosaved draft and the events a hosted session logs. Every
|
|
@@ -253,13 +253,7 @@ function parseEarlierReplies(v) {
|
|
|
253
253
|
function isLine(v) {
|
|
254
254
|
return typeof v === "number" && Number.isInteger(v) && v >= 1;
|
|
255
255
|
}
|
|
256
|
-
|
|
257
|
-
if (!isFields(v)) {
|
|
258
|
-
return "not an object";
|
|
259
|
-
}
|
|
260
|
-
const comment = parseComment(v.comment);
|
|
261
|
-
if (typeof comment === "string")
|
|
262
|
-
return comment;
|
|
256
|
+
function parsePlacement(v) {
|
|
263
257
|
const { unit, offsets, changed, path, newLine } = v;
|
|
264
258
|
if (unit !== null && !(typeof unit === "string" && isUnitId(unit)))
|
|
265
259
|
return "unit is not a unit id";
|
|
@@ -273,7 +267,6 @@ export function parseCarried(v) {
|
|
|
273
267
|
if (newLine !== null && !isLine(newLine))
|
|
274
268
|
return "newLine is not null or a line";
|
|
275
269
|
return {
|
|
276
|
-
comment,
|
|
277
270
|
unit,
|
|
278
271
|
offsets: offsets === null ? null : offsets.filter(isIndex),
|
|
279
272
|
changed,
|
|
@@ -281,6 +274,56 @@ export function parseCarried(v) {
|
|
|
281
274
|
newLine,
|
|
282
275
|
};
|
|
283
276
|
}
|
|
277
|
+
export function parseCarried(v) {
|
|
278
|
+
if (!isFields(v))
|
|
279
|
+
return "not an object";
|
|
280
|
+
const comment = parseComment(v.comment);
|
|
281
|
+
if (typeof comment === "string")
|
|
282
|
+
return comment;
|
|
283
|
+
const placement = parsePlacement(v);
|
|
284
|
+
return typeof placement === "string" ? placement : { comment, ...placement };
|
|
285
|
+
}
|
|
286
|
+
function parseSince(v) {
|
|
287
|
+
if (v === null)
|
|
288
|
+
return null;
|
|
289
|
+
return isPlanVersion(v) ? v : "since is not null or a plan version";
|
|
290
|
+
}
|
|
291
|
+
function parseCarriedRef(v) {
|
|
292
|
+
if (!isFields(v))
|
|
293
|
+
return "not an object";
|
|
294
|
+
const comment = asCommentId(v.comment);
|
|
295
|
+
if (comment === null)
|
|
296
|
+
return "comment is not a comment id";
|
|
297
|
+
if (!isPlanVersion(v.version))
|
|
298
|
+
return "version is not a plan version";
|
|
299
|
+
const since = parseSince(v.since);
|
|
300
|
+
if (typeof since === "string")
|
|
301
|
+
return since;
|
|
302
|
+
const placement = parsePlacement(v);
|
|
303
|
+
if (typeof placement === "string")
|
|
304
|
+
return placement;
|
|
305
|
+
return { ...placement, comment, version: v.version, since };
|
|
306
|
+
}
|
|
307
|
+
function parseCarriedConcern(v) {
|
|
308
|
+
if (!isFields(v)) {
|
|
309
|
+
return "not an object";
|
|
310
|
+
}
|
|
311
|
+
const concern = parsePlacedConcern(v.concern);
|
|
312
|
+
if (typeof concern === "string")
|
|
313
|
+
return `concern: ${concern}`;
|
|
314
|
+
if (!isIndex(v.commit))
|
|
315
|
+
return "commit is not a commit index";
|
|
316
|
+
const decision = parseDecision(v.decision);
|
|
317
|
+
if (typeof decision === "string")
|
|
318
|
+
return `decision: ${decision}`;
|
|
319
|
+
const since = parseSince(v.since);
|
|
320
|
+
if (typeof since === "string")
|
|
321
|
+
return since;
|
|
322
|
+
const placement = parsePlacement(v);
|
|
323
|
+
if (typeof placement === "string")
|
|
324
|
+
return placement;
|
|
325
|
+
return { ...placement, concern, commit: v.commit, decision, since };
|
|
326
|
+
}
|
|
284
327
|
/** Boundary parse of reviews/vN.json. Files written before walk mode existed have no mode. */
|
|
285
328
|
export function parseReview(v) {
|
|
286
329
|
if (!isFields(v)) {
|
|
@@ -314,6 +357,12 @@ export function parseReview(v) {
|
|
|
314
357
|
const earlier = parseList(v.earlierReplies, parseEarlierReplies, "earlierReplies");
|
|
315
358
|
if (typeof earlier === "string")
|
|
316
359
|
return earlier;
|
|
360
|
+
const carried = v.carried === undefined ? null : parseList(v.carried, parseCarriedRef, "carried");
|
|
361
|
+
if (typeof carried === "string")
|
|
362
|
+
return carried;
|
|
363
|
+
const concernsCarried = parseList(v.carriedConcerns, parseCarriedConcern, "carriedConcerns");
|
|
364
|
+
if (typeof concernsCarried === "string")
|
|
365
|
+
return concernsCarried;
|
|
317
366
|
return {
|
|
318
367
|
schema: v.schema,
|
|
319
368
|
mode,
|
|
@@ -325,6 +374,8 @@ export function parseReview(v) {
|
|
|
325
374
|
...(resolved.length === 0 ? {} : { resolved }),
|
|
326
375
|
...(anyExcluded ? { excluded } : {}),
|
|
327
376
|
...(earlier.length === 0 ? {} : { earlierReplies: earlier }),
|
|
377
|
+
...(carried === null ? {} : { carried }),
|
|
378
|
+
...(concernsCarried.length === 0 ? {} : { carriedConcerns: concernsCarried }),
|
|
328
379
|
};
|
|
329
380
|
}
|
|
330
381
|
function lenient(items, parse) {
|
|
@@ -350,6 +401,8 @@ export function parseDraft(v) {
|
|
|
350
401
|
resolved: lenientIds(v.resolved, isCommentIdValue),
|
|
351
402
|
excluded: typeof excluded === "string" ? { comments: [], concerns: [] } : excluded,
|
|
352
403
|
earlierReplies: lenient(v.earlierReplies, parseEarlierReplies),
|
|
404
|
+
carried: v.carried === undefined ? null : lenient(v.carried, parseCarriedRef),
|
|
405
|
+
carriedConcerns: lenient(v.carriedConcerns, parseCarriedConcern),
|
|
353
406
|
};
|
|
354
407
|
}
|
|
355
408
|
export function parseEvent(v) {
|
package/dist/src/evolve.js
CHANGED
|
@@ -78,50 +78,166 @@ function distance(hunk, units, line) {
|
|
|
78
78
|
const [from, to] = span(hunk, units);
|
|
79
79
|
return line < from ? from - line : line > to ? line - to : 0;
|
|
80
80
|
}
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
/** The body row whose tip line is `line`; a kept row before a removal at the same line. */
|
|
82
|
+
function rowAt(hunk, units, line) {
|
|
83
|
+
if (line === null)
|
|
84
|
+
return 0;
|
|
85
|
+
const lines = tipLines(hunk, units);
|
|
86
|
+
const row = hunk.body.findIndex((r, i) => r.op !== "-" && lines[i] === line);
|
|
87
|
+
return row === -1 ? 0 : row;
|
|
87
88
|
}
|
|
88
|
-
function
|
|
89
|
+
function pinnedComment(comment) {
|
|
89
90
|
const { anchor } = comment;
|
|
90
|
-
const nowhere = { comment, unit: null, offsets: null, newLine: null };
|
|
91
|
-
if (anchor.kind !== "hunk")
|
|
92
|
-
return { ...nowhere, changed: false, path: comment.context.path };
|
|
93
91
|
// A comment on a whole hunk has no line; inventing one moves its card off the hunk's end.
|
|
94
|
-
const whole = anchor.changes === null && anchor.offsets === undefined;
|
|
95
|
-
|
|
92
|
+
const whole = anchor.kind === "hunk" && anchor.changes === null && anchor.offsets === undefined;
|
|
93
|
+
return { whole, row: (hunk) => firstRow(comment, hunk) };
|
|
94
|
+
}
|
|
95
|
+
function pinnedConcern(concern) {
|
|
96
|
+
const { lines } = concern;
|
|
97
|
+
if (lines === undefined)
|
|
98
|
+
return { whole: true, row: () => 0 };
|
|
99
|
+
return {
|
|
100
|
+
whole: false,
|
|
101
|
+
row: (hunk) => {
|
|
102
|
+
const row = hunk.body.findIndex((r) => r.op !== " " && lines.includes(r.change));
|
|
103
|
+
return row === -1 ? 0 : row;
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Where a comment points on the version it was made on. */
|
|
108
|
+
function placedOwn(comment) {
|
|
109
|
+
const { anchor } = comment;
|
|
110
|
+
return {
|
|
111
|
+
unit: anchor.kind === "hunk" ? anchor.unit : null,
|
|
112
|
+
offsets: anchor.kind === "hunk" ? (anchor.offsets ?? null) : null,
|
|
113
|
+
changed: false,
|
|
114
|
+
path: comment.context.path,
|
|
115
|
+
newLine: null,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Pure. Moves a placement on the old diff to the new one: a unit the new diff still has keeps its
|
|
120
|
+
* place, else the hunk of the same path nearest its old tip line takes it, marked changed. A
|
|
121
|
+
* placement on no unit stays as it is.
|
|
122
|
+
*/
|
|
123
|
+
function move(at, pinned, oldUnits, newUnits) {
|
|
124
|
+
if (at.unit === null)
|
|
125
|
+
return at;
|
|
126
|
+
const kept = newUnits.get(at.unit);
|
|
127
|
+
const old = oldUnits.get(at.unit);
|
|
96
128
|
if (kept !== undefined) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
};
|
|
129
|
+
let newLine = null;
|
|
130
|
+
if (kept.kind === "hunk" && !pinned.whole) {
|
|
131
|
+
if (!at.changed)
|
|
132
|
+
newLine = tipLines(kept, newUnits)[pinned.row(kept)] ?? null;
|
|
133
|
+
else if (at.newLine !== null && old?.kind === "hunk") {
|
|
134
|
+
newLine = at.newLine - tipStartOf(old, oldUnits) + tipStartOf(kept, newUnits);
|
|
135
|
+
}
|
|
136
|
+
else
|
|
137
|
+
newLine = at.newLine;
|
|
138
|
+
}
|
|
139
|
+
return { ...at, path: kept.path, newLine };
|
|
108
140
|
}
|
|
109
|
-
const
|
|
110
|
-
const path = old?.path ?? comment.context.path;
|
|
141
|
+
const path = old?.path ?? at.path;
|
|
111
142
|
const onPath = [...newUnits.values()].filter((u) => u.path === path);
|
|
112
143
|
const first = onPath[0];
|
|
144
|
+
const moved = { offsets: null, changed: true, path };
|
|
113
145
|
if (first === undefined)
|
|
114
|
-
return { ...
|
|
115
|
-
const moved = { comment, offsets: null, changed: true, path };
|
|
146
|
+
return { ...moved, unit: null, newLine: null };
|
|
116
147
|
const hunks = onPath.filter((u) => u.kind === "hunk");
|
|
117
148
|
if (old?.kind !== "hunk" || hunks.length === 0)
|
|
118
149
|
return { ...moved, unit: first.id, newLine: null };
|
|
119
|
-
const row =
|
|
150
|
+
const row = at.changed ? rowAt(old, oldUnits, at.newLine) : pinned.row(old);
|
|
120
151
|
const oldLine = tipLines(old, oldUnits)[row] ?? tipStartOf(old, oldUnits);
|
|
121
152
|
const nearest = hunks.reduce((a, b) => distance(b, newUnits, oldLine) < distance(a, newUnits, oldLine) ? b : a);
|
|
122
153
|
return {
|
|
123
154
|
...moved,
|
|
124
155
|
unit: nearest.id,
|
|
125
|
-
newLine: whole ? null : landing(nearest, newUnits, oldLine, old.body[row]?.text),
|
|
156
|
+
newLine: pinned.whole ? null : landing(nearest, newUnits, oldLine, old.body[row]?.text),
|
|
126
157
|
};
|
|
127
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Pure. Places each comment of the previous version on the new diff: on its own unit when that
|
|
161
|
+
* hunk is unchanged, else on the hunk of the same path nearest its old tip line.
|
|
162
|
+
*/
|
|
163
|
+
export function carryComments(comments, oldUnits, newUnits) {
|
|
164
|
+
return comments.map((comment) => ({
|
|
165
|
+
comment,
|
|
166
|
+
...move(placedOwn(comment), pinnedComment(comment), oldUnits, newUnits),
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
/** since stays once set; a first change records the version the code still matched. */
|
|
170
|
+
function sinceOf(before, after, since, at) {
|
|
171
|
+
return since ?? (after.changed && !before.changed ? at : null);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Pure. What an open review carries from version `at` into the next: what it carried already,
|
|
175
|
+
* moved again, then the comments said on `at`. Nothing is dropped, so a comment of v1 is still
|
|
176
|
+
* there after the second swap.
|
|
177
|
+
*/
|
|
178
|
+
export function carrySession(carried, said, at, oldUnits, newUnits) {
|
|
179
|
+
const again = carried.map((c) => {
|
|
180
|
+
const moved = move(c, pinnedComment(c.comment), oldUnits, newUnits);
|
|
181
|
+
return { ...c, ...moved, since: sinceOf(c, moved, c.since, at) };
|
|
182
|
+
});
|
|
183
|
+
const fresh = said.map((comment) => {
|
|
184
|
+
const own = placedOwn(comment);
|
|
185
|
+
const moved = move(own, pinnedComment(comment), oldUnits, newUnits);
|
|
186
|
+
return { comment, version: at, ...moved, since: sinceOf(own, moved, null, at) };
|
|
187
|
+
});
|
|
188
|
+
return [...again, ...fresh];
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Pure. Kept concerns of version `at` that the next plan no longer has, plus those carried before,
|
|
192
|
+
* placed on the new diff. A concern the next plan has again keeps its decision there instead, so
|
|
193
|
+
* it is left out here.
|
|
194
|
+
*/
|
|
195
|
+
export function carryConcerns(carried, kept, inNext, at, oldUnits, newUnits) {
|
|
196
|
+
const fresh = kept.map((k) => {
|
|
197
|
+
const unit = k.concern.unit ?? null;
|
|
198
|
+
const path = unit === null ? null : (oldUnits.get(unit)?.path ?? null);
|
|
199
|
+
return { ...k, unit, offsets: null, changed: false, path, newLine: null, since: null };
|
|
200
|
+
});
|
|
201
|
+
return [...carried, ...fresh]
|
|
202
|
+
.filter((c) => !inNext.has(c.concern.id))
|
|
203
|
+
.map((c) => {
|
|
204
|
+
const moved = move(c, pinnedConcern(c.concern), oldUnits, newUnits);
|
|
205
|
+
return { ...c, ...moved, since: sinceOf(c, moved, c.since, at) };
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Pure. Where a carried hunk comment points on the diff it was carried to, as an anchor of that
|
|
210
|
+
* diff: its own rows when the hunk is unchanged, the row at its new line when it changed, the
|
|
211
|
+
* whole hunk when it has no line. Null when the code is gone or the comment is not on a hunk.
|
|
212
|
+
*/
|
|
213
|
+
export function anchorOn(carried, units, commit) {
|
|
214
|
+
const { anchor } = carried.comment;
|
|
215
|
+
if (anchor.kind !== "hunk" || carried.unit === null)
|
|
216
|
+
return null;
|
|
217
|
+
if (!carried.changed)
|
|
218
|
+
return { ...anchor, commit };
|
|
219
|
+
const hunk = units.get(carried.unit);
|
|
220
|
+
if (hunk?.kind !== "hunk" || carried.newLine === null) {
|
|
221
|
+
return { kind: "hunk", unit: carried.unit, commit, changes: null };
|
|
222
|
+
}
|
|
223
|
+
const row = rowAt(hunk, units, carried.newLine);
|
|
224
|
+
const at = hunk.body[row];
|
|
225
|
+
const changes = at === undefined || at.op === " " ? [] : [at.change];
|
|
226
|
+
return { kind: "hunk", unit: carried.unit, commit, changes, offsets: [row] };
|
|
227
|
+
}
|
|
228
|
+
/** Pure. A carried concern as a concern of the diff it was carried to; see anchorOn. */
|
|
229
|
+
export function concernOn(carried, units) {
|
|
230
|
+
const { concern } = carried;
|
|
231
|
+
if (carried.unit === null)
|
|
232
|
+
return concern;
|
|
233
|
+
if (!carried.changed)
|
|
234
|
+
return { ...concern, unit: carried.unit };
|
|
235
|
+
const { lines: _old, ...rest } = concern;
|
|
236
|
+
const hunk = units.get(carried.unit);
|
|
237
|
+
if (hunk?.kind !== "hunk" || carried.newLine === null)
|
|
238
|
+
return { ...rest, unit: carried.unit };
|
|
239
|
+
const at = hunk.body[rowAt(hunk, units, carried.newLine)];
|
|
240
|
+
return at === undefined || at.op === " "
|
|
241
|
+
? { ...rest, unit: carried.unit }
|
|
242
|
+
: { ...rest, unit: carried.unit, lines: [at.change] };
|
|
243
|
+
}
|
package/dist/src/host.js
CHANGED
|
@@ -6,8 +6,8 @@ import { join } from "node:path";
|
|
|
6
6
|
import { parseCarried, parseComment, parseEvent, parseEventBatch, } from "./comments.js";
|
|
7
7
|
import { carryComments } from "./evolve.js";
|
|
8
8
|
import { isPlanVersion, serializePlanFile, validatePlan } from "./plan.js";
|
|
9
|
-
import { HEALTH_APP, isSessionId,
|
|
10
|
-
import { BranchMovedError, diffOfPlan, isAlive, openSessionAt, SessionFileError, SessionLockedError, } from "./session.js";
|
|
9
|
+
import { HEALTH_APP, isSessionId, loadDraft, PortInUseError, RegistrationError, startServer, } from "./server.js";
|
|
10
|
+
import { BranchMovedError, diffOfPlan, isAlive, openSessionAt, SessionFileError, SessionLockedError, sessionComments, } from "./session.js";
|
|
11
11
|
const RECORD = "server.json";
|
|
12
12
|
const DEFAULT_IDLE_MS = 30_000;
|
|
13
13
|
const START_TIMEOUT_MS = 10_000;
|
|
@@ -127,7 +127,15 @@ async function loadHosted(registration, held) {
|
|
|
127
127
|
throw new RegistrationError(`plan v${version} no longer fits ${branch}; check it again`);
|
|
128
128
|
}
|
|
129
129
|
const reviewsDir = session.reviewsDir();
|
|
130
|
-
const
|
|
130
|
+
const draft = await loadDraft(reviewsDir, version);
|
|
131
|
+
const { comments } = draft;
|
|
132
|
+
// A restart in the middle of a review that swapped: the draft names what was said before.
|
|
133
|
+
const earlier = draft.carried === null
|
|
134
|
+
? null
|
|
135
|
+
: {
|
|
136
|
+
comments: sessionComments(draft.carried, reviewed.filter((r) => r.version < version).map((r) => r.review)),
|
|
137
|
+
concerns: draft.carriedConcerns,
|
|
138
|
+
};
|
|
131
139
|
const prior = session.priorReview(mode, version);
|
|
132
140
|
const previous = prior === null || prior.comments.length === 0
|
|
133
141
|
? null
|
|
@@ -151,6 +159,7 @@ async function loadHosted(registration, held) {
|
|
|
151
159
|
release,
|
|
152
160
|
previous,
|
|
153
161
|
carried,
|
|
162
|
+
earlier,
|
|
154
163
|
state: {
|
|
155
164
|
mode,
|
|
156
165
|
anchor: session.anchor,
|
package/dist/src/plan.js
CHANGED
|
@@ -90,6 +90,20 @@ function parseConcern(v, onUnit) {
|
|
|
90
90
|
return "lines: expected a list of change indices";
|
|
91
91
|
return { severity, kind, text, lines: [...new Set(indices)].sort((a, b) => a - b) };
|
|
92
92
|
}
|
|
93
|
+
/** A concern as validation placed it, stored beside a review: with its id and its unit. */
|
|
94
|
+
export function parsePlacedConcern(v) {
|
|
95
|
+
if (!isFields(v))
|
|
96
|
+
return "expected an object";
|
|
97
|
+
if (!isConcernId(v.id))
|
|
98
|
+
return "id: expected a concern id";
|
|
99
|
+
if (v.unit !== undefined && !(typeof v.unit === "string" && isUnitId(v.unit))) {
|
|
100
|
+
return "unit: expected a unit id";
|
|
101
|
+
}
|
|
102
|
+
const concern = parseConcern(v, v.unit !== undefined);
|
|
103
|
+
if (typeof concern === "string")
|
|
104
|
+
return concern;
|
|
105
|
+
return { ...concern, id: v.id, ...(v.unit === undefined ? {} : { unit: v.unit }) };
|
|
106
|
+
}
|
|
93
107
|
/** Absent, null and [] all mean no concerns, so a planner may always write the field. */
|
|
94
108
|
function parseConcerns(v, onUnit) {
|
|
95
109
|
if (v === undefined || v === null)
|
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);
|
|
@@ -383,10 +384,23 @@ function quoteHunk(units, commits, unit, k, picked) {
|
|
|
383
384
|
return { line, text: lines.map((l) => l.text) };
|
|
384
385
|
}
|
|
385
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
|
+
}
|
|
386
397
|
function lineLabel(line, fallback) {
|
|
387
398
|
return line === null ? fallback : `line ${line}`;
|
|
388
399
|
}
|
|
389
400
|
function whereComment(ctx, c) {
|
|
401
|
+
return marked(ctx, c.id, whereOnTip(ctx, c), c.context.lines ?? []);
|
|
402
|
+
}
|
|
403
|
+
function whereOnTip(ctx, c) {
|
|
390
404
|
const { anchor } = c;
|
|
391
405
|
switch (anchor.kind) {
|
|
392
406
|
case "plan":
|
|
@@ -429,6 +443,9 @@ function whereComment(ctx, c) {
|
|
|
429
443
|
}
|
|
430
444
|
}
|
|
431
445
|
function whereConcern(ctx, kept) {
|
|
446
|
+
return marked(ctx, kept.concern.id, concernOnTip(ctx, kept), []);
|
|
447
|
+
}
|
|
448
|
+
function concernOnTip(ctx, kept) {
|
|
432
449
|
const { concern, commit } = kept;
|
|
433
450
|
if (concern.unit === undefined) {
|
|
434
451
|
return { path: null, line: null, label: `commit ${commit + 1}`, quote: [] };
|
|
@@ -727,8 +744,12 @@ function threadOf(c) {
|
|
|
727
744
|
function withSeverity(severity, text) {
|
|
728
745
|
return severity === null ? text : `**${severity}**\n\n${text}`;
|
|
729
746
|
}
|
|
730
|
-
function
|
|
731
|
-
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;
|
|
732
753
|
const fromReviewer = review.comments.filter((c) => c.author !== "agent");
|
|
733
754
|
const walk = review.mode === "walk";
|
|
734
755
|
const rest = fromReviewer.filter((c) => c.kind !== "question");
|
|
@@ -748,6 +769,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
748
769
|
return {
|
|
749
770
|
where: it.where,
|
|
750
771
|
item: {
|
|
772
|
+
id: kept.concern.id,
|
|
751
773
|
...concernSpan(ctx, kept.concern),
|
|
752
774
|
path: it.where.path,
|
|
753
775
|
oldPath: it.where.path,
|
|
@@ -757,6 +779,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
757
779
|
thread,
|
|
758
780
|
resolved: false,
|
|
759
781
|
excluded: noConcerns.has(kept.concern.id),
|
|
782
|
+
...changedOf(ctx, kept.concern.id),
|
|
760
783
|
},
|
|
761
784
|
};
|
|
762
785
|
}
|
|
@@ -765,6 +788,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
765
788
|
return {
|
|
766
789
|
where: it.where,
|
|
767
790
|
item: {
|
|
791
|
+
id: first.id,
|
|
768
792
|
...commentSpan(ctx, first),
|
|
769
793
|
path: it.where.path,
|
|
770
794
|
oldPath: it.where.path,
|
|
@@ -774,6 +798,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
774
798
|
thread: threadOf(first),
|
|
775
799
|
resolved: resolved.has(first.id),
|
|
776
800
|
excluded: noComments.has(first.id),
|
|
801
|
+
...changedOf(ctx, first.id),
|
|
777
802
|
},
|
|
778
803
|
};
|
|
779
804
|
});
|
|
@@ -786,6 +811,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
786
811
|
out.push({
|
|
787
812
|
where,
|
|
788
813
|
item: {
|
|
814
|
+
id: q.id,
|
|
789
815
|
...commentSpan(ctx, q),
|
|
790
816
|
path: where.path,
|
|
791
817
|
oldPath: where.path,
|
|
@@ -795,6 +821,7 @@ function collectItems(review, commits, units, responses) {
|
|
|
795
821
|
thread: threadOf(q),
|
|
796
822
|
resolved: resolved.has(q.id),
|
|
797
823
|
excluded: noComments.has(q.id),
|
|
824
|
+
...changedOf(ctx, q.id),
|
|
798
825
|
},
|
|
799
826
|
});
|
|
800
827
|
}
|
|
@@ -805,7 +832,8 @@ function collectItems(review, commits, units, responses) {
|
|
|
805
832
|
* Excluded ones are present and marked; whoever posts them skips those.
|
|
806
833
|
*/
|
|
807
834
|
export function reportItems(review, commits, units, opts) {
|
|
808
|
-
|
|
835
|
+
const session = folded(review, commits, units, opts.earlier);
|
|
836
|
+
return collectItems(session.review, session.ctx, opts.responses).map((c) => c.item);
|
|
809
837
|
}
|
|
810
838
|
/** A short list of what the reviewer left out, so nothing vanishes without a trace. */
|
|
811
839
|
function renderExcluded(collected) {
|
|
@@ -821,6 +849,64 @@ function renderExcluded(collected) {
|
|
|
821
849
|
out.push("");
|
|
822
850
|
return out;
|
|
823
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
|
+
}
|
|
824
910
|
/**
|
|
825
911
|
* The consolidated report of a review, in the voices of both the reviewer and the agent: the
|
|
826
912
|
* reviewer's comments and the agent's concerns the reviewer kept, grouped by file and ordered by
|
|
@@ -829,22 +915,24 @@ function renderExcluded(collected) {
|
|
|
829
915
|
* Concerns the reviewer has not decided are left out, as are those dismissed.
|
|
830
916
|
*/
|
|
831
917
|
export function renderReport(full, commits, units, opts) {
|
|
832
|
-
const
|
|
918
|
+
const session = folded(full, commits, units, opts.earlier);
|
|
919
|
+
const { ctx } = session;
|
|
920
|
+
const excluded = session.review.excluded;
|
|
833
921
|
if (excluded === undefined)
|
|
834
|
-
return renderIncluded(
|
|
922
|
+
return renderIncluded(session.review, ctx, opts);
|
|
835
923
|
const noComments = new Set(excluded.comments);
|
|
836
924
|
const noConcerns = new Set(excluded.concerns);
|
|
837
925
|
const review = {
|
|
838
|
-
...
|
|
839
|
-
comments:
|
|
840
|
-
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)),
|
|
841
929
|
};
|
|
842
|
-
const tail = renderExcluded(collectItems(
|
|
843
|
-
const main = renderIncluded(review,
|
|
930
|
+
const tail = renderExcluded(collectItems(session.review, ctx, opts.responses));
|
|
931
|
+
const main = renderIncluded(review, ctx, opts);
|
|
844
932
|
return tail.length === 0 ? main : `${main}${main.endsWith("\n") ? "" : "\n"}${tail.join("\n")}`;
|
|
845
933
|
}
|
|
846
|
-
function renderIncluded(review,
|
|
847
|
-
const
|
|
934
|
+
function renderIncluded(review, ctx, opts) {
|
|
935
|
+
const { commits, units } = ctx;
|
|
848
936
|
const walk = review.mode === "walk";
|
|
849
937
|
const fromReviewer = review.comments.filter((c) => c.author !== "agent");
|
|
850
938
|
// A thread the agent started matters to it only once the reviewer has answered.
|