trackfw 2.16.0 → 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 +9 -0
- package/package.json +1 -1
- package/src/commands/init.js +11 -3
- package/src/commands/integrations.js +75 -4
- package/src/generators/init.js +28 -5
package/README.md
CHANGED
|
@@ -363,6 +363,15 @@ 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
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trackfw",
|
|
3
|
-
"version": "
|
|
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",
|
package/src/commands/init.js
CHANGED
|
@@ -4,6 +4,7 @@ const { t } = require('../i18n')
|
|
|
4
4
|
const identityStore = require('../identity')
|
|
5
5
|
const identityWizard = require('./identity-wizard')
|
|
6
6
|
const { resolveIdentityPreset, identityFileExists } = identityWizard
|
|
7
|
+
const { resolveScope } = require('./integrations')
|
|
7
8
|
|
|
8
9
|
const cmd = new Command('init')
|
|
9
10
|
cmd.description(t('init.description'))
|
|
@@ -48,9 +49,11 @@ cmd.action(async (options, command) => {
|
|
|
48
49
|
await generators.scaffold(cfg)
|
|
49
50
|
const aiTools = String(options.aiTools || '').split(',').map(tool => tool.trim()).filter(Boolean)
|
|
50
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 })
|
|
51
54
|
for (const tool of aiTools) {
|
|
52
55
|
if (!supported.has(tool)) throw new Error(`Unsupported AI tool: ${tool}`)
|
|
53
|
-
await generators.installIntegrationTarget(tool, process.cwd())
|
|
56
|
+
await generators.installIntegrationTarget(tool, process.cwd(), scope)
|
|
54
57
|
}
|
|
55
58
|
console.log(`\n${t('init.success')}`)
|
|
56
59
|
require('../generators/init').printArchitectNextSteps(process.cwd())
|
|
@@ -59,7 +62,7 @@ cmd.action(async (options, command) => {
|
|
|
59
62
|
|
|
60
63
|
const { input, select, checkbox } = require('@inquirer/prompts')
|
|
61
64
|
|
|
62
|
-
let projectName, projectType, frontend, pkgManager, backend, backendFramework, hooks, ci, aiTools, requireReqInCommit
|
|
65
|
+
let projectName, projectType, frontend, pkgManager, backend, backendFramework, hooks, ci, aiTools, requireReqInCommit, scope
|
|
63
66
|
|
|
64
67
|
try {
|
|
65
68
|
projectName = await input({
|
|
@@ -184,6 +187,11 @@ cmd.action(async (options, command) => {
|
|
|
184
187
|
],
|
|
185
188
|
})
|
|
186
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
|
+
|
|
187
195
|
} catch (err) {
|
|
188
196
|
// Fallback quando stdin fecha inesperadamente (ex: pipe em TTY simulado)
|
|
189
197
|
const cfg = {
|
|
@@ -231,7 +239,7 @@ cmd.action(async (options, command) => {
|
|
|
231
239
|
const cfg = { projectName, projectType, frontend, backend, backendFramework, pkgManager, hooks, ci, requireReqInCommit }
|
|
232
240
|
await generators.scaffold(cfg)
|
|
233
241
|
|
|
234
|
-
for (const tool of (aiTools || [])) await generators.installIntegrationTarget(tool, process.cwd())
|
|
242
|
+
for (const tool of (aiTools || [])) await generators.installIntegrationTarget(tool, process.cwd(), scope)
|
|
235
243
|
|
|
236
244
|
console.log(`\n${t('init.success')}`)
|
|
237
245
|
require('../generators/init').printArchitectNextSteps(process.cwd())
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const os = require('node:os')
|
|
4
4
|
const { Command } = require('commander')
|
|
5
|
-
const { catalog, execute, parseSurfaces } = require('../integrations')
|
|
5
|
+
const { catalog, execute, parseSurfaces, buildPlans } = require('../integrations')
|
|
6
6
|
const identityStore = require('../identity')
|
|
7
7
|
const identityWizard = require('./identity-wizard')
|
|
8
8
|
const { t } = require('../i18n')
|
|
@@ -27,6 +27,66 @@ async function promptSelection(kind, options, prompts = require('@inquirer/promp
|
|
|
27
27
|
options.items = await checkbox({ message: `${kind} to manage`, choices: catalog[kind].map(item => ({ name: item.name, value: item.id })), required: true })
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
// resolveScope decide o escopo de instalação (ADR-2026-07-25-escopo-de-
|
|
31
|
+
// instalacao-selecionavel-para-agents-e-skills):
|
|
32
|
+
//
|
|
33
|
+
// - `--scope` explícito (`options.scope !== undefined`) é sempre respeitado
|
|
34
|
+
// e nunca dispara prompt — apenas validado contra os valores aceitos. A
|
|
35
|
+
// detecção é por *flag-set* (undefined), nunca por comparação de valor,
|
|
36
|
+
// para não confundir um `--scope project` explícito com o default.
|
|
37
|
+
// - Sem flag e `interactive` desligado (comando `list`, D6): default
|
|
38
|
+
// `global`, nunca lança erro (list nunca é destrutivo).
|
|
39
|
+
// - Sem flag e stdin não é um TTY (D1): default `global` — exceto para
|
|
40
|
+
// `uninstall` (ADR D8), que falha exigindo `--scope` explícito. D1 foi
|
|
41
|
+
// aprovado no enquadramento "onde instalar"; aplicá-lo a uninstall
|
|
42
|
+
// permitiria que um script de CI que hoje limpa `.claude/agents/` do
|
|
43
|
+
// repositório passasse a apagar arquivos do home do usuário.
|
|
44
|
+
// - Sem flag, `interactive` ligado e TTY (D2): pergunta o escopo, com
|
|
45
|
+
// `global` pré-selecionado, para toda operação incluindo uninstall — o
|
|
46
|
+
// usuário vê a escolha antes de destruir. Usa o mesmo mecanismo de prompt
|
|
47
|
+
// (`@inquirer/prompts`) já empregado por `identity-wizard.js`, sem
|
|
48
|
+
// dependência nova.
|
|
49
|
+
//
|
|
50
|
+
// `interactive` é passado pelo chamador como `false` para `list` — comando de
|
|
51
|
+
// leitura que nunca deve bloquear em prompt, mas que ainda assim precisa do
|
|
52
|
+
// mesmo default `global` para não reportar deployments divergentes dos que o
|
|
53
|
+
// `install` gravou (D6). `operation` identifica install/update/uninstall/list
|
|
54
|
+
// para o branch de D8. Espelha
|
|
55
|
+
// internal/commands/integrations_flags.go:resolveScope.
|
|
56
|
+
async function resolveScope(options, { interactive = true, operation, prompts = require('@inquirer/prompts') } = {}) {
|
|
57
|
+
if (options.scope !== undefined) {
|
|
58
|
+
if (options.scope !== 'project' && options.scope !== 'global') throw new Error(`Unsupported scope: ${options.scope}`)
|
|
59
|
+
return options.scope
|
|
60
|
+
}
|
|
61
|
+
if (!interactive) return 'global'
|
|
62
|
+
if (!process.stdin.isTTY) {
|
|
63
|
+
if (operation === 'uninstall') throw new Error('uninstall requires --scope in non-interactive mode')
|
|
64
|
+
return 'global'
|
|
65
|
+
}
|
|
66
|
+
const { select } = prompts
|
|
67
|
+
return select({
|
|
68
|
+
message: 'Onde instalar os artefatos?',
|
|
69
|
+
default: 'global',
|
|
70
|
+
choices: [
|
|
71
|
+
{ name: 'Pasta do usuário (~/.claude) — vale para todos os projetos', value: 'global' },
|
|
72
|
+
{ name: 'Este projeto (.claude) — apenas neste repositório', value: 'project' },
|
|
73
|
+
],
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// printResolvedDestinations imprime, antes da gravação, o escopo escolhido e
|
|
78
|
+
// os caminhos de destino que serão afetados (ADR D5) — nunca em modo --json,
|
|
79
|
+
// que já é o canal determinístico consumido por scripts. Recalcula os planos
|
|
80
|
+
// via buildPlans (sem side effect) só para enumerar destinos; a gravação em
|
|
81
|
+
// si acontece depois, em execute().
|
|
82
|
+
function printResolvedDestinations(kind, options) {
|
|
83
|
+
if (options.json) return
|
|
84
|
+
const plans = buildPlans(kind, options)
|
|
85
|
+
const destinations = [...new Set(plans.map(plan => plan.destination))].sort()
|
|
86
|
+
console.log(`Destino (${options.scope}):`)
|
|
87
|
+
for (const destination of destinations) console.log(` ${destination}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
30
90
|
async function promptAmbiguousSurfaces(kind, options, prompts = require('@inquirer/prompts')) {
|
|
31
91
|
const { select } = prompts
|
|
32
92
|
const selected = parseSurfaces(options.surfaces)
|
|
@@ -47,7 +107,7 @@ function createLifecycleCommand(kind) {
|
|
|
47
107
|
const command = new Command(operation)
|
|
48
108
|
.option('--targets <targets>', 'Comma-separated target CLIs', csv)
|
|
49
109
|
.option('--items <items>', `Comma-separated ${kind} IDs`, csv)
|
|
50
|
-
.option('--scope <scope>', 'Installation scope: project or global
|
|
110
|
+
.option('--scope <scope>', 'Installation scope: project or global (default: global; asks interactively)')
|
|
51
111
|
.option('--surface <target=surface>', 'Surface selection (repeatable)', collect, [])
|
|
52
112
|
.option('--json', 'Print deterministic JSON')
|
|
53
113
|
.option('--force', 'Replace or remove modified artifacts')
|
|
@@ -65,7 +125,14 @@ function createLifecycleCommand(kind) {
|
|
|
65
125
|
|
|
66
126
|
command.action(async (options, cmd) => {
|
|
67
127
|
options.surfaces = options.surface || []
|
|
68
|
-
|
|
128
|
+
|
|
129
|
+
// Gate de escopo (ADR D1-D3, D6): resolvido incondicionalmente aqui,
|
|
130
|
+
// antes de qualquer seleção de targets/surfaces ou construção de
|
|
131
|
+
// planos, e independentemente de --targets já ter sido informado —
|
|
132
|
+
// caso contrário o caso mais comum (`agents install --targets claude`)
|
|
133
|
+
// nunca passaria por prompt algum. `list` (mutation === false) nunca
|
|
134
|
+
// pergunta (comando de leitura), apenas adota o default `global`.
|
|
135
|
+
options.scope = await resolveScope(options, { interactive: mutation, operation })
|
|
69
136
|
|
|
70
137
|
// O booleano de --identity nunca deve chegar a execute()/buildPlans()
|
|
71
138
|
// sob a chave "identity" — essa chave ali é reservada para uma Config
|
|
@@ -112,6 +179,10 @@ function createLifecycleCommand(kind) {
|
|
|
112
179
|
}
|
|
113
180
|
}
|
|
114
181
|
|
|
182
|
+
// D5: caminhos de destino impressos antes da gravação, apenas para
|
|
183
|
+
// operações de mutação (install/update/uninstall) e fora de --json.
|
|
184
|
+
if (mutation) printResolvedDestinations(kind, options)
|
|
185
|
+
|
|
115
186
|
const output = execute(kind, operation, options)
|
|
116
187
|
console.log(options.json ? JSON.stringify(output) : human(output))
|
|
117
188
|
})
|
|
@@ -120,4 +191,4 @@ function createLifecycleCommand(kind) {
|
|
|
120
191
|
return root
|
|
121
192
|
}
|
|
122
193
|
|
|
123
|
-
module.exports = { createLifecycleCommand, csv, human, promptSelection, promptAmbiguousSurfaces }
|
|
194
|
+
module.exports = { createLifecycleCommand, csv, human, promptSelection, promptAmbiguousSurfaces, resolveScope }
|
package/src/generators/init.js
CHANGED
|
@@ -1006,12 +1006,35 @@ async function installAmazonQ(cwd = process.cwd()) {
|
|
|
1006
1006
|
}
|
|
1007
1007
|
}
|
|
1008
1008
|
|
|
1009
|
-
/**
|
|
1010
|
-
|
|
1011
|
-
|
|
1009
|
+
/**
|
|
1010
|
+
* Install the canonical agents and skills for one AI target.
|
|
1011
|
+
*
|
|
1012
|
+
* `scope` defaults to "project" for backward compatibility with the
|
|
1013
|
+
* compatibility entrypoints below (installAgents, installGemini, etc.), that
|
|
1014
|
+
* historical tests exercise directly without going through the `init`
|
|
1015
|
+
* wizard's scope prompt. `trackfw init` itself always passes the scope it
|
|
1016
|
+
* resolved (ADR-2026-07-25-escopo-de-instalacao-selecionavel-para-agents-e-
|
|
1017
|
+
* skills, D4).
|
|
1018
|
+
*/
|
|
1019
|
+
async function installIntegrationTarget(target, cwd = process.cwd(), scope = 'project') {
|
|
1020
|
+
const { execute, buildPlans } = require('../integrations')
|
|
1012
1021
|
const roots = { projectRoot: cwd }
|
|
1013
|
-
|
|
1014
|
-
|
|
1022
|
+
const options = { targets: [target], scope }
|
|
1023
|
+
// D5 — transparency: print resolved destinations before writing anything.
|
|
1024
|
+
// buildPlans has no side effects, so it is safe to call here purely to
|
|
1025
|
+
// enumerate destinations; the actual write happens below via execute().
|
|
1026
|
+
// Mirrors printResolvedDestinations in commands/integrations.js and Go's
|
|
1027
|
+
// installAITools (internal/commands/init.go) — this call site (init's AI
|
|
1028
|
+
// tools install) was the one place that stayed silent (divergence #2,
|
|
1029
|
+
// ROADMAP-2026-07-25-escopo-de-instalacao-selecionavel-para-agents-e-skills).
|
|
1030
|
+
const destinations = [
|
|
1031
|
+
...buildPlans('agents', options).map(plan => plan.destination),
|
|
1032
|
+
...buildPlans('skills', options).map(plan => plan.destination),
|
|
1033
|
+
]
|
|
1034
|
+
console.log(`Destino (${scope}):`)
|
|
1035
|
+
for (const destination of [...new Set(destinations)].sort()) console.log(` ${destination}`)
|
|
1036
|
+
execute('agents', 'install', options, roots)
|
|
1037
|
+
execute('skills', 'install', options, roots)
|
|
1015
1038
|
console.log(` ✓ ${target} agents and skills`)
|
|
1016
1039
|
}
|
|
1017
1040
|
|