raptor-aios 0.14.1 → 0.16.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 +196 -1
- package/README.md +15 -8
- package/dist/_core/dist/agents/prompt-builder.js +19 -5
- package/dist/_core/dist/audit/logger.js +16 -5
- package/dist/_core/dist/audit/schema.js +10 -0
- package/dist/_core/dist/audit/validate.js +15 -0
- package/dist/_core/dist/constitution/core.js +4 -0
- package/dist/_core/dist/constitution/index.js +1 -1
- package/dist/_core/dist/design/asset-normalize.js +10 -2
- package/dist/_core/dist/design/downloader.js +38 -2
- package/dist/_core/dist/design/fetcher.js +41 -9
- package/dist/_core/dist/design/figma-rest.js +5 -2
- package/dist/_core/dist/design/mapper.js +2 -0
- package/dist/_core/dist/design/scaffold.js +85 -8
- package/dist/_core/dist/design/screens.js +23 -1
- package/dist/_core/dist/frontmatter/index.js +26 -0
- package/dist/_core/dist/gates/builtin.js +32 -9
- package/dist/_core/dist/gates/design-gates.js +162 -9
- package/dist/_core/dist/gates/m7-gates.js +28 -7
- package/dist/_core/dist/jira/mapper.js +100 -2
- package/dist/_core/dist/models/checklist.js +10 -6
- package/dist/_core/dist/models/clarify.js +69 -1
- package/dist/_core/dist/models/impl-log.js +92 -0
- package/dist/_core/dist/prompts/resumption.js +16 -2
- package/dist/_core/dist/state/feature.js +1 -1
- package/dist/_core/dist/transport/mcp.js +1 -0
- package/dist/_core/dist/verify/fidelity.js +7 -3
- package/dist/_core/package.json +1 -1
- package/dist/_core/templates/plan.md.hbs +4 -6
- package/dist/_core/templates/raptor.yml.hbs +0 -1
- package/dist/_core/templates/spec.md.hbs +29 -19
- package/dist/commands/approve.js +27 -11
- package/dist/commands/clarify.js +2 -1
- package/dist/commands/design/fidelity.js +41 -10
- package/dist/commands/design/pull.js +9 -1
- package/dist/commands/design/status.js +42 -6
- package/dist/commands/design/sync.js +27 -6
- package/dist/commands/doctor.js +0 -8
- package/dist/commands/implement.js +118 -7
- package/dist/commands/new.js +37 -6
- package/dist/commands/preset/add.js +8 -12
- package/dist/commands/taskstoissues.js +16 -5
- package/dist/commands/trace.js +2 -0
- package/dist/commands/upgrade.js +10 -13
- package/dist/commands/verify/fidelity.js +4 -0
- package/dist/shared/design.js +2 -1
- package/dist/shared/init-core.js +2 -1
- package/dist/shared/project.js +4 -4
- package/dist/shared/scaffold.js +15 -16
- package/package.json +1 -1
- package/scripts/bash/common.sh +77 -31
- package/scripts/bash/create-new-feature.sh +75 -14
- package/scripts/bash/setup-plan.sh +3 -3
- package/scripts/check-naming.sh +16 -8
- package/scripts/powershell/common.ps1 +51 -9
- package/scripts/powershell/create-new-feature.ps1 +3 -1
- package/scripts/prepare-npm.mjs +1 -1
- package/templates/commands/analyze.md +1 -1
- package/templates/commands/clarify.md +31 -12
- package/templates/commands/constitution.md +3 -3
- package/templates/commands/implement.md +38 -9
- package/templates/commands/plan.md +2 -2
- package/templates/commands/specify.md +22 -4
- package/templates/plan-template.md +1 -1
- package/templates/spec-template.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,202 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
|
|
6
|
-
## [
|
|
6
|
+
## [0.16.0] - 2026-06-24
|
|
7
|
+
|
|
8
|
+
Correções P0/P1/P2 do re-teste KAN-20 v2 (DuCorre/FastBoy): fechar o **processo
|
|
9
|
+
de fidelidade** para o erro não nascer, não só ser pego no fim, e endurecer
|
|
10
|
+
rastreabilidade/determinismo nas pontas (resiliência de gate, hash unificado,
|
|
11
|
+
backbone de fidelidade instalado, clarify determinístico, drift acionável). Esta
|
|
12
|
+
versão também **entrega os dois deferidos** — constituição em **fonte única** e
|
|
13
|
+
**edições pós-implement auditadas** — e incorpora os achados de uma **revisão
|
|
14
|
+
integrada adversarial** que a suíte verde escondia (corrupção latente do
|
|
15
|
+
`audit.jsonl` por campo `undefined`; vazamento residual do laço de fidelidade).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`gate.design.anatomy`** (M3, _required_) — a `## Anatomy` (contrato de fidelidade)
|
|
20
|
+
precisa **nascer do pixel real**: cada tela com anatomia tem que registrar um
|
|
21
|
+
`figma_screenshot:` (capturado via `get_screenshot`) presente em disco e ≥ 1 KB. Sem
|
|
22
|
+
isso o gate bloqueia o verify — impede o contrato de nascer cego e o `implement`
|
|
23
|
+
construir fielmente a coisa errada (F10-2). Os prompts (`spec.md.hbs`, `/raptor-specify`)
|
|
24
|
+
agora mandam capturar o screenshot ANTES de autorar a anatomia.
|
|
25
|
+
- **Backbone de fidelidade instalado no projeto** — `init`/`upgrade` agora copiam
|
|
26
|
+
`scripts/review/{hex-scan,diff-tokens}.mjs` e `scripts/app/screenshot.sh` para
|
|
27
|
+
`.raptor/scripts/`, e o prompt do scorer referencia `.raptor/scripts/...`. Antes os
|
|
28
|
+
comandos do scorer apontavam para `scripts/...` que nenhum comando instalava — hex-scan
|
|
29
|
+
e o screenshot do app ficavam indisponíveis no caminho documentado (F8-1).
|
|
30
|
+
- **`implement --finalize` audita edições pós-implement** — quando o finalize absorve um
|
|
31
|
+
artefato que mudou após ser registrado (drift), emite o evento `implement.drift_detected`
|
|
32
|
+
(dedup por `(path|hash)`, idempotente), tornando a edição pós-Fase-7 visível no trilho de
|
|
33
|
+
audit em vez de reconciliada em silêncio. A detecção é pura (`reconstructImplLogFromAudit`
|
|
34
|
+
expõe os drifts); o `verify` segue read-only e o gate M7 segue puro (F7-untracked).
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- **`raptor design fidelity` virou laço fechado** — sai **não-zero** enquanto houver
|
|
39
|
+
região < threshold, scorecard com problema ou `design-decision` pendente, e **0** só na
|
|
40
|
+
convergência. Um laço de agente/CI itera até passar, em vez de a convergência depender
|
|
41
|
+
de disciplina manual (F10-3). O `evaluateFeatureFidelity` agora expõe
|
|
42
|
+
`pendingDesignDecisions`, e tanto o loop quanto a lente `raptor verify fidelity` passam a
|
|
43
|
+
reportar **não-ok** numa `design-decision` pendente — alinhados com `gate.design.fidelity`
|
|
44
|
+
(antes a lente dava verde enquanto o gate bloqueava).
|
|
45
|
+
- **`raptor design fidelity` converge ao veredito do gate (vazamento residual do F10-3)** — a
|
|
46
|
+
lente do laço só olhava os scorecards, então o comando podia sair **0** ("converged") enquanto
|
|
47
|
+
`raptor verify` falharia por falta de evidência de screenshot ou cobertura por região da
|
|
48
|
+
anatomia. Agora o comando roda `gate.design.fidelity` e `gate.design.anatomy` como autoridade
|
|
49
|
+
da convergência (só engajam com spec aprovada). Um laço de agente que parava no verde otimista
|
|
50
|
+
passa a iterar até o gate de fato passar.
|
|
51
|
+
- **Constituição em fonte única** — `.raptor/constitution.md` passa a ser a **única** cópia; o
|
|
52
|
+
espelho `.raptor/memory/constitution.md` foi removido. `init` não o escreve mais, `upgrade`
|
|
53
|
+
remove o órfão (com backup antes), e `preset add`/`doctor` só olham o canônico. Os prompts
|
|
54
|
+
(`templates/commands/*`, `plan.md.hbs`), o `scripts/bash/common.sh` (`CONSTITUTION_PATH`) e os
|
|
55
|
+
docs foram repointados; snapshots E2E regenerados. Fim da duplicação que o `upgrade` mantinha
|
|
56
|
+
em sync (F5-1b). _(Scripts PowerShell ficam como follow-up.)_
|
|
57
|
+
- **Skill `/raptor-clarify` alinhada ao caminho determinístico** — a skill 2.1.0 deixou de
|
|
58
|
+
hand-editar `## Clarifications` num formato (`### Session` + `- Q:→A:`) divergente do CLI;
|
|
59
|
+
agora emite `answers.json` e roda `raptor clarify --apply` (o CLI é o dono único da seção,
|
|
60
|
+
formato canônico `- **label** — answer`, UPSERT idempotente). Fim do spec híbrido de dois
|
|
61
|
+
escritores (F3-1/2). Os snapshots E2E passam a cobrir `clarify` (trava o formato).
|
|
62
|
+
- **Drift do impl-log agora é acionável** — `gate.m7.diff_hash_matches` já re-hasha o working
|
|
63
|
+
tree no `verify` e flaga arquivo que mudou/sumiu após ser registrado; a mensagem agora nomeia
|
|
64
|
+
a causa (**impl-log STALE** — edição pós-implement não re-logada) e a remediação
|
|
65
|
+
(`raptor implement --finalize` ou reverter) (F7-stale).
|
|
66
|
+
|
|
67
|
+
### Fixed
|
|
68
|
+
|
|
69
|
+
- **`.gitignore` não engole mais os assets do app** — o `--design-fetch` deixou de
|
|
70
|
+
ignorar os diretórios `assets/{images,icons,fonts}` inteiros; agora ignora apenas os
|
|
71
|
+
arquivos exatos que o RAPTOR baixou (bloco gerenciado único, idempotente). Assets
|
|
72
|
+
próprios do app (fontes, imagens de onboarding) param de sumir silenciosamente do
|
|
73
|
+
controle de versão (F1-2).
|
|
74
|
+
- **`/raptor-specify` não duplica mais uma feature já seedada** — `create-new-feature.sh`
|
|
75
|
+
ganhou guarda determinística por **slug** (espelhando o `raptor new`): um
|
|
76
|
+
`.raptor/specs/NNN-<slug>` existente é **reusado** (`REUSED: true`), não recriado com um
|
|
77
|
+
número novo que orfanaria a feature seedada (F2-1). `--number` e `--timestamp` optam por
|
|
78
|
+
fora.
|
|
79
|
+
- **`gate.design.ready` resiliente a YAML inválido** — um campo irmão malformado no
|
|
80
|
+
frontmatter de uma tela (ex.: um `screen_role` não-quotado escrito pelo agente) fazia o
|
|
81
|
+
`parseFrontmatter` lançar e o gate descartar o `name`, caindo no slug do arquivo que nunca
|
|
82
|
+
casa (`[screen: X]` reportado como ausente). Novo helper `frontmatterField()` lê o campo
|
|
83
|
+
por line-scan quando o parse YAML falha; usado em `gate.design.ready` e `gate.design.anatomy`
|
|
84
|
+
(F9-1).
|
|
85
|
+
- **Hash da constituição unificado** — dentro do mesmo comando, o prompt-context gravava o
|
|
86
|
+
hash whole-file enquanto `plan`/audit gravavam o hash do bloco RAPTOR-CORE. `buildCanonicalPrompt`
|
|
87
|
+
passa a registrar o **mesmo** core-block hash (helper `constitutionHashFromContent`), então
|
|
88
|
+
prompt e artefato concordam em "qual constituição governou isto" (F5-1).
|
|
89
|
+
- **Serializer do `audit.jsonl` robusto a `undefined`** — o `stableStringify` emitia
|
|
90
|
+
`"chave":undefined` (JSON inválido) para uma propriedade com valor `undefined`, corrompendo a
|
|
91
|
+
linha e a cadeia `prev_hash` a partir dela — e isso **disparava de fato** em `raptor plan`/`init`
|
|
92
|
+
num projeto sem preset. Agora omite chaves `undefined` e mapeia `undefined` em array para `null`
|
|
93
|
+
(espelhando `JSON.stringify`). Achado da revisão integrada adversarial.
|
|
94
|
+
- **`upgrade` não apaga mais o espelho da constituição sem backup** (`backup()` antes do `rmSync`),
|
|
95
|
+
e a regex que remove a chave morta `branch_prefix` foi **ancorada na coluna 0** (não casa uma
|
|
96
|
+
chave homônima aninhada sob outra seção). Achados da revisão integrada.
|
|
97
|
+
|
|
98
|
+
#### Polimento (P2)
|
|
99
|
+
|
|
100
|
+
- **`raptor approve` emite `spec.approved`** (evento distinto, não mais o genérico `spec.updated`) e
|
|
101
|
+
grava `audit_ref` no frontmatter apontando para o id da entrada do audit — link bidirecional
|
|
102
|
+
artefato↔audit (C4), antes sempre null (F4-1/F4-2).
|
|
103
|
+
- **`clarify --apply` faz backup do spec** antes de sobrescrever (a recuperação não depende mais só
|
|
104
|
+
do ledger/hashes) (F3-3).
|
|
105
|
+
- **Warning de scope não empurra mais `--design-scope file`** quando outras refs foram capturadas —
|
|
106
|
+
uma ref sem node-id agora vira aviso suave ("linke o frame específico"), e o escape hatch
|
|
107
|
+
file-wide só aparece quando NADA foi capturado (F1-warn).
|
|
108
|
+
- **Nome de tela duplicado é desambiguado** — dois frames Figma com o mesmo `name` ganham um sufixo
|
|
109
|
+
de node-id no 2º+ , para `[screen: X]` resolver para um arquivo só (F1-dup).
|
|
110
|
+
- **`audit` `file_keys` deduplicado**; **tabela `Post-Design Check` do `plan.md.hbs`** consertada
|
|
111
|
+
(separador/linha partidos quebravam a renderização) (F5-2).
|
|
112
|
+
- **`audit.jsonl` ganha cadeia tamper-evident** — cada evento grava `prev_hash` (sha256 da linha
|
|
113
|
+
anterior; null no genesis), e `raptor verify audit --strict` valida a cadeia e detecta adulteração
|
|
114
|
+
de eventos passados. Campo opcional no schema (back-compat com logs pré-cadeia) (prev_hash).
|
|
115
|
+
- **Branch do `/raptor-specify` (bash) alinhada à convenção do `raptor new`** — o branch agora carrega
|
|
116
|
+
o prefixo `feat/` (`feat/NNN-slug`); o diretório da spec continua o `NNN-slug` puro. `get_next_feature_number`
|
|
117
|
+
e `check_feature_branch` passam a entender o prefixo. `raptor upgrade` remove a chave morta
|
|
118
|
+
`branch_prefix` de projetos legados e o label "legacy cleanup" só aparece quando algo foi de fato
|
|
119
|
+
limpo (F2-2 / F0-2 / F0-3).
|
|
120
|
+
- **`raptor design status --check`** valida o token figma-rest de verdade (chamada `/v1/me`): um token
|
|
121
|
+
clobberado/revogado-mas-presente agora reporta NOT connected, em vez do falso "connected" por
|
|
122
|
+
presença (F0-1).
|
|
123
|
+
|
|
124
|
+
## [0.15.0] - 2026-06-22
|
|
125
|
+
|
|
126
|
+
Tema do épico KAN-20: **determinismo e consentimento**. Tudo que antes "passava em
|
|
127
|
+
silêncio", "escrevia no card sem perguntar" ou "despejava o arquivo Figma inteiro"
|
|
128
|
+
agora é explícito, opt-in ou escopado por node.
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- **`--design-scope node|file`** (em `new`, `design sync`, `design pull`). Controla o
|
|
133
|
+
escopo da captura de telas/assets. Em `new` e `design sync` o default é `node` (só o
|
|
134
|
+
frame linkado pela URL); em `design pull` o default é `file` (inspeção do arquivo
|
|
135
|
+
inteiro). `--design-scope file` é o escape hatch para o comportamento legado (arquivo
|
|
136
|
+
todo). Uma ref COM node-id captura aquele node sob qualquer scope.
|
|
137
|
+
- **`raptor new --activate` / `--no-activate`.** `new` agora aponta
|
|
138
|
+
`.raptor/feature.json` para a feature recém-criada (default ligado). `--no-activate`
|
|
139
|
+
preserva a feature anterior.
|
|
140
|
+
- **`--sync-jira`** (em `approve`, `implement`, `taskstoissues`). Opt-in para escrever
|
|
141
|
+
de volta no card Jira (comentário de ciclo de vida + transição; em `taskstoissues`,
|
|
142
|
+
comentário-resumo dos issues criados). Sem a flag, só loga a ação pretendida.
|
|
143
|
+
- **`raptor implement --finalize`.** Reconstrói `impl-log.md` a partir dos eventos
|
|
144
|
+
`code.generated` do `audit.jsonl` quando as tasks foram fechadas sem `--task`.
|
|
145
|
+
Recupera traces de AC das tags `[AC-#]` do `tasks.md`; quando não há, deixa vazio
|
|
146
|
+
para o gate M7 cobrar (não fabrica pass). Respeita `--dry-run`.
|
|
147
|
+
|
|
148
|
+
### Changed
|
|
149
|
+
|
|
150
|
+
- **`--design-fetch` agora é escopo por node-id por default** (era arquivo inteiro,
|
|
151
|
+
até 107 telas). Card sem node-id pula a captura com aviso acionável; a spec referencia
|
|
152
|
+
`assets-manifest.json` em vez de listar tudo. **Migração:** use `--design-scope file`
|
|
153
|
+
para o comportamento antigo.
|
|
154
|
+
- **`raptor new` ativa a feature criada por default.** `feature.json` é dobrado no
|
|
155
|
+
auto-commit para empilhar specs sem sujar a árvore. **Migração:** use `--no-activate`
|
|
156
|
+
se contava com a feature anterior continuar ativa.
|
|
157
|
+
- **Escrita no Jira é opt-in (`--sync-jira`)** em `approve`/`implement`/`taskstoissues`.
|
|
158
|
+
**Migração:** pipelines que esperavam status-sync automático precisam adicionar a flag.
|
|
159
|
+
- **`gate.design.fidelity` bloqueia por scorecards.** Avalia scorecards sempre que
|
|
160
|
+
existirem; retorna `skipped` (nunca mais `pass` falso) só quando não há scorecards
|
|
161
|
+
nem impl-log. **Migração:** projetos cujo fidelity passava verde sem evidência podem
|
|
162
|
+
ver o gate bloquear agora.
|
|
163
|
+
- **Gates M7 falham (não skipam) quando há tasks concluídas mas falta `impl-log.md`.**
|
|
164
|
+
Sem tasks concluídas continua `skipped`. A skill `/raptor-implement` foi reescrita
|
|
165
|
+
para registrar cada task via `raptor implement --task`.
|
|
166
|
+
- **Namespace de specs unificado em `.raptor/specs`.** `get_next_feature_number` deixa
|
|
167
|
+
de saltar numeração por varrer `specs/` legado (002 → 011); legado vira só fallback
|
|
168
|
+
de leitura. Espelhado no PowerShell.
|
|
169
|
+
- **`branch_prefix` removido do schema/`raptor.yml`** (dead config, 0 leituras).
|
|
170
|
+
**Migração:** pode remover do `raptor.yml` — não tinha efeito.
|
|
171
|
+
- **stderr do MCP silenciado por default** (`StdioClientTransport` usa `stderr:'ignore'`).
|
|
172
|
+
Reative diagnóstico com `RAPTOR_MCP_DEBUG=1`.
|
|
173
|
+
- **Assets do fetch isolados com `.gitignore` RAPTOR-managed + guarda de colisão** —
|
|
174
|
+
binários baixados não entram em versionamento por acidente; a captura nunca
|
|
175
|
+
sobrescreve arte já versionada.
|
|
176
|
+
|
|
177
|
+
### Fixed
|
|
178
|
+
|
|
179
|
+
- **Falso negativo de cobertura de US eliminado** — `gate.spec.ready` normaliza IDs:
|
|
180
|
+
header `US-001` casa com `[US1]`.
|
|
181
|
+
- **Markers falsos não acusam mais** — detector ignora code-spans (`` `[PREENCHER]` ``)
|
|
182
|
+
e linhas de checklist negadoras; helper compartilhado em spec/plan/tasks.ready.
|
|
183
|
+
- **Checklist sem ID conta corretamente** — parser conta itens `[x]` mesmo sem `CK-#`
|
|
184
|
+
(contido em `status`; gates estruturados inalterados).
|
|
185
|
+
- **Path in-place no redesign** — `gate.tasks.ready` herda path por fase; reset por
|
|
186
|
+
heading preserva detecção de drift.
|
|
187
|
+
- **Hash stale agora orienta a correção** — `gate.plan/tasks.input_hash` sugere
|
|
188
|
+
`raptor resync <feature> --artifact plan|tasks`.
|
|
189
|
+
- **`clarify --apply` popula `## Clarifications`** (upsert das Q&A; CLI é dono único,
|
|
190
|
+
skill só produz `answers.json`), preserva frontmatter e não mascara marker real.
|
|
191
|
+
- **Wiki-markup do Jira → Markdown no seeding** — `flattenAdf` converte `{quote}`,
|
|
192
|
+
`{{...}}`, `||tabela||`, `hN.`, `{code}`/`{noformat}`, gateado por detecção de wiki
|
|
193
|
+
(não toca Markdown existente; preserva `5 * 4`).
|
|
194
|
+
- **Ator canônico único no audit** — `currentActor` fixa o `user` no `git user.email`
|
|
195
|
+
(fallback name → `USER`).
|
|
196
|
+
- **`HAS_GIT` correto no `--dry-run`** — `create-new-feature.sh` detecta git antes da
|
|
197
|
+
bifurcação dry-run. Skill `specify` passa `--short-name` (slug kebab-case).
|
|
198
|
+
- **Robustez do seeding de design** — tokens vazios direcionam ao MCP `get_variable_defs`
|
|
199
|
+
e o gate bloqueia `tokens.json` vazio (`radii` sempre presente); `gate.design.ready`
|
|
200
|
+
casa telas por `name` do frontmatter (tolerante a sufixo node-id); `design sync`
|
|
201
|
+
avisa (não apaga) telas órfãs da convenção antiga de slug.
|
|
7
202
|
|
|
8
203
|
## [0.14.1] - 2026-06-21
|
|
9
204
|
|
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
[](https://www.npmjs.com/package/raptor-aios)
|
|
8
8
|
[](https://nodejs.org)
|
|
9
9
|
[](https://reactnative.dev)
|
|
10
|
-
[](#️-desenvolvimento-local)
|
|
11
|
+
[](CHANGELOG.md)
|
|
12
12
|
[](LICENSE)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
@@ -24,7 +24,7 @@ npm install -g raptor-aios
|
|
|
24
24
|
cd meu-app-react-native
|
|
25
25
|
raptor setup # 🧭 assistente guiado: IA/IDE, preset, Jira/Figma (ou 'raptor init' direto)
|
|
26
26
|
raptor new login-biometrico -d "Permitir login com Face ID / digital"
|
|
27
|
-
# → cria a feature
|
|
27
|
+
# → cria a feature, forka a branch feat/001-login-biometrico do HEAD e a ativa
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
---
|
|
@@ -201,7 +201,7 @@ Isolamento e coerência **sem configurar nada** — o `init` já deixa tudo pron
|
|
|
201
201
|
|
|
202
202
|
### Uma branch por unidade de trabalho
|
|
203
203
|
|
|
204
|
-
`raptor new` cria e troca para a branch certa,
|
|
204
|
+
`raptor new` cria e troca para a branch certa, **forkando do HEAD atual** (modelo empilhado, não de `main`): cada nova feature ramifica de onde você está, então specs se **empilham** umas sobre as outras. Ao final, a feature recém-criada vira a **ativa** em `.raptor/feature.json` (comandos seguintes a resolvem sem `--feature`); use `--no-activate` para manter a feature anterior ativa (`--activate` é o default).
|
|
205
205
|
|
|
206
206
|
| Você roda | Branch criada |
|
|
207
207
|
| -------------------------------------- | --------------------------------------------------- |
|
|
@@ -210,7 +210,7 @@ Isolamento e coerência **sem configurar nada** — o `init` já deixa tudo pron
|
|
|
210
210
|
| `raptor new refactor-auth --kind=debt` | `debt/003-refactor-auth` |
|
|
211
211
|
| `raptor new login --jira APP-1234` | `feat/004-login.app-1234` *(tipo inferido do Jira)* |
|
|
212
212
|
|
|
213
|
-
Numeração sequencial global, prefixo por tipo (`feat`/`bugfix`/`debt`), e a chave do Jira no fim quando houver. Já vem ligado (`git.branch_per_feature`); desligue com `--no-branch` num comando ou `no_git` no projeto.
|
|
213
|
+
Numeração sequencial global, prefixo por tipo (`feat`/`bugfix`/`debt`), e a chave do Jira no fim quando houver. Já vem ligado (`git.branch_per_feature`); desligue com `--no-branch` num comando ou `no_git` no projeto. Por padrão a spec scaffoldada é **commitada** na própria branch (para o próximo `raptor new` empilhar em cima); `--no-commit` deixa a spec sem commit.
|
|
214
214
|
|
|
215
215
|
### Commits coerentes, sem travar seu fluxo
|
|
216
216
|
|
|
@@ -237,7 +237,7 @@ Você os executa **dentro do seu agente de IA**. São prompts versionados e rico
|
|
|
237
237
|
|
|
238
238
|
### 🧭 `/raptor-constitution`
|
|
239
239
|
|
|
240
|
-
Cria ou atualiza os princípios do projeto (`.raptor/
|
|
240
|
+
Cria ou atualiza os princípios do projeto (`.raptor/constitution.md`). É o "contrato social" que todas as fases seguintes respeitam. Use no início, ou quando quiser ratificar uma mudança de regra (com bump de versão).
|
|
241
241
|
|
|
242
242
|
### 📝 `/raptor-specify`
|
|
243
243
|
|
|
@@ -271,9 +271,11 @@ Gera checklists de qualidade temáticos (code quality, testes, segurança, perfo
|
|
|
271
271
|
|
|
272
272
|
Executa as tarefas em ordem de fase, respeitando paralelização e (se exigido) TDD. Marca cada tarefa `[x]` ao concluir e registra a **evidência M7** (qual arquivo cobre qual critério de aceitação).
|
|
273
273
|
|
|
274
|
+
> ⚠️ **`impl-log.md` é inegociável.** Cada `raptor implement --task <id> --files … --acceptance …` atualiza o `impl-log.md` com as execuções, artefatos e traces. Se ele estiver **ausente após a implementação**, os gates M7 **bloqueiam** o `verify` (deixam de "pular" por arquivo inexistente e passam a falhar). Se o agente esqueceu de registrar tarefa por tarefa via `--task`, rode `raptor implement <feat> --finalize` para **reconstruir** o `impl-log.md` a partir dos eventos `code.generated` do `audit.jsonl` — traces de aceitação ausentes ficam vazios para o gate M7 cobrar (complete-os com `--amend --acceptance <AC-#>`).
|
|
275
|
+
|
|
274
276
|
### 🎫 `/raptor-taskstoissues`
|
|
275
277
|
|
|
276
|
-
Converte as tarefas em issues de um tracker (GitHub via MCP, Jira via integração).
|
|
278
|
+
Converte as tarefas em issues de um tracker (GitHub via MCP, Jira via integração). Criar as issues é o propósito do comando, mas **escrever de volta no card pai** (comentário de resumo) é **opt-in via `--sync-jira`** — sem a flag, nada é mutado no ticket de origem.
|
|
277
279
|
|
|
278
280
|
### 🌿 `/raptor-commit-review`
|
|
279
281
|
|
|
@@ -521,6 +523,8 @@ raptor jira pull APP-1234 # importa a issue
|
|
|
521
523
|
raptor new login --jira APP-1234 # semeia a spec a partir da issue
|
|
522
524
|
```
|
|
523
525
|
|
|
526
|
+
> 🔒 **Escrita no Jira é opt-in.** O `raptor new --jira` só **lê** o card. Os comandos do ciclo que poderiam escrever de volta no ticket de origem (`approve`, `implement`, `taskstoissues`) **só mutam o Jira** — comentário de lifecycle e transição opcional — quando você passa **`--sync-jira`**. Sem a flag, o Raptor apenas anuncia o que faria.
|
|
527
|
+
|
|
524
528
|
> 📖 Como o card é capturado **por inteiro** (campos ricos, links de Figma) e redistribuído na spec — com os gates cobrando: veja [docs/jira-spec-enrichment.md](docs/jira-spec-enrichment.md).
|
|
525
529
|
|
|
526
530
|
### 🎨 Integração com Figma (design determinístico)
|
|
@@ -535,6 +539,8 @@ raptor design sync home # re-captura idempotente (nunca sobrescreve
|
|
|
535
539
|
|
|
536
540
|
O provider **`figma-rest`** usa a REST API da Figma com um PAT (`${FIGMA_TOKEN}` no `raptor.yml`), é determinístico e roda em CI. A captura gera `design/tokens.json` (colors/typography/spacing/radii), `design/screens/<slug>.md` (frames de topo) e `design/assets-manifest.json` (assets baixados com SHA-256, guarda anti-SSRF). Quando há captura real, os prompts fazem o **flip REDISTRIBUIR**: o agente passa a *refinar e redistribuir* os artefatos em disco em vez de "olhar o Figma" — cobrado pelo `gate.design.ready`.
|
|
537
541
|
|
|
542
|
+
A captura no `raptor new` é **opt-in via `--design-fetch`** (default ligado): com o Figma conectado, ela puxa tokens/telas/assets e semeia `design/`; com `--no-design-fetch` apenas scaffolda o link, sem buscar. O **escopo** é controlado por `--design-scope`: `node` (default) captura **só o frame linkado** (o node-id na URL); `file` é uma escotilha de fuga que puxa o **arquivo Figma inteiro** (comportamento legado). O mesmo `--design-scope` vale para `raptor design sync`.
|
|
543
|
+
|
|
538
544
|
> 🎯 **Fidelidade visual:** depois do `implement`, um loop garante que a tela **replica** o Figma (anatomia + nota por micro-região + `gate.design.fidelity`) — veja [🎯 Fidelidade visual (Figma → React Native)](#-fidelidade-visual-figma--react-native).
|
|
539
545
|
|
|
540
546
|
> 📖 Detalhes, providers e limitações: [docs/design-figma-sync.md](docs/design-figma-sync.md) e [docs/design-gate.md](docs/design-gate.md).
|
|
@@ -557,7 +563,7 @@ O provider **`figma-rest`** usa a REST API da Figma com um PAT (`${FIGMA_TOKEN}`
|
|
|
557
563
|
🎨 Figma/Design design connect · status · pull · sync · disconnect
|
|
558
564
|
```
|
|
559
565
|
|
|
560
|
-
Rode `raptor <comando> --help` para os detalhes (flags, exemplos) de cada um.
|
|
566
|
+
Rode `raptor <comando> --help` para os detalhes (flags, exemplos) de cada um, ou veja a **[📖 Referência de flags (docs/cli-reference.md)](docs/cli-reference.md)** — flags relevantes e novas da v0.15.0, por comando, com defaults.
|
|
561
567
|
|
|
562
568
|
### 💬 Quando você erra um comando
|
|
563
569
|
|
|
@@ -622,6 +628,7 @@ Comece pelo **[📖 Glossário Canônico](docs/glossary.md)** — a referência
|
|
|
622
628
|
| Documento | Sobre |
|
|
623
629
|
| ------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
624
630
|
| **[📖 docs/glossary.md](docs/glossary.md)** | Glossário canônico: conceitos, hierarquias, catálogo de 48 gates e de comandos CLI |
|
|
631
|
+
| [📖 docs/cli-reference.md](docs/cli-reference.md) | Referência de flags por comando (novas/relevantes da v0.15.0) com defaults |
|
|
625
632
|
| [🧭 docs/setup-wizard.md](docs/setup-wizard.md) | Assistente de instalação `raptor setup`: onboarding, IA/IDE, preset, Jira/Figma |
|
|
626
633
|
| [🛡️ docs/readiness-gates.md](docs/readiness-gates.md) | Gates de prontidão (`spec/plan/tasks.ready`) em detalhe |
|
|
627
634
|
| [🎨 docs/design-figma-sync.md](docs/design-figma-sync.md) | Captura determinística do Figma (`figma-rest`): tokens/telas/assets/fontes + flip REDISTRIBUIR |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { hashString, hashFile } from "../audit/hash.js";
|
|
4
|
+
import { constitutionHashFromContent } from "../constitution/core.js";
|
|
4
5
|
import { wrapSpec, wrapUntrusted } from "./prompt-markers.js";
|
|
5
6
|
const PHASE_MAP = {
|
|
6
7
|
new: {
|
|
@@ -80,14 +81,14 @@ const PHASE_MAP = {
|
|
|
80
81
|
"2. Extraia do Figma os valores REAIS do nó com `get_design_context` / `get_variable_defs` /",
|
|
81
82
|
" `get_screenshot`. REGRA DE OURO: o nó vence a percepção — nunca chute px/cor/raio/peso.",
|
|
82
83
|
"3. Capture o app RODANDO (evidência, não invente): rode",
|
|
83
|
-
"
|
|
84
|
+
" `.raptor/scripts/app/screenshot.sh design/visual-review/_shots/<tela>.<estado>.app.png` e salve o",
|
|
84
85
|
" screenshot do Figma como `…<estado>.figma.png`.",
|
|
85
86
|
"4. Compare Figma×app POR MICRO-REGIÃO e pontue 60–100 em cada dimensão",
|
|
86
87
|
" (layout, spacing, color, typography, dimensions, icon, states). A nota da região = a",
|
|
87
88
|
" dimensão MAIS FRACA (o elo). Mínimo de aprovação = 95 (ou o threshold do preset).",
|
|
88
|
-
"5. Backbone determinístico (não falsifique): rode `node scripts/review/hex-scan.mjs src --json`",
|
|
89
|
+
"5. Backbone determinístico (não falsifique): rode `node .raptor/scripts/review/hex-scan.mjs src --json`",
|
|
89
90
|
" e preencha `deterministic.hex_hardcoded` com o resultado REAL; rode",
|
|
90
|
-
" `node scripts/review/diff-tokens.mjs <figma-tokens.json> design/tokens.json --json` p/ o drift.",
|
|
91
|
+
" `node .raptor/scripts/review/diff-tokens.mjs <figma-tokens.json> design/tokens.json --json` p/ o drift.",
|
|
91
92
|
"6. Registre cada divergência `{ dimension, figma, rn, fix, severity, status }`. `status`:",
|
|
92
93
|
" `resolved` (corrigida), `open` (ainda falha) ou `design-decision` (desvio semântico —",
|
|
93
94
|
" troca de ícone/cor/copy — que EXIGE aprovação humana antes de passar o gate).",
|
|
@@ -158,6 +159,9 @@ const PHASE_MAP = {
|
|
|
158
159
|
"6. Se a feature tiver design (pasta `design/`): consuma os assets já catalogados em",
|
|
159
160
|
" `design/assets-manifest.json` (baixados em `assets/{images,icons,fonts}`) e baixe via",
|
|
160
161
|
" Figma MCP só o que faltar; referencie por caminho concreto (tags `[screen: X]` / `[asset: X]`).",
|
|
162
|
+
"7. PRESERVE o frontmatter existente de `tasks.md` ao reescrevê-lo — em especial o",
|
|
163
|
+
" campo `plan_hash` (e qualquer outro já presente). NÃO o remova nem o reescreva:",
|
|
164
|
+
" o gate de staleness o usa para detectar `tasks.md` desatualizado. (NÃO NEGOCIÁVEL)",
|
|
161
165
|
].join("\n"),
|
|
162
166
|
},
|
|
163
167
|
implement: {
|
|
@@ -215,6 +219,13 @@ const PHASE_MAP = {
|
|
|
215
219
|
"",
|
|
216
220
|
"### Saída esperada:",
|
|
217
221
|
'Um arquivo JSON contendo um array de objetos `{ "label": string, "answer": string }`.',
|
|
222
|
+
"",
|
|
223
|
+
"### Regras de escrita (NÃO NEGOCIÁVEL):",
|
|
224
|
+
"1. Produza APENAS `answers.json`. NÃO edite `spec.md` — nem a seção",
|
|
225
|
+
" `## Clarifications` nem os marcadores inline.",
|
|
226
|
+
"2. A escrita no spec é determinística e exclusiva do CLI (`raptor clarify --apply`):",
|
|
227
|
+
" ele resolve os marcadores e popula `## Clarifications` a partir do seu `answers.json`.",
|
|
228
|
+
" Editar o spec aqui causaria dupla-escrita e regrediria o determinismo.",
|
|
218
229
|
].join("\n"),
|
|
219
230
|
},
|
|
220
231
|
analyze: {
|
|
@@ -274,7 +285,10 @@ export function buildCanonicalPrompt(input) {
|
|
|
274
285
|
? join(raptorDir, "constitution.md")
|
|
275
286
|
: join(featureDir, inp.filename);
|
|
276
287
|
if (existsSync(path)) {
|
|
277
|
-
|
|
288
|
+
const hash = inp.filename === "constitution.md"
|
|
289
|
+
? constitutionHashFromContent(readFileSync(path, "utf8"))
|
|
290
|
+
: hashFile(path);
|
|
291
|
+
inputs.push({ path: inp.filename, hash, role: inp.role });
|
|
278
292
|
}
|
|
279
293
|
}
|
|
280
294
|
let body = phase.bodyTemplate;
|
|
@@ -57,15 +57,25 @@ export function nextSequence(jsonlPath) {
|
|
|
57
57
|
}
|
|
58
58
|
return lines.length + 1;
|
|
59
59
|
}
|
|
60
|
+
export function lastEventHash(jsonlPath) {
|
|
61
|
+
if (!existsSync(jsonlPath))
|
|
62
|
+
return null;
|
|
63
|
+
const trimmed = readFileSync(jsonlPath, "utf8").trimEnd();
|
|
64
|
+
if (trimmed.length === 0)
|
|
65
|
+
return null;
|
|
66
|
+
const lines = trimmed.split("\n");
|
|
67
|
+
return hashString(lines[lines.length - 1]);
|
|
68
|
+
}
|
|
60
69
|
function stableStringify(obj) {
|
|
61
70
|
if (obj === null || typeof obj !== "object")
|
|
62
71
|
return JSON.stringify(obj);
|
|
63
72
|
if (Array.isArray(obj))
|
|
64
|
-
return "[" +
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
return ("[" +
|
|
74
|
+
obj.map((v) => (v === undefined ? "null" : stableStringify(v))).join(",") +
|
|
75
|
+
"]");
|
|
76
|
+
const o = obj;
|
|
77
|
+
const keys = Object.keys(o).filter((k) => o[k] !== undefined).sort();
|
|
78
|
+
const parts = keys.map((k) => JSON.stringify(k) + ":" + stableStringify(o[k]));
|
|
69
79
|
return "{" + parts.join(",") + "}";
|
|
70
80
|
}
|
|
71
81
|
export function appendAuditEvent(jsonlPath, event) {
|
|
@@ -75,6 +85,7 @@ export function appendAuditEvent(jsonlPath, event) {
|
|
|
75
85
|
id: id ?? uuidv7(),
|
|
76
86
|
sequence: sequence ?? nextSequence(jsonlPath),
|
|
77
87
|
ts: ts ?? new Date().toISOString(),
|
|
88
|
+
prev_hash: lastEventHash(jsonlPath),
|
|
78
89
|
...rest,
|
|
79
90
|
};
|
|
80
91
|
assertC5(full);
|
|
@@ -27,6 +27,10 @@ export const auditEventSchema = {
|
|
|
27
27
|
format: "date-time",
|
|
28
28
|
description: "ISO 8601 with timezone, millisecond precision recommended.",
|
|
29
29
|
},
|
|
30
|
+
prev_hash: {
|
|
31
|
+
type: ["string", "null"],
|
|
32
|
+
description: "sha256 of the previous event's serialized line — a tamper-evident chain; null for the genesis event. Optional for back-compat with pre-chain logs.",
|
|
33
|
+
},
|
|
30
34
|
event: {
|
|
31
35
|
type: "string",
|
|
32
36
|
enum: [
|
|
@@ -34,6 +38,7 @@ export const auditEventSchema = {
|
|
|
34
38
|
"feature.archived",
|
|
35
39
|
"spec.generated",
|
|
36
40
|
"spec.updated",
|
|
41
|
+
"spec.approved",
|
|
37
42
|
"clarify.added",
|
|
38
43
|
"clarify.scanned",
|
|
39
44
|
"clarify.applied",
|
|
@@ -48,6 +53,7 @@ export const auditEventSchema = {
|
|
|
48
53
|
"checklist.generated",
|
|
49
54
|
"checklist.created",
|
|
50
55
|
"task.completed",
|
|
56
|
+
"implement.drift_detected",
|
|
51
57
|
"gate.evaluated",
|
|
52
58
|
"gate.skipped",
|
|
53
59
|
"gate.approved",
|
|
@@ -152,6 +158,10 @@ export const auditEventSchema = {
|
|
|
152
158
|
if: { properties: { event: { const: "code.generated" } } },
|
|
153
159
|
then: { required: ["outputs", "task"] },
|
|
154
160
|
},
|
|
161
|
+
{
|
|
162
|
+
if: { properties: { event: { const: "implement.drift_detected" } } },
|
|
163
|
+
then: { required: ["outputs", "detector"] },
|
|
164
|
+
},
|
|
155
165
|
{
|
|
156
166
|
if: {
|
|
157
167
|
properties: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { Ajv2020, } from "ajv/dist/2020.js";
|
|
3
3
|
import { auditEventSchema } from "./schema.js";
|
|
4
|
+
import { hashString } from "./hash.js";
|
|
4
5
|
let cachedValidator = null;
|
|
5
6
|
function getValidator() {
|
|
6
7
|
if (cachedValidator)
|
|
@@ -75,6 +76,7 @@ export function validateAuditFile(jsonlPath, opts = {}) {
|
|
|
75
76
|
let validLines = 0;
|
|
76
77
|
let prevSequence = 0;
|
|
77
78
|
let prevTs = "";
|
|
79
|
+
let prevRaw = null;
|
|
78
80
|
for (let i = 0; i < lines.length; i++) {
|
|
79
81
|
const lineNum = i + 1;
|
|
80
82
|
const raw = lines[i];
|
|
@@ -87,6 +89,8 @@ export function validateAuditFile(jsonlPath, opts = {}) {
|
|
|
87
89
|
});
|
|
88
90
|
continue;
|
|
89
91
|
}
|
|
92
|
+
const prevLine = prevRaw;
|
|
93
|
+
prevRaw = raw;
|
|
90
94
|
let parsed;
|
|
91
95
|
try {
|
|
92
96
|
parsed = JSON.parse(raw);
|
|
@@ -125,6 +129,17 @@ export function validateAuditFile(jsonlPath, opts = {}) {
|
|
|
125
129
|
keyword: "monotonicTs",
|
|
126
130
|
});
|
|
127
131
|
}
|
|
132
|
+
if (obj.prev_hash !== undefined) {
|
|
133
|
+
const expected = prevLine === null ? null : hashString(prevLine);
|
|
134
|
+
if (obj.prev_hash !== expected) {
|
|
135
|
+
errors.push({
|
|
136
|
+
line: lineNum,
|
|
137
|
+
path: "/prev_hash",
|
|
138
|
+
message: `prev_hash chain broken — expected ${expected ?? "null (genesis)"}, got ${obj.prev_hash ?? "null"}`,
|
|
139
|
+
keyword: "chain",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
128
143
|
prevSequence = obj.sequence;
|
|
129
144
|
prevTs = obj.ts;
|
|
130
145
|
}
|
|
@@ -89,6 +89,10 @@ export function extractCoreBlock(source) {
|
|
|
89
89
|
endIndex: endMarkerIdx + CORE_END_MARKER.length,
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
+
export function constitutionHashFromContent(content) {
|
|
93
|
+
const extracted = extractCoreBlock(content);
|
|
94
|
+
return extracted ? extracted.declaredHash : hashString(content);
|
|
95
|
+
}
|
|
92
96
|
export function rewriteCoreBlock(source) {
|
|
93
97
|
const canonical = coreBlock();
|
|
94
98
|
const extracted = extractCoreBlock(source);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { CORE_VERSION, CORE_ARTICLES_TEXT, CORE_END_MARKER, CORE_START_MARKER_PREFIX, CORE_START_MARKER_RE, coreBlock, coreHash, extractCoreBlock, rewriteCoreBlock, } from "./core.js";
|
|
1
|
+
export { CORE_VERSION, CORE_ARTICLES_TEXT, CORE_END_MARKER, CORE_START_MARKER_PREFIX, CORE_START_MARKER_RE, coreBlock, coreHash, constitutionHashFromContent, extractCoreBlock, rewriteCoreBlock, } from "./core.js";
|
|
@@ -7,6 +7,7 @@ export function normalizeImagesToAssets(renders, fills, opts = {}) {
|
|
|
7
7
|
const renderType = classifyAssetType(fmt);
|
|
8
8
|
const renderExt = extForFormat(fmt);
|
|
9
9
|
const renderDir = assetDirFor(renderType);
|
|
10
|
+
const screen = opts.screen?.trim() ? opts.screen.trim() : undefined;
|
|
10
11
|
for (const [nodeId, url] of Object.entries(renders)) {
|
|
11
12
|
if (!url)
|
|
12
13
|
continue;
|
|
@@ -18,7 +19,14 @@ export function normalizeImagesToAssets(renders, fills, opts = {}) {
|
|
|
18
19
|
path = `${renderDir}/${base}.${renderExt}`;
|
|
19
20
|
}
|
|
20
21
|
seen.add(path);
|
|
21
|
-
out.push({
|
|
22
|
+
out.push({
|
|
23
|
+
name: human || base,
|
|
24
|
+
type: renderType,
|
|
25
|
+
path,
|
|
26
|
+
url,
|
|
27
|
+
figma_node_id: nodeId,
|
|
28
|
+
...(screen ? { screen } : {}),
|
|
29
|
+
});
|
|
22
30
|
}
|
|
23
31
|
for (const [ref, url] of Object.entries(fills)) {
|
|
24
32
|
if (!url)
|
|
@@ -31,7 +39,7 @@ export function normalizeImagesToAssets(renders, fills, opts = {}) {
|
|
|
31
39
|
path = `assets/images/${base}.${ext}`;
|
|
32
40
|
}
|
|
33
41
|
seen.add(path);
|
|
34
|
-
out.push({ name: base, type: "image", path, url });
|
|
42
|
+
out.push({ name: base, type: "image", path, url, ...(screen ? { screen } : {}) });
|
|
35
43
|
}
|
|
36
44
|
return out;
|
|
37
45
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
4
|
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
@@ -64,16 +65,31 @@ export async function downloadAll(pending, root, opts = {}) {
|
|
|
64
65
|
if (isAbsolute(rel) || (within !== rootAbs && !within.startsWith(rootAbs + sep))) {
|
|
65
66
|
throw new DownloadError(`asset path escapes project root: ${a.path}`, 0);
|
|
66
67
|
}
|
|
68
|
+
if (isGitTracked(rootAbs, rel)) {
|
|
69
|
+
const w = `asset "${a.name}" colidiria com arquivo versionado ${rel} — não sobrescrito`;
|
|
70
|
+
if (opts.onWarn)
|
|
71
|
+
opts.onWarn(w);
|
|
72
|
+
else
|
|
73
|
+
process.stderr.write(`Warning: ${w}\n`);
|
|
74
|
+
results[i] = { ...base, status: "pending" };
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
67
77
|
const destAbs = join(root, rel);
|
|
68
78
|
const { hash } = await downloadAsset(a.url, destAbs, opts);
|
|
69
79
|
results[i] = { ...base, hash, status: "downloaded" };
|
|
70
80
|
}
|
|
71
81
|
catch (err) {
|
|
72
82
|
const msg = err instanceof Error ? err.message : String(err);
|
|
83
|
+
const where = assetWhere(a);
|
|
84
|
+
const oversized = err instanceof DownloadError && /exceeds max_bytes/.test(msg);
|
|
85
|
+
const advice = oversized
|
|
86
|
+
? " — pending, fora do manifest; eleve design.assets.max_bytes ou rebaixe via MCP"
|
|
87
|
+
: "";
|
|
88
|
+
const line = `asset "${a.name}"${where} not downloaded: ${msg}${advice}`;
|
|
73
89
|
if (opts.onWarn)
|
|
74
|
-
opts.onWarn(
|
|
90
|
+
opts.onWarn(line);
|
|
75
91
|
else
|
|
76
|
-
process.stderr.write(`Warning:
|
|
92
|
+
process.stderr.write(`Warning: ${line}\n`);
|
|
77
93
|
results[i] = { ...base, status: "pending" };
|
|
78
94
|
}
|
|
79
95
|
}
|
|
@@ -81,6 +97,26 @@ export async function downloadAll(pending, root, opts = {}) {
|
|
|
81
97
|
await Promise.all(Array.from({ length: Math.min(concurrency, pending.length) }, worker));
|
|
82
98
|
return results;
|
|
83
99
|
}
|
|
100
|
+
function isGitTracked(rootAbs, rel) {
|
|
101
|
+
try {
|
|
102
|
+
const r = spawnSync("git", ["ls-files", "--error-unmatch", "--", rel], {
|
|
103
|
+
cwd: rootAbs,
|
|
104
|
+
stdio: "ignore",
|
|
105
|
+
});
|
|
106
|
+
return r.status === 0;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function assetWhere(a) {
|
|
113
|
+
const parts = [];
|
|
114
|
+
if (a.screen)
|
|
115
|
+
parts.push(`screen: ${a.screen}`);
|
|
116
|
+
if (a.figma_node_id)
|
|
117
|
+
parts.push(`node ${a.figma_node_id}`);
|
|
118
|
+
return parts.length ? ` (${parts.join(", ")})` : "";
|
|
119
|
+
}
|
|
84
120
|
function assertSafeAssetUrl(url, allowHttp) {
|
|
85
121
|
let u;
|
|
86
122
|
try {
|