fix: hide status badge when execution phase badge is showing (#154)

* fix: analyzer Python compatibility and settings integration

Fixes project index analyzer failing with TypeError on Python type hints.

Changes:
- Added 'from __future__ import annotations' to all analysis modules
- Fixed project discovery to support new analyzer JSON format
- Read Python path directly from settings.json instead of pythonEnvManager
- Added stderr/stdout logging for analyzer debugging

Resolves 'Discovered 0 files' and 'TypeError: unsupported operand type' issues.

* auto-claude: subtask-1-1 - Hide status badge when execution phase badge is showing

When a task has an active execution (planning, coding, etc.), the
execution phase badge already displays the correct state with a spinner.
The status badge was also rendering, causing duplicate/confusing badges
(e.g., both "Planning" and "Pending" showing at the same time).

This fix wraps the status badge in a conditional that only renders when
there's no active execution, eliminating the redundant badge display.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(ipc): remove unused pythonEnvManager parameter and fix ES6 import

Address CodeRabbit review feedback:
- Remove unused pythonEnvManager parameter from registerProjectContextHandlers
  and registerContextHandlers (the code reads Python path directly from
  settings.json instead)
- Replace require('electron').app with proper ES6 import for consistency

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore(lint): fix import sorting in analysis module

Run ruff --fix to resolve I001 lint errors after merging develop.
All 23 files in apps/backend/analysis/ now have properly sorted imports.

---------

Co-authored-by: Joris Slagter <mail@jorisslagter.nl>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Andy
2025-12-23 13:31:44 +01:00
committed by GitHub
parent 6951251b33
commit 05f5d3038b
30 changed files with 190 additions and 13 deletions
+3
View File
@@ -6,6 +6,9 @@ Code analysis and project scanning tools.
"""
# Import from analyzers subpackage (these are the modular analyzers)
from __future__ import annotations
from .analyzers import (
ProjectAnalyzer as ModularProjectAnalyzer,
)
+2
View File
@@ -27,6 +27,8 @@ This module now serves as a facade to the modular analyzer system in the analyze
All actual implementation is in focused submodules for better maintainability.
"""
from __future__ import annotations
import json
from pathlib import Path
@@ -11,6 +11,8 @@ Main exports:
- analyze_service: Convenience function for service analysis
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
+2
View File
@@ -5,6 +5,8 @@ Base Analyzer Module
Provides common constants, utilities, and base functionality shared across all analyzers.
"""
from __future__ import annotations
import json
from pathlib import Path
@@ -5,6 +5,8 @@ Context Analyzer Package
Contains specialized detectors for comprehensive project context analysis.
"""
from __future__ import annotations
from .api_docs_detector import ApiDocsDetector
from .auth_detector import AuthDetector
from .env_detector import EnvironmentDetector
@@ -8,6 +8,8 @@ Detects API documentation tools and configurations:
- API documentation endpoints
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
@@ -11,6 +11,8 @@ Detects authentication and authorization patterns:
- Auth middleware and decorators
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
@@ -9,6 +9,8 @@ Detects and analyzes environment variables from multiple sources:
- Source code (os.getenv, process.env)
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
@@ -9,6 +9,8 @@ Detects background job and task queue systems:
- Scheduled tasks and cron jobs
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
@@ -10,6 +10,8 @@ Detects database migration tools and configurations:
- Prisma
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
@@ -9,6 +9,8 @@ Detects monitoring and observability setup:
- Logging infrastructure
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
@@ -13,6 +13,8 @@ Detects external service integrations based on dependencies:
- Monitoring tools (Sentry, Datadog, New Relic)
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
@@ -14,6 +14,8 @@ Orchestrates comprehensive project context analysis including:
This module delegates to specialized detectors for clean separation of concerns.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
@@ -7,6 +7,8 @@ Detects database models and schemas across different ORMs:
- JavaScript/TypeScript: Prisma, TypeORM, Drizzle, Mongoose
"""
from __future__ import annotations
import re
from pathlib import Path
@@ -6,6 +6,8 @@ Detects programming languages, frameworks, and related technologies across diffe
Supports Python, Node.js/TypeScript, Go, Rust, and Ruby frameworks.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
@@ -6,6 +6,8 @@ Detects application ports from multiple sources including entry points,
environment files, Docker Compose, configuration files, and scripts.
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
@@ -5,6 +5,8 @@ Project Analyzer Module
Analyzes entire projects, detecting monorepo structures, services, infrastructure, and conventions.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any
@@ -9,6 +9,8 @@ Detects API routes and endpoints across different frameworks:
- Rust: Axum, Actix
"""
from __future__ import annotations
import re
from pathlib import Path
@@ -6,6 +6,8 @@ Main ServiceAnalyzer class that coordinates all analysis for a single service/pa
Integrates framework detection, route analysis, database models, and context extraction.
"""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any
+2
View File
@@ -22,6 +22,8 @@ Usage:
print(f"Test Commands: {result.test_commands}")
"""
from __future__ import annotations
import json
import re
from dataclasses import dataclass, field
@@ -9,6 +9,8 @@ Uses the Claude Agent SDK (same as the rest of the system) for extraction.
Falls back to generic insights if extraction fails (never blocks the build).
"""
from __future__ import annotations
import json
import logging
import os
@@ -28,6 +28,9 @@ needed for the detected tech stack, while blocking dangerous operations.
"""
# Re-export all public API from the project module
from __future__ import annotations
from project import (
# Command registries
BASE_COMMANDS,
+2
View File
@@ -21,6 +21,8 @@ Usage:
test_types = classifier.get_required_test_types(spec_dir)
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from pathlib import Path
@@ -21,6 +21,8 @@ Usage:
print("Security issues found - blocking QA approval")
"""
from __future__ import annotations
import json
import subprocess
from dataclasses import dataclass, field
+2
View File
@@ -22,6 +22,8 @@ Usage:
print(f"Test command: {result['test_command']}")
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from pathlib import Path
+55 -1
View File
@@ -69,8 +69,62 @@ def get_project_index_stats(spec_dir: Path) -> dict:
try:
with open(spec_index) as f:
index_data = json.load(f)
# Support both old and new analyzer formats
file_count = 0
# Old format: top-level "files" array
if "files" in index_data:
file_count = len(index_data["files"])
# New format: count files in services
elif "services" in index_data:
services = index_data["services"]
for service_data in services.values():
if isinstance(service_data, dict):
# Config files
file_count += 3 # package.json, tsconfig.json, .env.example
# Entry point
if service_data.get("entry_point"):
file_count += 1
# Dependencies indicate source files
deps = service_data.get("dependencies", [])
dev_deps = service_data.get("dev_dependencies", [])
file_count += len(deps) // 2 # Rough estimate: 1 file per 2 deps
file_count += len(dev_deps) // 4 # Fewer files for dev deps
# Key directories (each represents multiple files)
key_dirs = service_data.get("key_directories", {})
file_count += len(key_dirs) * 8 # Estimate 8 files per directory
# Config files
if service_data.get("dockerfile"):
file_count += 1
if service_data.get("test_directory"):
file_count += 3 # Test files
# Infrastructure files
if "infrastructure" in index_data:
infra = index_data["infrastructure"]
if infra.get("docker_compose"):
file_count += len(infra["docker_compose"])
if infra.get("dockerfiles"):
file_count += len(infra["dockerfiles"])
# Convention files
if "conventions" in index_data:
conv = index_data["conventions"]
if conv.get("linting"):
file_count += 1 # eslintrc or similar
if conv.get("formatting"):
file_count += 1 # prettier config
if conv.get("git_hooks"):
file_count += 1 # husky/hooks
return {
"file_count": len(index_data.get("files", [])),
"file_count": file_count,
"project_type": index_data.get("project_type", "unknown"),
}
except Exception:
+18
View File
@@ -1,5 +1,6 @@
import { app, BrowserWindow, shell, nativeImage } from 'electron';
import { join } from 'path';
import { existsSync, readFileSync } from 'fs';
import { electronApp, optimizer, is } from '@electron-toolkit/utils';
import { setupIpcHandlers } from './ipc-setup';
import { AgentManager } from './agent';
@@ -120,6 +121,23 @@ app.whenReady().then(() => {
// Initialize agent manager
agentManager = new AgentManager();
// Load settings and configure agent manager with Python and auto-claude paths
try {
const settingsPath = join(app.getPath('userData'), 'settings.json');
if (existsSync(settingsPath)) {
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
if (settings.pythonPath || settings.autoBuildPath) {
console.warn('[main] Configuring AgentManager with settings:', {
pythonPath: settings.pythonPath,
autoBuildPath: settings.autoBuildPath
});
agentManager.configure(settings.pythonPath, settings.autoBuildPath);
}
}
} catch (error) {
console.warn('[main] Failed to load settings for agent configuration:', error);
}
// Initialize terminal manager
terminalManager = new TerminalManager(() => mainWindow);
@@ -1,4 +1,4 @@
import { ipcMain } from 'electron';
import { ipcMain, app } from 'electron';
import type { BrowserWindow } from 'electron';
import path from 'path';
import { existsSync, readFileSync } from 'fs';
@@ -21,6 +21,7 @@ import {
buildMemoryStatus
} from './memory-status-handlers';
import { loadFileBasedMemories } from './memory-data-handlers';
import { parsePythonCommand } from '../../python-detector';
/**
* Load project index from file
@@ -157,9 +158,30 @@ export function registerProjectContextHandlers(
const analyzerPath = path.join(autoBuildSource, 'analyzer.py');
const indexOutputPath = path.join(project.path, AUTO_BUILD_PATHS.PROJECT_INDEX);
// Get Python command directly from settings file (not pythonEnvManager which creates NEW venv)
let pythonCmd = 'python3';
try {
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
if (existsSync(settingsPath)) {
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
if (settings.pythonPath && existsSync(settings.pythonPath)) {
pythonCmd = settings.pythonPath;
console.log('[project-context] Using Python from settings:', pythonCmd);
}
}
} catch (err) {
console.warn('[project-context] Could not read Python from settings:', err);
}
const [pythonCommand, pythonBaseArgs] = parsePythonCommand(pythonCmd);
// Run analyzer
await new Promise<void>((resolve, reject) => {
const proc = spawn('python', [
let stdout = '';
let stderr = '';
const proc = spawn(pythonCommand, [
...pythonBaseArgs,
analyzerPath,
'--project-dir', project.path,
'--output', indexOutputPath
@@ -168,15 +190,30 @@ export function registerProjectContextHandlers(
env: { ...process.env }
});
proc.stdout?.on('data', (data) => {
stdout += data.toString();
});
proc.stderr?.on('data', (data) => {
stderr += data.toString();
});
proc.on('close', (code: number) => {
if (code === 0) {
console.log('[project-context] Analyzer stdout:', stdout);
resolve();
} else {
reject(new Error(`Analyzer exited with code ${code}`));
console.error('[project-context] Analyzer failed with code', code);
console.error('[project-context] Analyzer stderr:', stderr);
console.error('[project-context] Analyzer stdout:', stdout);
reject(new Error(`Analyzer exited with code ${code}: ${stderr || stdout}`));
}
});
proc.on('error', reject);
proc.on('error', (err) => {
console.error('[project-context] Analyzer spawn error:', err);
reject(err);
});
});
// Read the new index
@@ -2,7 +2,7 @@
* Path resolution utilities for Auto Claude updater
*/
import { existsSync } from 'fs';
import { existsSync, readFileSync } from 'fs';
import path from 'path';
import { app } from 'electron';
@@ -43,9 +43,22 @@ export function getUpdateCachePath(): string {
}
/**
* Get the effective source path (considers override from updates)
* Get the effective source path (considers override from updates and settings)
*/
export function getEffectiveSourcePath(): string {
// First, check user settings for configured autoBuildPath
try {
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
if (existsSync(settingsPath)) {
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
if (settings.autoBuildPath && existsSync(settings.autoBuildPath)) {
return settings.autoBuildPath;
}
}
} catch {
// Ignore settings read errors
}
if (app.isPackaged) {
// Check for user-updated source first
const overridePath = path.join(app.getPath('userData'), 'backend-source');
@@ -231,12 +231,15 @@ export function TaskCard({ task, onClick }: TaskCardProps) {
{EXECUTION_PHASE_LABELS[executionPhase]}
</Badge>
)}
<Badge
variant={isStuck ? 'warning' : isIncomplete ? 'warning' : getStatusBadgeVariant(task.status)}
className="text-[10px] px-1.5 py-0.5"
>
{isStuck ? 'Needs Recovery' : isIncomplete ? 'Needs Resume' : getStatusLabel(task.status)}
</Badge>
{/* Status badge - hide when execution phase badge is showing */}
{!hasActiveExecution && (
<Badge
variant={isStuck ? 'warning' : isIncomplete ? 'warning' : getStatusBadgeVariant(task.status)}
className="text-[10px] px-1.5 py-0.5"
>
{isStuck ? 'Needs Recovery' : isIncomplete ? 'Needs Resume' : getStatusLabel(task.status)}
</Badge>
)}
{/* Review reason badge - explains why task needs human review */}
{reviewReasonInfo && !isStuck && !isIncomplete && (
<Badge