x-fidelity 3.4.0 → 3.6.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/.xfi-config.json +1 -1
- package/CHANGELOG.md +26 -0
- package/README.md +18 -1
- package/dist/core/configManager.js +13 -5
- package/dist/demoConfig/node-fullstack.json +11 -3
- package/dist/demoConfig/rules/apiUsageConsistency-global-rule.json +37 -0
- package/dist/demoConfig/rules/factDoesNotAddResultToAlmanac-iterative-rule.json +33 -0
- package/dist/facts/globalFileAnalysisFacts.d.ts +2 -0
- package/dist/facts/globalFileAnalysisFacts.js +108 -0
- package/dist/facts/globalFileAnalysisFacts.test.d.ts +1 -0
- package/dist/facts/globalFileAnalysisFacts.test.js +69 -0
- package/dist/facts/index.js +5 -0
- package/dist/facts/repoFilesystemFacts.js +1 -0
- package/dist/operators/globalPatternCount.d.ts +3 -0
- package/dist/operators/globalPatternCount.js +39 -0
- package/dist/operators/globalPatternCount.test.d.ts +1 -0
- package/dist/operators/globalPatternCount.test.js +45 -0
- package/dist/operators/globalPatternRatio.d.ts +3 -0
- package/dist/operators/globalPatternRatio.js +56 -0
- package/dist/operators/globalPatternRatio.test.d.ts +1 -0
- package/dist/operators/globalPatternRatio.test.js +52 -0
- package/dist/operators/index.js +5 -1
- package/dist/types/typeDefs.d.ts +1 -0
- package/dist/utils/jsonSchemas.js +1 -0
- package/package.json +1 -1
- package/src/core/configManager.ts +15 -5
- package/src/demoConfig/node-fullstack.json +11 -3
- package/src/demoConfig/rules/apiUsageConsistency-global-rule.json +37 -0
- package/src/demoConfig/rules/factDoesNotAddResultToAlmanac-iterative-rule.json +33 -0
- package/src/facts/globalFileAnalysisFacts.test.ts +67 -0
- package/src/facts/globalFileAnalysisFacts.ts +116 -0
- package/src/facts/index.ts +6 -0
- package/src/facts/repoFilesystemFacts.ts +1 -0
- package/src/operators/globalPatternCount.test.ts +51 -0
- package/src/operators/globalPatternCount.ts +44 -0
- package/src/operators/globalPatternRatio.test.ts +59 -0
- package/src/operators/globalPatternRatio.ts +66 -0
- package/src/operators/index.ts +4 -0
- package/src/types/typeDefs.ts +1 -0
- package/src/utils/jsonSchemas.ts +1 -0
- package/website/docs/archetypes.md +5 -0
- package/website/docs/ci-cd/overview.md +12 -0
- package/website/docs/getting-started.md +4 -0
- package/website/docs/key-concepts.md +1 -0
- package/website/docs/plugins/overview.md +25 -4
- package/dist/demoConfig/rules/regexMatch-example-iterative-rule.json +0 -28
- package/src/demoConfig/rules/regexMatch-example-iterative-rule.json +0 -28
package/.xfi-config.json
CHANGED
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
# [3.6.0](https://github.com/zotoio/x-fidelity/compare/v3.5.0...v3.6.0) (2025-02-27)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* add type annotations to fix TypeScript errors in globalFileAnalysis ([588c814](https://github.com/zotoio/x-fidelity/commit/588c8140a348a4959e62c027bdadbd625a11dc51))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* add global file analysis with pattern matching and ratio operators ([ab456df](https://github.com/zotoio/x-fidelity/commit/ab456df857efa82fd4f394f474b0b6f85b1fc91e))
|
|
12
|
+
* add support for new and legacy pattern analysis ([6702ea0](https://github.com/zotoio/x-fidelity/commit/6702ea081cff3fbc2abceed35e4aee073ff02b56))
|
|
13
|
+
* **targeting:** facts and operators to support rules applied to specific codebase matches ([231cb9a](https://github.com/zotoio/x-fidelity/commit/231cb9adedac8dc18666058925af835b68fe60e3))
|
|
14
|
+
|
|
15
|
+
# [3.5.0](https://github.com/zotoio/x-fidelity/compare/v3.4.0...v3.5.0) (2025-02-27)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
* **regexmatch:** give better examples of iterative and negative matches ([3a4f211](https://github.com/zotoio/x-fidelity/commit/3a4f21157119a0677b29ebc211716526d8a762d0))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Features
|
|
24
|
+
|
|
25
|
+
* add plugin support to archetype configuration ([9e0a02b](https://github.com/zotoio/x-fidelity/commit/9e0a02be24fb812ca056157a0c3f1c7b8699b7f2))
|
|
26
|
+
|
|
1
27
|
# [3.4.0](https://github.com/zotoio/x-fidelity/compare/v3.3.1...v3.4.0) (2025-02-26)
|
|
2
28
|
|
|
3
29
|
|
package/README.md
CHANGED
|
@@ -179,6 +179,7 @@ x-fidelity is designed to be highly extensible, and only has demo rules and arch
|
|
|
179
179
|
2. **Custom Rules**: Add new JSON rule files in the `rules` subdirectory of your local config or on your config server.
|
|
180
180
|
3. **Custom Operators**: TODO: Implement new operators and add them to your x-fidelity fork or plugin.
|
|
181
181
|
4. **Custom Facts**: TODO: Create new fact providers and add them to your x-fidelity fork or plugin.
|
|
182
|
+
5. **Plugins**: Specify plugins in your archetype configuration or via the CLI to extend functionality.
|
|
182
183
|
|
|
183
184
|
> At minimum you should configure your archetypes and rules using the provided facts and operators. Facts and Operators are more complex and are not yet easily added without forking, and those provided are intended to be flexible enough for general codebase analysis.
|
|
184
185
|
|
|
@@ -188,6 +189,7 @@ Archetypes represent a pattern or template of git repo that you expect to be con
|
|
|
188
189
|
- **rules**: names of rules you have defined to perform either global (repo wide) checks, or checks on each file in a repo.
|
|
189
190
|
- **facts**: data prepared before analysing a repo such as dependency data and file structures.
|
|
190
191
|
- **operators**: these are custom operators, or provided operators that compare facts and return a boolean result. In x-fi boolean true means a rule failure.
|
|
192
|
+
- **plugins**: names of plugins to load when using this archetype. These extend the functionality with custom facts and operators.
|
|
191
193
|
- **config**: core configuration related to codebase analysis:
|
|
192
194
|
- minimumDependencyVersions: packages you want to conform to provided semver ranges.
|
|
193
195
|
- standardStructure: expected filesystem directories at high level
|
|
@@ -201,6 +203,7 @@ Example of a custom archetype JSON file (`my-custom-archetype.json`):
|
|
|
201
203
|
"rules": ["myCustomRule-global", "standardRule1-iterative", "standardRule2-iterative"],
|
|
202
204
|
"operators": ["myCustomOperator", "standardOperator1"],
|
|
203
205
|
"facts": ["myCustomFact", "standardFact1"],
|
|
206
|
+
"plugins": ["xfiPluginRequiredFiles", "myCustomPlugin"],
|
|
204
207
|
"config": {
|
|
205
208
|
"minimumDependencyVersions": {
|
|
206
209
|
"my-framework": ">2.0.0"
|
|
@@ -836,7 +839,10 @@ New extensions are available:
|
|
|
836
839
|
- *Remote String Validation Plugin:* (module: `xfiPluginRemoteStringValidator`) adds remote validation functionality via the `remoteSubstringValidation` fact and the `invalidRemoteValidation` operator.
|
|
837
840
|
- *Sample Custom Plugin:* (module: `xfiPluginSimpleExample`) shows how to add custom facts and operators.
|
|
838
841
|
|
|
839
|
-
|
|
842
|
+
There are two ways to load plugins:
|
|
843
|
+
|
|
844
|
+
1. **Via CLI option**: Pass plugin module names via the `-e` (or `--extensions`) CLI option.
|
|
845
|
+
2. **Via archetype configuration**: Specify plugins in the `plugins` array of your archetype JSON file.
|
|
840
846
|
|
|
841
847
|
x-fidelity supports custom extensions through npm modules. To use extensions:
|
|
842
848
|
|
|
@@ -852,6 +858,17 @@ xfidelity -e xfi-basic-plugin xfi-another-plugin
|
|
|
852
858
|
|
|
853
859
|
Multiple extensions can be specified by separating them with spaces.
|
|
854
860
|
|
|
861
|
+
Alternatively, specify plugins in your archetype configuration:
|
|
862
|
+
```json
|
|
863
|
+
{
|
|
864
|
+
"name": "my-archetype",
|
|
865
|
+
"plugins": ["xfi-basic-plugin", "xfi-another-plugin"],
|
|
866
|
+
// other archetype properties...
|
|
867
|
+
}
|
|
868
|
+
```
|
|
869
|
+
|
|
870
|
+
Plugins specified in both the CLI and the archetype will be loaded, with CLI-specified plugins loaded first.
|
|
871
|
+
|
|
855
872
|
### Creating Extensions
|
|
856
873
|
|
|
857
874
|
You can create custom extensions by implementing the XFiPlugin interface:
|
|
@@ -139,11 +139,6 @@ class ConfigManager {
|
|
|
139
139
|
const localConfigPath = cli_1.options.localConfigPath;
|
|
140
140
|
if (logPrefix)
|
|
141
141
|
(0, logger_1.setLogPrefix)(logPrefix);
|
|
142
|
-
// Load plugins during initialization
|
|
143
|
-
yield this.loadPlugins(cli_1.options.extensions).catch(error => {
|
|
144
|
-
logger_1.logger.error(error, `Error loading plugins`);
|
|
145
|
-
throw error;
|
|
146
|
-
});
|
|
147
142
|
logger_1.logger.info(`Initializing config manager for archetype: ${archetype}`);
|
|
148
143
|
logger_1.logger.debug(`Initialize params: ${JSON.stringify(params)}`);
|
|
149
144
|
const config = {
|
|
@@ -162,6 +157,19 @@ class ConfigManager {
|
|
|
162
157
|
if (!config.archetype || Object.keys(config.archetype).length === 0) {
|
|
163
158
|
throw new Error(`No valid configuration found for archetype: ${archetype}`);
|
|
164
159
|
}
|
|
160
|
+
// Load CLI-specified plugins first
|
|
161
|
+
yield this.loadPlugins(cli_1.options.extensions).catch(error => {
|
|
162
|
+
logger_1.logger.error(error, `Error loading CLI-specified plugins`);
|
|
163
|
+
throw error;
|
|
164
|
+
});
|
|
165
|
+
// Load archetype-specified plugins
|
|
166
|
+
if (config.archetype.plugins && config.archetype.plugins.length > 0) {
|
|
167
|
+
logger_1.logger.info(`Loading plugins specified by archetype: ${config.archetype.plugins.join(', ')}`);
|
|
168
|
+
yield this.loadPlugins(config.archetype.plugins).catch(error => {
|
|
169
|
+
logger_1.logger.error(error, `Error loading archetype-specified plugins`);
|
|
170
|
+
throw error;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
165
173
|
// Load all RuleConfig for the archetype
|
|
166
174
|
config.rules = yield (0, ruleUtils_1.loadRules)({
|
|
167
175
|
archetype,
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"openaiAnalysisA11y-global",
|
|
10
10
|
"invalidSystemIdConfigured-iterative",
|
|
11
11
|
"missingRequiredFiles-global",
|
|
12
|
-
"
|
|
12
|
+
"factDoesNotAddResultToAlmanac-iterative",
|
|
13
|
+
"apiUsageConsistency-global"
|
|
13
14
|
],
|
|
14
15
|
"operators": [
|
|
15
16
|
"fileContains",
|
|
@@ -18,14 +19,21 @@
|
|
|
18
19
|
"openaiAnalysisHighSeverity",
|
|
19
20
|
"invalidRemoteValidation",
|
|
20
21
|
"missingRequiredFiles",
|
|
21
|
-
"regexMatch"
|
|
22
|
+
"regexMatch",
|
|
23
|
+
"globalPatternRatio",
|
|
24
|
+
"globalPatternCount"
|
|
22
25
|
],
|
|
23
26
|
"facts": [
|
|
24
27
|
"repoFilesystemFacts",
|
|
25
28
|
"repoDependencyFacts",
|
|
26
29
|
"openaiAnalysisFacts",
|
|
27
30
|
"remoteSubstringValidation",
|
|
28
|
-
"missingRequiredFiles"
|
|
31
|
+
"missingRequiredFiles",
|
|
32
|
+
"globalFileAnalysisFacts"
|
|
33
|
+
],
|
|
34
|
+
"plugins": [
|
|
35
|
+
"xfiPluginRequiredFiles",
|
|
36
|
+
"xfiPluginRemoteStringValidator"
|
|
29
37
|
],
|
|
30
38
|
"config": {
|
|
31
39
|
"minimumDependencyVersions": {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apiUsageConsistency-global",
|
|
3
|
+
"conditions": {
|
|
4
|
+
"all": [
|
|
5
|
+
{
|
|
6
|
+
"fact": "fileData",
|
|
7
|
+
"path": "$.fileName",
|
|
8
|
+
"operator": "equal",
|
|
9
|
+
"value": "REPO_GLOBAL_CHECK"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"fact": "globalFileAnalysis",
|
|
13
|
+
"params": {
|
|
14
|
+
"newPatterns": [
|
|
15
|
+
"logger\\.info\\("
|
|
16
|
+
],
|
|
17
|
+
"legacyPatterns": [
|
|
18
|
+
"logger\\.debug\\("
|
|
19
|
+
],
|
|
20
|
+
"fileFilter": ".*\\.(ts|js)$",
|
|
21
|
+
"resultFact": "apiUsageAnalysis"
|
|
22
|
+
},
|
|
23
|
+
"operator": "globalPatternRatio",
|
|
24
|
+
"value": 0.3
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"event": {
|
|
29
|
+
"type": "warning",
|
|
30
|
+
"params": {
|
|
31
|
+
"message": "The codebase is not consistently using the new API methods. At least 80% of API calls should use the new methods.",
|
|
32
|
+
"details": {
|
|
33
|
+
"fact": "apiUsageAnalysis"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "factDoesNotAddResultToAlmanac-iterative",
|
|
3
|
+
"conditions": {
|
|
4
|
+
"all": [
|
|
5
|
+
{
|
|
6
|
+
"fact": "fileData",
|
|
7
|
+
"path": "$.filePath",
|
|
8
|
+
"operator": "regexMatch",
|
|
9
|
+
"value": "^.*\\/facts\\/(?!.*\\.test).*\\.ts$"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"not": {
|
|
13
|
+
"fact": "repoFileAnalysis",
|
|
14
|
+
"params": {
|
|
15
|
+
"checkPattern": [
|
|
16
|
+
"almanac\\.addRuntimeFact\\(params\\.resultFact"
|
|
17
|
+
],
|
|
18
|
+
"resultFact": "factAlmanacResults"
|
|
19
|
+
},
|
|
20
|
+
"operator": "fileContains",
|
|
21
|
+
"value": true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
"event": {
|
|
27
|
+
"type": "warning",
|
|
28
|
+
"params": {
|
|
29
|
+
"message": "xfi facts should always add results to the almanac so that details can be reported.",
|
|
30
|
+
"details": "eg. almanac.addRuntimeFact(params.resultFact, result)"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.globalFileAnalysis = void 0;
|
|
13
|
+
const logger_1 = require("../utils/logger");
|
|
14
|
+
const maskSensitiveData_1 = require("../utils/maskSensitiveData");
|
|
15
|
+
exports.globalFileAnalysis = {
|
|
16
|
+
name: 'globalFileAnalysis',
|
|
17
|
+
fn: (params, almanac) => __awaiter(void 0, void 0, void 0, function* () {
|
|
18
|
+
const result = { result: [], matchCounts: {}, fileMatches: {} };
|
|
19
|
+
try {
|
|
20
|
+
// Get all file data from the almanac
|
|
21
|
+
const globalFileMetadata = yield almanac.factValue('globalFileMetadata');
|
|
22
|
+
if (!Array.isArray(globalFileMetadata)) {
|
|
23
|
+
logger_1.logger.error('Invalid globalFileMetadata');
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
// Extract parameters
|
|
27
|
+
const newPatterns = Array.isArray(params.newPatterns) ? params.newPatterns : (params.newPatterns ? [params.newPatterns] : []);
|
|
28
|
+
const legacyPatterns = Array.isArray(params.legacyPatterns) ? params.legacyPatterns : (params.legacyPatterns ? [params.legacyPatterns] : []);
|
|
29
|
+
// For backward compatibility
|
|
30
|
+
const patterns = Array.isArray(params.patterns) ? params.patterns : (params.patterns ? [params.patterns] : []);
|
|
31
|
+
const fileFilter = params.fileFilter || '.*';
|
|
32
|
+
const fileFilterRegex = new RegExp(fileFilter);
|
|
33
|
+
// Combine all patterns for processing
|
|
34
|
+
const allPatterns = [...newPatterns, ...legacyPatterns, ...patterns];
|
|
35
|
+
logger_1.logger.info(`Running global file analysis with ${newPatterns.length} new patterns, ${legacyPatterns.length} legacy patterns, and ${patterns.length} regular patterns across ${globalFileMetadata.length} files`);
|
|
36
|
+
// Filter files based on fileFilter parameter
|
|
37
|
+
const filteredFiles = globalFileMetadata.filter(file => file.fileName !== 'REPO_GLOBAL_CHECK' && fileFilterRegex.test(file.filePath));
|
|
38
|
+
logger_1.logger.info(`Analyzing ${filteredFiles.length} files after filtering`);
|
|
39
|
+
// Initialize match counts for each pattern
|
|
40
|
+
allPatterns.forEach((pattern) => {
|
|
41
|
+
result.matchCounts[pattern] = 0;
|
|
42
|
+
result.fileMatches[pattern] = [];
|
|
43
|
+
});
|
|
44
|
+
// Process each file
|
|
45
|
+
for (const file of filteredFiles) {
|
|
46
|
+
const fileContent = file.fileContent;
|
|
47
|
+
if (!fileContent)
|
|
48
|
+
continue;
|
|
49
|
+
const lines = fileContent.split('\n');
|
|
50
|
+
// Check each pattern against the file
|
|
51
|
+
for (const pattern of allPatterns) {
|
|
52
|
+
const regex = new RegExp(pattern, 'g');
|
|
53
|
+
let fileMatches = 0;
|
|
54
|
+
const matchDetails = [];
|
|
55
|
+
// Check each line for matches
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
const line = lines[i];
|
|
58
|
+
let match;
|
|
59
|
+
while ((match = regex.exec(line)) !== null) {
|
|
60
|
+
fileMatches++;
|
|
61
|
+
matchDetails.push({
|
|
62
|
+
lineNumber: i + 1,
|
|
63
|
+
match: match[0],
|
|
64
|
+
context: (0, maskSensitiveData_1.maskSensitiveData)(line.trim())
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Update results if matches found
|
|
69
|
+
if (fileMatches > 0) {
|
|
70
|
+
result.matchCounts[pattern] += fileMatches;
|
|
71
|
+
result.fileMatches[pattern].push({
|
|
72
|
+
filePath: file.filePath,
|
|
73
|
+
matchCount: fileMatches,
|
|
74
|
+
matches: matchDetails
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Calculate totals for new and legacy patterns
|
|
80
|
+
const newPatternsTotal = newPatterns.reduce((sum, pattern) => sum + (result.matchCounts[pattern] || 0), 0);
|
|
81
|
+
const legacyPatternsTotal = legacyPatterns.reduce((sum, pattern) => sum + (result.matchCounts[pattern] || 0), 0);
|
|
82
|
+
// Add summary to result
|
|
83
|
+
result.summary = {
|
|
84
|
+
totalFiles: filteredFiles.length,
|
|
85
|
+
totalMatches: Object.values(result.matchCounts).reduce((sum, count) => sum + count, 0),
|
|
86
|
+
patternCounts: result.matchCounts,
|
|
87
|
+
newPatternCounts: newPatterns.reduce((counts, pattern) => {
|
|
88
|
+
counts[pattern] = result.matchCounts[pattern] || 0;
|
|
89
|
+
return counts;
|
|
90
|
+
}, {}),
|
|
91
|
+
legacyPatternCounts: legacyPatterns.reduce((counts, pattern) => {
|
|
92
|
+
counts[pattern] = result.matchCounts[pattern] || 0;
|
|
93
|
+
return counts;
|
|
94
|
+
}, {}),
|
|
95
|
+
newPatternsTotal: newPatternsTotal,
|
|
96
|
+
legacyPatternsTotal: legacyPatternsTotal
|
|
97
|
+
};
|
|
98
|
+
// Add the result to the almanac
|
|
99
|
+
almanac.addRuntimeFact(params.resultFact, result);
|
|
100
|
+
logger_1.logger.info(`Global file analysis complete: ${JSON.stringify(result.summary)}`);
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logger_1.logger.error(`Error in globalFileAnalysis: ${error}`);
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const globalFileAnalysisFacts_1 = require("./globalFileAnalysisFacts");
|
|
13
|
+
const logger_1 = require("../utils/logger");
|
|
14
|
+
jest.mock('../utils/logger', () => ({
|
|
15
|
+
logger: {
|
|
16
|
+
debug: jest.fn(),
|
|
17
|
+
error: jest.fn(),
|
|
18
|
+
info: jest.fn(),
|
|
19
|
+
warn: jest.fn(),
|
|
20
|
+
},
|
|
21
|
+
}));
|
|
22
|
+
describe('globalFileAnalysis', () => {
|
|
23
|
+
it('should analyze patterns across multiple files', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
24
|
+
const mockAlmanac = {
|
|
25
|
+
factValue: jest.fn().mockResolvedValue([
|
|
26
|
+
{
|
|
27
|
+
fileName: 'file1.ts',
|
|
28
|
+
filePath: '/path/to/file1.ts',
|
|
29
|
+
fileContent: 'function test() { newApiMethod(); legacyApiMethod(); }'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
fileName: 'file2.ts',
|
|
33
|
+
filePath: '/path/to/file2.ts',
|
|
34
|
+
fileContent: 'function test2() { newApiMethod(); newApiMethod(); }'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
fileName: 'REPO_GLOBAL_CHECK',
|
|
38
|
+
filePath: 'REPO_GLOBAL_CHECK',
|
|
39
|
+
fileContent: 'REPO_GLOBAL_CHECK'
|
|
40
|
+
}
|
|
41
|
+
]),
|
|
42
|
+
addRuntimeFact: jest.fn(),
|
|
43
|
+
};
|
|
44
|
+
const params = {
|
|
45
|
+
patterns: ['newApiMethod\\(', 'legacyApiMethod\\('],
|
|
46
|
+
fileFilter: '\\.ts$',
|
|
47
|
+
resultFact: 'apiUsageAnalysis'
|
|
48
|
+
};
|
|
49
|
+
const result = yield globalFileAnalysisFacts_1.globalFileAnalysis.fn(params, mockAlmanac);
|
|
50
|
+
expect(result.summary.totalFiles).toBe(2);
|
|
51
|
+
expect(result.matchCounts['newApiMethod\\(']).toBe(3);
|
|
52
|
+
expect(result.matchCounts['legacyApiMethod\\(']).toBe(1);
|
|
53
|
+
expect(mockAlmanac.addRuntimeFact).toHaveBeenCalledWith('apiUsageAnalysis', expect.any(Object));
|
|
54
|
+
}));
|
|
55
|
+
it('should handle errors gracefully', () => __awaiter(void 0, void 0, void 0, function* () {
|
|
56
|
+
const mockAlmanac = {
|
|
57
|
+
factValue: jest.fn().mockRejectedValue(new Error('Test error')),
|
|
58
|
+
addRuntimeFact: jest.fn(),
|
|
59
|
+
};
|
|
60
|
+
const params = {
|
|
61
|
+
patterns: ['newApiMethod\\('],
|
|
62
|
+
fileFilter: '\\.ts$',
|
|
63
|
+
resultFact: 'apiUsageAnalysis'
|
|
64
|
+
};
|
|
65
|
+
const result = yield globalFileAnalysisFacts_1.globalFileAnalysis.fn(params, mockAlmanac);
|
|
66
|
+
expect(result.result).toEqual([]);
|
|
67
|
+
expect(logger_1.logger.error).toHaveBeenCalled();
|
|
68
|
+
}));
|
|
69
|
+
});
|
package/dist/facts/index.js
CHANGED
|
@@ -13,6 +13,7 @@ exports.loadFacts = loadFacts;
|
|
|
13
13
|
const repoFilesystemFacts_1 = require("./repoFilesystemFacts");
|
|
14
14
|
const repoDependencyFacts_1 = require("./repoDependencyFacts");
|
|
15
15
|
const openaiAnalysisFacts_1 = require("./openaiAnalysisFacts");
|
|
16
|
+
const globalFileAnalysisFacts_1 = require("./globalFileAnalysisFacts");
|
|
16
17
|
const pluginRegistry_1 = require("../core/pluginRegistry");
|
|
17
18
|
const openaiUtils_1 = require("../utils/openaiUtils");
|
|
18
19
|
const logger_1 = require("../utils/logger");
|
|
@@ -29,6 +30,10 @@ function loadFacts(factNames) {
|
|
|
29
30
|
}, openaiAnalysisFacts: {
|
|
30
31
|
name: 'openaiAnalysis',
|
|
31
32
|
fn: openaiAnalysisFacts_1.openaiAnalysis
|
|
33
|
+
}, globalFileAnalysisFacts: {
|
|
34
|
+
name: 'globalFileAnalysis',
|
|
35
|
+
fn: globalFileAnalysisFacts_1.globalFileAnalysis.fn,
|
|
36
|
+
priority: 50 // Set priority to run after globalFileMetadata
|
|
32
37
|
} }, Object.fromEntries(pluginRegistry_1.pluginRegistry.getPluginFacts().map(fact => [fact.name, fact])));
|
|
33
38
|
logger_1.logger.info(`Loading facts: ${factNames.join(', ')}`);
|
|
34
39
|
const pluginFacts = pluginRegistry_1.pluginRegistry.getPluginFacts();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.globalPatternCount = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const globalPatternCount = {
|
|
6
|
+
'name': 'globalPatternCount',
|
|
7
|
+
'fn': (analysisResult, threshold) => {
|
|
8
|
+
try {
|
|
9
|
+
logger_1.logger.debug(`globalPatternCount: processing ${JSON.stringify(analysisResult)}`);
|
|
10
|
+
if (!analysisResult || !analysisResult.summary) {
|
|
11
|
+
logger_1.logger.debug('globalPatternCount: no analysis result available');
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// Check if we have new pattern totals
|
|
15
|
+
if (analysisResult.summary.newPatternsTotal !== undefined) {
|
|
16
|
+
const newTotal = analysisResult.summary.newPatternsTotal;
|
|
17
|
+
logger_1.logger.info(`globalPatternCount: new patterns total: ${newTotal}, threshold: ${threshold}`);
|
|
18
|
+
// Compare count with threshold
|
|
19
|
+
return newTotal >= threshold;
|
|
20
|
+
}
|
|
21
|
+
// Fallback to original behavior for backward compatibility
|
|
22
|
+
const patterns = Object.keys(analysisResult.matchCounts);
|
|
23
|
+
if (patterns.length < 1) {
|
|
24
|
+
logger_1.logger.debug('globalPatternCount: no patterns found in analysis');
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// Get count for the first pattern
|
|
28
|
+
const patternCount = analysisResult.matchCounts[patterns[0]] || 0;
|
|
29
|
+
logger_1.logger.info(`globalPatternCount: ${patternCount}, threshold: ${threshold}`);
|
|
30
|
+
// Compare count with threshold
|
|
31
|
+
return patternCount >= threshold;
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
logger_1.logger.error(`globalPatternCount error: ${e}`);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
exports.globalPatternCount = globalPatternCount;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globalPatternCount_1 = require("./globalPatternCount");
|
|
4
|
+
jest.mock('../utils/logger', () => ({
|
|
5
|
+
logger: {
|
|
6
|
+
debug: jest.fn(),
|
|
7
|
+
error: jest.fn(),
|
|
8
|
+
info: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
describe('globalPatternCount', () => {
|
|
12
|
+
it('should return true when count exceeds threshold', () => {
|
|
13
|
+
const analysisResult = {
|
|
14
|
+
matchCounts: {
|
|
15
|
+
'pattern1': 10
|
|
16
|
+
},
|
|
17
|
+
summary: {
|
|
18
|
+
totalFiles: 5,
|
|
19
|
+
totalMatches: 10
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
expect(globalPatternCount_1.globalPatternCount.fn(analysisResult, 5)).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
it('should return false when count is below threshold', () => {
|
|
25
|
+
const analysisResult = {
|
|
26
|
+
matchCounts: {
|
|
27
|
+
'pattern1': 3
|
|
28
|
+
},
|
|
29
|
+
summary: {
|
|
30
|
+
totalFiles: 5,
|
|
31
|
+
totalMatches: 3
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
expect(globalPatternCount_1.globalPatternCount.fn(analysisResult, 5)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it('should handle edge cases', () => {
|
|
37
|
+
// No patterns
|
|
38
|
+
expect(globalPatternCount_1.globalPatternCount.fn({ matchCounts: {}, summary: {} }, 1)).toBe(false);
|
|
39
|
+
// No matches
|
|
40
|
+
expect(globalPatternCount_1.globalPatternCount.fn({
|
|
41
|
+
matchCounts: { 'pattern1': 0 },
|
|
42
|
+
summary: { totalMatches: 0 }
|
|
43
|
+
}, 1)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.globalPatternRatio = void 0;
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const globalPatternRatio = {
|
|
6
|
+
'name': 'globalPatternRatio',
|
|
7
|
+
'fn': (analysisResult, threshold) => {
|
|
8
|
+
try {
|
|
9
|
+
logger_1.logger.debug(`globalPatternRatio: processing ${JSON.stringify(analysisResult)}`);
|
|
10
|
+
if (!analysisResult || !analysisResult.summary) {
|
|
11
|
+
logger_1.logger.debug('globalPatternRatio: no analysis result available');
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// Check if we have new and legacy pattern totals
|
|
15
|
+
if (analysisResult.summary.newPatternsTotal !== undefined &&
|
|
16
|
+
analysisResult.summary.legacyPatternsTotal !== undefined) {
|
|
17
|
+
const newTotal = analysisResult.summary.newPatternsTotal;
|
|
18
|
+
const legacyTotal = analysisResult.summary.legacyPatternsTotal;
|
|
19
|
+
const total = newTotal + legacyTotal;
|
|
20
|
+
// Avoid division by zero
|
|
21
|
+
if (total === 0) {
|
|
22
|
+
logger_1.logger.debug('globalPatternRatio: no pattern matches found');
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
// Calculate ratio of new patterns to total patterns
|
|
26
|
+
const ratio = newTotal / total;
|
|
27
|
+
logger_1.logger.info(`globalPatternRatio: ${newTotal}/(${newTotal}+${legacyTotal}) = ${ratio}, threshold: ${threshold}`);
|
|
28
|
+
// Compare ratio with threshold
|
|
29
|
+
return ratio >= threshold;
|
|
30
|
+
}
|
|
31
|
+
// Fallback to original behavior for backward compatibility
|
|
32
|
+
const patterns = Object.keys(analysisResult.matchCounts);
|
|
33
|
+
if (patterns.length < 2) {
|
|
34
|
+
logger_1.logger.debug('globalPatternRatio: need at least 2 patterns to calculate ratio');
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Calculate ratio between first two patterns
|
|
38
|
+
const pattern1Count = analysisResult.matchCounts[patterns[0]] || 0;
|
|
39
|
+
const pattern2Count = analysisResult.matchCounts[patterns[1]] || 0;
|
|
40
|
+
// Avoid division by zero
|
|
41
|
+
if (pattern2Count === 0) {
|
|
42
|
+
logger_1.logger.debug('globalPatternRatio: denominator pattern has zero matches');
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const ratio = pattern1Count / pattern2Count;
|
|
46
|
+
logger_1.logger.info(`globalPatternRatio: ${pattern1Count}/${pattern2Count} = ${ratio}, threshold: ${threshold}`);
|
|
47
|
+
// Compare ratio with threshold
|
|
48
|
+
return ratio >= threshold;
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
logger_1.logger.error(`globalPatternRatio error: ${e}`);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
exports.globalPatternRatio = globalPatternRatio;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const globalPatternRatio_1 = require("./globalPatternRatio");
|
|
4
|
+
jest.mock('../utils/logger', () => ({
|
|
5
|
+
logger: {
|
|
6
|
+
debug: jest.fn(),
|
|
7
|
+
error: jest.fn(),
|
|
8
|
+
info: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
describe('globalPatternRatio', () => {
|
|
12
|
+
it('should return true when ratio exceeds threshold', () => {
|
|
13
|
+
const analysisResult = {
|
|
14
|
+
matchCounts: {
|
|
15
|
+
'newApiMethod\\(': 8,
|
|
16
|
+
'legacyApiMethod\\(': 2
|
|
17
|
+
},
|
|
18
|
+
summary: {
|
|
19
|
+
totalFiles: 5,
|
|
20
|
+
totalMatches: 10
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
expect(globalPatternRatio_1.globalPatternRatio.fn(analysisResult, 3)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
it('should return false when ratio is below threshold', () => {
|
|
26
|
+
const analysisResult = {
|
|
27
|
+
matchCounts: {
|
|
28
|
+
'newApiMethod\\(': 4,
|
|
29
|
+
'legacyApiMethod\\(': 6
|
|
30
|
+
},
|
|
31
|
+
summary: {
|
|
32
|
+
totalFiles: 5,
|
|
33
|
+
totalMatches: 10
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
expect(globalPatternRatio_1.globalPatternRatio.fn(analysisResult, 1)).toBe(false);
|
|
37
|
+
});
|
|
38
|
+
it('should handle edge cases', () => {
|
|
39
|
+
// No patterns
|
|
40
|
+
expect(globalPatternRatio_1.globalPatternRatio.fn({ matchCounts: {}, summary: {} }, 1)).toBe(false);
|
|
41
|
+
// Only one pattern
|
|
42
|
+
expect(globalPatternRatio_1.globalPatternRatio.fn({
|
|
43
|
+
matchCounts: { 'pattern1': 5 },
|
|
44
|
+
summary: { totalMatches: 5 }
|
|
45
|
+
}, 1)).toBe(false);
|
|
46
|
+
// Denominator is zero
|
|
47
|
+
expect(globalPatternRatio_1.globalPatternRatio.fn({
|
|
48
|
+
matchCounts: { 'pattern1': 5, 'pattern2': 0 },
|
|
49
|
+
summary: { totalMatches: 5 }
|
|
50
|
+
}, 1)).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
});
|