@sebassdc/crap4ts 0.1.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/LICENSE ADDED
@@ -0,0 +1,27 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sebassdc
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.
22
+
23
+ ---
24
+
25
+ Attribution: the CRAP (Change Risk Anti-Pattern) metric was introduced by
26
+ Alberto Savoia and Bob Evans. This project is inspired by crap4clj by
27
+ Robert C. Martin and shares its goals with crap4py.
package/README.md ADDED
@@ -0,0 +1,454 @@
1
+ # crap4ts
2
+
3
+ **CRAP** (Change Risk Anti-Pattern) metric for TypeScript projects.
4
+
5
+ Combines cyclomatic complexity with test coverage to identify functions that are both complex and under-tested — the riskiest code to change.
6
+
7
+ ## Quick Start
8
+
9
+ Install from source:
10
+
11
+ ```bash
12
+ git clone https://github.com/sebassdc/crap4ts.git
13
+ cd crap4ts
14
+ npm install
15
+ npm run build
16
+ npm install -g .
17
+ ```
18
+
19
+ Configure your test runner to emit Istanbul JSON coverage:
20
+
21
+ **Vitest** (`vitest.config.ts`):
22
+ ```ts
23
+ export default defineConfig({
24
+ test: {
25
+ coverage: {
26
+ provider: 'v8', // or 'istanbul'
27
+ reporter: ['text', 'json'],
28
+ },
29
+ },
30
+ });
31
+ ```
32
+
33
+ **Jest** (`jest.config.ts`):
34
+ ```ts
35
+ export default {
36
+ coverageReporters: ['text', 'json'],
37
+ };
38
+ ```
39
+
40
+ Run from your project root (where `src/` lives):
41
+
42
+ ```bash
43
+ crap4ts
44
+ ```
45
+
46
+ crap4ts automatically deletes stale coverage data, runs your test suite with coverage, and prints the report.
47
+
48
+ ## Output
49
+
50
+ ```
51
+ CRAP Report
52
+ ===========
53
+ Function Module CC Cov% CRAP
54
+ -------------------------------------------------------------------------------------
55
+ complexFn my.module 12 45.0% 130.2
56
+ simpleFn my.module 1 100.0% 1.0
57
+ ```
58
+
59
+ ## CLI Options
60
+
61
+ ```bash
62
+ crap4ts --help # show usage and available options
63
+ crap4ts --version # print version number
64
+ crap4ts --src lib # analyze from lib/ instead of src/
65
+ crap4ts --exclude dist # exclude paths containing "dist"
66
+ crap4ts --timeout 120 # set analysis timeout to 120 seconds
67
+ ```
68
+
69
+ ## Configuration File
70
+
71
+ Instead of passing flags every time, create a `crap4ts.config.json` (or `.crap4tsrc.json`) in your project root:
72
+
73
+ ```json
74
+ {
75
+ "src": "lib",
76
+ "exclude": ["dist", "fixtures"],
77
+ "output": "json",
78
+ "failOnCrap": 30,
79
+ "timeout": 120
80
+ }
81
+ ```
82
+
83
+ ### File Discovery
84
+
85
+ crap4ts looks for config files in the current working directory in this order:
86
+
87
+ 1. `crap4ts.config.json` (preferred)
88
+ 2. `.crap4tsrc.json` (fallback)
89
+
90
+ The first file found is used. If neither exists, all options use their defaults.
91
+
92
+ To load a config file from a custom path, use the `--config` flag:
93
+
94
+ ```bash
95
+ crap4ts --config configs/crap4ts.json
96
+ ```
97
+
98
+ ### CLI Override Precedence
99
+
100
+ CLI flags always take precedence over config file values. For example, if your config file sets `"src": "lib"` but you run `crap4ts --src app`, the `app` directory is used.
101
+
102
+ ### Supported Keys
103
+
104
+ | Key | Type | Description | Default |
105
+ |----------------------|------------|--------------------------------------------------|---------|
106
+ | `src` | `string` | Source directory to analyze | `"src"` |
107
+ | `exclude` | `string[]` | Exclude paths containing these patterns | `[]` |
108
+ | `output` | `string` | Output format: `"text"`, `"json"`, `"markdown"`, or `"csv"` | `"text"`|
109
+ | `runner` | `string` | Test runner: `"vitest"` or `"jest"` | auto |
110
+ | `coverageCommand` | `string` | Custom shell command to generate coverage | none |
111
+ | `failOnCrap` | `number` | Fail if any CRAP score >= this value | none |
112
+ | `failOnComplexity` | `number` | Fail if any cyclomatic complexity >= this value | none |
113
+ | `failOnCoverageBelow`| `number` | Fail if any function coverage < this % (0-100) | none |
114
+ | `top` | `number` | Show only the top N entries | all |
115
+ | `timeout` | `number` | Analysis timeout in seconds | `600` |
116
+
117
+ Unknown keys are silently ignored, so config files are forward-compatible with future versions.
118
+
119
+ ## Programmatic API
120
+
121
+ crap4ts can be used as a library in your own tools and scripts. The API assumes coverage data already exists (run your test suite with coverage first).
122
+
123
+ ```ts
124
+ import { generateReport, crapScore, extractFunctions } from '@sebassdc/crap4ts';
125
+
126
+ // High-level: analyze an entire source tree against existing coverage
127
+ const { entries } = generateReport({
128
+ srcDir: 'src',
129
+ coverageDir: 'coverage',
130
+ });
131
+
132
+ entries.forEach(e => console.log(`${e.name}: CRAP ${e.crap}`));
133
+ ```
134
+
135
+ ### `generateReport(options)`
136
+
137
+ Finds source files, parses coverage, analyzes each file, and returns entries sorted by CRAP score. This does **not** run your test suite -- it reads from an existing `coverage-final.json`.
138
+
139
+ | Option | Type | Description | Default |
140
+ |---------------|------------|--------------------------------------------------|---------|
141
+ | `srcDir` | `string` | Source directory to scan for `.ts` files | -- |
142
+ | `coverageDir` | `string` | Directory containing `coverage-final.json` | -- |
143
+ | `filters` | `string[]` | Only include files matching these substrings | `[]` |
144
+ | `excludes` | `string[]` | Exclude files whose path contains these substrings| `[]` |
145
+
146
+ ### Low-level exports
147
+
148
+ For fine-grained control, individual functions are also exported:
149
+
150
+ ```ts
151
+ import {
152
+ extractFunctions, // parse a TS source string into FunctionInfo[]
153
+ parseCoverage, // read coverage-final.json from a directory
154
+ coverageForRange, // get coverage % for a line range
155
+ sourceToModule, // convert file path to dotted module name
156
+ crapScore, // compute CRAP score from complexity and coverage
157
+ sortByCrap, // sort CrapEntry[] by CRAP descending
158
+ formatReport, // render text table from CrapEntry[]
159
+ formatJsonReport, // render JSON string from CrapEntry[]
160
+ formatMarkdownReport, // render markdown table from CrapEntry[]
161
+ formatCsvReport, // render CSV string from CrapEntry[]
162
+ findSourceFiles, // find all .ts files in a directory
163
+ filterSources, // filter file list by substring patterns
164
+ analyzeFile, // analyze a single file against coverage data
165
+ } from '@sebassdc/crap4ts';
166
+ ```
167
+
168
+ TypeScript types `CrapEntry`, `FunctionInfo`, `CoverageData`, and `FileCoverageData` are also exported.
169
+
170
+ ## CI Integration
171
+
172
+ Use threshold flags to fail CI when code quality drops below acceptable levels:
173
+
174
+ ```bash
175
+ # Fail if any function has CRAP >= 30 or coverage below 70%
176
+ crap4ts --fail-on-crap 30 --fail-on-coverage-below 70
177
+
178
+ # Fail if any function has complexity >= 15, show only top 10
179
+ crap4ts --fail-on-complexity 15 --top 10
180
+ ```
181
+
182
+ Multiple thresholds can be combined. The report is always printed before any failure.
183
+
184
+ The `--top` flag limits displayed entries but all entries are evaluated against thresholds.
185
+
186
+ ### Exit Codes
187
+
188
+ | Code | Meaning |
189
+ |------|---------|
190
+ | 0 | Pass -- no threshold violations |
191
+ | 1 | Threshold violated or runtime error |
192
+ | 2 | Usage error (invalid flags or arguments) |
193
+
194
+ ## Output Formats
195
+
196
+ crap4ts supports four output formats:
197
+
198
+ ```bash
199
+ crap4ts # default text table
200
+ crap4ts --json # JSON (shorthand for --output json)
201
+ crap4ts --output markdown # Markdown table
202
+ crap4ts --output csv # CSV
203
+ ```
204
+
205
+ ### Text (default)
206
+
207
+ ```
208
+ CRAP Report
209
+ ===========
210
+ Function Module CC Cov% CRAP
211
+ -------------------------------------------------------------------------------------
212
+ complexFn my.module 12 45.0% 130.2
213
+ simpleFn my.module 1 100.0% 1.0
214
+ ```
215
+
216
+ ### JSON
217
+
218
+ ```json
219
+ {
220
+ "tool": "crap4ts",
221
+ "entries": [
222
+ {
223
+ "name": "complexFn",
224
+ "module": "my.module",
225
+ "complexity": 12,
226
+ "coverage": 45,
227
+ "crap": 130.2
228
+ }
229
+ ]
230
+ }
231
+ ```
232
+
233
+ ### Markdown
234
+
235
+ ```markdown
236
+ # CRAP Report
237
+
238
+ | Function | Module | CC | Cov% | CRAP |
239
+ |---|---|---:|---:|---:|
240
+ | complexFn | my.module | 12 | 45.0% | 130.2 |
241
+ | simpleFn | my.module | 1 | 100.0% | 1.0 |
242
+ ```
243
+
244
+ ### CSV
245
+
246
+ ```csv
247
+ Function,Module,CC,Coverage,CRAP
248
+ complexFn,my.module,12,45.0,130.2
249
+ simpleFn,my.module,1,100.0,1.0
250
+ ```
251
+
252
+ Text output is the default. Use `--json` as a shorthand or `--output <format>` for any format.
253
+
254
+ ## Excluding Paths
255
+
256
+ Use `--exclude` to filter out files whose path contains a given substring. The flag is repeatable:
257
+
258
+ ```bash
259
+ # Skip dist and fixtures directories
260
+ crap4ts --exclude dist --exclude fixtures
261
+
262
+ # Analyze lib/ but skip generated code
263
+ crap4ts --src lib --exclude __generated__
264
+
265
+ # Combine with other options
266
+ crap4ts --src packages/core/src --exclude __mocks__ --exclude .stories --json
267
+ ```
268
+
269
+ ## Filtering
270
+
271
+ Pass module path fragments as arguments to filter:
272
+
273
+ ```bash
274
+ crap4ts parser validator # only files matching those strings
275
+ ```
276
+
277
+ ## CRAP Formula
278
+
279
+ ```
280
+ CRAP(fn) = CC² × (1 - coverage)³ + CC
281
+ ```
282
+
283
+ - **CC** = cyclomatic complexity (decision points + 1)
284
+ - **coverage** = fraction of statements covered by tests
285
+
286
+ | Score | Risk |
287
+ |-------|------|
288
+ | 1–5 | Low — clean code |
289
+ | 5–30 | Moderate — refactor or add tests |
290
+ | 30+ | High — complex and under-tested |
291
+
292
+ ## What It Counts
293
+
294
+ Decision points that increase cyclomatic complexity:
295
+
296
+ - `if` / ternary (`c ? a : b`)
297
+ - `else if` (each adds 1)
298
+ - `for` / `for...of` / `for...in`
299
+ - `while` / `do...while`
300
+ - `catch` clauses (each adds 1)
301
+ - `case` clauses in `switch` (each `case` adds 1; `default` does not)
302
+ - `&&` / `||` / `??` operators (each operator adds 1)
303
+
304
+ Nested functions and class bodies are skipped — only the enclosing function's body is analyzed.
305
+
306
+ ## Compatibility
307
+
308
+ | Layout | Status | Notes |
309
+ |--------|--------|-------|
310
+ | Standard (`src/`) | Supported | Default, no config needed |
311
+ | Custom source dir | Supported | Use `--src <dir>` |
312
+ | Monorepo workspace | Supported | Point `--src` to package source |
313
+ | Multiple src dirs | Supported | Use `--exclude` to filter |
314
+ | Windows paths | Supported | Normalized internally |
315
+ | Istanbul JSON coverage | Required | Other formats not supported |
316
+ | Branch coverage | Not used | Statement coverage only |
317
+
318
+ ## Limitations
319
+
320
+ - Only TypeScript (`.ts`) files are analyzed — `.tsx`, `.js`, and `.jsx` files are ignored.
321
+ - Only functions found within the configured source directory (default: `src/`) are scanned.
322
+ - Coverage data must be in Istanbul JSON format (`coverage-final.json`). Other coverage formats are not supported.
323
+ - Runner detection is heuristic: crap4ts checks for Vitest config files first, then Jest config files, then falls back to the `scripts` field in `package.json`. Use `--runner vitest|jest` to override.
324
+ - Nested functions are attributed to their enclosing function rather than being extracted as separate symbols.
325
+ - Dynamic or computed method names (e.g., `[Symbol.iterator]()` or `["methodName"]()`) are not extracted.
326
+ - Only statement coverage is used when computing the coverage fraction — branch and function coverage are ignored.
327
+ - Coverage is calculated using statement-to-function overlap: a statement is attributed to a function if its line range overlaps the function's line range. This is an approximation; a multi-line statement that spans a function boundary may be counted for both the enclosing and the adjacent function.
328
+
329
+ For advanced usage patterns, see [docs/advanced-usage.md](docs/advanced-usage.md).
330
+
331
+ ## Extracted Symbols
332
+
333
+ - Top-level `function` declarations
334
+ - Top-level `const f = () => {}` and `const f = function() {}`
335
+ - Class `constructor`, methods, getters, and setters (named as `ClassName.methodName`)
336
+ - Object literal methods, getters, and setters in top-level variable declarations (named as `varName.methodName` or `varName['string-key']`)
337
+
338
+ Nested functions (functions defined inside other functions, methods, or arrows) are intentionally excluded. They are not extracted as separate symbols; their complexity is attributed to the enclosing function.
339
+
340
+ ## Cross-Agent Skill
341
+
342
+ crap4ts ships a bundled `SKILL.md` that you can install into the cross-agent
343
+ skill directory consumed by Claude Code, Codex, Pi, and any harness that reads
344
+ `.agents/skills/`.
345
+
346
+ ```bash
347
+ # Global install for the current user (~/.agents/skills/crap4ts/SKILL.md)
348
+ crap4ts skill install
349
+
350
+ # Project-local install (./.agents/skills/crap4ts/SKILL.md)
351
+ crap4ts skill install --project
352
+
353
+ # Print the bundled skill
354
+ crap4ts skill show
355
+
356
+ # Print where the skill is (or would be) installed
357
+ crap4ts skill path
358
+ crap4ts skill path --project
359
+
360
+ # Remove
361
+ crap4ts skill uninstall
362
+ crap4ts skill uninstall --project
363
+ ```
364
+
365
+ The bundled skill lives inside the published package at `src/skill/SKILL.md`
366
+ and is shipped via the `files` field in `package.json`.
367
+
368
+ ### Claude Code
369
+
370
+ Claude Code reads skills from `~/.claude/skills/`, not `~/.agents/skills/`.
371
+ After installing, symlink the skill so both directories stay in sync:
372
+
373
+ ```bash
374
+ crap4ts skill install
375
+ ln -s ~/.agents/skills/crap4ts ~/.claude/skills/crap4ts
376
+ ```
377
+
378
+ For project-local installs, symlink into `.claude/skills/` at the repo root:
379
+
380
+ ```bash
381
+ crap4ts skill install --project
382
+ ln -s .agents/skills/crap4ts .claude/skills/crap4ts
383
+ ```
384
+
385
+ ## Runner Configuration
386
+
387
+ crap4ts supports three ways to run your test suite for coverage, applied in this order of precedence:
388
+
389
+ ### 1. `--coverage-command` (highest priority)
390
+
391
+ Run an arbitrary shell command instead of the built-in runner logic. The command is executed with `shell: true`, so pipes, environment variables, and shell syntax all work.
392
+
393
+ ```bash
394
+ # Monorepo: run tests only for a specific package
395
+ crap4ts --coverage-command "npm run test:api -- --coverage"
396
+
397
+ # Custom script with environment variables
398
+ crap4ts --coverage-command "CI=1 yarn test --coverage --coverageReporters=json"
399
+
400
+ # Turborepo / Nx workspace
401
+ crap4ts --coverage-command "npx turbo run test -- --coverage"
402
+ ```
403
+
404
+ The command must produce a `coverage/coverage-final.json` file in Istanbul JSON format.
405
+
406
+ ### 2. `--runner vitest|jest` (skip auto-detection)
407
+
408
+ Use the built-in runner invocation for Vitest or Jest, but skip the config-file heuristic:
409
+
410
+ ```bash
411
+ # Force Jest even if a vitest.config.ts exists
412
+ crap4ts --runner jest
413
+
414
+ # Force Vitest in a project without a vitest.config file
415
+ crap4ts --runner vitest
416
+ ```
417
+
418
+ ### 3. Auto-detection (default)
419
+
420
+ When neither flag is provided, crap4ts detects the runner automatically:
421
+
422
+ 1. If any `vitest.config.*` file exists, use Vitest.
423
+ 2. If any `jest.config.*` file exists, use Jest.
424
+ 3. If `package.json` lists `jest` as a dependency, use Jest.
425
+ 4. Otherwise, default to Vitest.
426
+
427
+ ## Troubleshooting
428
+
429
+ | Error | Fix |
430
+ |-------|-----|
431
+ | `Source directory 'src' not found` | Use `--src <dir>` to point to your source directory |
432
+ | `No TypeScript files found` | Verify your source directory contains `.ts` files |
433
+ | `No files match the filters` | Check your filter arguments match actual file paths |
434
+ | `Unable to parse package.json` | Fix your `package.json` or use `--runner vitest\|jest` |
435
+ | `Coverage run failed` | Ensure your test suite passes independently before running crap4ts |
436
+ | `No coverage-final.json found` | Configure your test runner to output Istanbul JSON coverage (see Quick Start) |
437
+ | `Coverage run timed out` | Increase timeout with `--timeout <seconds>` |
438
+
439
+ ## Development
440
+
441
+ ```bash
442
+ npm install
443
+ npm test # run tests
444
+ npm run build # compile to dist/
445
+ npm run coverage # run tests with coverage
446
+ ```
447
+
448
+ ## Inspiration
449
+
450
+ This project was inspired by [crap4clj](https://github.com/unclebob/crap4clj) by Uncle Bob.
451
+
452
+ ## License
453
+
454
+ MIT
package/dist/api.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export { extractFunctions } from './complexity';
2
+ export type { FunctionInfo } from './complexity';
3
+ export { parseCoverage, coverageForRange, sourceToModule, normalizePath } from './coverage';
4
+ export type { CoverageData, FileCoverageData } from './coverage';
5
+ export { crapScore, sortByCrap, formatReport, formatJsonReport, formatMarkdownReport, formatCsvReport } from './crap';
6
+ export type { CrapEntry } from './crap';
7
+ export { findSourceFiles, filterSources, analyzeFile } from './core';
8
+ import type { CrapEntry } from './crap';
9
+ export interface ReportResult {
10
+ entries: CrapEntry[];
11
+ }
12
+ export interface GenerateReportOptions {
13
+ srcDir: string;
14
+ coverageDir: string;
15
+ filters?: string[];
16
+ excludes?: string[];
17
+ }
18
+ export declare function generateReport(opts: GenerateReportOptions): ReportResult;
package/dist/api.js ADDED
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ // Public programmatic API for crap4ts
3
+ // CLI users: import from 'crap4ts/cli' or use the bin entry point.
4
+ // Library users: import from 'crap4ts' (this file).
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.analyzeFile = exports.filterSources = exports.findSourceFiles = exports.formatCsvReport = exports.formatMarkdownReport = exports.formatJsonReport = exports.formatReport = exports.sortByCrap = exports.crapScore = exports.normalizePath = exports.sourceToModule = exports.coverageForRange = exports.parseCoverage = exports.extractFunctions = void 0;
7
+ exports.generateReport = generateReport;
8
+ var complexity_1 = require("./complexity");
9
+ Object.defineProperty(exports, "extractFunctions", { enumerable: true, get: function () { return complexity_1.extractFunctions; } });
10
+ var coverage_1 = require("./coverage");
11
+ Object.defineProperty(exports, "parseCoverage", { enumerable: true, get: function () { return coverage_1.parseCoverage; } });
12
+ Object.defineProperty(exports, "coverageForRange", { enumerable: true, get: function () { return coverage_1.coverageForRange; } });
13
+ Object.defineProperty(exports, "sourceToModule", { enumerable: true, get: function () { return coverage_1.sourceToModule; } });
14
+ Object.defineProperty(exports, "normalizePath", { enumerable: true, get: function () { return coverage_1.normalizePath; } });
15
+ var crap_1 = require("./crap");
16
+ Object.defineProperty(exports, "crapScore", { enumerable: true, get: function () { return crap_1.crapScore; } });
17
+ Object.defineProperty(exports, "sortByCrap", { enumerable: true, get: function () { return crap_1.sortByCrap; } });
18
+ Object.defineProperty(exports, "formatReport", { enumerable: true, get: function () { return crap_1.formatReport; } });
19
+ Object.defineProperty(exports, "formatJsonReport", { enumerable: true, get: function () { return crap_1.formatJsonReport; } });
20
+ Object.defineProperty(exports, "formatMarkdownReport", { enumerable: true, get: function () { return crap_1.formatMarkdownReport; } });
21
+ Object.defineProperty(exports, "formatCsvReport", { enumerable: true, get: function () { return crap_1.formatCsvReport; } });
22
+ var core_1 = require("./core");
23
+ Object.defineProperty(exports, "findSourceFiles", { enumerable: true, get: function () { return core_1.findSourceFiles; } });
24
+ Object.defineProperty(exports, "filterSources", { enumerable: true, get: function () { return core_1.filterSources; } });
25
+ Object.defineProperty(exports, "analyzeFile", { enumerable: true, get: function () { return core_1.analyzeFile; } });
26
+ const fs_1 = require("fs");
27
+ const path_1 = require("path");
28
+ const core_2 = require("./core");
29
+ const core_3 = require("./core");
30
+ const coverage_2 = require("./coverage");
31
+ const crap_2 = require("./crap");
32
+ function generateReport(opts) {
33
+ const srcDir = (0, path_1.resolve)(opts.srcDir);
34
+ const coverageDir = (0, path_1.resolve)(opts.coverageDir);
35
+ if (!(0, fs_1.existsSync)(coverageDir)) {
36
+ throw new Error(`Coverage directory not found: ${coverageDir}`);
37
+ }
38
+ const files = (0, core_2.findSourceFilesWithOptions)({
39
+ srcDirs: [srcDir],
40
+ excludes: opts.excludes ?? [],
41
+ });
42
+ const filtered = (0, core_3.filterSources)(files, opts.filters ?? []);
43
+ const coverageData = (0, coverage_2.parseCoverage)(coverageDir);
44
+ const entries = [];
45
+ for (const file of filtered) {
46
+ entries.push(...(0, core_3.analyzeFile)(file, coverageData, srcDir));
47
+ }
48
+ return { entries: (0, crap_2.sortByCrap)(entries) };
49
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runCli(argv: string[]): Promise<number>;
package/dist/cli.js ADDED
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runCli = runCli;
37
+ const fs_1 = require("fs");
38
+ const path_1 = require("path");
39
+ const report_1 = require("./report");
40
+ const options_1 = require("./options");
41
+ const config_1 = require("./config");
42
+ function getVersion() {
43
+ const pkg = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', 'package.json'), 'utf8'));
44
+ return pkg.version;
45
+ }
46
+ function formatHelp() {
47
+ return `Usage: crap4ts [options] [filters...]
48
+
49
+ Options:
50
+ --src <dir> Source directory to analyze (default: src)
51
+ --exclude <pattern> Exclude files whose path contains <pattern> (repeatable)
52
+ --timeout <seconds> Analysis timeout in seconds (default: 600)
53
+ --output <format> Output format: text, json, markdown, csv (default: text)
54
+ --json Shorthand for --output json
55
+ --runner <vitest|jest> Skip auto-detection, use specified test runner
56
+ --coverage-command <cmd> Run a custom shell command for coverage instead
57
+ --fail-on-crap <n> Exit 1 if any function CRAP score >= n
58
+ --fail-on-complexity <n> Exit 1 if any function complexity >= n
59
+ --fail-on-coverage-below <n> Exit 1 if any function coverage < n (0-100)
60
+ --top <n> Show only the top N entries (thresholds check all)
61
+ --config <path> Load config from a specific file
62
+ --help, -h Show this help message
63
+ --version, -v Show version number
64
+
65
+ Subcommands:
66
+ skill Manage the bundled AI skill (install | uninstall | show | path)`;
67
+ }
68
+ async function runCli(argv) {
69
+ if (argv[0] === 'skill') {
70
+ const { runSkillCommand } = await Promise.resolve().then(() => __importStar(require('./skill-cmd')));
71
+ return runSkillCommand(argv.slice(1));
72
+ }
73
+ try {
74
+ const opts = (0, options_1.parseOptions)(argv);
75
+ if (opts.mode === 'help') {
76
+ console.log(formatHelp());
77
+ return 0;
78
+ }
79
+ if (opts.mode === 'version') {
80
+ console.log(getVersion());
81
+ return 0;
82
+ }
83
+ const config = (0, config_1.loadConfig)(opts.configPath);
84
+ const merged = (0, config_1.mergeConfigIntoOptions)(opts, config);
85
+ return await (0, report_1.runReport)(merged);
86
+ }
87
+ catch (e) {
88
+ console.error(e.message);
89
+ return 2;
90
+ }
91
+ }
@@ -0,0 +1,7 @@
1
+ export interface FunctionInfo {
2
+ name: string;
3
+ startLine: number;
4
+ endLine: number;
5
+ complexity: number;
6
+ }
7
+ export declare function extractFunctions(source: string, filePath?: string): FunctionInfo[];