@shiyuphan/follow-me 0.5.0 → 0.6.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 +6 -3
- package/dist/src/cli.js +35 -11
- package/dist/src/comments.js +62 -9
- package/dist/src/definition.js +226 -0
- package/dist/src/evolve.js +145 -29
- package/dist/src/git.js +69 -13
- package/dist/src/host.js +12 -3
- package/dist/src/plan.js +26 -1
- package/dist/src/prompt.js +122 -12
- package/dist/src/server.js +120 -28
- package/dist/src/session.js +23 -0
- package/dist/src/types.js +2 -0
- package/dist/ui/dist/assets/{abnfDiagram-WEYOIA3C-BXkLs7mV.js → abnfDiagram-WEYOIA3C-fYSYLdMl.js} +1 -1
- package/dist/ui/dist/assets/{arc-DNzjw7Ie.js → arc-DGuGSnwa.js} +1 -1
- package/dist/ui/dist/assets/{architecture-WOLXFQ4H-D66kgJJt.js → architecture-WOLXFQ4H-VFox_hV1.js} +1 -1
- package/dist/ui/dist/assets/{architectureDiagram-ZHVYKT53-D8UWuhQr.js → architectureDiagram-ZHVYKT53-CBVK3nHZ.js} +1 -1
- package/dist/ui/dist/assets/{blockDiagram-RJAE7QA3-BLkmr62i.js → blockDiagram-RJAE7QA3-BFdsIWEr.js} +1 -1
- package/dist/ui/dist/assets/{c4Diagram-XDPPPE54-3rZPYwOc.js → c4Diagram-XDPPPE54-BGntcPId.js} +1 -1
- package/dist/ui/dist/assets/channel-gz6cS9Z1.js +1 -0
- package/dist/ui/dist/assets/{chunk-2E7WHVMD-MMnvAgpB.js → chunk-2E7WHVMD-CNMuK9zj.js} +1 -1
- package/dist/ui/dist/assets/{chunk-4EMNKR2T-D9FGWA3l.js → chunk-4EMNKR2T-ByeTTJWS.js} +1 -1
- package/dist/ui/dist/assets/{chunk-55XCU76K-DuOUaV8F.js → chunk-55XCU76K-99xE7cLH.js} +1 -1
- package/dist/ui/dist/assets/{chunk-7S6XSTFO-p3YWPv0m.js → chunk-7S6XSTFO-ZVmT7eMZ.js} +1 -1
- package/dist/ui/dist/assets/{chunk-AQDTSKZB-DWfKYjwk.js → chunk-AQDTSKZB-B981JORL.js} +1 -1
- package/dist/ui/dist/assets/{chunk-DSNLSHJZ-C3CIwpv0.js → chunk-DSNLSHJZ-B2z7FX4h.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HAGFOTED-Bwfl9gme.js → chunk-HAGFOTED-CHFbBS-4.js} +1 -1
- package/dist/ui/dist/assets/{chunk-HNE4Q36P-0_oeU0le.js → chunk-HNE4Q36P-rq11xv9X.js} +1 -1
- package/dist/ui/dist/assets/{chunk-NQOKRVIF-z_LPPPx5.js → chunk-NQOKRVIF-BYmg9kSX.js} +1 -1
- package/dist/ui/dist/assets/{chunk-UHW7PFBP-BH28V2yu.js → chunk-UHW7PFBP-JvL9IXWg.js} +1 -1
- package/dist/ui/dist/assets/{chunk-VQ5W62QI-BXDajDf7.js → chunk-VQ5W62QI-CYMQaAKn.js} +1 -1
- package/dist/ui/dist/assets/{chunk-WQQL4CDK-BphBlfKj.js → chunk-WQQL4CDK-CXnR0PHT.js} +1 -1
- package/dist/ui/dist/assets/{chunk-XXDRQBXY-BiCT5NwV.js → chunk-XXDRQBXY-CDUWJCXi.js} +1 -1
- package/dist/ui/dist/assets/{chunk-YZO67HFG-Bg5Pu8BX.js → chunk-YZO67HFG-mZfXIx0h.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZAUX2LTX-C_Tgd8tB.js → chunk-ZAUX2LTX-DBxVTCHX.js} +1 -1
- package/dist/ui/dist/assets/{chunk-ZCHMQSOR-MBRcMi8-.js → chunk-ZCHMQSOR-DrWA4IBo.js} +1 -1
- package/dist/ui/dist/assets/{classDiagram-v2-4MYWR3UA-1wyeCe5y.js → classDiagram-v2-4MYWR3UA-Beah5uxA.js} +1 -1
- package/dist/ui/dist/assets/{cose-bilkent-JH36ORCC-BtOYclF6.js → cose-bilkent-JH36ORCC-UaE-jnHk.js} +1 -1
- package/dist/ui/dist/assets/{cynefin-EF2NZ3EQ-dJmoOG8g.js → cynefin-EF2NZ3EQ-DKfGaCR8.js} +1 -1
- package/dist/ui/dist/assets/{cynefinDiagram-WF24UDRP-AloyqbEN.js → cynefinDiagram-WF24UDRP-tTUzTs3D.js} +1 -1
- package/dist/ui/dist/assets/{dagre-NK4GIZVX-NCZhdnfb.js → dagre-NK4GIZVX-jSn35_T7.js} +1 -1
- package/dist/ui/dist/assets/{diagram-CBK3MWTC-D8w7x5Gb.js → diagram-CBK3MWTC-7W2uw8Zc.js} +1 -1
- package/dist/ui/dist/assets/{diagram-I5XIIV2D-BUFnCGlp.js → diagram-I5XIIV2D-CcfyQVh9.js} +1 -1
- package/dist/ui/dist/assets/{diagram-ODIL2UCW-BULmHKaW.js → diagram-ODIL2UCW-BjGvaLis.js} +1 -1
- package/dist/ui/dist/assets/{diagram-PFBEJUXE-B5MqFo0r.js → diagram-PFBEJUXE-jvdCrt8B.js} +1 -1
- package/dist/ui/dist/assets/{diagram-RJA3I4VV-BIkDLL8E.js → diagram-RJA3I4VV-D65_Vpi_.js} +1 -1
- package/dist/ui/dist/assets/{diagram-TYUNFSNR-Ca4gsQV8.js → diagram-TYUNFSNR-C52roQBE.js} +1 -1
- package/dist/ui/dist/assets/{dist-G85JKY5k.js → dist-BJd4jp9Y.js} +1 -1
- package/dist/ui/dist/assets/{ebnfDiagram-V4P3TTPT-rVjcFFtb.js → ebnfDiagram-V4P3TTPT-DXysoIBq.js} +1 -1
- package/dist/ui/dist/assets/{elk-QONTBCTQ-BWPdvULh.js → elk-QONTBCTQ-BVZcsgV3.js} +1 -1
- package/dist/ui/dist/assets/{erDiagram-KYQIGQAA-BPxJJkRl.js → erDiagram-KYQIGQAA-BJnT4fVU.js} +1 -1
- package/dist/ui/dist/assets/{eventmodeling-K75KTNOO-BSB9WYV3.js → eventmodeling-K75KTNOO-C5MzxCMY.js} +1 -1
- package/dist/ui/dist/assets/flowDiagram-IUGLLWBU-BqUgZSCz.js +1 -0
- package/dist/ui/dist/assets/{ganttDiagram-R7GPNTJY-DKps530K.js → ganttDiagram-R7GPNTJY-CZRrnuah.js} +1 -1
- package/dist/ui/dist/assets/{gitGraph-VSP46ZUC-D1d4UAFV.js → gitGraph-VSP46ZUC-l58aGSkh.js} +1 -1
- package/dist/ui/dist/assets/{gitGraphDiagram-PDYL7XHY-N55yl6hc.js → gitGraphDiagram-PDYL7XHY-D4ks1WO-.js} +1 -1
- package/dist/ui/dist/assets/index-BW95UA5q.css +1 -0
- package/dist/ui/dist/assets/index-DOGXgoHF.js +134 -0
- package/dist/ui/dist/assets/{info-OHQRW6UA-CgxE3gf9.js → info-OHQRW6UA-BSqfzkdk.js} +1 -1
- package/dist/ui/dist/assets/{infoDiagram-MVDRKAX5-DOnUrnMf.js → infoDiagram-MVDRKAX5-B6NgVF_N.js} +1 -1
- package/dist/ui/dist/assets/{ishikawaDiagram-HPEHV2GE-BIY0fFHB.js → ishikawaDiagram-HPEHV2GE-DvtfssEZ.js} +1 -1
- package/dist/ui/dist/assets/{journeyDiagram-BOSHW7O7-CqA2Ern0.js → journeyDiagram-BOSHW7O7-DM64gT5v.js} +1 -1
- package/dist/ui/dist/assets/{kanban-definition-FNSA4HC5-vGrpQKIF.js → kanban-definition-FNSA4HC5-AliyRJHy.js} +1 -1
- package/dist/ui/dist/assets/{line-Brzv3qA6.js → line-CHncXbKO.js} +1 -1
- package/dist/ui/dist/assets/{linear-D9uIlqyh.js → linear-BWEWydI0.js} +1 -1
- package/dist/ui/dist/assets/{mermaid-parser.core-Bz9b-Thu.js → mermaid-parser.core-DGpOP8AM.js} +3 -3
- package/dist/ui/dist/assets/{mermaid.core-BaZipu3b.js → mermaid.core-BJtBFbJx.js} +4 -4
- package/dist/ui/dist/assets/{mindmap-definition-6OY57UG3-D6aie7uH.js → mindmap-definition-6OY57UG3-NjavnWBi.js} +1 -1
- package/dist/ui/dist/assets/{packet-JDAUHWVQ-28r3jVFY.js → packet-JDAUHWVQ-zuhpVsME.js} +1 -1
- package/dist/ui/dist/assets/{pegDiagram-ECIUJNBX-CbGvD4qP.js → pegDiagram-ECIUJNBX-sL4grM97.js} +1 -1
- package/dist/ui/dist/assets/{pie-XZMESJXO-DHpcGnZd.js → pie-XZMESJXO-C9l1Mp09.js} +1 -1
- package/dist/ui/dist/assets/{pieDiagram-GNMPY74T-DuhauwnY.js → pieDiagram-GNMPY74T-rlRBr2U4.js} +1 -1
- package/dist/ui/dist/assets/{prism-core-CbZMOvrb.js → prism-core-DmuST30i.js} +1 -1
- package/dist/ui/dist/assets/{quadrantDiagram-D57K4MC3-BfyNmVca.js → quadrantDiagram-D57K4MC3-4oQMip2Q.js} +1 -1
- package/dist/ui/dist/assets/{radar-ABXABTNO-Xp1TYwaj.js → radar-ABXABTNO-DvGg96L7.js} +1 -1
- package/dist/ui/dist/assets/{railroad-I3PHUGI6-c5FY4wwa.js → railroad-I3PHUGI6-8wdFKMZp.js} +1 -1
- package/dist/ui/dist/assets/{railroad-abnf-LNEFI6M7-RDyWXqcL.js → railroad-abnf-LNEFI6M7-DUtOO0gn.js} +1 -1
- package/dist/ui/dist/assets/{railroad-ebnf-DWYD2UWJ-CLD4jVou.js → railroad-ebnf-DWYD2UWJ-5YCGY1_d.js} +1 -1
- package/dist/ui/dist/assets/{railroad-peg-JR7BVNR7-17EYTA-w.js → railroad-peg-JR7BVNR7-Bwywxjhs.js} +1 -1
- package/dist/ui/dist/assets/{railroadDiagram-Y72NRBL7-C4p2wtKe.js → railroadDiagram-Y72NRBL7-CwO6HSNQ.js} +1 -1
- package/dist/ui/dist/assets/{requirementDiagram-EQYSW5RA-DTYIFN8T.js → requirementDiagram-EQYSW5RA-Cp0IbfNr.js} +1 -1
- package/dist/ui/dist/assets/{sankeyDiagram-SY24JHO5-YDYhRyJU.js → sankeyDiagram-SY24JHO5-DeQKsO68.js} +1 -1
- package/dist/ui/dist/assets/{sequenceDiagram-BLEHNHQU-Crvy6IlU.js → sequenceDiagram-BLEHNHQU-DTDalCxF.js} +1 -1
- package/dist/ui/dist/assets/{src-BPod-Ype.js → src-XnrqevnL.js} +1 -1
- package/dist/ui/dist/assets/{stateDiagram-v2-A6IESKWZ-BWr70YOQ.js → stateDiagram-v2-A6IESKWZ-2DZ-tKo9.js} +1 -1
- package/dist/ui/dist/assets/{swimlanes-B3ENRKFQ-DvbG4I9a.js → swimlanes-B3ENRKFQ-66yZJeRU.js} +1 -1
- package/dist/ui/dist/assets/swimlanesDiagram-AYQRV4AK-rbX6bYG4.js +8 -0
- package/dist/ui/dist/assets/{timeline-definition-JXYKGLGB-CR950Bzb.js → timeline-definition-JXYKGLGB-Dm1xGU-d.js} +1 -1
- package/dist/ui/dist/assets/{treeView-D4JQ5SDB-DSDDXGrk.js → treeView-D4JQ5SDB-BvgsbSot.js} +1 -1
- package/dist/ui/dist/assets/{treemap-SAJKECNS-Ct5w2_RR.js → treemap-SAJKECNS-C3lnbG1a.js} +1 -1
- package/dist/ui/dist/assets/{usecaseDiagram-WPOLBNNX-_eP0371y.js → usecaseDiagram-WPOLBNNX-B6iPNIhZ.js} +1 -1
- package/dist/ui/dist/assets/{vennDiagram-Q3Y3IDCW-Cu-IA57L.js → vennDiagram-Q3Y3IDCW-D5dgflg9.js} +1 -1
- package/dist/ui/dist/assets/{wardley-7MLQ67FV-r8yd6frN.js → wardley-7MLQ67FV-Dn9mLIB4.js} +1 -1
- package/dist/ui/dist/assets/{wardleyDiagram-IVAIDKWA-CBUUiRQm.js → wardleyDiagram-IVAIDKWA-W1EwzN_X.js} +1 -1
- package/dist/ui/dist/assets/{xychartDiagram-4URNHDU4-DEVQjvgG.js → xychartDiagram-4URNHDU4-CKRzMUvp.js} +1 -1
- package/dist/ui/dist/index.html +2 -2
- package/package.json +1 -1
- package/skills/follow-me/SKILL.md +3 -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-B8TYcxF8.css +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/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,76 @@ 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
|
+
/**
|
|
136
|
+
* Lines of the commit's tree, not the working tree, that match any of the POSIX extended
|
|
137
|
+
* patterns, in files matching the pathspecs. Binary files are skipped.
|
|
138
|
+
*/
|
|
139
|
+
export async function grepTip(repo, commit, patterns, pathspecs) {
|
|
140
|
+
const args = [
|
|
141
|
+
"grep",
|
|
142
|
+
"-n",
|
|
143
|
+
"-E",
|
|
144
|
+
"-I",
|
|
145
|
+
"-z",
|
|
146
|
+
"--no-color",
|
|
147
|
+
"--no-column",
|
|
148
|
+
...patterns.flatMap((p) => ["-e", p]),
|
|
149
|
+
commit,
|
|
150
|
+
"--",
|
|
151
|
+
...pathspecs,
|
|
152
|
+
];
|
|
153
|
+
let out;
|
|
154
|
+
try {
|
|
155
|
+
out = await git(repo, args);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
// grep exits 1 when nothing matched.
|
|
159
|
+
if (error instanceof GitError && error.status === 1)
|
|
160
|
+
return [];
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
const prefix = `${commit}:`;
|
|
164
|
+
return out
|
|
165
|
+
.toString("latin1")
|
|
166
|
+
.split("\n")
|
|
167
|
+
.flatMap((record) => {
|
|
168
|
+
const [name = "", line = "", ...text] = record.split("\0");
|
|
169
|
+
const path = name.slice(prefix.length);
|
|
170
|
+
if (!name.startsWith(prefix) || !isRepoPath(path) || !/^\d+$/.test(line))
|
|
171
|
+
return [];
|
|
172
|
+
return [{ path, line: Number(line), text: text.join("\0") }];
|
|
173
|
+
});
|
|
174
|
+
}
|
|
119
175
|
const DIFF_FLAGS = ["--no-renames", "--full-index", "--no-color", "--no-ext-diff", "--no-textconv"];
|
|
120
176
|
export async function loadDiff(repo, baseRef, tipRef) {
|
|
121
177
|
const [tip, baseCommit] = await Promise.all([
|
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
|
@@ -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) {
|
|
@@ -90,6 +94,20 @@ function parseConcern(v, onUnit) {
|
|
|
90
94
|
return "lines: expected a list of change indices";
|
|
91
95
|
return { severity, kind, text, lines: [...new Set(indices)].sort((a, b) => a - b) };
|
|
92
96
|
}
|
|
97
|
+
/** A concern as validation placed it, stored beside a review: with its id and its unit. */
|
|
98
|
+
export function parsePlacedConcern(v) {
|
|
99
|
+
if (!isFields(v))
|
|
100
|
+
return "expected an object";
|
|
101
|
+
if (!isConcernId(v.id))
|
|
102
|
+
return "id: expected a concern id";
|
|
103
|
+
if (v.unit !== undefined && !(typeof v.unit === "string" && isUnitId(v.unit))) {
|
|
104
|
+
return "unit: expected a unit id";
|
|
105
|
+
}
|
|
106
|
+
const concern = parseConcern(v, v.unit !== undefined);
|
|
107
|
+
if (typeof concern === "string")
|
|
108
|
+
return concern;
|
|
109
|
+
return { ...concern, id: v.id, ...(v.unit === undefined ? {} : { unit: v.unit }) };
|
|
110
|
+
}
|
|
93
111
|
/** Absent, null and [] all mean no concerns, so a planner may always write the field. */
|
|
94
112
|
function parseConcerns(v, onUnit) {
|
|
95
113
|
if (v === undefined || v === null)
|
|
@@ -209,6 +227,10 @@ export function parsePlanFile(draft) {
|
|
|
209
227
|
}
|
|
210
228
|
if (typeof c.message !== "string")
|
|
211
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
|
+
}
|
|
212
234
|
const extras = parseExtras(c, false);
|
|
213
235
|
if ("error" in extras)
|
|
214
236
|
bad(`${at}.${extras.field}`, extras.error);
|
|
@@ -225,7 +247,8 @@ export function parsePlanFile(draft) {
|
|
|
225
247
|
}
|
|
226
248
|
}
|
|
227
249
|
if (typeof c.message === "string" && !("error" in extras)) {
|
|
228
|
-
|
|
250
|
+
const placed = isCommitSection(section) ? { section } : {};
|
|
251
|
+
commits.push({ message: c.message, ...placed, ...extras, units });
|
|
229
252
|
}
|
|
230
253
|
}
|
|
231
254
|
}
|
|
@@ -273,6 +296,7 @@ export function serializePlanFile(file) {
|
|
|
273
296
|
tip: file.tip,
|
|
274
297
|
commits: file.commits.map((c) => ({
|
|
275
298
|
message: c.message,
|
|
299
|
+
...(c.section === undefined ? {} : { section: c.section }),
|
|
276
300
|
...serializeExtras(c),
|
|
277
301
|
units: c.units.map((ref) => ref.narration === undefined && ref.diagram === undefined && ref.concerns === undefined
|
|
278
302
|
? formatUnitRef(ref)
|
|
@@ -564,6 +588,7 @@ function project(diff, file, claims, issues) {
|
|
|
564
588
|
return {
|
|
565
589
|
index: k,
|
|
566
590
|
message: commit.message,
|
|
591
|
+
...(commit.section === undefined ? {} : { section: commit.section }),
|
|
567
592
|
...withText(commit.narration ?? null, commit.diagram ?? null),
|
|
568
593
|
...(mine.length === 0 ? {} : { concerns: mine }),
|
|
569
594
|
refs,
|
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.
|
|
@@ -977,3 +1065,25 @@ export function renderShownConcern(concern, commit, ctx) {
|
|
|
977
1065
|
"",
|
|
978
1066
|
].join("\n");
|
|
979
1067
|
}
|
|
1068
|
+
function utf8(latin1) {
|
|
1069
|
+
return Buffer.from(latin1, "latin1").toString("utf8");
|
|
1070
|
+
}
|
|
1071
|
+
/** Where a name is defined, each with the commit it lands in and its first lines at the tip. */
|
|
1072
|
+
export function renderShownDefinitions(name, candidates) {
|
|
1073
|
+
if (candidates.length === 0)
|
|
1074
|
+
return `no definition of ${name} at the tip\n`;
|
|
1075
|
+
return `${candidates
|
|
1076
|
+
.flatMap((c) => {
|
|
1077
|
+
const where = c.commit !== null
|
|
1078
|
+
? `in commit ${c.commit + 1}`
|
|
1079
|
+
: c.unit !== null
|
|
1080
|
+
? `in ${c.unit}, which no commit of the plan takes`
|
|
1081
|
+
: "unchanged by the branch";
|
|
1082
|
+
const width = String(c.newLine + c.preview.length - 1).length;
|
|
1083
|
+
return [
|
|
1084
|
+
`${utf8(c.path)}:${c.newLine} ${c.kind}, ${where}`,
|
|
1085
|
+
...c.preview.map((l, i) => ` ${String(c.newLine + i).padStart(width)} ${utf8(l)}`),
|
|
1086
|
+
];
|
|
1087
|
+
})
|
|
1088
|
+
.join("\n")}\n`;
|
|
1089
|
+
}
|