@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
package/dist/index.d.ts CHANGED
@@ -1,11 +1,20 @@
1
1
  export { analyzeProject, analyzeFile, RULE_WEIGHTS } from './analyzer.js';
2
2
  export { buildReport, formatMarkdown } from './reporter.js';
3
3
  export { computeDiff } from './diff.js';
4
+ export { runGuard, evaluateGuard } from './guard.js';
5
+ export type { GuardBaseline, GuardThresholds, GuardOptions, GuardMetrics, GuardCheck, GuardEvaluation, GuardResult, } from './guard-types.js';
4
6
  export { generateReview, formatReviewMarkdown } from './review.js';
7
+ export { runDoctor } from './doctor.js';
8
+ export type { DoctorOptions } from './doctor.js';
9
+ export { buildTrustReport, formatTrustConsole, formatTrustMarkdown, formatTrustJson, resolveTrustGatePolicy, evaluateTrustGate, shouldFailByMaxRisk, shouldFailTrustGate, normalizeMergeRiskLevel, MERGE_RISK_ORDER, } from './trust.js';
10
+ export type { TrustGateOptions, TrustGatePolicyResolutionOptions, TrustGatePolicyResolutionStep, TrustGateEvaluation, } from './trust.js';
11
+ export { computeTrustKpis, computeTrustKpisFromReports, formatTrustKpiConsole, formatTrustKpiJson, } from './trust-kpi.js';
12
+ export { toSarif, diffToSarif } from './sarif.js';
13
+ export type { SarifLevel, DriftSarifRule, DriftSarifResult, DriftSarifRun, DriftSarifLog, } from './sarif.js';
5
14
  export { generateArchitectureMap, generateArchitectureSvg } from './map.js';
6
- export type { DriftReport, FileReport, DriftIssue, DriftDiff, FileDiff, DriftConfig, RepoQualityScore, MaintenanceRiskMetrics, DriftPlugin, DriftPluginRule, } from './types.js';
15
+ export type { DriftReport, FileReport, DriftIssue, DriftDiff, FileDiff, DriftConfig, RepoQualityScore, MaintenanceRiskMetrics, DriftTrustReport, TrustReason, TrustFixPriority, TrustDiffContext, TrustKpiReport, TrustKpiDiagnostic, TrustDiffTrendSummary, TrustScoreStats, MergeRiskLevel, DriftPlugin, DriftPluginRule, TrustGatePolicyConfig, TrustAdvancedContext, } from './types.js';
7
16
  export { loadHistory, saveSnapshot } from './snapshot.js';
8
17
  export type { SnapshotEntry, SnapshotHistory } from './snapshot.js';
9
- export { DEFAULT_SAAS_POLICY, defaultSaasStorePath, resolveSaasPolicy, ingestSnapshotFromReport, getSaasSummary, generateSaasDashboardHtml, } from './saas.js';
10
- export type { SaasPolicy, SaasStore, SaasSummary, SaasSnapshot, IngestOptions, } from './saas.js';
18
+ export { DEFAULT_SAAS_POLICY, defaultSaasStorePath, resolveSaasPolicy, SaasActorRequiredError, SaasPermissionError, getRequiredRoleForOperation, assertSaasPermission, getSaasEffectiveLimits, getOrganizationEffectiveLimits, changeOrganizationPlan, listOrganizationPlanChanges, getOrganizationUsageSnapshot, ingestSnapshotFromReport, listSaasSnapshots, getSaasSummary, generateSaasDashboardHtml, } from './saas.js';
19
+ export type { SaasUser, SaasOrganization, SaasWorkspace, SaasRepo, SaasMembership, SaasRole, SaasPlan, SaasPolicy, SaasPolicyOverrides, SaasStore, SaasSummary, SaasSnapshot, SaasQueryOptions, IngestOptions, SaasPlanChange, SaasOperation, SaasPermissionContext, SaasPermissionResult, SaasEffectiveLimits, SaasOrganizationUsageSnapshot, ChangeOrganizationPlanOptions, SaasUsageQueryOptions, SaasPlanChangeQueryOptions, } from './saas.js';
11
20
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  export { analyzeProject, analyzeFile, RULE_WEIGHTS } from './analyzer.js';
2
2
  export { buildReport, formatMarkdown } from './reporter.js';
3
3
  export { computeDiff } from './diff.js';
4
+ export { runGuard, evaluateGuard } from './guard.js';
4
5
  export { generateReview, formatReviewMarkdown } from './review.js';
6
+ export { runDoctor } from './doctor.js';
7
+ export { buildTrustReport, formatTrustConsole, formatTrustMarkdown, formatTrustJson, resolveTrustGatePolicy, evaluateTrustGate, shouldFailByMaxRisk, shouldFailTrustGate, normalizeMergeRiskLevel, MERGE_RISK_ORDER, } from './trust.js';
8
+ export { computeTrustKpis, computeTrustKpisFromReports, formatTrustKpiConsole, formatTrustKpiJson, } from './trust-kpi.js';
9
+ export { toSarif, diffToSarif } from './sarif.js';
5
10
  export { generateArchitectureMap, generateArchitectureSvg } from './map.js';
6
11
  export { loadHistory, saveSnapshot } from './snapshot.js';
7
- export { DEFAULT_SAAS_POLICY, defaultSaasStorePath, resolveSaasPolicy, ingestSnapshotFromReport, getSaasSummary, generateSaasDashboardHtml, } from './saas.js';
12
+ export { DEFAULT_SAAS_POLICY, defaultSaasStorePath, resolveSaasPolicy, SaasActorRequiredError, SaasPermissionError, getRequiredRoleForOperation, assertSaasPermission, getSaasEffectiveLimits, getOrganizationEffectiveLimits, changeOrganizationPlan, listOrganizationPlanChanges, getOrganizationUsageSnapshot, ingestSnapshotFromReport, listSaasSnapshots, getSaasSummary, generateSaasDashboardHtml, } from './saas.js';
8
13
  //# sourceMappingURL=index.js.map
package/dist/init.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ interface InitOptions {
2
+ preset?: string;
3
+ ci?: boolean;
4
+ baseline?: boolean;
5
+ }
6
+ export declare const INIT_PRESETS: readonly ["node-backend", "react-app", "hexagonal", "monorepo"];
7
+ /**
8
+ * Initialize drift configuration with optional presets and scaffolding.
9
+ *
10
+ * @param projectRoot - Absolute path to project root
11
+ * @param options - Init options from CLI
12
+ */
13
+ export declare function runInit(projectRoot: string, options: InitOptions): Promise<void>;
14
+ export {};
15
+ //# sourceMappingURL=init.d.ts.map
package/dist/init.js ADDED
@@ -0,0 +1,273 @@
1
+ import { writeFileSync, existsSync, mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { analyzeProject } from './analyzer.js';
4
+ import { buildReport } from './reporter.js';
5
+ import { loadConfig } from './config.js';
6
+ import { scoreToGrade } from './utils.js';
7
+ export const INIT_PRESETS = ['node-backend', 'react-app', 'hexagonal', 'monorepo'];
8
+ const CONFIG_PRESET_CONTENT = {
9
+ 'node-backend': `import type { DriftConfig } from '@eduardbar/drift'
10
+
11
+ export default {
12
+ layers: [
13
+ {
14
+ name: 'api',
15
+ patterns: ['src/routes/**', 'src/controllers/**'],
16
+ canImportFrom: ['services', 'middleware', 'types'],
17
+ },
18
+ {
19
+ name: 'services',
20
+ patterns: ['src/services/**'],
21
+ canImportFrom: ['db', 'types'],
22
+ },
23
+ {
24
+ name: 'db',
25
+ patterns: ['src/db/**', 'src/models/**'],
26
+ canImportFrom: ['types'],
27
+ },
28
+ {
29
+ name: 'types',
30
+ patterns: ['src/types/**'],
31
+ canImportFrom: [],
32
+ },
33
+ ],
34
+ } satisfies DriftConfig
35
+ `,
36
+ 'react-app': `import type { DriftConfig } from '@eduardbar/drift'
37
+
38
+ export default {
39
+ layers: [
40
+ {
41
+ name: 'pages',
42
+ patterns: ['src/pages/**', 'src/app/**'],
43
+ canImportFrom: ['components', 'hooks', 'services', 'types'],
44
+ },
45
+ {
46
+ name: 'components',
47
+ patterns: ['src/components/**'],
48
+ canImportFrom: ['hooks', 'types'],
49
+ },
50
+ {
51
+ name: 'hooks',
52
+ patterns: ['src/hooks/**'],
53
+ canImportFrom: ['services', 'types'],
54
+ },
55
+ {
56
+ name: 'services',
57
+ patterns: ['src/services/**', 'src/api/**'],
58
+ canImportFrom: ['types'],
59
+ },
60
+ {
61
+ name: 'types',
62
+ patterns: ['src/types/**'],
63
+ canImportFrom: [],
64
+ },
65
+ ],
66
+ } satisfies DriftConfig
67
+ `,
68
+ hexagonal: `import type { DriftConfig } from '@eduardbar/drift'
69
+
70
+ export default {
71
+ layers: [
72
+ {
73
+ name: 'adapters',
74
+ patterns: ['src/adapters/**', 'src/infrastructure/**'],
75
+ canImportFrom: ['application', 'domain'],
76
+ },
77
+ {
78
+ name: 'application',
79
+ patterns: ['src/application/**', 'src/use-cases/**'],
80
+ canImportFrom: ['domain'],
81
+ },
82
+ {
83
+ name: 'domain',
84
+ patterns: ['src/domain/**'],
85
+ canImportFrom: [],
86
+ },
87
+ ],
88
+ } satisfies DriftConfig
89
+ `,
90
+ monorepo: `import type { DriftConfig } from '@eduardbar/drift'
91
+
92
+ export default {
93
+ modules: [
94
+ {
95
+ name: 'shared',
96
+ root: 'packages/shared',
97
+ allowedExternalImports: [],
98
+ },
99
+ {
100
+ name: 'api',
101
+ root: 'packages/api',
102
+ allowedExternalImports: ['@myorg/shared'],
103
+ },
104
+ {
105
+ name: 'web',
106
+ root: 'packages/web',
107
+ allowedExternalImports: ['@myorg/shared'],
108
+ },
109
+ ],
110
+ } satisfies DriftConfig
111
+ `,
112
+ };
113
+ const GITHUB_WORKFLOW_TEMPLATE = `name: drift PR Review
114
+
115
+ on:
116
+ pull_request:
117
+ branches: [main, master, develop]
118
+
119
+ permissions:
120
+ contents: read
121
+ pull-requests: write
122
+
123
+ jobs:
124
+ drift-review:
125
+ runs-on: ubuntu-latest
126
+ steps:
127
+ - uses: actions/checkout@v4
128
+ with:
129
+ fetch-depth: 0
130
+
131
+ - uses: actions/setup-node@v4
132
+ with:
133
+ node-version: 18
134
+
135
+ - name: Install drift
136
+ run: npm install -g @eduardbar/drift
137
+
138
+ - name: Run drift review
139
+ id: drift
140
+ run: |
141
+ npx drift review --base origin/\${{ github.base_ref }} --comment > drift-comment.md
142
+ echo "score=$(cat drift-comment.md | grep 'Score:' | awk '{print $2}')" >> $GITHUB_OUTPUT
143
+
144
+ - name: Comment PR
145
+ uses: actions/github-script@v7
146
+ with:
147
+ script: |
148
+ const fs = require('fs')
149
+ const comment = fs.readFileSync('drift-comment.md', 'utf8')
150
+
151
+ const { data: comments } = await github.rest.issues.listComments({
152
+ owner: context.repo.owner,
153
+ repo: context.repo.repo,
154
+ issue_number: context.issue.number,
155
+ })
156
+
157
+ const botComment = comments.find(c =>
158
+ c.user?.type === 'Bot' && c.body?.includes('<!-- drift-review -->')
159
+ )
160
+
161
+ const body = '<!-- drift-review -->\\n\\n' + comment
162
+
163
+ if (botComment) {
164
+ await github.rest.issues.updateComment({
165
+ owner: context.repo.owner,
166
+ repo: context.repo.repo,
167
+ comment_id: botComment.id,
168
+ body,
169
+ })
170
+ } else {
171
+ await github.rest.issues.createComment({
172
+ owner: context.repo.owner,
173
+ repo: context.repo.repo,
174
+ issue_number: context.issue.number,
175
+ body,
176
+ })
177
+ }
178
+ `;
179
+ function mapScoreToBaselineGrade(score) {
180
+ const { label } = scoreToGrade(score);
181
+ if (label === 'clean')
182
+ return 'CLEAN';
183
+ if (label === 'low')
184
+ return 'LOW';
185
+ if (label === 'moderate')
186
+ return 'MEDIUM';
187
+ if (label === 'high')
188
+ return 'HIGH';
189
+ return 'CRITICAL';
190
+ }
191
+ /**
192
+ * Initialize drift configuration with optional presets and scaffolding.
193
+ *
194
+ * @param projectRoot - Absolute path to project root
195
+ * @param options - Init options from CLI
196
+ */
197
+ export async function runInit(projectRoot, options) {
198
+ const tasks = [];
199
+ maybeWritePresetConfig(projectRoot, options.preset, tasks);
200
+ maybeWriteCiWorkflow(projectRoot, options.ci, tasks);
201
+ await maybeWriteBaseline(projectRoot, options.baseline, tasks);
202
+ if (tasks.length === 0) {
203
+ process.stdout.write('\n No actions taken. Use --preset, --ci, or --baseline flags.\n\n');
204
+ }
205
+ else {
206
+ process.stdout.write('\n drift init complete:\n\n');
207
+ for (const task of tasks) {
208
+ process.stdout.write(` ${task}\n`);
209
+ }
210
+ process.stdout.write('\n');
211
+ }
212
+ }
213
+ function isInitPreset(value) {
214
+ return INIT_PRESETS.includes(value);
215
+ }
216
+ function maybeWritePresetConfig(projectRoot, preset, tasks) {
217
+ if (!preset)
218
+ return;
219
+ if (!isInitPreset(preset)) {
220
+ throw new Error(`Invalid preset '${preset}'. Use one of: ${INIT_PRESETS.join(', ')}`);
221
+ }
222
+ const configPath = join(projectRoot, 'drift.config.ts');
223
+ if (existsSync(configPath)) {
224
+ process.stderr.write(` ⚠️ drift.config.ts already exists, skipping config generation\n`);
225
+ return;
226
+ }
227
+ writeFileSync(configPath, generateConfigPreset(preset), 'utf8');
228
+ tasks.push('✅ Generated drift.config.ts');
229
+ }
230
+ function maybeWriteCiWorkflow(projectRoot, ci, tasks) {
231
+ if (!ci)
232
+ return;
233
+ const workflowDir = join(projectRoot, '.github', 'workflows');
234
+ const workflowPath = join(workflowDir, 'drift-review.yml');
235
+ if (existsSync(workflowPath)) {
236
+ process.stderr.write(` ⚠️ .github/workflows/drift-review.yml already exists, skipping workflow generation\n`);
237
+ return;
238
+ }
239
+ if (!existsSync(workflowDir)) {
240
+ mkdirSync(workflowDir, { recursive: true });
241
+ }
242
+ writeFileSync(workflowPath, generateGitHubWorkflow(), 'utf8');
243
+ tasks.push('✅ Generated .github/workflows/drift-review.yml');
244
+ }
245
+ async function maybeWriteBaseline(projectRoot, baseline, tasks) {
246
+ if (!baseline)
247
+ return;
248
+ const baselinePath = join(projectRoot, 'drift-baseline.json');
249
+ if (existsSync(baselinePath)) {
250
+ process.stderr.write(` ⚠️ drift-baseline.json already exists, skipping baseline creation\n`);
251
+ return;
252
+ }
253
+ process.stderr.write(' Scanning project to create baseline...\n');
254
+ const config = await loadConfig(projectRoot);
255
+ const files = analyzeProject(projectRoot, config);
256
+ const report = buildReport(projectRoot, files);
257
+ const baselineSnapshot = {
258
+ createdAt: new Date().toISOString(),
259
+ score: report.totalScore,
260
+ grade: mapScoreToBaselineGrade(report.totalScore),
261
+ totalIssues: report.totalIssues,
262
+ files: report.files.length,
263
+ };
264
+ writeFileSync(baselinePath, JSON.stringify(baselineSnapshot, null, 2), 'utf8');
265
+ tasks.push(`✅ Created drift-baseline.json (score: ${report.totalScore}/100, grade: ${baselineSnapshot.grade})`);
266
+ }
267
+ function generateConfigPreset(preset) {
268
+ return CONFIG_PRESET_CONTENT[preset];
269
+ }
270
+ function generateGitHubWorkflow() {
271
+ return GITHUB_WORKFLOW_TEMPLATE;
272
+ }
273
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1,2 @@
1
+ export declare function detectCycleEdges(adjacency: Map<string, Set<string>>): Set<string>;
2
+ //# sourceMappingURL=map-cycles.d.ts.map
@@ -0,0 +1,34 @@
1
+ export function detectCycleEdges(adjacency) {
2
+ const visited = new Set();
3
+ const inStack = new Set();
4
+ const stack = [];
5
+ const cycleEdges = new Set();
6
+ function dfs(node) {
7
+ visited.add(node);
8
+ inStack.add(node);
9
+ stack.push(node);
10
+ for (const neighbor of adjacency.get(node) ?? []) {
11
+ if (!visited.has(neighbor)) {
12
+ dfs(neighbor);
13
+ continue;
14
+ }
15
+ if (!inStack.has(neighbor))
16
+ continue;
17
+ const startIndex = stack.indexOf(neighbor);
18
+ if (startIndex >= 0) {
19
+ for (let i = startIndex; i < stack.length - 1; i++) {
20
+ cycleEdges.add(`${stack[i]}->${stack[i + 1]}`);
21
+ }
22
+ }
23
+ cycleEdges.add(`${node}->${neighbor}`);
24
+ }
25
+ stack.pop();
26
+ inStack.delete(node);
27
+ }
28
+ for (const node of adjacency.keys()) {
29
+ if (!visited.has(node))
30
+ dfs(node);
31
+ }
32
+ return cycleEdges;
33
+ }
34
+ //# sourceMappingURL=map-cycles.js.map
@@ -0,0 +1,19 @@
1
+ interface LayerNode {
2
+ name: string;
3
+ files: Set<string>;
4
+ }
5
+ interface MapEdge {
6
+ key: string;
7
+ from: string;
8
+ to: string;
9
+ count: number;
10
+ kind: 'normal' | 'cycle' | 'violation';
11
+ }
12
+ export declare function renderArchitectureSvg(input: {
13
+ layers: Map<string, LayerNode>;
14
+ edgeList: MapEdge[];
15
+ cycleCount: number;
16
+ violationCount: number;
17
+ }): string;
18
+ export {};
19
+ //# sourceMappingURL=map-svg.d.ts.map
@@ -0,0 +1,97 @@
1
+ const SVG_WIDTH = 960;
2
+ const ROW_HEIGHT = 90;
3
+ const MIN_CANVAS_HEIGHT = 180;
4
+ const BOTTOM_PADDING = 120;
5
+ const BOX_WIDTH = 240;
6
+ const BOX_HEIGHT = 50;
7
+ const BOX_LEFT = 100;
8
+ const BOX_TOP_OFFSET = 60;
9
+ const NORMAL_EDGE_WIDTH = 2;
10
+ const HIGHLIGHT_EDGE_WIDTH = 3;
11
+ const EDGE_LABEL_Y_OFFSET = 4;
12
+ const NODE_TITLE_X_OFFSET = 12;
13
+ const NODE_TITLE_Y_OFFSET = 22;
14
+ const NODE_META_X_OFFSET = 12;
15
+ const NODE_META_Y_OFFSET = 38;
16
+ const LEGEND_CYCLE_START_X = 520;
17
+ const LEGEND_CYCLE_END_X = 560;
18
+ const LEGEND_CYCLE_LABEL_X = 567;
19
+ const LEGEND_VIOLATION_START_X = 630;
20
+ const LEGEND_VIOLATION_END_X = 670;
21
+ const LEGEND_VIOLATION_LABEL_X = 677;
22
+ const LEGEND_LINE_Y = 66;
23
+ const LEGEND_LABEL_Y = 69;
24
+ function esc(value) {
25
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
26
+ }
27
+ function buildSvgLayout(layers) {
28
+ const layerList = [...layers.values()].sort((a, b) => a.name.localeCompare(b.name));
29
+ const height = Math.max(MIN_CANVAS_HEIGHT, layerList.length * ROW_HEIGHT + BOTTOM_PADDING);
30
+ const boxes = layerList.map((layer, index) => ({
31
+ ...layer,
32
+ x: BOX_LEFT,
33
+ y: BOX_TOP_OFFSET + index * ROW_HEIGHT,
34
+ }));
35
+ return {
36
+ width: SVG_WIDTH,
37
+ height,
38
+ boxes,
39
+ };
40
+ }
41
+ function edgeStroke(kind) {
42
+ if (kind === 'violation')
43
+ return '#ef4444';
44
+ if (kind === 'cycle')
45
+ return '#f59e0b';
46
+ return '#64748b';
47
+ }
48
+ function edgeStrokeWidth(kind) {
49
+ return kind === 'normal' ? NORMAL_EDGE_WIDTH : HIGHLIGHT_EDGE_WIDTH;
50
+ }
51
+ function renderEdges(edgeList, boxByName) {
52
+ return edgeList.map((edge) => {
53
+ const a = boxByName.get(edge.from);
54
+ const b = boxByName.get(edge.to);
55
+ if (!a || !b)
56
+ return '';
57
+ const startX = a.x + BOX_WIDTH;
58
+ const startY = a.y + BOX_HEIGHT / 2;
59
+ const endX = b.x;
60
+ const endY = b.y + BOX_HEIGHT / 2;
61
+ const stroke = edgeStroke(edge.kind);
62
+ const widthPx = edgeStrokeWidth(edge.kind);
63
+ return `
64
+ <line x1="${startX}" y1="${startY}" x2="${endX}" y2="${endY}" stroke="${stroke}" stroke-width="${widthPx}" marker-end="url(#arrow)" data-edge="${esc(edge.key)}" data-kind="${edge.kind}" />
65
+ <text x="${(startX + endX) / 2}" y="${(startY + endY) / 2 - EDGE_LABEL_Y_OFFSET}" fill="#94a3b8" font-size="11" text-anchor="middle">${edge.count}</text>`;
66
+ }).join('');
67
+ }
68
+ function renderNodes(boxes) {
69
+ return boxes.map((box) => `
70
+ <g>
71
+ <rect x="${box.x}" y="${box.y}" width="${BOX_WIDTH}" height="${BOX_HEIGHT}" rx="8" fill="#0f172a" stroke="#334155" />
72
+ <text x="${box.x + NODE_TITLE_X_OFFSET}" y="${box.y + NODE_TITLE_Y_OFFSET}" fill="#e2e8f0" font-size="13" font-family="monospace">${esc(box.name)}</text>
73
+ <text x="${box.x + NODE_META_X_OFFSET}" y="${box.y + NODE_META_Y_OFFSET}" fill="#94a3b8" font-size="11" font-family="monospace">${box.files.size} file(s)</text>
74
+ </g>`).join('');
75
+ }
76
+ export function renderArchitectureSvg(input) {
77
+ const { width, height, boxes } = buildSvgLayout(input.layers);
78
+ const boxByName = new Map(boxes.map((box) => [box.name, box]));
79
+ const lines = renderEdges(input.edgeList, boxByName);
80
+ const nodes = renderNodes(boxes);
81
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
82
+ <defs>
83
+ <marker id="arrow" markerWidth="10" markerHeight="10" refX="6" refY="3" orient="auto">
84
+ <path d="M0,0 L0,6 L7,3 z" fill="#64748b"/>
85
+ </marker>
86
+ </defs>
87
+ <rect x="0" y="0" width="${width}" height="${height}" fill="#020617" />
88
+ <text x="28" y="34" fill="#f8fafc" font-size="16" font-family="monospace">drift architecture map</text>
89
+ <text x="28" y="54" fill="#94a3b8" font-size="11" font-family="monospace">Layers inferred from top-level directories</text>
90
+ <text x="28" y="72" fill="#94a3b8" font-size="11" font-family="monospace">Cycle edges: ${input.cycleCount} | Layer violations: ${input.violationCount}</text>
91
+ <line x1="${LEGEND_CYCLE_START_X}" y1="${LEGEND_LINE_Y}" x2="${LEGEND_CYCLE_END_X}" y2="${LEGEND_LINE_Y}" stroke="#f59e0b" stroke-width="${HIGHLIGHT_EDGE_WIDTH}" /><text x="${LEGEND_CYCLE_LABEL_X}" y="${LEGEND_LABEL_Y}" fill="#94a3b8" font-size="11" font-family="monospace">cycle</text>
92
+ <line x1="${LEGEND_VIOLATION_START_X}" y1="${LEGEND_LINE_Y}" x2="${LEGEND_VIOLATION_END_X}" y2="${LEGEND_LINE_Y}" stroke="#ef4444" stroke-width="${HIGHLIGHT_EDGE_WIDTH}" /><text x="${LEGEND_VIOLATION_LABEL_X}" y="${LEGEND_LABEL_Y}" fill="#94a3b8" font-size="11" font-family="monospace">violation</text>
93
+ ${lines}
94
+ ${nodes}
95
+ </svg>`;
96
+ }
97
+ //# sourceMappingURL=map-svg.js.map