Web quality auditor — Performance, SEO, Accessibility, Best Practices
Tengu : XWA submodule focused on web quality auditing
Tengu is a web quality auditor that evaluates web applications across four dimensions:
| Category | Scope |
|---|---|
| Performance | Page weight, Core Web Vitals (LCP, CLS, INP), resource waterfall, image optimization, font loading, cache policy, compression, third-party impact |
| SEO | Title tags, meta descriptions, heading hierarchy, canonical URLs, Open Graph, Twitter Cards, JSON-LD, sitemaps, robots.txt, hreflang, redirect chains, broken links |
| Accessibility | Alt text, heading structure, ARIA attributes, landmarks, color contrast (WCAG AA/AAA), keyboard navigation, form labels, link text, tables, iframes, viewport configuration |
| Best Practices | HTTPS enforcement, security headers (HSTS, CSP, XFO, etc.), cookie audit, GDPR banner detection, doctype validation, deprecated HTML, mixed content, SRI, console errors |
Local-first: SQLite persistence by default, no external services required. The backend is Python 3.12+ (FastAPI + httpx + BeautifulSoup/lxml).
./tengu.sh # same as: ./tengu.sh localStarts the Python backend on http://localhost:8070 with a SQLite database at
<repo>/tengu.db and serves the prebuilt Angular frontend when present
(static/browser/ or frontend/dist/). The script uses uv to create the
backend environment on first run; install it with:
curl -LsSf https://astral.sh/uv/install.sh | shcd backend
uv sync
TENGU_DB_PATH=/tmp/tengu.db uv run python -m tengu --port 8070The SPA is Angular 22 (standalone, zoneless, TypeScript 6, @angular/build,
Vitest). Node 24 is required.
export PATH="$HOME/.local/share/mise/installs/node/24/bin:$PATH"
cd frontend
npm ci # reproducible install from package-lock.json
npm test # Vitest unit tests (api, WS Event parsing, severity, exports)
npm run build # production build -> ../static (outputHashing: none)
npm start # dev server on :4270, talks to :8070The production build is written to <repo>/static/browser and served by the
FastAPI SPA fallback (STATIC_DIR). outputHashing: none keeps
main.js/styles.css stable so the backend can serve them without a manifest.
The backend never compiles the frontend; use ./tengu.sh build or the npm
commands above.
docker compose up -d --buildWeb UI and API: http://localhost:8070. The SQLite database lives in the
tengu_data volume at /data/tengu.db.
./tengu.sh # local (default): SQLite native, :8070
./tengu.sh local # explicit local mode
./tengu.sh docker # docker compose (persistent volume)
./tengu.sh docker --rm # ephemeral container
./tengu.sh build # build frontend only
./tengu.sh export [file] # start + export audits to ./exports/
./tengu.sh import <file> # start + import audits from file
./tengu.sh clean # delegate to ./clean.sh
./tengu.sh -b # force frontend rebuild before startingThe script prefers Node 24 from mise (~/.local/share/mise/installs/node/24/bin)
when building the frontend. It runs npm ci (falling back to npm install only
if the lockfile is missing) when frontend/node_modules is absent.
| Variable | Default | Description |
|---|---|---|
PORT |
8070 |
HTTP listen port (XWA standard) |
STATIC_DIR |
static/browser |
Directory served by the SPA fallback |
TENGU_DB_PATH |
tengu.db (/data/tengu.db in Docker) |
SQLite database file |
TENGU_MAX_HISTORY |
100 |
Maximum persisted audits (retention) |
TENGU_API_KEY |
— | Optional API key (X-API-Key or Authorization: Bearer) |
TENGU_HTTP_TIMEOUT |
30 |
HTTP client timeout (seconds) |
TENGU_HTTP_MAX_REDIRECTS |
10 |
Redirect limit |
TENGU_HTTP_RETRY |
2 |
Retries with exponential backoff + jitter |
TENGU_USER_AGENT |
Tengu/0.2.0 (+https://github.com/xwebanalysis/tengu) |
Crawler user agent |
TENGU_RATE_LIMIT_PER_MINUTE |
120 |
REST + WS token bucket rate |
TENGU_RATE_LIMIT_BURST |
30 |
Token bucket burst |
TENGU_MAX_CONCURRENT |
3 |
Concurrent audits |
TENGU_MAX_PAGES |
50 |
Maximum pages per full-site crawl |
TENGU_CRAWL_DEPTH |
2 |
Crawl depth from the entry URL |
TENGU_CRAWL_DELAY_MIN_MS / _MAX_MS |
200 / 800 |
Delay with jitter between crawl requests |
TENGU_ROBOTS_TXT |
1 |
Respect robots.txt during crawls (0 disables) |
XWA_CORS_ORIGINS |
localhost/LAN | Comma-separated allowed origins (credentials always disabled) |
| Method | Path | Description |
|---|---|---|
GET |
/api/health |
JSON health: {"status","service","version","database"} |
GET |
/api/metrics |
Prometheus text metrics |
GET |
/api/audit/live |
WebSocket streaming xwa-sdk Event envelopes |
GET |
/api/audits |
List persisted audits (legacy shape) |
GET |
/api/audits/{id} |
Get an audit (legacy shape) |
DELETE |
/api/audits/{id} |
Delete an audit |
DELETE |
/api/audits/clear |
Clear all audits |
GET |
/api/audits/export |
Export all audits as JSON |
POST |
/api/audits/import |
Import audits from JSON |
GET |
/api/analyses |
List analyses as xwa-sdk Analysis |
GET |
/api/analyses/{id} |
Analysis + findings (xwa-sdk contract) |
DELETE |
/api/analyses/{id} |
Delete an analysis |
GET |
/api/analyses/{id}/export?format=json|csv |
Download analysis (Content-Disposition) |
GET /api/audit/live?url=<target>&mode=<single|fullsite|batch>&checks=...
Every frame is a JSON Event from xwa-sdk 0.2.0:
{
"seq": 3,
"type": "item_found",
"tool": "tengu",
"analysis_id": "df2c…",
"ts": "2026-09-12T10:00:00Z",
"payload": {
"tool": "tengu",
"severity": "medium",
"title": "Missing alt attribute",
"description": "…",
"category": "accessibility",
"check": "alt_text",
"target_url": "https://example.com/",
"evidence": {"snippet": "<img src=\"…\">"}
}
}Event flow: analysis_started → analysis_progress* → item_found* →
log (pretty-printed HTML source) → analysis_completed (with summary) or
analysis_error (with the xwa-sdk error envelope).
Severity mapping: Pass → pass, Info → info, Warning → medium,
Error → high. The Angular client parses every frame with parseEvent()
(frontend/src/app/core/events.ts) and rebuilds exactly what the legacy text
protocol provided: meta/log lines in the terminal, crawled pages from
[PAGE] <url> progress messages, findings from item_found, the pretty HTML
from the log/html_source payload (data.html), and completion/error state
from analysis_completed/analysis_error.
Full-site mode honors robots.txt by default, paces requests with a random
200–800 ms delay, caps the crawl with TENGU_MAX_PAGES/TENGU_CRAWL_DEPTH,
and retries transient failures with exponential backoff + jitter.
tengu/
├── backend/
│ ├── pyproject.toml # Python 3.12+ dependencies (uv)
│ ├── uv.lock # Reproducible backend environment
│ ├── tengu/
│ │ ├── main.py # CLI entry point
│ │ ├── api.py # FastAPI REST + WebSocket + SPA fallback
│ │ ├── config.py # Environment configuration
│ │ ├── models.py # xwa-sdk 0.2.0 contracts
│ │ ├── storage.py # SQLite persistence (legacy schema compatible)
│ │ ├── events.py # xwa-sdk Event emitter
│ │ ├── http.py # httpx client, retries, robots/crawl/batch
│ │ └── audit/ # Performance, SEO, a11y, best-practices engines
│ └── tests/ # pytest suite (API, WS, storage, auditors)
├── frontend/ # Angular 22 SPA (zoneless, Nothing Design, Vitest)
│ ├── src/app/core/ # api, ws/event parsing, theme, i18n, exports
│ ├── src/app/shared/ # terminal, html-viewer, metric-card, tags, exports
│ └── src/app/features/ # audit, history
├── docs/ # Technical documentation
├── Dockerfile # Multi-stage Python + Angular build
├── docker-compose.yml # Single service with persistent SQLite volume
├── tengu.sh # Local-first launch script (uv + Python)
└── clean.sh # Local + Docker cleanup (removes tengu.db*)
FastAPI, Uvicorn, httpx, BeautifulSoup 4, lxml, and the Python standard
library sqlite3. The backend is managed by uv; see
backend/pyproject.toml and
docs/python-libraries.md.
| Document | Description |
|---|---|
| docs/manual.md | Deployment and usage manual |
| docs/ui-architecture.md | Frontend architecture overview |
| docs/python-libraries.md | Python backend dependency inventory |
| ROADMAP.md | Development phases and milestones |
| CHANGELOG.md | Release history and version log |
| SECURITY.md | Security policy and reporting |
Proprietary — All rights reserved. See LICENSE for the full terms. For permission requests: x@xscriptor.com