fcis 0.1.0 → 0.2.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/.plans/003-code-cleanup-consolidation.md +242 -0
- package/.plans/004-directory-depth-rollup.md +408 -0
- package/.plans/005-code-refinements.md +210 -0
- package/.plans/006-minor-refinements.md +149 -0
- package/.plans/007-compositional-function-scoring.md +514 -0
- package/README.md +38 -3
- package/TECHNICAL.md +125 -2
- package/dist/cli.js +595 -327
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -2
- package/dist/index.js +409 -240
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/cli-utils.ts +201 -0
- package/src/cli.ts +99 -117
- package/src/detection/markers.ts +0 -222
- package/src/extraction/extract-functions.ts +106 -2
- package/src/extraction/extractor.ts +35 -74
- package/src/reporting/report-console.ts +188 -102
- package/src/reporting/report-json.ts +26 -3
- package/src/scoring/scorer.ts +425 -160
- package/src/types.ts +9 -2
- package/tests/classifier.test.ts +0 -1
- package/tests/cli.test.ts +356 -0
- package/tests/detect-markers.test.ts +1 -3
- package/tests/extractor.test.ts +95 -1
- package/tests/integration.test.ts +344 -0
- package/tests/report-console.test.ts +92 -0
- package/tests/scorer.test.ts +886 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fcis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Functional Core, Imperative Shell analyzer for TypeScript codebases",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"chalk": "^5.3.0",
|
|
24
24
|
"cleye": "^1.3.2",
|
|
25
|
+
"jsonc-parser": "^3.3.1",
|
|
25
26
|
"ts-morph": "^24.0.0",
|
|
26
27
|
"ts-pattern": "^5.6.0",
|
|
27
28
|
"zod": "^3.24.0"
|
package/src/cli-utils.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Utilities - Pure Functions
|
|
3
|
+
*
|
|
4
|
+
* Extracted pure functions for CLI validation and configuration building.
|
|
5
|
+
* These are separated from cli.ts to enable unit testing without triggering
|
|
6
|
+
* the CLI entry point.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { z } from 'zod'
|
|
10
|
+
import type { AnalyzerConfig } from './types.js'
|
|
11
|
+
|
|
12
|
+
const VALID_FORMATS = ['console', 'json', 'summary'] as const
|
|
13
|
+
type OutputFormat = (typeof VALID_FORMATS)[number]
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* CLI flags as parsed by cleye
|
|
17
|
+
* Note: cleye automatically adds `version` and `help` flags
|
|
18
|
+
*/
|
|
19
|
+
export type CliFlags = {
|
|
20
|
+
json: boolean
|
|
21
|
+
output: string | undefined
|
|
22
|
+
minHealth: number | undefined
|
|
23
|
+
minPurity: number | undefined
|
|
24
|
+
minQuality: number | undefined
|
|
25
|
+
files: string | undefined
|
|
26
|
+
format: string
|
|
27
|
+
quiet: boolean
|
|
28
|
+
verbose: boolean
|
|
29
|
+
dirDepth: number | undefined
|
|
30
|
+
version: boolean | undefined
|
|
31
|
+
help: boolean | undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Validation result type
|
|
36
|
+
*/
|
|
37
|
+
export type ValidationResult = { valid: true } | { valid: false; error: string }
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validate a threshold value (0-100)
|
|
41
|
+
*/
|
|
42
|
+
const ThresholdSchema = z.number().min(0).max(100)
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Validate a positive integer (for --dir-depth)
|
|
46
|
+
*/
|
|
47
|
+
const PositiveIntegerSchema = z.number().int().min(1)
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Validate CLI flags (pure function)
|
|
51
|
+
*/
|
|
52
|
+
export function validateCliFlags(flags: CliFlags): ValidationResult {
|
|
53
|
+
// Validate threshold values
|
|
54
|
+
if (flags.minHealth !== undefined) {
|
|
55
|
+
const result = ThresholdSchema.safeParse(flags.minHealth)
|
|
56
|
+
if (!result.success) {
|
|
57
|
+
return {
|
|
58
|
+
valid: false,
|
|
59
|
+
error: '--min-health must be a number between 0 and 100',
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (flags.minPurity !== undefined) {
|
|
65
|
+
const result = ThresholdSchema.safeParse(flags.minPurity)
|
|
66
|
+
if (!result.success) {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
error: '--min-purity must be a number between 0 and 100',
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (flags.minQuality !== undefined) {
|
|
75
|
+
const result = ThresholdSchema.safeParse(flags.minQuality)
|
|
76
|
+
if (!result.success) {
|
|
77
|
+
return {
|
|
78
|
+
valid: false,
|
|
79
|
+
error: '--min-quality must be a number between 0 and 100',
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate format
|
|
85
|
+
if (!VALID_FORMATS.includes(flags.format as OutputFormat)) {
|
|
86
|
+
return {
|
|
87
|
+
valid: false,
|
|
88
|
+
error: `--format must be one of: ${VALID_FORMATS.join(', ')}`,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Validate dir-depth
|
|
93
|
+
if (flags.dirDepth !== undefined) {
|
|
94
|
+
const result = PositiveIntegerSchema.safeParse(flags.dirDepth)
|
|
95
|
+
if (!result.success) {
|
|
96
|
+
return {
|
|
97
|
+
valid: false,
|
|
98
|
+
error: '--dir-depth must be a positive integer (1 or greater)',
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return { valid: true }
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Validate a glob pattern (pure function)
|
|
108
|
+
*/
|
|
109
|
+
export function validateGlobPattern(pattern: string): ValidationResult {
|
|
110
|
+
if (!pattern || pattern.trim() === '') {
|
|
111
|
+
return { valid: false, error: 'Glob pattern cannot be empty' }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (pattern.startsWith('/')) {
|
|
115
|
+
return {
|
|
116
|
+
valid: false,
|
|
117
|
+
error: 'Glob pattern cannot be an absolute path (starting with /)',
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check for unbalanced brackets
|
|
122
|
+
let bracketDepth = 0
|
|
123
|
+
let braceDepth = 0
|
|
124
|
+
for (const char of pattern) {
|
|
125
|
+
if (char === '[') bracketDepth++
|
|
126
|
+
if (char === ']') bracketDepth--
|
|
127
|
+
if (char === '{') braceDepth++
|
|
128
|
+
if (char === '}') braceDepth--
|
|
129
|
+
if (bracketDepth < 0 || braceDepth < 0) {
|
|
130
|
+
return { valid: false, error: 'Glob pattern has unbalanced brackets' }
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (bracketDepth !== 0 || braceDepth !== 0) {
|
|
134
|
+
return { valid: false, error: 'Glob pattern has unbalanced brackets' }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { valid: true }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Build AnalyzerConfig from CLI flags (pure function)
|
|
142
|
+
*/
|
|
143
|
+
export function buildAnalyzerConfig(
|
|
144
|
+
tsconfigPath: string,
|
|
145
|
+
flags: CliFlags,
|
|
146
|
+
): AnalyzerConfig {
|
|
147
|
+
const config: AnalyzerConfig = {
|
|
148
|
+
tsconfigPath,
|
|
149
|
+
format: flags.format as 'console' | 'json' | 'summary',
|
|
150
|
+
quiet: flags.quiet,
|
|
151
|
+
verbose: flags.verbose,
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (flags.files !== undefined) {
|
|
155
|
+
config.filesGlob = flags.files
|
|
156
|
+
}
|
|
157
|
+
if (flags.minHealth !== undefined) {
|
|
158
|
+
config.minHealth = flags.minHealth
|
|
159
|
+
}
|
|
160
|
+
if (flags.minPurity !== undefined) {
|
|
161
|
+
config.minPurity = flags.minPurity
|
|
162
|
+
}
|
|
163
|
+
if (flags.minQuality !== undefined) {
|
|
164
|
+
config.minQuality = flags.minQuality
|
|
165
|
+
}
|
|
166
|
+
if (flags.output !== undefined) {
|
|
167
|
+
config.outputPath = flags.output
|
|
168
|
+
}
|
|
169
|
+
if (flags.dirDepth !== undefined) {
|
|
170
|
+
config.dirDepth = flags.dirDepth
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return config
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Build threshold config from CLI flags (pure function)
|
|
178
|
+
*/
|
|
179
|
+
export function buildThresholdConfig(flags: CliFlags): {
|
|
180
|
+
minHealth?: number
|
|
181
|
+
minPurity?: number
|
|
182
|
+
minQuality?: number
|
|
183
|
+
} {
|
|
184
|
+
const config: {
|
|
185
|
+
minHealth?: number
|
|
186
|
+
minPurity?: number
|
|
187
|
+
minQuality?: number
|
|
188
|
+
} = {}
|
|
189
|
+
|
|
190
|
+
if (flags.minHealth !== undefined) {
|
|
191
|
+
config.minHealth = flags.minHealth
|
|
192
|
+
}
|
|
193
|
+
if (flags.minPurity !== undefined) {
|
|
194
|
+
config.minPurity = flags.minPurity
|
|
195
|
+
}
|
|
196
|
+
if (flags.minQuality !== undefined) {
|
|
197
|
+
config.minQuality = flags.minQuality
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return config
|
|
201
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -13,26 +13,90 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { cli } from 'cleye'
|
|
16
|
-
import { z } from 'zod'
|
|
17
16
|
import chalk from 'chalk'
|
|
18
17
|
import * as fs from 'node:fs'
|
|
19
18
|
import * as path from 'node:path'
|
|
20
19
|
|
|
21
20
|
import { analyze, checkThresholds } from './analyzer.js'
|
|
22
|
-
import {
|
|
21
|
+
import { validateTsconfigContent } from './extraction/extractor.js'
|
|
23
22
|
import {
|
|
24
23
|
printConsoleReport,
|
|
25
24
|
generateSummaryLine,
|
|
26
25
|
} from './reporting/report-console.js'
|
|
27
26
|
import { generateJsonReport, writeJsonReport } from './reporting/report-json.js'
|
|
28
|
-
import type {
|
|
27
|
+
import type { ProjectScore } from './types.js'
|
|
28
|
+
import {
|
|
29
|
+
validateCliFlags,
|
|
30
|
+
validateGlobPattern,
|
|
31
|
+
buildAnalyzerConfig,
|
|
32
|
+
buildThresholdConfig,
|
|
33
|
+
type CliFlags,
|
|
34
|
+
type ValidationResult,
|
|
35
|
+
} from './cli-utils.js'
|
|
29
36
|
|
|
30
37
|
const EXIT_SUCCESS = 0
|
|
31
38
|
const EXIT_THRESHOLD_FAILED = 1
|
|
32
39
|
const EXIT_CONFIG_ERROR = 2
|
|
33
40
|
const EXIT_ANALYSIS_ERROR = 3
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Handle analysis output based on flags (shell function - performs I/O)
|
|
44
|
+
*/
|
|
45
|
+
function handleAnalysisOutput(score: ProjectScore, flags: CliFlags): void {
|
|
46
|
+
if (!flags.quiet) {
|
|
47
|
+
if (flags.json || flags.format === 'json') {
|
|
48
|
+
const jsonOptions: { pretty: boolean; dirDepth?: number } = {
|
|
49
|
+
pretty: true,
|
|
50
|
+
}
|
|
51
|
+
if (flags.dirDepth !== undefined) {
|
|
52
|
+
jsonOptions.dirDepth = flags.dirDepth
|
|
53
|
+
}
|
|
54
|
+
console.log(generateJsonReport(score, jsonOptions))
|
|
55
|
+
} else if (flags.format === 'summary') {
|
|
56
|
+
console.log(generateSummaryLine(score))
|
|
57
|
+
} else {
|
|
58
|
+
const reportOptions: { verbose?: boolean; dirDepth?: number } = {
|
|
59
|
+
verbose: flags.verbose,
|
|
60
|
+
}
|
|
61
|
+
if (flags.dirDepth !== undefined) {
|
|
62
|
+
reportOptions.dirDepth = flags.dirDepth
|
|
63
|
+
}
|
|
64
|
+
printConsoleReport(score, reportOptions)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Write JSON to file if requested
|
|
69
|
+
if (flags.output) {
|
|
70
|
+
const outputOptions: { pretty: boolean; dirDepth?: number } = {
|
|
71
|
+
pretty: true,
|
|
72
|
+
}
|
|
73
|
+
if (flags.dirDepth !== undefined) {
|
|
74
|
+
outputOptions.dirDepth = flags.dirDepth
|
|
75
|
+
}
|
|
76
|
+
writeJsonReport(score, flags.output, outputOptions)
|
|
77
|
+
if (!flags.quiet && flags.format !== 'json') {
|
|
78
|
+
console.log(chalk.green(`Report written to: ${flags.output}`))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Ensure output directory exists (shell function - performs I/O)
|
|
85
|
+
*/
|
|
86
|
+
function ensureOutputDirectory(outputPath: string): ValidationResult {
|
|
87
|
+
const outputDir = path.dirname(path.resolve(outputPath))
|
|
88
|
+
if (!fs.existsSync(outputDir)) {
|
|
89
|
+
try {
|
|
90
|
+
fs.mkdirSync(outputDir, { recursive: true })
|
|
91
|
+
} catch {
|
|
92
|
+
return {
|
|
93
|
+
valid: false,
|
|
94
|
+
error: `Cannot create output directory: ${outputDir}`,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { valid: true }
|
|
99
|
+
}
|
|
36
100
|
|
|
37
101
|
const argv = cli({
|
|
38
102
|
name: 'fcis',
|
|
@@ -83,6 +147,11 @@ const argv = cli({
|
|
|
83
147
|
description: 'Show per-file scores and all classified functions',
|
|
84
148
|
default: false,
|
|
85
149
|
},
|
|
150
|
+
dirDepth: {
|
|
151
|
+
type: Number,
|
|
152
|
+
description:
|
|
153
|
+
'Roll up directory metrics to depth N (e.g., 1 for top-level)',
|
|
154
|
+
},
|
|
86
155
|
},
|
|
87
156
|
parameters: ['<tsconfig>'],
|
|
88
157
|
help: {
|
|
@@ -102,13 +171,14 @@ async function main(): Promise<void> {
|
|
|
102
171
|
|
|
103
172
|
const tsconfigPath = args.tsconfig
|
|
104
173
|
|
|
105
|
-
// Validate tsconfig
|
|
174
|
+
// Validate tsconfig path provided
|
|
106
175
|
if (!tsconfigPath) {
|
|
107
176
|
console.error(chalk.red('Error: tsconfig path is required'))
|
|
108
177
|
console.error('Usage: fcis <tsconfig> [options]')
|
|
109
178
|
process.exit(EXIT_CONFIG_ERROR)
|
|
110
179
|
}
|
|
111
180
|
|
|
181
|
+
// Validate tsconfig exists
|
|
112
182
|
const absoluteTsconfigPath = path.resolve(tsconfigPath)
|
|
113
183
|
if (!fs.existsSync(absoluteTsconfigPath)) {
|
|
114
184
|
console.error(
|
|
@@ -117,71 +187,39 @@ async function main(): Promise<void> {
|
|
|
117
187
|
process.exit(EXIT_CONFIG_ERROR)
|
|
118
188
|
}
|
|
119
189
|
|
|
120
|
-
// Validate tsconfig
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
JSON.parse(strippedContent)
|
|
125
|
-
} catch (e) {
|
|
190
|
+
// Validate tsconfig content
|
|
191
|
+
const tsconfigContent = fs.readFileSync(absoluteTsconfigPath, 'utf-8')
|
|
192
|
+
const tsconfigValidation = validateTsconfigContent(tsconfigContent)
|
|
193
|
+
if (!tsconfigValidation.valid) {
|
|
126
194
|
console.error(
|
|
127
195
|
chalk.red(`Error: Invalid tsconfig.json at ${absoluteTsconfigPath}`),
|
|
128
196
|
)
|
|
129
|
-
console.error(
|
|
197
|
+
console.error(tsconfigValidation.error)
|
|
130
198
|
process.exit(EXIT_CONFIG_ERROR)
|
|
131
199
|
}
|
|
132
200
|
|
|
133
|
-
// Validate
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
chalk.red('Error: --min-health must be a number between 0 and 100'),
|
|
139
|
-
)
|
|
140
|
-
process.exit(EXIT_CONFIG_ERROR)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (flags.minPurity !== undefined) {
|
|
145
|
-
const result = ThresholdSchema.safeParse(flags.minPurity)
|
|
146
|
-
if (!result.success) {
|
|
147
|
-
console.error(
|
|
148
|
-
chalk.red('Error: --min-purity must be a number between 0 and 100'),
|
|
149
|
-
)
|
|
150
|
-
process.exit(EXIT_CONFIG_ERROR)
|
|
151
|
-
}
|
|
201
|
+
// Validate CLI flags
|
|
202
|
+
const flagsValidation = validateCliFlags(flags)
|
|
203
|
+
if (!flagsValidation.valid) {
|
|
204
|
+
console.error(chalk.red(`Error: ${flagsValidation.error}`))
|
|
205
|
+
process.exit(EXIT_CONFIG_ERROR)
|
|
152
206
|
}
|
|
153
207
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
)
|
|
208
|
+
// Validate glob pattern if provided
|
|
209
|
+
if (flags.files !== undefined) {
|
|
210
|
+
const globValidation = validateGlobPattern(flags.files)
|
|
211
|
+
if (!globValidation.valid) {
|
|
212
|
+
console.error(chalk.red(`Error: ${globValidation.error}`))
|
|
160
213
|
process.exit(EXIT_CONFIG_ERROR)
|
|
161
214
|
}
|
|
162
215
|
}
|
|
163
216
|
|
|
164
|
-
// Validate format
|
|
165
|
-
const validFormats = ['console', 'json', 'summary']
|
|
166
|
-
if (!validFormats.includes(flags.format)) {
|
|
167
|
-
console.error(
|
|
168
|
-
chalk.red(`Error: --format must be one of: ${validFormats.join(', ')}`),
|
|
169
|
-
)
|
|
170
|
-
process.exit(EXIT_CONFIG_ERROR)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
217
|
// Validate output path if provided
|
|
174
218
|
if (flags.output) {
|
|
175
|
-
const
|
|
176
|
-
if (!
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} catch (e) {
|
|
180
|
-
console.error(
|
|
181
|
-
chalk.red(`Error: Cannot create output directory: ${outputDir}`),
|
|
182
|
-
)
|
|
183
|
-
process.exit(EXIT_CONFIG_ERROR)
|
|
184
|
-
}
|
|
219
|
+
const outputValidation = ensureOutputDirectory(flags.output)
|
|
220
|
+
if (!outputValidation.valid) {
|
|
221
|
+
console.error(chalk.red(`Error: ${outputValidation.error}`))
|
|
222
|
+
process.exit(EXIT_CONFIG_ERROR)
|
|
185
223
|
}
|
|
186
224
|
}
|
|
187
225
|
|
|
@@ -191,69 +229,14 @@ async function main(): Promise<void> {
|
|
|
191
229
|
console.log(chalk.gray(`Analyzing ${process.cwd()}...`))
|
|
192
230
|
}
|
|
193
231
|
|
|
194
|
-
|
|
195
|
-
const config: AnalyzerConfig = {
|
|
196
|
-
tsconfigPath: absoluteTsconfigPath,
|
|
197
|
-
format: flags.format as 'console' | 'json' | 'summary',
|
|
198
|
-
quiet: flags.quiet,
|
|
199
|
-
verbose: flags.verbose,
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Only add optional properties if they are defined
|
|
203
|
-
if (flags.files !== undefined) {
|
|
204
|
-
config.filesGlob = flags.files
|
|
205
|
-
}
|
|
206
|
-
if (flags.minHealth !== undefined) {
|
|
207
|
-
config.minHealth = flags.minHealth
|
|
208
|
-
}
|
|
209
|
-
if (flags.minPurity !== undefined) {
|
|
210
|
-
config.minPurity = flags.minPurity
|
|
211
|
-
}
|
|
212
|
-
if (flags.minQuality !== undefined) {
|
|
213
|
-
config.minQuality = flags.minQuality
|
|
214
|
-
}
|
|
215
|
-
if (flags.output !== undefined) {
|
|
216
|
-
config.outputPath = flags.output
|
|
217
|
-
}
|
|
218
|
-
|
|
232
|
+
const config = buildAnalyzerConfig(absoluteTsconfigPath, flags)
|
|
219
233
|
const score = await analyze(config)
|
|
220
234
|
|
|
221
|
-
// Output results
|
|
222
|
-
|
|
223
|
-
if (flags.json || flags.format === 'json') {
|
|
224
|
-
console.log(generateJsonReport(score, { pretty: true }))
|
|
225
|
-
} else if (flags.format === 'summary') {
|
|
226
|
-
console.log(generateSummaryLine(score))
|
|
227
|
-
} else {
|
|
228
|
-
printConsoleReport(score, { verbose: flags.verbose })
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Write JSON to file if requested
|
|
233
|
-
if (flags.output) {
|
|
234
|
-
writeJsonReport(score, flags.output, { pretty: true })
|
|
235
|
-
if (!flags.quiet && flags.format !== 'json') {
|
|
236
|
-
console.log(chalk.green(`Report written to: ${flags.output}`))
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Check thresholds - build object only with defined values
|
|
241
|
-
const thresholdConfig: {
|
|
242
|
-
minHealth?: number
|
|
243
|
-
minPurity?: number
|
|
244
|
-
minQuality?: number
|
|
245
|
-
} = {}
|
|
246
|
-
|
|
247
|
-
if (flags.minHealth !== undefined) {
|
|
248
|
-
thresholdConfig.minHealth = flags.minHealth
|
|
249
|
-
}
|
|
250
|
-
if (flags.minPurity !== undefined) {
|
|
251
|
-
thresholdConfig.minPurity = flags.minPurity
|
|
252
|
-
}
|
|
253
|
-
if (flags.minQuality !== undefined) {
|
|
254
|
-
thresholdConfig.minQuality = flags.minQuality
|
|
255
|
-
}
|
|
235
|
+
// Output results
|
|
236
|
+
handleAnalysisOutput(score, flags)
|
|
256
237
|
|
|
238
|
+
// Check thresholds
|
|
239
|
+
const thresholdConfig = buildThresholdConfig(flags)
|
|
257
240
|
const thresholdResult = checkThresholds(score, thresholdConfig)
|
|
258
241
|
|
|
259
242
|
if (!thresholdResult.passed) {
|
|
@@ -274,7 +257,6 @@ async function main(): Promise<void> {
|
|
|
274
257
|
console.error(error instanceof Error ? error.message : String(error))
|
|
275
258
|
}
|
|
276
259
|
|
|
277
|
-
// Check if it's a "no files" error
|
|
278
260
|
if (error instanceof Error && error.message.includes('No source files')) {
|
|
279
261
|
process.exit(EXIT_ANALYSIS_ERROR)
|
|
280
262
|
}
|