wythe 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. wythe-0.1.0/.gitignore +28 -0
  2. wythe-0.1.0/LICENSE +21 -0
  3. wythe-0.1.0/PKG-INFO +230 -0
  4. wythe-0.1.0/README.md +186 -0
  5. wythe-0.1.0/backend/app/__init__.py +3 -0
  6. wythe-0.1.0/backend/app/adapters/__init__.py +50 -0
  7. wythe-0.1.0/backend/app/adapters/a11y_inspect.py +134 -0
  8. wythe-0.1.0/backend/app/adapters/autodetect.py +195 -0
  9. wythe-0.1.0/backend/app/adapters/base.py +353 -0
  10. wythe-0.1.0/backend/app/adapters/db_catalog_probe.py +179 -0
  11. wythe-0.1.0/backend/app/adapters/django_truth.py +249 -0
  12. wythe-0.1.0/backend/app/adapters/drizzle_truth.py +191 -0
  13. wythe-0.1.0/backend/app/adapters/frontend_common.py +209 -0
  14. wythe-0.1.0/backend/app/adapters/go_http.py +360 -0
  15. wythe-0.1.0/backend/app/adapters/legacy_snapshot_bridge.py +165 -0
  16. wythe-0.1.0/backend/app/adapters/postgres_sqlalchemy.py +311 -0
  17. wythe-0.1.0/backend/app/adapters/prisma_truth.py +215 -0
  18. wythe-0.1.0/backend/app/adapters/python_ast_inspect.py +331 -0
  19. wythe-0.1.0/backend/app/adapters/python_django.py +394 -0
  20. wythe-0.1.0/backend/app/adapters/python_fastapi.py +311 -0
  21. wythe-0.1.0/backend/app/adapters/redis_ephemeral.py +454 -0
  22. wythe-0.1.0/backend/app/adapters/rust_http.py +393 -0
  23. wythe-0.1.0/backend/app/adapters/rust_inspect.py +144 -0
  24. wythe-0.1.0/backend/app/adapters/rust_sql_truth.py +240 -0
  25. wythe-0.1.0/backend/app/adapters/rust_wasm_display.py +279 -0
  26. wythe-0.1.0/backend/app/adapters/sql_ddl_truth.py +165 -0
  27. wythe-0.1.0/backend/app/adapters/sveltekit_display.py +168 -0
  28. wythe-0.1.0/backend/app/adapters/treesitter_engine.py +388 -0
  29. wythe-0.1.0/backend/app/adapters/typescript_nextjs.py +522 -0
  30. wythe-0.1.0/backend/app/adapters/typescript_node.py +472 -0
  31. wythe-0.1.0/backend/app/adapters/typescript_react.py +143 -0
  32. wythe-0.1.0/backend/app/adapters/vue_sfc.py +122 -0
  33. wythe-0.1.0/backend/app/api/__init__.py +1 -0
  34. wythe-0.1.0/backend/app/api/routes/__init__.py +28 -0
  35. wythe-0.1.0/backend/app/api/routes/adapters.py +38 -0
  36. wythe-0.1.0/backend/app/api/routes/ask.py +38 -0
  37. wythe-0.1.0/backend/app/api/routes/focus.py +48 -0
  38. wythe-0.1.0/backend/app/api/routes/mcp_http.py +53 -0
  39. wythe-0.1.0/backend/app/api/routes/rules.py +32 -0
  40. wythe-0.1.0/backend/app/api/routes/rulings.py +44 -0
  41. wythe-0.1.0/backend/app/api/routes/scans.py +229 -0
  42. wythe-0.1.0/backend/app/api/routes/targets.py +73 -0
  43. wythe-0.1.0/backend/app/api/routes/vocabularies.py +48 -0
  44. wythe-0.1.0/backend/app/cli.py +431 -0
  45. wythe-0.1.0/backend/app/core/clock.py +20 -0
  46. wythe-0.1.0/backend/app/core/config.py +45 -0
  47. wythe-0.1.0/backend/app/core/policy_context.py +49 -0
  48. wythe-0.1.0/backend/app/db/base.py +9 -0
  49. wythe-0.1.0/backend/app/db/seed.py +429 -0
  50. wythe-0.1.0/backend/app/db/session.py +45 -0
  51. wythe-0.1.0/backend/app/main.py +73 -0
  52. wythe-0.1.0/backend/app/models/__init__.py +18 -0
  53. wythe-0.1.0/backend/app/models/audit_ruling.py +35 -0
  54. wythe-0.1.0/backend/app/models/policy_vocabulary.py +28 -0
  55. wythe-0.1.0/backend/app/models/rule_policy.py +31 -0
  56. wythe-0.1.0/backend/app/models/scan_snapshot.py +64 -0
  57. wythe-0.1.0/backend/app/models/stack_profile.py +28 -0
  58. wythe-0.1.0/backend/app/models/target_project.py +37 -0
  59. wythe-0.1.0/backend/app/schemas/__init__.py +49 -0
  60. wythe-0.1.0/backend/app/schemas/atlas.py +595 -0
  61. wythe-0.1.0/backend/app/services/__init__.py +32 -0
  62. wythe-0.1.0/backend/app/services/apple_fm_bridge.swift +154 -0
  63. wythe-0.1.0/backend/app/services/apple_fm_service.py +179 -0
  64. wythe-0.1.0/backend/app/services/diff_and_sarif_service.py +307 -0
  65. wythe-0.1.0/backend/app/services/gate_service.py +152 -0
  66. wythe-0.1.0/backend/app/services/governance_service.py +347 -0
  67. wythe-0.1.0/backend/app/services/llm_context_service.py +367 -0
  68. wythe-0.1.0/backend/app/services/mcp_service.py +352 -0
  69. wythe-0.1.0/backend/app/services/on_device_llm_service.py +377 -0
  70. wythe-0.1.0/backend/app/services/portable_config_service.py +264 -0
  71. wythe-0.1.0/backend/app/services/scanner_service.py +455 -0
  72. wythe-0.1.0/backend/app/services/source_peek_service.py +99 -0
  73. wythe-0.1.0/backend/app/services/ui_focus_service.py +322 -0
  74. wythe-0.1.0/backend/app/ui_dist/404.html +1 -0
  75. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/255-15b9cc804e0d9c7d.js +1 -0
  76. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/271.0e22de97daf8bd5d.js +1 -0
  77. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/484-3d0aa024c79e43c2.js +1 -0
  78. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/4bd1b696-c023c6e3521b1417.js +1 -0
  79. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/587.bfba9c61b4b8b4dd.js +1 -0
  80. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/854-20e64eb36f1ea9bd.js +1 -0
  81. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/app/_not-found/page-34ccb94a53b07c2e.js +1 -0
  82. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/app/layout-2a1afe0e8af2e7d3.js +1 -0
  83. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/app/page-db1dd94b92580ee1.js +1 -0
  84. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/b536a0f1.6112d0b47631385b.js +1 -0
  85. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/framework-28b60818f5cffd83.js +1 -0
  86. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/main-304d3bdb216e1c53.js +1 -0
  87. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/main-app-d5f696847cf7b405.js +1 -0
  88. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/pages/_app-7d307437aca18ad4.js +1 -0
  89. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/pages/_error-cb2a52f75f2162e2.js +1 -0
  90. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  91. wythe-0.1.0/backend/app/ui_dist/_next/static/chunks/webpack-c2a1ca9f6b7eac69.js +1 -0
  92. wythe-0.1.0/backend/app/ui_dist/_next/static/css/635f7fcdb3fbfb46.css +1 -0
  93. wythe-0.1.0/backend/app/ui_dist/_next/static/nVGQZh6kCJlURomjaOCjL/_buildManifest.js +1 -0
  94. wythe-0.1.0/backend/app/ui_dist/_next/static/nVGQZh6kCJlURomjaOCjL/_ssgManifest.js +1 -0
  95. wythe-0.1.0/backend/app/ui_dist/icon.svg +11 -0
  96. wythe-0.1.0/backend/app/ui_dist/index.html +1 -0
  97. wythe-0.1.0/backend/app/ui_dist/index.txt +21 -0
  98. wythe-0.1.0/backend/schema.sql +124 -0
  99. wythe-0.1.0/backend/tests/conftest.py +38 -0
  100. wythe-0.1.0/backend/tests/test_api_and_cli.py +155 -0
  101. wythe-0.1.0/backend/tests/test_db_domain_truth.py +218 -0
  102. wythe-0.1.0/backend/tests/test_diff_sarif_portable_and_redis.py +434 -0
  103. wythe-0.1.0/backend/tests/test_llm_and_mcp.py +408 -0
  104. wythe-0.1.0/backend/tests/test_multilang_django_and_go.py +237 -0
  105. wythe-0.1.0/backend/tests/test_multilang_ts_and_db.py +200 -0
  106. wythe-0.1.0/backend/tests/test_queues_a11y_and_compare.py +203 -0
  107. wythe-0.1.0/backend/tests/test_rust_stacks.py +249 -0
  108. wythe-0.1.0/backend/tests/test_three_tier_scanner.py +308 -0
  109. wythe-0.1.0/backend/tests/test_treesitter_engine.py +191 -0
  110. wythe-0.1.0/pyproject.toml +96 -0
wythe-0.1.0/.gitignore ADDED
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .venv/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .mypy_cache/
9
+ *.egg-info/
10
+ dist/
11
+ build/
12
+ *.db
13
+ *.sqlite3
14
+
15
+ # Node / Next.js
16
+ node_modules/
17
+ .next/
18
+ out/
19
+ coverage/
20
+ next-env.d.ts
21
+ *.tsbuildinfo
22
+
23
+ # OS / Local state
24
+ .DS_Store
25
+ .local/
26
+ .env
27
+ .env.*
28
+ !.env.example
wythe-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aaron Montgomery
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
wythe-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,230 @@
1
+ Metadata-Version: 2.5
2
+ Name: wythe
3
+ Version: 0.1.0
4
+ Summary: Why -> Therefore: 3-tier architecture verifier, cross-boundary lineage solver, and 3D WebGL atlas.
5
+ Project-URL: Homepage, https://github.com/Euda1mon1a/wythe
6
+ Project-URL: Repository, https://github.com/Euda1mon1a/wythe
7
+ Project-URL: Issues, https://github.com/Euda1mon1a/wythe/issues
8
+ Author: Aaron Montgomery
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: accessibility,architecture,lineage,mcp,openapi,sarif,static-analysis,three-tier
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Environment :: Web Environment
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Requires-Python: >=3.12
21
+ Requires-Dist: alembic>=1.14.0
22
+ Requires-Dist: fastapi>=0.115.0
23
+ Requires-Dist: httpx>=0.27.0
24
+ Requires-Dist: psycopg[binary]>=3.2.0
25
+ Requires-Dist: pydantic>=2.9.0
26
+ Requires-Dist: pyyaml>=6.0.2
27
+ Requires-Dist: sqlalchemy>=2.0.35
28
+ Requires-Dist: uvicorn>=0.32.0
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8.3.0; extra == 'dev'
31
+ Requires-Dist: ruff>=0.7.0; extra == 'dev'
32
+ Requires-Dist: tree-sitter-go>=0.23.0; extra == 'dev'
33
+ Requires-Dist: tree-sitter-javascript>=0.23.0; extra == 'dev'
34
+ Requires-Dist: tree-sitter-rust>=0.23.0; extra == 'dev'
35
+ Requires-Dist: tree-sitter-typescript>=0.23.0; extra == 'dev'
36
+ Requires-Dist: tree-sitter>=0.23.0; extra == 'dev'
37
+ Provides-Extra: treesitter
38
+ Requires-Dist: tree-sitter-go>=0.23.0; extra == 'treesitter'
39
+ Requires-Dist: tree-sitter-javascript>=0.23.0; extra == 'treesitter'
40
+ Requires-Dist: tree-sitter-rust>=0.23.0; extra == 'treesitter'
41
+ Requires-Dist: tree-sitter-typescript>=0.23.0; extra == 'treesitter'
42
+ Requires-Dist: tree-sitter>=0.23.0; extra == 'treesitter'
43
+ Description-Content-Type: text/markdown
44
+
45
+ <p align="center">
46
+ <picture>
47
+ <source media="(prefers-color-scheme: light)" srcset="docs/icon/icon-light.svg">
48
+ <img src="docs/icon/icon-dark.svg" width="96" height="96" alt="Wythe icon — three architectural wythe bars mortised by a Why (?) triskelion and Therefore (∴) core">
49
+ </picture>
50
+ </p>
51
+
52
+ # Wythe
53
+
54
+ **Why (`?`) → Therefore (`∴`)** — Generalizable 3-tier architecture verifier, cross-boundary lineage solver, interactive 3D WebGL latticework visualizer, and fail-closed CI gate built around three masonry wythe invariants:
55
+
56
+ 1. **Database holds truth** — Relational schemas (`PostgreSQL`, `MySQL`, `SQLite` via `SQLAlchemy 2.0` + `Alembic`, `Prisma`, `Drizzle ORM`, `Django ORM`, Rust `sqlx`/`Diesel`/`SeaORM`, or raw SQL DDL/`sqlc` + optional read-only catalog probes) own domain policy, thresholds, temporal validity, and write-lane guards, while `Redis` keyspaces and worker queues are modeled explicitly as ephemeral stores/lanes rather than durable shadow truth.
57
+ 2. **Backend manipulates truth** — Services, background workers, solvers, and routers (`Python/FastAPI`, `TypeScript/Node/Express/Hono/Fastify/NestJS/Next.js/SvelteKit`, `Python/Django/DRF/Ninja`, `Go net/http/chi/gin/echo`, `Rust axum/actix-web/rocket/Tauri`) execute mechanisms over database rows behind explicit transactional service/SQL doors without embedding shadow policy literals or host-clock drift.
58
+ 3. **Frontend displays truth** — UI surfaces (`Next.js`, `React/Vite/TanStack Router/Remix`, `Vue 3/Nuxt`, `Svelte 5/SvelteKit`, `Rust WASM Leptos/Dioxus/Yew` + Tauri `invoke()`) bind to typed contracts, centralized clock utilities, and WCAG-accessible markup without inventing client-side policy maps or unbacked HTTP/IPC calls.
59
+
60
+ Wythe supports **18 full-stack profiles** out of the box via `--profile auto`, pairs Python's native `ast` with an optional **Tree-sitter CST grammar engine** (`wythe[treesitter]` for TypeScript/TSX, JavaScript, Rust, and Go), traces background worker & scheduled beat queues (`Celery` / `Beat`, `BullMQ`, `Asynq`), enforces **WCAG accessibility semantics** (`DISP-005`), renders both **Simple Solids** and **120 fps Breathing Atomic Wythe Polyhedra** (with adaptive `System | Dark | Light` polarity and custom user palettes), and **self-dogfoods** its own 3-tier codebase (`wythe gate --strict wythe` passes with zero blocking and zero warning findings).
61
+
62
+ ---
63
+
64
+ ## Instant Usage (`uvx` / `uv`)
65
+
66
+ Run Wythe directly against any repository without polluting project dependencies (add `[treesitter]` to enable prebuilt Tree-sitter CST wheels for TS/TSX, JS, Rust, and Go):
67
+
68
+ ```bash
69
+ # Instant CLI gate & lineage scan from PyPI (once published) or directly from GitHub
70
+ uvx "wythe[treesitter]" gate --strict /path/to/repo
71
+ uvx --from "git+https://github.com/Euda1mon1a/wythe[treesitter]" wythe scan /path/to/repo
72
+ uvx --from "git+https://github.com/Euda1mon1a/wythe[treesitter]" wythe gate --strict /path/to/repo
73
+
74
+ # Launch single-port API + interactive 3D WebGL UI on http://127.0.0.1:8780 (no Node.js runtime needed)
75
+ uvx --from "git+https://github.com/Euda1mon1a/wythe[treesitter]" wythe serve /path/to/repo
76
+
77
+ # Or install persistently as an isolated CLI tool
78
+ uv tool install "git+https://github.com/Euda1mon1a/wythe[treesitter]"
79
+ wythe serve .
80
+ ```
81
+
82
+ ### GitHub Actions Integration
83
+
84
+ ```yaml
85
+ - uses: actions/checkout@v4
86
+ with:
87
+ fetch-depth: 0
88
+ - uses: Euda1mon1a/wythe@main
89
+ with:
90
+ mode: diff
91
+ base-ref: origin/main
92
+ sarif-file: wythe.sarif.json
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Persisted Rule Catalog (`atlas_rule_policies`)
98
+
99
+ Rule severity, enablement, gate-blocking behavior, and effective-date windows live in database truth (`atlas_rule_policies`) and round-trip cleanly to Git via `.wythe/governance.json`:
100
+
101
+ | Code | Tier | Default | Description |
102
+ | :--- | :--- | :--- | :--- |
103
+ | `TRUTH-001` | `truth` | Warning | Persisted input table has zero verified backend readers |
104
+ | `TRUTH-002` | `truth` | Blocking | ORM model, `schema.sql`/migrations, or live DB catalog table drift |
105
+ | `TRUTH-003` | `truth` | Blocking | Table or Redis store mutated directly inside HTTP route without service/DB door |
106
+ | `TRUTH-004` | `truth` | Warning | Unbounded Redis `SET`/`HSET` write without TTL risks volatile shadow truth |
107
+ | `MANIP-001` | `manipulate` | Blocking | Mutable domain policy/threshold constant hardcoded in code (`atlas_policy_vocabularies`) |
108
+ | `MANIP-002` | `manipulate` | Blocking | Direct host `date.today()` or naive `datetime.utcnow()` call |
109
+ | `MANIP-003` | `manipulate` | Warning | Service/solver module has zero verified inbound route, CLI, or worker callers |
110
+ | `DISP-001` | `display` | Blocking | Display tier defines domain policy threshold shadows in frontend code |
111
+ | `DISP-002` | `display` | Blocking | Raw `new Date()` or UTC `.toISOString().slice(0, 10)` date slicing |
112
+ | `DISP-003` | `display` | Warning | Frontend API client omits `api-generated` OpenAPI/schema types |
113
+ | `DISP-004` | `display` | Blocking | Frontend HTTP/IPC call targets a path absent from backend router graph |
114
+ | `DISP-005` | `display` | Warning | Display markup violates core WCAG semantics (missing `alt`, unlabeled form input, nameless icon button, non-keyboard `<div onClick>`) |
115
+
116
+ ---
117
+
118
+ ## CLI Commands
119
+
120
+ ```bash
121
+ # Scan any supported full-stack repository (--profile auto)
122
+ wythe scan /path/to/repo --json report.json --sarif report.sarif.json
123
+
124
+ # Optional metadata-only live DB & Redis keyspace probes (zero row or value reads)
125
+ wythe scan /path/to/repo \
126
+ --dsn "postgresql://readonly@127.0.0.1:5432/appdb" \
127
+ --redis-dsn "redis://127.0.0.1:6379/0"
128
+
129
+ # PR diff gate & directional 3-tier blast-radius slice
130
+ wythe diff /path/to/repo --base origin/main --sarif diff.sarif.json
131
+
132
+ # Git-portable governance bundle (.wythe/governance.json) for CI runners
133
+ wythe export-config /path/to/repo
134
+ wythe gate --sync-config /path/to/repo
135
+
136
+ # Single-port API + 3D WebGL UI (auto-registers target directory on startup)
137
+ wythe serve /path/to/repo --host 127.0.0.1 --port 8780
138
+ ```
139
+
140
+ ---
141
+
142
+ ## First-Class LLM & Human ↔ Vision/MCP Bridge (On-Device AI, Streamable HTTP MCP, OpenAPI & WebMCP)
143
+
144
+ Full JSON scans on large repositories span megabytes; Wythe exposes five zero-copy-paste Human ↔ LLM surfaces spanning bare-minimum on-device models through remote Tailnet agents:
145
+
146
+ 1. **Zero-Setup On-Device AI (`Ask AI [A]` / `⌘J`, `/api/v1/ask`)** — Zero-cloud, zero-API-key cascade with real-time Three.js camera-frustum & screen-color telemetry, `1024px` WebP vision frames, and rolling multi-turn sessions across:
147
+ - **Apple Intelligence (`apple`)**: macOS 26+/27 native `FoundationModels` helper (`arm64-apple-macos26.0`, serialized with `modelmanagerd` backoff).
148
+ - **Chrome Built-In (`chrome`)**: Client-side browser `LanguageModel` (Gemini Nano via Prompt API, including 1-click download and multimodal WebGL `<canvas>` vision when enabled).
149
+ - **Local OpenAI `/v1` (`local`)**: Auto-probes browser-host & server loopback `/v1/models` & `/v1/chat/completions` (`:8080`/`:8084`/`:8083` MLX, `:11434` Ollama, `:1234` LM Studio) without triggering weight reloads.
150
+ 2. **Streamable HTTP MCP (`POST /api/v1/mcp` & `GET /api/v1/mcp`) + Stdio MCP (`wythe mcp`)** — Connect local or remote Tailnet coding agents (`http://<host>:8780/api/v1/mcp`) with zero local install.
151
+ 3. **OpenAPI 3.1 (`/openapi.json`) + Remote CLI (`wythe focus --remote <url>`)** — Universal HTTP tool/Actions compatibility plus cross-host CLI X-Ray inspection.
152
+ 4. **In-Tab WebMCP (`navigator.modelContext` & `window.wythe`)** — Exposes `whatIsThis()`, `getLineageCone(query)`, `selectNode(nodeId)`, and `ask(question)` directly to browser agents and extensions.
153
+ 5. **Token-Budgeted `llms.txt` & Lineage Cone Slicer** — Deterministic ~2,000-token repo maps and ~300-token 3-tier cones.
154
+
155
+ ```bash
156
+ # 1. Token-budgeted llms.txt pointer map (~2,000 tokens regardless of repo size)
157
+ wythe llms /path/to/repo --max-tokens 2200 --out llms-wythe.txt
158
+
159
+ # 2. Surgical ~300-token 3-tier Lineage Cone around any table, file, route, or queue
160
+ wythe context /path/to/repo audit_exports
161
+ wythe context /path/to/repo /api/v1/scans --json
162
+
163
+ # 3. Zero-copy-paste Human <-> LLM Bridge (local state or remote private/Tailnet server)
164
+ wythe focus
165
+ wythe focus --remote http://100.64.0.10:8780
166
+
167
+ # 4. Zero-dependency read-only stdio MCP server (or connect directly to http://127.0.0.1:8780/api/v1/mcp)
168
+ wythe mcp
169
+ ```
170
+
171
+ MCP client configuration (Stdio or Streamable HTTP — exposes `wythe_what_is_this`, `wythe_list_targets`, `wythe_overview`, `wythe_lineage_cone`, `wythe_blast_radius`, `wythe_findings`):
172
+
173
+ ```json
174
+ {
175
+ "mcpServers": {
176
+ "wythe-http": {
177
+ "url": "http://127.0.0.1:8780/api/v1/mcp"
178
+ },
179
+ "wythe-stdio": {
180
+ "command": "uvx",
181
+ "args": ["--from", "git+https://github.com/Euda1mon1a/wythe[treesitter]", "wythe", "mcp"]
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## Supported Multi-Language & Multi-DB Adapters
190
+
191
+ | Stratum | Adapter Key | Stack Coverage |
192
+ | :--- | :--- | :--- |
193
+ | **Tier 1 (Truth)** | `postgres-sqlalchemy` | SQLAlchemy 2.0 + Alembic + `schema.sql` + PG/MySQL/SQLite live catalog |
194
+ | **Tier 1 (Truth)** | `prisma-schema` | Prisma `schema.prisma` (`@@map`) + `prisma/migrations/*.sql` |
195
+ | **Tier 1 (Truth)** | `drizzle-orm` | Drizzle `pgTable`/`mysqlTable`/`sqliteTable` + Drizzle SQL migrations |
196
+ | **Tier 1 (Truth)** | `django-orm` | Django `models.Model` (`Meta.db_table`) + `migrations/*.py` |
197
+ | **Tier 1 (Truth)** | `sql-ddl` | Multi-dialect SQL DDL + `golang-migrate`/`goose`/`Flyway` + `sqlc` |
198
+ | **Tier 1 (Truth)** | `rust-sql` | Rust `sqlx` (`migrations/*.sql`, `query_file!`), `Diesel` (`table!`), `SeaORM` entities |
199
+ | **Tier 1 (Ephemeral)** | `redis-ephemeral` | Cross-language Redis keyspaces + `Celery`/`Beat`, `BullMQ`, & `Asynq` named queues |
200
+ | **Tier 2 (Manipulate)** | `python-fastapi` | FastAPI routers, Python services/workers/CLI, SQLAlchemy/SQL AST |
201
+ | **Tier 2 (Manipulate)** | `typescript-node` | Next.js `route.ts`, SvelteKit `+page.server.ts`/`+server.ts`, Express, Hono, Fastify, NestJS |
202
+ | **Tier 2 (Manipulate)** | `python-django` | Django `urls.py` (`path`/`re_path`), DRF `ViewSet`/routers, Django Ninja + `Model.objects` |
203
+ | **Tier 2 (Manipulate)** | `go-http` | Go 1.22+ `net/http`, `chi`, `gin`, `echo` (nested router groups) + `sqlc`/GORM/SQL |
204
+ | **Tier 2 (Manipulate)** | `rust-http` | Rust Cargo workspaces, `axum`, `actix-web`, `rocket`, Tauri `#[tauri::command]` + `sqlx`/Diesel/SeaORM |
205
+ | **Tier 3 (Display)** | `typescript-nextjs` | Next.js App & Pages Router + custom fetch/RTK Query + Tauri `invoke()` + WCAG `DISP-005` |
206
+ | **Tier 3 (Display)** | `typescript-react` | React SPA, Vite, React Router, TanStack Router, Remix + barrel exports + WCAG `DISP-005` |
207
+ | **Tier 3 (Display)** | `vue-sfc` | Vue 3 & Nuxt 3 `.vue` SFCs + composables (`$fetch`, `useFetch`) + WCAG `DISP-005` |
208
+ | **Tier 3 (Display)** | `sveltekit` | Svelte 5 & SvelteKit `+page.svelte`, `+page.ts`, `$lib` + WCAG `DISP-005` |
209
+ | **Tier 3 (Display)** | `rust-wasm` | Rust WASM `Leptos`, `Dioxus`, `Yew` (`gloo-net` / `reqwest` / Tauri WASM) + WCAG `DISP-005` |
210
+
211
+ ### Architecture & Ecosystem Docs
212
+ - [`docs/architecture/ecosystem-and-positioning.md`](docs/architecture/ecosystem-and-positioning.md) — Ecosystem comparison (`tach`, `import-linter`, `dependency-cruiser`, `sqllineage`, `OpenLineage`, `CodeCharta`, `Semgrep`) and pairing `tach` + Wythe in CI.
213
+ - [`docs/architecture/multi-language-extensibility.md`](docs/architecture/multi-language-extensibility.md) — Language-neutral intermediate contract, adapter matrix, read-only DB/Redis probes, and `--profile auto` detection.
214
+
215
+ ---
216
+
217
+ ## Local Development
218
+
219
+ ```bash
220
+ make types # Export OpenAPI 3.1 -> generate TypeScript schema types -> run tsc --noEmit
221
+ make build-ui # Build static Next.js export into backend/app/ui_dist
222
+ make lint # Run ruff check
223
+ make test # Run serial pytest suite
224
+ make gate # Run strict self-dogfood 3-tier + WCAG gate
225
+ make package # Build standalone wheel & sdist containing bundled 3D WebGL UI
226
+ ```
227
+
228
+ ## License
229
+
230
+ MIT — see [`LICENSE`](LICENSE).
wythe-0.1.0/README.md ADDED
@@ -0,0 +1,186 @@
1
+ <p align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: light)" srcset="docs/icon/icon-light.svg">
4
+ <img src="docs/icon/icon-dark.svg" width="96" height="96" alt="Wythe icon — three architectural wythe bars mortised by a Why (?) triskelion and Therefore (∴) core">
5
+ </picture>
6
+ </p>
7
+
8
+ # Wythe
9
+
10
+ **Why (`?`) → Therefore (`∴`)** — Generalizable 3-tier architecture verifier, cross-boundary lineage solver, interactive 3D WebGL latticework visualizer, and fail-closed CI gate built around three masonry wythe invariants:
11
+
12
+ 1. **Database holds truth** — Relational schemas (`PostgreSQL`, `MySQL`, `SQLite` via `SQLAlchemy 2.0` + `Alembic`, `Prisma`, `Drizzle ORM`, `Django ORM`, Rust `sqlx`/`Diesel`/`SeaORM`, or raw SQL DDL/`sqlc` + optional read-only catalog probes) own domain policy, thresholds, temporal validity, and write-lane guards, while `Redis` keyspaces and worker queues are modeled explicitly as ephemeral stores/lanes rather than durable shadow truth.
13
+ 2. **Backend manipulates truth** — Services, background workers, solvers, and routers (`Python/FastAPI`, `TypeScript/Node/Express/Hono/Fastify/NestJS/Next.js/SvelteKit`, `Python/Django/DRF/Ninja`, `Go net/http/chi/gin/echo`, `Rust axum/actix-web/rocket/Tauri`) execute mechanisms over database rows behind explicit transactional service/SQL doors without embedding shadow policy literals or host-clock drift.
14
+ 3. **Frontend displays truth** — UI surfaces (`Next.js`, `React/Vite/TanStack Router/Remix`, `Vue 3/Nuxt`, `Svelte 5/SvelteKit`, `Rust WASM Leptos/Dioxus/Yew` + Tauri `invoke()`) bind to typed contracts, centralized clock utilities, and WCAG-accessible markup without inventing client-side policy maps or unbacked HTTP/IPC calls.
15
+
16
+ Wythe supports **18 full-stack profiles** out of the box via `--profile auto`, pairs Python's native `ast` with an optional **Tree-sitter CST grammar engine** (`wythe[treesitter]` for TypeScript/TSX, JavaScript, Rust, and Go), traces background worker & scheduled beat queues (`Celery` / `Beat`, `BullMQ`, `Asynq`), enforces **WCAG accessibility semantics** (`DISP-005`), renders both **Simple Solids** and **120 fps Breathing Atomic Wythe Polyhedra** (with adaptive `System | Dark | Light` polarity and custom user palettes), and **self-dogfoods** its own 3-tier codebase (`wythe gate --strict wythe` passes with zero blocking and zero warning findings).
17
+
18
+ ---
19
+
20
+ ## Instant Usage (`uvx` / `uv`)
21
+
22
+ Run Wythe directly against any repository without polluting project dependencies (add `[treesitter]` to enable prebuilt Tree-sitter CST wheels for TS/TSX, JS, Rust, and Go):
23
+
24
+ ```bash
25
+ # Instant CLI gate & lineage scan from PyPI (once published) or directly from GitHub
26
+ uvx "wythe[treesitter]" gate --strict /path/to/repo
27
+ uvx --from "git+https://github.com/Euda1mon1a/wythe[treesitter]" wythe scan /path/to/repo
28
+ uvx --from "git+https://github.com/Euda1mon1a/wythe[treesitter]" wythe gate --strict /path/to/repo
29
+
30
+ # Launch single-port API + interactive 3D WebGL UI on http://127.0.0.1:8780 (no Node.js runtime needed)
31
+ uvx --from "git+https://github.com/Euda1mon1a/wythe[treesitter]" wythe serve /path/to/repo
32
+
33
+ # Or install persistently as an isolated CLI tool
34
+ uv tool install "git+https://github.com/Euda1mon1a/wythe[treesitter]"
35
+ wythe serve .
36
+ ```
37
+
38
+ ### GitHub Actions Integration
39
+
40
+ ```yaml
41
+ - uses: actions/checkout@v4
42
+ with:
43
+ fetch-depth: 0
44
+ - uses: Euda1mon1a/wythe@main
45
+ with:
46
+ mode: diff
47
+ base-ref: origin/main
48
+ sarif-file: wythe.sarif.json
49
+ ```
50
+
51
+ ---
52
+
53
+ ## Persisted Rule Catalog (`atlas_rule_policies`)
54
+
55
+ Rule severity, enablement, gate-blocking behavior, and effective-date windows live in database truth (`atlas_rule_policies`) and round-trip cleanly to Git via `.wythe/governance.json`:
56
+
57
+ | Code | Tier | Default | Description |
58
+ | :--- | :--- | :--- | :--- |
59
+ | `TRUTH-001` | `truth` | Warning | Persisted input table has zero verified backend readers |
60
+ | `TRUTH-002` | `truth` | Blocking | ORM model, `schema.sql`/migrations, or live DB catalog table drift |
61
+ | `TRUTH-003` | `truth` | Blocking | Table or Redis store mutated directly inside HTTP route without service/DB door |
62
+ | `TRUTH-004` | `truth` | Warning | Unbounded Redis `SET`/`HSET` write without TTL risks volatile shadow truth |
63
+ | `MANIP-001` | `manipulate` | Blocking | Mutable domain policy/threshold constant hardcoded in code (`atlas_policy_vocabularies`) |
64
+ | `MANIP-002` | `manipulate` | Blocking | Direct host `date.today()` or naive `datetime.utcnow()` call |
65
+ | `MANIP-003` | `manipulate` | Warning | Service/solver module has zero verified inbound route, CLI, or worker callers |
66
+ | `DISP-001` | `display` | Blocking | Display tier defines domain policy threshold shadows in frontend code |
67
+ | `DISP-002` | `display` | Blocking | Raw `new Date()` or UTC `.toISOString().slice(0, 10)` date slicing |
68
+ | `DISP-003` | `display` | Warning | Frontend API client omits `api-generated` OpenAPI/schema types |
69
+ | `DISP-004` | `display` | Blocking | Frontend HTTP/IPC call targets a path absent from backend router graph |
70
+ | `DISP-005` | `display` | Warning | Display markup violates core WCAG semantics (missing `alt`, unlabeled form input, nameless icon button, non-keyboard `<div onClick>`) |
71
+
72
+ ---
73
+
74
+ ## CLI Commands
75
+
76
+ ```bash
77
+ # Scan any supported full-stack repository (--profile auto)
78
+ wythe scan /path/to/repo --json report.json --sarif report.sarif.json
79
+
80
+ # Optional metadata-only live DB & Redis keyspace probes (zero row or value reads)
81
+ wythe scan /path/to/repo \
82
+ --dsn "postgresql://readonly@127.0.0.1:5432/appdb" \
83
+ --redis-dsn "redis://127.0.0.1:6379/0"
84
+
85
+ # PR diff gate & directional 3-tier blast-radius slice
86
+ wythe diff /path/to/repo --base origin/main --sarif diff.sarif.json
87
+
88
+ # Git-portable governance bundle (.wythe/governance.json) for CI runners
89
+ wythe export-config /path/to/repo
90
+ wythe gate --sync-config /path/to/repo
91
+
92
+ # Single-port API + 3D WebGL UI (auto-registers target directory on startup)
93
+ wythe serve /path/to/repo --host 127.0.0.1 --port 8780
94
+ ```
95
+
96
+ ---
97
+
98
+ ## First-Class LLM & Human ↔ Vision/MCP Bridge (On-Device AI, Streamable HTTP MCP, OpenAPI & WebMCP)
99
+
100
+ Full JSON scans on large repositories span megabytes; Wythe exposes five zero-copy-paste Human ↔ LLM surfaces spanning bare-minimum on-device models through remote Tailnet agents:
101
+
102
+ 1. **Zero-Setup On-Device AI (`Ask AI [A]` / `⌘J`, `/api/v1/ask`)** — Zero-cloud, zero-API-key cascade with real-time Three.js camera-frustum & screen-color telemetry, `1024px` WebP vision frames, and rolling multi-turn sessions across:
103
+ - **Apple Intelligence (`apple`)**: macOS 26+/27 native `FoundationModels` helper (`arm64-apple-macos26.0`, serialized with `modelmanagerd` backoff).
104
+ - **Chrome Built-In (`chrome`)**: Client-side browser `LanguageModel` (Gemini Nano via Prompt API, including 1-click download and multimodal WebGL `<canvas>` vision when enabled).
105
+ - **Local OpenAI `/v1` (`local`)**: Auto-probes browser-host & server loopback `/v1/models` & `/v1/chat/completions` (`:8080`/`:8084`/`:8083` MLX, `:11434` Ollama, `:1234` LM Studio) without triggering weight reloads.
106
+ 2. **Streamable HTTP MCP (`POST /api/v1/mcp` & `GET /api/v1/mcp`) + Stdio MCP (`wythe mcp`)** — Connect local or remote Tailnet coding agents (`http://<host>:8780/api/v1/mcp`) with zero local install.
107
+ 3. **OpenAPI 3.1 (`/openapi.json`) + Remote CLI (`wythe focus --remote <url>`)** — Universal HTTP tool/Actions compatibility plus cross-host CLI X-Ray inspection.
108
+ 4. **In-Tab WebMCP (`navigator.modelContext` & `window.wythe`)** — Exposes `whatIsThis()`, `getLineageCone(query)`, `selectNode(nodeId)`, and `ask(question)` directly to browser agents and extensions.
109
+ 5. **Token-Budgeted `llms.txt` & Lineage Cone Slicer** — Deterministic ~2,000-token repo maps and ~300-token 3-tier cones.
110
+
111
+ ```bash
112
+ # 1. Token-budgeted llms.txt pointer map (~2,000 tokens regardless of repo size)
113
+ wythe llms /path/to/repo --max-tokens 2200 --out llms-wythe.txt
114
+
115
+ # 2. Surgical ~300-token 3-tier Lineage Cone around any table, file, route, or queue
116
+ wythe context /path/to/repo audit_exports
117
+ wythe context /path/to/repo /api/v1/scans --json
118
+
119
+ # 3. Zero-copy-paste Human <-> LLM Bridge (local state or remote private/Tailnet server)
120
+ wythe focus
121
+ wythe focus --remote http://100.64.0.10:8780
122
+
123
+ # 4. Zero-dependency read-only stdio MCP server (or connect directly to http://127.0.0.1:8780/api/v1/mcp)
124
+ wythe mcp
125
+ ```
126
+
127
+ MCP client configuration (Stdio or Streamable HTTP — exposes `wythe_what_is_this`, `wythe_list_targets`, `wythe_overview`, `wythe_lineage_cone`, `wythe_blast_radius`, `wythe_findings`):
128
+
129
+ ```json
130
+ {
131
+ "mcpServers": {
132
+ "wythe-http": {
133
+ "url": "http://127.0.0.1:8780/api/v1/mcp"
134
+ },
135
+ "wythe-stdio": {
136
+ "command": "uvx",
137
+ "args": ["--from", "git+https://github.com/Euda1mon1a/wythe[treesitter]", "wythe", "mcp"]
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Supported Multi-Language & Multi-DB Adapters
146
+
147
+ | Stratum | Adapter Key | Stack Coverage |
148
+ | :--- | :--- | :--- |
149
+ | **Tier 1 (Truth)** | `postgres-sqlalchemy` | SQLAlchemy 2.0 + Alembic + `schema.sql` + PG/MySQL/SQLite live catalog |
150
+ | **Tier 1 (Truth)** | `prisma-schema` | Prisma `schema.prisma` (`@@map`) + `prisma/migrations/*.sql` |
151
+ | **Tier 1 (Truth)** | `drizzle-orm` | Drizzle `pgTable`/`mysqlTable`/`sqliteTable` + Drizzle SQL migrations |
152
+ | **Tier 1 (Truth)** | `django-orm` | Django `models.Model` (`Meta.db_table`) + `migrations/*.py` |
153
+ | **Tier 1 (Truth)** | `sql-ddl` | Multi-dialect SQL DDL + `golang-migrate`/`goose`/`Flyway` + `sqlc` |
154
+ | **Tier 1 (Truth)** | `rust-sql` | Rust `sqlx` (`migrations/*.sql`, `query_file!`), `Diesel` (`table!`), `SeaORM` entities |
155
+ | **Tier 1 (Ephemeral)** | `redis-ephemeral` | Cross-language Redis keyspaces + `Celery`/`Beat`, `BullMQ`, & `Asynq` named queues |
156
+ | **Tier 2 (Manipulate)** | `python-fastapi` | FastAPI routers, Python services/workers/CLI, SQLAlchemy/SQL AST |
157
+ | **Tier 2 (Manipulate)** | `typescript-node` | Next.js `route.ts`, SvelteKit `+page.server.ts`/`+server.ts`, Express, Hono, Fastify, NestJS |
158
+ | **Tier 2 (Manipulate)** | `python-django` | Django `urls.py` (`path`/`re_path`), DRF `ViewSet`/routers, Django Ninja + `Model.objects` |
159
+ | **Tier 2 (Manipulate)** | `go-http` | Go 1.22+ `net/http`, `chi`, `gin`, `echo` (nested router groups) + `sqlc`/GORM/SQL |
160
+ | **Tier 2 (Manipulate)** | `rust-http` | Rust Cargo workspaces, `axum`, `actix-web`, `rocket`, Tauri `#[tauri::command]` + `sqlx`/Diesel/SeaORM |
161
+ | **Tier 3 (Display)** | `typescript-nextjs` | Next.js App & Pages Router + custom fetch/RTK Query + Tauri `invoke()` + WCAG `DISP-005` |
162
+ | **Tier 3 (Display)** | `typescript-react` | React SPA, Vite, React Router, TanStack Router, Remix + barrel exports + WCAG `DISP-005` |
163
+ | **Tier 3 (Display)** | `vue-sfc` | Vue 3 & Nuxt 3 `.vue` SFCs + composables (`$fetch`, `useFetch`) + WCAG `DISP-005` |
164
+ | **Tier 3 (Display)** | `sveltekit` | Svelte 5 & SvelteKit `+page.svelte`, `+page.ts`, `$lib` + WCAG `DISP-005` |
165
+ | **Tier 3 (Display)** | `rust-wasm` | Rust WASM `Leptos`, `Dioxus`, `Yew` (`gloo-net` / `reqwest` / Tauri WASM) + WCAG `DISP-005` |
166
+
167
+ ### Architecture & Ecosystem Docs
168
+ - [`docs/architecture/ecosystem-and-positioning.md`](docs/architecture/ecosystem-and-positioning.md) — Ecosystem comparison (`tach`, `import-linter`, `dependency-cruiser`, `sqllineage`, `OpenLineage`, `CodeCharta`, `Semgrep`) and pairing `tach` + Wythe in CI.
169
+ - [`docs/architecture/multi-language-extensibility.md`](docs/architecture/multi-language-extensibility.md) — Language-neutral intermediate contract, adapter matrix, read-only DB/Redis probes, and `--profile auto` detection.
170
+
171
+ ---
172
+
173
+ ## Local Development
174
+
175
+ ```bash
176
+ make types # Export OpenAPI 3.1 -> generate TypeScript schema types -> run tsc --noEmit
177
+ make build-ui # Build static Next.js export into backend/app/ui_dist
178
+ make lint # Run ruff check
179
+ make test # Run serial pytest suite
180
+ make gate # Run strict self-dogfood 3-tier + WCAG gate
181
+ make package # Build standalone wheel & sdist containing bundled 3D WebGL UI
182
+ ```
183
+
184
+ ## License
185
+
186
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,3 @@
1
+ """Truth Atlas backend package."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,50 @@
1
+ """Default adapter registry factory with multi-language 3-tier adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from app.adapters.base import AdapterRegistry
6
+ from app.adapters.django_truth import DjangoOrmAdapter
7
+ from app.adapters.drizzle_truth import DrizzleOrmAdapter
8
+ from app.adapters.go_http import GoHttpAdapter
9
+ from app.adapters.postgres_sqlalchemy import PostgresSqlAlchemyAdapter
10
+ from app.adapters.prisma_truth import PrismaSchemaAdapter
11
+ from app.adapters.python_django import PythonDjangoAdapter
12
+ from app.adapters.python_fastapi import PythonFastApiAdapter
13
+ from app.adapters.rust_http import RustHttpAdapter
14
+ from app.adapters.rust_sql_truth import RustSqlTruthAdapter
15
+ from app.adapters.rust_wasm_display import RustWasmDisplayAdapter
16
+ from app.adapters.sql_ddl_truth import SqlDdlTruthAdapter
17
+ from app.adapters.sveltekit_display import SvelteKitDisplayAdapter
18
+ from app.adapters.typescript_nextjs import TypeScriptNextJsAdapter
19
+ from app.adapters.typescript_node import TypeScriptNodeAdapter
20
+ from app.adapters.typescript_react import TypeScriptReactSpaAdapter
21
+ from app.adapters.vue_sfc import VueSfcDisplayAdapter
22
+
23
+
24
+ def build_default_registry() -> AdapterRegistry:
25
+ """Instantiate an AdapterRegistry populated with all implemented 3-tier adapters."""
26
+ registry = AdapterRegistry()
27
+
28
+ # Tier 1: Database & Schema/ORM Truth
29
+ registry.register_truth(PostgresSqlAlchemyAdapter())
30
+ registry.register_truth(PrismaSchemaAdapter())
31
+ registry.register_truth(DrizzleOrmAdapter())
32
+ registry.register_truth(DjangoOrmAdapter())
33
+ registry.register_truth(SqlDdlTruthAdapter())
34
+ registry.register_truth(RustSqlTruthAdapter())
35
+
36
+ # Tier 2: Backend Manipulation
37
+ registry.register_manipulator(PythonFastApiAdapter())
38
+ registry.register_manipulator(TypeScriptNodeAdapter())
39
+ registry.register_manipulator(PythonDjangoAdapter())
40
+ registry.register_manipulator(GoHttpAdapter())
41
+ registry.register_manipulator(RustHttpAdapter())
42
+
43
+ # Tier 3: Frontend Display
44
+ registry.register_display(TypeScriptNextJsAdapter())
45
+ registry.register_display(TypeScriptReactSpaAdapter())
46
+ registry.register_display(VueSfcDisplayAdapter())
47
+ registry.register_display(SvelteKitDisplayAdapter())
48
+ registry.register_display(RustWasmDisplayAdapter())
49
+
50
+ return registry
@@ -0,0 +1,134 @@
1
+ """Tier 3 Accessibility (`DISP-005`) static markup inspector for JSX/TSX, Vue, Svelte, and Rust WASM."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+
7
+ from app.adapters.base import RawCandidateFinding
8
+
9
+ # Match opening tags (<img ...>, <div ...>, <span ...>, <input ...>) without stopping early on JSX `=>` arrows
10
+ _IMG_TAG_RE = re.compile(r"<img\b(?P<attrs>(?:=>|[^>\"']|\"[^\"]*\"|'[^']*')*)>", re.IGNORECASE)
11
+ _NON_INTERACTIVE_CLICK_RE = re.compile(
12
+ r"<(?P<tag>div|span)\b(?P<attrs>(?:=>|[^>\"']|\"[^\"]*\"|'[^']*'){0,500}?"
13
+ r"\b(?:onClick|on:click|@click|onclick)\s*=[\s\S]{0,240}?)>",
14
+ )
15
+ _INPUT_TAG_RE = re.compile(
16
+ r"<(?P<tag>input|select|textarea)\b(?P<attrs>(?:=>|[^>\"']|\"[^\"]*\"|'[^']*')*)>",
17
+ )
18
+ _EMPTY_BUTTON_RE = re.compile(
19
+ r"<button\b(?P<attrs>(?:=>|[^>\"']|\"[^\"]*\"|'[^']*')*)>\s*"
20
+ r"(?:<[A-Z][A-Za-z0-9_.]*\b[^>]*/\s*>\s*|<(?:svg|i)\b[\s\S]{0,400}?/\s*>\s*|<(?:svg|i)\b[\s\S]{0,400}?</(?:svg|i)>\s*)+"
21
+ r"</button>",
22
+ )
23
+ _LABEL_FOR_RE = re.compile(r"""<label\b[^>]*?\b(?:htmlFor|for)\s*=\s*(?:['"](?P<id>[^'"]+)['"]|\{(?P<expr>[^}]+)\})""")
24
+ _WRAPPING_LABEL_RE = re.compile(r"<label\b(?:(?!</label>)[\s\S]){0,1500}?</label>")
25
+ _ID_ATTR_RE = re.compile(r"""\bid\s*=\s*(?:['"](?P<id>[^'"]+)['"]|\{(?P<expr>[^}]+)\})""")
26
+ _HIDDEN_INPUT_RE = re.compile(r"""\btype\s*=\s*['"](?:hidden|submit|reset|button)['"]""", re.IGNORECASE)
27
+
28
+
29
+ def scan_a11y_markup_findings(
30
+ raw_source: str,
31
+ node_id: str,
32
+ rel_repo: str,
33
+ ) -> list[RawCandidateFinding]:
34
+ """Inspect raw UI template/JSX/RSX markup for deterministic WCAG (`DISP-005`) violations."""
35
+ findings: list[RawCandidateFinding] = []
36
+ seen_lines: set[tuple[int, str]] = set()
37
+
38
+ def emit(lineno: int, kind: str, title: str, detail: str, remediation: str) -> None:
39
+ if (lineno, kind) in seen_lines or len(findings) >= 15:
40
+ return
41
+ seen_lines.add((lineno, kind))
42
+ findings.append(
43
+ RawCandidateFinding(
44
+ rule_code="DISP-005",
45
+ node_id=node_id,
46
+ source_file=rel_repo,
47
+ line=lineno,
48
+ title=title,
49
+ detail=detail,
50
+ remediation=remediation,
51
+ )
52
+ )
53
+
54
+ # 1. <img> without alt attribute (unless decorative aria-hidden="true" or role="presentation"/"none")
55
+ for m in _IMG_TAG_RE.finditer(raw_source):
56
+ attrs = m.group("attrs")
57
+ if re.search(r"\balt\s*=", attrs) or re.search(r"""\baria-hidden\s*=\s*['"]true['"]""", attrs):
58
+ continue
59
+ if re.search(r"""\brole\s*=\s*['"](?:presentation|none)['"]""", attrs):
60
+ continue
61
+ if "{..." in attrs:
62
+ continue
63
+ lineno = raw_source.count("\n", 0, m.start()) + 1
64
+ emit(
65
+ lineno,
66
+ "img_alt",
67
+ "Image element `<img>` missing `alt` text (`WCAG 1.1.1`)",
68
+ f"{rel_repo}:{lineno} renders `<img>` without an `alt` attribute (use `alt=\"\"` if purely decorative).",
69
+ "Provide descriptive `alt` text or `alt=\"\"` with `aria-hidden=\"true\"` for decorative images.",
70
+ )
71
+
72
+ # 2. Click handlers on non-interactive <div>/<span> without role + keyboard focusability
73
+ for m in _NON_INTERACTIVE_CLICK_RE.finditer(raw_source):
74
+ tag = m.group("tag")
75
+ attrs = m.group("attrs")
76
+ if re.search(r"""\baria-hidden\s*=\s*['"]true['"]""", attrs):
77
+ continue
78
+ has_role = bool(re.search(r"\brole\s*=", attrs))
79
+ has_tab = bool(re.search(r"\btab[Ii]ndex\s*=", attrs))
80
+ if has_role and has_tab:
81
+ continue
82
+ lineno = raw_source.count("\n", 0, m.start()) + 1
83
+ emit(
84
+ lineno,
85
+ "div_click",
86
+ f"Non-interactive `<{tag}>` has click handler without keyboard/ARIA semantics (`WCAG 2.1.1`)",
87
+ f"{rel_repo}:{lineno} attaches a click handler to `<{tag}>` without `role` and `tabIndex`.",
88
+ "Replace with a semantic `<button>` element, or add `role=\"button\"`, `tabIndex={0}`, and `onKeyDown`.",
89
+ )
90
+
91
+ # 3. Icon-only <button> without aria-label / aria-labelledby / title
92
+ for m in _EMPTY_BUTTON_RE.finditer(raw_source):
93
+ attrs = m.group("attrs")
94
+ if re.search(r"\b(?:aria-label|aria-labelledby|title)\s*=", attrs) or "{..." in attrs:
95
+ continue
96
+ lineno = raw_source.count("\n", 0, m.start()) + 1
97
+ emit(
98
+ lineno,
99
+ "icon_btn",
100
+ "Icon-only `<button>` missing accessible name (`WCAG 4.1.2`)",
101
+ f"{rel_repo}:{lineno} renders an icon-only `<button>` with no `aria-label`, `aria-labelledby`, or visible text.",
102
+ "Add `aria-label=\"...\"` or visually-hidden `<span className=\"sr-only\">` text inside the button.",
103
+ )
104
+
105
+ # 4. Orphaned form controls (<input>/<select>/<textarea> without label[for], wrapping <label>, or aria-label)
106
+ labeled_ids = {
107
+ (lm.group("id") or lm.group("expr") or "").strip()
108
+ for lm in _LABEL_FOR_RE.finditer(raw_source)
109
+ }
110
+ wrapped_spans = [wm.span() for wm in _WRAPPING_LABEL_RE.finditer(raw_source)]
111
+ for m in _INPUT_TAG_RE.finditer(raw_source):
112
+ tag = m.group("tag")
113
+ attrs = m.group("attrs")
114
+ if _HIDDEN_INPUT_RE.search(attrs) or "{..." in attrs:
115
+ continue
116
+ if re.search(r"\b(?:aria-label|aria-labelledby|title)\s*=", attrs):
117
+ continue
118
+ if any(ws <= m.start() and m.end() <= we for ws, we in wrapped_spans):
119
+ continue
120
+ idm = _ID_ATTR_RE.search(attrs)
121
+ if idm:
122
+ id_val = (idm.group("id") or idm.group("expr") or "").strip()
123
+ if id_val and id_val in labeled_ids:
124
+ continue
125
+ lineno = raw_source.count("\n", 0, m.start()) + 1
126
+ emit(
127
+ lineno,
128
+ "orphan_input",
129
+ f"Form control `<{tag}>` lacks associated `<label>` or `aria-label` (`WCAG 1.3.1`)",
130
+ f"{rel_repo}:{lineno} renders `<{tag}>` without a matching `<label htmlFor=...>` or `aria-label`.",
131
+ "Associate a `<label htmlFor=\"...\">`, wrap in `<label>`, or add `aria-label=\"...\"`.",
132
+ )
133
+
134
+ return findings