# ClaudeTools - Coding Guidelines

Project-specific standards. Generic language conventions (PEP 8, etc.) are assumed knowledge.

---

## Character Encoding

### NO EMOJIS - EVER

Never use emojis in code, scripts, config files, log messages, or output strings.

**Rationale:** Causes PowerShell parsing errors, encoding issues, terminal rendering problems.

**Use instead:**
```
[OK] [SUCCESS] [INFO] [WARNING] [ERROR] [CRITICAL]
```

**Exception:** User-facing web UI with proper UTF-8 handling.

---

## Naming Conventions

- **Python:** snake_case functions, PascalCase classes, UPPER_SNAKE constants
- **PowerShell:** PascalCase variables ($TaskName), approved verbs (Get-/Set-/New-)
- **Bash:** lowercase_underscore functions, quote all variables
- **DB tables:** lowercase plural (users, user_sessions), FK as {table}_id
- **DB columns:** created_at/updated_at timestamps, is_/has_ boolean prefixes

---

## PowerShell Execution (Windows)

### ALWAYS Use -NoProfile -File Pattern

Never use inline PowerShell commands (`-Command` or `-c`). Always write scripts to `.ps1` files and execute with `-NoProfile -File`.

**Rationale:**
- **Prevents font/codepage changes**: PowerShell profile scripts often set `chcp 65001` or modify `[Console]::OutputEncoding`, which changes the Claude Code CLI font and breaks rendering
- **Avoids Git Bash quoting issues**: Inline commands have unpredictable quote escaping and variable expansion (`$_`, `$foo`) before PowerShell sees them
- **Enforced by hooks**: `.claude/hooks/pre-bash-pwsh-script.sh` blocks inline execution and requires the file-based approach

**Correct:**
```bash
# Write script to file using Write tool
cat > /tmp/script.ps1 << 'EOF'
Get-Process | Select-Object -First 5 Name, CPU
EOF

# Execute with -NoProfile -File
pwsh -NoProfile -File /tmp/script.ps1
```

**Incorrect (BLOCKED BY HOOKS):**
```bash
# These will be rejected
powershell -Command "Get-Process"
pwsh -c "Get-Date"
powershell.exe -Command '$x = 5; Write-Host $x'
```

**Reference:** See `.claude/hooks/pre-bash-pwsh-script.sh` for enforcement details.

---

## Context Lookup — search before reading (wiki first for known entities)

For a **known entity's facts** (a specific client/project/system), check the **wiki** first — it is
the synthesized truth layer. For **code and discovery**, search with GrepAI or Grep before reading
any file; only open a file when you need its full content for editing or line-by-line review. Full
rule: `.claude/standards/context-lookup/grepai-first.md`.

| Goal | Tool |
|------|------|
| Find where a function is defined | `grepai_search` or `Grep` |
| Understand how a feature works | `grepai_search` |
| Find all callers of a function | `grepai_trace_callers` |
| Full file content needed (edit, review) | `Read` |
| Recent changes | `git log`, then `Read` specific file |

Reading a 500-line file to find one function costs ~3000 tokens. A targeted search costs ~100.
Never open a large file to scan for context. Search first, read only if the search is insufficient.

---

## Security

- Never hardcode credentials -- use SOPS vault or environment variables
- JWT tokens for API auth, Argon2 for password hashing
- Log all authentication attempts and sensitive operations
- `.env` files are gitignored, never committed

---

## API Standards

- RESTful with plural nouns: `/api/users`
- Consistent error format: `{"detail": "...", "error_code": "...", "status_code": N}`
- Paginate large result sets
- Document with OpenAPI (automatic with FastAPI)

---

## Output Markers

All scripts and tools use ASCII status markers:
```
[INFO] Starting process
[SUCCESS] Task completed
[WARNING] Configuration missing
[ERROR] Failed to connect
[CRITICAL] Database unavailable
```

---

## Git

- Commit types: feat, fix, refactor, docs, test, config
- Always include `Co-Authored-By` line for Claude commits
- Never commit .env, credentials, venv, __pycache__, *.log

---

## GuruRMM Agent — Platform Parity

All agent features that are not inherently platform-specific must ship on Windows, Linux, and macOS.
A feature that silently no-ops on one platform is a gap, not a cross-platform implementation.

### The rule

> "Add feature X to the agent" means Windows + Linux + macOS. All three, in the same change.
> No exceptions for convenience. If a real implementation is not feasible on a given platform,
> add a working stub and a `// TODO(platform): <os> — <reason>` comment in the same commit.
> A feature that silently no-ops on one platform without a stub and TODO is a bug, not a gap.

### cfg gating — choose the right target

| Condition | Attribute | When to use |
|-----------|-----------|-------------|
| Windows only | `#[cfg(windows)]` | Windows API (Win32, WMI, SCM, OpenSSH registry) |
| Linux + macOS | `#[cfg(unix)]` | POSIX: nix crate, signals, `/proc`, `/sys`, sockets |
| Linux only | `#[cfg(target_os = "linux")]` | `/sys/class/thermal`, systemd, procfs, D-Bus |
| macOS only | `#[cfg(target_os = "macos")]` | CoreFoundation, IOKit, launchd, NSStatusBar |
| Build flag | `#[cfg(feature = "native-service")]` | Service harness (Windows only in Cargo.toml) |

Never use `#[cfg(not(windows))]` as a proxy for "Linux + macOS works the same" without verifying
the macOS codepath. Linux and macOS diverge on `/sys`, D-Bus, and GUI IPC.

### Current parity matrix (as of 2026-05-15)

| Feature | Windows | Linux | macOS |
|---------|---------|-------|-------|
| CPU / memory / disk / network metrics | [OK] | [OK] | [OK] |
| Temperature via sysinfo | [OK] fallback | [WARN] empty if no hwmon | [WARN] empty if no sensors |
| Temperature via LibreHardwareMonitor | [OK] primary | N/A | N/A |
| Temperature via /sys/class/thermal | N/A | [GAP] not implemented | N/A |
| User detection (logged-in user) | [OK] | [OK] nix crate | [OK] nix crate |
| User idle time | [OK] GetLastInputInfo | [GAP] returns None | [GAP] returns None |
| IPC / tray | [OK] named pipe + WinTray | [GAP] stub no-op | [GAP] stub no-op |
| Watchdog (process monitor) | [OK] native-service | [GAP] stub no-op | [GAP] stub no-op |
| Script execution | [OK] cmd / PowerShell | [OK] bash / sh | [OK] bash / sh |
| Hardware inventory | [OK] WMI | [OK] /proc + lshw | [OK] system_profiler |
| Auto-updater | [OK] full | [OK] simpler | [OK] simpler |
| Checks (AV, updates, firewall) | [OK] full | [WARN] partial stub | [WARN] partial stub |
| Network discovery | [OK] | [OK] | [OK] |

### Known gaps — priority order

**1. Linux temperature collection** (`agent/src/metrics/mod.rs`)
- sysinfo `Components` returns empty on most Linux systems (requires kernel hwmon driver exposure).
- Correct approach: read `/sys/class/thermal/thermal_zone*/temp` directly (always available on Linux).
- Pattern:
  ```rust
  #[cfg(target_os = "linux")]
  fn collect_temps_linux() -> (Option<f32>, Option<f32>, Vec<TemperatureReading>) {
      // read /sys/class/thermal/thermal_zone*/temp
      // parse millidegrees, classify by type label in /sys/class/thermal/thermal_zone*/type
  }
  ```

**2. Linux / macOS user idle time** (`agent/src/metrics/mod.rs` — `get_user_idle_time()`)
- Linux: use X11 `XScreenSaverQueryInfo` (display sessions) or parse `/proc/interrupts` delta (headless).
- macOS: use `CGEventSourceSecondsSinceLastEventType` (IOKit, always available).
- Stub is acceptable short-term; mark with `// TODO(platform): linux/macos idle time`.

**3. Watchdog on Linux / macOS** (`agent/src/watchdog/`)
- Windows: Windows Service Control Manager restarts the agent.
- Linux: systemd `Restart=on-failure` in the unit file is the correct equivalent — no in-process watchdog needed.
- macOS: launchd `KeepAlive` key in the plist.
- Document the OS-native mechanism in `build-agents.sh` / installer rather than porting the Rust watchdog.

**4. Checks on Linux / macOS** (`agent/src/checks.rs`)
- Windows-specific checks (Windows Update pending, Windows Defender status, Windows Firewall) have no
  direct equivalents; that is expected.
- Cross-platform checks (disk SMART, certificate expiry, open ports) should run on all platforms.
- Add `// TODO(platform): linux/macos — <check name>` for each unimplemented cross-platform check.

### Cargo.toml dependency discipline

- Platform-specific crates go in `[target.'cfg(...)'.dependencies]`, never in `[dependencies]`.
- Keep `lhm` (LibreHardwareMonitor) and `windows-service` under `cfg(windows)`.
- Keep `nix` under `cfg(unix)`.
- When adding a new crate, verify it compiles on all three targets before merging. Use the build server
  for Windows; CI covers Linux. macOS cross-compile via `--target aarch64-apple-darwin` on Linux
  (requires `osxcross` toolchain — see build-agents.sh TODO-MACOS).

---

**Last Updated:** 2026-05-15
