fcis 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.
Files changed (151) hide show
  1. package/.plans/001-fcis-analyzer.md +832 -0
  2. package/.plans/002-fcis-analyzer-improvements.md +205 -0
  3. package/README.md +272 -0
  4. package/TECHNICAL.md +386 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +1836 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/index.d.ts +709 -0
  9. package/dist/index.js +1845 -0
  10. package/dist/index.js.map +1 -0
  11. package/package.json +47 -0
  12. package/pnpm-workspace.yaml +0 -0
  13. package/src/analyzer.ts +266 -0
  14. package/src/classification/classifier.ts +156 -0
  15. package/src/classification/derive-status.ts +171 -0
  16. package/src/classification/quality-scorer.ts +481 -0
  17. package/src/cli.ts +286 -0
  18. package/src/detection/detect-markers.ts +480 -0
  19. package/src/detection/markers.ts +332 -0
  20. package/src/extraction/extract-functions.ts +570 -0
  21. package/src/extraction/extractor.ts +188 -0
  22. package/src/index.ts +111 -0
  23. package/src/reporting/report-console.ts +416 -0
  24. package/src/reporting/report-json.ts +232 -0
  25. package/src/scoring/scorer.ts +504 -0
  26. package/src/types.ts +248 -0
  27. package/tests/classifier.test.ts +480 -0
  28. package/tests/derive-status.test.ts +464 -0
  29. package/tests/detect-markers.test.ts +639 -0
  30. package/tests/extractor.test.ts +155 -0
  31. package/tests/integration.test.ts +706 -0
  32. package/tests/quality-scorer.test.ts +650 -0
  33. package/tests/scorer.test.ts +768 -0
  34. package/tsconfig.json +34 -0
  35. package/tsup.config.ts +17 -0
  36. package/vendor/ts-morph/.editorconfig +10 -0
  37. package/vendor/ts-morph/.gitattributes +11 -0
  38. package/vendor/ts-morph/.github/CODE_OF_CONDUCT.md +77 -0
  39. package/vendor/ts-morph/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
  40. package/vendor/ts-morph/.github/ISSUE_TEMPLATE/custom.md +4 -0
  41. package/vendor/ts-morph/.github/ISSUE_TEMPLATE/feature_request.md +18 -0
  42. package/vendor/ts-morph/.github/workflows/ci.yml +50 -0
  43. package/vendor/ts-morph/.github/workflows/publish.yml +53 -0
  44. package/vendor/ts-morph/.vscode/settings.json +10 -0
  45. package/vendor/ts-morph/CONTRIBUTING.md +23 -0
  46. package/vendor/ts-morph/DEVELOPMENT.md +32 -0
  47. package/vendor/ts-morph/LICENSE +21 -0
  48. package/vendor/ts-morph/deno.json +8 -0
  49. package/vendor/ts-morph/deno.lock +1233 -0
  50. package/vendor/ts-morph/docs/CNAME +1 -0
  51. package/vendor/ts-morph/docs/Gemfile +2 -0
  52. package/vendor/ts-morph/docs/_config.yml +5 -0
  53. package/vendor/ts-morph/docs/_layouts/default.html +159 -0
  54. package/vendor/ts-morph/docs/_script-templates/main.ts +116 -0
  55. package/vendor/ts-morph/docs/assets/css/style.scss +212 -0
  56. package/vendor/ts-morph/docs/details/ambient.md +38 -0
  57. package/vendor/ts-morph/docs/details/async.md +31 -0
  58. package/vendor/ts-morph/docs/details/classes.md +314 -0
  59. package/vendor/ts-morph/docs/details/comment-ranges.md +7 -0
  60. package/vendor/ts-morph/docs/details/comments.md +122 -0
  61. package/vendor/ts-morph/docs/details/decorators.md +119 -0
  62. package/vendor/ts-morph/docs/details/documentation.md +73 -0
  63. package/vendor/ts-morph/docs/details/enums.md +117 -0
  64. package/vendor/ts-morph/docs/details/exports.md +308 -0
  65. package/vendor/ts-morph/docs/details/expressions.md +46 -0
  66. package/vendor/ts-morph/docs/details/functions.md +150 -0
  67. package/vendor/ts-morph/docs/details/generators.md +27 -0
  68. package/vendor/ts-morph/docs/details/identifiers.md +79 -0
  69. package/vendor/ts-morph/docs/details/imports.md +191 -0
  70. package/vendor/ts-morph/docs/details/index.md +52 -0
  71. package/vendor/ts-morph/docs/details/initializers.md +40 -0
  72. package/vendor/ts-morph/docs/details/interfaces.md +218 -0
  73. package/vendor/ts-morph/docs/details/literals.md +20 -0
  74. package/vendor/ts-morph/docs/details/modifiers.md +38 -0
  75. package/vendor/ts-morph/docs/details/modules.md +113 -0
  76. package/vendor/ts-morph/docs/details/namespaces.md +7 -0
  77. package/vendor/ts-morph/docs/details/object-literal-expressions.md +106 -0
  78. package/vendor/ts-morph/docs/details/parameters.md +64 -0
  79. package/vendor/ts-morph/docs/details/signatures.md +41 -0
  80. package/vendor/ts-morph/docs/details/source-files.md +292 -0
  81. package/vendor/ts-morph/docs/details/type-aliases.md +34 -0
  82. package/vendor/ts-morph/docs/details/type-parameters.md +72 -0
  83. package/vendor/ts-morph/docs/details/types.md +254 -0
  84. package/vendor/ts-morph/docs/details/variables.md +110 -0
  85. package/vendor/ts-morph/docs/emitting.md +151 -0
  86. package/vendor/ts-morph/docs/index.md +25 -0
  87. package/vendor/ts-morph/docs/manipulation/code-writer.md +20 -0
  88. package/vendor/ts-morph/docs/manipulation/formatting.md +76 -0
  89. package/vendor/ts-morph/docs/manipulation/index.md +136 -0
  90. package/vendor/ts-morph/docs/manipulation/order.md +14 -0
  91. package/vendor/ts-morph/docs/manipulation/performance.md +222 -0
  92. package/vendor/ts-morph/docs/manipulation/removing.md +31 -0
  93. package/vendor/ts-morph/docs/manipulation/renaming.md +106 -0
  94. package/vendor/ts-morph/docs/manipulation/settings.md +76 -0
  95. package/vendor/ts-morph/docs/manipulation/structures.md +117 -0
  96. package/vendor/ts-morph/docs/manipulation/transforms.md +84 -0
  97. package/vendor/ts-morph/docs/metrics/performance.json +4 -0
  98. package/vendor/ts-morph/docs/navigation/ambient-modules.md +22 -0
  99. package/vendor/ts-morph/docs/navigation/compiler-nodes.md +82 -0
  100. package/vendor/ts-morph/docs/navigation/directories.md +287 -0
  101. package/vendor/ts-morph/docs/navigation/example.md +50 -0
  102. package/vendor/ts-morph/docs/navigation/finding-references.md +53 -0
  103. package/vendor/ts-morph/docs/navigation/getting-source-files.md +59 -0
  104. package/vendor/ts-morph/docs/navigation/images/getChildrenVsForEachChild.gif +0 -0
  105. package/vendor/ts-morph/docs/navigation/index.md +94 -0
  106. package/vendor/ts-morph/docs/navigation/language-service.md +23 -0
  107. package/vendor/ts-morph/docs/navigation/program.md +25 -0
  108. package/vendor/ts-morph/docs/navigation/type-checker.md +33 -0
  109. package/vendor/ts-morph/docs/setup/adding-source-files.md +145 -0
  110. package/vendor/ts-morph/docs/setup/ast-viewers.md +46 -0
  111. package/vendor/ts-morph/docs/setup/diagnostics.md +109 -0
  112. package/vendor/ts-morph/docs/setup/file-system.md +106 -0
  113. package/vendor/ts-morph/docs/setup/images/atom-ast.png +0 -0
  114. package/vendor/ts-morph/docs/setup/images/atom-ast_small.png +0 -0
  115. package/vendor/ts-morph/docs/setup/images/atom-command-palette.png +0 -0
  116. package/vendor/ts-morph/docs/setup/images/atom-file.png +0 -0
  117. package/vendor/ts-morph/docs/setup/images/ts-ast-viewer.png +0 -0
  118. package/vendor/ts-morph/docs/setup/index.md +94 -0
  119. package/vendor/ts-morph/docs/utilities.md +55 -0
  120. package/vendor/ts-morph/dprint.json +23 -0
  121. package/vendor/ts-morph/package.json +30 -0
  122. package/vendor/ts-morph/packages/bootstrap/LICENSE +21 -0
  123. package/vendor/ts-morph/packages/bootstrap/lib/ts-morph-bootstrap.d.ts +397 -0
  124. package/vendor/ts-morph/packages/bootstrap/package.json +46 -0
  125. package/vendor/ts-morph/packages/bootstrap/readme.md +200 -0
  126. package/vendor/ts-morph/packages/common/LICENSE +21 -0
  127. package/vendor/ts-morph/packages/common/lib/ts-morph-common.d.ts +1082 -0
  128. package/vendor/ts-morph/packages/common/lib/typescript.d.ts +11439 -0
  129. package/vendor/ts-morph/packages/common/package.json +65 -0
  130. package/vendor/ts-morph/packages/common/readme.md +5 -0
  131. package/vendor/ts-morph/packages/scripts/changeTypeScriptVersion.ts +28 -0
  132. package/vendor/ts-morph/packages/scripts/createDeclarationProject.ts +47 -0
  133. package/vendor/ts-morph/packages/scripts/deps.ts +2 -0
  134. package/vendor/ts-morph/packages/scripts/execScript.ts +31 -0
  135. package/vendor/ts-morph/packages/scripts/folders.ts +11 -0
  136. package/vendor/ts-morph/packages/scripts/getDevCompilerVersions.ts +19 -0
  137. package/vendor/ts-morph/packages/scripts/mod.ts +7 -0
  138. package/vendor/ts-morph/packages/scripts/utils/Memoize.ts +36 -0
  139. package/vendor/ts-morph/packages/scripts/utils/forEachTypeText.ts +23 -0
  140. package/vendor/ts-morph/packages/scripts/utils/makeConstructorsPrivate.ts +26 -0
  141. package/vendor/ts-morph/packages/scripts/utils/mod.ts +4 -0
  142. package/vendor/ts-morph/packages/scripts/utils/printDiagnostics.ts +10 -0
  143. package/vendor/ts-morph/packages/ts-morph/LICENSE +21 -0
  144. package/vendor/ts-morph/packages/ts-morph/lib/ts-morph.d.ts +11198 -0
  145. package/vendor/ts-morph/packages/ts-morph/package.json +78 -0
  146. package/vendor/ts-morph/packages/ts-morph/readme.md +111 -0
  147. package/vendor/ts-morph/readme.md +14 -0
  148. package/vendor/ts-morph/rfcs/README.md +13 -0
  149. package/vendor/ts-morph/rfcs/RFC-0001 - Inserting Into Statements Handling Comments.md +181 -0
  150. package/vendor/ts-morph/tsconfig.common.json +17 -0
  151. package/vitest.config.ts +16 -0
package/src/cli.ts ADDED
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * FCIS CLI - Command Line Interface
4
+ *
5
+ * Entry point for the FCIS analyzer. Supports running analysis against
6
+ * a TypeScript project and outputting results in various formats.
7
+ *
8
+ * Exit codes:
9
+ * - 0: Success, all thresholds passed
10
+ * - 1: Analysis completed but below threshold
11
+ * - 2: Configuration error
12
+ * - 3: Analysis error (zero files could be analyzed)
13
+ */
14
+
15
+ import { cli } from 'cleye'
16
+ import { z } from 'zod'
17
+ import chalk from 'chalk'
18
+ import * as fs from 'node:fs'
19
+ import * as path from 'node:path'
20
+
21
+ import { analyze, checkThresholds } from './analyzer.js'
22
+ import { stripJsonComments } from './extraction/extractor.js'
23
+ import {
24
+ printConsoleReport,
25
+ generateSummaryLine,
26
+ } from './reporting/report-console.js'
27
+ import { generateJsonReport, writeJsonReport } from './reporting/report-json.js'
28
+ import type { AnalyzerConfig } from './types.js'
29
+
30
+ const EXIT_SUCCESS = 0
31
+ const EXIT_THRESHOLD_FAILED = 1
32
+ const EXIT_CONFIG_ERROR = 2
33
+ const EXIT_ANALYSIS_ERROR = 3
34
+
35
+ const ThresholdSchema = z.number().min(0).max(100)
36
+
37
+ const argv = cli({
38
+ name: 'fcis',
39
+ version: '0.1.0',
40
+ flags: {
41
+ json: {
42
+ type: Boolean,
43
+ description: 'Output JSON to stdout (for piping/parsing)',
44
+ default: false,
45
+ },
46
+ output: {
47
+ type: String,
48
+ alias: 'o',
49
+ description: 'Write JSON report to file',
50
+ },
51
+ minHealth: {
52
+ type: Number,
53
+ description: 'Exit with code 1 if project health < N (0-100)',
54
+ },
55
+ minPurity: {
56
+ type: Number,
57
+ description: 'Exit with code 1 if purity < N (0-100)',
58
+ },
59
+ minQuality: {
60
+ type: Number,
61
+ description: 'Exit with code 1 if impurity quality < N (0-100)',
62
+ },
63
+ files: {
64
+ type: String,
65
+ alias: 'f',
66
+ description:
67
+ 'Analyze only files matching glob (for incremental/pre-commit)',
68
+ },
69
+ format: {
70
+ type: String,
71
+ description: 'Output format: console (default), json, summary',
72
+ default: 'console',
73
+ },
74
+ quiet: {
75
+ type: Boolean,
76
+ alias: 'q',
77
+ description: 'Suppress all output except errors; rely on exit code',
78
+ default: false,
79
+ },
80
+ verbose: {
81
+ type: Boolean,
82
+ alias: 'v',
83
+ description: 'Show per-file scores and all classified functions',
84
+ default: false,
85
+ },
86
+ },
87
+ parameters: ['<tsconfig>'],
88
+ help: {
89
+ description:
90
+ 'Analyze TypeScript code for Functional Core, Imperative Shell patterns',
91
+ examples: [
92
+ 'fcis analyze tsconfig.json',
93
+ 'fcis analyze tsconfig.json --min-health 70',
94
+ 'fcis analyze tsconfig.json --format json --output report.json',
95
+ 'fcis analyze tsconfig.json --files "src/services/**/*.ts"',
96
+ ],
97
+ },
98
+ })
99
+
100
+ async function main(): Promise<void> {
101
+ const { flags, _: args } = argv
102
+
103
+ const tsconfigPath = args.tsconfig
104
+
105
+ // Validate tsconfig exists
106
+ if (!tsconfigPath) {
107
+ console.error(chalk.red('Error: tsconfig path is required'))
108
+ console.error('Usage: fcis <tsconfig> [options]')
109
+ process.exit(EXIT_CONFIG_ERROR)
110
+ }
111
+
112
+ const absoluteTsconfigPath = path.resolve(tsconfigPath)
113
+ if (!fs.existsSync(absoluteTsconfigPath)) {
114
+ console.error(
115
+ chalk.red(`Error: tsconfig.json not found at: ${absoluteTsconfigPath}`),
116
+ )
117
+ process.exit(EXIT_CONFIG_ERROR)
118
+ }
119
+
120
+ // Validate tsconfig is valid JSON (strip comments first since TypeScript allows them)
121
+ try {
122
+ const content = fs.readFileSync(absoluteTsconfigPath, 'utf-8')
123
+ const strippedContent = stripJsonComments(content)
124
+ JSON.parse(strippedContent)
125
+ } catch (e) {
126
+ console.error(
127
+ chalk.red(`Error: Invalid tsconfig.json at ${absoluteTsconfigPath}`),
128
+ )
129
+ console.error(e instanceof Error ? e.message : String(e))
130
+ process.exit(EXIT_CONFIG_ERROR)
131
+ }
132
+
133
+ // Validate threshold values
134
+ if (flags.minHealth !== undefined) {
135
+ const result = ThresholdSchema.safeParse(flags.minHealth)
136
+ if (!result.success) {
137
+ console.error(
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
+ }
152
+ }
153
+
154
+ if (flags.minQuality !== undefined) {
155
+ const result = ThresholdSchema.safeParse(flags.minQuality)
156
+ if (!result.success) {
157
+ console.error(
158
+ chalk.red('Error: --min-quality must be a number between 0 and 100'),
159
+ )
160
+ process.exit(EXIT_CONFIG_ERROR)
161
+ }
162
+ }
163
+
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
+ // Validate output path if provided
174
+ if (flags.output) {
175
+ const outputDir = path.dirname(path.resolve(flags.output))
176
+ if (!fs.existsSync(outputDir)) {
177
+ try {
178
+ fs.mkdirSync(outputDir, { recursive: true })
179
+ } catch (e) {
180
+ console.error(
181
+ chalk.red(`Error: Cannot create output directory: ${outputDir}`),
182
+ )
183
+ process.exit(EXIT_CONFIG_ERROR)
184
+ }
185
+ }
186
+ }
187
+
188
+ // Run analysis
189
+ try {
190
+ if (!flags.quiet && flags.format === 'console') {
191
+ console.log(chalk.gray(`Analyzing ${process.cwd()}...`))
192
+ }
193
+
194
+ // Build config with proper undefined handling
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
+
219
+ const score = await analyze(config)
220
+
221
+ // Output results based on format
222
+ if (!flags.quiet) {
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
+ }
256
+
257
+ const thresholdResult = checkThresholds(score, thresholdConfig)
258
+
259
+ if (!thresholdResult.passed) {
260
+ if (!flags.quiet) {
261
+ console.log()
262
+ console.log(chalk.red.bold('Threshold check failed:'))
263
+ for (const failure of thresholdResult.failures) {
264
+ console.log(chalk.red(` ✗ ${failure}`))
265
+ }
266
+ }
267
+ process.exit(EXIT_THRESHOLD_FAILED)
268
+ }
269
+
270
+ process.exit(EXIT_SUCCESS)
271
+ } catch (error) {
272
+ if (!flags.quiet) {
273
+ console.error(chalk.red('Analysis failed:'))
274
+ console.error(error instanceof Error ? error.message : String(error))
275
+ }
276
+
277
+ // Check if it's a "no files" error
278
+ if (error instanceof Error && error.message.includes('No source files')) {
279
+ process.exit(EXIT_ANALYSIS_ERROR)
280
+ }
281
+
282
+ process.exit(EXIT_ANALYSIS_ERROR)
283
+ }
284
+ }
285
+
286
+ main()