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.
- package/.plans/001-fcis-analyzer.md +832 -0
- package/.plans/002-fcis-analyzer-improvements.md +205 -0
- package/README.md +272 -0
- package/TECHNICAL.md +386 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1836 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +709 -0
- package/dist/index.js +1845 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
- package/pnpm-workspace.yaml +0 -0
- package/src/analyzer.ts +266 -0
- package/src/classification/classifier.ts +156 -0
- package/src/classification/derive-status.ts +171 -0
- package/src/classification/quality-scorer.ts +481 -0
- package/src/cli.ts +286 -0
- package/src/detection/detect-markers.ts +480 -0
- package/src/detection/markers.ts +332 -0
- package/src/extraction/extract-functions.ts +570 -0
- package/src/extraction/extractor.ts +188 -0
- package/src/index.ts +111 -0
- package/src/reporting/report-console.ts +416 -0
- package/src/reporting/report-json.ts +232 -0
- package/src/scoring/scorer.ts +504 -0
- package/src/types.ts +248 -0
- package/tests/classifier.test.ts +480 -0
- package/tests/derive-status.test.ts +464 -0
- package/tests/detect-markers.test.ts +639 -0
- package/tests/extractor.test.ts +155 -0
- package/tests/integration.test.ts +706 -0
- package/tests/quality-scorer.test.ts +650 -0
- package/tests/scorer.test.ts +768 -0
- package/tsconfig.json +34 -0
- package/tsup.config.ts +17 -0
- package/vendor/ts-morph/.editorconfig +10 -0
- package/vendor/ts-morph/.gitattributes +11 -0
- package/vendor/ts-morph/.github/CODE_OF_CONDUCT.md +77 -0
- package/vendor/ts-morph/.github/ISSUE_TEMPLATE/bug_report.md +29 -0
- package/vendor/ts-morph/.github/ISSUE_TEMPLATE/custom.md +4 -0
- package/vendor/ts-morph/.github/ISSUE_TEMPLATE/feature_request.md +18 -0
- package/vendor/ts-morph/.github/workflows/ci.yml +50 -0
- package/vendor/ts-morph/.github/workflows/publish.yml +53 -0
- package/vendor/ts-morph/.vscode/settings.json +10 -0
- package/vendor/ts-morph/CONTRIBUTING.md +23 -0
- package/vendor/ts-morph/DEVELOPMENT.md +32 -0
- package/vendor/ts-morph/LICENSE +21 -0
- package/vendor/ts-morph/deno.json +8 -0
- package/vendor/ts-morph/deno.lock +1233 -0
- package/vendor/ts-morph/docs/CNAME +1 -0
- package/vendor/ts-morph/docs/Gemfile +2 -0
- package/vendor/ts-morph/docs/_config.yml +5 -0
- package/vendor/ts-morph/docs/_layouts/default.html +159 -0
- package/vendor/ts-morph/docs/_script-templates/main.ts +116 -0
- package/vendor/ts-morph/docs/assets/css/style.scss +212 -0
- package/vendor/ts-morph/docs/details/ambient.md +38 -0
- package/vendor/ts-morph/docs/details/async.md +31 -0
- package/vendor/ts-morph/docs/details/classes.md +314 -0
- package/vendor/ts-morph/docs/details/comment-ranges.md +7 -0
- package/vendor/ts-morph/docs/details/comments.md +122 -0
- package/vendor/ts-morph/docs/details/decorators.md +119 -0
- package/vendor/ts-morph/docs/details/documentation.md +73 -0
- package/vendor/ts-morph/docs/details/enums.md +117 -0
- package/vendor/ts-morph/docs/details/exports.md +308 -0
- package/vendor/ts-morph/docs/details/expressions.md +46 -0
- package/vendor/ts-morph/docs/details/functions.md +150 -0
- package/vendor/ts-morph/docs/details/generators.md +27 -0
- package/vendor/ts-morph/docs/details/identifiers.md +79 -0
- package/vendor/ts-morph/docs/details/imports.md +191 -0
- package/vendor/ts-morph/docs/details/index.md +52 -0
- package/vendor/ts-morph/docs/details/initializers.md +40 -0
- package/vendor/ts-morph/docs/details/interfaces.md +218 -0
- package/vendor/ts-morph/docs/details/literals.md +20 -0
- package/vendor/ts-morph/docs/details/modifiers.md +38 -0
- package/vendor/ts-morph/docs/details/modules.md +113 -0
- package/vendor/ts-morph/docs/details/namespaces.md +7 -0
- package/vendor/ts-morph/docs/details/object-literal-expressions.md +106 -0
- package/vendor/ts-morph/docs/details/parameters.md +64 -0
- package/vendor/ts-morph/docs/details/signatures.md +41 -0
- package/vendor/ts-morph/docs/details/source-files.md +292 -0
- package/vendor/ts-morph/docs/details/type-aliases.md +34 -0
- package/vendor/ts-morph/docs/details/type-parameters.md +72 -0
- package/vendor/ts-morph/docs/details/types.md +254 -0
- package/vendor/ts-morph/docs/details/variables.md +110 -0
- package/vendor/ts-morph/docs/emitting.md +151 -0
- package/vendor/ts-morph/docs/index.md +25 -0
- package/vendor/ts-morph/docs/manipulation/code-writer.md +20 -0
- package/vendor/ts-morph/docs/manipulation/formatting.md +76 -0
- package/vendor/ts-morph/docs/manipulation/index.md +136 -0
- package/vendor/ts-morph/docs/manipulation/order.md +14 -0
- package/vendor/ts-morph/docs/manipulation/performance.md +222 -0
- package/vendor/ts-morph/docs/manipulation/removing.md +31 -0
- package/vendor/ts-morph/docs/manipulation/renaming.md +106 -0
- package/vendor/ts-morph/docs/manipulation/settings.md +76 -0
- package/vendor/ts-morph/docs/manipulation/structures.md +117 -0
- package/vendor/ts-morph/docs/manipulation/transforms.md +84 -0
- package/vendor/ts-morph/docs/metrics/performance.json +4 -0
- package/vendor/ts-morph/docs/navigation/ambient-modules.md +22 -0
- package/vendor/ts-morph/docs/navigation/compiler-nodes.md +82 -0
- package/vendor/ts-morph/docs/navigation/directories.md +287 -0
- package/vendor/ts-morph/docs/navigation/example.md +50 -0
- package/vendor/ts-morph/docs/navigation/finding-references.md +53 -0
- package/vendor/ts-morph/docs/navigation/getting-source-files.md +59 -0
- package/vendor/ts-morph/docs/navigation/images/getChildrenVsForEachChild.gif +0 -0
- package/vendor/ts-morph/docs/navigation/index.md +94 -0
- package/vendor/ts-morph/docs/navigation/language-service.md +23 -0
- package/vendor/ts-morph/docs/navigation/program.md +25 -0
- package/vendor/ts-morph/docs/navigation/type-checker.md +33 -0
- package/vendor/ts-morph/docs/setup/adding-source-files.md +145 -0
- package/vendor/ts-morph/docs/setup/ast-viewers.md +46 -0
- package/vendor/ts-morph/docs/setup/diagnostics.md +109 -0
- package/vendor/ts-morph/docs/setup/file-system.md +106 -0
- package/vendor/ts-morph/docs/setup/images/atom-ast.png +0 -0
- package/vendor/ts-morph/docs/setup/images/atom-ast_small.png +0 -0
- package/vendor/ts-morph/docs/setup/images/atom-command-palette.png +0 -0
- package/vendor/ts-morph/docs/setup/images/atom-file.png +0 -0
- package/vendor/ts-morph/docs/setup/images/ts-ast-viewer.png +0 -0
- package/vendor/ts-morph/docs/setup/index.md +94 -0
- package/vendor/ts-morph/docs/utilities.md +55 -0
- package/vendor/ts-morph/dprint.json +23 -0
- package/vendor/ts-morph/package.json +30 -0
- package/vendor/ts-morph/packages/bootstrap/LICENSE +21 -0
- package/vendor/ts-morph/packages/bootstrap/lib/ts-morph-bootstrap.d.ts +397 -0
- package/vendor/ts-morph/packages/bootstrap/package.json +46 -0
- package/vendor/ts-morph/packages/bootstrap/readme.md +200 -0
- package/vendor/ts-morph/packages/common/LICENSE +21 -0
- package/vendor/ts-morph/packages/common/lib/ts-morph-common.d.ts +1082 -0
- package/vendor/ts-morph/packages/common/lib/typescript.d.ts +11439 -0
- package/vendor/ts-morph/packages/common/package.json +65 -0
- package/vendor/ts-morph/packages/common/readme.md +5 -0
- package/vendor/ts-morph/packages/scripts/changeTypeScriptVersion.ts +28 -0
- package/vendor/ts-morph/packages/scripts/createDeclarationProject.ts +47 -0
- package/vendor/ts-morph/packages/scripts/deps.ts +2 -0
- package/vendor/ts-morph/packages/scripts/execScript.ts +31 -0
- package/vendor/ts-morph/packages/scripts/folders.ts +11 -0
- package/vendor/ts-morph/packages/scripts/getDevCompilerVersions.ts +19 -0
- package/vendor/ts-morph/packages/scripts/mod.ts +7 -0
- package/vendor/ts-morph/packages/scripts/utils/Memoize.ts +36 -0
- package/vendor/ts-morph/packages/scripts/utils/forEachTypeText.ts +23 -0
- package/vendor/ts-morph/packages/scripts/utils/makeConstructorsPrivate.ts +26 -0
- package/vendor/ts-morph/packages/scripts/utils/mod.ts +4 -0
- package/vendor/ts-morph/packages/scripts/utils/printDiagnostics.ts +10 -0
- package/vendor/ts-morph/packages/ts-morph/LICENSE +21 -0
- package/vendor/ts-morph/packages/ts-morph/lib/ts-morph.d.ts +11198 -0
- package/vendor/ts-morph/packages/ts-morph/package.json +78 -0
- package/vendor/ts-morph/packages/ts-morph/readme.md +111 -0
- package/vendor/ts-morph/readme.md +14 -0
- package/vendor/ts-morph/rfcs/README.md +13 -0
- package/vendor/ts-morph/rfcs/RFC-0001 - Inserting Into Statements Handling Comments.md +181 -0
- package/vendor/ts-morph/tsconfig.common.json +17 -0
- package/vitest.config.ts +16 -0
package/TECHNICAL.md
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# FCIS Analyzer - Technical Documentation
|
|
2
|
+
|
|
3
|
+
This document provides technical details for developers working on or extending the FCIS Analyzer.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
The FCIS Analyzer follows the Functional Core, Imperative Shell (FCIS) pattern itself:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
11
|
+
│ CLI (Shell) │
|
|
12
|
+
│ cli.ts │
|
|
13
|
+
└─────────────────────────────────┬───────────────────────────────┘
|
|
14
|
+
│
|
|
15
|
+
┌─────────────────────────────────▼───────────────────────────────┐
|
|
16
|
+
│ Analyzer (Shell) │
|
|
17
|
+
│ analyzer.ts │
|
|
18
|
+
│ Orchestrates the analysis pipeline │
|
|
19
|
+
└───────┬─────────────────────────────────────────────────┬───────┘
|
|
20
|
+
│ │
|
|
21
|
+
▼ ▼
|
|
22
|
+
┌───────────────────┐ ┌───────────────────┐
|
|
23
|
+
│ Extraction │ │ Reporting │
|
|
24
|
+
│ (Shell) │ │ (Shell) │
|
|
25
|
+
│ - extractor.ts │ │ - report-json.ts │
|
|
26
|
+
│ - extract-fns.ts │ │ - report-console │
|
|
27
|
+
└───────┬───────────┘ └───────────────────┘
|
|
28
|
+
│ ▲
|
|
29
|
+
▼ │
|
|
30
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
31
|
+
│ PURE CORE │
|
|
32
|
+
│ ┌─────────────┐ ┌─────────────────┐ ┌────────────────────┐ │
|
|
33
|
+
│ │ Detection │ │ Classification │ │ Scoring │ │
|
|
34
|
+
│ │ │ │ │ │ │ │
|
|
35
|
+
│ │ markers.ts │ │ classifier.ts │ │ scorer.ts │ │
|
|
36
|
+
│ │ detect- │ │ quality- │ │ │ │
|
|
37
|
+
│ │ markers.ts │ │ scorer.ts │ │ │ │
|
|
38
|
+
│ │ │ │ derive-status │ │ │ │
|
|
39
|
+
│ └─────────────┘ └─────────────────┘ └────────────────────┘ │
|
|
40
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Module Breakdown
|
|
44
|
+
|
|
45
|
+
### Types (`types.ts`)
|
|
46
|
+
|
|
47
|
+
Central type definitions shared across all modules:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Core extraction types
|
|
51
|
+
type CallSite = {
|
|
52
|
+
expression: string // e.g., "db.user.findFirst"
|
|
53
|
+
line: number // relative to function start
|
|
54
|
+
isAwaited: boolean
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type ExtractedFunction = {
|
|
58
|
+
name: string | null
|
|
59
|
+
filePath: string
|
|
60
|
+
startLine: number
|
|
61
|
+
endLine: number
|
|
62
|
+
isAsync: boolean
|
|
63
|
+
isExported: boolean
|
|
64
|
+
bodyLineCount: number
|
|
65
|
+
statementCount: number
|
|
66
|
+
hasConditionals: boolean
|
|
67
|
+
parentContext: string | null
|
|
68
|
+
callSites: CallSite[]
|
|
69
|
+
hasAwait: boolean
|
|
70
|
+
propertyAccessChains: string[]
|
|
71
|
+
kind: 'function' | 'method' | 'arrow' | 'function-expression' | 'getter' | 'setter'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Marker types - strict union for exhaustive matching
|
|
75
|
+
type MarkerType =
|
|
76
|
+
| 'await-expression'
|
|
77
|
+
| 'database-call'
|
|
78
|
+
| 'network-fetch'
|
|
79
|
+
| 'network-http'
|
|
80
|
+
| 'fs-import'
|
|
81
|
+
| 'fs-call'
|
|
82
|
+
| 'env-access'
|
|
83
|
+
| 'console-log'
|
|
84
|
+
| 'logging'
|
|
85
|
+
| 'telemetry'
|
|
86
|
+
| 'queue-enqueue'
|
|
87
|
+
| 'event-emit'
|
|
88
|
+
|
|
89
|
+
// Classification
|
|
90
|
+
type FunctionClassification = 'pure' | 'impure'
|
|
91
|
+
type Status = 'ok' | 'review' | 'refactor'
|
|
92
|
+
|
|
93
|
+
// Scores
|
|
94
|
+
type FileScore = { ... }
|
|
95
|
+
type DirectoryScore = { ... }
|
|
96
|
+
type ProjectScore = { ... }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Extraction Layer (Shell)
|
|
100
|
+
|
|
101
|
+
**`extractor.ts`** - Loads TypeScript projects via ts-morph:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
function loadProject(options: ExtractorOptions): ExtractorResult
|
|
105
|
+
function getCommitHash(): string | null
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**`extract-functions.ts`** - Extracts function data from AST:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
function extractFunctions(sourceFile: SourceFile): ExtractedFunction[]
|
|
112
|
+
function extractImports(sourceFile: SourceFile): FileImports
|
|
113
|
+
function isTypeOnlyFile(sourceFile: SourceFile): boolean
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Detection Layer (Pure Core)
|
|
117
|
+
|
|
118
|
+
**`markers.ts`** - Marker definitions and patterns:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
const MARKER_CATALOG: Record<MarkerType, MarkerDefinition>
|
|
122
|
+
const DATABASE_OPERATIONS: Set<string>
|
|
123
|
+
const FS_MODULES: Set<string>
|
|
124
|
+
const HTTP_MODULES: Set<string>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**`detect-markers.ts`** - Marker detection logic:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
function detectMarkers(fn: ExtractedFunction, context: DetectionContext): ImpurityMarker[]
|
|
131
|
+
function createDetectionContext(imports: FileImports): DetectionContext
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Classification Layer (Pure Core)
|
|
135
|
+
|
|
136
|
+
**`classifier.ts`** - Binary classification:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
function classifyFunction(fn: ExtractedFunction, markers: ImpurityMarker[]): FunctionClassification
|
|
140
|
+
function shouldExcludeFunction(fn: ExtractedFunction): boolean
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**`quality-scorer.ts`** - Quality scoring for impure functions:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
function computeQualityScore(fn: ExtractedFunction, markers: ImpurityMarker[], context: DetectionContext): number
|
|
147
|
+
function analyzeFunction(fn: ExtractedFunction, markers: ImpurityMarker[], context: DetectionContext): FunctionAnalysis
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**`derive-status.ts`** - Status derivation:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
function deriveStatus(classification: FunctionClassification, qualityScore: number | null, thresholds?: QualityThresholds): Status
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Scoring Layer (Pure Core)
|
|
157
|
+
|
|
158
|
+
**`scorer.ts`** - Metric aggregation:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
function scoreFile(filePath: string, functions: ClassifiedFunction[], isTypeOnly?: boolean): FileScore
|
|
162
|
+
function scoreDirectory(dirPath: string, fileScores: FileScore[]): DirectoryScore
|
|
163
|
+
function scoreProject(directoryScores: DirectoryScore[], options?: ProjectScoreOptions): ProjectScore
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Reporting Layer (Shell)
|
|
167
|
+
|
|
168
|
+
**`report-json.ts`** - JSON output:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
function generateJsonReport(score: ProjectScore, options?: JsonReportOptions): string
|
|
172
|
+
function writeJsonReport(score: ProjectScore, outputPath: string, options?: JsonReportOptions): void
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**`report-console.ts`** - Console output:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
function printConsoleReport(score: ProjectScore, options?: { verbose?: boolean }): void
|
|
179
|
+
function generateSummaryLine(score: ProjectScore): string
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Classification Rules
|
|
183
|
+
|
|
184
|
+
### Binary Classification
|
|
185
|
+
|
|
186
|
+
A function is **pure** if it has **zero** impurity markers.
|
|
187
|
+
A function is **impure** if it has **one or more** impurity markers.
|
|
188
|
+
|
|
189
|
+
**Important**: `async` alone does NOT make a function impure. Only actual I/O operations count.
|
|
190
|
+
|
|
191
|
+
### Marker Detection Scope
|
|
192
|
+
|
|
193
|
+
Markers are detected at the **function level**, not the file level:
|
|
194
|
+
|
|
195
|
+
- A function is marked with `logging` only if it **calls** a logging function
|
|
196
|
+
- A function is marked with `network-http` only if it **calls** an HTTP client function
|
|
197
|
+
- A function is marked with `fs-call` only if it **calls** a file system function
|
|
198
|
+
- File-level imports do NOT automatically taint all functions in the file
|
|
199
|
+
|
|
200
|
+
This ensures pure helper functions in mixed files are correctly classified as pure.
|
|
201
|
+
|
|
202
|
+
**Example**: A file that imports `@sai/logger` may contain:
|
|
203
|
+
- Pure functions that never call the logger (correctly classified as pure)
|
|
204
|
+
- Impure functions that call `log()` or `logger.info()` (correctly classified as impure)
|
|
205
|
+
|
|
206
|
+
### Marker Detection Rules
|
|
207
|
+
|
|
208
|
+
| Marker Type | Detection Pattern |
|
|
209
|
+
|-------------|-------------------|
|
|
210
|
+
| `await-expression` | Any `await` keyword in function body |
|
|
211
|
+
| `database-call` | `db.*.findFirst`, `prisma.*.create`, etc. |
|
|
212
|
+
| `network-fetch` | `fetch(...)` call |
|
|
213
|
+
| `network-http` | `axios.*` call |
|
|
214
|
+
| `fs-call` | `fs.readFile`, `fs.writeFile`, `readFile`, `writeFile`, etc. |
|
|
215
|
+
| `env-access` | `process.env.*` property access |
|
|
216
|
+
| `console-log` | `console.log`, `console.error`, etc. |
|
|
217
|
+
| `logging` | `logger.*`, `log(...)` calls |
|
|
218
|
+
| `telemetry` | `track*`, `analytics.*` calls |
|
|
219
|
+
| `queue-enqueue` | `*.enqueue`, `queue.add` calls |
|
|
220
|
+
| `event-emit` | `*.emit`, `*.dispatch` calls |
|
|
221
|
+
|
|
222
|
+
## Quality Scoring
|
|
223
|
+
|
|
224
|
+
Quality score (0-100) measures how well-structured an impure function is.
|
|
225
|
+
|
|
226
|
+
### Positive Signals
|
|
227
|
+
|
|
228
|
+
| Signal | Points | Description |
|
|
229
|
+
|--------|--------|-------------|
|
|
230
|
+
| Calls `.pure.ts` import | +30 | Explicit FCIS pattern |
|
|
231
|
+
| Calls `plan*/derive*/compute*` | +20 | Pure naming convention |
|
|
232
|
+
| I/O at start (GATHER) | +15 | Good structure |
|
|
233
|
+
| I/O at end (EXECUTE) | +15 | Good structure |
|
|
234
|
+
| Low complexity (≤5) | +10 | Simple orchestration |
|
|
235
|
+
| Shell naming (`handle*`) | +5 | Intent signal |
|
|
236
|
+
| Calls predicates (`is*/has*`) | +5 | Uses pure helpers |
|
|
237
|
+
|
|
238
|
+
### Negative Signals (Penalties)
|
|
239
|
+
|
|
240
|
+
| Signal | Penalty | Description |
|
|
241
|
+
|--------|---------|-------------|
|
|
242
|
+
| I/O interleaved | -20 | Tangled structure |
|
|
243
|
+
| High complexity (>10) | -15 | Complex logic mixed with I/O |
|
|
244
|
+
| Multiple I/O types | -10 | Too many responsibilities |
|
|
245
|
+
| No pure function calls | -10 | All logic inline |
|
|
246
|
+
| Very long (>100 lines) | -10 | God function |
|
|
247
|
+
|
|
248
|
+
### Status Thresholds
|
|
249
|
+
|
|
250
|
+
| Classification | Quality Score | Status |
|
|
251
|
+
|---------------|---------------|--------|
|
|
252
|
+
| pure | n/a | ✓ ok |
|
|
253
|
+
| impure | ≥ 70 | ✓ ok |
|
|
254
|
+
| impure | 40-69 | ◐ review |
|
|
255
|
+
| impure | < 40 | ✗ refactor |
|
|
256
|
+
|
|
257
|
+
## Metrics
|
|
258
|
+
|
|
259
|
+
### Purity
|
|
260
|
+
```
|
|
261
|
+
purity = (pureCount / (pureCount + impureCount)) × 100
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Impurity Quality
|
|
265
|
+
```
|
|
266
|
+
impurityQuality = sum(qualityScore for impure functions) / impureCount
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Health
|
|
270
|
+
```
|
|
271
|
+
health = (functions with status 'ok') / totalFunctions × 100
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Refactoring Priority
|
|
275
|
+
```
|
|
276
|
+
priority = bodyLineCount × (100 - qualityScore)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Higher priority = larger, lower-quality functions (most impactful to fix).
|
|
280
|
+
|
|
281
|
+
## CLI Exit Codes
|
|
282
|
+
|
|
283
|
+
| Code | Meaning |
|
|
284
|
+
|------|---------|
|
|
285
|
+
| 0 | Success, all thresholds passed |
|
|
286
|
+
| 1 | Below threshold (`--min-health`, `--min-purity`, `--min-quality`) |
|
|
287
|
+
| 2 | Configuration error |
|
|
288
|
+
| 3 | Analysis error (zero files analyzed) |
|
|
289
|
+
|
|
290
|
+
## Extending the Analyzer
|
|
291
|
+
|
|
292
|
+
### Adding New Markers
|
|
293
|
+
|
|
294
|
+
1. Add the marker type to the `MarkerType` union in `types.ts`
|
|
295
|
+
2. Add detection logic in `detect-markers.ts`
|
|
296
|
+
3. Add marker definition to `MARKER_CATALOG` in `markers.ts`
|
|
297
|
+
4. Add tests in `detect-markers.test.ts`
|
|
298
|
+
|
|
299
|
+
### Customizing Quality Scoring
|
|
300
|
+
|
|
301
|
+
Modify `QUALITY_WEIGHTS` in `quality-scorer.ts`:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
export const QUALITY_WEIGHTS = {
|
|
305
|
+
callsPureFile: 30,
|
|
306
|
+
callsPureNamingConvention: 20,
|
|
307
|
+
// ... add or modify weights
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### Customizing Status Thresholds
|
|
312
|
+
|
|
313
|
+
Pass custom thresholds to `deriveStatus`:
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
const customThresholds: QualityThresholds = {
|
|
317
|
+
okThreshold: 80, // Stricter: need 80+ for 'ok'
|
|
318
|
+
reviewThreshold: 50, // Stricter: need 50+ for 'review'
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Testing
|
|
323
|
+
|
|
324
|
+
### Unit Tests (Pure Core)
|
|
325
|
+
|
|
326
|
+
Test pure functions with direct input/output assertions:
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { detectMarkers } from '../src/detection/detect-markers.js'
|
|
330
|
+
|
|
331
|
+
it('should detect database calls', () => {
|
|
332
|
+
const fn = createTestFunction({
|
|
333
|
+
callSites: [{ expression: 'db.user.findFirst', line: 3, isAwaited: true }]
|
|
334
|
+
})
|
|
335
|
+
const markers = detectMarkers(fn, context)
|
|
336
|
+
expect(markers).toContainEqual({ type: 'database-call', ... })
|
|
337
|
+
})
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Integration Tests
|
|
341
|
+
|
|
342
|
+
Use ts-morph's in-memory file system:
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { Project } from 'ts-morph'
|
|
346
|
+
|
|
347
|
+
const project = new Project({ useInMemoryFileSystem: true })
|
|
348
|
+
project.createSourceFile('/test.ts', `
|
|
349
|
+
export async function getUser(id: string) {
|
|
350
|
+
return await db.user.findFirst({ where: { id } })
|
|
351
|
+
}
|
|
352
|
+
`)
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## Performance Considerations
|
|
356
|
+
|
|
357
|
+
- **Target**: Full monorepo analysis < 60 seconds
|
|
358
|
+
- **Single file**: < 2 seconds
|
|
359
|
+
- **Optimization**: ts-morph lazy loading, parallel file processing
|
|
360
|
+
|
|
361
|
+
### Future: Caching (Post-v1)
|
|
362
|
+
|
|
363
|
+
- Cache `ExtractedFunction[]` per file by content hash
|
|
364
|
+
- Store in `.fcis-cache/` (gitignored)
|
|
365
|
+
- Invalidate on ts-morph or fcis version change
|
|
366
|
+
|
|
367
|
+
## Dependencies
|
|
368
|
+
|
|
369
|
+
| Package | Purpose |
|
|
370
|
+
|---------|---------|
|
|
371
|
+
| `ts-morph` | TypeScript AST parsing |
|
|
372
|
+
| `zod` | CLI argument validation |
|
|
373
|
+
| `cleye` | CLI argument parsing |
|
|
374
|
+
| `chalk` | Colored console output |
|
|
375
|
+
| `ts-pattern` | Pattern matching (optional) |
|
|
376
|
+
|
|
377
|
+
## File Exclusions
|
|
378
|
+
|
|
379
|
+
Automatically excluded from analysis:
|
|
380
|
+
- `*.test.ts`, `*.spec.ts` - Test files
|
|
381
|
+
- `*.d.ts` - Type declaration files
|
|
382
|
+
- `*.stories.tsx` - Storybook files
|
|
383
|
+
- `/node_modules/` - Dependencies
|
|
384
|
+
- `/generated/` - Generated code
|
|
385
|
+
- Type-only files (only types/interfaces/enums)
|
|
386
|
+
- Trivial functions (< 3 statements, no conditionals)
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|