diff --git a/.claude/hooks/openapi-validate.sh b/.claude/hooks/openapi-validate.sh new file mode 100755 index 0000000..60b3d6f --- /dev/null +++ b/.claude/hooks/openapi-validate.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Read hook input from stdin (JSON with tool_input.file_path) +INPUT=$(cat) +FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))" 2>/dev/null || echo "") + +# Only run for OpenAPI spec files +case "$FILE_PATH" in + */openapi/*.yaml|*/openapi/*.yml) ;; + *) exit 0 ;; +esac + +cd "$CLAUDE_PROJECT_DIR/backend" + +# Run validation (zero-config: structural validity only) +if OUTPUT=$(npx @redocly/cli@latest lint src/main/resources/openapi/api.yaml --format=stylish 2>&1); then + echo '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"✓ OpenAPI spec validation passed."}}' +else + ESCAPED=$(echo "$OUTPUT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))") + echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":$ESCAPED}}" +fi diff --git a/.claude/settings.json b/.claude/settings.json index 4c307c7..f943f85 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -13,6 +13,11 @@ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/frontend-check.sh\"", "timeout": 120 + }, + { + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/openapi-validate.sh\"", + "timeout": 120 } ] } diff --git a/README.md b/README.md index 9364560..9641ebd 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,16 @@ ArchUnit enforces hexagonal boundaries: domain must not depend on adapters, appl |---------------------|------------------|-------------------| | Prettier | `npm run format` | Formatting issues | +### OpenAPI Spec (YAML) + +**After editing an `openapi/*.yaml` file** (PostToolUse hook): + +| What | Command | Fails on | +|---------------------|--------------------------|-----------------------------------| +| Redocly CLI | `redocly lint api.yaml` | Structural and ruleset violations | + +Validates the OpenAPI 3.1 spec against the Redocly `recommended` ruleset (with `security-defined` disabled, since endpoints are intentionally public). Runs via `npx @redocly/cli@latest`. + ## Deployment ### Docker Compose diff --git a/backend/redocly.yaml b/backend/redocly.yaml new file mode 100644 index 0000000..a1ca3ba --- /dev/null +++ b/backend/redocly.yaml @@ -0,0 +1,5 @@ +extends: + - recommended + +rules: + security-defined: off diff --git a/docs/agents/research/2026-03-04-openapi-validation-pipeline.md b/docs/agents/research/2026-03-04-openapi-validation-pipeline.md new file mode 100644 index 0000000..f0b927c --- /dev/null +++ b/docs/agents/research/2026-03-04-openapi-validation-pipeline.md @@ -0,0 +1,215 @@ +--- +date: "2026-03-04T22:27:37.933286+00:00" +git_commit: 91e566efea0cbf53ba06a29b63317b7435609bd8 +branch: master +topic: "Automatic OpenAPI Validation Pipelines for Backpressure Hooks" +tags: [research, openapi, validation, hooks, backpressure, linting] +status: complete +--- + +# Research: Automatic OpenAPI Validation Pipelines + +## Research Question + +What automatic validation pipelines exist for OpenAPI specs that can be integrated into the current Claude Code backpressure hook setup, running after the OpenAPI spec has been modified? + +## Summary + +The project already has a PostToolUse hook system that runs backend compile checks and frontend lint/type-checks after Edit/Write operations. Adding OpenAPI spec validation requires a new hook script that triggers specifically when `api.yaml` is modified. Several CLI tools support OpenAPI 3.1.0 validation — **Redocly CLI** is the strongest fit given the existing Node.js toolchain, MIT license, active maintenance, and zero-config baseline. + +## Current Backpressure Setup + +### Hook Architecture (`.claude/settings.json`) + +The project uses Claude Code hooks for automated quality gates: + +| Hook Event | Trigger | Scripts | +|---|---|---| +| `PostToolUse` | `Edit\|Write` tool calls | `backend-compile-check.sh`, `frontend-check.sh` | +| `Stop` | Agent attempts to stop | `run-tests.sh` | + +### How Hooks Work + +Each hook script: +1. Reads JSON from stdin containing `tool_input.file_path` +2. Pattern-matches the file path to decide if it should run +3. Executes validation (compile, lint, type-check, test) +4. Returns JSON with either success message or failure details +5. On failure: outputs `hookSpecificOutput` with error context (PostToolUse) or `{"decision":"block"}` (Stop) + +### Existing Pattern for File Matching + +```bash +# backend-compile-check.sh — matches Java files +case "$FILE_PATH" in + */backend/src/*.java|backend/src/*.java) ;; + *) exit 0 ;; +esac + +# frontend-check.sh — matches TS/Vue files +case "$FILE_PATH" in + */frontend/src/*.ts|*/frontend/src/*.vue|frontend/src/*.ts|frontend/src/*.vue) ;; + *) exit 0 ;; +esac +``` + +An OpenAPI validation hook would use the same pattern: +```bash +case "$FILE_PATH" in + */openapi/api.yaml|*/openapi/*.yaml) ;; + *) exit 0 ;; +esac +``` + +### Existing OpenAPI Tooling in the Project + +- **Backend:** `openapi-generator-maven-plugin` v7.20.0 generates Spring interfaces from `api.yaml` (`pom.xml:149-178`) +- **Frontend:** `openapi-typescript` v7.13.0 generates TypeScript types; `openapi-fetch` v0.17.0 provides type-safe client +- **No validation/linting tools** currently installed — no Redocly, Spectral, or other linter config exists + +## Tool Evaluation + +### Redocly CLI (`@redocly/cli`) + +| Attribute | Value | +|---|---| +| OpenAPI 3.1 | Full support | +| Install | `npm install -g @redocly/cli` or `npx @redocly/cli@latest` | +| CLI | `redocly lint api.yaml` | +| License | MIT | +| Maintenance | Very active — latest v2.20.3 (2026-03-03), daily/weekly releases | +| GitHub | ~1.4k stars (Redocly ecosystem: 24k+ combined) | + +**Checks:** Structural validity against OAS schema, configurable linting rules (naming, descriptions, operation IDs, security), style/consistency enforcement. Built-in rulesets: `minimal`, `recommended`, `recommended-strict`. Zero-config baseline works immediately. Custom rules via `redocly.yaml`. + +**Fit for this project:** Node.js already in the toolchain (frontend). `npx` form requires no permanent install. MIT license compatible with GPL-3.0. The `@redocly/openapi-core` package is already present as a transitive dependency of `openapi-typescript` in `node_modules`. + +### Spectral (`@stoplight/spectral-cli`) + +| Attribute | Value | +|---|---| +| OpenAPI 3.1 | Full support (since v6.x) | +| Install | `npm install -g @stoplight/spectral-cli` | +| CLI | `spectral lint api.yaml` | +| License | Apache 2.0 | +| Maintenance | Active — latest v6.15.0 (2025-04-22), slower cadence | +| GitHub | ~3k stars | + +**Checks:** Schema compliance, missing descriptions/tags/operationIds, contact/license metadata. Highly extensible custom rulesets via YAML/JS. Configurable severity levels. + +**Fit for this project:** Well-established industry standard. Apache 2.0 compatible with GPL. Less actively maintained than Redocly (10 months since last release). Heavier custom ruleset system may be over-engineered for current needs. + +### Vacuum (`daveshanley/vacuum`) + +| Attribute | Value | +|---|---| +| OpenAPI 3.1 | Full support (via libopenapi) | +| Install | `brew install daveshanley/vacuum/vacuum` or Go binary | +| CLI | `vacuum lint api.yaml` | +| License | MIT | +| Maintenance | Active — latest release 2025-12-22 | +| GitHub | ~1k stars | + +**Checks:** Structural validation, Spectral-compatible rulesets, OWASP security checks, naming conventions, descriptions/examples/tags. Single Go binary — no runtime dependencies. + +**Fit for this project:** Zero-dependency binary is appealing for CI. However, adds a non-Node.js tool dependency when the project already has Node.js. Spectral ruleset compatibility is a plus for portability. + +### oasdiff (`oasdiff/oasdiff`) + +| Attribute | Value | +|---|---| +| OpenAPI 3.1 | Beta | +| Install | `brew install oasdiff` or Go binary | +| CLI | `oasdiff breaking base.yaml revision.yaml` | +| License | Apache 2.0 | +| Maintenance | Active — latest v1.11.10 (2026-02-05) | +| GitHub | ~1.1k stars | + +**Checks:** 300+ breaking change detection rules (paths, parameters, schemas, security, headers, enums). Requires two spec versions to compare — not a standalone validator. + +**Fit for this project:** Different category — detects breaking changes between spec versions, not structural validity. Useful as a CI-only check comparing `HEAD~1` vs `HEAD`. OAS 3.1 support is still beta. + +### Not Recommended + +- **swagger-cli:** Abandoned, no OAS 3.1 support +- **IBM OpenAPI Validator:** Active but opinionated IBM-specific rules add configuration overhead for no benefit + +## Tool Comparison Matrix + +| Tool | OAS 3.1 | License | Last Release | Stars | Runtime | Category | +|---|---|---|---|---|---|---| +| **Redocly CLI** | Full | MIT | 2026-03-03 | ~1.4k | Node.js | Lint + validate | +| **Spectral** | Full | Apache 2.0 | 2025-04-22 | ~3k | Node.js | Lint | +| **Vacuum** | Full | MIT | 2025-12-22 | ~1k | Go binary | Lint + validate | +| **oasdiff** | Beta | Apache 2.0 | 2026-02-05 | ~1.1k | Go binary | Breaking changes | + +## Integration Pattern + +### Hook Script Structure + +An OpenAPI validation hook would follow the existing pattern in `.claude/hooks/`: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +INPUT=$(cat) +FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('file_path',''))" 2>/dev/null || echo "") + +# Only run for OpenAPI spec files +case "$FILE_PATH" in + */openapi/*.yaml|*/openapi/*.yml) ;; + *) exit 0 ;; +esac + +cd "$CLAUDE_PROJECT_DIR/backend" + +# Run validation +if OUTPUT=$(npx @redocly/cli@latest lint src/main/resources/openapi/api.yaml --format=stylish 2>&1); then + echo '{"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"✓ OpenAPI spec validation passed."}}' +else + ESCAPED=$(echo "$OUTPUT" | python3 -c "import sys,json; print(json.dumps(sys.stdin.read()))") + echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":$ESCAPED}}" +fi +``` + +### Registration in `.claude/settings.json` + +The hook would be added to the existing `PostToolUse` array alongside the compile and lint hooks: + +```json +{ + "type": "command", + "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/openapi-validate.sh\"", + "timeout": 120 +} +``` + +### Configuration (Optional) + +A `redocly.yaml` in the project root or `backend/` directory can customize rules: + +```yaml +extends: + - recommended + +rules: + operation-operationId: error + tag-description: warn + no-ambiguous-paths: error +``` + +## Code References + +- `.claude/settings.json:1-32` — Hook configuration (PostToolUse + Stop events) +- `.claude/hooks/backend-compile-check.sh` — Java file detection pattern + compile check +- `.claude/hooks/frontend-check.sh` — TS/Vue file detection pattern + type-check + lint +- `.claude/hooks/run-tests.sh` — Stop hook with test execution and block/approve logic +- `backend/pom.xml:149-178` — openapi-generator-maven-plugin configuration +- `backend/src/main/resources/openapi/api.yaml` — The OpenAPI 3.1.0 spec to validate + +## Open Questions + +- Should the validation use a pinned version (`npx @redocly/cli@1.x.x`) or latest? Pinned is more reproducible; latest gets rule updates automatically. +- Should a `redocly.yaml` config be added immediately with the `recommended` ruleset, or start with zero-config (structural validation only) and add rules incrementally? +- Is breaking change detection (oasdiff) desirable as a separate CI check, or is structural validation sufficient for now?