@eduardbar/drift 1.1.0 → 1.2.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.
- package/.github/workflows/review-pr.yml +61 -0
- package/README.md +39 -1
- package/dist/cli.js +97 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/map.d.ts +3 -2
- package/dist/map.js +98 -10
- package/dist/saas.d.ts +83 -0
- package/dist/saas.js +321 -0
- package/dist/types.d.ts +6 -0
- package/docs/PRD.md +125 -176
- package/package.json +1 -1
- package/packages/vscode-drift/src/code-actions.ts +53 -0
- package/packages/vscode-drift/src/extension.ts +11 -0
- package/src/cli.ts +112 -3
- package/src/index.ts +15 -0
- package/src/map.ts +112 -10
- package/src/saas.ts +433 -0
- package/src/types.ts +6 -0
- package/tests/new-features.test.ts +27 -0
- package/tests/saas-foundation.test.ts +107 -0
package/docs/PRD.md
CHANGED
|
@@ -1,208 +1,157 @@
|
|
|
1
1
|
# PRD - drift
|
|
2
2
|
|
|
3
|
-
Version: 1.
|
|
4
|
-
Estado:
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Estado: Activo
|
|
5
5
|
Producto: `@eduardbar/drift`
|
|
6
6
|
|
|
7
7
|
## 1) Contexto
|
|
8
8
|
|
|
9
|
-
`drift` es un CLI de analisis estatico para TypeScript que detecta deuda tecnica asociada a codigo generado por IA y calcula
|
|
9
|
+
`drift` es un CLI de analisis estatico para TypeScript que detecta deuda tecnica asociada a codigo generado por IA y calcula score por archivo y por repositorio.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Con release `v1.2.0`, el producto entrega comandos operativos, analisis AST, reglas de arquitectura configurables, salida accionable, workflow CI para PR comments, y foundations SaaS (`drift cloud ingest|summary|dashboard`) con politica free-until-7500.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## 2) Vision de producto
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Ser la herramienta de referencia para equipos que usan IA para programar y necesitan detectar, priorizar y corregir deuda tecnica antes de mergear a produccion.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
## 3) Killer Feature
|
|
17
|
+
## 3) Killer feature
|
|
20
18
|
|
|
21
19
|
## AI Code Smell Detector
|
|
22
20
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## 4)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
###
|
|
49
|
-
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### 5.7 Fix automatico (`drift fix`) con ejemplo antes/despues
|
|
108
|
-
|
|
109
|
-
- Objetivo: convertir hallazgos en cambios concretos de bajo riesgo.
|
|
110
|
-
- Alcance MVP:
|
|
111
|
-
- `drift fix` aplica fixes seguros en reglas seleccionadas.
|
|
112
|
-
- Modo preview con diff antes/despues.
|
|
113
|
-
- Modo write con confirmacion.
|
|
114
|
-
- Criterios de aceptacion:
|
|
115
|
-
- `drift fix --preview` imprime diff legible.
|
|
116
|
-
- `drift fix --write` modifica solo reglas soportadas.
|
|
117
|
-
- Tests de no-regresion para no romper sintaxis TS.
|
|
118
|
-
|
|
119
|
-
Ejemplo (antes/despues):
|
|
120
|
-
|
|
121
|
-
```ts
|
|
122
|
-
// Antes
|
|
123
|
-
console.log(userData)
|
|
124
|
-
|
|
125
|
-
// Despues (sugerencia simple)
|
|
126
|
-
// Removed debug leftover; use structured logger if needed.
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### 5.8 Reporte tecnico (`drift report` -> `drift-report.html`)
|
|
130
|
-
|
|
131
|
-
- Objetivo: entregar reporte compartible para devs, tech leads y QA.
|
|
132
|
-
- Alcance MVP:
|
|
133
|
-
- Salida HTML `drift-report.html` con score, breakdown y top issues.
|
|
134
|
-
- Secciones por archivo con snippets y sugerencias.
|
|
135
|
-
- Criterios de aceptacion:
|
|
136
|
-
- `drift report ./src --html` genera archivo navegable.
|
|
137
|
-
- El reporte puede adjuntarse en CI artifacts.
|
|
138
|
-
|
|
139
|
-
### 5.9 Metricas de riesgo de mantenimiento (hotspots)
|
|
140
|
-
|
|
141
|
-
- Objetivo: priorizar deuda por impacto real.
|
|
142
|
-
- Alcance MVP:
|
|
143
|
-
- Hotspots combinando score + frecuencia de cambios + criticidad.
|
|
144
|
-
- Ranking de archivos para plan de refactor.
|
|
145
|
-
- Criterios de aceptacion:
|
|
146
|
-
- `drift trend` o salida dedicada muestra top hotspots.
|
|
147
|
-
- Metodo de ranking documentado y testeado.
|
|
148
|
-
|
|
149
|
-
### 5.10 Plugin system (`drift-plugin-*`)
|
|
150
|
-
|
|
151
|
-
- Objetivo: extender drift sin tocar el core.
|
|
152
|
-
- Alcance MVP:
|
|
153
|
-
- Carga de plugins por convension `drift-plugin-*`.
|
|
154
|
-
- API minima para registrar reglas y metadata.
|
|
155
|
-
- Aislamiento de errores de plugins para no romper scan completo.
|
|
156
|
-
- Criterios de aceptacion:
|
|
157
|
-
- Plugin de ejemplo funcional en repo de ejemplo.
|
|
158
|
-
- Si un plugin falla, drift sigue ejecutando y reporta el error.
|
|
159
|
-
|
|
160
|
-
## 6) Roadmap Realista
|
|
161
|
-
|
|
162
|
-
### v1.1
|
|
163
|
-
|
|
164
|
-
- `drift review` para PR comments.
|
|
165
|
-
- Score de PR y score de repo con breakdown minimo.
|
|
166
|
-
|
|
167
|
-
### v1.2
|
|
21
|
+
Detectar patrones de olor tecnico vinculados a codigo IA, estimar probabilidad de origen IA y traducir hallazgos en acciones concretas (fixes, review de PR, reglas de arquitectura y reportes).
|
|
22
|
+
|
|
23
|
+
## 4) Estado de cumplimiento (actualizado)
|
|
24
|
+
|
|
25
|
+
### Entregado
|
|
26
|
+
|
|
27
|
+
- `drift review` en CLI para analizar diff contra base y producir markdown usable en PR.
|
|
28
|
+
- `drift map` basico para generar `architecture.svg`.
|
|
29
|
+
- Senial de IA en salida (`ai_likelihood` y `files_suspected`).
|
|
30
|
+
- Reglas de arquitectura configurables via `drift.config.ts`.
|
|
31
|
+
- Score y breakdown por dimensiones para lectura ejecutiva y tecnica.
|
|
32
|
+
- Metricas de maintenance risk/hotspots.
|
|
33
|
+
- Plugin system MVP (`drift-plugin-*`) con aislamiento de errores.
|
|
34
|
+
- `drift fix` con modos preview/write.
|
|
35
|
+
- Workflow CI para comentario automatico unico y actualizable de `drift review`.
|
|
36
|
+
- `drift map` con marcado de cycle edges y layer violations en el SVG.
|
|
37
|
+
- VSCode quick actions para fixes de bajo riesgo.
|
|
38
|
+
- Confirmacion interactiva para `drift fix --write` (con `--yes` para CI/no-interactive).
|
|
39
|
+
- `drift report` HTML (`drift-report.html`) sin flag extra.
|
|
40
|
+
- Documentacion y tests del release.
|
|
41
|
+
|
|
42
|
+
### Parcial
|
|
43
|
+
|
|
44
|
+
- Consolidacion/hardening de API de plugins para ecosistema externo amplio.
|
|
45
|
+
|
|
46
|
+
### Pendiente
|
|
47
|
+
|
|
48
|
+
- Hardening del contrato de plugins para ecosistema externo amplio.
|
|
49
|
+
- Evolucion del dashboard SaaS foundations a experiencia multi-tenant full (auth, permisos por rol y billing activo post-umbral).
|
|
50
|
+
|
|
51
|
+
## 5) Criterios de aceptacion vigentes
|
|
52
|
+
|
|
53
|
+
### 5.1 Entregables cerrados en v1.1.0
|
|
54
|
+
|
|
55
|
+
- `drift review --base <ref>` devuelve score delta de PR, issues nuevos/resueltos y markdown.
|
|
56
|
+
- `drift scan --ai` incluye `ai_likelihood` y ranking `files_suspected`.
|
|
57
|
+
- `drift map <path>` genera `architecture.svg` utilizable sin edicion manual.
|
|
58
|
+
- `drift report [path]` genera HTML self-contained (no requiere `--html`).
|
|
59
|
+
- `drift fix --preview` muestra antes/despues y `drift fix --write` aplica reglas soportadas.
|
|
60
|
+
|
|
61
|
+
### 5.1.b Entregables cerrados en v1.2 (scope tecnico)
|
|
62
|
+
|
|
63
|
+
- Workflow CI publica/actualiza comentario unico en PR para `drift review`.
|
|
64
|
+
- `drift map` marca visualmente ciclos y violaciones por capa.
|
|
65
|
+
- Extension VSCode expone quick actions para `debug-leftover` y `catch-swallow`.
|
|
66
|
+
- `drift fix --write` pide confirmacion interactiva por defecto y admite `--yes`.
|
|
67
|
+
|
|
68
|
+
### 5.2 Objetivos aun abiertos (CI/editor/UX)
|
|
69
|
+
|
|
70
|
+
- Hardening del contrato de plugins para compatibilidad de largo plazo (versionado/migraciones).
|
|
71
|
+
|
|
72
|
+
## 6) Roadmap actualizado
|
|
73
|
+
|
|
74
|
+
### v1.1 (completado - release 1.1.0)
|
|
75
|
+
|
|
76
|
+
Prioridades cerradas:
|
|
77
|
+
- CLI de review para PR, mapa basico, salida AI, reglas configurables, report HTML, fix preview/write, hotspots, plugin MVP.
|
|
78
|
+
|
|
79
|
+
Done del bloque:
|
|
80
|
+
- Features documentadas.
|
|
81
|
+
- Tests de paths principales.
|
|
82
|
+
- Salidas CLI/JSON/AI consistentes para uso local y CI.
|
|
83
|
+
|
|
84
|
+
### v1.2 (completado - cierre de pendientes tecnicos)
|
|
85
|
+
|
|
86
|
+
Prioridades cerradas:
|
|
87
|
+
- Comentario automatico actualizable en PR desde workflow CI.
|
|
88
|
+
- Mejora de `drift map` para destacar ciclos y violaciones.
|
|
89
|
+
- UX de seguridad para `drift fix --write` con confirmacion interactiva.
|
|
90
|
+
|
|
91
|
+
Done del bloque:
|
|
92
|
+
- Flujo CI reproducible con comentario unico por PR.
|
|
93
|
+
- Visualizaciones verificables en SVG sobre repos medianos.
|
|
94
|
+
- Confirmacion interactiva implementada para write mode.
|
|
95
|
+
|
|
96
|
+
### v2 (prioridad: experiencia de editor + extensibilidad)
|
|
97
|
+
|
|
98
|
+
Prioridades:
|
|
99
|
+
- Consolidacion de API de plugins y hardening de compatibilidad.
|
|
100
|
+
- Reglas de plugin versionadas y validacion de contrato avanzada.
|
|
101
|
+
|
|
102
|
+
Criterio de done:
|
|
103
|
+
- Plugins con contrato estable y manejo de errores robusto.
|
|
104
|
+
- Documentacion de versionado para autores de plugins.
|
|
168
105
|
|
|
169
|
-
|
|
170
|
-
- `drift map` y generacion de `architecture.svg`.
|
|
106
|
+
### v3 (fundations completadas en v1.2.0)
|
|
171
107
|
|
|
172
|
-
|
|
108
|
+
Prioridades cerradas:
|
|
109
|
+
- Base de datos local de snapshots para cloud MVP.
|
|
110
|
+
- Ingestion de reportes en storage local SaaS-like.
|
|
111
|
+
- Summary de uso/threshold y dashboard HTML inicial.
|
|
112
|
+
- Guardrails de fase gratuita por workspace + politica free-until-7500.
|
|
173
113
|
|
|
174
|
-
|
|
175
|
-
-
|
|
114
|
+
Siguiente incremento (v3.x):
|
|
115
|
+
- Auth real multi-tenant, permisos por equipo y backend remoto persistente.
|
|
116
|
+
- Activacion de billing cuando el umbral de 7.500 usuarios se cumpla.
|
|
176
117
|
|
|
177
|
-
|
|
118
|
+
## 6.1) Estrategia de monetizacion (aprobada)
|
|
178
119
|
|
|
179
|
-
-
|
|
120
|
+
- Fase gratuita: Drift SaaS gratis hasta alcanzar 7.500 usuarios registrados.
|
|
121
|
+
- Trigger de monetizacion: al alcanzar 7.500 usuarios, activar planes pagos para nuevos usuarios y definir politica de migracion para cohortes gratuitas.
|
|
122
|
+
- Objetivo: priorizar adopcion y proof-of-value temprano sin friccion comercial inicial.
|
|
123
|
+
- Guardrails durante fase gratuita:
|
|
124
|
+
- Limites tecnicos por workspace (runs/mes, retencion de historial, repositorios activos).
|
|
125
|
+
- Instrumentacion de uso desde el dia 1 para evitar abuso y medir unit economics.
|
|
126
|
+
- Feature flags de pricing listas antes del trigger para evitar corte abrupto.
|
|
180
127
|
|
|
181
|
-
## 7) Fuera de
|
|
128
|
+
## 7) Fuera de alcance actual
|
|
182
129
|
|
|
183
130
|
- Soporte multi-lenguaje completo fuera de TypeScript/JS.
|
|
184
|
-
- Autofix de reglas de alto riesgo sin confirmacion.
|
|
131
|
+
- Autofix de reglas de alto riesgo sin confirmacion explicita.
|
|
185
132
|
- Integraciones propietarias cerradas sin API estable.
|
|
186
133
|
|
|
187
|
-
## 8) KPIs de
|
|
134
|
+
## 8) KPIs de exito
|
|
188
135
|
|
|
189
136
|
- Reduccion de score promedio en repos activos.
|
|
190
137
|
- % de PRs con feedback drift resuelto antes de merge.
|
|
191
138
|
- Tiempo medio desde deteccion hasta fix aplicado.
|
|
192
|
-
- Adopcion de reglas de arquitectura por equipo.
|
|
139
|
+
- Adopcion de reglas de arquitectura configurables por equipo.
|
|
193
140
|
|
|
194
|
-
## 9) Dependencias y
|
|
141
|
+
## 9) Dependencias y riesgos
|
|
195
142
|
|
|
196
|
-
- Performance en repos grandes (AST +
|
|
197
|
-
- Calidad de senial en `ai_likelihood` (
|
|
198
|
-
-
|
|
199
|
-
-
|
|
143
|
+
- Performance en repos grandes (AST + cross-file).
|
|
144
|
+
- Calidad de senial en `ai_likelihood` (falsos positivos/negativos).
|
|
145
|
+
- Variabilidad de entornos CI para publicar comentarios de PR.
|
|
146
|
+
- Evolucion de API de plugins sin romper backward compatibility.
|
|
200
147
|
|
|
201
|
-
## 10)
|
|
148
|
+
## 10) Definition of Done por release
|
|
202
149
|
|
|
203
|
-
|
|
150
|
+
Checklist minimo por release:
|
|
204
151
|
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
152
|
+
- [ ] Scope del release cerrado y trazable a este PRD.
|
|
153
|
+
- [ ] Comandos/flujo documentados con ejemplos reales.
|
|
154
|
+
- [ ] Tests de regresion en paths principales y casos borde.
|
|
155
|
+
- [ ] Salidas CLI/JSON/AI estables para automatizacion.
|
|
156
|
+
- [ ] Criterios de aceptacion del bloque marcados como cumplidos o movidos a pendiente.
|
|
157
|
+
- [ ] Riesgos y tradeoffs explicitados en notas de release.
|
package/package.json
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
|
|
3
|
+
function buildRemoveLineEdit(document: vscode.TextDocument, line: number): vscode.WorkspaceEdit {
|
|
4
|
+
const edit = new vscode.WorkspaceEdit()
|
|
5
|
+
const targetLine = document.lineAt(line)
|
|
6
|
+
const start = targetLine.range.start
|
|
7
|
+
const end = line < document.lineCount - 1
|
|
8
|
+
? document.lineAt(line + 1).range.start
|
|
9
|
+
: targetLine.range.end
|
|
10
|
+
edit.delete(document.uri, new vscode.Range(start, end))
|
|
11
|
+
return edit
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildCatchTodoEdit(document: vscode.TextDocument, line: number): vscode.WorkspaceEdit {
|
|
15
|
+
const edit = new vscode.WorkspaceEdit()
|
|
16
|
+
const targetLine = document.lineAt(line)
|
|
17
|
+
const baseIndent = targetLine.text.match(/^\s*/)?.[0] ?? ''
|
|
18
|
+
const indent = `${baseIndent} `
|
|
19
|
+
edit.insert(document.uri, new vscode.Position(line + 1, 0), `${indent}// TODO: handle error\n`)
|
|
20
|
+
return edit
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class DriftCodeActionProvider implements vscode.CodeActionProvider {
|
|
24
|
+
provideCodeActions(
|
|
25
|
+
document: vscode.TextDocument,
|
|
26
|
+
_range: vscode.Range,
|
|
27
|
+
context: vscode.CodeActionContext,
|
|
28
|
+
): vscode.CodeAction[] {
|
|
29
|
+
const actions: vscode.CodeAction[] = []
|
|
30
|
+
|
|
31
|
+
for (const diagnostic of context.diagnostics) {
|
|
32
|
+
if (diagnostic.source !== 'drift') continue
|
|
33
|
+
const rule = String(diagnostic.code ?? '')
|
|
34
|
+
const line = diagnostic.range.start.line
|
|
35
|
+
|
|
36
|
+
if (rule === 'debug-leftover') {
|
|
37
|
+
const quickFix = new vscode.CodeAction('drift: remove debug leftover line', vscode.CodeActionKind.QuickFix)
|
|
38
|
+
quickFix.diagnostics = [diagnostic]
|
|
39
|
+
quickFix.edit = buildRemoveLineEdit(document, line)
|
|
40
|
+
actions.push(quickFix)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (rule === 'catch-swallow') {
|
|
44
|
+
const quickFix = new vscode.CodeAction('drift: add TODO in empty catch', vscode.CodeActionKind.QuickFix)
|
|
45
|
+
quickFix.diagnostics = [diagnostic]
|
|
46
|
+
quickFix.edit = buildCatchTodoEdit(document, line)
|
|
47
|
+
actions.push(quickFix)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return actions
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -5,6 +5,7 @@ import { analyzeFilePath } from './analyzer'
|
|
|
5
5
|
import { DriftDiagnosticsProvider } from './diagnostics'
|
|
6
6
|
import { DriftTreeProvider } from './treeview'
|
|
7
7
|
import { DriftStatusBarItem } from './statusbar'
|
|
8
|
+
import { DriftCodeActionProvider } from './code-actions'
|
|
8
9
|
import type { FileReport } from '@eduardbar/drift'
|
|
9
10
|
|
|
10
11
|
const SUPPORTED_LANGUAGES = ['typescript', 'typescriptreact', 'javascript', 'javascriptreact']
|
|
@@ -87,6 +88,7 @@ export function activate(context: vscode.ExtensionContext): void {
|
|
|
87
88
|
const diagnostics = new DriftDiagnosticsProvider()
|
|
88
89
|
const treeProvider = new DriftTreeProvider()
|
|
89
90
|
const statusBar = new DriftStatusBarItem()
|
|
91
|
+
const codeActions = new DriftCodeActionProvider()
|
|
90
92
|
|
|
91
93
|
const treeView = vscode.window.createTreeView('driftIssues', {
|
|
92
94
|
treeDataProvider: treeProvider,
|
|
@@ -121,6 +123,14 @@ export function activate(context: vscode.ExtensionContext): void {
|
|
|
121
123
|
}
|
|
122
124
|
)
|
|
123
125
|
|
|
126
|
+
const codeActionRegistration = vscode.languages.registerCodeActionsProvider(
|
|
127
|
+
SUPPORTED_LANGUAGES.map((language) => ({ language })),
|
|
128
|
+
codeActions,
|
|
129
|
+
{
|
|
130
|
+
providedCodeActionKinds: [vscode.CodeActionKind.QuickFix],
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
|
|
124
134
|
context.subscriptions.push(
|
|
125
135
|
{ dispose: () => diagnostics.dispose() },
|
|
126
136
|
{ dispose: () => statusBar.dispose() },
|
|
@@ -129,6 +139,7 @@ export function activate(context: vscode.ExtensionContext): void {
|
|
|
129
139
|
scanCmd,
|
|
130
140
|
clearCmd,
|
|
131
141
|
goToCmd,
|
|
142
|
+
codeActionRegistration,
|
|
132
143
|
)
|
|
133
144
|
}
|
|
134
145
|
|
package/src/cli.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// drift-ignore-file
|
|
3
3
|
import { Command } from 'commander'
|
|
4
4
|
import { writeFileSync } from 'node:fs'
|
|
5
|
-
import { resolve } from 'node:path'
|
|
5
|
+
import { basename, resolve } from 'node:path'
|
|
6
6
|
import { createRequire } from 'node:module'
|
|
7
|
+
import { createInterface } from 'node:readline/promises'
|
|
8
|
+
import { stdin as input, stdout as output } from 'node:process'
|
|
7
9
|
const require = createRequire(import.meta.url)
|
|
8
10
|
const { version: VERSION } = require('../package.json') as { version: string }
|
|
9
11
|
import { analyzeProject, analyzeFile, TrendAnalyzer, BlameAnalyzer } from './analyzer.js'
|
|
@@ -19,6 +21,7 @@ import { applyFixes, type FixResult } from './fix.js'
|
|
|
19
21
|
import { loadHistory, saveSnapshot, printHistory, printSnapshotDiff } from './snapshot.js'
|
|
20
22
|
import { generateReview } from './review.js'
|
|
21
23
|
import { generateArchitectureMap } from './map.js'
|
|
24
|
+
import { ingestSnapshotFromReport, getSaasSummary, generateSaasDashboardHtml } from './saas.js'
|
|
22
25
|
|
|
23
26
|
const program = new Command()
|
|
24
27
|
|
|
@@ -155,7 +158,8 @@ program
|
|
|
155
158
|
.action(async (targetPath: string | undefined, options: { output: string }) => {
|
|
156
159
|
const resolvedPath = resolve(targetPath ?? '.')
|
|
157
160
|
process.stderr.write(`\nBuilding architecture map for ${resolvedPath}...\n`)
|
|
158
|
-
const
|
|
161
|
+
const config = await loadConfig(resolvedPath)
|
|
162
|
+
const out = generateArchitectureMap(resolvedPath, options.output, config)
|
|
159
163
|
process.stderr.write(` Architecture map saved to ${out}\n\n`)
|
|
160
164
|
})
|
|
161
165
|
|
|
@@ -259,12 +263,38 @@ program
|
|
|
259
263
|
.option('--preview', 'Preview changes without writing files')
|
|
260
264
|
.option('--write', 'Write fixes to disk')
|
|
261
265
|
.option('--dry-run', 'Show what would change without writing files')
|
|
262
|
-
.
|
|
266
|
+
.option('-y, --yes', 'Skip interactive confirmation for --write')
|
|
267
|
+
.action(async (targetPath: string | undefined, options: { rule?: string; dryRun?: boolean; preview?: boolean; write?: boolean; yes?: boolean }) => {
|
|
263
268
|
const resolvedPath = resolve(targetPath ?? '.')
|
|
264
269
|
const config = await loadConfig(resolvedPath)
|
|
265
270
|
const previewMode = Boolean(options.preview || options.dryRun)
|
|
266
271
|
const writeMode = options.write ?? !previewMode
|
|
267
272
|
|
|
273
|
+
if (writeMode && !options.yes) {
|
|
274
|
+
const previewResults = await applyFixes(resolvedPath, config, {
|
|
275
|
+
rule: options.rule,
|
|
276
|
+
dryRun: true,
|
|
277
|
+
preview: true,
|
|
278
|
+
write: false,
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
if (previewResults.length === 0) {
|
|
282
|
+
console.log('No fixable issues found.')
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const files = new Set(previewResults.map((result) => result.file)).size
|
|
287
|
+
const prompt = `Apply ${previewResults.length} fix(es) across ${files} file(s)? [y/N] `
|
|
288
|
+
const rl = createInterface({ input, output })
|
|
289
|
+
const answer = (await rl.question(prompt)).trim().toLowerCase()
|
|
290
|
+
rl.close()
|
|
291
|
+
|
|
292
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
293
|
+
console.log('Aborted. No files were modified.')
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
268
298
|
const results = await applyFixes(resolvedPath, config, {
|
|
269
299
|
rule: options.rule,
|
|
270
300
|
dryRun: previewMode,
|
|
@@ -348,4 +378,83 @@ program
|
|
|
348
378
|
process.stdout.write(` Saved to drift-history.json\n\n`)
|
|
349
379
|
})
|
|
350
380
|
|
|
381
|
+
const cloud = program
|
|
382
|
+
.command('cloud')
|
|
383
|
+
.description('Local SaaS foundations: ingest, summary, and dashboard')
|
|
384
|
+
|
|
385
|
+
cloud
|
|
386
|
+
.command('ingest [path]')
|
|
387
|
+
.description('Scan path, build report, and store cloud snapshot')
|
|
388
|
+
.requiredOption('--workspace <id>', 'Workspace id')
|
|
389
|
+
.requiredOption('--user <id>', 'User id')
|
|
390
|
+
.option('--repo <name>', 'Repo name (default: basename of scanned path)')
|
|
391
|
+
.option('--store <file>', 'Store file path (default: .drift-cloud/store.json)')
|
|
392
|
+
.action(async (targetPath: string | undefined, options: { workspace: string; user: string; repo?: string; store?: string }) => {
|
|
393
|
+
const resolvedPath = resolve(targetPath ?? '.')
|
|
394
|
+
process.stderr.write(`\nScanning ${resolvedPath} for cloud ingest...\n`)
|
|
395
|
+
const config = await loadConfig(resolvedPath)
|
|
396
|
+
const files = analyzeProject(resolvedPath, config)
|
|
397
|
+
const report = buildReport(resolvedPath, files)
|
|
398
|
+
|
|
399
|
+
const snapshot = ingestSnapshotFromReport(report, {
|
|
400
|
+
workspaceId: options.workspace,
|
|
401
|
+
userId: options.user,
|
|
402
|
+
repoName: options.repo ?? basename(resolvedPath),
|
|
403
|
+
storeFile: options.store,
|
|
404
|
+
policy: config?.saas,
|
|
405
|
+
})
|
|
406
|
+
|
|
407
|
+
process.stdout.write(`Ingested snapshot ${snapshot.id}\n`)
|
|
408
|
+
process.stdout.write(`Workspace: ${snapshot.workspaceId} Repo: ${snapshot.repoName}\n`)
|
|
409
|
+
process.stdout.write(`Score: ${snapshot.totalScore}/100 Issues: ${snapshot.totalIssues}\n\n`)
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
cloud
|
|
413
|
+
.command('summary')
|
|
414
|
+
.description('Show SaaS usage metrics and free threshold status')
|
|
415
|
+
.option('--json', 'Output raw JSON summary')
|
|
416
|
+
.option('--store <file>', 'Store file path (default: .drift-cloud/store.json)')
|
|
417
|
+
.action((options: { json?: boolean; store?: string }) => {
|
|
418
|
+
const summary = getSaasSummary({ storeFile: options.store })
|
|
419
|
+
|
|
420
|
+
if (options.json) {
|
|
421
|
+
process.stdout.write(JSON.stringify(summary, null, 2) + '\n')
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
process.stdout.write('\n')
|
|
426
|
+
process.stdout.write(`Phase: ${summary.phase.toUpperCase()}\n`)
|
|
427
|
+
process.stdout.write(`Users registered: ${summary.usersRegistered}\n`)
|
|
428
|
+
process.stdout.write(`Active workspaces (30d): ${summary.workspacesActive}\n`)
|
|
429
|
+
process.stdout.write(`Active repos (30d): ${summary.reposActive}\n`)
|
|
430
|
+
process.stdout.write(`Total snapshots: ${summary.totalSnapshots}\n`)
|
|
431
|
+
process.stdout.write(`Free user threshold: ${summary.policy.freeUserThreshold}\n`)
|
|
432
|
+
process.stdout.write(`Threshold reached: ${summary.thresholdReached ? 'yes' : 'no'}\n`)
|
|
433
|
+
process.stdout.write(`Free users remaining: ${summary.freeUsersRemaining}\n`)
|
|
434
|
+
process.stdout.write('Runs per month:\n')
|
|
435
|
+
|
|
436
|
+
const monthly = Object.entries(summary.runsPerMonth).sort(([a], [b]) => a.localeCompare(b))
|
|
437
|
+
if (monthly.length === 0) {
|
|
438
|
+
process.stdout.write(' - none\n\n')
|
|
439
|
+
return
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
for (const [month, runs] of monthly) {
|
|
443
|
+
process.stdout.write(` - ${month}: ${runs}\n`)
|
|
444
|
+
}
|
|
445
|
+
process.stdout.write('\n')
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
cloud
|
|
449
|
+
.command('dashboard')
|
|
450
|
+
.description('Generate an HTML dashboard with trends and hotspots')
|
|
451
|
+
.option('-o, --output <file>', 'Output HTML file', 'drift-cloud-dashboard.html')
|
|
452
|
+
.option('--store <file>', 'Store file path (default: .drift-cloud/store.json)')
|
|
453
|
+
.action((options: { output: string; store?: string }) => {
|
|
454
|
+
const html = generateSaasDashboardHtml({ storeFile: options.store })
|
|
455
|
+
const outPath = resolve(options.output)
|
|
456
|
+
writeFileSync(outPath, html, 'utf8')
|
|
457
|
+
process.stdout.write(`Dashboard saved to ${outPath}\n`)
|
|
458
|
+
})
|
|
459
|
+
|
|
351
460
|
program.parse()
|
package/src/index.ts
CHANGED
|
@@ -17,3 +17,18 @@ export type {
|
|
|
17
17
|
} from './types.js'
|
|
18
18
|
export { loadHistory, saveSnapshot } from './snapshot.js'
|
|
19
19
|
export type { SnapshotEntry, SnapshotHistory } from './snapshot.js'
|
|
20
|
+
export {
|
|
21
|
+
DEFAULT_SAAS_POLICY,
|
|
22
|
+
defaultSaasStorePath,
|
|
23
|
+
resolveSaasPolicy,
|
|
24
|
+
ingestSnapshotFromReport,
|
|
25
|
+
getSaasSummary,
|
|
26
|
+
generateSaasDashboardHtml,
|
|
27
|
+
} from './saas.js'
|
|
28
|
+
export type {
|
|
29
|
+
SaasPolicy,
|
|
30
|
+
SaasStore,
|
|
31
|
+
SaasSummary,
|
|
32
|
+
SaasSnapshot,
|
|
33
|
+
IngestOptions,
|
|
34
|
+
} from './saas.js'
|