@lbroth/rothunter 1.0.0-rc.1
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.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/adapters/llm.d.ts +68 -0
- package/dist/adapters/llm.d.ts.map +1 -0
- package/dist/adapters/llm.js +189 -0
- package/dist/adapters/llm.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +81 -0
- package/dist/config.js.map +1 -0
- package/dist/detector-registry.d.ts +32 -0
- package/dist/detector-registry.d.ts.map +1 -0
- package/dist/detector-registry.js +74 -0
- package/dist/detector-registry.js.map +1 -0
- package/dist/detectors/api-race.d.ts +6 -0
- package/dist/detectors/api-race.d.ts.map +1 -0
- package/dist/detectors/api-race.js +222 -0
- package/dist/detectors/api-race.js.map +1 -0
- package/dist/detectors/bad-config.d.ts +6 -0
- package/dist/detectors/bad-config.d.ts.map +1 -0
- package/dist/detectors/bad-config.js +529 -0
- package/dist/detectors/bad-config.js.map +1 -0
- package/dist/detectors/console-log-prod.d.ts +6 -0
- package/dist/detectors/console-log-prod.d.ts.map +1 -0
- package/dist/detectors/console-log-prod.js +72 -0
- package/dist/detectors/console-log-prod.js.map +1 -0
- package/dist/detectors/dead-api.d.ts +10 -0
- package/dist/detectors/dead-api.d.ts.map +1 -0
- package/dist/detectors/dead-api.js +115 -0
- package/dist/detectors/dead-api.js.map +1 -0
- package/dist/detectors/dead-export.d.ts +12 -0
- package/dist/detectors/dead-export.d.ts.map +1 -0
- package/dist/detectors/dead-export.js +140 -0
- package/dist/detectors/dead-export.js.map +1 -0
- package/dist/detectors/dead-handler.d.ts +12 -0
- package/dist/detectors/dead-handler.d.ts.map +1 -0
- package/dist/detectors/dead-handler.js +40 -0
- package/dist/detectors/dead-handler.js.map +1 -0
- package/dist/detectors/dead-module.d.ts +14 -0
- package/dist/detectors/dead-module.d.ts.map +1 -0
- package/dist/detectors/dead-module.js +50 -0
- package/dist/detectors/dead-module.js.map +1 -0
- package/dist/detectors/deep-nesting.d.ts +12 -0
- package/dist/detectors/deep-nesting.d.ts.map +1 -0
- package/dist/detectors/deep-nesting.js +133 -0
- package/dist/detectors/deep-nesting.js.map +1 -0
- package/dist/detectors/duplicate-function.d.ts +9 -0
- package/dist/detectors/duplicate-function.d.ts.map +1 -0
- package/dist/detectors/duplicate-function.js +199 -0
- package/dist/detectors/duplicate-function.js.map +1 -0
- package/dist/detectors/duplicate-type.d.ts +9 -0
- package/dist/detectors/duplicate-type.d.ts.map +1 -0
- package/dist/detectors/duplicate-type.js +166 -0
- package/dist/detectors/duplicate-type.js.map +1 -0
- package/dist/detectors/hot-hub-file.d.ts +11 -0
- package/dist/detectors/hot-hub-file.d.ts.map +1 -0
- package/dist/detectors/hot-hub-file.js +42 -0
- package/dist/detectors/hot-hub-file.js.map +1 -0
- package/dist/detectors/long-file.d.ts +12 -0
- package/dist/detectors/long-file.d.ts.map +1 -0
- package/dist/detectors/long-file.js +82 -0
- package/dist/detectors/long-file.js.map +1 -0
- package/dist/detectors/long-function.d.ts +12 -0
- package/dist/detectors/long-function.d.ts.map +1 -0
- package/dist/detectors/long-function.js +45 -0
- package/dist/detectors/long-function.js.map +1 -0
- package/dist/detectors/magic-numbers.d.ts +10 -0
- package/dist/detectors/magic-numbers.d.ts.map +1 -0
- package/dist/detectors/magic-numbers.js +332 -0
- package/dist/detectors/magic-numbers.js.map +1 -0
- package/dist/detectors/mutable-globals.d.ts +6 -0
- package/dist/detectors/mutable-globals.d.ts.map +1 -0
- package/dist/detectors/mutable-globals.js +95 -0
- package/dist/detectors/mutable-globals.js.map +1 -0
- package/dist/detectors/mutation.d.ts +11 -0
- package/dist/detectors/mutation.d.ts.map +1 -0
- package/dist/detectors/mutation.js +397 -0
- package/dist/detectors/mutation.js.map +1 -0
- package/dist/detectors/public-any.d.ts +6 -0
- package/dist/detectors/public-any.d.ts.map +1 -0
- package/dist/detectors/public-any.js +52 -0
- package/dist/detectors/public-any.js.map +1 -0
- package/dist/detectors/race-condition.d.ts +6 -0
- package/dist/detectors/race-condition.d.ts.map +1 -0
- package/dist/detectors/race-condition.js +608 -0
- package/dist/detectors/race-condition.js.map +1 -0
- package/dist/detectors/shared-db-write.d.ts +6 -0
- package/dist/detectors/shared-db-write.d.ts.map +1 -0
- package/dist/detectors/shared-db-write.js +656 -0
- package/dist/detectors/shared-db-write.js.map +1 -0
- package/dist/detectors/silent-catch.d.ts +6 -0
- package/dist/detectors/silent-catch.d.ts.map +1 -0
- package/dist/detectors/silent-catch.js +167 -0
- package/dist/detectors/silent-catch.js.map +1 -0
- package/dist/detectors/similar-functions.d.ts +15 -0
- package/dist/detectors/similar-functions.d.ts.map +1 -0
- package/dist/detectors/similar-functions.js +334 -0
- package/dist/detectors/similar-functions.js.map +1 -0
- package/dist/detectors/skip-tests.d.ts +6 -0
- package/dist/detectors/skip-tests.d.ts.map +1 -0
- package/dist/detectors/skip-tests.js +69 -0
- package/dist/detectors/skip-tests.js.map +1 -0
- package/dist/detectors/todo-comments.d.ts +29 -0
- package/dist/detectors/todo-comments.d.ts.map +1 -0
- package/dist/detectors/todo-comments.js +154 -0
- package/dist/detectors/todo-comments.js.map +1 -0
- package/dist/detectors/unused-deps.d.ts +8 -0
- package/dist/detectors/unused-deps.d.ts.map +1 -0
- package/dist/detectors/unused-deps.js +115 -0
- package/dist/detectors/unused-deps.js.map +1 -0
- package/dist/extraction/api-race-confirmer.d.ts +31 -0
- package/dist/extraction/api-race-confirmer.d.ts.map +1 -0
- package/dist/extraction/api-race-confirmer.js +110 -0
- package/dist/extraction/api-race-confirmer.js.map +1 -0
- package/dist/extraction/llm-confirmer.d.ts +25 -0
- package/dist/extraction/llm-confirmer.d.ts.map +1 -0
- package/dist/extraction/llm-confirmer.js +118 -0
- package/dist/extraction/llm-confirmer.js.map +1 -0
- package/dist/extraction/mutation-confirmer.d.ts +30 -0
- package/dist/extraction/mutation-confirmer.d.ts.map +1 -0
- package/dist/extraction/mutation-confirmer.js +73 -0
- package/dist/extraction/mutation-confirmer.js.map +1 -0
- package/dist/extraction/prompt-chunking.d.ts +37 -0
- package/dist/extraction/prompt-chunking.d.ts.map +1 -0
- package/dist/extraction/prompt-chunking.js +61 -0
- package/dist/extraction/prompt-chunking.js.map +1 -0
- package/dist/extraction/race-confirmer.d.ts +28 -0
- package/dist/extraction/race-confirmer.d.ts.map +1 -0
- package/dist/extraction/race-confirmer.js +68 -0
- package/dist/extraction/race-confirmer.js.map +1 -0
- package/dist/extraction/shared-db-write-confirmer.d.ts +31 -0
- package/dist/extraction/shared-db-write-confirmer.d.ts.map +1 -0
- package/dist/extraction/shared-db-write-confirmer.js +141 -0
- package/dist/extraction/shared-db-write-confirmer.js.map +1 -0
- package/dist/extraction/triage-confirmer.d.ts +59 -0
- package/dist/extraction/triage-confirmer.d.ts.map +1 -0
- package/dist/extraction/triage-confirmer.js +104 -0
- package/dist/extraction/triage-confirmer.js.map +1 -0
- package/dist/graph/cfg.d.ts +45 -0
- package/dist/graph/cfg.d.ts.map +1 -0
- package/dist/graph/cfg.js +198 -0
- package/dist/graph/cfg.js.map +1 -0
- package/dist/graph/decorator-entries.d.ts +2 -0
- package/dist/graph/decorator-entries.d.ts.map +1 -0
- package/dist/graph/decorator-entries.js +89 -0
- package/dist/graph/decorator-entries.js.map +1 -0
- package/dist/graph/entry-points.d.ts +12 -0
- package/dist/graph/entry-points.d.ts.map +1 -0
- package/dist/graph/entry-points.js +282 -0
- package/dist/graph/entry-points.js.map +1 -0
- package/dist/graph/handler-conventions.d.ts +2 -0
- package/dist/graph/handler-conventions.d.ts.map +1 -0
- package/dist/graph/handler-conventions.js +26 -0
- package/dist/graph/handler-conventions.js.map +1 -0
- package/dist/graph/iac-entries.d.ts +2 -0
- package/dist/graph/iac-entries.d.ts.map +1 -0
- package/dist/graph/iac-entries.js +123 -0
- package/dist/graph/iac-entries.js.map +1 -0
- package/dist/graph/import-graph.d.ts +48 -0
- package/dist/graph/import-graph.d.ts.map +1 -0
- package/dist/graph/import-graph.js +86 -0
- package/dist/graph/import-graph.js.map +1 -0
- package/dist/graph/monorepo-detect.d.ts +3 -0
- package/dist/graph/monorepo-detect.d.ts.map +1 -0
- package/dist/graph/monorepo-detect.js +166 -0
- package/dist/graph/monorepo-detect.js.map +1 -0
- package/dist/graph/tsconfig-paths.d.ts +23 -0
- package/dist/graph/tsconfig-paths.d.ts.map +1 -0
- package/dist/graph/tsconfig-paths.js +217 -0
- package/dist/graph/tsconfig-paths.js.map +1 -0
- package/dist/multi-workspace-scanner.d.ts +13 -0
- package/dist/multi-workspace-scanner.d.ts.map +1 -0
- package/dist/multi-workspace-scanner.js +130 -0
- package/dist/multi-workspace-scanner.js.map +1 -0
- package/dist/normalizers/type-normalizer.d.ts +16 -0
- package/dist/normalizers/type-normalizer.d.ts.map +1 -0
- package/dist/normalizers/type-normalizer.js +189 -0
- package/dist/normalizers/type-normalizer.js.map +1 -0
- package/dist/parsers/typescript-parser.d.ts +57 -0
- package/dist/parsers/typescript-parser.d.ts.map +1 -0
- package/dist/parsers/typescript-parser.js +502 -0
- package/dist/parsers/typescript-parser.js.map +1 -0
- package/dist/reporter/json-reporter.d.ts +12 -0
- package/dist/reporter/json-reporter.d.ts.map +1 -0
- package/dist/reporter/json-reporter.js +28 -0
- package/dist/reporter/json-reporter.js.map +1 -0
- package/dist/reporter/markdown-reporter.d.ts +11 -0
- package/dist/reporter/markdown-reporter.d.ts.map +1 -0
- package/dist/reporter/markdown-reporter.js +77 -0
- package/dist/reporter/markdown-reporter.js.map +1 -0
- package/dist/rothunter.d.ts +125 -0
- package/dist/rothunter.d.ts.map +1 -0
- package/dist/rothunter.js +1038 -0
- package/dist/rothunter.js.map +1 -0
- package/dist/server/false-positives.d.ts +34 -0
- package/dist/server/false-positives.d.ts.map +1 -0
- package/dist/server/false-positives.js +85 -0
- package/dist/server/false-positives.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +1529 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/marked-to-fix.d.ts +16 -0
- package/dist/server/marked-to-fix.d.ts.map +1 -0
- package/dist/server/marked-to-fix.js +36 -0
- package/dist/server/marked-to-fix.js.map +1 -0
- package/dist/server/scan-store.d.ts +147 -0
- package/dist/server/scan-store.d.ts.map +1 -0
- package/dist/server/scan-store.js +291 -0
- package/dist/server/scan-store.js.map +1 -0
- package/dist/server/settings-store.d.ts +28 -0
- package/dist/server/settings-store.d.ts.map +1 -0
- package/dist/server/settings-store.js +46 -0
- package/dist/server/settings-store.js.map +1 -0
- package/dist/server/workspace-store.d.ts +39 -0
- package/dist/server/workspace-store.d.ts.map +1 -0
- package/dist/server/workspace-store.js +108 -0
- package/dist/server/workspace-store.js.map +1 -0
- package/dist/types/detector-input.d.ts +37 -0
- package/dist/types/detector-input.d.ts.map +1 -0
- package/dist/types/detector-input.js +2 -0
- package/dist/types/detector-input.js.map +1 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/clustering.d.ts +14 -0
- package/dist/utils/clustering.d.ts.map +1 -0
- package/dist/utils/clustering.js +56 -0
- package/dist/utils/clustering.js.map +1 -0
- package/dist/utils/gitignore.d.ts +32 -0
- package/dist/utils/gitignore.d.ts.map +1 -0
- package/dist/utils/gitignore.js +122 -0
- package/dist/utils/gitignore.js.map +1 -0
- package/dist/utils/hash.d.ts +11 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +14 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/ignore-annotation.d.ts +28 -0
- package/dist/utils/ignore-annotation.d.ts.map +1 -0
- package/dist/utils/ignore-annotation.js +46 -0
- package/dist/utils/ignore-annotation.js.map +1 -0
- package/dist/utils/llm-json.d.ts +2 -0
- package/dist/utils/llm-json.d.ts.map +1 -0
- package/dist/utils/llm-json.js +53 -0
- package/dist/utils/llm-json.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +4 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/project-conventions.d.ts +2 -0
- package/dist/utils/project-conventions.d.ts.map +1 -0
- package/dist/utils/project-conventions.js +108 -0
- package/dist/utils/project-conventions.js.map +1 -0
- package/dist/utils/regex.d.ts +9 -0
- package/dist/utils/regex.d.ts.map +1 -0
- package/dist/utils/regex.js +11 -0
- package/dist/utils/regex.js.map +1 -0
- package/dist/utils/snippet.d.ts +20 -0
- package/dist/utils/snippet.d.ts.map +1 -0
- package/dist/utils/snippet.js +28 -0
- package/dist/utils/snippet.js.map +1 -0
- package/dist/utils/source-reader.d.ts +19 -0
- package/dist/utils/source-reader.d.ts.map +1 -0
- package/dist/utils/source-reader.js +32 -0
- package/dist/utils/source-reader.js.map +1 -0
- package/logo.png +0 -0
- package/package.json +92 -0
- package/scripts/start-llm.mjs +161 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rothunter contributors
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="logo.png" alt="RotHunter" width="160" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">RotHunter</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
Self-hosted code-hygiene engine for TypeScript / JavaScript codebases.<br/>
|
|
9
|
+
Deterministic detectors + local LLM verdicts + dashboard.
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
## What it catches
|
|
13
|
+
|
|
14
|
+
24 detectors out of the box:
|
|
15
|
+
|
|
16
|
+
- Duplicate types / functions, dead modules / exports / handlers / api
|
|
17
|
+
- Race-condition / mutation / shared-db-write / api-race
|
|
18
|
+
- TSConfig / ESLint / Biome anti-patterns
|
|
19
|
+
- Silent catch, skip-tests (`.skip` / `.only`), TODO / FIXME / HACK comments
|
|
20
|
+
- Long files / functions, deep nesting
|
|
21
|
+
- Public `any`, mutable globals, unused deps, hot-hub files
|
|
22
|
+
- Similar functions (fuzzy clusters with canonical-pick + npm-package suggestion)
|
|
23
|
+
|
|
24
|
+
**Cross-service race detection** — `shared-db-write` and `api-race` run cross-workspace in monorepo mode, catching DB-column writes and write-endpoint calls that span service / repository boundaries. See [`docs/RACE-DETECTION.md`](./docs/RACE-DETECTION.md) for a walkthrough with three concrete scenarios.
|
|
25
|
+
|
|
26
|
+
Full detector list with severities + tunables: [`docs/DETECTORS.md`](./docs/DETECTORS.md).
|
|
27
|
+
|
|
28
|
+
### Coverage by mode
|
|
29
|
+
|
|
30
|
+
| Mode | Detectors |
|
|
31
|
+
|---|---|
|
|
32
|
+
| Single-workspace | All 24 |
|
|
33
|
+
| Multi-workspace (cross-repo via `rothunter.config.json`) | 9 cross-repo always-on (duplicate-type, duplicate-function, dead-module, dead-export, dead-api, long-function, deep-nesting, public-any, hot-hub-file) + the remaining 15 looped per workspace with workspace-namespaced fingerprints |
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
npm run dev:full # auto-picks the fastest LLM backend + server + UI
|
|
40
|
+
# → server on :3000, UI on :5173, LLM on :8080
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`dev:full` runs `scripts/start-llm.mjs` which auto-detects the best
|
|
44
|
+
available LLM backend on your host and starts it alongside the server +
|
|
45
|
+
UI. Selection order:
|
|
46
|
+
|
|
47
|
+
| # | Backend | When picked | Notes |
|
|
48
|
+
|---|---------|-------------|-------|
|
|
49
|
+
| 1 | **llama.cpp native** (`llama-server`) | `llama-server` on PATH | Uses Metal on macOS / CUDA on Linux when the binary was built with GPU support. |
|
|
50
|
+
| 2 | **Docker** (`docker compose up rothunter-llm`) | Docker Desktop available | Slower on macOS (no Metal inside the Linux VM) but works on any platform. |
|
|
51
|
+
|
|
52
|
+
Force a specific backend:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
ROTHUNTER_LLM_BACKEND=llamacpp npm run dev:full # or docker
|
|
56
|
+
ROTHUNTER_LLM_MODEL=bartowski/Qwen2.5-Coder-7B-Instruct-GGUF npm run dev:full
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Install an LLM backend
|
|
60
|
+
|
|
61
|
+
Pick ONE — `dev:full` picks the first available.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# Native llama.cpp (recommended — uses Metal on macOS, CUDA on Linux)
|
|
65
|
+
brew install llama.cpp # macOS
|
|
66
|
+
# Linux: see https://github.com/ggml-org/llama.cpp
|
|
67
|
+
|
|
68
|
+
# Cross-platform sandbox: Docker (slower on macOS — no GPU passthrough)
|
|
69
|
+
docker --version
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Run pieces individually
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm run dev # server + UI only (assumes the LLM is already up)
|
|
76
|
+
npm run llm # auto-detected LLM only
|
|
77
|
+
npm run docker # full docker-compose stack (server + llama.cpp sidecar)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Or point at a remote LLM
|
|
81
|
+
|
|
82
|
+
If you already have an OpenAI-compatible endpoint (vLLM, OpenRouter, LM
|
|
83
|
+
Studio, an on-prem cluster …) skip the auto-launch entirely:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export ROTHUNTER_LLM_BASE_URL=https://my-llm.internal/v1
|
|
87
|
+
export ROTHUNTER_LLM_MODEL=Qwen2.5-Coder-14B-Instruct
|
|
88
|
+
export ROTHUNTER_LLM_API_KEY=... # if the endpoint needs auth
|
|
89
|
+
export ROTHUNTER_LLM_CONCURRENCY=4 # 1 = sequential, raise for vLLM / llama.cpp --parallel N
|
|
90
|
+
npm run dev
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Layout
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
src/
|
|
97
|
+
detectors/ — 24 deterministic detectors
|
|
98
|
+
extraction/ — LLM confirmers
|
|
99
|
+
parsers/ — ts-morph symbol + import graph
|
|
100
|
+
graph/ — import-graph + entry-point resolution
|
|
101
|
+
server/ — Fastify HTTP API + SSE scan stream
|
|
102
|
+
ui/ — React / Vite / Tailwind dashboard
|
|
103
|
+
docker/ — compose + Dockerfile
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
Every knob is an environment variable; see [`.env.example`](./.env.example) for the full list with defaults. The most common:
|
|
109
|
+
|
|
110
|
+
| Variable | Default | Purpose |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| `ROTHUNTER_PORT` | `3000` | HTTP API port |
|
|
113
|
+
| `ROTHUNTER_HOST` | `127.0.0.1` | Bind address — `0.0.0.0` exposes the API to LAN |
|
|
114
|
+
| `ROTHUNTER_FS_ROOTS` | `$HOME` (+ `/workspace` in docker) | Colon-separated allow-roots for workspace switches |
|
|
115
|
+
| `ROTHUNTER_LLM_BASE_URL` | `http://127.0.0.1:8080/v1` | OpenAI-compatible LLM endpoint |
|
|
116
|
+
| `ROTHUNTER_LLM_MODEL` | `bartowski/Qwen2.5-Coder-14B-Instruct-GGUF` | HF repo id |
|
|
117
|
+
| `ROTHUNTER_LLM_CONCURRENCY` | `min(8, cores / 2)` | Parallel verdict requests |
|
|
118
|
+
|
|
119
|
+
## Security model
|
|
120
|
+
|
|
121
|
+
The HTTP API has no authentication — it relies on the loopback bind. Treat rothunter as a single-tenant developer tool, not a hosted service.
|
|
122
|
+
|
|
123
|
+
- Server binds `127.0.0.1` by default. Setting `ROTHUNTER_HOST=0.0.0.0` exposes the API to anyone on the network.
|
|
124
|
+
- The server can only read / write paths under `ROTHUNTER_FS_ROOTS`. Workspace switches outside this set return HTTP 403.
|
|
125
|
+
- LLM confirmers send code excerpts (±8 lines around the finding plus the enclosing signature) to the configured endpoint. Default is a loopback `llama.cpp` instance — nothing leaves the host. Verify the data-retention policy before pointing at a remote endpoint.
|
|
126
|
+
|
|
127
|
+
Full threat model + vulnerability reporting in [`SECURITY.md`](./SECURITY.md).
|
|
128
|
+
|
|
129
|
+
## Roadmap
|
|
130
|
+
|
|
131
|
+
See [`ROADMAP.md`](./ROADMAP.md) for planned detectors (TypeScript misuse:
|
|
132
|
+
`any-leak` / `god-type` / `everything-optional` / `wide-string-type` /
|
|
133
|
+
`boolean-trap`) and other queued improvements.
|
|
134
|
+
|
|
135
|
+
## Contributing
|
|
136
|
+
|
|
137
|
+
PRs welcome. See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for the detector-author checklist and quality bar.
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT — see [`LICENSE`](./LICENSE).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export interface ChatMessage {
|
|
2
|
+
role: 'system' | 'user' | 'assistant';
|
|
3
|
+
content: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ChatOptions {
|
|
6
|
+
temperature?: number;
|
|
7
|
+
json?: boolean;
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
maxTokens?: number;
|
|
10
|
+
}
|
|
11
|
+
export interface LlmClientOptions {
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
defaultTimeoutMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export declare class LlmClient {
|
|
18
|
+
private readonly baseUrl;
|
|
19
|
+
private readonly modelOverride;
|
|
20
|
+
private readonly apiKey?;
|
|
21
|
+
private readonly defaultTimeoutMs;
|
|
22
|
+
/**
|
|
23
|
+
* Resolved model id used in actual requests. Populated either from
|
|
24
|
+
* the explicit override (env / constructor) or via a one-shot probe
|
|
25
|
+
* of `GET /v1/models` against the configured base URL. The probe
|
|
26
|
+
* keeps `npm run dev:full` working when the operator's env override
|
|
27
|
+
* doesn't match what the backend actually has loaded.
|
|
28
|
+
*/
|
|
29
|
+
private resolvedModel;
|
|
30
|
+
/** Tag of the backend we resolved against — exposed for the settings UI. */
|
|
31
|
+
resolvedBackend: 'llamacpp' | 'remote' | 'unknown';
|
|
32
|
+
/**
|
|
33
|
+
* Pure constructor — never reads process.env. Pass explicit opts
|
|
34
|
+
* (typical production wiring is via `createDefaultLlmClient()`, which
|
|
35
|
+
* collects env defaults). Keeping the constructor env-free makes the
|
|
36
|
+
* class trivially mockable + test-isolated.
|
|
37
|
+
*/
|
|
38
|
+
constructor(opts?: LlmClientOptions);
|
|
39
|
+
/**
|
|
40
|
+
* One-shot model discovery against `/v1/models`. Picks a sensible
|
|
41
|
+
* default when the operator hasn't set `ROTHUNTER_LLM_MODEL` or when
|
|
42
|
+
* the operator's override is no longer loaded on the backend.
|
|
43
|
+
* Idempotent — repeat calls reuse `resolvedModel`.
|
|
44
|
+
*/
|
|
45
|
+
private resolveModel;
|
|
46
|
+
chat(messages: ChatMessage[], options?: ChatOptions): Promise<string>;
|
|
47
|
+
/**
|
|
48
|
+
* Issues a tiny completion to ensure the model is loaded into memory
|
|
49
|
+
* and the first forward pass is compiled. Uses a short timeout
|
|
50
|
+
* (5 s) so the orchestrator can detect "no LLM available" quickly
|
|
51
|
+
* and skip the confirmation pass instead of spending the full
|
|
52
|
+
* verdict timeout on every finding.
|
|
53
|
+
*
|
|
54
|
+
* Returns `true` on success, `false` on any failure — let the caller
|
|
55
|
+
* decide whether to continue, retry, or fall back to deterministic
|
|
56
|
+
* verdicts only.
|
|
57
|
+
*/
|
|
58
|
+
warmup(): Promise<boolean>;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Build an LlmClient from `ROTHUNTER_LLM_*` env vars (with the standard
|
|
62
|
+
* fallback chain for the API key: ROTHUNTER_LLM_API_KEY →
|
|
63
|
+
* OPENROUTER_API_KEY → OPENAI_API_KEY). All production call sites route
|
|
64
|
+
* through here so env coupling lives in one place. Tests construct
|
|
65
|
+
* `LlmClient` directly with explicit opts to bypass env reads.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createDefaultLlmClient(): LlmClient;
|
|
68
|
+
//# sourceMappingURL=llm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.d.ts","sourceRoot":"","sources":["../../src/adapters/llm.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAcD,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C;;;;;;OAMG;IACH,OAAO,CAAC,aAAa,CAAqB;IAC1C,4EAA4E;IACrE,eAAe,EAAE,UAAU,GAAG,QAAQ,GAAG,SAAS,CAAa;IAEtE;;;;;OAKG;gBACS,IAAI,GAAE,gBAAqB;IAQvC;;;;;OAKG;YACW,YAAY;IA2BpB,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,MAAM,CAAC;IA0C/E;;;;;;;;;;OAUG;IACG,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC;CAWjC;AAkBD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,SAAS,CA2BlD"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// OpenAI-compat /v1/chat/completions client. Works with llama-server,
|
|
2
|
+
// vLLM, OpenRouter, LM Studio, and any other backend that speaks the
|
|
3
|
+
// OpenAI chat-completion schema.
|
|
4
|
+
//
|
|
5
|
+
// Env: ROTHUNTER_LLM_BASE_URL / _MODEL / _API_KEY / _TIMEOUT_MS.
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import * as os from 'node:os';
|
|
9
|
+
const DEFAULT_BASE_URL = 'http://127.0.0.1:8080/v1';
|
|
10
|
+
// Qwen2.5-Coder-14B Q4_K_M (~8GB) hits 8/8 on the golden eval — the
|
|
11
|
+
// smallest local model we evaluated that gets every borderline pair
|
|
12
|
+
// right.
|
|
13
|
+
const DEFAULT_MODEL = 'bartowski/Qwen2.5-Coder-14B-Instruct-GGUF';
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
15
|
+
export class LlmClient {
|
|
16
|
+
baseUrl;
|
|
17
|
+
modelOverride;
|
|
18
|
+
apiKey;
|
|
19
|
+
defaultTimeoutMs;
|
|
20
|
+
/**
|
|
21
|
+
* Resolved model id used in actual requests. Populated either from
|
|
22
|
+
* the explicit override (env / constructor) or via a one-shot probe
|
|
23
|
+
* of `GET /v1/models` against the configured base URL. The probe
|
|
24
|
+
* keeps `npm run dev:full` working when the operator's env override
|
|
25
|
+
* doesn't match what the backend actually has loaded.
|
|
26
|
+
*/
|
|
27
|
+
resolvedModel;
|
|
28
|
+
/** Tag of the backend we resolved against — exposed for the settings UI. */
|
|
29
|
+
resolvedBackend = 'unknown';
|
|
30
|
+
/**
|
|
31
|
+
* Pure constructor — never reads process.env. Pass explicit opts
|
|
32
|
+
* (typical production wiring is via `createDefaultLlmClient()`, which
|
|
33
|
+
* collects env defaults). Keeping the constructor env-free makes the
|
|
34
|
+
* class trivially mockable + test-isolated.
|
|
35
|
+
*/
|
|
36
|
+
constructor(opts = {}) {
|
|
37
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
38
|
+
this.modelOverride = opts.model;
|
|
39
|
+
this.resolvedModel = opts.model;
|
|
40
|
+
this.apiKey = opts.apiKey;
|
|
41
|
+
this.defaultTimeoutMs = opts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* One-shot model discovery against `/v1/models`. Picks a sensible
|
|
45
|
+
* default when the operator hasn't set `ROTHUNTER_LLM_MODEL` or when
|
|
46
|
+
* the operator's override is no longer loaded on the backend.
|
|
47
|
+
* Idempotent — repeat calls reuse `resolvedModel`.
|
|
48
|
+
*/
|
|
49
|
+
async resolveModel() {
|
|
50
|
+
if (this.resolvedModel && this.resolvedBackend !== 'unknown')
|
|
51
|
+
return this.resolvedModel;
|
|
52
|
+
try {
|
|
53
|
+
const res = await fetch(`${this.baseUrl}/models`, {
|
|
54
|
+
signal: AbortSignal.timeout(5000),
|
|
55
|
+
headers: this.apiKey ? { authorization: `Bearer ${this.apiKey}` } : undefined,
|
|
56
|
+
});
|
|
57
|
+
if (!res.ok)
|
|
58
|
+
return this.resolvedModel;
|
|
59
|
+
const data = (await res.json());
|
|
60
|
+
const ids = (data.data ?? []).map((m) => m.id).filter((id) => typeof id === 'string');
|
|
61
|
+
if (ids.length === 0)
|
|
62
|
+
return this.resolvedModel;
|
|
63
|
+
const looksLocal = /127\.0\.0\.1|localhost|0\.0\.0\.0|host\.docker\.internal/.test(this.baseUrl);
|
|
64
|
+
this.resolvedBackend = looksLocal ? 'llamacpp' : 'remote';
|
|
65
|
+
// Honour the operator override when set + still reachable;
|
|
66
|
+
// otherwise fall back to the first reported id.
|
|
67
|
+
if (this.modelOverride && ids.includes(this.modelOverride)) {
|
|
68
|
+
this.resolvedModel = this.modelOverride;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.resolvedModel = ids[0];
|
|
72
|
+
}
|
|
73
|
+
return this.resolvedModel;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return this.resolvedModel;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async chat(messages, options = {}) {
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const timeoutMs = options.timeoutMs ?? this.defaultTimeoutMs;
|
|
82
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
83
|
+
const model = (await this.resolveModel()) ?? this.modelOverride ?? DEFAULT_MODEL;
|
|
84
|
+
const body = {
|
|
85
|
+
model,
|
|
86
|
+
messages,
|
|
87
|
+
stream: false,
|
|
88
|
+
temperature: options.temperature ?? 0,
|
|
89
|
+
// Hard cap by default. Without this the server happily generates
|
|
90
|
+
// thousands of tokens and a single request can monopolize the
|
|
91
|
+
// worker for minutes. 512 is enough for verdict JSON even when
|
|
92
|
+
// the backend is a reasoning-tuned model emitting a <think>
|
|
93
|
+
// block before the structured answer.
|
|
94
|
+
max_tokens: options.maxTokens ?? 512,
|
|
95
|
+
};
|
|
96
|
+
const headers = { 'content-type': 'application/json' };
|
|
97
|
+
if (this.apiKey)
|
|
98
|
+
headers.authorization = `Bearer ${this.apiKey}`;
|
|
99
|
+
try {
|
|
100
|
+
const res = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers,
|
|
103
|
+
body: JSON.stringify(body),
|
|
104
|
+
signal: controller.signal,
|
|
105
|
+
});
|
|
106
|
+
if (!res.ok) {
|
|
107
|
+
const text = await res.text().catch(() => '');
|
|
108
|
+
throw new Error(`LLM HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
109
|
+
}
|
|
110
|
+
const data = (await res.json());
|
|
111
|
+
const err = typeof data.error === 'string' ? data.error : data.error?.message;
|
|
112
|
+
if (err)
|
|
113
|
+
throw new Error(`LLM error: ${err}`);
|
|
114
|
+
return data.choices?.[0]?.message?.content ?? '';
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
clearTimeout(timer);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Issues a tiny completion to ensure the model is loaded into memory
|
|
122
|
+
* and the first forward pass is compiled. Uses a short timeout
|
|
123
|
+
* (5 s) so the orchestrator can detect "no LLM available" quickly
|
|
124
|
+
* and skip the confirmation pass instead of spending the full
|
|
125
|
+
* verdict timeout on every finding.
|
|
126
|
+
*
|
|
127
|
+
* Returns `true` on success, `false` on any failure — let the caller
|
|
128
|
+
* decide whether to continue, retry, or fall back to deterministic
|
|
129
|
+
* verdicts only.
|
|
130
|
+
*/
|
|
131
|
+
async warmup() {
|
|
132
|
+
try {
|
|
133
|
+
await this.chat([{ role: 'user', content: 'ok' }], { temperature: 0, maxTokens: 1, timeoutMs: 5_000 });
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Read the marker file written by `scripts/start-llm.mjs` so the server
|
|
143
|
+
* picks up the model + port that script actually launched, without
|
|
144
|
+
* needing the operator to keep ROTHUNTER_LLM_MODEL in sync by hand.
|
|
145
|
+
* Returns null when no marker exists.
|
|
146
|
+
*/
|
|
147
|
+
function readLlmMarker() {
|
|
148
|
+
try {
|
|
149
|
+
const p = path.join(os.homedir(), '.rothunter', 'llm-active.json');
|
|
150
|
+
if (!fs.existsSync(p))
|
|
151
|
+
return null;
|
|
152
|
+
return JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Build an LlmClient from `ROTHUNTER_LLM_*` env vars (with the standard
|
|
160
|
+
* fallback chain for the API key: ROTHUNTER_LLM_API_KEY →
|
|
161
|
+
* OPENROUTER_API_KEY → OPENAI_API_KEY). All production call sites route
|
|
162
|
+
* through here so env coupling lives in one place. Tests construct
|
|
163
|
+
* `LlmClient` directly with explicit opts to bypass env reads.
|
|
164
|
+
*/
|
|
165
|
+
export function createDefaultLlmClient() {
|
|
166
|
+
const envTimeout = Number(process.env.ROTHUNTER_LLM_TIMEOUT_MS);
|
|
167
|
+
// Marker beats the hard-coded default but loses to an explicit env
|
|
168
|
+
// override. Operator wins, automation second, default last.
|
|
169
|
+
const marker = readLlmMarker();
|
|
170
|
+
// Marker file lives under $HOME, so it could be doctored by another
|
|
171
|
+
// process running as the same user. Validate `port` is an integer
|
|
172
|
+
// in the legal TCP-port range before interpolating into a URL so a
|
|
173
|
+
// hostile marker can't redirect the engine at an arbitrary host.
|
|
174
|
+
const markerPort = marker?.port && /^\d{1,5}$/.test(marker.port) && Number(marker.port) > 0 && Number(marker.port) <= 65_535
|
|
175
|
+
? marker.port
|
|
176
|
+
: undefined;
|
|
177
|
+
const baseUrl = process.env.ROTHUNTER_LLM_BASE_URL ??
|
|
178
|
+
(markerPort ? `http://127.0.0.1:${markerPort}/v1` : undefined);
|
|
179
|
+
const model = process.env.ROTHUNTER_LLM_MODEL ?? marker?.model;
|
|
180
|
+
return new LlmClient({
|
|
181
|
+
baseUrl,
|
|
182
|
+
model,
|
|
183
|
+
apiKey: process.env.ROTHUNTER_LLM_API_KEY ??
|
|
184
|
+
process.env.OPENROUTER_API_KEY ??
|
|
185
|
+
process.env.OPENAI_API_KEY,
|
|
186
|
+
defaultTimeoutMs: Number.isFinite(envTimeout) && envTimeout > 0 ? envTimeout : undefined,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=llm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llm.js","sourceRoot":"","sources":["../../src/adapters/llm.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RotHunter configuration — one file per linked-workspaces group.
|
|
3
|
+
*
|
|
4
|
+
* Discovered at scan time by `loadRotHunterConfig(workspaceRoot)`. Looks for
|
|
5
|
+
* `rothunter.config.json` in the workspace root, then in `.rothunter/config.json`.
|
|
6
|
+
* Returns null if no config is present (single-workspace mode).
|
|
7
|
+
*
|
|
8
|
+
* Schema:
|
|
9
|
+
* {
|
|
10
|
+
* "workspaces": [
|
|
11
|
+
* { "path": ".", "name": "backend", "package": "@org/backend" },
|
|
12
|
+
* { "path": "../frontend", "name": "frontend", "package": "@org/frontend" }
|
|
13
|
+
* ]
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* - `path` is relative to the config file's directory (or absolute).
|
|
17
|
+
* - `name` is a stable logical identifier used as a path prefix in
|
|
18
|
+
* findings + fingerprints.
|
|
19
|
+
* - `package` is optional. When set, a bare import specifier matching
|
|
20
|
+
* that package name (e.g. `import { X } from '@org/backend'`) is
|
|
21
|
+
* resolved into that workspace's source tree.
|
|
22
|
+
*/
|
|
23
|
+
export interface WorkspaceConfig {
|
|
24
|
+
/** Absolute path on disk. */
|
|
25
|
+
rootAbs: string;
|
|
26
|
+
/** Logical name used as a workspace ID + finding prefix. */
|
|
27
|
+
name: string;
|
|
28
|
+
/** Package name (npm-style) for bare-specifier resolution into this workspace. */
|
|
29
|
+
packageName?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface RotHunterConfig {
|
|
32
|
+
workspaces: WorkspaceConfig[];
|
|
33
|
+
/** Absolute path of the config file that produced this object. */
|
|
34
|
+
configPath: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function loadRotHunterConfig(workspaceRoot: string): RotHunterConfig | null;
|
|
37
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,eAAe;IAC9B,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;CACpB;AA0BD,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAkBjF"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { discoverMonorepoWorkspaces } from './graph/monorepo-detect.js';
|
|
5
|
+
const CONFIG_CANDIDATES = ['rothunter.config.json', '.rothunter/config.json'];
|
|
6
|
+
// Shape mirrors src/schemas/rothunter.config.schema.json — that JSON
|
|
7
|
+
// Schema powers IDE autocomplete + inline validation, this zod schema
|
|
8
|
+
// powers runtime validation with line-accurate error messages. Keep the
|
|
9
|
+
// two in sync.
|
|
10
|
+
const RawConfigSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
$schema: z.string().url().optional(),
|
|
13
|
+
workspaces: z
|
|
14
|
+
.array(z
|
|
15
|
+
.object({
|
|
16
|
+
path: z.string().min(1, 'path is required'),
|
|
17
|
+
name: z.string().min(1, 'name is required'),
|
|
18
|
+
package: z.string().min(1).optional(),
|
|
19
|
+
})
|
|
20
|
+
.strict())
|
|
21
|
+
.min(1, 'workspaces must be a non-empty array'),
|
|
22
|
+
})
|
|
23
|
+
.strict();
|
|
24
|
+
export function loadRotHunterConfig(workspaceRoot) {
|
|
25
|
+
for (const candidate of CONFIG_CANDIDATES) {
|
|
26
|
+
const abs = path.join(workspaceRoot, candidate);
|
|
27
|
+
if (!fs.existsSync(abs))
|
|
28
|
+
continue;
|
|
29
|
+
return parseConfig(abs);
|
|
30
|
+
}
|
|
31
|
+
// Fall back to monorepo auto-detection. Reads package.json#workspaces
|
|
32
|
+
// (npm / yarn / yarn-berry / bun / Turbo / Lerna), pnpm-workspace.yaml,
|
|
33
|
+
// and nx.json. If any sibling packages exist we ingest them automatically
|
|
34
|
+
// so users don't have to write a rothunter.config.json by hand.
|
|
35
|
+
const auto = discoverMonorepoWorkspaces(workspaceRoot);
|
|
36
|
+
if (auto && auto.length > 0) {
|
|
37
|
+
return {
|
|
38
|
+
workspaces: auto,
|
|
39
|
+
configPath: `${workspaceRoot}/[auto-detected]`,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function parseConfig(configPath) {
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
throw new Error(`${configPath}: invalid JSON — ${err.message}`, { cause: err });
|
|
51
|
+
}
|
|
52
|
+
const parsed = RawConfigSchema.safeParse(raw);
|
|
53
|
+
if (!parsed.success) {
|
|
54
|
+
const issues = parsed.error.issues
|
|
55
|
+
.map((i) => ` - ${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
56
|
+
.join('\n');
|
|
57
|
+
throw new Error(`${configPath}: schema validation failed:\n${issues}`);
|
|
58
|
+
}
|
|
59
|
+
const data = parsed.data;
|
|
60
|
+
const configDir = path.dirname(configPath);
|
|
61
|
+
const workspaces = data.workspaces.map((entry, i) => {
|
|
62
|
+
const rootAbs = path.isAbsolute(entry.path) ? entry.path : path.resolve(configDir, entry.path);
|
|
63
|
+
if (!fs.existsSync(rootAbs)) {
|
|
64
|
+
throw new Error(`${configPath}: workspaces[${i}].path "${entry.path}" does not exist on disk.`);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
rootAbs,
|
|
68
|
+
name: entry.name,
|
|
69
|
+
packageName: entry.package,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
const names = new Set();
|
|
73
|
+
for (const w of workspaces) {
|
|
74
|
+
if (names.has(w.name)) {
|
|
75
|
+
throw new Error(`${configPath}: duplicate workspace name "${w.name}".`);
|
|
76
|
+
}
|
|
77
|
+
names.add(w.name);
|
|
78
|
+
}
|
|
79
|
+
return { workspaces, configPath };
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for detector IDs.
|
|
3
|
+
*
|
|
4
|
+
* Anything that lists every detector (the server's settings UI, the CLI
|
|
5
|
+
* `--detectors` flag validation, future detector documentation) imports
|
|
6
|
+
* from here. Previously the server/index.ts hardcoded a parallel list
|
|
7
|
+
* that drifted when new detectors were wired into rothunter.ts.
|
|
8
|
+
*
|
|
9
|
+
* To register a new detector:
|
|
10
|
+
* 1. Add its id below.
|
|
11
|
+
* 2. Import + invoke it from rothunter.ts.
|
|
12
|
+
* 3. (Optional) Tag it as `singleWorkspaceOnly` if it can't run when
|
|
13
|
+
* multi-workspace-scanner is active — see the `MULTI_WORKSPACE_*`
|
|
14
|
+
* sets below for the current split.
|
|
15
|
+
*/
|
|
16
|
+
export declare const DETECTOR_IDS: readonly ["duplicate-type", "duplicate-function", "dead-module", "dead-export", "dead-api", "long-function", "deep-nesting", "public-any", "hot-hub-file", "dead-handler", "mutation", "race-condition", "shared-db-write", "api-race", "bad-config", "silent-catch", "skip-tests", "long-file", "console-log-prod", "magic-numbers", "mutable-globals", "unused-deps", "similar-functions", "todo-comments"];
|
|
17
|
+
export type DetectorId = (typeof DETECTOR_IDS)[number];
|
|
18
|
+
/**
|
|
19
|
+
* Detectors that need real workspace-root-relative file paths or git
|
|
20
|
+
* access on a single workspace. Multi-workspace mode skips these because
|
|
21
|
+
* multi-workspace-scanner prefixes paths with the workspace name (see
|
|
22
|
+
* the warn-log in rothunter.ts run()).
|
|
23
|
+
*/
|
|
24
|
+
export declare const MULTI_WORKSPACE_SKIPPED: Set<"duplicate-type" | "duplicate-function" | "dead-module" | "dead-export" | "dead-api" | "long-function" | "deep-nesting" | "public-any" | "hot-hub-file" | "dead-handler" | "mutation" | "race-condition" | "shared-db-write" | "api-race" | "bad-config" | "silent-catch" | "skip-tests" | "long-file" | "console-log-prod" | "magic-numbers" | "mutable-globals" | "unused-deps" | "similar-functions" | "todo-comments">;
|
|
25
|
+
/**
|
|
26
|
+
* Cross-repo detector: only runs in multi-workspace mode (its whole
|
|
27
|
+
* purpose is to flag exported symbols that nobody imports across
|
|
28
|
+
* workspace boundaries — in single-workspace mode dead-export already
|
|
29
|
+
* covers that ground).
|
|
30
|
+
*/
|
|
31
|
+
export declare const MULTI_WORKSPACE_ONLY: Set<"duplicate-type" | "duplicate-function" | "dead-module" | "dead-export" | "dead-api" | "long-function" | "deep-nesting" | "public-any" | "hot-hub-file" | "dead-handler" | "mutation" | "race-condition" | "shared-db-write" | "api-race" | "bad-config" | "silent-catch" | "skip-tests" | "long-file" | "console-log-prod" | "magic-numbers" | "mutable-globals" | "unused-deps" | "similar-functions" | "todo-comments">;
|
|
32
|
+
//# sourceMappingURL=detector-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detector-registry.d.ts","sourceRoot":"","sources":["../src/detector-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,eAAO,MAAM,YAAY,+YA2Bf,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,gaAgBlC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,gaAAoC,CAAC"}
|