ios-app-review-plugin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +42 -0
- package/.github/actions/ios-review/action.yml +106 -0
- package/.github/workflows/ci.yml +103 -0
- package/.github/workflows/publish.yml +57 -0
- package/CHANGELOG.md +66 -0
- package/CONTRIBUTING.md +175 -0
- package/LICENSE +21 -0
- package/README.md +205 -0
- package/bitrise/step.sh +128 -0
- package/bitrise/step.yml +101 -0
- package/dist/analyzer.d.ts.map +1 -0
- package/dist/analyzers/asc-iap.d.ts.map +1 -0
- package/dist/analyzers/asc-metadata.d.ts.map +1 -0
- package/dist/analyzers/asc-screenshots.d.ts.map +1 -0
- package/dist/analyzers/asc-version.d.ts.map +1 -0
- package/dist/analyzers/code-scanner.d.ts.map +1 -0
- package/dist/analyzers/deprecated-api.d.ts.map +1 -0
- package/dist/analyzers/entitlements.d.ts.map +1 -0
- package/dist/analyzers/index.d.ts.map +1 -0
- package/dist/analyzers/info-plist.d.ts.map +1 -0
- package/dist/analyzers/privacy.d.ts.map +1 -0
- package/dist/analyzers/private-api.d.ts.map +1 -0
- package/dist/analyzers/security.d.ts.map +1 -0
- package/dist/analyzers/ui-ux.d.ts.map +1 -0
- package/dist/asc/auth.d.ts.map +1 -0
- package/dist/asc/client.d.ts.map +1 -0
- package/dist/asc/endpoints/apps.d.ts.map +1 -0
- package/dist/asc/endpoints/iap.d.ts.map +1 -0
- package/dist/asc/endpoints/screenshots.d.ts.map +1 -0
- package/dist/asc/endpoints/versions.d.ts.map +1 -0
- package/dist/asc/errors.d.ts.map +1 -0
- package/dist/asc/index.d.ts.map +1 -0
- package/dist/asc/types.d.ts.map +1 -0
- package/dist/badge/generator.d.ts.map +1 -0
- package/dist/badge/index.d.ts.map +1 -0
- package/dist/badge/types.d.ts.map +1 -0
- package/dist/cache/file-cache.d.ts.map +1 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cli/commands/help.d.ts.map +1 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/version.d.ts.map +1 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/git/diff.d.ts.map +1 -0
- package/dist/git/index.d.ts.map +1 -0
- package/dist/git/types.d.ts.map +1 -0
- package/dist/guidelines/database.d.ts.map +1 -0
- package/dist/guidelines/index.d.ts.map +1 -0
- package/dist/guidelines/matcher.d.ts.map +1 -0
- package/dist/guidelines/types.d.ts.map +1 -0
- package/dist/history/comparator.d.ts.map +1 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/history/types.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +994 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/plist.d.ts.map +1 -0
- package/dist/parsers/xcodeproj.d.ts.map +1 -0
- package/dist/progress/index.d.ts.map +1 -0
- package/dist/progress/reporter.d.ts.map +1 -0
- package/dist/progress/types.d.ts.map +1 -0
- package/dist/reports/html.d.ts.map +1 -0
- package/dist/reports/index.d.ts.map +1 -0
- package/dist/reports/json.d.ts.map +1 -0
- package/dist/reports/markdown.d.ts.map +1 -0
- package/dist/reports/types.d.ts.map +1 -0
- package/dist/rules/engine.d.ts.map +1 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/loader.d.ts.map +1 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/docs/ANALYZERS.md +237 -0
- package/docs/API.md +308 -0
- package/docs/BADGES.md +130 -0
- package/docs/CI_CD.md +283 -0
- package/docs/CLI.md +140 -0
- package/docs/REPORTS.md +212 -0
- package/docs/ROADMAP.md +267 -0
- package/docs/RULES.md +182 -0
- package/docs/SECURITY.md +89 -0
- package/docs/TROUBLESHOOTING.md +227 -0
- package/docs/tutorials/ASC_SETUP.md +188 -0
- package/docs/tutorials/CI_INTEGRATION.md +292 -0
- package/docs/tutorials/CUSTOM_RULES.md +291 -0
- package/docs/tutorials/GETTING_STARTED.md +226 -0
- package/docs/video-scripts/01-introduction.md +106 -0
- package/docs/video-scripts/02-cli-usage.md +120 -0
- package/docs/video-scripts/03-ci-integration.md +198 -0
- package/eslint.config.js +33 -0
- package/examples/.ios-review-rules.json +82 -0
- package/examples/bitrise-workflow.yml +129 -0
- package/examples/fastlane-lane.rb +71 -0
- package/examples/github-action.yml +147 -0
- package/fastlane/Fastfile.example +114 -0
- package/fastlane/README.md +99 -0
- package/jest.config.js +36 -0
- package/package.json +65 -0
- package/scripts/benchmark.ts +112 -0
- package/scripts/debug-parser.ts +37 -0
- package/scripts/debug-pbxproj.ts +36 -0
- package/scripts/debug-specific.ts +47 -0
- package/scripts/test-analyze.ts +67 -0
- package/scripts/xcode-cloud-review.sh +167 -0
- package/src/analyzer.ts +227 -0
- package/src/analyzers/asc-iap.ts +300 -0
- package/src/analyzers/asc-metadata.ts +326 -0
- package/src/analyzers/asc-screenshots.ts +310 -0
- package/src/analyzers/asc-version.ts +368 -0
- package/src/analyzers/code-scanner.ts +408 -0
- package/src/analyzers/deprecated-api.ts +390 -0
- package/src/analyzers/entitlements.ts +345 -0
- package/src/analyzers/index.ts +12 -0
- package/src/analyzers/info-plist.ts +409 -0
- package/src/analyzers/privacy.ts +376 -0
- package/src/analyzers/private-api.ts +377 -0
- package/src/analyzers/security.ts +327 -0
- package/src/analyzers/ui-ux.ts +509 -0
- package/src/asc/auth.ts +204 -0
- package/src/asc/client.ts +258 -0
- package/src/asc/endpoints/apps.ts +115 -0
- package/src/asc/endpoints/iap.ts +171 -0
- package/src/asc/endpoints/screenshots.ts +164 -0
- package/src/asc/endpoints/versions.ts +174 -0
- package/src/asc/errors.ts +109 -0
- package/src/asc/index.ts +108 -0
- package/src/asc/types.ts +369 -0
- package/src/badge/generator.ts +48 -0
- package/src/badge/index.ts +2 -0
- package/src/badge/types.ts +5 -0
- package/src/cache/file-cache.ts +75 -0
- package/src/cache/index.ts +2 -0
- package/src/cache/types.ts +10 -0
- package/src/cli/commands/help.ts +41 -0
- package/src/cli/commands/scan.ts +44 -0
- package/src/cli/commands/version.ts +12 -0
- package/src/cli/index.ts +92 -0
- package/src/cli/types.ts +17 -0
- package/src/git/diff.ts +21 -0
- package/src/git/index.ts +2 -0
- package/src/git/types.ts +5 -0
- package/src/guidelines/database.ts +344 -0
- package/src/guidelines/index.ts +4 -0
- package/src/guidelines/matcher.ts +84 -0
- package/src/guidelines/types.ts +28 -0
- package/src/history/comparator.ts +114 -0
- package/src/history/index.ts +3 -0
- package/src/history/store.ts +135 -0
- package/src/history/types.ts +40 -0
- package/src/index.ts +1113 -0
- package/src/parsers/index.ts +3 -0
- package/src/parsers/plist.ts +253 -0
- package/src/parsers/xcodeproj.ts +265 -0
- package/src/progress/index.ts +2 -0
- package/src/progress/reporter.ts +65 -0
- package/src/progress/types.ts +9 -0
- package/src/reports/html.ts +322 -0
- package/src/reports/index.ts +20 -0
- package/src/reports/json.ts +92 -0
- package/src/reports/markdown.ts +187 -0
- package/src/reports/types.ts +26 -0
- package/src/rules/engine.ts +121 -0
- package/src/rules/index.ts +3 -0
- package/src/rules/loader.ts +83 -0
- package/src/rules/types.ts +25 -0
- package/src/types/index.ts +247 -0
- package/tests/analyzer.test.ts +142 -0
- package/tests/analyzers/asc-iap.test.ts +228 -0
- package/tests/analyzers/asc-metadata.test.ts +210 -0
- package/tests/analyzers/asc-screenshots.test.ts +135 -0
- package/tests/analyzers/asc-version.test.ts +259 -0
- package/tests/analyzers/code-scanner.test.ts +745 -0
- package/tests/analyzers/deprecated-api.test.ts +286 -0
- package/tests/analyzers/entitlements.test.ts +411 -0
- package/tests/analyzers/info-plist.test.ts +148 -0
- package/tests/analyzers/privacy.test.ts +623 -0
- package/tests/analyzers/private-api.test.ts +255 -0
- package/tests/analyzers/security.test.ts +300 -0
- package/tests/analyzers/ui-ux.test.ts +357 -0
- package/tests/asc/auth.test.ts +189 -0
- package/tests/asc/client.test.ts +207 -0
- package/tests/asc/endpoints.test.ts +1359 -0
- package/tests/badge/generator.test.ts +73 -0
- package/tests/cache/file-cache.test.ts +124 -0
- package/tests/cli/cli-index.test.ts +510 -0
- package/tests/cli/commands.test.ts +67 -0
- package/tests/cli/scan.test.ts +152 -0
- package/tests/git/diff.test.ts +69 -0
- package/tests/guidelines/matcher.test.ts +209 -0
- package/tests/history/comparator.test.ts +272 -0
- package/tests/history/store.test.ts +200 -0
- package/tests/integration/cli.test.ts +95 -0
- package/tests/integration/e2e.test.ts +130 -0
- package/tests/parsers/plist.test.ts +240 -0
- package/tests/parsers/xcodeproj.test.ts +289 -0
- package/tests/progress/reporter.test.ts +117 -0
- package/tests/reports/html.test.ts +176 -0
- package/tests/reports/json.test.ts +235 -0
- package/tests/reports/markdown.test.ts +196 -0
- package/tests/rules/engine.test.ts +229 -0
- package/tests/rules/loader.test.ts +187 -0
- package/tests/setup.ts +15 -0
- package/tsconfig.json +27 -0
- package/tsconfig.test.json +9 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Issue } from '../types/index.js';
|
|
2
|
+
import type { ScanRecord, HistoricalComparison, TrendReport } from './types.js';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
export class ScanComparator {
|
|
6
|
+
/**
|
|
7
|
+
* Create a fingerprint for an issue that is stable across scans.
|
|
8
|
+
* Uses id + category + relative file path (excluding line number which can shift).
|
|
9
|
+
*/
|
|
10
|
+
fingerprint(issue: Issue, projectPath: string): string {
|
|
11
|
+
const relativePath = issue.filePath
|
|
12
|
+
? path.relative(projectPath, issue.filePath)
|
|
13
|
+
: '';
|
|
14
|
+
return `${issue.id}::${issue.category}::${relativePath}`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
compare(previous: ScanRecord, current: ScanRecord): HistoricalComparison {
|
|
18
|
+
const prevFingerprints = new Set<string>();
|
|
19
|
+
const currFingerprints = new Set<string>();
|
|
20
|
+
|
|
21
|
+
for (const result of previous.report.results) {
|
|
22
|
+
for (const issue of result.issues) {
|
|
23
|
+
prevFingerprints.add(this.fingerprint(issue, previous.projectPath));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (const result of current.report.results) {
|
|
28
|
+
for (const issue of result.issues) {
|
|
29
|
+
currFingerprints.add(this.fingerprint(issue, current.projectPath));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const newIssues: string[] = [];
|
|
34
|
+
const resolvedIssues: string[] = [];
|
|
35
|
+
const ongoingIssues: string[] = [];
|
|
36
|
+
|
|
37
|
+
for (const fp of currFingerprints) {
|
|
38
|
+
if (prevFingerprints.has(fp)) {
|
|
39
|
+
ongoingIssues.push(fp);
|
|
40
|
+
} else {
|
|
41
|
+
newIssues.push(fp);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const fp of prevFingerprints) {
|
|
46
|
+
if (!currFingerprints.has(fp)) {
|
|
47
|
+
resolvedIssues.push(fp);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const scoreDelta = current.score - previous.score;
|
|
52
|
+
let trend: 'improving' | 'declining' | 'stable';
|
|
53
|
+
if (scoreDelta > 2) {
|
|
54
|
+
trend = 'improving';
|
|
55
|
+
} else if (scoreDelta < -2) {
|
|
56
|
+
trend = 'declining';
|
|
57
|
+
} else {
|
|
58
|
+
trend = 'stable';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
previousScan: previous,
|
|
63
|
+
currentScan: current,
|
|
64
|
+
newIssues,
|
|
65
|
+
resolvedIssues,
|
|
66
|
+
ongoingIssues,
|
|
67
|
+
scoreDelta,
|
|
68
|
+
trend,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
analyzeTrend(scans: ScanRecord[]): TrendReport {
|
|
73
|
+
const scanData = scans.map((s) => ({
|
|
74
|
+
id: s.id,
|
|
75
|
+
timestamp: s.timestamp,
|
|
76
|
+
score: s.score,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
const scores = scanData.map((s) => s.score);
|
|
80
|
+
|
|
81
|
+
// Determine trend from last 3 scores
|
|
82
|
+
let overallTrend: 'improving' | 'declining' | 'stable' = 'stable';
|
|
83
|
+
if (scores.length >= 3) {
|
|
84
|
+
const recent = scores.slice(-3);
|
|
85
|
+
const first = recent[0]!;
|
|
86
|
+
const last = recent[recent.length - 1]!;
|
|
87
|
+
const delta = last - first;
|
|
88
|
+
if (delta > 5) {
|
|
89
|
+
overallTrend = 'improving';
|
|
90
|
+
} else if (delta < -5) {
|
|
91
|
+
overallTrend = 'declining';
|
|
92
|
+
}
|
|
93
|
+
} else if (scores.length >= 2) {
|
|
94
|
+
const first = scores[0]!;
|
|
95
|
+
const last = scores[scores.length - 1]!;
|
|
96
|
+
const delta = last - first;
|
|
97
|
+
if (delta > 2) {
|
|
98
|
+
overallTrend = 'improving';
|
|
99
|
+
} else if (delta < -2) {
|
|
100
|
+
overallTrend = 'declining';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sum = scores.reduce((a, b) => a + b, 0);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
scans: scanData,
|
|
108
|
+
overallTrend,
|
|
109
|
+
averageScore: scores.length > 0 ? Math.round(sum / scores.length) : 0,
|
|
110
|
+
bestScore: scores.length > 0 ? Math.max(...scores) : 0,
|
|
111
|
+
worstScore: scores.length > 0 ? Math.min(...scores) : 0,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as fs from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import * as crypto from 'crypto';
|
|
5
|
+
import type { AnalysisReport } from '../types/index.js';
|
|
6
|
+
import type { ScanRecord, ScanIndex } from './types.js';
|
|
7
|
+
|
|
8
|
+
export class HistoryStore {
|
|
9
|
+
private baseDir: string;
|
|
10
|
+
|
|
11
|
+
constructor(projectPath: string) {
|
|
12
|
+
this.baseDir = path.join(projectPath, '.ios-review-history');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private get scansDir(): string {
|
|
16
|
+
return path.join(this.baseDir, 'scans');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private get indexPath(): string {
|
|
20
|
+
return path.join(this.baseDir, 'index.json');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
private async ensureDirs(): Promise<void> {
|
|
24
|
+
await fs.mkdir(this.scansDir, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private async loadIndex(): Promise<ScanIndex> {
|
|
28
|
+
try {
|
|
29
|
+
const data = await fs.readFile(this.indexPath, 'utf-8');
|
|
30
|
+
return JSON.parse(data) as ScanIndex;
|
|
31
|
+
} catch {
|
|
32
|
+
return { scans: [] };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async saveIndex(index: ScanIndex): Promise<void> {
|
|
37
|
+
await fs.writeFile(this.indexPath, JSON.stringify(index, null, 2));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private getGitInfo(projectPath: string): { commit?: string; branch?: string } {
|
|
41
|
+
try {
|
|
42
|
+
const commit = execSync('git rev-parse HEAD', { cwd: projectPath, encoding: 'utf-8' }).trim();
|
|
43
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', { cwd: projectPath, encoding: 'utf-8' }).trim();
|
|
44
|
+
return { commit, branch };
|
|
45
|
+
} catch {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async saveScan(report: AnalysisReport, score: number): Promise<ScanRecord> {
|
|
51
|
+
await this.ensureDirs();
|
|
52
|
+
|
|
53
|
+
const id = crypto.randomUUID();
|
|
54
|
+
const gitInfo = this.getGitInfo(report.projectPath);
|
|
55
|
+
|
|
56
|
+
const record: ScanRecord = {
|
|
57
|
+
id,
|
|
58
|
+
timestamp: report.timestamp,
|
|
59
|
+
projectPath: report.projectPath,
|
|
60
|
+
gitCommit: gitInfo.commit,
|
|
61
|
+
gitBranch: gitInfo.branch,
|
|
62
|
+
report,
|
|
63
|
+
score,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Save scan file
|
|
67
|
+
const scanPath = path.join(this.scansDir, `${id}.json`);
|
|
68
|
+
await fs.writeFile(scanPath, JSON.stringify(record, null, 2));
|
|
69
|
+
|
|
70
|
+
// Update index
|
|
71
|
+
const index = await this.loadIndex();
|
|
72
|
+
index.scans.push({
|
|
73
|
+
id,
|
|
74
|
+
timestamp: record.timestamp,
|
|
75
|
+
projectPath: record.projectPath,
|
|
76
|
+
score,
|
|
77
|
+
gitCommit: gitInfo.commit,
|
|
78
|
+
gitBranch: gitInfo.branch,
|
|
79
|
+
});
|
|
80
|
+
await this.saveIndex(index);
|
|
81
|
+
|
|
82
|
+
return record;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getLatestScan(): Promise<ScanRecord | null> {
|
|
86
|
+
const index = await this.loadIndex();
|
|
87
|
+
if (index.scans.length === 0) return null;
|
|
88
|
+
|
|
89
|
+
const latest = index.scans[index.scans.length - 1];
|
|
90
|
+
if (!latest) return null;
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const data = await fs.readFile(path.join(this.scansDir, `${latest.id}.json`), 'utf-8');
|
|
94
|
+
return JSON.parse(data) as ScanRecord;
|
|
95
|
+
} catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getScan(id: string): Promise<ScanRecord | null> {
|
|
101
|
+
try {
|
|
102
|
+
const data = await fs.readFile(path.join(this.scansDir, `${id}.json`), 'utf-8');
|
|
103
|
+
return JSON.parse(data) as ScanRecord;
|
|
104
|
+
} catch {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async listScans(limit?: number): Promise<ScanIndex['scans']> {
|
|
110
|
+
const index = await this.loadIndex();
|
|
111
|
+
const scans = index.scans.slice().reverse(); // newest first
|
|
112
|
+
return limit ? scans.slice(0, limit) : scans;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async pruneHistory(keepCount: number): Promise<number> {
|
|
116
|
+
const index = await this.loadIndex();
|
|
117
|
+
if (index.scans.length <= keepCount) return 0;
|
|
118
|
+
|
|
119
|
+
const toRemove = index.scans.slice(0, index.scans.length - keepCount);
|
|
120
|
+
const kept = index.scans.slice(index.scans.length - keepCount);
|
|
121
|
+
|
|
122
|
+
for (const scan of toRemove) {
|
|
123
|
+
try {
|
|
124
|
+
await fs.unlink(path.join(this.scansDir, `${scan.id}.json`));
|
|
125
|
+
} catch {
|
|
126
|
+
// Ignore missing files
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
index.scans = kept;
|
|
131
|
+
await this.saveIndex(index);
|
|
132
|
+
|
|
133
|
+
return toRemove.length;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { AnalysisReport } from '../types/index.js';
|
|
2
|
+
|
|
3
|
+
export interface ScanRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
projectPath: string;
|
|
7
|
+
gitCommit?: string | undefined;
|
|
8
|
+
gitBranch?: string | undefined;
|
|
9
|
+
report: AnalysisReport;
|
|
10
|
+
score: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HistoricalComparison {
|
|
14
|
+
previousScan: ScanRecord;
|
|
15
|
+
currentScan: ScanRecord;
|
|
16
|
+
newIssues: string[]; // fingerprints
|
|
17
|
+
resolvedIssues: string[]; // fingerprints
|
|
18
|
+
ongoingIssues: string[]; // fingerprints
|
|
19
|
+
scoreDelta: number;
|
|
20
|
+
trend: 'improving' | 'declining' | 'stable';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TrendReport {
|
|
24
|
+
scans: Array<{ id: string; timestamp: string; score: number }>;
|
|
25
|
+
overallTrend: 'improving' | 'declining' | 'stable';
|
|
26
|
+
averageScore: number;
|
|
27
|
+
bestScore: number;
|
|
28
|
+
worstScore: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ScanIndex {
|
|
32
|
+
scans: Array<{
|
|
33
|
+
id: string;
|
|
34
|
+
timestamp: string;
|
|
35
|
+
projectPath: string;
|
|
36
|
+
score: number;
|
|
37
|
+
gitCommit?: string | undefined;
|
|
38
|
+
gitBranch?: string | undefined;
|
|
39
|
+
}>;
|
|
40
|
+
}
|