trackfw 1.1.0 → 2.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.
- package/package.json +1 -1
- package/src/commands/adr.js +1 -1
- package/src/commands/context.js +189 -0
- package/src/commands/discover.js +359 -0
- package/src/commands/index.js +4 -0
- package/src/commands/init.js +11 -2
- package/src/commands/metrics.js +235 -0
- package/src/commands/plugins.js +73 -0
- package/src/commands/req.js +1 -1
- package/src/commands/roadmap.js +6 -1
- package/src/commands/sync.js +362 -0
- package/src/commands/validate.js +9 -1
- package/src/config/index.js +92 -0
- package/src/generators/adr.js +20 -7
- package/src/generators/init.js +51 -1
- package/src/generators/req.js +12 -3
- package/src/generators/roadmap.js +292 -58
- package/src/i18n/locales/en-US.json +9 -1
- package/src/i18n/locales/es-ES.json +9 -1
- package/src/i18n/locales/pt-BR.json +9 -1
- package/src/validator/index.js +369 -97
|
@@ -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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
48
|
-
*
|
|
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
|
-
*
|
|
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
|
|
86
|
-
|
|
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
|
-
|
|
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(
|
|
175
|
+
appendTransitionLog(logBasename, fromState, state)
|
|
120
176
|
console.log(`✓ moved ${basename} → ${targetDir}`)
|
|
121
177
|
}
|
|
122
178
|
|
|
123
179
|
/**
|
|
124
|
-
* appendTransitionLog — append em
|
|
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,26 +190,48 @@ function appendTransitionLog(basename, fromState, toState) {
|
|
|
135
190
|
const line = `${timestamp} ${basename.padEnd(50)} ${fromState} → ${toState}\n`
|
|
136
191
|
|
|
137
192
|
try {
|
|
138
|
-
|
|
139
|
-
fs.
|
|
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
|
|
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
|
-
|
|
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
|
-
const body =
|
|
227
|
+
const body = `---
|
|
228
|
+
status: backlog
|
|
229
|
+
date: ${date}
|
|
230
|
+
req: ""
|
|
231
|
+
squad: ""
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
# Roadmap: ${title}
|
|
158
235
|
|
|
159
236
|
> Created: ${date} | Status: backlog
|
|
160
237
|
|
|
@@ -179,25 +256,180 @@ REQ: ${reqPath || ''}
|
|
|
179
256
|
console.log(`✓ created ${filename}`)
|
|
180
257
|
}
|
|
181
258
|
|
|
259
|
+
/**
|
|
260
|
+
* newRoadmapFromReq — lê uma REQ e gera roadmap pré-preenchido com MLs extraídos
|
|
261
|
+
* dos critérios de aceite.
|
|
262
|
+
*/
|
|
263
|
+
function newRoadmapFromReq(reqPath) {
|
|
264
|
+
let data
|
|
265
|
+
try {
|
|
266
|
+
data = fs.readFileSync(reqPath, 'utf8')
|
|
267
|
+
} catch (err) {
|
|
268
|
+
console.error(`reading REQ: ${err.message}`)
|
|
269
|
+
process.exitCode = 1
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const { title: parsedTitle, criteria, linkedADR } = parseReqForRoadmap(data)
|
|
274
|
+
const basename = path.basename(reqPath)
|
|
275
|
+
const title = parsedTitle || basename.replace(/\.md$/, '').replace(/^REQ-/, '')
|
|
276
|
+
|
|
277
|
+
const cfg = config.load()
|
|
278
|
+
const now = new Date()
|
|
279
|
+
const yyyy = now.getFullYear()
|
|
280
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0')
|
|
281
|
+
const dd = String(now.getDate()).padStart(2, '0')
|
|
282
|
+
const date = `${yyyy}-${mm}-${dd}`
|
|
283
|
+
const slug = toSlug(title)
|
|
284
|
+
|
|
285
|
+
let backlogDir
|
|
286
|
+
if (cfg.roadmapNamespacing === config.NAMESPACING_BY_AGENT) {
|
|
287
|
+
backlogDir = agentStateDir(null, 'backlog')
|
|
288
|
+
if (!backlogDir) {
|
|
289
|
+
console.error('cannot resolve backlog dir in by_agent mode')
|
|
290
|
+
process.exitCode = 1
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
backlogDir = cfg.roadmapDir + '/backlog'
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const filename = `${backlogDir}/ROADMAP-${date}-${slug}.md`
|
|
298
|
+
try { fs.mkdirSync(backlogDir, { recursive: true }) } catch (_) {}
|
|
299
|
+
|
|
300
|
+
// Gerar seção de MLs a partir dos critérios de aceite
|
|
301
|
+
const mlLines = ['## Wave 1 — Implementation (derived from REQ criteria)', '> Dependencies: none']
|
|
302
|
+
for (let i = 0; i < criteria.length; i++) {
|
|
303
|
+
const mlLabel = `ML-1${String.fromCharCode(65 + i)}`
|
|
304
|
+
const crit = criteria[i]
|
|
305
|
+
mlLines.push(`\n### ${mlLabel} — ${crit}`)
|
|
306
|
+
mlLines.push('**Status:** pending')
|
|
307
|
+
mlLines.push('**Files affected:**')
|
|
308
|
+
mlLines.push('**Actions:**')
|
|
309
|
+
mlLines.push('**Acceptance criteria:**')
|
|
310
|
+
mlLines.push(`- [ ] ${crit}`)
|
|
311
|
+
mlLines.push('- [ ] build passes')
|
|
312
|
+
mlLines.push('- [ ] tests green')
|
|
313
|
+
}
|
|
314
|
+
const mlSection = mlLines.join('\n')
|
|
315
|
+
|
|
316
|
+
const adrRef = linkedADR ? `\nADR: ${linkedADR}` : ''
|
|
317
|
+
|
|
318
|
+
const body = `---
|
|
319
|
+
status: backlog
|
|
320
|
+
date: ${date}
|
|
321
|
+
req: "${basename}"
|
|
322
|
+
squad: ""
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
# Roadmap: ${title}
|
|
326
|
+
|
|
327
|
+
> Created: ${date} | Status: backlog
|
|
328
|
+
|
|
329
|
+
## Context
|
|
330
|
+
<!-- Derived from REQ: ${basename} -->
|
|
331
|
+
REQ: ${reqPath}${adrRef}
|
|
332
|
+
|
|
333
|
+
${mlSection}
|
|
334
|
+
`
|
|
335
|
+
|
|
336
|
+
fs.writeFileSync(filename, body, 'utf8')
|
|
337
|
+
console.log(`✓ created ${filename}`)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* parseReqForRoadmap — extrai título, critérios de aceite e ADR linkada de conteúdo REQ.
|
|
342
|
+
*/
|
|
343
|
+
function parseReqForRoadmap(content) {
|
|
344
|
+
const lines = content.split('\n')
|
|
345
|
+
let title = ''
|
|
346
|
+
let linkedADR = ''
|
|
347
|
+
const criteria = []
|
|
348
|
+
let inCriteria = false
|
|
349
|
+
|
|
350
|
+
for (const line of lines) {
|
|
351
|
+
if (line.startsWith('# REQ: ')) {
|
|
352
|
+
title = line.replace('# REQ: ', '').trim()
|
|
353
|
+
continue
|
|
354
|
+
}
|
|
355
|
+
if (line.startsWith('# REQ — ')) {
|
|
356
|
+
title = line.replace('# REQ — ', '').trim()
|
|
357
|
+
continue
|
|
358
|
+
}
|
|
359
|
+
if (line.startsWith('# REQ - ')) {
|
|
360
|
+
title = line.replace('# REQ - ', '').trim()
|
|
361
|
+
continue
|
|
362
|
+
}
|
|
363
|
+
if (line.startsWith('**ADR:**')) {
|
|
364
|
+
linkedADR = line.replace('**ADR:**', '').trim()
|
|
365
|
+
continue
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const lower = line.trim().toLowerCase()
|
|
369
|
+
if (lower === '## critérios de aceite' || lower === '## acceptance criteria') {
|
|
370
|
+
inCriteria = true
|
|
371
|
+
continue
|
|
372
|
+
}
|
|
373
|
+
if (inCriteria && line.startsWith('## ')) {
|
|
374
|
+
inCriteria = false
|
|
375
|
+
continue
|
|
376
|
+
}
|
|
377
|
+
if (inCriteria) {
|
|
378
|
+
const trimmed = line.trim()
|
|
379
|
+
const checkboxPrefixes = ['- [ ]', '- [x]', '- [X]']
|
|
380
|
+
for (const prefix of checkboxPrefixes) {
|
|
381
|
+
if (trimmed.startsWith(prefix)) {
|
|
382
|
+
const item = trimmed.slice(prefix.length).trim().replace(/`/g, '')
|
|
383
|
+
if (item) criteria.push(item)
|
|
384
|
+
break
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return { title, criteria, linkedADR }
|
|
390
|
+
}
|
|
391
|
+
|
|
182
392
|
// --- helpers ---
|
|
183
393
|
|
|
184
394
|
/**
|
|
185
395
|
* findRoadmapMatches — retorna array de paths que contêm `name` (case-insensitive) em qualquer estado.
|
|
396
|
+
* Suporta modo flat (1 nível) e by_agent (2 níveis).
|
|
186
397
|
*/
|
|
187
398
|
function findRoadmapMatches(name) {
|
|
399
|
+
const cfg = config.load()
|
|
188
400
|
const matches = []
|
|
189
401
|
const nameLower = name.toLowerCase()
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
402
|
+
|
|
403
|
+
if (cfg.roadmapNamespacing === config.NAMESPACING_BY_AGENT) {
|
|
404
|
+
let agents = cfg.agents || []
|
|
405
|
+
if (agents.length === 0) {
|
|
406
|
+
try {
|
|
407
|
+
agents = fs.readdirSync(cfg.roadmapDir).filter(f => {
|
|
408
|
+
try { return fs.statSync(path.join(cfg.roadmapDir, f)).isDirectory() } catch (_) { return false }
|
|
409
|
+
})
|
|
410
|
+
} catch (_) { agents = ['default'] }
|
|
411
|
+
}
|
|
412
|
+
for (const agent of agents) {
|
|
413
|
+
for (const state of STATE_ORDER) {
|
|
414
|
+
const dir = cfg.roadmapDir + '/' + agent + '/' + state
|
|
415
|
+
let files = []
|
|
416
|
+
try { files = fs.readdirSync(dir) } catch (_) { continue }
|
|
417
|
+
for (const f of files) {
|
|
418
|
+
if (f.toLowerCase().includes(nameLower) && f.endsWith('.md')) {
|
|
419
|
+
matches.push(path.join(dir, f))
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
197
423
|
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
424
|
+
} else {
|
|
425
|
+
for (const state of STATE_ORDER) {
|
|
426
|
+
const dir = cfg.roadmapDir + '/' + state
|
|
427
|
+
let files = []
|
|
428
|
+
try { files = fs.readdirSync(dir) } catch (_) { continue }
|
|
429
|
+
for (const f of files) {
|
|
430
|
+
if (f.toLowerCase().includes(nameLower) && f.endsWith('.md')) {
|
|
431
|
+
matches.push(path.join(dir, f))
|
|
432
|
+
}
|
|
201
433
|
}
|
|
202
434
|
}
|
|
203
435
|
}
|
|
@@ -215,10 +447,12 @@ function toSlug(s) {
|
|
|
215
447
|
}
|
|
216
448
|
|
|
217
449
|
module.exports = {
|
|
218
|
-
VALID_STATES,
|
|
219
450
|
listRoadmaps,
|
|
220
451
|
showRoadmap,
|
|
221
452
|
moveRoadmap,
|
|
222
453
|
appendTransitionLog,
|
|
223
454
|
newRoadmap,
|
|
455
|
+
newRoadmapFromReq,
|
|
456
|
+
stateDir,
|
|
457
|
+
agentStateDir,
|
|
224
458
|
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"gitHooks": "Git hooks?",
|
|
12
12
|
"ci": "CI system?",
|
|
13
13
|
"aiTools": "Which AI assistants do you use?",
|
|
14
|
+
"require_req_in_commit": "Require REQ reference in commit messages for feat/fix branches?",
|
|
14
15
|
"projectType_fullstack": "Full-stack (frontend + backend)",
|
|
15
16
|
"projectType_frontend": "Frontend only",
|
|
16
17
|
"projectType_backend": "Backend only",
|
|
@@ -87,7 +88,8 @@
|
|
|
87
88
|
"description": "Validate governance rules (use as CI gate)",
|
|
88
89
|
"ok": "✓ No violations found.",
|
|
89
90
|
"violations": "✗ Violations ({{count}}):",
|
|
90
|
-
"warnings": "⚠ Warnings ({{count}}):"
|
|
91
|
+
"warnings": "⚠ Warnings ({{count}}):",
|
|
92
|
+
"lenient_mode": "Governance violations treated as warnings until {{date}}"
|
|
91
93
|
},
|
|
92
94
|
"status": {
|
|
93
95
|
"description": "Show project governance status"
|
|
@@ -114,6 +116,12 @@
|
|
|
114
116
|
"success": "Plugin \"{{name}}\" removed."
|
|
115
117
|
}
|
|
116
118
|
},
|
|
119
|
+
"metrics": {
|
|
120
|
+
"description": "Show delivery metrics",
|
|
121
|
+
"no_data": "No transitions recorded yet.",
|
|
122
|
+
"since": "Filter by period (e.g. 7d, 30d, 90d)",
|
|
123
|
+
"export": "Export metrics to CSV file"
|
|
124
|
+
},
|
|
117
125
|
"errors": {
|
|
118
126
|
"notFound": "Not found: {{path}}",
|
|
119
127
|
"downloadFailed": "download failed: HTTP {{status}} for {{url}}",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"gitHooks": "¿Git hooks?",
|
|
12
12
|
"ci": "¿Sistema de CI?",
|
|
13
13
|
"aiTools": "¿Qué asistentes de IA usas?",
|
|
14
|
+
"require_req_in_commit": "¿Requerir referencia REQ en mensajes de commit de ramas feat/fix?",
|
|
14
15
|
"projectType_fullstack": "Full-stack (frontend + backend)",
|
|
15
16
|
"projectType_frontend": "Solo frontend",
|
|
16
17
|
"projectType_backend": "Solo backend",
|
|
@@ -87,7 +88,8 @@
|
|
|
87
88
|
"description": "Validar reglas de gobernanza (úsalo como gate de CI)",
|
|
88
89
|
"ok": "✓ No se encontraron violaciones.",
|
|
89
90
|
"violations": "✗ Violaciones ({{count}}):",
|
|
90
|
-
"warnings": "⚠ Avisos ({{count}}):"
|
|
91
|
+
"warnings": "⚠ Avisos ({{count}}):",
|
|
92
|
+
"lenient_mode": "Las violaciones de gobernanza se tratan como avisos hasta {{date}}"
|
|
91
93
|
},
|
|
92
94
|
"status": {
|
|
93
95
|
"description": "Mostrar el estado actual de gobernanza del proyecto"
|
|
@@ -114,6 +116,12 @@
|
|
|
114
116
|
"success": "Plugin \"{{name}}\" eliminado."
|
|
115
117
|
}
|
|
116
118
|
},
|
|
119
|
+
"metrics": {
|
|
120
|
+
"description": "Muestra métricas de entrega",
|
|
121
|
+
"no_data": "No hay transiciones registradas aún.",
|
|
122
|
+
"since": "Filtrar por período (ej: 7d, 30d, 90d)",
|
|
123
|
+
"export": "Exportar métricas a archivo CSV"
|
|
124
|
+
},
|
|
117
125
|
"errors": {
|
|
118
126
|
"notFound": "No encontrado: {{path}}",
|
|
119
127
|
"downloadFailed": "fallo en la descarga: HTTP {{status}} para {{url}}",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"gitHooks": "Git hooks?",
|
|
12
12
|
"ci": "Sistema de CI?",
|
|
13
13
|
"aiTools": "Quais assistentes de IA você usa?",
|
|
14
|
+
"require_req_in_commit": "Exigir referência REQ nas mensagens de commit de branches feat/fix?",
|
|
14
15
|
"projectType_fullstack": "Full-stack (frontend + backend)",
|
|
15
16
|
"projectType_frontend": "Somente frontend",
|
|
16
17
|
"projectType_backend": "Somente backend",
|
|
@@ -87,7 +88,8 @@
|
|
|
87
88
|
"description": "Validar regras de governança (use como gate de CI)",
|
|
88
89
|
"ok": "✓ Nenhuma violação encontrada.",
|
|
89
90
|
"violations": "✗ Violações ({{count}}):",
|
|
90
|
-
"warnings": "⚠ Avisos ({{count}}):"
|
|
91
|
+
"warnings": "⚠ Avisos ({{count}}):",
|
|
92
|
+
"lenient_mode": "Violações de governança tratadas como avisos até {{date}}"
|
|
91
93
|
},
|
|
92
94
|
"status": {
|
|
93
95
|
"description": "Exibir o estado atual de governança do projeto"
|
|
@@ -114,6 +116,12 @@
|
|
|
114
116
|
"success": "Plugin \"{{name}}\" removido."
|
|
115
117
|
}
|
|
116
118
|
},
|
|
119
|
+
"metrics": {
|
|
120
|
+
"description": "Exibe métricas de delivery",
|
|
121
|
+
"no_data": "Nenhuma transição registrada ainda.",
|
|
122
|
+
"since": "Filtrar por período (ex: 7d, 30d, 90d)",
|
|
123
|
+
"export": "Exportar métricas para arquivo CSV"
|
|
124
|
+
},
|
|
117
125
|
"errors": {
|
|
118
126
|
"notFound": "Não encontrado: {{path}}",
|
|
119
127
|
"downloadFailed": "falha no download: HTTP {{status}} para {{url}}",
|