raptor-aios 0.14.0 → 0.14.1
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 +13 -0
- package/README.md +40 -13
- package/dist/_core/dist/design/anatomy.js +4 -1
- package/dist/_core/dist/design/fidelity.js +8 -1
- package/dist/_core/dist/design/token-diff.js +4 -2
- package/dist/_core/dist/gates/design-gates.js +33 -8
- package/dist/_core/package.json +1 -1
- package/package.json +1 -1
- package/scripts/app/screenshot.sh +6 -2
- package/scripts/prepare-npm.mjs +1 -1
- package/scripts/review/hex-scan.mjs +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.14.1] - 2026-06-21
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Correções do code-review xhigh do épico de fidelidade visual.** `gate.design.fidelity`
|
|
13
|
+
resiliente a race (`readFileSync`/`statSync` não crasham mais) e com contenção de
|
|
14
|
+
evidência à prova de symlink (`realpathSync`); duplicata de screenshot comparada por
|
|
15
|
+
path resolvido. Parser do scorecard mais estrito: divergência com enum inválido e
|
|
16
|
+
região sem `id` são **descartadas** (em vez de retornar objeto com tipo inválido).
|
|
17
|
+
`token-diff` normaliza hex de 4 dígitos (`#rgba`). Scripts `hex-scan`/`screenshot.sh`
|
|
18
|
+
validam argumentos (sem crash de `shift`; resolução do core com mensagem clara). Doc:
|
|
19
|
+
diagrama Mermaid corrigido (linter havia transformado arestas em tabela).
|
|
20
|
+
|
|
8
21
|
## [0.14.0] - 2026-06-21
|
|
9
22
|
|
|
10
23
|
### Added
|
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>
|
|
@@ -40,6 +40,7 @@ raptor new login-biometrico -d "Permitir login com Face ID / digital"
|
|
|
40
40
|
- [📱 Fluxo correto com React Native](#-fluxo-correto-com-react-native)
|
|
41
41
|
- [🛡️ Gates: o que bloqueia o quê](#️-gates-o-que-bloqueia-o-quê)
|
|
42
42
|
- [🔬 A ponta de verificação (`verify`)](#-a-ponta-de-verificação-verify)
|
|
43
|
+
- [🎯 Fidelidade visual (Figma → React Native)](#-fidelidade-visual-figma--react-native)
|
|
43
44
|
- [🧰 Capacidades do Raptor](#-capacidades-do-raptor)
|
|
44
45
|
- [📖 Referência de comandos CLI](#-referência-de-comandos-cli)
|
|
45
46
|
- [🗂️ Estrutura criada no projeto](#️-estrutura-criada-no-projeto)
|
|
@@ -61,6 +62,7 @@ O Raptor leva o SDD a sério para o mundo mobile, adicionando o que falta para u
|
|
|
61
62
|
| 📜 **Trilha de auditoria** | Cada passo emite eventos em `audit.jsonl`, validáveis contra um JSON Schema canônico. |
|
|
62
63
|
| 🔗 **Rastreabilidade código↔spec (M7)** | Cada artefato de código aponta para os critérios de aceitação que satisfaz, com evidência pareada (*code + test*). |
|
|
63
64
|
| 🔬 **Verificação real** | `verify a11y/perf/stores/os-matrix` medem o app de verdade e comparam com o que a spec/plan declararam. |
|
|
65
|
+
| 🎯 **Fidelidade visual (Figma→RN)** | Decompõe a tela em **anatomia**, dá **nota por micro-região** (mín. 95) e **trava o merge** via `gate.design.fidelity` até a implementação replicar o Figma. Guia: [docs/visual-fidelity-loop.md](docs/visual-fidelity-loop.md). |
|
|
64
66
|
| 🤖 **Agnóstico de agente** | Materializa slash commands para Claude Code, Cursor, Copilot, Codex, Gemini e Antigravity. |
|
|
65
67
|
| ✨ **Specs sem pontas soltas** | A spec nasce preenchida (defaults informados), com teto de 3 dúvidas de alto impacto, e um `/raptor-clarify` investigativo que caça lacunas antes do planejamento. |
|
|
66
68
|
| 🌿 **Branch & commit sob controle** | Cada feature ganha sua branch (`feat/001-…`) automaticamente, e hooks *warn-only* alertam sobre arquivos sensíveis ou fora do contexto — sem travar seu fluxo git. |
|
|
@@ -461,6 +463,41 @@ Declarar não é cumprir. O Raptor **mede** e compara com o declarado:
|
|
|
461
463
|
|
|
462
464
|
---
|
|
463
465
|
|
|
466
|
+
## 🎯 Fidelidade visual (Figma → React Native)
|
|
467
|
+
|
|
468
|
+
> **Novo na v0.14.0.** Transforma _"a tela tem os tokens"_ em _"a tela **replica** o Figma"_.
|
|
469
|
+
|
|
470
|
+
<p align="center"><img src="docs/assets/visual-fidelity-loop.svg" alt="Visual Fidelity Loop — anatomia, loop de fidelidade e gate" width="760"></p>
|
|
471
|
+
|
|
472
|
+
Capturar o design não garante fidelidade. O Raptor decompõe cada tela em **anatomia**
|
|
473
|
+
(regiões → elementos amarrados a token), pontua cada **micro-região de 60 a 100** (mínimo
|
|
474
|
+
**95**) e **trava o `verify`** via `gate.design.fidelity` até a implementação replicar o
|
|
475
|
+
protótipo — com um **backbone determinístico** (hex / `rgb()` / token + screenshots de
|
|
476
|
+
evidência) que o Raptor mede sozinho, então o agente **não consegue inflar a nota**.
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
raptor design fidelity 001-home # driver do loop: reporta a fidelidade + emite o prompt do scorer
|
|
480
|
+
raptor verify fidelity 001-home # lente read-only: nota por micro-região (60–100, mín. 95)
|
|
481
|
+
raptor verify 001-home # gate.design.fidelity (estático) trava se alguma região < mínimo
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
```
|
|
485
|
+
=== fidelity: 001-home ===
|
|
486
|
+
✅ home-unavailable [unavailable] header: 97/95
|
|
487
|
+
✅ home-unavailable [unavailable] earnings-card: 99/95
|
|
488
|
+
✅ home-unavailable [unavailable] status-component: 98/95
|
|
489
|
+
Summary: 5 region(s) scored — 5 pass, 0 fail.
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
O agente `design-fidelity` (read-only, _"na dúvida, reprova"_) é acionado **depois do
|
|
493
|
+
implement**: extrai os valores reais do Figma, captura o app rodando, pontua cada
|
|
494
|
+
micro-região e escreve `design/visual-review/<slug>.fidelity.json`. O gate **estático** lê
|
|
495
|
+
esse scorecard (como os gates M7 leem `impl-log.md`) e nunca roda o app.
|
|
496
|
+
|
|
497
|
+
> 📖 **Guia completo** — use cases, workflow, exemplos e diagramas: [docs/visual-fidelity-loop.md](docs/visual-fidelity-loop.md).
|
|
498
|
+
|
|
499
|
+
---
|
|
500
|
+
|
|
464
501
|
## 🧰 Capacidades do Raptor
|
|
465
502
|
|
|
466
503
|
| Capacidade | Para que serve | Comandos |
|
|
@@ -498,17 +535,7 @@ raptor design sync home # re-captura idempotente (nunca sobrescreve
|
|
|
498
535
|
|
|
499
536
|
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`.
|
|
500
537
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
Para garantir que a tela implementada **replica o Figma** (e não só "tem os tokens"), o Raptor adota anatomia + nota por micro-região:
|
|
504
|
-
|
|
505
|
-
```bash
|
|
506
|
-
raptor design fidelity home-entregador # driver do loop: reporta a fidelidade e emite o prompt do scorer
|
|
507
|
-
raptor verify fidelity home-entregador # lente read-only: nota por micro-região (60–100, mín. 95)
|
|
508
|
-
raptor verify home-entregador # gate.design.fidelity (estático) trava se alguma região < mínimo
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
Cada tela ganha uma seção `## Anatomy` em `design/screens/<slug>.md` (regiões→elementos amarrados a token) — o **contrato** de fidelidade. O agente `design-fidelity` (read-only, adversarial — "na dúvida, reprova") extrai os valores reais do Figma, captura o app rodando (`scripts/app/screenshot.sh`), pontua cada micro-região e escreve `design/visual-review/<slug>.fidelity.json` (schema `raptor.fidelity/v1`). O **backbone determinístico** que o Raptor mede sozinho — hex hardcoded (`scripts/review/hex-scan.mjs`), drift de token (`scripts/review/diff-tokens.mjs`), screenshots de evidência — impede inflar a nota. O `gate.design.fidelity` é **estático** (lê o scorecard, nunca roda o app), como os gates M7 leem o `impl-log.md`. Detalhes: [docs/visual-fidelity-loop.md](docs/visual-fidelity-loop.md).
|
|
538
|
+
> 🎯 **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).
|
|
512
539
|
|
|
513
540
|
> 📖 Detalhes, providers e limitações: [docs/design-figma-sync.md](docs/design-figma-sync.md) e [docs/design-gate.md](docs/design-gate.md).
|
|
514
541
|
|
|
@@ -4,8 +4,11 @@ const REGION_PREFIX = /^(?:region|regi[aã]o)\s*:\s*(.+)$/i;
|
|
|
4
4
|
const DELIM = /\s+[—–-]\s+|\s*:\s+/;
|
|
5
5
|
function splitHead(text) {
|
|
6
6
|
const m = DELIM.exec(text);
|
|
7
|
-
if (!m
|
|
7
|
+
if (!m)
|
|
8
8
|
return { head: text.trim() };
|
|
9
|
+
if (m.index <= 0) {
|
|
10
|
+
return { head: "", tail: text.slice(m.index + m[0].length).trim() || undefined };
|
|
11
|
+
}
|
|
9
12
|
return {
|
|
10
13
|
head: text.slice(0, m.index).trim(),
|
|
11
14
|
tail: text.slice(m.index + m[0].length).trim() || undefined,
|
|
@@ -22,18 +22,24 @@ function validateDivergence(d, label, problems) {
|
|
|
22
22
|
problems.push(`${label}: divergence must be an object`);
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
|
+
let valid = true;
|
|
25
26
|
const dim = d["dimension"];
|
|
26
27
|
if (typeof dim !== "string" || !FIDELITY_DIMENSIONS.includes(dim)) {
|
|
27
28
|
problems.push(`${label}: divergence "dimension" must be one of ${FIDELITY_DIMENSIONS.join("|")}`);
|
|
29
|
+
valid = false;
|
|
28
30
|
}
|
|
29
31
|
const sev = d["severity"];
|
|
30
32
|
if (typeof sev !== "string" || !DIVERGENCE_SEVERITIES.includes(sev)) {
|
|
31
33
|
problems.push(`${label}: divergence "severity" must be one of ${DIVERGENCE_SEVERITIES.join("|")}`);
|
|
34
|
+
valid = false;
|
|
32
35
|
}
|
|
33
36
|
const status = d["status"];
|
|
34
37
|
if (typeof status !== "string" || !DIVERGENCE_STATUSES.includes(status)) {
|
|
35
38
|
problems.push(`${label}: divergence "status" must be one of ${DIVERGENCE_STATUSES.join("|")}`);
|
|
39
|
+
valid = false;
|
|
36
40
|
}
|
|
41
|
+
if (!valid)
|
|
42
|
+
return null;
|
|
37
43
|
return {
|
|
38
44
|
dimension: dim,
|
|
39
45
|
figma: typeof d["figma"] === "string" ? d["figma"] : "",
|
|
@@ -51,6 +57,7 @@ function validateRegion(r, label, problems) {
|
|
|
51
57
|
const id = r["id"];
|
|
52
58
|
if (typeof id !== "string" || !id.trim()) {
|
|
53
59
|
problems.push(`${label}: missing "id"`);
|
|
60
|
+
return null;
|
|
54
61
|
}
|
|
55
62
|
if (!isScore(r["score"])) {
|
|
56
63
|
problems.push(`${label}: "score" must be a number 0–100`);
|
|
@@ -90,7 +97,7 @@ function validateRegion(r, label, problems) {
|
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
return {
|
|
93
|
-
id
|
|
100
|
+
id,
|
|
94
101
|
...(typeof r["label"] === "string" ? { label: r["label"] } : {}),
|
|
95
102
|
score: isScore(r["score"]) ? r["score"] : 0,
|
|
96
103
|
...(dimensions ? { dimensions } : {}),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { slugify } from "./slug.js";
|
|
2
2
|
function normColor(v) {
|
|
3
3
|
const s = v.trim().toUpperCase();
|
|
4
|
-
const m = /^#([0-9A-F])([0-9A-F])([0-9A-F])
|
|
5
|
-
|
|
4
|
+
const m = /^#([0-9A-F])([0-9A-F])([0-9A-F])([0-9A-F])?$/.exec(s);
|
|
5
|
+
if (!m)
|
|
6
|
+
return s;
|
|
7
|
+
return "#" + m.slice(1).filter(Boolean).map((c) => c + c).join("");
|
|
6
8
|
}
|
|
7
9
|
function normDim(v) {
|
|
8
10
|
return v.trim().replace(/px$/i, "").trim();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import { join, resolve, sep } from "node:path";
|
|
3
3
|
import { parseSpec } from "../models/spec.js";
|
|
4
4
|
import { parseTasks } from "../models/tasks.js";
|
|
@@ -185,6 +185,12 @@ export const gateDesignFidelity = {
|
|
|
185
185
|
}
|
|
186
186
|
const reviewDir = join(designDir, "visual-review");
|
|
187
187
|
const featureRoot = resolve(featureDir);
|
|
188
|
+
let featureRealRoot = featureRoot;
|
|
189
|
+
try {
|
|
190
|
+
featureRealRoot = realpathSync(featureRoot);
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
}
|
|
188
194
|
const problems = [];
|
|
189
195
|
const warnings = [];
|
|
190
196
|
let anatomyScreens = 0;
|
|
@@ -197,7 +203,14 @@ export const gateDesignFidelity = {
|
|
|
197
203
|
}
|
|
198
204
|
for (const file of screenFiles) {
|
|
199
205
|
const slug = slugify(file.slice(0, -3));
|
|
200
|
-
|
|
206
|
+
let screenContent;
|
|
207
|
+
try {
|
|
208
|
+
screenContent = readFileSync(join(screensDir, file), "utf8");
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const { regions: anatomy, problems: anatomyProblems } = parseAnatomy(screenContent);
|
|
201
214
|
if (anatomyProblems.some((p) => /no `## Anatomy` section/.test(p)))
|
|
202
215
|
continue;
|
|
203
216
|
anatomyScreens++;
|
|
@@ -246,15 +259,27 @@ export const gateDesignFidelity = {
|
|
|
246
259
|
problems.push(`${slug} [${st.state}]: ${kind} screenshot ${shot} escapes the feature directory`);
|
|
247
260
|
continue;
|
|
248
261
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
262
|
+
try {
|
|
263
|
+
if (!existsSync(abs)) {
|
|
264
|
+
problems.push(`${slug} [${st.state}]: ${kind} screenshot ${shot} not found on disk`);
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
const real = realpathSync(abs);
|
|
268
|
+
if (real !== featureRealRoot && !real.startsWith(featureRealRoot + sep)) {
|
|
269
|
+
problems.push(`${slug} [${st.state}]: ${kind} screenshot ${shot} escapes the feature directory`);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (statSync(real).size < MIN_SCREENSHOT_BYTES) {
|
|
273
|
+
problems.push(`${slug} [${st.state}]: ${kind} screenshot ${shot} is too small to be real evidence (<${MIN_SCREENSHOT_BYTES}B)`);
|
|
274
|
+
}
|
|
252
275
|
}
|
|
253
|
-
|
|
254
|
-
problems.push(`${slug} [${st.state}]: ${kind} screenshot ${shot} is
|
|
276
|
+
catch {
|
|
277
|
+
problems.push(`${slug} [${st.state}]: ${kind} screenshot ${shot} is not readable`);
|
|
255
278
|
}
|
|
256
279
|
}
|
|
257
|
-
if (pair.figma &&
|
|
280
|
+
if (pair.figma &&
|
|
281
|
+
pair.app &&
|
|
282
|
+
resolve(featureRoot, normalizePath(pair.figma)) === resolve(featureRoot, normalizePath(pair.app))) {
|
|
258
283
|
problems.push(`${slug} [${st.state}]: figma and app screenshots are the same file (illusory evidence)`);
|
|
259
284
|
}
|
|
260
285
|
}
|
package/dist/_core/package.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "raptor-aios",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Raptor — Spec-Driven Development (SDD) CLI for modern mobile apps. Constitutional gates, audit trail, real verification (a11y/perf/stores/OS matrix), and AI-agent slash commands.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -16,8 +16,12 @@ DEVICE=""
|
|
|
16
16
|
|
|
17
17
|
while [ $# -gt 0 ]; do
|
|
18
18
|
case "$1" in
|
|
19
|
-
--platform)
|
|
20
|
-
|
|
19
|
+
--platform)
|
|
20
|
+
[ $# -ge 2 ] || { echo "error: --platform needs a value (ios|android)" >&2; exit 2; }
|
|
21
|
+
PLATFORM="$2"; shift 2 ;;
|
|
22
|
+
--device)
|
|
23
|
+
[ $# -ge 2 ] || { echo "error: --device needs a value" >&2; exit 2; }
|
|
24
|
+
DEVICE="$2"; shift 2 ;;
|
|
21
25
|
-h|--help)
|
|
22
26
|
sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//'
|
|
23
27
|
exit 0 ;;
|
package/scripts/prepare-npm.mjs
CHANGED
|
@@ -23,7 +23,11 @@ function resolveCore() {
|
|
|
23
23
|
resolve(HERE, "..", "..", "packages", "core", "dist", "index.js"),
|
|
24
24
|
resolve(HERE, "..", "..", "dist", "_core", "dist", "index.js"),
|
|
25
25
|
];
|
|
26
|
-
|
|
26
|
+
const found = candidates.find((c) => existsSync(c));
|
|
27
|
+
if (!found) {
|
|
28
|
+
die(`@raptor/core build not found — run \`pnpm -r build\` (monorepo) or reinstall raptor-aios. Tried:\n ${candidates.join("\n ")}`, 2);
|
|
29
|
+
}
|
|
30
|
+
return found;
|
|
27
31
|
}
|
|
28
32
|
const { scanHardcodedHex, hexFindingsToScorecard } = await import(resolveCore());
|
|
29
33
|
|
|
@@ -40,7 +44,7 @@ for (let i = 0; i < argv.length; i++) {
|
|
|
40
44
|
if (argv[i] === "--json") continue;
|
|
41
45
|
if (argv[i] === "--allow") {
|
|
42
46
|
const v = argv[++i];
|
|
43
|
-
if (!v) die("--allow needs a value", 2);
|
|
47
|
+
if (!v || v.startsWith("--")) die("--allow needs a value (got a flag or nothing)", 2);
|
|
44
48
|
allowPaths.push(v);
|
|
45
49
|
} else {
|
|
46
50
|
positionals.push(argv[i]);
|