@eduardbar/drift 1.2.0 → 1.4.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 (195) hide show
  1. package/.gga +50 -0
  2. package/.github/actions/drift-review/README.md +60 -0
  3. package/.github/actions/drift-review/action.yml +131 -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 +3 -3
  7. package/.github/workflows/publish.yml +3 -3
  8. package/.github/workflows/review-pr.yml +94 -9
  9. package/AGENTS.md +75 -245
  10. package/CHANGELOG.md +28 -0
  11. package/README.md +308 -51
  12. package/ROADMAP.md +6 -5
  13. package/dist/analyzer.d.ts +2 -2
  14. package/dist/analyzer.js +420 -159
  15. package/dist/benchmark.d.ts +2 -0
  16. package/dist/benchmark.js +204 -0
  17. package/dist/cli.js +693 -67
  18. package/dist/config.js +16 -2
  19. package/dist/diff.js +66 -10
  20. package/dist/doctor.d.ts +5 -0
  21. package/dist/doctor.js +133 -0
  22. package/dist/format.d.ts +17 -0
  23. package/dist/format.js +45 -0
  24. package/dist/git.js +12 -0
  25. package/dist/guard-types.d.ts +57 -0
  26. package/dist/guard-types.js +2 -0
  27. package/dist/guard.d.ts +14 -0
  28. package/dist/guard.js +239 -0
  29. package/dist/index.d.ts +12 -3
  30. package/dist/index.js +6 -1
  31. package/dist/init.d.ts +15 -0
  32. package/dist/init.js +273 -0
  33. package/dist/map-cycles.d.ts +2 -0
  34. package/dist/map-cycles.js +34 -0
  35. package/dist/map-svg.d.ts +19 -0
  36. package/dist/map-svg.js +97 -0
  37. package/dist/map.js +78 -138
  38. package/dist/metrics.js +70 -55
  39. package/dist/output-metadata.d.ts +13 -0
  40. package/dist/output-metadata.js +17 -0
  41. package/dist/plugins-capabilities.d.ts +4 -0
  42. package/dist/plugins-capabilities.js +21 -0
  43. package/dist/plugins-messages.d.ts +10 -0
  44. package/dist/plugins-messages.js +16 -0
  45. package/dist/plugins-rules.d.ts +9 -0
  46. package/dist/plugins-rules.js +137 -0
  47. package/dist/plugins.d.ts +2 -1
  48. package/dist/plugins.js +80 -28
  49. package/dist/printer.js +4 -0
  50. package/dist/reporter-constants.d.ts +16 -0
  51. package/dist/reporter-constants.js +39 -0
  52. package/dist/reporter.d.ts +3 -3
  53. package/dist/reporter.js +35 -55
  54. package/dist/review.d.ts +2 -1
  55. package/dist/review.js +4 -3
  56. package/dist/rules/comments.js +2 -2
  57. package/dist/rules/complexity.js +2 -7
  58. package/dist/rules/nesting.js +3 -13
  59. package/dist/rules/phase0-basic.js +10 -10
  60. package/dist/rules/phase3-configurable.js +23 -15
  61. package/dist/rules/shared.d.ts +2 -0
  62. package/dist/rules/shared.js +27 -3
  63. package/dist/saas/constants.d.ts +15 -0
  64. package/dist/saas/constants.js +48 -0
  65. package/dist/saas/dashboard.d.ts +8 -0
  66. package/dist/saas/dashboard.js +132 -0
  67. package/dist/saas/errors.d.ts +19 -0
  68. package/dist/saas/errors.js +37 -0
  69. package/dist/saas/helpers.d.ts +21 -0
  70. package/dist/saas/helpers.js +110 -0
  71. package/dist/saas/ingest.d.ts +3 -0
  72. package/dist/saas/ingest.js +249 -0
  73. package/dist/saas/organization.d.ts +5 -0
  74. package/dist/saas/organization.js +82 -0
  75. package/dist/saas/plan-change.d.ts +10 -0
  76. package/dist/saas/plan-change.js +15 -0
  77. package/dist/saas/store.d.ts +21 -0
  78. package/dist/saas/store.js +159 -0
  79. package/dist/saas/types.d.ts +191 -0
  80. package/dist/saas/types.js +2 -0
  81. package/dist/saas.d.ts +8 -82
  82. package/dist/saas.js +7 -320
  83. package/dist/sarif.d.ts +74 -0
  84. package/dist/sarif.js +122 -0
  85. package/dist/trust-advanced.d.ts +14 -0
  86. package/dist/trust-advanced.js +65 -0
  87. package/dist/trust-kpi-fs.d.ts +3 -0
  88. package/dist/trust-kpi-fs.js +141 -0
  89. package/dist/trust-kpi-parse.d.ts +7 -0
  90. package/dist/trust-kpi-parse.js +186 -0
  91. package/dist/trust-kpi-types.d.ts +16 -0
  92. package/dist/trust-kpi-types.js +2 -0
  93. package/dist/trust-kpi.d.ts +7 -0
  94. package/dist/trust-kpi.js +185 -0
  95. package/dist/trust-policy.d.ts +32 -0
  96. package/dist/trust-policy.js +160 -0
  97. package/dist/trust-render.d.ts +9 -0
  98. package/dist/trust-render.js +54 -0
  99. package/dist/trust-scoring.d.ts +9 -0
  100. package/dist/trust-scoring.js +208 -0
  101. package/dist/trust.d.ts +37 -0
  102. package/dist/trust.js +168 -0
  103. package/dist/types/app.d.ts +30 -0
  104. package/dist/types/app.js +2 -0
  105. package/dist/types/config.d.ts +25 -0
  106. package/dist/types/config.js +2 -0
  107. package/dist/types/core.d.ts +100 -0
  108. package/dist/types/core.js +2 -0
  109. package/dist/types/diff.d.ts +55 -0
  110. package/dist/types/diff.js +2 -0
  111. package/dist/types/plugin.d.ts +41 -0
  112. package/dist/types/plugin.js +2 -0
  113. package/dist/types/trust.d.ts +120 -0
  114. package/dist/types/trust.js +2 -0
  115. package/dist/types.d.ts +8 -211
  116. package/docs/PRD.md +187 -109
  117. package/docs/plugin-contract.md +61 -0
  118. package/docs/release-notes-draft.md +40 -0
  119. package/docs/rules-catalog.md +49 -0
  120. package/docs/trust-core-release-checklist.md +87 -0
  121. package/package.json +6 -3
  122. package/packages/vscode-drift/src/code-actions.ts +1 -1
  123. package/schemas/drift-ai-output.v1.json +162 -0
  124. package/schemas/drift-report.v1.json +151 -0
  125. package/schemas/drift-trust.v1.json +131 -0
  126. package/scripts/smoke-repo.mjs +394 -0
  127. package/src/analyzer.ts +484 -155
  128. package/src/benchmark.ts +266 -0
  129. package/src/cli.ts +840 -85
  130. package/src/config.ts +19 -2
  131. package/src/diff.ts +84 -10
  132. package/src/doctor.ts +173 -0
  133. package/src/format.ts +81 -0
  134. package/src/git.ts +16 -0
  135. package/src/guard-types.ts +64 -0
  136. package/src/guard.ts +324 -0
  137. package/src/index.ts +83 -0
  138. package/src/init.ts +298 -0
  139. package/src/map-cycles.ts +38 -0
  140. package/src/map-svg.ts +124 -0
  141. package/src/map.ts +111 -142
  142. package/src/metrics.ts +78 -59
  143. package/src/output-metadata.ts +30 -0
  144. package/src/plugins-capabilities.ts +36 -0
  145. package/src/plugins-messages.ts +35 -0
  146. package/src/plugins-rules.ts +296 -0
  147. package/src/plugins.ts +148 -27
  148. package/src/printer.ts +4 -0
  149. package/src/reporter-constants.ts +46 -0
  150. package/src/reporter.ts +64 -65
  151. package/src/review.ts +6 -4
  152. package/src/rules/comments.ts +2 -2
  153. package/src/rules/complexity.ts +2 -7
  154. package/src/rules/nesting.ts +3 -13
  155. package/src/rules/phase0-basic.ts +11 -12
  156. package/src/rules/phase3-configurable.ts +39 -26
  157. package/src/rules/shared.ts +31 -3
  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 -433
  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 +210 -0
  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 +260 -0
  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 +78 -238
  185. package/tests/cli-sarif.test.ts +92 -0
  186. package/tests/diff.test.ts +124 -0
  187. package/tests/format.test.ts +157 -0
  188. package/tests/new-features.test.ts +80 -1
  189. package/tests/phase1-init-doctor-guard.test.ts +199 -0
  190. package/tests/plugins.test.ts +219 -0
  191. package/tests/rules.test.ts +23 -1
  192. package/tests/saas-foundation.test.ts +358 -1
  193. package/tests/sarif.test.ts +160 -0
  194. package/tests/trust-kpi.test.ts +147 -0
  195. package/tests/trust.test.ts +602 -0
@@ -0,0 +1,141 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { dirname, isAbsolute, resolve } from 'node:path';
3
+ const IGNORED_DIRECTORIES = new Set(['node_modules', '.git', 'dist', '.next', 'build']);
4
+ function toPosixPath(path) {
5
+ return path.replace(/\\/g, '/');
6
+ }
7
+ function processDirectoryEntry(current, entry, stack, out) {
8
+ const fullPath = resolve(current, entry);
9
+ const info = statSync(fullPath);
10
+ if (!info.isDirectory()) {
11
+ out.push(fullPath);
12
+ return;
13
+ }
14
+ if (IGNORED_DIRECTORIES.has(entry))
15
+ return;
16
+ stack.push(fullPath);
17
+ }
18
+ function listFilesRecursively(root) {
19
+ if (!existsSync(root))
20
+ return [];
21
+ const out = [];
22
+ const stack = [root];
23
+ while (stack.length > 0) {
24
+ const current = stack.pop();
25
+ for (const entry of readdirSync(current)) {
26
+ processDirectoryEntry(current, entry, stack, out);
27
+ }
28
+ }
29
+ return out;
30
+ }
31
+ function isGlobPattern(input) {
32
+ return /[*?[\]{}]/.test(input);
33
+ }
34
+ function escapeRegex(char) {
35
+ return /[\\^$+?.()|{}\[\]]/.test(char) ? `\\${char}` : char;
36
+ }
37
+ function globToRegex(pattern) {
38
+ const normalized = toPosixPath(pattern);
39
+ let expression = '^';
40
+ for (let index = 0; index < normalized.length; index += 1) {
41
+ const char = normalized[index];
42
+ const nextChar = normalized[index + 1];
43
+ const nextNextChar = normalized[index + 2];
44
+ if (char === '*' && nextChar === '*') {
45
+ if (nextNextChar === '/') {
46
+ expression += '(?:.*/)?';
47
+ index += 2;
48
+ continue;
49
+ }
50
+ expression += '.*';
51
+ index += 1;
52
+ continue;
53
+ }
54
+ if (char === '*') {
55
+ expression += '[^/]*';
56
+ continue;
57
+ }
58
+ if (char === '?') {
59
+ expression += '[^/]';
60
+ continue;
61
+ }
62
+ expression += escapeRegex(char);
63
+ }
64
+ expression += '$';
65
+ return new RegExp(expression);
66
+ }
67
+ function globBaseDir(pattern) {
68
+ const normalized = toPosixPath(pattern);
69
+ const wildcardIndex = normalized.search(/[*?[\]{}]/);
70
+ if (wildcardIndex < 0)
71
+ return dirname(pattern);
72
+ const prefix = normalized.slice(0, wildcardIndex);
73
+ const slashIndex = prefix.lastIndexOf('/');
74
+ if (slashIndex < 0)
75
+ return '.';
76
+ if (slashIndex === 0)
77
+ return '/';
78
+ return prefix.slice(0, slashIndex);
79
+ }
80
+ function discoverFromGlob(source, cwd) {
81
+ const diagnostics = [];
82
+ const absolutePattern = isAbsolute(source) ? source : resolve(cwd, source);
83
+ const regex = globToRegex(toPosixPath(absolutePattern));
84
+ const base = resolve(cwd, globBaseDir(source));
85
+ if (!existsSync(base)) {
86
+ diagnostics.push({
87
+ level: 'error',
88
+ code: 'path-not-found',
89
+ message: `Glob base path does not exist: ${base}`,
90
+ });
91
+ return { files: [], diagnostics };
92
+ }
93
+ const matched = listFilesRecursively(base)
94
+ .filter((filePath) => regex.test(toPosixPath(filePath)))
95
+ .filter((filePath) => filePath.toLowerCase().endsWith('.json'))
96
+ .sort((a, b) => a.localeCompare(b));
97
+ return { files: matched, diagnostics };
98
+ }
99
+ function discoverFromPath(source, cwd) {
100
+ const diagnostics = [];
101
+ const absolute = isAbsolute(source) ? source : resolve(cwd, source);
102
+ if (!existsSync(absolute)) {
103
+ diagnostics.push({
104
+ level: 'error',
105
+ code: 'path-not-found',
106
+ message: `Path does not exist: ${absolute}`,
107
+ });
108
+ return { files: [], diagnostics };
109
+ }
110
+ const info = statSync(absolute);
111
+ if (info.isDirectory()) {
112
+ const files = listFilesRecursively(absolute)
113
+ .filter((filePath) => filePath.toLowerCase().endsWith('.json'))
114
+ .sort((a, b) => a.localeCompare(b));
115
+ return { files, diagnostics };
116
+ }
117
+ if (info.isFile()) {
118
+ if (!absolute.toLowerCase().endsWith('.json')) {
119
+ diagnostics.push({
120
+ level: 'warning',
121
+ code: 'path-not-supported',
122
+ file: absolute,
123
+ message: 'Input file is not JSON; attempting to parse anyway',
124
+ });
125
+ }
126
+ return { files: [absolute], diagnostics };
127
+ }
128
+ diagnostics.push({
129
+ level: 'error',
130
+ code: 'path-not-supported',
131
+ message: `Path is neither a file nor directory: ${absolute}`,
132
+ });
133
+ return { files: [], diagnostics };
134
+ }
135
+ export function discoverTrustJsonFiles(input, cwd) {
136
+ const source = input.trim() || '.';
137
+ return isGlobPattern(source)
138
+ ? discoverFromGlob(source, cwd)
139
+ : discoverFromPath(source, cwd);
140
+ }
141
+ //# sourceMappingURL=trust-kpi-fs.js.map
@@ -0,0 +1,7 @@
1
+ import type { TrustKpiDiagnostic } from './types.js';
2
+ import type { ParsedTrustArtifact } from './trust-kpi-types.js';
3
+ export declare function parseTrustArtifact(filePath: string): {
4
+ record?: ParsedTrustArtifact;
5
+ diagnostics: TrustKpiDiagnostic[];
6
+ };
7
+ //# sourceMappingURL=trust-kpi-parse.d.ts.map
@@ -0,0 +1,186 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { MERGE_RISK_ORDER, normalizeMergeRiskLevel } from './trust.js';
3
+ const DIFF_STATUS_VALUES = new Set(['improved', 'regressed', 'neutral']);
4
+ function isObjectLike(value) {
5
+ return typeof value === 'object' && value !== null;
6
+ }
7
+ function createDiffContextWarning(message) {
8
+ return {
9
+ diagnostic: {
10
+ level: 'warning',
11
+ code: 'invalid-diff-context',
12
+ message,
13
+ },
14
+ };
15
+ }
16
+ function getFiniteNumber(raw, key) {
17
+ const value = raw[key];
18
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
19
+ }
20
+ function resolveDiffStatus(rawStatus, scoreDelta) {
21
+ if (typeof rawStatus === 'string' && DIFF_STATUS_VALUES.has(rawStatus)) {
22
+ return rawStatus;
23
+ }
24
+ if (scoreDelta < 0)
25
+ return 'improved';
26
+ if (scoreDelta > 0)
27
+ return 'regressed';
28
+ return 'neutral';
29
+ }
30
+ function parseDiffContextBase(raw) {
31
+ return {
32
+ baseRef: typeof raw.baseRef === 'string' ? raw.baseRef : 'unknown',
33
+ scoreDelta: getFiniteNumber(raw, 'scoreDelta'),
34
+ newIssues: getFiniteNumber(raw, 'newIssues'),
35
+ resolvedIssues: getFiniteNumber(raw, 'resolvedIssues'),
36
+ filesChanged: getFiniteNumber(raw, 'filesChanged') ?? 0,
37
+ penalty: getFiniteNumber(raw, 'penalty') ?? 0,
38
+ bonus: getFiniteNumber(raw, 'bonus') ?? 0,
39
+ netImpact: getFiniteNumber(raw, 'netImpact') ?? 0,
40
+ };
41
+ }
42
+ function normalizeDiffContext(raw) {
43
+ if (!isObjectLike(raw)) {
44
+ return createDiffContextWarning('diff_context is present but malformed; skipping diff trend fields for this artifact');
45
+ }
46
+ const parsed = parseDiffContextBase(raw);
47
+ if (parsed.scoreDelta == null || parsed.newIssues == null || parsed.resolvedIssues == null) {
48
+ return createDiffContextWarning('diff_context is missing numeric scoreDelta/newIssues/resolvedIssues; skipping diff trend fields for this artifact');
49
+ }
50
+ const normalizedStatus = resolveDiffStatus(raw.status, parsed.scoreDelta);
51
+ return {
52
+ diffContext: {
53
+ baseRef: parsed.baseRef,
54
+ status: normalizedStatus,
55
+ scoreDelta: parsed.scoreDelta,
56
+ newIssues: parsed.newIssues,
57
+ resolvedIssues: parsed.resolvedIssues,
58
+ filesChanged: parsed.filesChanged,
59
+ penalty: parsed.penalty,
60
+ bonus: parsed.bonus,
61
+ netImpact: parsed.netImpact,
62
+ },
63
+ };
64
+ }
65
+ function readJsonFile(filePath) {
66
+ let rawContent = '';
67
+ try {
68
+ rawContent = readFileSync(filePath, 'utf8');
69
+ }
70
+ catch (error) {
71
+ return {
72
+ diagnostics: [{
73
+ level: 'error',
74
+ code: 'read-failed',
75
+ file: filePath,
76
+ message: error instanceof Error ? error.message : String(error),
77
+ }],
78
+ };
79
+ }
80
+ try {
81
+ return { parsed: JSON.parse(rawContent), diagnostics: [] };
82
+ }
83
+ catch (error) {
84
+ return {
85
+ diagnostics: [{
86
+ level: 'error',
87
+ code: 'parse-failed',
88
+ file: filePath,
89
+ message: error instanceof Error ? error.message : String(error),
90
+ }],
91
+ };
92
+ }
93
+ }
94
+ function normalizeArtifactShape(parsed, filePath) {
95
+ if (isObjectLike(parsed)) {
96
+ const rawSchema = parsed.$schema;
97
+ if (rawSchema !== undefined && rawSchema !== 'schemas/drift-trust.v1.json') {
98
+ return {
99
+ diagnostics: [{
100
+ level: 'error',
101
+ code: 'invalid-shape',
102
+ file: filePath,
103
+ message: 'Invalid $schema for trust artifact (expected schemas/drift-trust.v1.json)',
104
+ }],
105
+ };
106
+ }
107
+ return { artifact: parsed, diagnostics: [] };
108
+ }
109
+ return {
110
+ diagnostics: [{
111
+ level: 'error',
112
+ code: 'invalid-shape',
113
+ file: filePath,
114
+ message: 'Trust artifact must be a JSON object',
115
+ }],
116
+ };
117
+ }
118
+ function parseTrustScore(raw, filePath) {
119
+ const trustScore = raw.trust_score;
120
+ if (typeof trustScore === 'number' && Number.isFinite(trustScore)) {
121
+ return { trustScore, diagnostics: [] };
122
+ }
123
+ return {
124
+ diagnostics: [{
125
+ level: 'error',
126
+ code: 'invalid-shape',
127
+ file: filePath,
128
+ message: 'Missing numeric trust_score',
129
+ }],
130
+ };
131
+ }
132
+ function parseMergeRisk(raw, filePath) {
133
+ const mergeRisk = typeof raw.merge_risk === 'string'
134
+ ? normalizeMergeRiskLevel(raw.merge_risk)
135
+ : undefined;
136
+ if (mergeRisk) {
137
+ return { mergeRisk, diagnostics: [] };
138
+ }
139
+ return {
140
+ diagnostics: [{
141
+ level: 'error',
142
+ code: 'invalid-shape',
143
+ file: filePath,
144
+ message: `Missing/invalid merge_risk (expected one of ${MERGE_RISK_ORDER.join(', ')})`,
145
+ }],
146
+ };
147
+ }
148
+ export function parseTrustArtifact(filePath) {
149
+ const readResult = readJsonFile(filePath);
150
+ if (readResult.diagnostics.length > 0) {
151
+ return { diagnostics: readResult.diagnostics };
152
+ }
153
+ const shape = normalizeArtifactShape(readResult.parsed, filePath);
154
+ if (!shape.artifact) {
155
+ return { diagnostics: shape.diagnostics };
156
+ }
157
+ const trustScoreResult = parseTrustScore(shape.artifact, filePath);
158
+ if (trustScoreResult.trustScore === undefined) {
159
+ return { diagnostics: trustScoreResult.diagnostics };
160
+ }
161
+ const mergeRiskResult = parseMergeRisk(shape.artifact, filePath);
162
+ if (!mergeRiskResult.mergeRisk) {
163
+ return { diagnostics: mergeRiskResult.diagnostics };
164
+ }
165
+ const diagnostics = [];
166
+ let diffContext;
167
+ if (shape.artifact.diff_context !== undefined) {
168
+ const normalized = normalizeDiffContext(shape.artifact.diff_context);
169
+ if (normalized.diagnostic) {
170
+ diagnostics.push({ ...normalized.diagnostic, file: filePath });
171
+ }
172
+ else {
173
+ diffContext = normalized.diffContext;
174
+ }
175
+ }
176
+ return {
177
+ record: {
178
+ filePath,
179
+ trustScore: trustScoreResult.trustScore,
180
+ mergeRisk: mergeRiskResult.mergeRisk,
181
+ diffContext,
182
+ },
183
+ diagnostics,
184
+ };
185
+ }
186
+ //# sourceMappingURL=trust-kpi-parse.js.map
@@ -0,0 +1,16 @@
1
+ import type { MergeRiskLevel, TrustDiffContext } from './types.js';
2
+ export interface ParsedTrustArtifact {
3
+ filePath: string;
4
+ trustScore: number;
5
+ mergeRisk: MergeRiskLevel;
6
+ diffContext?: TrustDiffContext;
7
+ }
8
+ export interface DiscoverResult {
9
+ files: string[];
10
+ diagnostics: import('./types.js').TrustKpiDiagnostic[];
11
+ }
12
+ export type DiffStatus = 'improved' | 'regressed' | 'neutral';
13
+ export interface TrustKpiOptions {
14
+ cwd?: string;
15
+ }
16
+ //# sourceMappingURL=trust-kpi-types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=trust-kpi-types.js.map
@@ -0,0 +1,7 @@
1
+ import type { DriftTrustReport, TrustKpiReport } from './types.js';
2
+ import type { TrustKpiOptions } from './trust-kpi-types.js';
3
+ export declare function computeTrustKpis(input: string, options?: TrustKpiOptions): TrustKpiReport;
4
+ export declare function formatTrustKpiConsole(kpi: TrustKpiReport): string;
5
+ export declare function formatTrustKpiJson(kpi: TrustKpiReport): string;
6
+ export declare function computeTrustKpisFromReports(reports: DriftTrustReport[]): TrustKpiReport;
7
+ //# sourceMappingURL=trust-kpi.d.ts.map
@@ -0,0 +1,185 @@
1
+ import { normalizeMergeRiskLevel } from './trust.js';
2
+ import { discoverTrustJsonFiles } from './trust-kpi-fs.js';
3
+ import { parseTrustArtifact } from './trust-kpi-parse.js';
4
+ function round(value, decimals = 2) {
5
+ return Number(value.toFixed(decimals));
6
+ }
7
+ function median(values) {
8
+ if (values.length === 0)
9
+ return null;
10
+ const sorted = [...values].sort((a, b) => a - b);
11
+ const mid = Math.floor(sorted.length / 2);
12
+ if (sorted.length % 2 === 0) {
13
+ return round((sorted[mid - 1] + sorted[mid]) / 2);
14
+ }
15
+ return round(sorted[mid]);
16
+ }
17
+ function average(values) {
18
+ if (values.length === 0)
19
+ return null;
20
+ return round(values.reduce((sum, value) => sum + value, 0) / values.length);
21
+ }
22
+ function buildDiffTrend(records) {
23
+ const withDiff = records.filter((record) => record.diffContext);
24
+ if (withDiff.length === 0) {
25
+ return {
26
+ available: false,
27
+ samples: 0,
28
+ statusDistribution: {
29
+ improved: 0,
30
+ regressed: 0,
31
+ neutral: 0,
32
+ },
33
+ scoreDelta: {
34
+ average: null,
35
+ median: null,
36
+ },
37
+ issues: {
38
+ newTotal: 0,
39
+ resolvedTotal: 0,
40
+ netNew: 0,
41
+ },
42
+ };
43
+ }
44
+ const scoreDeltas = withDiff.map((record) => record.diffContext.scoreDelta);
45
+ const newIssues = withDiff.reduce((sum, record) => sum + record.diffContext.newIssues, 0);
46
+ const resolvedIssues = withDiff.reduce((sum, record) => sum + record.diffContext.resolvedIssues, 0);
47
+ const statusDistribution = {
48
+ improved: withDiff.filter((record) => record.diffContext.status === 'improved').length,
49
+ regressed: withDiff.filter((record) => record.diffContext.status === 'regressed').length,
50
+ neutral: withDiff.filter((record) => record.diffContext.status === 'neutral').length,
51
+ };
52
+ return {
53
+ available: true,
54
+ samples: withDiff.length,
55
+ statusDistribution,
56
+ scoreDelta: {
57
+ average: average(scoreDeltas),
58
+ median: median(scoreDeltas),
59
+ },
60
+ issues: {
61
+ newTotal: newIssues,
62
+ resolvedTotal: resolvedIssues,
63
+ netNew: newIssues - resolvedIssues,
64
+ },
65
+ };
66
+ }
67
+ const KPI_RATIO_DECIMALS = 4;
68
+ export function computeTrustKpis(input, options) {
69
+ const cwd = options?.cwd ?? process.cwd();
70
+ const discovered = discoverTrustJsonFiles(input, cwd);
71
+ const records = [];
72
+ const diagnostics = [...discovered.diagnostics];
73
+ for (const filePath of discovered.files) {
74
+ const parsed = parseTrustArtifact(filePath);
75
+ diagnostics.push(...parsed.diagnostics);
76
+ if (parsed.record)
77
+ records.push(parsed.record);
78
+ }
79
+ const trustScores = records.map((record) => record.trustScore);
80
+ const mergeRiskDistribution = {
81
+ LOW: 0,
82
+ MEDIUM: 0,
83
+ HIGH: 0,
84
+ CRITICAL: 0,
85
+ };
86
+ for (const record of records) {
87
+ mergeRiskDistribution[record.mergeRisk] += 1;
88
+ }
89
+ const highRiskCount = mergeRiskDistribution.HIGH + mergeRiskDistribution.CRITICAL;
90
+ return {
91
+ generatedAt: new Date().toISOString(),
92
+ input,
93
+ files: {
94
+ matched: discovered.files.length,
95
+ parsed: records.length,
96
+ malformed: discovered.files.length - records.length,
97
+ },
98
+ prsEvaluated: records.length,
99
+ mergeRiskDistribution,
100
+ trustScore: {
101
+ average: average(trustScores),
102
+ median: median(trustScores),
103
+ min: trustScores.length > 0 ? Math.min(...trustScores) : null,
104
+ max: trustScores.length > 0 ? Math.max(...trustScores) : null,
105
+ },
106
+ highRiskRatio: records.length > 0 ? round(highRiskCount / records.length, KPI_RATIO_DECIMALS) : null,
107
+ diffTrend: buildDiffTrend(records),
108
+ diagnostics,
109
+ };
110
+ }
111
+ export function formatTrustKpiConsole(kpi) {
112
+ const parts = [
113
+ 'drift kpi',
114
+ '',
115
+ `Input: ${kpi.input}`,
116
+ `Files matched: ${kpi.files.matched} | parsed: ${kpi.files.parsed} | malformed: ${kpi.files.malformed}`,
117
+ `PRs evaluated: ${kpi.prsEvaluated}`,
118
+ `Trust score (avg/median): ${kpi.trustScore.average ?? 'n/a'} / ${kpi.trustScore.median ?? 'n/a'}`,
119
+ `High-risk ratio (HIGH+CRITICAL): ${kpi.highRiskRatio == null ? 'n/a' : `${round(kpi.highRiskRatio * 100, 2)}%`}`,
120
+ `Merge risk distribution: LOW=${kpi.mergeRiskDistribution.LOW} MEDIUM=${kpi.mergeRiskDistribution.MEDIUM} HIGH=${kpi.mergeRiskDistribution.HIGH} CRITICAL=${kpi.mergeRiskDistribution.CRITICAL}`,
121
+ ];
122
+ if (kpi.diffTrend.available) {
123
+ const avgDelta = kpi.diffTrend.scoreDelta.average;
124
+ const signedDelta = avgDelta == null ? 'n/a' : `${avgDelta >= 0 ? '+' : ''}${avgDelta}`;
125
+ parts.push(`Diff trend samples: ${kpi.diffTrend.samples} | avg score delta: ${signedDelta} | new/resolved: +${kpi.diffTrend.issues.newTotal}/-${kpi.diffTrend.issues.resolvedTotal}`);
126
+ }
127
+ else {
128
+ parts.push('Diff trend samples: 0 (no diff_context found)');
129
+ }
130
+ if (kpi.diagnostics.length > 0) {
131
+ const errorCount = kpi.diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
132
+ const warningCount = kpi.diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
133
+ parts.push(`Diagnostics: ${errorCount} error(s), ${warningCount} warning(s)`);
134
+ }
135
+ return parts.join('\n');
136
+ }
137
+ export function formatTrustKpiJson(kpi) {
138
+ return JSON.stringify(kpi, null, 2);
139
+ }
140
+ export function computeTrustKpisFromReports(reports) {
141
+ const tempRecords = reports.reduce((acc, report, index) => {
142
+ const mergeRisk = normalizeMergeRiskLevel(report.merge_risk);
143
+ if (!mergeRisk || typeof report.trust_score !== 'number')
144
+ return acc;
145
+ acc.push({
146
+ filePath: `report-${index + 1}`,
147
+ trustScore: report.trust_score,
148
+ mergeRisk,
149
+ diffContext: report.diff_context,
150
+ });
151
+ return acc;
152
+ }, []);
153
+ const trustScores = tempRecords.map((record) => record.trustScore);
154
+ const mergeRiskDistribution = {
155
+ LOW: 0,
156
+ MEDIUM: 0,
157
+ HIGH: 0,
158
+ CRITICAL: 0,
159
+ };
160
+ for (const record of tempRecords) {
161
+ mergeRiskDistribution[record.mergeRisk] += 1;
162
+ }
163
+ const highRiskCount = mergeRiskDistribution.HIGH + mergeRiskDistribution.CRITICAL;
164
+ return {
165
+ generatedAt: new Date().toISOString(),
166
+ input: 'in-memory',
167
+ files: {
168
+ matched: reports.length,
169
+ parsed: tempRecords.length,
170
+ malformed: reports.length - tempRecords.length,
171
+ },
172
+ prsEvaluated: tempRecords.length,
173
+ mergeRiskDistribution,
174
+ trustScore: {
175
+ average: average(trustScores),
176
+ median: median(trustScores),
177
+ min: trustScores.length > 0 ? Math.min(...trustScores) : null,
178
+ max: trustScores.length > 0 ? Math.max(...trustScores) : null,
179
+ },
180
+ highRiskRatio: tempRecords.length > 0 ? round(highRiskCount / tempRecords.length, KPI_RATIO_DECIMALS) : null,
181
+ diffTrend: buildDiffTrend(tempRecords),
182
+ diagnostics: [],
183
+ };
184
+ }
185
+ //# sourceMappingURL=trust-kpi.js.map
@@ -0,0 +1,32 @@
1
+ import type { DriftConfig, MergeRiskLevel } from './types.js';
2
+ export interface TrustGateOptions {
3
+ enabled?: boolean;
4
+ minTrust?: number;
5
+ maxRisk?: MergeRiskLevel;
6
+ }
7
+ export interface TrustGatePolicyResolutionOptions {
8
+ branchName?: string;
9
+ policyPack?: string;
10
+ overrides?: TrustGateOptions;
11
+ }
12
+ export interface TrustGatePolicyResolutionStep {
13
+ source: 'base' | 'policy-pack' | 'branch-preset' | 'overrides';
14
+ name: string;
15
+ values: TrustGateOptions;
16
+ }
17
+ export interface TrustGatePolicyExplanation {
18
+ effectivePolicy: TrustGateOptions;
19
+ branchName?: string;
20
+ selectedPolicyPack?: string;
21
+ invalidPolicyPack?: string;
22
+ steps: TrustGatePolicyResolutionStep[];
23
+ }
24
+ export declare const MERGE_RISK_ORDER: MergeRiskLevel[];
25
+ export declare function normalizeMergeRiskLevel(value: string): MergeRiskLevel | undefined;
26
+ export declare function detectBranchName(env?: NodeJS.ProcessEnv): string | undefined;
27
+ export declare function explainTrustGatePolicy(config: DriftConfig | undefined, branchName?: string, overrides?: TrustGateOptions): TrustGatePolicyExplanation;
28
+ export declare function explainTrustGatePolicy(config: DriftConfig | undefined, options?: TrustGatePolicyResolutionOptions): TrustGatePolicyExplanation;
29
+ export declare function resolveTrustGatePolicy(config: DriftConfig | undefined, branchName?: string, overrides?: TrustGateOptions): TrustGateOptions;
30
+ export declare function resolveTrustGatePolicy(config: DriftConfig | undefined, options?: TrustGatePolicyResolutionOptions): TrustGateOptions;
31
+ export declare function formatTrustGatePolicyExplanation(explanation: TrustGatePolicyExplanation): string;
32
+ //# sourceMappingURL=trust-policy.d.ts.map