@ash-technologia/fathom 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 (236) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/LICENSE +21 -0
  3. package/README.md +725 -0
  4. package/dist/analyzers/architecture/graph.d.ts +29 -0
  5. package/dist/analyzers/architecture/graph.d.ts.map +1 -0
  6. package/dist/analyzers/architecture/graph.js +347 -0
  7. package/dist/analyzers/architecture/graph.js.map +1 -0
  8. package/dist/analyzers/architecture/imports.d.ts +24 -0
  9. package/dist/analyzers/architecture/imports.d.ts.map +1 -0
  10. package/dist/analyzers/architecture/imports.js +155 -0
  11. package/dist/analyzers/architecture/imports.js.map +1 -0
  12. package/dist/analyzers/architecture/index.d.ts +13 -0
  13. package/dist/analyzers/architecture/index.d.ts.map +1 -0
  14. package/dist/analyzers/architecture/index.js +217 -0
  15. package/dist/analyzers/architecture/index.js.map +1 -0
  16. package/dist/analyzers/architecture/types.d.ts +65 -0
  17. package/dist/analyzers/architecture/types.d.ts.map +1 -0
  18. package/dist/analyzers/architecture/types.js +2 -0
  19. package/dist/analyzers/architecture/types.js.map +1 -0
  20. package/dist/analyzers/cicd/index.d.ts +11 -0
  21. package/dist/analyzers/cicd/index.d.ts.map +1 -0
  22. package/dist/analyzers/cicd/index.js +157 -0
  23. package/dist/analyzers/cicd/index.js.map +1 -0
  24. package/dist/analyzers/dependencies/index.d.ts +11 -0
  25. package/dist/analyzers/dependencies/index.d.ts.map +1 -0
  26. package/dist/analyzers/dependencies/index.js +403 -0
  27. package/dist/analyzers/dependencies/index.js.map +1 -0
  28. package/dist/analyzers/dependencies/lockfiles.d.ts +19 -0
  29. package/dist/analyzers/dependencies/lockfiles.d.ts.map +1 -0
  30. package/dist/analyzers/dependencies/lockfiles.js +172 -0
  31. package/dist/analyzers/dependencies/lockfiles.js.map +1 -0
  32. package/dist/analyzers/dependencies/online.d.ts +19 -0
  33. package/dist/analyzers/dependencies/online.d.ts.map +1 -0
  34. package/dist/analyzers/dependencies/online.js +116 -0
  35. package/dist/analyzers/dependencies/online.js.map +1 -0
  36. package/dist/analyzers/dependencies/types.d.ts +49 -0
  37. package/dist/analyzers/dependencies/types.d.ts.map +1 -0
  38. package/dist/analyzers/dependencies/types.js +2 -0
  39. package/dist/analyzers/dependencies/types.js.map +1 -0
  40. package/dist/analyzers/documentation/index.d.ts +11 -0
  41. package/dist/analyzers/documentation/index.d.ts.map +1 -0
  42. package/dist/analyzers/documentation/index.js +166 -0
  43. package/dist/analyzers/documentation/index.js.map +1 -0
  44. package/dist/analyzers/git/index.d.ts +11 -0
  45. package/dist/analyzers/git/index.d.ts.map +1 -0
  46. package/dist/analyzers/git/index.js +180 -0
  47. package/dist/analyzers/git/index.js.map +1 -0
  48. package/dist/analyzers/project/index.d.ts +11 -0
  49. package/dist/analyzers/project/index.d.ts.map +1 -0
  50. package/dist/analyzers/project/index.js +95 -0
  51. package/dist/analyzers/project/index.js.map +1 -0
  52. package/dist/analyzers/quality/index.d.ts +11 -0
  53. package/dist/analyzers/quality/index.d.ts.map +1 -0
  54. package/dist/analyzers/quality/index.js +198 -0
  55. package/dist/analyzers/quality/index.js.map +1 -0
  56. package/dist/analyzers/security/index.d.ts +11 -0
  57. package/dist/analyzers/security/index.d.ts.map +1 -0
  58. package/dist/analyzers/security/index.js +236 -0
  59. package/dist/analyzers/security/index.js.map +1 -0
  60. package/dist/analyzers/testing/index.d.ts +11 -0
  61. package/dist/analyzers/testing/index.d.ts.map +1 -0
  62. package/dist/analyzers/testing/index.js +134 -0
  63. package/dist/analyzers/testing/index.js.map +1 -0
  64. package/dist/baseline/baseline.d.ts +20 -0
  65. package/dist/baseline/baseline.d.ts.map +1 -0
  66. package/dist/baseline/baseline.js +134 -0
  67. package/dist/baseline/baseline.js.map +1 -0
  68. package/dist/baseline/compare.d.ts +14 -0
  69. package/dist/baseline/compare.d.ts.map +1 -0
  70. package/dist/baseline/compare.js +113 -0
  71. package/dist/baseline/compare.js.map +1 -0
  72. package/dist/baseline/types.d.ts +25 -0
  73. package/dist/baseline/types.d.ts.map +1 -0
  74. package/dist/baseline/types.js +2 -0
  75. package/dist/baseline/types.js.map +1 -0
  76. package/dist/cli/commands.d.ts +39 -0
  77. package/dist/cli/commands.d.ts.map +1 -0
  78. package/dist/cli/commands.js +277 -0
  79. package/dist/cli/commands.js.map +1 -0
  80. package/dist/cli/index.d.ts +3 -0
  81. package/dist/cli/index.d.ts.map +1 -0
  82. package/dist/cli/index.js +29 -0
  83. package/dist/cli/index.js.map +1 -0
  84. package/dist/cli/options.d.ts +55 -0
  85. package/dist/cli/options.d.ts.map +1 -0
  86. package/dist/cli/options.js +258 -0
  87. package/dist/cli/options.js.map +1 -0
  88. package/dist/core/analyzer.d.ts +33 -0
  89. package/dist/core/analyzer.d.ts.map +1 -0
  90. package/dist/core/analyzer.js +19 -0
  91. package/dist/core/analyzer.js.map +1 -0
  92. package/dist/core/context.d.ts +90 -0
  93. package/dist/core/context.d.ts.map +1 -0
  94. package/dist/core/context.js +159 -0
  95. package/dist/core/context.js.map +1 -0
  96. package/dist/core/errors.d.ts +74 -0
  97. package/dist/core/errors.d.ts.map +1 -0
  98. package/dist/core/errors.js +109 -0
  99. package/dist/core/errors.js.map +1 -0
  100. package/dist/core/findings.d.ts +59 -0
  101. package/dist/core/findings.d.ts.map +1 -0
  102. package/dist/core/findings.js +34 -0
  103. package/dist/core/findings.js.map +1 -0
  104. package/dist/core/orchestrator.d.ts +44 -0
  105. package/dist/core/orchestrator.d.ts.map +1 -0
  106. package/dist/core/orchestrator.js +160 -0
  107. package/dist/core/orchestrator.js.map +1 -0
  108. package/dist/core/result.d.ts +169 -0
  109. package/dist/core/result.d.ts.map +1 -0
  110. package/dist/core/result.js +2 -0
  111. package/dist/core/result.js.map +1 -0
  112. package/dist/detectors/framework.d.ts +22 -0
  113. package/dist/detectors/framework.d.ts.map +1 -0
  114. package/dist/detectors/framework.js +147 -0
  115. package/dist/detectors/framework.js.map +1 -0
  116. package/dist/detectors/language.d.ts +13 -0
  117. package/dist/detectors/language.d.ts.map +1 -0
  118. package/dist/detectors/language.js +100 -0
  119. package/dist/detectors/language.js.map +1 -0
  120. package/dist/detectors/package-manager.d.ts +17 -0
  121. package/dist/detectors/package-manager.d.ts.map +1 -0
  122. package/dist/detectors/package-manager.js +67 -0
  123. package/dist/detectors/package-manager.js.map +1 -0
  124. package/dist/detectors/project-type.d.ts +20 -0
  125. package/dist/detectors/project-type.d.ts.map +1 -0
  126. package/dist/detectors/project-type.js +48 -0
  127. package/dist/detectors/project-type.js.map +1 -0
  128. package/dist/diff/analyzer.d.ts +16 -0
  129. package/dist/diff/analyzer.d.ts.map +1 -0
  130. package/dist/diff/analyzer.js +296 -0
  131. package/dist/diff/analyzer.js.map +1 -0
  132. package/dist/diff/git-diff.d.ts +44 -0
  133. package/dist/diff/git-diff.d.ts.map +1 -0
  134. package/dist/diff/git-diff.js +256 -0
  135. package/dist/diff/git-diff.js.map +1 -0
  136. package/dist/diff/types.d.ts +75 -0
  137. package/dist/diff/types.d.ts.map +1 -0
  138. package/dist/diff/types.js +2 -0
  139. package/dist/diff/types.js.map +1 -0
  140. package/dist/integrations/github/context.d.ts +22 -0
  141. package/dist/integrations/github/context.d.ts.map +1 -0
  142. package/dist/integrations/github/context.js +115 -0
  143. package/dist/integrations/github/context.js.map +1 -0
  144. package/dist/integrations/github/types.d.ts +30 -0
  145. package/dist/integrations/github/types.d.ts.map +1 -0
  146. package/dist/integrations/github/types.js +2 -0
  147. package/dist/integrations/github/types.js.map +1 -0
  148. package/dist/plugins/context.d.ts +13 -0
  149. package/dist/plugins/context.d.ts.map +1 -0
  150. package/dist/plugins/context.js +102 -0
  151. package/dist/plugins/context.js.map +1 -0
  152. package/dist/plugins/examples/react.d.ts +8 -0
  153. package/dist/plugins/examples/react.d.ts.map +1 -0
  154. package/dist/plugins/examples/react.js +129 -0
  155. package/dist/plugins/examples/react.js.map +1 -0
  156. package/dist/plugins/index.d.ts +5 -0
  157. package/dist/plugins/index.d.ts.map +1 -0
  158. package/dist/plugins/index.js +4 -0
  159. package/dist/plugins/index.js.map +1 -0
  160. package/dist/plugins/registry.d.ts +45 -0
  161. package/dist/plugins/registry.d.ts.map +1 -0
  162. package/dist/plugins/registry.js +236 -0
  163. package/dist/plugins/registry.js.map +1 -0
  164. package/dist/plugins/types.d.ts +148 -0
  165. package/dist/plugins/types.d.ts.map +1 -0
  166. package/dist/plugins/types.js +2 -0
  167. package/dist/plugins/types.js.map +1 -0
  168. package/dist/reporters/html.d.ts +11 -0
  169. package/dist/reporters/html.d.ts.map +1 -0
  170. package/dist/reporters/html.js +1352 -0
  171. package/dist/reporters/html.js.map +1 -0
  172. package/dist/reporters/json.d.ts +10 -0
  173. package/dist/reporters/json.d.ts.map +1 -0
  174. package/dist/reporters/json.js +11 -0
  175. package/dist/reporters/json.js.map +1 -0
  176. package/dist/reporters/markdown.d.ts +7 -0
  177. package/dist/reporters/markdown.d.ts.map +1 -0
  178. package/dist/reporters/markdown.js +74 -0
  179. package/dist/reporters/markdown.js.map +1 -0
  180. package/dist/reporters/sarif.d.ts +133 -0
  181. package/dist/reporters/sarif.d.ts.map +1 -0
  182. package/dist/reporters/sarif.js +268 -0
  183. package/dist/reporters/sarif.js.map +1 -0
  184. package/dist/reporters/terminal.d.ts +17 -0
  185. package/dist/reporters/terminal.d.ts.map +1 -0
  186. package/dist/reporters/terminal.js +423 -0
  187. package/dist/reporters/terminal.js.map +1 -0
  188. package/dist/rules/categories.d.ts +8 -0
  189. package/dist/rules/categories.d.ts.map +1 -0
  190. package/dist/rules/categories.js +23 -0
  191. package/dist/rules/categories.js.map +1 -0
  192. package/dist/rules/definitions.d.ts +25 -0
  193. package/dist/rules/definitions.d.ts.map +1 -0
  194. package/dist/rules/definitions.js +429 -0
  195. package/dist/rules/definitions.js.map +1 -0
  196. package/dist/rules/registry.d.ts +36 -0
  197. package/dist/rules/registry.d.ts.map +1 -0
  198. package/dist/rules/registry.js +64 -0
  199. package/dist/rules/registry.js.map +1 -0
  200. package/dist/rules/severity.d.ts +13 -0
  201. package/dist/rules/severity.d.ts.map +1 -0
  202. package/dist/rules/severity.js +15 -0
  203. package/dist/rules/severity.js.map +1 -0
  204. package/dist/scoring/confidence.d.ts +12 -0
  205. package/dist/scoring/confidence.d.ts.map +1 -0
  206. package/dist/scoring/confidence.js +22 -0
  207. package/dist/scoring/confidence.js.map +1 -0
  208. package/dist/scoring/score.d.ts +27 -0
  209. package/dist/scoring/score.d.ts.map +1 -0
  210. package/dist/scoring/score.js +81 -0
  211. package/dist/scoring/score.js.map +1 -0
  212. package/dist/scoring/weights.d.ts +11 -0
  213. package/dist/scoring/weights.d.ts.map +1 -0
  214. package/dist/scoring/weights.js +26 -0
  215. package/dist/scoring/weights.js.map +1 -0
  216. package/dist/utils/filesystem.d.ts +65 -0
  217. package/dist/utils/filesystem.d.ts.map +1 -0
  218. package/dist/utils/filesystem.js +299 -0
  219. package/dist/utils/filesystem.js.map +1 -0
  220. package/dist/utils/git.d.ts +47 -0
  221. package/dist/utils/git.d.ts.map +1 -0
  222. package/dist/utils/git.js +125 -0
  223. package/dist/utils/git.js.map +1 -0
  224. package/dist/utils/logger.d.ts +14 -0
  225. package/dist/utils/logger.d.ts.map +1 -0
  226. package/dist/utils/logger.js +25 -0
  227. package/dist/utils/logger.js.map +1 -0
  228. package/dist/utils/paths.d.ts +30 -0
  229. package/dist/utils/paths.d.ts.map +1 -0
  230. package/dist/utils/paths.js +48 -0
  231. package/dist/utils/paths.js.map +1 -0
  232. package/dist/utils/timing.d.ts +12 -0
  233. package/dist/utils/timing.d.ts.map +1 -0
  234. package/dist/utils/timing.js +19 -0
  235. package/dist/utils/timing.js.map +1 -0
  236. package/package.json +73 -0
@@ -0,0 +1,134 @@
1
+ import { createFindingId } from '../../core/findings.js';
2
+ import { createTimer } from '../../utils/timing.js';
3
+ const TEST_DIR_NAMES = new Set([
4
+ 'test',
5
+ 'tests',
6
+ '__tests__',
7
+ 'spec',
8
+ 'specs',
9
+ 'e2e',
10
+ 'integration',
11
+ 'unit',
12
+ 'test-utils',
13
+ 'testing',
14
+ ]);
15
+ /**
16
+ * Common test script names in package.json scripts.
17
+ */
18
+ const TEST_SCRIPT_NAMES = [
19
+ 'test',
20
+ 'test:unit',
21
+ 'test:integration',
22
+ 'test:e2e',
23
+ 'vitest',
24
+ 'jest',
25
+ 'mocha',
26
+ 'jasmine',
27
+ ];
28
+ export class TestingAnalyzer {
29
+ id = 'testing';
30
+ name = 'Testing';
31
+ category = 'testing';
32
+ description = 'Checks for test infrastructure: directories, test files, and test scripts.';
33
+ async analyze(context) {
34
+ const elapsed = createTimer();
35
+ const findings = [];
36
+ const warnings = [];
37
+ const testFiles = context.files.filter((f) => f.isTest);
38
+ const sourceFiles = context.files.filter((f) => f.isSource && !f.isTest);
39
+ // TEST-001: Test directories
40
+ const allDirs = new Set();
41
+ for (const file of context.files) {
42
+ const parts = file.relativePath.split('/');
43
+ for (let i = 1; i < parts.length; i++) {
44
+ allDirs.add(parts[i - 1] ?? '');
45
+ }
46
+ }
47
+ const testDirs = [...allDirs].filter((d) => TEST_DIR_NAMES.has(d.toLowerCase()));
48
+ if (testDirs.length === 0 && context.files.filter((f) => f.isSource).length > 0) {
49
+ findings.push({
50
+ id: createFindingId('TEST-001'),
51
+ ruleId: 'TEST-001',
52
+ category: 'testing',
53
+ severity: 'low',
54
+ title: 'No standard test directory found',
55
+ description: 'No directories matching common test naming conventions (test, tests, __tests__, spec, etc.) were detected.',
56
+ recommendation: 'Create a test directory and add tests for your source code.',
57
+ confidence: 0.8,
58
+ autoFixable: false,
59
+ });
60
+ }
61
+ // TEST-002: Test files
62
+ if (testFiles.length === 0) {
63
+ findings.push({
64
+ id: createFindingId('TEST-002'),
65
+ ruleId: 'TEST-002',
66
+ category: 'testing',
67
+ severity: 'medium',
68
+ title: 'No test files detected',
69
+ description: 'No files matching common test naming conventions were found.',
70
+ recommendation: 'Add tests for your source code to improve reliability.',
71
+ confidence: 0.85,
72
+ autoFixable: false,
73
+ });
74
+ }
75
+ // TEST-003: Test scripts in package.json
76
+ let hasTestScript = false;
77
+ if (context.manifests.packageJson?.scripts) {
78
+ const scripts = context.manifests.packageJson.scripts;
79
+ hasTestScript = TEST_SCRIPT_NAMES.some((name) => name in scripts);
80
+ if (!hasTestScript) {
81
+ findings.push({
82
+ id: createFindingId('TEST-003'),
83
+ ruleId: 'TEST-003',
84
+ category: 'testing',
85
+ severity: 'medium',
86
+ title: 'No test script in package.json',
87
+ description: 'A "test" script makes it easy to run tests consistently.',
88
+ recommendation: 'Add a "test" script to package.json.',
89
+ confidence: 0.9,
90
+ location: { file: 'package.json' },
91
+ autoFixable: false,
92
+ });
93
+ }
94
+ }
95
+ // TEST-004: Test-to-source ratio heuristic
96
+ const ratio = sourceFiles.length > 0 ? testFiles.length / sourceFiles.length : 0;
97
+ let ratioSeverity = 'info';
98
+ if (ratio === 0 && sourceFiles.length > 5)
99
+ ratioSeverity = 'medium';
100
+ else if (ratio < 0.1 && sourceFiles.length > 10)
101
+ ratioSeverity = 'low';
102
+ if (ratio < 0.1 && sourceFiles.length > 5) {
103
+ findings.push({
104
+ id: createFindingId('TEST-004', 'ratio'),
105
+ ruleId: 'TEST-004',
106
+ category: 'testing',
107
+ severity: ratioSeverity,
108
+ title: 'Low test-to-source file ratio',
109
+ description: `Test files represent only ${(ratio * 100).toFixed(0)}% of source files (${testFiles.length} tests vs ${sourceFiles.length} source files).`,
110
+ recommendation: 'Increase test coverage for critical application logic.',
111
+ confidence: 0.7,
112
+ autoFixable: false,
113
+ });
114
+ }
115
+ const metrics = {
116
+ testFiles: testFiles.length,
117
+ sourceFiles: sourceFiles.length,
118
+ testDirs: testDirs,
119
+ hasTestScript,
120
+ testToSourceRatio: +ratio.toFixed(2),
121
+ };
122
+ return {
123
+ analyzerId: this.id,
124
+ analyzerName: this.name,
125
+ category: this.category,
126
+ status: 'success',
127
+ findings,
128
+ metrics,
129
+ durationMs: elapsed(),
130
+ warnings,
131
+ };
132
+ }
133
+ }
134
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/analyzers/testing/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { AnalysisResult } from '../core/result.js';
2
+ import type { BaselineData } from './types.js';
3
+ export declare const CURRENT_BASELINE_SCHEMA_VERSION = "1.0";
4
+ export declare const BASELINE_DIR_NAME = ".fathom";
5
+ export declare const BASELINE_FILE_NAME = "baseline.json";
6
+ /**
7
+ * Get the absolute path to the baseline file for a given repository root.
8
+ */
9
+ export declare function getBaselinePath(repositoryRoot: string): string;
10
+ /**
11
+ * Save an AnalysisResult as a deterministic baseline file in .fathom/baseline.json.
12
+ */
13
+ export declare function saveBaseline(repositoryRoot: string, result: AnalysisResult): Promise<string>;
14
+ /**
15
+ * Load and validate .fathom/baseline.json from a repository root.
16
+ * Throws FathomBaselineMissingError if missing.
17
+ * Throws FathomBaselineCorruptError if malformed or invalid schema.
18
+ */
19
+ export declare function loadBaseline(repositoryRoot: string): Promise<BaselineData>;
20
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/baseline/baseline.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG/C,eAAO,MAAM,+BAA+B,QAAQ,CAAC;AACrD,eAAO,MAAM,iBAAiB,YAAY,CAAC;AAC3C,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD;;GAEG;AACH,wBAAgB,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAE9D;AAqCD;;GAEG;AACH,wBAAsB,YAAY,CAChC,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,MAAM,CAAC,CA4CjB;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAmDhF"}
@@ -0,0 +1,134 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { FathomBaselineMissingError, FathomBaselineCorruptError } from '../core/errors.js';
4
+ export const CURRENT_BASELINE_SCHEMA_VERSION = '1.0';
5
+ export const BASELINE_DIR_NAME = '.fathom';
6
+ export const BASELINE_FILE_NAME = 'baseline.json';
7
+ /**
8
+ * Get the absolute path to the baseline file for a given repository root.
9
+ */
10
+ export function getBaselinePath(repositoryRoot) {
11
+ return path.resolve(repositoryRoot, BASELINE_DIR_NAME, BASELINE_FILE_NAME);
12
+ }
13
+ /**
14
+ * Sanitize a finding for baseline storage.
15
+ * Ensures evidence never contains secrets and repository code is never stored.
16
+ */
17
+ function sanitizeFindingForBaseline(f) {
18
+ const finding = {
19
+ id: f.id,
20
+ ruleId: f.ruleId,
21
+ category: f.category,
22
+ severity: f.severity,
23
+ title: f.title,
24
+ description: f.description,
25
+ recommendation: f.recommendation,
26
+ confidence: f.confidence,
27
+ autoFixable: f.autoFixable,
28
+ };
29
+ if (f.location) {
30
+ finding.location = {};
31
+ if (f.location.file !== undefined)
32
+ finding.location.file = f.location.file;
33
+ if (f.location.line !== undefined)
34
+ finding.location.line = f.location.line;
35
+ if (f.location.column !== undefined)
36
+ finding.location.column = f.location.column;
37
+ }
38
+ if (f.evidence) {
39
+ finding.evidence = f.evidence.slice(0, 200);
40
+ }
41
+ if (f.references) {
42
+ finding.references = [...f.references];
43
+ }
44
+ return finding;
45
+ }
46
+ /**
47
+ * Save an AnalysisResult as a deterministic baseline file in .fathom/baseline.json.
48
+ */
49
+ export async function saveBaseline(repositoryRoot, result) {
50
+ const baselinePath = getBaselinePath(repositoryRoot);
51
+ const baselineDir = path.dirname(baselinePath);
52
+ // Ensure .fathom directory exists
53
+ await fs.mkdir(baselineDir, { recursive: true });
54
+ // Map analyzer metrics
55
+ const metrics = {};
56
+ for (const analyzer of result.analyzers) {
57
+ metrics[analyzer.analyzerId] = analyzer.metrics;
58
+ }
59
+ const baselineData = {
60
+ schemaVersion: CURRENT_BASELINE_SCHEMA_VERSION,
61
+ fathomVersion: result.fathomVersion,
62
+ createdAt: result.timestamp,
63
+ repositoryPath: path.basename(result.repositoryPath),
64
+ score: {
65
+ overall: result.score.overall,
66
+ band: result.score.band,
67
+ categories: result.score.categories.map((c) => ({
68
+ category: c.category,
69
+ score: c.score,
70
+ maxScore: c.maxScore,
71
+ weight: c.weight,
72
+ findingCount: c.findingCount,
73
+ })),
74
+ },
75
+ // Sort findings stably by ruleId and file for deterministic output
76
+ findings: result.findings.map(sanitizeFindingForBaseline).sort((a, b) => {
77
+ const cmpRule = a.ruleId.localeCompare(b.ruleId);
78
+ if (cmpRule !== 0)
79
+ return cmpRule;
80
+ const fileA = a.location?.file ?? '';
81
+ const fileB = b.location?.file ?? '';
82
+ return fileA.localeCompare(fileB);
83
+ }),
84
+ metrics,
85
+ };
86
+ const json = JSON.stringify(baselineData, null, 2);
87
+ await fs.writeFile(baselinePath, json + '\n', 'utf8');
88
+ return baselinePath;
89
+ }
90
+ /**
91
+ * Load and validate .fathom/baseline.json from a repository root.
92
+ * Throws FathomBaselineMissingError if missing.
93
+ * Throws FathomBaselineCorruptError if malformed or invalid schema.
94
+ */
95
+ export async function loadBaseline(repositoryRoot) {
96
+ const baselinePath = getBaselinePath(repositoryRoot);
97
+ let rawContent;
98
+ try {
99
+ rawContent = await fs.readFile(baselinePath, 'utf8');
100
+ }
101
+ catch (err) {
102
+ const error = err;
103
+ if (error?.code === 'ENOENT') {
104
+ throw new FathomBaselineMissingError(baselinePath);
105
+ }
106
+ throw new FathomBaselineCorruptError(baselinePath, `Cannot read baseline file (${error?.message ?? String(err)})`);
107
+ }
108
+ let parsed;
109
+ try {
110
+ parsed = JSON.parse(rawContent);
111
+ }
112
+ catch {
113
+ throw new FathomBaselineCorruptError(baselinePath, 'File contains invalid JSON syntax');
114
+ }
115
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
116
+ throw new FathomBaselineCorruptError(baselinePath, 'Expected a root JSON object');
117
+ }
118
+ const data = parsed;
119
+ // Validate required schema fields
120
+ if (!data.schemaVersion || typeof data.schemaVersion !== 'string') {
121
+ throw new FathomBaselineCorruptError(baselinePath, 'Missing or invalid "schemaVersion" field');
122
+ }
123
+ if (!data.score || typeof data.score !== 'object' || typeof data.score.overall !== 'number') {
124
+ throw new FathomBaselineCorruptError(baselinePath, 'Missing or invalid "score" object');
125
+ }
126
+ if (!Array.isArray(data.score.categories)) {
127
+ throw new FathomBaselineCorruptError(baselinePath, 'Missing or invalid "score.categories" array');
128
+ }
129
+ if (!Array.isArray(data.findings)) {
130
+ throw new FathomBaselineCorruptError(baselinePath, 'Missing or invalid "findings" array');
131
+ }
132
+ return data;
133
+ }
134
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/baseline/baseline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { AnalysisResult } from '../core/result.js';
2
+ import type { Finding } from '../core/findings.js';
3
+ import type { BaselineData, ComparisonResult } from './types.js';
4
+ /**
5
+ * Generate a stable identity key for a finding.
6
+ * Uses ruleId, normalized file path, and title.
7
+ * Excludes line numbers so that line shifts in source files do not produce false diffs.
8
+ */
9
+ export declare function getStableFindingKey(finding: Finding): string;
10
+ /**
11
+ * Compare a current AnalysisResult against a loaded BaselineData.
12
+ */
13
+ export declare function compareWithBaseline(currentResult: AnalysisResult, baseline: BaselineData): ComparisonResult;
14
+ //# sourceMappingURL=compare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.d.ts","sourceRoot":"","sources":["../../src/baseline/compare.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,YAAY,EAAqB,gBAAgB,EAAiB,MAAM,YAAY,CAAC;AAEnG;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAK5D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,cAAc,EAC7B,QAAQ,EAAE,YAAY,GACrB,gBAAgB,CAqHlB"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Generate a stable identity key for a finding.
3
+ * Uses ruleId, normalized file path, and title.
4
+ * Excludes line numbers so that line shifts in source files do not produce false diffs.
5
+ */
6
+ export function getStableFindingKey(finding) {
7
+ const fileKey = finding.location?.file
8
+ ? finding.location.file.replace(/\\/g, '/').toLowerCase()
9
+ : '__nofile__';
10
+ return `${finding.ruleId}:${fileKey}:${finding.title}`;
11
+ }
12
+ /**
13
+ * Compare a current AnalysisResult against a loaded BaselineData.
14
+ */
15
+ export function compareWithBaseline(currentResult, baseline) {
16
+ const currentScore = currentResult.score.overall;
17
+ const baselineScore = baseline.score.overall;
18
+ const scoreDelta = currentScore - baselineScore;
19
+ // 1. Compare category scores
20
+ const categoryDiffs = [];
21
+ const baselineCategoryMap = new Map(baseline.score.categories.map((c) => [c.category, c.score]));
22
+ for (const currentCat of currentResult.score.categories) {
23
+ const baseCatScore = baselineCategoryMap.get(currentCat.category) ?? 100;
24
+ categoryDiffs.push({
25
+ category: currentCat.category,
26
+ baselineScore: baseCatScore,
27
+ currentScore: currentCat.score,
28
+ delta: currentCat.score - baseCatScore,
29
+ });
30
+ }
31
+ // 2. Stable 1-to-1 Finding Diffing
32
+ // Group by stable key
33
+ const baselineBuckets = new Map();
34
+ for (const finding of baseline.findings) {
35
+ const key = getStableFindingKey(finding);
36
+ const bucket = baselineBuckets.get(key) ?? [];
37
+ bucket.push(finding);
38
+ baselineBuckets.set(key, bucket);
39
+ }
40
+ const currentBuckets = new Map();
41
+ for (const finding of currentResult.findings) {
42
+ const key = getStableFindingKey(finding);
43
+ const bucket = currentBuckets.get(key) ?? [];
44
+ bucket.push(finding);
45
+ currentBuckets.set(key, bucket);
46
+ }
47
+ const newFindings = [];
48
+ const resolvedFindings = [];
49
+ const unchangedFindings = [];
50
+ const changedFindings = [];
51
+ // Union of all unique keys
52
+ const allKeys = new Set([...baselineBuckets.keys(), ...currentBuckets.keys()]);
53
+ for (const key of allKeys) {
54
+ const baseList = baselineBuckets.get(key) ?? [];
55
+ const currList = currentBuckets.get(key) ?? [];
56
+ const commonCount = Math.min(baseList.length, currList.length);
57
+ // Compare paired findings
58
+ for (let i = 0; i < commonCount; i++) {
59
+ const baseFinding = baseList[i];
60
+ const currFinding = currList[i];
61
+ if (!baseFinding || !currFinding)
62
+ continue;
63
+ const severityChanged = currFinding.severity !== baseFinding.severity;
64
+ const confidenceChanged = Math.abs(currFinding.confidence - baseFinding.confidence) > 0.05;
65
+ if (severityChanged || confidenceChanged) {
66
+ changedFindings.push({
67
+ current: currFinding,
68
+ baseline: baseFinding,
69
+ severityChanged,
70
+ confidenceChanged,
71
+ });
72
+ }
73
+ else {
74
+ unchangedFindings.push(currFinding);
75
+ }
76
+ }
77
+ // Surplus in current -> newly introduced
78
+ if (currList.length > commonCount) {
79
+ newFindings.push(...currList.slice(commonCount));
80
+ }
81
+ // Surplus in baseline -> resolved
82
+ if (baseList.length > commonCount) {
83
+ resolvedFindings.push(...baseList.slice(commonCount));
84
+ }
85
+ }
86
+ // 3. Determine if regression occurred
87
+ // Regression is flagged if:
88
+ // - Overall score decreased
89
+ // - Any individual category score decreased
90
+ // - Newly introduced critical or high severity finding
91
+ // - Existing finding escalated to critical or high severity
92
+ const scoreDecreased = scoreDelta < 0;
93
+ const anyCategoryDecreased = categoryDiffs.some((d) => d.delta < 0);
94
+ const newCriticalOrHigh = newFindings.some((f) => f.severity === 'critical' || f.severity === 'high');
95
+ const escalatedToCriticalOrHigh = changedFindings.some((cf) => (cf.current.severity === 'critical' || cf.current.severity === 'high') &&
96
+ cf.baseline.severity !== 'critical' &&
97
+ cf.baseline.severity !== 'high');
98
+ const isRegression = scoreDecreased || anyCategoryDecreased || newCriticalOrHigh || escalatedToCriticalOrHigh;
99
+ return {
100
+ baselineTimestamp: baseline.createdAt,
101
+ baselineVersion: baseline.fathomVersion,
102
+ baselineScore,
103
+ currentScore,
104
+ scoreDelta,
105
+ isRegression,
106
+ categoryDiffs,
107
+ newFindings,
108
+ resolvedFindings,
109
+ unchangedFindings,
110
+ changedFindings,
111
+ };
112
+ }
113
+ //# sourceMappingURL=compare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare.js","sourceRoot":"","sources":["../../src/baseline/compare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { Finding } from '../core/findings.js';
2
+ import type { HealthScore, Metrics, CategoryScoreDiff, FindingChange, ComparisonResult } from '../core/result.js';
3
+ export type { CategoryScoreDiff, FindingChange, ComparisonResult };
4
+ /**
5
+ * Persisted format for .fathom/baseline.json.
6
+ * Versioned for future compatibility.
7
+ * Contains only metadata, findings, scores, and metrics — NEVER raw source code or secrets.
8
+ */
9
+ export interface BaselineData {
10
+ /** Baseline schema version (currently "1.0") */
11
+ schemaVersion: string;
12
+ /** Fathom CLI version that generated the baseline */
13
+ fathomVersion: string;
14
+ /** ISO timestamp of when the baseline was created */
15
+ createdAt: string;
16
+ /** Relative or resolved repository path */
17
+ repositoryPath: string;
18
+ /** Overall health score and category breakdown */
19
+ score: HealthScore;
20
+ /** Sanitized findings from the baseline analysis */
21
+ findings: Finding[];
22
+ /** Metrics map from all analyzers */
23
+ metrics: Record<string, Metrics>;
24
+ }
25
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/baseline/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EACV,WAAW,EACX,OAAO,EACP,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EACjB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC;AAEnE;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,gDAAgD;IAChD,aAAa,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,qDAAqD;IACrD,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,KAAK,EAAE,WAAW,CAAC;IACnB,oDAAoD;IACpD,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,qCAAqC;IACrC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/baseline/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ export interface AnalyzeOptions {
2
+ json?: boolean;
3
+ sarif?: boolean;
4
+ html?: string | boolean;
5
+ ci?: boolean;
6
+ failUnder?: number;
7
+ /** Show all findings in terminal output (no 10-finding cap) */
8
+ verbose?: boolean;
9
+ /** Write JSON or SARIF output to this file instead of stdout */
10
+ output?: string;
11
+ /** Save the current analysis as baseline in .fathom/baseline.json */
12
+ baseline?: boolean;
13
+ /** Compare current analysis against .fathom/baseline.json */
14
+ compare?: boolean;
15
+ /** Analyze changes introduced by Git diff against base ref */
16
+ diff?: string | boolean;
17
+ /** Write Markdown PR summary (to file or GITHUB_STEP_SUMMARY) */
18
+ summary?: string | boolean;
19
+ /** Post PR summary comment to GitHub pull request (requires GITHUB_TOKEN) */
20
+ prComment?: boolean;
21
+ /** Display architecture model, layers, and boundary checks */
22
+ architecture?: boolean;
23
+ /** Display dependency intelligence breakdown */
24
+ deps?: boolean;
25
+ /** Enable online dependency vulnerability and freshness checks */
26
+ online?: boolean;
27
+ }
28
+ /**
29
+ * Exit codes:
30
+ * 0 = success
31
+ * 1 = analysis completed but threshold / high-severity findings triggered
32
+ * 2 = invalid usage / config error
33
+ * 3 = unexpected failure
34
+ */
35
+ /**
36
+ * The main analyze command.
37
+ */
38
+ export declare function analyzeCommand(targetPath: string, options: AnalyzeOptions): Promise<void>;
39
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,EAAE,CAAC,EAAE,OAAO,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACxB,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,6EAA6E;IAC7E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8DAA8D;IAC9D,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gDAAgD;IAChD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kEAAkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AAEH;;GAEG;AACH,wBAAsB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAiR/F"}