@ipation/specbridge 1.1.2 → 1.2.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/CHANGELOG.md +90 -0
- package/dist/cli.js +999 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +116 -1
- package/dist/index.js +288 -0
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -1718,6 +1718,121 @@ declare function formatConsoleReport(report: ComplianceReport): string;
|
|
|
1718
1718
|
*/
|
|
1719
1719
|
declare function formatMarkdownReport(report: ComplianceReport): string;
|
|
1720
1720
|
|
|
1721
|
+
interface StoredReport {
|
|
1722
|
+
timestamp: string;
|
|
1723
|
+
report: ComplianceReport;
|
|
1724
|
+
}
|
|
1725
|
+
/**
|
|
1726
|
+
* ReportStorage - Handles persistence and retrieval of historical reports
|
|
1727
|
+
*/
|
|
1728
|
+
declare class ReportStorage {
|
|
1729
|
+
private storageDir;
|
|
1730
|
+
constructor(basePath: string);
|
|
1731
|
+
/**
|
|
1732
|
+
* Save a compliance report to storage
|
|
1733
|
+
*/
|
|
1734
|
+
save(report: ComplianceReport): Promise<string>;
|
|
1735
|
+
/**
|
|
1736
|
+
* Load the most recent report
|
|
1737
|
+
*/
|
|
1738
|
+
loadLatest(): Promise<StoredReport | null>;
|
|
1739
|
+
/**
|
|
1740
|
+
* Load historical reports for the specified number of days
|
|
1741
|
+
*/
|
|
1742
|
+
loadHistory(days?: number): Promise<StoredReport[]>;
|
|
1743
|
+
/**
|
|
1744
|
+
* Load a specific report by date
|
|
1745
|
+
*/
|
|
1746
|
+
loadByDate(date: string): Promise<ComplianceReport | null>;
|
|
1747
|
+
/**
|
|
1748
|
+
* Get all available report dates
|
|
1749
|
+
*/
|
|
1750
|
+
getAvailableDates(): Promise<string[]>;
|
|
1751
|
+
/**
|
|
1752
|
+
* Clear old reports (keep only the most recent N days)
|
|
1753
|
+
*/
|
|
1754
|
+
cleanup(keepDays?: number): Promise<number>;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
/**
|
|
1758
|
+
* Drift detection - Analyze compliance trends between reports
|
|
1759
|
+
*/
|
|
1760
|
+
|
|
1761
|
+
type TrendDirection = 'improving' | 'stable' | 'degrading';
|
|
1762
|
+
interface DriftAnalysis {
|
|
1763
|
+
decisionId: string;
|
|
1764
|
+
title: string;
|
|
1765
|
+
trend: TrendDirection;
|
|
1766
|
+
complianceChange: number;
|
|
1767
|
+
newViolations: number;
|
|
1768
|
+
fixedViolations: number;
|
|
1769
|
+
currentCompliance: number;
|
|
1770
|
+
previousCompliance: number;
|
|
1771
|
+
}
|
|
1772
|
+
interface OverallDrift {
|
|
1773
|
+
trend: TrendDirection;
|
|
1774
|
+
complianceChange: number;
|
|
1775
|
+
summary: {
|
|
1776
|
+
newViolations: {
|
|
1777
|
+
critical: number;
|
|
1778
|
+
high: number;
|
|
1779
|
+
medium: number;
|
|
1780
|
+
low: number;
|
|
1781
|
+
total: number;
|
|
1782
|
+
};
|
|
1783
|
+
fixedViolations: {
|
|
1784
|
+
critical: number;
|
|
1785
|
+
high: number;
|
|
1786
|
+
medium: number;
|
|
1787
|
+
low: number;
|
|
1788
|
+
total: number;
|
|
1789
|
+
};
|
|
1790
|
+
};
|
|
1791
|
+
byDecision: DriftAnalysis[];
|
|
1792
|
+
mostImproved: DriftAnalysis[];
|
|
1793
|
+
mostDegraded: DriftAnalysis[];
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Detect drift between current and previous compliance reports
|
|
1797
|
+
*/
|
|
1798
|
+
declare function detectDrift(current: ComplianceReport, previous: ComplianceReport): Promise<OverallDrift>;
|
|
1799
|
+
/**
|
|
1800
|
+
* Analyze compliance trend over multiple reports
|
|
1801
|
+
*/
|
|
1802
|
+
interface TrendAnalysis {
|
|
1803
|
+
period: {
|
|
1804
|
+
start: string;
|
|
1805
|
+
end: string;
|
|
1806
|
+
days: number;
|
|
1807
|
+
};
|
|
1808
|
+
overall: {
|
|
1809
|
+
startCompliance: number;
|
|
1810
|
+
endCompliance: number;
|
|
1811
|
+
change: number;
|
|
1812
|
+
trend: TrendDirection;
|
|
1813
|
+
dataPoints: Array<{
|
|
1814
|
+
date: string;
|
|
1815
|
+
compliance: number;
|
|
1816
|
+
}>;
|
|
1817
|
+
};
|
|
1818
|
+
decisions: Array<{
|
|
1819
|
+
decisionId: string;
|
|
1820
|
+
title: string;
|
|
1821
|
+
startCompliance: number;
|
|
1822
|
+
endCompliance: number;
|
|
1823
|
+
change: number;
|
|
1824
|
+
trend: TrendDirection;
|
|
1825
|
+
dataPoints: Array<{
|
|
1826
|
+
date: string;
|
|
1827
|
+
compliance: number;
|
|
1828
|
+
}>;
|
|
1829
|
+
}>;
|
|
1830
|
+
}
|
|
1831
|
+
declare function analyzeTrend(reports: Array<{
|
|
1832
|
+
timestamp: string;
|
|
1833
|
+
report: ComplianceReport;
|
|
1834
|
+
}>): Promise<TrendAnalysis>;
|
|
1835
|
+
|
|
1721
1836
|
/**
|
|
1722
1837
|
* Agent context generator
|
|
1723
1838
|
*/
|
|
@@ -1905,4 +2020,4 @@ declare function matchesAnyPattern(filePath: string, patterns: string[], options
|
|
|
1905
2020
|
cwd?: string;
|
|
1906
2021
|
}): boolean;
|
|
1907
2022
|
|
|
1908
|
-
export { type AffectedFile, type AgentContext, AgentContextGenerator, AlreadyInitializedError, type Analyzer, AnalyzerNotFoundError, ApiVerifier, type ApplicableConstraint, type ApplicableDecision, AstCache, AutofixEngine, type AutofixPatch, type AutofixResult, CodeScanner, ComplexityVerifier, type ComplianceReport, ConfigError, type Constraint, type ConstraintException, ConstraintExceptionSchema, type ConstraintExceptionSchema_, ConstraintSchema, type ConstraintSchema_, type ConstraintType, ConstraintTypeSchema, type ConstraintTypeSchema_, type ContextOptions, type Decision, type DecisionCompliance, type DecisionContent, DecisionContentSchema, type DecisionContentSchema_, type DecisionFilter, type DecisionMetadata, DecisionMetadataSchema, type DecisionMetadataSchema_, DecisionNotFoundError, DecisionSchema, type DecisionStatus, DecisionStatusSchema, type DecisionStatusSchema_, type DecisionTypeSchema, DecisionValidationError, type DependencyGraph, DependencyVerifier, ErrorsAnalyzer, ErrorsVerifier, FileSystemError, type GlobOptions, type GraphNode, HookError, type ImpactAnalysis, ImportsAnalyzer, ImportsVerifier, InferenceEngine, InferenceError, type InferenceOptions, type InferenceResult, type LevelConfig, LinksSchema, type LoadError, type LoadResult, type LoadedDecision, type McpServerOptions, type MigrationStep, NamingAnalyzer, NamingVerifier, NotInitializedError, type Pattern, type PatternExample, type PromptTemplate, PropagationEngine, type PropagationOptions, RegexVerifier, Registry, type RegistryConstraintMatch, RegistryError, type RegistryOptions, type ReportOptions, Reporter, type ScanOptions, type ScanResult, type ScannedFile, SecurityVerifier, type Severity, SeveritySchema, type SeveritySchema_, type SpecBridgeConfig, SpecBridgeConfigSchema, type SpecBridgeConfigType, SpecBridgeError, SpecBridgeMcpServer, StructureAnalyzer, type TextEdit, type TrendData, type VerificationConfig, VerificationConfigSchema, type VerificationConfigSchema_, type VerificationContext, VerificationEngine, VerificationError, type VerificationFrequency, VerificationFrequencySchema, type VerificationFrequencySchema_, type VerificationLevel, type VerificationOptions, type VerificationResult, type Verifier, VerifierNotFoundError, type Violation, type ViolationFix, buildDependencyGraph, builtinAnalyzers, builtinVerifiers, calculateConfidence, checkDegradation, createInferenceEngine, createPattern, createPropagationEngine, createRegistry, createScannerFromConfig, createVerificationEngine, createViolation, defaultConfig, ensureDir, extractSnippet, formatConsoleReport, formatContextAsJson, formatContextAsMarkdown, formatContextAsMcp, formatError, formatMarkdownReport, formatValidationErrors, generateContext, generateFormattedContext, generateReport, getAffectedFiles, getAffectingDecisions, getAnalyzer, getAnalyzerIds, getChangedFiles, getConfigPath, getDecisionsDir, getInferredDir, getReportsDir, getSpecBridgeDir, getTransitiveDependencies, getVerifier, getVerifierIds, getVerifiersDir, glob, isConstraintExcepted, isDirectory, loadConfig, loadDecisionFile, loadDecisionsFromDir, matchesAnyPattern, matchesPattern, mergeWithDefaults, normalizePath, parseYaml, parseYamlDocument, pathExists, readFilesInDir, readTextFile, runInference, selectVerifierForConstraint, shouldApplyConstraintToFile, stringifyYaml, templates, updateYamlDocument, validateConfig, validateDecision, validateDecisionFile, writeTextFile };
|
|
2023
|
+
export { type AffectedFile, type AgentContext, AgentContextGenerator, AlreadyInitializedError, type Analyzer, AnalyzerNotFoundError, ApiVerifier, type ApplicableConstraint, type ApplicableDecision, AstCache, AutofixEngine, type AutofixPatch, type AutofixResult, CodeScanner, ComplexityVerifier, type ComplianceReport, ConfigError, type Constraint, type ConstraintException, ConstraintExceptionSchema, type ConstraintExceptionSchema_, ConstraintSchema, type ConstraintSchema_, type ConstraintType, ConstraintTypeSchema, type ConstraintTypeSchema_, type ContextOptions, type Decision, type DecisionCompliance, type DecisionContent, DecisionContentSchema, type DecisionContentSchema_, type DecisionFilter, type DecisionMetadata, DecisionMetadataSchema, type DecisionMetadataSchema_, DecisionNotFoundError, DecisionSchema, type DecisionStatus, DecisionStatusSchema, type DecisionStatusSchema_, type DecisionTypeSchema, DecisionValidationError, type DependencyGraph, DependencyVerifier, type DriftAnalysis, ErrorsAnalyzer, ErrorsVerifier, FileSystemError, type GlobOptions, type GraphNode, HookError, type ImpactAnalysis, ImportsAnalyzer, ImportsVerifier, InferenceEngine, InferenceError, type InferenceOptions, type InferenceResult, type LevelConfig, LinksSchema, type LoadError, type LoadResult, type LoadedDecision, type McpServerOptions, type MigrationStep, NamingAnalyzer, NamingVerifier, NotInitializedError, type OverallDrift, type Pattern, type PatternExample, type PromptTemplate, PropagationEngine, type PropagationOptions, RegexVerifier, Registry, type RegistryConstraintMatch, RegistryError, type RegistryOptions, type ReportOptions, ReportStorage, Reporter, type ScanOptions, type ScanResult, type ScannedFile, SecurityVerifier, type Severity, SeveritySchema, type SeveritySchema_, type SpecBridgeConfig, SpecBridgeConfigSchema, type SpecBridgeConfigType, SpecBridgeError, SpecBridgeMcpServer, type StoredReport, StructureAnalyzer, type TextEdit, type TrendAnalysis, type TrendData, type TrendDirection, type VerificationConfig, VerificationConfigSchema, type VerificationConfigSchema_, type VerificationContext, VerificationEngine, VerificationError, type VerificationFrequency, VerificationFrequencySchema, type VerificationFrequencySchema_, type VerificationLevel, type VerificationOptions, type VerificationResult, type Verifier, VerifierNotFoundError, type Violation, type ViolationFix, analyzeTrend, buildDependencyGraph, builtinAnalyzers, builtinVerifiers, calculateConfidence, checkDegradation, createInferenceEngine, createPattern, createPropagationEngine, createRegistry, createScannerFromConfig, createVerificationEngine, createViolation, defaultConfig, detectDrift, ensureDir, extractSnippet, formatConsoleReport, formatContextAsJson, formatContextAsMarkdown, formatContextAsMcp, formatError, formatMarkdownReport, formatValidationErrors, generateContext, generateFormattedContext, generateReport, getAffectedFiles, getAffectingDecisions, getAnalyzer, getAnalyzerIds, getChangedFiles, getConfigPath, getDecisionsDir, getInferredDir, getReportsDir, getSpecBridgeDir, getTransitiveDependencies, getVerifier, getVerifierIds, getVerifiersDir, glob, isConstraintExcepted, isDirectory, loadConfig, loadDecisionFile, loadDecisionsFromDir, matchesAnyPattern, matchesPattern, mergeWithDefaults, normalizePath, parseYaml, parseYamlDocument, pathExists, readFilesInDir, readTextFile, runInference, selectVerifierForConstraint, shouldApplyConstraintToFile, stringifyYaml, templates, updateYamlDocument, validateConfig, validateDecision, validateDecisionFile, writeTextFile };
|
package/dist/index.js
CHANGED
|
@@ -3552,6 +3552,291 @@ function formatProgressBar(percentage) {
|
|
|
3552
3552
|
return `\`${filledChar.repeat(filled)}${emptyChar.repeat(empty)}\` ${percentage}%`;
|
|
3553
3553
|
}
|
|
3554
3554
|
|
|
3555
|
+
// src/reporting/storage.ts
|
|
3556
|
+
import { join as join3 } from "path";
|
|
3557
|
+
var ReportStorage = class {
|
|
3558
|
+
storageDir;
|
|
3559
|
+
constructor(basePath) {
|
|
3560
|
+
this.storageDir = join3(getSpecBridgeDir(basePath), "reports", "history");
|
|
3561
|
+
}
|
|
3562
|
+
/**
|
|
3563
|
+
* Save a compliance report to storage
|
|
3564
|
+
*/
|
|
3565
|
+
async save(report) {
|
|
3566
|
+
await ensureDir(this.storageDir);
|
|
3567
|
+
const date = new Date(report.timestamp).toISOString().split("T")[0];
|
|
3568
|
+
const filename = `report-${date}.json`;
|
|
3569
|
+
const filepath = join3(this.storageDir, filename);
|
|
3570
|
+
await writeTextFile(filepath, JSON.stringify(report, null, 2));
|
|
3571
|
+
return filepath;
|
|
3572
|
+
}
|
|
3573
|
+
/**
|
|
3574
|
+
* Load the most recent report
|
|
3575
|
+
*/
|
|
3576
|
+
async loadLatest() {
|
|
3577
|
+
if (!await pathExists(this.storageDir)) {
|
|
3578
|
+
return null;
|
|
3579
|
+
}
|
|
3580
|
+
const files = await readFilesInDir(this.storageDir);
|
|
3581
|
+
if (files.length === 0) {
|
|
3582
|
+
return null;
|
|
3583
|
+
}
|
|
3584
|
+
const sortedFiles = files.filter((f) => f.startsWith("report-") && f.endsWith(".json")).sort().reverse();
|
|
3585
|
+
if (sortedFiles.length === 0) {
|
|
3586
|
+
return null;
|
|
3587
|
+
}
|
|
3588
|
+
const latestFile = sortedFiles[0];
|
|
3589
|
+
if (!latestFile) {
|
|
3590
|
+
return null;
|
|
3591
|
+
}
|
|
3592
|
+
const content = await readTextFile(join3(this.storageDir, latestFile));
|
|
3593
|
+
const report = JSON.parse(content);
|
|
3594
|
+
return {
|
|
3595
|
+
timestamp: latestFile.replace("report-", "").replace(".json", ""),
|
|
3596
|
+
report
|
|
3597
|
+
};
|
|
3598
|
+
}
|
|
3599
|
+
/**
|
|
3600
|
+
* Load historical reports for the specified number of days
|
|
3601
|
+
*/
|
|
3602
|
+
async loadHistory(days = 30) {
|
|
3603
|
+
if (!await pathExists(this.storageDir)) {
|
|
3604
|
+
return [];
|
|
3605
|
+
}
|
|
3606
|
+
const files = await readFilesInDir(this.storageDir);
|
|
3607
|
+
const reportFiles = files.filter((f) => f.startsWith("report-") && f.endsWith(".json")).sort().reverse();
|
|
3608
|
+
const recentFiles = reportFiles.slice(0, days);
|
|
3609
|
+
const reports = [];
|
|
3610
|
+
for (const file of recentFiles) {
|
|
3611
|
+
try {
|
|
3612
|
+
const content = await readTextFile(join3(this.storageDir, file));
|
|
3613
|
+
const report = JSON.parse(content);
|
|
3614
|
+
const timestamp = file.replace("report-", "").replace(".json", "");
|
|
3615
|
+
reports.push({ timestamp, report });
|
|
3616
|
+
} catch (error) {
|
|
3617
|
+
console.warn(`Warning: Failed to load report ${file}:`, error);
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
return reports;
|
|
3621
|
+
}
|
|
3622
|
+
/**
|
|
3623
|
+
* Load a specific report by date
|
|
3624
|
+
*/
|
|
3625
|
+
async loadByDate(date) {
|
|
3626
|
+
const filepath = join3(this.storageDir, `report-${date}.json`);
|
|
3627
|
+
if (!await pathExists(filepath)) {
|
|
3628
|
+
return null;
|
|
3629
|
+
}
|
|
3630
|
+
const content = await readTextFile(filepath);
|
|
3631
|
+
return JSON.parse(content);
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* Get all available report dates
|
|
3635
|
+
*/
|
|
3636
|
+
async getAvailableDates() {
|
|
3637
|
+
if (!await pathExists(this.storageDir)) {
|
|
3638
|
+
return [];
|
|
3639
|
+
}
|
|
3640
|
+
const files = await readFilesInDir(this.storageDir);
|
|
3641
|
+
return files.filter((f) => f.startsWith("report-") && f.endsWith(".json")).map((f) => f.replace("report-", "").replace(".json", "")).sort().reverse();
|
|
3642
|
+
}
|
|
3643
|
+
/**
|
|
3644
|
+
* Clear old reports (keep only the most recent N days)
|
|
3645
|
+
*/
|
|
3646
|
+
async cleanup(keepDays = 90) {
|
|
3647
|
+
if (!await pathExists(this.storageDir)) {
|
|
3648
|
+
return 0;
|
|
3649
|
+
}
|
|
3650
|
+
const files = await readFilesInDir(this.storageDir);
|
|
3651
|
+
const reportFiles = files.filter((f) => f.startsWith("report-") && f.endsWith(".json")).sort().reverse();
|
|
3652
|
+
const filesToDelete = reportFiles.slice(keepDays);
|
|
3653
|
+
for (const file of filesToDelete) {
|
|
3654
|
+
try {
|
|
3655
|
+
const filepath = join3(this.storageDir, file);
|
|
3656
|
+
const fs = await import("fs/promises");
|
|
3657
|
+
await fs.unlink(filepath);
|
|
3658
|
+
} catch (error) {
|
|
3659
|
+
console.warn(`Warning: Failed to delete old report ${file}:`, error);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
return filesToDelete.length;
|
|
3663
|
+
}
|
|
3664
|
+
};
|
|
3665
|
+
|
|
3666
|
+
// src/reporting/drift.ts
|
|
3667
|
+
async function detectDrift(current, previous) {
|
|
3668
|
+
const byDecision = [];
|
|
3669
|
+
for (const currDecision of current.byDecision) {
|
|
3670
|
+
const prevDecision = previous.byDecision.find(
|
|
3671
|
+
(d) => d.decisionId === currDecision.decisionId
|
|
3672
|
+
);
|
|
3673
|
+
if (!prevDecision) {
|
|
3674
|
+
byDecision.push({
|
|
3675
|
+
decisionId: currDecision.decisionId,
|
|
3676
|
+
title: currDecision.title,
|
|
3677
|
+
trend: "stable",
|
|
3678
|
+
complianceChange: 0,
|
|
3679
|
+
newViolations: currDecision.violations,
|
|
3680
|
+
fixedViolations: 0,
|
|
3681
|
+
currentCompliance: currDecision.compliance,
|
|
3682
|
+
previousCompliance: currDecision.compliance
|
|
3683
|
+
});
|
|
3684
|
+
continue;
|
|
3685
|
+
}
|
|
3686
|
+
const complianceChange = currDecision.compliance - prevDecision.compliance;
|
|
3687
|
+
const violationDiff = currDecision.violations - prevDecision.violations;
|
|
3688
|
+
let trend;
|
|
3689
|
+
if (complianceChange > 5) {
|
|
3690
|
+
trend = "improving";
|
|
3691
|
+
} else if (complianceChange < -5) {
|
|
3692
|
+
trend = "degrading";
|
|
3693
|
+
} else {
|
|
3694
|
+
trend = "stable";
|
|
3695
|
+
}
|
|
3696
|
+
byDecision.push({
|
|
3697
|
+
decisionId: currDecision.decisionId,
|
|
3698
|
+
title: currDecision.title,
|
|
3699
|
+
trend,
|
|
3700
|
+
complianceChange,
|
|
3701
|
+
newViolations: Math.max(0, violationDiff),
|
|
3702
|
+
fixedViolations: Math.max(0, -violationDiff),
|
|
3703
|
+
currentCompliance: currDecision.compliance,
|
|
3704
|
+
previousCompliance: prevDecision.compliance
|
|
3705
|
+
});
|
|
3706
|
+
}
|
|
3707
|
+
const overallComplianceChange = current.summary.compliance - previous.summary.compliance;
|
|
3708
|
+
let overallTrend;
|
|
3709
|
+
if (overallComplianceChange > 5) {
|
|
3710
|
+
overallTrend = "improving";
|
|
3711
|
+
} else if (overallComplianceChange < -5) {
|
|
3712
|
+
overallTrend = "degrading";
|
|
3713
|
+
} else {
|
|
3714
|
+
overallTrend = "stable";
|
|
3715
|
+
}
|
|
3716
|
+
const newViolations = {
|
|
3717
|
+
critical: Math.max(
|
|
3718
|
+
0,
|
|
3719
|
+
current.summary.violations.critical - previous.summary.violations.critical
|
|
3720
|
+
),
|
|
3721
|
+
high: Math.max(0, current.summary.violations.high - previous.summary.violations.high),
|
|
3722
|
+
medium: Math.max(0, current.summary.violations.medium - previous.summary.violations.medium),
|
|
3723
|
+
low: Math.max(0, current.summary.violations.low - previous.summary.violations.low),
|
|
3724
|
+
total: 0
|
|
3725
|
+
};
|
|
3726
|
+
newViolations.total = newViolations.critical + newViolations.high + newViolations.medium + newViolations.low;
|
|
3727
|
+
const fixedViolations = {
|
|
3728
|
+
critical: Math.max(
|
|
3729
|
+
0,
|
|
3730
|
+
previous.summary.violations.critical - current.summary.violations.critical
|
|
3731
|
+
),
|
|
3732
|
+
high: Math.max(0, previous.summary.violations.high - current.summary.violations.high),
|
|
3733
|
+
medium: Math.max(0, previous.summary.violations.medium - current.summary.violations.medium),
|
|
3734
|
+
low: Math.max(0, previous.summary.violations.low - current.summary.violations.low),
|
|
3735
|
+
total: 0
|
|
3736
|
+
};
|
|
3737
|
+
fixedViolations.total = fixedViolations.critical + fixedViolations.high + fixedViolations.medium + fixedViolations.low;
|
|
3738
|
+
const improving = byDecision.filter((d) => d.trend === "improving");
|
|
3739
|
+
const degrading = byDecision.filter((d) => d.trend === "degrading");
|
|
3740
|
+
const mostImproved = improving.sort((a, b) => b.complianceChange - a.complianceChange).slice(0, 5);
|
|
3741
|
+
const mostDegraded = degrading.sort((a, b) => a.complianceChange - b.complianceChange).slice(0, 5);
|
|
3742
|
+
return {
|
|
3743
|
+
trend: overallTrend,
|
|
3744
|
+
complianceChange: overallComplianceChange,
|
|
3745
|
+
summary: {
|
|
3746
|
+
newViolations,
|
|
3747
|
+
fixedViolations
|
|
3748
|
+
},
|
|
3749
|
+
byDecision,
|
|
3750
|
+
mostImproved,
|
|
3751
|
+
mostDegraded
|
|
3752
|
+
};
|
|
3753
|
+
}
|
|
3754
|
+
async function analyzeTrend(reports) {
|
|
3755
|
+
if (reports.length === 0) {
|
|
3756
|
+
throw new Error("No reports provided for trend analysis");
|
|
3757
|
+
}
|
|
3758
|
+
const sortedReports = reports.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
3759
|
+
const firstReport = sortedReports[0]?.report;
|
|
3760
|
+
const lastReport = sortedReports[sortedReports.length - 1]?.report;
|
|
3761
|
+
if (!firstReport || !lastReport) {
|
|
3762
|
+
throw new Error("Invalid reports data");
|
|
3763
|
+
}
|
|
3764
|
+
const overallChange = lastReport.summary.compliance - firstReport.summary.compliance;
|
|
3765
|
+
let overallTrend;
|
|
3766
|
+
if (overallChange > 5) {
|
|
3767
|
+
overallTrend = "improving";
|
|
3768
|
+
} else if (overallChange < -5) {
|
|
3769
|
+
overallTrend = "degrading";
|
|
3770
|
+
} else {
|
|
3771
|
+
overallTrend = "stable";
|
|
3772
|
+
}
|
|
3773
|
+
const overallDataPoints = sortedReports.map((r) => ({
|
|
3774
|
+
date: r.timestamp,
|
|
3775
|
+
compliance: r.report.summary.compliance
|
|
3776
|
+
}));
|
|
3777
|
+
const decisionMap = /* @__PURE__ */ new Map();
|
|
3778
|
+
for (const { report } of sortedReports) {
|
|
3779
|
+
for (const decision of report.byDecision) {
|
|
3780
|
+
if (!decisionMap.has(decision.decisionId)) {
|
|
3781
|
+
decisionMap.set(decision.decisionId, []);
|
|
3782
|
+
}
|
|
3783
|
+
decisionMap.get(decision.decisionId).push(decision);
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
const decisions = Array.from(decisionMap.entries()).map(([decisionId, data]) => {
|
|
3787
|
+
const first = data[0];
|
|
3788
|
+
const last = data[data.length - 1];
|
|
3789
|
+
if (!first || !last) {
|
|
3790
|
+
throw new Error(`Invalid decision data for ${decisionId}`);
|
|
3791
|
+
}
|
|
3792
|
+
const change = last.compliance - first.compliance;
|
|
3793
|
+
let trend;
|
|
3794
|
+
if (change > 5) {
|
|
3795
|
+
trend = "improving";
|
|
3796
|
+
} else if (change < -5) {
|
|
3797
|
+
trend = "degrading";
|
|
3798
|
+
} else {
|
|
3799
|
+
trend = "stable";
|
|
3800
|
+
}
|
|
3801
|
+
const dataPoints = sortedReports.map((r) => {
|
|
3802
|
+
const decision = r.report.byDecision.find((d) => d.decisionId === decisionId);
|
|
3803
|
+
return {
|
|
3804
|
+
date: r.timestamp,
|
|
3805
|
+
compliance: decision?.compliance ?? 0
|
|
3806
|
+
};
|
|
3807
|
+
});
|
|
3808
|
+
return {
|
|
3809
|
+
decisionId,
|
|
3810
|
+
title: last.title,
|
|
3811
|
+
startCompliance: first.compliance,
|
|
3812
|
+
endCompliance: last.compliance,
|
|
3813
|
+
change,
|
|
3814
|
+
trend,
|
|
3815
|
+
dataPoints
|
|
3816
|
+
};
|
|
3817
|
+
});
|
|
3818
|
+
const firstTimestamp = sortedReports[0]?.timestamp;
|
|
3819
|
+
const lastTimestamp = sortedReports[sortedReports.length - 1]?.timestamp;
|
|
3820
|
+
if (!firstTimestamp || !lastTimestamp) {
|
|
3821
|
+
throw new Error("Invalid report timestamps");
|
|
3822
|
+
}
|
|
3823
|
+
return {
|
|
3824
|
+
period: {
|
|
3825
|
+
start: firstTimestamp,
|
|
3826
|
+
end: lastTimestamp,
|
|
3827
|
+
days: sortedReports.length
|
|
3828
|
+
},
|
|
3829
|
+
overall: {
|
|
3830
|
+
startCompliance: firstReport.summary.compliance,
|
|
3831
|
+
endCompliance: lastReport.summary.compliance,
|
|
3832
|
+
change: overallChange,
|
|
3833
|
+
trend: overallTrend,
|
|
3834
|
+
dataPoints: overallDataPoints
|
|
3835
|
+
},
|
|
3836
|
+
decisions
|
|
3837
|
+
};
|
|
3838
|
+
}
|
|
3839
|
+
|
|
3555
3840
|
// src/agent/context.generator.ts
|
|
3556
3841
|
async function generateContext(filePath, config, options = {}) {
|
|
3557
3842
|
const { includeRationale = config.agent?.includeRationale ?? true, cwd = process.cwd() } = options;
|
|
@@ -4010,6 +4295,7 @@ export {
|
|
|
4010
4295
|
RegexVerifier,
|
|
4011
4296
|
Registry,
|
|
4012
4297
|
RegistryError,
|
|
4298
|
+
ReportStorage,
|
|
4013
4299
|
Reporter,
|
|
4014
4300
|
SecurityVerifier,
|
|
4015
4301
|
SeveritySchema,
|
|
@@ -4022,6 +4308,7 @@ export {
|
|
|
4022
4308
|
VerificationError,
|
|
4023
4309
|
VerificationFrequencySchema,
|
|
4024
4310
|
VerifierNotFoundError,
|
|
4311
|
+
analyzeTrend,
|
|
4025
4312
|
buildDependencyGraph2 as buildDependencyGraph,
|
|
4026
4313
|
builtinAnalyzers,
|
|
4027
4314
|
builtinVerifiers,
|
|
@@ -4035,6 +4322,7 @@ export {
|
|
|
4035
4322
|
createVerificationEngine,
|
|
4036
4323
|
createViolation,
|
|
4037
4324
|
defaultConfig,
|
|
4325
|
+
detectDrift,
|
|
4038
4326
|
ensureDir,
|
|
4039
4327
|
extractSnippet,
|
|
4040
4328
|
formatConsoleReport,
|