diff --git a/.env.example b/.env.example index 7d1feb2..a05bc14 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,35 @@ PORT=3000 # 在Linux/Mac终端: openssl rand -hex 32 # 或者在Node.js中: require('crypto').randomBytes(32).toString('hex') WEBHOOK_SECRET=your_webhook_secret + +# Agent审查配置(默认关闭,开启请设置为agent) +REVIEW_ENGINE=legacy +REVIEW_WORKDIR=/tmp/gitea-assistant +REVIEW_MODEL_PLANNER=gpt-4o-mini +REVIEW_MODEL_SPECIALIST=gpt-4o-mini +REVIEW_MODEL_JUDGE=gpt-4o-mini +REVIEW_MAX_PARALLEL_RUNS=2 +REVIEW_MAX_FILES_PER_RUN=200 +REVIEW_MAX_FILE_CONTENT_CHARS=40000 +REVIEW_AUTO_PUBLISH_MIN_CONFIDENCE=0.8 +REVIEW_ENABLE_HUMAN_GATE=true +REVIEW_ALLOWED_COMMANDS=git,rg,cat,sed,wc +REVIEW_COMMAND_TIMEOUT_MS=10000 + +# 向量记忆和学习系统配置(可选,第二阶段功能) +# Qdrant向量数据库URL(如果不配置则禁用记忆系统) +QDRANT_URL=http://localhost:6333 +# 是否启用记忆系统(需要先配置QDRANT_URL) +ENABLE_MEMORY=false +# Few-shot学习示例数量(0-20) +FEW_SHOT_EXAMPLES_COUNT=10 + +# Reflection和Debate配置(可选,第三阶段功能) +# 是否启用Reflection自我批评机制(提升审查质量) +ENABLE_REFLECTION=false +# Reflection最大轮次(1-5) +MAX_REFLECTION_ROUNDS=2 +# 是否启用Debate多代理辩论机制(提升高严重性问题准确性) +ENABLE_DEBATE=false +# Debate触发阈值(high=仅高严重性, medium=高和中等严重性) +DEBATE_THRESHOLD=high diff --git a/bun.lock b/bun.lock index 786c774..d8108e6 100644 --- a/bun.lock +++ b/bun.lock @@ -1,16 +1,19 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "ai-review", "dependencies": { "@hono/zod-validator": "^0.4.3", + "@qdrant/js-client-rest": "^1.16.2", "axios": "^1.8.3", "dotenv": "^16.4.7", - "hono": "^4.7.4", + "hono": "^4.11.9", "lodash-es": "^4.17.21", "openai": "^4.87.3", - "zod": "^3.24.2", + "zod": "^3.25.1", + "zod-to-json-schema": "^3.25.1", }, "devDependencies": { "@types/lodash-es": "^4.17.12", @@ -27,7 +30,11 @@ "@hono/zod-validator": ["@hono/zod-validator@0.4.3", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-xIgMYXDyJ4Hj6ekm9T9Y27s080Nl9NXHcJkOvkXPhubOLj8hZkOL8pDnnXfvCf5xEE8Q4oMFenQUZZREUY2gqQ=="], - "@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="], + "@qdrant/js-client-rest": ["@qdrant/js-client-rest@1.16.2", "", { "dependencies": { "@qdrant/openapi-typescript-fetch": "1.2.6", "undici": "^6.0.0" }, "peerDependencies": { "typescript": ">=4.7" } }, "sha512-Zm4wEZURrZ24a+Hmm4l1QQYjiz975Ep3vF0yzWR7ICGcxittNz47YK2iBOk8kb8qseCu8pg7WmO1HOIsO8alvw=="], + + "@qdrant/openapi-typescript-fetch": ["@qdrant/openapi-typescript-fetch@1.2.6", "", {}, "sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA=="], + + "@types/lodash": ["@types/lodash@4.17.23", "", {}, "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA=="], "@types/lodash-es": ["@types/lodash-es@4.17.12", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="], @@ -117,7 +124,7 @@ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], - "hono": ["hono@4.7.4", "", {}, "sha512-Pst8FuGqz3L7tFF+u9Pu70eI0xa5S3LPUmrNd5Jm8nTHze9FxLTK9Kaj5g/k4UcwuJSXTP65SyHOPLrffpcAJg=="], + "hono": ["hono@4.11.9", "", {}, "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ=="], "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], @@ -131,7 +138,7 @@ "js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], - "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -183,6 +190,8 @@ "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], + "undici": ["undici@6.23.0", "", {}, "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g=="], + "undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="], "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], @@ -193,7 +202,9 @@ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "zod": ["zod@3.24.2", "", {}, "sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ=="], + "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + + "zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="], "@types/node-fetch/@types/node": ["@types/node@18.19.80", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-kEWeMwMeIvxYkeg1gTc01awpwLbfMRZXdIhwRcakd/KlK53jmRC26LqcbIt7fnAQTu5GzlnWmzA3H6+l1u6xxQ=="], diff --git a/check-engine.js b/check-engine.js new file mode 100644 index 0000000..e531dbb --- /dev/null +++ b/check-engine.js @@ -0,0 +1,5 @@ +const a = require('./dist/review/engine'); +const b = require('@/review/engine'); +console.log('same?', a.reviewEngine === b.reviewEngine); +console.log('a file', require.resolve('./dist/review/engine')); +console.log('b file', require.resolve('@/review/engine')); diff --git a/package.json b/package.json index 8357c08..e63fac4 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,14 @@ }, "dependencies": { "@hono/zod-validator": "^0.4.3", + "@qdrant/js-client-rest": "^1.16.2", "axios": "^1.8.3", "dotenv": "^16.4.7", - "hono": "^4.7.4", + "hono": "^4.11.9", "lodash-es": "^4.17.21", "openai": "^4.87.3", - "zod": "^3.24.2" + "zod": "^3.25.1", + "zod-to-json-schema": "^3.25.1" }, "devDependencies": { "@types/lodash-es": "^4.17.12", @@ -30,7 +32,8 @@ "build": "rm -rf dist && tsc", "start": "bun run src/index.ts", "start:prod": "bun run dist/index.js", - "lint": "tslint -c tslint.json src/**/*.ts" + "lint": "tslint -c tslint.json src/**/*.ts", + "test": "bun test" }, "keywords": [ "code-review", diff --git a/src/config/index.ts b/src/config/index.ts index 836f9e9..195e03d 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -6,6 +6,7 @@ config(); // 判断是否为开发环境 const isDev = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV; +const defaultAllowedReviewCommands = ['git', 'rg', 'cat', 'sed', 'wc']; // 环境变量验证模式 const envSchema = z.object({ @@ -32,6 +33,46 @@ const envSchema = z.object({ // 管理后台配置 ADMIN_PASSWORD: z.string().default('password'), JWT_SECRET: z.string().default('a-secure-secret-for-jwt'), + + // Agent审查配置 + REVIEW_ENGINE: z.enum(['legacy', 'agent']).default('legacy'), + REVIEW_WORKDIR: z.string().default('/tmp/gitea-assistant'), + REVIEW_MODEL_PLANNER: z.string().default('gpt-4o-mini'), + REVIEW_MODEL_SPECIALIST: z.string().default('gpt-4o-mini'), + REVIEW_MODEL_JUDGE: z.string().default('gpt-4o-mini'), + REVIEW_MAX_PARALLEL_RUNS: z.coerce.number().int().min(1).max(8).default(2), + REVIEW_MAX_FILES_PER_RUN: z.coerce.number().int().min(1).max(1000).default(200), + REVIEW_MAX_FILE_CONTENT_CHARS: z.coerce.number().int().min(1000).max(1_000_000).default(40_000), + REVIEW_AUTO_PUBLISH_MIN_CONFIDENCE: z.coerce.number().min(0).max(1).default(0.8), + REVIEW_ENABLE_HUMAN_GATE: z + .enum(['true', 'false']) + .default('true') + .transform((value) => value === 'true'), + REVIEW_ALLOWED_COMMANDS: z.string().default(defaultAllowedReviewCommands.join(',')), + REVIEW_COMMAND_TIMEOUT_MS: z.coerce.number().int().min(1000).max(300000).default(10000), + + // 向量记忆和学习系统配置 + QDRANT_URL: z.preprocess( + (val) => (typeof val === 'string' && val.trim() === '' ? undefined : val), + z.string().url().optional() + ), + ENABLE_MEMORY: z + .enum(['true', 'false']) + .default('false') + .transform((value) => value === 'true'), + FEW_SHOT_EXAMPLES_COUNT: z.coerce.number().int().min(0).max(20).default(10), + + // Reflection和Debate配置(第三阶段) + ENABLE_REFLECTION: z + .enum(['true', 'false']) + .default('false') + .transform((value) => value === 'true'), + MAX_REFLECTION_ROUNDS: z.coerce.number().int().min(1).max(5).default(2), + ENABLE_DEBATE: z + .enum(['true', 'false']) + .default('false') + .transform((value) => value === 'true'), + DEBATE_THRESHOLD: z.enum(['high', 'medium']).default('high'), }); // 处理验证结果 @@ -74,4 +115,31 @@ export default { jwtSecret: envParseResult.success ? envParseResult.data.JWT_SECRET : 'a-secure-secret-for-jwt', giteaAdminToken: envParseResult.success ? envParseResult.data.GITEA_ADMIN_TOKEN : undefined, }, + review: { + engine: envParseResult.success ? envParseResult.data.REVIEW_ENGINE : 'legacy', + workdir: envParseResult.success ? envParseResult.data.REVIEW_WORKDIR : '/tmp/gitea-assistant', + modelPlanner: envParseResult.success ? envParseResult.data.REVIEW_MODEL_PLANNER : 'gpt-4o-mini', + modelSpecialist: envParseResult.success ? envParseResult.data.REVIEW_MODEL_SPECIALIST : 'gpt-4o-mini', + modelJudge: envParseResult.success ? envParseResult.data.REVIEW_MODEL_JUDGE : 'gpt-4o-mini', + maxParallelRuns: envParseResult.success ? envParseResult.data.REVIEW_MAX_PARALLEL_RUNS : 2, + maxFilesPerRun: envParseResult.success ? envParseResult.data.REVIEW_MAX_FILES_PER_RUN : 200, + maxFileContentChars: envParseResult.success ? envParseResult.data.REVIEW_MAX_FILE_CONTENT_CHARS : 40_000, + autoPublishMinConfidence: envParseResult.success + ? envParseResult.data.REVIEW_AUTO_PUBLISH_MIN_CONFIDENCE + : 0.8, + enableHumanGate: envParseResult.success ? envParseResult.data.REVIEW_ENABLE_HUMAN_GATE : true, + allowedCommands: envParseResult.success + ? envParseResult.data.REVIEW_ALLOWED_COMMANDS.split(',') + .map((item) => item.trim()) + .filter(Boolean) + : defaultAllowedReviewCommands, + commandTimeoutMs: envParseResult.success ? envParseResult.data.REVIEW_COMMAND_TIMEOUT_MS : 10000, + qdrantUrl: envParseResult.success ? envParseResult.data.QDRANT_URL : undefined, + enableMemory: envParseResult.success ? envParseResult.data.ENABLE_MEMORY : false, + fewShotExamplesCount: envParseResult.success ? envParseResult.data.FEW_SHOT_EXAMPLES_COUNT : 10, + enableReflection: envParseResult.success ? envParseResult.data.ENABLE_REFLECTION : false, + maxReflectionRounds: envParseResult.success ? envParseResult.data.MAX_REFLECTION_ROUNDS : 2, + enableDebate: envParseResult.success ? envParseResult.data.ENABLE_DEBATE : false, + debateThreshold: envParseResult.success ? envParseResult.data.DEBATE_THRESHOLD : 'high', + }, };