- Fix unstable-agent-babysitter: add promptAsync to test mock - Fix claude-code-mcp-loader: isolate tests from user home configs - Fix npm-dist-tags: encode packageName for scoped packages - Fix agent-builder: clone source to prevent shared object mutation - Fix add-plugin-to-opencode-config: handle JSONC with leading comments - Fix auth-plugins/add-provider-config: error on parse failures - Fix bun-install: clear timeout on completion - Fix git-diff-stats: include untracked files in diff summary
134 lines
3.6 KiB
TypeScript
134 lines
3.6 KiB
TypeScript
import { execSync } from "node:child_process"
|
|
|
|
interface GitFileStat {
|
|
path: string
|
|
added: number
|
|
removed: number
|
|
status: "modified" | "added" | "deleted"
|
|
}
|
|
|
|
export function getGitDiffStats(directory: string): GitFileStat[] {
|
|
try {
|
|
const statusOutput = execSync("git status --porcelain", {
|
|
cwd: directory,
|
|
encoding: "utf-8",
|
|
timeout: 5000,
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
}).trim()
|
|
|
|
if (!statusOutput) return []
|
|
|
|
const statusMap = new Map<string, "modified" | "added" | "deleted">()
|
|
const untrackedFiles: string[] = []
|
|
for (const line of statusOutput.split("\n")) {
|
|
if (!line) continue
|
|
const status = line.substring(0, 2).trim()
|
|
const filePath = line.substring(3)
|
|
if (status === "??") {
|
|
statusMap.set(filePath, "added")
|
|
untrackedFiles.push(filePath)
|
|
} else if (status === "A") {
|
|
statusMap.set(filePath, "added")
|
|
} else if (status === "D") {
|
|
statusMap.set(filePath, "deleted")
|
|
} else {
|
|
statusMap.set(filePath, "modified")
|
|
}
|
|
}
|
|
|
|
const output = execSync("git diff --numstat HEAD", {
|
|
cwd: directory,
|
|
encoding: "utf-8",
|
|
timeout: 5000,
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
}).trim()
|
|
|
|
const stats: GitFileStat[] = []
|
|
const trackedPaths = new Set<string>()
|
|
|
|
if (output) {
|
|
for (const line of output.split("\n")) {
|
|
const parts = line.split("\t")
|
|
if (parts.length < 3) continue
|
|
|
|
const [addedStr, removedStr, path] = parts
|
|
const added = addedStr === "-" ? 0 : parseInt(addedStr, 10)
|
|
const removed = removedStr === "-" ? 0 : parseInt(removedStr, 10)
|
|
trackedPaths.add(path)
|
|
|
|
stats.push({
|
|
path,
|
|
added,
|
|
removed,
|
|
status: statusMap.get(path) ?? "modified",
|
|
})
|
|
}
|
|
}
|
|
|
|
for (const filePath of untrackedFiles) {
|
|
if (trackedPaths.has(filePath)) continue
|
|
try {
|
|
const content = execSync(`wc -l < "${filePath}"`, {
|
|
cwd: directory,
|
|
encoding: "utf-8",
|
|
timeout: 3000,
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
}).trim()
|
|
const lineCount = parseInt(content, 10) || 0
|
|
stats.push({ path: filePath, added: lineCount, removed: 0, status: "added" })
|
|
} catch {
|
|
stats.push({ path: filePath, added: 0, removed: 0, status: "added" })
|
|
}
|
|
}
|
|
|
|
return stats
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
export function formatFileChanges(stats: GitFileStat[], notepadPath?: string): string {
|
|
if (stats.length === 0) return "[FILE CHANGES SUMMARY]\nNo file changes detected.\n"
|
|
|
|
const modified = stats.filter((s) => s.status === "modified")
|
|
const added = stats.filter((s) => s.status === "added")
|
|
const deleted = stats.filter((s) => s.status === "deleted")
|
|
|
|
const lines: string[] = ["[FILE CHANGES SUMMARY]"]
|
|
|
|
if (modified.length > 0) {
|
|
lines.push("Modified files:")
|
|
for (const f of modified) {
|
|
lines.push(` ${f.path} (+${f.added}, -${f.removed})`)
|
|
}
|
|
lines.push("")
|
|
}
|
|
|
|
if (added.length > 0) {
|
|
lines.push("Created files:")
|
|
for (const f of added) {
|
|
lines.push(` ${f.path} (+${f.added})`)
|
|
}
|
|
lines.push("")
|
|
}
|
|
|
|
if (deleted.length > 0) {
|
|
lines.push("Deleted files:")
|
|
for (const f of deleted) {
|
|
lines.push(` ${f.path} (-${f.removed})`)
|
|
}
|
|
lines.push("")
|
|
}
|
|
|
|
if (notepadPath) {
|
|
const notepadStat = stats.find((s) => s.path.includes("notepad") || s.path.includes(".sisyphus"))
|
|
if (notepadStat) {
|
|
lines.push("[NOTEPAD UPDATED]")
|
|
lines.push(` ${notepadStat.path} (+${notepadStat.added})`)
|
|
lines.push("")
|
|
}
|
|
}
|
|
|
|
return lines.join("\n")
|
|
}
|