trackfw 1.0.3 → 1.1.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.
@@ -0,0 +1,122 @@
1
+ {
2
+ "init": {
3
+ "description": "Inicializa la gobernanza trackfw en el proyecto actual",
4
+ "prompt": {
5
+ "projectName": "¿Nombre del proyecto?",
6
+ "projectType": "¿Tipo de proyecto?",
7
+ "frontendStack": "¿Stack de frontend?",
8
+ "pkgManager": "¿Gestor de paquetes?",
9
+ "backendLang": "¿Lenguaje de backend?",
10
+ "backendFramework": "¿Framework de backend?",
11
+ "gitHooks": "¿Git hooks?",
12
+ "ci": "¿Sistema de CI?",
13
+ "aiTools": "¿Qué asistentes de IA usas?",
14
+ "projectType_fullstack": "Full-stack (frontend + backend)",
15
+ "projectType_frontend": "Solo frontend",
16
+ "projectType_backend": "Solo backend",
17
+ "projectType_governance": "Solo gobernanza (sin stack de build)"
18
+ },
19
+ "success": "✓ trackfw inicializado — ejecuta 'trackfw status' para ver el estado de gobernanza."
20
+ },
21
+ "adr": {
22
+ "description": "Gestionar Architecture Decision Records",
23
+ "new": {
24
+ "description": "Crear un nuevo Architecture Decision Record",
25
+ "prompt": {
26
+ "title": "¿Título del ADR?",
27
+ "status": "¿Estado inicial?",
28
+ "context": "¿Contexto (qué motiva esta decisión)?",
29
+ "decision": "¿Decisión (qué fue decidido)?",
30
+ "consequences": "¿Consecuencias (positivas y negativas)?",
31
+ "alternatives": "¿Alternativas consideradas?"
32
+ },
33
+ "created": "✓ ADR creado: {{path}}"
34
+ },
35
+ "list": {
36
+ "description": "Listar todos los ADRs con estado",
37
+ "empty": "No se encontraron ADRs en docs/adr/"
38
+ }
39
+ },
40
+ "req": {
41
+ "description": "Gestionar Requisitos",
42
+ "new": {
43
+ "description": "Crear un nuevo requisito",
44
+ "prompt": {
45
+ "title": "Requisito del proyecto",
46
+ "motivation": "¿Motivación (por qué lo necesitamos)?",
47
+ "criteria": "Criterios de aceptación (uno por línea)",
48
+ "domainQuestion_authentication": "¿Cómo se autenticarán los usuarios?",
49
+ "domainQuestion_ui": "¿Existe un framework de UI o design system ya elegido?",
50
+ "domainQuestion_persistence": "¿Qué motor de base de datos se usará?",
51
+ "domainQuestion_api": "¿Qué protocolo de API se usará?",
52
+ "domainQuestion_deploy": "¿Cuál es el destino de despliegue?",
53
+ "domainQuestion_events": "¿Qué message broker se usará?"
54
+ },
55
+ "detectedDomains": "Dominios detectados: {{domains}}",
56
+ "created": "✓ REQ creado: {{path}}",
57
+ "adrDraftsCreated": "ADR borradores creados:",
58
+ "resolveADRs": "Resuelve estos ADRs (establece Status: Accepted) antes de crear un roadmap.",
59
+ "adrWarning": "advertencia: no se pudo crear ADR borrador para {{slug}}: {{message}}"
60
+ },
61
+ "list": {
62
+ "description": "Listar todos los REQs con estado",
63
+ "empty": "No se encontraron REQs en docs/req/"
64
+ }
65
+ },
66
+ "roadmap": {
67
+ "description": "Gestionar Roadmaps",
68
+ "list": {
69
+ "description": "Listar todos los roadmaps agrupados por estado",
70
+ "empty": "No se encontraron roadmaps."
71
+ },
72
+ "show": {
73
+ "description": "Mostrar roadmap por nombre (coincidencia parcial)",
74
+ "notFound": "Roadmap no encontrado: {{name}}"
75
+ },
76
+ "move": {
77
+ "description": "Mover un roadmap entre estados (backlog|wip|blocked|done|abandoned)",
78
+ "success": "✓ Movido {{name}} → {{state}}",
79
+ "notFound": "Roadmap no encontrado: {{name}}"
80
+ },
81
+ "new": {
82
+ "description": "Crear un nuevo roadmap desde un REQ",
83
+ "created": "✓ Roadmap creado: {{path}}"
84
+ }
85
+ },
86
+ "validate": {
87
+ "description": "Validar reglas de gobernanza (úsalo como gate de CI)",
88
+ "ok": "✓ No se encontraron violaciones.",
89
+ "violations": "✗ Violaciones ({{count}}):",
90
+ "warnings": "⚠ Avisos ({{count}}):"
91
+ },
92
+ "status": {
93
+ "description": "Mostrar el estado actual de gobernanza del proyecto"
94
+ },
95
+ "log": {
96
+ "description": "Mostrar historial de transiciones de estado de los roadmaps",
97
+ "empty": "Aún no hay transiciones registradas.",
98
+ "tail": "Número de transiciones recientes a mostrar",
99
+ "header": "── trackfw log ─────────────────────────"
100
+ },
101
+ "plugins": {
102
+ "description": "Gestionar plugins de trackfw",
103
+ "list": {
104
+ "description": "Listar plugins instalados",
105
+ "empty": "No hay plugins instalados. Usa `trackfw plugins add <user/repo>` para instalar uno."
106
+ },
107
+ "add": {
108
+ "description": "Instalar un plugin desde GitHub Releases (user/repo o user/repo@tag)",
109
+ "installing": "Instalando plugin desde {{repo}}...",
110
+ "success": "Plugin \"{{name}}\" instalado correctamente."
111
+ },
112
+ "remove": {
113
+ "description": "Eliminar un plugin instalado",
114
+ "success": "Plugin \"{{name}}\" eliminado."
115
+ }
116
+ },
117
+ "errors": {
118
+ "notFound": "No encontrado: {{path}}",
119
+ "downloadFailed": "fallo en la descarga: HTTP {{status}} para {{url}}",
120
+ "pluginNotFound": "plugin \"{{name}}\" no encontrado"
121
+ }
122
+ }
@@ -0,0 +1,122 @@
1
+ {
2
+ "init": {
3
+ "description": "Inicializa a governança trackfw no projeto atual",
4
+ "prompt": {
5
+ "projectName": "Nome do projeto?",
6
+ "projectType": "Tipo de projeto?",
7
+ "frontendStack": "Stack de frontend?",
8
+ "pkgManager": "Gerenciador de pacotes?",
9
+ "backendLang": "Linguagem de backend?",
10
+ "backendFramework": "Framework de backend?",
11
+ "gitHooks": "Git hooks?",
12
+ "ci": "Sistema de CI?",
13
+ "aiTools": "Quais assistentes de IA você usa?",
14
+ "projectType_fullstack": "Full-stack (frontend + backend)",
15
+ "projectType_frontend": "Somente frontend",
16
+ "projectType_backend": "Somente backend",
17
+ "projectType_governance": "Somente governança (sem stack de build)"
18
+ },
19
+ "success": "✓ trackfw inicializado — execute 'trackfw status' para ver o estado de governança."
20
+ },
21
+ "adr": {
22
+ "description": "Gerenciar Architecture Decision Records",
23
+ "new": {
24
+ "description": "Criar um novo Architecture Decision Record",
25
+ "prompt": {
26
+ "title": "Título do ADR?",
27
+ "status": "Status inicial?",
28
+ "context": "Contexto (o que motiva esta decisão)?",
29
+ "decision": "Decisão (o que foi decidido)?",
30
+ "consequences": "Consequências (positivas e negativas)?",
31
+ "alternatives": "Alternativas consideradas?"
32
+ },
33
+ "created": "✓ ADR criado: {{path}}"
34
+ },
35
+ "list": {
36
+ "description": "Listar todos os ADRs com status",
37
+ "empty": "Nenhum ADR encontrado em docs/adr/"
38
+ }
39
+ },
40
+ "req": {
41
+ "description": "Gerenciar Requisitos",
42
+ "new": {
43
+ "description": "Criar um novo requisito",
44
+ "prompt": {
45
+ "title": "Requisito do projeto",
46
+ "motivation": "Motivação (por que isso é necessário)?",
47
+ "criteria": "Critérios de aceite (um por linha)",
48
+ "domainQuestion_authentication": "Como os usuários serão autenticados?",
49
+ "domainQuestion_ui": "Existe um framework de UI ou design system já escolhido?",
50
+ "domainQuestion_persistence": "Qual engine de banco de dados será usada?",
51
+ "domainQuestion_api": "Qual protocolo de API será usado?",
52
+ "domainQuestion_deploy": "Qual é o destino de deploy?",
53
+ "domainQuestion_events": "Qual message broker será usado?"
54
+ },
55
+ "detectedDomains": "Domínios detectados: {{domains}}",
56
+ "created": "✓ REQ criado: {{path}}",
57
+ "adrDraftsCreated": "ADR drafts criados:",
58
+ "resolveADRs": "Resolva estes ADRs (defina Status: Accepted) antes de criar um roadmap.",
59
+ "adrWarning": "aviso: não foi possível criar ADR draft para {{slug}}: {{message}}"
60
+ },
61
+ "list": {
62
+ "description": "Listar todos os REQs com status",
63
+ "empty": "Nenhum REQ encontrado em docs/req/"
64
+ }
65
+ },
66
+ "roadmap": {
67
+ "description": "Gerenciar Roadmaps",
68
+ "list": {
69
+ "description": "Listar todos os roadmaps agrupados por estado",
70
+ "empty": "Nenhum roadmap encontrado."
71
+ },
72
+ "show": {
73
+ "description": "Exibir roadmap pelo nome (correspondência parcial)",
74
+ "notFound": "Roadmap não encontrado: {{name}}"
75
+ },
76
+ "move": {
77
+ "description": "Mover um roadmap entre estados (backlog|wip|blocked|done|abandoned)",
78
+ "success": "✓ Movido {{name}} → {{state}}",
79
+ "notFound": "Roadmap não encontrado: {{name}}"
80
+ },
81
+ "new": {
82
+ "description": "Criar um novo roadmap a partir de uma REQ",
83
+ "created": "✓ Roadmap criado: {{path}}"
84
+ }
85
+ },
86
+ "validate": {
87
+ "description": "Validar regras de governança (use como gate de CI)",
88
+ "ok": "✓ Nenhuma violação encontrada.",
89
+ "violations": "✗ Violações ({{count}}):",
90
+ "warnings": "⚠ Avisos ({{count}}):"
91
+ },
92
+ "status": {
93
+ "description": "Exibir o estado atual de governança do projeto"
94
+ },
95
+ "log": {
96
+ "description": "Exibir histórico de transições de estado dos roadmaps",
97
+ "empty": "Nenhuma transição registrada ainda.",
98
+ "tail": "Número de transições recentes a exibir",
99
+ "header": "── trackfw log ─────────────────────────"
100
+ },
101
+ "plugins": {
102
+ "description": "Gerenciar plugins do trackfw",
103
+ "list": {
104
+ "description": "Listar plugins instalados",
105
+ "empty": "Nenhum plugin instalado. Use `trackfw plugins add <user/repo>` para instalar um."
106
+ },
107
+ "add": {
108
+ "description": "Instalar um plugin do GitHub Releases (user/repo ou user/repo@tag)",
109
+ "installing": "Instalando plugin de {{repo}}...",
110
+ "success": "Plugin \"{{name}}\" instalado com sucesso."
111
+ },
112
+ "remove": {
113
+ "description": "Remover um plugin instalado",
114
+ "success": "Plugin \"{{name}}\" removido."
115
+ }
116
+ },
117
+ "errors": {
118
+ "notFound": "Não encontrado: {{path}}",
119
+ "downloadFailed": "falha no download: HTTP {{status}} para {{url}}",
120
+ "pluginNotFound": "plugin \"{{name}}\" não encontrado"
121
+ }
122
+ }
@@ -0,0 +1,340 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+
6
+ const STALE_WIP_DAYS = 7
7
+
8
+ // listDir retorna array de nomes de arquivo (não-diretórios) em dir.
9
+ // Retorna [] se o diretório não existir.
10
+ function listDir(dir) {
11
+ try {
12
+ return fs.readdirSync(dir).filter(name => {
13
+ try {
14
+ return !fs.statSync(path.join(dir, name)).isDirectory()
15
+ } catch (_) {
16
+ return false
17
+ }
18
+ })
19
+ } catch (_) {
20
+ return []
21
+ }
22
+ }
23
+
24
+ // parseBlockedADRs extrai basenames de ADRs da seção "## Blocked by ADRs" de um arquivo REQ.
25
+ function parseBlockedADRs(filePath) {
26
+ let content
27
+ try {
28
+ content = fs.readFileSync(filePath, 'utf8')
29
+ } catch (_) {
30
+ return []
31
+ }
32
+ const lines = content.split('\n')
33
+ const adrs = []
34
+ let inSection = false
35
+ for (const line of lines) {
36
+ if (line === '## Blocked by ADRs') {
37
+ inSection = true
38
+ continue
39
+ }
40
+ if (inSection) {
41
+ if (line.startsWith('## ')) break
42
+ if (line.startsWith('- ')) {
43
+ const item = line.slice(2).trim()
44
+ const parts = item.split(/\s+/)
45
+ if (parts.length > 0 && parts[0].endsWith('.md')) {
46
+ adrs.push(parts[0])
47
+ }
48
+ }
49
+ }
50
+ }
51
+ return adrs
52
+ }
53
+
54
+ // adrIsDraft verifica se docs/adr/<basename> contém "Status: Draft".
55
+ function adrIsDraft(basename) {
56
+ try {
57
+ const content = fs.readFileSync(path.join('docs', 'adr', basename), 'utf8')
58
+ return content.includes('Status: Draft')
59
+ } catch (_) {
60
+ return false
61
+ }
62
+ }
63
+
64
+ // validateWIPHasREQ — roadmaps em docs/roadmaps/wip/ sem "REQ:" no conteúdo → violation
65
+ function validateWIPHasREQ() {
66
+ const entries = listDir('docs/roadmaps/wip')
67
+ const violations = []
68
+ for (const name of entries) {
69
+ try {
70
+ const content = fs.readFileSync(path.join('docs/roadmaps/wip', name), 'utf8')
71
+ if (!content.includes('REQ:') || content.includes('REQ: \n')) {
72
+ violations.push(`roadmap "${name}" is in wip but has no linked REQ`)
73
+ }
74
+ } catch (_) {
75
+ // ignorar erro de leitura
76
+ }
77
+ }
78
+ return violations
79
+ }
80
+
81
+ // validateREQsHaveADR — REQs em docs/req/ sem "ADR:" no conteúdo → violation
82
+ function validateREQsHaveADR() {
83
+ const entries = listDir('docs/req')
84
+ const violations = []
85
+ for (const name of entries) {
86
+ try {
87
+ const content = fs.readFileSync(path.join('docs/req', name), 'utf8')
88
+ if (!content.includes('ADR:') || content.includes('ADR: \n')) {
89
+ violations.push(`req "${name}" has no linked ADR`)
90
+ }
91
+ } catch (_) {
92
+ // ignorar
93
+ }
94
+ }
95
+ return violations
96
+ }
97
+
98
+ // validateBlockedHasREQ — roadmaps em docs/roadmaps/blocked/ sem "REQ:" → violation
99
+ function validateBlockedHasREQ() {
100
+ const entries = listDir('docs/roadmaps/blocked')
101
+ const violations = []
102
+ for (const name of entries) {
103
+ try {
104
+ const content = fs.readFileSync(path.join('docs/roadmaps/blocked', name), 'utf8')
105
+ if (!content.includes('REQ:') || content.includes('REQ: \n')) {
106
+ violations.push(`roadmap "${name}" is in blocked but has no linked REQ`)
107
+ }
108
+ } catch (_) {
109
+ // ignorar
110
+ }
111
+ }
112
+ return violations
113
+ }
114
+
115
+ // validateREQsHaveRoadmap — REQs sem "Roadmap:" → violation
116
+ function validateREQsHaveRoadmap() {
117
+ const entries = listDir('docs/req')
118
+ const violations = []
119
+ for (const name of entries) {
120
+ try {
121
+ const content = fs.readFileSync(path.join('docs/req', name), 'utf8')
122
+ if (!content.includes('Roadmap:') || content.includes('Roadmap: \n')) {
123
+ violations.push(`req "${name}" has no linked Roadmap`)
124
+ }
125
+ } catch (_) {
126
+ // ignorar
127
+ }
128
+ }
129
+ return violations
130
+ }
131
+
132
+ // validateADRsAreReferenced — ADRs em docs/adr/ não referenciados em nenhuma REQ → violation
133
+ function validateADRsAreReferenced() {
134
+ const adrs = listDir('docs/adr')
135
+ const reqEntries = listDir('docs/req')
136
+
137
+ let combined = ''
138
+ for (const name of reqEntries) {
139
+ try {
140
+ combined += fs.readFileSync(path.join('docs/req', name), 'utf8')
141
+ } catch (_) {
142
+ // ignorar
143
+ }
144
+ }
145
+
146
+ const violations = []
147
+ for (const adr of adrs) {
148
+ if (!combined.includes(adr)) {
149
+ violations.push(`adr "${adr}" is not referenced by any REQ`)
150
+ }
151
+ }
152
+ return violations
153
+ }
154
+
155
+ // validateWIPHasAcceptanceCriteria — roadmaps wip sem bloco de critérios de aceite → violation
156
+ function validateWIPHasAcceptanceCriteria() {
157
+ const entries = listDir('docs/roadmaps/wip')
158
+ const violations = []
159
+ for (const name of entries) {
160
+ try {
161
+ const content = fs.readFileSync(path.join('docs/roadmaps/wip', name), 'utf8')
162
+ const hasBlock =
163
+ content.includes('## Acceptance Criteria') ||
164
+ content.includes('## Critérios de Aceite') ||
165
+ content.includes('acceptance criteria') ||
166
+ content.includes('Acceptance Criteria:')
167
+ if (!hasBlock) {
168
+ violations.push(`roadmap "${name}" is in wip but has no acceptance criteria block`)
169
+ }
170
+ } catch (_) {
171
+ // ignorar
172
+ }
173
+ }
174
+ return violations
175
+ }
176
+
177
+ // validateSingleWIP — mais de 1 roadmap em wip → warning
178
+ function validateSingleWIP() {
179
+ const entries = listDir('docs/roadmaps/wip')
180
+ if (entries.length > 1) {
181
+ return [`${entries.length} roadmaps in wip/ (recommended: keep only 1 active at a time)`]
182
+ }
183
+ return []
184
+ }
185
+
186
+ // validateStaleWIP — roadmaps wip com mtime >= 7 dias → warning
187
+ function validateStaleWIP() {
188
+ let files = []
189
+ try {
190
+ files = fs.readdirSync('docs/roadmaps/wip')
191
+ .filter(f => f.endsWith('.md'))
192
+ .map(f => path.join('docs/roadmaps/wip', f))
193
+ } catch (_) {
194
+ return []
195
+ }
196
+
197
+ const warnings = []
198
+ const now = Date.now()
199
+ for (const filePath of files) {
200
+ try {
201
+ const stat = fs.statSync(filePath)
202
+ const ageMs = now - stat.mtimeMs
203
+ const days = Math.floor(ageMs / (1000 * 60 * 60 * 24))
204
+ if (days >= STALE_WIP_DAYS) {
205
+ const lastModified = stat.mtime.toISOString().slice(0, 10)
206
+ const basename = path.basename(filePath)
207
+ warnings.push(
208
+ `roadmap/wip/${basename} has been in WIP for ${days} days (last modified ${lastModified})`
209
+ )
210
+ }
211
+ } catch (_) {
212
+ // ignorar
213
+ }
214
+ }
215
+ return warnings
216
+ }
217
+
218
+ // validateREQsNotBlockedByDraftADRs — REQs Open com ADRs Draft na seção "## Blocked by ADRs" → violation
219
+ function validateREQsNotBlockedByDraftADRs() {
220
+ const entries = listDir('docs/req')
221
+ const violations = []
222
+ for (const name of entries) {
223
+ const filePath = path.join('docs/req', name)
224
+ let content
225
+ try {
226
+ content = fs.readFileSync(filePath, 'utf8')
227
+ } catch (_) {
228
+ continue
229
+ }
230
+ if (!content.includes('Status: Open')) continue
231
+
232
+ const blockedADRs = parseBlockedADRs(filePath)
233
+ for (const adrBasename of blockedADRs) {
234
+ if (adrIsDraft(adrBasename)) {
235
+ violations.push(`REQ ${name} is blocked by Draft ADR: ${adrBasename}`)
236
+ }
237
+ }
238
+ }
239
+ return violations
240
+ }
241
+
242
+ // blockedREQs retorna mapa de reqBasename → [adrBasenames Draft] para uso em getStatus()
243
+ function blockedREQs() {
244
+ const entries = listDir('docs/req')
245
+ const result = {}
246
+ for (const name of entries) {
247
+ const filePath = path.join('docs/req', name)
248
+ let content
249
+ try {
250
+ content = fs.readFileSync(filePath, 'utf8')
251
+ } catch (_) {
252
+ continue
253
+ }
254
+ if (!content.includes('Status: Open')) continue
255
+
256
+ const adrNames = parseBlockedADRs(filePath)
257
+ const draftADRs = adrNames.filter(a => adrIsDraft(a))
258
+ if (draftADRs.length > 0) {
259
+ result[name] = draftADRs
260
+ }
261
+ }
262
+ return result
263
+ }
264
+
265
+ // validate executa todas as validações e retorna { violations, warnings }
266
+ async function validate() {
267
+ const violations = [
268
+ ...validateWIPHasREQ(),
269
+ ...validateREQsHaveADR(),
270
+ ...validateBlockedHasREQ(),
271
+ ...validateREQsHaveRoadmap(),
272
+ ...validateADRsAreReferenced(),
273
+ ...validateWIPHasAcceptanceCriteria(),
274
+ ...validateREQsNotBlockedByDraftADRs(),
275
+ ]
276
+ const warnings = [
277
+ ...validateSingleWIP(),
278
+ ...validateStaleWIP(),
279
+ ]
280
+ return { violations, warnings }
281
+ }
282
+
283
+ // getStatus retorna string formatada com o status de governança do projeto
284
+ async function getStatus() {
285
+ const wip = listDir('docs/roadmaps/wip')
286
+ const blocked = listDir('docs/roadmaps/blocked')
287
+ const done = listDir('docs/roadmaps/done')
288
+
289
+ let out = ''
290
+ out += '── trackfw status ──────────────────────\n'
291
+
292
+ out += `\n🔄 WIP (${wip.length})\n`
293
+ for (const f of wip) out += ` ${f}\n`
294
+
295
+ out += `\n❌ Blocked (${blocked.length})\n`
296
+ for (const f of blocked) out += ` ${f}\n`
297
+
298
+ const staleWIPs = validateStaleWIP()
299
+ if (staleWIPs.length > 0) {
300
+ out += `\n⚠ Stale WIP (${staleWIPs.length})\n`
301
+ for (const w of staleWIPs) out += ` ${w}\n`
302
+ }
303
+
304
+ const blockedByDraft = blockedREQs()
305
+ const blockedKeys = Object.keys(blockedByDraft)
306
+ if (blockedKeys.length > 0) {
307
+ out += `\n⏳ REQs blocked by Draft ADRs (${blockedKeys.length})\n`
308
+ for (const reqFile of blockedKeys) {
309
+ out += ` ${reqFile}\n`
310
+ for (const adr of blockedByDraft[reqFile]) {
311
+ out += ` → ${adr} (Draft)\n`
312
+ }
313
+ }
314
+ }
315
+
316
+ out += `\n✅ Done (last 5)\n`
317
+ const last5 = done.length > 5 ? done.slice(done.length - 5) : done
318
+ for (const f of last5) out += ` ${f}\n`
319
+
320
+ out += '\n────────────────────────────────────────\n'
321
+ return out
322
+ }
323
+
324
+ module.exports = {
325
+ validate,
326
+ getStatus,
327
+ // exportadas para testes unitários
328
+ validateWIPHasREQ,
329
+ validateREQsHaveADR,
330
+ validateBlockedHasREQ,
331
+ validateREQsHaveRoadmap,
332
+ validateADRsAreReferenced,
333
+ validateWIPHasAcceptanceCriteria,
334
+ validateSingleWIP,
335
+ validateStaleWIP,
336
+ validateREQsNotBlockedByDraftADRs,
337
+ parseBlockedADRs,
338
+ adrIsDraft,
339
+ listDir,
340
+ }
package/bin/.gitkeep DELETED
File without changes