0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
"""Shared utilities for headroom learn plugins.
|
|
|
|
Error classification, tool name normalization, and other helpers
|
|
used across all scanner plugins.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
|
|
from .models import ErrorCategory
|
|
|
|
|
|
def claude_config_dir() -> Path:
|
|
"""Resolve the Claude Code config directory, honoring ``CLAUDE_CONFIG_DIR``.
|
|
|
|
Claude Code relocates its config (including ``projects/`` logs and the
|
|
global ``CLAUDE.md``) when ``CLAUDE_CONFIG_DIR`` is set. ``headroom learn``
|
|
must read and write the same directory, mirroring the override already
|
|
honored in ``subscription`` and ``mcp_registry``. Falls back to
|
|
``~/.claude``.
|
|
"""
|
|
base = os.environ.get("CLAUDE_CONFIG_DIR")
|
|
return Path(base) if base else Path.home() / ".claude"
|
|
|
|
|
|
# =============================================================================
|
|
# Error Classification
|
|
# =============================================================================
|
|
|
|
# Patterns checked in order — first match wins
|
|
_ERROR_PATTERNS: list[tuple[re.Pattern[str], ErrorCategory]] = [
|
|
(
|
|
re.compile(r"No such file or directory|ENOENT|FileNotFoundError|does not exist", re.I),
|
|
ErrorCategory.FILE_NOT_FOUND,
|
|
),
|
|
(
|
|
re.compile(r"ModuleNotFoundError|ImportError|No module named", re.I),
|
|
ErrorCategory.MODULE_NOT_FOUND,
|
|
),
|
|
(re.compile(r"command not found", re.I), ErrorCategory.COMMAND_NOT_FOUND),
|
|
(
|
|
re.compile(r"Permission denied|EACCES|EPERM|auto-denied", re.I),
|
|
ErrorCategory.PERMISSION_DENIED,
|
|
),
|
|
(
|
|
re.compile(r"file is too large|too many lines|exceeds.*limit", re.I),
|
|
ErrorCategory.FILE_TOO_LARGE,
|
|
),
|
|
(re.compile(r"EISDIR|Is a directory", re.I), ErrorCategory.IS_DIRECTORY),
|
|
(re.compile(r"SyntaxError|IndentationError", re.I), ErrorCategory.SYNTAX_ERROR),
|
|
(re.compile(r"Traceback \(most recent|Exception:|Error:", re.I), ErrorCategory.RUNTIME_ERROR),
|
|
(re.compile(r"timed? ?out|TimeoutError|deadline exceeded", re.I), ErrorCategory.TIMEOUT),
|
|
(re.compile(r"No (?:matches|files|results) found|0 matches", re.I), ErrorCategory.NO_MATCHES),
|
|
(
|
|
re.compile(r"user.*reject|user.*denied|declined|didn't want to proceed", re.I),
|
|
ErrorCategory.USER_REJECTED,
|
|
),
|
|
(re.compile(r"[Ss]ibling tool call errored", re.I), ErrorCategory.SIBLING_ERROR),
|
|
(re.compile(r"exit code|non-zero|exited with", re.I), ErrorCategory.EXIT_CODE),
|
|
(
|
|
re.compile(r"ConnectionError|ConnectionRefused|ECONNREFUSED|network", re.I),
|
|
ErrorCategory.CONNECTION_ERROR,
|
|
),
|
|
(
|
|
re.compile(r"BUILD FAILED|compilation error|compile error", re.I),
|
|
ErrorCategory.BUILD_FAILURE,
|
|
),
|
|
]
|
|
|
|
|
|
def classify_error(content: str) -> ErrorCategory:
|
|
"""Classify an error message into a category."""
|
|
for pattern, category in _ERROR_PATTERNS:
|
|
if pattern.search(content[:2000]): # Only check first 2KB
|
|
return category
|
|
return ErrorCategory.UNKNOWN
|
|
|
|
|
|
def is_error_content(content: str) -> bool:
|
|
"""Heuristic: does this tool result look like an error?"""
|
|
if not content or len(content) < 10:
|
|
return False
|
|
# Check for common error indicators in first 1KB
|
|
snippet = content[:1000]
|
|
indicators = [
|
|
"Error:",
|
|
"error:",
|
|
"ENOENT",
|
|
"No such file",
|
|
"command not found",
|
|
"Permission denied",
|
|
"ModuleNotFoundError",
|
|
"Traceback (most recent",
|
|
"FAILED",
|
|
"EISDIR",
|
|
"auto-denied",
|
|
"Sibling tool call errored",
|
|
"timed out",
|
|
"exit code",
|
|
"FileNotFoundError",
|
|
]
|
|
return any(ind in snippet for ind in indicators)
|
|
|
|
|
|
# =============================================================================
|
|
# Tool Name Normalization
|
|
# =============================================================================
|
|
|
|
# Consolidated mapping from all agent-specific tool names to the cross-agent schema.
|
|
# Plugins can use normalize_tool_name() or extend this map for custom tools.
|
|
_TOOL_NAME_MAP: dict[str, str] = {
|
|
# Shell / command execution
|
|
"shell": "Bash",
|
|
"run_shell_command": "Bash",
|
|
"execute_command": "Bash",
|
|
"exec_command": "Bash",
|
|
"terminal": "Bash",
|
|
"run_command": "Bash",
|
|
"run_terminal_command": "Bash",
|
|
# File reading
|
|
"read_file": "Read",
|
|
"read_many_files": "Read",
|
|
"readfile": "Read",
|
|
"view_file": "Read",
|
|
"cat": "Read",
|
|
# File writing
|
|
"write_file": "Write",
|
|
"write_new_file": "Write",
|
|
"create_file": "Write",
|
|
"writefile": "Write",
|
|
# File editing
|
|
"edit_file": "Edit",
|
|
"replace_in_file": "Edit",
|
|
"editfile": "Edit",
|
|
"apply_diff": "Edit",
|
|
# File search / glob
|
|
"search_files": "Glob",
|
|
"find_files": "Glob",
|
|
"glob": "Glob",
|
|
"list_directory": "Glob",
|
|
"list_dir": "Glob",
|
|
# Text search / grep
|
|
"grep": "Grep",
|
|
"search_text": "Grep",
|
|
"search_code": "Grep",
|
|
"codebase_search": "Grep",
|
|
# Web
|
|
"browser": "WebFetch",
|
|
"web_search": "WebSearch",
|
|
}
|
|
|
|
|
|
def normalize_tool_name(name: str) -> str:
|
|
"""Map agent-specific tool names to the cross-agent schema.
|
|
|
|
Looks up the name (case-insensitive) in the shared tool name map.
|
|
Returns the original name if no mapping exists.
|
|
"""
|
|
return _TOOL_NAME_MAP.get(name.lower(), _TOOL_NAME_MAP.get(name, name))
|