40fa1dc001
* feat(ui): add one-time version 2.7.5 reauthentication warning modal Users upgrading to v2.7.5 need to reauthenticate their Claude profile due to authentication changes. This adds a modal that: - Shows once on first launch of 2.7.5 - Guides users to Settings > Integrations to reauthenticate - Persists dismissal state in seenVersionWarnings setting - Supports EN/FR translations Also fixes a duplicate test case in rate-limit-detector tests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(subprocess): use platform abstraction for auth failure process killing - Replace direct process.platform checks with isWindows() from platform module - Use execFile() instead of exec() for Windows taskkill command to avoid string interpolation in shell commands (security best practice) - Remove redundant require('child_process') since exec/execFile already imported - Add real-time auth failure detection in subprocess stdout/stderr - Kill subprocess immediately on 401 auth errors to prevent error spam - Use process groups on Unix (-pid) and taskkill /T on Windows for tree killing These changes improve code consistency with project cross-platform guidelines and fix 401 auth detection for Claude API errors during GitHub/GitLab operations. * feat(auth): enhance authentication failure detection and handling - Updated rate-limit-detector to recognize additional auth failure patterns, including OAuth token expiration and specific Claude API error messages. - Integrated auth failure detection into GitHub and GitLab auto-fix handlers, enabling real-time feedback on authentication issues. - Enhanced PR review and triage handlers to log and communicate auth failures to the renderer. - Modified AuthFailureModal to direct users to the integrations settings for re-authentication. These changes improve user experience by providing clearer feedback on authentication issues and streamline the re-authentication process. * fix: address PR review issues and improve code quality - Fix killed subprocess incorrectly reported as successful (HIGH) - Change exit code handling from `code ?? 0` to `code ?? -1` - Add `killedDueToAuthFailure` flag to track auth failure kills - Check flag in close handler before checking exitCode - Fix subprocess not killed if onAuthFailure callback throws (MEDIUM) - Wrap onAuthFailure callback in try-catch - Add missing onAuthFailure callback in checkNewIssues (MEDIUM) - Add optional onAuthFailure parameter to checkNewIssues function - Pass callback from IPC handler - Add error handling for getAppVersion() async call (LOW) - Wrap in try-catch to prevent unhandled promise rejection Code quality improvements: - Extract VERSION_WARNING_275 constant to avoid magic strings - Create createAuthFailureCallback() helper to reduce duplication - Use isWindows() consistently instead of process.platform checks Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: remove node_modules symlink and clean up package-lock.json - Deleted the node_modules symlink to ensure a clean project structure. - Removed unnecessary "peer" properties from several dependencies in package-lock.json to streamline the file and improve clarity. * fix(security): replace dangerouslySetInnerHTML with Trans component and persist version warning - Replace dangerouslySetInnerHTML in OAuthStep.tsx and ClaudeOAuthFlow.tsx with react-i18next Trans component to prevent XSS vulnerabilities - Fix version warning persistence: use saveSettings() instead of updateSettings() to persist seenVersionWarnings to disk (not just in-memory) - Map <code> and <strong> HTML tags in translations to safe React elements Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(subprocess): add comprehensive auth failure detection tests Add 7 new test cases for auth failure handling in subprocess-runner: - Auth failure detection from stdout - Auth failure detection from stderr - Only emit auth failure once (dedupe) - Process kill on auth failure - No callback when no auth failure - Graceful handling of callback errors - Result error set when killed due to auth failure Also change console.log to console.warn for taskkill error handling to improve visibility of error conditions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Alex <63423455+AlexMadera@users.noreply.github.com>
205 lines
6.4 KiB
Python
205 lines
6.4 KiB
Python
"""
|
|
Subtask Management Tools
|
|
========================
|
|
|
|
Tools for managing subtask status in implementation_plan.json.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from core.file_utils import write_json_atomic
|
|
from spec.validate_pkg.auto_fix import auto_fix_plan
|
|
|
|
try:
|
|
from claude_agent_sdk import tool
|
|
|
|
SDK_TOOLS_AVAILABLE = True
|
|
except ImportError:
|
|
SDK_TOOLS_AVAILABLE = False
|
|
tool = None
|
|
|
|
|
|
def _update_subtask_in_plan(
|
|
plan: dict[str, Any],
|
|
subtask_id: str,
|
|
status: str,
|
|
notes: str,
|
|
) -> bool:
|
|
"""
|
|
Update a subtask in the plan.
|
|
|
|
Args:
|
|
plan: The implementation plan dict
|
|
subtask_id: ID of the subtask to update
|
|
status: New status (pending, in_progress, completed, failed)
|
|
notes: Optional notes to add
|
|
|
|
Returns:
|
|
True if subtask was found and updated, False otherwise
|
|
"""
|
|
subtask_found = False
|
|
for phase in plan.get("phases", []):
|
|
for subtask in phase.get("subtasks", []):
|
|
if subtask.get("id") == subtask_id:
|
|
subtask["status"] = status
|
|
if notes:
|
|
subtask["notes"] = notes
|
|
subtask["updated_at"] = datetime.now(timezone.utc).isoformat()
|
|
subtask_found = True
|
|
break
|
|
if subtask_found:
|
|
break
|
|
|
|
if subtask_found:
|
|
plan["last_updated"] = datetime.now(timezone.utc).isoformat()
|
|
|
|
return subtask_found
|
|
|
|
|
|
def create_subtask_tools(spec_dir: Path, project_dir: Path) -> list:
|
|
"""
|
|
Create subtask management tools.
|
|
|
|
Args:
|
|
spec_dir: Path to the spec directory
|
|
project_dir: Path to the project root
|
|
|
|
Returns:
|
|
List of subtask tool functions
|
|
"""
|
|
if not SDK_TOOLS_AVAILABLE:
|
|
return []
|
|
|
|
tools = []
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Tool: update_subtask_status
|
|
# -------------------------------------------------------------------------
|
|
@tool(
|
|
"update_subtask_status",
|
|
"Update the status of a subtask in implementation_plan.json. Use this when completing or starting a subtask.",
|
|
{"subtask_id": str, "status": str, "notes": str},
|
|
)
|
|
async def update_subtask_status(args: dict[str, Any]) -> dict[str, Any]:
|
|
"""Update subtask status in the implementation plan."""
|
|
subtask_id = args["subtask_id"]
|
|
status = args["status"]
|
|
notes = args.get("notes", "")
|
|
|
|
valid_statuses = ["pending", "in_progress", "completed", "failed"]
|
|
if status not in valid_statuses:
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"Error: Invalid status '{status}'. Must be one of: {valid_statuses}",
|
|
}
|
|
]
|
|
}
|
|
|
|
plan_file = spec_dir / "implementation_plan.json"
|
|
if not plan_file.exists():
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": "Error: implementation_plan.json not found",
|
|
}
|
|
]
|
|
}
|
|
|
|
try:
|
|
with open(plan_file, encoding="utf-8") as f:
|
|
plan = json.load(f)
|
|
|
|
subtask_found = _update_subtask_in_plan(plan, subtask_id, status, notes)
|
|
|
|
if not subtask_found:
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"Error: Subtask '{subtask_id}' not found in implementation plan",
|
|
}
|
|
]
|
|
}
|
|
|
|
# Use atomic write to prevent file corruption
|
|
write_json_atomic(plan_file, plan, indent=2)
|
|
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"Successfully updated subtask '{subtask_id}' to status '{status}'",
|
|
}
|
|
]
|
|
}
|
|
|
|
except json.JSONDecodeError as e:
|
|
# Attempt to auto-fix the plan and retry
|
|
if auto_fix_plan(spec_dir):
|
|
# Retry after fix
|
|
try:
|
|
with open(plan_file, encoding="utf-8") as f:
|
|
plan = json.load(f)
|
|
|
|
subtask_found = _update_subtask_in_plan(
|
|
plan, subtask_id, status, notes
|
|
)
|
|
|
|
if subtask_found:
|
|
write_json_atomic(plan_file, plan, indent=2)
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"Successfully updated subtask '{subtask_id}' to status '{status}' (after auto-fix)",
|
|
}
|
|
]
|
|
}
|
|
else:
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"Error: Subtask '{subtask_id}' not found in implementation plan (after auto-fix)",
|
|
}
|
|
]
|
|
}
|
|
except Exception as retry_err:
|
|
logging.warning(
|
|
f"Subtask update retry failed after auto-fix: {retry_err}"
|
|
)
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"Error: Subtask update failed after auto-fix: {retry_err}",
|
|
}
|
|
]
|
|
}
|
|
|
|
return {
|
|
"content": [
|
|
{
|
|
"type": "text",
|
|
"text": f"Error: Invalid JSON in implementation_plan.json: {e}",
|
|
}
|
|
]
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
"content": [
|
|
{"type": "text", "text": f"Error updating subtask status: {e}"}
|
|
]
|
|
}
|
|
|
|
tools.append(update_subtask_status)
|
|
|
|
return tools
|