fcis 0.1.0 → 0.2.1
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 +214 -132
- package/TECHNICAL.md +125 -2
- package/dist/cli.js +599 -328
- 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/docs/images/fc-is-submarine.webp +0 -0
- package/package.json +3 -1
- package/src/cli-utils.ts +201 -0
- package/src/cli.ts +105 -118
- 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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Plan 005: Code Refinements
|
|
2
|
+
|
|
3
|
+
## Background
|
|
4
|
+
|
|
5
|
+
Following the implementation of Plan 004 (Directory Depth Rollup), a code review identified several opportunities for simplification, reducing redundancy, and improving adherence to the Functional Core / Imperative Shell (FC/IS) pattern.
|
|
6
|
+
|
|
7
|
+
The FCIS analyzer is designed to follow FC/IS principles itself, with `scorer.ts` documented as a "PURE module." However, the recent addition of `rollupDirectoriesByDepth()` introduced a `path` import that performs I/O-adjacent operations, breaking this contract.
|
|
8
|
+
|
|
9
|
+
Additionally, several patterns are repeated throughout the codebase that could be consolidated for maintainability.
|
|
10
|
+
|
|
11
|
+
## Problem Statement
|
|
12
|
+
|
|
13
|
+
1. **FC/IS violation:** `scorer.ts` imports `node:path` and uses `path.relative()` in `rollupDirectoriesByDepth()`, violating its documented pure module contract
|
|
14
|
+
2. **Duplicate color logic:** Health/purity/quality color selection is repeated 6+ times in `report-console.ts`
|
|
15
|
+
3. **Duplicate empty-case handling:** `rollupDirectoriesByDepth()` manually constructs empty metrics instead of reusing `aggregateMetrics([])`
|
|
16
|
+
4. **Similar directory printing functions:** `printDirectoryBreakdown()` and `printRolledUpDirectories()` share nearly identical structure
|
|
17
|
+
|
|
18
|
+
## Success Criteria
|
|
19
|
+
|
|
20
|
+
1. `scorer.ts` has no `node:path` import — path relativization happens in the shell layer
|
|
21
|
+
2. A single `getMetricColor()` function replaces all repeated color logic
|
|
22
|
+
3. Empty directory metrics use `aggregateMetrics([])` for consistency
|
|
23
|
+
4. Directory printing functions are unified into a single parameterized function
|
|
24
|
+
5. All existing tests continue to pass
|
|
25
|
+
6. No regression in CLI behavior
|
|
26
|
+
|
|
27
|
+
## The Gap
|
|
28
|
+
|
|
29
|
+
| Aspect | Current State | Target State |
|
|
30
|
+
|--------|---------------|--------------|
|
|
31
|
+
| `scorer.ts` purity | Imports `node:path` | Pure module, no I/O imports |
|
|
32
|
+
| Color logic | Repeated 6+ times | Single reusable function |
|
|
33
|
+
| Empty metrics | Manual construction | Uses `aggregateMetrics([])` |
|
|
34
|
+
| Directory printing | Two similar functions | Single unified function |
|
|
35
|
+
|
|
36
|
+
## Phases and Tasks
|
|
37
|
+
|
|
38
|
+
### Phase 1: Extract Color Helper ✅
|
|
39
|
+
|
|
40
|
+
- Create `getMetricColor()` function in `report-console.ts` ✅
|
|
41
|
+
- Create `getMetricColorNullable()` for nullable values ✅
|
|
42
|
+
- Replace all repeated color logic with helper calls ✅
|
|
43
|
+
- Verify console output unchanged ✅
|
|
44
|
+
|
|
45
|
+
Function signatures:
|
|
46
|
+
```typescript
|
|
47
|
+
type ChalkFn = typeof chalk.green
|
|
48
|
+
|
|
49
|
+
function getMetricColor(value: number): ChalkFn
|
|
50
|
+
|
|
51
|
+
function getMetricColorNullable(value: number | null): ChalkFn
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Phase 2: Unify Directory Printing ✅
|
|
55
|
+
|
|
56
|
+
- Create unified `printDirectoryTable()` function ✅
|
|
57
|
+
- Define `DirectoryTableOptions` type for configuration ✅
|
|
58
|
+
- Refactor `printDirectoryBreakdown()` to use unified function ✅
|
|
59
|
+
- Refactor `printRolledUpDirectories()` to use unified function ✅
|
|
60
|
+
- Verify console output unchanged ✅
|
|
61
|
+
|
|
62
|
+
Type definition:
|
|
63
|
+
```typescript
|
|
64
|
+
type DirectoryTableOptions = {
|
|
65
|
+
title: string
|
|
66
|
+
includeQualityColumn: boolean
|
|
67
|
+
sortBy: 'health-asc' | 'alphabetical'
|
|
68
|
+
emptyMessage?: string
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Phase 3: Move Path Relativization to Shell ✅
|
|
73
|
+
|
|
74
|
+
- Remove `path` import from `scorer.ts` ✅
|
|
75
|
+
- Modify `rollupDirectoriesByDepth()` to accept pre-relativized paths ✅
|
|
76
|
+
- Create helper in `report-console.ts` to relativize paths before calling rollup ✅
|
|
77
|
+
- Update `report-json.ts` similarly ✅
|
|
78
|
+
- Update tests to use relative paths directly ✅
|
|
79
|
+
|
|
80
|
+
New function signature:
|
|
81
|
+
```typescript
|
|
82
|
+
// scorer.ts - now pure, expects relative paths in input
|
|
83
|
+
export function rollupDirectoriesByDepth(
|
|
84
|
+
directories: DirectoryScore[],
|
|
85
|
+
depth: number,
|
|
86
|
+
): DirectoryScore[]
|
|
87
|
+
|
|
88
|
+
// report-console.ts - shell helper
|
|
89
|
+
function relativizeDirectoryPaths(
|
|
90
|
+
directories: DirectoryScore[],
|
|
91
|
+
projectRoot: string,
|
|
92
|
+
): DirectoryScore[]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Phase 4: Use aggregateMetrics for Empty Case ✅
|
|
96
|
+
|
|
97
|
+
- Replace manual empty metrics construction in `rollupDirectoriesByDepth()` ✅
|
|
98
|
+
- Use `aggregateMetrics([])` spread with `dirPath` and `fileScores` overrides ✅
|
|
99
|
+
- Verify behavior unchanged with existing tests ✅
|
|
100
|
+
|
|
101
|
+
### Phase 5: Documentation ✅
|
|
102
|
+
|
|
103
|
+
- Update TECHNICAL.md to reflect refactored architecture ✅
|
|
104
|
+
- Verify `scorer.ts` header comment still accurate ✅
|
|
105
|
+
|
|
106
|
+
## Tests
|
|
107
|
+
|
|
108
|
+
### Existing Tests
|
|
109
|
+
|
|
110
|
+
All changes are refactoring — existing tests in `scorer.test.ts` should continue to pass without modification. The tests for `rollupDirectoriesByDepth()` may need minor updates to pass relative paths instead of absolute paths.
|
|
111
|
+
|
|
112
|
+
### New Tests
|
|
113
|
+
|
|
114
|
+
No new test files needed. Verify:
|
|
115
|
+
|
|
116
|
+
1. **Color helper:** Visual inspection of CLI output
|
|
117
|
+
2. **Unified printing:** Visual inspection of CLI output
|
|
118
|
+
3. **Path relativization:** Update existing `rollupDirectoriesByDepth` tests to use relative paths
|
|
119
|
+
|
|
120
|
+
### Test Modifications
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
// scorer.test.ts - update rollup tests to use relative paths
|
|
124
|
+
describe('rollupDirectoriesByDepth', () => {
|
|
125
|
+
it('should aggregate directories at depth 1', () => {
|
|
126
|
+
const dirs = [
|
|
127
|
+
// Before: createDirScoreForRollup('/project/src/services/auth', ...)
|
|
128
|
+
// After: paths are already relative
|
|
129
|
+
createDirScoreForRollup('src/services/auth', ...),
|
|
130
|
+
createDirScoreForRollup('src/services/users', ...),
|
|
131
|
+
createDirScoreForRollup('src/utils/format', ...),
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
// Before: rollupDirectoriesByDepth(dirs, 1, '/project')
|
|
135
|
+
// After: no projectRoot parameter
|
|
136
|
+
const rolled = rollupDirectoriesByDepth(dirs, 1)
|
|
137
|
+
// ... assertions unchanged
|
|
138
|
+
})
|
|
139
|
+
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Transitive Effect Analysis
|
|
143
|
+
|
|
144
|
+
### Phase 1 (Color Helper)
|
|
145
|
+
- `report-console.ts` → internal refactoring only
|
|
146
|
+
- No external API changes
|
|
147
|
+
- No transitive effects
|
|
148
|
+
|
|
149
|
+
### Phase 2 (Unified Printing)
|
|
150
|
+
- `report-console.ts` → internal refactoring only
|
|
151
|
+
- No external API changes
|
|
152
|
+
- No transitive effects
|
|
153
|
+
|
|
154
|
+
### Phase 3 (Path Relativization)
|
|
155
|
+
- `scorer.ts` → **API change**: `rollupDirectoriesByDepth()` signature changes
|
|
156
|
+
- `report-console.ts` → must call new helper before rollup
|
|
157
|
+
- `report-json.ts` → must call new helper before rollup
|
|
158
|
+
- `scorer.test.ts` → must update test data to use relative paths
|
|
159
|
+
- No external consumers affected (rollup is internal)
|
|
160
|
+
|
|
161
|
+
### Phase 4 (Empty Case)
|
|
162
|
+
- `scorer.ts` → internal refactoring
|
|
163
|
+
- No API changes
|
|
164
|
+
- No transitive effects
|
|
165
|
+
|
|
166
|
+
## Resources for Implementation
|
|
167
|
+
|
|
168
|
+
**Files to modify:**
|
|
169
|
+
- `fcis/src/scoring/scorer.ts` — Phase 3, Phase 4
|
|
170
|
+
- `fcis/src/reporting/report-console.ts` — Phase 1, Phase 2, Phase 3
|
|
171
|
+
- `fcis/src/reporting/report-json.ts` — Phase 3
|
|
172
|
+
- `fcis/tests/scorer.test.ts` — Phase 3
|
|
173
|
+
|
|
174
|
+
**Reference:**
|
|
175
|
+
- Current `rollupDirectoriesByDepth()` implementation
|
|
176
|
+
- Current `printDirectoryBreakdown()` and `printRolledUpDirectories()` implementations
|
|
177
|
+
- `aggregateMetrics()` empty case return value
|
|
178
|
+
|
|
179
|
+
## Documentation Updates
|
|
180
|
+
|
|
181
|
+
### TECHNICAL.md
|
|
182
|
+
|
|
183
|
+
Update the architecture diagram description to note:
|
|
184
|
+
- `scorer.ts` is a pure module with no I/O dependencies
|
|
185
|
+
- Path relativization happens in the shell layer before calling scoring functions
|
|
186
|
+
|
|
187
|
+
### scorer.ts Header
|
|
188
|
+
|
|
189
|
+
Verify the header comment still accurately describes the module:
|
|
190
|
+
```typescript
|
|
191
|
+
/**
|
|
192
|
+
* Scoring Engine - Pure Core
|
|
193
|
+
*
|
|
194
|
+
* Aggregates classification and quality scores into metrics at
|
|
195
|
+
* file, directory, and project levels. This is a PURE module -
|
|
196
|
+
* all functions take data in and return data out with no I/O.
|
|
197
|
+
*/
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Estimated Line Impact
|
|
201
|
+
|
|
202
|
+
- Phase 1 (Color Helper): ~-20 lines (net reduction)
|
|
203
|
+
- Phase 2 (Unified Printing): ~-40 lines (net reduction)
|
|
204
|
+
- Phase 3 (Path Relativization): ~+10 lines (moves code, slight increase)
|
|
205
|
+
- Phase 4 (Empty Case): ~-10 lines (net reduction)
|
|
206
|
+
- Phase 5 (Documentation): ~5 lines
|
|
207
|
+
|
|
208
|
+
**Total: ~-55 lines** — net simplification
|
|
209
|
+
|
|
210
|
+
This is well under the 2000 line PR limit.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Plan 006: Minor Refinements
|
|
2
|
+
|
|
3
|
+
## Background
|
|
4
|
+
|
|
5
|
+
Following the implementation of Plan 005 (Code Refinements), a code review identified three minor issues:
|
|
6
|
+
|
|
7
|
+
1. **Misleading `sortBy` option name:** The `DirectoryTableOptions.sortBy` type includes `'alphabetical'`, but the implementation actually preserves input order rather than sorting alphabetically.
|
|
8
|
+
|
|
9
|
+
2. **Inconsistent empty case handling:** `rollupDirectoriesByDepth()` now uses `aggregateMetrics([])` for empty groups, but `scoreDirectory()` and `scoreProject()` still manually construct empty metrics inline.
|
|
10
|
+
|
|
11
|
+
3. **Untested shell helper:** The `relativizeDirectoryPaths()` function in `report-console.ts` has no direct unit tests.
|
|
12
|
+
|
|
13
|
+
## Problem Statement
|
|
14
|
+
|
|
15
|
+
1. The `sortBy: 'alphabetical'` option is misleading — it implies active sorting but actually means "preserve input order"
|
|
16
|
+
2. Empty metric construction is inconsistent across scoring functions, increasing maintenance burden
|
|
17
|
+
3. `relativizeDirectoryPaths()` is only tested indirectly through CLI integration tests
|
|
18
|
+
|
|
19
|
+
## Success Criteria
|
|
20
|
+
|
|
21
|
+
1. `sortBy` option renamed to `'preserve-order'` with accurate documentation
|
|
22
|
+
2. `scoreDirectory()` and `scoreProject()` use `aggregateMetrics([])` for empty cases
|
|
23
|
+
3. `relativizeDirectoryPaths()` has focused unit tests
|
|
24
|
+
4. All existing tests continue to pass
|
|
25
|
+
|
|
26
|
+
## The Gap
|
|
27
|
+
|
|
28
|
+
| Aspect | Current State | Target State |
|
|
29
|
+
|--------|---------------|--------------|
|
|
30
|
+
| `sortBy` naming | `'alphabetical'` (misleading) | `'preserve-order'` (accurate) |
|
|
31
|
+
| Empty metrics in `scoreDirectory` | Manual construction | Uses `aggregateMetrics([])` |
|
|
32
|
+
| Empty metrics in `scoreProject` | Manual construction | Uses `aggregateMetrics([])` |
|
|
33
|
+
| `relativizeDirectoryPaths` tests | None | Focused unit tests |
|
|
34
|
+
|
|
35
|
+
## Phases and Tasks
|
|
36
|
+
|
|
37
|
+
### Phase 1: Rename sortBy Option ✅
|
|
38
|
+
|
|
39
|
+
- Rename `'alphabetical'` to `'preserve-order'` in `DirectoryTableOptions` type ✅
|
|
40
|
+
- Update call site in `printConsoleReport()` ✅
|
|
41
|
+
- Update comment to accurately describe behavior ✅
|
|
42
|
+
|
|
43
|
+
Type change:
|
|
44
|
+
```typescript
|
|
45
|
+
type DirectoryTableOptions = {
|
|
46
|
+
title: string
|
|
47
|
+
includeQualityColumn: boolean
|
|
48
|
+
sortBy: 'health-asc' | 'preserve-order' // was 'alphabetical'
|
|
49
|
+
emptyMessage?: string
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Phase 2: Unify Empty Metrics Construction ✅
|
|
54
|
+
|
|
55
|
+
- Refactor `scoreDirectory()` empty case to use `aggregateMetrics([])` ✅
|
|
56
|
+
- Refactor `scoreProject()` empty case to use `aggregateMetrics([])` ✅
|
|
57
|
+
- Verify existing tests still pass ✅
|
|
58
|
+
|
|
59
|
+
### Phase 3: Add relativizeDirectoryPaths Tests ✅
|
|
60
|
+
|
|
61
|
+
- Export `relativizeDirectoryPaths()` for testing (or test via module internals) ✅
|
|
62
|
+
- Add unit tests for path relativization ✅
|
|
63
|
+
- Test cross-platform path handling (backslash normalization) ✅
|
|
64
|
+
|
|
65
|
+
## Tests
|
|
66
|
+
|
|
67
|
+
### Phase 1 Tests
|
|
68
|
+
|
|
69
|
+
No new tests needed — existing tests verify behavior, only the name changes.
|
|
70
|
+
|
|
71
|
+
### Phase 2 Tests
|
|
72
|
+
|
|
73
|
+
Existing tests in `scorer.test.ts` cover empty cases. Verify they still pass.
|
|
74
|
+
|
|
75
|
+
### Phase 3 Tests
|
|
76
|
+
|
|
77
|
+
Add to a new or existing test file:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
describe('relativizeDirectoryPaths', () => {
|
|
81
|
+
it('should convert absolute paths to relative paths', () => {
|
|
82
|
+
const dirs = [
|
|
83
|
+
{ dirPath: '/project/src/services', ...otherProps },
|
|
84
|
+
{ dirPath: '/project/src/utils', ...otherProps },
|
|
85
|
+
]
|
|
86
|
+
const result = relativizeDirectoryPaths(dirs, '/project')
|
|
87
|
+
expect(result[0]?.dirPath).toBe('src/services')
|
|
88
|
+
expect(result[1]?.dirPath).toBe('src/utils')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('should normalize backslashes to forward slashes', () => {
|
|
92
|
+
const dirs = [{ dirPath: 'C:\\project\\src', ...otherProps }]
|
|
93
|
+
const result = relativizeDirectoryPaths(dirs, 'C:\\project')
|
|
94
|
+
expect(result[0]?.dirPath).toBe('src')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should preserve other directory properties', () => {
|
|
98
|
+
const dirs = [{ dirPath: '/project/src', pureCount: 5, impureCount: 3, ...otherProps }]
|
|
99
|
+
const result = relativizeDirectoryPaths(dirs, '/project')
|
|
100
|
+
expect(result[0]?.pureCount).toBe(5)
|
|
101
|
+
expect(result[0]?.impureCount).toBe(3)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Transitive Effect Analysis
|
|
107
|
+
|
|
108
|
+
### Phase 1 (sortBy Rename)
|
|
109
|
+
- `report-console.ts` → internal type and call site change
|
|
110
|
+
- No external API changes
|
|
111
|
+
- No transitive effects
|
|
112
|
+
|
|
113
|
+
### Phase 2 (Empty Metrics)
|
|
114
|
+
- `scorer.ts` → internal refactoring
|
|
115
|
+
- Behavior unchanged (same default values)
|
|
116
|
+
- Existing tests verify correct output
|
|
117
|
+
- No transitive effects
|
|
118
|
+
|
|
119
|
+
### Phase 3 (relativizeDirectoryPaths Tests)
|
|
120
|
+
- `report-console.ts` → may need to export function for testing
|
|
121
|
+
- Alternative: test via integration or keep as private with indirect coverage
|
|
122
|
+
- No production code changes needed if testing via integration
|
|
123
|
+
|
|
124
|
+
## Resources for Implementation
|
|
125
|
+
|
|
126
|
+
**Files to modify:**
|
|
127
|
+
- `fcis/src/reporting/report-console.ts` — Phase 1, Phase 3
|
|
128
|
+
- `fcis/src/scoring/scorer.ts` — Phase 2
|
|
129
|
+
|
|
130
|
+
**Files to add/update tests:**
|
|
131
|
+
- `fcis/tests/report-console.test.ts` (new) — Phase 3
|
|
132
|
+
- Or `fcis/tests/scorer.test.ts` — verify Phase 2
|
|
133
|
+
|
|
134
|
+
**Reference:**
|
|
135
|
+
- Current `DirectoryTableOptions` type definition
|
|
136
|
+
- Current `scoreDirectory()` and `scoreProject()` empty case handling
|
|
137
|
+
- Current `relativizeDirectoryPaths()` implementation
|
|
138
|
+
|
|
139
|
+
## Documentation Updates
|
|
140
|
+
|
|
141
|
+
No documentation updates needed — these are internal implementation details not exposed in README or TECHNICAL.md.
|
|
142
|
+
|
|
143
|
+
## Estimated Line Impact
|
|
144
|
+
|
|
145
|
+
- Phase 1 (sortBy Rename): ~5 lines changed
|
|
146
|
+
- Phase 2 (Empty Metrics): ~20 lines changed (net reduction)
|
|
147
|
+
- Phase 3 (Tests): ~30 lines added
|
|
148
|
+
|
|
149
|
+
**Total: ~55 lines** — well under the 2000 line PR limit.
|