@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,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
@@ -0,0 +1,208 @@
1
+ import { RULE_WEIGHTS } from './analyzer.js';
2
+ const ARCHITECTURE_RULES = new Set([
3
+ 'circular-dependency',
4
+ 'layer-violation',
5
+ 'cross-boundary-import',
6
+ 'controller-no-db',
7
+ 'service-no-http',
8
+ ]);
9
+ const RULE_SUGGESTIONS = {
10
+ 'circular-dependency': 'Break cycles first to reduce hidden merge blast radius.',
11
+ 'layer-violation': 'Fix layer violations to keep architecture boundaries enforceable.',
12
+ 'high-complexity': 'Split branch-heavy functions before adding more logic.',
13
+ 'deep-nesting': 'Flatten control flow with early returns.',
14
+ 'large-file': 'Split monolithic files by responsibility before merge.',
15
+ 'large-function': 'Extract smaller functions to reduce review complexity.',
16
+ 'catch-swallow': 'Handle or rethrow swallowed errors to avoid silent failures.',
17
+ 'debug-leftover': 'Remove debug leftovers from production paths.',
18
+ 'semantic-duplication': 'Consolidate duplicated logic to prevent divergent fixes.',
19
+ 'dead-file': 'Delete or wire dead files to avoid stale merge artifacts.',
20
+ };
21
+ const SYSTEMIC_RULES = new Set([
22
+ 'circular-dependency',
23
+ 'layer-violation',
24
+ 'cross-boundary-import',
25
+ 'unused-export',
26
+ 'unused-dependency',
27
+ 'dead-file',
28
+ 'semantic-duplication',
29
+ 'controller-no-db',
30
+ 'service-no-http',
31
+ ]);
32
+ const TRUST_LOW_MIN = 80;
33
+ const TRUST_MEDIUM_MIN = 60;
34
+ const TRUST_HIGH_MIN = 40;
35
+ const DRIFT_PRESSURE_FACTOR = 0.55;
36
+ const ERROR_IMPACT_CAP = 22;
37
+ const ERROR_IMPACT_FACTOR = 4;
38
+ const ARCHITECTURE_IMPACT_CAP = 24;
39
+ const ARCHITECTURE_IMPACT_FACTOR = 6;
40
+ const HOTSPOT_IMPACT_CAP = 25;
41
+ const HOTSPOT_IMPACT_FACTOR = 0.25;
42
+ const WORST_FILE_IMPACT_CAP = 15;
43
+ const WORST_FILE_IMPACT_FACTOR = 0.15;
44
+ const TOP_REASONS_LIMIT = 4;
45
+ const EFFORT_LOW_MAX_WEIGHT = 6;
46
+ const EFFORT_MEDIUM_MAX_WEIGHT = 12;
47
+ const SCORE_REGRESSION_PENALTY_FACTOR = 2;
48
+ const NEW_ISSUE_PENALTY_FACTOR = 3;
49
+ const CHURN_FILE_THRESHOLD = 15;
50
+ const CHURN_PENALTY = 4;
51
+ const PENALTY_CAP = 30;
52
+ const RESOLVED_ISSUE_BONUS_FACTOR = 2;
53
+ const BONUS_CAP = 20;
54
+ const SEVERITY_ERROR_SCORE = 4;
55
+ const SEVERITY_WARNING_SCORE = 2;
56
+ const SEVERITY_INFO_SCORE = 1;
57
+ const PRIORITY_OCCURRENCE_FACTOR = 2;
58
+ const SYSTEMIC_CONFIDENCE_BOOST = 2;
59
+ const CONFIDENCE_HIGH_MIN = 12;
60
+ const CONFIDENCE_MEDIUM_MIN = 7;
61
+ const DEFAULT_WEIGHT_CONFIG = { severity: 'warning', weight: EFFORT_LOW_MAX_WEIGHT };
62
+ const SEVERITY_BOOST_ERROR = 25;
63
+ const SEVERITY_BOOST_WARNING = 12;
64
+ const SEVERITY_BOOST_INFO = 4;
65
+ const SYSTEMIC_PRIORITY_BOOST = 25;
66
+ const TRUST_GAIN_MAX = 30;
67
+ const TRUST_GAIN_MIN = 3;
68
+ const TRUST_GAIN_DIVISOR = 4;
69
+ const FIX_PRIORITIES_LIMIT = 5;
70
+ export const TOP_REASONS_SLICE = TOP_REASONS_LIMIT;
71
+ export function clamp(value, min, max) {
72
+ return Math.max(min, Math.min(max, value));
73
+ }
74
+ export function toMergeRisk(trustScore) {
75
+ if (trustScore >= TRUST_LOW_MIN)
76
+ return 'LOW';
77
+ if (trustScore >= TRUST_MEDIUM_MIN)
78
+ return 'MEDIUM';
79
+ if (trustScore >= TRUST_HIGH_MIN)
80
+ return 'HIGH';
81
+ return 'CRITICAL';
82
+ }
83
+ export function computeReasons(report) {
84
+ const architectureIssues = Object.entries(report.summary.byRule)
85
+ .filter(([rule]) => ARCHITECTURE_RULES.has(rule))
86
+ .reduce((sum, [, count]) => sum + count, 0);
87
+ const worstHotspot = report.maintenanceRisk.hotspots[0];
88
+ const reasons = [
89
+ {
90
+ label: 'Drift score pressure',
91
+ detail: `Repository drift score is ${report.totalScore}/100.`,
92
+ impact: Math.round(report.totalScore * DRIFT_PRESSURE_FACTOR),
93
+ },
94
+ {
95
+ label: 'Error-level issues',
96
+ detail: `${report.summary.errors} error issue(s) increase merge volatility.`,
97
+ impact: Math.min(ERROR_IMPACT_CAP, report.summary.errors * ERROR_IMPACT_FACTOR),
98
+ },
99
+ {
100
+ label: 'Architecture signals',
101
+ detail: `${architectureIssues} architecture-related issue(s) detected.`,
102
+ impact: Math.min(ARCHITECTURE_IMPACT_CAP, architectureIssues * ARCHITECTURE_IMPACT_FACTOR),
103
+ },
104
+ {
105
+ label: 'Maintenance hotspots',
106
+ detail: `Maintenance risk is ${report.maintenanceRisk.level.toUpperCase()} (${report.maintenanceRisk.score}/100).`,
107
+ impact: Math.min(HOTSPOT_IMPACT_CAP, Math.round(report.maintenanceRisk.score * HOTSPOT_IMPACT_FACTOR)),
108
+ },
109
+ {
110
+ label: 'Highest-risk file',
111
+ detail: worstHotspot
112
+ ? `${worstHotspot.file} has hotspot risk ${worstHotspot.risk}/100.`
113
+ : 'No hotspot concentration detected.',
114
+ impact: worstHotspot ? Math.min(WORST_FILE_IMPACT_CAP, Math.round(worstHotspot.risk * WORST_FILE_IMPACT_FACTOR)) : 0,
115
+ },
116
+ ];
117
+ return reasons
118
+ .filter((reason) => reason.impact > 0)
119
+ .sort((a, b) => b.impact - a.impact)
120
+ .slice(0, TOP_REASONS_LIMIT);
121
+ }
122
+ function effortFromWeight(weight) {
123
+ if (weight <= EFFORT_LOW_MAX_WEIGHT)
124
+ return 'low';
125
+ if (weight <= EFFORT_MEDIUM_MAX_WEIGHT)
126
+ return 'medium';
127
+ return 'high';
128
+ }
129
+ export function computeDiffContext(diff) {
130
+ const scoreRegressionPenalty = Math.max(0, diff.totalDelta) * SCORE_REGRESSION_PENALTY_FACTOR;
131
+ const newIssuePenalty = diff.newIssuesCount * NEW_ISSUE_PENALTY_FACTOR;
132
+ const churnPenalty = diff.files.length >= CHURN_FILE_THRESHOLD ? CHURN_PENALTY : 0;
133
+ const penalty = clamp(scoreRegressionPenalty + newIssuePenalty + churnPenalty, 0, PENALTY_CAP);
134
+ const scoreImprovementBonus = Math.max(0, -diff.totalDelta) * SCORE_REGRESSION_PENALTY_FACTOR;
135
+ const resolvedIssueBonus = diff.resolvedIssuesCount * RESOLVED_ISSUE_BONUS_FACTOR;
136
+ const bonus = clamp(scoreImprovementBonus + resolvedIssueBonus, 0, BONUS_CAP);
137
+ const netImpact = penalty - bonus;
138
+ const status = netImpact > 0 ? 'regressed' : netImpact < 0 ? 'improved' : 'neutral';
139
+ return {
140
+ baseRef: diff.baseRef,
141
+ status,
142
+ scoreDelta: diff.totalDelta,
143
+ newIssues: diff.newIssuesCount,
144
+ resolvedIssues: diff.resolvedIssuesCount,
145
+ filesChanged: diff.files.length,
146
+ penalty,
147
+ bonus,
148
+ netImpact,
149
+ };
150
+ }
151
+ function confidenceFromPrioritySignals(occurrences, severity, systemic) {
152
+ const severityScore = severity === 'error' ? SEVERITY_ERROR_SCORE : severity === 'warning' ? SEVERITY_WARNING_SCORE : SEVERITY_INFO_SCORE;
153
+ const systemicScore = systemic ? SYSTEMIC_CONFIDENCE_BOOST : 0;
154
+ const score = occurrences * PRIORITY_OCCURRENCE_FACTOR + severityScore + systemicScore;
155
+ if (score >= CONFIDENCE_HIGH_MIN)
156
+ return 'high';
157
+ if (score >= CONFIDENCE_MEDIUM_MIN)
158
+ return 'medium';
159
+ return 'low';
160
+ }
161
+ export function computeFixPriorities(report, advancedMode = false) {
162
+ const ordered = Object.entries(report.summary.byRule)
163
+ .map(([rule, occurrences]) => {
164
+ const weightConfig = RULE_WEIGHTS[rule] ?? DEFAULT_WEIGHT_CONFIG;
165
+ const severityBoost = weightConfig.severity === 'error' ? SEVERITY_BOOST_ERROR : weightConfig.severity === 'warning' ? SEVERITY_BOOST_WARNING : SEVERITY_BOOST_INFO;
166
+ const systemic = SYSTEMIC_RULES.has(rule);
167
+ const systemicBoost = advancedMode && systemic ? SYSTEMIC_PRIORITY_BOOST : 0;
168
+ const priorityScore = occurrences * weightConfig.weight + severityBoost + systemicBoost;
169
+ const confidence = confidenceFromPrioritySignals(occurrences, weightConfig.severity, systemic);
170
+ const explanation = advancedMode
171
+ ? systemic
172
+ ? 'System-level rule that propagates risk across multiple teams and modules.'
173
+ : 'Local rule with contained impact; treat as team-level cleanup after systemic fixes.'
174
+ : undefined;
175
+ return {
176
+ rule,
177
+ severity: weightConfig.severity,
178
+ occurrences,
179
+ systemic,
180
+ priorityScore,
181
+ estimatedTrustGain: Math.min(TRUST_GAIN_MAX, Math.max(TRUST_GAIN_MIN, Math.round(priorityScore / TRUST_GAIN_DIVISOR))),
182
+ effort: effortFromWeight(weightConfig.weight),
183
+ suggestion: RULE_SUGGESTIONS[rule] ?? 'Address this rule in the highest-scored files first.',
184
+ confidence,
185
+ explanation,
186
+ };
187
+ })
188
+ .sort((a, b) => b.priorityScore - a.priorityScore)
189
+ .slice(0, FIX_PRIORITIES_LIMIT);
190
+ return ordered.map((item, index) => ({
191
+ rank: index + 1,
192
+ rule: item.rule,
193
+ severity: item.severity,
194
+ occurrences: item.occurrences,
195
+ estimated_trust_gain: item.estimatedTrustGain,
196
+ effort: item.effort,
197
+ suggestion: item.suggestion,
198
+ ...(advancedMode ? { confidence: item.confidence, explanation: item.explanation, systemic: item.systemic } : {}),
199
+ }));
200
+ }
201
+ export function buildDiffRegressionReason(diffContext) {
202
+ return {
203
+ label: 'Diff regression signals',
204
+ detail: `Against ${diffContext.baseRef}: score delta ${diffContext.scoreDelta >= 0 ? '+' : ''}${diffContext.scoreDelta}, +${diffContext.newIssues} new issue(s), -${diffContext.resolvedIssues} resolved.`,
205
+ impact: diffContext.netImpact,
206
+ };
207
+ }
208
+ //# sourceMappingURL=trust-scoring.js.map
@@ -0,0 +1,37 @@
1
+ import type { DriftDiff, DriftReport, DriftTrustReport, MergeRiskLevel } from './types.js';
2
+ import type { SnapshotEntry } from './snapshot.js';
3
+ import type { TrustGateOptions } from './trust-policy.js';
4
+ export { MERGE_RISK_ORDER, detectBranchName, explainTrustGatePolicy, formatTrustGatePolicyExplanation, normalizeMergeRiskLevel, resolveTrustGatePolicy, } from './trust-policy.js';
5
+ export type { TrustGatePolicyExplanation, TrustGatePolicyResolutionOptions, TrustGatePolicyResolutionStep, TrustGateOptions, } from './trust-policy.js';
6
+ interface BuildTrustOptions {
7
+ diff?: DriftDiff;
8
+ advanced?: {
9
+ enabled?: boolean;
10
+ previousTrust?: Partial<DriftTrustReport>;
11
+ snapshots?: SnapshotEntry[];
12
+ };
13
+ }
14
+ interface TrustRenderOptions {
15
+ json?: boolean;
16
+ markdown?: boolean;
17
+ }
18
+ export interface TrustGateEvaluation {
19
+ shouldFail: boolean;
20
+ reasons: string[];
21
+ checks: {
22
+ gateDisabled: boolean;
23
+ belowMinTrust: boolean;
24
+ aboveMaxRisk: boolean;
25
+ minTrust?: number;
26
+ maxRisk?: MergeRiskLevel;
27
+ };
28
+ }
29
+ export declare function buildTrustReport(report: DriftReport, options?: BuildTrustOptions): DriftTrustReport;
30
+ export declare function formatTrustConsole(trust: DriftTrustReport): string;
31
+ export declare function formatTrustMarkdown(trust: DriftTrustReport): string;
32
+ export declare function formatTrustJson(trust: DriftTrustReport): string;
33
+ export declare function renderTrustOutput(trust: DriftTrustReport, options?: TrustRenderOptions): string;
34
+ export declare function shouldFailByMaxRisk(actual: MergeRiskLevel, allowedMaxRisk: MergeRiskLevel): boolean;
35
+ export declare function evaluateTrustGate(trust: DriftTrustReport, options: TrustGateOptions): TrustGateEvaluation;
36
+ export declare function shouldFailTrustGate(trust: DriftTrustReport, options: TrustGateOptions): boolean;
37
+ //# sourceMappingURL=trust.d.ts.map
package/dist/trust.js ADDED
@@ -0,0 +1,168 @@
1
+ import { MERGE_RISK_ORDER } from './trust-policy.js';
2
+ import { buildAdvancedContext } from './trust-advanced.js';
3
+ import { TOP_REASONS_SLICE, buildDiffRegressionReason, clamp, computeDiffContext, computeFixPriorities, computeReasons, toMergeRisk, } from './trust-scoring.js';
4
+ import { renderTrustAdvancedComparison, renderTrustAdvancedGuidance, renderTrustDiffBlock, renderTrustMarkdownPriorities, renderTrustMarkdownReasons, renderTrustPriorities, renderTrustReasons, } from './trust-render.js';
5
+ import { OUTPUT_SCHEMA, withOutputMetadata } from './output-metadata.js';
6
+ export { MERGE_RISK_ORDER, detectBranchName, explainTrustGatePolicy, formatTrustGatePolicyExplanation, normalizeMergeRiskLevel, resolveTrustGatePolicy, } from './trust-policy.js';
7
+ const CONSOLE_DIFF_INSERT_INDEX = 5;
8
+ export function buildTrustReport(report, options) {
9
+ const reasons = computeReasons(report);
10
+ const diffContext = options?.diff ? computeDiffContext(options.diff) : undefined;
11
+ if (diffContext && diffContext.netImpact > 0) {
12
+ reasons.push(buildDiffRegressionReason(diffContext));
13
+ }
14
+ const rankedReasons = reasons
15
+ .filter((reason) => reason.impact > 0)
16
+ .sort((a, b) => b.impact - a.impact)
17
+ .slice(0, TOP_REASONS_SLICE);
18
+ const totalPenalty = rankedReasons.reduce((sum, reason) => sum + reason.impact, 0);
19
+ const totalBonus = diffContext && diffContext.netImpact < 0 ? Math.abs(diffContext.netImpact) : 0;
20
+ const trustScore = clamp(Math.round(100 - totalPenalty + totalBonus), 0, 100);
21
+ const advancedMode = options?.advanced?.enabled === true;
22
+ const fixPriorities = computeFixPriorities(report, advancedMode);
23
+ const advancedContext = buildAdvancedContext({
24
+ report,
25
+ advancedOptions: options?.advanced,
26
+ trustScore,
27
+ fixPriorities,
28
+ diffContext,
29
+ });
30
+ return {
31
+ scannedAt: new Date().toISOString(),
32
+ targetPath: report.targetPath,
33
+ trust_score: trustScore,
34
+ merge_risk: toMergeRisk(trustScore),
35
+ top_reasons: rankedReasons,
36
+ fix_priorities: fixPriorities,
37
+ diff_context: diffContext,
38
+ ...(advancedContext ? { advanced_context: advancedContext } : {}),
39
+ };
40
+ }
41
+ export function formatTrustConsole(trust) {
42
+ const diffContext = trust.diff_context;
43
+ const diffLines = diffContext
44
+ ? [
45
+ `- base: ${diffContext.baseRef}`,
46
+ `- status: ${diffContext.status.toUpperCase()}`,
47
+ `- score delta: ${diffContext.scoreDelta >= 0 ? '+' : ''}${diffContext.scoreDelta}`,
48
+ `- issues: +${diffContext.newIssues} new / -${diffContext.resolvedIssues} resolved`,
49
+ `- impact: +${diffContext.penalty} penalty / -${diffContext.bonus} bonus (net ${diffContext.netImpact >= 0 ? '+' : ''}${diffContext.netImpact})`,
50
+ ].join('\n')
51
+ : undefined;
52
+ const reasons = renderTrustReasons(trust.top_reasons);
53
+ const priorities = renderTrustPriorities(trust.fix_priorities);
54
+ const advanced = trust.advanced_context;
55
+ const advancedComparison = advanced?.comparison
56
+ ? [
57
+ `- source: ${advanced.comparison.source}`,
58
+ `- trend: ${advanced.comparison.trend.toUpperCase()}`,
59
+ `- summary: ${advanced.comparison.summary}`,
60
+ ].join('\n')
61
+ : '- no historical comparison available';
62
+ const advancedGuidance = advanced?.team_guidance?.length
63
+ ? advanced.team_guidance.map((item) => `- ${item}`).join('\n')
64
+ : '- none';
65
+ const sections = [
66
+ 'drift trust',
67
+ '',
68
+ `Trust Score: ${trust.trust_score}/100`,
69
+ `Merge Risk: ${trust.merge_risk}`,
70
+ '',
71
+ 'Top Reasons:',
72
+ reasons,
73
+ '',
74
+ 'Fix Priorities:',
75
+ priorities,
76
+ ];
77
+ if (diffLines) {
78
+ sections.splice(CONSOLE_DIFF_INSERT_INDEX, 0, 'Diff Context:', diffLines, '');
79
+ }
80
+ if (advanced) {
81
+ sections.push('', 'Advanced Team Guidance:', advancedComparison, '', advancedGuidance);
82
+ }
83
+ return sections.join('\n');
84
+ }
85
+ export function formatTrustMarkdown(trust) {
86
+ const reasons = renderTrustMarkdownReasons(trust.top_reasons);
87
+ const priorities = renderTrustMarkdownPriorities(trust.fix_priorities);
88
+ const diffBlock = renderTrustDiffBlock(trust.diff_context);
89
+ const advancedComparison = renderTrustAdvancedComparison(trust.advanced_context);
90
+ const advancedGuidance = renderTrustAdvancedGuidance(trust.advanced_context);
91
+ const sections = [
92
+ '## drift trust',
93
+ '',
94
+ `- Trust Score: **${trust.trust_score}/100**`,
95
+ `- Merge Risk: **${trust.merge_risk}**`,
96
+ `- Target: \`${trust.targetPath}\``,
97
+ '',
98
+ '### Diff signals',
99
+ diffBlock,
100
+ '',
101
+ '### Top reasons',
102
+ reasons,
103
+ '',
104
+ '### Fix priorities',
105
+ priorities,
106
+ ];
107
+ if (trust.advanced_context) {
108
+ sections.push('', '### Advanced comparison', advancedComparison, '', '### Team guidance', advancedGuidance);
109
+ }
110
+ return sections.join('\n');
111
+ }
112
+ function formatTrustJsonObject(trust) {
113
+ return withOutputMetadata(trust, OUTPUT_SCHEMA.trust);
114
+ }
115
+ export function formatTrustJson(trust) {
116
+ return JSON.stringify(formatTrustJsonObject(trust), null, 2);
117
+ }
118
+ export function renderTrustOutput(trust, options) {
119
+ if (options?.json)
120
+ return formatTrustJson(trust);
121
+ if (options?.markdown)
122
+ return formatTrustMarkdown(trust);
123
+ return formatTrustConsole(trust);
124
+ }
125
+ export function shouldFailByMaxRisk(actual, allowedMaxRisk) {
126
+ return MERGE_RISK_ORDER.indexOf(actual) > MERGE_RISK_ORDER.indexOf(allowedMaxRisk);
127
+ }
128
+ export function evaluateTrustGate(trust, options) {
129
+ if (options.enabled === false) {
130
+ return {
131
+ shouldFail: false,
132
+ reasons: ['trust gate disabled by policy'],
133
+ checks: {
134
+ gateDisabled: true,
135
+ belowMinTrust: false,
136
+ aboveMaxRisk: false,
137
+ minTrust: options.minTrust,
138
+ maxRisk: options.maxRisk,
139
+ },
140
+ };
141
+ }
142
+ const belowMinTrust = typeof options.minTrust === 'number' &&
143
+ !Number.isNaN(options.minTrust) &&
144
+ trust.trust_score < options.minTrust;
145
+ const aboveMaxRisk = Boolean(options.maxRisk && shouldFailByMaxRisk(trust.merge_risk, options.maxRisk));
146
+ const reasons = [];
147
+ if (belowMinTrust) {
148
+ reasons.push(`trust ${trust.trust_score} is below minTrust ${options.minTrust}`);
149
+ }
150
+ if (aboveMaxRisk && options.maxRisk) {
151
+ reasons.push(`merge risk ${trust.merge_risk} exceeds maxRisk ${options.maxRisk}`);
152
+ }
153
+ return {
154
+ shouldFail: belowMinTrust || aboveMaxRisk,
155
+ reasons,
156
+ checks: {
157
+ gateDisabled: false,
158
+ belowMinTrust,
159
+ aboveMaxRisk,
160
+ minTrust: options.minTrust,
161
+ maxRisk: options.maxRisk,
162
+ },
163
+ };
164
+ }
165
+ export function shouldFailTrustGate(trust, options) {
166
+ return evaluateTrustGate(trust, options).shouldFail;
167
+ }
168
+ //# sourceMappingURL=trust.js.map
@@ -0,0 +1,30 @@
1
+ import type { DriftPerformanceConfig, LayerDefinition, ModuleBoundary } from './config.js';
2
+ import type { TrustGatePolicyConfig } from './trust.js';
3
+ export interface DriftConfig {
4
+ layers?: LayerDefinition[];
5
+ modules?: ModuleBoundary[];
6
+ moduleBoundaries?: ModuleBoundary[];
7
+ boundaries?: ModuleBoundary[];
8
+ plugins?: string[];
9
+ performance?: DriftPerformanceConfig;
10
+ architectureRules?: {
11
+ controllerNoDb?: boolean;
12
+ serviceNoHttp?: boolean;
13
+ maxFunctionLines?: number;
14
+ };
15
+ saas?: {
16
+ freeUserThreshold?: number;
17
+ maxRunsPerWorkspacePerMonth?: number;
18
+ maxReposPerWorkspace?: number;
19
+ retentionDays?: number;
20
+ strictActorEnforcement?: boolean;
21
+ maxWorkspacesPerOrganizationByPlan?: {
22
+ free?: number;
23
+ sponsor?: number;
24
+ team?: number;
25
+ business?: number;
26
+ };
27
+ };
28
+ trustGate?: TrustGatePolicyConfig;
29
+ }
30
+ //# sourceMappingURL=app.d.ts.map