-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: handle milliseconds in ISO 8601 rate limit reset timestamps #1846
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 11 commits
45456a0
0a1ae80
ca88820
304995d
905d8e2
fedaf42
06f90f1
6a9dc4b
426cde6
d88744c
b718374
9c52252
a86a7e2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -175,3 +175,6 @@ OPUS_ANALYSIS_AND_IDEAS.md | |||||
| /shared_docs | ||||||
| logs/security/ | ||||||
| Agents.md | ||||||
| desktop.env | ||||||
| auto-claude-desktop.sh | ||||||
| images/ | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This pattern matches at every level of the tree, not just the root. If any package or sub-project has an Proposed fix-images/
+/images/📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| """ | ||
|
|
||
| import json | ||
| import re | ||
| import shutil | ||
| import subprocess | ||
| from pathlib import Path | ||
|
|
@@ -60,8 +61,19 @@ def handle_batch_create_command(batch_file: str, project_dir: str) -> bool: | |
| for idx, task in enumerate(tasks, 1): | ||
| spec_id = f"{next_id:03d}" | ||
| task_title = task.get("title", f"Task {idx}") | ||
| task_slug = task_title.lower().replace(" ", "-")[:50] | ||
| spec_name = f"{spec_id}-{task_slug}" | ||
|
|
||
| # Extract category tag like [sec-001] from title if present | ||
| tag_match = re.match(r"^\[(\w+-\d+)\]\s*(.*)", task_title) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Tag regex only matches a single
🤖 Prompt for AI Agents |
||
| if tag_match: | ||
| tag = tag_match.group(1) # e.g. "sec-001" | ||
| title_rest = tag_match.group(2) # e.g. "Remove hardcoded API key..." | ||
| title_slug = re.sub(r"[^\w\-]", "-", title_rest.lower()) | ||
| title_slug = re.sub(r"-+", "-", title_slug).strip("-")[:50] | ||
| spec_name = f"{spec_id}-[{tag}]-{title_slug}" | ||
|
Comment on lines
+66
to
+72
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Brackets in directory names can break shell globbing and downstream tooling.
Consider escaping or replacing brackets in the spec name: ♻️ Suggested fix — use parentheses or plain hyphens instead of brackets- spec_name = f"{spec_id}-[{tag}]-{title_slug}"
+ spec_name = f"{spec_id}-{tag}-{title_slug}"This keeps the tag visible in the directory name without introducing glob-sensitive characters. If visual grouping of the tag is desired, parentheses 🤖 Prompt for AI Agents |
||
| else: | ||
| task_slug = re.sub(r"[^\w\-]", "-", task_title.lower()) | ||
| task_slug = re.sub(r"-+", "-", task_slug).strip("-")[:50] | ||
| spec_name = f"{spec_id}-{task_slug}" | ||
| spec_dir = specs_dir / spec_name | ||
| spec_dir.mkdir(exist_ok=True) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -344,7 +344,11 @@ def get_worktree_path(self, spec_name: str) -> Path: | |
|
|
||
| def get_branch_name(self, spec_name: str) -> str: | ||
| """Get the branch name for a spec.""" | ||
| return f"auto-claude/{spec_name}" | ||
| # Sanitize spec_name: remove characters invalid in git branch names | ||
| sanitized = re.sub(r'[\[\]~^:?*\\{}]', '', spec_name) | ||
| # Collapse repeated dashes from removal | ||
| sanitized = re.sub(r'-{2,}', '-', sanitized) | ||
| return f"auto-claude/{sanitized}" | ||
|
Comment on lines
345
to
+351
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial 🧩 Analysis chain🏁 Script executed: # Find all calls to get_branch_name
rg -n 'get_branch_name\(' --type=py -B2 -A2Repository: AndyMik90/Auto-Claude Length of output: 2309 🏁 Script executed: # Check for any validation of spec_name before it reaches this function
rg -n 'spec_name' --type=py -B2 -A2 | head -100Repository: AndyMik90/Auto-Claude Length of output: 6249 🏁 Script executed: # Look for tests of the get_branch_name function
rg -n 'test.*get_branch_name|get_branch_name.*test' --type=py -B1 -A5Repository: AndyMik90/Auto-Claude Length of output: 47 🏁 Script executed: # Examine the worktree.py file around the function
head -360 apps/backend/core/worktree.py | tail -60Repository: AndyMik90/Auto-Claude Length of output: 2603 🏁 Script executed: # Find _generate_spec_name implementation
rg -n '_generate_spec_name' --type=py -A10 | head -60Repository: AndyMik90/Auto-Claude Length of output: 4293 🏁 Script executed: # Check the full test_create_worktree_with_spec_name test
rg -n 'test_create_worktree_with_spec_name' --type=py -A20Repository: AndyMik90/Auto-Claude Length of output: 1429 🏁 Script executed: # Look for any validation of spec_name in the codebase
rg -n 'spec_name.*validate|validate.*spec_name|spec_name.*check' --type=py -B2 -A2Repository: AndyMik90/Auto-Claude Length of output: 47 🏁 Script executed: # Find the actual implementation of _generate_spec_name
rg -n 'def _generate_spec_name' --type=py -A30Repository: AndyMik90/Auto-Claude Length of output: 2753 🏁 Script executed: # Check if spec_name can come from external sources (not just _generate_spec_name)
rg -n 'spec_name\s*=' --type=py | grep -v test | head -30Repository: AndyMik90/Auto-Claude Length of output: 1751 🏁 Script executed: # Find the actual generate_spec_name implementation
rg -n 'def generate_spec_name' --type=py -A20Repository: AndyMik90/Auto-Claude Length of output: 1360 🏁 Script executed: # Check batch_commands.py line 64 to see spec_name construction
sed -n '60,70p' apps/backend/cli/batch_commands.pyRepository: AndyMik90/Auto-Claude Length of output: 511 🏁 Script executed: # Get the complete generate_spec_name function
sed -n '123,180p' apps/backend/spec/pipeline/models.pyRepository: AndyMik90/Auto-Claude Length of output: 1080 🏁 Script executed: # Get more of the generate_spec_name function including the actual sanitization logic
sed -n '123,200p' apps/backend/spec/pipeline/models.pyRepository: AndyMik90/Auto-Claude Length of output: 1552 🏁 Script executed: # Get the complete generate_spec_name function
sed -n '123,210p' apps/backend/spec/pipeline/models.pyRepository: AndyMik90/Auto-Claude Length of output: 1837 Branch name sanitization is technically incomplete but mitigated by upstream validation. Git's branch naming rules forbid spaces, The current implementation is safe and functional, but adding comprehensive git ref validation here would be good defensive practice—for instance, if 🤖 Prompt for AI Agents |
||
|
|
||
| def worktree_exists(self, spec_name: str) -> bool: | ||
| """Check if a worktree exists for a spec.""" | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,276 @@ | ||||||||||||||||||||||
| #!/usr/bin/env python3 | ||||||||||||||||||||||
| """Auto-Claude Preflight Check & Self-Healing Script. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Run before any Auto-Claude command to detect and fix common issues. | ||||||||||||||||||||||
| Usage: python preflight.py [--fix] | ||||||||||||||||||||||
| """ | ||||||||||||||||||||||
| import json | ||||||||||||||||||||||
| import os | ||||||||||||||||||||||
Check noticeCode scanning / CodeQL Unused import Note
Import of 'os' is not used.
|
||||||||||||||||||||||
| import sys | ||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||
| import time | ||||||||||||||||||||||
Check noticeCode scanning / CodeQL Unused import Note
Import of 'time' is not used.
|
||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||
|
Comment on lines
+7
to
+12
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix linting errors: unsorted imports and unused The CI pipeline is failing on this file. Proposed fix-import json
-import os
-import sys
-import subprocess
-import time
-from pathlib import Path
+import json
+import subprocess
+import sys
+from pathlib import Path📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Actions: Lint[error] 7-7: I001 Import block is un-sorted or un-formatted 🪛 GitHub Check: Python (Ruff)[failure] 11-11: Ruff (F401) [failure] 8-8: Ruff (F401) [failure] 7-12: Ruff (I001) 🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| BACKEND_DIR = Path(__file__).parent | ||||||||||||||||||||||
| ENV_FILE = BACKEND_DIR / ".env" | ||||||||||||||||||||||
| VENV_PYTHON = BACKEND_DIR / ".venv" / "bin" / "python" | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested fix-VENV_PYTHON = BACKEND_DIR / ".venv" / "bin" / "python"
+import sysconfig
+VENV_PYTHON = BACKEND_DIR / ".venv" / ("Scripts" if sys.platform == "win32" else "bin") / "python"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| RED = "\033[91m" | ||||||||||||||||||||||
| GREEN = "\033[92m" | ||||||||||||||||||||||
| YELLOW = "\033[93m" | ||||||||||||||||||||||
| BLUE = "\033[94m" | ||||||||||||||||||||||
| RESET = "\033[0m" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def ok(msg): | ||||||||||||||||||||||
| print(f" {GREEN}✓{RESET} {msg}") | ||||||||||||||||||||||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (password) Error loading related location Loading |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def warn(msg): | ||||||||||||||||||||||
| print(f" {YELLOW}⚠{RESET} {msg}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def fail(msg): | ||||||||||||||||||||||
| print(f" {RED}✗{RESET} {msg}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def info(msg): | ||||||||||||||||||||||
| print(f" {BLUE}ℹ{RESET} {msg}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class PreflightCheck: | ||||||||||||||||||||||
| def __init__(self, auto_fix=False): | ||||||||||||||||||||||
| self.auto_fix = auto_fix | ||||||||||||||||||||||
| self.issues = [] | ||||||||||||||||||||||
| self.fixed = [] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def check_env_file(self): | ||||||||||||||||||||||
| """Verify .env exists and has required fields.""" | ||||||||||||||||||||||
| print(f"\n{BLUE}[1/6] Checking .env configuration{RESET}") | ||||||||||||||||||||||
| if not ENV_FILE.exists(): | ||||||||||||||||||||||
| fail(".env file not found") | ||||||||||||||||||||||
| self.issues.append("missing_env") | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| env = {} | ||||||||||||||||||||||
| with open(ENV_FILE) as f: | ||||||||||||||||||||||
| for line in f: | ||||||||||||||||||||||
| line = line.strip() | ||||||||||||||||||||||
| if line and not line.startswith("#") and "=" in line: | ||||||||||||||||||||||
| key, _, val = line.partition("=") | ||||||||||||||||||||||
| env[key.strip()] = val.strip() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Check OAuth token | ||||||||||||||||||||||
| token = env.get("CLAUDE_CODE_OAUTH_TOKEN", "") | ||||||||||||||||||||||
| if not token or token.startswith("sk-ant-oat01-cnqsmZU"): | ||||||||||||||||||||||
| fail("OAuth token missing or known-expired") | ||||||||||||||||||||||
| self.issues.append("expired_token") | ||||||||||||||||||||||
| if self.auto_fix: | ||||||||||||||||||||||
| self._fix_token(env) | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| ok(f"OAuth token present ({token[:20]}...)") | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security: Logging partial OAuth token to stdout (same issue as Line 67 prints 🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Check Ollama URL | ||||||||||||||||||||||
| ollama_url = env.get("OLLAMA_BASE_URL", "") | ||||||||||||||||||||||
| if not ollama_url: | ||||||||||||||||||||||
| fail("OLLAMA_BASE_URL not set") | ||||||||||||||||||||||
| self.issues.append("missing_ollama_url") | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| ok(f"Ollama URL: {ollama_url}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Check required providers | ||||||||||||||||||||||
| for key in ["GRAPHITI_LLM_PROVIDER", "GRAPHITI_EMBEDDER_PROVIDER"]: | ||||||||||||||||||||||
| if key in env: | ||||||||||||||||||||||
| ok(f"{key}={env[key]}") | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| warn(f"{key} not set") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def _fix_token(self, env): | ||||||||||||||||||||||
| """Auto-fix expired OAuth token from ~/.claude/.credentials.json.""" | ||||||||||||||||||||||
| creds_file = Path.home() / ".claude" / ".credentials.json" | ||||||||||||||||||||||
| if not creds_file.exists(): | ||||||||||||||||||||||
| fail("Cannot auto-fix: ~/.claude/.credentials.json not found") | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| with open(creds_file) as f: | ||||||||||||||||||||||
| creds = json.load(f) | ||||||||||||||||||||||
| new_token = creds.get("claudeAiOauth", {}).get("accessToken", "") | ||||||||||||||||||||||
| if not new_token: | ||||||||||||||||||||||
| fail("No access token in credentials file") | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Read and update .env | ||||||||||||||||||||||
| content = ENV_FILE.read_text() | ||||||||||||||||||||||
| old_token = env.get("CLAUDE_CODE_OAUTH_TOKEN", "") | ||||||||||||||||||||||
| if old_token: | ||||||||||||||||||||||
| content = content.replace(old_token, new_token) | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| content = f"CLAUDE_CODE_OAUTH_TOKEN={new_token}\n" + content | ||||||||||||||||||||||
| ENV_FILE.write_text(content) | ||||||||||||||||||||||
Check failureCode scanning / CodeQL Clear-text storage of sensitive information High
This expression stores
sensitive data (password) Error loading related location Loading
Comment on lines
+99
to
+106
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fragile token replacement: If the old token string appears in a comment or another value, Suggested approach (matching preflight_hook.py pattern)- if old_token:
- content = content.replace(old_token, new_token)
- else:
- content = f"CLAUDE_CODE_OAUTH_TOKEN={new_token}\n" + content
+ lines = content.split("\n")
+ updated = False
+ for i, line in enumerate(lines):
+ if line.strip().startswith("CLAUDE_CODE_OAUTH_TOKEN="):
+ lines[i] = f"CLAUDE_CODE_OAUTH_TOKEN={new_token}"
+ updated = True
+ break
+ if not updated:
+ lines.insert(0, f"CLAUDE_CODE_OAUTH_TOKEN={new_token}")
+ content = "\n".join(lines)🤖 Prompt for AI Agents |
||||||||||||||||||||||
| ok(f"Token auto-fixed from ~/.claude/.credentials.json ({new_token[:20]}...)") | ||||||||||||||||||||||
| self.fixed.append("expired_token") | ||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||
| fail(f"Auto-fix failed: {e}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def check_ollama(self): | ||||||||||||||||||||||
| """Verify Ollama is reachable and models are available.""" | ||||||||||||||||||||||
| print(f"\n{BLUE}[2/6] Checking Ollama connectivity{RESET}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Read URL from .env | ||||||||||||||||||||||
| ollama_url = "http://192.168.0.234:11434" | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This hardcoded IP address
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same hardcoded private IP This should default to 🤖 Prompt for AI Agents |
||||||||||||||||||||||
| if ENV_FILE.exists(): | ||||||||||||||||||||||
| with open(ENV_FILE) as f: | ||||||||||||||||||||||
| for line in f: | ||||||||||||||||||||||
| if line.strip().startswith("OLLAMA_BASE_URL="): | ||||||||||||||||||||||
| ollama_url = line.strip().split("=", 1)[1] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| result = subprocess.run( | ||||||||||||||||||||||
| ["curl", "-s", "-m", "5", f"{ollama_url}/api/tags"], | ||||||||||||||||||||||
| capture_output=True, text=True, timeout=10 | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
|
Comment on lines
+125
to
+128
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using |
||||||||||||||||||||||
| if result.returncode != 0: | ||||||||||||||||||||||
| fail(f"Ollama unreachable at {ollama_url}") | ||||||||||||||||||||||
| self.issues.append("ollama_unreachable") | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| data = json.loads(result.stdout) | ||||||||||||||||||||||
| models = [m["name"] for m in data.get("models", [])] | ||||||||||||||||||||||
| ok(f"Ollama responding ({len(models)} models)") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Check required models | ||||||||||||||||||||||
| required = ["qwen2.5-coder:14b", "nomic-embed-text"] | ||||||||||||||||||||||
| for model in required: | ||||||||||||||||||||||
| found = any(model in m for m in models) | ||||||||||||||||||||||
| if found: | ||||||||||||||||||||||
| ok(f"Model available: {model}") | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| fail(f"Model missing: {model}") | ||||||||||||||||||||||
| self.issues.append(f"missing_model_{model}") | ||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||
| fail(f"Ollama check failed: {e}") | ||||||||||||||||||||||
| self.issues.append("ollama_error") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def check_venv(self): | ||||||||||||||||||||||
| """Verify Python venv and dependencies.""" | ||||||||||||||||||||||
| print(f"\n{BLUE}[3/6] Checking Python environment{RESET}") | ||||||||||||||||||||||
| if not VENV_PYTHON.exists(): | ||||||||||||||||||||||
| fail(f"venv not found at {VENV_PYTHON}") | ||||||||||||||||||||||
| self.issues.append("missing_venv") | ||||||||||||||||||||||
| if self.auto_fix: | ||||||||||||||||||||||
| info("Run: cd apps/backend && python3 -m venv .venv && pip install -r requirements.txt") | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ok(f"venv exists at {VENV_PYTHON}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Check key imports | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| result = subprocess.run( | ||||||||||||||||||||||
| [str(VENV_PYTHON), "-c", "from core.client import create_client; print('OK')"], | ||||||||||||||||||||||
| capture_output=True, text=True, timeout=10, cwd=str(BACKEND_DIR) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| if "OK" in result.stdout: | ||||||||||||||||||||||
| ok("Core imports working") | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| fail(f"Import error: {result.stderr[:100]}") | ||||||||||||||||||||||
| self.issues.append("import_error") | ||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||
| fail(f"venv check failed: {e}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def check_stuck_specs(self): | ||||||||||||||||||||||
| """Find and optionally clear stuck specs/locks.""" | ||||||||||||||||||||||
| print(f"\n{BLUE}[4/6] Checking for stuck specs/locks{RESET}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Check common project locations | ||||||||||||||||||||||
| project_dirs = [ | ||||||||||||||||||||||
| Path.home() / "projects", | ||||||||||||||||||||||
| Path("/aidata/projects"), | ||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||||||||||||||||||||||
| ] | ||||||||||||||||||||||
|
Comment on lines
+182
to
+185
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded project paths Same issue as in 🤖 Prompt for AI Agents |
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| stuck_count = 0 | ||||||||||||||||||||||
| for pdir in project_dirs: | ||||||||||||||||||||||
| if not pdir.exists(): | ||||||||||||||||||||||
| continue | ||||||||||||||||||||||
| for spec_dir in pdir.glob("*/.auto-claude/specs/*/.state"): | ||||||||||||||||||||||
| stuck_count += 1 | ||||||||||||||||||||||
| warn(f"State cache: {spec_dir}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for lock_file in pdir.glob("*/.auto-claude/specs/*/.lock"): | ||||||||||||||||||||||
| stuck_count += 1 | ||||||||||||||||||||||
| warn(f"Lock file: {lock_file}") | ||||||||||||||||||||||
| if self.auto_fix: | ||||||||||||||||||||||
| lock_file.unlink() | ||||||||||||||||||||||
| ok(f"Removed stale lock: {lock_file}") | ||||||||||||||||||||||
| self.fixed.append(f"lock_{lock_file.name}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if stuck_count == 0: | ||||||||||||||||||||||
| ok("No stuck specs or locks found") | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| info(f"Found {stuck_count} items (use --fix to clean)") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def check_node(self): | ||||||||||||||||||||||
| """Verify Node.js version for Claude Code.""" | ||||||||||||||||||||||
| print(f"\n{BLUE}[5/6] Checking Node.js{RESET}") | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| result = subprocess.run( | ||||||||||||||||||||||
| ["node", "--version"], capture_output=True, text=True, timeout=5 | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| version = result.stdout.strip() | ||||||||||||||||||||||
| major = int(version.lstrip("v").split(".")[0]) | ||||||||||||||||||||||
| if major >= 24: | ||||||||||||||||||||||
| ok(f"Node.js {version}") | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| warn(f"Node.js {version} - Auto-Claude needs v24+") | ||||||||||||||||||||||
| self.issues.append("old_node") | ||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||
| warn("Node.js not found in PATH") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def check_git_status(self): | ||||||||||||||||||||||
| """Check for uncommitted Auto-Claude changes in projects.""" | ||||||||||||||||||||||
| print(f"\n{BLUE}[6/6] Checking git status{RESET}") | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| result = subprocess.run( | ||||||||||||||||||||||
| ["git", "status", "--porcelain"], capture_output=True, text=True, | ||||||||||||||||||||||
| timeout=5, cwd=str(BACKEND_DIR) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| if result.stdout.strip(): | ||||||||||||||||||||||
| lines = result.stdout.strip().split("\n") | ||||||||||||||||||||||
| warn(f"Auto-Claude repo has {len(lines)} uncommitted changes") | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| ok("Auto-Claude repo clean") | ||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||
| warn("Could not check git status") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def run(self): | ||||||||||||||||||||||
| print(f"\n{'='*60}") | ||||||||||||||||||||||
| print(f" Auto-Claude Preflight Check {'(+ Auto-Fix)' if self.auto_fix else ''}") | ||||||||||||||||||||||
| print(f"{'='*60}") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| self.check_env_file() | ||||||||||||||||||||||
| self.check_ollama() | ||||||||||||||||||||||
| self.check_venv() | ||||||||||||||||||||||
| self.check_stuck_specs() | ||||||||||||||||||||||
| self.check_node() | ||||||||||||||||||||||
| self.check_git_status() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Summary | ||||||||||||||||||||||
| print(f"\n{'='*60}") | ||||||||||||||||||||||
| if not self.issues: | ||||||||||||||||||||||
| print(f" {GREEN}All checks passed! Auto-Claude is ready.{RESET}") | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| print(f" {YELLOW}{len(self.issues)} issue(s) found", end="") | ||||||||||||||||||||||
| if self.fixed: | ||||||||||||||||||||||
| print(f", {len(self.fixed)} auto-fixed", end="") | ||||||||||||||||||||||
| print(f"{RESET}") | ||||||||||||||||||||||
| remaining = [i for i in self.issues if i not in self.fixed] | ||||||||||||||||||||||
| if remaining: | ||||||||||||||||||||||
| print(f" {RED}Remaining: {', '.join(remaining)}{RESET}") | ||||||||||||||||||||||
| if not self.auto_fix: | ||||||||||||||||||||||
| print(f"\n Run with --fix to attempt auto-repair") | ||||||||||||||||||||||
|
Comment on lines
+264
to
+266
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. F-string without placeholders on line 266 (Ruff F541). - print(f"\n Run with --fix to attempt auto-repair")
+ print("\n Run with --fix to attempt auto-repair")📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: Python (Ruff)[failure] 266-266: Ruff (F541) 🤖 Prompt for AI Agents |
||||||||||||||||||||||
| print(f"{'='*60}\n") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return len(self.issues) - len(self.fixed) == 0 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||
| auto_fix = "--fix" in sys.argv | ||||||||||||||||||||||
| checker = PreflightCheck(auto_fix=auto_fix) | ||||||||||||||||||||||
| success = checker.run() | ||||||||||||||||||||||
| sys.exit(0 if success else 1) | ||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Consider placing
desktop.envandauto-claude-desktop.shin the appropriate existing sections.desktop.envis already covered by.env.*on line 16, so this entry is redundant unless the file is literally nameddesktop.env(no dot prefix).auto-claude-desktop.shwould fit better under the "Auto Claude Generated" section (lines 57–66) rather than appended at the end under "Misc / Development."🤖 Prompt for AI Agents