Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/agent-system/todolist-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,7 @@ All tasks for "${session.name}" completed at ${session.updatedAt}.
if (createdMatch) createdAt = createdMatch[1];

// Parse tasks
const taskMatch = line.match(/^[[~x\s\-\]]\s+Task\s+([a-f0-9]+):\s*(.+)$/);
const taskMatch = line.match(/^\[[ ~x\-✗]\]\s+Task\s+([a-f0-9]+):\s*(.+)$/);
if (taskMatch) {
const taskId = taskMatch[1];
const subject = taskMatch[2];
Expand Down
110 changes: 48 additions & 62 deletions src/agents/drift-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import path from 'path';
import { glob } from 'glob';
import { OpenRouterClient } from '../embeddings/openrouter.js';
import { MODEL_CONFIG, getModelFor } from '../config/models.js';
import { parseAIResponse } from '../utils/ai-parser.js';

/**
* A detected drift issue
Expand Down Expand Up @@ -151,43 +152,33 @@ export class DriftAgent {
// Discover relevant source code for comparison
const sourceFiles = await this.discoverSourceFiles();

// Analyze files for drift in parallel with concurrency limit
const CONCURRENCY_LIMIT = 5;
const queue = [...filesToProcess];

const workers = Array(Math.min(CONCURRENCY_LIMIT, queue.length)).fill(null).map(async () => {
while (queue.length > 0) {
const relativePath = queue.shift();
if (!relativePath) break;

try {
const drift = await this.checkFileForDrift(relativePath, sourceFiles);
if (drift) {
drifts.push(drift);
// Analyze each file for drift
for (const relativePath of filesToProcess) {
try {
const drift = await this.checkFileForDrift(relativePath, sourceFiles);
if (drift) {
drifts.push(drift);

if (options.autoFix && drift.suggestion) {
const didFix = await this.fixDrift(relativePath, drift);
if (didFix) fixed++;
}
if (options.autoFix && drift.suggestion) {
const didFix = await this.fixDrift(relativePath, drift);
if (didFix) fixed++;
}
} catch (error) {
// Check for authentication errors
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('401') ||
errorMessage.includes('Authentication') ||
errorMessage.includes('Unauthorized') ||
errorMessage.includes('API key')) {
authFailures.push(relativePath);
} else {
errors.push({ file: relativePath, error: errorMessage });
}
// Log error but continue
console.error(`Failed to analyze ${relativePath}: ${errorMessage}`);
}
} catch (error) {
// Check for authentication errors
const errorMessage = error instanceof Error ? error.message : String(error);
if (errorMessage.includes('401') ||
errorMessage.includes('Authentication') ||
errorMessage.includes('Unauthorized') ||
errorMessage.includes('API key')) {
authFailures.push(relativePath);
} else {
errors.push({ file: relativePath, error: errorMessage });
}
// Log error but continue
console.error(`Failed to analyze ${relativePath}: ${errorMessage}`);
}
});

await Promise.all(workers);
}

return {
drifts,
Expand Down Expand Up @@ -322,24 +313,7 @@ Severity guidelines:
suggestion?: string;
line?: number;
} | null {
try {
// Try direct parse first
return JSON.parse(response);
} catch {
// Try to extract JSON from response
const start = response.indexOf('{');
const end = response.lastIndexOf('}');

if (start !== -1 && end !== -1 && end > start) {
try {
const jsonSubstring = response.slice(start, end + 1);
return JSON.parse(jsonSubstring);
} catch {
// Still failed, return null
}
}
}
return null;
return parseAIResponse(response);
}

/**
Expand Down Expand Up @@ -399,25 +373,37 @@ Severity guidelines:
'**/*.java'
];

return await glob(patterns, {
cwd: this.projectRoot,
ignore: DEFAULT_IGNORE,
absolute: false
});
const allFiles: string[] = [];

for (const pattern of patterns) {
const files = await glob(pattern, {
cwd: this.projectRoot,
ignore: DEFAULT_IGNORE,
absolute: false
});
allFiles.push(...files);
}

return allFiles;
}

/**
* Get all context files to check
*/
private async getContextFiles(): Promise<string[]> {
const matched = await glob(DEFAULT_CONTEXT_FILES, {
cwd: this.projectRoot,
ignore: DEFAULT_IGNORE,
absolute: false
});
const files: string[] = [];

for (const pattern of DEFAULT_CONTEXT_FILES) {
const matched = await glob(pattern, {
cwd: this.projectRoot,
ignore: DEFAULT_IGNORE,
absolute: false
});
files.push(...matched);
}

// Deduplicate
return Array.from(new Set(matched));
return Array.from(new Set(files));
}

/**
Expand Down
53 changes: 19 additions & 34 deletions src/agents/fact-check-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import path from 'path';
import { glob } from 'glob';
import { OpenRouterClient } from '../embeddings/openrouter.js';
import { getModelFor, MODEL_CONFIG } from '../config/models.js';
import { parseAIResponse } from '../utils/ai-parser.js';

/**
* A fact-check result for a single claim
Expand Down Expand Up @@ -213,40 +214,24 @@ Confidence guidelines:
* Parse the fact-check response
*/
private parseFactCheckResponse(response: string): FactCheckClaim[] {
try {
// Try direct parse first
const parsed = JSON.parse(response);
if (parsed.claims && Array.isArray(parsed.claims)) {
return parsed.claims.map((c: any) => ({
claim: String(c.claim || ''),
factual: Boolean(c.factual),
correction: c.correction ? String(c.correction) : undefined,
confidence: Number(c.confidence || 0.5),
line: c.line ? Number(c.line) : undefined
}));
}
} catch {
// Try to extract JSON from response
const start = response.indexOf('{');
const end = response.lastIndexOf('}');

if (start !== -1 && end !== -1 && end > start) {
try {
const jsonSubstring = response.slice(start, end + 1);
const parsed = JSON.parse(jsonSubstring);
if (parsed.claims && Array.isArray(parsed.claims)) {
return parsed.claims.map((c: any) => ({
claim: String(c.claim || ''),
factual: Boolean(c.factual),
correction: c.correction ? String(c.correction) : undefined,
confidence: Number(c.confidence || 0.5),
line: c.line ? Number(c.line) : undefined
}));
}
} catch {
// Fall through
}
}
interface RawClaim {
claim?: string;
factual?: boolean;
correction?: string;
confidence?: number;
line?: number;
}

const parsed = parseAIResponse<{ claims: RawClaim[] }>(response);

if (parsed && parsed.claims && Array.isArray(parsed.claims)) {
return parsed.claims.map((c) => ({
claim: String(c.claim || ''),
factual: Boolean(c.factual),
correction: c.correction ? String(c.correction) : undefined,
confidence: Number(c.confidence || 0.5),
line: c.line ? Number(c.line) : undefined
}));
}

return [];
Expand Down
20 changes: 5 additions & 15 deletions src/analyzer/intelligent-analyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { glob } from 'glob';
import { OpenRouterClient, createOpenRouterClient, hasOpenRouterKey } from '../embeddings/openrouter.js';
import { AI_TOOLS, AI_TOOL_FOLDERS, type AITool } from '../db/schema.js';
import { estimateTokens, chunkForEmbedding } from '../utils/chunking.js';
import { parseAIResponse } from '../utils/ai-parser.js';

/**
* Discovery result for a file
Expand Down Expand Up @@ -485,21 +486,9 @@ Return ONLY valid JSON, no markdown formatting.
{ role: 'user', content: analysisPrompt }
], { temperature: 0.2, maxTokens: 4096 });

// First, try to parse the whole response as JSON.
try {
return JSON.parse(response);
} catch {
// If that fails, attempt to extract the JSON substring between the
// first '{' and the last '}' and parse that.
if (typeof response === 'string') {
const start = response.indexOf('{');
const end = response.lastIndexOf('}');

if (start !== -1 && end !== -1 && end > start) {
const jsonSubstring = response.slice(start, end + 1);
return JSON.parse(jsonSubstring);
}
}
const parsed = parseAIResponse<Partial<AnalysisResult>>(response);
if (parsed) {
return parsed;
}
} catch (error) {
console.warn('Failed to parse intelligent analysis response.');
Expand Down Expand Up @@ -675,6 +664,7 @@ Return ONLY valid JSON, no markdown formatting.
path.join(this.projectRoot, 'docs')
];

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const watcher = (chokidar as any).watch(watchPaths, {
ignored: /node_modules|\.git|dist/,
persistent: true,
Expand Down
Loading
Loading