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.
- package/README.md +28 -11
- package/bin/trackfw.js +81 -0
- package/package.json +14 -8
- package/bin/trackfw +0 -41
- package/src/branch/prune.js +0 -450
- package/src/branch/runner.js +0 -153
- package/src/changelog/index.js +0 -114
- package/src/commands/adr.js +0 -56
- package/src/commands/agents.js +0 -3
- package/src/commands/audit-surface.js +0 -520
- package/src/commands/barrier.js +0 -822
- package/src/commands/baseline.js +0 -13
- package/src/commands/branch.js +0 -98
- package/src/commands/changelog.js +0 -29
- package/src/commands/commit.js +0 -66
- package/src/commands/configure.js +0 -106
- package/src/commands/context.js +0 -215
- package/src/commands/discover.js +0 -628
- package/src/commands/doctor.js +0 -66
- package/src/commands/help.js +0 -358
- package/src/commands/identity-wizard.js +0 -258
- package/src/commands/index.js +0 -68
- package/src/commands/init.js +0 -296
- package/src/commands/integrations.js +0 -305
- package/src/commands/log.js +0 -31
- package/src/commands/metrics.js +0 -242
- package/src/commands/note.js +0 -24
- package/src/commands/push.js +0 -51
- package/src/commands/release.js +0 -47
- package/src/commands/req.js +0 -98
- package/src/commands/roadmap.js +0 -44
- package/src/commands/serve.js +0 -345
- package/src/commands/ship.js +0 -58
- package/src/commands/skills.js +0 -3
- package/src/commands/status.js +0 -12
- package/src/commands/sync.js +0 -348
- package/src/commands/thirdparty.js +0 -390
- package/src/commands/update-harness.js +0 -1002
- package/src/commands/update.js +0 -500
- package/src/commands/validate.js +0 -57
- package/src/commands/version.js +0 -10
- package/src/commit/runner.js +0 -352
- package/src/config/index.js +0 -457
- package/src/forge/adapter.js +0 -189
- package/src/forge/resolve.js +0 -191
- package/src/generators/adr.js +0 -198
- package/src/generators/codex.js +0 -79
- package/src/generators/date.js +0 -23
- package/src/generators/hooks.js +0 -2414
- package/src/generators/init.js +0 -1563
- package/src/generators/note.js +0 -154
- package/src/generators/req.js +0 -436
- package/src/generators/roadmap.js +0 -841
- package/src/homedir.js +0 -30
- package/src/i18n/index.js +0 -53
- package/src/i18n/locales/en-US.json +0 -124
- package/src/i18n/locales/es-ES.json +0 -124
- package/src/i18n/locales/pt-BR.json +0 -124
- package/src/identity/config.js +0 -134
- package/src/identity/index.js +0 -19
- package/src/identity/preset.js +0 -219
- package/src/identity/slug.js +0 -62
- package/src/integrations/assets/agents/architect.md +0 -61
- package/src/integrations/assets/agents/backend.md +0 -41
- package/src/integrations/assets/agents/code-quality.md +0 -48
- package/src/integrations/assets/agents/data.md +0 -41
- package/src/integrations/assets/agents/dba.md +0 -41
- package/src/integrations/assets/agents/frontend.md +0 -41
- package/src/integrations/assets/agents/iac.md +0 -46
- package/src/integrations/assets/agents/infra.md +0 -43
- package/src/integrations/assets/agents/qa.md +0 -41
- package/src/integrations/assets/agents/security.md +0 -51
- package/src/integrations/assets/agents/tooling.md +0 -47
- package/src/integrations/assets/agents/ux.md +0 -48
- package/src/integrations/assets/catalog.json +0 -48
- package/src/integrations/assets/skills/architecture.md +0 -66
- package/src/integrations/assets/skills/backend.md +0 -67
- package/src/integrations/assets/skills/code-quality.md +0 -78
- package/src/integrations/assets/skills/data.md +0 -75
- package/src/integrations/assets/skills/dba.md +0 -77
- package/src/integrations/assets/skills/frontend.md +0 -70
- package/src/integrations/assets/skills/governance.md +0 -6
- package/src/integrations/assets/skills/iac.md +0 -90
- package/src/integrations/assets/skills/implement.md +0 -6
- package/src/integrations/assets/skills/infra.md +0 -62
- package/src/integrations/assets/skills/plan.md +0 -6
- package/src/integrations/assets/skills/qa.md +0 -63
- package/src/integrations/assets/skills/release.md +0 -6
- package/src/integrations/assets/skills/review.md +0 -6
- package/src/integrations/assets/skills/security.md +0 -74
- package/src/integrations/assets/skills/tooling.md +0 -80
- package/src/integrations/assets/skills/ux.md +0 -70
- package/src/integrations/catalog.js +0 -58
- package/src/integrations/doctor.js +0 -0
- package/src/integrations/doctor_remote.js +0 -224
- package/src/integrations/index.js +0 -171
- package/src/integrations/legacy.js +0 -35
- package/src/integrations/manager.js +0 -438
- package/src/integrations/register-agent.js +0 -106
- package/src/integrations/render.js +0 -563
- package/src/integrations/scaffold_doctor.js +0 -395
- package/src/lib/fatal-error.js +0 -64
- package/src/lib/pathfmt.js +0 -43
- package/src/lib/unknown-command.js +0 -82
- package/src/lib/update-engine.js +0 -242
- package/src/push/runner.js +0 -226
- package/src/release/runner.js +0 -474
- package/src/serve/api_attention.js +0 -24
- package/src/serve/api_board.js +0 -105
- package/src/serve/api_chain.js +0 -246
- package/src/serve/api_file.js +0 -117
- package/src/serve/api_metrics.js +0 -139
- package/src/serve/static/app.js +0 -1151
- package/src/serve/static/index.html +0 -338
- package/src/serve/static/style.css +0 -297
- package/src/ship/runner.js +0 -775
- package/src/thirdparty/fetch.js +0 -140
- package/src/thirdparty/markers.js +0 -207
- package/src/thirdparty/provenance.js +0 -114
- package/src/thirdparty/quarantine.js +0 -113
- package/src/thirdparty/references.js +0 -194
- package/src/validator/git-exec.js +0 -72
- package/src/validator/index.js +0 -4086
- package/src/validator/traceid.js +0 -155
package/src/commands/help.js
DELETED
|
@@ -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
|
-
}
|
package/src/commands/index.js
DELETED
|
@@ -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 }
|