trackfw 2.5.0 → 2.5.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "CLI de governança para entrega de software: ADR → REQ → ROADMAP → kanban. Suporte nativo a agentes de IA (Claude Code, Gemini CLI, Cursor).",
5
5
  "keywords": [
6
6
  "cli",
@@ -162,6 +162,48 @@ const configDocs = {
162
162
  description: 'Severidade: REQ bloqueada por ADR em rascunho.',
163
163
  example: 'rules:\n blocked_by_draft_adr: warning',
164
164
  impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
165
+ },
166
+ trace_id_field: {
167
+ type: 'string',
168
+ default: '""',
169
+ description: 'Campo de frontmatter usado como trace ID para verificação bidirecional REQ↔Roadmap. Vazio desativa a verificação.',
170
+ example: 'trace_id_field: req_id',
171
+ impact: 'Quando configurado, ativa as regras traceid_* que garantem que toda REQ tem Roadmap correspondente e vice-versa.'
172
+ },
173
+ 'rules.traceid_orphan_roadmap': {
174
+ type: 'off|warning|error',
175
+ default: '"error"',
176
+ description: 'Severidade: Roadmap com trace ID sem REQ correspondente.',
177
+ example: 'rules:\n traceid_orphan_roadmap: warning',
178
+ impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
179
+ },
180
+ 'rules.traceid_orphan_req': {
181
+ type: 'off|warning|error',
182
+ default: '"error"',
183
+ description: 'Severidade: REQ com trace ID sem Roadmap correspondente.',
184
+ example: 'rules:\n traceid_orphan_req: warning',
185
+ impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
186
+ },
187
+ 'rules.traceid_state_mismatch': {
188
+ type: 'off|warning|error',
189
+ default: '"error"',
190
+ description: 'Severidade: REQ e Roadmap com mesmo trace ID em estados diferentes (ex: REQ em done, Roadmap em wip).',
191
+ example: 'rules:\n traceid_state_mismatch: warning',
192
+ impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
193
+ },
194
+ 'rules.traceid_duplicate_req': {
195
+ type: 'off|warning|error',
196
+ default: '"error"',
197
+ description: 'Severidade: mesmo trace ID aparece em mais de uma REQ.',
198
+ example: 'rules:\n traceid_duplicate_req: warning',
199
+ impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
200
+ },
201
+ 'rules.traceid_duplicate_roadmap': {
202
+ type: 'off|warning|error',
203
+ default: '"error"',
204
+ description: 'Severidade: mesmo trace ID aparece em mais de um Roadmap.',
205
+ example: 'rules:\n traceid_duplicate_roadmap: warning',
206
+ impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
165
207
  }
166
208
  }
167
209
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
  const { Command } = require('commander')
3
- const { validate, isLenient, lenientUntilDate } = require('../validator')
3
+ const { validate, isLenient, lenientUntilDate, getItemMeta } = require('../validator')
4
4
  const { t } = require('../i18n')
5
5
 
6
6
  const cmd = new Command('validate')
@@ -20,8 +20,8 @@ cmd.action(async (options) => {
20
20
  mode,
21
21
  exit_code: exitCode,
22
22
  },
23
- violations: violations.map(v => ({ message: v })),
24
- warnings: warnings.map(w => ({ message: w })),
23
+ violations: violations.map(v => { const m = getItemMeta(v); return { message: v, rule: m.rule, file: m.file } }),
24
+ warnings: warnings.map(w => { const m = getItemMeta(w); return { message: w, rule: m.rule, file: m.file } }),
25
25
  }
26
26
  console.log(JSON.stringify(output, null, 2))
27
27
  process.exit(exitCode)
@@ -700,6 +700,27 @@ function validateFilenameUniqueness() {
700
700
  return violations
701
701
  }
702
702
 
703
+ // _itemMeta: mapa de message → { rule, file } para enriquecer saída JSON.
704
+ // Populado em applyRule e nos pushs diretos do validateUnfiltered.
705
+ // Permanece em memória apenas durante a execução de uma chamada validate*.
706
+ const _itemMeta = new Map()
707
+
708
+ // _setMeta registra metadados de rule/file para uma mensagem.
709
+ function _setMeta(msg, ruleName) {
710
+ const m = /"([^"]+)"/.exec(msg)
711
+ _itemMeta.set(msg, { rule: ruleName, file: m ? m[1] : '' })
712
+ }
713
+
714
+ // getItemMeta retorna { rule, file } para uma mensagem, ou { rule: '', file: '' } se ausente.
715
+ function getItemMeta(msg) {
716
+ return _itemMeta.get(msg) || { rule: '', file: '' }
717
+ }
718
+
719
+ // resetMeta limpa o mapa entre execuções (usado internamente).
720
+ function resetMeta() {
721
+ _itemMeta.clear()
722
+ }
723
+
703
724
  // ruleSeverity retorna a severidade configurada para uma regra ('error'|'warning'|'off').
704
725
  function ruleSeverity(name) {
705
726
  const cfg = config.load()
@@ -708,14 +729,15 @@ function ruleSeverity(name) {
708
729
 
709
730
  // applyRule distribui msgs para violations ou warnings conforme a severidade configurada.
710
731
  // Se severidade for 'off', descarta silenciosamente.
732
+ // Também popula _itemMeta com rule/file para cada mensagem aceita.
711
733
  function applyRule(ruleName, msgs, violations, warnings) {
712
734
  if (!msgs || msgs.length === 0) return
713
735
  const severity = ruleSeverity(ruleName)
714
736
  if (severity === 'off') return
715
737
  if (severity === 'warning') {
716
- warnings.push(...msgs)
738
+ for (const msg of msgs) { _setMeta(msg, ruleName); warnings.push(msg) }
717
739
  } else {
718
- violations.push(...msgs)
740
+ for (const msg of msgs) { _setMeta(msg, ruleName); violations.push(msg) }
719
741
  }
720
742
  }
721
743
 
@@ -745,11 +767,12 @@ function saveBaseline(violations, warnings) {
745
767
 
746
768
  // validateUnfiltered executa todas as validações e retorna { violations, warnings } sem ratchet.
747
769
  async function validateUnfiltered() {
770
+ resetMeta()
748
771
  const wipLimitResult = validateWIPLimit()
749
772
  const violations = []
750
773
  const warnings = []
751
774
 
752
- // Regras com severidade configurável via applyRule
775
+ // Regras com severidade configurável via applyRule (popula _itemMeta automaticamente)
753
776
  applyRule('wip_has_req', validateWIPHasREQ(), violations, warnings)
754
777
  applyRule('wip_acceptance', validateWIPHasAcceptanceCriteria(), violations, warnings)
755
778
  applyRule('wip_limit', wipLimitResult.violations, violations, warnings)
@@ -761,18 +784,24 @@ async function validateUnfiltered() {
761
784
  applyRule('blocked_by_draft_adr', validateREQsNotBlockedByDraftADRs(), violations, warnings)
762
785
 
763
786
  // Regras diretas (sem configuração de severidade): violations sempre
764
- violations.push(...validateREQsHaveADR())
765
- violations.push(...validateBlockedHasREQ())
766
- violations.push(...validateREQsHaveRoadmap())
767
- violations.push(...validateFrontmatterPresence())
787
+ // Popular _itemMeta manualmente para manter rastreabilidade no JSON
788
+ for (const msg of validateREQsHaveADR()) { _setMeta(msg, 'req_has_adr'); violations.push(msg) }
789
+ for (const msg of validateBlockedHasREQ()) { _setMeta(msg, 'blocked_has_req'); violations.push(msg) }
790
+ for (const msg of validateREQsHaveRoadmap()) { _setMeta(msg, 'req_has_roadmap'); violations.push(msg) }
791
+ for (const msg of validateFrontmatterPresence()) { _setMeta(msg, 'frontmatter_presence'); violations.push(msg) }
768
792
 
769
793
  // warnings diretos do WIP limit (não configuráveis)
770
- warnings.push(...wipLimitResult.warnings)
794
+ for (const msg of wipLimitResult.warnings) { _setMeta(msg, 'wip_limit'); warnings.push(msg) }
771
795
 
772
796
  // Verificação bidirecional de trace ID (somente se traceIdField configurado)
773
797
  const cfg = config.load()
774
798
  if (cfg.traceIdField) {
775
- violations.push(...checkTraceIds(cfg.reqDir, cfg.roadmapDir, cfg.traceIdField))
799
+ for (const msg of checkTraceIds(cfg.reqDir, cfg.roadmapDir, cfg.traceIdField)) {
800
+ // O prefixo da mensagem traceid já carrega o nome da regra (ex: "traceid_orphan_roadmap: ...")
801
+ const ruleName = msg.split(':')[0].trim()
802
+ _setMeta(msg, ruleName)
803
+ violations.push(msg)
804
+ }
776
805
  }
777
806
 
778
807
  return { violations, warnings }
@@ -917,4 +946,7 @@ module.exports = {
917
946
  contentHasMarker,
918
947
  ruleSeverity,
919
948
  applyRule,
949
+ // novas funções ML-1B (v2.5.1)
950
+ getItemMeta,
951
+ resetMeta,
920
952
  }