d48cda4081
CI / Test (ubuntu-latest, Node 18.x, bun) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, npm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, pnpm) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 18.x, yarn) (push) Failing after 15m1s
CI / Test (ubuntu-latest, Node 20.x, bun) (push) Failing after 17m13s
CI / Test (ubuntu-latest, Node 20.x, npm) (push) Failing after 18m42s
CI / Test (ubuntu-latest, Node 20.x, pnpm) (push) Failing after 15m0s
CI / Test (ubuntu-latest, Node 20.x, yarn) (push) Failing after 49m44s
CI / Test (ubuntu-latest, Node 22.x, bun) (push) Failing after 51m55s
CI / Test (ubuntu-latest, Node 22.x, pnpm) (push) Failing after 21m57s
CI / Test (ubuntu-latest, Node 22.x, npm) (push) Failing after 37m39s
CI / Test (ubuntu-latest, Node 22.x, yarn) (push) Failing after 34m7s
CI / Validate Components (push) Failing after 37m15s
CI / Python Tests (push) Failing after 10m1s
CI / Security Scan (push) Failing after 10m1s
CI / Lint (push) Failing after 17m12s
CI / Coverage (push) Failing after 20m19s
CI / Test (macos-latest, Node 18.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 18.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 20.x, yarn) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, bun) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (macos-latest, Node 22.x, yarn) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, npm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, pnpm) (push) Has been cancelled
CI / Test (windows-latest, Node 22.x, yarn) (push) Has been cancelled
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
/**
|
|
* ECC Custom Tool: Git Summary
|
|
*
|
|
* Returns branch/status/log/diff details for the active repository.
|
|
*/
|
|
|
|
import { tool, type ToolDefinition } from "@opencode-ai/plugin/tool"
|
|
import { execFileSync } from "child_process"
|
|
|
|
// Conservative subset of git's allowed ref-name characters. Rejects shell
|
|
// metacharacters and option-like leading `-` so a model-supplied baseBranch
|
|
// cannot inject into the shell command line built below.
|
|
const SAFE_GIT_REF = /^[A-Za-z0-9._/-]+$/
|
|
|
|
function isSafeRef(ref: string): boolean {
|
|
if (typeof ref !== "string" || ref.length === 0 || ref.length > 200) return false
|
|
if (!SAFE_GIT_REF.test(ref)) return false
|
|
if (ref.startsWith("-") || ref.startsWith(".") || ref.startsWith("/")) return false
|
|
if (ref.includes("..") || ref.includes("//")) return false
|
|
return true
|
|
}
|
|
|
|
function isSafeDepth(value: unknown): value is number {
|
|
return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= 1000
|
|
}
|
|
|
|
const gitSummaryTool: ToolDefinition = tool({
|
|
description:
|
|
"Generate git summary with branch, status, recent commits, and optional diff stats.",
|
|
args: {
|
|
depth: tool.schema
|
|
.number()
|
|
.optional()
|
|
.describe("Number of recent commits to include (default: 5)"),
|
|
includeDiff: tool.schema
|
|
.boolean()
|
|
.optional()
|
|
.describe("Include diff stats against base branch (default: true)"),
|
|
baseBranch: tool.schema
|
|
.string()
|
|
.optional()
|
|
.describe("Base branch for diff comparison (default: main)"),
|
|
},
|
|
async execute(args, context) {
|
|
const cwd = context.worktree || context.directory
|
|
const depth = isSafeDepth(args.depth) ? args.depth : 5
|
|
const includeDiff = args.includeDiff ?? true
|
|
const baseBranch = args.baseBranch ?? "main"
|
|
|
|
const result: Record<string, string> = {
|
|
branch: runArgs(["branch", "--show-current"], cwd) || "unknown",
|
|
status: runArgs(["status", "--short"], cwd) || "clean",
|
|
log: runArgs(["log", "--oneline", `-${depth}`], cwd) || "no commits found",
|
|
}
|
|
|
|
if (includeDiff) {
|
|
result.stagedDiff = runArgs(["diff", "--cached", "--stat"], cwd) || ""
|
|
result.branchDiff = isSafeRef(baseBranch)
|
|
? runArgs(["diff", `${baseBranch}...HEAD`, "--stat"], cwd) ||
|
|
`unable to diff against ${baseBranch}`
|
|
: `unable to diff against ${baseBranch} (invalid ref)`
|
|
}
|
|
|
|
return JSON.stringify(result)
|
|
},
|
|
})
|
|
|
|
export default gitSummaryTool
|
|
|
|
function runArgs(args: string[], cwd: string): string {
|
|
try {
|
|
return execFileSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] }).trim()
|
|
} catch {
|
|
return ""
|
|
}
|
|
}
|