Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:43:17 +08:00
commit 8967d326a7
30 changed files with 5154 additions and 0 deletions

View File

@@ -0,0 +1,308 @@
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
# AI CLI Preparation - Agent Guide (Root)
**Thin root file**: See scoped AGENTS.md files for specific areas.
## Precedence & Scoped Files
This root AGENTS.md provides global defaults. **Nearest AGENTS.md wins** for specific rules.
**Scoped files:**
- [scripts/AGENTS.md](scripts/AGENTS.md) - Shell installation scripts
## Overview
AI CLI Preparation is an environment audit tool ensuring AI coding agents (like Claude Code) have access to necessary developer tools. It detects 50+ tools, reports versions, and provides installation guidance.
**Architecture:**
- **Phase 1 (Complete)**: Tool detection, version auditing, offline-first caching
- **Phase 2 (Planned)**: Context-aware installation/upgrade management (see [docs/PRD.md](docs/PRD.md))
**Tech Stack:**
- Python 3.10+ (standard library only, no external deps for core)
- Make for task automation
- Shell scripts (Bash) for installation
- JSON for caching (latest_versions.json, tools_snapshot.json)
**Key Files:**
- `cli_audit.py` (2,375 lines): Main audit engine, 50+ tool definitions
- `smart_column.py`: ANSI/emoji-aware table formatting
- `scripts/`: 13+ installation scripts (install/update/uninstall/reconcile)
- `docs/`: Comprehensive technical documentation (12 files, 189KB)
## Setup
**Requirements:**
- Python 3.10+ (Python 3.14.0rc2 tested)
- Standard library only (no pip install needed for core)
- Optional: pyflakes for linting
**First-time setup:**
```bash
# Allow direnv (if using)
direnv allow
# Show available commands
make help
# Update snapshot (requires network)
make update
# Run audit from snapshot (fast, offline-capable)
make audit
```
**Environment variables:**
```bash
# See .env.default for all options
CLI_AUDIT_COLLECT=1 # Collect-only mode (write snapshot)
CLI_AUDIT_RENDER=1 # Render-only mode (read snapshot)
CLI_AUDIT_OFFLINE=1 # Offline mode (manual cache only)
CLI_AUDIT_DEBUG=1 # Debug output
CLI_AUDIT_JSON=1 # JSON output
```
## Build & Tests
**Primary commands:**
```bash
make audit # Render from snapshot (no network, <100ms)
make update # Collect fresh data, write snapshot (~10s)
make audit-offline # Offline audit with hints
make lint # Run pyflakes (if installed)
make upgrade # Interactive upgrade guide
```
**Single tool audit:**
```bash
make audit-ripgrep # Audit specific tool
make audit-offline-python-core # Role-based preset
```
**Installation scripts:**
```bash
make install-python # Install Python toolchain (uv)
make install-node # Install Node.js (nvm)
make install-core # Install core tools (fd, fzf, ripgrep, jq, etc.)
```
**Testing:**
```bash
# Smoke test (verifies table output and JSON format)
./scripts/test_smoke.sh
# Test single tool detection
CLI_AUDIT_DEBUG=1 python3 cli_audit.py --only ripgrep
# Validate snapshot
jq '.__meta__' tools_snapshot.json
```
**No formal test suite yet** - README acknowledges: "currently ships without tests"
- Smoke tests exist (test_smoke.sh)
- Manual validation workflows documented
## Code Style
**Python:**
- PEP 8 style (4-space indent, snake_case)
- Type hints used (`from __future__ import annotations`)
- Frozen dataclasses for immutability (`@dataclass(frozen=True)`)
- Docstrings minimal (focus on inline comments)
**Formatting:**
- EditorConfig enforced: LF, UTF-8, 4 spaces, trim trailing whitespace
- No auto-formatter configured (manual formatting)
- Lint via pyflakes: `make lint`
**Shell scripts:**
- Bash with `set -euo pipefail`
- Shellcheck-compliant (best effort)
- Consistent error handling (see scripts/lib/)
**Conventions:**
- File paths: Absolute paths, no auto-commit
- Functions: Snake_case, descriptive names
- Constants: UPPER_CASE (e.g., MANUAL_LOCK, HINTS_LOCK)
- Lock ordering: MANUAL_LOCK → HINTS_LOCK (enforced for safety)
## Security
**Secrets:**
- No secrets in VCS
- GITHUB_TOKEN optional (for GitHub API rate limit increase)
- Set via environment: `export GITHUB_TOKEN=ghp_...`
**Network:**
- HTTPS-only for upstream queries
- Retry logic with exponential backoff
- Per-origin rate limits (GitHub: 5/min, PyPI: 10/min, crates.io: 5/min)
- Timeout enforcement (default: 3s, configurable)
**Input validation:**
- Tool names validated against TOOLS registry
- Version strings sanitized (extract_version_number)
- Subprocess calls use lists, not shell=True (where possible)
**Caching:**
- Atomic file writes prevent corruption
- Offline-first design (committed latest_versions.json)
- No arbitrary code execution (package manager commands only)
## PR/Commit Checklist
**Before commit:**
- [ ] Run `make lint` (pyflakes clean)
- [ ] Run `make audit` (verify snapshot renders)
- [ ] Test affected tool: `make audit-<tool>`
- [ ] Update docs if behavior changed (README.md, docs/, scripts/README.md)
- [ ] Add/update smoke test if new output format
**Commit messages:**
- Conventional Commits format: `type(scope): description`
- Examples:
- `feat(audit): add snapshot-based collect/render modes`
- `fix(locks): enforce MANUAL_LOCK→HINTS_LOCK ordering`
- `docs(prd): add Phase 2 specifications and ADRs`
- `chore(cache): update latest_versions.json`
**Pull requests:**
- Keep PRs small (~≤300 net LOC changed)
- Link to issue/ticket if exists
- Update CHANGELOG section in PR description
- Ensure CI passes (when added)
## Good vs Bad Examples
**Good: Atomic dataclass with type safety**
```python
@dataclass(frozen=True)
class Tool:
name: str
candidates: tuple[str, ...]
source_kind: str # gh|pypi|crates|npm|gnu|skip
source_args: tuple[str, ...]
```
**Bad: Mutable dict with unclear types**
```python
# Don't do this
tool = {
'name': 'ripgrep',
'candidates': ['rg'], # List instead of tuple
'source': 'github', # Unclear allowed values
}
```
**Good: Lock ordering enforcement**
```python
def update_manual_cache(tool: str, version: str) -> None:
with MANUAL_LOCK: # Acquire first
with HINTS_LOCK: # Then hints
# Safe: consistent ordering prevents deadlock
```
**Bad: Lock ordering violation**
```python
def update_cache(tool: str) -> None:
with HINTS_LOCK: # Wrong order!
with MANUAL_LOCK:
# Deadlock risk
```
**Good: Parallel execution with isolation**
```python
with ThreadPoolExecutor(max_workers=16) as executor:
futures = [executor.submit(audit_tool, tool) for tool in TOOLS]
for future in as_completed(futures):
result = future.result() # Failures isolated
```
**Bad: Sequential execution**
```python
results = []
for tool in TOOLS: # Slow: 50 tools * 3s = 150s
results.append(audit_tool(tool))
```
## When Stuck
**Tool detection failing:**
1. Check PATH: `echo $PATH | tr ':' '\n'`
2. Debug single tool: `CLI_AUDIT_DEBUG=1 python3 cli_audit.py --only <tool>`
3. Check version flag: `<tool> --version` or `<tool> -v`
4. See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#version-detection-failures)
**Network issues:**
1. Increase timeout: `CLI_AUDIT_TIMEOUT_SECONDS=10 make update`
2. More retries: `CLI_AUDIT_HTTP_RETRIES=5 make update`
3. Use offline mode: `make audit-offline`
4. See [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#network-timeout-issues)
**Cache corruption:**
1. Remove caches: `rm latest_versions.json tools_snapshot.json`
2. Regenerate: `make update`
**Installation script fails:**
1. Check permissions: `make scripts-perms`
2. Debug script: `bash -x ./scripts/install_<tool>.sh`
3. See [scripts/README.md](scripts/README.md) for per-script troubleshooting
**Documentation:**
- Start with [docs/QUICK_REFERENCE.md](docs/QUICK_REFERENCE.md) for one-liners
- See [docs/INDEX.md](docs/INDEX.md) for navigation by role/task
- Architecture details: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
- API reference: [docs/API_REFERENCE.md](docs/API_REFERENCE.md)
## House Rules
**Defaults** (override in scoped AGENTS.md if needed):
**Commits:**
- Atomic commits (single logical change)
- Conventional Commits: `type(scope): description`
- Keep PRs small (~≤300 net LOC changed)
- Ticket IDs in commits/PRs if exists
**Type-safety:**
- Use type hints (`from __future__ import annotations`)
- Frozen dataclasses for immutability
- No `Any` unless truly dynamic
**Design principles:**
- SOLID, KISS, DRY, YAGNI
- Composition > Inheritance
- Law of Demeter (minimal coupling)
**Dependencies:**
- Standard library preferred (no external Python deps for core)
- Latest stable versions when external deps needed
- Document why in Decision Log (ADRs for Phase 2)
**Security:**
- No secrets in VCS
- HTTPS-only for network calls
- No arbitrary code execution
- Input validation on external data
**Documentation currency:**
- Update docs in same PR as behavior changes
- No drift between code and docs
- Document non-obvious decisions in ADRs (see docs/adr/)
**Testing:**
- Aim for ≥80% coverage on changed code (when test suite added)
- Bugfixes use TDD: failing test first, then fix
- New code paths need tests (future requirement)
**Current status:**
- Phase 1: Production-ready detection/audit
- Phase 2: Planned installation/upgrade (see [docs/PRD.md](docs/PRD.md))
- No unit tests yet (acknowledged in README)
---
**Quick Start:** New to the project? Start with [README.md](README.md) → [docs/QUICK_REFERENCE.md](docs/QUICK_REFERENCE.md) → [docs/INDEX.md](docs/INDEX.md)
**Contributing:** See [docs/DEVELOPER_GUIDE.md](docs/DEVELOPER_GUIDE.md) for detailed contribution guide

View File

@@ -0,0 +1,389 @@
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
# Installation Scripts - Agent Guide
**Scope:** Shell scripts for tool installation, update, uninstall, reconcile
## Overview
13+ Bash scripts for installing developer tools with multiple actions:
- **install**: Fresh installation (default action)
- **update**: Upgrade to latest version
- **uninstall**: Remove installation
- **reconcile**: Switch to preferred installation method (e.g., system → user)
**Key scripts:**
- `install_core.sh`: Core tools (fd, fzf, ripgrep, jq, yq, bat, delta, just)
- `install_python.sh`: Python toolchain via uv
- `install_node.sh`: Node.js via nvm
- `install_rust.sh`: Rust via rustup
- `install_go.sh`, `install_aws.sh`, `install_kubectl.sh`, etc.
- `guide.sh`: Interactive upgrade guide
- `test_smoke.sh`: Smoke test for audit output
**Shared utilities:** `lib/` directory (colors, logging, common functions)
## Setup
**Requirements:**
- Bash 4.0+
- `curl` or `wget` for downloads
- Internet access for fresh installs
- Appropriate permissions (user for `~/.local/bin`, sudo for system)
**Environment variables:**
```bash
INSTALL_PREFIX=${INSTALL_PREFIX:-~/.local} # Default: user-level
FORCE_INSTALL=1 # Skip confirmation prompts
DEBUG=1 # Verbose output
```
**Permissions:**
```bash
make scripts-perms # Ensure all scripts are executable
```
## Build & Tests
**Run individual script:**
```bash
# Install action (default)
./scripts/install_python.sh
# Update action
./scripts/install_python.sh update
# Uninstall action
./scripts/install_python.sh uninstall
# Reconcile action (switch installation method)
./scripts/install_node.sh reconcile
```
**Via Make:**
```bash
make install-python # Install Python toolchain
make update-python # Update Python toolchain
make uninstall-python # Uninstall Python toolchain
make reconcile-node # Switch Node.js to nvm-managed
```
**Smoke test:**
```bash
./scripts/test_smoke.sh # Verify audit output format
```
**Debug mode:**
```bash
DEBUG=1 ./scripts/install_python.sh
bash -x ./scripts/install_python.sh # Trace execution
```
## Code Style
**Shell standards:**
- Bash 4.0+ features allowed
- Shebang: `#!/usr/bin/env bash` or `#!/bin/bash`
- Set strict mode: `set -euo pipefail`
- `-e`: Exit on error
- `-u`: Error on undefined variables
- `-o pipefail`: Fail on pipe errors
**Formatting:**
- 4-space indentation (matches EditorConfig)
- Function names: lowercase_with_underscores
- Constants: UPPER_CASE
- Local variables: lowercase
**Structure:**
```bash
#!/usr/bin/env bash
set -euo pipefail
# Source shared utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/colors.sh" || true
source "${SCRIPT_DIR}/lib/common.sh" || true
# Main function per action
install_tool() {
echo_info "Installing <tool>..."
# Implementation
}
update_tool() {
echo_info "Updating <tool>..."
# Implementation
}
uninstall_tool() {
echo_info "Uninstalling <tool>..."
# Implementation
}
reconcile_tool() {
echo_info "Reconciling <tool>..."
# Implementation
}
# Action dispatcher
ACTION="${1:-install}"
case "$ACTION" in
install) install_tool ;;
update) update_tool ;;
uninstall) uninstall_tool ;;
reconcile) reconcile_tool ;;
*) echo "Usage: $0 {install|update|uninstall|reconcile}"; exit 1 ;;
esac
```
**Error handling:**
```bash
# Good: Check command exists before using
if ! command -v curl >/dev/null 2>&1; then
echo_error "curl not found. Install it first."
exit 1
fi
# Good: Check return codes
if ! download_file "$URL" "$DEST"; then
echo_error "Download failed"
exit 1
fi
# Good: Cleanup on error
trap 'rm -rf "$TMPDIR"' EXIT ERR
```
**Confirmation prompts:**
```bash
# Good: Skip prompt if FORCE_INSTALL=1
if [[ "${FORCE_INSTALL:-0}" != "1" ]]; then
read -p "Install <tool>? [y/N] " -n 1 -r
echo
[[ ! $REPLY =~ ^[Yy]$ ]] && exit 0
fi
```
## Security
**Download verification:**
```bash
# Always use HTTPS
URL="https://github.com/owner/repo/releases/download/..."
# Verify checksums when available
EXPECTED_SHA256="abc123..."
ACTUAL_SHA256=$(sha256sum "$FILE" | awk '{print $1}')
if [[ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]]; then
echo_error "Checksum mismatch!"
exit 1
fi
```
**Path safety:**
```bash
# Good: Quote variables, use absolute paths
INSTALL_DIR="${HOME}/.local/bin"
mkdir -p "$INSTALL_DIR"
mv "$TMPFILE" "$INSTALL_DIR/tool"
# Bad: Unquoted, relative paths
mkdir -p $INSTALL_DIR
mv tool bin/
```
**Sudo usage:**
```bash
# Good: Prompt for sudo only when needed
if [[ "$INSTALL_PREFIX" == "/usr/local" ]]; then
if ! sudo -v; then
echo_error "Sudo required for system installation"
exit 1
fi
sudo mv "$FILE" "$INSTALL_PREFIX/bin/"
else
# User-level, no sudo
mv "$FILE" "$INSTALL_PREFIX/bin/"
fi
```
**No secrets in scripts:**
- No API keys, tokens, passwords in scripts
- Use environment variables: `${GITHUB_TOKEN:-}`
- Document required env vars in script comments
## PR/Commit Checklist
**Before commit:**
- [ ] Run `shellcheck <script>` (if available)
- [ ] Test install action: `./scripts/install_<tool>.sh`
- [ ] Test update action: `./scripts/install_<tool>.sh update`
- [ ] Test uninstall action: `./scripts/install_<tool>.sh uninstall`
- [ ] Update `scripts/README.md` if new script or behavior change
- [ ] Verify script permissions: `make scripts-perms`
**Script checklist:**
- [ ] Shebang: `#!/usr/bin/env bash`
- [ ] Strict mode: `set -euo pipefail`
- [ ] Source shared lib: `source "${SCRIPT_DIR}/lib/colors.sh"`
- [ ] Action dispatcher (install/update/uninstall/reconcile)
- [ ] Error handling (check return codes, trap on exit)
- [ ] Confirmation prompts (respect FORCE_INSTALL)
- [ ] PATH updates (add to ~/.bashrc or ~/.zshrc if needed)
**Commit messages:**
- `feat(scripts): add install_terraform.sh`
- `fix(install-python): handle uv bootstrap failure`
- `docs(scripts): update README with reconcile action`
## Good vs Bad Examples
**Good: Robust download with fallback**
```bash
download_file() {
local url="$1"
local dest="$2"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" -o "$dest"
elif command -v wget >/dev/null 2>&1; then
wget -q "$url" -O "$dest"
else
echo_error "Neither curl nor wget found"
return 1
fi
}
```
**Bad: Assumes curl exists**
```bash
download_file() {
curl -fsSL "$1" -o "$2" # Fails if curl not installed
}
```
**Good: Version comparison**
```bash
version_gt() {
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
}
CURRENT_VERSION="1.2.3"
LATEST_VERSION="1.3.0"
if version_gt "$LATEST_VERSION" "$CURRENT_VERSION"; then
echo "Upgrade available"
fi
```
**Bad: String comparison for versions**
```bash
if [[ "$LATEST_VERSION" > "$CURRENT_VERSION" ]]; then
# Wrong: "1.10.0" < "1.9.0" (string comparison)
echo "Upgrade available"
fi
```
**Good: Cleanup on exit**
```bash
TMPDIR=$(mktemp -d)
trap 'rm -rf "$TMPDIR"' EXIT ERR
# Download to temp
download_file "$URL" "$TMPDIR/file"
# ... process ...
# Cleanup happens automatically via trap
```
**Bad: Manual cleanup (error-prone)**
```bash
TMPDIR=$(mktemp -d)
download_file "$URL" "$TMPDIR/file"
# ... process ...
rm -rf "$TMPDIR" # Skipped if earlier command fails
```
**Good: Action-specific logic**
```bash
install_rust() {
if command -v rustup >/dev/null 2>&1; then
echo_warn "rustup already installed"
return 0
fi
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
}
update_rust() {
if ! command -v rustup >/dev/null 2>&1; then
echo_error "rustup not installed. Run install first."
return 1
fi
rustup update
}
```
## When Stuck
**Script fails silently:**
1. Add debug: `bash -x ./scripts/install_<tool>.sh`
2. Check logs: `./scripts/install_<tool>.sh 2>&1 | tee install.log`
3. Verify permissions: `ls -la scripts/`
**Download fails:**
1. Check network: `curl -I https://github.com`
2. Check URL: `echo "$URL"` (verify it's correct)
3. Try manual download: `curl -fsSL "$URL"`
**Installation fails:**
1. Check prerequisites (e.g., Python for uv, curl for rustup)
2. Check disk space: `df -h`
3. Check permissions: `ls -ld "$INSTALL_PREFIX"`
**PATH not updated:**
1. Source shell config: `source ~/.bashrc` or `source ~/.zshrc`
2. Check PATH: `echo $PATH | tr ':' '\n' | grep local`
3. Verify binary location: `ls -la ~/.local/bin/<tool>`
**Reconcile fails:**
1. Check current installation: `which <tool>`
2. Check installation method: `cli_audit.py --only <tool>`
3. Manually remove old version first: `apt remove <tool>` or `cargo uninstall <tool>`
**Documentation:**
- Script-specific docs: [README.md](README.md) (this directory)
- Troubleshooting: [../docs/TROUBLESHOOTING.md](../docs/TROUBLESHOOTING.md)
- Architecture: [../docs/DEPLOYMENT.md](../docs/DEPLOYMENT.md#installation-scripts)
## House Rules
**Installation preferences** (Phase 2 planning):
- User-level preferred: `~/.local/bin` (workstations)
- System-level for servers: `/usr/local/bin`
- Vendor tools first: rustup, nvm, uv over system packages
- See [../docs/adr/ADR-002-package-manager-hierarchy.md](../docs/adr/ADR-002-package-manager-hierarchy.md)
**Reconciliation strategy:**
- Parallel approach: Keep both installations, prefer user via PATH
- No automatic removal (user chooses)
- See [../docs/adr/ADR-003-parallel-installation-approach.md](../docs/adr/ADR-003-parallel-installation-approach.md)
**Version policy:**
- Always latest by default
- Warn on major version upgrades
- See [../docs/adr/ADR-004-always-latest-version-policy.md](../docs/adr/ADR-004-always-latest-version-policy.md)
**Script structure:**
- Multi-action support: install, update, uninstall, reconcile
- Shared utilities in `lib/`
- Consistent error handling and logging
- Make integration via `make install-<tool>`
---
**Quick Start:** Run `make install-core` to install essential tools, then `make audit` to verify.
**Troubleshooting:** See [README.md](README.md) for per-script troubleshooting and [../docs/TROUBLESHOOTING.md](../docs/TROUBLESHOOTING.md) for general issues.

View File

@@ -0,0 +1,282 @@
# Agent Guidelines
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
**Precedence**: Nearest AGENTS.md wins. This is the root file with global defaults.
**Project**: LDAP Selfservice Password Changer — hybrid Go + TypeScript web application with WCAG 2.2 AAA accessibility compliance.
## Quick Navigation
- [internal/AGENTS.md](internal/AGENTS.md) - Go backend services
- [internal/web/AGENTS.md](internal/web/AGENTS.md) - TypeScript frontend & Tailwind CSS
## Global Defaults
### Project Overview
Self-service password change/reset web app for LDAP/ActiveDirectory with email-based password reset, rate limiting, and strict accessibility standards. Single binary deployment with embedded assets.
**Stack**: Go 1.25 + Fiber, TypeScript (ultra-strict), Tailwind CSS 4, Docker multi-stage builds, pnpm 10.18
**Key characteristics**:
- Docker-first: All dev/CI must work via Docker
- Accessibility: WCAG 2.2 AAA compliance (7:1 contrast, keyboard nav, screen readers)
- Type-safe: Go with testcontainers, TypeScript with all strict flags
- Security-focused: LDAPS, rate limiting, token-based reset, no password storage
### Setup
**Prerequisites**: Docker + Docker Compose (required), Go 1.25+, Node.js 24+, pnpm 10.18+ (for native dev)
```bash
# Clone and setup environment
git clone <repo>
cd ldap-selfservice-password-changer
cp .env.local.example .env.local # Edit with your LDAP config
# Docker (recommended)
docker compose --profile dev up
# Native development
pnpm install
go mod download
pnpm dev # Runs concurrent TS watch, CSS watch, and Go with hot-reload
```
See [docs/development-guide.md](docs/development-guide.md) for comprehensive setup.
### Build & Test Commands
**Package manager**: pnpm (specified in package.json: `pnpm@10.18.1`)
```bash
# Build everything
pnpm build # Build frontend assets + Go binary
# Frontend only
pnpm build:assets # TypeScript + Tailwind CSS
pnpm js:build # TypeScript compile + minify
pnpm css:build # Tailwind CSS + PostCSS
# Development (watch mode)
pnpm dev # Concurrent: TS watch, CSS watch, Go hot-reload
pnpm js:dev # TypeScript watch
pnpm css:dev # CSS watch
pnpm go:dev # Go with nodemon hot-reload
# Tests
go test -v ./... # All Go tests with verbose output
go test ./internal/... # Specific package tests
# Formatting
pnpm prettier --write . # Format TS, Go templates, config files
pnpm prettier --check . # Check formatting (CI)
# Type checking
pnpm js:build # TypeScript strict compilation (no emit in dev)
go build -v ./... # Go compilation + type checking
```
**CI commands** (from `.github/workflows/check.yml`):
- Type check: `pnpm js:build` and `go build -v ./...`
- Format check: `pnpm prettier --check .`
- Tests: `go test -v ./...`
### Code Style
**TypeScript**:
- Ultra-strict tsconfig: `strict: true`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, `noPropertyAccessFromIndexSignature`
- Prettier formatting: 120 char width, semicolons, double quotes, 2-space tabs
- No `any` types - use proper type definitions
- Follow existing patterns in `internal/web/static/`
**Go**:
- Standard Go formatting (`go fmt`)
- Prettier with `prettier-plugin-go-template` for HTML templates
- Follow Go project layout: `internal/` for private packages, `main.go` at root
- Use testcontainers for integration tests (see `*_test.go` files)
- Error wrapping with context
**General**:
- Composition over inheritance
- SOLID, KISS, DRY, YAGNI principles
- Law of Demeter: minimize coupling
- No secrets in VCS (use .env.local, excluded from git)
### Security
- **No secrets in git**: Use `.env.local` (gitignored), never commit LDAP credentials
- **LDAPS required**: Production must use encrypted LDAP connections
- **Rate limiting**: 3 requests/hour per IP (configurable via `RATE_LIMIT_*` env vars)
- **Token security**: Cryptographic random tokens with configurable expiry
- **Input validation**: Strict validation on all user inputs (see `internal/validators/`)
- **Dependency scanning**: Renovate enabled, review changelogs for major updates
- **No PII logging**: Redact sensitive data in logs
- **Run as non-root**: Dockerfile uses UID 65534 (nobody)
### PR/Commit Checklist
**Before commit**:
- [ ] Run `pnpm prettier --write .` (format all)
- [ ] Run `pnpm js:build` (TypeScript strict check)
- [ ] Run `go test ./...` (all tests pass)
- [ ] Run `go build` (compilation check)
- [ ] No secrets in changed files
- [ ] Update docs if behavior changed
- [ ] WCAG 2.2 AAA compliance maintained (if UI changed)
**Commit format**: Conventional Commits
```
type(scope): description
Examples:
feat(auth): add password reset via email
fix(validators): correct password policy regex
docs(api): update JSON-RPC examples
chore(deps): update pnpm to v10.18.1
```
**No Claude attribution** in commit messages.
**PR requirements**:
- [ ] All CI checks pass (types, formatting, tests)
- [ ] Keep PRs small (~≤300 net LOC if possible)
- [ ] Include ticket ID if applicable: `fix(rate-limit): ISSUE-123: fix memory leak`
- [ ] Update relevant docs in same PR (README, AGENTS.md, docs/)
### Good vs Bad Examples
**✅ Good - TypeScript strict types**:
```typescript
interface PasswordPolicy {
minLength: number;
requireNumbers: boolean;
}
function validatePassword(password: string, policy: PasswordPolicy): boolean {
const hasNumber = /\d/.test(password);
return password.length >= policy.minLength && (!policy.requireNumbers || hasNumber);
}
```
**❌ Bad - Using any or unsafe access**:
```typescript
function validatePassword(password: any, policy: any) {
// ❌ any types
return password.length >= policy.minLength; // ❌ unsafe access
}
```
**✅ Good - Go error handling**:
```go
func connectLDAP(config LDAPConfig) (*ldap.Conn, error) {
conn, err := ldap.DialURL(config.URL)
if err != nil {
return nil, fmt.Errorf("failed to connect to LDAP at %s: %w", config.URL, err)
}
return conn, nil
}
```
**❌ Bad - Ignoring errors**:
```go
func connectLDAP(config LDAPConfig) *ldap.Conn {
conn, _ := ldap.DialURL(config.URL) // ❌ ignoring error
return conn // ❌ may return nil
}
```
**✅ Good - Accessible UI**:
```html
<button type="submit" aria-label="Submit password change" class="bg-blue-600 hover:bg-blue-700 focus:ring-4">
Change Password
</button>
```
**❌ Bad - Inaccessible UI**:
```html
<div onclick="submit()">Submit</div>
❌ not keyboard accessible, wrong semantics
```
### When Stuck
1. **Check existing docs**: [docs/](docs/) has comprehensive guides
2. **Review similar code**: Look for patterns in `internal/` packages
3. **Run tests**: `go test -v ./...` often reveals issues
4. **Check CI logs**: GitHub Actions shows exact failure points
5. **Verify environment**: Ensure `.env.local` is properly configured
6. **Docker issues**: `docker compose down -v && docker compose --profile dev up --build`
7. **Type errors**: Review `tsconfig.json` strict flags, use proper types
8. **Accessibility**: See [docs/accessibility.md](docs/accessibility.md) for WCAG 2.2 AAA guidelines
### House Rules
**Docker-First Philosophy**:
- All dev and CI must work via Docker Compose
- Native setup is optional convenience, not requirement
- Use profiles in compose.yml: `--profile dev` or `--profile test`
**Documentation Currency**:
- Update docs in same PR as code changes
- No drift between code and documentation
- Keep README, AGENTS.md, and docs/ synchronized
**Testing Standards**:
- Aim for ≥80% coverage on changed code
- Use testcontainers for integration tests (see existing `*_test.go`)
- For bugfixes: write failing test first (TDD)
- Tests must pass before PR approval
**Scope Discipline**:
- Build only what's requested
- No speculative features
- MVP first, iterate based on feedback
- YAGNI: You Aren't Gonna Need It
**Accessibility Non-Negotiable**:
- WCAG 2.2 AAA compliance required
- 7:1 contrast ratios for text
- Full keyboard navigation support
- Screen reader tested (VoiceOver/NVDA)
- See [docs/accessibility.md](docs/accessibility.md)
**Commit Practices**:
- Atomic commits: one logical change per commit
- Conventional Commits format enforced
- Never commit secrets or `.env.local`
- Keep PRs focused and reviewable
**Type Safety**:
- TypeScript: No `any`, all strict flags enabled
- Go: Leverage type system, avoid `interface{}`
- Validate inputs at boundaries
**Dependency Management**:
- Renovate auto-updates enabled
- Major version updates require changelog review
- Use Context7 MCP or official docs for migrations
- Keep pnpm-lock.yaml and go.sum committed

View File

@@ -0,0 +1,371 @@
# Go Backend Services
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
**Scope**: Go backend packages in `internal/` directory
**See also**: [../AGENTS.md](../AGENTS.md) for global standards, [web/AGENTS.md](web/AGENTS.md) for frontend
## Overview
Backend services for LDAP selfservice password change/reset functionality. Organized as internal Go packages:
- **email/**: SMTP email service for password reset tokens
- **options/**: Configuration management from environment variables
- **ratelimit/**: IP-based rate limiting (3 req/hour default)
- **resettoken/**: Cryptographic token generation and validation
- **rpc/**: JSON-RPC 2.0 API handlers (password change/reset)
- **validators/**: Password policy validation logic
- **web/**: HTTP server setup, static assets, routing (see [web/AGENTS.md](web/AGENTS.md))
## Setup/Environment
**Required environment variables** (configure in `.env.local`):
```bash
# LDAP connection
LDAP_URL=ldaps://ldap.example.com:636
LDAP_USER_BASE_DN=ou=users,dc=example,dc=com
LDAP_BIND_DN=cn=admin,dc=example,dc=com
LDAP_BIND_PASSWORD=secret
# Email for password reset
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@example.com
SMTP_PASSWORD=secret
SMTP_FROM=noreply@example.com
APP_BASE_URL=https://passwd.example.com
# Rate limiting (optional)
RATE_LIMIT_REQUESTS=3
RATE_LIMIT_WINDOW=1h
# Token expiry (optional)
TOKEN_EXPIRY_DURATION=1h
```
**Go toolchain**: Requires Go 1.25+ (specified in `go.mod`)
**Key dependencies**:
- `github.com/gofiber/fiber/v2` - HTTP server
- `github.com/netresearch/simple-ldap-go` - LDAP client
- `github.com/testcontainers/testcontainers-go` - Integration testing
- `github.com/joho/godotenv` - Environment loading
## Build & Tests
```bash
# Development
go run . # Start server with hot-reload (via pnpm go:dev)
go build -v ./... # Compile all packages
go test -v ./... # Run all tests with verbose output
# Specific package testing
go test ./internal/validators/... # Test password validators
go test ./internal/ratelimit/... # Test rate limiter
go test ./internal/resettoken/... # Test token generation
go test -run TestSpecificFunction # Run specific test
# Integration tests (uses testcontainers)
go test -v ./internal/email/... # Requires Docker for MailHog container
# Coverage
go test -cover ./... # Coverage summary
go test -coverprofile=coverage.out ./... && go tool cover -html=coverage.out
# Build optimized binary
CGO_ENABLED=0 go build -ldflags="-w -s" -o ldap-passwd
```
**CI validation** (from `.github/workflows/check.yml`):
```bash
go mod download
go build -v ./...
go test -v ./...
```
## Code Style
**Go Standards**:
- Use `go fmt` (automatic via Prettier with go-template plugin)
- Follow [Effective Go](https://go.dev/doc/effective_go)
- Package-level documentation comments required
- Exported functions must have doc comments
**Project Conventions**:
- Internal packages only: No public API outside this project
- Error wrapping with context: `fmt.Errorf("context: %w", err)`
- Use structured logging (consider adding in future)
- Prefer explicit over implicit
- Use interfaces for testability (see `email/service.go`)
**Naming**:
- `internal/package/file.go` - implementation
- `internal/package/file_test.go` - tests
- Descriptive variable names (not `x`, `y`, `tmp`)
- No stuttering: `email.Service`, not `email.EmailService`
**Error Handling**:
```go
// ✅ Good: wrap with context
if err != nil {
return fmt.Errorf("failed to connect LDAP at %s: %w", config.URL, err)
}
// ❌ Bad: lose context
if err != nil {
return err
}
// ❌ Worse: ignore
conn, _ := ldap.Dial(url)
```
**Testing**:
- Table-driven tests preferred
- Use testcontainers for external dependencies (LDAP, SMTP)
- Test files colocated with code: `validators/validate_test.go`
- Descriptive test names: `TestPasswordValidation_RequiresMinimumLength`
## Security
**LDAP Security**:
- Always use LDAPS in production (`ldaps://` URLs)
- Bind credentials in environment, never hardcoded
- Validate user input before LDAP queries (prevent injection)
- Use `simple-ldap-go` helpers to avoid raw LDAP filter construction
**Password Security**:
- Never log passwords (plain or hashed)
- No password storage - passwords go directly to LDAP
- Passwords only in memory during request lifetime
- HTTPS required for transport security
**Token Security**:
- Cryptographic random tokens (see `resettoken/token.go`)
- Configurable expiry (default 1h)
- Single-use tokens (invalidated after use)
- No token storage in logs or metrics
**Rate Limiting**:
- IP-based limits: 3 requests/hour default
- Configurable via `RATE_LIMIT_*` env vars
- In-memory store (consider Redis for multi-instance)
- Apply to both change and reset endpoints
**Input Validation**:
- Strict validation on all user inputs (see `validators/`)
- Reject malformed requests early
- Validate email format, username format, password policies
- No HTML/script injection vectors
## PR/Commit Checklist
**Before committing Go code**:
- [ ] Run `go fmt ./...` (or `pnpm prettier --write .`)
- [ ] Run `go vet ./...` (static analysis)
- [ ] Run `go test ./...` (all tests pass)
- [ ] Run `go build` (compilation check)
- [ ] Update package doc comments if API changed
- [ ] Add/update tests for new functionality
- [ ] Check for sensitive data in logs
- [ ] Verify error messages provide useful context
**Testing requirements**:
- New features must have tests
- Bug fixes must have regression tests
- Aim for ≥80% coverage on changed packages
- Integration tests for external dependencies
**Documentation**:
- Update package doc comments (godoc)
- Update [docs/api-reference.md](../docs/api-reference.md) for RPC changes
- Update [docs/development-guide.md](../docs/development-guide.md) for new setup steps
- Update environment variable examples in `.env` and docs
## Good vs Bad Examples
**✅ Good: Type-safe configuration**
```go
type Config struct {
LDAPURL string `env:"LDAP_URL" validate:"required,url"`
BindDN string `env:"LDAP_BIND_DN" validate:"required"`
BindPassword string `env:"LDAP_BIND_PASSWORD" validate:"required"`
}
func LoadConfig() (*Config, error) {
var cfg Config
if err := env.Parse(&cfg); err != nil {
return nil, fmt.Errorf("parse config: %w", err)
}
return &cfg, nil
}
```
**❌ Bad: Unsafe configuration**
```go
func LoadConfig() *Config {
return &Config{
LDAPURL: os.Getenv("LDAP_URL"), // ❌ no validation, may be empty
}
}
```
**✅ Good: Table-driven tests**
```go
func TestPasswordValidation(t *testing.T) {
tests := []struct {
name string
password string
policy PasswordPolicy
wantErr bool
}{
{"valid password", "Test123!", PasswordPolicy{MinLength: 8}, false},
{"too short", "Ab1!", PasswordPolicy{MinLength: 8}, true},
{"no numbers", "TestTest", PasswordPolicy{RequireNumbers: true}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidatePassword(tt.password, tt.policy)
if (err != nil) != tt.wantErr {
t.Errorf("got error %v, wantErr %v", err, tt.wantErr)
}
})
}
}
```
**❌ Bad: Non-descriptive tests**
```go
func TestPassword(t *testing.T) {
err := ValidatePassword("test") // ❌ what policy? what's expected?
if err == nil {
t.Fail()
}
}
```
**✅ Good: Interface for testability**
```go
type EmailService interface {
SendResetToken(ctx context.Context, to, token string) error
}
type SMTPService struct {
host string
port int
}
func (s *SMTPService) SendResetToken(ctx context.Context, to, token string) error {
// real implementation
}
// In tests, use mock implementation
type MockEmailService struct {
SendFunc func(ctx context.Context, to, token string) error
}
```
**❌ Bad: Hard-to-test concrete dependency**
```go
func ResetPassword(username string) error {
service := NewSMTPService() // ❌ hardcoded, can't mock
return service.SendEmail(...)
}
```
## When Stuck
**Go-specific issues**:
1. **Module issues**: `go mod tidy` to clean dependencies
2. **Import errors**: Check `go.mod` requires correct versions
3. **Test failures**: `go test -v ./... -run FailingTest` for verbose output
4. **LDAP connection**: Verify `LDAP_URL` format and network access
5. **Email testing**: Ensure Docker running for testcontainers (MailHog)
6. **Rate limit testing**: Tests may fail if system time incorrect
**Debugging**:
```bash
# Verbose test output
go test -v ./internal/package/...
# Run specific test
go test -run TestName ./internal/package/
# Race detector (for concurrency issues)
go test -race ./...
# Build with debug info
go build -gcflags="all=-N -l"
```
**Common pitfalls**:
- **Nil pointer dereference**: Check error returns before using values
- **Context cancellation**: Always respect `context.Context` in long operations
- **Resource leaks**: Defer `Close()` calls immediately after acquiring resources
- **Goroutine leaks**: Ensure all goroutines can exit
- **Time zones**: Use `time.UTC` for consistency
## Package-Specific Notes
### email/
- Uses testcontainers for integration tests
- MailHog container spins up automatically in tests
- Mock `EmailService` interface for unit tests in other packages
### options/
- Configuration loaded from environment via `godotenv`
- Validation happens at startup (fail-fast)
- See `.env.local.example` for required variables
### ratelimit/
- In-memory store (map with mutex)
- Consider Redis for multi-instance deployments
- Tests use fixed time.Now for deterministic results
### resettoken/
- Crypto/rand for token generation (never math/rand)
- Base64 URL encoding (safe for URLs)
- Store tokens server-side with expiry
### rpc/
- JSON-RPC 2.0 specification compliance
- Error codes defined in [docs/api-reference.md](../docs/api-reference.md)
- Request validation before processing
### validators/
- Pure functions (no side effects)
- Configurable policies from environment
- Clear error messages for user feedback

View File

@@ -0,0 +1,448 @@
# Frontend - TypeScript & Tailwind CSS
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-09 -->
**Scope**: Frontend assets in `internal/web/` directory - TypeScript, Tailwind CSS, HTML templates
**See also**: [../../AGENTS.md](../../AGENTS.md) for global standards, [../AGENTS.md](../AGENTS.md) for Go backend
## Overview
Frontend implementation for LDAP selfservice password changer with strict accessibility compliance:
- **static/**: Client-side TypeScript, compiled CSS, static assets
- **js/**: TypeScript source files (compiled to ES modules)
- **styles.css**: Tailwind CSS output
- Icons, logos, favicons, manifest
- **templates/**: Go HTML templates (\*.gohtml)
- **handlers.go**: HTTP route handlers
- **middleware.go**: Security headers, CORS, etc.
- **server.go**: Fiber server setup
**Key characteristics**:
- **WCAG 2.2 AAA**: 7:1 contrast, keyboard navigation, screen reader support, adaptive density
- **Ultra-strict TypeScript**: All strict flags enabled, no `any` types
- **Tailwind CSS 4**: Utility-first, dark mode, responsive, accessible patterns
- **Progressive enhancement**: Works without JavaScript (forms submit via HTTP)
- **Password manager friendly**: Proper autocomplete attributes
## Setup/Environment
**Prerequisites**: Node.js 24+, pnpm 10.18+ (from root `package.json`)
```bash
# From project root
pnpm install # Install dependencies
# Development (watch mode)
pnpm css:dev # Tailwind CSS watch
pnpm js:dev # TypeScript watch
# OR
pnpm dev # Concurrent: CSS + TS + Go hot-reload
```
**No .env needed for frontend** - all config comes from Go backend
**Browser targets**: Modern browsers with ES module support (Chrome 90+, Firefox 88+, Safari 14+, Edge 90+)
## Build & Tests
```bash
# Build frontend assets
pnpm build:assets # TypeScript + CSS (production builds)
# TypeScript
pnpm js:build # Compile TS → ES modules + minify
pnpm js:dev # Watch mode with preserveWatchOutput
tsc --noEmit # Type check only (no output)
# CSS
pnpm css:build # Tailwind + PostCSS → styles.css
pnpm css:dev # Watch mode
# Formatting
pnpm prettier --write internal/web/ # Format TS, CSS, HTML templates
pnpm prettier --check internal/web/ # Check formatting (CI)
```
**No unit tests yet** - TypeScript strict mode catches most errors, integration via Go tests
**CI validation** (from `.github/workflows/check.yml`):
```bash
pnpm install
pnpm js:build # TypeScript strict compilation
pnpm prettier --check .
```
**Accessibility testing**:
- Keyboard navigation: Tab through all interactive elements
- Screen reader: Test with VoiceOver (macOS/iOS) or NVDA (Windows)
- Contrast: Verify 7:1 ratios with browser dev tools
- See [../../docs/accessibility.md](../../docs/accessibility.md) for comprehensive guide
## Code Style
**TypeScript Ultra-Strict** (from `tsconfig.json`):
```json
{
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true
}
```
**No `any` types allowed**:
```typescript
// ✅ Good: explicit types
function validatePassword(password: string, minLength: number): boolean {
return password.length >= minLength;
}
// ❌ Bad: any type
function validatePassword(password: any): boolean {
return password.length >= 8; // ❌ unsafe
}
```
**Prettier formatting**:
- 120 char width
- 2-space indentation
- Semicolons required
- Double quotes (not single)
- Trailing comma: none
**File organization**:
- TypeScript source: `static/js/*.ts`
- Output: `static/js/*.js` (minified ES modules)
- CSS input: `tailwind.css` (Tailwind directives)
- CSS output: `static/styles.css` (PostCSS processed)
## Accessibility Standards (WCAG 2.2 AAA)
**Required compliance** - not optional:
### Keyboard Navigation
- All interactive elements focusable with Tab
- Visual focus indicators (4px outline, 7:1 contrast)
- Logical tab order (top to bottom, left to right)
- No keyboard traps
- Skip links where needed
### Screen Readers
- Semantic HTML: `<button>`, `<input>`, `<label>`, not `<div onclick>`
- ARIA labels on icon-only buttons: `aria-label="Submit"`
- Error messages: `aria-describedby` linking to error text
- Live regions for dynamic content: `aria-live="polite"`
- Form field associations: `<label for="id">` + `<input id="id">`
### Color & Contrast
- Text: 7:1 contrast ratio (AAA)
- Large text (18pt+): 4.5:1 minimum
- Focus indicators: 3:1 against adjacent colors
- Dark mode: same contrast requirements
- Never rely on color alone (use icons, text, patterns)
### Responsive & Adaptive
- Responsive: layout adapts to viewport size
- Text zoom: 200% without horizontal scroll
- Adaptive density: spacing adjusts for user preferences
- Touch targets: 44×44 CSS pixels minimum (mobile)
### Examples
**✅ Good: Accessible button**
```html
<button type="submit" class="btn-primary focus:ring-4 focus:ring-blue-300" aria-label="Submit password change">
<svg aria-hidden="true">...</svg>
Change Password
</button>
```
**❌ Bad: Inaccessible div-button**
```html
<div onclick="submit()" class="button">❌ not keyboard accessible Submit</div>
```
**✅ Good: Form with error handling**
```html
<form>
<label for="password">New Password</label>
<input
id="password"
type="password"
aria-describedby="password-error"
aria-invalid="true"
autocomplete="new-password"
/>
<div id="password-error" role="alert">Password must be at least 8 characters</div>
</form>
```
**❌ Bad: Form without associations**
```html
<form>
<div>Password</div>
❌ not a label, no association <input type="password" /> ❌ no autocomplete, no error linkage
<div style="color: red">Error</div>
❌ no role="alert", only color
</form>
```
## Tailwind CSS Patterns
**Use utility classes**, not custom CSS:
**✅ Good: Utility classes**
```html
<button
class="rounded-lg bg-blue-600 px-4 py-2 font-semibold text-white hover:bg-blue-700 focus:ring-4 focus:ring-blue-300"
>
Submit
</button>
```
**❌ Bad: Custom CSS**
```html
<button class="custom-button">Submit</button>
<style>
.custom-button {
background: blue;
} /* ❌ Use Tailwind utilities */
</style>
```
**Dark mode support**:
```html
<div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">Content</div>
```
**Responsive design**:
```html
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Responsive grid: 1 col mobile, 2 tablet, 3 desktop -->
</div>
```
**Focus states (required)**:
```html
<button class="focus:ring-4 focus:ring-blue-300 focus:outline-none">
<!-- 4px focus ring, 7:1 contrast -->
</button>
```
## TypeScript Patterns
**Strict null checking**:
```typescript
// ✅ Good: handle nulls explicitly
function getElement(id: string): HTMLElement | null {
return document.getElementById(id);
}
const el = getElement("password");
if (el) {
// ✅ null check
el.textContent = "Hello";
}
// ❌ Bad: assume non-null
const el = getElement("password");
el.textContent = "Hello"; // ❌ may crash if null
```
**Type guards**:
```typescript
// ✅ Good: type guard for forms
function isHTMLFormElement(element: Element): element is HTMLFormElement {
return element instanceof HTMLFormElement;
}
const form = document.querySelector("form");
if (form && isHTMLFormElement(form)) {
form.addEventListener("submit", handleSubmit);
}
```
**No unsafe array access**:
```typescript
// ✅ Good: check array bounds
const items = ["a", "b", "c"];
const first = items[0]; // string | undefined (noUncheckedIndexedAccess)
if (first) {
console.log(first.toUpperCase());
}
// ❌ Bad: unsafe access
console.log(items[0].toUpperCase()); // ❌ may crash if empty array
```
## PR/Commit Checklist
**Before committing frontend code**:
- [ ] Run `pnpm js:build` (TypeScript strict check)
- [ ] Run `pnpm prettier --write internal/web/`
- [ ] Verify keyboard navigation works
- [ ] Test with screen reader (VoiceOver/NVDA)
- [ ] Check contrast ratios (7:1 for text)
- [ ] Test dark mode
- [ ] Verify password manager autofill works
- [ ] No console errors in browser
- [ ] Test on mobile viewport (responsive)
**Accessibility checklist**:
- [ ] All interactive elements keyboard accessible
- [ ] Focus indicators visible (4px outline, 7:1 contrast)
- [ ] ARIA labels on icon-only buttons
- [ ] Form fields properly labeled
- [ ] Error messages linked with aria-describedby
- [ ] No color-only information conveyance
- [ ] Touch targets ≥44×44 CSS pixels (mobile)
**Performance checklist**:
- [ ] Minified JS (via `pnpm js:minify`)
- [ ] CSS optimized (cssnano via PostCSS)
- [ ] No unused Tailwind classes (purged automatically)
- [ ] No console.log in production code
## Good vs Bad Examples
**✅ Good: Type-safe DOM access**
```typescript
function setupPasswordToggle(): void {
const toggle = document.getElementById("toggle-password");
const input = document.getElementById("password");
if (!toggle || !(input instanceof HTMLInputElement)) {
return; // Guard against missing elements
}
toggle.addEventListener("click", () => {
input.type = input.type === "password" ? "text" : "password";
});
}
```
**❌ Bad: Unsafe DOM access**
```typescript
function setupPasswordToggle() {
const toggle = document.getElementById("toggle-password")!; // ❌ non-null assertion
const input = document.getElementById("password") as any; // ❌ any type
toggle.addEventListener("click", () => {
input.type = input.type === "password" ? "text" : "password"; // ❌ may crash
});
}
```
**✅ Good: Accessible form validation**
```typescript
function showError(input: HTMLInputElement, message: string): void {
const errorId = `${input.id}-error`;
let errorEl = document.getElementById(errorId);
if (!errorEl) {
errorEl = document.createElement("div");
errorEl.id = errorId;
errorEl.setAttribute("role", "alert");
errorEl.className = "text-red-600 dark:text-red-400 text-sm mt-1";
input.parentElement?.appendChild(errorEl);
}
errorEl.textContent = message;
input.setAttribute("aria-invalid", "true");
input.setAttribute("aria-describedby", errorId);
}
```
**❌ Bad: Inaccessible validation**
```typescript
function showError(input: any, message: string) {
// ❌ any type
input.style.borderColor = "red"; // ❌ color only, no text
alert(message); // ❌ blocks UI, not persistent
}
```
## When Stuck
**TypeScript issues**:
1. **Type errors**: Check `tsconfig.json` flags, use proper types (no `any`)
2. **Null errors**: Add null checks or type guards
3. **Module errors**: Ensure ES module syntax (`import`/`export`)
4. **Build errors**: `pnpm install` to refresh dependencies
**CSS issues**:
1. **Styles not applying**: Check Tailwind purge config, rebuild with `pnpm css:build`
2. **Dark mode broken**: Use `dark:` prefix on utilities
3. **Responsive broken**: Use `md:`, `lg:` breakpoint prefixes
4. **Custom classes**: Don't - use Tailwind utilities instead
**Accessibility issues**:
1. **Keyboard nav broken**: Check tab order, focus indicators
2. **Screen reader confusion**: Verify ARIA labels, semantic HTML
3. **Contrast failure**: Use darker colors, test with dev tools
4. **See**: [../../docs/accessibility.md](../../docs/accessibility.md)
**Browser dev tools**:
- Accessibility tab: Check ARIA, contrast, structure
- Lighthouse: Run accessibility audit (aim for 100 score)
- Console: No errors in production code
## Testing Workflow
**Manual testing required** (no automated frontend tests yet):
1. **Visual testing**: Check all pages in light/dark mode
2. **Keyboard testing**: Tab through all interactive elements
3. **Screen reader testing**: Use VoiceOver (Cmd+F5) or NVDA
4. **Responsive testing**: Test mobile, tablet, desktop viewports
5. **Browser testing**: Chrome, Firefox, Safari, Edge
6. **Password manager**: Test autofill with 1Password, LastPass, etc.
**Accessibility testing tools**:
- Browser dev tools Lighthouse
- axe DevTools extension
- WAVE browser extension
- Manual keyboard/screen reader testing (required)
**Integration testing**: Go backend tests exercise full request/response flow including frontend templates

View File

@@ -0,0 +1,27 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-09-29 -->
# AGENTS.md (root)
This file explains repo-wide conventions and where to find scoped rules.
**Precedence:** the **closest `AGENTS.md`** to the files you're changing wins. Root holds global defaults only.
## Global rules
- Keep diffs small; add tests for new code paths
- Ask first before: adding heavy deps, running full e2e suites, or repo-wide rewrites
- Never commit secrets or sensitive data to the repository
- Follow Go 1.24 conventions and idioms
- Maintain minimum test coverage of 40%
## Minimal pre-commit checks
- Typecheck (all packages): `go build -v ./...`
- Lint/format (file scope): `gofmt -w <file.go>` and `~/go/bin/golangci-lint run ./...`
- Unit tests (fast): `go test -v -race -short -timeout=10s ./...`
## Index of scoped AGENTS.md
- `./examples/AGENTS.md` — Example applications and usage patterns
- `./testutil/AGENTS.md` — Testing utilities and container management
- `./docs/AGENTS.md` — Documentation and guides
## When instructions conflict
- The nearest `AGENTS.md` wins. Explicit user prompts override files.
- For Go-specific patterns, defer to language idioms and standard library conventions

View File

@@ -0,0 +1,45 @@
<!-- Managed by agent: keep sections and order; edit content, not structure. Last updated: 2025-09-29 -->
# AGENTS.md — Examples
## Overview
Example applications demonstrating library usage patterns for authentication, user management, performance optimization, context handling, and error patterns. Entry points are the main.go files in each subdirectory.
## Setup & environment
- Install: `go mod download`
- Run example: `go run examples/<name>/main.go`
- Env: Examples use environment variables from `.env` files when present
## Build & tests (prefer file-scoped)
- Typecheck a file: `go build -v examples/<name>/main.go`
- Format a file: `gofmt -w examples/<name>/main.go`
- Run example: `go run examples/<name>/main.go`
## Code style & conventions
- Examples should be self-contained and runnable
- Use clear variable names that explain the concept
- Include comments explaining non-obvious patterns
- Error handling should demonstrate best practices
- Keep examples focused on a single concept
## Security & safety
- Never include real credentials in examples
- Use placeholder values like "ldap.example.com"
- Document required permissions clearly
- Examples should fail gracefully without real LDAP server
## PR/commit checklist
- Examples must compile without errors
- Include README.md explaining the example's purpose
- Test example with both real and mock LDAP servers if possible
- Ensure examples follow library best practices
## Good vs. bad examples
- Good: `authentication/main.go` (clear flow, error handling)
- Good: `context-usage/main.go` (proper context propagation)
- Pattern to follow: Simple, focused, well-commented demonstrations
## When stuck
- Check the main library documentation in ../docs/
- Review similar examples in sibling directories
- Ensure you have the latest library version

View File

@@ -0,0 +1,348 @@
# AI Agent Development Guide
**Project:** rte_ckeditor_image - TYPO3 CKEditor 5 Image Extension
**Type:** TYPO3 CMS Extension (PHP 8.2+ + JavaScript/ES6)
**License:** GPL-2.0-or-later
## 📋 Documentation Structure
This project uses a three-tier documentation system:
- **[claudedocs/](claudedocs/)** - AI session context (Markdown, gitignored, temporary)
- **[Documentation/](Documentation/)** - Official TYPO3 docs (RST, published, permanent)
- **Root** - Project essentials (README, CONTRIBUTING, SECURITY, LICENSE)
See **[claudedocs/INDEX.md](claudedocs/INDEX.md)** for AI context navigation and **[Documentation/AGENTS.md](Documentation/AGENTS.md)** for TYPO3 documentation system guide.
## 🎯 Quick Start
```bash
# First time setup
composer install
make help # See all available targets
# Development workflow
make lint # Run all linters
make format # Fix code style
make test # Run tests
make ci # Full CI check (pre-commit)
# Composer shortcuts (if make unavailable)
composer ci:test:php:lint # PHP syntax check
composer ci:test:php:phpstan # Static analysis
composer ci:test:php:cgl # Code style check
composer ci:cgl # Fix code style
```
## 🏗️ Setup
### Prerequisites
- **PHP:** 8.2-8.9 with extensions: dom, libxml
- **Composer:** Latest stable
- **TYPO3:** 13.4+ (cms-core, cms-backend, cms-frontend, cms-rte-ckeditor)
- **direnv:** Optional but recommended
### Installation
```bash
# Clone and install
git clone https://github.com/netresearch/t3x-rte_ckeditor_image.git
cd t3x-rte_ckeditor_image
composer install
# Enable direnv (optional)
direnv allow
```
## 🔧 Build & Test Commands
### Fast Quality Checks (Pre-commit)
```bash
make lint # PHP lint + PHPStan + style check
make format # Auto-fix code style
make typecheck # PHPStan static analysis
```
### Full CI Suite
```bash
make ci # Complete CI pipeline
make test # All tests (when available)
```
### Individual Commands
```bash
# PHP Linting
composer ci:test:php:lint
# Static Analysis
composer ci:test:php:phpstan
# Code Style Check
composer ci:test:php:cgl
# Code Style Fix
composer ci:cgl
# Rector (PHP Modernization)
composer ci:test:php:rector
composer ci:rector # Apply changes
```
## 📝 Code Style
### PHP Standards
- **Base:** PSR-12 + PER-CS 2.0
- **Strict types:** Required in all files (`declare(strict_types=1);`)
- **Header comments:** Auto-managed by PHP-CS-Fixer
- **Config:** `Build/.php-cs-fixer.dist.php`
### Key Rules
```php
<?php
declare(strict_types=1);
/**
* This file is part of the package netresearch/rte-ckeditor-image.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
namespace Netresearch\RteCKEditorImage\Controller;
// Imports: Classes, constants, functions
use TYPO3\CMS\Core\Utility\GeneralUtility;
// Alignment on = and =>
$config = [
'short' => 'value',
'longer' => 'another',
];
```
### JavaScript Standards
- **ES6 modules:** CKEditor 5 plugin format
- **No npm tooling:** TYPO3-managed assets
- **Style:** Follow CKEditor 5 conventions
- **Location:** `Resources/Public/JavaScript/`
## 🔒 Security
- **No secrets in VCS:** Use TYPO3's environment configuration
- **Dependency scanning:** Renovate enabled (see `renovate.json`)
- **Static analysis:** PHPStan with strict rules
- **TYPO3 security:** Follow TYPO3 Security Guidelines
- **File uploads:** Use FAL (File Abstraction Layer)
- **XSS prevention:** All output escaped via Fluid templates
## ✅ PR/Commit Checklist
Before committing:
1.**Lint passed:** `make lint` or `composer ci:test:php:lint`
2.**Style fixed:** `make format` or `composer ci:cgl`
3.**Static analysis:** `composer ci:test:php:phpstan` (no new errors)
4.**Rector check:** `composer ci:test:php:rector` (no suggestions)
5.**Docs updated:** Update relevant docs/ files if API changed
6.**CHANGELOG:** Add entry if user-facing change
7.**Conventional Commits:** Use format: `type(scope): message`
8.**Small PRs:** Keep ≤300 net LOC changed
### Commit Format
```
<type>(<scope>): <subject>
[optional body]
[optional footer]
```
**Types:** `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`
**Scopes:** `backend`, `frontend`, `config`, `docs`, `build`
**Examples:**
```
feat(backend): add image processing hook for WebP
fix(frontend): resolve style drop-down disabled for typo3image
docs(api): update DataHandling API reference
```
## 🎓 Good vs Bad Examples
### ✅ Good: TYPO3 Pattern
```php
<?php
declare(strict_types=1);
namespace Netresearch\RteCKEditorImage\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Resource\ResourceFactory;
final class SelectImageController
{
public function __construct(
private readonly ResourceFactory $resourceFactory
) {
}
public function infoAction(ServerRequestInterface $request): ResponseInterface
{
$fileUid = (int)($request->getQueryParams()['fileId'] ?? 0);
// Implementation...
}
}
```
### ❌ Bad: Missing strict types, no DI
```php
<?php
namespace Netresearch\RteCKEditorImage\Controller;
class SelectImageController
{
public function infoAction($request)
{
$fileUid = $_GET['fileId']; // Direct superglobal access
$factory = new ResourceFactory(); // No DI
}
}
```
### ✅ Good: CKEditor 5 Plugin
```javascript
export default class Typo3Image extends Core.Plugin {
static get requires() {
return ['StyleUtils', 'GeneralHtmlSupport'];
}
init() {
const editor = this.editor;
this.listenTo(styleUtils, 'isStyleEnabledForBlock', (event, [style]) => {
// Event handling...
});
}
}
```
### ❌ Bad: Missing dependencies
```javascript
class Typo3Image extends Core.Plugin {
// Missing requires() - breaks style integration
init() {
// Implementation...
}
}
```
## 🆘 When Stuck
1. **AI Context:** Start with [claudedocs/INDEX.md](claudedocs/INDEX.md) for project navigation
2. **Architecture:** Review [claudedocs/ARCHITECTURE.md](claudedocs/ARCHITECTURE.md)
3. **Security:** Check [claudedocs/SECURITY.md](claudedocs/SECURITY.md)
4. **API Reference:** See [claudedocs/API_REFERENCE.md](claudedocs/API_REFERENCE.md)
5. **Development Guide:** Follow [claudedocs/DEVELOPMENT_GUIDE.md](claudedocs/DEVELOPMENT_GUIDE.md)
6. **TYPO3 Docs Guide:** Read [Documentation/AGENTS.md](Documentation/AGENTS.md)
7. **Published Manual:** https://docs.typo3.org/p/netresearch/rte-ckeditor-image/main/en-us/
8. **TYPO3 Core Docs:** https://docs.typo3.org/
9. **Issues:** https://github.com/netresearch/t3x-rte_ckeditor_image/issues
### Common Issues
- **Style drop-down disabled:** Missing `GeneralHtmlSupport` dependency (v13.0.0+)
- **Images not in frontend:** Missing static template include
- **PHPStan errors:** Run `composer ci:test:php:phpstan:baseline` to update baseline
- **Code style fails:** Run `composer ci:cgl` to auto-fix
## 📐 House Rules
### Commits & PRs
- **Atomic commits:** One logical change per commit
- **Conventional Commits:** Required format (see checklist)
- **Small PRs:** Target ≤300 net LOC changed
- **Branch naming:** `feature/short-description`, `fix/issue-123`
### Design Principles
- **SOLID:** Single responsibility, Open/closed, Liskov, Interface segregation, Dependency inversion
- **KISS:** Keep it simple, stupid
- **DRY:** Don't repeat yourself
- **YAGNI:** You aren't gonna need it
- **Composition > Inheritance:** Prefer composition
- **Law of Demeter:** Minimize coupling
### Dependencies
- **Latest stable:** Use current TYPO3 13.4+ versions
- **Renovate:** Auto-updates enabled
- **Major updates:** Require changelog review + migration notes
- **Composer:** Lock file committed
### API & Versioning
- **SemVer:** Semantic versioning (MAJOR.MINOR.PATCH)
- **TYPO3 compatibility:** Follow TYPO3 versioning
- **Breaking changes:** Increment major version
- **Deprecations:** Add `@deprecated` tag + removal plan
### Testing
- **TYPO3 Testing Framework:** Use `typo3/testing-framework`
- **Functional tests:** For database/integration scenarios
- **Unit tests:** For isolated logic
- **Test location:** `Tests/Functional/`, `Tests/Unit/`
### Licensing
- **License:** AGPL-3.0-or-later
- **SPDX:** Use SPDX identifiers
- **Headers:** Auto-managed by PHP-CS-Fixer
- **Third-party:** Document in CHANGELOG
## 🔗 Related Files
**Root Documentation:**
- **[README.md](README.md)** - Project overview and quick links
- **[CONTRIBUTING.md](CONTRIBUTING.md)** - Contribution guidelines
- **[SECURITY.md](SECURITY.md)** - Security policy
- **[LICENSE](LICENSE)** - GPL-2.0-or-later license
**AI Session Context (gitignored):**
- **[claudedocs/INDEX.md](claudedocs/INDEX.md)** - Navigation hub
- **[claudedocs/PROJECT_OVERVIEW.md](claudedocs/PROJECT_OVERVIEW.md)** - Project summary
- **[claudedocs/ARCHITECTURE.md](claudedocs/ARCHITECTURE.md)** - System design
- **[claudedocs/DEVELOPMENT_GUIDE.md](claudedocs/DEVELOPMENT_GUIDE.md)** - Development workflow
- **[claudedocs/API_REFERENCE.md](claudedocs/API_REFERENCE.md)** - PHP API docs
- **[claudedocs/SECURITY.md](claudedocs/SECURITY.md)** - Security analysis
**Official TYPO3 Documentation:**
- **[Documentation/](Documentation/)** - RST documentation (published)
- **[Documentation/AGENTS.md](Documentation/AGENTS.md)** - TYPO3 docs system guide
**Configuration:**
- **[composer.json](composer.json)** - Dependencies & scripts
- **[Build/](Build/)** - Development tools configuration
## 📚 Additional Resources
- **Repository:** https://github.com/netresearch/t3x-rte_ckeditor_image
- **Packagist:** https://packagist.org/packages/netresearch/rte-ckeditor-image
- **TYPO3 Ext:** https://extensions.typo3.org/extension/rte_ckeditor_image
- **TYPO3 Docs:** https://docs.typo3.org/
- **CKEditor 5:** https://ckeditor.com/docs/ckeditor5/

View File

@@ -0,0 +1,392 @@
# Classes/AGENTS.md
<!-- Managed by agent: keep sections & order; edit content, not structure. Last updated: 2025-10-15 -->
**Scope:** PHP backend components (Controllers, EventListeners, DataHandling, Utils)
**Parent:** [../AGENTS.md](../AGENTS.md)
## 📋 Overview
PHP backend implementation for TYPO3 CKEditor Image extension. Components:
### Controllers
- **SelectImageController** - Image browser wizard, file selection, image info API
- **ImageRenderingController** - Image rendering and processing for frontend
- **ImageLinkRenderingController** - Link-wrapped image rendering
### EventListeners
- **RteConfigurationListener** - PSR-14 event for RTE configuration injection
### DataHandling
- **RteImagesDbHook** - Database hooks for image magic reference handling
- **RteImageSoftReferenceParser** - Soft reference parsing for RTE images
### Backend Components
- **RteImagePreviewRenderer** - Backend preview rendering
### Utilities
- **ProcessedFilesHandler** - File processing and manipulation utilities
## 🏗️ Architecture Patterns
### TYPO3 Patterns
- **FAL (File Abstraction Layer):** All file operations via ResourceFactory
- **PSR-7 Request/Response:** HTTP message interfaces for controllers
- **PSR-14 Events:** Event-driven configuration and hooks
- **Dependency Injection:** Constructor-based DI (TYPO3 v13+)
- **Service Configuration:** `Configuration/Services.yaml` for DI registration
### File Structure
```
Classes/
├── Backend/
│ └── Preview/
│ └── RteImagePreviewRenderer.php
├── Controller/
│ ├── ImageLinkRenderingController.php
│ ├── ImageRenderingController.php
│ └── SelectImageController.php
├── DataHandling/
│ └── SoftReference/
│ └── RteImageSoftReferenceParser.php
├── Database/
│ └── RteImagesDbHook.php
├── EventListener/
│ └── RteConfigurationListener.php
└── Utils/
└── ProcessedFilesHandler.php
```
## 🔧 Build & Tests
```bash
# PHP-specific quality checks
make lint # All linters (syntax + PHPStan + Rector + style)
composer ci:test:php:lint # PHP syntax check
composer ci:test:php:phpstan # Static analysis
composer ci:test:php:rector # Rector modernization check
composer ci:test:php:cgl # Code style check
# Fixes
make format # Auto-fix code style
composer ci:cgl # Alternative: fix style
composer ci:rector # Apply Rector changes
# Full CI
make ci # Complete pipeline
```
## 📝 Code Style
### Required Patterns
**1. Strict Types (Always First)**
```php
<?php
declare(strict_types=1);
```
**2. File Header (Auto-managed by PHP-CS-Fixer)**
```php
/**
* This file is part of the package netresearch/rte-ckeditor-image.
*
* For the full copyright and license information, please read the
* LICENSE file that was distributed with this source code.
*/
```
**3. Import Order**
- Classes first
- Functions second
- Constants third
- One blank line before namespace
**4. Type Hints**
- All parameters must have type hints
- All return types must be declared
- Use nullable types `?Type` when appropriate
- Use union types `Type1|Type2` for PHP 8+
**5. Property Types**
```php
private ResourceFactory $resourceFactory; // Required type declaration
private readonly ResourceFactory $factory; // Readonly for immutability
```
**6. Alignment**
```php
$config = [
'short' => 'value', // Align on =>
'longer' => 'another',
];
```
## 🔒 Security
### FAL (File Abstraction Layer)
- **Always use FAL:** Never direct file system access
- **ResourceFactory:** For retrieving files by UID
- **File validation:** Check isDeleted(), isMissing()
- **ProcessedFile:** Use process() for image manipulation
```php
// ✅ Good: FAL usage
$file = $this->resourceFactory->getFileObject($id);
if ($file->isDeleted() || $file->isMissing()) {
throw new \Exception('File not found');
}
// ❌ Bad: Direct file access
$file = file_get_contents('/var/www/uploads/' . $filename);
```
### Input Validation
- **Type cast superglobals:** `(int)($request->getQueryParams()['id'] ?? 0)`
- **Validate before use:** Check ranges, formats, existence
- **Exit on error:** Use HTTP status codes with `HttpUtility::HTTP_STATUS_*`
### XSS Prevention
- **Fluid templates:** Auto-escaping enabled by default
- **JSON responses:** Use `JsonResponse` class
- **Localization:** Via `LocalizationUtility::translate()`
## ✅ PR/Commit Checklist
### PHP-Specific Checks
1.**Strict types:** `declare(strict_types=1);` in all files
2.**Type hints:** All parameters and return types declared
3.**PHPStan:** Zero errors (`composer ci:test:php:phpstan`)
4.**Code style:** PSR-12/PER-CS2.0 compliant (`make format`)
5.**Rector:** No modernization suggestions (`composer ci:test:php:rector`)
6.**FAL usage:** No direct file system access
7.**DI pattern:** Constructor injection, no `new ClassName()`
8.**PSR-7:** Request/Response for controllers
9.**Documentation:** PHPDoc for public methods
## 🎓 Good vs Bad Examples
### ✅ Good: Controller Pattern
```php
<?php
declare(strict_types=1);
namespace Netresearch\RteCKEditorImage\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Http\JsonResponse;
use TYPO3\CMS\Core\Resource\ResourceFactory;
final class SelectImageController
{
public function __construct(
private readonly ResourceFactory $resourceFactory
) {
}
public function infoAction(ServerRequestInterface $request): ResponseInterface
{
$fileUid = (int)($request->getQueryParams()['fileId'] ?? 0);
if ($fileUid <= 0) {
return new JsonResponse(['error' => 'Invalid file ID'], 400);
}
$file = $this->resourceFactory->getFileObject($fileUid);
return new JsonResponse([
'uid' => $file->getUid(),
'width' => $file->getProperty('width'),
'height' => $file->getProperty('height'),
]);
}
}
```
### ❌ Bad: Anti-patterns
```php
<?php
// ❌ Missing strict types
namespace Netresearch\RteCKEditorImage\Controller;
// ❌ Missing PSR-7 types
class SelectImageController
{
// ❌ No constructor DI
public function infoAction($request)
{
// ❌ Direct superglobal access
$fileUid = $_GET['fileId'];
// ❌ No DI - manual instantiation
$factory = new ResourceFactory();
// ❌ No type safety, no validation
$file = $factory->getFileObject($fileUid);
// ❌ Manual JSON encoding
header('Content-Type: application/json');
echo json_encode(['uid' => $file->getUid()]);
exit;
}
}
```
### ✅ Good: EventListener Pattern
```php
<?php
declare(strict_types=1);
namespace Netresearch\RteCKEditorImage\EventListener;
use TYPO3\CMS\Backend\Routing\UriBuilder;
use TYPO3\CMS\RteCKEditor\Form\Element\Event\AfterPrepareConfigurationForEditorEvent;
final class RteConfigurationListener
{
public function __construct(
private readonly UriBuilder $uriBuilder
) {
}
public function __invoke(AfterPrepareConfigurationForEditorEvent $event): void
{
$configuration = $event->getConfiguration();
$configuration['style']['typo3image'] = [
'routeUrl' => (string)$this->uriBuilder->buildUriFromRoute('rteckeditorimage_wizard_select_image'),
];
$event->setConfiguration($configuration);
}
}
```
### ❌ Bad: EventListener Anti-pattern
```php
<?php
namespace Netresearch\RteCKEditorImage\EventListener;
class RteConfigurationListener
{
// ❌ Wrong signature - not invokable
public function handle($event)
{
// ❌ Manual instantiation instead of DI
$uriBuilder = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(UriBuilder::class);
// ❌ Array access without type safety
$config = $event->getConfiguration();
$config['style']['typo3image']['routeUrl'] = $uriBuilder->buildUriFromRoute('rteckeditorimage_wizard_select_image');
$event->setConfiguration($config);
}
}
```
### ✅ Good: FAL Usage
```php
protected function getImage(int $id): File
{
try {
$file = $this->resourceFactory->getFileObject($id);
if ($file->isDeleted() || $file->isMissing()) {
throw new FileNotFoundException('File not found or deleted', 1234567890);
}
} catch (\Exception $e) {
throw new FileNotFoundException('Could not load file', 1234567891, $e);
}
return $file;
}
```
### ❌ Bad: Direct File Access
```php
// ❌ Multiple issues
protected function getImage($id) // Missing return type, no type hint
{
// ❌ Direct file system access, bypassing FAL
$path = '/var/www/html/fileadmin/' . $id;
// ❌ No validation, no error handling
if (file_exists($path)) {
return file_get_contents($path);
}
return null; // ❌ Should throw exception or return typed null
}
```
## 🆘 When Stuck
### Documentation
- **API Reference:** [docs/API/Controllers.md](../docs/API/Controllers.md) - Controller APIs
- **Event Listeners:** [docs/API/EventListeners.md](../docs/API/EventListeners.md) - PSR-14 events
- **Data Handling:** [docs/API/DataHandling.md](../docs/API/DataHandling.md) - Database hooks
- **Architecture:** [docs/Architecture/Overview.md](../docs/Architecture/Overview.md) - System design
### TYPO3 Resources
- **FAL Documentation:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Fal/Index.html
- **PSR-14 Events:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Events/Index.html
- **Dependency Injection:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/DependencyInjection/Index.html
- **Controllers:** https://docs.typo3.org/m/typo3/reference-coreapi/main/en-us/ApiOverview/Backend/Controllers/Index.html
### Common Issues
- **ResourceFactory errors:** Check file exists, not deleted, proper UID
- **DI not working:** Verify `Configuration/Services.yaml` registration
- **PHPStan errors:** Update baseline: `composer ci:test:php:phpstan:baseline`
- **Type errors:** Enable strict_types, add all type hints
## 📐 House Rules
### Controllers
- **Extend framework controllers:** ElementBrowserController for browsers
- **Final by default:** Use `final class` unless inheritance required
- **PSR-7 types:** ServerRequestInterface → ResponseInterface
- **JSON responses:** Use `JsonResponse` class
- **Validation first:** Validate all input parameters at method start
### EventListeners
- **Invokable:** Use `__invoke()` method signature
- **Event type hints:** Type-hint specific event classes
- **Immutability aware:** Get, modify, set configuration/state
- **Final classes:** Event listeners should be final
### DataHandling
- **Soft references:** Implement soft reference parsing for data integrity
- **Database hooks:** Use for maintaining referential integrity
- **Transaction safety:** Consider rollback scenarios
### Dependencies
- **Constructor injection:** All dependencies via constructor
- **Readonly properties:** Use `readonly` for immutable dependencies
- **Interface over implementation:** Depend on interfaces when available
- **GeneralUtility::makeInstance:** Only for factories or when DI unavailable
### Error Handling
- **Type-specific exceptions:** Use TYPO3 exception hierarchy
- **HTTP status codes:** Via HttpUtility constants
- **Meaningful messages:** Include context in exception messages
- **Log important errors:** Use TYPO3 logging framework
### Testing
- **Functional tests:** For controllers, database operations
- **Unit tests:** For utilities, isolated logic
- **Mock FAL:** Use TYPO3 testing framework FAL mocks
- **Test location:** `Tests/Functional/` and `Tests/Unit/`
## 🔗 Related
- **[Resources/AGENTS.md](../Resources/AGENTS.md)** - JavaScript/CKEditor integration
- **[Tests/AGENTS.md](../Tests/AGENTS.md)** - Testing patterns
- **[Configuration/Services.yaml](../Configuration/Services.yaml)** - DI container configuration
- **[docs/API/](../docs/API/)** - Complete API documentation