trackfw 7.6.0 → 8.0.0-rc2

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.
Files changed (124) hide show
  1. package/README.md +28 -11
  2. package/bin/trackfw.js +81 -0
  3. package/package.json +14 -8
  4. package/bin/trackfw +0 -41
  5. package/src/branch/prune.js +0 -450
  6. package/src/branch/runner.js +0 -153
  7. package/src/changelog/index.js +0 -114
  8. package/src/commands/adr.js +0 -56
  9. package/src/commands/agents.js +0 -3
  10. package/src/commands/audit-surface.js +0 -520
  11. package/src/commands/barrier.js +0 -822
  12. package/src/commands/baseline.js +0 -13
  13. package/src/commands/branch.js +0 -98
  14. package/src/commands/changelog.js +0 -29
  15. package/src/commands/commit.js +0 -66
  16. package/src/commands/configure.js +0 -106
  17. package/src/commands/context.js +0 -215
  18. package/src/commands/discover.js +0 -628
  19. package/src/commands/doctor.js +0 -66
  20. package/src/commands/help.js +0 -358
  21. package/src/commands/identity-wizard.js +0 -258
  22. package/src/commands/index.js +0 -68
  23. package/src/commands/init.js +0 -296
  24. package/src/commands/integrations.js +0 -305
  25. package/src/commands/log.js +0 -31
  26. package/src/commands/metrics.js +0 -242
  27. package/src/commands/note.js +0 -24
  28. package/src/commands/push.js +0 -51
  29. package/src/commands/release.js +0 -47
  30. package/src/commands/req.js +0 -98
  31. package/src/commands/roadmap.js +0 -44
  32. package/src/commands/serve.js +0 -345
  33. package/src/commands/ship.js +0 -58
  34. package/src/commands/skills.js +0 -3
  35. package/src/commands/status.js +0 -12
  36. package/src/commands/sync.js +0 -348
  37. package/src/commands/thirdparty.js +0 -390
  38. package/src/commands/update-harness.js +0 -1002
  39. package/src/commands/update.js +0 -500
  40. package/src/commands/validate.js +0 -57
  41. package/src/commands/version.js +0 -10
  42. package/src/commit/runner.js +0 -352
  43. package/src/config/index.js +0 -457
  44. package/src/forge/adapter.js +0 -189
  45. package/src/forge/resolve.js +0 -191
  46. package/src/generators/adr.js +0 -198
  47. package/src/generators/codex.js +0 -79
  48. package/src/generators/date.js +0 -23
  49. package/src/generators/hooks.js +0 -2414
  50. package/src/generators/init.js +0 -1563
  51. package/src/generators/note.js +0 -154
  52. package/src/generators/req.js +0 -436
  53. package/src/generators/roadmap.js +0 -841
  54. package/src/homedir.js +0 -30
  55. package/src/i18n/index.js +0 -53
  56. package/src/i18n/locales/en-US.json +0 -124
  57. package/src/i18n/locales/es-ES.json +0 -124
  58. package/src/i18n/locales/pt-BR.json +0 -124
  59. package/src/identity/config.js +0 -134
  60. package/src/identity/index.js +0 -19
  61. package/src/identity/preset.js +0 -219
  62. package/src/identity/slug.js +0 -62
  63. package/src/integrations/assets/agents/architect.md +0 -61
  64. package/src/integrations/assets/agents/backend.md +0 -41
  65. package/src/integrations/assets/agents/code-quality.md +0 -48
  66. package/src/integrations/assets/agents/data.md +0 -41
  67. package/src/integrations/assets/agents/dba.md +0 -41
  68. package/src/integrations/assets/agents/frontend.md +0 -41
  69. package/src/integrations/assets/agents/iac.md +0 -46
  70. package/src/integrations/assets/agents/infra.md +0 -43
  71. package/src/integrations/assets/agents/qa.md +0 -41
  72. package/src/integrations/assets/agents/security.md +0 -51
  73. package/src/integrations/assets/agents/tooling.md +0 -47
  74. package/src/integrations/assets/agents/ux.md +0 -48
  75. package/src/integrations/assets/catalog.json +0 -48
  76. package/src/integrations/assets/skills/architecture.md +0 -66
  77. package/src/integrations/assets/skills/backend.md +0 -67
  78. package/src/integrations/assets/skills/code-quality.md +0 -78
  79. package/src/integrations/assets/skills/data.md +0 -75
  80. package/src/integrations/assets/skills/dba.md +0 -77
  81. package/src/integrations/assets/skills/frontend.md +0 -70
  82. package/src/integrations/assets/skills/governance.md +0 -6
  83. package/src/integrations/assets/skills/iac.md +0 -90
  84. package/src/integrations/assets/skills/implement.md +0 -6
  85. package/src/integrations/assets/skills/infra.md +0 -62
  86. package/src/integrations/assets/skills/plan.md +0 -6
  87. package/src/integrations/assets/skills/qa.md +0 -63
  88. package/src/integrations/assets/skills/release.md +0 -6
  89. package/src/integrations/assets/skills/review.md +0 -6
  90. package/src/integrations/assets/skills/security.md +0 -74
  91. package/src/integrations/assets/skills/tooling.md +0 -80
  92. package/src/integrations/assets/skills/ux.md +0 -70
  93. package/src/integrations/catalog.js +0 -58
  94. package/src/integrations/doctor.js +0 -0
  95. package/src/integrations/doctor_remote.js +0 -224
  96. package/src/integrations/index.js +0 -171
  97. package/src/integrations/legacy.js +0 -35
  98. package/src/integrations/manager.js +0 -438
  99. package/src/integrations/register-agent.js +0 -106
  100. package/src/integrations/render.js +0 -563
  101. package/src/integrations/scaffold_doctor.js +0 -395
  102. package/src/lib/fatal-error.js +0 -64
  103. package/src/lib/pathfmt.js +0 -43
  104. package/src/lib/unknown-command.js +0 -82
  105. package/src/lib/update-engine.js +0 -242
  106. package/src/push/runner.js +0 -226
  107. package/src/release/runner.js +0 -474
  108. package/src/serve/api_attention.js +0 -24
  109. package/src/serve/api_board.js +0 -105
  110. package/src/serve/api_chain.js +0 -246
  111. package/src/serve/api_file.js +0 -117
  112. package/src/serve/api_metrics.js +0 -139
  113. package/src/serve/static/app.js +0 -1151
  114. package/src/serve/static/index.html +0 -338
  115. package/src/serve/static/style.css +0 -297
  116. package/src/ship/runner.js +0 -775
  117. package/src/thirdparty/fetch.js +0 -140
  118. package/src/thirdparty/markers.js +0 -207
  119. package/src/thirdparty/provenance.js +0 -114
  120. package/src/thirdparty/quarantine.js +0 -113
  121. package/src/thirdparty/references.js +0 -194
  122. package/src/validator/git-exec.js +0 -72
  123. package/src/validator/index.js +0 -4086
  124. package/src/validator/traceid.js +0 -155
@@ -1,358 +0,0 @@
1
- 'use strict'
2
- const { Command } = require('commander')
3
-
4
- const configDocs = {
5
- adr_dirs: {
6
- type: 'list of strings',
7
- default: '["docs/adr"]',
8
- description: 'Diretórios onde os ADRs são armazenados.',
9
- example: 'adr_dirs:\n - docs/adr\n - docs/adr/zeus',
10
- impact: 'Todos os diretórios listados são varridos na validação de ADRs.'
11
- },
12
- req_dir: {
13
- type: 'string',
14
- default: '"docs/req"',
15
- description: 'Diretório onde as REQs são armazenadas.',
16
- example: 'req_dir: docs/requisicoes',
17
- impact: 'Altera onde o trackfw busca e cria arquivos de REQ.'
18
- },
19
- roadmap_dir: {
20
- type: 'string',
21
- default: '"docs/roadmaps"',
22
- description: 'Diretório raiz dos roadmaps.',
23
- example: 'roadmap_dir: docs/roadmaps',
24
- impact: 'Subtrees backlog/, wip/, blocked/, done/, abandoned/ são relativos a este diretório.'
25
- },
26
- roadmap_namespacing: {
27
- type: 'flat|by_agent',
28
- default: '"flat"',
29
- description: 'Estratégia de namespacing dos roadmaps.',
30
- example: 'roadmap_namespacing: by_agent',
31
- impact: 'Com by_agent, roadmaps ficam em subpastas por agente (ex: wip/apolo/RM-001.md).'
32
- },
33
- agents: {
34
- type: 'list of strings',
35
- default: '[]',
36
- description: 'Lista de agentes ativos no projeto.',
37
- example: 'agents:\n - apolo\n - afrodite',
38
- impact: 'Agentes listados têm suas subpastas criadas automaticamente em roadmap_dir.'
39
- },
40
- governance_mode: {
41
- type: 'string',
42
- default: '""',
43
- description: 'Modo de governança (strict, lenient).',
44
- example: 'governance_mode: strict',
45
- impact: 'strict bloqueia commits/merges com violations; lenient emite apenas avisos.'
46
- },
47
- lenient_until: {
48
- type: 'date (YYYY-MM-DD)',
49
- default: '""',
50
- description: 'Data até quando o modo lenient está ativo.',
51
- example: 'lenient_until: 2026-12-31',
52
- impact: 'Após a data, o modo volta automaticamente para strict.'
53
- },
54
- wip_limit: {
55
- type: 'integer',
56
- default: '1',
57
- description: 'Limite de itens WIP simultâneos.',
58
- example: 'wip_limit: 3',
59
- impact: 'Aumentar reduz a frequência de bloqueio.'
60
- },
61
- wip_by_squad: {
62
- type: 'boolean',
63
- default: 'false',
64
- description: 'Aplicar limite WIP por squad individualmente.',
65
- example: 'wip_by_squad: true',
66
- impact: 'Cada squad tem seu próprio contador de WIP em vez de um limite global.'
67
- },
68
- require_req_in_commit: {
69
- type: 'boolean',
70
- default: 'false',
71
- description: 'Exigir referência de REQ em mensagens de commit.',
72
- example: 'require_req_in_commit: true',
73
- impact: 'Commits sem menção a uma REQ são rejeitados pelo hook de pre-commit.'
74
- },
75
- 'link_fields.req': {
76
- type: 'list of strings',
77
- default: '["REQ:"]',
78
- description: 'Marcadores que identificam link a REQ.',
79
- example: 'link_fields:\n req:\n - "REQ:"\n - "Requisito:"',
80
- impact: 'Qualquer marcador listado é aceito para detectar vínculo com REQ.'
81
- },
82
- 'link_fields.adr': {
83
- type: 'list of strings',
84
- default: '["ADR:"]',
85
- description: 'Marcadores que identificam link a ADR.',
86
- example: 'link_fields:\n adr:\n - "ADR:"\n - "Decision:"',
87
- impact: 'Qualquer marcador listado é aceito para detectar vínculo com ADR.'
88
- },
89
- 'link_fields.roadmap': {
90
- type: 'list of strings',
91
- default: '["Roadmap:"]',
92
- description: 'Marcadores que identificam link a Roadmap.',
93
- example: 'link_fields:\n roadmap:\n - "Roadmap:"',
94
- impact: 'Qualquer marcador listado é aceito para detectar vínculo com Roadmap.'
95
- },
96
- acceptance_markers: {
97
- type: 'list of strings',
98
- default: '["## Acceptance Criteria", "## Critérios de Aceite"]',
99
- description: 'Marcadores de critério de aceite.',
100
- example: 'acceptance_markers:\n - "## Acceptance Criteria"\n - "## AC"',
101
- impact: 'Roadmaps WIP sem nenhum desses marcadores disparam a regra wip_acceptance.'
102
- },
103
- 'rules.wip_has_req': {
104
- type: 'off|warning|error',
105
- default: '"error"',
106
- description: 'Severidade: WIP sem REQ linkada.',
107
- example: 'rules:\n wip_has_req: warning',
108
- impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
109
- },
110
- 'rules.wip_acceptance': {
111
- type: 'off|warning|error',
112
- default: '"error"',
113
- description: 'Severidade: WIP sem critérios de aceite.',
114
- example: 'rules:\n wip_acceptance: warning',
115
- impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
116
- },
117
- 'rules.wip_limit': {
118
- type: 'off|warning|error',
119
- default: '"error"',
120
- description: 'Severidade: excesso de itens WIP.',
121
- example: 'rules:\n wip_limit: warning',
122
- impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
123
- },
124
- 'rules.stale_wip': {
125
- type: 'off|warning|error',
126
- default: '"warning"',
127
- description: 'Severidade: WIP sem atualização recente.',
128
- example: 'rules:\n stale_wip: error',
129
- impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
130
- },
131
- 'rules.adr_orphan': {
132
- type: 'off|warning|error',
133
- default: '"warning"',
134
- description: 'Severidade: ADR sem REQ vinculada.',
135
- example: 'rules:\n adr_orphan: error',
136
- impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
137
- },
138
- 'rules.ref_targets_exist': {
139
- type: 'off|warning|error',
140
- default: '"warning"',
141
- description: 'Severidade: referências com destino inexistente.',
142
- example: 'rules:\n ref_targets_exist: error',
143
- impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
144
- },
145
- 'rules.folder_status': {
146
- type: 'off|warning|error',
147
- default: '"warning"',
148
- description: 'Severidade: coerência entre pasta e status do arquivo.',
149
- example: 'rules:\n folder_status: error',
150
- impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
151
- },
152
- 'rules.filename_uniqueness': {
153
- type: 'off|warning|error',
154
- default: '"error"',
155
- description: 'Severidade: nomes de arquivo duplicados.',
156
- example: 'rules:\n filename_uniqueness: warning',
157
- impact: 'error bloqueia; warning apenas reporta; off desativa a regra.'
158
- },
159
- 'rules.blocked_by_draft_adr': {
160
- type: 'off|warning|error',
161
- default: '"error"',
162
- description: 'Severidade: REQ bloqueada por ADR em rascunho.',
163
- example: 'rules:\n blocked_by_draft_adr: warning',
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.'
207
- }
208
- }
209
-
210
- /**
211
- * Retorna string com a listagem tabular de todas as keys configuráveis.
212
- * @returns {string}
213
- */
214
- function listKeys() {
215
- const COL_KEY = 28
216
- const COL_DEFAULT = 36
217
- const header = 'KEY'.padEnd(COL_KEY) + 'DEFAULT'.padEnd(COL_DEFAULT) + 'DESCRIÇÃO'
218
- const sep = '─'.repeat(COL_KEY + COL_DEFAULT + 40)
219
- const lines = [header, sep]
220
- for (const [key, doc] of Object.entries(configDocs)) {
221
- lines.push(
222
- key.padEnd(COL_KEY) +
223
- doc.default.padEnd(COL_DEFAULT) +
224
- doc.description
225
- )
226
- }
227
- return lines.join('\n')
228
- }
229
-
230
- /**
231
- * Retorna string com a documentação detalhada de uma key.
232
- * Retorna null se a key não existir.
233
- * @param {string} key
234
- * @returns {string|null}
235
- */
236
- function describeKey(key) {
237
- const doc = configDocs[key]
238
- if (!doc) return null
239
- return [
240
- key,
241
- ` Type: ${doc.type}`,
242
- ` Default: ${doc.default}`,
243
- ` Desc: ${doc.description}`,
244
- ` Example:`,
245
- ...doc.example.split('\n').map(l => ` ${l}`),
246
- ` Impact: ${doc.impact}`
247
- ].join('\n')
248
- }
249
-
250
- /**
251
- * Retorna a listagem de comandos disponíveis a partir do programa raiz.
252
- * @param {import('commander').Command} root
253
- * @returns {string}
254
- */
255
- function listCommands(root) {
256
- const visible = root.commands.filter((sub) => !sub._hidden)
257
- const nameWidth = Math.max(...visible.map((sub) => sub.name().length)) + 2
258
- const lines = ['Comandos disponíveis:']
259
- for (const sub of visible) {
260
- lines.push(` ${sub.name().padEnd(nameWidth)}${sub.description()}`)
261
- }
262
- return lines.join('\n')
263
- }
264
-
265
- /**
266
- * Distância de edição (Levenshtein) entre duas strings.
267
- * @param {string} a
268
- * @param {string} b
269
- * @returns {number}
270
- */
271
- function levenshtein(a, b) {
272
- const la = a.length
273
- const lb = b.length
274
- let prev = new Array(lb + 1)
275
- let curr = new Array(lb + 1)
276
- for (let j = 0; j <= lb; j++) prev[j] = j
277
- for (let i = 1; i <= la; i++) {
278
- curr[0] = i
279
- for (let j = 1; j <= lb; j++) {
280
- const cost = a[i - 1] === b[j - 1] ? 0 : 1
281
- curr[j] = Math.min(curr[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost)
282
- }
283
- const tmp = prev
284
- prev = curr
285
- curr = tmp
286
- }
287
- return prev[lb]
288
- }
289
-
290
- /**
291
- * Retorna o comando ou chave de configuração conhecida mais próxima de
292
- * topic (distância de edição <= 3), ou null se nenhuma for próxima o
293
- * suficiente.
294
- * @param {string} topic
295
- * @param {import('commander').Command} root
296
- * @returns {string|null}
297
- */
298
- function suggestTopic(topic, root) {
299
- const threshold = 3
300
- const candidates = [
301
- ...root.commands.filter((sub) => !sub._hidden).map((sub) => sub.name()),
302
- ...Object.keys(configDocs)
303
- ]
304
- let best = null
305
- let bestDist = threshold + 1
306
- for (const candidate of candidates) {
307
- const dist = levenshtein(topic, candidate)
308
- if (dist < bestDist) {
309
- bestDist = dist
310
- best = candidate
311
- }
312
- }
313
- return bestDist <= threshold ? best : null
314
- }
315
-
316
- const cmd = new Command('help')
317
- cmd.description('Exibe ajuda de comandos e documentação das chaves de configuração do trackfw.yaml')
318
- cmd.argument('[topic]', 'comando ou chave de configuração para detalhar')
319
- cmd.action(function (topic, options, thisCommand) {
320
- const self = thisCommand || this
321
- const root = self.parent || self
322
-
323
- if (!topic) {
324
- console.log(listCommands(root))
325
- console.log()
326
- console.log(listKeys())
327
- return
328
- }
329
-
330
- // 1) comando conhecido → ajuda do comando.
331
- const sub = root.commands.find((c) => c.name() === topic)
332
- if (sub) {
333
- console.log(sub.helpInformation().trimEnd())
334
- return
335
- }
336
-
337
- // 2) chave de configuração conhecida → documentação da chave.
338
- const output = describeKey(topic)
339
- if (output) {
340
- console.log(output)
341
- return
342
- }
343
-
344
- // 3) assunto desconhecido → erro com sugestão útil.
345
- let msg = `assunto desconhecido: ${topic}`
346
- const suggestion = suggestTopic(topic, root)
347
- if (suggestion) {
348
- msg += `\nVocê quis dizer: ${suggestion}?`
349
- }
350
- console.error(msg)
351
- process.exitCode = 1
352
- })
353
-
354
- module.exports = cmd
355
- module.exports.listKeys = listKeys
356
- module.exports.describeKey = describeKey
357
- module.exports.listCommands = listCommands
358
- module.exports.suggestTopic = suggestTopic
@@ -1,258 +0,0 @@
1
- 'use strict'
2
-
3
- const fs = require('node:fs')
4
- const path = require('node:path')
5
-
6
- const identityStore = require('../identity')
7
- const { t } = require('../i18n')
8
-
9
- // identityFileExists reporta se ~/.trackfw/identity.json já existe, sem
10
- // depender de identityStore.load (que retorna config vazia para "ausente").
11
- // Espelha internal/commands/init.go:identityFileExists.
12
- function identityFileExists(home) {
13
- return fs.existsSync(path.join(home, '.trackfw', 'identity.json'))
14
- }
15
-
16
- // resolveIdentityPreset traduz o valor da flag --identity-preset para uma
17
- // Config a persistir. "none" e "neutral" significam "não gravar nada" — o
18
- // chamador não deve criar ~/.trackfw/identity.json para esses valores. Um
19
- // valor desconhecido é sempre um erro, listando os valores aceitos. Espelha
20
- // internal/commands/init.go:resolveIdentityPreset.
21
- function resolveIdentityPreset(value) {
22
- if (value === 'none' || value === 'neutral') return { cfg: null, shouldSave: false }
23
- let cfg
24
- try {
25
- cfg = identityStore.preset(value)
26
- } catch {
27
- const valid = ['none', 'neutral', ...identityStore.presetNames()]
28
- throw new Error(`identity-preset invalido "${value}" (validos: ${valid.join(', ')})`)
29
- }
30
- return { cfg, shouldSave: true }
31
- }
32
-
33
- // applyIdentityPresetFlag resolve e persiste --identity-preset para
34
- // `agents install|update|uninstall`, reusando resolveIdentityPreset para que
35
- // ambos os comandos aceitem exatamente os mesmos nomes de preset e rejeitem
36
- // valores inválidos com o mesmo formato de erro. Espelha
37
- // internal/commands/integrations_flags.go:applyIdentityPresetFlag.
38
- function applyIdentityPresetFlag(home, presetValue, operation) {
39
- const { cfg, shouldSave } = resolveIdentityPreset(presetValue)
40
- if (!shouldSave) return
41
- try {
42
- identityStore.validate(cfg, identityStore.knownAgentIds())
43
- } catch (err) {
44
- throw new Error(`${operation}: identidade invalida: ${err.message}`)
45
- }
46
- try {
47
- identityStore.save(home, cfg)
48
- } catch (err) {
49
- throw new Error(`${operation}: falha ao gravar identidade: ${err.message}`)
50
- }
51
- }
52
-
53
- // shouldPromptIdentity decide se `trackfw agents install` (e as operações
54
- // update/uninstall, que compartilham o mesmo fluxo) deve mostrar o wizard
55
- // interativo de identidade. Todas as condições devem valer simultaneamente
56
- // (ADR D2):
57
- //
58
- // 1. kind é "agents" — skills não têm identidade e nunca devem perguntar.
59
- // 2. stdin é um TTY — nunca bloquear uma execução não-interativa.
60
- // 3. ou não há identidade configurada ainda, ou o usuário pediu
61
- // explicitamente para reconfigurá-la via --identity.
62
- //
63
- // Com uma identidade já configurada e sem a flag --identity, o wizard NÃO
64
- // deve reaparecer — um wizard que aparece a cada install vira incentivo a
65
- // automatizar o "pular", o que esvazia a feature. Espelha
66
- // internal/commands/identity_wizard.go:shouldPromptIdentity.
67
- function shouldPromptIdentity(kind, isTTY, identityExists, forceFlag) {
68
- if (kind !== 'agents') return false
69
- if (!isTTY) return false
70
- return !identityExists || Boolean(forceFlag)
71
- }
72
-
73
- // identityPresetOptions é a lista fixa de presets temáticos de identidade
74
- // oferecidos pelo wizard, compartilhada literalmente entre init e agents
75
- // install para que nenhum consumidor divirja do outro. Espelha
76
- // internal/commands/identity_wizard.go:identityPresetOptions.
77
- function identityPresetOptions() {
78
- return [
79
- { name: 'Panteão grego (Zeus, Apolo, Afrodite...)', value: 'greek' },
80
- { name: 'Mitologia nórdica (Odin, Thor, Freya...)', value: 'norse' },
81
- { name: 'Pioneiros da computação (Turing, Codd, Knuth...)', value: 'pioneers' },
82
- { name: 'Harry Potter (Dumbledore, Snape, Luna...)', value: 'potter' },
83
- { name: 'Game of Thrones (Tyrion, Jon, Arya...)', value: 'thrones' },
84
- { name: 'Senhor dos Anéis (Gandalf, Aragorn, Arwen...)', value: 'tolkien' },
85
- { name: 'Star Wars (Yoda, Leia, Vader...)', value: 'starwars' },
86
- { name: 'Chaves (Girafales, Madruga, Chiquinha...)', value: 'chaves' },
87
- { name: 'Turma da Mônica (Franjinha, Cebolinha, Mônica...)', value: 'turma' },
88
- { name: 'Panteão egípcio (Thoth, Ísis, Anúbis...)', value: 'egyptian' },
89
- { name: 'Personalizar um a um', value: 'custom' },
90
- { name: 'Nomes neutros (padrão)', value: 'neutral' },
91
- ]
92
- }
93
-
94
- // catalogAgentItem busca o item "agents" do catálogo pelo id. Todo id vindo
95
- // de identity.knownAgentIds() deve existir no catálogo embedado — se não
96
- // existir, é erro de programação: falha alto em vez de rotular
97
- // silenciosamente errado. Espelha o panic em
98
- // internal/commands/identity_wizard.go:buildCustomIdentityGroup.
99
- function catalogAgentItem(catalog, id) {
100
- const found = (catalog.agents || []).find(entry => entry.id === id)
101
- if (!found) {
102
- throw new Error(`identity wizard: agent id "${id}" from identity.knownAgentIds() has no entry in the agents catalog`)
103
- }
104
- return found
105
- }
106
-
107
- // buildIdentityConfig constrói a Config em memória para o caminho escolhido
108
- // no wizard, sem tocar em disco. Espelha
109
- // internal/commands/identity_wizard.go:buildIdentityConfig.
110
- function buildIdentityConfig(identitySelect, knownAgentIds, customDisplayNames, userNickname) {
111
- let cfg
112
- if (identitySelect === 'custom') {
113
- const agents = {}
114
- knownAgentIds.forEach((id, index) => {
115
- let slug
116
- try {
117
- slug = identityStore.slugify(customDisplayNames[index])
118
- } catch (err) {
119
- throw new Error(`identidade customizada invalida para "${id}": ${err.message}`)
120
- }
121
- agents[id] = { display_name: customDisplayNames[index], slug }
122
- })
123
- cfg = { agents }
124
- } else {
125
- cfg = identityStore.preset(identitySelect)
126
- }
127
- cfg.user_nickname = userNickname
128
- return cfg
129
- }
130
-
131
- // promptCustomNames pede um nome de exibição para cada agente conhecido,
132
- // rotulado com a especialidade do catálogo (Item.name + Item.description) —
133
- // nunca com o id técnico cru (ADR D4). Espelha
134
- // internal/commands/identity_wizard.go:buildCustomIdentityGroup.
135
- async function promptCustomNames(catalog, knownAgentIds, prompts) {
136
- const { input } = prompts
137
- const helperText = t('init.prompt.identityCustomName')
138
- const customDisplayNames = []
139
- for (const id of knownAgentIds) {
140
- const item = catalogAgentItem(catalog, id)
141
- const label = `${item.name} — ${item.description}`
142
- // eslint-disable-next-line no-await-in-loop
143
- const value = await input({
144
- message: `${label}\n ${helperText}`,
145
- validate: candidate => {
146
- let slug
147
- try {
148
- slug = identityStore.slugify(candidate)
149
- } catch (err) {
150
- return err.message
151
- }
152
- for (let index = 0; index < customDisplayNames.length; index += 1) {
153
- let otherSlug
154
- try {
155
- otherSlug = identityStore.slugify(customDisplayNames[index])
156
- } catch {
157
- continue
158
- }
159
- if (otherSlug === slug) return `slug "${slug}" duplicado com o agente "${knownAgentIds[index]}"`
160
- }
161
- return true
162
- },
163
- })
164
- customDisplayNames.push(value)
165
- }
166
- return customDisplayNames
167
- }
168
-
169
- // confirmIdentitySelection exibe o mapeamento especialidade → nome de
170
- // exibição (ADR D3) e pede confirmação. O mapeamento é impresso com
171
- // console.log simples (não por um campo de prompt) para que o alinhamento
172
- // de colunas siga exatamente o exemplo do ADR. Espelha
173
- // internal/commands/identity_wizard.go:confirmIdentitySelection.
174
- async function confirmIdentitySelection(catalog, knownAgentIds, cfg, prompts) {
175
- const { confirm } = prompts
176
- const nicknameLabel = t('identity.wizard.nicknameRowLabel')
177
- let labelWidth = nicknameLabel.length
178
- const labels = knownAgentIds.map(id => {
179
- const item = catalogAgentItem(catalog, id)
180
- if (item.description.length > labelWidth) labelWidth = item.description.length
181
- return item.description
182
- })
183
-
184
- console.log()
185
- console.log(t('identity.wizard.confirmHeader'))
186
- knownAgentIds.forEach((id, index) => {
187
- const agent = cfg.agents[id]
188
- console.log(` ${labels[index].padEnd(labelWidth)} → ${agent.display_name}`)
189
- })
190
- if (cfg.user_nickname) {
191
- console.log(` ${nicknameLabel.padEnd(labelWidth)} ${cfg.user_nickname}`)
192
- }
193
- console.log()
194
-
195
- return confirm({ message: t('identity.wizard.confirmQuestion') })
196
- }
197
-
198
- // runIdentityWizard é o único wizard interativo de identidade, consumido
199
- // tanto por `trackfw init` quanto por `trackfw agents install`
200
- // (ADR-2026-07-25-wizard-unificado-de-identidade-no-agents-install, D1).
201
- //
202
- // Mostra, em ordem: seleção de preset/custom, nomes livres em texto (só no
203
- // modo custom), o apelido do usuário e uma tela de confirmação (D3, D6). Se
204
- // o usuário recusar a confirmação, o wizard volta à seleção de preset sem
205
- // persistir nada — nunca retorna tendo gravado uma config parcial ou
206
- // recusada em disco.
207
- //
208
- // Retorna [cfg, persisted]. "neutral" (ou deixar o select vazio) é o
209
- // caminho "não gravar nada" e retorna [{}, false].
210
- async function runIdentityWizard(catalog, home, prompts = require('@inquirer/prompts')) {
211
- const { select, input } = prompts
212
- const knownAgentIds = identityStore.knownAgentIds()
213
-
214
- const titleIdentityPreset = t('init.prompt.identityPreset')
215
- const titleIdentityNickname = t('init.prompt.identityNickname')
216
-
217
- for (;;) {
218
- const identitySelect = await select({
219
- message: titleIdentityPreset,
220
- choices: identityPresetOptions(),
221
- })
222
-
223
- if (identitySelect === '' || identitySelect === 'neutral') {
224
- return [{}, false]
225
- }
226
-
227
- let customDisplayNames = []
228
- if (identitySelect === 'custom') {
229
- customDisplayNames = await promptCustomNames(catalog, knownAgentIds, prompts)
230
- }
231
-
232
- const userNickname = await input({ message: titleIdentityNickname, default: '' })
233
-
234
- const cfg = buildIdentityConfig(identitySelect, knownAgentIds, customDisplayNames, userNickname)
235
- identityStore.validate(cfg, knownAgentIds)
236
-
237
- // eslint-disable-next-line no-await-in-loop
238
- const confirmed = await confirmIdentitySelection(catalog, knownAgentIds, cfg, prompts)
239
- if (!confirmed) {
240
- // D3: recusar volta à seleção de preset sem persistir absolutamente nada.
241
- continue
242
- }
243
-
244
- identityStore.save(home, cfg)
245
- return [cfg, true]
246
- }
247
- }
248
-
249
- module.exports = {
250
- identityFileExists,
251
- resolveIdentityPreset,
252
- applyIdentityPresetFlag,
253
- shouldPromptIdentity,
254
- identityPresetOptions,
255
- buildIdentityConfig,
256
- confirmIdentitySelection,
257
- runIdentityWizard,
258
- }
@@ -1,68 +0,0 @@
1
- 'use strict'
2
-
3
- const { Command } = require('commander')
4
- const { version } = require('../../package.json')
5
- const { formatUnknownCommandError } = require('../lib/unknown-command')
6
-
7
- function createProgram() {
8
- const program = new Command()
9
- program
10
- .name('trackfw')
11
- .description('trackfw — governed software delivery framework\nADR → REQ → ROADMAP → kanban')
12
- .version(`trackfw ${version}`)
13
- // enablePositionalOptions: garante que uma flag de subcomando (ex.: o
14
- // "changelog --version <x.y.z>") não seja capturada pela flag global
15
- // "--version" do root — sem isto, commander casa "--version" contra o
16
- // primeiro registro conhecido em toda a árvore de comandos.
17
- program.enablePositionalOptions()
18
-
19
- // Comando desconhecido — mensagem canônica compartilhada pelos 3 CLIs
20
- // (ADR-2026-08-15-remocao-do-subsistema-de-plugins-em-vez-de-gate-de-binario-
21
- // de-terceiro.md D3). Registrar um listener 'command:*' intercepta ANTES do
22
- // commander chamar seu próprio Command.unknownCommand() — evita reformatar
23
- // uma mensagem já impressa por ele (o padrão que causava dupla-impressão no
24
- // Go, ver internal/commands/root.go) e evita depender do texto/threshold
25
- // próprio de commander.suggestSimilar, que diverge de cobra/argparse.
26
- program.on('command:*', (operands) => {
27
- const typed = operands[0]
28
- const candidates = program.commands.map((cmd) => cmd.name())
29
- process.stderr.write(formatUnknownCommandError(typed, candidates, program.name()) + '\n')
30
- process.exit(1)
31
- })
32
-
33
- program.addCommand(require('./init'))
34
- program.addCommand(require('./adr'))
35
- program.addCommand(require('./req'))
36
- program.addCommand(require('./roadmap'))
37
- program.addCommand(require('./validate'))
38
- program.addCommand(require('./status'))
39
- program.addCommand(require('./changelog'))
40
- program.addCommand(require('./log'))
41
- program.addCommand(require('./discover'))
42
- program.addCommand(require('./update'))
43
- program.addCommand(require('./metrics'))
44
- program.addCommand(require('./sync'))
45
- program.addCommand(require('./context'))
46
- program.addCommand(require('./baseline'))
47
- program.addCommand(require('./help'))
48
- program.addCommand(require('./configure'))
49
- program.addCommand(require('./version'))
50
- program.addCommand(require('./agents'))
51
- program.addCommand(require('./skills'))
52
- program.addCommand(require('./note'))
53
- program.addCommand(require('./ship'))
54
- program.addCommand(require('./release'))
55
- program.addCommand(require('./barrier'))
56
- program.addCommand(require('./branch'))
57
- program.addCommand(require('./commit'))
58
- program.addCommand(require('./push'))
59
- program.addCommand(require('./doctor'))
60
- program.addCommand(require('./audit-surface'))
61
-
62
- const { createServeCommand } = require('./serve')
63
- program.addCommand(createServeCommand())
64
-
65
- return program
66
- }
67
-
68
- module.exports = { createProgram }