From 321e1ea33a5d5cc1108183a7f1f45567f390349c Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Thu, 21 May 2026 20:21:32 +0800 Subject: [PATCH] chore: add codex workspace hooks --- .codex/config.toml | 23 ++++ .codex/hooks/post-edit-codegraph-sync.mjs | 51 ++++++++ .codex/hooks/pre-submit-compile-check.mjs | 122 ++++++++++++++++++ .hermes/shared-memory/development-workflow.md | 9 +- ...发运维】本地开发验证与生产运维-2026-05-15.md | 8 +- 5 files changed, 211 insertions(+), 2 deletions(-) create mode 100644 .codex/config.toml create mode 100644 .codex/hooks/post-edit-codegraph-sync.mjs create mode 100644 .codex/hooks/pre-submit-compile-check.mjs diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 00000000..7cef809b --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,23 @@ +# Genarrative 项目级 Codex 配置。 +# 这里仅保存可进入仓库的 hook 配置与脚本;个人 token、MCP server、模型路由仍放在个人 ~/.codex/config.toml。 + +[features] +hooks = true + +# Codex 准备执行 git commit 前检查 TypeScript / admin-web / api-server 编译错误。 +# 脚本也可手动运行: +# node .codex/hooks/pre-submit-compile-check.mjs +[[hooks.PreToolUse]] +matcher = "Bash|shell_command|functions.shell_command" +command = "node .codex/hooks/pre-submit-compile-check.mjs" +timeout = 180 +statusMessage = "提交前检查编译错误" + +# Codex 每次工具修改文件后执行:同步 CodeGraph 索引。 +# 脚本也可手动运行: +# node .codex/hooks/post-edit-codegraph-sync.mjs +[[hooks.PostToolUse]] +matcher = "Bash|Edit|MultiEdit|Write|apply_patch|shell_command|functions.shell_command|functions.apply_patch" +command = "node .codex/hooks/post-edit-codegraph-sync.mjs" +timeout = 60 +statusMessage = "更新 CodeGraph 索引" diff --git a/.codex/hooks/post-edit-codegraph-sync.mjs b/.codex/hooks/post-edit-codegraph-sync.mjs new file mode 100644 index 00000000..864eb041 --- /dev/null +++ b/.codex/hooks/post-edit-codegraph-sync.mjs @@ -0,0 +1,51 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process'; +import { existsSync, mkdirSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(scriptDir, '..', '..'); +const logDir = resolve(repoRoot, '.codex', 'logs'); +const hasCodegraphConfig = existsSync(resolve(repoRoot, '.codegraph', 'config.json')); +const npmCommand = process.platform === 'win32' ? 'cmd' : 'npm'; + +if (!hasCodegraphConfig) { + console.log('[codex-hook] 未发现 .codegraph/config.json,跳过 CodeGraph 同步。'); + process.exit(0); +} + +const result = spawnSync(npmCommand, process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run codegraph:sync'] : ['run', 'codegraph:sync'], { + cwd: repoRoot, + shell: false, + encoding: 'utf8', + env: { + ...process.env, + NO_COLOR: process.env.NO_COLOR ?? '1', + }, +}); + +mkdirSync(logDir, { recursive: true }); +if (result.stdout) { + process.stdout.write(result.stdout); +} +if (result.stderr) { + process.stderr.write(result.stderr); +} + +if (result.error) { + console.error(`[codex-hook] CodeGraph 同步启动失败:${result.error.message}`); + process.exit(1); +} + +if (result.signal) { + console.error(`[codex-hook] CodeGraph 同步被信号终止:${result.signal}`); + process.exit(1); +} + +if ((result.status ?? 0) !== 0) { + console.error('[codex-hook] CodeGraph 同步失败,请手动运行 npm run codegraph:sync 查看详情。'); + process.exit(result.status ?? 1); +} + +console.log('[codex-hook] CodeGraph 已同步。'); diff --git a/.codex/hooks/pre-submit-compile-check.mjs b/.codex/hooks/pre-submit-compile-check.mjs new file mode 100644 index 00000000..97a5b305 --- /dev/null +++ b/.codex/hooks/pre-submit-compile-check.mjs @@ -0,0 +1,122 @@ +#!/usr/bin/env node +import { spawnSync } from 'node:child_process'; +import { readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const scriptDir = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(scriptDir, '..', '..'); +const npmCommand = process.platform === 'win32' ? 'cmd' : 'npm'; +const hookInput = readHookInput(); + +if (hookInput && !isGitCommitCommand(extractShellCommand(hookInput))) { + process.exit(0); +} + +const validationSteps = [ + { + label: 'TypeScript typecheck', + command: npmCommand, + args: process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run typecheck'] : ['run', 'typecheck'], + }, + { + label: 'Admin web typecheck', + command: npmCommand, + args: process.platform === 'win32' ? ['/d', '/s', '/c', 'npm run admin-web:typecheck'] : ['run', 'admin-web:typecheck'], + }, + { + label: 'Rust api-server compile check', + command: 'cargo', + args: ['check', '-p', 'api-server', '--manifest-path', 'server-rs/Cargo.toml'], + }, +]; + +for (const step of validationSteps) { + const result = runStep(step); + if (!result.ok) { + const reason = `[codex-hook] 提交前编译检查失败:${step.label}。请修复编译错误后再提交。`; + console.error(reason); + if (hookInput) { + console.log(JSON.stringify({ decision: 'block', reason })); + process.exit(0); + } + process.exit(result.status ?? 1); + } +} + +console.error('[codex-hook] 提交前编译检查通过。'); + +function runStep(step) { + console.error(`[codex-hook] ${step.label}`); + const result = spawnSync(step.command, step.args, { + cwd: repoRoot, + shell: false, + encoding: 'utf8', + env: { + ...process.env, + NO_COLOR: process.env.NO_COLOR ?? '1', + }, + }); + + if (result.stdout) { + process.stderr.write(result.stdout); + } + if (result.stderr) { + process.stderr.write(result.stderr); + } + + if (result.error) { + console.error(`[codex-hook] ${step.label} 启动失败:${result.error.message}`); + return { ok: false, status: 1 }; + } + + if (result.signal) { + console.error(`[codex-hook] ${step.label} 被信号终止:${result.signal}`); + return { ok: false, status: 1 }; + } + + return { + ok: (result.status ?? 0) === 0, + status: result.status ?? 1, + }; +} + +function readHookInput() { + try { + const rawInput = readFileSync(0, 'utf8').trim(); + if (!rawInput) { + return null; + } + return JSON.parse(rawInput); + } catch { + return null; + } +} + +function extractShellCommand(input) { + const candidates = [ + input?.tool_input?.command, + input?.toolInput?.command, + input?.tool_args?.command, + input?.toolArgs?.command, + input?.arguments?.command, + input?.params?.command, + input?.command, + ]; + + const command = candidates.find(value => typeof value === 'string' && value.trim().length > 0); + if (command) { + return command; + } + + const shellCommand = input?.tool_input?.cmd ?? input?.toolInput?.cmd ?? input?.arguments?.cmd; + if (Array.isArray(shellCommand)) { + return shellCommand.join(' '); + } + + return ''; +} + +function isGitCommitCommand(command) { + return /(^|[;&|]\s*)git(?:\.exe)?\b[\s\S]{0,200}\bcommit\b/iu.test(command); +} diff --git a/.hermes/shared-memory/development-workflow.md b/.hermes/shared-memory/development-workflow.md index 92d002a2..bb3daa40 100644 --- a/.hermes/shared-memory/development-workflow.md +++ b/.hermes/shared-memory/development-workflow.md @@ -121,7 +121,14 @@ npm run codegraph:sync npm run codegraph:index ``` -`.codegraph/config.json` 可随仓库共享;`.codegraph/codegraph.db`、缓存和日志为本机生成物,不提交。Codex CLI / Cursor / Claude Code 等 MCP 客户端配置属于个人环境;需要时由成员本机执行 `codegraph install` 或查看 `codegraph install --print-config codex`,不要提交个人全局配置。 +`.codegraph/config.json` 可随仓库共享;`.codegraph/codegraph.db`、缓存和日志为本机生成物,不提交。 + +Codex 项目级 hook 保存在 `.codex/config.toml` 与 `.codex/hooks/`: + +- `PreToolUse` hook:`node .codex/hooks/pre-submit-compile-check.mjs`,Codex 准备执行 `git commit` 前检查 `npm run typecheck`、`npm run admin-web:typecheck`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`。 +- `PostToolUse` hook:`node .codex/hooks/post-edit-codegraph-sync.mjs`,工具修改文件后执行 `npm run codegraph:sync`。 + +个人 token、模型路由、MCP server 仍属于个人环境;需要时由成员本机执行 `codegraph install` 或查看 `codegraph install --print-config codex`,不要提交个人全局配置。 ## 常用检查命令 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index d6f9ac21..cde26bf4 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -113,7 +113,13 @@ npm run codegraph:sync npm run codegraph:index ``` -若要把 CodeGraph 接到 Codex CLI / Cursor / Claude Code 等 MCP 客户端,按本机 agent 配置执行 `codegraph install` 或参考 `codegraph install --print-config codex` 输出;不要把个人全局 agent 配置、token 或本机绝对路径提交到仓库。Codex CLI 当前没有项目级 MCP 配置,需由使用者在个人 `~/.codex/config.toml` 中配置。 +Codex 项目级 hook 已放在 `.codex/config.toml` 与 `.codex/hooks/`: + +- `PreToolUse` hook 会在 Codex 准备执行 `git commit` 前运行 `node .codex/hooks/pre-submit-compile-check.mjs`,依次执行 `npm run typecheck`、`npm run admin-web:typecheck`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`,发现编译错误会阻止本次提交。 +- `PostToolUse` hook 会在 Codex 工具修改文件后运行 `node .codex/hooks/post-edit-codegraph-sync.mjs`,执行 `npm run codegraph:sync` 刷新本地语义索引。 +- 如果某个 Codex 客户端版本尚未自动加载项目级 hook,可先手动运行 `node .codex/hooks/pre-submit-compile-check.mjs` 与 `node .codex/hooks/post-edit-codegraph-sync.mjs`;个人模型、token、MCP server 仍放在个人 `~/.codex/config.toml`,不要提交。 + +若要把 CodeGraph 接到 Codex CLI / Cursor / Claude Code 等 MCP 客户端,按本机 agent 配置执行 `codegraph install` 或参考 `codegraph install --print-config codex` 输出;不要把个人全局 agent 配置、token 或本机绝对路径提交到仓库。 ## 后端改动验收