trackfw 1.0.4 → 2.0.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.
@@ -1,40 +1,80 @@
1
1
  'use strict'
2
2
  const fs = require('fs')
3
3
  const path = require('path')
4
+ const config = require('../config')
4
5
 
5
- const VALID_STATES = {
6
- backlog: 'docs/roadmaps/backlog',
7
- wip: 'docs/roadmaps/wip',
8
- blocked: 'docs/roadmaps/blocked',
9
- done: 'docs/roadmaps/done',
10
- abandoned: 'docs/roadmaps/abandoned',
6
+ const STATE_ORDER = ['wip', 'backlog', 'blocked', 'done', 'abandoned']
7
+
8
+ // stateDir retorna o caminho do diretório para um estado válido no modo flat, ou null se inválido.
9
+ function stateDir(state) {
10
+ const cfg = config.load()
11
+ const valid = ['backlog', 'wip', 'blocked', 'done', 'abandoned']
12
+ if (!valid.includes(state)) return null
13
+ return cfg.roadmapDir + '/' + state
11
14
  }
12
15
 
13
- const STATE_ORDER = ['wip', 'backlog', 'blocked', 'done', 'abandoned']
16
+ // agentStateDir retorna o diretório para um agente+estado em modo by_agent.
17
+ // agent=null usa o primeiro agente configurado (ou "default" se lista vazia).
18
+ function agentStateDir(agent, state) {
19
+ const cfg = config.load()
20
+ const valid = ['backlog', 'wip', 'blocked', 'done', 'abandoned']
21
+ if (!valid.includes(state)) return null
22
+ if (!agent) {
23
+ agent = cfg.agents && cfg.agents.length > 0 ? cfg.agents[0] : 'default'
24
+ }
25
+ return cfg.roadmapDir + '/' + agent + '/' + state
26
+ }
14
27
 
15
- const TRANSITION_LOG_PATH = 'docs/roadmaps/.trackfw-log'
28
+ // logPath retorna o caminho do arquivo de log de transições.
29
+ function logPath() {
30
+ return config.load().roadmapDir + '/.trackfw-log'
31
+ }
16
32
 
17
33
  /**
18
- * listRoadmaps — lista roadmaps agrupados por estado (wip, backlog, blocked, done, abandoned).
34
+ * listRoadmaps — lista roadmaps agrupados por estado (e por agente em modo by_agent).
19
35
  * Se nenhum encontrado imprime mensagem orientando o usuário.
20
36
  */
21
37
  function listRoadmaps() {
38
+ const cfg = config.load()
22
39
  let found = false
23
40
 
24
- for (const state of STATE_ORDER) {
25
- const dir = VALID_STATES[state]
26
- let files = []
27
- try {
28
- files = fs.readdirSync(dir).filter(f => !fs.statSync(path.join(dir, f)).isDirectory() && f.endsWith('.md'))
29
- } catch (_) {
30
- continue
41
+ if (cfg.roadmapNamespacing === config.NAMESPACING_BY_AGENT) {
42
+ let agents = cfg.agents || []
43
+ if (agents.length === 0) {
44
+ try {
45
+ agents = fs.readdirSync(cfg.roadmapDir).filter(f => {
46
+ try { return fs.statSync(path.join(cfg.roadmapDir, f)).isDirectory() } catch (_) { return false }
47
+ })
48
+ } catch (_) { agents = [] }
31
49
  }
32
- if (files.length === 0) continue
33
-
34
- found = true
35
- console.log(`[${state}]`)
36
- for (const f of files) {
37
- console.log(` ${f}`)
50
+ for (const agent of agents) {
51
+ for (const state of STATE_ORDER) {
52
+ const dir = cfg.roadmapDir + '/' + agent + '/' + state
53
+ let files = []
54
+ try {
55
+ files = fs.readdirSync(dir).filter(f => {
56
+ try { return !fs.statSync(path.join(dir, f)).isDirectory() && f.endsWith('.md') } catch (_) { return false }
57
+ })
58
+ } catch (_) { continue }
59
+ if (files.length === 0) continue
60
+ found = true
61
+ console.log(`[${agent}/${state}]`)
62
+ for (const f of files) console.log(` ${f}`)
63
+ }
64
+ }
65
+ } else {
66
+ for (const state of STATE_ORDER) {
67
+ const dir = cfg.roadmapDir + '/' + state
68
+ let files = []
69
+ try {
70
+ files = fs.readdirSync(dir).filter(f => {
71
+ try { return !fs.statSync(path.join(dir, f)).isDirectory() && f.endsWith('.md') } catch (_) { return false }
72
+ })
73
+ } catch (_) { continue }
74
+ if (files.length === 0) continue
75
+ found = true
76
+ console.log(`[${state}]`)
77
+ for (const f of files) console.log(` ${f}`)
38
78
  }
39
79
  }
40
80
 
@@ -44,8 +84,8 @@ function listRoadmaps() {
44
84
  }
45
85
 
46
86
  /**
47
- * showRoadmap — busca docs/roadmaps/ESTADO/NOME*.md (partial match), imprime cabeçalho + conteúdo.
48
- * 0 matches: erro. múltiplos: lista + erro. 1 match: imprime cabeçalho e conteúdo.
87
+ * showRoadmap — busca <roadmapDir>/ESTADO/NOME*.md (partial match, flat) ou
88
+ * <roadmapDir>/AGENTE/ESTADO/NOME*.md (by_agent), imprime cabeçalho + conteúdo.
49
89
  */
50
90
  function showRoadmap(name) {
51
91
  const matches = findRoadmapMatches(name)
@@ -58,9 +98,7 @@ function showRoadmap(name) {
58
98
 
59
99
  if (matches.length > 1) {
60
100
  console.log('Multiple roadmaps found — be more specific:')
61
- for (const m of matches) {
62
- console.log(` ${m}`)
63
- }
101
+ for (const m of matches) console.log(` ${m}`)
64
102
  console.error(`ambiguous match for "${name}"`)
65
103
  process.exitCode = 1
66
104
  return
@@ -78,12 +116,12 @@ function showRoadmap(name) {
78
116
 
79
117
  /**
80
118
  * moveRoadmap — move arquivo para diretório do estado alvo.
81
- * Valida estado, procura arquivo em qualquer estado (case-insensitive partial match),
82
- * move com fs.renameSync, chama appendTransitionLog, imprime confirmação.
119
+ * Em modo by_agent, mantém o agente na hierarquia.
83
120
  */
84
121
  function moveRoadmap(name, state) {
85
- const targetDir = VALID_STATES[state]
86
- if (!targetDir) {
122
+ const cfg = config.load()
123
+ const valid = ['backlog', 'wip', 'blocked', 'done', 'abandoned']
124
+ if (!valid.includes(state)) {
87
125
  console.error(`invalid state "${state}" — valid states: backlog, wip, blocked, done, abandoned`)
88
126
  process.exitCode = 1
89
127
  return
@@ -97,9 +135,7 @@ function moveRoadmap(name, state) {
97
135
  }
98
136
  if (matches.length > 1) {
99
137
  console.log('Multiple roadmaps found — be more specific:')
100
- for (const m of matches) {
101
- console.log(` ${m}`)
102
- }
138
+ for (const m of matches) console.log(` ${m}`)
103
139
  console.error(`ambiguous match for "${name}"`)
104
140
  process.exitCode = 1
105
141
  return
@@ -107,22 +143,41 @@ function moveRoadmap(name, state) {
107
143
 
108
144
  const src = matches[0]
109
145
  const basename = path.basename(src)
110
- const fromState = path.basename(path.dirname(src))
146
+ let targetDir, fromState, logBasename
147
+
148
+ if (cfg.roadmapNamespacing === config.NAMESPACING_BY_AGENT) {
149
+ const agentDir = path.dirname(path.dirname(src))
150
+ const agent = path.basename(agentDir)
151
+ fromState = path.basename(path.dirname(src))
152
+ targetDir = agentStateDir(agent, state)
153
+ if (!targetDir) {
154
+ console.error(`invalid state "${state}"`)
155
+ process.exitCode = 1
156
+ return
157
+ }
158
+ logBasename = agent + '/' + basename
159
+ } else {
160
+ fromState = path.basename(path.dirname(src))
161
+ targetDir = stateDir(state)
162
+ if (!targetDir) {
163
+ console.error(`invalid state "${state}"`)
164
+ process.exitCode = 1
165
+ return
166
+ }
167
+ logBasename = basename
168
+ }
111
169
 
112
- try {
113
- fs.mkdirSync(targetDir, { recursive: true })
114
- } catch (_) {}
170
+ try { fs.mkdirSync(targetDir, { recursive: true }) } catch (_) {}
115
171
 
116
172
  const dst = path.join(targetDir, basename)
117
173
  fs.renameSync(src, dst)
118
174
 
119
- appendTransitionLog(basename, fromState, state)
175
+ appendTransitionLog(logBasename, fromState, state)
120
176
  console.log(`✓ moved ${basename} → ${targetDir}`)
121
177
  }
122
178
 
123
179
  /**
124
- * appendTransitionLog — append em docs/roadmaps/.trackfw-log.
125
- * Formato: `YYYY-MM-DD HH:mm <basename padded to 50> <fromState> → <toState>\n`
180
+ * appendTransitionLog — append em <roadmapDir>/.trackfw-log.
126
181
  */
127
182
  function appendTransitionLog(basename, fromState, toState) {
128
183
  const now = new Date()
@@ -135,24 +190,39 @@ function appendTransitionLog(basename, fromState, toState) {
135
190
  const line = `${timestamp} ${basename.padEnd(50)} ${fromState} → ${toState}\n`
136
191
 
137
192
  try {
138
- fs.mkdirSync(path.dirname(TRANSITION_LOG_PATH), { recursive: true })
139
- fs.appendFileSync(TRANSITION_LOG_PATH, line, 'utf8')
193
+ const lp = logPath()
194
+ fs.mkdirSync(path.dirname(lp), { recursive: true })
195
+ fs.appendFileSync(lp, line, 'utf8')
140
196
  } catch (_) {}
141
197
  }
142
198
 
143
199
  /**
144
- * newRoadmap — cria roadmap em docs/roadmaps/backlog/ROADMAP-YYYY-MM-DD-<slug>.md.
200
+ * newRoadmap — cria roadmap em <roadmapDir>/backlog/ROADMAP-YYYY-MM-DD-<slug>.md.
201
+ * Em modo by_agent, usa o primeiro agente configurado.
145
202
  */
146
203
  function newRoadmap(title, reqPath) {
204
+ const cfg = config.load()
147
205
  const now = new Date()
148
206
  const yyyy = now.getFullYear()
149
207
  const mm = String(now.getMonth() + 1).padStart(2, '0')
150
208
  const dd = String(now.getDate()).padStart(2, '0')
151
209
  const date = `${yyyy}-${mm}-${dd}`
152
210
  const slug = toSlug(title)
153
- const filename = `docs/roadmaps/backlog/ROADMAP-${date}-${slug}.md`
154
211
 
155
- fs.mkdirSync('docs/roadmaps/backlog', { recursive: true })
212
+ let backlogDir
213
+ if (cfg.roadmapNamespacing === config.NAMESPACING_BY_AGENT) {
214
+ backlogDir = agentStateDir(null, 'backlog')
215
+ if (!backlogDir) {
216
+ console.error('cannot resolve backlog dir in by_agent mode')
217
+ process.exitCode = 1
218
+ return
219
+ }
220
+ } else {
221
+ backlogDir = cfg.roadmapDir + '/backlog'
222
+ }
223
+
224
+ const filename = `${backlogDir}/ROADMAP-${date}-${slug}.md`
225
+ fs.mkdirSync(backlogDir, { recursive: true })
156
226
 
157
227
  const body = `# Roadmap: ${title}
158
228
 
@@ -183,21 +253,43 @@ REQ: ${reqPath || ''}
183
253
 
184
254
  /**
185
255
  * findRoadmapMatches — retorna array de paths que contêm `name` (case-insensitive) em qualquer estado.
256
+ * Suporta modo flat (1 nível) e by_agent (2 níveis).
186
257
  */
187
258
  function findRoadmapMatches(name) {
259
+ const cfg = config.load()
188
260
  const matches = []
189
261
  const nameLower = name.toLowerCase()
190
- for (const state of STATE_ORDER) {
191
- const dir = VALID_STATES[state]
192
- let files = []
193
- try {
194
- files = fs.readdirSync(dir)
195
- } catch (_) {
196
- continue
262
+
263
+ if (cfg.roadmapNamespacing === config.NAMESPACING_BY_AGENT) {
264
+ let agents = cfg.agents || []
265
+ if (agents.length === 0) {
266
+ try {
267
+ agents = fs.readdirSync(cfg.roadmapDir).filter(f => {
268
+ try { return fs.statSync(path.join(cfg.roadmapDir, f)).isDirectory() } catch (_) { return false }
269
+ })
270
+ } catch (_) { agents = ['default'] }
271
+ }
272
+ for (const agent of agents) {
273
+ for (const state of STATE_ORDER) {
274
+ const dir = cfg.roadmapDir + '/' + agent + '/' + state
275
+ let files = []
276
+ try { files = fs.readdirSync(dir) } catch (_) { continue }
277
+ for (const f of files) {
278
+ if (f.toLowerCase().includes(nameLower) && f.endsWith('.md')) {
279
+ matches.push(path.join(dir, f))
280
+ }
281
+ }
282
+ }
197
283
  }
198
- for (const f of files) {
199
- if (f.toLowerCase().includes(nameLower) && f.endsWith('.md')) {
200
- matches.push(path.join(dir, f))
284
+ } else {
285
+ for (const state of STATE_ORDER) {
286
+ const dir = cfg.roadmapDir + '/' + state
287
+ let files = []
288
+ try { files = fs.readdirSync(dir) } catch (_) { continue }
289
+ for (const f of files) {
290
+ if (f.toLowerCase().includes(nameLower) && f.endsWith('.md')) {
291
+ matches.push(path.join(dir, f))
292
+ }
201
293
  }
202
294
  }
203
295
  }
@@ -215,10 +307,11 @@ function toSlug(s) {
215
307
  }
216
308
 
217
309
  module.exports = {
218
- VALID_STATES,
219
310
  listRoadmaps,
220
311
  showRoadmap,
221
312
  moveRoadmap,
222
313
  appendTransitionLog,
223
314
  newRoadmap,
315
+ stateDir,
316
+ agentStateDir,
224
317
  }
@@ -0,0 +1,53 @@
1
+ 'use strict'
2
+ const path = require('path')
3
+ const fs = require('fs')
4
+
5
+ function detectLocale() {
6
+ const raw = process.env.LANG || process.env.LC_ALL || process.env.LANGUAGE || ''
7
+ // Mapeia pt_BR.UTF-8 → pt-BR, es_ES.UTF-8 → es-ES, en_US.UTF-8 → en-US
8
+ const map = { pt: 'pt-BR', es: 'es-ES' }
9
+ const code = raw.split('.')[0].replace('_', '-') // pt-BR
10
+ const lang = code.split('-')[0] // pt
11
+ if (map[lang]) return map[lang]
12
+ // Fallback para Windows: usar Intl
13
+ try {
14
+ const loc = Intl.DateTimeFormat().resolvedOptions().locale
15
+ const l = loc.split('-')[0]
16
+ if (map[l]) return map[l]
17
+ } catch (_) {}
18
+ return 'en-US'
19
+ }
20
+
21
+ let _locale = null
22
+ let _messages = null
23
+
24
+ function load() {
25
+ if (_messages) return
26
+ _locale = detectLocale()
27
+ const filePath = path.join(__dirname, 'locales', `${_locale}.json`)
28
+ const fallback = path.join(__dirname, 'locales', 'en-US.json')
29
+ try {
30
+ _messages = JSON.parse(fs.readFileSync(fs.existsSync(filePath) ? filePath : fallback, 'utf8'))
31
+ } catch (_) {
32
+ _messages = {}
33
+ }
34
+ }
35
+
36
+ function t(key, vars = {}) {
37
+ load()
38
+ const keys = key.split('.')
39
+ let val = _messages
40
+ for (const k of keys) {
41
+ val = val?.[k]
42
+ if (val === undefined) break
43
+ }
44
+ if (typeof val !== 'string') return key
45
+ return val.replace(/\{\{(\w+)\}\}/g, (_, k) => vars[k] ?? `{{${k}}}`)
46
+ }
47
+
48
+ function locale() {
49
+ load()
50
+ return _locale
51
+ }
52
+
53
+ module.exports = { t, locale }
@@ -0,0 +1,122 @@
1
+ {
2
+ "init": {
3
+ "description": "Initialize trackfw governance in the current project",
4
+ "prompt": {
5
+ "projectName": "Project name?",
6
+ "projectType": "Project type?",
7
+ "frontendStack": "Frontend stack?",
8
+ "pkgManager": "Package manager?",
9
+ "backendLang": "Backend language?",
10
+ "backendFramework": "Backend framework?",
11
+ "gitHooks": "Git hooks?",
12
+ "ci": "CI system?",
13
+ "aiTools": "Which AI assistants do you use?",
14
+ "projectType_fullstack": "Full-stack (frontend + backend)",
15
+ "projectType_frontend": "Frontend only",
16
+ "projectType_backend": "Backend only",
17
+ "projectType_governance": "Governance only (no build stack)"
18
+ },
19
+ "success": "✓ trackfw initialized — run 'trackfw status' to see your governance state."
20
+ },
21
+ "adr": {
22
+ "description": "Manage Architecture Decision Records",
23
+ "new": {
24
+ "description": "Create a new Architecture Decision Record",
25
+ "prompt": {
26
+ "title": "ADR title?",
27
+ "status": "Initial status?",
28
+ "context": "Context (what motivates this decision)?",
29
+ "decision": "Decision (what was decided)?",
30
+ "consequences": "Consequences (positive and negative)?",
31
+ "alternatives": "Alternatives considered?"
32
+ },
33
+ "created": "✓ ADR created: {{path}}"
34
+ },
35
+ "list": {
36
+ "description": "List all ADRs with status",
37
+ "empty": "No ADRs found in docs/adr/"
38
+ }
39
+ },
40
+ "req": {
41
+ "description": "Manage Requirements",
42
+ "new": {
43
+ "description": "Create a new requirement",
44
+ "prompt": {
45
+ "title": "Project requirement",
46
+ "motivation": "Motivation (why is this needed)?",
47
+ "criteria": "Acceptance Criteria (one per line)",
48
+ "domainQuestion_authentication": "How will users authenticate?",
49
+ "domainQuestion_ui": "Is there an existing UI framework or design system?",
50
+ "domainQuestion_persistence": "Which database engine will be used?",
51
+ "domainQuestion_api": "Which API protocol will be used?",
52
+ "domainQuestion_deploy": "What is the deployment target?",
53
+ "domainQuestion_events": "Which event broker will be used?"
54
+ },
55
+ "detectedDomains": "Detected domains: {{domains}}",
56
+ "created": "✓ REQ created: {{path}}",
57
+ "adrDraftsCreated": "ADR drafts created:",
58
+ "resolveADRs": "Resolve these ADRs (set Status: Accepted) before creating a roadmap.",
59
+ "adrWarning": "warning: could not create ADR draft for {{slug}}: {{message}}"
60
+ },
61
+ "list": {
62
+ "description": "List all REQs with status",
63
+ "empty": "No REQs found in docs/req/"
64
+ }
65
+ },
66
+ "roadmap": {
67
+ "description": "Manage Roadmaps",
68
+ "list": {
69
+ "description": "List all roadmaps grouped by state",
70
+ "empty": "No roadmaps found."
71
+ },
72
+ "show": {
73
+ "description": "Show a roadmap by name (partial match)",
74
+ "notFound": "Roadmap not found: {{name}}"
75
+ },
76
+ "move": {
77
+ "description": "Move a roadmap between states (backlog|wip|blocked|done|abandoned)",
78
+ "success": "✓ Moved {{name}} → {{state}}",
79
+ "notFound": "Roadmap not found: {{name}}"
80
+ },
81
+ "new": {
82
+ "description": "Create a new roadmap from a REQ",
83
+ "created": "✓ Roadmap created: {{path}}"
84
+ }
85
+ },
86
+ "validate": {
87
+ "description": "Validate governance rules (use as CI gate)",
88
+ "ok": "✓ No violations found.",
89
+ "violations": "✗ Violations ({{count}}):",
90
+ "warnings": "⚠ Warnings ({{count}}):"
91
+ },
92
+ "status": {
93
+ "description": "Show project governance status"
94
+ },
95
+ "log": {
96
+ "description": "Show roadmap state transition history",
97
+ "empty": "No transitions recorded yet.",
98
+ "tail": "Number of recent transitions to show",
99
+ "header": "── trackfw log ─────────────────────────"
100
+ },
101
+ "plugins": {
102
+ "description": "Manage trackfw plugins",
103
+ "list": {
104
+ "description": "List installed plugins",
105
+ "empty": "No plugins installed. Use `trackfw plugins add <user/repo>` to install one."
106
+ },
107
+ "add": {
108
+ "description": "Install a plugin from GitHub Releases (user/repo or user/repo@tag)",
109
+ "installing": "Installing plugin from {{repo}}...",
110
+ "success": "Plugin \"{{name}}\" installed successfully."
111
+ },
112
+ "remove": {
113
+ "description": "Remove an installed plugin",
114
+ "success": "Plugin \"{{name}}\" removed."
115
+ }
116
+ },
117
+ "errors": {
118
+ "notFound": "Not found: {{path}}",
119
+ "downloadFailed": "download failed: HTTP {{status}} for {{url}}",
120
+ "pluginNotFound": "plugin \"{{name}}\" not found"
121
+ }
122
+ }
@@ -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
+ }