@shiyuphan/follow-me 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/src/cli.js +181 -42
- package/dist/src/comments.js +215 -9
- package/dist/src/coverage.js +163 -25
- package/dist/src/host.js +23 -0
- package/dist/src/plan.js +128 -6
- package/dist/src/prompt.js +55 -2
- package/dist/src/refs.js +239 -0
- package/dist/src/server.js +65 -3
- package/dist/src/session.js +1 -0
- package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-cHEJvcBn.js → abnfDiagram-WEYOIA3C-DztIEwNE.js} +1 -1
- package/dist/ui/dist/assets/{arc-xolFyc0v.js → arc-DUYh-EqZ.js} +1 -1
- package/dist/ui/dist/assets/{architecture-WOLXFQ4H-B3hvvNV2.js → architecture-WOLXFQ4H-BQYTB92_.js} +1 -1
- package/dist/ui/dist/assets/{architectureDiagram-ZHVYKT53-BNTMbPJW.js → architectureDiagram-ZHVYKT53-Dl7095A5.js} +1 -1
- package/dist/ui/dist/assets/{blockDiagram-RJAE7QA3-NYY6qeuY.js → blockDiagram-RJAE7QA3-QDZ5kE1J.js} +1 -1
- package/dist/ui/dist/assets/{c4Diagram-XDPPPE54-B_bNpXHQ.js → c4Diagram-XDPPPE54-CKJZ1xOw.js} +1 -1
- package/dist/ui/dist/assets/channel-DF08Hd8f.js +1 -0
- package/dist/ui/dist/assets/{chunk-2E7WHVMD-BTet9STn.js → chunk-2E7WHVMD-BXBjaqd1.js} +1 -1
- package/dist/ui/dist/assets/{chunk-4EMNKR2T-qoQt4Fpx.js → chunk-4EMNKR2T-DPzfVuke.js} +1 -1
- package/dist/ui/dist/assets/{chunk-55XCU76K-C3I6EB-H.js → chunk-55XCU76K-B33Sj3w8.js} +1 -1
- package/dist/ui/dist/assets/{chunk-7S6XSTFO-CL-9s_8p.js → chunk-7S6XSTFO-B_u7dS03.js} +1 -1
- package/dist/ui/dist/assets/{chunk-AQDTSKZB-DHoGnGyP.js → chunk-AQDTSKZB-B4EzH5gx.js} +1 -1
- package/dist/ui/dist/assets/{chunk-DSNLSHJZ-Bk8WO5JF.js → chunk-DSNLSHJZ-CfWeHABr.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HAGFOTED-CwAC85lg.js → chunk-HAGFOTED-B8G8PjLZ.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HNE4Q36P-CIllywWr.js → chunk-HNE4Q36P-BA-PqoTF.js} +1 -1
- package/dist/ui/dist/assets/{chunk-NQOKRVIF-DDlpyYiH.js → chunk-NQOKRVIF-JvhuFcnR.js} +1 -1
- package/dist/ui/dist/assets/{chunk-UHW7PFBP-Cxdndcon.js → chunk-UHW7PFBP-BrfL5CGY.js} +1 -1
- package/dist/ui/dist/assets/{chunk-VQ5W62QI-CL9d9_iJ.js → chunk-VQ5W62QI-dBJqgdDy.js} +1 -1
- package/dist/ui/dist/assets/{chunk-WQQL4CDK-C4kzN8QU.js → chunk-WQQL4CDK-dfqd3k_P.js} +1 -1
- package/dist/ui/dist/assets/{chunk-XXDRQBXY-Cv_2FSiY.js → chunk-XXDRQBXY-kViWdB9M.js} +1 -1
- package/dist/ui/dist/assets/{chunk-YZO67HFG-oHiuYaCJ.js → chunk-YZO67HFG-ukfs4wT3.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZAUX2LTX-Cy_8d0XT.js → chunk-ZAUX2LTX-BxZHki81.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZCHMQSOR-piIpIcQi.js → chunk-ZCHMQSOR-PjluhR_E.js} +1 -1
- package/dist/ui/dist/assets/{classDiagram-v2-4MYWR3UA-DNKRIujO.js → classDiagram-v2-4MYWR3UA-Ct89LJec.js} +1 -1
- package/dist/ui/dist/assets/{cose-bilkent-JH36ORCC-BWKTGCHY.js → cose-bilkent-JH36ORCC-BnQceGmB.js} +1 -1
- package/dist/ui/dist/assets/{cynefin-EF2NZ3EQ-yx7KKkAk.js → cynefin-EF2NZ3EQ-D6WkCVE8.js} +1 -1
- package/dist/ui/dist/assets/{cynefinDiagram-WF24UDRP-BTnZ7QVL.js → cynefinDiagram-WF24UDRP-dn6DUPAb.js} +1 -1
- package/dist/ui/dist/assets/{dagre-NK4GIZVX-CPh5Zn5I.js → dagre-NK4GIZVX-BtZSlLk7.js} +1 -1
- package/dist/ui/dist/assets/{diagram-CBK3MWTC-pI6NFdrD.js → diagram-CBK3MWTC-Ds4_tv6X.js} +1 -1
- package/dist/ui/dist/assets/{diagram-I5XIIV2D-DUIfARoS.js → diagram-I5XIIV2D-DcoVkg45.js} +1 -1
- package/dist/ui/dist/assets/{diagram-ODIL2UCW-lafSPvAw.js → diagram-ODIL2UCW-T47FAA3k.js} +1 -1
- package/dist/ui/dist/assets/{diagram-PFBEJUXE-u1lZas3a.js → diagram-PFBEJUXE-CRZcaVKP.js} +1 -1
- package/dist/ui/dist/assets/{diagram-RJA3I4VV-mc0GLMHo.js → diagram-RJA3I4VV-D6KXeUPg.js} +1 -1
- package/dist/ui/dist/assets/{diagram-TYUNFSNR-1NRbBrZW.js → diagram-TYUNFSNR-BHEIVyNN.js} +1 -1
- package/dist/ui/dist/assets/{dist-CHZ9mrl_.js → dist-DLlKIWBp.js} +1 -1
- package/dist/ui/dist/assets/{ebnfDiagram-V4P3TTPT-DdStjTDc.js → ebnfDiagram-V4P3TTPT-nUDvYRBV.js} +1 -1
- package/dist/ui/dist/assets/{elk-QONTBCTQ-BzRoFO45.js → elk-QONTBCTQ-D21BcRzc.js} +1 -1
- package/dist/ui/dist/assets/{erDiagram-KYQIGQAA-DlMkgQxj.js → erDiagram-KYQIGQAA-CTpHuftU.js} +1 -1
- package/dist/ui/dist/assets/{eventmodeling-K75KTNOO-BRYIOrod.js → eventmodeling-K75KTNOO-B6JaKuDY.js} +1 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-BTeOUR-p.js +1 -0
- package/dist/ui/dist/assets/{ganttDiagram-R7GPNTJY-D8fQZ7Ah.js → ganttDiagram-R7GPNTJY-D2-pMcnC.js} +1 -1
- package/dist/ui/dist/assets/{gitGraph-VSP46ZUC-C7ZydyWX.js → gitGraph-VSP46ZUC-B2yxwhEo.js} +1 -1
- package/dist/ui/dist/assets/{gitGraphDiagram-PDYL7XHY-DCItam0v.js → gitGraphDiagram-PDYL7XHY-DnrhBMH1.js} +1 -1
- package/dist/ui/dist/assets/index-D74Skifa.css +1 -0
- package/dist/ui/dist/assets/index-Dnte9tPd.js +136 -0
- package/dist/ui/dist/assets/{info-OHQRW6UA-C3KneBKT.js → info-OHQRW6UA-D8oh6gvs.js} +1 -1
- package/dist/ui/dist/assets/{infoDiagram-MVDRKAX5-6wK09D32.js → infoDiagram-MVDRKAX5-BLSZjjgB.js} +1 -1
- package/dist/ui/dist/assets/{ishikawaDiagram-HPEHV2GE-DTP5C8VX.js → ishikawaDiagram-HPEHV2GE-BXiznLSn.js} +1 -1
- package/dist/ui/dist/assets/{journeyDiagram-BOSHW7O7-B--UIMtq.js → journeyDiagram-BOSHW7O7-BSYmVcO_.js} +1 -1
- package/dist/ui/dist/assets/{kanban-definition-FNSA4HC5-Ku4N9QGt.js → kanban-definition-FNSA4HC5-COthkkzK.js} +1 -1
- package/dist/ui/dist/assets/{line-C4-H5h1-.js → line-6ND8OaOL.js} +1 -1
- package/dist/ui/dist/assets/{linear-DhAID3hm.js → linear-DbWEPFJX.js} +1 -1
- package/dist/ui/dist/assets/{mermaid-parser.core-CMPNc-u6.js → mermaid-parser.core-Btsk294z.js} +3 -3
- package/dist/ui/dist/assets/{mermaid.core-Nd_xOg2y.js → mermaid.core-B4llDSFO.js} +4 -4
- package/dist/ui/dist/assets/{mindmap-definition-6OY57UG3-BlVsym5h.js → mindmap-definition-6OY57UG3-hv2PfX-x.js} +1 -1
- package/dist/ui/dist/assets/{packet-JDAUHWVQ-rsk7SM7S.js → packet-JDAUHWVQ-Dr4vBgjt.js} +1 -1
- package/dist/ui/dist/assets/{pegDiagram-ECIUJNBX-C0L20M8z.js → pegDiagram-ECIUJNBX-DvlFov3N.js} +1 -1
- package/dist/ui/dist/assets/{pie-XZMESJXO-B23-iZSN.js → pie-XZMESJXO-C9QsqC4n.js} +1 -1
- package/dist/ui/dist/assets/{pieDiagram-GNMPY74T-BqFhe4Oh.js → pieDiagram-GNMPY74T-CrrHOj67.js} +1 -1
- package/dist/ui/dist/assets/{prism-core-DYsiqJeR.js → prism-core-Cc8H7LPG.js} +1 -1
- package/dist/ui/dist/assets/{quadrantDiagram-D57K4MC3-BKP75B-m.js → quadrantDiagram-D57K4MC3-DQ7J6-ti.js} +1 -1
- package/dist/ui/dist/assets/{radar-ABXABTNO-BrEk2mQ7.js → radar-ABXABTNO-BBBPaujh.js} +1 -1
- package/dist/ui/dist/assets/{railroad-I3PHUGI6-wWO4Eapc.js → railroad-I3PHUGI6-C-P6Igaz.js} +1 -1
- package/dist/ui/dist/assets/{railroad-abnf-LNEFI6M7-DfY-IPvE.js → railroad-abnf-LNEFI6M7-CAkorRLs.js} +1 -1
- package/dist/ui/dist/assets/{railroad-ebnf-DWYD2UWJ-19wedWGk.js → railroad-ebnf-DWYD2UWJ-D8Hz6gCf.js} +1 -1
- package/dist/ui/dist/assets/{railroad-peg-JR7BVNR7-9vIslnoJ.js → railroad-peg-JR7BVNR7-D-XNMkOs.js} +1 -1
- package/dist/ui/dist/assets/{railroadDiagram-Y72NRBL7-DVnmXs8w.js → railroadDiagram-Y72NRBL7-DE9LwxJQ.js} +1 -1
- package/dist/ui/dist/assets/{requirementDiagram-EQYSW5RA-BEoMdlzR.js → requirementDiagram-EQYSW5RA-FrKFY6NJ.js} +1 -1
- package/dist/ui/dist/assets/{sankeyDiagram-SY24JHO5-D7Tn2Gbs.js → sankeyDiagram-SY24JHO5-BDxI6RDX.js} +1 -1
- package/dist/ui/dist/assets/{sequenceDiagram-BLEHNHQU-CyjUDBaQ.js → sequenceDiagram-BLEHNHQU-pV1XcKX7.js} +1 -1
- package/dist/ui/dist/assets/{src-BiC9XNcI.js → src-snNJTXXq.js} +1 -1
- package/dist/ui/dist/assets/{stateDiagram-v2-A6IESKWZ-CMp1VCO9.js → stateDiagram-v2-A6IESKWZ-BSc_5aAf.js} +1 -1
- package/dist/ui/dist/assets/{swimlanes-B3ENRKFQ-KEciLd6r.js → swimlanes-B3ENRKFQ-DC6qUXLh.js} +1 -1
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-F1HykV3o.js +8 -0
- package/dist/ui/dist/assets/{timeline-definition-JXYKGLGB-C8YQtv7M.js → timeline-definition-JXYKGLGB-C8L0Dxtu.js} +1 -1
- package/dist/ui/dist/assets/{treeView-D4JQ5SDB-DgAzPaOO.js → treeView-D4JQ5SDB-DDyZIG5T.js} +1 -1
- package/dist/ui/dist/assets/{treemap-SAJKECNS-eCRqZMYc.js → treemap-SAJKECNS-CUaLgge0.js} +1 -1
- package/dist/ui/dist/assets/{usecaseDiagram-WPOLBNNX-Dirryw9G.js → usecaseDiagram-WPOLBNNX-BHRBd5pV.js} +1 -1
- package/dist/ui/dist/assets/{vennDiagram-Q3Y3IDCW-BjxQbAUx.js → vennDiagram-Q3Y3IDCW-Du4iwwkh.js} +1 -1
- package/dist/ui/dist/assets/{wardley-7MLQ67FV-CqHX5RUz.js → wardley-7MLQ67FV-DUgENI0s.js} +1 -1
- package/dist/ui/dist/assets/{wardleyDiagram-IVAIDKWA-Ds1HoEUY.js → wardleyDiagram-IVAIDKWA-B86zOdke.js} +1 -1
- package/dist/ui/dist/assets/{xychartDiagram-4URNHDU4-kodzDb12.js → xychartDiagram-4URNHDU4-twq9irqO.js} +1 -1
- package/dist/ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/follow-me/SKILL.md +5 -3
- package/dist/ui/dist/assets/channel-DPDY0XJP.js +0 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-C1IBh_GZ.js +0 -1
- package/dist/ui/dist/assets/index-8BehwQ7E.js +0 -134
- package/dist/ui/dist/assets/index-mC2OW2Av.css +0 -1
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DzestpDE.js +0 -8
package/dist/src/comments.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { parseLoc } from "./coverage.js";
|
|
1
2
|
import { isRepoPath, isSha, isUnitId } from "./diff.js";
|
|
2
|
-
import { diagramProblem, isArray, isCommentId, isConcernId, isFields, isPlanVersion, isSeverity, parsePlacedConcern, } from "./plan.js";
|
|
3
|
+
import { diagramProblem, isArray, isCommentId, isConcernId, isFields, isOfferId, isPlanVersion, isSeverity, parsePlacedConcern, } from "./plan.js";
|
|
3
4
|
/**
|
|
4
5
|
* The wire formats of review state, parsed once at the boundary: comments, replies, concern
|
|
5
6
|
* decisions, the review file, its autosaved draft and the events a hosted session logs. Every
|
|
@@ -48,10 +49,159 @@ function parseAnchor(v) {
|
|
|
48
49
|
}
|
|
49
50
|
return { kind: "hunk", unit: v.unit, commit, changes, offsets };
|
|
50
51
|
}
|
|
52
|
+
function parseLocs(v, at, needed) {
|
|
53
|
+
const what = `${at}: expected a list of { path, start, end }${needed ? " with at least one place" : ""}`;
|
|
54
|
+
if (!isArray(v) || (needed && v.length === 0))
|
|
55
|
+
return what;
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const [i, raw] of v.entries()) {
|
|
58
|
+
const loc = parseLoc(raw);
|
|
59
|
+
if (loc === null)
|
|
60
|
+
return `${at}[${i}]: expected { path, start, end } with 1 <= start <= end`;
|
|
61
|
+
out.push(loc);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
const TRANSITION_ENDS = ["from", "to"];
|
|
66
|
+
function escapeRegExp(text) {
|
|
67
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Boundary parse of a reply's state map. Every state has at least one place, every transition
|
|
71
|
+
* names states of the map, and with a stateDiagram every state name appears in it. The reason
|
|
72
|
+
* names the bad entry, as map.states.<name>[i] or map.transitions[i].<field>.
|
|
73
|
+
*/
|
|
74
|
+
export function parseStateMap(v, diagram) {
|
|
75
|
+
if (!isFields(v))
|
|
76
|
+
return "map: expected { states, transitions }";
|
|
77
|
+
if (!isFields(v.states) || Object.keys(v.states).length === 0) {
|
|
78
|
+
return "map.states: expected an object of state name to places";
|
|
79
|
+
}
|
|
80
|
+
const states = [];
|
|
81
|
+
for (const [name, raw] of Object.entries(v.states)) {
|
|
82
|
+
if (name.trim() === "")
|
|
83
|
+
return "map.states: a state has an empty name";
|
|
84
|
+
const locs = parseLocs(raw, `map.states.${name}`, true);
|
|
85
|
+
if (typeof locs === "string")
|
|
86
|
+
return locs;
|
|
87
|
+
states.push([name, locs]);
|
|
88
|
+
}
|
|
89
|
+
const named = new Set(states.map(([name]) => name));
|
|
90
|
+
const transitions = [];
|
|
91
|
+
const rawTransitions = v.transitions ?? [];
|
|
92
|
+
if (!isArray(rawTransitions))
|
|
93
|
+
return "map.transitions: expected a list";
|
|
94
|
+
for (const [i, raw] of rawTransitions.entries()) {
|
|
95
|
+
const at = `map.transitions[${i}]`;
|
|
96
|
+
if (!isFields(raw))
|
|
97
|
+
return `${at}: expected { from, to, event, at }`;
|
|
98
|
+
for (const end of TRANSITION_ENDS) {
|
|
99
|
+
const state = raw[end];
|
|
100
|
+
if (typeof state !== "string" || !named.has(state)) {
|
|
101
|
+
return `${at}.${end}: ${JSON.stringify(state)} is not a state in map.states`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const { from, to, event } = raw;
|
|
105
|
+
if (typeof from !== "string" || typeof to !== "string")
|
|
106
|
+
return `${at}: expected from and to`;
|
|
107
|
+
if (event !== undefined && typeof event !== "string")
|
|
108
|
+
return `${at}.event: expected a string`;
|
|
109
|
+
const locs = parseLocs(raw.at, `${at}.at`, false);
|
|
110
|
+
if (typeof locs === "string")
|
|
111
|
+
return locs;
|
|
112
|
+
transitions.push({ from, to, ...(event === undefined ? {} : { event }), at: locs });
|
|
113
|
+
}
|
|
114
|
+
const first = (diagram ?? "")
|
|
115
|
+
.split("\n")
|
|
116
|
+
.find((line) => line.trim() !== "")
|
|
117
|
+
?.trim() ?? "";
|
|
118
|
+
if (first.startsWith("stateDiagram")) {
|
|
119
|
+
for (const name of named) {
|
|
120
|
+
if (!new RegExp(`(^|[^\\w])${escapeRegExp(name)}([^\\w]|$)`, "m").test(diagram ?? "")) {
|
|
121
|
+
return `map.states.${name}: not a state of the stateDiagram`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
states: Object.fromEntries(states),
|
|
127
|
+
...(transitions.length === 0 ? {} : { transitions }),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Where a map points past the tip: a path the tip has no file at, or lines beyond its end. lines
|
|
132
|
+
* holds the line count of every tip file the map names that exists; show prints a path.
|
|
133
|
+
*/
|
|
134
|
+
export function mapOffTip(map, lines, show = (path) => path) {
|
|
135
|
+
const check = (loc, at) => {
|
|
136
|
+
const count = lines.get(loc.path);
|
|
137
|
+
if (count === undefined)
|
|
138
|
+
return [`${at}: ${show(loc.path)} is not a file at the tip`];
|
|
139
|
+
if (loc.end > count) {
|
|
140
|
+
return [
|
|
141
|
+
`${at}: lines ${loc.start}-${loc.end} are past the end of ${show(loc.path)}, which has ${count} lines at the tip`,
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
return [];
|
|
145
|
+
};
|
|
146
|
+
return [
|
|
147
|
+
...Object.entries(map.states).flatMap(([name, locs]) => locs.flatMap((loc, i) => check(loc, `map.states.${name}[${i}]`))),
|
|
148
|
+
...(map.transitions ?? []).flatMap((t, i) => t.at.flatMap((loc, j) => check(loc, `map.transitions[${i}].at[${j}]`))),
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
function parseSpan(v) {
|
|
152
|
+
if (v === null)
|
|
153
|
+
return null;
|
|
154
|
+
if (!isFields(v) || !isLine(v.start) || !isLine(v.end) || v.end < v.start) {
|
|
155
|
+
return "expected null or { start, end } with 1 <= start <= end";
|
|
156
|
+
}
|
|
157
|
+
return { start: v.start, end: v.end };
|
|
158
|
+
}
|
|
159
|
+
function parseCodeRef(v) {
|
|
160
|
+
if (!isFields(v))
|
|
161
|
+
return "expected an object";
|
|
162
|
+
const { n, path, start, end, at, quote, tip } = v;
|
|
163
|
+
if (!isLine(n))
|
|
164
|
+
return "n is not a number from 1";
|
|
165
|
+
if (typeof path !== "string" || !isRepoPath(path))
|
|
166
|
+
return "path is not a repo path";
|
|
167
|
+
if (!isLine(start) || !isLine(end) || end < start)
|
|
168
|
+
return "expected 1 <= start <= end";
|
|
169
|
+
if (at !== null && at !== "base" && !isLine(at))
|
|
170
|
+
return 'at is not a commit, "base" or null';
|
|
171
|
+
if (!isArray(quote) || !quote.every(isString))
|
|
172
|
+
return "quote is not a list of strings";
|
|
173
|
+
if (typeof tip !== "string" || !isSha(tip))
|
|
174
|
+
return "tip is not a commit sha";
|
|
175
|
+
const lines = parseSpan(v.lines);
|
|
176
|
+
const old = parseSpan(v.old);
|
|
177
|
+
if (typeof lines === "string")
|
|
178
|
+
return `lines: ${lines}`;
|
|
179
|
+
if (typeof old === "string")
|
|
180
|
+
return `old: ${old}`;
|
|
181
|
+
return { n, path, start, end, at, quote: quote.filter(isString), tip, lines, old };
|
|
182
|
+
}
|
|
183
|
+
/** refs as stored beside an agent's words: absent, or a list numbered 1, 2, 3 in order. */
|
|
184
|
+
function parseCodeRefs(v) {
|
|
185
|
+
if (v === undefined)
|
|
186
|
+
return [];
|
|
187
|
+
if (!isArray(v))
|
|
188
|
+
return "refs is not a list";
|
|
189
|
+
const refs = [];
|
|
190
|
+
for (const [i, raw] of v.entries()) {
|
|
191
|
+
const ref = parseCodeRef(raw);
|
|
192
|
+
if (typeof ref === "string")
|
|
193
|
+
return `refs[${i}]: ${ref}`;
|
|
194
|
+
if (ref.n !== i + 1)
|
|
195
|
+
return `refs[${i}]: n is not ${i + 1}`;
|
|
196
|
+
refs.push(ref);
|
|
197
|
+
}
|
|
198
|
+
return refs;
|
|
199
|
+
}
|
|
51
200
|
/** A stored reply. Replies a client posts go through parseNewReply, which mints id and time. */
|
|
52
201
|
export function parseReply(v) {
|
|
53
|
-
if (!isFields(v))
|
|
202
|
+
if (!isFields(v)) {
|
|
54
203
|
return "not an object";
|
|
204
|
+
}
|
|
55
205
|
if (!isReplyId(v.id))
|
|
56
206
|
return "id is not a reply id";
|
|
57
207
|
if (v.author !== "agent" && v.author !== "reviewer")
|
|
@@ -61,30 +211,62 @@ export function parseReply(v) {
|
|
|
61
211
|
}
|
|
62
212
|
if (v.diagram !== undefined && typeof v.diagram !== "string")
|
|
63
213
|
return "diagram is not a string";
|
|
214
|
+
const map = v.map === undefined ? undefined : parseStateMap(v.map, v.diagram);
|
|
215
|
+
if (typeof map === "string")
|
|
216
|
+
return map;
|
|
217
|
+
const refs = parseCodeRefs(v.refs);
|
|
218
|
+
if (typeof refs === "string")
|
|
219
|
+
return refs;
|
|
64
220
|
return {
|
|
65
221
|
id: v.id,
|
|
66
222
|
author: v.author,
|
|
67
223
|
body: v.body,
|
|
68
224
|
...(v.diagram === undefined ? {} : { diagram: v.diagram }),
|
|
225
|
+
...(map === undefined ? {} : { map }),
|
|
226
|
+
...(refs.length === 0 ? {} : { refs }),
|
|
69
227
|
createdAt: v.createdAt,
|
|
70
228
|
};
|
|
71
229
|
}
|
|
72
|
-
/**
|
|
230
|
+
/**
|
|
231
|
+
* The body of POST .../replies: who speaks, what, and an optional Mermaid diagram. The body may be
|
|
232
|
+
* empty only when the diagram is the answer. Only the agent's words carry refs.
|
|
233
|
+
*/
|
|
73
234
|
export function parseNewReply(v) {
|
|
74
|
-
if (!isFields(v))
|
|
235
|
+
if (!isFields(v)) {
|
|
75
236
|
return "the reply is not an object";
|
|
237
|
+
}
|
|
76
238
|
if (v.author !== "agent" && v.author !== "reviewer")
|
|
77
239
|
return 'author is not "agent" or "reviewer"';
|
|
78
|
-
if (typeof v.body !== "string"
|
|
79
|
-
return "body is not a
|
|
80
|
-
|
|
81
|
-
|
|
240
|
+
if (typeof v.body !== "string")
|
|
241
|
+
return "body is not a string";
|
|
242
|
+
const refs = parseCodeRefs(v.refs);
|
|
243
|
+
if (typeof refs === "string")
|
|
244
|
+
return refs;
|
|
245
|
+
if (refs.length > 0 && v.author !== "agent")
|
|
246
|
+
return "refs are only for the agent's words";
|
|
247
|
+
const withRefs = refs.length === 0 ? {} : { refs };
|
|
248
|
+
if (v.diagram === undefined) {
|
|
249
|
+
if (v.map !== undefined)
|
|
250
|
+
return "map needs the diagram it ties to code";
|
|
251
|
+
if (v.body.trim() === "")
|
|
252
|
+
return "body is empty; a reply needs words or a diagram";
|
|
253
|
+
return { author: v.author, body: v.body, ...withRefs };
|
|
254
|
+
}
|
|
82
255
|
if (typeof v.diagram !== "string")
|
|
83
256
|
return "diagram is not a string";
|
|
84
257
|
const problem = diagramProblem(v.diagram);
|
|
85
258
|
if (problem !== null)
|
|
86
259
|
return `diagram: ${problem}`;
|
|
87
|
-
|
|
260
|
+
const map = v.map === undefined ? undefined : parseStateMap(v.map, v.diagram);
|
|
261
|
+
if (typeof map === "string")
|
|
262
|
+
return map;
|
|
263
|
+
return {
|
|
264
|
+
author: v.author,
|
|
265
|
+
body: v.body,
|
|
266
|
+
diagram: v.diagram,
|
|
267
|
+
...(map === undefined ? {} : { map }),
|
|
268
|
+
...withRefs,
|
|
269
|
+
};
|
|
88
270
|
}
|
|
89
271
|
function isString(v) {
|
|
90
272
|
return typeof v === "string";
|
|
@@ -133,6 +315,19 @@ export function parseComment(v) {
|
|
|
133
315
|
const severity = v.severity;
|
|
134
316
|
if (severity !== undefined && !isSeverity(severity))
|
|
135
317
|
return "severity is unknown";
|
|
318
|
+
const offer = v.offer;
|
|
319
|
+
if (offer !== undefined) {
|
|
320
|
+
if (!isOfferId(offer))
|
|
321
|
+
return "offer is not an offer id";
|
|
322
|
+
if (v.kind !== "question" || v.author !== undefined) {
|
|
323
|
+
return "offer is only for a reviewer's question";
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const refs = parseCodeRefs(v.refs);
|
|
327
|
+
if (typeof refs === "string")
|
|
328
|
+
return refs;
|
|
329
|
+
if (refs.length > 0 && v.author !== "agent")
|
|
330
|
+
return "refs are only for an agent's comment";
|
|
136
331
|
const replies = parseList(v.replies, parseReply, "replies");
|
|
137
332
|
if (typeof replies === "string")
|
|
138
333
|
return replies;
|
|
@@ -152,6 +347,8 @@ export function parseComment(v) {
|
|
|
152
347
|
...(replies.length === 0 ? {} : { replies }),
|
|
153
348
|
...(v.author === "agent" ? { author: "agent" } : {}),
|
|
154
349
|
...(v.diagram === undefined ? {} : { diagram: v.diagram }),
|
|
350
|
+
...(refs.length === 0 ? {} : { refs }),
|
|
351
|
+
...(offer === undefined ? {} : { offer }),
|
|
155
352
|
};
|
|
156
353
|
}
|
|
157
354
|
function withDecision(concern, v) {
|
|
@@ -363,6 +560,9 @@ export function parseReview(v) {
|
|
|
363
560
|
const concernsCarried = parseList(v.carriedConcerns, parseCarriedConcern, "carriedConcerns");
|
|
364
561
|
if (typeof concernsCarried === "string")
|
|
365
562
|
return concernsCarried;
|
|
563
|
+
const dismissed = parseIds(v.dismissedOffers, isOfferId, "dismissedOffers", "offer");
|
|
564
|
+
if (typeof dismissed === "string")
|
|
565
|
+
return dismissed;
|
|
366
566
|
return {
|
|
367
567
|
schema: v.schema,
|
|
368
568
|
mode,
|
|
@@ -376,6 +576,7 @@ export function parseReview(v) {
|
|
|
376
576
|
...(earlier.length === 0 ? {} : { earlierReplies: earlier }),
|
|
377
577
|
...(carried === null ? {} : { carried }),
|
|
378
578
|
...(concernsCarried.length === 0 ? {} : { carriedConcerns: concernsCarried }),
|
|
579
|
+
...(dismissed.length === 0 ? {} : { dismissedOffers: dismissed }),
|
|
379
580
|
};
|
|
380
581
|
}
|
|
381
582
|
function lenient(items, parse) {
|
|
@@ -403,6 +604,7 @@ export function parseDraft(v) {
|
|
|
403
604
|
earlierReplies: lenient(v.earlierReplies, parseEarlierReplies),
|
|
404
605
|
carried: v.carried === undefined ? null : lenient(v.carried, parseCarriedRef),
|
|
405
606
|
carriedConcerns: lenient(v.carriedConcerns, parseCarriedConcern),
|
|
607
|
+
dismissedOffers: lenientIds(v.dismissedOffers, isOfferId),
|
|
406
608
|
};
|
|
407
609
|
}
|
|
408
610
|
export function parseEvent(v) {
|
|
@@ -444,6 +646,10 @@ export function parseEvent(v) {
|
|
|
444
646
|
}
|
|
445
647
|
return { seq, type: v.type, commentId: id, resolved: v.resolved };
|
|
446
648
|
}
|
|
649
|
+
case "offer-dismissed":
|
|
650
|
+
return isOfferId(v.offer)
|
|
651
|
+
? { seq, type: v.type, offer: v.offer }
|
|
652
|
+
: "offer is not an offer id";
|
|
447
653
|
case "version": {
|
|
448
654
|
if (!isPlanVersion(v.version) || typeof v.hash !== "string")
|
|
449
655
|
return "malformed version event";
|
package/dist/src/coverage.js
CHANGED
|
@@ -15,9 +15,12 @@ export class LcovError extends Error {
|
|
|
15
15
|
const RECORD = /^([A-Z][A-Z0-9_]*):(.*)$/;
|
|
16
16
|
const DA = /^([1-9][0-9]*),([0-9]+)(?:,.*)?$/;
|
|
17
17
|
const BRDA = /^([1-9][0-9]*),([^,]+),(.+),(-|[0-9]+)$/;
|
|
18
|
+
/** FN:<start>,<name>, or lcov 2's FN:<start>,<end>,<name>. */
|
|
19
|
+
const FN = /^([1-9][0-9]*),(?:([1-9][0-9]*),)?(.+)$/;
|
|
18
20
|
/**
|
|
19
21
|
* Reads lcov's tracefile format: TN names the test of the records after it, SF opens a file, DA
|
|
20
|
-
* and BRDA give line and branch hits, end_of_record closes it. Other
|
|
22
|
+
* and BRDA give line and branch hits, FN a function's place, end_of_record closes it. Other
|
|
23
|
+
* records are skipped.
|
|
21
24
|
*/
|
|
22
25
|
export function parseLcov(text) {
|
|
23
26
|
const records = [];
|
|
@@ -50,10 +53,10 @@ export function parseLcov(text) {
|
|
|
50
53
|
close();
|
|
51
54
|
if (value === "")
|
|
52
55
|
throw new LcovError(n, "SF needs a path");
|
|
53
|
-
open = { path: value, test, lines: new Map(), branches: new Map() };
|
|
56
|
+
open = { path: value, test, lines: new Map(), branches: new Map(), functions: [] };
|
|
54
57
|
continue;
|
|
55
58
|
}
|
|
56
|
-
if (key !== "DA" && key !== "BRDA")
|
|
59
|
+
if (key !== "DA" && key !== "BRDA" && key !== "FN")
|
|
57
60
|
continue;
|
|
58
61
|
if (open === null)
|
|
59
62
|
throw new LcovError(n, `${key} comes before any SF`);
|
|
@@ -65,15 +68,27 @@ export function parseLcov(text) {
|
|
|
65
68
|
open.lines.set(line, (open.lines.get(line) ?? 0) + Number(da[2]));
|
|
66
69
|
continue;
|
|
67
70
|
}
|
|
71
|
+
if (key === "FN") {
|
|
72
|
+
const fn = FN.exec(value);
|
|
73
|
+
if (fn === null)
|
|
74
|
+
throw new LcovError(n, `FN needs <line>,<name>, got "${value}"`);
|
|
75
|
+
const start = Number(fn[1]);
|
|
76
|
+
const end = fn[2] === undefined ? undefined : Number(fn[2]);
|
|
77
|
+
const name = fn[3] ?? "";
|
|
78
|
+
if (end !== undefined && end < start)
|
|
79
|
+
throw new LcovError(n, "FN ends before it starts");
|
|
80
|
+
open.functions.push(end === undefined ? { name, start } : { name, start, end });
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
68
83
|
const br = BRDA.exec(value);
|
|
69
84
|
if (br === null) {
|
|
70
85
|
throw new LcovError(n, `BRDA needs <line>,<block>,<branch>,<taken>, got "${value}"`);
|
|
71
86
|
}
|
|
72
87
|
const line = Number(br[1]);
|
|
73
88
|
const id = `${br[2]},${br[3]}`;
|
|
74
|
-
const taken = br[4]
|
|
89
|
+
const taken = br[4] === "-" ? 0 : Number(br[4]);
|
|
75
90
|
const branches = open.branches.get(line) ?? new Map();
|
|
76
|
-
branches.set(id, (branches.get(id) ??
|
|
91
|
+
branches.set(id, (branches.get(id) ?? 0) + taken);
|
|
77
92
|
open.branches.set(line, branches);
|
|
78
93
|
}
|
|
79
94
|
close();
|
|
@@ -110,10 +125,42 @@ export function tipPlacer(root, cwd, atTip) {
|
|
|
110
125
|
return null;
|
|
111
126
|
};
|
|
112
127
|
}
|
|
128
|
+
/** Adds a record's branch counts into `into`, keeping lcov's branch order per line. */
|
|
129
|
+
function countInto(into, path, record) {
|
|
130
|
+
const joined = into.get(path) ?? new Map();
|
|
131
|
+
for (const [line, ids] of record.branches) {
|
|
132
|
+
const on = joined.get(line) ?? new Map();
|
|
133
|
+
for (const [id, n] of ids)
|
|
134
|
+
on.set(id, (on.get(id) ?? 0) + n);
|
|
135
|
+
joined.set(line, on);
|
|
136
|
+
}
|
|
137
|
+
into.set(path, joined);
|
|
138
|
+
}
|
|
139
|
+
function countsOf(counting) {
|
|
140
|
+
const out = new Map();
|
|
141
|
+
for (const [path, byLine] of counting) {
|
|
142
|
+
if (byLine.size === 0)
|
|
143
|
+
continue;
|
|
144
|
+
out.set(path, sorted(new Map([...byLine].map(([line, ids]) => [line, [...ids.values()]]))));
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
/** Functions of both lists, once each by start and name, in line order. */
|
|
149
|
+
function joinFunctions(a, b) {
|
|
150
|
+
const seen = new Map();
|
|
151
|
+
for (const f of [...a, ...b]) {
|
|
152
|
+
const key = `${f.start}|${f.name}`;
|
|
153
|
+
const had = seen.get(key);
|
|
154
|
+
if (had === undefined || (had.end === undefined && f.end !== undefined))
|
|
155
|
+
seen.set(key, f);
|
|
156
|
+
}
|
|
157
|
+
return [...seen.values()].sort((x, y) => x.start - y.start);
|
|
158
|
+
}
|
|
113
159
|
/** Sums hits and joins branches over records of the same file; drops what `place` rejects. */
|
|
114
160
|
export function runOf(records, place) {
|
|
115
161
|
const lines = new Map();
|
|
116
162
|
const branches = new Map();
|
|
163
|
+
const functions = new Map();
|
|
117
164
|
const tests = new Map();
|
|
118
165
|
for (const record of records) {
|
|
119
166
|
const path = place(record.path);
|
|
@@ -123,37 +170,42 @@ export function runOf(records, place) {
|
|
|
123
170
|
for (const [line, n] of record.lines)
|
|
124
171
|
hits.set(line, (hits.get(line) ?? 0) + n);
|
|
125
172
|
lines.set(path, hits);
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const on = joined.get(line) ?? new Map();
|
|
129
|
-
for (const [id, taken] of ids)
|
|
130
|
-
on.set(id, (on.get(id) ?? false) || taken);
|
|
131
|
-
joined.set(line, on);
|
|
132
|
-
}
|
|
133
|
-
branches.set(path, joined);
|
|
173
|
+
countInto(branches, path, record);
|
|
174
|
+
functions.set(path, joinFunctions(functions.get(path) ?? [], record.functions));
|
|
134
175
|
if (record.test === null)
|
|
135
176
|
continue;
|
|
136
|
-
const ran = tests.get(record.test) ?? new Map();
|
|
137
|
-
const set = ran.get(path) ?? new Set();
|
|
177
|
+
const ran = tests.get(record.test) ?? { lines: new Map(), branches: new Map() };
|
|
178
|
+
const set = ran.lines.get(path) ?? new Set();
|
|
138
179
|
for (const [line, n] of record.lines)
|
|
139
180
|
if (n > 0)
|
|
140
181
|
set.add(line);
|
|
141
|
-
ran.set(path, set);
|
|
182
|
+
ran.lines.set(path, set);
|
|
183
|
+
countInto(ran.branches, path, record);
|
|
142
184
|
tests.set(record.test, ran);
|
|
143
185
|
}
|
|
144
186
|
const files = new Map();
|
|
145
187
|
for (const [path, hits] of lines) {
|
|
146
188
|
const counted = new Map();
|
|
147
189
|
for (const [line, ids] of branches.get(path) ?? []) {
|
|
148
|
-
|
|
190
|
+
const n = [...ids.values()];
|
|
191
|
+
counted.set(line, [n.filter((c) => c > 0).length, n.length]);
|
|
149
192
|
}
|
|
150
|
-
|
|
193
|
+
const fns = functions.get(path) ?? [];
|
|
194
|
+
files.set(path, {
|
|
195
|
+
lines: sorted(hits),
|
|
196
|
+
branches: sorted(counted),
|
|
197
|
+
...(fns.length === 0 ? {} : { functions: fns }),
|
|
198
|
+
});
|
|
151
199
|
}
|
|
152
200
|
return {
|
|
153
201
|
files,
|
|
202
|
+
counts: countsOf(branches),
|
|
154
203
|
tests: new Map([...tests].map(([id, ran]) => [
|
|
155
204
|
id,
|
|
156
|
-
|
|
205
|
+
{
|
|
206
|
+
lines: new Map([...ran.lines].map(([path, set]) => [path, [...set].sort((a, b) => a - b)])),
|
|
207
|
+
branches: countsOf(ran.branches),
|
|
208
|
+
},
|
|
157
209
|
])),
|
|
158
210
|
};
|
|
159
211
|
}
|
|
@@ -184,10 +236,17 @@ function mergeMax(a, b) {
|
|
|
184
236
|
const [t, n] = branches.get(l) ?? [0, 0];
|
|
185
237
|
branches.set(l, [Math.max(taken, t), Math.max(total, n)]);
|
|
186
238
|
}
|
|
187
|
-
|
|
239
|
+
const functions = joinFunctions(had.functions ?? [], f.functions ?? []);
|
|
240
|
+
out.set(path, {
|
|
241
|
+
lines: sorted(lines),
|
|
242
|
+
branches: sorted(branches),
|
|
243
|
+
...(functions.length === 0 ? {} : { functions }),
|
|
244
|
+
});
|
|
188
245
|
}
|
|
189
246
|
return out;
|
|
190
247
|
}
|
|
248
|
+
/** How many tip lines a test outside the branch's changes carries for the page's peek. */
|
|
249
|
+
export const PREVIEW_LINES = 8;
|
|
191
250
|
/**
|
|
192
251
|
* The coverage after this run. A plain run replaces everything, its TN blocks becoming its tests.
|
|
193
252
|
* A named test's run adds to what is already there for the same tip, and starts over otherwise.
|
|
@@ -195,19 +254,24 @@ function mergeMax(a, b) {
|
|
|
195
254
|
export function recordRun(prior, run, at) {
|
|
196
255
|
const { tip, takenAt, test } = at;
|
|
197
256
|
if (test === undefined) {
|
|
198
|
-
const tests = new Map([...run.tests].map(([id,
|
|
257
|
+
const tests = new Map([...run.tests].map(([id, ran]) => [id, testEntry(ran)]));
|
|
199
258
|
return { tip, takenAt, files: run.files, tests };
|
|
200
259
|
}
|
|
201
260
|
const base = prior !== null && prior.tip === tip ? prior : null;
|
|
202
261
|
const tests = new Map(base?.tests ?? []);
|
|
203
262
|
const entry = {
|
|
204
263
|
...(test.at === undefined ? {} : { at: test.at }),
|
|
205
|
-
|
|
264
|
+
...(test.preview === undefined ? {} : { preview: test.preview }),
|
|
265
|
+
...testEntry({ lines: hitLines(run.files), branches: run.counts }),
|
|
206
266
|
};
|
|
207
267
|
tests.set(test.id, entry);
|
|
208
268
|
const files = base === null ? run.files : mergeMax(base.files, run.files);
|
|
209
269
|
return { tip, takenAt, files, tests };
|
|
210
270
|
}
|
|
271
|
+
/** A test's lines, and its branch counts when the run had any. */
|
|
272
|
+
function testEntry(ran) {
|
|
273
|
+
return { lines: ran.lines, ...(ran.branches.size === 0 ? {} : { branches: ran.branches }) };
|
|
274
|
+
}
|
|
211
275
|
/**
|
|
212
276
|
* Per file, the branch's added rows that lcov calls executable, and how many ran. Every added
|
|
213
277
|
* row is a tip line, which is where the run was taken. Files without such rows are left out.
|
|
@@ -275,13 +339,20 @@ export function serializeCoverage(c) {
|
|
|
275
339
|
{
|
|
276
340
|
lines: Object.fromEntries(f.lines),
|
|
277
341
|
...(f.branches.size === 0 ? {} : { branches: Object.fromEntries(f.branches) }),
|
|
342
|
+
...(f.functions === undefined ? {} : { functions: f.functions }),
|
|
278
343
|
},
|
|
279
344
|
]));
|
|
280
345
|
const tests = Object.fromEntries([...c.tests].map(([id, t]) => [
|
|
281
346
|
id,
|
|
282
347
|
{
|
|
283
348
|
...(t.at === undefined ? {} : { at: t.at }),
|
|
349
|
+
...(t.preview === undefined ? {} : { preview: [...t.preview] }),
|
|
284
350
|
lines: Object.fromEntries([...t.lines].map(([p, ls]) => [p, [...ls]])),
|
|
351
|
+
...(t.branches === undefined
|
|
352
|
+
? {}
|
|
353
|
+
: {
|
|
354
|
+
branches: Object.fromEntries([...t.branches].map(([p, byLine]) => [p, Object.fromEntries(byLine)])),
|
|
355
|
+
}),
|
|
285
356
|
},
|
|
286
357
|
]));
|
|
287
358
|
return { tip: c.tip, takenAt: c.takenAt, files, ...(c.tests.size === 0 ? {} : { tests }) };
|
|
@@ -296,7 +367,8 @@ function isLine(v) {
|
|
|
296
367
|
function entries(v) {
|
|
297
368
|
return isFields(v) ? Object.entries(v) : null;
|
|
298
369
|
}
|
|
299
|
-
|
|
370
|
+
/** A { path, start, end } with 1 <= start <= end, or null. */
|
|
371
|
+
export function parseLoc(v) {
|
|
300
372
|
if (!isFields(v))
|
|
301
373
|
return null;
|
|
302
374
|
const { path, start, end } = v;
|
|
@@ -316,6 +388,52 @@ export function parseLocArg(raw, place) {
|
|
|
316
388
|
const end = m[3] === undefined ? start : Number(m[3]);
|
|
317
389
|
return end < start ? `--at ${raw} runs backwards` : { path, start, end };
|
|
318
390
|
}
|
|
391
|
+
function parseFunction(v) {
|
|
392
|
+
if (!isFields(v))
|
|
393
|
+
return null;
|
|
394
|
+
const { name, start, end } = v;
|
|
395
|
+
if (typeof name !== "string" || !isLine(start))
|
|
396
|
+
return null;
|
|
397
|
+
if (end === undefined)
|
|
398
|
+
return { name, start };
|
|
399
|
+
return isLine(end) && end >= start ? { name, start, end } : null;
|
|
400
|
+
}
|
|
401
|
+
function parseFunctions(v) {
|
|
402
|
+
if (v === undefined)
|
|
403
|
+
return undefined;
|
|
404
|
+
if (!Array.isArray(v))
|
|
405
|
+
return "functions is not a list";
|
|
406
|
+
const out = [];
|
|
407
|
+
for (const raw of v) {
|
|
408
|
+
const f = parseFunction(raw);
|
|
409
|
+
if (f === null)
|
|
410
|
+
return "a function is not a { name, start, end? }";
|
|
411
|
+
out.push(f);
|
|
412
|
+
}
|
|
413
|
+
return out;
|
|
414
|
+
}
|
|
415
|
+
function parseCounts(v) {
|
|
416
|
+
if (v === undefined)
|
|
417
|
+
return undefined;
|
|
418
|
+
const files = entries(v);
|
|
419
|
+
if (files === null)
|
|
420
|
+
return "branches is not an object";
|
|
421
|
+
const out = new Map();
|
|
422
|
+
for (const [path, raw] of files) {
|
|
423
|
+
const lines = entries(raw);
|
|
424
|
+
if (!isRepoPath(path) || lines === null)
|
|
425
|
+
return `branches of ${path} are not by line`;
|
|
426
|
+
const byLine = new Map();
|
|
427
|
+
for (const [key, counts] of lines) {
|
|
428
|
+
if (!LINE_KEY.test(key) || !Array.isArray(counts) || !counts.every(isCount)) {
|
|
429
|
+
return `branches of ${path}:${key} are not counts`;
|
|
430
|
+
}
|
|
431
|
+
byLine.set(Number(key), counts.filter(isCount));
|
|
432
|
+
}
|
|
433
|
+
out.set(path, sorted(byLine));
|
|
434
|
+
}
|
|
435
|
+
return out;
|
|
436
|
+
}
|
|
319
437
|
function parseFile(v) {
|
|
320
438
|
if (!isFields(v))
|
|
321
439
|
return "a file is not an object";
|
|
@@ -342,7 +460,14 @@ function parseFile(v) {
|
|
|
342
460
|
}
|
|
343
461
|
branches.set(Number(key), [taken, total]);
|
|
344
462
|
}
|
|
345
|
-
|
|
463
|
+
const functions = parseFunctions(v.functions);
|
|
464
|
+
if (typeof functions === "string")
|
|
465
|
+
return functions;
|
|
466
|
+
return {
|
|
467
|
+
lines: sorted(hits),
|
|
468
|
+
branches: sorted(branches),
|
|
469
|
+
...(functions === undefined ? {} : { functions }),
|
|
470
|
+
};
|
|
346
471
|
}
|
|
347
472
|
function parseTest(v) {
|
|
348
473
|
if (!isFields(v))
|
|
@@ -350,6 +475,11 @@ function parseTest(v) {
|
|
|
350
475
|
const at = v.at === undefined ? undefined : parseLoc(v.at);
|
|
351
476
|
if (at === null)
|
|
352
477
|
return "at is not a { path, start, end }";
|
|
478
|
+
const preview = v.preview;
|
|
479
|
+
if (preview !== undefined &&
|
|
480
|
+
!(Array.isArray(preview) && preview.every((l) => typeof l === "string"))) {
|
|
481
|
+
return "preview is not a list of lines";
|
|
482
|
+
}
|
|
353
483
|
const lineEntries = entries(v.lines);
|
|
354
484
|
if (lineEntries === null)
|
|
355
485
|
return "lines is not an object";
|
|
@@ -360,7 +490,15 @@ function parseTest(v) {
|
|
|
360
490
|
}
|
|
361
491
|
lines.set(path, list);
|
|
362
492
|
}
|
|
363
|
-
|
|
493
|
+
const branches = parseCounts(v.branches);
|
|
494
|
+
if (typeof branches === "string")
|
|
495
|
+
return branches;
|
|
496
|
+
return {
|
|
497
|
+
...(at === undefined ? {} : { at }),
|
|
498
|
+
...(preview === undefined ? {} : { preview: preview.filter((l) => typeof l === "string") }),
|
|
499
|
+
lines,
|
|
500
|
+
...(branches === undefined ? {} : { branches }),
|
|
501
|
+
};
|
|
364
502
|
}
|
|
365
503
|
/** The one place coverage.json becomes a Coverage. */
|
|
366
504
|
export function parseCoverage(v) {
|
package/dist/src/host.js
CHANGED
|
@@ -320,6 +320,29 @@ export async function fetchComments(port, summary) {
|
|
|
320
320
|
}
|
|
321
321
|
return out;
|
|
322
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Offers accepted during the hosted review, on this version or a carried one, whose comment has no
|
|
325
|
+
* reply from the agent yet, oldest first.
|
|
326
|
+
*/
|
|
327
|
+
export async function fetchWaitingOffers(port, summary) {
|
|
328
|
+
const state = await call(port, "GET", `${summary.url}api/state`);
|
|
329
|
+
if (!isFields(state))
|
|
330
|
+
throw new RegistrationError("the server sent no state");
|
|
331
|
+
const comments = Array.isArray(state["comments"]) ? state["comments"] : [];
|
|
332
|
+
const carried = Array.isArray(state["carried"]) ? state["carried"] : [];
|
|
333
|
+
const said = [
|
|
334
|
+
...comments.map((raw) => parseComment(raw)),
|
|
335
|
+
...carried.map((raw) => {
|
|
336
|
+
const c = parseCarried(raw);
|
|
337
|
+
return typeof c === "string" ? c : c.comment;
|
|
338
|
+
}),
|
|
339
|
+
];
|
|
340
|
+
return said
|
|
341
|
+
.flatMap((c) => (typeof c === "string" ? [] : [c]))
|
|
342
|
+
.filter((c) => !(c.replies ?? []).some((r) => r.author === "agent"))
|
|
343
|
+
.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
|
|
344
|
+
.flatMap((c) => (c.offer === undefined ? [] : [{ offer: c.offer, comment: c.id }]));
|
|
345
|
+
}
|
|
323
346
|
/** One long poll of the session's event log: what happened after `since`, or nothing at timeout. */
|
|
324
347
|
export async function pollEvents(port, summary, since, timeoutMs) {
|
|
325
348
|
const path = `${summary.url}api/events?since=${since}&timeout=${timeoutMs}`;
|