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.
Files changed (172) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +142 -0
  3. package/dist/cli/cmux.d.ts +9 -0
  4. package/dist/cli/cmux.js +274 -0
  5. package/dist/cli/comment.d.ts +2 -0
  6. package/dist/cli/comment.js +91 -0
  7. package/dist/cli/github.d.ts +84 -0
  8. package/dist/cli/github.js +364 -0
  9. package/dist/cli/index.d.ts +2 -0
  10. package/dist/cli/index.js +397 -0
  11. package/dist/cli/turns.d.ts +14 -0
  12. package/dist/cli/turns.js +333 -0
  13. package/dist/cli/utils.d.ts +27 -0
  14. package/dist/cli/utils.js +204 -0
  15. package/dist/client/assets/arc-CHYjdFTk.js +1 -0
  16. package/dist/client/assets/architecture-7EHR7CIX-weHBYDgM.js +1 -0
  17. package/dist/client/assets/architectureDiagram-3BPJPVTR-DRa2msuJ.js +36 -0
  18. package/dist/client/assets/array-BifhSqXX.js +1 -0
  19. package/dist/client/assets/blockDiagram-GPEHLZMM-BTdKsXy5.js +132 -0
  20. package/dist/client/assets/c4Diagram-AAUBKEIU-CwTvCkOg.js +10 -0
  21. package/dist/client/assets/channel-Cv40nQW1.js +1 -0
  22. package/dist/client/assets/chunk-2J33WTMH-BFsjrTtc.js +1 -0
  23. package/dist/client/assets/chunk-3OPIFGDE-V_xVw1dq.js +62 -0
  24. package/dist/client/assets/chunk-4BX2VUAB-DoLO6LLy.js +1 -0
  25. package/dist/client/assets/chunk-55IACEB6-CHgBwbdB.js +1 -0
  26. package/dist/client/assets/chunk-5ZQYHXKU-DMixAgQG.js +2 -0
  27. package/dist/client/assets/chunk-727SXJPM-C-3FZTmm.js +206 -0
  28. package/dist/client/assets/chunk-AQP2D5EJ-DpQZhXt-.js +231 -0
  29. package/dist/client/assets/chunk-BSJP7CBP-BnO9E_G4.js +1 -0
  30. package/dist/client/assets/chunk-CSCIHK7Q-BxItphyO.js +124 -0
  31. package/dist/client/assets/chunk-FMBD7UC4-4hEcfvfT.js +15 -0
  32. package/dist/client/assets/chunk-KSCS5N6A-DnArkpOI.js +10 -0
  33. package/dist/client/assets/chunk-L5ZTLDWV-DQoYJ1BW.js +1 -0
  34. package/dist/client/assets/chunk-LZXEDZCA-CdCevBX-.js +2 -0
  35. package/dist/client/assets/chunk-ND2GUHAM-Dt89U_nU.js +1 -0
  36. package/dist/client/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
  37. package/dist/client/assets/chunk-NZK2D7GU-C2Ev_f_w.js +1 -0
  38. package/dist/client/assets/chunk-O5CBEL6O-BeZ3MFnJ.js +70 -0
  39. package/dist/client/assets/chunk-QZHKN3VN-BmDOSWHp.js +1 -0
  40. package/dist/client/assets/chunk-WU5MYG2G-gLuInTk4.js +1 -0
  41. package/dist/client/assets/chunk-XPW4576I-DPbDc8_T.js +32 -0
  42. package/dist/client/assets/classDiagram-4FO5ZUOK-DOpVKUrb.js +1 -0
  43. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DOpVKUrb.js +1 -0
  44. package/dist/client/assets/cose-bilkent-S5V4N54A-CCPyalms.js +1 -0
  45. package/dist/client/assets/cytoscape.esm-FqbQrHcz.js +321 -0
  46. package/dist/client/assets/dagre-BM42HDAG-6jrYMjX3.js +4 -0
  47. package/dist/client/assets/dagre-Bx709z4p.js +1 -0
  48. package/dist/client/assets/defaultLocale-C8Fc0cco.js +1 -0
  49. package/dist/client/assets/diagram-2AECGRRQ-dCQ_KoXl.js +43 -0
  50. package/dist/client/assets/diagram-5GNKFQAL-B3Xj4-XE.js +10 -0
  51. package/dist/client/assets/diagram-KO2AKTUF-DaGgF2yL.js +3 -0
  52. package/dist/client/assets/diagram-LMA3HP47-CF_c4tPT.js +24 -0
  53. package/dist/client/assets/diagram-OG6HWLK6-BgBHbJkT.js +24 -0
  54. package/dist/client/assets/dist-7wVkzZne.js +1 -0
  55. package/dist/client/assets/erDiagram-TEJ5UH35-BZgX1grR.js +85 -0
  56. package/dist/client/assets/eventmodeling-FCH6USID-BaI6RNKr.js +1 -0
  57. package/dist/client/assets/flowDiagram-I6XJVG4X-BFNUa12V.js +162 -0
  58. package/dist/client/assets/ganttDiagram-6RSMTGT7-C8t6BThI.js +292 -0
  59. package/dist/client/assets/gitGraph-WXDBUCRP-BlicfDkM.js +1 -0
  60. package/dist/client/assets/gitGraphDiagram-PVQCEYII-BmnIXuXP.js +106 -0
  61. package/dist/client/assets/graphlib-B8gBHxth.js +1 -0
  62. package/dist/client/assets/index-BfVtbKTZ.css +2 -0
  63. package/dist/client/assets/index-p8zzfjdu.js +87 -0
  64. package/dist/client/assets/info-J43DQDTF-DHLG38N3.js +1 -0
  65. package/dist/client/assets/infoDiagram-5YYISTIA-oQ-lq-45.js +2 -0
  66. package/dist/client/assets/init-D6jRqBbL.js +1 -0
  67. package/dist/client/assets/ishikawaDiagram-YF4QCWOH-DrODlBLD.js +70 -0
  68. package/dist/client/assets/journeyDiagram-JHISSGLW-Bxg5wCQz.js +139 -0
  69. package/dist/client/assets/kanban-definition-UN3LZRKU-DAdvUDIB.js +89 -0
  70. package/dist/client/assets/katex-Vhh-h91d.js +257 -0
  71. package/dist/client/assets/line-D8kNd46u.js +1 -0
  72. package/dist/client/assets/linear-jwGZSrg4.js +1 -0
  73. package/dist/client/assets/mermaid-parser.core-PVPfUfyU.js +4 -0
  74. package/dist/client/assets/mermaid.core-DwdP6CFg.js +9 -0
  75. package/dist/client/assets/mindmap-definition-RKZ34NQL-DeOoL56Q.js +96 -0
  76. package/dist/client/assets/ordinal-hYBb2elL.js +1 -0
  77. package/dist/client/assets/packet-YPE3B663-BBEaxdJP.js +1 -0
  78. package/dist/client/assets/path-BWPyau1x.js +1 -0
  79. package/dist/client/assets/pie-LRSECV5Y-fvTRFm-2.js +1 -0
  80. package/dist/client/assets/pieDiagram-4H26LBE5-oep1px2r.js +30 -0
  81. package/dist/client/assets/prism-bash-D6zCJ74D.js +1 -0
  82. package/dist/client/assets/prism-clojure-_z-zoNcE.js +1 -0
  83. package/dist/client/assets/prism-csharp-XPn2bw0d.js +1 -0
  84. package/dist/client/assets/prism-dart-CDv16Fbu.js +1 -0
  85. package/dist/client/assets/prism-elixir-DhHhV5Rj.js +1 -0
  86. package/dist/client/assets/prism-haskell-5hq1Nh9c.js +1 -0
  87. package/dist/client/assets/prism-hcl-B6Z0Ouoo.js +1 -0
  88. package/dist/client/assets/prism-java-6wtusvIl.js +1 -0
  89. package/dist/client/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
  90. package/dist/client/assets/prism-nix-C88dTX93.js +1 -0
  91. package/dist/client/assets/prism-perl-Lsbi34sH.js +1 -0
  92. package/dist/client/assets/prism-php-Co4fM40K.js +1 -0
  93. package/dist/client/assets/prism-protobuf-yzSHlhwy.js +1 -0
  94. package/dist/client/assets/prism-ruby-DFCASkuD.js +1 -0
  95. package/dist/client/assets/prism-scala-VkM3wCOL.js +1 -0
  96. package/dist/client/assets/prism-solidity-DTom4k5p.js +1 -0
  97. package/dist/client/assets/prism-sql-CYhVDMEE.js +1 -0
  98. package/dist/client/assets/prism-vim-BgpkjCK5.js +1 -0
  99. package/dist/client/assets/quadrantDiagram-W4KKPZXB-D9l66CHb.js +7 -0
  100. package/dist/client/assets/radar-GUYGQ44K-DAU5EPVd.js +1 -0
  101. package/dist/client/assets/requirementDiagram-4Y6WPE33-helpQlwb.js +84 -0
  102. package/dist/client/assets/rough.esm-CSKSodPl.js +1 -0
  103. package/dist/client/assets/sankeyDiagram-5OEKKPKP-C9sNuuvA.js +40 -0
  104. package/dist/client/assets/sequenceDiagram-3UESZ5HK-BP0bhCwQ.js +162 -0
  105. package/dist/client/assets/src-CDAPGf2l.js +1 -0
  106. package/dist/client/assets/stateDiagram-AJRCARHV-CsFizpla.js +1 -0
  107. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Bvn8tanl.js +1 -0
  108. package/dist/client/assets/timeline-definition-PNZ67QCA-BnkmHs1F.js +120 -0
  109. package/dist/client/assets/treeView-BLDUP644-DD9HZO5W.js +1 -0
  110. package/dist/client/assets/treemap-LRROVOQU-BBKuQ1lo.js +1 -0
  111. package/dist/client/assets/vennDiagram-CIIHVFJN-BPFTHflF.js +34 -0
  112. package/dist/client/assets/wardley-L42UT6IY-y88gDkwb.js +1 -0
  113. package/dist/client/assets/wardleyDiagram-YWT4CUSO-BlcA7IVh.js +78 -0
  114. package/dist/client/assets/xychartDiagram-2RQKCTM6-DX5vUCRH.js +7 -0
  115. package/dist/client/favicon.png +0 -0
  116. package/dist/client/index.html +14 -0
  117. package/dist/client/logo.png +0 -0
  118. package/dist/client/site-data/blobs/080c0e6/cHVibGljL3NpdGUtZGF0YS9vZy1pbWFnZS5wbmc.png +0 -0
  119. package/dist/client/site-data/blobs/55f23a1/bGFuZGluZy9wdWJsaWMvZGlmaXQvbG9nby5wbmc.png +0 -0
  120. package/dist/client/site-data/blobs/66ff7c6/cHVibGljL2xvZ28ucG5n.png +0 -0
  121. package/dist/client/site-data/blobs/e6977fe/cHVibGljL2xvZ28ucG5n.png +0 -0
  122. package/dist/client/site-data/manifest.json +1 -0
  123. package/dist/client/site-data/og-image.png +0 -0
  124. package/dist/client/site-data/snapshots/55f23a1...080c0e6.json +1 -0
  125. package/dist/client/site-data/snapshots/66ff7c6...e6977fe.json +1 -0
  126. package/dist/client/site-data/snapshots/7d40fd4...a72112f-comments.json +1 -0
  127. package/dist/client/site-data/snapshots/7d40fd4...a72112f.json +1 -0
  128. package/dist/server/file-watcher.d.ts +33 -0
  129. package/dist/server/file-watcher.js +340 -0
  130. package/dist/server/generated-file-check.d.ts +17 -0
  131. package/dist/server/generated-file-check.js +280 -0
  132. package/dist/server/git-diff.d.ts +94 -0
  133. package/dist/server/git-diff.js +909 -0
  134. package/dist/server/grouping/cache.d.ts +3 -0
  135. package/dist/server/grouping/cache.js +22 -0
  136. package/dist/server/grouping/claudeCli.d.ts +10 -0
  137. package/dist/server/grouping/claudeCli.js +95 -0
  138. package/dist/server/grouping/digest.d.ts +15 -0
  139. package/dist/server/grouping/digest.js +38 -0
  140. package/dist/server/grouping/reconcile.d.ts +6 -0
  141. package/dist/server/grouping/reconcile.js +34 -0
  142. package/dist/server/grouping/types.d.ts +9 -0
  143. package/dist/server/grouping/types.js +1 -0
  144. package/dist/server/server.d.ts +37 -0
  145. package/dist/server/server.js +1283 -0
  146. package/dist/shared/grouping.d.ts +4 -0
  147. package/dist/shared/grouping.js +170 -0
  148. package/dist/types/diff.d.ts +263 -0
  149. package/dist/types/diff.js +1 -0
  150. package/dist/types/watch.d.ts +59 -0
  151. package/dist/types/watch.js +8 -0
  152. package/dist/utils/commentFormatting.d.ts +6 -0
  153. package/dist/utils/commentFormatting.js +90 -0
  154. package/dist/utils/commentImports.d.ts +11 -0
  155. package/dist/utils/commentImports.js +386 -0
  156. package/dist/utils/createId.d.ts +1 -0
  157. package/dist/utils/createId.js +5 -0
  158. package/dist/utils/diffMode.d.ts +3 -0
  159. package/dist/utils/diffMode.js +13 -0
  160. package/dist/utils/diffSelection.d.ts +13 -0
  161. package/dist/utils/diffSelection.js +39 -0
  162. package/dist/utils/editorOptions.d.ts +60 -0
  163. package/dist/utils/editorOptions.js +171 -0
  164. package/dist/utils/fileUtils.d.ts +12 -0
  165. package/dist/utils/fileUtils.js +21 -0
  166. package/dist/utils/hubConfig.d.ts +32 -0
  167. package/dist/utils/hubConfig.js +51 -0
  168. package/dist/utils/suggestionUtils.d.ts +19 -0
  169. package/dist/utils/suggestionUtils.js +31 -0
  170. package/dist/utils/turnLog.d.ts +47 -0
  171. package/dist/utils/turnLog.js +133 -0
  172. package/package.json +105 -0
@@ -0,0 +1,3 @@
1
+ import type { GroupingResult } from './types.js';
2
+ export declare function readCache(baseDir: string, targetHash: string, model: string): GroupingResult | null;
3
+ export declare function writeCache(baseDir: string, targetHash: string, model: string, result: GroupingResult): void;
@@ -0,0 +1,22 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ function fileFor(baseDir, targetHash, model) {
4
+ const safe = `${targetHash}-${model}`.replace(/[^a-zA-Z0-9._-]/g, '_');
5
+ return join(baseDir, 'grouping', `${safe}.json`);
6
+ }
7
+ export function readCache(baseDir, targetHash, model) {
8
+ const file = fileFor(baseDir, targetHash, model);
9
+ if (!existsSync(file))
10
+ return null;
11
+ try {
12
+ return JSON.parse(readFileSync(file, 'utf8'));
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export function writeCache(baseDir, targetHash, model, result) {
19
+ const file = fileFor(baseDir, targetHash, model);
20
+ mkdirSync(join(baseDir, 'grouping'), { recursive: true });
21
+ writeFileSync(file, JSON.stringify(result), 'utf8');
22
+ }
@@ -0,0 +1,10 @@
1
+ import type { DiffResponse } from '../../types/diff.js';
2
+ import type { GroupingResult } from './types.js';
3
+ /** Injectable so tests never spawn a process. */
4
+ export interface ClaudeRunner {
5
+ run(prompt: string, model: string): Promise<string>;
6
+ }
7
+ export declare function groupWithClaude(diff: DiffResponse, intent: string | null, opts: {
8
+ model: string;
9
+ runner?: ClaudeRunner;
10
+ }): Promise<GroupingResult>;
@@ -0,0 +1,95 @@
1
+ // src/server/grouping/claudeCli.ts
2
+ import { spawn } from 'child_process';
3
+ import { groupSmart } from '../../shared/grouping.js';
4
+ import { buildDigest } from './digest.js';
5
+ import { reconcile } from './reconcile.js';
6
+ // User-invoked (they click Group and wait), and a cold `claude -p` start with a
7
+ // multi-file digest routinely exceeds 20s — a short timeout made the AI path
8
+ // almost always fall back to heuristic. 60s gives real calls room to finish.
9
+ const DEFAULT_TIMEOUT_MS = 60_000;
10
+ function buildPrompt(digest) {
11
+ return [
12
+ 'You are grouping the changed files of a code diff for review.',
13
+ 'Return ONLY a JSON object, no prose, of the form:',
14
+ '{"groups":[{"label":"short intent phrase","rationale":"one line","files":["path"]}],"noise":["generated/path"]}',
15
+ 'Rules: use ONLY the paths listed below; put generated/lockfile/build-output paths in "noise";',
16
+ 'order groups so the most important thing to review is first; every non-noise path in exactly one group.',
17
+ '',
18
+ digest,
19
+ ].join('\n');
20
+ }
21
+ /** Extract a JSON object from the CLI envelope, tolerant of surrounding prose. */
22
+ function extractGrouping(stdout) {
23
+ const envelope = JSON.parse(stdout);
24
+ if (typeof envelope.result !== 'string')
25
+ throw new Error('envelope missing result');
26
+ const inner = envelope.result;
27
+ const start = inner.indexOf('{');
28
+ const end = inner.lastIndexOf('}');
29
+ if (start === -1 || end === -1 || end < start)
30
+ throw new Error('no JSON object in output');
31
+ return JSON.parse(inner.slice(start, end + 1));
32
+ }
33
+ const defaultRunner = {
34
+ run(prompt, model) {
35
+ return new Promise((resolve, reject) => {
36
+ const child = spawn('claude', ['-p', '--output-format', 'json', '--model', model], {
37
+ // Capture stderr so a failing call reports *why* instead of silently
38
+ // falling back to heuristic with no diagnostic.
39
+ stdio: ['pipe', 'pipe', 'pipe'],
40
+ // Mark this Claude session as diff-easy-internal so our own turn-capture
41
+ // hook (the `snapshot` command) no-ops instead of recording the grouping
42
+ // call as a spurious agent turn. See src/cli/turns.ts:runSnapshot.
43
+ env: { ...process.env, DIFIT_INTERNAL_CLAUDE: '1' },
44
+ });
45
+ let out = '';
46
+ let err = '';
47
+ let settled = false;
48
+ const timer = setTimeout(() => {
49
+ if (settled)
50
+ return;
51
+ settled = true;
52
+ child.kill('SIGKILL');
53
+ reject(new Error(`claude timed out after ${DEFAULT_TIMEOUT_MS}ms`));
54
+ }, DEFAULT_TIMEOUT_MS);
55
+ child.stdout.on('data', (d) => (out += d.toString()));
56
+ child.stderr.on('data', (d) => (err += d.toString()));
57
+ child.on('error', (e) => {
58
+ clearTimeout(timer);
59
+ if (settled)
60
+ return;
61
+ settled = true;
62
+ reject(e);
63
+ });
64
+ child.on('close', (code) => {
65
+ clearTimeout(timer);
66
+ if (settled)
67
+ return;
68
+ settled = true;
69
+ if (code === 0)
70
+ resolve(out);
71
+ else
72
+ reject(new Error(`claude exited ${code}: ${err.trim().slice(0, 300) || '(no stderr)'}`));
73
+ });
74
+ child.stdin.end(prompt);
75
+ });
76
+ },
77
+ };
78
+ export async function groupWithClaude(diff, intent, opts) {
79
+ const runner = opts.runner ?? defaultRunner;
80
+ const { text, changedPaths } = buildDigest(diff, intent);
81
+ try {
82
+ const stdout = await runner.run(buildPrompt(text), opts.model);
83
+ const raw = extractGrouping(stdout);
84
+ const { groups, noise } = reconcile(raw, changedPaths);
85
+ return { mode: 'ai', groups, noise, model: opts.model, generatedAt: Date.now() };
86
+ }
87
+ catch (error) {
88
+ // Any failure (process error, timeout, unparseable output, invalid shape)
89
+ // degrades gracefully to the heuristic provider rather than surfacing an error.
90
+ // Log why, so "grouped without AI" is diagnosable instead of a silent mystery.
91
+ const reason = error instanceof Error ? error.message : String(error);
92
+ console.warn(`[grouping] claude grouping failed for ${changedPaths.length} files; using heuristic. Reason: ${reason}`);
93
+ return { ...groupSmart(diff.files), fallbackFrom: 'ai' };
94
+ }
95
+ }
@@ -0,0 +1,15 @@
1
+ import type { DiffResponse } from '../../types/diff.js';
2
+ export interface Digest {
3
+ text: string;
4
+ changedPaths: string[];
5
+ }
6
+ /** Hunk headers add function/scope context but drive most of the model's output
7
+ * tokens (which dominate latency). They pay off on small diffs and starve large
8
+ * ones — a 64-file digest with 8 headers/file made the `claude -p` call exceed
9
+ * the 60s timeout, while paths alone grouped the same files in ~18s. So scale
10
+ * the per-file header budget down as the file count grows. */
11
+ export declare function hunkHeaderBudget(fileCount: number): number;
12
+ /** Compact, token-bounded representation of a diff for the grouping model.
13
+ * We send file paths + status + line counts + (for smaller diffs) a few hunk
14
+ * headers, not the full diff, to keep latency/cost low. */
15
+ export declare function buildDigest(diff: DiffResponse, intent: string | null): Digest;
@@ -0,0 +1,38 @@
1
+ /** Hunk headers add function/scope context but drive most of the model's output
2
+ * tokens (which dominate latency). They pay off on small diffs and starve large
3
+ * ones — a 64-file digest with 8 headers/file made the `claude -p` call exceed
4
+ * the 60s timeout, while paths alone grouped the same files in ~18s. So scale
5
+ * the per-file header budget down as the file count grows. */
6
+ export function hunkHeaderBudget(fileCount) {
7
+ if (fileCount > 40)
8
+ return 0;
9
+ if (fileCount > 15)
10
+ return 3;
11
+ return 8;
12
+ }
13
+ /** Compact, token-bounded representation of a diff for the grouping model.
14
+ * We send file paths + status + line counts + (for smaller diffs) a few hunk
15
+ * headers, not the full diff, to keep latency/cost low. */
16
+ export function buildDigest(diff, intent) {
17
+ const changedPaths = diff.files.map((f) => f.path);
18
+ const perFileHeaders = hunkHeaderBudget(diff.files.length);
19
+ const lines = [];
20
+ if (intent && intent.trim()) {
21
+ lines.push('INTENT (the change was made to accomplish this):');
22
+ lines.push(intent.trim().slice(0, 2000));
23
+ lines.push('');
24
+ }
25
+ lines.push(`CHANGED FILES (${diff.files.length}):`);
26
+ for (const file of diff.files) {
27
+ lines.push(`${file.status} ${file.path} (+${file.additions} -${file.deletions})`);
28
+ if (perFileHeaders === 0)
29
+ continue;
30
+ const headers = file.chunks
31
+ .map((c) => c.header)
32
+ .filter((h) => typeof h === 'string' && h.length > 0)
33
+ .slice(0, perFileHeaders);
34
+ for (const h of headers)
35
+ lines.push(` ${h}`);
36
+ }
37
+ return { text: lines.join('\n'), changedPaths };
38
+ }
@@ -0,0 +1,6 @@
1
+ import type { FileGroup, RawGrouping } from './types.js';
2
+ /** Force the model's loose output onto the real changed-file set. */
3
+ export declare function reconcile(raw: RawGrouping, changedPaths: string[]): {
4
+ groups: FileGroup[];
5
+ noise: string[];
6
+ };
@@ -0,0 +1,34 @@
1
+ function asStringArray(value) {
2
+ if (!Array.isArray(value))
3
+ return [];
4
+ return value.filter((v) => typeof v === 'string');
5
+ }
6
+ /** Force the model's loose output onto the real changed-file set. */
7
+ export function reconcile(raw, changedPaths) {
8
+ const changed = new Set(changedPaths);
9
+ const assigned = new Set();
10
+ const noise = asStringArray(raw.noise).filter((p) => changed.has(p));
11
+ for (const p of noise)
12
+ assigned.add(p);
13
+ const groups = [];
14
+ for (const g of Array.isArray(raw.groups) ? raw.groups : []) {
15
+ const files = asStringArray(g.files).filter((p) => changed.has(p) && !assigned.has(p));
16
+ if (files.length === 0)
17
+ continue;
18
+ for (const p of files)
19
+ assigned.add(p);
20
+ groups.push({
21
+ id: `g${groups.length + 1}`,
22
+ label: typeof g.label === 'string' && g.label.trim()
23
+ ? g.label.trim()
24
+ : `Group ${groups.length + 1}`,
25
+ rationale: typeof g.rationale === 'string' ? g.rationale : undefined,
26
+ files,
27
+ });
28
+ }
29
+ const leftover = changedPaths.filter((p) => !assigned.has(p));
30
+ if (leftover.length > 0) {
31
+ groups.push({ id: `g${groups.length + 1}`, label: 'Other', files: leftover });
32
+ }
33
+ return { groups, noise };
34
+ }
@@ -0,0 +1,9 @@
1
+ export type { FileGroup, GroupingResult } from '../../types/diff.js';
2
+ export interface RawGrouping {
3
+ groups: Array<{
4
+ label?: unknown;
5
+ rationale?: unknown;
6
+ files?: unknown;
7
+ }>;
8
+ noise?: unknown;
9
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,37 @@
1
+ import { type Server } from 'http';
2
+ import { DiffMode } from '../types/watch.js';
3
+ import { type CommentImport, type DiffSelection } from '@/types/diff.js';
4
+ interface ServerOptions {
5
+ selection?: DiffSelection;
6
+ stdinDiff?: string;
7
+ preferredPort?: number;
8
+ host?: string;
9
+ openBrowser?: boolean;
10
+ ignoreWhitespace?: boolean;
11
+ clearComments?: boolean;
12
+ commentImports?: CommentImport[];
13
+ keepAlive?: boolean;
14
+ diffMode?: DiffMode;
15
+ repoPath?: string;
16
+ contextLines?: number;
17
+ /** Hub mode: start with no workspace and let the UI pick one at runtime. */
18
+ hub?: boolean;
19
+ /** CLI `--pr` launch: the PR's title/body, for grouping intent. */
20
+ prTitle?: string;
21
+ prBody?: string;
22
+ }
23
+ /**
24
+ * Disable git's optional index-refresh lock for every git child this process
25
+ * spawns. Read commands (git status/diff) otherwise opportunistically rewrite
26
+ * `.git/index`'s stat cache; the file watcher treats that as a change and
27
+ * triggers an endless reload loop (fetch → index rewrite → reload → fetch …).
28
+ * `??=` respects an explicit operator override.
29
+ */
30
+ export declare function disableGitOptionalLocks(): void;
31
+ export declare function startServer(options: ServerOptions): Promise<{
32
+ port: number;
33
+ url: string;
34
+ isEmpty?: boolean;
35
+ server?: Server;
36
+ }>;
37
+ export {};