mirror of
https://github.com/jeffusion/gitea-ai-assistant.git
synced 2026-03-27 10:05:50 +00:00
fix(lint): resolve biome violations across src modules
This commit is contained in:
@@ -29,12 +29,12 @@ describe('secrets — AES-256-GCM encryption', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
savedEncryptionKey = process.env.ENCRYPTION_KEY;
|
||||
delete process.env.ENCRYPTION_KEY;
|
||||
Reflect.deleteProperty(process.env, 'ENCRYPTION_KEY');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (savedEncryptionKey === undefined) {
|
||||
delete process.env.ENCRYPTION_KEY;
|
||||
Reflect.deleteProperty(process.env, 'ENCRYPTION_KEY');
|
||||
} else {
|
||||
process.env.ENCRYPTION_KEY = savedEncryptionKey;
|
||||
}
|
||||
@@ -53,7 +53,7 @@ describe('secrets — AES-256-GCM encryption', () => {
|
||||
});
|
||||
|
||||
test('throws if ENCRYPTION_KEY is not set', async () => {
|
||||
delete process.env.ENCRYPTION_KEY;
|
||||
Reflect.deleteProperty(process.env, 'ENCRYPTION_KEY');
|
||||
|
||||
const secrets = await importFresh();
|
||||
expect(() => secrets.initMasterKey()).toThrow('ENCRYPTION_KEY env var is required');
|
||||
|
||||
@@ -32,7 +32,7 @@ describe('provider-repo', () => {
|
||||
afterEach(() => {
|
||||
closeDatabase();
|
||||
if (savedDbPath === undefined) {
|
||||
delete process.env.DATABASE_PATH;
|
||||
Reflect.deleteProperty(process.env, 'DATABASE_PATH');
|
||||
} else {
|
||||
process.env.DATABASE_PATH = savedDbPath;
|
||||
}
|
||||
@@ -212,7 +212,7 @@ describe('provider-repo', () => {
|
||||
});
|
||||
|
||||
test('deleting provider does not affect other providers', () => {
|
||||
const p1 = providerRepo.create({ ...sampleInput, name: 'Keep' });
|
||||
providerRepo.create({ ...sampleInput, name: 'Keep' });
|
||||
const p2 = providerRepo.create({ ...sampleInput, name: 'Delete' });
|
||||
|
||||
providerRepo.delete(p2.id);
|
||||
|
||||
@@ -37,7 +37,9 @@ describe('secret-repo', () => {
|
||||
mkdirSync(tmpDir, { recursive: true });
|
||||
dbPath = join(tmpDir, 'test.db');
|
||||
process.env.DATABASE_PATH = dbPath;
|
||||
process.env.ENCRYPTION_KEY = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('hex');
|
||||
process.env.ENCRYPTION_KEY = Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString(
|
||||
'hex'
|
||||
);
|
||||
|
||||
initMasterKey();
|
||||
initDatabase();
|
||||
@@ -49,12 +51,12 @@ describe('secret-repo', () => {
|
||||
afterEach(() => {
|
||||
closeDatabase();
|
||||
if (savedDbPath === undefined) {
|
||||
delete process.env.DATABASE_PATH;
|
||||
Reflect.deleteProperty(process.env, 'DATABASE_PATH');
|
||||
} else {
|
||||
process.env.DATABASE_PATH = savedDbPath;
|
||||
}
|
||||
if (savedEncryptionKey === undefined) {
|
||||
delete process.env.ENCRYPTION_KEY;
|
||||
Reflect.deleteProperty(process.env, 'ENCRYPTION_KEY');
|
||||
} else {
|
||||
process.env.ENCRYPTION_KEY = savedEncryptionKey;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,10 @@ import { llmConfigRouter } from './controllers/llm-config';
|
||||
import { handleGiteaWebhook } from './controllers/review';
|
||||
import { initMasterKey } from './crypto/secrets';
|
||||
import { initDatabase } from './db/database';
|
||||
import { cleanupScheduler } from './review/cleanup-scheduler';
|
||||
import { codexEngine } from './review/codex/codex-engine';
|
||||
import { mcpRouter } from './review/codex/mcp-handler';
|
||||
import { reviewEngine } from './review/engine';
|
||||
import { cleanupScheduler } from './review/cleanup-scheduler';
|
||||
|
||||
initMasterKey();
|
||||
initDatabase();
|
||||
|
||||
@@ -118,8 +118,8 @@ describe('LLMSemaphore', () => {
|
||||
expect(sem.activeCount).toBe(2);
|
||||
expect(sem.pendingCount).toBe(0);
|
||||
|
||||
const p3 = sem.acquire().then(() => sequence.push('acquire3'));
|
||||
const p4 = sem.acquire().then(() => sequence.push('acquire4'));
|
||||
sem.acquire().then(() => sequence.push('acquire3'));
|
||||
sem.acquire().then(() => sequence.push('acquire4'));
|
||||
const p5 = sem.acquire().then(() => sequence.push('acquire5'));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||
@@ -344,7 +344,7 @@ describe('retryWithBackoff', () => {
|
||||
try {
|
||||
await retryWithBackoff(fn, { maxAttempts: 2 });
|
||||
expect(true).toBe(false);
|
||||
} catch (e: any) {
|
||||
} catch {
|
||||
expect(callCount).toBe(2);
|
||||
}
|
||||
});
|
||||
@@ -509,7 +509,7 @@ describe('withResilience', () => {
|
||||
},
|
||||
{ maxAttempts: 3, baseDelayMs: 10, jitter: false }
|
||||
);
|
||||
} catch (e: any) {
|
||||
} catch {
|
||||
expect(callCount).toBe(3);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -40,10 +40,7 @@ export class LLMGateway {
|
||||
private semaphore: LLMSemaphore;
|
||||
private retryOptions: Partial<RetryOptions>;
|
||||
|
||||
constructor(
|
||||
maxConcurrent = 4,
|
||||
retryOptions?: Partial<RetryOptions>
|
||||
) {
|
||||
constructor(maxConcurrent = 4, retryOptions?: Partial<RetryOptions>) {
|
||||
this.semaphore = new LLMSemaphore(maxConcurrent);
|
||||
this.retryOptions = retryOptions ?? {};
|
||||
}
|
||||
@@ -105,7 +102,10 @@ export class LLMGateway {
|
||||
() => {
|
||||
const provider = this.getOrCreateProvider(assignment.provider_id);
|
||||
if (!provider.embed) {
|
||||
throw new LLMError(`Provider '${provider.type}' does not support embeddings`, provider.type);
|
||||
throw new LLMError(
|
||||
`Provider '${provider.type}' does not support embeddings`,
|
||||
provider.type
|
||||
);
|
||||
}
|
||||
return provider.embed(texts);
|
||||
},
|
||||
|
||||
@@ -32,7 +32,6 @@ export interface LLMProvider {
|
||||
|
||||
/** Optional: embedding interface (only for providers that support it). */
|
||||
embed?(texts: string[]): Promise<number[][]>;
|
||||
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -50,7 +50,6 @@ class CodexEngine {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
await this.store.init();
|
||||
const recovered = await this.store.recoverInterruptedRuns();
|
||||
if (recovered > 0) {
|
||||
|
||||
@@ -218,12 +218,12 @@ export class CodexRunner {
|
||||
`name = "OpenAI"`,
|
||||
`base_url = "${apiUrl}"`,
|
||||
`env_key = "OPENAI_API_KEY"`,
|
||||
`requires_openai_auth = false`,
|
||||
'requires_openai_auth = false',
|
||||
'',
|
||||
'[mcp_servers.gitea-review]',
|
||||
`url = "http://127.0.0.1:${port}/mcp/gitea-review"`,
|
||||
`http_headers = { "X-Review-Run-Id" = "${runId}" }`,
|
||||
`required = true`,
|
||||
'required = true',
|
||||
'',
|
||||
];
|
||||
|
||||
@@ -276,9 +276,7 @@ export class CodexRunner {
|
||||
sections.push(`## 项目级审查要求\n\n${projectPrompt}`);
|
||||
}
|
||||
|
||||
sections.push(
|
||||
'当要求冲突时,优先级为:项目级审查要求 > 全局审查要求 > 审查原则。'
|
||||
);
|
||||
sections.push('当要求冲突时,优先级为:项目级审查要求 > 全局审查要求 > 审查原则。');
|
||||
|
||||
const contextLines: string[] = ['## 当前审查目标'];
|
||||
|
||||
@@ -288,7 +286,7 @@ export class CodexRunner {
|
||||
if (run.baseSha) contextLines.push(`- Base SHA:${run.baseSha}`);
|
||||
if (run.headSha) contextLines.push(`- Head SHA:${run.headSha}`);
|
||||
if (lastReviewedHead) {
|
||||
contextLines.push(`- 增量审查模式:仅审查上次审查后的新变更`);
|
||||
contextLines.push('- 增量审查模式:仅审查上次审查后的新变更');
|
||||
contextLines.push(`- 上次审查 SHA:${lastReviewedHead}`);
|
||||
contextLines.push(`- 请使用 \`git diff ${lastReviewedHead}..${run.headSha}\` 获取增量差异`);
|
||||
} else {
|
||||
@@ -314,7 +312,11 @@ export class CodexRunner {
|
||||
/**
|
||||
* 执行 codex exec 子进程(自定义 prompt + MCP 工具)
|
||||
*/
|
||||
private async runCodexProcess(workspacePath: string, run: ReviewRun, lastReviewedHead?: string): Promise<void> {
|
||||
private async runCodexProcess(
|
||||
workspacePath: string,
|
||||
run: ReviewRun,
|
||||
lastReviewedHead?: string
|
||||
): Promise<void> {
|
||||
const timeoutMs = config.review.codexTimeoutMs;
|
||||
const codexHome = path.join(workspacePath, '.codex');
|
||||
|
||||
|
||||
@@ -166,14 +166,21 @@ export class McpToolExecutor {
|
||||
if (!prNumber) {
|
||||
prNumber = ctx.relatedPrNumber;
|
||||
if (!prNumber && ctx.commitSha) {
|
||||
const related = await giteaService.getRelatedPullRequest(ctx.owner, ctx.repo, ctx.commitSha);
|
||||
const related = await giteaService.getRelatedPullRequest(
|
||||
ctx.owner,
|
||||
ctx.repo,
|
||||
ctx.commitSha
|
||||
);
|
||||
prNumber = related?.number;
|
||||
}
|
||||
}
|
||||
|
||||
if (prNumber) {
|
||||
await giteaService.addPullRequestComment(ctx.owner, ctx.repo, prNumber, body);
|
||||
logger.info('Codex MCP: \u5df2\u53d1\u5e03 PR \u5ba1\u67e5\u603b\u7ed3', { runId: ctx.runId, prNumber });
|
||||
logger.info('Codex MCP: \u5df2\u53d1\u5e03 PR \u5ba1\u67e5\u603b\u7ed3', {
|
||||
runId: ctx.runId,
|
||||
prNumber,
|
||||
});
|
||||
} else if (ctx.commitSha) {
|
||||
await giteaService.addCommitComment(ctx.owner, ctx.repo, ctx.commitSha, body);
|
||||
logger.info('Codex MCP: \u5df2\u53d1\u5e03 Commit \u5ba1\u67e5\u603b\u7ed3', {
|
||||
@@ -182,7 +189,12 @@ export class McpToolExecutor {
|
||||
});
|
||||
} else {
|
||||
return {
|
||||
content: [{ type: 'text', text: '\u65e0\u6cd5\u53d1\u5e03\uff1a\u7f3a\u5c11 PR number \u6216 commit SHA' }],
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: '\u65e0\u6cd5\u53d1\u5e03\uff1a\u7f3a\u5c11 PR number \u6216 commit SHA',
|
||||
},
|
||||
],
|
||||
isError: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -112,7 +112,16 @@ export class LocalRepoManager {
|
||||
// fetch使用认证参数
|
||||
await this.sandboxExec.run(
|
||||
'git',
|
||||
[...authArgs, '--git-dir', mirrorPath, 'fetch', '--prune', 'origin', '+refs/*:refs/*', '^refs/reviewed/*'],
|
||||
[
|
||||
...authArgs,
|
||||
'--git-dir',
|
||||
mirrorPath,
|
||||
'fetch',
|
||||
'--prune',
|
||||
'origin',
|
||||
'+refs/*:refs/*',
|
||||
'^refs/reviewed/*',
|
||||
],
|
||||
{
|
||||
cwd: this.workDir,
|
||||
timeoutMs: this.commandTimeoutMs,
|
||||
@@ -252,27 +261,24 @@ export class LocalRepoManager {
|
||||
* 保存审查快照 ref,记录 PR 最后一次成功审查的 baseSha 和 headSha
|
||||
* 存储在 mirror 的 refs/reviewed/pr/{prNumber}/head 和 refs/reviewed/pr/{prNumber}/base
|
||||
*/
|
||||
async saveReviewedRef(mirrorPath: string, prNumber: number, baseSha: string, headSha: string): Promise<void> {
|
||||
async saveReviewedRef(
|
||||
mirrorPath: string,
|
||||
prNumber: number,
|
||||
baseSha: string,
|
||||
headSha: string
|
||||
): Promise<void> {
|
||||
const unlock = await this.acquireMirrorLock(mirrorPath);
|
||||
try {
|
||||
const headRef = `refs/reviewed/pr/${prNumber}/head`;
|
||||
const baseRef = `refs/reviewed/pr/${prNumber}/base`;
|
||||
await this.sandboxExec.run(
|
||||
'git',
|
||||
['--git-dir', mirrorPath, 'update-ref', headRef, headSha],
|
||||
{
|
||||
await this.sandboxExec.run('git', ['--git-dir', mirrorPath, 'update-ref', headRef, headSha], {
|
||||
cwd: this.workDir,
|
||||
timeoutMs: this.commandTimeoutMs,
|
||||
}
|
||||
);
|
||||
await this.sandboxExec.run(
|
||||
'git',
|
||||
['--git-dir', mirrorPath, 'update-ref', baseRef, baseSha],
|
||||
{
|
||||
});
|
||||
await this.sandboxExec.run('git', ['--git-dir', mirrorPath, 'update-ref', baseRef, baseSha], {
|
||||
cwd: this.workDir,
|
||||
timeoutMs: this.commandTimeoutMs,
|
||||
}
|
||||
);
|
||||
});
|
||||
logger.info('已保存审查快照 ref', { mirrorPath, prNumber, baseSha, headSha });
|
||||
} finally {
|
||||
unlock();
|
||||
@@ -283,7 +289,10 @@ export class LocalRepoManager {
|
||||
* 解析上次审查的快照(baseSha + headSha)
|
||||
* 如果任一 ref 不存在,返回 null
|
||||
*/
|
||||
async resolveReviewedRef(mirrorPath: string, prNumber: number): Promise<{ baseSha: string; headSha: string } | null> {
|
||||
async resolveReviewedRef(
|
||||
mirrorPath: string,
|
||||
prNumber: number
|
||||
): Promise<{ baseSha: string; headSha: string } | null> {
|
||||
try {
|
||||
const headRef = `refs/reviewed/pr/${prNumber}/head`;
|
||||
const baseRef = `refs/reviewed/pr/${prNumber}/base`;
|
||||
@@ -375,7 +384,10 @@ export class LocalRepoManager {
|
||||
if (now - lastActive > maxAgeMs) {
|
||||
await rm(mirrorPath, { recursive: true, force: true });
|
||||
cleaned++;
|
||||
logger.info('已清理过期 mirror 目录', { mirrorPath, lastActiveDaysAgo: Math.floor((now - lastActive) / (24 * 60 * 60 * 1000)) });
|
||||
logger.info('已清理过期 mirror 目录', {
|
||||
mirrorPath,
|
||||
lastActiveDaysAgo: Math.floor((now - lastActive) / (24 * 60 * 60 * 1000)),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('检查/清理 mirror 目录失败', {
|
||||
|
||||
@@ -243,7 +243,11 @@ export class TokenCounter {
|
||||
});
|
||||
}, CATALOG_TTL_MS);
|
||||
// Don't prevent process exit
|
||||
if (this.refreshTimer && typeof this.refreshTimer === 'object' && 'unref' in this.refreshTimer) {
|
||||
if (
|
||||
this.refreshTimer &&
|
||||
typeof this.refreshTimer === 'object' &&
|
||||
'unref' in this.refreshTimer
|
||||
) {
|
||||
(this.refreshTimer as NodeJS.Timeout).unref();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,10 @@ class ReviewEngine {
|
||||
}
|
||||
|
||||
/** Fresh DiffExtractor that reads current config values. */
|
||||
private createDiffExtractor(sandboxExec: SandboxExec, localRepoManager: LocalRepoManager): DiffExtractor {
|
||||
private createDiffExtractor(
|
||||
sandboxExec: SandboxExec,
|
||||
localRepoManager: LocalRepoManager
|
||||
): DiffExtractor {
|
||||
return new DiffExtractor(
|
||||
sandboxExec,
|
||||
localRepoManager,
|
||||
@@ -66,13 +69,10 @@ class ReviewEngine {
|
||||
}
|
||||
|
||||
// Configure LLM Gateway resilience from current config
|
||||
llmGateway.updateResilienceConfig(
|
||||
config.review.llmMaxConcurrentCalls,
|
||||
{
|
||||
llmGateway.updateResilienceConfig(config.review.llmMaxConcurrentCalls, {
|
||||
maxAttempts: config.review.llmRetryMaxAttempts,
|
||||
baseDelayMs: config.review.llmRetryBaseDelayMs,
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// Preload dynamic model catalog from models.dev (non-blocking)
|
||||
tokenCounter.refreshCatalog().catch((error) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { LLMMessage } from '../../llm/types';
|
||||
import config from '../../config';
|
||||
import type { LLMMessage } from '../../llm/types';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { VectorMemoryStore } from '../memory/vector-store';
|
||||
import { FileReviewStore } from '../store/file-review-store';
|
||||
|
||||
@@ -78,11 +78,7 @@ export function createFunctionReferenceSearchTool(sandbox: SandboxExec): Tool {
|
||||
|
||||
for (const task of tasks) {
|
||||
const pattern = task.patterns.join('|');
|
||||
const args = [
|
||||
'--json',
|
||||
'--max-count',
|
||||
String(max_results || 30),
|
||||
];
|
||||
const args = ['--json', '--max-count', String(max_results || 30)];
|
||||
|
||||
if (file_types && file_types.length > 0) {
|
||||
args.push('--type-add', `custom:*.{${file_types.join(',')}}`);
|
||||
|
||||
Reference in New Issue
Block a user