Files

165 lines
5.1 KiB
Python

"""Augment Code CLI native user-hook integration."""
from __future__ import annotations
import shutil
import subprocess # nosec B404
from .augment_contract import (
AGENT_KEY,
AugmentHooks,
build_handler,
remove_managed_hooks,
render_augment_document,
runtime_contracts,
)
from .augment_installation import (
AugmentRuntimePaths,
commit_augment_installation,
commit_augment_uninstall,
preflight_augment_installation,
prepare_augment_installation,
prepare_augment_uninstall,
)
from .augment_io import augment_runtime_files_exist, read_augment_document
from .base import AgentPlugin, InstallConfig, PluginStatus
_REGEX_VALIDATION_TIMEOUT_SECONDS = 10
_REGEX_VALIDATOR = """import fs from "node:fs";
const pattern = fs.readFileSync(0, "utf8");
try {
new RegExp(pattern);
} catch (error) {
process.stderr.write(error instanceof Error ? error.message : String(error));
process.exit(1);
}
"""
def _resolve_node_runtime() -> str:
node_path = shutil.which("node")
if node_path is None:
raise FileNotFoundError(
"Node.js runtime is required to validate Auggie matcher expressions"
)
return node_path
def _validate_matcher(node_path: str, label: str, matcher: str) -> None:
try:
result = subprocess.run( # nosec B603
[
node_path,
"--input-type=module",
"--eval",
_REGEX_VALIDATOR,
],
input=matcher,
text=True,
capture_output=True,
check=False,
timeout=_REGEX_VALIDATION_TIMEOUT_SECONDS,
)
except subprocess.TimeoutExpired as error:
raise TimeoutError(
f"{label} matcher validation timed out after "
f"{_REGEX_VALIDATION_TIMEOUT_SECONDS} seconds"
) from error
except OSError as error:
raise OSError(f"Run Node.js matcher validator: {error}") from error
if result.returncode == 0:
return
message = (
result.stderr.strip()
or result.stdout.strip()
or f"Node.js exited with code {result.returncode}"
)
raise ValueError(
f"{label} matcher must be a valid JavaScript regular expression: {message}"
)
def _validate_matchers(hooks: AugmentHooks) -> None:
matchers = [
(event, group_index, group["matcher"])
for event in sorted(hooks)
for group_index, group in enumerate(hooks[event])
if "matcher" in group
]
if not matchers:
return
node_path = _resolve_node_runtime()
for event, group_index, matcher in matchers:
label = f"Auggie settings group hooks.{event}[{group_index}]"
_validate_matcher(node_path, label, matcher)
def _installed_hooks(hooks: AugmentHooks, paths: AugmentRuntimePaths) -> AugmentHooks:
cleaned, _ = remove_managed_hooks(hooks)
return {
**cleaned,
"PreToolUse": [
*cleaned.get("PreToolUse", []),
{
"matcher": ".*",
"hooks": [build_handler(paths.guard_wrapper_path)],
},
],
"PostToolUse": [
*cleaned.get("PostToolUse", []),
{
"matcher": ".*",
"hooks": [build_handler(paths.audit_wrapper_path)],
},
],
}
class AugmentPlugin(AgentPlugin):
"""Install Elydora into Auggie's global user settings."""
manages_guard_runtime = True
def preflight_install(self, config: InstallConfig) -> None:
document = read_augment_document()
_validate_matchers(document.hooks)
preflight_augment_installation(config, document)
def install(self, config: InstallConfig) -> None:
document = read_augment_document()
_validate_matchers(document.hooks)
paths = preflight_augment_installation(config, document)
rendered = render_augment_document(
document, _installed_hooks(document.hooks, paths)
)
changes = prepare_augment_installation(config, paths, rendered)
commit_augment_installation(changes)
print("Auggie: user-level PreToolUse and PostToolUse hooks installed.")
def uninstall(self, agent_id: str = "") -> None:
document = read_augment_document()
if not document.exists:
return
hooks, changed = remove_managed_hooks(document.hooks, agent_id)
if not changed:
return
rendered = render_augment_document(document, hooks)
commit_augment_uninstall(prepare_augment_uninstall(rendered))
def status(self) -> PluginStatus:
document = read_augment_document()
contracts = runtime_contracts(document.hooks)
configured = bool(contracts)
installed = configured and augment_runtime_files_exist(contracts)
details = (
f"Config: {document.config_path}"
if installed
else f"Configured at {document.config_path}; managed contract incomplete"
)
return PluginStatus(
installed=installed,
agent=AGENT_KEY,
details=details if configured else "Not installed",
)