- Rebrand commits already in history (gsd → forge) - Sync pre-existing doc, docker, and CI config updates - All rebrand artifacts verified in place: * Native crates: forge-engine, forge-ast, forge-grep * Log prefixes: [forge] across 22+ files * Binary: ~/bin/sf-run * Workspace scopes: @sf-run/*, @singularity-forge/* * Nix flake: Rust toolchain ready System ready for: nix develop && bun run build:native Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
122 lines
3.6 KiB
TypeScript
122 lines
3.6 KiB
TypeScript
import * as vscode from "vscode";
|
|
import { execFile } from "node:child_process";
|
|
import type { GsdChangeTracker } from "./change-tracker.js";
|
|
|
|
/**
|
|
* Provides git integration for agent changes — commit, branch, and diff.
|
|
*/
|
|
export class GsdGitIntegration implements vscode.Disposable {
|
|
private disposables: vscode.Disposable[] = [];
|
|
|
|
constructor(
|
|
private readonly tracker: GsdChangeTracker,
|
|
private readonly cwd: string,
|
|
) {}
|
|
|
|
/**
|
|
* Commit all files modified by the agent with a user-provided message.
|
|
*/
|
|
async commitAgentChanges(): Promise<void> {
|
|
const files = this.tracker.modifiedFiles;
|
|
if (files.length === 0) {
|
|
vscode.window.showInformationMessage("No agent changes to commit.");
|
|
return;
|
|
}
|
|
|
|
const defaultMsg = `feat: agent changes (${files.length} file${files.length !== 1 ? "s" : ""})`;
|
|
const message = await vscode.window.showInputBox({
|
|
prompt: "Commit message for agent changes",
|
|
value: defaultMsg,
|
|
placeHolder: "feat: describe the changes",
|
|
});
|
|
if (!message) return;
|
|
|
|
try {
|
|
// Stage the modified files
|
|
await this.git(["add", ...files]);
|
|
// Commit
|
|
await this.git(["commit", "-m", message]);
|
|
|
|
// Accept all changes (clear tracking since they're committed)
|
|
this.tracker.acceptAll();
|
|
|
|
vscode.window.showInformationMessage(`Committed ${files.length} file${files.length !== 1 ? "s" : ""}.`);
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
vscode.window.showErrorMessage(`Git commit failed: ${msg}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new branch for agent work and switch to it.
|
|
*/
|
|
async createAgentBranch(): Promise<void> {
|
|
const branchName = await vscode.window.showInputBox({
|
|
prompt: "Branch name for agent work",
|
|
placeHolder: "feat/agent-changes",
|
|
validateInput: (value) => {
|
|
if (!value.trim()) return "Branch name is required";
|
|
if (/\s/.test(value)) return "Branch name cannot contain spaces";
|
|
return null;
|
|
},
|
|
});
|
|
if (!branchName) return;
|
|
|
|
try {
|
|
await this.git(["checkout", "-b", branchName]);
|
|
vscode.window.showInformationMessage(`Created and switched to branch: ${branchName}`);
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
vscode.window.showErrorMessage(`Failed to create branch: ${msg}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Show a git diff of all agent-modified files.
|
|
*/
|
|
async showAgentDiff(): Promise<void> {
|
|
const files = this.tracker.modifiedFiles;
|
|
if (files.length === 0) {
|
|
vscode.window.showInformationMessage("No agent changes to diff.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const diff = await this.git(["diff"]);
|
|
if (!diff.trim()) {
|
|
// Files may be untracked — show status instead
|
|
const status = await this.git(["status", "--short"]);
|
|
const channel = vscode.window.createOutputChannel("SF Git Diff");
|
|
channel.appendLine("# Agent-modified files (unstaged):");
|
|
channel.appendLine(status);
|
|
channel.show();
|
|
} else {
|
|
const channel = vscode.window.createOutputChannel("SF Git Diff");
|
|
channel.clear();
|
|
channel.appendLine(diff);
|
|
channel.show();
|
|
}
|
|
} catch (err) {
|
|
const msg = err instanceof Error ? err.message : String(err);
|
|
vscode.window.showErrorMessage(`Git diff failed: ${msg}`);
|
|
}
|
|
}
|
|
|
|
dispose(): void {
|
|
for (const d of this.disposables) {
|
|
d.dispose();
|
|
}
|
|
}
|
|
|
|
private git(args: string[]): Promise<string> {
|
|
return new Promise((resolve, reject) => {
|
|
execFile("git", args, { cwd: this.cwd, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
if (err) {
|
|
reject(new Error(stderr.trim() || err.message));
|
|
} else {
|
|
resolve(stdout);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|