119e18c810
- Extract atlas/ into 15 focused modules (hook, event handler, tool policies, types, etc.) - Split auto-update-checker into checker/ and hook/ subdirectories with single-purpose files - Decompose session-recovery into separate recovery strategy files per error type - Extract todo-continuation-enforcer from monolith to directory with dedicated modules - Split background-task/tools.ts into individual tool creator files - Extract command-executor, tmux-utils into focused sub-modules - Split config/schema.ts into domain-specific schema files - Decompose cli/config-manager.ts into focused modules - Rollback skill-mcp-manager, model-availability, index.ts splits that broke tests - Fix all import path depths for moved files (../../ -> ../../../) - Add explicit type annotations to resolve TS7006 implicit any errors Typecheck: 0 errors Tests: 2359 pass, 5 fail (all pre-existing)
109 lines
2.8 KiB
TypeScript
109 lines
2.8 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 output = execSync("git diff --numstat HEAD", {
|
|
cwd: directory,
|
|
encoding: "utf-8",
|
|
timeout: 5000,
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
}).trim()
|
|
|
|
if (!output) return []
|
|
|
|
const statusOutput = execSync("git status --porcelain", {
|
|
cwd: directory,
|
|
encoding: "utf-8",
|
|
timeout: 5000,
|
|
stdio: ["pipe", "pipe", "pipe"],
|
|
}).trim()
|
|
|
|
const statusMap = new Map<string, "modified" | "added" | "deleted">()
|
|
for (const line of statusOutput.split("\n")) {
|
|
if (!line) continue
|
|
const status = line.substring(0, 2).trim()
|
|
const filePath = line.substring(3)
|
|
if (status === "A" || status === "??") {
|
|
statusMap.set(filePath, "added")
|
|
} else if (status === "D") {
|
|
statusMap.set(filePath, "deleted")
|
|
} else {
|
|
statusMap.set(filePath, "modified")
|
|
}
|
|
}
|
|
|
|
const stats: GitFileStat[] = []
|
|
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)
|
|
|
|
stats.push({
|
|
path,
|
|
added,
|
|
removed,
|
|
status: statusMap.get(path) ?? "modified",
|
|
})
|
|
}
|
|
|
|
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")
|
|
}
|