trackfw 2.15.1 → 3.0.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/README.md CHANGED
@@ -363,10 +363,192 @@ are never replaced or removed unless `--force` is explicit, and unmanaged files
363
363
  are never removed. Known historical templates are adopted without overwriting;
364
364
  unknown unmanaged content cannot be adopted by `update`, even with `--force`.
365
365
 
366
+ Without `--scope`, `install`/`update` default to `global` (`~/.claude/...`)
367
+ when stdin is not a TTY, and otherwise prompt interactively with `global`
368
+ pre-selected — the resolved destination paths are printed before anything is
369
+ written. `uninstall` is the one exception: without `--scope` and without a
370
+ TTY it fails instead of guessing, since silently defaulting a destructive
371
+ operation could delete artifacts from the user's home directory. `list` never
372
+ prompts and always assumes `global` unless `--scope` is given, so it reports
373
+ the same destinations `install` actually wrote to.
374
+
366
375
  The 10 roles installed for each tool: **architect · backend · frontend · qa · infra · security · code-quality · dba · ux · data**
367
376
 
368
377
  ---
369
378
 
379
+ ## Agent identity — give your agents a name
380
+
381
+ By default the agents are functional and impersonal: `trackfw-architect`,
382
+ `trackfw-backend`, … Agent identity lets you name all ten, pick how they
383
+ address you, and call them by name.
384
+
385
+ ```bash
386
+ # Non-interactive: pick a themed preset
387
+ trackfw init --identity-preset greek
388
+
389
+ # Or answer the wizard, which also offers "name them one by one"
390
+ trackfw init
391
+ ```
392
+
393
+ `--identity-preset` accepts ten themed presets plus two opt-outs:
394
+
395
+ `greek` · `norse` · `potter` · `thrones` · `chaves` · `pioneers` · `starwars` · `tolkien` · `turma` · `egyptian` · `neutral` · `none`
396
+
397
+ `neutral` and `none` write nothing and keep the current behavior.
398
+
399
+ Sample mapping (three of the ten presets):
400
+
401
+ | Agent | `greek` | `pioneers` | `tolkien` |
402
+ |---|---|---|---|
403
+ | architect | Zeus | Turing | Gandalf |
404
+ | backend | Apolo | Ritchie | Aragorn |
405
+ | frontend | Afrodite | Berners-Lee | Arwen |
406
+ | qa | Ártemis | Hamilton | Legolas |
407
+ | infra | Ares | Torvalds | Gimli |
408
+ | security | Hades | Diffie | Boromir |
409
+ | dba | Poseidon | Codd | Elrond |
410
+ | ux | Atena | Norman | Galadriel |
411
+ | code-quality | Hefesto | Knuth | Faramir |
412
+ | data | Métis | Hopper | Bilbo |
413
+
414
+ The remaining presets are `norse` (Odin, Thor, Freya…), `potter` (Dumbledore,
415
+ Snape, Luna…), `thrones` (Tyrion, Jon, Arya…), `chaves` (Girafales, Madruga,
416
+ Chiquinha…), `starwars` (Yoda, Han, Leia…), `turma` (Franjinha, Cebolinha,
417
+ Magali…), and `egyptian` (Thoth, Rá, Ísis…).
418
+
419
+ ### Custom mode and your nickname
420
+
421
+ The interactive wizard also offers **name them one by one**: you type all ten
422
+ display names yourself. Each entry is validated as you go — an invalid name is
423
+ rejected with an inline error, never silently corrected, and two names that
424
+ resolve to the same identifier are rejected too.
425
+
426
+ The wizard then asks for an optional **nickname for you**, which is how the
427
+ agents will address you.
428
+
429
+ ### `agents install` also runs the wizard
430
+
431
+ `trackfw init` is not the only entry point: `trackfw agents install`, the
432
+ natural path in a project that is already governed, offers the same wizard.
433
+ The rule is identical across the three CLIs — it appears **only** when
434
+ **all** of the following hold:
435
+
436
+ - the command is `agents` (never `skills`: skills have no identity);
437
+ - stdin is a TTY (a non-interactive run never blocks on a prompt);
438
+ - and either no `~/.trackfw/identity.json` exists yet, or `--identity` was
439
+ passed to force reconfiguration.
440
+
441
+ With an identity already configured and no `--identity`, the command asks
442
+ nothing — it prints `identity: N custom agent(s)` and installs directly.
443
+
444
+ ```bash
445
+ # First run on this machine: offers the wizard, then installs
446
+ trackfw agents install --targets claude
447
+
448
+ # Identity already configured: no prompt, installs directly
449
+ trackfw agents install --targets claude
450
+
451
+ # Force reconfiguration
452
+ trackfw agents install --targets claude --identity
453
+
454
+ # Non-interactive, same semantics as init --identity-preset
455
+ trackfw agents install --targets claude --identity-preset chaves
456
+ ```
457
+
458
+ Two new flags exist only on `agents install` (`skills install` never
459
+ registers them): `--identity` (bool, forces reconfiguration even if a file
460
+ already exists) and `--identity-preset <preset>` (same ten themed presets
461
+ plus `neutral` and `none`; an invalid value errors out listing the valid
462
+ ones).
463
+
464
+ In **name them one by one** mode, each field is now labeled by the agent's
465
+ specialty, taken from the catalog, never by its technical id:
466
+
467
+ ```
468
+ Architect — Architecture, ADRs and governed coordination
469
+ > _
470
+ ```
471
+
472
+ Before anything is written to disk — for a themed preset **or** for custom
473
+ names — a confirmation screen lists all ten `specialty → name` pairs plus
474
+ your nickname:
475
+
476
+ ```
477
+ ── Confirmation ──────────────────────────────
478
+ Architecture, ADRs and governed coordination → Girafales
479
+ Backend APIs, domain logic and integrations → Madruga
480
+ ...
481
+ What we'll call you: chefe
482
+
483
+ ? Confirm?
484
+ ```
485
+
486
+ Answering no returns to preset selection; nothing is written until you
487
+ confirm.
488
+
489
+ Everything is stored in a single global file, shared by the Go, npm, and PyPI
490
+ distributions:
491
+
492
+ ```json
493
+ // ~/.trackfw/identity.json
494
+ {
495
+ "schema_version": 1,
496
+ "user_nickname": "Kleber",
497
+ "agents": {
498
+ "architect": { "display_name": "Zeus", "slug": "zeus" }
499
+ }
500
+ }
501
+ ```
502
+
503
+ The generated artifact — still installed at the unchanged path
504
+ `~/.claude/agents/trackfw-architect.md`:
505
+
506
+ ```markdown
507
+ ---
508
+ name: zeus-tf
509
+ description: Zeus — Principal software architect for system design, ADRs and governed multi-agent coordination.
510
+ model: opus
511
+ ---
512
+
513
+ Você é Zeus. Trate o usuário como Kleber.
514
+
515
+ # Architect
516
+ ...
517
+ ```
518
+
519
+ ### Why the `-tf` suffix
520
+
521
+ The `name` always ends in `-tf`. Two agents sharing the same `name` in the same
522
+ directory make Claude Code load *"only one of them, chosen by filesystem read
523
+ order rather than a documented precedence"* — a silent, non-deterministic
524
+ shadowing that the user cannot detect. If you already keep a personal `zeus.md`
525
+ agent, `zeus-tf` guarantees both survive.
526
+
527
+ The suffix belongs to the technical identifier only. It never appears in how
528
+ the agent presents itself: the `description` and the body both say **Zeus**.
529
+
530
+ ### How to invoke it
531
+
532
+ | You type | What happens |
533
+ |---|---|
534
+ | `@agent-zeus-tf` | Works — explicit mention resolves against `name` |
535
+ | "chame o Zeus" / "ask Zeus to…" | Works — natural-language routing reads `description` |
536
+ | "quem é você?" | Answers "Sou Zeus" — the body is loaded after selection |
537
+
538
+ ### Cost and non-regression
539
+
540
+ The agent **never reads the configuration at runtime**. Identity is
541
+ materialized into the artifact at install time, so the per-interaction cost is
542
+ essentially zero: the `description` is substituted rather than extended, and
543
+ the body grows by tens of tokens that are loaded only after the agent has
544
+ already been selected. No tool call, no file read, no permanent instruction.
545
+
546
+ Without `~/.trackfw/identity.json`, the generated artifacts are **byte for byte
547
+ identical** to the current ones in all three CLIs. The feature is opt-in and
548
+ regresses nothing.
549
+
550
+ ---
551
+
370
552
  ## `trackfw init` — stack-aware scaffolding
371
553
 
372
554
  ```
@@ -377,6 +559,7 @@ The 10 roles installed for each tool: **architect · backend · frontend · qa
377
559
  ? Git hooks? husky / lefthook / none
378
560
  ? CI system? GitHub Actions / GitLab CI / none
379
561
  ? Which AI assistants? Claude / Codex / Gemini / Antigravity / Cursor / Copilot / Windsurf / Amazon Q / Kiro
562
+ ? Agent identity? Greek / Norse / Potter / Thrones / Chaves / Pioneers / Star Wars / Tolkien / Turma / Egyptian / Name them one by one / Neutral
380
563
  ```
381
564
 
382
565
  The governance structure (`docs/adr/`, `docs/req/`, `docs/roadmaps/`) is always identical — stack-agnostic. The generated hooks, workflows, and AI integrations adapt to your answers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trackfw",
3
- "version": "2.15.1",
3
+ "version": "3.0.0",
4
4
  "description": "Governance CLI for AI-native software delivery and AI coding agents. Enforces traceability: ADR → REQ → ROADMAP → kanban. Native support for Codex, Claude Code, Gemini CLI, Cursor, Copilot, Windsurf, and Amazon Q.",
5
5
  "keywords": [
6
6
  "cli",
@@ -0,0 +1,258 @@
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,14 +1,40 @@
1
1
  'use strict'
2
2
  const { Command } = require('commander')
3
3
  const { t } = require('../i18n')
4
+ const identityStore = require('../identity')
5
+ const identityWizard = require('./identity-wizard')
6
+ const { resolveIdentityPreset, identityFileExists } = identityWizard
7
+ const { resolveScope } = require('./integrations')
4
8
 
5
9
  const cmd = new Command('init')
6
10
  cmd.description(t('init.description'))
7
11
  cmd.option('--ai-tools <tools>', 'Comma-separated AI tools to configure (claude,codex,gemini,antigravity,cursor,copilot,windsurf,amazonq,kiro)', '')
8
- cmd.action(async (options) => {
12
+ cmd.option('--identity-preset <preset>', `Agent identity preset: none, neutral, ${identityStore.presetNames().join(', ')}`, 'none')
13
+ cmd.action(async (options, command) => {
14
+ const os = require('os')
9
15
  const path = require('path')
10
16
  const generators = require('../generators/init')
11
17
 
18
+ const home = os.homedir()
19
+
20
+ // A validação e persistência da flag acontecem incondicionalmente, acima
21
+ // do early-return não-TTY abaixo — é isso que faz um --identity-preset
22
+ // inválido falhar alto em CI em vez de silenciosamente não fazer nada.
23
+ const presetChanged = command.getOptionValueSource('identityPreset') === 'cli'
24
+ if (presetChanged) {
25
+ const { cfg, shouldSave } = resolveIdentityPreset(options.identityPreset)
26
+ if (shouldSave) {
27
+ identityStore.validate(cfg, identityStore.knownAgentIds())
28
+ identityStore.save(home, cfg)
29
+ }
30
+ }
31
+
32
+ // Pula o wizard de identidade inteiramente quando a flag foi passada
33
+ // explicitamente (já tratado acima) ou quando um arquivo de identidade já
34
+ // existe — re-executar init nunca deve sobrescrever silenciosamente uma
35
+ // identidade já configurada.
36
+ const skipIdentityWizard = presetChanged || identityFileExists(home)
37
+
12
38
  // Modo não-TTY: usar defaults e chamar scaffold diretamente
13
39
  if (!process.stdin.isTTY) {
14
40
  const cfg = {
@@ -23,9 +49,11 @@ cmd.action(async (options) => {
23
49
  await generators.scaffold(cfg)
24
50
  const aiTools = String(options.aiTools || '').split(',').map(tool => tool.trim()).filter(Boolean)
25
51
  const supported = new Set(['claude', 'codex', 'gemini', 'antigravity', 'cursor', 'copilot', 'windsurf', 'amazonq', 'kiro'])
52
+ // Sem TTY, o escopo nunca é perguntado: default `global` (ADR D1/D4).
53
+ const scope = await resolveScope({}, { interactive: false })
26
54
  for (const tool of aiTools) {
27
55
  if (!supported.has(tool)) throw new Error(`Unsupported AI tool: ${tool}`)
28
- await generators.installIntegrationTarget(tool, process.cwd())
56
+ await generators.installIntegrationTarget(tool, process.cwd(), scope)
29
57
  }
30
58
  console.log(`\n${t('init.success')}`)
31
59
  require('../generators/init').printArchitectNextSteps(process.cwd())
@@ -34,7 +62,7 @@ cmd.action(async (options) => {
34
62
 
35
63
  const { input, select, checkbox } = require('@inquirer/prompts')
36
64
 
37
- let projectName, projectType, frontend, pkgManager, backend, backendFramework, hooks, ci, aiTools, requireReqInCommit
65
+ let projectName, projectType, frontend, pkgManager, backend, backendFramework, hooks, ci, aiTools, requireReqInCommit, scope
38
66
 
39
67
  try {
40
68
  projectName = await input({
@@ -158,6 +186,12 @@ cmd.action(async (options) => {
158
186
  { name: 'Kiro', value: 'kiro' },
159
187
  ],
160
188
  })
189
+
190
+ // Escopo de instalação (ADR D4): só pergunta quando há de fato algo para
191
+ // instalar. `resolveScope` é a mesma função (mesmas strings/mecanismo de
192
+ // prompt) usada por `agents install` / `skills install`.
193
+ scope = aiTools.length ? await resolveScope({}, { interactive: true }) : 'global'
194
+
161
195
  } catch (err) {
162
196
  // Fallback quando stdin fecha inesperadamente (ex: pipe em TTY simulado)
163
197
  const cfg = {
@@ -175,10 +209,37 @@ cmd.action(async (options) => {
175
209
  return
176
210
  }
177
211
 
212
+ // O wizard de identidade roda como seu próprio formulário, logo após o
213
+ // formulário principal — o mesmo componente compartilhado (ADR D1) usado
214
+ // por `agents install`. É pulado inteiramente (nenhuma pergunta) quando a
215
+ // flag já resolveu a identidade acima, ou quando um arquivo de identidade
216
+ // já existe.
217
+ if (!skipIdentityWizard) {
218
+ const { catalog } = require('../integrations')
219
+ try {
220
+ await identityWizard.runIdentityWizard(catalog, home)
221
+ } catch {
222
+ // Fallback quando stdin fecha inesperadamente durante o wizard de
223
+ // identidade (mesma tolerância aplicada ao formulário principal acima).
224
+ await generators.scaffold({
225
+ projectName: path.basename(process.cwd()),
226
+ projectType: 'governance',
227
+ frontend: '',
228
+ backend: '',
229
+ pkgManager: 'npm',
230
+ hooks: 'none',
231
+ ci: 'none',
232
+ })
233
+ console.log(`\n${t('init.success')}`)
234
+ require('../generators/init').printArchitectNextSteps(process.cwd())
235
+ return
236
+ }
237
+ }
238
+
178
239
  const cfg = { projectName, projectType, frontend, backend, backendFramework, pkgManager, hooks, ci, requireReqInCommit }
179
240
  await generators.scaffold(cfg)
180
241
 
181
- for (const tool of (aiTools || [])) await generators.installIntegrationTarget(tool, process.cwd())
242
+ for (const tool of (aiTools || [])) await generators.installIntegrationTarget(tool, process.cwd(), scope)
182
243
 
183
244
  console.log(`\n${t('init.success')}`)
184
245
  require('../generators/init').printArchitectNextSteps(process.cwd())