diff-easy 0.1.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/LICENSE +22 -0
- package/README.md +142 -0
- package/dist/cli/cmux.d.ts +9 -0
- package/dist/cli/cmux.js +274 -0
- package/dist/cli/comment.d.ts +2 -0
- package/dist/cli/comment.js +91 -0
- package/dist/cli/github.d.ts +84 -0
- package/dist/cli/github.js +364 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +397 -0
- package/dist/cli/turns.d.ts +14 -0
- package/dist/cli/turns.js +333 -0
- package/dist/cli/utils.d.ts +27 -0
- package/dist/cli/utils.js +204 -0
- package/dist/client/assets/arc-CHYjdFTk.js +1 -0
- package/dist/client/assets/architecture-7EHR7CIX-weHBYDgM.js +1 -0
- package/dist/client/assets/architectureDiagram-3BPJPVTR-DRa2msuJ.js +36 -0
- package/dist/client/assets/array-BifhSqXX.js +1 -0
- package/dist/client/assets/blockDiagram-GPEHLZMM-BTdKsXy5.js +132 -0
- package/dist/client/assets/c4Diagram-AAUBKEIU-CwTvCkOg.js +10 -0
- package/dist/client/assets/channel-Cv40nQW1.js +1 -0
- package/dist/client/assets/chunk-2J33WTMH-BFsjrTtc.js +1 -0
- package/dist/client/assets/chunk-3OPIFGDE-V_xVw1dq.js +62 -0
- package/dist/client/assets/chunk-4BX2VUAB-DoLO6LLy.js +1 -0
- package/dist/client/assets/chunk-55IACEB6-CHgBwbdB.js +1 -0
- package/dist/client/assets/chunk-5ZQYHXKU-DMixAgQG.js +2 -0
- package/dist/client/assets/chunk-727SXJPM-C-3FZTmm.js +206 -0
- package/dist/client/assets/chunk-AQP2D5EJ-DpQZhXt-.js +231 -0
- package/dist/client/assets/chunk-BSJP7CBP-BnO9E_G4.js +1 -0
- package/dist/client/assets/chunk-CSCIHK7Q-BxItphyO.js +124 -0
- package/dist/client/assets/chunk-FMBD7UC4-4hEcfvfT.js +15 -0
- package/dist/client/assets/chunk-KSCS5N6A-DnArkpOI.js +10 -0
- package/dist/client/assets/chunk-L5ZTLDWV-DQoYJ1BW.js +1 -0
- package/dist/client/assets/chunk-LZXEDZCA-CdCevBX-.js +2 -0
- package/dist/client/assets/chunk-ND2GUHAM-Dt89U_nU.js +1 -0
- package/dist/client/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
- package/dist/client/assets/chunk-NZK2D7GU-C2Ev_f_w.js +1 -0
- package/dist/client/assets/chunk-O5CBEL6O-BeZ3MFnJ.js +70 -0
- package/dist/client/assets/chunk-QZHKN3VN-BmDOSWHp.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-gLuInTk4.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-DPbDc8_T.js +32 -0
- package/dist/client/assets/classDiagram-4FO5ZUOK-DOpVKUrb.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DOpVKUrb.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-CCPyalms.js +1 -0
- package/dist/client/assets/cytoscape.esm-FqbQrHcz.js +321 -0
- package/dist/client/assets/dagre-BM42HDAG-6jrYMjX3.js +4 -0
- package/dist/client/assets/dagre-Bx709z4p.js +1 -0
- package/dist/client/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/dist/client/assets/diagram-2AECGRRQ-dCQ_KoXl.js +43 -0
- package/dist/client/assets/diagram-5GNKFQAL-B3Xj4-XE.js +10 -0
- package/dist/client/assets/diagram-KO2AKTUF-DaGgF2yL.js +3 -0
- package/dist/client/assets/diagram-LMA3HP47-CF_c4tPT.js +24 -0
- package/dist/client/assets/diagram-OG6HWLK6-BgBHbJkT.js +24 -0
- package/dist/client/assets/dist-7wVkzZne.js +1 -0
- package/dist/client/assets/erDiagram-TEJ5UH35-BZgX1grR.js +85 -0
- package/dist/client/assets/eventmodeling-FCH6USID-BaI6RNKr.js +1 -0
- package/dist/client/assets/flowDiagram-I6XJVG4X-BFNUa12V.js +162 -0
- package/dist/client/assets/ganttDiagram-6RSMTGT7-C8t6BThI.js +292 -0
- package/dist/client/assets/gitGraph-WXDBUCRP-BlicfDkM.js +1 -0
- package/dist/client/assets/gitGraphDiagram-PVQCEYII-BmnIXuXP.js +106 -0
- package/dist/client/assets/graphlib-B8gBHxth.js +1 -0
- package/dist/client/assets/index-BfVtbKTZ.css +2 -0
- package/dist/client/assets/index-p8zzfjdu.js +87 -0
- package/dist/client/assets/info-J43DQDTF-DHLG38N3.js +1 -0
- package/dist/client/assets/infoDiagram-5YYISTIA-oQ-lq-45.js +2 -0
- package/dist/client/assets/init-D6jRqBbL.js +1 -0
- package/dist/client/assets/ishikawaDiagram-YF4QCWOH-DrODlBLD.js +70 -0
- package/dist/client/assets/journeyDiagram-JHISSGLW-Bxg5wCQz.js +139 -0
- package/dist/client/assets/kanban-definition-UN3LZRKU-DAdvUDIB.js +89 -0
- package/dist/client/assets/katex-Vhh-h91d.js +257 -0
- package/dist/client/assets/line-D8kNd46u.js +1 -0
- package/dist/client/assets/linear-jwGZSrg4.js +1 -0
- package/dist/client/assets/mermaid-parser.core-PVPfUfyU.js +4 -0
- package/dist/client/assets/mermaid.core-DwdP6CFg.js +9 -0
- package/dist/client/assets/mindmap-definition-RKZ34NQL-DeOoL56Q.js +96 -0
- package/dist/client/assets/ordinal-hYBb2elL.js +1 -0
- package/dist/client/assets/packet-YPE3B663-BBEaxdJP.js +1 -0
- package/dist/client/assets/path-BWPyau1x.js +1 -0
- package/dist/client/assets/pie-LRSECV5Y-fvTRFm-2.js +1 -0
- package/dist/client/assets/pieDiagram-4H26LBE5-oep1px2r.js +30 -0
- package/dist/client/assets/prism-bash-D6zCJ74D.js +1 -0
- package/dist/client/assets/prism-clojure-_z-zoNcE.js +1 -0
- package/dist/client/assets/prism-csharp-XPn2bw0d.js +1 -0
- package/dist/client/assets/prism-dart-CDv16Fbu.js +1 -0
- package/dist/client/assets/prism-elixir-DhHhV5Rj.js +1 -0
- package/dist/client/assets/prism-haskell-5hq1Nh9c.js +1 -0
- package/dist/client/assets/prism-hcl-B6Z0Ouoo.js +1 -0
- package/dist/client/assets/prism-java-6wtusvIl.js +1 -0
- package/dist/client/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
- package/dist/client/assets/prism-nix-C88dTX93.js +1 -0
- package/dist/client/assets/prism-perl-Lsbi34sH.js +1 -0
- package/dist/client/assets/prism-php-Co4fM40K.js +1 -0
- package/dist/client/assets/prism-protobuf-yzSHlhwy.js +1 -0
- package/dist/client/assets/prism-ruby-DFCASkuD.js +1 -0
- package/dist/client/assets/prism-scala-VkM3wCOL.js +1 -0
- package/dist/client/assets/prism-solidity-DTom4k5p.js +1 -0
- package/dist/client/assets/prism-sql-CYhVDMEE.js +1 -0
- package/dist/client/assets/prism-vim-BgpkjCK5.js +1 -0
- package/dist/client/assets/quadrantDiagram-W4KKPZXB-D9l66CHb.js +7 -0
- package/dist/client/assets/radar-GUYGQ44K-DAU5EPVd.js +1 -0
- package/dist/client/assets/requirementDiagram-4Y6WPE33-helpQlwb.js +84 -0
- package/dist/client/assets/rough.esm-CSKSodPl.js +1 -0
- package/dist/client/assets/sankeyDiagram-5OEKKPKP-C9sNuuvA.js +40 -0
- package/dist/client/assets/sequenceDiagram-3UESZ5HK-BP0bhCwQ.js +162 -0
- package/dist/client/assets/src-CDAPGf2l.js +1 -0
- package/dist/client/assets/stateDiagram-AJRCARHV-CsFizpla.js +1 -0
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Bvn8tanl.js +1 -0
- package/dist/client/assets/timeline-definition-PNZ67QCA-BnkmHs1F.js +120 -0
- package/dist/client/assets/treeView-BLDUP644-DD9HZO5W.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-BBKuQ1lo.js +1 -0
- package/dist/client/assets/vennDiagram-CIIHVFJN-BPFTHflF.js +34 -0
- package/dist/client/assets/wardley-L42UT6IY-y88gDkwb.js +1 -0
- package/dist/client/assets/wardleyDiagram-YWT4CUSO-BlcA7IVh.js +78 -0
- package/dist/client/assets/xychartDiagram-2RQKCTM6-DX5vUCRH.js +7 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/index.html +14 -0
- package/dist/client/logo.png +0 -0
- package/dist/client/site-data/blobs/080c0e6/cHVibGljL3NpdGUtZGF0YS9vZy1pbWFnZS5wbmc.png +0 -0
- package/dist/client/site-data/blobs/55f23a1/bGFuZGluZy9wdWJsaWMvZGlmaXQvbG9nby5wbmc.png +0 -0
- package/dist/client/site-data/blobs/66ff7c6/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/blobs/e6977fe/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/manifest.json +1 -0
- package/dist/client/site-data/og-image.png +0 -0
- package/dist/client/site-data/snapshots/55f23a1...080c0e6.json +1 -0
- package/dist/client/site-data/snapshots/66ff7c6...e6977fe.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f-comments.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f.json +1 -0
- package/dist/server/file-watcher.d.ts +33 -0
- package/dist/server/file-watcher.js +340 -0
- package/dist/server/generated-file-check.d.ts +17 -0
- package/dist/server/generated-file-check.js +280 -0
- package/dist/server/git-diff.d.ts +94 -0
- package/dist/server/git-diff.js +909 -0
- package/dist/server/grouping/cache.d.ts +3 -0
- package/dist/server/grouping/cache.js +22 -0
- package/dist/server/grouping/claudeCli.d.ts +10 -0
- package/dist/server/grouping/claudeCli.js +95 -0
- package/dist/server/grouping/digest.d.ts +15 -0
- package/dist/server/grouping/digest.js +38 -0
- package/dist/server/grouping/reconcile.d.ts +6 -0
- package/dist/server/grouping/reconcile.js +34 -0
- package/dist/server/grouping/types.d.ts +9 -0
- package/dist/server/grouping/types.js +1 -0
- package/dist/server/server.d.ts +37 -0
- package/dist/server/server.js +1283 -0
- package/dist/shared/grouping.d.ts +4 -0
- package/dist/shared/grouping.js +170 -0
- package/dist/types/diff.d.ts +263 -0
- package/dist/types/diff.js +1 -0
- package/dist/types/watch.d.ts +59 -0
- package/dist/types/watch.js +8 -0
- package/dist/utils/commentFormatting.d.ts +6 -0
- package/dist/utils/commentFormatting.js +90 -0
- package/dist/utils/commentImports.d.ts +11 -0
- package/dist/utils/commentImports.js +386 -0
- package/dist/utils/createId.d.ts +1 -0
- package/dist/utils/createId.js +5 -0
- package/dist/utils/diffMode.d.ts +3 -0
- package/dist/utils/diffMode.js +13 -0
- package/dist/utils/diffSelection.d.ts +13 -0
- package/dist/utils/diffSelection.js +39 -0
- package/dist/utils/editorOptions.d.ts +60 -0
- package/dist/utils/editorOptions.js +171 -0
- package/dist/utils/fileUtils.d.ts +12 -0
- package/dist/utils/fileUtils.js +21 -0
- package/dist/utils/hubConfig.d.ts +32 -0
- package/dist/utils/hubConfig.js +51 -0
- package/dist/utils/suggestionUtils.d.ts +19 -0
- package/dist/utils/suggestionUtils.js +31 -0
- package/dist/utils/turnLog.d.ts +47 -0
- package/dist/utils/turnLog.js +133 -0
- package/package.json +105 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DiffFile, GroupingResult } from '../types/diff.js';
|
|
2
|
+
export type GroupInputFile = Pick<DiffFile, 'path' | 'additions' | 'deletions' | 'status' | 'isGenerated'>;
|
|
3
|
+
export declare function groupByModule(files: GroupInputFile[]): GroupingResult;
|
|
4
|
+
export declare function groupSmart(files: GroupInputFile[]): GroupingResult;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const churn = (fs) => fs.reduce((s, f) => s + f.additions + f.deletions, 0);
|
|
2
|
+
function splitNoise(files) {
|
|
3
|
+
const noise = [];
|
|
4
|
+
const kept = [];
|
|
5
|
+
for (const f of files) {
|
|
6
|
+
if (f.isGenerated)
|
|
7
|
+
noise.push(f.path);
|
|
8
|
+
else
|
|
9
|
+
kept.push(f);
|
|
10
|
+
}
|
|
11
|
+
return { noise, kept };
|
|
12
|
+
}
|
|
13
|
+
/** Longest common *directory* prefix (ending in `/`, or '' if none). */
|
|
14
|
+
function commonDirPrefix(paths) {
|
|
15
|
+
if (paths.length === 0)
|
|
16
|
+
return '';
|
|
17
|
+
const split = paths.map((p) => p.split('/'));
|
|
18
|
+
const first = split[0] ?? [];
|
|
19
|
+
let i = 0;
|
|
20
|
+
for (; i < first.length - 1; i++) {
|
|
21
|
+
const seg = first[i];
|
|
22
|
+
if (!split.every((parts) => parts.length - 1 > i && parts[i] === seg))
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
return i > 0 ? first.slice(0, i).join('/') + '/' : '';
|
|
26
|
+
}
|
|
27
|
+
export function groupByModule(files) {
|
|
28
|
+
const { noise, kept } = splitNoise(files);
|
|
29
|
+
const prefix = commonDirPrefix(kept.map((f) => f.path));
|
|
30
|
+
const buckets = new Map();
|
|
31
|
+
for (const f of kept) {
|
|
32
|
+
const rest = f.path.slice(prefix.length);
|
|
33
|
+
const slash = rest.indexOf('/');
|
|
34
|
+
const key = slash === -1 ? '(root)' : rest.slice(0, slash);
|
|
35
|
+
const arr = buckets.get(key) ?? [];
|
|
36
|
+
arr.push(f);
|
|
37
|
+
buckets.set(key, arr);
|
|
38
|
+
}
|
|
39
|
+
const groups = [...buckets.entries()]
|
|
40
|
+
.map(([label, fs]) => ({ label, fs, weight: churn(fs) }))
|
|
41
|
+
.sort((a, b) => b.weight - a.weight || a.label.localeCompare(b.label))
|
|
42
|
+
.map((g, i) => ({
|
|
43
|
+
id: `g${i + 1}`,
|
|
44
|
+
label: g.label,
|
|
45
|
+
files: g.fs.map((f) => f.path).sort((a, b) => a.localeCompare(b)),
|
|
46
|
+
}));
|
|
47
|
+
return { mode: 'module', groups, noise, generatedAt: Date.now() };
|
|
48
|
+
}
|
|
49
|
+
const GENERIC_STEMS = new Set(['index', 'main', 'mod', 'utils', 'types', 'helpers', 'constants']);
|
|
50
|
+
const TAIL_ORDER = ['Tests', 'Styles', 'Config', 'Docs'];
|
|
51
|
+
function basename(path) {
|
|
52
|
+
return path.split('/').pop() ?? path;
|
|
53
|
+
}
|
|
54
|
+
function dirname(path) {
|
|
55
|
+
const i = path.lastIndexOf('/');
|
|
56
|
+
return i === -1 ? '' : path.slice(0, i);
|
|
57
|
+
}
|
|
58
|
+
/** Lowercased base name with extension + test/spec/module/etc. qualifiers stripped. */
|
|
59
|
+
function stemOf(path) {
|
|
60
|
+
const name = basename(path).toLowerCase();
|
|
61
|
+
const dot = name.indexOf('.');
|
|
62
|
+
return dot === -1 ? name : name.slice(0, dot);
|
|
63
|
+
}
|
|
64
|
+
function titleCase(stem) {
|
|
65
|
+
return stem.length ? stem.charAt(0).toUpperCase() + stem.slice(1) : stem;
|
|
66
|
+
}
|
|
67
|
+
function roleOf(path) {
|
|
68
|
+
const name = basename(path).toLowerCase();
|
|
69
|
+
if (/\.(test|spec)\.[cm]?[jt]sx?$/.test(name) ||
|
|
70
|
+
/_test\.go$/.test(name) ||
|
|
71
|
+
/test\.(java|kt|scala)$/.test(name) ||
|
|
72
|
+
/(^|\/)(tests?|__tests__|spec)\//.test(path))
|
|
73
|
+
return 'test';
|
|
74
|
+
if (/\.(css|scss|sass|less|styl)$/.test(name))
|
|
75
|
+
return 'style';
|
|
76
|
+
if (/\.(json|ya?ml|toml|ini)$/.test(name) ||
|
|
77
|
+
/(^|\/)\.env/.test(path) ||
|
|
78
|
+
/^dockerfile$/.test(name) ||
|
|
79
|
+
/^makefile$/.test(name) ||
|
|
80
|
+
/\.config\.[cm]?[jt]s$/.test(name) ||
|
|
81
|
+
/(^|\/)\.(github|circleci)\//.test(path))
|
|
82
|
+
return 'config';
|
|
83
|
+
if (/\.(md|mdx|markdown|rst|txt)$/.test(name) || /^(readme|changelog|license)/.test(name))
|
|
84
|
+
return 'doc';
|
|
85
|
+
return 'source';
|
|
86
|
+
}
|
|
87
|
+
const roleToTail = {
|
|
88
|
+
test: 'Tests',
|
|
89
|
+
style: 'Styles',
|
|
90
|
+
config: 'Config',
|
|
91
|
+
doc: 'Docs',
|
|
92
|
+
};
|
|
93
|
+
const complete = (fs) => fs.some((f) => roleOf(f.path) === 'test') && fs.some((f) => roleOf(f.path) === 'source');
|
|
94
|
+
export function groupSmart(files) {
|
|
95
|
+
const { noise, kept } = splitNoise(files);
|
|
96
|
+
// Pass 2: feature-stem clusters. Distinctive stems merge across dirs; generic
|
|
97
|
+
// stems only cluster within one directory.
|
|
98
|
+
const clusterKey = (f) => {
|
|
99
|
+
const stem = stemOf(f.path);
|
|
100
|
+
const distinctive = stem.length >= 4 && !GENERIC_STEMS.has(stem);
|
|
101
|
+
return distinctive ? `stem:${stem}` : `dir:${dirname(f.path)}:${stem}`;
|
|
102
|
+
};
|
|
103
|
+
const byKey = new Map();
|
|
104
|
+
for (const f of kept) {
|
|
105
|
+
const k = clusterKey(f);
|
|
106
|
+
const arr = byKey.get(k) ?? [];
|
|
107
|
+
arr.push(f);
|
|
108
|
+
byKey.set(k, arr);
|
|
109
|
+
}
|
|
110
|
+
const claimed = new Set();
|
|
111
|
+
const featureCohorts = [];
|
|
112
|
+
for (const fs of byKey.values()) {
|
|
113
|
+
if (fs.length < 2)
|
|
114
|
+
continue;
|
|
115
|
+
const firstFile = fs[0];
|
|
116
|
+
if (!firstFile)
|
|
117
|
+
continue;
|
|
118
|
+
featureCohorts.push({ label: titleCase(stemOf(firstFile.path)), files: fs });
|
|
119
|
+
for (const f of fs)
|
|
120
|
+
claimed.add(f.path);
|
|
121
|
+
}
|
|
122
|
+
// Pass 3: role-tail extraction from unclaimed files.
|
|
123
|
+
const tailBuckets = new Map();
|
|
124
|
+
const forDir = [];
|
|
125
|
+
for (const f of kept) {
|
|
126
|
+
if (claimed.has(f.path))
|
|
127
|
+
continue;
|
|
128
|
+
const tail = roleToTail[roleOf(f.path)];
|
|
129
|
+
if (tail) {
|
|
130
|
+
const arr = tailBuckets.get(tail) ?? [];
|
|
131
|
+
arr.push(f);
|
|
132
|
+
tailBuckets.set(tail, arr);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
forDir.push(f);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Pass 4: directory affinity for remaining (source) singletons.
|
|
139
|
+
const dirBuckets = new Map();
|
|
140
|
+
for (const f of forDir) {
|
|
141
|
+
const d = dirname(f.path) || '(root)';
|
|
142
|
+
const arr = dirBuckets.get(d) ?? [];
|
|
143
|
+
arr.push(f);
|
|
144
|
+
dirBuckets.set(d, arr);
|
|
145
|
+
}
|
|
146
|
+
const dirCohorts = [...dirBuckets.entries()].map(([label, files]) => ({
|
|
147
|
+
label,
|
|
148
|
+
files,
|
|
149
|
+
}));
|
|
150
|
+
// Pass 5: order feature + dir cohorts by completeness, then churn, then label.
|
|
151
|
+
const ranked = [...featureCohorts, ...dirCohorts].sort((a, b) => {
|
|
152
|
+
const c = Number(complete(b.files)) - Number(complete(a.files));
|
|
153
|
+
if (c !== 0)
|
|
154
|
+
return c;
|
|
155
|
+
const k = churn(b.files) - churn(a.files);
|
|
156
|
+
if (k !== 0)
|
|
157
|
+
return k;
|
|
158
|
+
return a.label.localeCompare(b.label);
|
|
159
|
+
});
|
|
160
|
+
const tailCohorts = TAIL_ORDER.filter((l) => tailBuckets.get(l)?.length).map((l) => ({
|
|
161
|
+
label: l,
|
|
162
|
+
files: tailBuckets.get(l) ?? [],
|
|
163
|
+
}));
|
|
164
|
+
const groups = [...ranked, ...tailCohorts].map((c, i) => ({
|
|
165
|
+
id: `g${i + 1}`,
|
|
166
|
+
label: c.label,
|
|
167
|
+
files: c.files.map((f) => f.path).sort((a, b) => a.localeCompare(b)),
|
|
168
|
+
}));
|
|
169
|
+
return { mode: 'smart', groups, noise, generatedAt: Date.now() };
|
|
170
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
export interface DiffFile {
|
|
2
|
+
path: string;
|
|
3
|
+
oldPath?: string;
|
|
4
|
+
status: 'modified' | 'added' | 'deleted' | 'renamed';
|
|
5
|
+
additions: number;
|
|
6
|
+
deletions: number;
|
|
7
|
+
chunks: DiffChunk[];
|
|
8
|
+
isGenerated?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface DiffChunk {
|
|
11
|
+
header: string;
|
|
12
|
+
oldStart: number;
|
|
13
|
+
oldLines: number;
|
|
14
|
+
newStart: number;
|
|
15
|
+
newLines: number;
|
|
16
|
+
lines: DiffLine[];
|
|
17
|
+
}
|
|
18
|
+
export interface DiffLine {
|
|
19
|
+
type: 'add' | 'delete' | 'normal' | 'hunk' | 'remove' | 'context' | 'header';
|
|
20
|
+
content: string;
|
|
21
|
+
oldLineNumber?: number;
|
|
22
|
+
newLineNumber?: number;
|
|
23
|
+
}
|
|
24
|
+
export type DiffViewMode = 'split' | 'unified';
|
|
25
|
+
export type DiffSide = 'old' | 'new';
|
|
26
|
+
export type DiffLineRange = number | {
|
|
27
|
+
start: number;
|
|
28
|
+
end: number;
|
|
29
|
+
};
|
|
30
|
+
export interface DiffCommentPosition {
|
|
31
|
+
side: DiffSide;
|
|
32
|
+
line: DiffLineRange;
|
|
33
|
+
}
|
|
34
|
+
export interface DiffCommentCodeSnapshot {
|
|
35
|
+
content: string;
|
|
36
|
+
language?: string;
|
|
37
|
+
}
|
|
38
|
+
export type BaseMode = 'direct' | 'merge-base';
|
|
39
|
+
export interface DiffSelection {
|
|
40
|
+
baseCommitish: string;
|
|
41
|
+
targetCommitish: string;
|
|
42
|
+
baseMode?: BaseMode;
|
|
43
|
+
}
|
|
44
|
+
export interface DiffResponse {
|
|
45
|
+
commit: string;
|
|
46
|
+
files: DiffFile[];
|
|
47
|
+
ignoreWhitespace?: boolean;
|
|
48
|
+
isEmpty?: boolean;
|
|
49
|
+
openInEditorAvailable?: boolean;
|
|
50
|
+
baseCommitish?: string;
|
|
51
|
+
targetCommitish?: string;
|
|
52
|
+
requestedBaseCommitish?: string;
|
|
53
|
+
requestedTargetCommitish?: string;
|
|
54
|
+
requestedBaseMode?: BaseMode;
|
|
55
|
+
clearComments?: boolean;
|
|
56
|
+
repositoryId?: string;
|
|
57
|
+
/** diff-easy hub: basename of the active workspace folder, for the toolbar label. */
|
|
58
|
+
repoName?: string;
|
|
59
|
+
/** diff-easy hub: home-relative path of the active workspace (e.g. ~/code/repo), for the tooltip. */
|
|
60
|
+
repoPath?: string;
|
|
61
|
+
commentImports?: CommentImport[];
|
|
62
|
+
commentImportId?: string;
|
|
63
|
+
/** diff-easy: PR title, for a GitHub-PR static-patch workspace (grouping intent). */
|
|
64
|
+
prTitle?: string;
|
|
65
|
+
/** diff-easy: PR body, for a GitHub-PR static-patch workspace (grouping intent). */
|
|
66
|
+
prBody?: string;
|
|
67
|
+
/** diff-easy: set when the diff couldn't be produced (e.g. a pruned snapshot). */
|
|
68
|
+
diffError?: string;
|
|
69
|
+
/** diff-easy hub: set when no workspace is selected yet (show the picker). */
|
|
70
|
+
needsWorkspace?: boolean;
|
|
71
|
+
}
|
|
72
|
+
export interface GeneratedStatusResponse {
|
|
73
|
+
path: string;
|
|
74
|
+
ref: string;
|
|
75
|
+
isGenerated: boolean;
|
|
76
|
+
source: 'path' | 'content';
|
|
77
|
+
}
|
|
78
|
+
export type LineNumber = number | [number, number];
|
|
79
|
+
export interface Comment {
|
|
80
|
+
id: string;
|
|
81
|
+
file: string;
|
|
82
|
+
line: LineNumber;
|
|
83
|
+
body: string;
|
|
84
|
+
timestamp: string;
|
|
85
|
+
author?: string;
|
|
86
|
+
codeContent?: string;
|
|
87
|
+
side?: DiffSide;
|
|
88
|
+
}
|
|
89
|
+
export interface LineSelection {
|
|
90
|
+
side: DiffSide;
|
|
91
|
+
lineNumber: number;
|
|
92
|
+
}
|
|
93
|
+
export interface LegacyDiffComment {
|
|
94
|
+
id: string;
|
|
95
|
+
filePath: string;
|
|
96
|
+
body: string;
|
|
97
|
+
author?: string;
|
|
98
|
+
createdAt: string;
|
|
99
|
+
updatedAt: string;
|
|
100
|
+
position: DiffCommentPosition;
|
|
101
|
+
codeSnapshot?: DiffCommentCodeSnapshot;
|
|
102
|
+
}
|
|
103
|
+
export interface DiffCommentMessage {
|
|
104
|
+
id: string;
|
|
105
|
+
body: string;
|
|
106
|
+
author?: string;
|
|
107
|
+
createdAt: string;
|
|
108
|
+
updatedAt: string;
|
|
109
|
+
}
|
|
110
|
+
export interface DiffCommentThread {
|
|
111
|
+
id: string;
|
|
112
|
+
filePath: string;
|
|
113
|
+
createdAt: string;
|
|
114
|
+
updatedAt: string;
|
|
115
|
+
position: DiffCommentPosition;
|
|
116
|
+
codeSnapshot?: DiffCommentCodeSnapshot;
|
|
117
|
+
messages: DiffCommentMessage[];
|
|
118
|
+
}
|
|
119
|
+
interface CommentImportBase {
|
|
120
|
+
id?: string;
|
|
121
|
+
filePath: string;
|
|
122
|
+
position: DiffCommentPosition;
|
|
123
|
+
body: string;
|
|
124
|
+
author?: string;
|
|
125
|
+
createdAt?: string;
|
|
126
|
+
updatedAt?: string;
|
|
127
|
+
codeSnapshot?: DiffCommentCodeSnapshot;
|
|
128
|
+
}
|
|
129
|
+
export interface ThreadCommentImport extends CommentImportBase {
|
|
130
|
+
type: 'thread';
|
|
131
|
+
}
|
|
132
|
+
export interface ReplyCommentImport extends CommentImportBase {
|
|
133
|
+
type: 'reply';
|
|
134
|
+
}
|
|
135
|
+
export type CommentImport = ThreadCommentImport | ReplyCommentImport;
|
|
136
|
+
export interface ViewedFileRecord {
|
|
137
|
+
filePath: string;
|
|
138
|
+
viewedAt: string;
|
|
139
|
+
diffContentHash: string;
|
|
140
|
+
}
|
|
141
|
+
export interface ViewedHashIndexEntry {
|
|
142
|
+
filePath: string;
|
|
143
|
+
diffContentHash: string;
|
|
144
|
+
hashVersion: 1;
|
|
145
|
+
viewedAt: string;
|
|
146
|
+
}
|
|
147
|
+
export interface ViewedHashIndex {
|
|
148
|
+
version: 1;
|
|
149
|
+
lastModifiedAt: string;
|
|
150
|
+
entries: ViewedHashIndexEntry[];
|
|
151
|
+
}
|
|
152
|
+
export interface LegacyDiffContextStorage {
|
|
153
|
+
version: 1;
|
|
154
|
+
baseCommitish: string;
|
|
155
|
+
targetCommitish: string;
|
|
156
|
+
createdAt: string;
|
|
157
|
+
lastModifiedAt: string;
|
|
158
|
+
comments: LegacyDiffComment[];
|
|
159
|
+
viewedFiles: ViewedFileRecord[];
|
|
160
|
+
}
|
|
161
|
+
export interface DiffContextStorage {
|
|
162
|
+
version: 2;
|
|
163
|
+
baseCommitish: string;
|
|
164
|
+
targetCommitish: string;
|
|
165
|
+
baseMode?: BaseMode;
|
|
166
|
+
createdAt: string;
|
|
167
|
+
lastModifiedAt: string;
|
|
168
|
+
threads: DiffCommentThread[];
|
|
169
|
+
viewedFiles: ViewedFileRecord[];
|
|
170
|
+
appliedCommentImportIds: string[];
|
|
171
|
+
}
|
|
172
|
+
export interface CommentThread {
|
|
173
|
+
id: string;
|
|
174
|
+
file: string;
|
|
175
|
+
line: LineNumber;
|
|
176
|
+
side?: DiffSide;
|
|
177
|
+
createdAt: string;
|
|
178
|
+
updatedAt: string;
|
|
179
|
+
codeContent?: string;
|
|
180
|
+
isOutdated?: boolean;
|
|
181
|
+
messages: DiffCommentMessage[];
|
|
182
|
+
}
|
|
183
|
+
interface RevisionOption {
|
|
184
|
+
value: string;
|
|
185
|
+
label: string;
|
|
186
|
+
}
|
|
187
|
+
interface BranchInfo {
|
|
188
|
+
name: string;
|
|
189
|
+
current: boolean;
|
|
190
|
+
}
|
|
191
|
+
export interface CommitInfo {
|
|
192
|
+
hash: string;
|
|
193
|
+
shortHash: string;
|
|
194
|
+
/** Subject line (git `%s`). */
|
|
195
|
+
message: string;
|
|
196
|
+
/** Full commit body (git `%b`), if any. Used by the context bar's expanded view. */
|
|
197
|
+
body?: string;
|
|
198
|
+
/** Author name (git `%aN`). */
|
|
199
|
+
author?: string;
|
|
200
|
+
/** Author date, ISO 8601 (git `%aI`). */
|
|
201
|
+
date?: string;
|
|
202
|
+
}
|
|
203
|
+
export interface RevisionsResponse {
|
|
204
|
+
specialOptions: RevisionOption[];
|
|
205
|
+
branches: BranchInfo[];
|
|
206
|
+
commits: CommitInfo[];
|
|
207
|
+
originDefaultBranch?: string;
|
|
208
|
+
resolvedBase?: string;
|
|
209
|
+
resolvedTarget?: string;
|
|
210
|
+
currentBranch?: string;
|
|
211
|
+
/** From `git config branch.<name>.pgit-parent`; undefined when unset. */
|
|
212
|
+
pgitParent?: string;
|
|
213
|
+
/**
|
|
214
|
+
* Effective base for the "Current branch" view, resolved server-side:
|
|
215
|
+
* the pgit parent when set, else the origin default branch when the current
|
|
216
|
+
* branch actually diverges from it. Undefined when neither applies (→ the
|
|
217
|
+
* client lands on the working-tree view and prompts for a base).
|
|
218
|
+
*/
|
|
219
|
+
branchBase?: string;
|
|
220
|
+
/**
|
|
221
|
+
* How `branchBase` was chosen: 'pgit' is explicit; 'branch' (nearest ancestor
|
|
222
|
+
* branch) and 'origin-default' are inferred best-guesses.
|
|
223
|
+
*/
|
|
224
|
+
branchBaseSource?: 'pgit' | 'branch' | 'origin-default';
|
|
225
|
+
/** Stable per-repo id (sha256 of the repo path) — keys per-repo UI prefs. */
|
|
226
|
+
repositoryId?: string;
|
|
227
|
+
}
|
|
228
|
+
export interface ExpandedLinesState {
|
|
229
|
+
[filePath: string]: FileExpandedState;
|
|
230
|
+
}
|
|
231
|
+
export interface FileExpandedState {
|
|
232
|
+
oldContent?: string[];
|
|
233
|
+
newContent?: string[];
|
|
234
|
+
expandedRanges: ExpandedRange[];
|
|
235
|
+
oldTotalLines?: number;
|
|
236
|
+
newTotalLines?: number;
|
|
237
|
+
}
|
|
238
|
+
interface ExpandedRange {
|
|
239
|
+
chunkIndex: number;
|
|
240
|
+
direction: 'up' | 'down';
|
|
241
|
+
count: number;
|
|
242
|
+
}
|
|
243
|
+
export interface ExpandedLine extends DiffLine {
|
|
244
|
+
isExpanded?: boolean;
|
|
245
|
+
}
|
|
246
|
+
export interface FileGroup {
|
|
247
|
+
id: string;
|
|
248
|
+
label: string;
|
|
249
|
+
rationale?: string;
|
|
250
|
+
files: string[];
|
|
251
|
+
}
|
|
252
|
+
export interface GroupingResult {
|
|
253
|
+
/** Which algorithm produced this result. */
|
|
254
|
+
mode: 'smart' | 'module' | 'ai';
|
|
255
|
+
groups: FileGroup[];
|
|
256
|
+
noise: string[];
|
|
257
|
+
/** AI only: the model that produced the grouping. */
|
|
258
|
+
model?: string;
|
|
259
|
+
/** Set when AI grouping was requested but degraded to the deterministic `smart` result. */
|
|
260
|
+
fallbackFrom?: 'ai';
|
|
261
|
+
generatedAt: number;
|
|
262
|
+
}
|
|
263
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export declare enum DiffMode {
|
|
2
|
+
DEFAULT = "default",// HEAD^ vs HEAD
|
|
3
|
+
WORKING = "working",// staged vs working
|
|
4
|
+
STAGED = "staged",// HEAD vs staged
|
|
5
|
+
DOT = "dot",// HEAD vs working (all changes)
|
|
6
|
+
SPECIFIC = "specific"
|
|
7
|
+
}
|
|
8
|
+
type WatchChangeType = 'file' | 'commit' | 'staging';
|
|
9
|
+
interface ConnectedWatchEvent {
|
|
10
|
+
type: 'connected';
|
|
11
|
+
diffMode: DiffMode;
|
|
12
|
+
changeType: WatchChangeType;
|
|
13
|
+
timestamp: string;
|
|
14
|
+
message?: string;
|
|
15
|
+
}
|
|
16
|
+
interface ReloadWatchEvent {
|
|
17
|
+
type: 'reload';
|
|
18
|
+
diffMode: DiffMode;
|
|
19
|
+
changeType: WatchChangeType;
|
|
20
|
+
timestamp: string;
|
|
21
|
+
message?: string;
|
|
22
|
+
}
|
|
23
|
+
interface ErrorWatchEvent {
|
|
24
|
+
type: 'error';
|
|
25
|
+
diffMode: DiffMode;
|
|
26
|
+
changeType: WatchChangeType;
|
|
27
|
+
timestamp: string;
|
|
28
|
+
message?: string;
|
|
29
|
+
}
|
|
30
|
+
interface CommentsChangedWatchEvent {
|
|
31
|
+
type: 'commentsChanged';
|
|
32
|
+
version: number;
|
|
33
|
+
timestamp: string;
|
|
34
|
+
}
|
|
35
|
+
interface TurnCompleteWatchEvent {
|
|
36
|
+
type: 'turn-complete';
|
|
37
|
+
timestamp: string;
|
|
38
|
+
}
|
|
39
|
+
interface WorkspaceChangedWatchEvent {
|
|
40
|
+
type: 'workspace-changed';
|
|
41
|
+
timestamp: string;
|
|
42
|
+
}
|
|
43
|
+
interface WorkspaceActivityWatchEvent {
|
|
44
|
+
type: 'workspace-activity';
|
|
45
|
+
repoId: string;
|
|
46
|
+
count: number;
|
|
47
|
+
timestamp: string;
|
|
48
|
+
}
|
|
49
|
+
export type WatchEvent = ConnectedWatchEvent | ReloadWatchEvent | ErrorWatchEvent | CommentsChangedWatchEvent | TurnCompleteWatchEvent | WorkspaceChangedWatchEvent | WorkspaceActivityWatchEvent;
|
|
50
|
+
export interface ClientWatchState {
|
|
51
|
+
isWatchEnabled: boolean;
|
|
52
|
+
diffMode: DiffMode;
|
|
53
|
+
shouldReload: boolean;
|
|
54
|
+
isReloading: boolean;
|
|
55
|
+
lastChangeTime: Date | null;
|
|
56
|
+
lastChangeType: WatchChangeType | null;
|
|
57
|
+
connectionStatus: 'connected' | 'disconnected' | 'reconnecting';
|
|
58
|
+
}
|
|
59
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Comment, CommentThread } from '../types/diff';
|
|
2
|
+
export declare function formatCommentPrompt(file: string, line: number | number[], body: string, codeContent?: string): string;
|
|
3
|
+
export declare function formatAllCommentsPrompt(comments: Comment[]): string;
|
|
4
|
+
export declare function formatCommentThreadPrompt(thread: CommentThread): string;
|
|
5
|
+
export declare function formatAllCommentThreadsPrompt(threads: CommentThread[]): string;
|
|
6
|
+
export declare function formatCommentsOutput(input: Comment[] | CommentThread[]): string;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { hasSuggestionBlock, parseSuggestionBlocks } from './suggestionUtils.js';
|
|
2
|
+
function getLineInfo(line) {
|
|
3
|
+
return typeof line === 'number' ? `L${line}` : `L${line[0]}-L${line[1]}`;
|
|
4
|
+
}
|
|
5
|
+
function formatCommentContent(body, codeContent) {
|
|
6
|
+
if (hasSuggestionBlock(body)) {
|
|
7
|
+
const suggestions = parseSuggestionBlocks(body);
|
|
8
|
+
if (suggestions.length > 0) {
|
|
9
|
+
let result = '';
|
|
10
|
+
let lastIndex = 0;
|
|
11
|
+
for (const suggestion of suggestions) {
|
|
12
|
+
const textBefore = body.slice(lastIndex, suggestion.startIndex).trim();
|
|
13
|
+
if (textBefore) {
|
|
14
|
+
result += `${result ? '\n' : ''}${textBefore}`;
|
|
15
|
+
}
|
|
16
|
+
if (codeContent) {
|
|
17
|
+
result += `${result ? '\n' : ''}ORIGINAL:\n\`\`\`\n${codeContent}\n\`\`\``;
|
|
18
|
+
}
|
|
19
|
+
result += `${result ? '\n' : ''}SUGGESTED:\n\`\`\`\n${suggestion.suggestedCode}\n\`\`\``;
|
|
20
|
+
lastIndex = suggestion.endIndex;
|
|
21
|
+
}
|
|
22
|
+
const textAfter = body.slice(lastIndex).trim();
|
|
23
|
+
if (textAfter) {
|
|
24
|
+
result += `${result ? '\n' : ''}${textAfter}`;
|
|
25
|
+
}
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return body;
|
|
30
|
+
}
|
|
31
|
+
function normalizeLegacyComment(comment) {
|
|
32
|
+
return {
|
|
33
|
+
id: comment.id,
|
|
34
|
+
file: comment.file,
|
|
35
|
+
line: comment.line,
|
|
36
|
+
side: comment.side,
|
|
37
|
+
createdAt: comment.timestamp,
|
|
38
|
+
updatedAt: comment.timestamp,
|
|
39
|
+
codeContent: comment.codeContent,
|
|
40
|
+
messages: [
|
|
41
|
+
{
|
|
42
|
+
id: comment.id,
|
|
43
|
+
body: comment.body,
|
|
44
|
+
author: comment.author,
|
|
45
|
+
createdAt: comment.timestamp,
|
|
46
|
+
updatedAt: comment.timestamp,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function formatCommentPrompt(file, line, body, codeContent) {
|
|
52
|
+
const filePath = file || '<unknown file>';
|
|
53
|
+
return `${filePath}:${getLineInfo(line)}\n${formatCommentContent(body, codeContent)}`;
|
|
54
|
+
}
|
|
55
|
+
export function formatAllCommentsPrompt(comments) {
|
|
56
|
+
if (comments.length === 0)
|
|
57
|
+
return '';
|
|
58
|
+
const prompts = comments.map((comment) => formatCommentPrompt(comment.file, comment.line, comment.body, comment.codeContent));
|
|
59
|
+
return prompts.join('\n=====\n');
|
|
60
|
+
}
|
|
61
|
+
export function formatCommentThreadPrompt(thread) {
|
|
62
|
+
const sections = [`${thread.file || '<unknown file>'}:${getLineInfo(thread.line)}`];
|
|
63
|
+
thread.messages.forEach((message, index) => {
|
|
64
|
+
if (index === 0) {
|
|
65
|
+
sections.push(formatCommentContent(message.body, thread.codeContent));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const replyIndex = index;
|
|
69
|
+
const authorLabel = message.author?.trim() || 'Unknown';
|
|
70
|
+
sections.push(`Reply ${replyIndex} (${authorLabel})`);
|
|
71
|
+
sections.push(formatCommentContent(message.body, thread.codeContent));
|
|
72
|
+
});
|
|
73
|
+
return sections.filter(Boolean).join('\n');
|
|
74
|
+
}
|
|
75
|
+
export function formatAllCommentThreadsPrompt(threads) {
|
|
76
|
+
if (threads.length === 0)
|
|
77
|
+
return '';
|
|
78
|
+
return threads.map((thread) => formatCommentThreadPrompt(thread)).join('\n=====\n');
|
|
79
|
+
}
|
|
80
|
+
export function formatCommentsOutput(input) {
|
|
81
|
+
const threads = input.map((item) => ('messages' in item ? item : normalizeLegacyComment(item)));
|
|
82
|
+
const allPrompts = formatAllCommentThreadsPrompt(threads);
|
|
83
|
+
return [
|
|
84
|
+
'\n📝 Comments from review session:',
|
|
85
|
+
'='.repeat(50),
|
|
86
|
+
allPrompts,
|
|
87
|
+
'='.repeat(50),
|
|
88
|
+
`Total comments: ${threads.length}\n`,
|
|
89
|
+
].join('\n');
|
|
90
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CommentImport, DiffCommentThread } from '../types/diff.js';
|
|
2
|
+
interface MergeCommentImportsResult {
|
|
3
|
+
threads: DiffCommentThread[];
|
|
4
|
+
warnings: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function normalizeCommentImports(input: unknown): CommentImport[];
|
|
7
|
+
export declare function parseCommentImportValue(value: string): CommentImport[];
|
|
8
|
+
export declare function mergeCommentThreads(existingThreads: DiffCommentThread[], incomingThreads: DiffCommentThread[]): MergeCommentImportsResult;
|
|
9
|
+
export declare function serializeCommentImports(commentImports: CommentImport[]): string;
|
|
10
|
+
export declare function mergeCommentImports(existingThreads: DiffCommentThread[], commentImports: CommentImport[]): MergeCommentImportsResult;
|
|
11
|
+
export {};
|