@eduardbar/drift 1.3.0 → 1.5.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 (198) hide show
  1. package/.gga +50 -0
  2. package/.github/actions/drift-review/README.md +62 -0
  3. package/.github/actions/drift-review/action.yml +148 -0
  4. package/.github/actions/drift-scan/README.md +28 -32
  5. package/.github/actions/drift-scan/action.yml +78 -14
  6. package/.github/workflows/publish-vscode.yml +1 -3
  7. package/.github/workflows/publish.yml +8 -0
  8. package/.github/workflows/quality.yml +15 -0
  9. package/.github/workflows/reusable-quality-checks.yml +95 -0
  10. package/.github/workflows/review-pr.yml +33 -41
  11. package/AGENTS.md +75 -251
  12. package/CHANGELOG.md +41 -0
  13. package/README.md +177 -43
  14. package/benchmarks/fixtures/critical/drift.config.ts +21 -0
  15. package/benchmarks/fixtures/critical/src/app/user-service.ts +30 -0
  16. package/benchmarks/fixtures/critical/src/domain/entities.ts +19 -0
  17. package/benchmarks/fixtures/critical/src/domain/policies.ts +22 -0
  18. package/benchmarks/fixtures/critical/src/index.ts +10 -0
  19. package/benchmarks/fixtures/critical/src/infra/memory-user-repo.ts +14 -0
  20. package/benchmarks/perf-budget.v1.json +27 -0
  21. package/dist/benchmark.d.ts +1 -1
  22. package/dist/benchmark.js +83 -52
  23. package/dist/cli.js +243 -8
  24. package/dist/config.js +16 -2
  25. package/dist/diff.js +42 -50
  26. package/dist/doctor.d.ts +26 -0
  27. package/dist/doctor.js +140 -0
  28. package/dist/format.d.ts +17 -0
  29. package/dist/format.js +45 -0
  30. package/dist/guard-baseline.d.ts +12 -0
  31. package/dist/guard-baseline.js +57 -0
  32. package/dist/guard-metrics.d.ts +6 -0
  33. package/dist/guard-metrics.js +39 -0
  34. package/dist/guard-types.d.ts +58 -0
  35. package/dist/guard-types.js +2 -0
  36. package/dist/guard.d.ts +16 -0
  37. package/dist/guard.js +178 -0
  38. package/dist/index.d.ts +10 -3
  39. package/dist/index.js +4 -1
  40. package/dist/init.d.ts +15 -0
  41. package/dist/init.js +273 -0
  42. package/dist/map-cycles.d.ts +2 -0
  43. package/dist/map-cycles.js +34 -0
  44. package/dist/map-svg.d.ts +19 -0
  45. package/dist/map-svg.js +97 -0
  46. package/dist/map.js +78 -138
  47. package/dist/metrics.js +70 -55
  48. package/dist/output-metadata.d.ts +15 -0
  49. package/dist/output-metadata.js +19 -0
  50. package/dist/plugins-capabilities.d.ts +4 -0
  51. package/dist/plugins-capabilities.js +21 -0
  52. package/dist/plugins-messages.d.ts +10 -0
  53. package/dist/plugins-messages.js +16 -0
  54. package/dist/plugins-rules.d.ts +9 -0
  55. package/dist/plugins-rules.js +137 -0
  56. package/dist/plugins.d.ts +1 -1
  57. package/dist/plugins.js +45 -142
  58. package/dist/reporter-constants.d.ts +16 -0
  59. package/dist/reporter-constants.js +39 -0
  60. package/dist/reporter.d.ts +3 -3
  61. package/dist/reporter.js +35 -55
  62. package/dist/review.d.ts +2 -1
  63. package/dist/review.js +2 -1
  64. package/dist/rules/phase3-configurable.js +23 -15
  65. package/dist/saas/constants.d.ts +15 -0
  66. package/dist/saas/constants.js +48 -0
  67. package/dist/saas/dashboard.d.ts +8 -0
  68. package/dist/saas/dashboard.js +132 -0
  69. package/dist/saas/errors.d.ts +19 -0
  70. package/dist/saas/errors.js +37 -0
  71. package/dist/saas/helpers.d.ts +21 -0
  72. package/dist/saas/helpers.js +110 -0
  73. package/dist/saas/ingest.d.ts +3 -0
  74. package/dist/saas/ingest.js +249 -0
  75. package/dist/saas/organization.d.ts +5 -0
  76. package/dist/saas/organization.js +82 -0
  77. package/dist/saas/plan-change.d.ts +10 -0
  78. package/dist/saas/plan-change.js +15 -0
  79. package/dist/saas/store.d.ts +21 -0
  80. package/dist/saas/store.js +159 -0
  81. package/dist/saas/types.d.ts +191 -0
  82. package/dist/saas/types.js +2 -0
  83. package/dist/saas.d.ts +8 -218
  84. package/dist/saas.js +7 -761
  85. package/dist/sarif.d.ts +74 -0
  86. package/dist/sarif.js +122 -0
  87. package/dist/trust-advanced.d.ts +14 -0
  88. package/dist/trust-advanced.js +65 -0
  89. package/dist/trust-kpi-fs.d.ts +3 -0
  90. package/dist/trust-kpi-fs.js +141 -0
  91. package/dist/trust-kpi-parse.d.ts +7 -0
  92. package/dist/trust-kpi-parse.js +186 -0
  93. package/dist/trust-kpi-types.d.ts +16 -0
  94. package/dist/trust-kpi-types.js +2 -0
  95. package/dist/trust-kpi.d.ts +1 -3
  96. package/dist/trust-kpi.js +6 -266
  97. package/dist/trust-policy.d.ts +32 -0
  98. package/dist/trust-policy.js +160 -0
  99. package/dist/trust-render.d.ts +9 -0
  100. package/dist/trust-render.js +54 -0
  101. package/dist/trust-scoring.d.ts +9 -0
  102. package/dist/trust-scoring.js +208 -0
  103. package/dist/trust.d.ts +5 -32
  104. package/dist/trust.js +29 -432
  105. package/dist/types/app.d.ts +30 -0
  106. package/dist/types/app.js +2 -0
  107. package/dist/types/config.d.ts +25 -0
  108. package/dist/types/config.js +2 -0
  109. package/dist/types/core.d.ts +100 -0
  110. package/dist/types/core.js +2 -0
  111. package/dist/types/diff.d.ts +55 -0
  112. package/dist/types/diff.js +2 -0
  113. package/dist/types/plugin.d.ts +41 -0
  114. package/dist/types/plugin.js +2 -0
  115. package/dist/types/trust.d.ts +120 -0
  116. package/dist/types/trust.js +2 -0
  117. package/dist/types.d.ts +8 -365
  118. package/docs/AGENTS.md +1 -1
  119. package/docs/release-notes-draft.md +40 -0
  120. package/docs/rules-catalog.md +49 -0
  121. package/docs/trust-core-release-checklist.md +37 -5
  122. package/package.json +11 -4
  123. package/packages/vscode-drift/src/code-actions.ts +1 -1
  124. package/schemas/drift-ai-output.v1.json +162 -0
  125. package/schemas/drift-doctor.v1.json +57 -0
  126. package/schemas/drift-guard.v1.json +298 -0
  127. package/schemas/drift-report.v1.json +151 -0
  128. package/schemas/drift-trust.v1.json +131 -0
  129. package/scripts/check-docs-drift.mjs +154 -0
  130. package/scripts/check-performance-budget.mjs +360 -0
  131. package/scripts/check-runtime-policy.mjs +66 -0
  132. package/scripts/smoke-repo.mjs +394 -0
  133. package/src/benchmark.ts +92 -53
  134. package/src/cli.ts +285 -13
  135. package/src/config.ts +19 -2
  136. package/src/diff.ts +57 -48
  137. package/src/doctor.ts +185 -0
  138. package/src/format.ts +81 -0
  139. package/src/guard-baseline.ts +74 -0
  140. package/src/guard-metrics.ts +52 -0
  141. package/src/guard-types.ts +66 -0
  142. package/src/guard.ts +248 -0
  143. package/src/index.ts +36 -0
  144. package/src/init.ts +298 -0
  145. package/src/map-cycles.ts +38 -0
  146. package/src/map-svg.ts +124 -0
  147. package/src/map.ts +111 -142
  148. package/src/metrics.ts +78 -59
  149. package/src/output-metadata.ts +32 -0
  150. package/src/plugins-capabilities.ts +36 -0
  151. package/src/plugins-messages.ts +35 -0
  152. package/src/plugins-rules.ts +296 -0
  153. package/src/plugins.ts +76 -283
  154. package/src/reporter-constants.ts +46 -0
  155. package/src/reporter.ts +64 -65
  156. package/src/review.ts +4 -2
  157. package/src/rules/phase3-configurable.ts +39 -26
  158. package/src/saas/constants.ts +56 -0
  159. package/src/saas/dashboard.ts +172 -0
  160. package/src/saas/errors.ts +45 -0
  161. package/src/saas/helpers.ts +140 -0
  162. package/src/saas/ingest.ts +278 -0
  163. package/src/saas/organization.ts +99 -0
  164. package/src/saas/plan-change.ts +19 -0
  165. package/src/saas/store.ts +172 -0
  166. package/src/saas/types.ts +216 -0
  167. package/src/saas.ts +49 -1031
  168. package/src/sarif.ts +232 -0
  169. package/src/trust-advanced.ts +99 -0
  170. package/src/trust-kpi-fs.ts +169 -0
  171. package/src/trust-kpi-parse.ts +219 -0
  172. package/src/trust-kpi-types.ts +19 -0
  173. package/src/trust-kpi.ts +8 -316
  174. package/src/trust-policy.ts +246 -0
  175. package/src/trust-render.ts +61 -0
  176. package/src/trust-scoring.ts +231 -0
  177. package/src/trust.ts +62 -576
  178. package/src/types/app.ts +30 -0
  179. package/src/types/config.ts +27 -0
  180. package/src/types/core.ts +105 -0
  181. package/src/types/diff.ts +61 -0
  182. package/src/types/plugin.ts +46 -0
  183. package/src/types/trust.ts +134 -0
  184. package/src/types.ts +79 -409
  185. package/tests/ci-quality-matrix.test.ts +37 -0
  186. package/tests/ci-smoke-gate.test.ts +26 -0
  187. package/tests/ci-version-alignment.test.ts +93 -0
  188. package/tests/cli-sarif.test.ts +92 -0
  189. package/tests/docs-drift-check.test.ts +115 -0
  190. package/tests/format.test.ts +157 -0
  191. package/tests/new-features.test.ts +11 -3
  192. package/tests/perf-budget-check.test.ts +146 -0
  193. package/tests/phase1-init-doctor-guard.test.ts +301 -0
  194. package/tests/runtime-policy-alignment.test.ts +46 -0
  195. package/tests/sarif.test.ts +160 -0
  196. package/tests/trust-kpi.test.ts +31 -4
  197. package/tests/trust.test.ts +18 -0
  198. package/vitest.config.ts +2 -0
package/dist/diff.js CHANGED
@@ -8,6 +8,43 @@ function normalizeIssueText(value) {
8
8
  .replace(/\s+/g, ' ')
9
9
  .trim();
10
10
  }
11
+ const SNIPPET_PREFIX_LENGTH = 80;
12
+ function strictIssueKey(i) {
13
+ return `${i.rule}:${i.line}:${i.column}`;
14
+ }
15
+ function normalizedIssueKey(i) {
16
+ const normalizedMessage = normalizeIssueText(i.message);
17
+ const normalizedSnippetPrefix = normalizeIssueText(i.snippet).slice(0, SNIPPET_PREFIX_LENGTH);
18
+ return `${i.rule}:${i.severity}:${i.line}:${normalizedMessage}:${normalizedSnippetPrefix}`;
19
+ }
20
+ function buildIssueIndex(issues, getKey, skip) {
21
+ const index = new Map();
22
+ for (const [idx, issue] of issues.entries()) {
23
+ if (skip?.has(idx))
24
+ continue;
25
+ const key = getKey(issue);
26
+ const bucket = index.get(key);
27
+ if (bucket)
28
+ bucket.push(idx);
29
+ else
30
+ index.set(key, [idx]);
31
+ }
32
+ return index;
33
+ }
34
+ function matchIssues(currentIssues, index, state, getKey) {
35
+ for (const [currentIndex, issue] of currentIssues.entries()) {
36
+ if (state.matchedCurrentIndexes.has(currentIndex))
37
+ continue;
38
+ const bucket = index.get(getKey(issue));
39
+ if (!bucket || bucket.length === 0)
40
+ continue;
41
+ const matchedIndex = bucket.shift();
42
+ if (matchedIndex === undefined)
43
+ continue;
44
+ state.matchedBaseIndexes.add(matchedIndex);
45
+ state.matchedCurrentIndexes.add(currentIndex);
46
+ }
47
+ }
11
48
  /**
12
49
  * Compute the diff between two DriftReports.
13
50
  *
@@ -26,58 +63,13 @@ function computeFileDiff(filePath, baseFile, currentFile) {
26
63
  const scoreDelta = scoreAfter - scoreBefore;
27
64
  const baseIssues = baseFile?.issues ?? [];
28
65
  const currentIssues = currentFile?.issues ?? [];
29
- const strictIssueKey = (i) => `${i.rule}:${i.line}:${i.column}`;
30
- const normalizedIssueKey = (i) => {
31
- const normalizedMessage = normalizeIssueText(i.message);
32
- const normalizedSnippetPrefix = normalizeIssueText(i.snippet).slice(0, 80);
33
- return `${i.rule}:${i.severity}:${i.line}:${normalizedMessage}:${normalizedSnippetPrefix}`;
34
- };
35
66
  const matchedBaseIndexes = new Set();
36
67
  const matchedCurrentIndexes = new Set();
37
- const baseStrictIndex = new Map();
38
- for (const [index, issue] of baseIssues.entries()) {
39
- const key = strictIssueKey(issue);
40
- const bucket = baseStrictIndex.get(key);
41
- if (bucket)
42
- bucket.push(index);
43
- else
44
- baseStrictIndex.set(key, [index]);
45
- }
46
- for (const [currentIndex, issue] of currentIssues.entries()) {
47
- const key = strictIssueKey(issue);
48
- const bucket = baseStrictIndex.get(key);
49
- if (!bucket || bucket.length === 0)
50
- continue;
51
- const matchedIndex = bucket.shift();
52
- if (matchedIndex === undefined)
53
- continue;
54
- matchedBaseIndexes.add(matchedIndex);
55
- matchedCurrentIndexes.add(currentIndex);
56
- }
57
- const baseNormalizedIndex = new Map();
58
- for (const [index, issue] of baseIssues.entries()) {
59
- if (matchedBaseIndexes.has(index))
60
- continue;
61
- const key = normalizedIssueKey(issue);
62
- const bucket = baseNormalizedIndex.get(key);
63
- if (bucket)
64
- bucket.push(index);
65
- else
66
- baseNormalizedIndex.set(key, [index]);
67
- }
68
- for (const [currentIndex, issue] of currentIssues.entries()) {
69
- if (matchedCurrentIndexes.has(currentIndex))
70
- continue;
71
- const key = normalizedIssueKey(issue);
72
- const bucket = baseNormalizedIndex.get(key);
73
- if (!bucket || bucket.length === 0)
74
- continue;
75
- const matchedIndex = bucket.shift();
76
- if (matchedIndex === undefined)
77
- continue;
78
- matchedBaseIndexes.add(matchedIndex);
79
- matchedCurrentIndexes.add(currentIndex);
80
- }
68
+ const matchState = { matchedBaseIndexes, matchedCurrentIndexes };
69
+ const baseStrictIndex = buildIssueIndex(baseIssues, strictIssueKey);
70
+ matchIssues(currentIssues, baseStrictIndex, matchState, strictIssueKey);
71
+ const baseNormalizedIndex = buildIssueIndex(baseIssues, normalizedIssueKey, matchedBaseIndexes);
72
+ matchIssues(currentIssues, baseNormalizedIndex, matchState, normalizedIssueKey);
81
73
  const newIssues = currentIssues.filter((_, index) => !matchedCurrentIndexes.has(index));
82
74
  const resolvedIssues = baseIssues.filter((_, index) => !matchedBaseIndexes.has(index));
83
75
  if (scoreDelta !== 0 || newIssues.length > 0 || resolvedIssues.length > 0) {
@@ -0,0 +1,26 @@
1
+ import type { DriftOutputMetadata } from './types.js';
2
+ export interface DoctorOptions {
3
+ json?: boolean;
4
+ }
5
+ interface DoctorReport {
6
+ targetPath: string;
7
+ node: {
8
+ version: string;
9
+ major: number;
10
+ supported: boolean;
11
+ };
12
+ project: {
13
+ packageJsonFound: boolean;
14
+ esm: boolean;
15
+ tsconfigFound: boolean;
16
+ sourceFilesCount: number;
17
+ lowMemorySuggested: boolean;
18
+ driftConfigFile: string | null;
19
+ };
20
+ }
21
+ export type DoctorReportJson = DoctorReport & DriftOutputMetadata;
22
+ export declare function formatDoctorJsonObject(report: DoctorReport): DoctorReportJson;
23
+ export declare function formatDoctorJson(report: DoctorReport): string;
24
+ export declare function runDoctor(projectPath: string, options?: DoctorOptions): Promise<number>;
25
+ export {};
26
+ //# sourceMappingURL=doctor.d.ts.map
package/dist/doctor.js ADDED
@@ -0,0 +1,140 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import kleur from 'kleur';
4
+ import { OUTPUT_SCHEMA, withOutputMetadata } from './output-metadata.js';
5
+ const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx']);
6
+ const IGNORED_DIRECTORIES = new Set(['node_modules', '.git', 'dist', '.next', 'coverage']);
7
+ const DECIMAL_RADIX = 10;
8
+ const MIN_SUPPORTED_NODE_MAJOR = 20;
9
+ const LOW_MEMORY_SOURCE_FILE_THRESHOLD = 500;
10
+ const DRIFT_CONFIG_CANDIDATES = [
11
+ 'drift.config.ts',
12
+ 'drift.config.js',
13
+ 'drift.config.mjs',
14
+ 'drift.config.cjs',
15
+ 'drift.config.json',
16
+ ];
17
+ function parseNodeMajor(version) {
18
+ const parsed = Number.parseInt(version.replace(/^v/, '').split('.')[0] ?? '0', DECIMAL_RADIX);
19
+ return Number.isFinite(parsed) ? parsed : 0;
20
+ }
21
+ function detectDriftConfig(projectPath) {
22
+ for (const candidate of DRIFT_CONFIG_CANDIDATES) {
23
+ if (existsSync(join(projectPath, candidate))) {
24
+ return candidate;
25
+ }
26
+ }
27
+ return null;
28
+ }
29
+ function countSourceFiles(projectPath) {
30
+ let total = 0;
31
+ const stack = [projectPath];
32
+ while (stack.length > 0) {
33
+ const currentDir = stack.pop();
34
+ if (!currentDir)
35
+ continue;
36
+ const entries = readdirSync(currentDir, { withFileTypes: true });
37
+ for (const entry of entries) {
38
+ if (entry.isDirectory() && !IGNORED_DIRECTORIES.has(entry.name)) {
39
+ stack.push(join(currentDir, entry.name));
40
+ continue;
41
+ }
42
+ if (entry.isDirectory()) {
43
+ continue;
44
+ }
45
+ if (!entry.isFile())
46
+ continue;
47
+ const lastDot = entry.name.lastIndexOf('.');
48
+ if (lastDot === -1)
49
+ continue;
50
+ const extension = entry.name.slice(lastDot);
51
+ if (SOURCE_EXTENSIONS.has(extension)) {
52
+ total += 1;
53
+ }
54
+ }
55
+ }
56
+ return total;
57
+ }
58
+ function buildDoctorReport(projectPath) {
59
+ const nodeMajor = parseNodeMajor(process.version);
60
+ const packageJsonPath = join(projectPath, 'package.json');
61
+ const packageJsonFound = existsSync(packageJsonPath);
62
+ let esm = false;
63
+ if (packageJsonFound) {
64
+ const parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
65
+ esm = parsed.type === 'module';
66
+ }
67
+ const sourceFilesCount = countSourceFiles(projectPath);
68
+ return {
69
+ targetPath: projectPath,
70
+ node: {
71
+ version: process.version,
72
+ major: nodeMajor,
73
+ supported: nodeMajor >= MIN_SUPPORTED_NODE_MAJOR,
74
+ },
75
+ project: {
76
+ packageJsonFound,
77
+ esm,
78
+ tsconfigFound: existsSync(join(projectPath, 'tsconfig.json')),
79
+ sourceFilesCount,
80
+ lowMemorySuggested: sourceFilesCount > LOW_MEMORY_SOURCE_FILE_THRESHOLD,
81
+ driftConfigFile: detectDriftConfig(projectPath),
82
+ },
83
+ };
84
+ }
85
+ function printConsoleReport(report) {
86
+ const icons = {
87
+ check: kleur.green('✓'),
88
+ warn: kleur.yellow('⚠'),
89
+ error: kleur.red('✗'),
90
+ info: kleur.cyan('ℹ'),
91
+ };
92
+ process.stdout.write('\n');
93
+ process.stdout.write(`${kleur.bold().white('drift doctor')} ${kleur.gray('- environment diagnostics')}\n\n`);
94
+ const nodeStatus = report.node.supported
95
+ ? `${icons.check} ${kleur.green('Node runtime supported')}`
96
+ : `${icons.warn} ${kleur.yellow('Node runtime below supported minimum (>=20)')}`;
97
+ process.stdout.write(`${nodeStatus} ${kleur.gray(`(${report.node.version})`)}\n`);
98
+ if (report.project.packageJsonFound) {
99
+ process.stdout.write(`${icons.check} package.json found\n`);
100
+ process.stdout.write(`${icons.info} ESM mode: ${report.project.esm ? kleur.green('yes') : kleur.yellow('no')}\n`);
101
+ }
102
+ else {
103
+ process.stdout.write(`${icons.warn} package.json not found\n`);
104
+ process.stdout.write(`${icons.info} ESM mode: ${kleur.gray('unknown')}\n`);
105
+ }
106
+ if (report.project.tsconfigFound) {
107
+ process.stdout.write(`${icons.check} tsconfig.json found\n`);
108
+ }
109
+ else {
110
+ process.stdout.write(`${icons.warn} tsconfig.json not found\n`);
111
+ }
112
+ process.stdout.write(`${icons.info} Source files (.ts/.tsx/.js/.jsx): ${report.project.sourceFilesCount}\n`);
113
+ if (report.project.lowMemorySuggested) {
114
+ process.stdout.write(`${icons.warn} Large codebase detected, consider ${kleur.bold('--low-memory')}\n`);
115
+ }
116
+ if (report.project.driftConfigFile) {
117
+ process.stdout.write(`${icons.check} Drift config: ${report.project.driftConfigFile}\n`);
118
+ }
119
+ else {
120
+ process.stdout.write(`${icons.warn} Drift config not found (drift.config.*)\n`);
121
+ }
122
+ process.stdout.write('\n');
123
+ }
124
+ export function formatDoctorJsonObject(report) {
125
+ return withOutputMetadata(report, OUTPUT_SCHEMA.doctor);
126
+ }
127
+ export function formatDoctorJson(report) {
128
+ return JSON.stringify(formatDoctorJsonObject(report), null, 2);
129
+ }
130
+ export async function runDoctor(projectPath, options) {
131
+ const report = buildDoctorReport(projectPath);
132
+ if (options?.json) {
133
+ process.stdout.write(`${formatDoctorJson(report)}\n`);
134
+ }
135
+ else {
136
+ printConsoleReport(report);
137
+ }
138
+ return 0;
139
+ }
140
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1,17 @@
1
+ declare const UNIFIED_FORMAT_VALUES: readonly ["console", "json", "markdown", "ai", "sarif"];
2
+ type UnifiedOutputFormat = (typeof UNIFIED_FORMAT_VALUES)[number];
3
+ type LegacyAlias = {
4
+ flag: string;
5
+ used?: boolean;
6
+ mapsTo: UnifiedOutputFormat;
7
+ };
8
+ interface ResolveOutputFormatOptions {
9
+ command: string;
10
+ format?: string;
11
+ supported: readonly UnifiedOutputFormat[];
12
+ legacyAliases?: LegacyAlias[];
13
+ onWarning?: (message: string) => void;
14
+ }
15
+ export declare function resolveOutputFormat(options: ResolveOutputFormatOptions): UnifiedOutputFormat;
16
+ export {};
17
+ //# sourceMappingURL=format.d.ts.map
package/dist/format.js ADDED
@@ -0,0 +1,45 @@
1
+ const UNIFIED_FORMAT_VALUES = ['console', 'json', 'markdown', 'ai', 'sarif'];
2
+ function assertSupportedFormatValue(command, format) {
3
+ if (UNIFIED_FORMAT_VALUES.includes(format))
4
+ return;
5
+ throw new Error(`Invalid --format '${format}' for '${command}'. Allowed values: ${UNIFIED_FORMAT_VALUES.join(', ')}.`);
6
+ }
7
+ function throwUnsupportedFormat(command, selected, supported) {
8
+ throw new Error(`Format '${selected}' is not supported for '${command}'. Supported formats: ${supported.join(', ')}.`);
9
+ }
10
+ function normalizeLegacyFormatSelection(command, selectedLegacyFormats) {
11
+ if (selectedLegacyFormats.length === 0)
12
+ return undefined;
13
+ const uniqueFormats = [...new Set(selectedLegacyFormats)];
14
+ if (uniqueFormats.length > 1) {
15
+ throw new Error(`Conflicting legacy format flags for '${command}': ${uniqueFormats.join(' vs ')}. Use a single format option.`);
16
+ }
17
+ return uniqueFormats[0];
18
+ }
19
+ export function resolveOutputFormat(options) {
20
+ const { command, format, supported, onWarning } = options;
21
+ const legacyAliases = options.legacyAliases ?? [];
22
+ for (const alias of legacyAliases) {
23
+ if (!alias.used)
24
+ continue;
25
+ onWarning?.(`Warning: --${alias.flag} is deprecated for '${command}'. Use --format ${alias.mapsTo} instead.`);
26
+ }
27
+ const selectedLegacyFormat = normalizeLegacyFormatSelection(command, legacyAliases.filter((alias) => alias.used).map((alias) => alias.mapsTo));
28
+ const selectedFormat = format?.trim();
29
+ if (selectedFormat) {
30
+ assertSupportedFormatValue(command, selectedFormat);
31
+ if (selectedLegacyFormat && selectedLegacyFormat !== selectedFormat) {
32
+ throw new Error(`Conflicting format flags for '${command}': --format ${selectedFormat} and legacy alias for ${selectedLegacyFormat}.`);
33
+ }
34
+ if (!supported.includes(selectedFormat)) {
35
+ throwUnsupportedFormat(command, selectedFormat, supported);
36
+ }
37
+ return selectedFormat;
38
+ }
39
+ const resolvedFromLegacy = selectedLegacyFormat ?? 'console';
40
+ if (!supported.includes(resolvedFromLegacy)) {
41
+ throwUnsupportedFormat(command, resolvedFromLegacy, supported);
42
+ }
43
+ return resolvedFromLegacy;
44
+ }
45
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1,12 @@
1
+ import type { GuardBaseline, IssueSeverity } from './guard-types.js';
2
+ export interface NormalizedBaseline {
3
+ score?: number;
4
+ totalIssues?: number;
5
+ bySeverity: Partial<Record<IssueSeverity, number>>;
6
+ }
7
+ export declare function normalizeBaseline(baseline: GuardBaseline): NormalizedBaseline;
8
+ export declare function readBaselineFromFile(projectPath: string, baselinePath?: string): {
9
+ baseline: NormalizedBaseline;
10
+ path: string;
11
+ } | undefined;
12
+ //# sourceMappingURL=guard-baseline.d.ts.map
@@ -0,0 +1,57 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ function parseNumber(value) {
4
+ return typeof value === 'number' && !Number.isNaN(value) ? value : undefined;
5
+ }
6
+ function firstDefinedNumber(values) {
7
+ for (const value of values) {
8
+ const parsed = parseNumber(value);
9
+ if (parsed !== undefined) {
10
+ return parsed;
11
+ }
12
+ }
13
+ return undefined;
14
+ }
15
+ function normalizeSeverity(baseline, severity) {
16
+ const summaryBySeverity = baseline.summary?.[`${severity}s`];
17
+ return firstDefinedNumber([
18
+ baseline.bySeverity?.[severity],
19
+ severity === 'error' ? baseline.errors : undefined,
20
+ severity === 'warning' ? baseline.warnings : undefined,
21
+ severity === 'info' ? baseline.infos : undefined,
22
+ summaryBySeverity,
23
+ ]);
24
+ }
25
+ function hasAnchor(baseline) {
26
+ if (baseline.score !== undefined || baseline.totalIssues !== undefined) {
27
+ return true;
28
+ }
29
+ const severities = ['error', 'warning', 'info'];
30
+ return severities.some((severity) => baseline.bySeverity[severity] !== undefined);
31
+ }
32
+ export function normalizeBaseline(baseline) {
33
+ const normalized = {
34
+ score: parseNumber(baseline.score),
35
+ totalIssues: parseNumber(baseline.totalIssues),
36
+ bySeverity: {
37
+ error: normalizeSeverity(baseline, 'error'),
38
+ warning: normalizeSeverity(baseline, 'warning'),
39
+ info: normalizeSeverity(baseline, 'info'),
40
+ },
41
+ };
42
+ if (!hasAnchor(normalized)) {
43
+ throw new Error('Invalid guard baseline: expected score, totalIssues, or severity counters (error/warning/info).');
44
+ }
45
+ return normalized;
46
+ }
47
+ export function readBaselineFromFile(projectPath, baselinePath) {
48
+ const resolvedBaselinePath = resolve(projectPath, baselinePath ?? 'drift-baseline.json');
49
+ if (!existsSync(resolvedBaselinePath))
50
+ return undefined;
51
+ const raw = JSON.parse(readFileSync(resolvedBaselinePath, 'utf8'));
52
+ return {
53
+ baseline: normalizeBaseline(raw),
54
+ path: resolvedBaselinePath,
55
+ };
56
+ }
57
+ //# sourceMappingURL=guard-baseline.js.map
@@ -0,0 +1,6 @@
1
+ import type { DriftDiff, DriftReport } from './types.js';
2
+ import type { GuardMetrics } from './guard-types.js';
3
+ import type { NormalizedBaseline } from './guard-baseline.js';
4
+ export declare function buildMetricsFromDiff(diff: DriftDiff): GuardMetrics;
5
+ export declare function buildMetricsFromBaseline(current: DriftReport, baseline: NormalizedBaseline): GuardMetrics;
6
+ //# sourceMappingURL=guard-metrics.d.ts.map
@@ -0,0 +1,39 @@
1
+ function createSeverityDelta() {
2
+ return {
3
+ error: 0,
4
+ warning: 0,
5
+ info: 0,
6
+ };
7
+ }
8
+ function applySeverityDelta(delta, issues, direction) {
9
+ for (const issue of issues) {
10
+ delta[issue.severity] += direction;
11
+ }
12
+ }
13
+ function countSeverityDeltaFromDiff(diff) {
14
+ const severityDelta = createSeverityDelta();
15
+ for (const file of diff.files) {
16
+ applySeverityDelta(severityDelta, file.newIssues, 1);
17
+ applySeverityDelta(severityDelta, file.resolvedIssues, -1);
18
+ }
19
+ return severityDelta;
20
+ }
21
+ export function buildMetricsFromDiff(diff) {
22
+ return {
23
+ scoreDelta: diff.totalDelta,
24
+ totalIssuesDelta: diff.newIssuesCount - diff.resolvedIssuesCount,
25
+ severityDelta: countSeverityDeltaFromDiff(diff),
26
+ };
27
+ }
28
+ export function buildMetricsFromBaseline(current, baseline) {
29
+ return {
30
+ scoreDelta: current.totalScore - (baseline.score ?? current.totalScore),
31
+ totalIssuesDelta: current.totalIssues - (baseline.totalIssues ?? current.totalIssues),
32
+ severityDelta: {
33
+ error: current.summary.errors - (baseline.bySeverity.error ?? current.summary.errors),
34
+ warning: current.summary.warnings - (baseline.bySeverity.warning ?? current.summary.warnings),
35
+ info: current.summary.infos - (baseline.bySeverity.info ?? current.summary.infos),
36
+ },
37
+ };
38
+ }
39
+ //# sourceMappingURL=guard-metrics.js.map
@@ -0,0 +1,58 @@
1
+ import type { DriftAnalysisOptions, DriftDiff, DriftIssue, DriftOutputMetadata, DriftReport } from './types.js';
2
+ export type IssueSeverity = DriftIssue['severity'];
3
+ export interface GuardBaseline {
4
+ score?: number;
5
+ totalIssues?: number;
6
+ errors?: number;
7
+ warnings?: number;
8
+ infos?: number;
9
+ bySeverity?: Partial<Record<IssueSeverity, number>>;
10
+ summary?: {
11
+ errors?: number;
12
+ warnings?: number;
13
+ infos?: number;
14
+ };
15
+ }
16
+ export interface GuardThresholds {
17
+ error?: number;
18
+ warning?: number;
19
+ info?: number;
20
+ }
21
+ export interface GuardOptions {
22
+ baseRef?: string;
23
+ baselinePath?: string;
24
+ baseline?: GuardBaseline;
25
+ budget?: number;
26
+ bySeverity?: GuardThresholds;
27
+ analysis?: DriftAnalysisOptions;
28
+ }
29
+ export interface GuardMetrics {
30
+ scoreDelta: number;
31
+ totalIssuesDelta: number;
32
+ severityDelta: Record<IssueSeverity, number>;
33
+ }
34
+ export interface GuardCheck {
35
+ id: string;
36
+ passed: boolean;
37
+ actual: number;
38
+ limit: number;
39
+ message: string;
40
+ }
41
+ export interface GuardEvaluation {
42
+ passed: boolean;
43
+ checks: GuardCheck[];
44
+ }
45
+ export interface GuardResult {
46
+ scannedAt: string;
47
+ projectPath: string;
48
+ mode: 'diff' | 'baseline';
49
+ passed: boolean;
50
+ baseRef?: string;
51
+ baselinePath?: string;
52
+ metrics: GuardMetrics;
53
+ checks: GuardCheck[];
54
+ current: DriftReport;
55
+ diff?: DriftDiff;
56
+ }
57
+ export type GuardResultJson = GuardResult & DriftOutputMetadata;
58
+ //# sourceMappingURL=guard-types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=guard-types.js.map
@@ -0,0 +1,16 @@
1
+ import type { GuardEvaluation, GuardMetrics, GuardOptions, GuardResult, GuardResultJson, GuardThresholds } from './guard-types.js';
2
+ interface GuardEvalInput {
3
+ metrics: GuardMetrics;
4
+ budget?: number;
5
+ bySeverity?: GuardThresholds;
6
+ enforceNoRegression: {
7
+ score: boolean;
8
+ totalIssues: boolean;
9
+ };
10
+ }
11
+ export declare function evaluateGuard(input: GuardEvalInput): GuardEvaluation;
12
+ export declare function formatGuardJsonObject(result: GuardResult): GuardResultJson;
13
+ export declare function formatGuardJson(result: GuardResult): string;
14
+ export declare function runGuard(targetPath: string, options?: GuardOptions): Promise<GuardResult>;
15
+ export {};
16
+ //# sourceMappingURL=guard.d.ts.map