Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .cursor/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"version": 1,
"hooks": {
"afterMCPExecution": [
{
"command": ".cursor/hooks/sonar-track-analysis.sh",
"matcher": "analyze_file_list|analyze_code_snippet"
}
],
"postToolUse": [
{
"command": ".cursor/hooks/sonar-track-analysis.sh",
"matcher": "CallDynamicTool|MCP:.*(analyze_file_list|analyze_code_snippet)"
}
],
"beforeShellExecution": [
{
"command": ".cursor/hooks/sonar-gate-commit.sh",
"matcher": "git\\s+commit",
"failClosed": true
}
],
"preToolUse": [
{
"command": ".cursor/hooks/sonar-secrets/build-scripts/pre-tool-use-secrets.sh",
"matcher": "Read",
"timeout": 60,
"failClosed": false
}
],
"beforeReadFile": [
{
"command": ".cursor/hooks/sonar-secrets/build-scripts/before-read-file-secrets.sh",
"matcher": "Read|TabRead",
"timeout": 60,
"failClosed": false
}
],
"beforeSubmitPrompt": [
{
"command": ".cursor/hooks/sonar-secrets/build-scripts/prompt-secrets.sh",
"matcher": "UserPromptSubmit",
"timeout": 60,
"failClosed": false
}
]
}
}
62 changes: 62 additions & 0 deletions .cursor/hooks/sonar-gate-commit.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env bash
# Deny agent git commits that stage JS/TS/PHP unless Sonar MCP analysis ran this session.
set -euo pipefail

input=$(cat)
command=$(printf '%s' "$input" | node -e '
let d=""; process.stdin.on("data",c=>d+=c); process.stdin.on("end",()=>{
try { console.log(JSON.parse(d).command||""); } catch { console.log(""); }
});
')

# Only gate real commits (not commit --help / dry-run style if ever passed oddly).
if ! printf '%s' "$command" | grep -Eq '(^|[[:space:];|&])git[[:space:]]+commit([[:space:]]|$)'; then
printf '%s\n' '{"permission":"allow"}'
exit 0
fi

root="${CURSOR_PROJECT_DIR:-.}"
cd "$root"

staged=$(git diff --cached --name-only --diff-filter=ACMR 2>/dev/null || true)
analyzed=$(printf '%s\n' "$staged" | grep -E '\.(ts|tsx|js|jsx|php)$' || true)

if [[ -z "$analyzed" ]]; then
printf '%s\n' '{"permission":"allow"}'
exit 0
fi

stamp_file="$root/.git/sonar-agent-analyzed"
if [[ ! -f "$stamp_file" ]]; then
node -e '
const msg = "Sonar before commit: staged JS/TS/PHP files require SonarQube MCP analyze_code_snippet on each changed file first (see AGENTS.md). Fix any issues, then retry the commit. Lefthook/Biome is not a substitute.";
console.log(JSON.stringify({
permission: "deny",
user_message: "Blocked: run SonarQube MCP analyze_code_snippet on staged JS/TS/PHP before committing.",
agent_message: msg
}));
'
exit 0
fi

# Require a successful analysis stamp from the last hour (same agent session window).
if ! node -e '
const fs = require("fs");
const path = process.argv[1];
const st = fs.statSync(path);
const ageMs = Date.now() - st.mtimeMs;
process.exit(ageMs <= 60 * 60 * 1000 ? 0 : 1);
' "$stamp_file"; then
node -e '
const msg = "Sonar before commit: previous analyze_code_snippet stamp is stale (>1h). Re-run SonarQube MCP analyze_code_snippet on each staged JS/TS/PHP file, then retry the commit.";
console.log(JSON.stringify({
permission: "deny",
user_message: "Blocked: Sonar analysis stamp is stale. Re-analyze staged JS/TS/PHP before committing.",
agent_message: msg
}));
'
exit 0
fi

printf '%s\n' '{"permission":"allow"}'
exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
if ! command -v sonar &> /dev/null; then
exit 0
fi
sonar hook cursor-pre-file-read
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
if ! command -v sonar &> /dev/null; then
exit 0
fi
sonar hook cursor-pre-tool-use
5 changes: 5 additions & 0 deletions .cursor/hooks/sonar-secrets/build-scripts/prompt-secrets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash
if ! command -v sonar &> /dev/null; then
exit 0
fi
sonar hook cursor-prompt-submit
38 changes: 38 additions & 0 deletions .cursor/hooks/sonar-track-analysis.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Record successful SonarQube MCP live-analysis calls for the commit gate.
set -euo pipefail

input=$(cat)
root="${CURSOR_PROJECT_DIR:-$(pwd)}"
stamp_dir="${root}/.git"
stamp_file="${stamp_dir}/sonar-agent-analyzed"

is_analyze=$(printf '%s' "$input" | node -e '
let d=""; process.stdin.on("data",c=>d+=c); process.stdin.on("end",()=>{
try {
const j=JSON.parse(d);
const blob=JSON.stringify(j).toLowerCase();
const hit =
blob.includes("analyze_file_list") ||
blob.includes("analyze_code_snippet") ||
/analyze_file_list|analyze_code_snippet/.test(
String(j.toolName||j.tool_name||j.name||j.tool||"")
);
const failed =
/"status"\s*:\s*"(error|failed|failure|denied)"/i.test(blob) ||
j.error != null;
process.stdout.write(hit && !failed ? "yes" : "no");
} catch {
process.stdout.write("no");
}
});
')

if [[ "$is_analyze" != "yes" ]]; then
exit 0
fi

mkdir -p "$stamp_dir"
date -u +%Y-%m-%dT%H:%M:%SZ >> "$stamp_file"
tail -n 50 "$stamp_file" > "${stamp_file}.tmp" && mv "${stamp_file}.tmp" "$stamp_file"
exit 0
18 changes: 18 additions & 0 deletions .cursor/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"mcpServers": {
"sonarqube": {
"command": "sonar",
"args": [
"run",
"mcp",
"--project",
"tebuto_wordpress-plugin_d85838b5-fae9-48e3-9636-0ec145a330f3",
"--toolsets",
"analysis,ide,issues,quality-gates,rules,duplications,measures,security-hotspots,coverage"
],
"env": {
"SONARQUBE_IDE_PORT": "64120"
}
}
}
}
14 changes: 14 additions & 0 deletions .cursor/rules/sonarqube_mcp_instructions.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
description: SonarQube live analysis for agents
alwaysApply: true
---

# SonarQube (local)

Live analysis is SonarQube MCP `analyze_code_snippet` (workspace-mounted; pass project-relative `filePath`, `language`, `scope`). Use `analyze_file_list` instead only if that tool exists. Do not run `sonar-scanner`. Do not use `search_sonar_issues_in_projects` to verify edits (the server lags local analysis).

Project key is `sonar.projectKey` in `sonar-project.properties`. Do not look it up.

After creating or modifying `*.ts`, `*.tsx`, `*.js`, `*.jsx`, or `*.php`, analyze each changed file. Fix every issue, re-analyze, then finish. The same analysis is required before `git commit` of JS/TS/PHP (Lefthook/Biome/PHPCS is not a substitute).

Leave IDE automatic analysis on. Do not call `toggle_automatic_analysis`.
23 changes: 18 additions & 5 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,35 @@ updates:
directory: /
target-branch: main
schedule:
interval: monthly
interval: yearly
open-pull-requests-limit: 10
assignees:
- jonasscholl
groups:
all:
dependencies:
patterns:
- "*"

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
interval: yearly
open-pull-requests-limit: 5
assignees:
- JonasScholl
open-pull-requests-limit: 10
groups:
all:
github-actions:
patterns:
- "*"

- package-ecosystem: composer
directory: /
schedule:
interval: yearly
open-pull-requests-limit: 5
assignees:
- jonasscholl
groups:
composer:
patterns:
- "*"
13 changes: 13 additions & 0 deletions .github/workflows/branch.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
name: Branch

# Runner: ubuntu-latest (tebuto org) — see docs/ci.md.

on:
push:
branches:
- main
pull_request:

concurrency:
group: branch-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: Build & Lint
Expand Down Expand Up @@ -69,3 +75,10 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}

- name: SonarQube Quality Gate
uses: SonarSource/sonarqube-quality-gate-action@v1
timeout-minutes: 5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ vendor/

# Local tooling
.playwright-mcp/
.scannerwork/

.vscode/*
!.vscode/extensions.json
!.vscode/settings.json

# npm lockfiles (this repo uses pnpm)
package-lock.json
1 change: 0 additions & 1 deletion .husky/pre-commit

This file was deleted.

4 changes: 4 additions & 0 deletions .sonarlint/connectedMode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"sonarQubeUri": "https://sonar.artus-engineering.de",
"projectKey": "tebuto_wordpress-plugin_d85838b5-fae9-48e3-9636-0ec145a330f3"
}
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["biomejs.biome", "sonarsource.sonarlint-vscode"]
}
32 changes: 25 additions & 7 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
{
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"files.insertFinalNewline": true,
"files.trimTrailingWhitespace": true,
"editor.codeActionsOnSave": {
"source.fixAll.biome": "explicit",
"source.organizeImports.biome": "explicit"
},
"[javascript]": {
"editor.defaultFormatter": "vscode.json-language-features"
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "vscode.json-language-features"
"editor.defaultFormatter": "biomejs.biome"
},
"[jsonc]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[markdown]": {
"editor.defaultFormatter": "vscode.markdown-language-features"
"[css]": {
"editor.defaultFormatter": "biomejs.biome"
},
"markdown.extension.toc.levels": "1..6"
"sonarlint.connectedMode.project": {
"connectionId": "https-sonar-artus-engineering-de",
"projectKey": "tebuto_wordpress-plugin_d85838b5-fae9-48e3-9636-0ec145a330f3"
}
}
8 changes: 6 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,13 @@ pnpm version:bump 2.3.0 # Bump version in all tracked files (or patch/minor/ma

Local WordPress: http://localhost:8000 — plugin path `wordpress/wp-content/plugins/tebuto-online-terminbuchung/`.

CI (`.github/workflows/branch.yaml`): `pnpm install --frozen-lockfile`, `composer install`, `lint`, `build:block`, `version:check`, `build.sh`.
CI (`.github/workflows/branch.yaml`): `pnpm install --frozen-lockfile`, `composer install`, `lint`, `build:block`, `version:check`, `build.sh` → SonarQube scan + quality gate on `ubuntu-latest`. See [`docs/ci.md`](docs/ci.md).

Pre-commit (Husky): runs `pnpm lint:fix` on the whole codebase (Biome + PHPCS). Requires `pnpm install` and `composer install`.
**Lefthook** formats/lints staged JS/JSON/CSS on commit (`lefthook.yml`). Run `pnpm lint` / `pnpm lint:php` for PHP before committing PHP changes.

**SonarQube:** `sonar-project.properties`, SonarLint, Cursor MCP `analyze_code_snippet` on changed JS/TS/PHP before agent commits. See `.cursor/rules/sonarqube_mcp_instructions.mdc`. Lefthook/Biome/PHPCS is not a substitute.

**Dependabot** runs yearly; weekly updates via Cursor Automation. Cross-project guide: Artus portal wiki **Repository Tooling (SonarQube, CI, Cursor Agents)**.

## Coding conventions

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
19 changes: 19 additions & 0 deletions docs/ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# CI (GitHub Actions)

Runner: `ubuntu-latest` for tebuto org repos. Cross-project tooling: Artus portal wiki **Repository Tooling (SonarQube, CI, Cursor Agents)**.

## Workflows

| File | Purpose |
| --- | --- |
| `.github/workflows/branch.yaml` | Build, lint (JS + PHP) → SonarQube scan → quality gate |

## Required checks

After rollout, enable on `main`:

- **Build & Lint**
- **SonarQube Scan**
- **SonarQube Quality Gate**

Secrets: `SONAR_TOKEN`, `SONAR_HOST_URL`.
6 changes: 6 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pre-commit:
commands:
check:
glob: "*.{js,ts,mts,cts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc,css}"
run: pnpm exec biome check --write --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
stage_fixed: true
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"lint:php": "composer lint",
"lint:php:fix": "composer fix || [ $? -eq 1 ]",
"lint:fix": "pnpm format && pnpm lint:php:fix && pnpm lint",
"prepare": "husky",
"prepare": "node -e \"const on=v=>!!v&&v!=='0'&&v!=='false';if(on(process.env.CI)&&!on(process.env.LEFTHOOK))process.exit(0);try{require('child_process').execSync('lefthook install --reset-hooks-path',{stdio:'inherit'})}catch{}\"",
"start": "pnpm --filter tebuto-online-terminbuchung-block run start",
"dev:setup": "./scripts/dev-setup.sh",
"dev:up": "docker compose up -d",
Expand Down Expand Up @@ -53,6 +53,6 @@
"@biomejs/biome": "2.5.9",
"chokidar-cli": "^3",
"concurrently": "^10.0.5",
"husky": "^9"
"lefthook": "^2.1.10"
}
}
Loading