guito 1.0.10 → 1.0.11
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/bin/ai-review.js +4 -4
- package/bin/guito-server.js +92 -9
- package/bin/ui/browser/index.html +1 -1
- package/bin/ui/browser/main-3MOB345X.js +67 -0
- package/bin/working-review.js +329 -0
- package/{guito-1.0.10.vsix → guito-1.0.11.vsix} +0 -0
- package/package.json +1 -1
- package/bin/ui/browser/main-PE4C5Y3Z.js +0 -67
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to Guito are documented in this file. The project follows [S
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.0.11] - 2026-10-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Keyboard shortcuts for the commit history. **Ctrl+F** focuses the commit search box, **Ctrl+J** selects and scrolls to the current branch's commit (or the checked-out commit when HEAD is detached), and **Ctrl+G** opens a **Go to Commit** dialog. Type a row number to jump to that row of the list as currently shown, or a commit hash (at least 4 characters) to jump to that commit. History that isn't loaded yet is fetched as needed, and the dialog explains when a row is out of range, a hash is ambiguous or unknown, or the commit is hidden by the current branch or search filter. On macOS, Cmd works in place of Ctrl. The shortcuts do nothing while a dialog is open.
|
|
12
|
+
- **Code review of uncommitted changes.** A new **Code review** button sits after the stash buttons on the uncommitted changes panel. It has Claude Code review everything not yet committed: staged, unstaged and untracked files. While the review runs, the button turns into **Stop review**. When it finishes, the results open in a new tab: a VS Code editor tab inside the extension, or a browser tab in standalone Guito. Each finding shows its severity and comment and links to the file and line. Its **Auto fix** button has Claude Code fix that one problem in the working tree. Claude Code may only read and edit files; it cannot run commands or use git. The fix shows up as an ordinary uncommitted edit, and **Stop** cancels a running fix. Reviews and fixes use the configured review model, effort, instructions and timeout (`guito.aiReview.*`). The last review is kept per worktree in `.git/guito-working-review.json`.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Ref pills in the commit history now match the graph. Each branch pill takes the color of its graph line; the current branch stays filled, now in its line color instead of a fixed blue. Local and remote branches and tags share one pill style. Remote branches get a cloud icon and tags keep the tag icon, and tags use a soft yellow that no graph line uses. To keep that yellow unique, the graph's two yellowish line colors were replaced with violet and silver. Prefer a quieter look? The new **Gray branch and tag pills** setting (`guito.grayRefBadges` in VS Code, off by default) draws every branch and tag pill in one neutral gray, with the current branch filled.
|
|
17
|
+
- The close buttons on the uncommitted changes panel, commit detail, settings, pull request, diff and merge conflict views, and the error banner's dismiss button now share one look and behavior, matching the commit panel's close button.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- Stashes no longer appear in the commit history. The "Show stashes" setting (`guito.showStashes` in VS Code) is gone, and the latest stash commit is no longer drawn in the graph; stashes are listed only in the repository panel.
|
|
22
|
+
|
|
7
23
|
## [1.0.10] - 2026-10-10
|
|
8
24
|
|
|
9
25
|
### Added
|
package/README.md
CHANGED
|
@@ -97,9 +97,9 @@ The settings dialog also manages preferences that have no VS Code equivalent: th
|
|
|
97
97
|
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
98
98
|
| `guito.autoReload` | Reload commits and the working tree automatically when the repository changes outside Guito, for example from a terminal. On by default. |
|
|
99
99
|
| `guito.showGraph` | Show the Git graph beside the commit history. On by default. |
|
|
100
|
-
| `guito.showStashes` | Show stash entries above the commit history. Off by default. |
|
|
101
100
|
| `guito.showTags` | Show tag badges attached to commits. On by default. |
|
|
102
101
|
| `guito.showRemoteBranches` | Show remote branches in the repository panel and the branch pills on commits. On by default. |
|
|
102
|
+
| `guito.grayRefBadges` | Show branch and tag pills in a single neutral gray instead of graph lane colors. Off by default. |
|
|
103
103
|
| `guito.fileListView` | How changed-file lists are shown: `flat` (default) lists every file with its full path, `tree` groups them into collapsible folders. |
|
|
104
104
|
| `guito.refListView` | How branches and tags are shown in the repository panel: `flat` (default) lists full names, `tree` groups them into collapsible namespace folders. |
|
|
105
105
|
| `guito.sidePanelSectionsExpanded`| Open the repository panel with all sections expanded. On by default. |
|
package/bin/ai-review.js
CHANGED
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
-
const SEVERITIES = ['blocker', 'concern', 'suggestion', 'nit'];
|
|
5
|
+
export const SEVERITIES = ['blocker', 'concern', 'suggestion', 'nit'];
|
|
6
6
|
/** Effort levels Claude Code accepts for --effort; '' leaves it to Claude Code. */
|
|
7
7
|
export const AI_REVIEW_EFFORTS = ['', 'low', 'medium', 'high', 'xhigh', 'max'];
|
|
8
8
|
/** How much a drafted commit message says beyond its subject line. */
|
|
@@ -267,7 +267,7 @@ export function parseModelReview(raw) {
|
|
|
267
267
|
};
|
|
268
268
|
}
|
|
269
269
|
/** Identity of a finding across re-reviews: same place, same point. */
|
|
270
|
-
function fingerprint(file, title) {
|
|
270
|
+
export function fingerprint(file, title) {
|
|
271
271
|
const normalized = title
|
|
272
272
|
.toLowerCase()
|
|
273
273
|
.replace(/[^a-z0-9]+/g, ' ')
|
|
@@ -275,7 +275,7 @@ function fingerprint(file, title) {
|
|
|
275
275
|
return createHash('sha1').update(`${file}\u0000${normalized}`).digest('hex').slice(0, 16);
|
|
276
276
|
}
|
|
277
277
|
/** Renders one file's diff with the line numbers the model must cite. */
|
|
278
|
-
function renderFileDiff(diff, budget) {
|
|
278
|
+
export function renderFileDiff(diff, budget) {
|
|
279
279
|
const header = `--- FILE: ${diff.path}${diff.oldPath && diff.oldPath !== diff.path ? ` (renamed from ${diff.oldPath})` : ''} [${diff.status}] ---`;
|
|
280
280
|
if (diff.binary)
|
|
281
281
|
return `${header}\n(binary file, not reviewable)\n`;
|
|
@@ -320,7 +320,7 @@ function renderThreads(threads) {
|
|
|
320
320
|
})
|
|
321
321
|
.join('\n');
|
|
322
322
|
}
|
|
323
|
-
const REVIEW_CONTRACT = `Reply with one JSON object and nothing else:
|
|
323
|
+
export const REVIEW_CONTRACT = `Reply with one JSON object and nothing else:
|
|
324
324
|
{"summary": "<one or two sentences on the change as a whole>",
|
|
325
325
|
"findings": [
|
|
326
326
|
{"file": "<path exactly as given in the diff header, or \\"\\" for the change as a whole>",
|
package/bin/guito-server.js
CHANGED
|
@@ -16,6 +16,7 @@ import { avatarCache } from './avatars.js';
|
|
|
16
16
|
import { repositoryState } from './repository-state.js';
|
|
17
17
|
import { createAiReviewer, resolveAiReviewConfig, } from './ai-review.js';
|
|
18
18
|
import { generateCommitMessage } from './commit-message.js';
|
|
19
|
+
import { WorkingReviewBusyError, createWorkingReviewer, } from './working-review.js';
|
|
19
20
|
const execFileAsync = promisify(execFile);
|
|
20
21
|
const AZURE_API_VERSION = '5.0-preview';
|
|
21
22
|
// The Policy API has been GA since 4.1 and does not answer to the
|
|
@@ -250,7 +251,7 @@ const azureErrorMessage = (result) => {
|
|
|
250
251
|
return result.body;
|
|
251
252
|
}
|
|
252
253
|
};
|
|
253
|
-
export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 8080, apiToken, azureDevOpsUrl, prBranchNameTemplate, autoReload, diffViewer, showGraph,
|
|
254
|
+
export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 8080, apiToken, azureDevOpsUrl, prBranchNameTemplate, autoReload, diffViewer, showGraph, showTags, showRemoteBranches, grayRefBadges, issueRegex, issueUrl, allowMerge, fileListView, refListView, sidePanelSectionsExpanded, searchMode, searchCaseSensitive, worktreeSort, prSort, aiReview, azureRequestImpl, aiReviewModelImpl, avatarFetchImpl, onLog, }) {
|
|
254
255
|
// Initialize server
|
|
255
256
|
const app = fastify({
|
|
256
257
|
logger: false,
|
|
@@ -501,14 +502,18 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
501
502
|
// azureDevOpsUrl, which wins over the file.
|
|
502
503
|
let settingsPath = '';
|
|
503
504
|
let reviewStorePath = '';
|
|
505
|
+
// Per worktree: each one has its own uncommitted changes.
|
|
506
|
+
let workingReviewStorePath = '';
|
|
504
507
|
try {
|
|
505
508
|
const gitDir = (await git.revparse(['--absolute-git-dir'])).trim();
|
|
506
509
|
settingsPath = join(gitDir, 'guito-settings.json');
|
|
507
510
|
reviewStorePath = join(gitDir, 'guito-ai-reviews.json');
|
|
511
|
+
workingReviewStorePath = join(gitDir, 'guito-working-review.json');
|
|
508
512
|
}
|
|
509
513
|
catch {
|
|
510
514
|
settingsPath = join(repositoryPath, '.git', 'guito-settings.json');
|
|
511
515
|
reviewStorePath = join(repositoryPath, '.git', 'guito-ai-reviews.json');
|
|
516
|
+
workingReviewStorePath = join(repositoryPath, '.git', 'guito-working-review.json');
|
|
512
517
|
}
|
|
513
518
|
const readSettings = async () => {
|
|
514
519
|
try {
|
|
@@ -674,9 +679,9 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
674
679
|
const [file, azure] = await Promise.all([readSettings(), effectiveAzureUrl()]);
|
|
675
680
|
const fileAutoReload = typeof file.autoReload === 'boolean' ? file.autoReload : undefined;
|
|
676
681
|
const fileShowGraph = typeof file.showGraph === 'boolean' ? file.showGraph : undefined;
|
|
677
|
-
const fileShowStashes = typeof file.showStashes === 'boolean' ? file.showStashes : undefined;
|
|
678
682
|
const fileShowTags = typeof file.showTags === 'boolean' ? file.showTags : undefined;
|
|
679
683
|
const fileShowRemoteBranches = typeof file.showRemoteBranches === 'boolean' ? file.showRemoteBranches : undefined;
|
|
684
|
+
const fileGrayRefBadges = typeof file.grayRefBadges === 'boolean' ? file.grayRefBadges : undefined;
|
|
680
685
|
const fileFileListView = file.fileListView === 'tree' || file.fileListView === 'flat' ? file.fileListView : undefined;
|
|
681
686
|
const fileRefListView = file.refListView === 'tree' || file.refListView === 'flat' ? file.refListView : undefined;
|
|
682
687
|
const fileSidePanelSectionsExpanded = typeof file.sidePanelSectionsExpanded === 'boolean'
|
|
@@ -735,11 +740,11 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
735
740
|
autoReload: typeof autoReload === 'boolean' ? autoReload : (fileAutoReload ?? true),
|
|
736
741
|
diffViewer: diffViewer === 'vscode' ? 'vscode' : 'guito',
|
|
737
742
|
showGraph: typeof showGraph === 'boolean' ? showGraph : (fileShowGraph ?? true),
|
|
738
|
-
showStashes: typeof showStashes === 'boolean' ? showStashes : (fileShowStashes ?? false),
|
|
739
743
|
showTags: typeof showTags === 'boolean' ? showTags : (fileShowTags ?? true),
|
|
740
744
|
showRemoteBranches: typeof showRemoteBranches === 'boolean'
|
|
741
745
|
? showRemoteBranches
|
|
742
746
|
: (fileShowRemoteBranches ?? true),
|
|
747
|
+
grayRefBadges: typeof grayRefBadges === 'boolean' ? grayRefBadges : (fileGrayRefBadges ?? false),
|
|
743
748
|
fileListView: fileListView ?? fileFileListView ?? 'flat',
|
|
744
749
|
refListView: refListView ?? fileRefListView ?? 'flat',
|
|
745
750
|
sidePanelSectionsExpanded: typeof sidePanelSectionsExpanded === 'boolean'
|
|
@@ -804,6 +809,8 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
804
809
|
author_email: '%aE',
|
|
805
810
|
parents: '%P',
|
|
806
811
|
},
|
|
812
|
+
// Stashes are listed only in the side panel, never in the history graph.
|
|
813
|
+
'--exclude=refs/stash': null,
|
|
807
814
|
'--all': null,
|
|
808
815
|
};
|
|
809
816
|
if (Number.isFinite(limit) && limit > 0) {
|
|
@@ -814,7 +821,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
814
821
|
}
|
|
815
822
|
const [log, count, identity] = await Promise.all([
|
|
816
823
|
git.log(options),
|
|
817
|
-
git.raw(['rev-list', '--count', '--all']),
|
|
824
|
+
git.raw(['rev-list', '--count', '--exclude=refs/stash', '--all']),
|
|
818
825
|
configuredIdentity(),
|
|
819
826
|
]);
|
|
820
827
|
const commits = log.all.map((commit) => ({
|
|
@@ -850,7 +857,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
850
857
|
// NUL separators are safe because Git commit messages cannot contain
|
|
851
858
|
// NUL bytes. Searching in JS lets insensitive mode fold accents too,
|
|
852
859
|
// which Git's --regexp-ignore-case does not do consistently.
|
|
853
|
-
const raw = await git.raw(['log', '--all', '--format=%H%x00%B%x00']);
|
|
860
|
+
const raw = await git.raw(['log', '--exclude=refs/stash', '--all', '--format=%H%x00%B%x00']);
|
|
854
861
|
const fields = raw.split('\0');
|
|
855
862
|
const normalizedQuery = normalizeSearchText(query, caseSensitive);
|
|
856
863
|
const hashes = [];
|
|
@@ -1851,15 +1858,15 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
1851
1858
|
if (typeof body.autoReload === 'boolean') {
|
|
1852
1859
|
settings.autoReload = body.autoReload;
|
|
1853
1860
|
}
|
|
1854
|
-
if (typeof body.showStashes === 'boolean') {
|
|
1855
|
-
settings.showStashes = body.showStashes;
|
|
1856
|
-
}
|
|
1857
1861
|
if (typeof body.showTags === 'boolean') {
|
|
1858
1862
|
settings.showTags = body.showTags;
|
|
1859
1863
|
}
|
|
1860
1864
|
if (typeof body.showRemoteBranches === 'boolean') {
|
|
1861
1865
|
settings.showRemoteBranches = body.showRemoteBranches;
|
|
1862
1866
|
}
|
|
1867
|
+
if (typeof body.grayRefBadges === 'boolean') {
|
|
1868
|
+
settings.grayRefBadges = body.grayRefBadges;
|
|
1869
|
+
}
|
|
1863
1870
|
if (body.fileListView === 'tree' || body.fileListView === 'flat') {
|
|
1864
1871
|
settings.fileListView = body.fileListView;
|
|
1865
1872
|
}
|
|
@@ -3465,6 +3472,79 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
3465
3472
|
return resp.status(400).type('application/json').send({ error: err.message });
|
|
3466
3473
|
}
|
|
3467
3474
|
});
|
|
3475
|
+
// ==================== Code review of uncommitted changes ====================
|
|
3476
|
+
// One Claude Code run over everything not yet committed; the findings open
|
|
3477
|
+
// in their own tab, where each one can be handed back to Claude Code to fix.
|
|
3478
|
+
// Runs are started here and followed by polling GET /api/working-review.
|
|
3479
|
+
const workingReviewer = createWorkingReviewer({
|
|
3480
|
+
storePath: workingReviewStorePath,
|
|
3481
|
+
cwd: repositoryPath,
|
|
3482
|
+
loadChanges: async () => {
|
|
3483
|
+
await mutationQueue;
|
|
3484
|
+
const snapshot = await working.snapshot();
|
|
3485
|
+
let branch = '';
|
|
3486
|
+
try {
|
|
3487
|
+
branch = await currentBranch();
|
|
3488
|
+
}
|
|
3489
|
+
catch {
|
|
3490
|
+
branch = '';
|
|
3491
|
+
}
|
|
3492
|
+
return {
|
|
3493
|
+
branch,
|
|
3494
|
+
diffs: snapshot.files.map((file) => ({
|
|
3495
|
+
path: file.path,
|
|
3496
|
+
oldPath: file.oldPath ?? '',
|
|
3497
|
+
status: file.status,
|
|
3498
|
+
binary: file.status === 'binary',
|
|
3499
|
+
lines: file.lines ?? [],
|
|
3500
|
+
})),
|
|
3501
|
+
};
|
|
3502
|
+
},
|
|
3503
|
+
config: refreshAiReviewConfig,
|
|
3504
|
+
runModel: aiReviewModelImpl,
|
|
3505
|
+
log: (line) => onLog?.(line),
|
|
3506
|
+
});
|
|
3507
|
+
const workingReviewError = (resp, err) => resp
|
|
3508
|
+
.status(err instanceof WorkingReviewBusyError ? 409 : 400)
|
|
3509
|
+
.type('application/json')
|
|
3510
|
+
.send({ error: err.message });
|
|
3511
|
+
app.get('/api/working-review', async (_req, resp) => {
|
|
3512
|
+
try {
|
|
3513
|
+
return resp.type('application/json').send({ state: await workingReviewer.state() });
|
|
3514
|
+
}
|
|
3515
|
+
catch (err) {
|
|
3516
|
+
return workingReviewError(resp, err);
|
|
3517
|
+
}
|
|
3518
|
+
});
|
|
3519
|
+
app.post('/api/working-review', async (req, resp) => {
|
|
3520
|
+
try {
|
|
3521
|
+
const state = await workingReviewer.start({
|
|
3522
|
+
model: strField(req.body?.model),
|
|
3523
|
+
effort: typeof req.body?.effort === 'string' ? req.body.effort : undefined,
|
|
3524
|
+
});
|
|
3525
|
+
return resp.type('application/json').send({ state });
|
|
3526
|
+
}
|
|
3527
|
+
catch (err) {
|
|
3528
|
+
return workingReviewError(resp, err);
|
|
3529
|
+
}
|
|
3530
|
+
});
|
|
3531
|
+
app.post('/api/working-review/cancel', async (_req, resp) => {
|
|
3532
|
+
workingReviewer.cancel();
|
|
3533
|
+
return resp.type('application/json').send({ ok: true });
|
|
3534
|
+
});
|
|
3535
|
+
app.post('/api/working-review/findings/:id/fix', async (req, resp) => {
|
|
3536
|
+
try {
|
|
3537
|
+
const state = await workingReviewer.startFix(String(req.params?.id ?? ''));
|
|
3538
|
+
return resp.type('application/json').send({ state });
|
|
3539
|
+
}
|
|
3540
|
+
catch (err) {
|
|
3541
|
+
return workingReviewError(resp, err);
|
|
3542
|
+
}
|
|
3543
|
+
});
|
|
3544
|
+
app.post('/api/working-review/fix/cancel', async (_req, resp) => {
|
|
3545
|
+
workingReviewer.cancelFix();
|
|
3546
|
+
return resp.type('application/json').send({ ok: true });
|
|
3547
|
+
});
|
|
3468
3548
|
// ==================== Commit Detail ====================
|
|
3469
3549
|
app.post('/api/commit/detail', async (req, resp) => {
|
|
3470
3550
|
try {
|
|
@@ -3657,8 +3737,11 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
3657
3737
|
address,
|
|
3658
3738
|
aiReview: aiReviewer,
|
|
3659
3739
|
generateCommitMessage: draftCommitMessage,
|
|
3740
|
+
workingReview: workingReviewer,
|
|
3660
3741
|
close: async () => {
|
|
3661
3742
|
aiReviewer.stop();
|
|
3743
|
+
workingReviewer.cancel();
|
|
3744
|
+
workingReviewer.cancelFix();
|
|
3662
3745
|
await app.close();
|
|
3663
3746
|
},
|
|
3664
3747
|
updateSettings: (settings) => {
|
|
@@ -3667,7 +3750,6 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
3667
3750
|
autoReload = settings.autoReload;
|
|
3668
3751
|
diffViewer = settings.diffViewer;
|
|
3669
3752
|
showGraph = settings.showGraph;
|
|
3670
|
-
showStashes = settings.showStashes;
|
|
3671
3753
|
fileListView = settings.fileListView;
|
|
3672
3754
|
sidePanelSectionsExpanded = settings.sidePanelSectionsExpanded;
|
|
3673
3755
|
searchMode = settings.searchMode;
|
|
@@ -3676,6 +3758,7 @@ export async function startGuitoServer({ repositoryPath, uiRoot, host, port = 80
|
|
|
3676
3758
|
prSort = settings.prSort;
|
|
3677
3759
|
showTags = settings.showTags;
|
|
3678
3760
|
showRemoteBranches = settings.showRemoteBranches;
|
|
3761
|
+
grayRefBadges = settings.grayRefBadges;
|
|
3679
3762
|
issueRegex = settings.issueRegex;
|
|
3680
3763
|
issueUrl = settings.issueUrl;
|
|
3681
3764
|
allowMerge = settings.allowMerge;
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
<style>:root{--bg:#1e1e1e;--bg-alt:#232323;--bg-hover:#2a2d2e;--border:#3c3c3c;--text:#cccccc;--text-bright:#e8e8e8;--text-dim:#9d9d9d;--accent:#3794ff;--red:#f14c4c;--mono:ui-monospace, "Cascadia Code", "SF Mono", Menlo, Consolas, monospace}*{box-sizing:border-box}html,body{height:100%;margin:0}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;font-size:13px;color:var(--text);background:var(--bg);overflow:hidden}</style><link rel="stylesheet" href="styles-I7K2YZTW.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles-I7K2YZTW.css"></noscript></head>
|
|
10
10
|
<body>
|
|
11
11
|
<app-root></app-root>
|
|
12
|
-
<script src="polyfills-5CFQRCPP.js" type="module"></script><script src="main-
|
|
12
|
+
<script src="polyfills-5CFQRCPP.js" type="module"></script><script src="main-3MOB345X.js" type="module"></script></body>
|
|
13
13
|
</html>
|