Skip to content

Commit b1b97c5

Browse files
committed
feat: add auto-sync, provenance, and sync UI
1 parent f58f1db commit b1b97c5

18 files changed

+1192
-7
lines changed

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ Guidelines for autonomous contributors working on this repository.
1515
- Whether it’s TypeScript, shell scripts, or build helpers, any newly introduced function or class must include a concise docstring explaining its role. Update existing docstrings when behavior changes.
1616

1717
5. **Tests and validation**
18-
- Run any available automated checks relevant to your change (unit tests, linting, packaging). If something can’t be run in the current environment, clearly state what remains unverified.
18+
- Always run the test suite and linter before considering a coding task complete. If something can’t be run in the current environment, clearly state what remains unverified.
19+
- Run any other available automated checks relevant to your change (e.g., packaging). If something can’t be run in the current environment, clearly state what remains unverified.
1920

2021
6. **Commit messaging**
2122
- When suggesting or creating commit titles, always follow the [Conventional Commits](https://www.conventionalcommits.org/) format (e.g., `feat: add highlight toggle command`). Include scope when it adds clarity.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ See the [Build and install](#build-and-install) section below for how to build a
3636
- [**View Mode**](#view-mode) - View findings in a list, or grouped by filename.
3737
- [**Multiple Users**](#multiple-users) - Findings can be viewed from multiple different users.
3838
- [**Hide Findings**](#hide-findings) - Hide all findings associated with a specific user.
39+
- [**Auto Sync (Git)**](#auto-sync-git) - Automatically sync .weaudit files across auditors via a dedicated branch.
3940
- [**Search & Filter Findings**](#search--filter-findings) - Search and filter the findings in the _List of Findings_ panel.
4041
- [**Export Findings**](#export-findings) - Export findings to a markdown file.
4142
- [**Drag & drop Findings and Locations**](#drag--drop-findings-and-locations) - Drag and drop findings and locations in the _List of Findings_ panel.
@@ -87,6 +88,7 @@ You can quickly navigate through all partially audited regions in your workspace
8788
### Detailed Findings
8889

8990
You can fill detailed information about a finding by clicking on it in the _List of Findings_ view in the sidebar. The respective _Finding Details_ panel will open, where you can fill the information.
91+
The panel also shows a read-only provenance field (defaulting to "human") to indicate who created the finding.
9092

9193
![Finding Details](media/readme/finding_details.png)
9294

@@ -140,6 +142,7 @@ You can share the weAudit file with you co-auditors to share findings. This file
140142

141143
In the `weAudit Files` panel, you can toggle to show or hide the findings from each user by clicking on the entries.
142144
There are color settings for other user's findings and notes, and for your own findings and notes.
145+
Findings and notes show the author's username after the filename/line number in the _List of Findings_ panel.
143146

144147
![Multiple Users](media/readme/multi_user.png)
145148

@@ -148,6 +151,22 @@ You can hide all findings associated with a specific user by clicking on that us
148151

149152
![Hide Findings associated to a user](media/readme/gifs/hide_findings.gif)
150153

154+
### Auto Sync (Git)
155+
weAudit can automatically sync `.weaudit` files across auditors using a dedicated git branch.
156+
157+
To enable, set `weAudit.sync.enabled` to `true` in your settings. By default, weAudit:
158+
- uses the `weaudit-sync` branch on the `origin` remote;
159+
- pulls the latest sync branch before committing local `.weaudit` changes;
160+
- polls every minute for remote updates (configurable);
161+
- syncs only `.vscode/*.weaudit` files.
162+
163+
Sync runs from a dedicated git worktree stored in VS Code's global storage, so your current branch and working tree stay untouched.
164+
165+
You can also configure these settings in the **Sync Configuration** panel in the weAudit sidebar.
166+
The panel shows the timestamp of the last successful sync.
167+
168+
You can trigger a manual sync at any time with the `weAudit: Sync Findings Now` command.
169+
151170
### Toggle Highlights
152171
Hide every findings/notes highlight in the editor by running the `weAudit: Toggle Findings Highlighting` command from the Command Palette. Run the command again to bring the highlights back whenever you need to review them.
153172

@@ -177,6 +196,16 @@ You can drag and drop findings and locations in the _List of Findings_ panel to:
177196
- `weAudit.general.username`: Username to use as finding's author (defaults to system username if empty)
178197
- `weAudit.general.permalinkSeparator`: Separator to use in permalinks (\\n is interpreted as newline)
179198

199+
#### Sync settings
200+
201+
- `weAudit.sync.enabled`: Enable git-based auto sync (opt-in)
202+
- `weAudit.sync.remoteName`: Git remote to use (default: "origin")
203+
- `weAudit.sync.branchName`: Sync branch name (default: "weaudit-sync")
204+
- `weAudit.sync.pollMinutes`: Remote polling interval in minutes (default: 1)
205+
- `weAudit.sync.debounceMs`: Debounce delay for local changes in milliseconds
206+
207+
Sync settings are stored per-workspace (user-level settings are ignored).
208+
180209
#### Background colors
181210

182211
Each background color is customizable via the VSCode settings page. Write as #RGB, #RGBA, #RRGGBB or #RRGGBBAA:

esbuild.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ const gitConfigWebviewConfig = {
3333
outfile: "./out/gitConfigWebview.js",
3434
};
3535

36+
const syncConfigWebviewConfig = {
37+
...baseConfig,
38+
target: "es2020",
39+
format: "esm",
40+
entryPoints: ["./src/webview/syncConfigMain.ts"],
41+
outfile: "./out/syncConfigWebview.js",
42+
};
43+
3644
const watchPlugin = {
3745
name: "watch-plugin",
3846
setup(build) {
@@ -65,15 +73,21 @@ const watchPlugin = {
6573
...gitConfigWebviewConfig,
6674
plugins: [watchPlugin],
6775
});
76+
const syncConfigWebviewContext = await context({
77+
...syncConfigWebviewConfig,
78+
plugins: [watchPlugin],
79+
});
6880

6981
await extensionContext.watch();
7082
await webviewContext.watch();
7183
await gitConfigWebviewContext.watch();
84+
await syncConfigWebviewContext.watch();
7285
} else {
7386
// Build extension and webview code
7487
await build(extensionConfig);
7588
await build(webviewConfig);
7689
await build(gitConfigWebviewConfig);
90+
await build(syncConfigWebviewConfig);
7791
console.log("build complete");
7892
}
7993
} catch (err) {

media/style.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@
88
padding-right: 0.5em;
99
}
1010

11+
.detailValue {
12+
flex: 1;
13+
}
14+
1115
vscode-dropdown,
1216
vscode-text-area,
1317
vscode-text-field {

package.json

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
},
2323
"icon": "media/icon.png",
2424
"engines": {
25-
"vscode": "^1.107.0"
25+
"vscode": "^1.108.0"
2626
},
2727
"activationEvents": [
2828
"onStartupFinished",
@@ -64,6 +64,13 @@
6464
"visibility": "collapsed",
6565
"contextualTitle": "weAudit"
6666
},
67+
{
68+
"type": "webview",
69+
"id": "syncConfig",
70+
"name": "Sync Configuration",
71+
"visibility": "collapsed",
72+
"contextualTitle": "weAudit"
73+
},
6774
{
6875
"id": "savedFindings",
6976
"name": "weAudit Files",
@@ -240,6 +247,10 @@
240247
{
241248
"command": "weAudit.toggleFindingsHighlighting",
242249
"title": "weAudit: Toggle Findings Highlighting"
250+
},
251+
{
252+
"command": "weAudit.syncNow",
253+
"title": "weAudit: Sync Findings Now"
243254
}
244255
],
245256
"keybindings": [
@@ -387,6 +398,11 @@
387398
"when": "view == gitConfig",
388399
"group": "navigation@0"
389400
},
401+
{
402+
"command": "weAudit.syncNow",
403+
"when": "view == syncConfig",
404+
"group": "navigation@0"
405+
},
390406
{
391407
"command": "weAudit.openGithubIssueFromDetails",
392408
"when": "view == findingDetails && weAudit.findingDetailsHasEntry",
@@ -530,6 +546,39 @@
530546
"description": "Background color for other user's notes (write as #RGB, #RGBA, #RRGGBB or #RRGGBBAA)."
531547
}
532548
}
549+
},
550+
{
551+
"title": "Sync",
552+
"order": 3,
553+
"properties": {
554+
"weAudit.sync.enabled": {
555+
"type": "boolean",
556+
"default": false,
557+
"description": "Enable git-based auto sync for .weaudit files."
558+
},
559+
"weAudit.sync.remoteName": {
560+
"type": "string",
561+
"default": "origin",
562+
"description": "The git remote to use for auto sync."
563+
},
564+
"weAudit.sync.branchName": {
565+
"type": "string",
566+
"default": "weaudit-sync",
567+
"description": "The branch name used for auto sync."
568+
},
569+
"weAudit.sync.pollMinutes": {
570+
"type": "number",
571+
"default": 1,
572+
"minimum": 1,
573+
"description": "Polling interval (in minutes) for pulling remote .weaudit changes."
574+
},
575+
"weAudit.sync.debounceMs": {
576+
"type": "number",
577+
"default": 1000,
578+
"minimum": 0,
579+
"description": "Debounce delay (in milliseconds) before syncing local .weaudit changes."
580+
}
581+
}
533582
}
534583
]
535584
},

src/codeMarker.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,6 +2035,11 @@ export class CodeMarker implements vscode.TreeDataProvider<TreeEntry> {
20352035
void this.exportFindingsInMarkdown();
20362036
});
20372037

2038+
// Reload selected configs from disk to pick up external sync updates.
2039+
vscode.commands.registerCommand("weAudit.reloadSavedFindingsFromDisk", () => {
2040+
this.reloadSavedFindingsFromDisk();
2041+
});
2042+
20382043
// Gets the filtered entries from the current tree that correspond to a specific username and workspace root
20392044
vscode.commands.registerCommand("weAudit.getFilteredEntriesForSaving", (username: string, root: WARoot) => {
20402045
return this.getFilteredEntriesForSaving(username, root);
@@ -3378,6 +3383,9 @@ export class CodeMarker implements vscode.TreeDataProvider<TreeEntry> {
33783383
partiallyAuditedFile.path = normalizePathForOS(rootPath, partiallyAuditedFile.path);
33793384
});
33803385

3386+
this.ensureEntryDetailsProvenance(fullParsedEntries.treeEntries);
3387+
this.ensureEntryDetailsProvenance(fullParsedEntries.resolvedEntries);
3388+
33813389
if (update) {
33823390
if (add) {
33833391
// Remove potential entries of username which appear on the tree.
@@ -3439,6 +3447,61 @@ export class CodeMarker implements vscode.TreeDataProvider<TreeEntry> {
34393447
return fullParsedEntries;
34403448
}
34413449

3450+
/**
3451+
* Reload all selected configurations from disk to keep the tree in sync with external changes.
3452+
*/
3453+
reloadSavedFindingsFromDisk(): void {
3454+
const selectedConfigs = this.workspaces.getSelectedConfigurations();
3455+
for (const config of selectedConfigs) {
3456+
if (!fs.existsSync(config.path)) {
3457+
// If a synced file disappeared, clear the in-memory entries for that user/root.
3458+
this.removeEntriesForConfig(config);
3459+
continue;
3460+
}
3461+
this.loadSavedDataFromConfig(config, true, false);
3462+
this.loadSavedDataFromConfig(config, true, true);
3463+
}
3464+
3465+
vscode.commands.executeCommand("weAudit.refreshSavedFindings", selectedConfigs);
3466+
this.resolvedEntriesTree.setResolvedEntries(this.resolvedEntries);
3467+
this.refreshTree();
3468+
this.decorate();
3469+
}
3470+
3471+
/**
3472+
* Ensure all entry details include a provenance value.
3473+
*/
3474+
private ensureEntryDetailsProvenance(entries: FullEntry[]): void {
3475+
for (const entry of entries) {
3476+
if (entry.details.provenance === undefined) {
3477+
entry.details.provenance = "human";
3478+
}
3479+
}
3480+
}
3481+
3482+
/**
3483+
* Remove entries for a config's username within its workspace root from in-memory state.
3484+
*/
3485+
private removeEntriesForConfig(config: ConfigurationEntry): void {
3486+
const [wsRoot, _relativePath] = this.workspaces.getCorrespondingRootAndPath(config.path);
3487+
if (wsRoot === undefined) {
3488+
return;
3489+
}
3490+
this.treeEntries = this.treeEntries.filter(
3491+
(entry) =>
3492+
entry.author !== config.username ||
3493+
entry.locations.findIndex((loc) => this.workspaces.getUniqueLabel(loc.rootPath) !== config.root.label) !== -1,
3494+
);
3495+
wsRoot.filterAudited(config.username);
3496+
wsRoot.filterPartiallyAudited(config.username);
3497+
this.resolvedEntries = this.resolvedEntries.filter(
3498+
(entry) =>
3499+
entry.author !== config.username ||
3500+
entry.locations.findIndex((loc) => this.workspaces.getUniqueLabel(loc.rootPath) !== config.root.label) !== -1,
3501+
);
3502+
this.markPathMapDirty();
3503+
}
3504+
34423505
/**
34433506
* Implicitly called in this._onDidChangeFileDecorationsEmitter.fire(uri);
34443507
* which is called on this.refresh(uri)
@@ -3738,6 +3801,7 @@ export class CodeMarker implements vscode.TreeDataProvider<TreeEntry> {
37383801
if (entry.location.endLine !== entry.location.startLine) {
37393802
description += "-" + (entry.location.endLine + 1).toString();
37403803
}
3804+
description += " (" + entry.parentEntry.author + ")";
37413805
let mainLabel: string;
37423806
if (this.treeViewMode === TreeViewMode.List) {
37433807
mainLabel = entry.location.label;
@@ -3782,9 +3846,7 @@ export class CodeMarker implements vscode.TreeDataProvider<TreeEntry> {
37823846
const basePath = path.basename(mainLocation.path);
37833847
treeItem.description = basePath + ":" + (mainLocation.startLine + 1).toString();
37843848

3785-
if (entry.author !== this.username) {
3786-
treeItem.description += " (" + entry.author + ")";
3787-
}
3849+
treeItem.description += " (" + entry.author + ")";
37883850

37893851
treeItem.command = {
37903852
command: "weAudit.openFileLines",

src/extension.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { AuditMarker } from "./codeMarker";
66
import { MultipleSavedFindings } from "./multiConfigs";
77
import { activateFindingDetailsWebview } from "./panels/findingDetailsPanel";
88
import { activateGitConfigWebview } from "./panels/gitConfigPanel";
9+
import { activateSyncConfigWebview } from "./panels/syncConfigPanel";
10+
import { GitAutoSyncManager } from "./sync/gitAutoSync";
911

1012
export function activate(context: vscode.ExtensionContext): void {
1113
// if there are no open folders, return
@@ -23,6 +25,9 @@ export function activate(context: vscode.ExtensionContext): void {
2325
new MultipleSavedFindings(context);
2426
activateFindingDetailsWebview(context);
2527
activateGitConfigWebview(context);
28+
activateSyncConfigWebview(context);
29+
const gitAutoSyncManager = new GitAutoSyncManager(context);
30+
context.subscriptions.push(gitAutoSyncManager);
2631
}
2732

2833
async function openResource(resource: vscode.Uri, startLine: number, endLine: number): Promise<void> {

src/panels/findingDetails.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
<span class="detailSpan">Title:</span>
44
<vscode-text-field id="label-area"></vscode-text-field>
55
</div>
6+
<div class="detailsDiv">
7+
<span class="detailSpan">Provenance:</span>
8+
<span id="provenance-value" class="detailValue"></span>
9+
</div>
610

711
<div class="detailsDiv">
812
<span class="detailSpan">Severity:</span>

src/panels/findingDetailsPanel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class FindingDetailsProvider implements vscode.WebviewViewProvider {
6868
description: entry.description,
6969
exploit: entry.exploit,
7070
recommendation: entry.recommendation,
71+
provenance: entry.provenance ?? "human",
7172
title: title,
7273
});
7374

0 commit comments

Comments
 (0)