theforge-cli 0.0.0-stage → 0.2.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/CHANGELOG.md +662 -0
- package/LICENSE +21 -0
- package/README.md +271 -2
- package/dist/index.js +24307 -0
- package/docs/arquitetura.md +149 -0
- package/docs/capabilities.md +155 -0
- package/docs/registry-state-contract.md +81 -0
- package/docs/solucao-de-problemas.md +85 -0
- package/package.json +71 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Todas as mudanças notáveis deste projeto são documentadas aqui, no
|
|
4
|
+
formato [Keep a Changelog](https://keepachangelog.com/pt-BR/1.1.0/).
|
|
5
|
+
|
|
6
|
+
## [0.2.0] - 2026-10-17
|
|
7
|
+
|
|
8
|
+
### Cycle 11.1 — hardening de produção, MCP real e Trusted Publishing
|
|
9
|
+
|
|
10
|
+
#### P0-001 — npm Trusted Publishing (OIDC)
|
|
11
|
+
|
|
12
|
+
- `publish-npm.yml` reescrito: `workflow_dispatch(version, tag?)` →
|
|
13
|
+
job `validate` (gates completos + `npm pack --dry-run` + release
|
|
14
|
+
gate) → job `publish` (`environment: npm`, `id-token: write`,
|
|
15
|
+
checkout pinado no SHA validado — sem TOCTOU), provenance e
|
|
16
|
+
smoke pós-publish (registry check + install limpo + `--help`).
|
|
17
|
+
- `scripts/release/verify-version.mjs`: recusas nomeadas —
|
|
18
|
+
`version-invalid`, `version-mismatch`, `version-exists`,
|
|
19
|
+
`tag-missing`, `tag-mismatch`, `repo-url`, `license-missing`.
|
|
20
|
+
- `docs/releasing/npm-trusted-publishing.md`: setup do publisher,
|
|
21
|
+
environment, gates, troubleshooting e recovery.
|
|
22
|
+
- Setup externo (publisher no npmjs.com + environment `npm` com
|
|
23
|
+
aprovação) é ação do mantenedor — `BLOCKED_EXTERNAL` até então.
|
|
24
|
+
|
|
25
|
+
#### P0-002 — pureza de `--plan`/`--check`/`--dry-run`
|
|
26
|
+
|
|
27
|
+
- `resolveRelease` ganha `ephemeral`: em plano, o fetch PyPI pode
|
|
28
|
+
acontecer mas **nada persiste** — nem o cache TTL de metadados
|
|
29
|
+
(`resolveRelease` não chama mais `writeCache` sob ephemeral).
|
|
30
|
+
- `tests/plan-purity.test.ts`: snapshot sha256 da árvore do home
|
|
31
|
+
antes/depois — `install --plan`, `update --check` e uninstall
|
|
32
|
+
não-TTY sem `--yes` (needsYes) produzem zero mutação observável.
|
|
33
|
+
|
|
34
|
+
#### P1-002 — verificação MCP de protocolo real
|
|
35
|
+
|
|
36
|
+
- `src/mcp/handshake.ts`: probe stdio JSON-RPC — spawn (argv array,
|
|
37
|
+
sem shell) → `initialize` → `notifications/initialized` →
|
|
38
|
+
`tools/list` → kill. Níveis `MCP_INITIALIZED`/`TOOLS_DISCOVERED`;
|
|
39
|
+
timeouts por etapa e global, caps de tamanho/contagem de linhas,
|
|
40
|
+
`tools/call` nunca executado.
|
|
41
|
+
- `forge-cli mcp --verify` agora roda o probe real (não mais
|
|
42
|
+
`--version`): JSON reporta `probe.{ok,levels,tools,server,protocol,
|
|
43
|
+
error}` com erros nomeados (`stdout-non-json`, `server-exit`,
|
|
44
|
+
`initialize-invalid`, `tools-list-invalid`, `timeout`, `spawn-failed`).
|
|
45
|
+
|
|
46
|
+
#### P1-001 — registry-first na experiência
|
|
47
|
+
|
|
48
|
+
- README: instalação npm primeiro (sem clone), contribuidor via
|
|
49
|
+
clone depois; seção PyPI marcada como método recomendado
|
|
50
|
+
(registry-first, não registry-only).
|
|
51
|
+
|
|
52
|
+
### Cycle 11 — instalação registry-first (PyPI)
|
|
53
|
+
|
|
54
|
+
#### Wave 7 — performance (números medidos, não inventados)
|
|
55
|
+
|
|
56
|
+
- `scripts/bench.mjs`: mede startup (`--help`), `status`, `registry`,
|
|
57
|
+
resolve `--plan` (rede), install frio, reinstall e `update --check`
|
|
58
|
+
(cache). Mediana de N runs (`--runs`), JSON
|
|
59
|
+
`forge-cli/BenchReport/v1` em stdout, `FORGE_CLI_HOME` hermético.
|
|
60
|
+
- Medido local (Node 24, Linux, 3 runs): `--help` ~259ms,
|
|
61
|
+
`status` ~72ms, `registry` ~67ms, `resolve --plan` ~290ms (PyPI
|
|
62
|
+
online), install frio ~3.2s e reinstall ~3.5s (venv+pip dominam —
|
|
63
|
+
fora do nosso controle), `update --check` com cache ~217ms.
|
|
64
|
+
- **`FORGE_CLI_HOME`**: novo env override para o home de estado
|
|
65
|
+
(`~/.forge-cli`) — bench/testes/containers rodam isolados sem tocar
|
|
66
|
+
o estado real. Não afeta detecção de hosts (esses ficam no home real).
|
|
67
|
+
|
|
68
|
+
#### Wave 8 — supply chain / hardening
|
|
69
|
+
|
|
70
|
+
- **Retry com backoff no PyPI**: `fetchWithRetry` — 429/5xx e erros de
|
|
71
|
+
rede retentam 2x (250ms→500ms); 404 e respostas definitivas retornam
|
|
72
|
+
imediato. Retry nunca mascara "não existe".
|
|
73
|
+
- **`receipt.provenance`**: `{registry:'pypi.org', file, url,
|
|
74
|
+
resolvedVia:'pypi'|'offline-cache'}` — proveniência auditável do
|
|
75
|
+
artefato instalado junto ao `verifiedHash`.
|
|
76
|
+
- **Permissões do state dir**: `~/.forge-cli` e `forges/` recebem
|
|
77
|
+
`chmod 0700` no POSIX (Windows: ACL do perfil, chmod é no-op).
|
|
78
|
+
|
|
79
|
+
#### Wave 4 — hosts/MCP registry-aware
|
|
80
|
+
|
|
81
|
+
- **`forge-cli mcp [forja…]`**: resolve o argv MCP por Forja e reporta
|
|
82
|
+
presença do binário. Fonte: `forge.json` do clone, ou — sem clone —
|
|
83
|
+
receipt registry + template `mcp` do catálogo (`{bin}` → bin da venv,
|
|
84
|
+
`{binDir}/x` → script irmão; `.exe` no Windows). `--verify` roda
|
|
85
|
+
`<argv[0]> --version` como handshake mínimo. `--json` emit
|
|
86
|
+
`{forge, mcp_command, binPresent, handshake?}` por item. Forja sem
|
|
87
|
+
`mcp_command` honesto (ex.: the-forge) sai `ok:false`, nunca inventa.
|
|
88
|
+
- **`hosts plan|register|validate|rollback` sem clone**: `forgeDescriptor`
|
|
89
|
+
cai para o receipt + catálogo quando o manifest não está no
|
|
90
|
+
workspace — `hosts register claude api-forge` numa instalação
|
|
91
|
+
registry grava `mcpServers['api-forge'].command` apontando para o
|
|
92
|
+
`apiforge-mcp` da venv. Backup/rollback/`--yes` inalterados.
|
|
93
|
+
- Catálogo: campo `mcp` por Forja espelhando o `mcp_command` de cada
|
|
94
|
+
`forge.json` (api-forge `apiforge-mcp`, platform-forge
|
|
95
|
+
`platformforge-mcp`, doctors `[bin] mcp`, spark-forge `[bin] mcp serve`).
|
|
96
|
+
- `CliOptions.verify` + flag `--verify` registrada (parser é strict).
|
|
97
|
+
|
|
98
|
+
#### Wave 3 — lifecycle registry
|
|
99
|
+
|
|
100
|
+
- **`update --method registry`**: resolve a mais nova no PyPI, compara
|
|
101
|
+
com o receipt e reinstala limpo quando há versão nova. `--check`/
|
|
102
|
+
`--plan` mostram o plano sem mutar; `--ref <ver>` é override pontual
|
|
103
|
+
(não cria nem remove pin). Versões anteriores são empilhadas em
|
|
104
|
+
`receipt.history` — insumo do rollback.
|
|
105
|
+
- **`reinstall --method registry`**: reinstala a mesma versão do
|
|
106
|
+
receipt (reparo de drift — venv apagada mas state presente recria).
|
|
107
|
+
Preserva `pinned` e `history` — reinstall não é lifecycle de versão.
|
|
108
|
+
- **`rollback --method registry`**: desempilha `history.at(-1)` e
|
|
109
|
+
empilha a versão abandonada — rollback de rollback retorna à versão
|
|
110
|
+
original. Sem histórico → recusa honesta (`rollbackNoPrevious`), exit 1.
|
|
111
|
+
- **`pin`/`unpin --method registry`**: trava/remove a versão instalada
|
|
112
|
+
no state; `update` respeita (`skipped: fixada em <ver>`).
|
|
113
|
+
- **Ghost-forge no lifecycle**: Forja removida do catálogo mas com
|
|
114
|
+
receipt continua atualizável/reinstalável — `ghostEntry()` deriva
|
|
115
|
+
dist/bin do receipt e o piso Python do interpretador que a instalou.
|
|
116
|
+
- **Contrato uniforme**: `isSafeRepoName`/`resolveInside` nos mesmos
|
|
117
|
+
pontos do uninstall; flags git ignoradas avisam também no lifecycle;
|
|
118
|
+
`update` com todos os alvos não-instalados sai 1 (nada aconteceu);
|
|
119
|
+
confirmação interativa só em TTY duplo (automação segue com `--yes`).
|
|
120
|
+
|
|
121
|
+
#### Wave 2 — TUI registry + delegação nativa (fase 2)
|
|
122
|
+
|
|
123
|
+
- **Menu guiado `--method registry`**: `shouldShowMenu` não exclui mais
|
|
124
|
+
o método — `menuItemsFor` filtra o menu para os verbos suportados
|
|
125
|
+
(install/update/reinstall/uninstall/status/exit). `status` mapeia
|
|
126
|
+
para `forge-cli registry` (receipts). Install escolhe no catálogo
|
|
127
|
+
PyPI com hint de dist+piso Python; lifecycle escolhe nas instaladas.
|
|
128
|
+
- **Delegação ao instalador nativo** (`delegateNative`): `--native` ou
|
|
129
|
+
qualquer flag de fase 2 (`--profile/--scope/--components/--host/
|
|
130
|
+
--gateway-profile`) roda `<bin> install` **pelo bin da venv
|
|
131
|
+
verificada** — nunca um shim global. Sem `--yes` roda `--dry-run`
|
|
132
|
+
(default-deny). `{python} scripts/...` (forge-doctor-api) é skip
|
|
133
|
+
honesto — o script vive no repositório, não na wheel.
|
|
134
|
+
- **Native em lifecycle**: update/reinstall rodam a op `update`
|
|
135
|
+
documentada; `uninstall --native` roda `ops.uninstall` **antes** de
|
|
136
|
+
remover a venv. Tudo gravado em `receipt.native` (argv, ran, dryRun,
|
|
137
|
+
exitCode, skipped, ignored).
|
|
138
|
+
- **Validação fail-fast**: `--profile/--scope/--components/--host/
|
|
139
|
+
--gateway-profile` inválidos recusam antes de gastar rede (exit 1),
|
|
140
|
+
mesmo contrato do fluxo git. Flags saem de REGISTRY_IGNORED_FLAGS —
|
|
141
|
+
agora são honradas.
|
|
142
|
+
- **`.env` preservado**: instalações registry vivem em venvs isoladas
|
|
143
|
+
sob `~/.forge-cli/` — nunca tocam `.env` de projetos do usuário.
|
|
144
|
+
|
|
145
|
+
#### Corrigido após revisão (7ª rodada)
|
|
146
|
+
|
|
147
|
+
- **Uninstall não-TTY sem `--yes` (crítico)**: `uninstall --method
|
|
148
|
+
registry` pendurava em `confirm()` com stdin não-TTY (CI, pipes,
|
|
149
|
+
`--json --report` em automação). Agora recusa limpa — imprime
|
|
150
|
+
`needsYes`, grava o relatório se `--report` e sai 0, mesmo contrato
|
|
151
|
+
do fluxo git. Verificado real: `uninstall < /dev/null` → exit 0.
|
|
152
|
+
- **`state.json` com valores não-objeto não derruba**: `{"forges":
|
|
153
|
+
{"api-forge":"garbage"}}` é filtrado em `loadForgeState`; `registry`,
|
|
154
|
+
`status` e `list` não crasham (antes `TypeError` em `inst.verify.ok`)
|
|
155
|
+
— verificado real com o payload exato da revisão.
|
|
156
|
+
- **Own-property no state**: `state.forges[name]` via `Object.hasOwn` —
|
|
157
|
+
`uninstall constructor`/`toString` sai 2 como desconhecido em vez de
|
|
158
|
+
casar membros do `Object.prototype` (a mesma classe do review-9, no
|
|
159
|
+
resolver — agora também no state).
|
|
160
|
+
- **Recusa antes da resolução**: venv existente sem `--clean` falha
|
|
161
|
+
`already-installed` antes de gastar a chamada PyPI (fail fast,
|
|
162
|
+
inclusive offline).
|
|
163
|
+
- **`--ref` pós-dedup**: `install api-forge apiforge-af --ref` é uma só
|
|
164
|
+
Forja (dedup por alias na validação do dispatcher).
|
|
165
|
+
- **Confirmação mostra a versão resolvida**: o note de confirmação do
|
|
166
|
+
install interativo exibe `dist==versão` real (resolução antes do
|
|
167
|
+
confirm usa o cache no install seguinte — sem rede duplicada).
|
|
168
|
+
- **Piso composto**: `minPython` do catálogo compõe com
|
|
169
|
+
`requires_python` (max implícito) — release que alargue o spec para
|
|
170
|
+
baixo do piso da forge-cli não habilita interpretadores sem suporte.
|
|
171
|
+
- **`binPresent` estruturado**: `forge-cli registry --json` emite
|
|
172
|
+
`binPresent: boolean` em vez de embutir "(ausente)" no campo `bin`.
|
|
173
|
+
- **`mergeRegistryPhase` tolerante**: own-property + campos checados —
|
|
174
|
+
receipt sem `verify`/`bin` não derruba o `status`/`list`.
|
|
175
|
+
|
|
176
|
+
#### Corrigido após revisão (6ª rodada)
|
|
177
|
+
|
|
178
|
+
- **Path traversal no uninstall (bloqueio)**: `name`/`dist` vindos do
|
|
179
|
+
`state.json`/receipt (dado não confiável — entrada fora do catálogo é
|
|
180
|
+
desinstalável por design) agora passam por `isSafeRepoName` +
|
|
181
|
+
`resolveInside`, a mesma convenção do `safety.ts` para caminhos
|
|
182
|
+
destrutivos. Nome inseguro = recusa nomeada com exit 2, nunca chega
|
|
183
|
+
ao `rmSync`; `dist` maliciosa só impede a limpeza do wheel cache
|
|
184
|
+
(venv+state ainda saem).
|
|
185
|
+
- **Pin não-exato**: `--version 0.1` casa a chave `0.1.0` do PyPI por
|
|
186
|
+
equivalência parseada (PEP 440 zero-padding — o pip aceita `==0.1`).
|
|
187
|
+
- **Confirmação interativa no install**: TTY + sem `--yes` mostra o
|
|
188
|
+
plano (`confirmRun`) antes de instalar, mesmo contrato do fluxo git;
|
|
189
|
+
`--json`/pipes/`--yes` seguem sem perguntar.
|
|
190
|
+
- **`requires_python` autoritativo**: o piso `minPython` do catálogo só
|
|
191
|
+
se aplica quando os metadados do PyPI não declaram spec — o release
|
|
192
|
+
pode alargar o suporte sem depender de atualização do catálogo.
|
|
193
|
+
- **`info.requires_python` escopado**: o campo descreve a release mais
|
|
194
|
+
nova; como fallback só vale quando a versão resolvida é ela (um pin
|
|
195
|
+
antigo não recebe o spec novo por engano).
|
|
196
|
+
- **Cache e leitura defensivos**: `writeCache` atômico; cap de 16 MiB
|
|
197
|
+
aplicado antes de bufar o corpo (content-length + cancel do stream);
|
|
198
|
+
`state` com `forges: []` tratado como vazio.
|
|
199
|
+
- **Quarentena em hash-mismatch**: artefato divergente é renomeado para
|
|
200
|
+
`.rejected` — preserva evidência, desbloqueia o retry e nunca é
|
|
201
|
+
selecionável pelo `--find-links` (extensão fora de `.whl`/`.tar.gz`).
|
|
202
|
+
- **stderr redacted**: detalhes de erro de subprocesso passam por
|
|
203
|
+
`redactSecrets` — credenciais de `PIP_INDEX_URL` não vazam no stdout
|
|
204
|
+
JSON, no `--report` nem no log humano.
|
|
205
|
+
|
|
206
|
+
#### Corrigido após revisão (5ª rodada)
|
|
207
|
+
|
|
208
|
+
- **Dedupe pós-normalização**: `install api-forge apiforge-af` (nome +
|
|
209
|
+
alias) não falha mais com `already-installed` espúrio; uninstall
|
|
210
|
+
planeja uma única remoção.
|
|
211
|
+
- **`status` honesto sob drift**: receipt registry com venv/bin ausente
|
|
212
|
+
vira `drifted` + "(bin ausente)" (antes marcava `installed` só pelo
|
|
213
|
+
state). `forge-cli registry` e `status` agora concordam.
|
|
214
|
+
- **`--report` honrado**: escreve o documento JSON no arquivo (mesmo
|
|
215
|
+
contrato do fluxo git) em vez de só desviar o modo de saída;
|
|
216
|
+
`--json` sozinho continua emitindo no stdout.
|
|
217
|
+
- **Flags ignoradas no uninstall**: `--dir`/`--profile`/etc. agora
|
|
218
|
+
avisam no uninstall também (antes só no install).
|
|
219
|
+
- **Uninstall completo**: remove venv + `cache/wheels/<dist>` + state —
|
|
220
|
+
reclaim de disco; Forja fora do catálogo mas presente no state
|
|
221
|
+
continua desinstalável (entrada removida do catálogo não trava).
|
|
222
|
+
- **Escritas atômicas**: `state.json` e `install.json` usam
|
|
223
|
+
`writeFileAtomic` (convenção do codebase) — kill no meio não corrompe.
|
|
224
|
+
- **Sem dir órfão**: falha de install remove `forges/<nome>/` vazio.
|
|
225
|
+
- **`--find-links` só no offline**: online as deps resolvem no índice —
|
|
226
|
+
wheel cache não compete desnecessariamente. Limite documentado:
|
|
227
|
+
offline, deps transitivas resolvem por nome sem pin de hash.
|
|
228
|
+
- **Cache não envenenável**: payload 200 sem `releases` não sobrescreve
|
|
229
|
+
cache sadio — cai no cache ou falha `malformed`.
|
|
230
|
+
- **Menu sem dead-end**: `--method registry` sozinho não abre o menu
|
|
231
|
+
interativo (que só roteava o fluxo git) — recusa limpa no dispatcher.
|
|
232
|
+
- **`--method git` em comandos alheios**: agora avisa em vez de engolir
|
|
233
|
+
(ex.: `status --method git`).
|
|
234
|
+
- **pip sem prompts**: `--no-input` em download/install; timeout do
|
|
235
|
+
subprocesso preserva o stderr real; payload PyPI tem cap de 16 MiB;
|
|
236
|
+
`available` do `version-not-found` vem ordenado.
|
|
237
|
+
- **Banner do plano honesto**: "venv e estado não alterados" (o cache
|
|
238
|
+
de metadados pode ser atualizado — mesmo modelo de `update --check`).
|
|
239
|
+
- Docs: `arquitetura.md` e `capabilities.md` descrevem o método
|
|
240
|
+
registry; decisões registradas como diferidas — `--python`
|
|
241
|
+
selecionável (§4.7), pipx/uv (§4.6), canal rc/beta (§4.1), lock de
|
|
242
|
+
state.json entre processos concorrentes.
|
|
243
|
+
|
|
244
|
+
#### Corrigido após revisão (4ª rodada)
|
|
245
|
+
|
|
246
|
+
- **Membership own-property**: `--version toString` (ou qualquer membro
|
|
247
|
+
do prototype) não é mais tratado como release existente — `plan`
|
|
248
|
+
chegava a certificar `apiforge-af==toString` como instalável.
|
|
249
|
+
- **Instalação por artefato verificado**: o pip recebe o caminho local
|
|
250
|
+
da wheel/sdist com sha256 conferido — um arquivo plantado no wheel
|
|
251
|
+
cache com nome da dist nunca é escolhido por resolução de nome.
|
|
252
|
+
Sem artefato verificável → `hash-mismatch`, não instala. (Limite
|
|
253
|
+
honesto: dependências transitivas resolvem por nome em
|
|
254
|
+
`--find-links`; só o artefato da dist é vinculado por hash.)
|
|
255
|
+
- **Reinstall sem `--clean`** recusa com `already-installed` — antes
|
|
256
|
+
`python -m venv` recriava sobre a venv boa e uma falha posterior a
|
|
257
|
+
destruía com o state intacto.
|
|
258
|
+
- **`--report`/`--verbose`**: label correto (`--report`, não
|
|
259
|
+
`--report-path`) e `--verbose` entra na lista de avisados.
|
|
260
|
+
- **Exit code consistente**: uninstall com nome desconhecido sai 2 em
|
|
261
|
+
modo humano também (antes só `--json` retornava erro).
|
|
262
|
+
- **Cache TTL robusto a clock skew**: `fetchedAt` futuro conta como
|
|
263
|
+
stale, não como eternamente fresh.
|
|
264
|
+
|
|
265
|
+
#### Corrigido após revisão (3ª rodada)
|
|
266
|
+
|
|
267
|
+
- **PEP 440**: `XaN.postM` agora compara `post` dentro da fase de
|
|
268
|
+
pre-release — antes `1.0a1.post1` empatava com `1.0a1` e `!=`
|
|
269
|
+
avaliava errado.
|
|
270
|
+
- **Alias de dist normalizado**: `registry apiforge-af` filtra a linha
|
|
271
|
+
certa e `uninstall --method registry apiforge-af` resolve o estado
|
|
272
|
+
pelo nome canônico — antes passavam validação e produziam saída
|
|
273
|
+
vazia/`não instalado` espúrio.
|
|
274
|
+
- **`--clean` limpa o `state.json`** junto com venv/receipt — falha
|
|
275
|
+
posterior não deixa versão antiga registrada sem artefatos.
|
|
276
|
+
- **subprocessos com timeout** (venv/download/pip/verify, 5 min
|
|
277
|
+
default) — antes só o fetch era limitado.
|
|
278
|
+
- `selectInterpreter` ordena internamente (não depende do chamador);
|
|
279
|
+
`checkDistHashes` verifica **todos** os artefatos presentes;
|
|
280
|
+
`--version vX.Y.Z` aceita prefixo `v` no método registry.
|
|
281
|
+
|
|
282
|
+
#### Corrigido após revisão (dispatch + integridade, 2ª rodada)
|
|
283
|
+
|
|
284
|
+
- **`--plan`/`--dry-run`/`--check` honrados no registry**: resolvem e
|
|
285
|
+
mostram o plano (dist==versão, interpretador, venv, wheel cache) sem
|
|
286
|
+
criar venv, baixar artefatos ou gravar estado — antes executavam uma
|
|
287
|
+
instalação real.
|
|
288
|
+
- **`--method registry` recusado no topo do dispatch** (`versions`,
|
|
289
|
+
`status`, `list`, `self update`, `doctor`, …) — antes só
|
|
290
|
+
`update`/`clone`/`reinstall`/`rollback`/`pin`/`unpin` alcançavam o
|
|
291
|
+
check e o resto executava o fluxo git.
|
|
292
|
+
- **`--json` limpo nos caminhos de erro/aviso**: nomes desconhecidos,
|
|
293
|
+
skips de uninstall e a exigência de `--yes` emitem JSON estruturado
|
|
294
|
+
(`error`, `planned`, `skipped`, `warnings`) — antes logs humanos
|
|
295
|
+
poluíam stdout e quebravam `JSON.parse`.
|
|
296
|
+
- **Flags ignoradas cobertas por completo**: `--skip-scripts`,
|
|
297
|
+
`--shallow`, `--fix`, `--rollback`, `--gateway-profile`,
|
|
298
|
+
`--trust-custom`, `--run-after-sync-failure`, `--report`,
|
|
299
|
+
`--only-changed` agora avisam também; `--clean` é honrado (recria a
|
|
300
|
+
venv) e `--check` funciona como plano.
|
|
301
|
+
- **HTTP 5xx/429 do PyPI cai no cache local** (antes só exceções de
|
|
302
|
+
transporte); sem cache continua falhando honestamente.
|
|
303
|
+
- **`--offline` verifica sha256 do wheel cacheado** contra o digest do
|
|
304
|
+
PyPI — cache adulterado aborta com `hash-mismatch`.
|
|
305
|
+
- **Interpretador pré-release** só é escolhido quando nenhum estável
|
|
306
|
+
satisfaz `requires_python` (alinha com `packaging`).
|
|
307
|
+
- **`distArtifactFiles`** usa prefixo normalizado com separador —
|
|
308
|
+
`dist_extra-…` não colide e sdists legados com `-` são encontrados.
|
|
309
|
+
- **Ordenação PEP 440**: `XaN.devM`/`X.postN.devM` ordenam antes do
|
|
310
|
+
mesmo marco sem `.dev` (e branch morto removido).
|
|
311
|
+
- **`registry <nome>`** filtra o catálogo; nome fora do catálogo →
|
|
312
|
+
erro 2.
|
|
313
|
+
- **Pós-instalação** mostra o caminho do binário instalado (venv
|
|
314
|
+
isolada — PATH é decisão do usuário).
|
|
315
|
+
|
|
316
|
+
#### Corrigido após revisão (dispatch + integridade)
|
|
317
|
+
|
|
318
|
+
- **`--ref`/`--version` em multi-Forja** agora recusa como no fluxo git
|
|
319
|
+
(`errRefNeedsOne`) — antes aplicava o mesmo pin a todas as Forjas.
|
|
320
|
+
- **`uninstall --method registry`**: dispatch movido para antes do
|
|
321
|
+
branch legado (sem nomes não executa mais a remoção do PATH),
|
|
322
|
+
`--all`/`--only` resolvem o conjunto instalado, e a remoção segue a
|
|
323
|
+
convenção destrutiva do repo — plano visível + confirmação/`--yes`.
|
|
324
|
+
- **`--method registry` em comandos não suportados** (`update`, `clone`,
|
|
325
|
+
`reinstall`, `rollback`, …) é recusado em vez de cair no fluxo git.
|
|
326
|
+
- **Offline real**: instalação online baixa artefatos para
|
|
327
|
+
`cache/wheels/<dist>/`; `--offline` instala com
|
|
328
|
+
`--no-index --find-links` e recusa com o artefato faltante nomeado
|
|
329
|
+
(nunca finge instalar sem rede).
|
|
330
|
+
- **Integridade**: sha256 do artefato da dist é conferido byte a byte
|
|
331
|
+
após o download e gravado como `verifiedHash` no receipt; divergência
|
|
332
|
+
aborta antes do pip.
|
|
333
|
+
- **Yanked/empty releases** não entram na escolha automática
|
|
334
|
+
(PEP 592); pin explícito instala e marca `yanked` no receipt.
|
|
335
|
+
- **`status`** mostra Forjas instaladas via registry (fase
|
|
336
|
+
`installed`, `pypi dist==ver`) em vez de `not-installed`.
|
|
337
|
+
- **`registry`** marca `(verify falhou)` em installs que não passaram
|
|
338
|
+
no verify; `--json` expõe `verifyOk`.
|
|
339
|
+
- **`--json` no install/uninstall registry** emite JSON limpo (banner,
|
|
340
|
+
spinner e logs suprimidos); flags do fluxo git ignoradas viram aviso
|
|
341
|
+
explícito (`--dir`, `--profile`, `--scope`, `--components`,
|
|
342
|
+
`--host`, `--native`, `--skip-verify`, `--timeout`, `--jobs`,
|
|
343
|
+
`--retry-failed`).
|
|
344
|
+
- **`requires_python` por arquivo**: prefere o da wheel (o que o pip
|
|
345
|
+
instala) ao do sdist/`info`.
|
|
346
|
+
- **Especificador fora do subconjunto** (`===`, `~=X`, wildcard com
|
|
347
|
+
operador errado) vira `specifier-unknown`, não `python-incompatible`.
|
|
348
|
+
- **Interpretador pré-release** (`Python 3.14.0a1`) parseia com sufixo.
|
|
349
|
+
- **`state.json` corrompido** é preservado em `.corrupt` antes de ser
|
|
350
|
+
ignorado — nunca clobber silencioso.
|
|
351
|
+
- **Venv órfã** removida em falha de venv/download/pip.
|
|
352
|
+
- **Pisos do catálogo** corrigidos para o `requires_python` real
|
|
353
|
+
publicado (`apiforge-af` ≥3.12, doctors ≥3.11).
|
|
354
|
+
|
|
355
|
+
#### Added
|
|
356
|
+
|
|
357
|
+
- **`install --method registry`**: instala Forjas direto do PyPI sem
|
|
358
|
+
exigir clone do repositório nem o The Forge instalado. Cada Forja vai
|
|
359
|
+
para uma venv isolada em `~/.forge-cli/forges/<nome>/`, com
|
|
360
|
+
`pip install <dist>==<versão>` pinado e verify do console script.
|
|
361
|
+
- **`forge-cli registry`**: lista o catálogo PyPI (7 dists), a versão
|
|
362
|
+
instalada e o binário de cada Forja; `--json` exporta.
|
|
363
|
+
- **`uninstall --method registry`**: remove a venv e o receipt de uma
|
|
364
|
+
Forja instalada via registry.
|
|
365
|
+
- **Resolver PyPI** (`src/registry/`): consulta o índice JSON do PyPI,
|
|
366
|
+
escolhe a estável mais nova (pré-releases ignoradas), honra
|
|
367
|
+
`--version`/`--ref` como pin e registra `source` no receipt
|
|
368
|
+
(`pinned|latest-stable|cache`).
|
|
369
|
+
- **Compatibilidade Python pré-install**: `requires_python` do release é
|
|
370
|
+
avaliado por um subconjunto PEP 440 (`~=`, `==`/`!=` com `.*`, `>=`,
|
|
371
|
+
`>`, `<=`, `<`) contra os interpretadores detectados — o mais novo que
|
|
372
|
+
satisfaz a faixa vence, e falta de interpretador compatível é erro
|
|
373
|
+
honesto antes de qualquer mutação.
|
|
374
|
+
- **Cache offline**: `--offline` resolve apenas pelo cache local
|
|
375
|
+
(`~/.forge-cli/cache/pypi/`, TTL 1h); falha de rede cai no cache
|
|
376
|
+
automaticamente quando existe.
|
|
377
|
+
- **Receipts auditáveis**: `install.json` por Forja + `state.json`
|
|
378
|
+
consolidado registram dist, versão, `requires_python`, interpretador
|
|
379
|
+
escolhido, digests sha256 dos artefatos e o verify (comando + ok) —
|
|
380
|
+
incluindo installs que falharam no verify, para diagnóstico.
|
|
381
|
+
|
|
382
|
+
#### Fixed
|
|
383
|
+
|
|
384
|
+
- `install` sem nomes no modo registry agora recusa com orientação
|
|
385
|
+
explícita em vez de cair no fluxo interativo do git.
|
|
386
|
+
|
|
387
|
+
### Hardening pós-release
|
|
388
|
+
|
|
389
|
+
#### Fixed
|
|
390
|
+
|
|
391
|
+
- **`update --check` sem upstream**: repositório sem remote configurado
|
|
392
|
+
(ou sem tracking no branch) reportava `não foi possível verificar
|
|
393
|
+
(unknown)` — implicando falha transitória onde o estado honesto é
|
|
394
|
+
`no-upstream`. Novo literal em `SyncFailure`; `updateCheck` resolve
|
|
395
|
+
`@{u}` **antes** do fetch (cobre `branch.<name>.remote` apontando para
|
|
396
|
+
`.` ou URL sem remote nomeado) e só então classifica `no-upstream`;
|
|
397
|
+
o texto mostra `sem upstream configurado` e o JSON propaga
|
|
398
|
+
`failure: "no-upstream"`.
|
|
399
|
+
|
|
400
|
+
#### Changed
|
|
401
|
+
|
|
402
|
+
- **`planActionsFor` exportado** (`src/wizard.ts`): o construtor das
|
|
403
|
+
ações do plano revisado no wizard vira superfície testável, junto a
|
|
404
|
+
`renderOperationPlan` — o fluxo interativo (@clack/prompts) continua
|
|
405
|
+
fronteira de TUI não simulada.
|
|
406
|
+
|
|
407
|
+
#### Tests
|
|
408
|
+
|
|
409
|
+
- `tests/wizard.test.ts` (novo, 15 casos): renderização do plano
|
|
410
|
+
(hash truncado, expiração, passos, marcador de não-reversível,
|
|
411
|
+
truncamento de arquivos em 8) e `planActionsFor` (clone vs pull,
|
|
412
|
+
gating por passo, `.env` e `verify` sempre presentes).
|
|
413
|
+
- `tests/hosts.test.ts` (+12 casos): adapter **cursor** completo
|
|
414
|
+
(detect absent→installed→configured→integrated, merge-json com
|
|
415
|
+
preview e sem copyable, manual quando integrado, JSON inválido
|
|
416
|
+
recusado sem clobber, dry-run), **copilot** (detect via `gh copilot`
|
|
417
|
+
- `.config/gh`, plan gated por `mcp-config.json` existente) e
|
|
418
|
+
**devin** (detect via `.config/devin`, apply/validate recusam com
|
|
419
|
+
`hostUnsupported`).
|
|
420
|
+
- `tests/config.test.ts` (+6 casos): bloco `ai` do `loadConfig` —
|
|
421
|
+
chaves válidas, limites (512–262144 bytes, 1000–300000 ms,
|
|
422
|
+
0–86400000 ms TTL), tipos errados descartados, `ai` não-objeto
|
|
423
|
+
ignorado sem erro.
|
|
424
|
+
- `tests/ai-cache.test.ts` (novo, 8 casos): chave canônica (ordem de
|
|
425
|
+
chaves irrelevante, tipos distintos não colidem), TTL (expirado,
|
|
426
|
+
timestamp futuro, `ttl<=0`), entrada corrompida/shape inválido →
|
|
427
|
+
miss, eviction bounded por idade com tiebreak por nome.
|
|
428
|
+
- `tests/git-revs.test.ts` (+6 casos): `updateCheck` reporta
|
|
429
|
+
`no-upstream` com e sem remote configurado, tracking local
|
|
430
|
+
(`branch.<name>.remote=.`, `git remote` vazio) resolve `@{u}` e não é
|
|
431
|
+
`no-upstream`, fetch falho classificado sem throw, `remoteTags` em
|
|
432
|
+
remote `file://` real (ordem semver, `--limit`, remote inacessível →
|
|
433
|
+
`null`).
|
|
434
|
+
- `tests/e2e-cli.test.ts` (+1 caso): `update --check` ponta a ponta
|
|
435
|
+
reporta `no-upstream`/`sem upstream configurado` sem pendência.
|
|
436
|
+
- `tests/doctor2.test.ts` (+4 casos): helpers de contexto (`listDir`
|
|
437
|
+
ordenado/nulo, `readJson` ok/erro, `boundEntries` com flag de
|
|
438
|
+
truncamento, `mkCheck`/`errMsg`).
|
|
439
|
+
|
|
440
|
+
### Programa ecosystem-2026 (cyc8-05)
|
|
441
|
+
|
|
442
|
+
#### Added
|
|
443
|
+
|
|
444
|
+
- **Compatibilidade com o manifesto do ecossistema**: `src/compat.ts`
|
|
445
|
+
consome `forge/EcosystemCompatibility/v1` (the-forge
|
|
446
|
+
`ecosystem-compatibility.json`) — `update --check` reporta
|
|
447
|
+
`installed_compat: certified|incompatible|unknown` por repositório e o
|
|
448
|
+
banner do manifesto (release + sha256) na saída texto.
|
|
449
|
+
- **Validação estrita do manifesto**: `manifestProblems()` espelha
|
|
450
|
+
`load_manifest(strict=True)` do Python — release `YYYY.MM`, `created_at`
|
|
451
|
+
ISO, ids de componente/família/contrato, versão pinada (rejeita
|
|
452
|
+
`latest`/`""`) no scheme declarado, `source` obrigatório, janelas
|
|
453
|
+
`min_major`/`max_major`, famílias únicas. Manifesto inválido → `null`
|
|
454
|
+
(nunca certifica silenciosamente).
|
|
455
|
+
|
|
456
|
+
#### Changed
|
|
457
|
+
|
|
458
|
+
- **`update --check --json`**: o payload passa de array puro de
|
|
459
|
+
repositórios para envelope `{manifest: {release, sha256, path} | null,
|
|
460
|
+
repos: [...]}` — a identidade do manifesto avaliado agora é evidência no
|
|
461
|
+
JSON. Consumidores do array anterior devem ler `payload.repos`.
|
|
462
|
+
- **`compat:` → `installed_compat:`** no texto e no JSON: o veredito se
|
|
463
|
+
refere à versão **instalada**, não ao update pendente (repos `behind`
|
|
464
|
+
não sugerem mais que a atualização é certificada).
|
|
465
|
+
|
|
466
|
+
### Programa evo-cli-portal (F0→C6, ciclos `audit/f0-baseline`→`cycle/c6-intelligence`)
|
|
467
|
+
|
|
468
|
+
#### Added
|
|
469
|
+
|
|
470
|
+
- **Contratos canônicos (C1)**: família `forge/<Name>/v1` como JSON Schemas
|
|
471
|
+
em `contracts/schemas/` (Descriptor, Capability, EnvironmentSchema,
|
|
472
|
+
HostDescriptor, InstallContract, OperationPlan, OperationReceipt,
|
|
473
|
+
HealthReport), validator Python vendorizável (`contracts/forge_contracts.py`,
|
|
474
|
+
stdlib-only), política de descoberta com os sete estados
|
|
475
|
+
(`declared/detected/configured/integrated/validated/degraded/unknown`) e
|
|
476
|
+
precedência explícita (`src/discovery/negotiate.ts`).
|
|
477
|
+
- **Descoberta e recomendação (C2)**: `forge-cli discover` (walk bounded por
|
|
478
|
+
budget, sinais de stack, probes de host, `truncated` honesto) e
|
|
479
|
+
`forge-cli recommend` (recomendações explicáveis com confidence).
|
|
480
|
+
- **Wizard de instalação (C2)**: fluxo interativo de 7 passos + planner que
|
|
481
|
+
emite `forge/ForgeOperationPlan/v1` com hash sha256 canônico verificável.
|
|
482
|
+
- **Env Intelligence (C2)**: `forge-cli env inspect|plan|init|validate|
|
|
483
|
+
migrate|doctor` — valores nunca exibidos, migração com rollback.
|
|
484
|
+
- **Host Intelligence (C2/C4)**: adapters por host (`forge-cli hosts
|
|
485
|
+
list|plan|register|validate|rollback`) — detecção ≠ validação
|
|
486
|
+
(`verified|absent|unknown`), comando copiável em vez de escrita opaca.
|
|
487
|
+
- **Ciclo de vida (C2)**: `lock`, `profile`, `recover`, análise de
|
|
488
|
+
breaking-changes, estado persistido.
|
|
489
|
+
- **Doctor 2.0 + self-healing (C5)**: `forge-cli doctor` cobre 5 domínios
|
|
490
|
+
(`forge/DoctorReport/v1`, health score gated — `HEALTH_MODEL.md`);
|
|
491
|
+
`forge-cli heal` com classificação `SAFE_AUTOMATIC`/`REVIEW_REQUIRED`/
|
|
492
|
+
`MANUAL_ONLY`/`UNSUPPORTED`, fluxo de reparo em 8 passos, receipts
|
|
493
|
+
imutáveis (`forge/RepairReceipt/v1`) com correlation IDs.
|
|
494
|
+
- **Observabilidade e versões (C5)**: métricas locais, `versions`,
|
|
495
|
+
overhead medido (PERFORMANCE_BASELINE.md).
|
|
496
|
+
- **Forge Intelligence Engine (C6)**: `forge-cli ask <domínio>`
|
|
497
|
+
(discovery/recommendation/repair — respostas com evidência, confidence,
|
|
498
|
+
ordenação estável), `forge-cli predict` (compatibilidade preditiva com
|
|
499
|
+
evidência/versão/certainty — silêncio honesto sem evidência),
|
|
500
|
+
`forge-cli context` (`forge/ForgeContext/v1`, 8 KiB bounded).
|
|
501
|
+
- **Modo IA opcional (C6)**: `forge-cli ai <task>` — opt-in, BYOK via
|
|
502
|
+
bridge configurável (`ai.command`), sanitize tripla de segredos,
|
|
503
|
+
propostas schema-validadas (`forge/AiProposal/v1`) com refs
|
|
504
|
+
re-validados e rechecagem determinística; **zero execução** de conteúdo
|
|
505
|
+
do modelo; fallback tipado para determinístico (`AI_MODE_POLICY.md`).
|
|
506
|
+
- **Portabilidade (C6)**: `forge-cli bundle export|import|verify`
|
|
507
|
+
(`forge-cli/Bundle/v1`) — profile, env lock + check, pins, manifesto de
|
|
508
|
+
integridade sha256 (`forge/IntegrityManifest/v1`); valores de segredo
|
|
509
|
+
excluídos por default (`env-values.json` só com whitelist explícita e
|
|
510
|
+
recusa fail-closed de chaves secretas); `planReinstall` gera plano
|
|
511
|
+
verificável (`PORTABILITY.md`).
|
|
512
|
+
- **Aprendizado local (C6)**: `forge-cli learning status|enable|disable|
|
|
513
|
+
report` — opt-in, `.forge-cli/learning.json` bounded a 1000
|
|
514
|
+
observações, redacted, FIFO, nunca altera política (`LEARNING_EVAL.md`).
|
|
515
|
+
|
|
516
|
+
#### Changed
|
|
517
|
+
|
|
518
|
+
- `forge.json` passa a ser validado contra `ForgeDescriptor/v1` quando o
|
|
519
|
+
campo `schema` é declarado — fail-closed, aditivo.
|
|
520
|
+
- Ordenações em intel/predict usam comparação por code-unit (estável,
|
|
521
|
+
independente de locale).
|
|
522
|
+
- `bundle export` aceita chaves não-secretas posicionais para inclusão
|
|
523
|
+
explícita de valores.
|
|
524
|
+
|
|
525
|
+
#### Fixed
|
|
526
|
+
|
|
527
|
+
- `doctor`/`env`/`hosts`/`discover`/`recommend`/`context`/`predict`
|
|
528
|
+
respeitam `--json` sem banner (via `JSON_QUERY_COMMANDS`).
|
|
529
|
+
- `bundle export` captura `SecretKeysRefusedError` e exibe recusa clara.
|
|
530
|
+
- Heal: `REVIEW_REQUIRED` nunca é aplicado por `--yes` — aguarda revisão
|
|
531
|
+
por chave (Gate C5).
|
|
532
|
+
|
|
533
|
+
#### Security
|
|
534
|
+
|
|
535
|
+
- Logs, receipts e erros mascaram segredos (`redactSecrets`); contexto de
|
|
536
|
+
IA usa denylist de chave + redact + detecção de valor-secret-shaped
|
|
537
|
+
(`sk-live-`, `ghp_`, JWT, `AKIA`, `xox*`).
|
|
538
|
+
- `assertNoSecrets` devolve paths ofensores ordenados; cache de IA bounded
|
|
539
|
+
(32 entradas, TTL, anti clock-skew, chave sha256 de JSON canônico).
|
|
540
|
+
- Bridge de IA tem boundary injetável único, timeout, cap de contexto e
|
|
541
|
+
nenhum payload de comando na saída do modelo.
|
|
542
|
+
- `npm ci` auditado, zero vulnerabilidades; runtime deps congeladas.
|
|
543
|
+
|
|
544
|
+
#### Compatibility
|
|
545
|
+
|
|
546
|
+
- Sem novas dependências de runtime desde F0; Node >= 20, npm >= 10.
|
|
547
|
+
- Contratos são aditivos: `forge/PortalApi/1` intocado; Forjas
|
|
548
|
+
consumidoras não requerem mudança.
|
|
549
|
+
- `--offline` usa apenas clones locais; clone ausente → recusa explícita
|
|
550
|
+
(sem tentativa de rede).
|
|
551
|
+
|
|
552
|
+
#### Migration
|
|
553
|
+
|
|
554
|
+
- Nenhuma migração obrigatória. `forge.json` legado continua válido
|
|
555
|
+
(campos desconhecidos ignorados); `env migrate` oferece plano+rollback.
|
|
556
|
+
|
|
557
|
+
#### Known limitations
|
|
558
|
+
|
|
559
|
+
- Preview de plano exibe `git pull` mesmo com `--offline` (cosmético —
|
|
560
|
+
execução honra a flag; P3).
|
|
561
|
+
- Sem lock cross-process entre dois CLIs no mesmo target (P2).
|
|
562
|
+
- Manifests de Forja (`forge/SpecialistAgenticManifest/v1`) ainda não
|
|
563
|
+
emitidos pelos repos consumidores — engine reporta silêncio correto.
|
|
564
|
+
- Defect pré-existente em `forge-doctor-api`
|
|
565
|
+
(`test_malformed_stdio_input_resilient`) descoberto na homologação —
|
|
566
|
+
escopo do especialista, P2.
|
|
567
|
+
|
|
568
|
+
### Adicionado
|
|
569
|
+
|
|
570
|
+
- Licença MIT (`LICENSE`, campo `license`), alinhada às demais Forjas.
|
|
571
|
+
- `forge-doctor-api`: `update`/`uninstall` nativos e `--host`/`--gateway-profile`
|
|
572
|
+
confirmados em `scripts/forge_install.py`.
|
|
573
|
+
- Testes de TUI com teclas reais simuladas (menu e seleção com busca) e fluxo
|
|
574
|
+
do menu testável sem TTY (`applyMenu`).
|
|
575
|
+
|
|
576
|
+
### Corrigido
|
|
577
|
+
|
|
578
|
+
- Ajuda da seleção de Forjas dizia "espaço marca"; a tecla correta é Tab.
|
|
579
|
+
- Instaladores nativos (itens 6/7/9/10): `forge-doctor-api` via
|
|
580
|
+
`scripts/forge_install.py` (Python resolvido em runtime), `--host` e
|
|
581
|
+
`--gateway-profile` repassados onde a Forja suporta, `--native` em
|
|
582
|
+
`update` e `uninstall`, e `forge.json` opcional (campos validados)
|
|
583
|
+
para derivar a verificação de Forjas sem `verify` no catálogo.
|
|
584
|
+
- `--json` em `status`, `list`, `versions`, `components`, `update --check`
|
|
585
|
+
e `doctor`: só JSON no stdout, sem banner.
|
|
586
|
+
- TUI: seleção de Forjas com busca (digite para filtrar) e dica de fase/versão
|
|
587
|
+
por Forja; `--verbose` para saída completa dos comandos; logs com
|
|
588
|
+
timestamp. Código de saída 1 quando alguma Forja falha.
|
|
589
|
+
|
|
590
|
+
### Corrigido
|
|
591
|
+
|
|
592
|
+
- O log da execução (`.forge-cli/logs`) agora mascara segredos e URLs com
|
|
593
|
+
credenciais; antes só relatórios e mensagens de erro eram mascarados.
|
|
594
|
+
- Aprovação e estados (pós-roadmap): resumo de execução (origem, revisão,
|
|
595
|
+
confiança, comandos exatos) antes da confirmação, aprovação por
|
|
596
|
+
repositório customizado, e fases persistidas (`cloned`, `prepared`,
|
|
597
|
+
`installed`, `validated`, `partial`, `failed`, `drifted`) no `status`.
|
|
598
|
+
- `help`, `components [forjas]` (flags do instalador nativo por Forja),
|
|
599
|
+
`doctor <forja>` e `--offline` (usa só clones existentes; nunca clona
|
|
600
|
+
nem faz pull/fetch).
|
|
601
|
+
- Adaptadores leves (Cycle 0/6): `--profile`, `--scope` e `--components`
|
|
602
|
+
acionam o instalador nativo da Forja (`<cli> install ...`) depois do
|
|
603
|
+
bootstrap, somente para Forjas que o documentam; sempre `--dry-run`
|
|
604
|
+
salvo `--yes`. Matriz de capacidades real em `docs/capabilities.md`.
|
|
605
|
+
- Portabilidade/distribuição (Cycle 8/9): `self update` respeitando o
|
|
606
|
+
método de instalação (npm global automático com `--yes`; shim e
|
|
607
|
+
código-fonte mostram o passo manual), CI em Linux/Windows/macOS e
|
|
608
|
+
testes dos cenários de independência A–F.
|
|
609
|
+
- TUI (Cycle 2): menu principal ao rodar `forge-cli` em terminal
|
|
610
|
+
interativo (instalar, atualizar, reinstalar, reparar, versões, estado,
|
|
611
|
+
desinstalar, diagnosticar, sair). Sem TTY ou com comando/flags de
|
|
612
|
+
seleção, o comportamento anterior é mantido. `uninstall` pede
|
|
613
|
+
confirmação interativa quando não há `--yes`.
|
|
614
|
+
- Ciclo de vida (Cycle 4/5/7): `pin`/`unpin` (install/update respeitam a
|
|
615
|
+
fixação), `rollback` (só o checkout Git, ao HEAD anterior registrado),
|
|
616
|
+
`reinstall` (mantém a revisão atual; `--clean` remove e reclona com
|
|
617
|
+
backup) e `uninstall <forjas>` (plano por padrão, exige `--yes`; recusa
|
|
618
|
+
tree suja, remote diferente, symlink e diretórios sem .git; faz backup
|
|
619
|
+
de `.env`/configs em `.forge-cli/backups`).
|
|
620
|
+
- Versões e revisões (Cycle 3/5): `update [forjas]` (sem nomes: todas as
|
|
621
|
+
clonadas), `update --check` (só fetch, sem alterar), `versions`,
|
|
622
|
+
`--ref <sha|tag>` e `--version <tag>` (checkout destacado; recusa
|
|
623
|
+
working tree modificada; exige uma única Forja).
|
|
624
|
+
- Subcomandos (Cycle 3): `install [forjas]`, `clone <forjas>` (sem
|
|
625
|
+
scripts), `status [forjas]`, `self install|uninstall`. **Mudança:**
|
|
626
|
+
`forge-cli install` sem nomes agora abre o fluxo interativo; o
|
|
627
|
+
registro no PATH é `forge-cli self install`.
|
|
628
|
+
- Sync Git (Cycle 1): detecta remote divergente do catálogo e classifica
|
|
629
|
+
falhas de pull (divergente/rede/autenticação). Scripts não rodam após
|
|
630
|
+
falha de sync sem `--run-after-sync-failure`; repositórios de
|
|
631
|
+
`forge-cli.repos.json` só executam scripts com `--trust-custom`.
|
|
632
|
+
- Segurança (Cycle 1): validação de nomes/URLs de repositórios (bloqueia
|
|
633
|
+
path traversal, `--opção`, `ext::`, `file://` e credenciais embutidas),
|
|
634
|
+
recusa de destino symlink/junction, `git clone --`, redação de
|
|
635
|
+
segredos em logs/relatórios/erros e escrita atômica de estado.
|
|
636
|
+
- Provisionamento de `.env` por repositório antes do script de
|
|
637
|
+
instalação: copia `.env.example`, cria modelo comentado ou preserva
|
|
638
|
+
`.env` existente (lf-0002).
|
|
639
|
+
- Verificação de ambiente (preflight): `git`, Node.js >= 20 e
|
|
640
|
+
`powershell.exe` no Windows, com saída `exit 1` antes dos prompts.
|
|
641
|
+
- UI/UX: banner animado, tema de cores centralizado, ícones emoji,
|
|
642
|
+
spinners com cronômetro, tempo por repositório e relatório final
|
|
643
|
+
enriquecido.
|
|
644
|
+
|
|
645
|
+
### Alterado
|
|
646
|
+
|
|
647
|
+
- Refatoração interna: `index.ts` reduzido a orquestrador; novos
|
|
648
|
+
módulos `prompts`, `runner`, `report`, `env`, `preflight` e
|
|
649
|
+
`ui/{theme,strings}` (lf-0002).
|
|
650
|
+
|
|
651
|
+
## [0.1.0] - 2026-10-11
|
|
652
|
+
|
|
653
|
+
### Adicionado
|
|
654
|
+
|
|
655
|
+
- CLI interativa que clona/atualiza os 8 repositórios do ecossistema
|
|
656
|
+
Forge e executa `install.ps1` (Windows) ou `install.sh`
|
|
657
|
+
(Linux/macOS) (lf-0001).
|
|
658
|
+
- Fluxo `@clack/prompts`: diretório de destino (padrão
|
|
659
|
+
`./forge-workspace`), multiseleção, confirmação, spinners e
|
|
660
|
+
relatório `[OK]`/`[AVISO]`/`[ERRO]`.
|
|
661
|
+
- Cancelamento limpo via `isCancel()` e `SIGINT`.
|
|
662
|
+
- Testes `vitest` para o catálogo e utilitários de sistema.
|