claude-crap 0.3.8 → 0.4.0
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/CHANGELOG.md +33 -0
- package/README.md +69 -27
- package/dist/adapters/common.d.ts +1 -1
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +1 -1
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/dotnet-format.d.ts +35 -0
- package/dist/adapters/dotnet-format.d.ts.map +1 -0
- package/dist/adapters/dotnet-format.js +96 -0
- package/dist/adapters/dotnet-format.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +4 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/crap-config.d.ts +2 -0
- package/dist/crap-config.d.ts.map +1 -1
- package/dist/crap-config.js +19 -4
- package/dist/crap-config.js.map +1 -1
- package/dist/dashboard/server.js +1 -1
- package/dist/index.js +74 -5
- package/dist/index.js.map +1 -1
- package/dist/monorepo/project-map.d.ts +112 -0
- package/dist/monorepo/project-map.d.ts.map +1 -0
- package/dist/monorepo/project-map.js +384 -0
- package/dist/monorepo/project-map.js.map +1 -0
- package/dist/scanner/bootstrap.d.ts.map +1 -1
- package/dist/scanner/bootstrap.js +6 -1
- package/dist/scanner/bootstrap.js.map +1 -1
- package/dist/scanner/detector.d.ts.map +1 -1
- package/dist/scanner/detector.js +7 -2
- package/dist/scanner/detector.js.map +1 -1
- package/dist/scanner/runner.d.ts.map +1 -1
- package/dist/scanner/runner.js +13 -0
- package/dist/scanner/runner.js.map +1 -1
- package/dist/schemas/tool-schemas.d.ts +16 -1
- package/dist/schemas/tool-schemas.d.ts.map +1 -1
- package/dist/schemas/tool-schemas.js +16 -1
- package/dist/schemas/tool-schemas.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +37 -0
- package/plugin/bundle/mcp-server.mjs +395 -29
- package/plugin/bundle/mcp-server.mjs.map +4 -4
- package/plugin/package-lock.json +2 -2
- package/plugin/package.json +1 -1
- package/src/adapters/common.ts +1 -1
- package/src/adapters/dotnet-format.ts +125 -0
- package/src/adapters/index.ts +4 -0
- package/src/crap-config.ts +27 -4
- package/src/dashboard/server.ts +1 -1
- package/src/index.ts +88 -5
- package/src/monorepo/project-map.ts +476 -0
- package/src/scanner/bootstrap.ts +7 -1
- package/src/scanner/detector.ts +7 -2
- package/src/scanner/runner.ts +13 -0
- package/src/schemas/tool-schemas.ts +17 -1
- package/src/tests/adapters/dispatch.test.ts +1 -1
- package/src/tests/auto-scan.test.ts +2 -2
- package/src/tests/boot-monorepo.test.ts +804 -0
- package/src/tests/boot-scanner-detection.test.ts +692 -0
- package/src/tests/boot-single-project.test.ts +780 -0
- package/src/tests/integration/mcp-server.integration.test.ts +2 -1
- package/src/tests/project-map.test.ts +302 -0
- package/src/tests/scanner-detector.test.ts +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,39 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-04-13
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Monorepo project map** — auto-discovers sub-projects at session boot
|
|
13
|
+
by probing npm workspaces and `apps/`, `packages/`, `libs/` directories.
|
|
14
|
+
Persisted to `.claude-crap/projects.json`.
|
|
15
|
+
- **`list_projects` MCP tool** — returns all sub-projects with type, path,
|
|
16
|
+
recommended scanner, and availability.
|
|
17
|
+
- **Scoped `score_project`** — optional `scope` parameter to score a
|
|
18
|
+
single sub-project instead of the entire monorepo.
|
|
19
|
+
- **Dart analyzer scanner** — `dart analyze --format=json` → SARIF 2.1.0.
|
|
20
|
+
Auto-detected in monorepo subdirectories via `pubspec.yaml`.
|
|
21
|
+
- **`dotnet format` scanner** — built-in Roslyn analyzer for C# projects.
|
|
22
|
+
No extra install needed when .NET SDK is present.
|
|
23
|
+
- **Centralized file exclusions** — shared module replaces 3 independent
|
|
24
|
+
`SKIP_DIRS` sets. Covers `bundle/`, `vendor/`, `.astro`, `.svelte-kit`,
|
|
25
|
+
`.dart_tool`, `.expo`, `.angular`, `.turbo`, and 15+ more.
|
|
26
|
+
- **User-configurable `exclude`** — `.claude-crap.json` now supports an
|
|
27
|
+
`exclude` array with glob patterns and directory exclusions.
|
|
28
|
+
- **Auto-bootstrap ESLint** in monorepos — detects JS/TS sub-projects
|
|
29
|
+
and installs ESLint at root automatically.
|
|
30
|
+
- **Auto-sync plugin cache** — `npm run build:plugin` syncs to all
|
|
31
|
+
cached versions under `~/.claude/plugins/cache/`.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- C# projects now use `dotnet format` instead of Semgrep.
|
|
36
|
+
- Scanner detector validates `node_modules/.bin/` binary exists before
|
|
37
|
+
marking a package.json dependency as available.
|
|
38
|
+
- Workspace LOC excludes bundle/vendor files (dropped from ~25K to ~15K
|
|
39
|
+
for the claude-crap repo itself).
|
|
40
|
+
|
|
8
41
|
## [0.3.6] - 2026-04-12
|
|
9
42
|
|
|
10
43
|
### Fixed
|
package/README.md
CHANGED
|
@@ -100,8 +100,9 @@ CRAP formula, TDR formula, letter ratings, and adoption strategy.
|
|
|
100
100
|
| **Stop quality gate** | `plugin/hooks/stop-quality-gate.mjs` | Reads the SARIF store, computes CRAP / TDR / reliability / security ratings, and blocks task close if any metric is outside policy. |
|
|
101
101
|
| **MCP server** | `src/index.ts` | Stdio-transport server exposing CRAP, TDR, tree-sitter AST, and SARIF engines as deterministic tools. |
|
|
102
102
|
| **SARIF store** | `src/sarif/sarif-store.ts` | On-disk consolidated report with finding deduplication. Tolerates malformed entries so a tampered file can't DoS the boot. |
|
|
103
|
-
| **Scanner adapters** | `src/adapters/` |
|
|
104
|
-
| **
|
|
103
|
+
| **Scanner adapters** | `src/adapters/` | ESLint, Semgrep, Bandit, Stryker, `dart analyze`, `dotnet format` — each stamps `effortMinutes` for uniform TDR computation. |
|
|
104
|
+
| **Project map** | `src/monorepo/project-map.ts` | Auto-discovers monorepo sub-projects at boot, persists to `.claude-crap/projects.json`. |
|
|
105
|
+
| **Dashboard** | `src/dashboard/server.ts` | Fastify on `127.0.0.1:5117` serving a Vue 3 SPA. Offline-capable (vendored runtime). PID-based port management. |
|
|
105
106
|
|
|
106
107
|
All findings are normalized to **SARIF 2.1.0** — one vocabulary,
|
|
107
108
|
exact coordinates, no grep walls in the context window.
|
|
@@ -113,7 +114,7 @@ for the boot sequence, data flow, and design decisions.
|
|
|
113
114
|
|
|
114
115
|
## MCP Tools
|
|
115
116
|
|
|
116
|
-
|
|
117
|
+
Ten deterministic tools and two resources, all with strict JSON Schema validation.
|
|
117
118
|
|
|
118
119
|
| Tool | Purpose |
|
|
119
120
|
| :--- | :------ |
|
|
@@ -121,11 +122,12 @@ Nine deterministic tools and two resources, all with strict JSON Schema validati
|
|
|
121
122
|
| `compute_tdr` | Technical Debt Ratio and A..E maintainability rating. |
|
|
122
123
|
| `analyze_file_ast` | Tree-sitter AST metrics: LOC + per-function cyclomatic complexity. TypeScript, JavaScript, Python, Java, C#. |
|
|
123
124
|
| `ingest_sarif` | Merge a raw SARIF 2.1.0 document into the store with deduplication. |
|
|
124
|
-
| `ingest_scanner_output` | Route native scanner output through adapter, enrich with `effortMinutes`, persist as SARIF. |
|
|
125
|
+
| `ingest_scanner_output` | Route native scanner output through adapter, enrich with `effortMinutes`, persist as SARIF. Supports ESLint, Semgrep, Bandit, Stryker, `dart_analyze`, and `dotnet_format`. |
|
|
125
126
|
| `require_test_harness` | Check whether a source file has an accompanying test file. |
|
|
126
|
-
| `score_project` | Aggregate workspace into
|
|
127
|
-
| `auto_scan` | Auto-detect scanners, run them, ingest findings. |
|
|
127
|
+
| `score_project` | Aggregate workspace into A..E grades. Optional `scope` parameter to score a single monorepo sub-project. |
|
|
128
|
+
| `auto_scan` | Auto-detect scanners (including monorepo subdirectories), run them, ingest findings. |
|
|
128
129
|
| `bootstrap_scanner` | Detect project type, install the right scanner, configure, and verify. |
|
|
130
|
+
| `list_projects` | List all discovered monorepo sub-projects with type, scanner, and availability. |
|
|
129
131
|
|
|
130
132
|
| Resource | Description |
|
|
131
133
|
| :------- | :---------- |
|
|
@@ -151,26 +153,66 @@ See [docs/contributing.md](./docs/contributing.md) for Windows setup details.
|
|
|
151
153
|
|
|
152
154
|
## Supported Languages & Scanners
|
|
153
155
|
|
|
154
|
-
| Language | Extensions | AST analysis | Scanner |
|
|
155
|
-
| :------- | :--------- | :----------: | :------ |
|
|
156
|
-
| TypeScript | `.ts` `.tsx` `.mts` `.cts` | Cyclomatic complexity | ESLint |
|
|
157
|
-
| JavaScript | `.js` `.jsx` `.mjs` `.cjs` | Cyclomatic complexity | ESLint |
|
|
158
|
-
| Python | `.py` `.pyi` | Cyclomatic complexity | Bandit |
|
|
159
|
-
| Java | `.java` | Cyclomatic complexity | Semgrep |
|
|
160
|
-
| C# | `.cs` | Cyclomatic complexity |
|
|
161
|
-
| Dart / Flutter | `.dart` | LOC only | `dart analyze` |
|
|
162
|
-
| Vue | `.vue` | LOC only | ESLint (via root) |
|
|
163
|
-
| Go | `.go` | LOC only | — | — |
|
|
164
|
-
| Rust | `.rs` | LOC only | — | — |
|
|
165
|
-
| Ruby | `.rb` | LOC only | — | — |
|
|
166
|
-
| PHP | `.php` | LOC only | — | — |
|
|
167
|
-
| Swift | `.swift` | LOC only | — | — |
|
|
168
|
-
| Kotlin | `.kt` | LOC only | — | — |
|
|
169
|
-
| Scala | `.scala` | LOC only | — | — |
|
|
170
|
-
|
|
171
|
-
**AST analysis** = tree-sitter cyclomatic complexity per function.
|
|
172
|
-
|
|
173
|
-
|
|
156
|
+
| Language | Extensions | AST analysis | Scanner | Setup |
|
|
157
|
+
| :------- | :--------- | :----------: | :------ | :---- |
|
|
158
|
+
| TypeScript | `.ts` `.tsx` `.mts` `.cts` | Cyclomatic complexity | ESLint | **Auto-installed** via npm |
|
|
159
|
+
| JavaScript | `.js` `.jsx` `.mjs` `.cjs` | Cyclomatic complexity | ESLint | **Auto-installed** via npm |
|
|
160
|
+
| Python | `.py` `.pyi` | Cyclomatic complexity | Bandit | `pip install bandit` |
|
|
161
|
+
| Java | `.java` | Cyclomatic complexity | Semgrep | `brew install semgrep` |
|
|
162
|
+
| C# / .NET | `.cs` | Cyclomatic complexity | `dotnet format` | **Included in .NET SDK** |
|
|
163
|
+
| Dart / Flutter | `.dart` | LOC only | `dart analyze` | **Included in Dart/Flutter SDK** |
|
|
164
|
+
| Vue | `.vue` | LOC only | ESLint (via root config) | Auto with TypeScript |
|
|
165
|
+
| Go | `.go` | LOC only | — | — |
|
|
166
|
+
| Rust | `.rs` | LOC only | — | — |
|
|
167
|
+
| Ruby | `.rb` | LOC only | — | — |
|
|
168
|
+
| PHP | `.php` | LOC only | — | — |
|
|
169
|
+
| Swift | `.swift` | LOC only | — | — |
|
|
170
|
+
| Kotlin | `.kt` | LOC only | — | — |
|
|
171
|
+
| Scala | `.scala` | LOC only | — | — |
|
|
172
|
+
|
|
173
|
+
**AST analysis** = tree-sitter cyclomatic complexity per function.
|
|
174
|
+
**LOC only** = counted toward workspace metrics but no per-function analysis.
|
|
175
|
+
|
|
176
|
+
### Monorepo auto-discovery
|
|
177
|
+
|
|
178
|
+
In monorepos, claude-crap automatically discovers sub-projects at
|
|
179
|
+
session startup — no per-project configuration needed. The plugin
|
|
180
|
+
probes npm workspaces and common directories (`apps/`, `packages/`,
|
|
181
|
+
`libs/`, `modules/`, `services/`) to build a **project map**:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
Session start
|
|
185
|
+
→ discover project map
|
|
186
|
+
→ detect: www (TypeScript), app (TypeScript), mobile (Dart), api (C#)
|
|
187
|
+
→ ESLint not installed? → auto-install at monorepo root
|
|
188
|
+
→ run ESLint from root (covers all JS/TS)
|
|
189
|
+
→ run dart analyze from apps/mobile/
|
|
190
|
+
→ run dotnet format from apps/api/
|
|
191
|
+
→ aggregate all findings into one SARIF store
|
|
192
|
+
→ score_project ready with real data
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The project map is persisted to `.claude-crap/projects.json` and
|
|
196
|
+
exposed via the `list_projects` MCP tool. Use `score_project` with
|
|
197
|
+
the optional `scope` parameter to score a single sub-project:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
// Score only the mobile app
|
|
201
|
+
score_project({ format: "both", scope: "mobile" })
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**File exclusions** are centralized and cover all major frameworks
|
|
205
|
+
out of the box: `dist/`, `build/`, `bundle/`, `vendor/`,
|
|
206
|
+
`.next`, `.nuxt`, `.astro`, `.svelte-kit`, `.dart_tool`,
|
|
207
|
+
`.expo`, `.angular`, `.turbo`, and more. Custom exclusions can be
|
|
208
|
+
added via `.claude-crap.json`:
|
|
209
|
+
|
|
210
|
+
```jsonc
|
|
211
|
+
{
|
|
212
|
+
"strictness": "strict",
|
|
213
|
+
"exclude": ["apps/legacy/", "generated/", "*.proto.ts"]
|
|
214
|
+
}
|
|
215
|
+
```
|
|
174
216
|
|
|
175
217
|
---
|
|
176
218
|
|
|
@@ -195,7 +237,7 @@ See [docs/contributing.md](./docs/contributing.md) for Windows setup details.
|
|
|
195
237
|
|
|
196
238
|
```bash
|
|
197
239
|
npm install # postinstall builds dist/ automatically
|
|
198
|
-
npm test #
|
|
240
|
+
npm test # 265 tests across 46 suites
|
|
199
241
|
npm run build:fast # esbuild dev build (10-20x faster than tsc)
|
|
200
242
|
npm run doctor # full diagnostic
|
|
201
243
|
```
|
|
@@ -21,7 +21,7 @@ import type { SarifLevel } from "../sarif/sarif-builder.js";
|
|
|
21
21
|
* `ingest_scanner_output` MCP tool uses this as its `enum` constraint,
|
|
22
22
|
* so keeping it narrow prevents drift.
|
|
23
23
|
*/
|
|
24
|
-
export declare const KNOWN_SCANNERS: readonly ["semgrep", "eslint", "bandit", "stryker", "dart_analyze"];
|
|
24
|
+
export declare const KNOWN_SCANNERS: readonly ["semgrep", "eslint", "bandit", "stryker", "dart_analyze", "dotnet_format"];
|
|
25
25
|
/**
|
|
26
26
|
* Union of supported scanner identifiers.
|
|
27
27
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"common.d.ts","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,cAAc,sFAAuF,CAAC;AAEnH;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,EAAE,QAAQ,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAK1E,CAAC;AAEH;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,mFAAmF;IACnF,QAAQ,CAAC,UAAU,EAAE,YAAY,CAAC;IAClC,gFAAgF;IAChF,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,0EAA0E;IAC1E,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,YAAY,EACxB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,GAC7B,cAAc,CAgBhB;AAyBD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAM9F"}
|
package/dist/adapters/common.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* `ingest_scanner_output` MCP tool uses this as its `enum` constraint,
|
|
20
20
|
* so keeping it narrow prevents drift.
|
|
21
21
|
*/
|
|
22
|
-
export const KNOWN_SCANNERS = ["semgrep", "eslint", "bandit", "stryker", "dart_analyze"];
|
|
22
|
+
export const KNOWN_SCANNERS = ["semgrep", "eslint", "bandit", "stryker", "dart_analyze", "dotnet_format"];
|
|
23
23
|
/**
|
|
24
24
|
* Default remediation effort in minutes per SARIF severity level. These
|
|
25
25
|
* numbers are deliberately conservative — real projects should override
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,CAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../src/adapters/common.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,cAAc,EAAE,eAAe,CAAU,CAAC;AAOnH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAyC,MAAM,CAAC,MAAM,CAAC;IAC5F,KAAK,EAAE,EAAE;IACT,OAAO,EAAE,EAAE;IACX,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,CAAC;CACR,CAAC,CAAC;AAmBH;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAChC,UAAwB,EACxB,OAAe,EACf,OAA8B;IAE9B,OAAO;QACL,OAAO,EAAE,qEAAqE;QAC9E,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE;oBACJ,MAAM,EAAE;wBACN,IAAI,EAAE,UAAU;wBAChB,OAAO;qBACR;iBACF;gBACD,OAAO,EAAE,OAA0C;aACpD;SACF;KACgB,CAAC;AACtB,CAAC;AAyBD;;;;;;;GAOG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAA6B,EAAE,QAAiB;IACpF,IAAI,OAAO,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;QAC/E,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,IAAI,GAAG,0BAA0B,CAAC,KAAK,IAAI,SAAS,CAAC,CAAC;IAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter: `dotnet format --report <path>` JSON output → SARIF 2.1.0.
|
|
3
|
+
*
|
|
4
|
+
* The dotnet format tool emits a JSON array with this shape:
|
|
5
|
+
*
|
|
6
|
+
* [
|
|
7
|
+
* {
|
|
8
|
+
* "DocumentId": { "ProjectId": { "Id": "..." }, "Id": "..." },
|
|
9
|
+
* "FileName": "AuthController.cs",
|
|
10
|
+
* "FilePath": "/absolute/path/to/AuthController.cs",
|
|
11
|
+
* "FileChanges": [
|
|
12
|
+
* {
|
|
13
|
+
* "LineNumber": 84,
|
|
14
|
+
* "CharNumber": 16,
|
|
15
|
+
* "DiagnosticId": "WHITESPACE",
|
|
16
|
+
* "FormatDescription": "Fix whitespace formatting. Delete 5 characters."
|
|
17
|
+
* }
|
|
18
|
+
* ]
|
|
19
|
+
* }
|
|
20
|
+
* ]
|
|
21
|
+
*
|
|
22
|
+
* All dotnet format findings are style/formatting issues, so they
|
|
23
|
+
* map uniformly to SARIF "warning" level with a 5-minute effort
|
|
24
|
+
* estimate (formatting fixes are quick, mechanical changes).
|
|
25
|
+
*
|
|
26
|
+
* @module adapters/dotnet-format
|
|
27
|
+
*/
|
|
28
|
+
import { type AdapterResult } from "./common.js";
|
|
29
|
+
/**
|
|
30
|
+
* Convert `dotnet format --report <path>` JSON output to SARIF 2.1.0.
|
|
31
|
+
*
|
|
32
|
+
* @param rawOutput The JSON string or pre-parsed array from `dotnet format`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function adaptDotnetFormat(rawOutput: unknown): AdapterResult;
|
|
35
|
+
//# sourceMappingURL=dotnet-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dotnet-format.d.ts","sourceRoot":"","sources":["../../src/adapters/dotnet-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACL,KAAK,aAAa,EAEnB,MAAM,aAAa,CAAC;AAuBrB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,OAAO,GAAG,aAAa,CAiEnE"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter: `dotnet format --report <path>` JSON output → SARIF 2.1.0.
|
|
3
|
+
*
|
|
4
|
+
* The dotnet format tool emits a JSON array with this shape:
|
|
5
|
+
*
|
|
6
|
+
* [
|
|
7
|
+
* {
|
|
8
|
+
* "DocumentId": { "ProjectId": { "Id": "..." }, "Id": "..." },
|
|
9
|
+
* "FileName": "AuthController.cs",
|
|
10
|
+
* "FilePath": "/absolute/path/to/AuthController.cs",
|
|
11
|
+
* "FileChanges": [
|
|
12
|
+
* {
|
|
13
|
+
* "LineNumber": 84,
|
|
14
|
+
* "CharNumber": 16,
|
|
15
|
+
* "DiagnosticId": "WHITESPACE",
|
|
16
|
+
* "FormatDescription": "Fix whitespace formatting. Delete 5 characters."
|
|
17
|
+
* }
|
|
18
|
+
* ]
|
|
19
|
+
* }
|
|
20
|
+
* ]
|
|
21
|
+
*
|
|
22
|
+
* All dotnet format findings are style/formatting issues, so they
|
|
23
|
+
* map uniformly to SARIF "warning" level with a 5-minute effort
|
|
24
|
+
* estimate (formatting fixes are quick, mechanical changes).
|
|
25
|
+
*
|
|
26
|
+
* @module adapters/dotnet-format
|
|
27
|
+
*/
|
|
28
|
+
import { wrapResultsInSarif, } from "./common.js";
|
|
29
|
+
// ── Public API ─────────────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* Convert `dotnet format --report <path>` JSON output to SARIF 2.1.0.
|
|
32
|
+
*
|
|
33
|
+
* @param rawOutput The JSON string or pre-parsed array from `dotnet format`.
|
|
34
|
+
*/
|
|
35
|
+
export function adaptDotnetFormat(rawOutput) {
|
|
36
|
+
let parsed;
|
|
37
|
+
if (typeof rawOutput === "string") {
|
|
38
|
+
try {
|
|
39
|
+
parsed = JSON.parse(rawOutput);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new Error("[dotnet-format adapter] rawOutput is not valid JSON");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else if (Array.isArray(rawOutput)) {
|
|
46
|
+
parsed = rawOutput;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
throw new Error("[dotnet-format adapter] rawOutput must be a JSON string or an array of document entries");
|
|
50
|
+
}
|
|
51
|
+
if (!Array.isArray(parsed)) {
|
|
52
|
+
throw new Error("[dotnet-format adapter] parsed output must be an array");
|
|
53
|
+
}
|
|
54
|
+
const EFFORT_MINUTES = 5;
|
|
55
|
+
const results = [];
|
|
56
|
+
let findingCount = 0;
|
|
57
|
+
let totalEffortMinutes = 0;
|
|
58
|
+
for (const doc of parsed) {
|
|
59
|
+
if (!Array.isArray(doc.FileChanges))
|
|
60
|
+
continue;
|
|
61
|
+
for (const change of doc.FileChanges) {
|
|
62
|
+
findingCount++;
|
|
63
|
+
totalEffortMinutes += EFFORT_MINUTES;
|
|
64
|
+
results.push({
|
|
65
|
+
ruleId: change.DiagnosticId,
|
|
66
|
+
level: "warning",
|
|
67
|
+
message: {
|
|
68
|
+
text: change.FormatDescription,
|
|
69
|
+
},
|
|
70
|
+
locations: [
|
|
71
|
+
{
|
|
72
|
+
physicalLocation: {
|
|
73
|
+
artifactLocation: {
|
|
74
|
+
uri: doc.FilePath,
|
|
75
|
+
},
|
|
76
|
+
region: {
|
|
77
|
+
startLine: change.LineNumber,
|
|
78
|
+
startColumn: change.CharNumber,
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
properties: {
|
|
84
|
+
effortMinutes: EFFORT_MINUTES,
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
document: wrapResultsInSarif("dotnet_format", "1.0.0", results),
|
|
91
|
+
sourceTool: "dotnet_format",
|
|
92
|
+
findingCount,
|
|
93
|
+
totalEffortMinutes,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=dotnet-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dotnet-format.js","sourceRoot":"","sources":["../../src/adapters/dotnet-format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAEL,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAqBrB,sEAAsE;AAEtE;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAkB;IAClD,IAAI,MAA8B,CAAC;IAEnC,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAA2B,CAAC;QAC3D,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;SAAM,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;QACpC,MAAM,GAAG,SAAmC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,cAAc,GAAG,CAAC,CAAC;IACzB,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAE3B,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;YAAE,SAAS;QAE9C,KAAK,MAAM,MAAM,IAAI,GAAG,CAAC,WAAW,EAAE,CAAC;YACrC,YAAY,EAAE,CAAC;YACf,kBAAkB,IAAI,cAAc,CAAC;YAErC,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM,EAAE,MAAM,CAAC,YAAY;gBAC3B,KAAK,EAAE,SAAS;gBAChB,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM,CAAC,iBAAiB;iBAC/B;gBACD,SAAS,EAAE;oBACT;wBACE,gBAAgB,EAAE;4BAChB,gBAAgB,EAAE;gCAChB,GAAG,EAAE,GAAG,CAAC,QAAQ;6BAClB;4BACD,MAAM,EAAE;gCACN,SAAS,EAAE,MAAM,CAAC,UAAU;gCAC5B,WAAW,EAAE,MAAM,CAAC,UAAU;6BAC/B;yBACF;qBACF;iBACF;gBACD,UAAU,EAAE;oBACV,aAAa,EAAE,cAAc;iBAC9B;aACF,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,kBAAkB,CAAC,eAAe,EAAE,OAAO,EAAE,OAAO,CAAC;QAC/D,UAAU,EAAE,eAAe;QAC3B,YAAY;QACZ,kBAAkB;KACnB,CAAC;AACJ,CAAC"}
|
package/dist/adapters/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export { adaptEslint } from "./eslint.js";
|
|
|
30
30
|
export { adaptBandit } from "./bandit.js";
|
|
31
31
|
export { adaptStryker } from "./stryker.js";
|
|
32
32
|
export { adaptDartAnalyzer } from "./dart-analyzer.js";
|
|
33
|
+
export { adaptDotnetFormat } from "./dotnet-format.js";
|
|
33
34
|
export { DEFAULT_EFFORT_BY_SEVERITY, KNOWN_SCANNERS, estimateEffortMinutes, wrapResultsInSarif, } from "./common.js";
|
|
34
35
|
export type { AdapterResult, KnownScanner } from "./common.js";
|
|
35
36
|
import type { AdapterResult, KnownScanner } from "./common.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAQ/D,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE/D;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,OAAO,GACjB,aAAa,CAmBf"}
|
package/dist/adapters/index.js
CHANGED
|
@@ -30,12 +30,14 @@ export { adaptEslint } from "./eslint.js";
|
|
|
30
30
|
export { adaptBandit } from "./bandit.js";
|
|
31
31
|
export { adaptStryker } from "./stryker.js";
|
|
32
32
|
export { adaptDartAnalyzer } from "./dart-analyzer.js";
|
|
33
|
+
export { adaptDotnetFormat } from "./dotnet-format.js";
|
|
33
34
|
export { DEFAULT_EFFORT_BY_SEVERITY, KNOWN_SCANNERS, estimateEffortMinutes, wrapResultsInSarif, } from "./common.js";
|
|
34
35
|
import { adaptSemgrep } from "./semgrep.js";
|
|
35
36
|
import { adaptEslint } from "./eslint.js";
|
|
36
37
|
import { adaptBandit } from "./bandit.js";
|
|
37
38
|
import { adaptStryker } from "./stryker.js";
|
|
38
39
|
import { adaptDartAnalyzer } from "./dart-analyzer.js";
|
|
40
|
+
import { adaptDotnetFormat } from "./dotnet-format.js";
|
|
39
41
|
/**
|
|
40
42
|
* Route a raw scanner output to the correct adapter based on its
|
|
41
43
|
* name. Preferred entry point for the `ingest_scanner_output` MCP
|
|
@@ -59,6 +61,8 @@ export function adaptScannerOutput(scanner, rawOutput) {
|
|
|
59
61
|
return adaptStryker(rawOutput);
|
|
60
62
|
case "dart_analyze":
|
|
61
63
|
return adaptDartAnalyzer(rawOutput);
|
|
64
|
+
case "dotnet_format":
|
|
65
|
+
return adaptDotnetFormat(rawOutput);
|
|
62
66
|
default: {
|
|
63
67
|
const exhaustive = scanner;
|
|
64
68
|
throw new Error(`[adapters] Unknown scanner: ${String(exhaustive)}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAqB,EACrB,SAAkB;IAElB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,KAAK,cAAc;YACjB,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACtC,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAU,OAAO,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAqB,EACrB,SAAkB;IAElB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,KAAK,cAAc;YACjB,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACtC,KAAK,eAAe;YAClB,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;QACtC,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAU,OAAO,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/crap-config.d.ts
CHANGED
|
@@ -72,6 +72,8 @@ export interface CrapConfig {
|
|
|
72
72
|
readonly strictnessSource: "env" | "file" | "default";
|
|
73
73
|
/** User-defined exclusion patterns (directories with trailing `/`, or file globs). */
|
|
74
74
|
readonly exclude: ReadonlyArray<string>;
|
|
75
|
+
/** Relative paths to directories containing sub-projects (e.g. `["apps", "packages"]`). */
|
|
76
|
+
readonly projectDirs: ReadonlyArray<string>;
|
|
75
77
|
}
|
|
76
78
|
/**
|
|
77
79
|
* Options accepted by {@link loadCrapConfig}. The only required
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crap-config.d.ts","sourceRoot":"","sources":["../src/crap-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,yCAA0C,CAAC;AAEzE;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,UAAqB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;IACtD,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"crap-config.d.ts","sourceRoot":"","sources":["../src/crap-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,yCAA0C,CAAC;AAEzE;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,UAAqB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;IACtD,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACxC,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC7C;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,UAAU,CAwBzE"}
|
package/dist/crap-config.js
CHANGED
|
@@ -72,6 +72,7 @@ export function loadCrapConfig(options) {
|
|
|
72
72
|
// comes from the environment variable.
|
|
73
73
|
const fileResult = readFromFile(options.workspaceRoot);
|
|
74
74
|
const exclude = fileResult?.exclude ?? [];
|
|
75
|
+
const projectDirs = fileResult?.projectDirs ?? [];
|
|
75
76
|
const envRaw = process.env["CLAUDE_CRAP_STRICTNESS"];
|
|
76
77
|
if (typeof envRaw === "string" && envRaw.trim() !== "") {
|
|
77
78
|
const normalized = envRaw.trim().toLowerCase();
|
|
@@ -79,12 +80,12 @@ export function loadCrapConfig(options) {
|
|
|
79
80
|
throw new CrapConfigError(`[crap-config] CLAUDE_CRAP_STRICTNESS="${envRaw}" is not a valid strictness. ` +
|
|
80
81
|
`Expected one of: ${STRICTNESS_VALUES.join(", ")}.`);
|
|
81
82
|
}
|
|
82
|
-
return { strictness: normalized, strictnessSource: "env", exclude };
|
|
83
|
+
return { strictness: normalized, strictnessSource: "env", exclude, projectDirs };
|
|
83
84
|
}
|
|
84
85
|
if (fileResult?.strictness) {
|
|
85
|
-
return { strictness: fileResult.strictness, strictnessSource: "file", exclude };
|
|
86
|
+
return { strictness: fileResult.strictness, strictnessSource: "file", exclude, projectDirs };
|
|
86
87
|
}
|
|
87
|
-
return { strictness: DEFAULT_STRICTNESS, strictnessSource: "default", exclude };
|
|
88
|
+
return { strictness: DEFAULT_STRICTNESS, strictnessSource: "default", exclude, projectDirs };
|
|
88
89
|
}
|
|
89
90
|
function readFromFile(workspaceRoot) {
|
|
90
91
|
const filePath = join(workspaceRoot, ".claude-crap.json");
|
|
@@ -137,7 +138,21 @@ function readFromFile(workspaceRoot) {
|
|
|
137
138
|
}
|
|
138
139
|
exclude = raw;
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
+
// Parse projectDirs
|
|
142
|
+
let projectDirs = [];
|
|
143
|
+
if ("projectDirs" in doc) {
|
|
144
|
+
const raw = doc["projectDirs"];
|
|
145
|
+
if (!Array.isArray(raw)) {
|
|
146
|
+
throw new CrapConfigError(`[crap-config] ${filePath}: 'projectDirs' must be an array of strings`);
|
|
147
|
+
}
|
|
148
|
+
for (const item of raw) {
|
|
149
|
+
if (typeof item !== "string") {
|
|
150
|
+
throw new CrapConfigError(`[crap-config] ${filePath}: every entry in 'projectDirs' must be a string, got ${typeof item}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
projectDirs = raw;
|
|
154
|
+
}
|
|
155
|
+
return { strictness, exclude, projectDirs };
|
|
141
156
|
}
|
|
142
157
|
/**
|
|
143
158
|
* Runtime type guard for the {@link Strictness} union. Lets callers
|
package/dist/crap-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crap-config.js","sourceRoot":"","sources":["../src/crap-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AASzE;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAe,QAAQ,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"crap-config.js","sourceRoot":"","sources":["../src/crap-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAU,CAAC;AASzE;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAe,QAAQ,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAiCD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,kEAAkE;IAClE,uCAAuC;IACvC,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,UAAU,EAAE,OAAO,IAAI,EAAE,CAAC;IAC1C,MAAM,WAAW,GAAG,UAAU,EAAE,WAAW,IAAI,EAAE,CAAC;IAElD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACrD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC/C,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,eAAe,CACvB,yCAAyC,MAAM,+BAA+B;gBAC5E,oBAAoB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACtD,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,gBAAgB,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACnF,CAAC;IAED,IAAI,UAAU,EAAE,UAAU,EAAE,CAAC;QAC3B,OAAO,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IAC/F,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAC/F,CAAC;AAqBD,SAAS,YAAY,CAAC,aAAqB;IACzC,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC;IAC1D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,GAA4B,CAAC;QAC3C,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,IAAI,eAAe,CACvB,gCAAgC,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAC7D,CAAC;IACJ,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,eAAe,CACvB,iBAAiB,QAAQ,uBAAwB,GAAa,CAAC,OAAO,EAAE,CACzE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnE,MAAM,IAAI,eAAe,CACvB,iBAAiB,QAAQ,yCAAyC,CACnE,CAAC;IACJ,CAAC;IACD,MAAM,GAAG,GAAG,MAAiC,CAAC;IAE9C,mBAAmB;IACnB,IAAI,UAAU,GAAsB,IAAI,CAAC;IACzC,IAAI,YAAY,IAAI,GAAG,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC,CAAC;QAChC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,eAAe,CACvB,iBAAiB,QAAQ,wCAAwC,OAAO,KAAK,EAAE,CAChF,CAAC;QACJ,CAAC;QACD,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC9C,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,eAAe,CACvB,iBAAiB,QAAQ,sBAAsB,KAAK,KAAK;gBACvD,mBAAmB,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACrD,CAAC;QACJ,CAAC;QACD,UAAU,GAAG,UAAU,CAAC;IAC1B,CAAC;IAED,gBAAgB;IAChB,IAAI,OAAO,GAAa,EAAE,CAAC;IAC3B,IAAI,SAAS,IAAI,GAAG,EAAE,CAAC;QACrB,MAAM,GAAG,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,eAAe,CACvB,iBAAiB,QAAQ,yCAAyC,CACnE,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;YACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,IAAI,eAAe,CACvB,iBAAiB,QAAQ,oDAAoD,OAAO,IAAI,EAAE,CAC3F,CAAC;YACJ,CAAC;QACH,CAAC;QACD,OAAO,GAAG,GAAe,CAAC;IAC5B,CAAC;IAED,oBAAoB;IACpB,IAAI,WAAW,GAAa,EAAE,CAAC;IAC/B,IAAI,aAAa,IAAI,GAAG,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,GAAG,CAAC,aAAa,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,eAAe,CACvB,iBAAiB,QAAQ,6CAA6C,CACvE,CAAC;QACJ,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,GAAG,EAAE,CAAC;YACvB,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,MAAM,IAAI,eAAe,CACvB,iBAAiB,QAAQ,wDAAwD,OAAO,IAAI,EAAE,CAC/F,CAAC;YACJ,CAAC;QACH,CAAC;QACD,WAAW,GAAG,GAAe,CAAC;IAChC,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,SAAS,YAAY,CAAC,KAAa;IACjC,OAAQ,iBAA2C,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACtE,CAAC"}
|
package/dist/dashboard/server.js
CHANGED
|
@@ -57,7 +57,7 @@ export async function startDashboard(options) {
|
|
|
57
57
|
// ------------------------------------------------------------------
|
|
58
58
|
// /api/health — liveness probe
|
|
59
59
|
// ------------------------------------------------------------------
|
|
60
|
-
fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.
|
|
60
|
+
fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.4.0" }));
|
|
61
61
|
// ------------------------------------------------------------------
|
|
62
62
|
// /api/score — live project score
|
|
63
63
|
// ------------------------------------------------------------------
|
package/dist/index.js
CHANGED
|
@@ -47,7 +47,8 @@ import { findTestFile } from "./tools/test-harness.js";
|
|
|
47
47
|
import { resolveWithinWorkspace } from "./workspace-guard.js";
|
|
48
48
|
import { autoScan } from "./scanner/auto-scan.js";
|
|
49
49
|
import { bootstrapScanner } from "./scanner/bootstrap.js";
|
|
50
|
-
import {
|
|
50
|
+
import { discoverProjectMap, persistProjectMap } from "./monorepo/project-map.js";
|
|
51
|
+
import { autoScanSchema, bootstrapScannerSchema, computeCrapSchema, computeTdrSchema, analyzeFileAstSchema, ingestSarifSchema, ingestScannerOutputSchema, listProjectsSchema, requireTestHarnessSchema, scoreProjectSchema, } from "./schemas/tool-schemas.js";
|
|
51
52
|
// IMPORTANT: the MCP stdio transport uses stdout for JSON-RPC framing.
|
|
52
53
|
// Anything the server logs MUST go to stderr (fd 2) to avoid corrupting
|
|
53
54
|
// the wire format. We configure pino explicitly to write to fd 2.
|
|
@@ -62,14 +63,19 @@ const logger = pino({ level: process.env.CLAUDE_CRAP_LOG_LEVEL ?? "info" }, pino
|
|
|
62
63
|
async function main() {
|
|
63
64
|
const config = loadConfig();
|
|
64
65
|
logger.info({ config: { ...config, pluginRoot: "<redacted>" } }, "claude-crap MCP server starting");
|
|
65
|
-
// Load user-defined exclusions from .claude-crap.json (non-fatal).
|
|
66
|
+
// Load user-defined exclusions and projectDirs from .claude-crap.json (non-fatal).
|
|
66
67
|
let userExclusions = [];
|
|
68
|
+
let userProjectDirs = [];
|
|
67
69
|
try {
|
|
68
70
|
const crapConfig = loadCrapConfig({ workspaceRoot: config.pluginRoot });
|
|
69
71
|
userExclusions = crapConfig.exclude;
|
|
72
|
+
userProjectDirs = crapConfig.projectDirs;
|
|
70
73
|
if (userExclusions.length > 0) {
|
|
71
74
|
logger.info({ exclude: userExclusions }, "user exclusions loaded from .claude-crap.json");
|
|
72
75
|
}
|
|
76
|
+
if (userProjectDirs.length > 0) {
|
|
77
|
+
logger.info({ projectDirs: userProjectDirs }, "user projectDirs loaded from .claude-crap.json");
|
|
78
|
+
}
|
|
73
79
|
}
|
|
74
80
|
catch {
|
|
75
81
|
// Non-fatal — use empty exclusions.
|
|
@@ -82,6 +88,34 @@ async function main() {
|
|
|
82
88
|
});
|
|
83
89
|
await sarifStore.loadLatest();
|
|
84
90
|
logger.info({ findings: sarifStore.size(), path: sarifStore.consolidatedReportPath }, "SARIF store ready");
|
|
91
|
+
// Discover monorepo project map (non-fatal).
|
|
92
|
+
let projectMap = null;
|
|
93
|
+
try {
|
|
94
|
+
projectMap = await discoverProjectMap(config.pluginRoot, { projectDirs: userProjectDirs });
|
|
95
|
+
if (projectMap.isMonorepo) {
|
|
96
|
+
logger.info({ projects: projectMap.projects.map((p) => `${p.name}(${p.type})`), count: projectMap.projects.length }, "monorepo project map discovered");
|
|
97
|
+
await persistProjectMap(projectMap, config.pluginRoot);
|
|
98
|
+
// If any JS/TS sub-projects need ESLint and it's not available,
|
|
99
|
+
// run bootstrap at the monorepo root to auto-install it. In
|
|
100
|
+
// monorepos, ESLint is hoisted to the root node_modules.
|
|
101
|
+
const needsEslint = projectMap.projects.some((p) => (p.type === "typescript" || p.type === "javascript") && !p.scannerAvailable);
|
|
102
|
+
if (needsEslint) {
|
|
103
|
+
logger.info("monorepo: JS/TS projects detected but ESLint not installed — bootstrapping");
|
|
104
|
+
try {
|
|
105
|
+
await bootstrapScanner(config.pluginRoot, sarifStore, logger);
|
|
106
|
+
// Re-discover after install so scannerAvailable reflects reality
|
|
107
|
+
projectMap = await discoverProjectMap(config.pluginRoot, { projectDirs: userProjectDirs });
|
|
108
|
+
await persistProjectMap(projectMap, config.pluginRoot);
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
logger.warn({ err: err.message }, "monorepo ESLint bootstrap failed");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
logger.warn({ err: err.message }, "project map discovery failed");
|
|
118
|
+
}
|
|
85
119
|
// Try to start the local Vue.js dashboard. Failures here are
|
|
86
120
|
// intentionally non-fatal — the MCP server still works without it.
|
|
87
121
|
let dashboard = null;
|
|
@@ -185,6 +219,11 @@ async function main() {
|
|
|
185
219
|
description: "Detect project type, install the right scanner (ESLint for JS/TS, Bandit for Python, Semgrep for Java/C#), create minimal config, and run auto_scan to verify.",
|
|
186
220
|
inputSchema: bootstrapScannerSchema,
|
|
187
221
|
},
|
|
222
|
+
{
|
|
223
|
+
name: "list_projects",
|
|
224
|
+
description: "List all discovered sub-projects in the workspace. In a monorepo, returns each sub-project with its type, path, and recommended scanner.",
|
|
225
|
+
inputSchema: listProjectsSchema,
|
|
226
|
+
},
|
|
188
227
|
],
|
|
189
228
|
}));
|
|
190
229
|
// ------------------------------------------------------------------
|
|
@@ -193,9 +232,16 @@ async function main() {
|
|
|
193
232
|
// The MCP SDK has already validated `args` against the tool's JSON
|
|
194
233
|
// Schema by the time this handler runs, so we cast to the expected
|
|
195
234
|
// shape without re-validating. Each branch delegates to a pure engine.
|
|
235
|
+
// Tool dispatch is split across two functions to keep cyclomatic
|
|
236
|
+
// complexity within the configured threshold (15) as the tool count
|
|
237
|
+
// grows. Each function handles a subset of tools.
|
|
196
238
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
197
239
|
const { name, arguments: args } = request.params;
|
|
198
240
|
logger.info({ tool: name }, "Tool call received");
|
|
241
|
+
return handleToolCall(name, args);
|
|
242
|
+
});
|
|
243
|
+
/** Dispatch a tool call to the correct handler. */
|
|
244
|
+
async function handleToolCall(name, args) {
|
|
199
245
|
switch (name) {
|
|
200
246
|
case "compute_crap": {
|
|
201
247
|
const typed = args;
|
|
@@ -268,10 +314,19 @@ async function main() {
|
|
|
268
314
|
case "score_project": {
|
|
269
315
|
const typed = (args ?? {});
|
|
270
316
|
const format = typed.format ?? "both";
|
|
317
|
+
// Resolve scope to a workspace subdirectory
|
|
318
|
+
let scoreRoot = config.pluginRoot;
|
|
319
|
+
if (typed.scope && projectMap) {
|
|
320
|
+
const project = projectMap.projects.find((p) => p.name === typed.scope);
|
|
321
|
+
if (project) {
|
|
322
|
+
const { join } = await import("node:path");
|
|
323
|
+
scoreRoot = join(config.pluginRoot, project.path);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
271
326
|
try {
|
|
272
|
-
const workspace = await estimateWorkspaceLoc(
|
|
327
|
+
const workspace = await estimateWorkspaceLoc(scoreRoot, { exclude: userExclusions });
|
|
273
328
|
const score = computeProjectScore({
|
|
274
|
-
workspaceRoot:
|
|
329
|
+
workspaceRoot: scoreRoot,
|
|
275
330
|
minutesPerLoc: config.minutesPerLoc,
|
|
276
331
|
tdrMaxRating: config.tdrMaxRating,
|
|
277
332
|
workspace: { physicalLoc: workspace.physicalLoc, fileCount: workspace.fileCount },
|
|
@@ -508,10 +563,24 @@ async function main() {
|
|
|
508
563
|
};
|
|
509
564
|
}
|
|
510
565
|
}
|
|
566
|
+
case "list_projects": {
|
|
567
|
+
return {
|
|
568
|
+
content: [
|
|
569
|
+
{
|
|
570
|
+
type: "text",
|
|
571
|
+
text: JSON.stringify({
|
|
572
|
+
tool: "list_projects",
|
|
573
|
+
isMonorepo: projectMap?.isMonorepo ?? false,
|
|
574
|
+
projects: projectMap?.projects ?? [],
|
|
575
|
+
}, null, 2),
|
|
576
|
+
},
|
|
577
|
+
],
|
|
578
|
+
};
|
|
579
|
+
}
|
|
511
580
|
default:
|
|
512
581
|
throw new Error(`[claude-crap] Unknown tool: ${name}`);
|
|
513
582
|
}
|
|
514
|
-
}
|
|
583
|
+
}
|
|
515
584
|
// ------------------------------------------------------------------
|
|
516
585
|
// Resources — topology and reports
|
|
517
586
|
// ------------------------------------------------------------------
|