assisted-review 0.0.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 +195 -0
- package/build/claude.js +154 -0
- package/build/claude.js.map +1 -0
- package/build/cli.js +95 -0
- package/build/cli.js.map +1 -0
- package/build/env.js +26 -0
- package/build/env.js.map +1 -0
- package/build/fetch.js +41 -0
- package/build/fetch.js.map +1 -0
- package/build/jira.js +119 -0
- package/build/jira.js.map +1 -0
- package/build/mock-ai.js +47 -0
- package/build/mock-ai.js.map +1 -0
- package/build/parse-diff.js +229 -0
- package/build/parse-diff.js.map +1 -0
- package/build/parse-ref.js +28 -0
- package/build/parse-ref.js.map +1 -0
- package/build/review.js +38 -0
- package/build/review.js.map +1 -0
- package/build/server.js +272 -0
- package/build/server.js.map +1 -0
- package/build/state.js +151 -0
- package/build/state.js.map +1 -0
- package/build/submit.js +123 -0
- package/build/submit.js.map +1 -0
- package/build/types.js +10 -0
- package/build/types.js.map +1 -0
- package/dist/assets/ibm-plex-mono-cyrillic-400-normal-BSMlKf0J.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-400-normal-CEL4l2ZJ.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-500-normal-Ael50iVv.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-500-normal-Bq9vWWag.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-DMdlQ8Kv.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-400-normal-xuaO2J-f.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BIfNGwUT.woff +0 -0
- package/dist/assets/ibm-plex-mono-cyrillic-ext-500-normal-BqneJy0T.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-400-normal-BmRBH3aV.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-400-normal-D3D2R8hC.woff +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-500-normal-CAhNIIs5.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-latin-ext-500-normal-CZ70TYgx.woff +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-400-normal-BulugwFq.woff2 +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-400-normal-DDuiU_S-.woff +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-500-normal-C8zxqsMH.woff +0 -0
- package/dist/assets/ibm-plex-mono-vietnamese-500-normal-DZ4AoWbu.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-400-normal-BTotfTJu.woff +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-400-normal-DZqxrq2p.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-500-normal-ByOcLdNv.woff +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-500-normal-CocWQlwt.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-600-normal-71GNu3SW.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-600-normal-BGq0mW3O.woff +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-Dsrv2Tcn.woff +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-ext-400-normal-g30qAdWV.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-Cs5J6C77.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-ext-500-normal-DB5PtV2g.woff +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-Bz0x94Yp.woff +0 -0
- package/dist/assets/ibm-plex-sans-cyrillic-ext-600-normal-DUMzJB7m.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-greek-400-normal-D9ESIMu3.woff +0 -0
- package/dist/assets/ibm-plex-sans-greek-400-normal-_efipK4i.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-greek-500-normal-CuWXN6rf.woff +0 -0
- package/dist/assets/ibm-plex-sans-greek-500-normal-JMMifIXV.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-greek-600-normal-D-CqTdkO.woff +0 -0
- package/dist/assets/ibm-plex-sans-greek-600-normal-DzTrcv_p.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-ext-400-normal-C5H60-Va.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-ext-400-normal-RBey6euL.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-ext-500-normal-D0aIdm-b.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-ext-500-normal-DakdToA3.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-latin-ext-600-normal-DIrixKbi.woff +0 -0
- package/dist/assets/ibm-plex-sans-latin-ext-600-normal-DOrvGEcy.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-vietnamese-400-normal-DG4YqDda.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-vietnamese-400-normal-fK1oJ5dG.woff +0 -0
- package/dist/assets/ibm-plex-sans-vietnamese-500-normal-BEb3_waV.woff +0 -0
- package/dist/assets/ibm-plex-sans-vietnamese-500-normal-e4dixQRQ.woff2 +0 -0
- package/dist/assets/ibm-plex-sans-vietnamese-600-normal-DgdngZtN.woff +0 -0
- package/dist/assets/ibm-plex-sans-vietnamese-600-normal-DpPYBSTl.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-cyrillic-400-italic-C_ad97oI.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-cyrillic-400-italic-CygxzOWU.woff +0 -0
- package/dist/assets/ibm-plex-serif-cyrillic-400-normal-C7IY3oUc.woff +0 -0
- package/dist/assets/ibm-plex-serif-cyrillic-400-normal-CPQ8oqB-.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-cyrillic-ext-400-italic-CPw2or01.woff +0 -0
- package/dist/assets/ibm-plex-serif-cyrillic-ext-400-italic-o20Cx6Xj.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-cyrillic-ext-400-normal-BcBv-TKp.woff +0 -0
- package/dist/assets/ibm-plex-serif-cyrillic-ext-400-normal-CxUI4jC_.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-latin-400-italic-BCf4TsCA.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-latin-400-italic-Dd68USph.woff +0 -0
- package/dist/assets/ibm-plex-serif-latin-400-normal-BB-zNvJB.woff +0 -0
- package/dist/assets/ibm-plex-serif-latin-400-normal-BIGslYFI.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-latin-ext-400-italic-4IJS-XHX.woff +0 -0
- package/dist/assets/ibm-plex-serif-latin-ext-400-italic-hOoDEQwh.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-latin-ext-400-normal-CNMooFZX.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-latin-ext-400-normal-DwktX9jl.woff +0 -0
- package/dist/assets/ibm-plex-serif-vietnamese-400-italic-1VBVfWB7.woff +0 -0
- package/dist/assets/ibm-plex-serif-vietnamese-400-italic-BSp0Db6W.woff2 +0 -0
- package/dist/assets/ibm-plex-serif-vietnamese-400-normal-BY9Vij9A.woff +0 -0
- package/dist/assets/ibm-plex-serif-vietnamese-400-normal-DGubAMUE.woff2 +0 -0
- package/dist/assets/index-BAji2qJH.css +1 -0
- package/dist/assets/index-D9hEIdX7.js +326 -0
- package/dist/icon.svg +3 -0
- package/dist/index.html +14 -0
- package/dist/logo-dark.svg +22 -0
- package/dist/logo.svg +22 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jira.js","sourceRoot":"","sources":["../src/jira.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,8EAA8E;AAC9E,qEAAqE;AACrE,EAAE;AACF,wDAAwD;AACxD,kCAAkC;AAClC,8BAA8B;AAC9B,uFAAuF;AACvF,EAAE;AACF,+EAA+E;AAC/E,wCAAwC;AAIxC,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AACtE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;AACzC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE,CAAC;AAC3C,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,mBAAmB,CAAC;AACtE,MAAM,UAAU,GACd,iFAAiF;IACjF,uEAAuE,CAAC;AAE1E,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,GAAG,KAAoC;IACtE,MAAM,EAAE,GAAG,yBAAyB,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;AACnB,CAAC;AAED,SAAS,UAAU;IACjB,OAAO,OAAO,CAAC,QAAQ,IAAI,IAAI,IAAI,KAAK,CAAC,CAAC;AAC5C,CAAC;AAED,mEAAmE;AACnE,SAAS,SAAS,CAAC,IAAa;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC1C,MAAM,CAAC,GAAG,IAA6D,CAAC;IACxE,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjD,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,MAAM;YACT,OAAO,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QACtB,KAAK,WAAW;YACd,OAAO,IAAI,CAAC;QACd,KAAK,WAAW,CAAC;QACjB,KAAK,UAAU,CAAC;QAChB,KAAK,YAAY,CAAC;QAClB,KAAK,SAAS;YACZ,OAAO,KAAK,EAAE,GAAG,IAAI,CAAC;QACxB,KAAK,YAAY,CAAC;QAClB,KAAK,aAAa;YAChB,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvE;YACE,OAAO,KAAK,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,GAAW;IACnC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,+CAA+C,UAAU,EAAE,CAAC;IAC3E,MAAM,IAAI,GAAG,IAAI,eAAe,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,GAAG,QAAQ,qBAAqB,kBAAkB,CAAC,GAAG,CAAC,WAAW,MAAM,EAAE,EAC1E;YACE,OAAO,EAAE,EAAE,aAAa,EAAE,SAAS,IAAI,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE;YACvE,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CACF,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAsD,CAAC;QACrF,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,CAAC,CAAC,MAAsC,CAAC;QACxD,MAAM,QAAQ,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC;QAC/B,OAAO;YACL,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,OAAO,EAAG,CAAC,CAAC,OAAkB,IAAI,EAAE;YACpC,MAAM,EAAG,CAAC,CAAC,MAA4B,EAAE,IAAI,IAAI,EAAE;YACnD,IAAI,EAAG,CAAC,CAAC,SAA+B,EAAE,IAAI,IAAI,EAAE;YACpD,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,EAAE;YAC5C,GAAG,EAAE,GAAG,QAAQ,WAAW,IAAI,CAAC,GAAG,EAAE;YACrC,QAAQ,EAAE,CAAC,OAAO,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,MAAM,EAAE,GAAG;SAC/E,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,qEAAqE;AACrE,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAc;IACnD,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClB,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,iCAAiC;YACzC,UAAU,EAAE,UAAU;YACtB,IAAI;YACJ,MAAM,EAAE,EAAE;SACX,CAAC;IACJ,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAEpE,MAAM,MAAM,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CACzE,CAAC,CAAC,EAAkB,EAAE,CAAC,CAAC,KAAK,IAAI,CAClC,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,mBAAmB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,sCAAsC;YAChF,UAAU,EAAE,UAAU;YACtB,IAAI;YACJ,MAAM,EAAE,EAAE;SACX,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACzD,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACxD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AACjD,CAAC"}
|
package/build/mock-ai.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Placeholder AI commentary for UI development. Deterministic per chunk so the
|
|
2
|
+
// layout is stable across reloads. Swap this for the headless-Claude bridge
|
|
3
|
+
// (slice 3) without touching the UI — it reads `chunk.ai_notes` either way.
|
|
4
|
+
const LOREM = [
|
|
5
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
|
|
6
|
+
'Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
|
7
|
+
'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.',
|
|
8
|
+
'Duis aute irure dolor in reprehenderit in voluptate velit esse cillum.',
|
|
9
|
+
'Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia.',
|
|
10
|
+
'Deserunt mollit anim id est laborum, quis aute iure reprehenderit.',
|
|
11
|
+
'Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil.',
|
|
12
|
+
'Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus.',
|
|
13
|
+
];
|
|
14
|
+
// Tiny deterministic string hash (djb2-ish).
|
|
15
|
+
function hash(s) {
|
|
16
|
+
let h = 5381;
|
|
17
|
+
for (let i = 0; i < s.length; i++)
|
|
18
|
+
h = (h * 33) ^ s.charCodeAt(i);
|
|
19
|
+
return Math.abs(h);
|
|
20
|
+
}
|
|
21
|
+
function sentences(seed, count) {
|
|
22
|
+
const out = [];
|
|
23
|
+
for (let i = 0; i < count; i++)
|
|
24
|
+
out.push(LOREM[(seed + i) % LOREM.length]);
|
|
25
|
+
return out.join(' ');
|
|
26
|
+
}
|
|
27
|
+
function mockNotesForChunk(chunk, index) {
|
|
28
|
+
const h = hash(chunk.id);
|
|
29
|
+
const notes = [
|
|
30
|
+
{
|
|
31
|
+
kind: 'initial',
|
|
32
|
+
body: sentences(h, 2 + (h % 3)), // 2–4 sentences
|
|
33
|
+
suggested_action: sentences(h + 1, 1),
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
// Sprinkle an extra "context" note on some chunks to exercise the stacked
|
|
37
|
+
// multi-note layout.
|
|
38
|
+
if (index % 3 === 1) {
|
|
39
|
+
notes.push({ kind: 'context', body: sentences(h + 3, 1 + (h % 2)) });
|
|
40
|
+
}
|
|
41
|
+
return notes;
|
|
42
|
+
}
|
|
43
|
+
/** Return a copy of the chunks with placeholder `ai_notes` attached. */
|
|
44
|
+
export function attachMockNotes(chunks) {
|
|
45
|
+
return chunks.map((c, i) => ({ ...c, ai_notes: mockNotesForChunk(c, i) }));
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=mock-ai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mock-ai.js","sourceRoot":"","sources":["../src/mock-ai.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,4EAA4E;AAC5E,4EAA4E;AAI5E,MAAM,KAAK,GAAG;IACZ,0DAA0D;IAC1D,oEAAoE;IACpE,qEAAqE;IACrE,wEAAwE;IACxE,4EAA4E;IAC5E,oEAAoE;IACpE,uEAAuE;IACvE,8EAA8E;CAC/E,CAAC;AAEF,6CAA6C;AAC7C,SAAS,IAAI,CAAC,CAAS;IACrB,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAClE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACrB,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,KAAa;IAC5C,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3E,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAY,EAAE,KAAa;IACpD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACzB,MAAM,KAAK,GAAa;QACtB;YACE,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,SAAS,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,gBAAgB;YACjD,gBAAgB,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;SACtC;KACF,CAAC;IACF,0EAA0E;IAC1E,qBAAqB;IACrB,IAAI,KAAK,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACpB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,eAAe,CAAC,MAAe;IAC7C,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// Parse a unified diff into grouped hunks. Pure TS — same output shape as the
|
|
2
|
+
// original parse-diff.py: an array of groups, each with
|
|
3
|
+
// { id, file, hunk_header, old_range, new_range, context, diff, members }.
|
|
4
|
+
const HUNK_HEADER_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)$/;
|
|
5
|
+
function warn(msg) {
|
|
6
|
+
console.error(`parse-diff: warning: ${msg}`);
|
|
7
|
+
}
|
|
8
|
+
// Extract (old, new) paths from a `diff --git a/<old> b/<new>` line.
|
|
9
|
+
// Returns [null, null] for ambiguous cases (paths containing " b/"); the
|
|
10
|
+
// caller falls back to the --- / +++ markers.
|
|
11
|
+
function parseGitDiffPaths(line) {
|
|
12
|
+
const rest = line.slice('diff --git '.length);
|
|
13
|
+
if (rest.startsWith('a/')) {
|
|
14
|
+
const idx = rest.indexOf(' b/');
|
|
15
|
+
if (idx !== -1) {
|
|
16
|
+
const old = rest.slice(2, idx);
|
|
17
|
+
const neu = rest.slice(idx + 3).replace(/\n$/, '');
|
|
18
|
+
return [old, neu];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return [null, null];
|
|
22
|
+
}
|
|
23
|
+
// Extract path from a `--- a/<path>` or `+++ b/<path>` marker line.
|
|
24
|
+
function extractPathFromMarker(line, prefix) {
|
|
25
|
+
let s = line.slice(prefix.length).replace(/\n$/, '');
|
|
26
|
+
if (s === '/dev/null')
|
|
27
|
+
return null;
|
|
28
|
+
if (s.startsWith('a/') || s.startsWith('b/'))
|
|
29
|
+
s = s.slice(2);
|
|
30
|
+
const tabIdx = s.indexOf('\t');
|
|
31
|
+
if (tabIdx !== -1)
|
|
32
|
+
s = s.slice(0, tabIdx);
|
|
33
|
+
return s;
|
|
34
|
+
}
|
|
35
|
+
export function parseDiff(text) {
|
|
36
|
+
// Match Python's str.splitlines() closely enough for diffs: split on \n and
|
|
37
|
+
// drop a single trailing \r so CRLF diffs don't leave \r in the body.
|
|
38
|
+
const lines = text.split('\n').map((l) => l.replace(/\r$/, ''));
|
|
39
|
+
const chunks = [];
|
|
40
|
+
let chunkCounter = 0;
|
|
41
|
+
let curOldPath = null;
|
|
42
|
+
let curNewPath = null;
|
|
43
|
+
let isBinary = false;
|
|
44
|
+
let inHunk = false;
|
|
45
|
+
let hunkHeaderLine = '';
|
|
46
|
+
let hunkBody = [];
|
|
47
|
+
let hunkOldStart = 0;
|
|
48
|
+
let hunkOldCount = 0;
|
|
49
|
+
let hunkNewStart = 0;
|
|
50
|
+
let hunkNewCount = 0;
|
|
51
|
+
let hunkContext = '';
|
|
52
|
+
function finalizeHunk() {
|
|
53
|
+
if (!inHunk)
|
|
54
|
+
return;
|
|
55
|
+
let filePath;
|
|
56
|
+
if (curNewPath !== null)
|
|
57
|
+
filePath = curNewPath;
|
|
58
|
+
else if (curOldPath !== null)
|
|
59
|
+
filePath = curOldPath;
|
|
60
|
+
else {
|
|
61
|
+
warn('hunk encountered with no known file path; skipping');
|
|
62
|
+
inHunk = false;
|
|
63
|
+
hunkBody = [];
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
chunkCounter += 1;
|
|
67
|
+
const oldRange = hunkOldCount === 0
|
|
68
|
+
? [hunkOldStart, hunkOldStart]
|
|
69
|
+
: [hunkOldStart, hunkOldStart + hunkOldCount - 1];
|
|
70
|
+
const newRange = hunkNewCount === 0
|
|
71
|
+
? [hunkNewStart, hunkNewStart]
|
|
72
|
+
: [hunkNewStart, hunkNewStart + hunkNewCount - 1];
|
|
73
|
+
const diffText = [hunkHeaderLine, ...hunkBody].join('\n');
|
|
74
|
+
chunks.push({
|
|
75
|
+
id: `c${chunkCounter}`,
|
|
76
|
+
file: filePath,
|
|
77
|
+
hunk_header: hunkHeaderLine,
|
|
78
|
+
old_range: oldRange,
|
|
79
|
+
new_range: newRange,
|
|
80
|
+
context: hunkContext.trim(),
|
|
81
|
+
diff: diffText,
|
|
82
|
+
});
|
|
83
|
+
inHunk = false;
|
|
84
|
+
hunkBody = [];
|
|
85
|
+
}
|
|
86
|
+
const SKIP_PREFIXES = [
|
|
87
|
+
'index ',
|
|
88
|
+
'similarity ',
|
|
89
|
+
'dissimilarity ',
|
|
90
|
+
'rename ',
|
|
91
|
+
'copy ',
|
|
92
|
+
'new file mode',
|
|
93
|
+
'deleted file mode',
|
|
94
|
+
'old mode',
|
|
95
|
+
'new mode',
|
|
96
|
+
'GIT binary patch',
|
|
97
|
+
];
|
|
98
|
+
let i = 0;
|
|
99
|
+
const n = lines.length;
|
|
100
|
+
while (i < n) {
|
|
101
|
+
const line = lines[i];
|
|
102
|
+
if (line.startsWith('diff --git ')) {
|
|
103
|
+
finalizeHunk();
|
|
104
|
+
[curOldPath, curNewPath] = parseGitDiffPaths(line);
|
|
105
|
+
isBinary = false;
|
|
106
|
+
i += 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (line.startsWith('Binary files ') && line.endsWith('differ')) {
|
|
110
|
+
finalizeHunk();
|
|
111
|
+
isBinary = true;
|
|
112
|
+
i += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (isBinary) {
|
|
116
|
+
i += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (line.startsWith('--- ')) {
|
|
120
|
+
finalizeHunk();
|
|
121
|
+
const p = extractPathFromMarker(line, '--- ');
|
|
122
|
+
if (p !== null)
|
|
123
|
+
curOldPath = p;
|
|
124
|
+
i += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (line.startsWith('+++ ')) {
|
|
128
|
+
finalizeHunk();
|
|
129
|
+
const p = extractPathFromMarker(line, '+++ ');
|
|
130
|
+
if (p !== null)
|
|
131
|
+
curNewPath = p;
|
|
132
|
+
i += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (SKIP_PREFIXES.some((p) => line.startsWith(p))) {
|
|
136
|
+
i += 1;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const m = line.match(HUNK_HEADER_RE);
|
|
140
|
+
if (m) {
|
|
141
|
+
finalizeHunk();
|
|
142
|
+
hunkOldStart = Number(m[1]);
|
|
143
|
+
hunkOldCount = m[2] !== undefined ? Number(m[2]) : 1;
|
|
144
|
+
hunkNewStart = Number(m[3]);
|
|
145
|
+
hunkNewCount = m[4] !== undefined ? Number(m[4]) : 1;
|
|
146
|
+
hunkContext = m[5] || '';
|
|
147
|
+
hunkHeaderLine = line;
|
|
148
|
+
hunkBody = [];
|
|
149
|
+
inHunk = true;
|
|
150
|
+
i += 1;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (inHunk) {
|
|
154
|
+
if (/^[+\- \\]/.test(line)) {
|
|
155
|
+
hunkBody.push(line);
|
|
156
|
+
i += 1;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Unexpected line inside a hunk — end it and reprocess this line.
|
|
160
|
+
finalizeHunk();
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (line.trim() === '') {
|
|
164
|
+
i += 1;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
warn(`skipping unrecognized line: ${JSON.stringify(line)}`);
|
|
168
|
+
i += 1;
|
|
169
|
+
}
|
|
170
|
+
finalizeHunk();
|
|
171
|
+
return chunks;
|
|
172
|
+
}
|
|
173
|
+
function singletonGroup(ch) {
|
|
174
|
+
return {
|
|
175
|
+
id: ch.id,
|
|
176
|
+
file: ch.file,
|
|
177
|
+
hunk_header: ch.hunk_header,
|
|
178
|
+
old_range: [...ch.old_range],
|
|
179
|
+
new_range: [...ch.new_range],
|
|
180
|
+
context: ch.context ?? '',
|
|
181
|
+
diff: ch.diff,
|
|
182
|
+
members: [
|
|
183
|
+
{
|
|
184
|
+
hunk_header: ch.hunk_header,
|
|
185
|
+
old_range: [...ch.old_range],
|
|
186
|
+
new_range: [...ch.new_range],
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// Merge adjacent hunks in the same file separated by <= `gap` unchanged
|
|
192
|
+
// new-file lines. gap <= 0 disables merging (every hunk is its own group).
|
|
193
|
+
export function groupChunks(chunks, gap = 20) {
|
|
194
|
+
if (chunks.length === 0)
|
|
195
|
+
return [];
|
|
196
|
+
const groups = [singletonGroup(chunks[0])];
|
|
197
|
+
if (gap <= 0) {
|
|
198
|
+
for (const ch of chunks.slice(1))
|
|
199
|
+
groups.push(singletonGroup(ch));
|
|
200
|
+
return groups;
|
|
201
|
+
}
|
|
202
|
+
for (const ch of chunks.slice(1)) {
|
|
203
|
+
const cur = groups[groups.length - 1];
|
|
204
|
+
const sameFile = ch.file === cur.file;
|
|
205
|
+
const prevEnd = cur.new_range[1];
|
|
206
|
+
const thisStart = ch.new_range[0];
|
|
207
|
+
const newGap = Math.max(0, thisStart - prevEnd - 1);
|
|
208
|
+
if (sameFile && newGap <= gap) {
|
|
209
|
+
cur.diff = `${cur.diff}\n${ch.diff}`;
|
|
210
|
+
cur.new_range[1] = ch.new_range[1];
|
|
211
|
+
cur.old_range[1] = ch.old_range[1];
|
|
212
|
+
const member = {
|
|
213
|
+
hunk_header: ch.hunk_header,
|
|
214
|
+
old_range: [...ch.old_range],
|
|
215
|
+
new_range: [...ch.new_range],
|
|
216
|
+
};
|
|
217
|
+
cur.members.push(member);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
groups.push(singletonGroup(ch));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return groups;
|
|
224
|
+
}
|
|
225
|
+
// Convenience: raw diff text -> grouped chunks.
|
|
226
|
+
export function chunksFromDiff(diffText, gap = 20) {
|
|
227
|
+
return groupChunks(parseDiff(diffText), gap);
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=parse-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-diff.js","sourceRoot":"","sources":["../src/parse-diff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Parse a PR reference into {owner, repo, number}.
|
|
2
|
+
// Accepts: "owner/repo#123" or a github.com PR URL.
|
|
3
|
+
export function parseRef(ref) {
|
|
4
|
+
if (!ref || typeof ref !== 'string') {
|
|
5
|
+
throw new Error('missing PR reference (expected owner/repo#N or a PR URL)');
|
|
6
|
+
}
|
|
7
|
+
const trimmed = ref.trim();
|
|
8
|
+
// owner/repo#123
|
|
9
|
+
const shortMatch = trimmed.match(/^([^/\s]+)\/([^/#\s]+)#(\d+)$/);
|
|
10
|
+
if (shortMatch) {
|
|
11
|
+
return {
|
|
12
|
+
owner: shortMatch[1],
|
|
13
|
+
repo: shortMatch[2],
|
|
14
|
+
number: Number(shortMatch[3]),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// https://github.com/owner/repo/pull/123
|
|
18
|
+
const urlMatch = trimmed.match(/github\.com\/([^/\s]+)\/([^/\s]+)\/pull\/(\d+)/);
|
|
19
|
+
if (urlMatch) {
|
|
20
|
+
return {
|
|
21
|
+
owner: urlMatch[1],
|
|
22
|
+
repo: urlMatch[2],
|
|
23
|
+
number: Number(urlMatch[3]),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`unrecognized PR reference: "${ref}" (expected owner/repo#N or a PR URL)`);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=parse-ref.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-ref.js","sourceRoot":"","sources":["../src/parse-ref.ts"],"names":[],"mappings":"AAEA,mDAAmD;AACnD,oDAAoD;AACpD,MAAM,UAAU,QAAQ,CAAC,GAAuB;IAC9C,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAE3B,iBAAiB;IACjB,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAClE,IAAI,UAAU,EAAE,CAAC;QACf,OAAO;YACL,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;YACpB,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC;YACnB,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;SAC9B,CAAC;IACJ,CAAC;IAED,yCAAyC;IACzC,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACjF,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO;YACL,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;YAClB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;YACjB,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;SAC5B,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,KAAK,CACb,+BAA+B,GAAG,uCAAuC,CAC1E,CAAC;AACJ,CAAC"}
|
package/build/review.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Shared PR-loading logic used by both cli.ts (startup) and server.ts (open endpoint).
|
|
2
|
+
// Keeps cli.ts from being imported by the server (it self-executes via void main()).
|
|
3
|
+
import { fetchDiff, fetchMeta } from './fetch.js';
|
|
4
|
+
import { chunksFromDiff } from './parse-diff.js';
|
|
5
|
+
import { attachMockNotes } from './mock-ai.js';
|
|
6
|
+
import { loadState } from './state.js';
|
|
7
|
+
import { buildJiraContext, extractIssueKeys } from './jira.js';
|
|
8
|
+
async function jiraWithTimeout(keys, ms = 12000) {
|
|
9
|
+
return Promise.race([
|
|
10
|
+
buildJiraContext(keys),
|
|
11
|
+
new Promise((resolve) => setTimeout(() => resolve({
|
|
12
|
+
available: false,
|
|
13
|
+
reason: 'Jira request timed out',
|
|
14
|
+
keys,
|
|
15
|
+
issues: [],
|
|
16
|
+
}), ms)),
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
export async function loadReview(pr, opts = {}) {
|
|
20
|
+
const [diffText, meta] = await Promise.all([fetchDiff(pr), fetchMeta(pr)]);
|
|
21
|
+
let chunks = chunksFromDiff(diffText);
|
|
22
|
+
if (opts.mockAi)
|
|
23
|
+
chunks = attachMockNotes(chunks);
|
|
24
|
+
const keys = extractIssueKeys(meta.title, meta.head_ref, meta.body);
|
|
25
|
+
const jira = await jiraWithTimeout(keys);
|
|
26
|
+
const review = {
|
|
27
|
+
pr,
|
|
28
|
+
meta,
|
|
29
|
+
chunks,
|
|
30
|
+
overview: { jira },
|
|
31
|
+
generated_at: new Date().toISOString(),
|
|
32
|
+
};
|
|
33
|
+
const state = await loadState(pr, meta.head_sha);
|
|
34
|
+
// Cache meta in state so the reviews listing can show titles without re-fetching.
|
|
35
|
+
state.meta = meta;
|
|
36
|
+
return { review, state };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=review.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"review.js","sourceRoot":"","sources":["../src/review.ts"],"names":[],"mappings":"AAAA,uFAAuF;AACvF,qFAAqF;AAErF,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG/D,KAAK,UAAU,eAAe,CAC5B,IAAc,EACd,EAAE,GAAG,KAAK;IAEV,OAAO,OAAO,CAAC,IAAI,CAAC;QAClB,gBAAgB,CAAC,IAAI,CAAC;QACtB,IAAI,OAAO,CAAc,CAAC,OAAO,EAAE,EAAE,CACnC,UAAU,CACR,GAAG,EAAE,CACH,OAAO,CAAC;YACN,SAAS,EAAE,KAAK;YAChB,MAAM,EAAE,wBAAwB;YAChC,IAAI;YACJ,MAAM,EAAE,EAAE;SACX,CAAC,EACJ,EAAE,CACH,CACF;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,EAAS,EACT,OAA6B,EAAE;IAE/B,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3E,IAAI,MAAM,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IACtC,IAAI,IAAI,CAAC,MAAM;QAAE,MAAM,GAAG,eAAe,CAAC,MAAM,CAAC,CAAC;IAElD,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;IAEzC,MAAM,MAAM,GAAW;QACrB,EAAE;QACF,IAAI;QACJ,MAAM;QACN,QAAQ,EAAE,EAAE,IAAI,EAAE;QAClB,YAAY,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACvC,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjD,kFAAkF;IAClF,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;IAElB,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC"}
|
package/build/server.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// Local-only HTTP server: serves the built UI (dist/), the review payload, and
|
|
2
|
+
// the mutable review state. Binds to 127.0.0.1 only — never exposed off-machine.
|
|
3
|
+
import { createServer, } from 'node:http';
|
|
4
|
+
import { readFile } from 'node:fs/promises';
|
|
5
|
+
import { join, normalize, extname } from 'node:path';
|
|
6
|
+
import { applyAction, deleteReview, listReviews, saveState } from './state.js';
|
|
7
|
+
import { buildOverviewPrompt, buildPrompt, splitSuggestedAction, streamClaude, } from './claude.js';
|
|
8
|
+
import { buildReviewPayload, submitReview, VERDICTS, } from './submit.js';
|
|
9
|
+
import { parseRef } from './parse-ref.js';
|
|
10
|
+
import { loadReview } from './review.js';
|
|
11
|
+
import { OVERVIEW_ID, } from './types.js';
|
|
12
|
+
// dist/ is a sibling of this file's dir (src/ under tsx, build/ after tsc).
|
|
13
|
+
const DIST_DIR = join(import.meta.dirname, '..', 'dist');
|
|
14
|
+
const MIME = {
|
|
15
|
+
'.html': 'text/html; charset=utf-8',
|
|
16
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
17
|
+
'.css': 'text/css; charset=utf-8',
|
|
18
|
+
'.json': 'application/json; charset=utf-8',
|
|
19
|
+
'.svg': 'image/svg+xml',
|
|
20
|
+
'.ico': 'image/x-icon',
|
|
21
|
+
'.woff2': 'font/woff2',
|
|
22
|
+
};
|
|
23
|
+
function sendJson(res, status, obj) {
|
|
24
|
+
res.writeHead(status, { 'content-type': MIME['.json'] });
|
|
25
|
+
res.end(JSON.stringify(obj));
|
|
26
|
+
}
|
|
27
|
+
function readBody(req) {
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
let body = '';
|
|
30
|
+
req.on('data', (chunk) => (body += chunk));
|
|
31
|
+
req.on('end', () => resolve(body));
|
|
32
|
+
req.on('error', reject);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function serveStatic(res, urlPath) {
|
|
36
|
+
const rel = urlPath === '/' ? '/index.html' : urlPath;
|
|
37
|
+
const filePath = normalize(join(DIST_DIR, rel));
|
|
38
|
+
if (!filePath.startsWith(DIST_DIR)) {
|
|
39
|
+
res.writeHead(403);
|
|
40
|
+
res.end('forbidden');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const data = await readFile(filePath);
|
|
45
|
+
res.writeHead(200, {
|
|
46
|
+
'content-type': MIME[extname(filePath)] ?? 'application/octet-stream',
|
|
47
|
+
});
|
|
48
|
+
res.end(data);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
if (!extname(filePath)) {
|
|
52
|
+
try {
|
|
53
|
+
const html = await readFile(join(DIST_DIR, 'index.html'));
|
|
54
|
+
res.writeHead(200, { 'content-type': MIME['.html'] });
|
|
55
|
+
res.end(html);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
/* fall through */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
res.writeHead(404);
|
|
63
|
+
res.end('not found');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
export function startServer(ctx, { port = 4319, host = '127.0.0.1', serveUi = true, mockAi = false, } = {}) {
|
|
67
|
+
// Track the active Claude SSE stream cancel fn — called before switching reviews
|
|
68
|
+
// to prevent a finishing stream from writing notes into the wrong review's state.
|
|
69
|
+
let currentCancel = null;
|
|
70
|
+
const server = createServer((req, res) => {
|
|
71
|
+
void handle(req, res).catch((err) => {
|
|
72
|
+
sendJson(res, 500, {
|
|
73
|
+
error: err instanceof Error ? err.message : String(err),
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
async function handle(req, res) {
|
|
78
|
+
const url = new URL(req.url ?? '/', `http://${host}`);
|
|
79
|
+
if (url.pathname === '/api/review') {
|
|
80
|
+
if (req.method === 'GET') {
|
|
81
|
+
if (!ctx.review) {
|
|
82
|
+
res.writeHead(204);
|
|
83
|
+
res.end();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
return sendJson(res, 200, ctx.review);
|
|
87
|
+
}
|
|
88
|
+
if (req.method === 'DELETE') {
|
|
89
|
+
currentCancel?.();
|
|
90
|
+
currentCancel = null;
|
|
91
|
+
if (ctx.review)
|
|
92
|
+
await deleteReview(ctx.review.pr).catch(() => { });
|
|
93
|
+
ctx.review = null;
|
|
94
|
+
ctx.state = null;
|
|
95
|
+
return sendJson(res, 200, { ok: true });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (url.pathname === '/api/state') {
|
|
99
|
+
if (!ctx.state) {
|
|
100
|
+
res.writeHead(204);
|
|
101
|
+
res.end();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
return sendJson(res, 200, ctx.state);
|
|
105
|
+
}
|
|
106
|
+
if (url.pathname === '/api/action' && req.method === 'POST') {
|
|
107
|
+
const { review, state } = ctx;
|
|
108
|
+
if (!review || !state)
|
|
109
|
+
return sendJson(res, 503, { error: 'no active review' });
|
|
110
|
+
const action = JSON.parse(await readBody(req));
|
|
111
|
+
const nextState = applyAction(state, action);
|
|
112
|
+
ctx.state = nextState;
|
|
113
|
+
await saveState(nextState);
|
|
114
|
+
return sendJson(res, 200, nextState);
|
|
115
|
+
}
|
|
116
|
+
// Publish drafted comments as a real GitHub PR review.
|
|
117
|
+
if (url.pathname === '/api/submit' && req.method === 'POST') {
|
|
118
|
+
const { review, state } = ctx;
|
|
119
|
+
if (!review || !state)
|
|
120
|
+
return sendJson(res, 503, { error: 'no active review' });
|
|
121
|
+
const { verdict, body } = JSON.parse(await readBody(req));
|
|
122
|
+
if (!verdict || !VERDICTS.includes(verdict)) {
|
|
123
|
+
return sendJson(res, 400, {
|
|
124
|
+
ok: false,
|
|
125
|
+
error: `verdict must be one of ${VERDICTS.join(', ')}`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (state.submitted) {
|
|
129
|
+
return sendJson(res, 410, {
|
|
130
|
+
ok: false,
|
|
131
|
+
error: 'this review was already submitted',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
const payload = buildReviewPayload(review.chunks, state.comments, verdict, body ?? '', state.head_sha);
|
|
135
|
+
const result = await submitReview(review.pr, payload);
|
|
136
|
+
const nextState = result.ok
|
|
137
|
+
? { ...state, submitted: { at: new Date().toISOString(), verdict, url: result.html_url } }
|
|
138
|
+
: state;
|
|
139
|
+
if (result.ok) {
|
|
140
|
+
ctx.state = nextState;
|
|
141
|
+
await saveState(nextState);
|
|
142
|
+
}
|
|
143
|
+
const status = result.ok ? 200 : result.stale ? 409 : 502;
|
|
144
|
+
return sendJson(res, status, { ...result, state: nextState });
|
|
145
|
+
}
|
|
146
|
+
// List all persisted reviews (for the review picker menu).
|
|
147
|
+
if (url.pathname === '/api/reviews' && req.method === 'GET') {
|
|
148
|
+
return sendJson(res, 200, await listReviews());
|
|
149
|
+
}
|
|
150
|
+
// Delete a review's persisted state file.
|
|
151
|
+
const deleteMatch = url.pathname.match(/^\/api\/reviews\/([^/]+)\/([^/]+)\/(\d+)$/);
|
|
152
|
+
if (deleteMatch && req.method === 'DELETE') {
|
|
153
|
+
const [, owner, repo, num] = deleteMatch;
|
|
154
|
+
const pr = { owner, repo, number: Number(num) };
|
|
155
|
+
try {
|
|
156
|
+
await deleteReview(pr);
|
|
157
|
+
return sendJson(res, 200, { ok: true });
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return sendJson(res, 404, { ok: false, error: 'review not found' });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Open (fetch + load) a review and make it the active one.
|
|
164
|
+
if (url.pathname === '/api/reviews/open' && req.method === 'POST') {
|
|
165
|
+
let ref;
|
|
166
|
+
try {
|
|
167
|
+
({ ref } = JSON.parse(await readBody(req)));
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return sendJson(res, 400, { error: 'request body must be valid JSON' });
|
|
171
|
+
}
|
|
172
|
+
if (!ref)
|
|
173
|
+
return sendJson(res, 400, { error: 'ref is required' });
|
|
174
|
+
let pr;
|
|
175
|
+
try {
|
|
176
|
+
pr = parseRef(ref);
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
return sendJson(res, 400, { error: err.message });
|
|
180
|
+
}
|
|
181
|
+
// Cancel any in-flight SSE stream so it can't write stale notes into the new ctx.
|
|
182
|
+
currentCancel?.();
|
|
183
|
+
currentCancel = null;
|
|
184
|
+
try {
|
|
185
|
+
const { review, state } = await loadReview(pr, { mockAi });
|
|
186
|
+
ctx.review = review;
|
|
187
|
+
ctx.state = state;
|
|
188
|
+
await saveState(state);
|
|
189
|
+
return sendJson(res, 200, { review: ctx.review, state: ctx.state });
|
|
190
|
+
}
|
|
191
|
+
catch (err) {
|
|
192
|
+
return sendJson(res, 502, { error: err.message });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Server-Sent Events: stream a Claude note for a chunk (or the overview).
|
|
196
|
+
if (url.pathname === '/api/claude') {
|
|
197
|
+
const { review, state: initialState } = ctx;
|
|
198
|
+
if (!review || !initialState)
|
|
199
|
+
return sendJson(res, 503, { error: 'no active review' });
|
|
200
|
+
const chunkId = url.searchParams.get('chunk_id') ?? '';
|
|
201
|
+
const question = url.searchParams.get('q') ?? '';
|
|
202
|
+
const isOverview = chunkId === OVERVIEW_ID;
|
|
203
|
+
const chunk = isOverview
|
|
204
|
+
? null
|
|
205
|
+
: review.chunks.find((c) => c.id === chunkId);
|
|
206
|
+
if (!isOverview && !chunk)
|
|
207
|
+
return sendJson(res, 404, { error: 'unknown chunk' });
|
|
208
|
+
// An empty question means "explain/summarize" (an initial note).
|
|
209
|
+
const kind = question.trim() ? 'investigation' : 'initial';
|
|
210
|
+
const prompt = isOverview
|
|
211
|
+
? buildOverviewPrompt(review.meta, review.chunks, review.overview.jira, question)
|
|
212
|
+
: buildPrompt(chunk, kind, question);
|
|
213
|
+
// Suggested-action line only applies to per-chunk "explain" notes.
|
|
214
|
+
const wantsAction = !isOverview && kind === 'initial';
|
|
215
|
+
res.writeHead(200, {
|
|
216
|
+
'content-type': 'text/event-stream',
|
|
217
|
+
'cache-control': 'no-cache',
|
|
218
|
+
connection: 'keep-alive',
|
|
219
|
+
});
|
|
220
|
+
const sse = (event, data) => res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
221
|
+
const cancel = streamClaude(prompt, {
|
|
222
|
+
onDelta: (text) => sse('delta', { text }),
|
|
223
|
+
onError: (message) => {
|
|
224
|
+
if (currentCancel === cancel)
|
|
225
|
+
currentCancel = null;
|
|
226
|
+
sse('error', { message });
|
|
227
|
+
res.end();
|
|
228
|
+
},
|
|
229
|
+
onDone: (full) => {
|
|
230
|
+
if (currentCancel === cancel)
|
|
231
|
+
currentCancel = null;
|
|
232
|
+
// Guard: the review may have been cleared while the stream was in flight.
|
|
233
|
+
if (!ctx.state) {
|
|
234
|
+
res.end();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const { body, suggestedAction } = wantsAction
|
|
238
|
+
? splitSuggestedAction(full)
|
|
239
|
+
: { body: full.trim(), suggestedAction: undefined };
|
|
240
|
+
const nextState = applyAction(ctx.state, {
|
|
241
|
+
type: 'add_note',
|
|
242
|
+
chunk_id: isOverview ? OVERVIEW_ID : chunk.id,
|
|
243
|
+
kind,
|
|
244
|
+
prompt: question.trim() || undefined,
|
|
245
|
+
body,
|
|
246
|
+
suggested_action: suggestedAction,
|
|
247
|
+
});
|
|
248
|
+
ctx.state = nextState;
|
|
249
|
+
void saveState(nextState).then(() => {
|
|
250
|
+
sse('done', { state: nextState });
|
|
251
|
+
res.end();
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
currentCancel = cancel;
|
|
256
|
+
req.on('close', () => {
|
|
257
|
+
if (currentCancel === cancel)
|
|
258
|
+
currentCancel = null;
|
|
259
|
+
cancel();
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (!serveUi)
|
|
264
|
+
return sendJson(res, 404, { error: 'not found (api-only mode)' });
|
|
265
|
+
return serveStatic(res, url.pathname);
|
|
266
|
+
}
|
|
267
|
+
return new Promise((resolve, reject) => {
|
|
268
|
+
server.on('error', reject);
|
|
269
|
+
server.listen(port, host, () => resolve({ url: `http://${host}:${port}` }));
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
//# sourceMappingURL=server.js.map
|