@eduardbar/drift 1.3.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 (168) 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/review-pr.yml +34 -41
  7. package/AGENTS.md +75 -251
  8. package/CHANGELOG.md +28 -0
  9. package/README.md +148 -41
  10. package/dist/benchmark.d.ts +1 -1
  11. package/dist/benchmark.js +71 -52
  12. package/dist/cli.js +243 -8
  13. package/dist/config.js +16 -2
  14. package/dist/diff.js +42 -50
  15. package/dist/doctor.d.ts +5 -0
  16. package/dist/doctor.js +133 -0
  17. package/dist/format.d.ts +17 -0
  18. package/dist/format.js +45 -0
  19. package/dist/guard-types.d.ts +57 -0
  20. package/dist/guard-types.js +2 -0
  21. package/dist/guard.d.ts +14 -0
  22. package/dist/guard.js +239 -0
  23. package/dist/index.d.ts +10 -3
  24. package/dist/index.js +4 -1
  25. package/dist/init.d.ts +15 -0
  26. package/dist/init.js +273 -0
  27. package/dist/map-cycles.d.ts +2 -0
  28. package/dist/map-cycles.js +34 -0
  29. package/dist/map-svg.d.ts +19 -0
  30. package/dist/map-svg.js +97 -0
  31. package/dist/map.js +78 -138
  32. package/dist/metrics.js +70 -55
  33. package/dist/output-metadata.d.ts +13 -0
  34. package/dist/output-metadata.js +17 -0
  35. package/dist/plugins-capabilities.d.ts +4 -0
  36. package/dist/plugins-capabilities.js +21 -0
  37. package/dist/plugins-messages.d.ts +10 -0
  38. package/dist/plugins-messages.js +16 -0
  39. package/dist/plugins-rules.d.ts +9 -0
  40. package/dist/plugins-rules.js +137 -0
  41. package/dist/plugins.d.ts +1 -1
  42. package/dist/plugins.js +45 -142
  43. package/dist/reporter-constants.d.ts +16 -0
  44. package/dist/reporter-constants.js +39 -0
  45. package/dist/reporter.d.ts +3 -3
  46. package/dist/reporter.js +35 -55
  47. package/dist/review.d.ts +2 -1
  48. package/dist/review.js +2 -1
  49. package/dist/rules/phase3-configurable.js +23 -15
  50. package/dist/saas/constants.d.ts +15 -0
  51. package/dist/saas/constants.js +48 -0
  52. package/dist/saas/dashboard.d.ts +8 -0
  53. package/dist/saas/dashboard.js +132 -0
  54. package/dist/saas/errors.d.ts +19 -0
  55. package/dist/saas/errors.js +37 -0
  56. package/dist/saas/helpers.d.ts +21 -0
  57. package/dist/saas/helpers.js +110 -0
  58. package/dist/saas/ingest.d.ts +3 -0
  59. package/dist/saas/ingest.js +249 -0
  60. package/dist/saas/organization.d.ts +5 -0
  61. package/dist/saas/organization.js +82 -0
  62. package/dist/saas/plan-change.d.ts +10 -0
  63. package/dist/saas/plan-change.js +15 -0
  64. package/dist/saas/store.d.ts +21 -0
  65. package/dist/saas/store.js +159 -0
  66. package/dist/saas/types.d.ts +191 -0
  67. package/dist/saas/types.js +2 -0
  68. package/dist/saas.d.ts +8 -218
  69. package/dist/saas.js +7 -761
  70. package/dist/sarif.d.ts +74 -0
  71. package/dist/sarif.js +122 -0
  72. package/dist/trust-advanced.d.ts +14 -0
  73. package/dist/trust-advanced.js +65 -0
  74. package/dist/trust-kpi-fs.d.ts +3 -0
  75. package/dist/trust-kpi-fs.js +141 -0
  76. package/dist/trust-kpi-parse.d.ts +7 -0
  77. package/dist/trust-kpi-parse.js +186 -0
  78. package/dist/trust-kpi-types.d.ts +16 -0
  79. package/dist/trust-kpi-types.js +2 -0
  80. package/dist/trust-kpi.d.ts +1 -3
  81. package/dist/trust-kpi.js +6 -266
  82. package/dist/trust-policy.d.ts +32 -0
  83. package/dist/trust-policy.js +160 -0
  84. package/dist/trust-render.d.ts +9 -0
  85. package/dist/trust-render.js +54 -0
  86. package/dist/trust-scoring.d.ts +9 -0
  87. package/dist/trust-scoring.js +208 -0
  88. package/dist/trust.d.ts +4 -32
  89. package/dist/trust.js +29 -432
  90. package/dist/types/app.d.ts +30 -0
  91. package/dist/types/app.js +2 -0
  92. package/dist/types/config.d.ts +25 -0
  93. package/dist/types/config.js +2 -0
  94. package/dist/types/core.d.ts +100 -0
  95. package/dist/types/core.js +2 -0
  96. package/dist/types/diff.d.ts +55 -0
  97. package/dist/types/diff.js +2 -0
  98. package/dist/types/plugin.d.ts +41 -0
  99. package/dist/types/plugin.js +2 -0
  100. package/dist/types/trust.d.ts +120 -0
  101. package/dist/types/trust.js +2 -0
  102. package/dist/types.d.ts +8 -365
  103. package/docs/release-notes-draft.md +40 -0
  104. package/docs/rules-catalog.md +49 -0
  105. package/docs/trust-core-release-checklist.md +37 -5
  106. package/package.json +3 -2
  107. package/packages/vscode-drift/src/code-actions.ts +1 -1
  108. package/schemas/drift-ai-output.v1.json +162 -0
  109. package/schemas/drift-report.v1.json +151 -0
  110. package/schemas/drift-trust.v1.json +131 -0
  111. package/scripts/smoke-repo.mjs +394 -0
  112. package/src/benchmark.ts +75 -53
  113. package/src/cli.ts +285 -13
  114. package/src/config.ts +19 -2
  115. package/src/diff.ts +57 -48
  116. package/src/doctor.ts +173 -0
  117. package/src/format.ts +81 -0
  118. package/src/guard-types.ts +64 -0
  119. package/src/guard.ts +324 -0
  120. package/src/index.ts +35 -0
  121. package/src/init.ts +298 -0
  122. package/src/map-cycles.ts +38 -0
  123. package/src/map-svg.ts +124 -0
  124. package/src/map.ts +111 -142
  125. package/src/metrics.ts +78 -59
  126. package/src/output-metadata.ts +30 -0
  127. package/src/plugins-capabilities.ts +36 -0
  128. package/src/plugins-messages.ts +35 -0
  129. package/src/plugins-rules.ts +296 -0
  130. package/src/plugins.ts +76 -283
  131. package/src/reporter-constants.ts +46 -0
  132. package/src/reporter.ts +64 -65
  133. package/src/review.ts +4 -2
  134. package/src/rules/phase3-configurable.ts +39 -26
  135. package/src/saas/constants.ts +56 -0
  136. package/src/saas/dashboard.ts +172 -0
  137. package/src/saas/errors.ts +45 -0
  138. package/src/saas/helpers.ts +140 -0
  139. package/src/saas/ingest.ts +278 -0
  140. package/src/saas/organization.ts +99 -0
  141. package/src/saas/plan-change.ts +19 -0
  142. package/src/saas/store.ts +172 -0
  143. package/src/saas/types.ts +216 -0
  144. package/src/saas.ts +49 -1031
  145. package/src/sarif.ts +232 -0
  146. package/src/trust-advanced.ts +99 -0
  147. package/src/trust-kpi-fs.ts +169 -0
  148. package/src/trust-kpi-parse.ts +219 -0
  149. package/src/trust-kpi-types.ts +19 -0
  150. package/src/trust-kpi.ts +8 -316
  151. package/src/trust-policy.ts +246 -0
  152. package/src/trust-render.ts +61 -0
  153. package/src/trust-scoring.ts +231 -0
  154. package/src/trust.ts +62 -576
  155. package/src/types/app.ts +30 -0
  156. package/src/types/config.ts +27 -0
  157. package/src/types/core.ts +105 -0
  158. package/src/types/diff.ts +61 -0
  159. package/src/types/plugin.ts +46 -0
  160. package/src/types/trust.ts +134 -0
  161. package/src/types.ts +78 -409
  162. package/tests/cli-sarif.test.ts +92 -0
  163. package/tests/format.test.ts +157 -0
  164. package/tests/new-features.test.ts +10 -2
  165. package/tests/phase1-init-doctor-guard.test.ts +199 -0
  166. package/tests/sarif.test.ts +160 -0
  167. package/tests/trust-kpi.test.ts +31 -4
  168. package/tests/trust.test.ts +18 -0
package/dist/trust-kpi.js CHANGED
@@ -1,10 +1,6 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
- import { dirname, isAbsolute, resolve } from 'node:path';
3
- import { MERGE_RISK_ORDER, normalizeMergeRiskLevel } from './trust.js';
4
- const IGNORED_DIRECTORIES = new Set(['node_modules', '.git', 'dist', '.next', 'build']);
5
- function toPosixPath(path) {
6
- return path.replace(/\\/g, '/');
7
- }
1
+ import { normalizeMergeRiskLevel } from './trust.js';
2
+ import { discoverTrustJsonFiles } from './trust-kpi-fs.js';
3
+ import { parseTrustArtifact } from './trust-kpi-parse.js';
8
4
  function round(value, decimals = 2) {
9
5
  return Number(value.toFixed(decimals));
10
6
  }
@@ -23,263 +19,6 @@ function average(values) {
23
19
  return null;
24
20
  return round(values.reduce((sum, value) => sum + value, 0) / values.length);
25
21
  }
26
- function listFilesRecursively(root) {
27
- if (!existsSync(root))
28
- return [];
29
- const out = [];
30
- const stack = [root];
31
- while (stack.length > 0) {
32
- const current = stack.pop();
33
- for (const entry of readdirSync(current)) {
34
- const fullPath = resolve(current, entry);
35
- const info = statSync(fullPath);
36
- if (info.isDirectory()) {
37
- if (IGNORED_DIRECTORIES.has(entry))
38
- continue;
39
- stack.push(fullPath);
40
- }
41
- else {
42
- out.push(fullPath);
43
- }
44
- }
45
- }
46
- return out;
47
- }
48
- function isGlobPattern(input) {
49
- return /[*?[\]{}]/.test(input);
50
- }
51
- function escapeRegex(char) {
52
- return /[\\^$+?.()|{}\[\]]/.test(char) ? `\\${char}` : char;
53
- }
54
- function globToRegex(pattern) {
55
- const normalized = toPosixPath(pattern);
56
- let expression = '^';
57
- for (let index = 0; index < normalized.length; index += 1) {
58
- const char = normalized[index];
59
- const nextChar = normalized[index + 1];
60
- const nextNextChar = normalized[index + 2];
61
- if (char === '*' && nextChar === '*') {
62
- if (nextNextChar === '/') {
63
- expression += '(?:.*/)?';
64
- index += 2;
65
- continue;
66
- }
67
- expression += '.*';
68
- index += 1;
69
- continue;
70
- }
71
- if (char === '*') {
72
- expression += '[^/]*';
73
- continue;
74
- }
75
- if (char === '?') {
76
- expression += '[^/]';
77
- continue;
78
- }
79
- expression += escapeRegex(char);
80
- }
81
- expression += '$';
82
- return new RegExp(expression);
83
- }
84
- function globBaseDir(pattern) {
85
- const normalized = toPosixPath(pattern);
86
- const wildcardIndex = normalized.search(/[*?[\]{}]/);
87
- if (wildcardIndex < 0)
88
- return dirname(pattern);
89
- const prefix = normalized.slice(0, wildcardIndex);
90
- const slashIndex = prefix.lastIndexOf('/');
91
- if (slashIndex < 0)
92
- return '.';
93
- if (slashIndex === 0)
94
- return '/';
95
- return prefix.slice(0, slashIndex);
96
- }
97
- function discoverTrustJsonFiles(input, cwd) {
98
- const diagnostics = [];
99
- const source = input.trim() || '.';
100
- if (isGlobPattern(source)) {
101
- const absolutePattern = isAbsolute(source) ? source : resolve(cwd, source);
102
- const regex = globToRegex(toPosixPath(absolutePattern));
103
- const base = resolve(cwd, globBaseDir(source));
104
- if (!existsSync(base)) {
105
- diagnostics.push({
106
- level: 'error',
107
- code: 'path-not-found',
108
- message: `Glob base path does not exist: ${base}`,
109
- });
110
- return { files: [], diagnostics };
111
- }
112
- const matched = listFilesRecursively(base)
113
- .filter((filePath) => regex.test(toPosixPath(filePath)))
114
- .filter((filePath) => filePath.toLowerCase().endsWith('.json'))
115
- .sort((a, b) => a.localeCompare(b));
116
- return { files: matched, diagnostics };
117
- }
118
- const absolute = isAbsolute(source) ? source : resolve(cwd, source);
119
- if (!existsSync(absolute)) {
120
- diagnostics.push({
121
- level: 'error',
122
- code: 'path-not-found',
123
- message: `Path does not exist: ${absolute}`,
124
- });
125
- return { files: [], diagnostics };
126
- }
127
- const info = statSync(absolute);
128
- if (info.isDirectory()) {
129
- const files = listFilesRecursively(absolute)
130
- .filter((filePath) => filePath.toLowerCase().endsWith('.json'))
131
- .sort((a, b) => a.localeCompare(b));
132
- return { files, diagnostics };
133
- }
134
- if (info.isFile()) {
135
- if (!absolute.toLowerCase().endsWith('.json')) {
136
- diagnostics.push({
137
- level: 'warning',
138
- code: 'path-not-supported',
139
- file: absolute,
140
- message: 'Input file is not JSON; attempting to parse anyway',
141
- });
142
- }
143
- return { files: [absolute], diagnostics };
144
- }
145
- diagnostics.push({
146
- level: 'error',
147
- code: 'path-not-supported',
148
- message: `Path is neither a file nor directory: ${absolute}`,
149
- });
150
- return { files: [], diagnostics };
151
- }
152
- function isObjectLike(value) {
153
- return typeof value === 'object' && value !== null;
154
- }
155
- function normalizeDiffContext(raw) {
156
- if (!isObjectLike(raw)) {
157
- return {
158
- diagnostic: {
159
- level: 'warning',
160
- code: 'invalid-diff-context',
161
- message: 'diff_context is present but malformed; skipping diff trend fields for this artifact',
162
- },
163
- };
164
- }
165
- const baseRef = typeof raw.baseRef === 'string' ? raw.baseRef : 'unknown';
166
- const status = raw.status;
167
- const scoreDelta = typeof raw.scoreDelta === 'number' && Number.isFinite(raw.scoreDelta) ? raw.scoreDelta : null;
168
- const newIssues = typeof raw.newIssues === 'number' && Number.isFinite(raw.newIssues) ? raw.newIssues : null;
169
- const resolvedIssues = typeof raw.resolvedIssues === 'number' && Number.isFinite(raw.resolvedIssues) ? raw.resolvedIssues : null;
170
- const filesChanged = typeof raw.filesChanged === 'number' && Number.isFinite(raw.filesChanged) ? raw.filesChanged : 0;
171
- const penalty = typeof raw.penalty === 'number' && Number.isFinite(raw.penalty) ? raw.penalty : 0;
172
- const bonus = typeof raw.bonus === 'number' && Number.isFinite(raw.bonus) ? raw.bonus : 0;
173
- const netImpact = typeof raw.netImpact === 'number' && Number.isFinite(raw.netImpact) ? raw.netImpact : 0;
174
- if (scoreDelta == null || newIssues == null || resolvedIssues == null) {
175
- return {
176
- diagnostic: {
177
- level: 'warning',
178
- code: 'invalid-diff-context',
179
- message: 'diff_context is missing numeric scoreDelta/newIssues/resolvedIssues; skipping diff trend fields for this artifact',
180
- },
181
- };
182
- }
183
- const normalizedStatus = status === 'improved' || status === 'regressed' || status === 'neutral'
184
- ? status
185
- : scoreDelta < 0
186
- ? 'improved'
187
- : scoreDelta > 0
188
- ? 'regressed'
189
- : 'neutral';
190
- return {
191
- diffContext: {
192
- baseRef,
193
- status: normalizedStatus,
194
- scoreDelta,
195
- newIssues,
196
- resolvedIssues,
197
- filesChanged,
198
- penalty,
199
- bonus,
200
- netImpact,
201
- },
202
- };
203
- }
204
- function parseTrustArtifact(filePath) {
205
- const diagnostics = [];
206
- let rawContent = '';
207
- try {
208
- rawContent = readFileSync(filePath, 'utf8');
209
- }
210
- catch (error) {
211
- diagnostics.push({
212
- level: 'error',
213
- code: 'read-failed',
214
- file: filePath,
215
- message: error instanceof Error ? error.message : String(error),
216
- });
217
- return { diagnostics };
218
- }
219
- let parsed;
220
- try {
221
- parsed = JSON.parse(rawContent);
222
- }
223
- catch (error) {
224
- diagnostics.push({
225
- level: 'error',
226
- code: 'parse-failed',
227
- file: filePath,
228
- message: error instanceof Error ? error.message : String(error),
229
- });
230
- return { diagnostics };
231
- }
232
- if (!isObjectLike(parsed)) {
233
- diagnostics.push({
234
- level: 'error',
235
- code: 'invalid-shape',
236
- file: filePath,
237
- message: 'Trust artifact must be a JSON object',
238
- });
239
- return { diagnostics };
240
- }
241
- const trustScore = parsed.trust_score;
242
- if (typeof trustScore !== 'number' || !Number.isFinite(trustScore)) {
243
- diagnostics.push({
244
- level: 'error',
245
- code: 'invalid-shape',
246
- file: filePath,
247
- message: 'Missing numeric trust_score',
248
- });
249
- return { diagnostics };
250
- }
251
- const mergeRisk = typeof parsed.merge_risk === 'string'
252
- ? normalizeMergeRiskLevel(parsed.merge_risk)
253
- : undefined;
254
- if (!mergeRisk) {
255
- diagnostics.push({
256
- level: 'error',
257
- code: 'invalid-shape',
258
- file: filePath,
259
- message: `Missing/invalid merge_risk (expected one of ${MERGE_RISK_ORDER.join(', ')})`,
260
- });
261
- return { diagnostics };
262
- }
263
- let diffContext;
264
- if (parsed.diff_context !== undefined) {
265
- const normalized = normalizeDiffContext(parsed.diff_context);
266
- if (normalized.diagnostic) {
267
- diagnostics.push({ ...normalized.diagnostic, file: filePath });
268
- }
269
- else {
270
- diffContext = normalized.diffContext;
271
- }
272
- }
273
- return {
274
- record: {
275
- filePath,
276
- trustScore,
277
- mergeRisk,
278
- diffContext,
279
- },
280
- diagnostics,
281
- };
282
- }
283
22
  function buildDiffTrend(records) {
284
23
  const withDiff = records.filter((record) => record.diffContext);
285
24
  if (withDiff.length === 0) {
@@ -325,6 +64,7 @@ function buildDiffTrend(records) {
325
64
  },
326
65
  };
327
66
  }
67
+ const KPI_RATIO_DECIMALS = 4;
328
68
  export function computeTrustKpis(input, options) {
329
69
  const cwd = options?.cwd ?? process.cwd();
330
70
  const discovered = discoverTrustJsonFiles(input, cwd);
@@ -363,7 +103,7 @@ export function computeTrustKpis(input, options) {
363
103
  min: trustScores.length > 0 ? Math.min(...trustScores) : null,
364
104
  max: trustScores.length > 0 ? Math.max(...trustScores) : null,
365
105
  },
366
- highRiskRatio: records.length > 0 ? round(highRiskCount / records.length, 4) : null,
106
+ highRiskRatio: records.length > 0 ? round(highRiskCount / records.length, KPI_RATIO_DECIMALS) : null,
367
107
  diffTrend: buildDiffTrend(records),
368
108
  diagnostics,
369
109
  };
@@ -437,7 +177,7 @@ export function computeTrustKpisFromReports(reports) {
437
177
  min: trustScores.length > 0 ? Math.min(...trustScores) : null,
438
178
  max: trustScores.length > 0 ? Math.max(...trustScores) : null,
439
179
  },
440
- highRiskRatio: tempRecords.length > 0 ? round(highRiskCount / tempRecords.length, 4) : null,
180
+ highRiskRatio: tempRecords.length > 0 ? round(highRiskCount / tempRecords.length, KPI_RATIO_DECIMALS) : null,
441
181
  diffTrend: buildDiffTrend(tempRecords),
442
182
  diagnostics: [],
443
183
  };
@@ -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
@@ -0,0 +1,160 @@
1
+ export const MERGE_RISK_ORDER = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
2
+ const BRANCH_ENV_CANDIDATES = [
3
+ 'DRIFT_BRANCH',
4
+ 'GITHUB_HEAD_REF',
5
+ 'GITHUB_REF_NAME',
6
+ 'CI_COMMIT_REF_NAME',
7
+ 'BRANCH_NAME',
8
+ ];
9
+ const PATTERN_EXACT_BOOST = 10_000;
10
+ const PATTERN_STATIC_CHAR_WEIGHT = 10;
11
+ function formatTrustGatePolicyValues(values) {
12
+ const enabled = typeof values.enabled === 'boolean' ? String(values.enabled) : 'inherit';
13
+ const minTrust = typeof values.minTrust === 'number' ? String(values.minTrust) : 'inherit';
14
+ const maxRisk = values.maxRisk ?? 'inherit';
15
+ return `enabled=${enabled} minTrust=${minTrust} maxRisk=${maxRisk}`;
16
+ }
17
+ export function normalizeMergeRiskLevel(value) {
18
+ const normalized = value.toUpperCase();
19
+ return MERGE_RISK_ORDER.find((level) => level === normalized);
20
+ }
21
+ function branchPatternToRegExp(pattern) {
22
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, '\\$&').replace(/\*/g, '.*');
23
+ return new RegExp(`^${escaped}$`);
24
+ }
25
+ function patternSpecificity(pattern) {
26
+ const wildcardCount = (pattern.match(/\*/g) ?? []).length;
27
+ const staticChars = pattern.replace(/\*/g, '').length;
28
+ const exactBoost = wildcardCount === 0 ? PATTERN_EXACT_BOOST : 0;
29
+ return exactBoost + staticChars * PATTERN_STATIC_CHAR_WEIGHT - wildcardCount;
30
+ }
31
+ function resolvePresetsForBranch(branchName, presets) {
32
+ if (!presets || presets.length === 0)
33
+ return [];
34
+ const matched = [];
35
+ for (let index = 0; index < presets.length; index += 1) {
36
+ const preset = presets[index];
37
+ if (!preset?.branch)
38
+ continue;
39
+ const regex = branchPatternToRegExp(preset.branch);
40
+ if (!regex.test(branchName))
41
+ continue;
42
+ matched.push({ preset, specificity: patternSpecificity(preset.branch), index });
43
+ }
44
+ matched.sort((a, b) => a.specificity - b.specificity || a.index - b.index);
45
+ return matched.map((entry) => entry.preset);
46
+ }
47
+ function normalizeMinTrust(value) {
48
+ return typeof value === 'number' && !Number.isNaN(value) ? value : undefined;
49
+ }
50
+ function normalizeMaxRisk(value) {
51
+ if (typeof value !== 'string')
52
+ return undefined;
53
+ return normalizeMergeRiskLevel(value);
54
+ }
55
+ function normalizeTrustGateOptions(source) {
56
+ if (!source)
57
+ return {};
58
+ return {
59
+ enabled: typeof source.enabled === 'boolean' ? source.enabled : undefined,
60
+ minTrust: normalizeMinTrust(source.minTrust),
61
+ maxRisk: normalizeMaxRisk(source.maxRisk),
62
+ };
63
+ }
64
+ function mergeTrustGateOptions(base, layer) {
65
+ return {
66
+ enabled: typeof layer.enabled === 'boolean' ? layer.enabled : base.enabled,
67
+ minTrust: layer.minTrust ?? base.minTrust,
68
+ maxRisk: layer.maxRisk ?? base.maxRisk,
69
+ };
70
+ }
71
+ function normalizeResolutionOptions(branchNameOrOptions, explicitOverrides) {
72
+ if (typeof branchNameOrOptions === 'string') {
73
+ return {
74
+ branchName: branchNameOrOptions,
75
+ overrides: explicitOverrides,
76
+ };
77
+ }
78
+ if (!branchNameOrOptions) {
79
+ return { overrides: explicitOverrides };
80
+ }
81
+ return {
82
+ ...branchNameOrOptions,
83
+ overrides: explicitOverrides
84
+ ? mergeTrustGateOptions(normalizeTrustGateOptions(branchNameOrOptions.overrides), normalizeTrustGateOptions(explicitOverrides))
85
+ : branchNameOrOptions.overrides,
86
+ };
87
+ }
88
+ function resolvePolicyPack(policyPacks, policyPackName) {
89
+ const normalizedName = policyPackName?.trim();
90
+ if (!normalizedName)
91
+ return {};
92
+ if (!policyPacks)
93
+ return { name: normalizedName, invalid: normalizedName };
94
+ const pack = policyPacks[normalizedName];
95
+ if (!pack)
96
+ return { name: normalizedName, invalid: normalizedName };
97
+ return { name: normalizedName, pack };
98
+ }
99
+ export function detectBranchName(env = process.env) {
100
+ for (const key of BRANCH_ENV_CANDIDATES) {
101
+ const value = env[key]?.trim();
102
+ if (value)
103
+ return value;
104
+ }
105
+ return undefined;
106
+ }
107
+ export function explainTrustGatePolicy(config, branchNameOrOptions, explicitOverrides) {
108
+ const policy = config?.trustGate;
109
+ const resolution = normalizeResolutionOptions(branchNameOrOptions, explicitOverrides);
110
+ const normalizedBranch = resolution.branchName?.trim();
111
+ const packResolution = resolvePolicyPack(policy?.policyPacks, resolution.policyPack);
112
+ const steps = [];
113
+ const base = normalizeTrustGateOptions(policy);
114
+ let effective = base;
115
+ steps.push({ source: 'base', name: 'trustGate', values: base });
116
+ if (packResolution.pack) {
117
+ const packOptions = normalizeTrustGateOptions(packResolution.pack);
118
+ effective = mergeTrustGateOptions(effective, packOptions);
119
+ steps.push({ source: 'policy-pack', name: packResolution.name ?? 'unknown', values: packOptions });
120
+ }
121
+ if (normalizedBranch) {
122
+ const matchedPresets = resolvePresetsForBranch(normalizedBranch, policy?.presets);
123
+ for (const preset of matchedPresets) {
124
+ const presetOptions = normalizeTrustGateOptions(preset);
125
+ effective = mergeTrustGateOptions(effective, presetOptions);
126
+ steps.push({ source: 'branch-preset', name: preset.branch, values: presetOptions });
127
+ }
128
+ }
129
+ const normalizedOverrides = normalizeTrustGateOptions(resolution.overrides);
130
+ if (Object.values(normalizedOverrides).some((value) => value !== undefined)) {
131
+ effective = mergeTrustGateOptions(effective, normalizedOverrides);
132
+ steps.push({ source: 'overrides', name: 'cli', values: normalizedOverrides });
133
+ }
134
+ return {
135
+ effectivePolicy: effective,
136
+ branchName: normalizedBranch,
137
+ selectedPolicyPack: packResolution.name,
138
+ invalidPolicyPack: packResolution.invalid,
139
+ steps,
140
+ };
141
+ }
142
+ export function resolveTrustGatePolicy(config, branchNameOrOptions, explicitOverrides) {
143
+ const options = normalizeResolutionOptions(branchNameOrOptions, explicitOverrides);
144
+ return explainTrustGatePolicy(config, options).effectivePolicy;
145
+ }
146
+ export function formatTrustGatePolicyExplanation(explanation) {
147
+ const lines = ['Trust gate policy resolution:'];
148
+ lines.push(`- branch: ${explanation.branchName ?? 'not provided'}`);
149
+ lines.push(`- policy pack: ${explanation.selectedPolicyPack ?? 'not selected'}`);
150
+ if (explanation.invalidPolicyPack) {
151
+ lines.push(`- invalid policy pack: ${explanation.invalidPolicyPack}`);
152
+ }
153
+ lines.push('- steps:');
154
+ for (const [index, step] of explanation.steps.entries()) {
155
+ lines.push(` ${index + 1}. ${step.source} (${step.name}): ${formatTrustGatePolicyValues(step.values)}`);
156
+ }
157
+ lines.push(`- effective: ${formatTrustGatePolicyValues(explanation.effectivePolicy)}`);
158
+ return lines.join('\n');
159
+ }
160
+ //# sourceMappingURL=trust-policy.js.map
@@ -0,0 +1,9 @@
1
+ import type { DriftTrustReport, TrustDiffContext, TrustFixPriority, TrustReason } from './types.js';
2
+ export declare function renderTrustReasons(reasons: TrustReason[]): string;
3
+ export declare function renderTrustPriorities(priorities: TrustFixPriority[]): string;
4
+ export declare function renderTrustMarkdownReasons(reasons: TrustReason[]): string;
5
+ export declare function renderTrustMarkdownPriorities(priorities: TrustFixPriority[]): string;
6
+ export declare function renderTrustDiffBlock(diffContext: TrustDiffContext | undefined): string;
7
+ export declare function renderTrustAdvancedComparison(advancedContext: DriftTrustReport['advanced_context']): string;
8
+ export declare function renderTrustAdvancedGuidance(advancedContext: DriftTrustReport['advanced_context']): string;
9
+ //# sourceMappingURL=trust-render.d.ts.map
@@ -0,0 +1,54 @@
1
+ export function renderTrustReasons(reasons) {
2
+ if (reasons.length === 0)
3
+ return '- none';
4
+ return reasons.map((reason) => `- ${reason.label}: ${reason.detail} (impact ${reason.impact})`).join('\n');
5
+ }
6
+ export function renderTrustPriorities(priorities) {
7
+ if (priorities.length === 0)
8
+ return '- none';
9
+ return priorities
10
+ .map((priority) => `- #${priority.rank} ${priority.rule} (${priority.severity}, x${priority.occurrences}${priority.confidence ? `, confidence ${priority.confidence}` : ''}): ${priority.suggestion}`)
11
+ .join('\n');
12
+ }
13
+ export function renderTrustMarkdownReasons(reasons) {
14
+ if (reasons.length === 0)
15
+ return '- none';
16
+ return reasons.map((reason) => `- **${reason.label}**: ${reason.detail} (impact ${reason.impact})`).join('\n');
17
+ }
18
+ export function renderTrustMarkdownPriorities(priorities) {
19
+ if (priorities.length === 0)
20
+ return '- none';
21
+ return priorities
22
+ .map((priority) => `- #${priority.rank} \`${priority.rule}\` (${priority.severity}, x${priority.occurrences}, effort: ${priority.effort}${priority.confidence ? `, confidence: ${priority.confidence}` : ''}) - ${priority.suggestion}${priority.explanation ? ` ${priority.explanation}` : ''}`)
23
+ .join('\n');
24
+ }
25
+ export function renderTrustDiffBlock(diffContext) {
26
+ if (!diffContext) {
27
+ return [
28
+ '- Base ref: not provided',
29
+ '- Diff-aware adjustment: not applied',
30
+ ].join('\n');
31
+ }
32
+ return [
33
+ `- Base ref: \`${diffContext.baseRef}\``,
34
+ `- Diff status: **${diffContext.status.toUpperCase()}**`,
35
+ `- Score delta: **${diffContext.scoreDelta >= 0 ? '+' : ''}${diffContext.scoreDelta}**`,
36
+ `- Issues: **+${diffContext.newIssues}** new / **-${diffContext.resolvedIssues}** resolved`,
37
+ `- Trust adjustment: **+${diffContext.penalty}** penalty / **-${diffContext.bonus}** bonus (net ${diffContext.netImpact >= 0 ? '+' : ''}${diffContext.netImpact})`,
38
+ ].join('\n');
39
+ }
40
+ export function renderTrustAdvancedComparison(advancedContext) {
41
+ if (!advancedContext?.comparison)
42
+ return '- Historical comparison not available';
43
+ return [
44
+ `- Source: \`${advancedContext.comparison.source}\``,
45
+ `- Trend: **${advancedContext.comparison.trend.toUpperCase()}**`,
46
+ `- Summary: ${advancedContext.comparison.summary}`,
47
+ ].join('\n');
48
+ }
49
+ export function renderTrustAdvancedGuidance(advancedContext) {
50
+ if (!advancedContext?.team_guidance?.length)
51
+ return '- none';
52
+ return advancedContext.team_guidance.map((item) => `- ${item}`).join('\n');
53
+ }
54
+ //# sourceMappingURL=trust-render.js.map
@@ -0,0 +1,9 @@
1
+ import type { DriftDiff, DriftReport, MergeRiskLevel, TrustDiffContext, TrustFixPriority, TrustReason } from './types.js';
2
+ export declare const TOP_REASONS_SLICE = 4;
3
+ export declare function clamp(value: number, min: number, max: number): number;
4
+ export declare function toMergeRisk(trustScore: number): MergeRiskLevel;
5
+ export declare function computeReasons(report: DriftReport): TrustReason[];
6
+ export declare function computeDiffContext(diff: DriftDiff): TrustDiffContext;
7
+ export declare function computeFixPriorities(report: DriftReport, advancedMode?: boolean): TrustFixPriority[];
8
+ export declare function buildDiffRegressionReason(diffContext: TrustDiffContext): TrustReason;
9
+ //# sourceMappingURL=trust-scoring.d.ts.map