mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-06-23 08:31:26 +08:00
feat(opencode): 全面升级OpenCode集成 (#2251)
- 修复ecc-hooks.ts中的硬编码ECC_VERSION(从package.json读取) - 改进错误处理机制(统一模式、详细错误信息) - 增强类型安全(添加ToolArgs、ToolInput等类型定义) - 改进跨平台兼容性(支持macOS、Windows、Linux) - 添加dependency-analyzer工具(依赖分析) - 改进format-code工具(错误处理、跨平台支持) - 改进lint-check工具(错误处理、跨平台支持) - 更新文档(代理26个、工具8个、命令26个) - 添加工具测试(6个测试用例) - 改进现有测试(7个测试用例) 所有测试通过(16/16) Co-authored-by: Pual-LI-6 <dj2112236494@outlook.com>
This commit is contained in:
committed by
GitHub
parent
e53b4d9e39
commit
3a08b0c7a8
+108
-29
@@ -22,6 +22,52 @@ import {
|
||||
clearChanges,
|
||||
} from "./lib/changed-files-store.js"
|
||||
import changedFilesTool from "../tools/changed-files.js"
|
||||
import dependencyAnalyzerTool from "../tools/dependency-analyzer.js"
|
||||
|
||||
/**
|
||||
* Type definitions for better type safety
|
||||
*/
|
||||
interface ToolArgs {
|
||||
filePath?: string
|
||||
file_path?: string
|
||||
path?: string
|
||||
command?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface ToolInput {
|
||||
tool: string
|
||||
callID?: string
|
||||
args?: ToolArgs
|
||||
}
|
||||
|
||||
interface PermissionEvent {
|
||||
tool: string
|
||||
args: unknown
|
||||
}
|
||||
|
||||
interface FileEvent {
|
||||
path: string
|
||||
type?: string
|
||||
}
|
||||
|
||||
interface TodoEvent {
|
||||
todos: Array<{ text: string; done: boolean }>
|
||||
}
|
||||
|
||||
/**
|
||||
* Read ECC version from package.json
|
||||
* Falls back to a default if package.json cannot be read
|
||||
*/
|
||||
function getECCVersion(): string {
|
||||
try {
|
||||
const packageJsonPath = path.resolve(__dirname, "../../package.json")
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"))
|
||||
return packageJson.version || "2.0.0"
|
||||
} catch {
|
||||
return "2.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
type ECCHooksPluginFn = (input: PluginInput) => Promise<Record<string, unknown>>
|
||||
|
||||
@@ -54,7 +100,7 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
const pendingToolChanges = new Map<string, { path: string; type: "added" | "modified" }>()
|
||||
let writeCounter = 0
|
||||
|
||||
function getFilePath(args: Record<string, unknown> | undefined): string | null {
|
||||
function getFilePath(args: ToolArgs | undefined): string | null {
|
||||
if (!args) return null
|
||||
const p = (args.filePath ?? args.file_path ?? args.path) as string | undefined
|
||||
return typeof p === "string" && p.trim() ? p : null
|
||||
@@ -115,8 +161,10 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
try {
|
||||
await $`prettier --write ${event.path} 2>/dev/null`
|
||||
log("info", `[ECC] Formatted: ${event.path}`)
|
||||
} catch {
|
||||
// Prettier not installed or failed - silently continue
|
||||
} catch (error: unknown) {
|
||||
// Prettier not installed or failed - log but continue
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
log("debug", `[ECC] Prettier formatting failed for ${event.path}: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,10 +193,10 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
* Action: Runs tsc --noEmit to check for type errors
|
||||
*/
|
||||
"tool.execute.after": async (
|
||||
input: { tool: string; callID?: string; args?: { filePath?: string; file_path?: string; path?: string } },
|
||||
input: ToolInput,
|
||||
output: unknown
|
||||
) => {
|
||||
const filePath = getFilePath(input.args as Record<string, unknown>)
|
||||
const filePath = getFilePath(input.args)
|
||||
if (input.tool === "edit" && filePath) {
|
||||
recordChange(filePath, "modified")
|
||||
}
|
||||
@@ -201,7 +249,7 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
* Action: Warns about potential security issues
|
||||
*/
|
||||
"tool.execute.before": async (
|
||||
input: { tool: string; callID?: string; args?: Record<string, unknown> }
|
||||
input: ToolInput
|
||||
) => {
|
||||
if (input.tool === "write") {
|
||||
const filePath = getFilePath(input.args)
|
||||
@@ -332,11 +380,22 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
log("info", "[ECC] Audit passed: No console.log statements found")
|
||||
}
|
||||
|
||||
// Desktop notification (macOS)
|
||||
// Desktop notification (cross-platform)
|
||||
try {
|
||||
await $`osascript -e 'display notification "Task completed!" with title "OpenCode ECC"' 2>/dev/null`
|
||||
} catch {
|
||||
// Notification not supported or failed
|
||||
if (process.platform === "darwin") {
|
||||
// macOS
|
||||
await $`osascript -e 'display notification "Task completed!" with title "OpenCode ECC"' 2>/dev/null`
|
||||
} else if (process.platform === "win32") {
|
||||
// Windows - PowerShell notification
|
||||
await $`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show('Task completed!', 'OpenCode ECC', 'OK', 'Information')" 2>/dev/null`
|
||||
} else if (process.platform === "linux") {
|
||||
// Linux - notify-send (requires libnotify)
|
||||
await $`notify-send "OpenCode ECC" "Task completed!" 2>/dev/null`
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
// Notification not supported or failed - log but continue
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
log("debug", `[ECC] Desktop notification failed: ${errorMessage}`)
|
||||
}
|
||||
|
||||
// Clear tracked files for next task
|
||||
@@ -399,7 +458,7 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
*/
|
||||
"shell.env": async () => {
|
||||
const env: Record<string, string> = {
|
||||
ECC_VERSION: "1.8.0",
|
||||
ECC_VERSION: getECCVersion(),
|
||||
ECC_PLUGIN: "true",
|
||||
ECC_HOOK_PROFILE: currentProfile,
|
||||
ECC_DISABLED_HOOKS: process.env.ECC_DISABLED_HOOKS || "",
|
||||
@@ -487,32 +546,52 @@ export const ECCHooksPlugin: ECCHooksPluginFn = async ({
|
||||
* Triggers: When permission is requested
|
||||
* Action: Auto-approve reads, formatters, and test commands; log all for audit
|
||||
*/
|
||||
"permission.ask": async (event: { tool: string; args: unknown }) => {
|
||||
"permission.ask": async (event: PermissionEvent) => {
|
||||
log("info", `[ECC] Permission requested for: ${event.tool}`)
|
||||
|
||||
const cmd = String((event.args as Record<string, unknown>)?.command || event.args || "")
|
||||
try {
|
||||
// Handle both string args and object args with command property
|
||||
let cmd: string
|
||||
if (typeof event.args === "string") {
|
||||
cmd = event.args
|
||||
} else if (event.args && typeof event.args === "object") {
|
||||
cmd = String((event.args as Record<string, unknown>).command || "")
|
||||
} else {
|
||||
cmd = String(event.args || "")
|
||||
}
|
||||
|
||||
// Auto-approve: read/search tools
|
||||
if (["read", "glob", "grep", "search", "list"].includes(event.tool)) {
|
||||
return { approved: true, reason: "Read-only operation" }
|
||||
// Auto-approve: read/search tools
|
||||
if (["read", "glob", "grep", "search", "list"].includes(event.tool)) {
|
||||
log("debug", `[ECC] Auto-approved read-only tool: ${event.tool}`)
|
||||
return { approved: true, reason: "Read-only operation" }
|
||||
}
|
||||
|
||||
// Auto-approve: formatters
|
||||
if (event.tool === "bash" && /^(npx )?(@biomejs\/biome|prettier|black|gofmt|rustfmt|swift-format)/.test(cmd)) {
|
||||
log("debug", `[ECC] Auto-approved formatter: ${cmd}`)
|
||||
return { approved: true, reason: "Formatter execution" }
|
||||
}
|
||||
|
||||
// Auto-approve: test execution
|
||||
if (event.tool === "bash" && /^(npm test|npx vitest|npx jest|pytest|go test|cargo test)/.test(cmd)) {
|
||||
log("debug", `[ECC] Auto-approved test execution: ${cmd}`)
|
||||
return { approved: true, reason: "Test execution" }
|
||||
}
|
||||
|
||||
// Everything else: let user decide
|
||||
log("debug", `[ECC] Permission requires user approval: ${event.tool}`)
|
||||
return { approved: undefined }
|
||||
} catch (error: unknown) {
|
||||
// Error in permission handling - log and deny for safety
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
log("error", `[ECC] Permission handling error for ${event.tool}: ${errorMessage}`)
|
||||
return { approved: false, reason: `Error: ${errorMessage}` }
|
||||
}
|
||||
|
||||
// Auto-approve: formatters
|
||||
if (event.tool === "bash" && /^(npx )?(prettier|biome|black|gofmt|rustfmt|swift-format)/.test(cmd)) {
|
||||
return { approved: true, reason: "Formatter execution" }
|
||||
}
|
||||
|
||||
// Auto-approve: test execution
|
||||
if (event.tool === "bash" && /^(npm test|npx vitest|npx jest|pytest|go test|cargo test)/.test(cmd)) {
|
||||
return { approved: true, reason: "Test execution" }
|
||||
}
|
||||
|
||||
// Everything else: let user decide
|
||||
return { approved: undefined }
|
||||
},
|
||||
|
||||
tool: {
|
||||
"changed-files": changedFilesTool,
|
||||
"dependency-analyzer": dependencyAnalyzerTool,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user