@aiready/cli 0.14.1 → 0.14.3

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 (33) hide show
  1. package/.aiready/aiready-report-20260314-164626.json +2 -5
  2. package/.aiready/aiready-report-20260314-164741.json +2 -5
  3. package/.turbo/turbo-build.log +7 -7
  4. package/.turbo/turbo-test.log +19 -110
  5. package/dist/cli.js +149 -125
  6. package/dist/cli.mjs +69 -45
  7. package/package.json +12 -12
  8. package/packages/core/src/.aiready/aiready-report-20260314-161145.json +4 -10
  9. package/packages/core/src/.aiready/aiready-report-20260314-161152.json +10 -28
  10. package/packages/pattern-detect/src/.aiready/aiready-report-20260314-161139.json +4 -10
  11. package/src/.aiready/aiready-report-20260312-103623.json +3 -9
  12. package/src/.aiready/aiready-report-20260312-110843.json +3 -9
  13. package/src/.aiready/aiready-report-20260312-110955.json +3 -9
  14. package/src/.aiready/aiready-report-20260314-203209.json +30713 -0
  15. package/src/.aiready/aiready-report-20260314-203736.json +30713 -0
  16. package/src/.aiready/aiready-report-20260314-203857.json +30713 -0
  17. package/src/.aiready/aiready-report-20260314-204047.json +30713 -0
  18. package/src/__tests__/config-shape.test.ts +1 -1
  19. package/src/cli.ts +2 -1
  20. package/src/commands/__tests__/consistency.test.ts +3 -0
  21. package/src/commands/__tests__/extra-commands.test.ts +29 -37
  22. package/src/commands/__tests__/scan.test.ts +3 -1
  23. package/src/commands/__tests__/visualize.test.ts +3 -7
  24. package/src/commands/ai-signal-clarity.ts +1 -56
  25. package/src/commands/deps-health.ts +1 -65
  26. package/src/commands/doc-drift.ts +1 -62
  27. package/src/commands/init.ts +62 -2
  28. package/src/commands/scan.ts +11 -8
  29. package/src/commands/shared/configured-tool-action.ts +35 -0
  30. package/src/commands/shared/standard-tool-actions.ts +126 -0
  31. package/src/commands/visualize.ts +2 -3
  32. package/src/utils/helpers.ts +85 -36
  33. package/vitest.config.ts +5 -12
@@ -76,7 +76,7 @@ describe('CLI Configuration Shape', () => {
76
76
  rootDir: '/test',
77
77
  tools: [ToolName.PatternDetect],
78
78
  useSmartDefaults: true,
79
- // @ts-ignore - testing internal key stripping
79
+ // @ts-expect-error - testing internal key stripping
80
80
  batchSize: 50,
81
81
  });
82
82
 
package/src/cli.ts CHANGED
@@ -132,9 +132,10 @@ program
132
132
  '--js',
133
133
  'Generate configuration as a JavaScript file (aiready.config.js)'
134
134
  )
135
+ .option('--full', 'Generate a full configuration with all available options')
135
136
  .action(async (options) => {
136
137
  const format = options.js ? 'js' : 'json';
137
- await initAction({ force: options.force, format });
138
+ await initAction({ force: options.force, format, full: options.full });
138
139
  });
139
140
 
140
141
  // Patterns command - Detect duplicate code patterns
@@ -60,6 +60,7 @@ describe('Consistency CLI Action', () => {
60
60
  consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
61
61
  vi.mocked(core.loadMergedConfig).mockResolvedValue({
62
62
  output: { format: 'console' },
63
+ rootDir: '/test',
63
64
  });
64
65
  });
65
66
 
@@ -75,6 +76,7 @@ describe('Consistency CLI Action', () => {
75
76
  it('supports JSON output', async () => {
76
77
  vi.mocked(core.loadMergedConfig).mockResolvedValue({
77
78
  output: { format: 'json' },
79
+ rootDir: '/test',
78
80
  });
79
81
  await consistencyAction('.', {});
80
82
  expect(core.handleJSONOutput).toHaveBeenCalled();
@@ -83,6 +85,7 @@ describe('Consistency CLI Action', () => {
83
85
  it('supports Markdown output', async () => {
84
86
  vi.mocked(core.loadMergedConfig).mockResolvedValue({
85
87
  output: { format: 'markdown' },
88
+ rootDir: '/test',
86
89
  });
87
90
  await consistencyAction('.', {});
88
91
  expect(fs.writeFileSync).toHaveBeenCalled();
@@ -18,7 +18,7 @@ vi.mock('@aiready/core', async () => {
18
18
  .fn()
19
19
  .mockImplementation((c, d) => ({ ...d, ...c })),
20
20
  loadMergedConfig: vi.fn().mockResolvedValue({
21
- rootDir: '.',
21
+ rootDir: '/test',
22
22
  output: { format: 'console' },
23
23
  checkNaming: true,
24
24
  checkPatterns: true,
@@ -33,13 +33,11 @@ vi.mock('@aiready/core', async () => {
33
33
 
34
34
  // Mock all spokes
35
35
  vi.mock('@aiready/change-amplification', () => ({
36
- analyzeChangeAmplification: vi
37
- .fn()
38
- .mockResolvedValue({
39
- results: [],
40
- summary: { rating: 'contained' },
41
- recommendations: [],
42
- }),
36
+ analyzeChangeAmplification: vi.fn().mockResolvedValue({
37
+ results: [],
38
+ summary: { rating: 'contained' },
39
+ recommendations: [],
40
+ }),
43
41
  calculateChangeAmplificationScore: vi.fn().mockReturnValue({ score: 80 }),
44
42
  }));
45
43
  vi.mock('@aiready/agent-grounding', () => ({
@@ -91,39 +89,33 @@ vi.mock('@aiready/deps', () => ({
91
89
  calculateDepsScore: vi.fn().mockReturnValue({ score: 80 }),
92
90
  }));
93
91
  vi.mock('@aiready/pattern-detect', () => ({
94
- analyzePatterns: vi
95
- .fn()
96
- .mockResolvedValue({
97
- results: [],
98
- summary: { totalPatterns: 0 },
99
- config: {},
100
- }),
101
- generateSummary: vi
102
- .fn()
103
- .mockReturnValue({
104
- totalPatterns: 0,
105
- totalTokenCost: 0,
106
- patternsByType: {},
107
- topDuplicates: [],
108
- }),
92
+ analyzePatterns: vi.fn().mockResolvedValue({
93
+ results: [],
94
+ summary: { totalPatterns: 0 },
95
+ config: {},
96
+ }),
97
+ generateSummary: vi.fn().mockReturnValue({
98
+ totalPatterns: 0,
99
+ totalTokenCost: 0,
100
+ patternsByType: {},
101
+ topDuplicates: [],
102
+ }),
109
103
  getSmartDefaults: vi.fn().mockResolvedValue({}),
110
104
  }));
111
105
  vi.mock('@aiready/context-analyzer', () => ({
112
106
  analyzeContext: vi.fn().mockResolvedValue([]),
113
- generateSummary: vi
114
- .fn()
115
- .mockReturnValue({
116
- score: 80,
117
- rating: 'good',
118
- totalFiles: 0,
119
- totalTokens: 0,
120
- avgImportDepth: 0,
121
- maxImportDepth: 0,
122
- avgFragmentation: 0,
123
- criticalIssues: 0,
124
- majorIssues: 0,
125
- totalPotentialSavings: 0,
126
- }),
107
+ generateSummary: vi.fn().mockReturnValue({
108
+ score: 80,
109
+ rating: 'good',
110
+ totalFiles: 0,
111
+ totalTokens: 0,
112
+ avgImportDepth: 0,
113
+ maxImportDepth: 0,
114
+ avgFragmentation: 0,
115
+ criticalIssues: 0,
116
+ majorIssues: 0,
117
+ totalPotentialSavings: 0,
118
+ }),
127
119
  getSmartDefaults: vi.fn().mockResolvedValue({}),
128
120
  }));
129
121
 
@@ -58,6 +58,7 @@ describe('Scan CLI Action', () => {
58
58
  vi.mocked(core.loadMergedConfig).mockResolvedValue({
59
59
  tools: ['pattern-detect'],
60
60
  output: { format: 'console' },
61
+ rootDir: '/test',
61
62
  });
62
63
  vi.mocked(index.analyzeUnified).mockResolvedValue({
63
64
  summary: {
@@ -126,7 +127,7 @@ describe('Scan CLI Action', () => {
126
127
 
127
128
  expect(exitSpy).toHaveBeenCalledWith(1);
128
129
  expect(consoleSpy).toHaveBeenCalledWith(
129
- expect.stringContaining('PR BLOCKED')
130
+ expect.stringContaining('SCAN FAILED')
130
131
  );
131
132
  // Verify annotations are emitted
132
133
  expect(consoleSpy).toHaveBeenCalledWith(
@@ -144,6 +145,7 @@ describe('Scan CLI Action', () => {
144
145
  vi.mocked(core.loadMergedConfig).mockResolvedValue({
145
146
  tools: ['pattern-detect'],
146
147
  output: { format: 'json', file: 'out.json' },
148
+ rootDir: '/test',
147
149
  });
148
150
  await scanAction('.', {});
149
151
  expect(core.handleJSONOutput).toHaveBeenCalled();
@@ -2,7 +2,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
2
  import { visualizeAction } from '../visualize';
3
3
  import * as fs from 'fs';
4
4
  import * as core from '@aiready/core';
5
- import * as helpers from '../../utils/helpers';
6
5
  import { spawn } from 'child_process';
7
6
 
8
7
  vi.mock('fs', async () => {
@@ -29,10 +28,7 @@ vi.mock('@aiready/visualizer/graph', () => ({
29
28
  vi.mock('@aiready/core', () => ({
30
29
  handleCLIError: vi.fn(),
31
30
  generateHTML: vi.fn().mockReturnValue('<html></html>'),
32
- }));
33
-
34
- vi.mock('../../utils/helpers', () => ({
35
- findLatestScanReport: vi.fn(),
31
+ findLatestReport: vi.fn(),
36
32
  }));
37
33
 
38
34
  describe('Visualize CLI Action', () => {
@@ -54,7 +50,7 @@ describe('Visualize CLI Action', () => {
54
50
  });
55
51
 
56
52
  it('should find latest report if none specified', async () => {
57
- vi.spyOn(helpers, 'findLatestScanReport').mockReturnValue('latest.json');
53
+ vi.mocked(core.findLatestReport).mockReturnValue('latest.json');
58
54
  await visualizeAction('.', {});
59
55
  expect(fs.readFileSync).toHaveBeenCalledWith(
60
56
  expect.stringContaining('latest.json'),
@@ -64,7 +60,7 @@ describe('Visualize CLI Action', () => {
64
60
 
65
61
  it('should handle missing reports', async () => {
66
62
  vi.spyOn(fs, 'existsSync').mockReturnValue(false);
67
- vi.spyOn(helpers, 'findLatestScanReport').mockReturnValue(null);
63
+ vi.mocked(core.findLatestReport).mockReturnValue(null);
68
64
  const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
69
65
 
70
66
  await visualizeAction('.', { report: 'missing.json' });
@@ -1,56 +1 @@
1
- /**
2
- * AI signal clarity command for unified CLI
3
- */
4
-
5
- import chalk from 'chalk';
6
- import { loadConfig, mergeConfigWithDefaults } from '@aiready/core';
7
- import type { ToolScoringOutput } from '@aiready/core';
8
-
9
- export async function aiSignalClarityAction(
10
- directory: string,
11
- options: any
12
- ): Promise<ToolScoringOutput | undefined> {
13
- const { analyzeAiSignalClarity, calculateAiSignalClarityScore } =
14
- await import('@aiready/ai-signal-clarity');
15
-
16
- const config = await loadConfig(directory);
17
- const merged = mergeConfigWithDefaults(config, {
18
- minSeverity: 'info',
19
- });
20
-
21
- const report = await analyzeAiSignalClarity({
22
- rootDir: directory,
23
- minSeverity: options.minSeverity ?? merged.minSeverity ?? 'info',
24
- include: options.include,
25
- exclude: options.exclude,
26
- });
27
-
28
- const scoring = calculateAiSignalClarityScore(report);
29
-
30
- if (options.output === 'json') {
31
- return scoring;
32
- }
33
-
34
- const { summary } = report;
35
- const ratingColors: Record<string, (s: string) => string> = {
36
- minimal: chalk.green,
37
- low: chalk.cyan,
38
- moderate: chalk.yellow,
39
- high: chalk.red,
40
- severe: chalk.bgRed.white,
41
- };
42
- const color = ratingColors[summary.rating] ?? chalk.white;
43
- console.log(
44
- ` 🧠 AI Signal Clarity: ${chalk.bold(scoring.score + '/100')} (${color(summary.rating)})`
45
- );
46
- console.log(` Top Risk: ${chalk.italic(summary.topRisk)}`);
47
- if (summary.totalSignals > 0) {
48
- console.log(
49
- chalk.dim(
50
- ` ${summary.criticalSignals} critical ${summary.majorSignals} major ${summary.minorSignals} minor signals`
51
- )
52
- );
53
- }
54
-
55
- return scoring;
56
- }
1
+ export { aiSignalClarityAction } from './shared/standard-tool-actions';
@@ -1,65 +1 @@
1
- /**
2
- * Dependency health command for unified CLI
3
- */
4
-
5
- import chalk from 'chalk';
6
- import { loadConfig, mergeConfigWithDefaults } from '@aiready/core';
7
- import type { ToolScoringOutput } from '@aiready/core';
8
-
9
- export async function depsHealthAction(
10
- directory: string,
11
- options: any
12
- ): Promise<ToolScoringOutput | undefined> {
13
- const { analyzeDeps } = await import('@aiready/deps');
14
-
15
- const config = await loadConfig(directory);
16
- const merged = mergeConfigWithDefaults(config, {
17
- trainingCutoffYear: 2023,
18
- });
19
-
20
- const report = await analyzeDeps({
21
- rootDir: directory,
22
- include: options.include,
23
- exclude: options.exclude,
24
- trainingCutoffYear:
25
- options.trainingCutoffYear ?? merged.trainingCutoffYear ?? 2023,
26
- });
27
-
28
- const scoring: ToolScoringOutput = {
29
- toolName: 'dependency-health',
30
- score: report.summary.score,
31
- rawMetrics: report.rawData,
32
- factors: [],
33
- recommendations: report.recommendations.map((action: string) => ({
34
- action,
35
- estimatedImpact: 5,
36
- priority: 'medium',
37
- })),
38
- };
39
-
40
- if (options.output === 'json') {
41
- return scoring;
42
- }
43
-
44
- const { summary } = report;
45
- const ratingColors: Record<string, (s: string) => string> = {
46
- excellent: chalk.green,
47
- good: chalk.blueBright,
48
- moderate: chalk.yellow,
49
- poor: chalk.red,
50
- hazardous: chalk.bgRed.white,
51
- };
52
- const color = ratingColors[summary.rating] ?? chalk.white;
53
- console.log(
54
- ` šŸ“¦ Dependency Health: ${chalk.bold(scoring.score + '/100 health')} (${color(summary.rating)})`
55
- );
56
- if (report.issues.length > 0) {
57
- console.log(
58
- chalk.dim(` Found ${report.issues.length} dependency issues.`)
59
- );
60
- } else {
61
- console.log(chalk.dim(` Dependencies look healthy for AI assistance.`));
62
- }
63
-
64
- return scoring;
65
- }
1
+ export { depsHealthAction } from './shared/standard-tool-actions';
@@ -1,62 +1 @@
1
- /**
2
- * Doc drift risk command for unified CLI
3
- */
4
-
5
- import chalk from 'chalk';
6
- import { loadConfig, mergeConfigWithDefaults } from '@aiready/core';
7
- import type { ToolScoringOutput } from '@aiready/core';
8
-
9
- export async function docDriftAction(
10
- directory: string,
11
- options: any
12
- ): Promise<ToolScoringOutput | undefined> {
13
- const { analyzeDocDrift } = await import('@aiready/doc-drift');
14
-
15
- const config = await loadConfig(directory);
16
- const merged = mergeConfigWithDefaults(config, {
17
- staleMonths: 6,
18
- });
19
-
20
- const report = await analyzeDocDrift({
21
- rootDir: directory,
22
- include: options.include,
23
- exclude: options.exclude,
24
- staleMonths: options.staleMonths ?? merged.staleMonths ?? 6,
25
- });
26
-
27
- const scoring: ToolScoringOutput = {
28
- toolName: 'doc-drift',
29
- score: report.summary.score,
30
- rawMetrics: report.rawData,
31
- factors: [],
32
- recommendations: report.recommendations.map((action: string) => ({
33
- action,
34
- estimatedImpact: 5,
35
- priority: 'medium',
36
- })),
37
- };
38
-
39
- if (options.output === 'json') {
40
- return scoring;
41
- }
42
-
43
- const { summary } = report;
44
- const ratingColors: Record<string, (s: string) => string> = {
45
- minimal: chalk.green,
46
- low: chalk.cyan,
47
- moderate: chalk.yellow,
48
- high: chalk.red,
49
- severe: chalk.bgRed.white,
50
- };
51
- const color = ratingColors[summary.rating] ?? chalk.white;
52
- console.log(
53
- ` šŸ“ Documentation Drift: ${chalk.bold(100 - scoring.score + '/100 health')} (${color(summary.rating)} risk)`
54
- );
55
- if (report.issues.length > 0) {
56
- console.log(chalk.dim(` Found ${report.issues.length} drift issues.`));
57
- } else {
58
- console.log(chalk.dim(` No documentation drift detected.`));
59
- }
60
-
61
- return scoring;
62
- }
1
+ export { docDriftAction } from './shared/standard-tool-actions';
@@ -6,6 +6,7 @@ import { ToolName } from '@aiready/core';
6
6
  export async function initAction(options: {
7
7
  force?: boolean;
8
8
  format?: 'json' | 'js';
9
+ full?: boolean;
9
10
  }) {
10
11
  const fileExt = options.format === 'js' ? 'js' : 'json';
11
12
  const fileName = fileExt === 'js' ? 'aiready.config.js' : 'aiready.json';
@@ -18,7 +19,7 @@ export async function initAction(options: {
18
19
  process.exit(1);
19
20
  }
20
21
 
21
- const defaultConfig = {
22
+ const baseConfig = {
22
23
  scan: {
23
24
  include: [
24
25
  'src/**/*.ts',
@@ -49,28 +50,87 @@ export async function initAction(options: {
49
50
  [ToolName.PatternDetect]: {
50
51
  minSimilarity: 0.8,
51
52
  minLines: 5,
53
+ ...(options.full
54
+ ? {
55
+ batchSize: 50,
56
+ approx: true,
57
+ minSharedTokens: 10,
58
+ maxCandidatesPerBlock: 100,
59
+ }
60
+ : {}),
52
61
  },
53
62
  [ToolName.ContextAnalyzer]: {
54
63
  maxContextBudget: 128000,
55
64
  minCohesion: 0.6,
65
+ ...(options.full
66
+ ? {
67
+ maxDepth: 7,
68
+ maxFragmentation: 0.4,
69
+ focus: 'all',
70
+ includeNodeModules: false,
71
+ }
72
+ : {}),
56
73
  },
57
74
  [ToolName.NamingConsistency]: {
58
75
  shortWords: ['id', 'db', 'ui', 'ai'],
76
+ ...(options.full
77
+ ? { acceptedAbbreviations: [], disableChecks: [] }
78
+ : {}),
59
79
  },
60
80
  [ToolName.AiSignalClarity]: {
61
81
  checkMagicLiterals: true,
62
82
  checkBooleanTraps: true,
63
83
  checkAmbiguousNames: true,
64
84
  checkUndocumentedExports: true,
85
+ ...(options.full
86
+ ? { checkImplicitSideEffects: false, checkDeepCallbacks: false }
87
+ : {}),
65
88
  },
89
+ ...(options.full
90
+ ? {
91
+ [ToolName.AgentGrounding]: {
92
+ maxRecommendedDepth: 5,
93
+ readmeStaleDays: 30,
94
+ },
95
+ [ToolName.TestabilityIndex]: {
96
+ minCoverageRatio: 0.7,
97
+ testPatterns: ['**/*.test.ts', '**/__tests__/**'],
98
+ },
99
+ [ToolName.DocDrift]: {
100
+ maxCommits: 50,
101
+ staleMonths: 3,
102
+ },
103
+ [ToolName.DependencyHealth]: {
104
+ trainingCutoffYear: 2023,
105
+ },
106
+ }
107
+ : {}),
66
108
  },
67
109
  scoring: {
68
110
  threshold: 70,
69
111
  showBreakdown: true,
112
+ ...(options.full ? { profile: 'default' } : {}),
70
113
  },
114
+ ...(options.full
115
+ ? {
116
+ output: {
117
+ format: fileExt,
118
+ file: 'aiready-report.json',
119
+ },
120
+ visualizer: {
121
+ groupingDirs: ['packages', 'src', 'lib'],
122
+ graph: {
123
+ maxNodes: 5000,
124
+ maxEdges: 10000,
125
+ },
126
+ },
127
+ }
128
+ : {}),
71
129
  };
72
130
 
73
- let content = '';
131
+ const defaultConfig = baseConfig;
132
+
133
+ let content: string;
74
134
  if (fileExt === 'js') {
75
135
  content = `/** @type {import('@aiready/core').AIReadyConfig} */\nmodule.exports = ${JSON.stringify(
76
136
  defaultConfig,
@@ -151,7 +151,7 @@ export async function scanAction(directory: string, options: ScanOptions) {
151
151
  )) as any;
152
152
 
153
153
  // Apply smart defaults for pattern detection if requested
154
- let finalOptions = { ...baseOptions };
154
+ const finalOptions = { ...baseOptions };
155
155
  if (
156
156
  baseOptions.tools.includes(ToolName.PatternDetect) ||
157
157
  baseOptions.tools.includes('patterns')
@@ -469,20 +469,20 @@ export async function scanAction(directory: string, options: ScanOptions) {
469
469
  }
470
470
  await warnIfGraphCapExceeded(outputData, resolvedDir);
471
471
 
472
- // CI/CD Gatekeeper logic
473
- const isCI = options.ci || process.env.CI === 'true';
474
- if (isCI && scoringResult) {
472
+ // Score Check & Gatekeeper logic
473
+ if (scoringResult) {
475
474
  const threshold = options.threshold
476
475
  ? parseInt(options.threshold)
477
476
  : undefined;
478
477
  const failOnLevel = options.failOn || 'critical';
478
+ const isCI = options.ci || process.env.CI === 'true';
479
479
 
480
480
  let shouldFail = false;
481
481
  let failReason = '';
482
482
 
483
- // Emit annotations for all issues found
483
+ // Emit annotations only in CI
484
484
  const report = mapToUnifiedReport(results, scoringResult);
485
- if (report.results && report.results.length > 0) {
485
+ if (isCI && report.results && report.results.length > 0) {
486
486
  console.log(
487
487
  chalk.cyan(
488
488
  `\nšŸ“ Emitting GitHub Action annotations for ${report.results.length} issues...`
@@ -495,6 +495,9 @@ export async function scanAction(directory: string, options: ScanOptions) {
495
495
  shouldFail = true;
496
496
  failReason = `Score ${scoringResult.overall} < threshold ${threshold}`;
497
497
  }
498
+
499
+ // If failOnLevel is set (default 'critical'), check for issues
500
+ // But only fail if not 'none'
498
501
  if (failOnLevel !== 'none') {
499
502
  if (failOnLevel === 'critical' && report.summary.criticalIssues > 0) {
500
503
  shouldFail = true;
@@ -509,10 +512,10 @@ export async function scanAction(directory: string, options: ScanOptions) {
509
512
  }
510
513
 
511
514
  if (shouldFail) {
512
- console.log(chalk.red(`\n🚫 PR BLOCKED: ${failReason}`));
515
+ console.log(chalk.red(`\n🚫 SCAN FAILED: ${failReason}`));
513
516
  process.exit(1);
514
517
  } else {
515
- console.log(chalk.green('\nāœ… PR PASSED'));
518
+ console.log(chalk.green('\nāœ… SCAN PASSED'));
516
519
  }
517
520
  }
518
521
  } catch (error) {
@@ -0,0 +1,35 @@
1
+ import type { ToolScoringOutput } from '@aiready/core';
2
+ import { runConfiguredToolCommand } from '../../utils/helpers';
3
+
4
+ export interface ConfiguredToolActionConfig<TReport> {
5
+ defaults: Record<string, unknown>;
6
+ analyze: (scanOptions: any) => Promise<TReport>;
7
+ getExtras: (
8
+ options: any,
9
+ merged: Record<string, unknown>
10
+ ) => Record<string, unknown>;
11
+ score: (report: TReport) => ToolScoringOutput;
12
+ render: (report: TReport, scoring: ToolScoringOutput) => void;
13
+ }
14
+
15
+ export async function runConfiguredToolAction<TReport>(
16
+ directory: string,
17
+ options: any,
18
+ config: ConfiguredToolActionConfig<TReport>
19
+ ): Promise<ToolScoringOutput | undefined> {
20
+ const { report, scoring } = await runConfiguredToolCommand({
21
+ directory,
22
+ options,
23
+ defaults: config.defaults,
24
+ analyze: config.analyze,
25
+ getExtras: config.getExtras,
26
+ score: config.score,
27
+ });
28
+
29
+ if (options.output === 'json') {
30
+ return scoring;
31
+ }
32
+
33
+ config.render(report, scoring);
34
+ return scoring;
35
+ }