@aiworker/web 1.22.2 → 1.23.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 (173) hide show
  1. package/bin/cli.js +7 -0
  2. package/dist/assets/{CodeMirrorCommentWidgets-CSICpWQD.js → CodeMirrorCommentWidgets-B1Gizi3a.js} +1 -1
  3. package/dist/assets/DiffView-DXjVsEOw.js +4 -0
  4. package/dist/assets/{DirtyBranchSwitchDialog-C22HdWj4.js → DirtyBranchSwitchDialog-aig6zJjg.js} +1 -1
  5. package/dist/assets/FilesView-CJdG0-wk.js +17 -0
  6. package/dist/assets/{GitView-VQQkYuUw.js → GitView-BXsS3jG6.js} +4 -4
  7. package/dist/assets/{JsonTreeView-6lrhwD3q.js → JsonTreeView-D_hpXBnF.js} +1 -1
  8. package/dist/assets/{LinearIssuesView-Ba6Q9569.js → LinearIssuesView-BoUiLQG1.js} +1 -1
  9. package/dist/assets/MarkdownImageGallery-B_B_a9KS.js +1 -0
  10. package/dist/assets/MarkdownRendererImpl-xgyyLiBM.js +12 -0
  11. package/dist/assets/OnboardingScreen-DIp84OWt.js +1 -0
  12. package/dist/assets/{PatchDiff-BzcZPlOq.js → PatchDiff-8p6nLJaU.js} +1 -1
  13. package/dist/assets/PierreDiffViewer-DjC8nTk9.js +161 -0
  14. package/dist/assets/{PlanView-DyDt_nZ_.js → PlanView-CJTmOF6I.js} +2 -2
  15. package/dist/assets/SettingsView-DD-JqIJx.js +28 -0
  16. package/dist/assets/{SettingsWindow-DDQYaKWQ.js → SettingsWindow-Cpb4hM6q.js} +1 -1
  17. package/dist/assets/SymbolsNerdFontMono-Regular-aK5vsLov.woff2 +0 -0
  18. package/dist/assets/{ToolOutputDialog-iYF8Y2bz.js → ToolOutputDialog-DfaKZXNU.js} +6 -6
  19. package/dist/assets/{ToolPartDiffPreview-CdjbOVMM.js → ToolPartDiffPreview-DtZLzrSK.js} +2 -2
  20. package/dist/assets/{VirtualizedFileDiff-fFFm2QgH.js → VirtualizedFileDiff-C8i9HpTB.js} +1 -1
  21. package/dist/assets/WalkthroughView-DeKMvZlL.js +4 -0
  22. package/dist/assets/{computeVirtualFileMetrics-CzgLum_K.js → computeVirtualFileMetrics-BBI7ZwrC.js} +2 -2
  23. package/dist/assets/contextPanelEmbeddedChat-BdA6bdky.js +1 -0
  24. package/dist/assets/de-DCrsQ5zS.js +10 -0
  25. package/dist/assets/es-Dwv4YfH6.js +16 -0
  26. package/dist/assets/fr-BCWg4Cnx.js +14 -0
  27. package/dist/assets/ghostty-vt-DdA0Zryv.wasm +0 -0
  28. package/dist/assets/index-B68h5S5B.css +1 -0
  29. package/dist/assets/index-DVwMarHI.js +39 -0
  30. package/dist/assets/{index-Bl3LSbjn.js → index-jXSK5RrB.js} +1 -1
  31. package/dist/assets/{isDiffPlainText-Dx-dB_8l.js → isDiffPlainText-B_x_rVon.js} +1 -1
  32. package/dist/assets/{isFilePlainText-CWFPwmnU.js → isFilePlainText-DW_xMer1.js} +1 -1
  33. package/dist/assets/ja-Bruw3RYC.js +16 -0
  34. package/dist/assets/ko-idhXGF8j.js +16 -0
  35. package/dist/assets/main-BkyaQhJX.js +930 -0
  36. package/dist/assets/{main-DnBAoktx.js → main-CzdPuL0s.js} +2 -2
  37. package/dist/assets/markdownCore-Cn4Tyejo.js +5 -0
  38. package/dist/assets/{miniChat-BKyvLseN.js → miniChat-CCkqKOLp.js} +2 -2
  39. package/dist/assets/{mobile-DKjeFgMN.js → mobile-mvxTo055.js} +2 -2
  40. package/dist/assets/patchFileDiff-Csk4iNUi.js +13 -0
  41. package/dist/assets/pl-CGZWUqZJ.js +16 -0
  42. package/dist/assets/pt-BR-B8V7u7tA.js +16 -0
  43. package/dist/assets/renderElectronMiniChatApp-BxoyujA5.js +2 -0
  44. package/dist/assets/renderMobileApp-B48CsP4r.js +25 -0
  45. package/dist/assets/runtimeEndpointReset-Bg5gx18H.js +17 -0
  46. package/dist/assets/scheduledTasksApi-BW0Etzwz.js +1 -0
  47. package/dist/assets/{shikiHighlight-TEQCmzW8.js → shikiHighlight-CRl0SUC9.js} +1 -1
  48. package/dist/assets/surface-oMvhsog3.js +1 -0
  49. package/dist/assets/tr-BK_KiO2V.js +7 -0
  50. package/dist/assets/uk-CG74xV8N.js +16 -0
  51. package/dist/assets/{useAppFontEffects-D7sNpuL2.js → useAppFontEffects-CPKeOpUo.js} +160 -157
  52. package/dist/assets/useGitComparison-DH220WmO.js +1 -0
  53. package/dist/assets/useInlineCommentController-BiWz4AVb.js +2 -0
  54. package/dist/assets/useRootScrollLock-BS4-kr7P.js +1 -0
  55. package/dist/assets/{vendor-base-ui-BeMD8X_w.js → vendor-base-ui-DdV8vhHn.js} +1 -1
  56. package/dist/assets/{vendor-codemirror-language-data-Dp3KMRQG.js → vendor-codemirror-language-data-Bg4AEeCr.js} +2 -2
  57. package/dist/assets/vendor-zod-BPAANqvH.js +39 -0
  58. package/dist/assets/{web-components-_mNEMjFY.js → web-components-z7hfnS8n.js} +1 -1
  59. package/dist/assets/zh-CN-T4AXofZI.js +16 -0
  60. package/dist/assets/zh-TW-Dte40sxp.js +16 -0
  61. package/dist/index.html +5 -43
  62. package/dist/mini-chat.html +4 -4
  63. package/dist/mobile.html +4 -4
  64. package/package.json +2 -3
  65. package/server/index.d.ts +15 -0
  66. package/server/index.js +45 -9
  67. package/server/lib/data-dir-migration.js +40 -0
  68. package/server/lib/data-dir-migration.test.js +59 -0
  69. package/server/lib/event-stream/runtime.test.js +12 -12
  70. package/server/lib/fs/DOCUMENTATION.md +3 -0
  71. package/server/lib/fs/routes.js +31 -0
  72. package/server/lib/fs/routes.test.js +110 -1
  73. package/server/lib/git/DOCUMENTATION.md +29 -5
  74. package/server/lib/git/routes.js +24 -4
  75. package/server/lib/git/service.js +184 -198
  76. package/server/lib/git/service.test.js +422 -25
  77. package/server/lib/inherited-env.js +16 -9
  78. package/server/lib/inherited-env.test.js +8 -9
  79. package/server/lib/message-queue/DOCUMENTATION.md +4 -1
  80. package/server/lib/network-defaults.js +23 -0
  81. package/server/lib/network-defaults.test.js +52 -0
  82. package/server/lib/opencode/DOCUMENTATION.md +17 -1
  83. package/server/lib/opencode/bootstrap-runtime.js +2 -0
  84. package/server/lib/opencode/feature-routes-runtime.js +2 -0
  85. package/server/lib/opencode/openchamber-routes.js +64 -4
  86. package/server/lib/opencode/openchamber-routes.test.js +128 -1
  87. package/server/lib/opencode/routes.js +5 -3
  88. package/server/lib/opencode/settings-files.js +214 -0
  89. package/server/lib/opencode/settings-helpers.js +85 -41
  90. package/server/lib/opencode/settings-helpers.test.js +166 -1
  91. package/server/lib/opencode/settings-registry.json +747 -0
  92. package/server/lib/opencode/settings-runtime.js +139 -18
  93. package/server/lib/opencode/settings-runtime.test.js +163 -5
  94. package/server/lib/project-context/DOCUMENTATION.md +25 -1
  95. package/server/lib/project-context/routes.http.test.js +18 -0
  96. package/server/lib/project-context/routes.js +15 -0
  97. package/server/lib/project-context/runtime.js +248 -23
  98. package/server/lib/project-context/runtime.test.js +106 -3
  99. package/server/lib/projects/DOCUMENTATION.md +76 -0
  100. package/server/lib/projects/project-config.js +142 -9
  101. package/server/lib/projects/project-id.js +17 -0
  102. package/server/lib/projects/project-setup.js +409 -0
  103. package/server/lib/projects/project-setup.test.js +557 -0
  104. package/server/lib/projects/routes.js +59 -0
  105. package/server/lib/quota/DOCUMENTATION.md +31 -1
  106. package/server/lib/quota/providers/cline-pass.js +125 -0
  107. package/server/lib/quota/providers/cline-pass.test.js +103 -0
  108. package/server/lib/quota/providers/index.js +7 -0
  109. package/server/lib/quota/providers/neuralwatt.js +3 -6
  110. package/server/lib/quota/providers/neuralwatt.test.js +7 -7
  111. package/server/lib/quota/providers/ollama-cloud.js +31 -0
  112. package/server/lib/quota/providers/ollama-cloud.test.js +33 -1
  113. package/server/lib/quota/providers/opencode-go.js +1 -1
  114. package/server/lib/quota/providers/opencode-go.test.js +26 -1
  115. package/server/lib/quota/providers/openrouter.js +119 -24
  116. package/server/lib/quota/providers/openrouter.test.js +330 -0
  117. package/server/lib/relay/DOCUMENTATION.md +55 -0
  118. package/server/lib/relay/cross-compat.test.js +26 -0
  119. package/server/lib/relay/downstream-scheduler.js +154 -0
  120. package/server/lib/relay/downstream-scheduler.test.js +162 -0
  121. package/server/lib/relay/e2ee.js +10 -8
  122. package/server/lib/relay/flow-control.test.js +177 -0
  123. package/server/lib/relay/host-client.js +37 -8
  124. package/server/lib/relay/tunnel-codec.js +9 -0
  125. package/server/lib/relay/tunnel-host.js +66 -17
  126. package/server/lib/session-assist/runtime.js +6 -10
  127. package/server/lib/session-goal/runtime.js +4 -9
  128. package/server/lib/small-model/DOCUMENTATION.md +3 -2
  129. package/server/lib/small-model/call.js +9 -4
  130. package/server/lib/small-model/call.test.js +29 -0
  131. package/server/lib/small-model/index.js +5 -10
  132. package/server/lib/small-model/runtime-providers.js +15 -7
  133. package/server/lib/small-model/runtime-providers.test.js +7 -1
  134. package/server/lib/terminal/DOCUMENTATION.md +4 -4
  135. package/server/lib/terminal/runtime.js +11 -6
  136. package/server/lib/terminal/runtime.test.js +20 -13
  137. package/server/lib/walkthrough/DOCUMENTATION.md +33 -14
  138. package/server/lib/walkthrough/model-settings.js +8 -12
  139. package/server/lib/walkthrough/prompt.js +3 -1
  140. package/server/lib/walkthrough/sources.js +23 -2
  141. package/dist/assets/DiffView-hDBKesA_.js +0 -4
  142. package/dist/assets/FilesView-CdWzeOp9.js +0 -17
  143. package/dist/assets/MarkdownImageGallery-Czv_mhvS.js +0 -1
  144. package/dist/assets/MarkdownRendererImpl-BrMcswiy.js +0 -12
  145. package/dist/assets/OnboardingScreen-ChxQoAkx.js +0 -1
  146. package/dist/assets/PierreDiffViewer-BchBtZlp.js +0 -157
  147. package/dist/assets/SettingsView-BKf5xBsP.js +0 -28
  148. package/dist/assets/WalkthroughView-DOeCsMju.js +0 -4
  149. package/dist/assets/contextPanelEmbeddedChat-C0ZLAepL.js +0 -1
  150. package/dist/assets/de-B5JaA3SA.js +0 -10
  151. package/dist/assets/es-EPIH88ii.js +0 -16
  152. package/dist/assets/fr-CAMfxUdZ.js +0 -14
  153. package/dist/assets/index-DF5kMP-i.js +0 -39
  154. package/dist/assets/index-Dl3xbAB_.css +0 -1
  155. package/dist/assets/ja-CifOE1VN.js +0 -16
  156. package/dist/assets/ko-BBDyAyYQ.js +0 -16
  157. package/dist/assets/main-MhSaOZoB.js +0 -930
  158. package/dist/assets/markdownCore-BrBwOOMn.js +0 -3
  159. package/dist/assets/patchFileDiff-o8ms1me8.js +0 -10
  160. package/dist/assets/pl-DgEHYfFM.js +0 -16
  161. package/dist/assets/pt-BR-DuWHojUw.js +0 -16
  162. package/dist/assets/renderElectronMiniChatApp-CDgBwwCS.js +0 -2
  163. package/dist/assets/renderMobileApp-DJIKIK1r.js +0 -25
  164. package/dist/assets/runtimeEndpointReset-CYJ9O_xX.js +0 -17
  165. package/dist/assets/scheduledTasksApi-D8yamh1b.js +0 -1
  166. package/dist/assets/tr-D5m2yIWL.js +0 -7
  167. package/dist/assets/uk-D_q60oog.js +0 -16
  168. package/dist/assets/useInlineCommentController-Brz_cLcQ.js +0 -2
  169. package/dist/assets/useRootScrollLock-lXEnWlaU.js +0 -1
  170. package/dist/assets/vendor-ghostty-web-CrxinGGH.js +0 -13
  171. package/dist/assets/vendor-zod-CK21ihK-.js +0 -39
  172. package/dist/assets/zh-CN-D8QvsVeg.js +0 -16
  173. package/dist/assets/zh-TW-B9t7rQQO.js +0 -16
@@ -25,9 +25,11 @@ The following functions are exported and used by the web server:
25
25
 
26
26
  ### Status and Diff Operations
27
27
  - `getStatus(directory)`: Get comprehensive Git status including current branch, tracking, ahead/behind, file changes, diff stats, merge/rebase state.
28
- - `getDiff(directory, { path, staged, contextLines })`: Get diff output for files or entire working tree. Untracked symbolic links are represented as link entries without following their targets.
29
- - `getRangeDiff(directory, { base, head, path, contextLines })`: Get diff between two refs. Uses three-dot `base...head` semantics, so work merged into `head` from `base` is excluded and only the branch's own changes are returned. Prefers `origin/<base>` when that remote-tracking ref exists, so a stale local base branch does not resurface already-merged commits. Exposed as `GET /api/git/range-diff` (`path` optional; omit it for the whole range).
30
- - `getRangeFiles(directory, { base, head })`: Get list of changed files between two refs.
28
+ - `getDiff(directory, { path, staged, contextLines })`: Get diff output for files or entire working tree with full Git blob identities. Untracked symbolic links are represented as link entries without following their targets.
29
+ - `getRangeDiff(directory, { base, head, path, contextLines, includeWorkingTree })`: Compare the merge base of the exact selected refs with `head`. With `includeWorkingTree: true`, compare with the checked-out branch's current files instead, including committed, staged, unstaged, and untracked work in one net diff. This mode rejects a head that is not the checked-out branch. Exposed as `GET /api/git/range-diff`; omit `path` for the whole comparison.
30
+ - `getRangeFiles(directory, { base, head, includeWorkingTree })`: List changed paths using the same comparison as `getRangeDiff`. A successful empty list means the final files match the merge base, even if staging and working-tree changes cancel each other out.
31
+ - Both range operations honor refs literally. A local `main` is never replaced with `origin/main`, and an unavailable ref fails rather than choosing a different remote. The UI picker sends qualified refs to distinguish local and remote branches with matching display names.
32
+ - Working-tree comparisons use the real index read-only. When untracked paths exist, a temporary copy of the index receives intent-to-add entries so Git computes additions, deletions, recreations, and renames together. Current contents come from the working tree, symlinks remain links, ignored files stay excluded, and temporary files are removed on success or failure.
31
33
  - `getFileDiff(directory, { path, staged })`: Get original and modified file contents for a single file (handles images as data URLs and symbolic links as their link-target text).
32
34
  - `listUntrackedPaths(directory)`: List individual untracked file paths honoring ignore rules. Much cheaper than `getStatus` when that is all a caller needs. Deliberately not `--directory`: collapsed directory entries end in a slash and are rejected by the per-file diff helpers, so a caller would silently lose every file inside a new directory.
33
35
  - `getUntrackedDiffs(directory, filePaths, { concurrency, contextLines })`: Diffs for untracked files against an empty tree. Resolves the repository context once instead of per file (`getDiff` re-resolves every call, costing an extra `rev-parse` each time) and bounds how many diff processes run at once. Returns one entry per input path in order; unreadable paths yield `''` rather than failing the batch.
@@ -35,9 +37,10 @@ The following functions are exported and used by the web server:
35
37
  - `revertFile(directory, filePath, options)`: Revert a file. Default scope `all` discards staged and working-tree changes; scope `working` discards only unstaged/working-tree changes.
36
38
  - `stageFile(directory, filePath)`: Add one file path to the index.
37
39
  - `unstageFile(directory, filePath)`: Remove one file path from the index while preserving working-tree content.
38
- - `applyHunk(directory, filePath, options)`: Apply a single-hunk patch via `git apply`. `options.action` is `stage` (`git apply --cached`), `unstage` (`git apply --cached --reverse`), or `discard` (`git apply --reverse` in the working tree). The patch is written to a temp file; a `--check` runs first so a stale hunk fails with a clear "refresh and try again" error instead of a partial mutation. The patch target path must match the requested file.
40
+ - `applyHunk(directory, filePath, options)`: Apply a single-hunk patch via `git apply`. `options.action` is `stage` (`git apply --cached`), `unstage` (`git apply --cached --reverse`), or `discard` (`git apply --reverse` in the working tree). Inside the index mutation queue, the server verifies that the complete patch exactly matches one current three-context-line hunk for that file and scope, then runs `--check` before applying. Applicability alone cannot prove an unstaged change: old staged or committed hunks can reverse cleanly too. Stale, historical and multi-file patches fail with a refresh error. Temporary patch files are removed on success and failure; hunk content retains CRLF bytes.
39
41
 
40
42
  ### Branch Operations
43
+ - `getBranchBase(directory, branch)`: Read a named creation source from reflog. After a rebase, the creation source is no longer a current parent record, so return `null` and let the user choose a base. Explicit per-runtime, directory, and branch choices in the shared UI outrank detection.
41
44
  - `getBranches(directory)`: Get list of local and remote branches (filtered to active remote branches).
42
45
  - `getUnpushedBranchCounts(directory, branchNames)`: Count commits ahead of each locally known upstream for up to five supplied local branches. This reads local refs only and omits branches without an upstream.
43
46
  - `createBranch(directory, branchName, options)`: Create and checkout a new branch.
@@ -71,7 +74,8 @@ bootstrap, tracking is left unset rather than writing `branch.*.remote` /
71
74
 
72
75
  ### Log Operations
73
76
  - `getLog(directory, options)`: Get commit history with stats (supports maxCount, from, to, file filters).
74
- - `getCommitFiles(directory, commitHash)`: Get file changes for a specific commit.
77
+ - `getCommitFiles(directory, commitHash)`: Get file changes for a specific commit relative to its first parent, or the empty tree for a root commit. NUL-delimited paths preserve whitespace; renamed files return their destination in `path` and source in `previousPath`.
78
+ - `getCommitDiff(directory, { hash, path, previousPath, contextLines })`: Get the same commit's patch, with optional file filtering and context depth. `previousPath` keeps a rename's old and new paths in the per-file patch. Reads committed objects only, never the working tree. Exposed as `GET /api/git/commit-diff`; an unavailable hash fails rather than returning an empty diff.
75
79
  - `getCommitFileDiff(directory, hash, filePath, isBinary)`: Get before/after content for a specific file in a commit. Returns `{ original, modified, isBinary }`. Runs `git show <hash>^:<path>` and `git show <hash>:<path>` in parallel; returns empty strings on failure (added/deleted/root-commit edge cases).
76
80
 
77
81
  ### Merge and Rebase Operations
@@ -130,8 +134,28 @@ The following functions are internal helpers used by exported functions:
130
134
 
131
135
  ### Runtime availability of range diffs
132
136
  - `GET /api/git/range-diff` is served by the AiWorker web server, so it is available to web, desktop, and mobile clients. The shared `GitAPI.getGitRangeDiff` is therefore optional: web supplies the HTTP implementation, and VS Code does not implement it because the extension host serves Git through its own bridge rather than these routes. Features built on range diffs (currently the AI diff walkthrough) are not offered in VS Code.
137
+ - Commit comparison uses the same server boundary through optional `GitAPI.getGitCommitDiff`. Desktop Changes, mobile Changes, and the existing walkthrough surface share branch/commit comparison semantics. Mobile Changes uses the same selectors and `useGitComparison` file-list owner, with a read-only list-to-detail flow. VS Code keeps its existing modes because its Git bridge does not provide these comparison operations. The HTTP operations are available to web, Electron, hosted mobile, and Capacitor clients.
133
138
 
134
139
  ### Staged and unstaged change handling
140
+ - Desktop Changes floats a compact action capsule after each hunk's last changed row,
141
+ including single-hunk files. Whole-file controls remain in the Git panel.
142
+ `getPatchHunkAnchors` uses the canonical patch's final changed row and side, so a hunk
143
+ ending in deletions is anchored after those deletions rather than above them.
144
+ Zero-height Pierre annotation slots anchor the capsule over following context
145
+ without a separate band. At EOF the capsule lifts inside the code column;
146
+ a one-line code column has a minimum hit-target height. React controls mount only
147
+ for currently rendered slots and only after their rendered diff and anchor
148
+ identities match the current props. Comment annotations remain independent.
149
+ - The canonical three-line-context action patch stays separate from the full-file
150
+ display patch. Their bytes must be identical, or their file headers and full
151
+ blob identities must match, including when reusing a cached action patch.
152
+ Mismatch leaves actions unavailable until Retry obtains a matching pair.
153
+ Successful hunk mutations invalidate
154
+ every mounted view of that path through `sessionEvents.requestGitRefresh`.
155
+ Actions remain unavailable until the refresh succeeds. Last turn, Branch and
156
+ Commit snapshots never expose hunk mutations. Mobile uses its separate Changes
157
+ surface and VS Code does not mount these controls.
158
+ - Untracked patches from `getDiff` and `getUntrackedDiffs` use `git diff --no-index` with separate stdout, stderr, and process exit status. Exit codes 0 and 1 return stdout only, so line-ending warnings never become patch text or request failures. Other exits and process failures reject the single-file request; the batch keeps an empty entry for the failed path and preserves the other results.
135
159
  - `status.files` exposes both `index` and `working_dir` codes. Shared UI uses these as separate scopes: staged rows are derived from non-empty `index` statuses, while unstaged rows are derived from `working_dir` statuses and untracked files.
136
160
  - A file with both staged and unstaged changes can appear in both UI sections. Staged rows request diffs with `staged: true`; unstaged rows request normal working-tree diffs.
137
161
  - The shared Git panel exposes explicit staging actions. Unstaged rows use `stageFile`, staged rows use `unstageFile`, and commits operate on the current staged index.
@@ -7,13 +7,13 @@ export function registerGitRoutes(app) {
7
7
  return gitLibraries;
8
8
  };
9
9
 
10
- const resolveDirectoryQuery = (value) => {
10
+ const resolveDirectoryQuery = (value, preserveWhitespace = false) => {
11
11
  const raw = Array.isArray(value) ? value[0] : value;
12
12
  if (typeof raw !== 'string') {
13
13
  return null;
14
14
  }
15
- const trimmed = raw.trim();
16
- return trimmed || null;
15
+ const normalized = preserveWhitespace ? raw : raw.trim();
16
+ return normalized || null;
17
17
  };
18
18
 
19
19
  const extractGitErrorText = (error) => {
@@ -417,6 +417,7 @@ export function registerGitRoutes(app) {
417
417
  const diff = await getRangeDiff(directory, {
418
418
  base,
419
419
  head,
420
+ includeWorkingTree: req.query.includeWorkingTree === 'true',
420
421
  path: pathParam,
421
422
  contextLines: Number.isFinite(context) ? context : 3,
422
423
  });
@@ -463,7 +464,7 @@ export function registerGitRoutes(app) {
463
464
  return res.status(400).json({ error: 'base and head parameters are required' });
464
465
  }
465
466
 
466
- const files = await getRangeFiles(directory, { base, head });
467
+ const files = await getRangeFiles(directory, { base, head, includeWorkingTree: req.query.includeWorkingTree === 'true' });
467
468
  res.json({ files });
468
469
  } catch (error) {
469
470
  console.error('Failed to get git range files:', error);
@@ -1300,6 +1301,25 @@ export function registerGitRoutes(app) {
1300
1301
  }
1301
1302
  });
1302
1303
 
1304
+ app.get('/api/git/commit-diff', async (req, res) => {
1305
+ const { getCommitDiff } = await getGitLibraries();
1306
+ try {
1307
+ const directory = resolveDirectoryQuery(req.query.directory);
1308
+ const hash = resolveDirectoryQuery(req.query.hash);
1309
+ if (!directory || !hash) return res.status(400).json({ error: 'directory and hash are required' });
1310
+ const context = Number(req.query.context ?? 3);
1311
+ const diff = await getCommitDiff(directory, {
1312
+ hash,
1313
+ path: resolveDirectoryQuery(req.query.path, true) ?? undefined,
1314
+ previousPath: resolveDirectoryQuery(req.query.previousPath, true) ?? undefined,
1315
+ contextLines: Number.isFinite(context) ? context : 3,
1316
+ });
1317
+ res.json({ diff });
1318
+ } catch (error) {
1319
+ res.status(500).json({ error: error.message || 'Failed to get commit diff' });
1320
+ }
1321
+ });
1322
+
1303
1323
  app.get('/api/git/commit-file-diff', async (req, res) => {
1304
1324
  const { getCommitFileDiff } = await getGitLibraries();
1305
1325
  try {