@shiyuphan/follow-me 0.5.1 → 0.7.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/README.md +9 -3
- package/dist/src/cli.js +112 -19
- package/dist/src/comments.js +6 -1
- package/dist/src/coverage.js +397 -0
- package/dist/src/definition.js +226 -0
- package/dist/src/git.js +87 -13
- package/dist/src/host.js +4 -0
- package/dist/src/plan.js +12 -1
- package/dist/src/prompt.js +80 -23
- package/dist/src/server.js +65 -8
- package/dist/src/session.js +33 -0
- package/dist/src/types.js +4 -0
- package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-8UsLPznO.js → abnfDiagram-WEYOIA3C-cHEJvcBn.js} +1 -1
- package/dist/ui/dist/assets/{arc-vovjGalz.js → arc-xolFyc0v.js} +1 -1
- package/dist/ui/dist/assets/{architecture-WOLXFQ4H-BBMK-PUu.js → architecture-WOLXFQ4H-B3hvvNV2.js} +1 -1
- package/dist/ui/dist/assets/{architectureDiagram-ZHVYKT53-Bw_ehaQk.js → architectureDiagram-ZHVYKT53-BNTMbPJW.js} +1 -1
- package/dist/ui/dist/assets/{blockDiagram-RJAE7QA3-CAmD0wKf.js → blockDiagram-RJAE7QA3-NYY6qeuY.js} +1 -1
- package/dist/ui/dist/assets/{c4Diagram-XDPPPE54-DCP-3zgt.js → c4Diagram-XDPPPE54-B_bNpXHQ.js} +1 -1
- package/dist/ui/dist/assets/channel-DPDY0XJP.js +1 -0
- package/dist/ui/dist/assets/{chunk-2E7WHVMD-BiHSeJMu.js → chunk-2E7WHVMD-BTet9STn.js} +1 -1
- package/dist/ui/dist/assets/{chunk-4EMNKR2T-CR3lHKf_.js → chunk-4EMNKR2T-qoQt4Fpx.js} +1 -1
- package/dist/ui/dist/assets/{chunk-55XCU76K-CtaoloTT.js → chunk-55XCU76K-C3I6EB-H.js} +1 -1
- package/dist/ui/dist/assets/{chunk-7S6XSTFO-eABdc7fu.js → chunk-7S6XSTFO-CL-9s_8p.js} +1 -1
- package/dist/ui/dist/assets/{chunk-AQDTSKZB-CPlbJWi-.js → chunk-AQDTSKZB-DHoGnGyP.js} +1 -1
- package/dist/ui/dist/assets/{chunk-DSNLSHJZ-YiMuxsb-.js → chunk-DSNLSHJZ-Bk8WO5JF.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HAGFOTED-1VcdL78a.js → chunk-HAGFOTED-CwAC85lg.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HNE4Q36P-u-sr_0Qu.js → chunk-HNE4Q36P-CIllywWr.js} +1 -1
- package/dist/ui/dist/assets/{chunk-NQOKRVIF-C_QlWSze.js → chunk-NQOKRVIF-DDlpyYiH.js} +1 -1
- package/dist/ui/dist/assets/{chunk-UHW7PFBP-CNuZhsbU.js → chunk-UHW7PFBP-Cxdndcon.js} +1 -1
- package/dist/ui/dist/assets/{chunk-VQ5W62QI-DAHWbLqU.js → chunk-VQ5W62QI-CL9d9_iJ.js} +1 -1
- package/dist/ui/dist/assets/{chunk-WQQL4CDK-DWQnxFCd.js → chunk-WQQL4CDK-C4kzN8QU.js} +1 -1
- package/dist/ui/dist/assets/{chunk-XXDRQBXY-B0lGgSOR.js → chunk-XXDRQBXY-Cv_2FSiY.js} +1 -1
- package/dist/ui/dist/assets/{chunk-YZO67HFG-BDw7efkN.js → chunk-YZO67HFG-oHiuYaCJ.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZAUX2LTX-B5wfHsZv.js → chunk-ZAUX2LTX-Cy_8d0XT.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZCHMQSOR--rWtYtAG.js → chunk-ZCHMQSOR-piIpIcQi.js} +1 -1
- package/dist/ui/dist/assets/{classDiagram-v2-4MYWR3UA-BKuQ_xoS.js → classDiagram-v2-4MYWR3UA-DNKRIujO.js} +1 -1
- package/dist/ui/dist/assets/{cose-bilkent-JH36ORCC-Dk5fIpU0.js → cose-bilkent-JH36ORCC-BWKTGCHY.js} +1 -1
- package/dist/ui/dist/assets/{cynefin-EF2NZ3EQ-DIykJb2g.js → cynefin-EF2NZ3EQ-yx7KKkAk.js} +1 -1
- package/dist/ui/dist/assets/{cynefinDiagram-WF24UDRP-Cg8tBqUb.js → cynefinDiagram-WF24UDRP-BTnZ7QVL.js} +1 -1
- package/dist/ui/dist/assets/{dagre-NK4GIZVX-BC_tnnjz.js → dagre-NK4GIZVX-CPh5Zn5I.js} +1 -1
- package/dist/ui/dist/assets/{diagram-CBK3MWTC-B_MQGixU.js → diagram-CBK3MWTC-pI6NFdrD.js} +1 -1
- package/dist/ui/dist/assets/{diagram-I5XIIV2D-DO681pZ5.js → diagram-I5XIIV2D-DUIfARoS.js} +1 -1
- package/dist/ui/dist/assets/{diagram-ODIL2UCW-D14jLzU6.js → diagram-ODIL2UCW-lafSPvAw.js} +1 -1
- package/dist/ui/dist/assets/{diagram-PFBEJUXE-cML48gUS.js → diagram-PFBEJUXE-u1lZas3a.js} +1 -1
- package/dist/ui/dist/assets/{diagram-RJA3I4VV-DSoq8jFk.js → diagram-RJA3I4VV-mc0GLMHo.js} +1 -1
- package/dist/ui/dist/assets/{diagram-TYUNFSNR-CaJ4pvnl.js → diagram-TYUNFSNR-1NRbBrZW.js} +1 -1
- package/dist/ui/dist/assets/{dist-D3j-DODP.js → dist-CHZ9mrl_.js} +1 -1
- package/dist/ui/dist/assets/{ebnfDiagram-V4P3TTPT-CyRpjSix.js → ebnfDiagram-V4P3TTPT-DdStjTDc.js} +1 -1
- package/dist/ui/dist/assets/{elk-QONTBCTQ-ICJdzVFL.js → elk-QONTBCTQ-BzRoFO45.js} +1 -1
- package/dist/ui/dist/assets/{erDiagram-KYQIGQAA-B57hNTfw.js → erDiagram-KYQIGQAA-DlMkgQxj.js} +1 -1
- package/dist/ui/dist/assets/{eventmodeling-K75KTNOO-BG8tUVIQ.js → eventmodeling-K75KTNOO-BRYIOrod.js} +1 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-C1IBh_GZ.js +1 -0
- package/dist/ui/dist/assets/{ganttDiagram-R7GPNTJY-Czt6Klik.js → ganttDiagram-R7GPNTJY-D8fQZ7Ah.js} +1 -1
- package/dist/ui/dist/assets/{gitGraph-VSP46ZUC-De_ZWJLj.js → gitGraph-VSP46ZUC-C7ZydyWX.js} +1 -1
- package/dist/ui/dist/assets/{gitGraphDiagram-PDYL7XHY-BMCJoOnT.js → gitGraphDiagram-PDYL7XHY-DCItam0v.js} +1 -1
- package/dist/ui/dist/assets/index-8BehwQ7E.js +134 -0
- package/dist/ui/dist/assets/index-mC2OW2Av.css +1 -0
- package/dist/ui/dist/assets/{info-OHQRW6UA-D75H9kUq.js → info-OHQRW6UA-C3KneBKT.js} +1 -1
- package/dist/ui/dist/assets/{infoDiagram-MVDRKAX5-Bsm_I9dt.js → infoDiagram-MVDRKAX5-6wK09D32.js} +1 -1
- package/dist/ui/dist/assets/{ishikawaDiagram-HPEHV2GE-98xZAJ9y.js → ishikawaDiagram-HPEHV2GE-DTP5C8VX.js} +1 -1
- package/dist/ui/dist/assets/{journeyDiagram-BOSHW7O7-BWVGuc0P.js → journeyDiagram-BOSHW7O7-B--UIMtq.js} +1 -1
- package/dist/ui/dist/assets/{kanban-definition-FNSA4HC5-DgUE_KDd.js → kanban-definition-FNSA4HC5-Ku4N9QGt.js} +1 -1
- package/dist/ui/dist/assets/{line-i__8Rvas.js → line-C4-H5h1-.js} +1 -1
- package/dist/ui/dist/assets/{linear-BXKj0Fip.js → linear-DhAID3hm.js} +1 -1
- package/dist/ui/dist/assets/{mermaid-parser.core-DxvaorTa.js → mermaid-parser.core-CMPNc-u6.js} +3 -3
- package/dist/ui/dist/assets/{mermaid.core-ujYTsRZz.js → mermaid.core-Nd_xOg2y.js} +4 -4
- package/dist/ui/dist/assets/{mindmap-definition-6OY57UG3-B7QD6sjk.js → mindmap-definition-6OY57UG3-BlVsym5h.js} +1 -1
- package/dist/ui/dist/assets/{packet-JDAUHWVQ-B6aIwfD1.js → packet-JDAUHWVQ-rsk7SM7S.js} +1 -1
- package/dist/ui/dist/assets/{pegDiagram-ECIUJNBX-_RStHj-a.js → pegDiagram-ECIUJNBX-C0L20M8z.js} +1 -1
- package/dist/ui/dist/assets/{pie-XZMESJXO-CNLC-pTe.js → pie-XZMESJXO-B23-iZSN.js} +1 -1
- package/dist/ui/dist/assets/{pieDiagram-GNMPY74T-0uT7MT-u.js → pieDiagram-GNMPY74T-BqFhe4Oh.js} +1 -1
- package/dist/ui/dist/assets/{prism-core-DtxziK1i.js → prism-core-DYsiqJeR.js} +1 -1
- package/dist/ui/dist/assets/{quadrantDiagram-D57K4MC3-CTMKb5hl.js → quadrantDiagram-D57K4MC3-BKP75B-m.js} +1 -1
- package/dist/ui/dist/assets/{radar-ABXABTNO-D53h6I0N.js → radar-ABXABTNO-BrEk2mQ7.js} +1 -1
- package/dist/ui/dist/assets/{railroad-I3PHUGI6-CwjhW4mc.js → railroad-I3PHUGI6-wWO4Eapc.js} +1 -1
- package/dist/ui/dist/assets/{railroad-abnf-LNEFI6M7-DDbQ3RmB.js → railroad-abnf-LNEFI6M7-DfY-IPvE.js} +1 -1
- package/dist/ui/dist/assets/{railroad-ebnf-DWYD2UWJ-Am7YJAqO.js → railroad-ebnf-DWYD2UWJ-19wedWGk.js} +1 -1
- package/dist/ui/dist/assets/{railroad-peg-JR7BVNR7-Ci32KoVW.js → railroad-peg-JR7BVNR7-9vIslnoJ.js} +1 -1
- package/dist/ui/dist/assets/{railroadDiagram-Y72NRBL7-BPU5A6XW.js → railroadDiagram-Y72NRBL7-DVnmXs8w.js} +1 -1
- package/dist/ui/dist/assets/{requirementDiagram-EQYSW5RA-_0Pq4QdV.js → requirementDiagram-EQYSW5RA-BEoMdlzR.js} +1 -1
- package/dist/ui/dist/assets/{sankeyDiagram-SY24JHO5-c9h-7uf8.js → sankeyDiagram-SY24JHO5-D7Tn2Gbs.js} +1 -1
- package/dist/ui/dist/assets/{sequenceDiagram-BLEHNHQU-B9-S4Rc9.js → sequenceDiagram-BLEHNHQU-CyjUDBaQ.js} +1 -1
- package/dist/ui/dist/assets/{src-BZ3kG7lO.js → src-BiC9XNcI.js} +1 -1
- package/dist/ui/dist/assets/{stateDiagram-v2-A6IESKWZ-py1smSen.js → stateDiagram-v2-A6IESKWZ-CMp1VCO9.js} +1 -1
- package/dist/ui/dist/assets/{swimlanes-B3ENRKFQ-C1E90aH4.js → swimlanes-B3ENRKFQ-KEciLd6r.js} +1 -1
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-DzestpDE.js +8 -0
- package/dist/ui/dist/assets/{timeline-definition-JXYKGLGB-DNe1To6B.js → timeline-definition-JXYKGLGB-C8YQtv7M.js} +1 -1
- package/dist/ui/dist/assets/{treeView-D4JQ5SDB-D6MG89i6.js → treeView-D4JQ5SDB-DgAzPaOO.js} +1 -1
- package/dist/ui/dist/assets/{treemap-SAJKECNS-CZpt7Mlh.js → treemap-SAJKECNS-eCRqZMYc.js} +1 -1
- package/dist/ui/dist/assets/{usecaseDiagram-WPOLBNNX-CKsv-03M.js → usecaseDiagram-WPOLBNNX-Dirryw9G.js} +1 -1
- package/dist/ui/dist/assets/{vennDiagram-Q3Y3IDCW-DGJ1qlyJ.js → vennDiagram-Q3Y3IDCW-BjxQbAUx.js} +1 -1
- package/dist/ui/dist/assets/{wardley-7MLQ67FV-ClOzBemC.js → wardley-7MLQ67FV-CqHX5RUz.js} +1 -1
- package/dist/ui/dist/assets/{wardleyDiagram-IVAIDKWA-C68ahPyF.js → wardleyDiagram-IVAIDKWA-Ds1HoEUY.js} +1 -1
- package/dist/ui/dist/assets/{xychartDiagram-4URNHDU4-DWDamCoH.js → xychartDiagram-4URNHDU4-kodzDb12.js} +1 -1
- package/dist/ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/follow-me/SKILL.md +5 -1
- package/dist/ui/dist/assets/channel-B7CT_oIp.js +0 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU--HPNoVQJ.js +0 -1
- package/dist/ui/dist/assets/index-B8TYcxF8.css +0 -1
- package/dist/ui/dist/assets/index-CYKbk4QS.js +0 -134
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-I98Q8AqP.js +0 -8
package/dist/src/git.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { link, mkdir, mkdtemp, readdir, rm } from "node:fs/promises";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { isSha, neededBlobs, parseDiff } from "./diff.js";
|
|
4
|
+
import { isRepoPath, isSha, neededBlobs, parseDiff } from "./diff.js";
|
|
5
5
|
export class GitError extends Error {
|
|
6
6
|
name = "GitError";
|
|
7
7
|
args;
|
|
8
|
-
|
|
8
|
+
/** git's exit status, or null when it did not run or was killed. */
|
|
9
|
+
status;
|
|
10
|
+
constructor(args, stderr, status = null) {
|
|
9
11
|
super(`git ${args.join(" ")}: ${stderr}`);
|
|
10
12
|
this.args = args;
|
|
13
|
+
this.status = status;
|
|
11
14
|
}
|
|
12
15
|
}
|
|
13
16
|
function git(repo, args, opts = {}) {
|
|
@@ -18,8 +21,10 @@ function git(repo, args, opts = {}) {
|
|
|
18
21
|
maxBuffer: 1 << 30,
|
|
19
22
|
env: { ...process.env, ...opts.env },
|
|
20
23
|
}, (error, stdout, stderr) => {
|
|
21
|
-
if (error)
|
|
22
|
-
|
|
24
|
+
if (error) {
|
|
25
|
+
const status = typeof error.code === "number" ? error.code : null;
|
|
26
|
+
reject(new GitError(args, stderr.toString("utf8").trim() || error.message, status));
|
|
27
|
+
}
|
|
23
28
|
else
|
|
24
29
|
resolve(stdout);
|
|
25
30
|
});
|
|
@@ -97,25 +102,94 @@ export async function defaultBaseRef(repo) {
|
|
|
97
102
|
}
|
|
98
103
|
throw new Error("neither main nor master exists; pass --base <ref>");
|
|
99
104
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (
|
|
103
|
-
return
|
|
104
|
-
const
|
|
105
|
+
/** The blob named by each object spec, in order, as latin1; throws when one is not a blob. */
|
|
106
|
+
async function catBatch(repo, specs) {
|
|
107
|
+
if (specs.length === 0)
|
|
108
|
+
return [];
|
|
109
|
+
const input = Buffer.from(`${specs.join("\n")}\n`, "latin1");
|
|
110
|
+
const out = await git(repo, ["cat-file", "--batch"], { input });
|
|
111
|
+
const blobs = [];
|
|
105
112
|
let at = 0;
|
|
106
|
-
for (const
|
|
113
|
+
for (const spec of specs) {
|
|
107
114
|
const nl = out.indexOf(0x0a, at);
|
|
108
115
|
const header = out.subarray(at, nl).toString("latin1");
|
|
109
116
|
const m = /^([0-9a-f]+) blob (\d+)$/.exec(header);
|
|
110
|
-
if (nl === -1 || m === null || m[1] !==
|
|
111
|
-
throw new Error(`cat-file returned "${header}" for ${
|
|
117
|
+
if (nl === -1 || m === null || (isSha(spec) && m[1] !== spec)) {
|
|
118
|
+
throw new Error(`cat-file returned "${header}" for ${spec}`);
|
|
112
119
|
}
|
|
113
120
|
const size = Number(m[2]);
|
|
114
|
-
blobs.
|
|
121
|
+
blobs.push(out.subarray(nl + 1, nl + 1 + size).toString("latin1"));
|
|
115
122
|
at = nl + 1 + size + 1;
|
|
116
123
|
}
|
|
117
124
|
return blobs;
|
|
118
125
|
}
|
|
126
|
+
async function catBlobs(repo, shas) {
|
|
127
|
+
const blobs = await catBatch(repo, shas);
|
|
128
|
+
return new Map(shas.map((sha, i) => [sha, blobs[i] ?? ""]));
|
|
129
|
+
}
|
|
130
|
+
/** Each path's bytes at the commit, as latin1. */
|
|
131
|
+
export async function blobsAt(repo, commit, paths) {
|
|
132
|
+
const blobs = await catBatch(repo, paths.map((p) => `${commit}:${p}`));
|
|
133
|
+
return new Map(paths.map((p, i) => [p, blobs[i] ?? ""]));
|
|
134
|
+
}
|
|
135
|
+
function pathList(out) {
|
|
136
|
+
return new Set(out
|
|
137
|
+
.toString("latin1")
|
|
138
|
+
.split("\0")
|
|
139
|
+
.filter((p) => isRepoPath(p)));
|
|
140
|
+
}
|
|
141
|
+
/** Every file path in the commit's tree. */
|
|
142
|
+
export async function pathsAt(repo, commit) {
|
|
143
|
+
return pathList(await git(repo, ["ls-tree", "-r", "-z", "--name-only", commit]));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Paths whose content differs between the commit and `to`: another commit, or with null the
|
|
147
|
+
* working tree and index. Untracked files are not listed.
|
|
148
|
+
*/
|
|
149
|
+
export async function pathsChanged(repo, from, to) {
|
|
150
|
+
const target = to === null ? [] : [to];
|
|
151
|
+
return pathList(await git(repo, ["diff", "--name-only", "-z", ...DIFF_FLAGS, from, ...target, "--"]));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Lines of the commit's tree, not the working tree, that match any of the POSIX extended
|
|
155
|
+
* patterns, in files matching the pathspecs. Binary files are skipped.
|
|
156
|
+
*/
|
|
157
|
+
export async function grepTip(repo, commit, patterns, pathspecs) {
|
|
158
|
+
const args = [
|
|
159
|
+
"grep",
|
|
160
|
+
"-n",
|
|
161
|
+
"-E",
|
|
162
|
+
"-I",
|
|
163
|
+
"-z",
|
|
164
|
+
"--no-color",
|
|
165
|
+
"--no-column",
|
|
166
|
+
...patterns.flatMap((p) => ["-e", p]),
|
|
167
|
+
commit,
|
|
168
|
+
"--",
|
|
169
|
+
...pathspecs,
|
|
170
|
+
];
|
|
171
|
+
let out;
|
|
172
|
+
try {
|
|
173
|
+
out = await git(repo, args);
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
// grep exits 1 when nothing matched.
|
|
177
|
+
if (error instanceof GitError && error.status === 1)
|
|
178
|
+
return [];
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
181
|
+
const prefix = `${commit}:`;
|
|
182
|
+
return out
|
|
183
|
+
.toString("latin1")
|
|
184
|
+
.split("\n")
|
|
185
|
+
.flatMap((record) => {
|
|
186
|
+
const [name = "", line = "", ...text] = record.split("\0");
|
|
187
|
+
const path = name.slice(prefix.length);
|
|
188
|
+
if (!name.startsWith(prefix) || !isRepoPath(path) || !/^\d+$/.test(line))
|
|
189
|
+
return [];
|
|
190
|
+
return [{ path, line: Number(line), text: text.join("\0") }];
|
|
191
|
+
});
|
|
192
|
+
}
|
|
119
193
|
const DIFF_FLAGS = ["--no-renames", "--full-index", "--no-color", "--no-ext-diff", "--no-textconv"];
|
|
120
194
|
export async function loadDiff(repo, baseRef, tipRef) {
|
|
121
195
|
const [tip, baseCommit] = await Promise.all([
|
package/dist/src/host.js
CHANGED
|
@@ -373,6 +373,10 @@ export async function postComment(port, summary, comment) {
|
|
|
373
373
|
}
|
|
374
374
|
return id;
|
|
375
375
|
}
|
|
376
|
+
/** Tells the hosted page that coverage.json changed, so it reloads its marks. */
|
|
377
|
+
export async function postCoverage(port, summary) {
|
|
378
|
+
await call(port, "POST", `${summary.url}api/coverage`);
|
|
379
|
+
}
|
|
376
380
|
/** The consolidated report of the hosted session as it stands. */
|
|
377
381
|
export async function fetchReport(port, summary, format = "markdown") {
|
|
378
382
|
const res = await fetch(`${sessionUrl(port, summary)}api/report?format=${format}`);
|
package/dist/src/plan.js
CHANGED
|
@@ -17,6 +17,10 @@ const STATUSES = ["addressed", "declined", "deferred", "answered"];
|
|
|
17
17
|
function isResponseStatus(s) {
|
|
18
18
|
return STATUSES.some((x) => x === s);
|
|
19
19
|
}
|
|
20
|
+
const SECTIONS = ["story", "steps", "details", "wiring"];
|
|
21
|
+
function isCommitSection(s) {
|
|
22
|
+
return SECTIONS.some((x) => x === s);
|
|
23
|
+
}
|
|
20
24
|
const SEVERITIES = ["blocker", "major", "minor"];
|
|
21
25
|
const CONCERN_KINDS = ["bug", "missing-test", "naming", "risk"];
|
|
22
26
|
export function isSeverity(s) {
|
|
@@ -223,6 +227,10 @@ export function parsePlanFile(draft) {
|
|
|
223
227
|
}
|
|
224
228
|
if (typeof c.message !== "string")
|
|
225
229
|
bad(`${at}.message`, "expected a string");
|
|
230
|
+
const section = c.section ?? null;
|
|
231
|
+
if (section !== null && !isCommitSection(section)) {
|
|
232
|
+
bad(`${at}.section`, `expected one of ${SECTIONS.join(", ")}`);
|
|
233
|
+
}
|
|
226
234
|
const extras = parseExtras(c, false);
|
|
227
235
|
if ("error" in extras)
|
|
228
236
|
bad(`${at}.${extras.field}`, extras.error);
|
|
@@ -239,7 +247,8 @@ export function parsePlanFile(draft) {
|
|
|
239
247
|
}
|
|
240
248
|
}
|
|
241
249
|
if (typeof c.message === "string" && !("error" in extras)) {
|
|
242
|
-
|
|
250
|
+
const placed = isCommitSection(section) ? { section } : {};
|
|
251
|
+
commits.push({ message: c.message, ...placed, ...extras, units });
|
|
243
252
|
}
|
|
244
253
|
}
|
|
245
254
|
}
|
|
@@ -287,6 +296,7 @@ export function serializePlanFile(file) {
|
|
|
287
296
|
tip: file.tip,
|
|
288
297
|
commits: file.commits.map((c) => ({
|
|
289
298
|
message: c.message,
|
|
299
|
+
...(c.section === undefined ? {} : { section: c.section }),
|
|
290
300
|
...serializeExtras(c),
|
|
291
301
|
units: c.units.map((ref) => ref.narration === undefined && ref.diagram === undefined && ref.concerns === undefined
|
|
292
302
|
? formatUnitRef(ref)
|
|
@@ -578,6 +588,7 @@ function project(diff, file, claims, issues) {
|
|
|
578
588
|
return {
|
|
579
589
|
index: k,
|
|
580
590
|
message: commit.message,
|
|
591
|
+
...(commit.section === undefined ? {} : { section: commit.section }),
|
|
581
592
|
...withText(commit.narration ?? null, commit.diagram ?? null),
|
|
582
593
|
...(mine.length === 0 ? {} : { concerns: mine }),
|
|
583
594
|
refs,
|
package/dist/src/prompt.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { changedLines } from "./coverage.js";
|
|
1
2
|
import { tipStartOf } from "./diff.js";
|
|
2
3
|
import { anchorOn, concernOn } from "./evolve.js";
|
|
3
4
|
/** Consecutive runs collapse: [0,1,2,3,7] becomes "0-3,7". */
|
|
@@ -581,14 +582,14 @@ function itemHeading(item, level) {
|
|
|
581
582
|
parts.push(kept === null ? "raised by the reviewer" : "raised by the agent, kept by the reviewer");
|
|
582
583
|
return `${"#".repeat(level)} ${parts.join(" · ")}`;
|
|
583
584
|
}
|
|
584
|
-
function renderItems(items, units, level) {
|
|
585
|
+
function renderItems(items, units, level, tallies) {
|
|
585
586
|
const out = [];
|
|
586
587
|
const sorted = sortItems(items, units);
|
|
587
588
|
const paths = [...new Set(sorted.flatMap((i) => (i.where.path === null ? [] : [i.where.path])))];
|
|
588
|
-
const group = (title, members) => {
|
|
589
|
+
const group = (title, members, lead = []) => {
|
|
589
590
|
if (members.length === 0)
|
|
590
591
|
return;
|
|
591
|
-
out.push(`${"#".repeat(level)} ${title}`, "");
|
|
592
|
+
out.push(`${"#".repeat(level)} ${title}`, "", ...lead);
|
|
592
593
|
for (const item of members) {
|
|
593
594
|
out.push(itemHeading(item, level + 1), "", ...quoteBlock(item.where.quote));
|
|
594
595
|
if (item.where.quote.length > 0)
|
|
@@ -596,8 +597,12 @@ function renderItems(items, units, level) {
|
|
|
596
597
|
out.push(...voices(item), "");
|
|
597
598
|
}
|
|
598
599
|
};
|
|
599
|
-
for (const path of paths)
|
|
600
|
-
|
|
600
|
+
for (const path of paths) {
|
|
601
|
+
const tally = tallies.get(path);
|
|
602
|
+
group(path, sorted.filter((i) => i.where.path === path), tally === undefined
|
|
603
|
+
? []
|
|
604
|
+
: [`coverage: ${tally.covered} of ${tally.executable} changed lines`, ""]);
|
|
605
|
+
}
|
|
601
606
|
group("General", sorted.filter((i) => i.where.path === null));
|
|
602
607
|
return out;
|
|
603
608
|
}
|
|
@@ -719,24 +724,43 @@ function spanOf(rows) {
|
|
|
719
724
|
startOldLine: range && first.op !== "+" ? first.base : null,
|
|
720
725
|
};
|
|
721
726
|
}
|
|
722
|
-
function
|
|
727
|
+
function commentRows(ctx, c) {
|
|
723
728
|
const { anchor } = c;
|
|
724
729
|
if (anchor.kind !== "hunk")
|
|
725
|
-
return
|
|
730
|
+
return null;
|
|
726
731
|
const hunk = ctx.units.get(anchor.unit);
|
|
727
732
|
if (hunk?.kind !== "hunk")
|
|
728
|
-
return
|
|
733
|
+
return null;
|
|
729
734
|
const { offsets, changes } = anchor;
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
735
|
+
const want = offsets !== undefined
|
|
736
|
+
? (i) => offsets.includes(i)
|
|
737
|
+
: (_, n) => n !== null && (changes === null || changes.includes(n));
|
|
738
|
+
return { path: hunk.path, rows: numberedRows(hunk, ctx.units, want) };
|
|
733
739
|
}
|
|
734
|
-
function
|
|
740
|
+
function concernRows(ctx, concern) {
|
|
735
741
|
const hunk = concern.unit === undefined ? undefined : ctx.units.get(concern.unit);
|
|
736
742
|
if (hunk?.kind !== "hunk")
|
|
737
|
-
return
|
|
743
|
+
return null;
|
|
738
744
|
const lines = concern.lines;
|
|
739
|
-
|
|
745
|
+
const want = (_, n) => n !== null && (lines === undefined || lines.includes(n));
|
|
746
|
+
return { path: hunk.path, rows: numberedRows(hunk, ctx.units, want) };
|
|
747
|
+
}
|
|
748
|
+
function spanAt(at) {
|
|
749
|
+
return at === null ? NO_SPAN : spanOf(at.rows);
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Whether the test run ran every executable added row the item points at. Absent without a run
|
|
753
|
+
* that still fits the file, when it picks no such row, or when its code changed after it was said.
|
|
754
|
+
*/
|
|
755
|
+
function coveredOf(ctx, id, at) {
|
|
756
|
+
const file = at === null ? undefined : ctx.coverage?.get(at.path);
|
|
757
|
+
if (at === null || file === undefined || ctx.marks?.has(id))
|
|
758
|
+
return {};
|
|
759
|
+
const hits = at.rows.flatMap((r) => {
|
|
760
|
+
const n = r.op === "+" ? file.lines.get(r.tip) : undefined;
|
|
761
|
+
return n === undefined ? [] : [n];
|
|
762
|
+
});
|
|
763
|
+
return hits.length === 0 ? {} : { covered: hits.every((n) => n > 0) };
|
|
740
764
|
}
|
|
741
765
|
function threadOf(c) {
|
|
742
766
|
return (c.replies ?? []).map((r) => ({ author: r.author, body: r.body }));
|
|
@@ -761,6 +785,7 @@ function collectItems(review, ctx, responses) {
|
|
|
761
785
|
const { kept } = it;
|
|
762
786
|
const first = it.comments[0];
|
|
763
787
|
if (kept !== null) {
|
|
788
|
+
const at = concernRows(ctx, kept.concern);
|
|
764
789
|
const note = kept.note === null ? "" : `\n\nReviewer: ${kept.note}`;
|
|
765
790
|
const thread = it.comments.flatMap((c) => [
|
|
766
791
|
{ author: "reviewer", body: c.body },
|
|
@@ -770,7 +795,7 @@ function collectItems(review, ctx, responses) {
|
|
|
770
795
|
where: it.where,
|
|
771
796
|
item: {
|
|
772
797
|
id: kept.concern.id,
|
|
773
|
-
...
|
|
798
|
+
...spanAt(at),
|
|
774
799
|
path: it.where.path,
|
|
775
800
|
oldPath: it.where.path,
|
|
776
801
|
severity: it.severity,
|
|
@@ -780,16 +805,18 @@ function collectItems(review, ctx, responses) {
|
|
|
780
805
|
resolved: false,
|
|
781
806
|
excluded: noConcerns.has(kept.concern.id),
|
|
782
807
|
...changedOf(ctx, kept.concern.id),
|
|
808
|
+
...coveredOf(ctx, kept.concern.id, at),
|
|
783
809
|
},
|
|
784
810
|
};
|
|
785
811
|
}
|
|
786
812
|
if (first === undefined)
|
|
787
813
|
throw new Error("an item with neither a concern nor a comment");
|
|
814
|
+
const at = commentRows(ctx, first);
|
|
788
815
|
return {
|
|
789
816
|
where: it.where,
|
|
790
817
|
item: {
|
|
791
818
|
id: first.id,
|
|
792
|
-
...
|
|
819
|
+
...spanAt(at),
|
|
793
820
|
path: it.where.path,
|
|
794
821
|
oldPath: it.where.path,
|
|
795
822
|
severity: it.severity,
|
|
@@ -799,6 +826,7 @@ function collectItems(review, ctx, responses) {
|
|
|
799
826
|
resolved: resolved.has(first.id),
|
|
800
827
|
excluded: noComments.has(first.id),
|
|
801
828
|
...changedOf(ctx, first.id),
|
|
829
|
+
...coveredOf(ctx, first.id, at),
|
|
802
830
|
},
|
|
803
831
|
};
|
|
804
832
|
});
|
|
@@ -812,7 +840,7 @@ function collectItems(review, ctx, responses) {
|
|
|
812
840
|
where,
|
|
813
841
|
item: {
|
|
814
842
|
id: q.id,
|
|
815
|
-
...
|
|
843
|
+
...spanAt(commentRows(ctx, q)),
|
|
816
844
|
path: where.path,
|
|
817
845
|
oldPath: where.path,
|
|
818
846
|
severity: q.severity ?? null,
|
|
@@ -832,7 +860,7 @@ function collectItems(review, ctx, responses) {
|
|
|
832
860
|
* Excluded ones are present and marked; whoever posts them skips those.
|
|
833
861
|
*/
|
|
834
862
|
export function reportItems(review, commits, units, opts) {
|
|
835
|
-
const session = folded(review, commits, units, opts
|
|
863
|
+
const session = folded(review, commits, units, opts);
|
|
836
864
|
return collectItems(session.review, session.ctx, opts.responses).map((c) => c.item);
|
|
837
865
|
}
|
|
838
866
|
/** A short list of what the reviewer left out, so nothing vanishes without a trace. */
|
|
@@ -855,9 +883,11 @@ function renderExcluded(collected) {
|
|
|
855
883
|
* taking their unit, and a mark on each whose code changed. Each id appears once; this
|
|
856
884
|
* version's own copy wins.
|
|
857
885
|
*/
|
|
858
|
-
function folded(review, commits, units,
|
|
886
|
+
function folded(review, commits, units, opts) {
|
|
887
|
+
const { earlier } = opts;
|
|
888
|
+
const coverage = opts.coverage === undefined ? {} : { coverage: opts.coverage };
|
|
859
889
|
if (earlier === undefined)
|
|
860
|
-
return { review, ctx: { commits, units } };
|
|
890
|
+
return { review, ctx: { commits, units, ...coverage } };
|
|
861
891
|
const marks = new Map();
|
|
862
892
|
const mark = (id, at) => {
|
|
863
893
|
if (at.since !== null)
|
|
@@ -895,6 +925,7 @@ function folded(review, commits, units, earlier) {
|
|
|
895
925
|
ctx: {
|
|
896
926
|
units,
|
|
897
927
|
marks,
|
|
928
|
+
...coverage,
|
|
898
929
|
commits: commits.map((commit, k) => {
|
|
899
930
|
const mine = lost.filter((c) => owner(c.unit, c.commit) === k);
|
|
900
931
|
if (mine.length === 0)
|
|
@@ -915,7 +946,7 @@ function folded(review, commits, units, earlier) {
|
|
|
915
946
|
* Concerns the reviewer has not decided are left out, as are those dismissed.
|
|
916
947
|
*/
|
|
917
948
|
export function renderReport(full, commits, units, opts) {
|
|
918
|
-
const session = folded(full, commits, units, opts
|
|
949
|
+
const session = folded(full, commits, units, opts);
|
|
919
950
|
const { ctx } = session;
|
|
920
951
|
const excluded = session.review.excluded;
|
|
921
952
|
if (excluded === undefined)
|
|
@@ -942,12 +973,14 @@ function renderIncluded(review, ctx, opts) {
|
|
|
942
973
|
const issues = walk ? rest : rest.filter((c) => c.severity !== undefined);
|
|
943
974
|
const feedback = walk ? [] : rest.filter((c) => c.severity === undefined);
|
|
944
975
|
const items = buildItems(ctx, keptConcerns(review, commits), issues);
|
|
976
|
+
// Keyed by path as the items name it, so a file's heading finds its count.
|
|
977
|
+
const tallies = new Map(ctx.coverage === undefined ? [] : changedLines(units, ctx.coverage));
|
|
945
978
|
const out = [];
|
|
946
979
|
if (walk) {
|
|
947
980
|
out.push(`# Code review: ${opts.branch} (${commits.length === 1 ? "1 commit" : `${commits.length} commits`}, ${summarizeIssues(items)})`, "");
|
|
948
981
|
if (items.length === 0 && questions.length === 0)
|
|
949
982
|
out.push("The reviewer raised no issues.", "");
|
|
950
|
-
out.push(...renderItems(items, units, 2));
|
|
983
|
+
out.push(...renderItems(items, units, 2, tallies));
|
|
951
984
|
}
|
|
952
985
|
else {
|
|
953
986
|
out.push(`# Review of plan v${review.planVersion}`, "");
|
|
@@ -960,7 +993,7 @@ function renderIncluded(review, ctx, opts) {
|
|
|
960
993
|
}
|
|
961
994
|
out.push(...renderPlanFeedback(ctx, feedback));
|
|
962
995
|
if (items.length > 0) {
|
|
963
|
-
out.push(`## Code issues (${summarizeIssues(items)})`, "", ...renderItems(items, units, 3));
|
|
996
|
+
out.push(`## Code issues (${summarizeIssues(items)})`, "", ...renderItems(items, units, 3, tallies));
|
|
964
997
|
}
|
|
965
998
|
}
|
|
966
999
|
if (questions.length > 0)
|
|
@@ -1016,6 +1049,8 @@ function renderEvent(event, ctx) {
|
|
|
1016
1049
|
return [`- ${event.commentId} ${event.resolved ? "resolved" : "reopened"}`];
|
|
1017
1050
|
case "version":
|
|
1018
1051
|
return [`- plan v${event.version} is open`];
|
|
1052
|
+
case "coverage":
|
|
1053
|
+
return [`- the page shows coverage from ${event.tip.slice(0, 12)}`];
|
|
1019
1054
|
case "submitted":
|
|
1020
1055
|
return ["- the reviewer submitted the review"];
|
|
1021
1056
|
case "ended":
|
|
@@ -1065,3 +1100,25 @@ export function renderShownConcern(concern, commit, ctx) {
|
|
|
1065
1100
|
"",
|
|
1066
1101
|
].join("\n");
|
|
1067
1102
|
}
|
|
1103
|
+
function utf8(latin1) {
|
|
1104
|
+
return Buffer.from(latin1, "latin1").toString("utf8");
|
|
1105
|
+
}
|
|
1106
|
+
/** Where a name is defined, each with the commit it lands in and its first lines at the tip. */
|
|
1107
|
+
export function renderShownDefinitions(name, candidates) {
|
|
1108
|
+
if (candidates.length === 0)
|
|
1109
|
+
return `no definition of ${name} at the tip\n`;
|
|
1110
|
+
return `${candidates
|
|
1111
|
+
.flatMap((c) => {
|
|
1112
|
+
const where = c.commit !== null
|
|
1113
|
+
? `in commit ${c.commit + 1}`
|
|
1114
|
+
: c.unit !== null
|
|
1115
|
+
? `in ${c.unit}, which no commit of the plan takes`
|
|
1116
|
+
: "unchanged by the branch";
|
|
1117
|
+
const width = String(c.newLine + c.preview.length - 1).length;
|
|
1118
|
+
return [
|
|
1119
|
+
`${utf8(c.path)}:${c.newLine} ${c.kind}, ${where}`,
|
|
1120
|
+
...c.preview.map((l, i) => ` ${String(c.newLine + i).padStart(width)} ${utf8(l)}`),
|
|
1121
|
+
];
|
|
1122
|
+
})
|
|
1123
|
+
.join("\n")}\n`;
|
|
1124
|
+
}
|
package/dist/src/server.js
CHANGED
|
@@ -5,10 +5,13 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { basename, isAbsolute, join } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { isReplyId, parseComment, parseDraft, parseNewDecision, parseNewReply, } from "./comments.js";
|
|
8
|
+
import { serializeCoverage, staleFiles } from "./coverage.js";
|
|
9
|
+
import { definitionIndex, isIdentifier } from "./definition.js";
|
|
10
|
+
import { isRepoPath } from "./diff.js";
|
|
8
11
|
import { carryConcerns, carrySession } from "./evolve.js";
|
|
9
12
|
import { isConcernId, isPlanVersion } from "./plan.js";
|
|
10
13
|
import { renderReport, reportItems } from "./prompt.js";
|
|
11
|
-
import { isLockToken } from "./session.js";
|
|
14
|
+
import { coverageAt, isLockToken, readCoverageIn, SessionFileError } from "./session.js";
|
|
12
15
|
/** The requested port is taken; host.ts decides whether that is our server or a stranger. */
|
|
13
16
|
export class PortInUseError extends Error {
|
|
14
17
|
name = "PortInUseError";
|
|
@@ -255,6 +258,7 @@ function viewOf(registration, loaded, draft, earlier = loaded.earlier) {
|
|
|
255
258
|
unitIds: new Set(state.units.map((u) => u.id)),
|
|
256
259
|
hunkRows: new Map(state.units.flatMap((u) => u.kind === "hunk" ? [[u.id, u.body.length]] : [])),
|
|
257
260
|
concernIds: new Set(state.commits.flatMap((c) => (c.concerns ?? []).map((k) => k.id))),
|
|
261
|
+
definitions: definitionIndex(loaded.repo, state.anchor.tip, new Map(state.units.map((u) => [u.id, u])), state.commits),
|
|
258
262
|
draftFile: draftPath(reviewsDir, state.version),
|
|
259
263
|
reviewFile: join(reviewsDir, `v${state.version}.json`),
|
|
260
264
|
};
|
|
@@ -468,25 +472,44 @@ function hostSession(id, registration, loaded, draft, load) {
|
|
|
468
472
|
...carriedRefs(view),
|
|
469
473
|
};
|
|
470
474
|
};
|
|
471
|
-
const reportOptions = () => {
|
|
475
|
+
const reportOptions = async () => {
|
|
472
476
|
const comments = sessionOf(view);
|
|
477
|
+
const { repo, state } = view.loaded;
|
|
478
|
+
const coverage = await coverageAt(repo, view.registration.dir, state.anchor.tip);
|
|
473
479
|
return {
|
|
474
|
-
branch:
|
|
480
|
+
branch: state.anchor.branch,
|
|
475
481
|
responses: [],
|
|
476
482
|
...(comments === null ? {} : { earlier: { comments, concerns: view.earlierConcerns } }),
|
|
483
|
+
...(coverage === null ? {} : { coverage }),
|
|
477
484
|
};
|
|
478
485
|
};
|
|
479
486
|
const unitsOf = () => new Map(view.loaded.state.units.map((u) => [u.id, u]));
|
|
480
|
-
const report = (finished) => renderReport(finished, view.loaded.state.commits, unitsOf(), reportOptions());
|
|
487
|
+
const report = async (finished) => renderReport(finished, view.loaded.state.commits, unitsOf(), await reportOptions());
|
|
481
488
|
/** Writes reviews/vN.json and its report once, and removes the autosave. */
|
|
482
489
|
const writeReview = async (finished) => {
|
|
483
490
|
await writes;
|
|
484
491
|
await mkdir(view.loaded.reviewsDir, { recursive: true });
|
|
485
492
|
await writeAtomic(view.reviewFile, `${JSON.stringify(finished, null, 2)}\n`);
|
|
486
|
-
await writeAtomic(view.reviewFile.replace(/\.json$/, ".md"), report(finished));
|
|
493
|
+
await writeAtomic(view.reviewFile.replace(/\.json$/, ".md"), await report(finished));
|
|
487
494
|
await rm(view.draftFile, { force: true });
|
|
488
495
|
return view.reviewFile;
|
|
489
496
|
};
|
|
497
|
+
/** coverage.json as the agent last wrote it, or a 404 before its first run. */
|
|
498
|
+
const coverageFile = () => {
|
|
499
|
+
let coverage;
|
|
500
|
+
try {
|
|
501
|
+
coverage = readCoverageIn(view.registration.dir);
|
|
502
|
+
}
|
|
503
|
+
catch (error) {
|
|
504
|
+
if (error instanceof SessionFileError)
|
|
505
|
+
throw new HttpError(500, error.message);
|
|
506
|
+
throw error;
|
|
507
|
+
}
|
|
508
|
+
if (coverage === null) {
|
|
509
|
+
throw new HttpError(404, "no coverage yet; the agent records it with follow-me coverage");
|
|
510
|
+
}
|
|
511
|
+
return coverage;
|
|
512
|
+
};
|
|
490
513
|
const hasWork = () => view.comments.size > 0 || view.decisions.size > 0 || view.earlierReplies.size > 0;
|
|
491
514
|
return {
|
|
492
515
|
id,
|
|
@@ -544,7 +567,28 @@ function hostSession(id, registration, loaded, draft, load) {
|
|
|
544
567
|
return waitForEvents(since, timeoutMs, signal);
|
|
545
568
|
},
|
|
546
569
|
report: () => report(review()),
|
|
547
|
-
|
|
570
|
+
/**
|
|
571
|
+
* The agent's last test run, read from disk so a restarted server serves it too, with the
|
|
572
|
+
* covered files whose content changed since its tip. A new plan for the same tip stales nothing.
|
|
573
|
+
*/
|
|
574
|
+
coverage: async () => {
|
|
575
|
+
const coverage = coverageFile();
|
|
576
|
+
const stale = await staleFiles(view.loaded.repo, coverage, view.loaded.state.anchor.tip);
|
|
577
|
+
return { ...serializeCoverage(coverage), stale };
|
|
578
|
+
},
|
|
579
|
+
/** The agent wrote coverage.json; the page hears it as one event and reloads its marks. */
|
|
580
|
+
coverageTaken: () => {
|
|
581
|
+
open();
|
|
582
|
+
const { tip, takenAt } = coverageFile();
|
|
583
|
+
emit({ type: "coverage", tip, takenAt });
|
|
584
|
+
return { tip, takenAt };
|
|
585
|
+
},
|
|
586
|
+
/** The page reads a definition the plan shows from its hunk, so only others carry a preview. */
|
|
587
|
+
define: async (name, from) => {
|
|
588
|
+
const found = await view.definitions.lookup(name, from !== null && isRepoPath(from) ? from : null);
|
|
589
|
+
return found.map((c) => (c.unit === null ? c : { ...c, preview: [] }));
|
|
590
|
+
},
|
|
591
|
+
reportItems: async () => reportItems(review(), view.loaded.state.commits, unitsOf(), await reportOptions()),
|
|
548
592
|
add: async (raw) => {
|
|
549
593
|
open();
|
|
550
594
|
if (!isRecord(raw))
|
|
@@ -943,12 +987,25 @@ export function startServer(opts) {
|
|
|
943
987
|
const timeout = queryInt(url, "timeout", 0, MAX_POLL_MS, DEFAULT_POLL_MS);
|
|
944
988
|
return sendJson(res, 200, await session.poll(since, timeout, abort.signal));
|
|
945
989
|
}
|
|
990
|
+
if (method === "GET" && rest === "/api/definition") {
|
|
991
|
+
const name = url.searchParams.get("name") ?? "";
|
|
992
|
+
if (!isIdentifier(name))
|
|
993
|
+
throw new HttpError(400, "name must be an identifier");
|
|
994
|
+
const candidates = await session.define(name, url.searchParams.get("from"));
|
|
995
|
+
return sendJson(res, 200, { candidates });
|
|
996
|
+
}
|
|
997
|
+
if (method === "GET" && rest === "/api/coverage") {
|
|
998
|
+
return sendJson(res, 200, await session.coverage());
|
|
999
|
+
}
|
|
1000
|
+
if (method === "POST" && rest === "/api/coverage") {
|
|
1001
|
+
return sendJson(res, 200, session.coverageTaken());
|
|
1002
|
+
}
|
|
946
1003
|
if (method === "GET" && rest === "/api/report" && url.searchParams.get("format") === "json") {
|
|
947
|
-
return sendJson(res, 200, session.reportItems());
|
|
1004
|
+
return sendJson(res, 200, await session.reportItems());
|
|
948
1005
|
}
|
|
949
1006
|
if (method === "GET" && rest === "/api/report") {
|
|
950
1007
|
res.writeHead(200, { "content-type": "text/markdown; charset=utf-8" });
|
|
951
|
-
res.end(session.report());
|
|
1008
|
+
res.end(await session.report());
|
|
952
1009
|
return;
|
|
953
1010
|
}
|
|
954
1011
|
if (method === "POST" && rest === "/api/version") {
|
package/dist/src/session.js
CHANGED
|
@@ -2,6 +2,7 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { existsSync, linkSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
3
3
|
import { basename, dirname, join } from "node:path";
|
|
4
4
|
import { parseDraft, parseReview } from "./comments.js";
|
|
5
|
+
import { freshFiles, parseCoverage, serializeCoverage, staleFiles } from "./coverage.js";
|
|
5
6
|
import { isSha } from "./diff.js";
|
|
6
7
|
import { evolvePlan } from "./evolve.js";
|
|
7
8
|
import { branchExists, currentBranch, defaultBaseRef, GitError, loadDiff, objectsDir, QUARANTINE_PREFIX, repoRoot, resolveCommit, } from "./git.js";
|
|
@@ -200,6 +201,36 @@ function parseApplied(v) {
|
|
|
200
201
|
return null;
|
|
201
202
|
return { schema, version, branch, head, hash, signed };
|
|
202
203
|
}
|
|
204
|
+
function coveragePath(dir) {
|
|
205
|
+
return join(dir, "coverage.json");
|
|
206
|
+
}
|
|
207
|
+
/** The coverage.json of the session in `dir`, or null when there is none. */
|
|
208
|
+
export function readCoverageIn(dir) {
|
|
209
|
+
const path = coveragePath(dir);
|
|
210
|
+
const raw = readJson(path);
|
|
211
|
+
if (raw === undefined)
|
|
212
|
+
return null;
|
|
213
|
+
const coverage = parseCoverage(raw);
|
|
214
|
+
if (typeof coverage === "string")
|
|
215
|
+
throw new SessionFileError(`${path}: ${coverage}`);
|
|
216
|
+
return coverage;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* What the report counts: the files of the session's test run that still describe `tip`. Null
|
|
220
|
+
* without a run, and when coverage.json cannot be read, so a report never fails on it.
|
|
221
|
+
*/
|
|
222
|
+
export async function coverageAt(repo, dir, tip) {
|
|
223
|
+
let coverage;
|
|
224
|
+
try {
|
|
225
|
+
coverage = readCoverageIn(dir);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (error instanceof SessionFileError)
|
|
229
|
+
return null;
|
|
230
|
+
throw error;
|
|
231
|
+
}
|
|
232
|
+
return coverage === null ? null : freshFiles(coverage, await staleFiles(repo, coverage, tip));
|
|
233
|
+
}
|
|
203
234
|
function makeSession(root, dir, anchor, diff) {
|
|
204
235
|
const plansDir = join(dir, "plans");
|
|
205
236
|
const splitReviews = join(dir, "reviews");
|
|
@@ -310,6 +341,8 @@ function makeSession(root, dir, anchor, diff) {
|
|
|
310
341
|
writeJson(join(plansDir, `v${version}.json`), serializePlanFile(plan.file));
|
|
311
342
|
return version;
|
|
312
343
|
},
|
|
344
|
+
coverage: () => readCoverageIn(dir),
|
|
345
|
+
writeCoverage: (coverage) => writeJson(coveragePath(dir), serializeCoverage(coverage)),
|
|
313
346
|
lock: (command) => acquireLock(dir, anchor.branch, command),
|
|
314
347
|
adoptLock: (from, command) => adoptLock(dir, from, command),
|
|
315
348
|
lockState: () => readLock(join(dir, "lock")),
|
package/dist/src/types.js
CHANGED
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* session.ts the .follow-me/<slug>/ layout: session anchor, plans, reviews, draft.
|
|
11
11
|
* comments.ts the wire formats of review state: comments, replies, concern decisions, the
|
|
12
12
|
* review file, the autosaved draft and the server's events. Pure parsers.
|
|
13
|
+
* definition.ts where an identifier is defined at the branch tip, and where that lands in the
|
|
14
|
+
* plan: per-language patterns, ranking, placement and a per-tip cache.
|
|
15
|
+
* coverage.ts lcov from the agent's test run: parse, place at the tip, merge runs, count the
|
|
16
|
+
* changed lines covered, and which files went stale since.
|
|
13
17
|
* prompt.ts the planner prompt for an agent, the feedback and the consolidated report.
|
|
14
18
|
* server.ts the review server: a registry of hosted sessions, their JSON API, and ui/.
|
|
15
19
|
* host.ts the one server per user: its record in the state dir, attach, start, stop.
|
package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-8UsLPznO.js → abnfDiagram-WEYOIA3C-cHEJvcBn.js}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{m as e,t}from"./mermaid-parser.core-
|
|
1
|
+
import{m as e,t}from"./mermaid-parser.core-CMPNc-u6.js";import{n}from"./chunk-Y2CYZVJY-DsF7k-Jl.js";import{m as r}from"./src-BiC9XNcI.js";import"./chunk-DSNLSHJZ-Bk8WO5JF.js";import{n as i,r as a,t as o}from"./chunk-2E7WHVMD-BTet9STn.js";import{t as s}from"./chunk-JWPE2WC7-DVXcaiue.js";import"./mermaid.core-Nd_xOg2y.js";var c=e().RailroadAbnf.parser.LangiumParser,l=n(e=>{let t=e.alternatives.map(u);return t.length===1?t[0]:{type:`choice`,alternatives:t}},`transformAlternation`),u=n(e=>{let t=e.elements.map(f);return t.length===1?t[0]:{type:`sequence`,elements:t}},`transformConcatenation`),d=n(e=>{if(e.includes(`*`)){let[t,n]=e.split(`*`);return{min:t?parseInt(t,10):0,max:n?parseInt(n,10):1/0}}let t=parseInt(e,10);return{min:t,max:t}},`parseRepeat`),f=n(e=>{let t=p(e.primary);if(!e.repeat)return t;let{min:n,max:r}=d(e.repeat);return n===0&&r===1?{type:`optional`,element:t}:{type:`repetition`,element:t,min:n,max:r}},`transformElement`),p=n(e=>{switch(e.$type){case`AbnfStringLiteral`:return{type:`terminal`,value:e.value};case`AbnfNumVal`:return{type:`terminal`,value:e.value};case`AbnfRuleName`:return{type:`nonterminal`,name:e.name};case`AbnfGroup`:return l(e.element);case`AbnfOptionalGroup`:return{type:`optional`,element:l(e.element)};default:throw Error(`Unsupported ABNF primary node: ${e.$type}`)}},`transformPrimary`),m=n(e=>({name:e.name,definition:l(e.definition)}),`transformRule`),h=n(e=>{s(e,o),e.title&&o.setTitle(e.title),e.rules.map(e=>o.addRule(m(e)))},`populateDb`),g={parser:{parse:n(e=>{o.clear(),r.debug(`[ABNF Parser] Starting Langium parse`);let n=c.parse(e);if(n.lexerErrors.length>0||n.parserErrors.length>0)throw new t(n);let i=n.value;r.debug(`[ABNF Parser] Parsed rules:`,i.rules.length),h(i),r.debug(`[ABNF Parser] Parse complete`)},`parse`),parser:{yy:o}},db:o,renderer:a,styles:i};export{g as diagram};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-
|
|
1
|
+
import{n as e,t}from"./path-fybaL0A-.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-CHZ9mrl_.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i){if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
|