trackfw 7.5.0 → 7.6.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.
@@ -414,18 +414,33 @@ function listReqMdFiles(dir) {
414
414
  } catch (_) { return [] }
415
415
  }
416
416
 
417
+ // resolveAgentForWrite — ponto único de resolução do agente de escrita em modo by_agent.
418
+ // Regra (AC5, AC10, KG 2026-08-29):
419
+ // - explicitAgent fornecido → usa-o (AC4/AC5b: valor fora de agents: é permitido, produz violação de namespace)
420
+ // - agents: com UM namespace → usa aquele, sem erro (AC14: projeto single-agent nunca vê o erro)
421
+ // - agents: vazio → usa "default"
422
+ // - agents: com VÁRIOS → lança erro nomeando todas as opções (AC5: silêncio de agents[0] deixa de existir)
423
+ // Nomes vazios em agents: não contam (agents: ["", "zeus"] é UM namespace) — filter antes de contar.
424
+ function resolveAgentForWrite(cfg, explicitAgent) {
425
+ if (explicitAgent) return explicitAgent
426
+ const agents = (cfg.agents || []).filter(a => a)
427
+ if (agents.length === 0) return 'default'
428
+ if (agents.length === 1) return agents[0]
429
+ throw new Error(`by_agent project has multiple agent namespaces (${agents.join(', ')}): use --agent to specify one`)
430
+ }
431
+
417
432
  // reqWriteDir é o PONTO ÚNICO que decide ONDE uma REQ nova é gravada (ADR-2026-09-03, D2/D4):
418
433
  // flat → req_dir/
419
- // by_agent → req_dir/<agente>/ (primeiro de agents:, ou "default" se a lista é vazia)
434
+ // by_agent → req_dir/<agente>/ (agente resolvido via resolveAgentForWrite — AC4/AC5/AC10)
420
435
  // Consumido pelo gerador (newREQ); a união de leitura abaixo contém este diretório por construção.
421
- function reqWriteDir(cfg) {
436
+ // Lança Error em by_agent com múltiplos namespaces e sem agente explícito (AC5).
437
+ function reqWriteDir(cfg, agent) {
422
438
  const reqDir = cfg.reqDir || cfg.req_dir || ''
423
439
  if (!reqDir) return ''
424
440
  const namespacing = cfg.roadmapNamespacing || cfg.roadmap_namespacing || ''
425
441
  if (namespacing === 'by_agent') {
426
- const agents = (cfg.agents || []).filter(a => a)
427
- const agent = agents.length > 0 ? agents[0] : 'default'
428
- return path.join(reqDir, agent)
442
+ const resolvedAgent = resolveAgentForWrite(cfg, agent)
443
+ return path.join(reqDir, resolvedAgent)
429
444
  }
430
445
  return reqDir
431
446
  }
@@ -1500,12 +1515,37 @@ function branchSlugMatchesRoadmap(branchSlug, wipDirs, doneDirs) {
1500
1515
  return { matched, candidates }
1501
1516
  }
1502
1517
 
1518
+ // isMultiAgentByAgent returns {multi: bool, agents: string[]} — parity with Go's IsMultiAgentByAgent.
1519
+ // multi is true only when roadmap_namespacing is "by_agent" AND there are 2+ non-empty agent names.
1520
+ function isMultiAgentByAgent(cfg) {
1521
+ const ns = cfg.roadmapNamespacing || cfg.roadmap_namespacing || ''
1522
+ if (ns !== 'by_agent') return { multi: false, agents: [] }
1523
+ const nonEmpty = (cfg.agents || []).filter(a => a)
1524
+ if (nonEmpty.length < 2) return { multi: false, agents: nonEmpty }
1525
+ return { multi: true, agents: nonEmpty }
1526
+ }
1527
+
1528
+ // reqNewLine returns the correct `trackfw req new` command for the project — parity with Go's ReqNewLine.
1529
+ function reqNewLine(cfg) {
1530
+ const { multi, agents } = isMultiAgentByAgent(cfg)
1531
+ if (multi) return `trackfw req new --agent <agent> "title" # agents: ${agents.join(', ')}`
1532
+ return 'trackfw req new "title"'
1533
+ }
1534
+
1535
+ // roadmapNewLine returns the correct `trackfw roadmap new` command — parity with Go's RoadmapNewLine.
1536
+ function roadmapNewLine(cfg) {
1537
+ const { multi } = isMultiAgentByAgent(cfg)
1538
+ if (multi) return 'trackfw roadmap new --agent <agent> "title"'
1539
+ return 'trackfw roadmap new "title"'
1540
+ }
1541
+
1503
1542
  // branchGovernanceOrientation is the guidance message printed when a feat/fix/refactor branch has
1504
1543
  // no roadmap in wip/ nor done/ at all (candidates is empty). Shared by validateBranchHasWIPRoadmap
1505
1544
  // and `trackfw branch new` — never duplicate this string. Byte-identical to Go's
1506
1545
  // BranchGovernanceOrientation.
1507
- function branchGovernanceOrientation(branch) {
1508
- return `branch "${branch}" is a feat/fix/refactor branch but no roadmap is in wip/ nor done/ — create governance artifacts first:\n trackfw req new "title"\n trackfw roadmap new "title"\n trackfw roadmap move <name> wip`
1546
+ function branchGovernanceOrientation(branch, cfg) {
1547
+ const cfgObj = cfg || {}
1548
+ return `branch "${branch}" is a feat/fix/refactor branch but no roadmap is in wip/ nor done/ — create governance artifacts first:\n ${reqNewLine(cfgObj)}\n ${roadmapNewLine(cfgObj)}\n trackfw roadmap move <name> wip`
1509
1549
  }
1510
1550
 
1511
1551
  // branchNoMatchingRoadmapMessage is the guidance message printed when roadmaps exist in wip/ or
@@ -1548,7 +1588,7 @@ function validateBranchHasWIPRoadmap() {
1548
1588
  if (matched) return []
1549
1589
 
1550
1590
  if (candidates.length === 0) {
1551
- return [branchGovernanceOrientation(branch)]
1591
+ return [branchGovernanceOrientation(branch, cfg)]
1552
1592
  }
1553
1593
  return [branchNoMatchingRoadmapMessage(branch, candidates)]
1554
1594
  }
@@ -3962,6 +4002,7 @@ module.exports = {
3962
4002
  tryListDir,
3963
4003
  resolveReqFiles,
3964
4004
  reqWriteDir,
4005
+ resolveAgentForWrite,
3965
4006
  resolveStateDirs,
3966
4007
  resolveWIPDirs,
3967
4008
  resolveDoneDirs,
@@ -3980,6 +4021,9 @@ module.exports = {
3980
4021
  validateBranchHasWIPRoadmap,
3981
4022
  // novas funções — trackfw branch new (extraídas do gate branch_has_wip_roadmap)
3982
4023
  branchSlugMatchesRoadmap,
4024
+ isMultiAgentByAgent,
4025
+ reqNewLine,
4026
+ roadmapNewLine,
3983
4027
  branchGovernanceOrientation,
3984
4028
  branchNoMatchingRoadmapMessage,
3985
4029
  normalizeBranchSlug,