@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,277 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import ora from 'ora';
4
+ import { runAnalysis, createDefaultRegistry } from '../core/orchestrator.js';
5
+ import { TerminalReporter } from '../reporters/terminal.js';
6
+ import { JsonReporter } from '../reporters/json.js';
7
+ import { HtmlReporter } from '../reporters/html.js';
8
+ import { SarifReporter } from '../reporters/sarif.js';
9
+ import { loadConfig } from './options.js';
10
+ import { FathomConfigError, FathomRepositoryError, FathomBaselineMissingError, FathomBaselineCorruptError, FathomGitDiffError, } from '../core/errors.js';
11
+ import { saveBaseline, loadBaseline } from '../baseline/baseline.js';
12
+ import { compareWithBaseline } from '../baseline/compare.js';
13
+ import { analyzePR } from '../diff/analyzer.js';
14
+ import { buildRepositoryContext } from '../core/context.js';
15
+ import { generatePRMarkdownSummary } from '../reporters/markdown.js';
16
+ import { detectGitHubContext, publishStepSummary, publishPRComment, } from '../integrations/github/context.js';
17
+ import { reactPlugin } from '../plugins/examples/react.js';
18
+ /**
19
+ * Exit codes:
20
+ * 0 = success
21
+ * 1 = analysis completed but threshold / high-severity findings triggered
22
+ * 2 = invalid usage / config error
23
+ * 3 = unexpected failure
24
+ */
25
+ /**
26
+ * The main analyze command.
27
+ */
28
+ export async function analyzeCommand(targetPath, options) {
29
+ const isJsonMode = options.json === true;
30
+ const isSarifMode = options.sarif === true;
31
+ const isCIMode = options.ci === true;
32
+ const isVerbose = options.verbose === true;
33
+ // Validate repository path
34
+ const resolvedPath = path.resolve(targetPath);
35
+ try {
36
+ const stat = await fs.stat(resolvedPath);
37
+ if (!stat.isDirectory()) {
38
+ process.stderr.write(`Error: "${resolvedPath}" is not a directory.\n`);
39
+ process.exit(2);
40
+ }
41
+ }
42
+ catch {
43
+ process.stderr.write(`Error: "${resolvedPath}" does not exist.\n`);
44
+ process.exit(2);
45
+ }
46
+ // Load configuration
47
+ let config;
48
+ try {
49
+ config = await loadConfig(resolvedPath);
50
+ }
51
+ catch (err) {
52
+ if (err instanceof FathomConfigError) {
53
+ process.stderr.write(`Configuration error: ${err.message}\n`);
54
+ process.exit(2);
55
+ }
56
+ throw err;
57
+ }
58
+ // Warn explicitly about any disabled security checks
59
+ if (config.disabledSecurityRules && config.disabledSecurityRules.length > 0) {
60
+ for (const secRule of config.disabledSecurityRules) {
61
+ process.stderr.write(`[Fathom] Warning: Security check ${secRule} has been explicitly disabled via configuration.\n`);
62
+ }
63
+ }
64
+ // Pre-load baseline if comparison requested
65
+ let baseline = null;
66
+ if (options.compare) {
67
+ try {
68
+ baseline = await loadBaseline(resolvedPath);
69
+ }
70
+ catch (err) {
71
+ if (err instanceof FathomBaselineMissingError || err instanceof FathomBaselineCorruptError) {
72
+ process.stderr.write(`Error: ${err.message}\n`);
73
+ process.exit(2);
74
+ }
75
+ throw err;
76
+ }
77
+ }
78
+ // Spinner (not in JSON, SARIF, or CI mode)
79
+ let spinner = null;
80
+ if (!isJsonMode && !isSarifMode && !isCIMode) {
81
+ spinner = ora({
82
+ text: `Scanning ${path.basename(resolvedPath)}...`,
83
+ color: 'cyan',
84
+ }).start();
85
+ }
86
+ else if (isCIMode) {
87
+ process.stderr.write(`Scanning ${resolvedPath}...\n`);
88
+ }
89
+ let result;
90
+ try {
91
+ const pluginsToLoad = [];
92
+ if (config.plugins && config.plugins.length > 0) {
93
+ for (const pName of config.plugins) {
94
+ if (pName === '@fathom/plugin-react' || pName === 'react') {
95
+ pluginsToLoad.push(reactPlugin);
96
+ }
97
+ }
98
+ }
99
+ const registry = createDefaultRegistry();
100
+ result = await runAnalysis(registry, {
101
+ repositoryPath: resolvedPath,
102
+ ignorePatterns: config.ignore ?? [],
103
+ ruleOverrides: config.rules ?? {},
104
+ online: Boolean(options.online),
105
+ plugins: pluginsToLoad,
106
+ });
107
+ }
108
+ catch (err) {
109
+ spinner?.fail('Analysis failed.');
110
+ if (err instanceof FathomRepositoryError) {
111
+ process.stderr.write(`Repository error: ${err.message}\n`);
112
+ process.exit(3);
113
+ }
114
+ process.stderr.write(`Unexpected error: ${String(err)}\n`);
115
+ process.exit(3);
116
+ }
117
+ spinner?.stop();
118
+ // Attach comparison if baseline was loaded
119
+ if (options.compare && baseline) {
120
+ result.comparison = compareWithBaseline(result, baseline);
121
+ }
122
+ // Attach PR diff analysis if requested
123
+ if (options.diff !== undefined) {
124
+ try {
125
+ const currentContext = await buildRepositoryContext(resolvedPath, config.ignore ?? []);
126
+ const githubContext = await detectGitHubContext();
127
+ // If diff baseRef was not explicitly specified and GITHUB_BASE_REF is set, use it
128
+ const effectiveBaseRef = (options.diff === true || options.diff === '') && githubContext.baseRef
129
+ ? githubContext.baseRef
130
+ : options.diff;
131
+ result.prAnalysis = await analyzePR(resolvedPath, {
132
+ baseRef: effectiveBaseRef,
133
+ ignorePatterns: config.ignore ?? [],
134
+ currentResult: result,
135
+ currentContext,
136
+ });
137
+ // Handle Markdown PR summary if requested or running in GitHub Actions
138
+ const shouldWriteSummary = options.summary !== undefined || githubContext.isGitHubActions;
139
+ if (shouldWriteSummary && result.prAnalysis) {
140
+ const markdown = generatePRMarkdownSummary(result.prAnalysis);
141
+ // If specific file requested via --summary <file>, write directly
142
+ if (typeof options.summary === 'string' && options.summary.length > 0) {
143
+ await fs.writeFile(options.summary, markdown, 'utf8');
144
+ if (!isJsonMode && !isSarifMode) {
145
+ process.stderr.write(`PR summary written to: ${options.summary}\n`);
146
+ }
147
+ }
148
+ // In GitHub Actions, publish to GITHUB_STEP_SUMMARY
149
+ if (githubContext.isGitHubActions || options.summary === true) {
150
+ await publishStepSummary(markdown);
151
+ }
152
+ // Optional PR comment posting
153
+ if (options.prComment) {
154
+ if (githubContext.repository && githubContext.prNumber && githubContext.token) {
155
+ const commentRes = await publishPRComment({
156
+ repository: githubContext.repository,
157
+ prNumber: githubContext.prNumber,
158
+ token: githubContext.token,
159
+ body: markdown,
160
+ });
161
+ if (commentRes.success) {
162
+ process.stderr.write('✓ Published PR summary comment to GitHub.\n');
163
+ }
164
+ else {
165
+ process.stderr.write(`[Fathom] Warning: Failed to post PR comment: ${commentRes.error}\n`);
166
+ }
167
+ }
168
+ else {
169
+ process.stderr.write('[Fathom] Warning: --pr-comment requested, but GITHUB_TOKEN, repository, or PR number could not be detected.\n');
170
+ }
171
+ }
172
+ }
173
+ }
174
+ catch (err) {
175
+ if (err instanceof FathomGitDiffError) {
176
+ process.stderr.write(`Error: ${err.message}\n`);
177
+ process.exit(2);
178
+ }
179
+ throw err;
180
+ }
181
+ }
182
+ // Save baseline if requested
183
+ if (options.baseline) {
184
+ try {
185
+ const savedPath = await saveBaseline(resolvedPath, result);
186
+ if (!isJsonMode && !isSarifMode) {
187
+ const rel = path.relative(resolvedPath, savedPath) || savedPath;
188
+ process.stdout.write(`\n✓ Baseline saved to: ${rel}\n`);
189
+ }
190
+ else {
191
+ const rel = path.relative(resolvedPath, savedPath) || savedPath;
192
+ process.stderr.write(`✓ Baseline saved to: ${rel}\n`);
193
+ }
194
+ }
195
+ catch (err) {
196
+ process.stderr.write(`Error: Failed to save baseline: ${String(err)}\n`);
197
+ process.exit(3);
198
+ }
199
+ }
200
+ // Report
201
+ if (isSarifMode) {
202
+ const reporter = new SarifReporter();
203
+ if (options.output) {
204
+ const sarif = reporter.generateSarif(result);
205
+ await fs.writeFile(options.output, sarif, 'utf8');
206
+ process.stderr.write(`SARIF report written to: ${options.output}\n`);
207
+ }
208
+ else {
209
+ await reporter.report(result);
210
+ }
211
+ }
212
+ else if (isJsonMode) {
213
+ const reporter = new JsonReporter();
214
+ if (options.output) {
215
+ // Write JSON to file
216
+ const json = JSON.stringify(result, null, 2);
217
+ await fs.writeFile(options.output, json, 'utf8');
218
+ process.stderr.write(`JSON report written to: ${options.output}\n`);
219
+ }
220
+ else {
221
+ await reporter.report(result);
222
+ }
223
+ }
224
+ else if (options.html !== undefined) {
225
+ const htmlPath = typeof options.html === 'string' && options.html !== '' ? options.html : 'fathom-report.html';
226
+ const termReporter = new TerminalReporter();
227
+ await termReporter.report(result, isCIMode, isVerbose);
228
+ const htmlReporter = new HtmlReporter();
229
+ await htmlReporter.report(result, htmlPath);
230
+ process.stdout.write(`HTML report written to: ${htmlPath}\n`);
231
+ }
232
+ else {
233
+ const termReporter = new TerminalReporter();
234
+ if (options.architecture) {
235
+ termReporter.printArchitectureReport(result);
236
+ }
237
+ else if (options.deps) {
238
+ termReporter.printDependencyReport(result);
239
+ }
240
+ else {
241
+ await termReporter.report(result, isCIMode, isVerbose);
242
+ }
243
+ }
244
+ // fail-under threshold check (CLI flag takes precedence over .fathom.json)
245
+ const threshold = options.failUnder ?? config.failUnder;
246
+ if (typeof threshold === 'number') {
247
+ if (result.score.overall < threshold) {
248
+ if (!isJsonMode && !isSarifMode) {
249
+ process.stderr.write(`\nScore ${result.score.overall} is below threshold ${threshold}. Exiting with code 1.\n`);
250
+ }
251
+ process.exit(1);
252
+ }
253
+ }
254
+ // CI mode regression check
255
+ if (isCIMode && options.compare && result.comparison?.isRegression) {
256
+ process.stderr.write(`\nRegression detected compared to baseline. Exiting with code 1.\n`);
257
+ process.exit(1);
258
+ }
259
+ // CI mode PR diff check: fail build if changes introduce new findings or regression
260
+ if (isCIMode && options.diff !== undefined && result.prAnalysis) {
261
+ if (!result.prAnalysis.verdict.passed) {
262
+ process.stderr.write(`\nPR analysis check failed: ${result.prAnalysis.verdict.summary}. Exiting with code 1.\n`);
263
+ process.exit(1);
264
+ }
265
+ }
266
+ // CI mode: exit 1 on any critical or high finding (no score threshold —
267
+ // a single leaked key must fail CI even if overall score is 90).
268
+ if (isCIMode && typeof threshold !== 'number') {
269
+ const blocking = result.findings.filter((f) => f.severity === 'critical' || f.severity === 'high');
270
+ if (blocking.length > 0) {
271
+ process.stderr.write(`\n${blocking.length} critical/high finding${blocking.length > 1 ? 's' : ''} detected. Exiting with code 1.\n`);
272
+ process.exit(1);
273
+ }
274
+ }
275
+ process.exit(0);
276
+ }
277
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { analyzeCommand } from './commands.js';
4
+ const program = new Command();
5
+ program
6
+ .name('fathom')
7
+ .description("Know what's beneath the surface. Local-first repository intelligence.")
8
+ .version('0.1.0', '-v, --version', 'Output the current version')
9
+ .argument('[path]', 'Path to the repository to analyze', '.')
10
+ .option('--json', 'Output results as JSON (to stdout)')
11
+ .option('--sarif', 'Output results in SARIF 2.1.0 format (to stdout or file with -o)')
12
+ .option('--html [file]', 'Generate an HTML report (default: fathom-report.html)')
13
+ .option('--ci', 'CI mode: compact output, exit 1 on critical findings')
14
+ .option('--fail-under <number>', 'Exit 1 if the health score is below this value', parseFloat)
15
+ .option('-o, --output <file>', 'Write JSON or SARIF report directly to a file')
16
+ .option('--verbose', 'Show all findings in terminal output without truncation')
17
+ .option('--baseline', 'Save the current analysis as baseline in .fathom/baseline.json')
18
+ .option('--compare', 'Compare current analysis against .fathom/baseline.json and report regressions')
19
+ .option('--diff [ref]', 'Analyze changes introduced by Git diff against base ref (default: auto-detect)')
20
+ .option('--summary [file]', 'Generate Markdown PR summary (writes to GITHUB_STEP_SUMMARY or specified file)')
21
+ .option('--pr-comment', 'Post PR summary comment to GitHub pull request (requires GITHUB_TOKEN)')
22
+ .option('--architecture', 'Display architecture model, layers, and boundary checks')
23
+ .option('--deps', 'Display dependency intelligence, duplicates, and configuration hygiene')
24
+ .option('--online', 'Enable online dependency vulnerability and freshness queries (opt-in)')
25
+ .action(async (targetPath, options) => {
26
+ await analyzeCommand(targetPath, options);
27
+ });
28
+ program.parse(process.argv);
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,uEAAuE,CAAC;KACpF,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,4BAA4B,CAAC;KAC/D,QAAQ,CAAC,QAAQ,EAAE,mCAAmC,EAAE,GAAG,CAAC;KAC5D,MAAM,CAAC,QAAQ,EAAE,oCAAoC,CAAC;KACtD,MAAM,CAAC,SAAS,EAAE,kEAAkE,CAAC;KACrF,MAAM,CAAC,eAAe,EAAE,uDAAuD,CAAC;KAChF,MAAM,CAAC,MAAM,EAAE,sDAAsD,CAAC;KACtE,MAAM,CAAC,uBAAuB,EAAE,gDAAgD,EAAE,UAAU,CAAC;KAC7F,MAAM,CAAC,qBAAqB,EAAE,+CAA+C,CAAC;KAC9E,MAAM,CAAC,WAAW,EAAE,yDAAyD,CAAC;KAC9E,MAAM,CAAC,YAAY,EAAE,gEAAgE,CAAC;KACtF,MAAM,CACL,WAAW,EACX,+EAA+E,CAChF;KACA,MAAM,CACL,cAAc,EACd,gFAAgF,CACjF;KACA,MAAM,CACL,kBAAkB,EAClB,gFAAgF,CACjF;KACA,MAAM,CAAC,cAAc,EAAE,wEAAwE,CAAC;KAChG,MAAM,CAAC,gBAAgB,EAAE,yDAAyD,CAAC;KACnF,MAAM,CAAC,QAAQ,EAAE,wEAAwE,CAAC;KAC1F,MAAM,CAAC,UAAU,EAAE,uEAAuE,CAAC;KAC3F,MAAM,CACL,KAAK,EACH,UAAkB,EAClB,OAgBC,EACD,EAAE;IACF,MAAM,cAAc,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC,CACF,CAAC;AAEJ,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { RuleConfigValue } from '../rules/registry.js';
2
+ /**
3
+ * Valid string states for rules.
4
+ */
5
+ export type RuleState = 'off' | 'warning' | 'error';
6
+ /**
7
+ * Fathom configuration file schema (.fathom.json).
8
+ */
9
+ export interface FathomConfig {
10
+ /** Configuration schema version (currently 1) */
11
+ version?: number | undefined;
12
+ /** Glob patterns to ignore during analysis */
13
+ ignore: string[];
14
+ /** Minimum overall health score before CI exit 1 */
15
+ failUnder?: number | undefined;
16
+ /** Explicit opt-in flag required to disable critical security checks */
17
+ allowDisableSecurity?: boolean | undefined;
18
+ /** Thresholds for various checks */
19
+ thresholds: {
20
+ /** Large file size threshold in MB (default: 10) */
21
+ largeFileMB: number;
22
+ /** Large file line threshold (default: 500) */
23
+ largeFileLines: number;
24
+ };
25
+ /** Per-rule overrides */
26
+ rules: Record<string, RuleConfigValue>;
27
+ /** Explicit list of security rules that were intentionally disabled */
28
+ disabledSecurityRules?: string[] | undefined;
29
+ /** Optional list of enabled plugins (e.g. ["@fathom/plugin-react"]) */
30
+ plugins?: string[] | undefined;
31
+ }
32
+ /**
33
+ * Default configuration.
34
+ */
35
+ export declare const DEFAULT_CONFIG: FathomConfig;
36
+ /**
37
+ * Expands an ignore pattern into fast-glob compatible patterns.
38
+ * Supports directory patterns, root-anchored paths, and file extensions.
39
+ */
40
+ export declare function expandIgnorePattern(pattern: string): string[];
41
+ /**
42
+ * Parses the contents of a .fathomignore file.
43
+ * Ignores comments (lines starting with #) and blank lines.
44
+ */
45
+ export declare function parseFathomIgnore(content: string): string[];
46
+ /**
47
+ * Reads and parses .fathomignore if present in the repository root.
48
+ */
49
+ export declare function loadFathomIgnore(repositoryRoot: string): Promise<string[]>;
50
+ /**
51
+ * Load and validate configuration from .fathom.json and .fathomignore.
52
+ * Returns default config merged with .fathomignore if no .fathom.json exists.
53
+ */
54
+ export declare function loadConfig(repositoryRoot: string): Promise<FathomConfig>;
55
+ //# sourceMappingURL=options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../../src/cli/options.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,8CAA8C;IAC9C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,wEAAwE;IACxE,oBAAoB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3C,oCAAoC;IACpC,UAAU,EAAE;QACV,oDAAoD;QACpD,WAAW,EAAE,MAAM,CAAC;QACpB,+CAA+C;QAC/C,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,uEAAuE;IACvE,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC7C,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;CAChC;AAED;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,YAQ5B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CA+B7D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAa3D;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAQhF;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAgP9E"}
@@ -0,0 +1,258 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { FathomConfigError } from '../core/errors.js';
4
+ import { RULE_DEFINITIONS } from '../rules/definitions.js';
5
+ /**
6
+ * Default configuration.
7
+ */
8
+ export const DEFAULT_CONFIG = {
9
+ version: 1,
10
+ ignore: [],
11
+ thresholds: {
12
+ largeFileMB: 10,
13
+ largeFileLines: 500,
14
+ },
15
+ rules: {},
16
+ };
17
+ /**
18
+ * Expands an ignore pattern into fast-glob compatible patterns.
19
+ * Supports directory patterns, root-anchored paths, and file extensions.
20
+ */
21
+ export function expandIgnorePattern(pattern) {
22
+ let p = pattern.trim().replace(/\\/g, '/');
23
+ if (!p)
24
+ return [];
25
+ const isRootAnchored = p.startsWith('/');
26
+ if (isRootAnchored) {
27
+ p = p.slice(1);
28
+ }
29
+ const isDir = p.endsWith('/');
30
+ if (isDir) {
31
+ p = p.slice(0, -1);
32
+ }
33
+ const results = [];
34
+ if (isDir) {
35
+ results.push(`${p}/**`);
36
+ if (!isRootAnchored) {
37
+ results.push(`**/${p}/**`);
38
+ }
39
+ }
40
+ else {
41
+ results.push(p);
42
+ results.push(`${p}/**`);
43
+ if (!isRootAnchored) {
44
+ results.push(`**/${p}`);
45
+ results.push(`**/${p}/**`);
46
+ }
47
+ }
48
+ return [...new Set(results)];
49
+ }
50
+ /**
51
+ * Parses the contents of a .fathomignore file.
52
+ * Ignores comments (lines starting with #) and blank lines.
53
+ */
54
+ export function parseFathomIgnore(content) {
55
+ const patterns = [];
56
+ const lines = content.split(/\r?\n/);
57
+ for (const line of lines) {
58
+ const trimmed = line.trim();
59
+ if (!trimmed || trimmed.startsWith('#')) {
60
+ continue;
61
+ }
62
+ patterns.push(...expandIgnorePattern(trimmed));
63
+ }
64
+ return [...new Set(patterns)];
65
+ }
66
+ /**
67
+ * Reads and parses .fathomignore if present in the repository root.
68
+ */
69
+ export async function loadFathomIgnore(repositoryRoot) {
70
+ const ignorePath = path.join(repositoryRoot, '.fathomignore');
71
+ try {
72
+ const content = await fs.readFile(ignorePath, 'utf8');
73
+ return parseFathomIgnore(content);
74
+ }
75
+ catch {
76
+ return [];
77
+ }
78
+ }
79
+ /**
80
+ * Load and validate configuration from .fathom.json and .fathomignore.
81
+ * Returns default config merged with .fathomignore if no .fathom.json exists.
82
+ */
83
+ export async function loadConfig(repositoryRoot) {
84
+ const fathomIgnorePatterns = await loadFathomIgnore(repositoryRoot);
85
+ const configPath = path.join(repositoryRoot, '.fathom.json');
86
+ let raw;
87
+ try {
88
+ raw = await fs.readFile(configPath, 'utf8');
89
+ }
90
+ catch {
91
+ // No .fathom.json file — use defaults merged with .fathomignore
92
+ return {
93
+ ...DEFAULT_CONFIG,
94
+ ignore: fathomIgnorePatterns,
95
+ };
96
+ }
97
+ let parsed;
98
+ try {
99
+ parsed = JSON.parse(raw);
100
+ }
101
+ catch {
102
+ throw new FathomConfigError(`.fathom.json is not valid JSON. Please fix the syntax.`, configPath);
103
+ }
104
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
105
+ throw new FathomConfigError(`.fathom.json must be a JSON object.`, configPath);
106
+ }
107
+ const config = parsed;
108
+ // Validate allowed top-level keys
109
+ const allowedKeys = new Set([
110
+ 'version',
111
+ 'ignore',
112
+ 'failUnder',
113
+ 'allowDisableSecurity',
114
+ 'thresholds',
115
+ 'rules',
116
+ ]);
117
+ for (const key of Object.keys(config)) {
118
+ if (!allowedKeys.has(key)) {
119
+ throw new FathomConfigError(`Unknown configuration property: "${key}". Allowed properties: ${[...allowedKeys].join(', ')}.`, configPath);
120
+ }
121
+ }
122
+ // Validate version (if present, must be 1)
123
+ if (config['version'] !== undefined) {
124
+ if (typeof config['version'] !== 'number' || config['version'] !== 1) {
125
+ throw new FathomConfigError(`Unsupported configuration version: ${JSON.stringify(config['version'])}. Supported versions: 1.`, configPath);
126
+ }
127
+ }
128
+ // Validate failUnder (0–100 number)
129
+ if (config['failUnder'] !== undefined) {
130
+ if (typeof config['failUnder'] !== 'number' ||
131
+ config['failUnder'] < 0 ||
132
+ config['failUnder'] > 100 ||
133
+ Number.isNaN(config['failUnder'])) {
134
+ throw new FathomConfigError(`"failUnder" must be a number between 0 and 100.`, configPath);
135
+ }
136
+ }
137
+ // Validate allowDisableSecurity (boolean)
138
+ if (config['allowDisableSecurity'] !== undefined &&
139
+ typeof config['allowDisableSecurity'] !== 'boolean') {
140
+ throw new FathomConfigError(`"allowDisableSecurity" must be a boolean.`, configPath);
141
+ }
142
+ // Validate ignore array
143
+ const jsonIgnorePatterns = [];
144
+ if (config['ignore'] !== undefined) {
145
+ if (!Array.isArray(config['ignore']) || !config['ignore'].every((x) => typeof x === 'string')) {
146
+ throw new FathomConfigError(`"ignore" in .fathom.json must be an array of strings.`, configPath);
147
+ }
148
+ for (const pat of config['ignore']) {
149
+ jsonIgnorePatterns.push(...expandIgnorePattern(pat));
150
+ }
151
+ }
152
+ // Combine ignore patterns from .fathomignore and .fathom.json
153
+ const combinedIgnore = [...new Set([...fathomIgnorePatterns, ...jsonIgnorePatterns])].sort();
154
+ // Validate thresholds
155
+ const thresholds = { ...DEFAULT_CONFIG.thresholds };
156
+ if (config['thresholds'] !== undefined) {
157
+ if (typeof config['thresholds'] !== 'object' ||
158
+ config['thresholds'] === null ||
159
+ Array.isArray(config['thresholds'])) {
160
+ throw new FathomConfigError(`"thresholds" in .fathom.json must be an object.`, configPath);
161
+ }
162
+ const t = config['thresholds'];
163
+ if (t['largeFileMB'] !== undefined) {
164
+ if (typeof t['largeFileMB'] !== 'number' || t['largeFileMB'] <= 0) {
165
+ throw new FathomConfigError(`"thresholds.largeFileMB" must be a positive number.`, configPath);
166
+ }
167
+ thresholds.largeFileMB = t['largeFileMB'];
168
+ }
169
+ if (t['largeFileLines'] !== undefined) {
170
+ if (typeof t['largeFileLines'] !== 'number' || t['largeFileLines'] <= 0) {
171
+ throw new FathomConfigError(`"thresholds.largeFileLines" must be a positive number.`, configPath);
172
+ }
173
+ thresholds.largeFileLines = t['largeFileLines'];
174
+ }
175
+ }
176
+ // Validate rules
177
+ const rules = {};
178
+ const disabledSecurityRules = [];
179
+ const allowDisableSecurity = config['allowDisableSecurity'] === true;
180
+ if (config['rules'] !== undefined) {
181
+ if (typeof config['rules'] !== 'object' ||
182
+ config['rules'] === null ||
183
+ Array.isArray(config['rules'])) {
184
+ throw new FathomConfigError(`"rules" in .fathom.json must be an object.`, configPath);
185
+ }
186
+ const rawRules = config['rules'];
187
+ const validStates = new Set(['off', 'warning', 'error']);
188
+ const validSeverities = new Set(['critical', 'high', 'medium', 'low', 'info']);
189
+ for (const [ruleId, val] of Object.entries(rawRules)) {
190
+ if (typeof val === 'string') {
191
+ if (!validStates.has(val)) {
192
+ throw new FathomConfigError(`Invalid rule state "${val}" for rule "${ruleId}". Expected "off", "warning", or "error".`, configPath);
193
+ }
194
+ rules[ruleId] = val;
195
+ }
196
+ else if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
197
+ const obj = val;
198
+ if (obj['enabled'] !== undefined && typeof obj['enabled'] !== 'boolean') {
199
+ throw new FathomConfigError(`"enabled" for rule "${ruleId}" must be a boolean.`, configPath);
200
+ }
201
+ if (obj['severity'] !== undefined &&
202
+ (typeof obj['severity'] !== 'string' || !validSeverities.has(obj['severity']))) {
203
+ throw new FathomConfigError(`Invalid severity ${JSON.stringify(obj['severity'])} for rule "${ruleId}". Expected one of: ${[...validSeverities].join(', ')}.`, configPath);
204
+ }
205
+ if (obj['reason'] !== undefined && typeof obj['reason'] !== 'string') {
206
+ throw new FathomConfigError(`"reason" for rule "${ruleId}" must be a string.`, configPath);
207
+ }
208
+ rules[ruleId] = {
209
+ enabled: typeof obj['enabled'] === 'boolean' ? obj['enabled'] : undefined,
210
+ severity: typeof obj['severity'] === 'string' ? obj['severity'] : undefined,
211
+ reason: typeof obj['reason'] === 'string' ? obj['reason'] : undefined,
212
+ };
213
+ }
214
+ else {
215
+ throw new FathomConfigError(`Invalid configuration for rule "${ruleId}". Expected "off", "warning", "error", or an object.`, configPath);
216
+ }
217
+ // Check if this rule is a security check and is being disabled
218
+ const isSecurityRule = ruleId.startsWith('SEC-') || RULE_DEFINITIONS[ruleId]?.category === 'security';
219
+ const isDisabled = rules[ruleId] === 'off' ||
220
+ (typeof rules[ruleId] === 'object' &&
221
+ rules[ruleId].enabled === false);
222
+ if (isSecurityRule && isDisabled) {
223
+ const hasExplicitReason = typeof rules[ruleId] === 'object' &&
224
+ typeof rules[ruleId].reason === 'string' &&
225
+ rules[ruleId].reason.trim().length > 0;
226
+ if (!allowDisableSecurity && !hasExplicitReason) {
227
+ throw new FathomConfigError(`Cannot silently disable security check "${ruleId}". Disabling security checks requires explicit confirmation: set "allowDisableSecurity": true or provide a non-empty "reason" (e.g. "${ruleId}": { "enabled": false, "reason": "Justification..." }).`, configPath);
228
+ }
229
+ disabledSecurityRules.push(ruleId);
230
+ }
231
+ }
232
+ }
233
+ // Validate plugins if declared
234
+ let pluginsList;
235
+ if (config['plugins'] !== undefined) {
236
+ if (!Array.isArray(config['plugins'])) {
237
+ throw new FathomConfigError(`"plugins" in .fathom.json must be an array of plugin names.`, configPath);
238
+ }
239
+ pluginsList = [];
240
+ for (const p of config['plugins']) {
241
+ if (typeof p !== 'string' || !p.trim()) {
242
+ throw new FathomConfigError(`Each entry in "plugins" must be a non-empty string.`, configPath);
243
+ }
244
+ pluginsList.push(p.trim());
245
+ }
246
+ }
247
+ return {
248
+ version: typeof config['version'] === 'number' ? config['version'] : 1,
249
+ ignore: combinedIgnore,
250
+ failUnder: typeof config['failUnder'] === 'number' ? config['failUnder'] : undefined,
251
+ allowDisableSecurity,
252
+ thresholds,
253
+ rules,
254
+ disabledSecurityRules,
255
+ plugins: pluginsList,
256
+ };
257
+ }
258
+ //# sourceMappingURL=options.js.map