@semacode/cli 3.8.5 → 3.9.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.
@@ -1,5 +1,5 @@
1
1
  import type { IntervaloFonte } from "../diagnosticos/index.js";
2
- export type TipoBloco = "module" | "use" | "database" | "type" | "entity" | "enum" | "task" | "worker" | "evento" | "fila" | "cron" | "webhook" | "cache" | "storage" | "policy" | "input" | "output" | "rules" | "effects" | "impl" | "vinculos" | "execucao" | "auth" | "authz" | "dados" | "audit" | "segredos" | "forbidden" | "guarantees" | "state" | "flow" | "route" | "tests" | "error" | "docs" | "comments" | "table" | "view" | "query" | "transaction" | "index" | "constraint" | "relationship" | "collection" | "document" | "keyspace" | "stream" | "lock" | "retention" | "replication" | "fields" | "invariants" | "transitions" | "given" | "when" | "expect" | "case" | "desconhecido";
2
+ export type TipoBloco = "module" | "use" | "design" | "tokens" | "database" | "type" | "entity" | "enum" | "task" | "worker" | "evento" | "fila" | "cron" | "webhook" | "cache" | "storage" | "policy" | "input" | "output" | "rules" | "effects" | "impl" | "vinculos" | "execucao" | "auth" | "authz" | "dados" | "audit" | "segredos" | "forbidden" | "guarantees" | "state" | "flow" | "route" | "tests" | "error" | "docs" | "comments" | "table" | "view" | "query" | "transaction" | "index" | "constraint" | "relationship" | "collection" | "document" | "keyspace" | "stream" | "lock" | "retention" | "replication" | "fields" | "invariants" | "transitions" | "given" | "when" | "expect" | "case" | "desconhecido";
3
3
  export interface NoAstBase {
4
4
  tipo: string;
5
5
  intervalo?: IntervaloFonte;
@@ -101,6 +101,7 @@ export interface ModuloAst extends NoAstBase {
101
101
  vinculos?: BlocoGenericoAst;
102
102
  docs?: BlocoGenericoAst;
103
103
  comments?: BlocoGenericoAst;
104
+ design?: BlocoGenericoAst;
104
105
  databases: BlocoGenericoAst[];
105
106
  types: TypeAst[];
106
107
  entities: EntityAst[];
@@ -271,8 +271,25 @@ export function converterParaIr(modulo, diagnosticos, contexto) {
271
271
  ...superficies.flatMap((superficie) => superficie.vinculos),
272
272
  ],
273
273
  });
274
+ const designBloco = modulo.design;
275
+ const tokensDesign = designBloco?.blocos.find((bloco) => bloco.tipo === "bloco_generico" && bloco.palavraChave === "tokens");
276
+ const design = designBloco
277
+ ? {
278
+ dominio: designBloco.campos.find((campo) => campo.nome === "dominio")?.valor,
279
+ identidade: designBloco.campos.find((campo) => campo.nome === "identidade")?.valor,
280
+ tokens: {
281
+ ...Object.fromEntries((tokensDesign?.campos ?? [])
282
+ .filter((campo) => ["paleta", "tipografia", "densidade", "forma", "movimento"].includes(campo.nome))
283
+ .map((campo) => [campo.nome, campo.valor])),
284
+ overrides: Object.fromEntries((tokensDesign?.campos ?? [])
285
+ .filter((campo) => !["paleta", "tipografia", "densidade", "forma", "movimento"].includes(campo.nome))
286
+ .map((campo) => [campo.nome, campo.valor])),
287
+ },
288
+ }
289
+ : undefined;
274
290
  return {
275
291
  nome: modulo.nome,
292
+ design,
276
293
  uses: contexto?.modulosImportados.length
277
294
  ? [...contexto.modulosImportados]
278
295
  : modulo.uses.filter((use) => use.origem === "sema").map((use) => use.caminho),
@@ -325,8 +325,22 @@ export interface IrBancoDados {
325
325
  block: IrBlocoDeclarativo;
326
326
  resources: IrRecursoPersistencia[];
327
327
  }
328
+ export interface IrDesignTokens {
329
+ paleta?: string;
330
+ tipografia?: string;
331
+ densidade?: string;
332
+ forma?: string;
333
+ movimento?: string;
334
+ overrides: Record<string, string>;
335
+ }
336
+ export interface IrDesign {
337
+ dominio?: string;
338
+ identidade?: string;
339
+ tokens: IrDesignTokens;
340
+ }
328
341
  export interface IrModulo {
329
342
  nome: string;
343
+ design?: IrDesign;
330
344
  uses: string[];
331
345
  imports: IrUse[];
332
346
  interoperabilidades: IrInteropExterno[];
@@ -35,6 +35,8 @@ export const PALAVRAS_CHAVE = new Set([
35
35
  "error",
36
36
  "docs",
37
37
  "comments",
38
+ "design",
39
+ "tokens",
38
40
  "table",
39
41
  "view",
40
42
  "query",
@@ -9,6 +9,7 @@ export function parseModuloParser(parser) {
9
9
  const nome = parser.consumirTipo("identificador", "Era esperado o nome do modulo.").valor;
10
10
  parser.consumirValor("{", "Era esperado abrir o corpo do modulo com {.");
11
11
  const uses = [];
12
+ let design;
12
13
  let vinculos;
13
14
  const databases = [];
14
15
  const types = [];
@@ -58,6 +59,13 @@ export function parseModuloParser(parser) {
58
59
  }
59
60
  extras.push(parser.parseBlocoGenerico("desconhecido"));
60
61
  break;
62
+ case "design":
63
+ if (parser.iniciaBlocoSimples("design")) {
64
+ design = parser.parseBlocoGenerico("design");
65
+ break;
66
+ }
67
+ extras.push(parser.parseBlocoGenerico("desconhecido"));
68
+ break;
61
69
  case "vinculos":
62
70
  if (parser.iniciaBlocoSimples("vinculos")) {
63
71
  vinculos = parser.parseBlocoGenerico("vinculos");
@@ -195,6 +203,7 @@ export function parseModuloParser(parser) {
195
203
  tipo: "module",
196
204
  nome,
197
205
  uses,
206
+ design,
198
207
  vinculos,
199
208
  docs,
200
209
  comments,
@@ -4,6 +4,6 @@ export interface ResultadoParser {
4
4
  modulo?: ModuloAst;
5
5
  diagnosticos: Diagnostico[];
6
6
  }
7
- export type PalavraBloco = "database" | "docs" | "comments" | "table" | "view" | "query" | "transaction" | "index" | "constraint" | "relationship" | "collection" | "document" | "keyspace" | "stream" | "lock" | "retention" | "replication" | "fields" | "invariants" | "transitions" | "input" | "output" | "rules" | "effects" | "impl" | "vinculos" | "execucao" | "auth" | "authz" | "dados" | "audit" | "segredos" | "forbidden" | "guarantees" | "state" | "tests" | "error" | "flow" | "route" | "worker" | "evento" | "fila" | "cron" | "webhook" | "cache" | "storage" | "policy" | "when" | "given" | "expect";
7
+ export type PalavraBloco = "database" | "docs" | "comments" | "design" | "tokens" | "table" | "view" | "query" | "transaction" | "index" | "constraint" | "relationship" | "collection" | "document" | "keyspace" | "stream" | "lock" | "retention" | "replication" | "fields" | "invariants" | "transitions" | "input" | "output" | "rules" | "effects" | "impl" | "vinculos" | "execucao" | "auth" | "authz" | "dados" | "audit" | "segredos" | "forbidden" | "guarantees" | "state" | "tests" | "error" | "flow" | "route" | "worker" | "evento" | "fila" | "cron" | "webhook" | "cache" | "storage" | "policy" | "when" | "given" | "expect";
8
8
  export declare const PALAVRAS_BLOCO_NOMEADO_LIVRE: Set<PalavraBloco>;
9
9
  export declare function decodificarTextoLiteral(valor: string): string;
@@ -66,6 +66,8 @@ export class Parser {
66
66
  return [
67
67
  "docs",
68
68
  "comments",
69
+ "design",
70
+ "tokens",
69
71
  "fields",
70
72
  "invariants",
71
73
  "transitions",
@@ -1,3 +1,11 @@
1
+ import { type Diagnostico } from "../diagnosticos/index.js";
1
2
  import type { ModuloAst } from "../ast/tipos.js";
2
3
  import { OpcoesAnaliseSemantica, ResultadoSemantico } from "./analisador.part01.js";
3
4
  export declare function analisarSemantica(modulo: ModuloAst, opcoes?: OpcoesAnaliseSemantica): ResultadoSemantico;
5
+ export declare const PALETAS_DESIGN_SUPORTADAS: Set<string>;
6
+ export declare const TIPOGRAFIAS_DESIGN_SUPORTADAS: Set<string>;
7
+ export declare const DENSIDADES_DESIGN_SUPORTADAS: Set<string>;
8
+ export declare const FORMAS_DESIGN_SUPORTADAS: Set<string>;
9
+ export declare const MOVIMENTOS_DESIGN_SUPORTADAS: Set<string>;
10
+ export declare const CAMPOS_TOKENS_DESIGN: Set<string>;
11
+ export declare function validarDesignModulo(modulo: import("../ast/tipos.js").ModuloAst, diagnosticos: Diagnostico[]): void;
@@ -2,7 +2,7 @@
2
2
  // Descricao: nucleo semantico particionado; consulte contratos/sema/software.sema antes de editar.
3
3
  import { criarDiagnostico } from "../diagnosticos/index.js";
4
4
  import { parsearTransicaoEstado, } from "./estruturas.js";
5
- import { PADRAO_CAMINHO_INTEROP, TIPOS_PRIMITIVOS, coletarResumoTask, diagnosticoDuplicado, localizarBloco, localizarCampo, validarCamposDeTipos, validarVinculos } from "./analisador.part01.js";
5
+ import { PADRAO_CAMINHO_INTEROP, TIPOS_PRIMITIVOS, coletarResumoTask, diagnosticoDuplicado, localizarBloco, localizarCampo, validarCamposDeTipos, validarVinculos, valorCampoCompleto } from "./analisador.part01.js";
6
6
  import { descreverSugestoes, recomporCaminhoRoute, resolverUseSema, serializarTransicao, validarSuperficie } from "./analisador.part04.js";
7
7
  import { validarFlow, validarInvariantesDeCampos, validarState } from "./analisador.part05.js";
8
8
  import { validarStatusTextoComState, validarTask } from "./analisador.part07.js";
@@ -184,6 +184,7 @@ export function analisarSemantica(modulo, opcoes = {}) {
184
184
  validarRoute(route, tasksConhecidas, tarefasDetalhadas, diagnosticos);
185
185
  }
186
186
  validarVinculos(modulo.vinculos, diagnosticos, `modulo ${modulo.nome}`);
187
+ validarDesignModulo(modulo, diagnosticos);
187
188
  for (const worker of modulo.workers) {
188
189
  validarSuperficie(worker, "worker", tasksConhecidas, tiposConhecidos, diagnosticos);
189
190
  }
@@ -246,4 +247,47 @@ export function analisarSemantica(modulo, opcoes = {}) {
246
247
  diagnosticos,
247
248
  };
248
249
  }
250
+ export const PALETAS_DESIGN_SUPORTADAS = new Set(["padrao", "terra", "floresta", "oceano", "noturno", "grafite", "neon", "pixel"]);
251
+ export const TIPOGRAFIAS_DESIGN_SUPORTADAS = new Set(["padrao", "humanista", "tecnica", "display"]);
252
+ export const DENSIDADES_DESIGN_SUPORTADAS = new Set(["compacta", "padrao", "confortavel"]);
253
+ export const FORMAS_DESIGN_SUPORTADAS = new Set(["reta", "padrao", "arredondada", "pill"]);
254
+ export const MOVIMENTOS_DESIGN_SUPORTADAS = new Set(["nenhum", "padrao", "suave"]);
255
+ export const CAMPOS_TOKENS_DESIGN = new Set([
256
+ "paleta", "tipografia", "densidade", "forma", "movimento",
257
+ "cor_primaria", "cor_primaria_hover", "cor_primaria_suave", "cor_fundo", "cor_superficie",
258
+ "cor_texto", "cor_texto_secundario", "cor_borda", "fonte", "fonte_titulo", "fonte_mono", "raio_base",
259
+ ]);
260
+ export function validarDesignModulo(modulo, diagnosticos) {
261
+ const design = modulo.design;
262
+ if (!design) {
263
+ return;
264
+ }
265
+ for (const campo of design.campos) {
266
+ if (!["dominio", "identidade"].includes(campo.nome)) {
267
+ diagnosticos.push(criarDiagnostico("SEM110", `Design do modulo "${modulo.nome}" declarou campo invalido: "${campo.nome}".`, "erro", campo.intervalo, "Use dominio, identidade ou o subbloco tokens { ... } com paleta, tipografia, densidade, forma, movimento ou overrides de cor/fonte/raio."));
268
+ }
269
+ }
270
+ const PRESETS = [
271
+ [PALETAS_DESIGN_SUPORTADAS, "paleta"],
272
+ [TIPOGRAFIAS_DESIGN_SUPORTADAS, "tipografia"],
273
+ [DENSIDADES_DESIGN_SUPORTADAS, "densidade"],
274
+ [FORMAS_DESIGN_SUPORTADAS, "forma"],
275
+ [MOVIMENTOS_DESIGN_SUPORTADAS, "movimento"],
276
+ ];
277
+ const tokens = design.blocos.find((bloco) => bloco.tipo === "bloco_generico" && bloco.palavraChave === "tokens");
278
+ if (!tokens) {
279
+ return;
280
+ }
281
+ for (const campo of tokens.campos) {
282
+ if (!CAMPOS_TOKENS_DESIGN.has(campo.nome)) {
283
+ diagnosticos.push(criarDiagnostico("SEM111", `Tokens de design do modulo "${modulo.nome}" declarou campo invalido: "${campo.nome}".`, "erro", campo.intervalo, `Use ${[...CAMPOS_TOKENS_DESIGN].join(", ")} dentro de tokens { ... }.`));
284
+ continue;
285
+ }
286
+ const preset = PRESETS.find(([conjunto, nome]) => nome === campo.nome);
287
+ const valor = valorCampoCompleto(campo) ?? "";
288
+ if (preset && !preset[0].has(valor)) {
289
+ diagnosticos.push(criarDiagnostico("SEM112", `Design do modulo "${modulo.nome}" declarou ${campo.nome} invalida: "${valor}".`, "erro", campo.intervalo, `Use ${[...preset[0]].join(", ")} ou um override livre (ex.: cor_primaria: "#b45309").`));
290
+ }
291
+ }
292
+ }
249
293
  //# sourceMappingURL=analisador.part08.js.map
@@ -0,0 +1,54 @@
1
+ export interface CoresDesign {
2
+ primaria: string;
3
+ primariaHover: string;
4
+ primariaSuave: string;
5
+ fundo: string;
6
+ superficie: string;
7
+ texto: string;
8
+ textoSecundario: string;
9
+ borda: string;
10
+ bordaFoco: string;
11
+ }
12
+ export interface TipografiaDesign {
13
+ fonte: string;
14
+ fonteTitulo: string;
15
+ fonteMono: string;
16
+ }
17
+ export interface EscalaTipografica {
18
+ base: string;
19
+ titulo: string;
20
+ subtitulo: string;
21
+ pequeno: string;
22
+ }
23
+ export interface FormaDesign {
24
+ raio: string;
25
+ raioLg: string;
26
+ }
27
+ export interface DesignTokensResolvidos {
28
+ cores: CoresDesign;
29
+ tipografia: TipografiaDesign;
30
+ escala: EscalaTipografica;
31
+ forma: FormaDesign;
32
+ movimentoDuracao: string;
33
+ }
34
+ interface DesignDeclarado {
35
+ dominio?: string;
36
+ identidade?: string;
37
+ tokens?: {
38
+ paleta?: string;
39
+ tipografia?: string;
40
+ densidade?: string;
41
+ forma?: string;
42
+ movimento?: string;
43
+ overrides?: Record<string, string>;
44
+ };
45
+ }
46
+ export declare function resolverDesignTokens(design?: DesignDeclarado): DesignTokensResolvidos;
47
+ export declare function listarNomesDesign(): {
48
+ paletas: string[];
49
+ tipografias: string[];
50
+ densidades: string[];
51
+ formas: string[];
52
+ movimentos: string[];
53
+ };
54
+ export {};
@@ -0,0 +1,131 @@
1
+ // SEMA-GOVERNED: sema.produto.dsl_design
2
+ // Descrição: presets de identidade visual e resolucao de tokens de design a partir do bloco design do contrato.
3
+ const PALETAS = {
4
+ padrao: {
5
+ primaria: "#6366f1", primariaHover: "#4f46e5", primariaSuave: "#eef2ff",
6
+ fundo: "#fafafa", superficie: "#ffffff", texto: "#18181b",
7
+ textoSecundario: "#71717a", borda: "#e4e4e7", bordaFoco: "#a5b4fc",
8
+ },
9
+ terra: {
10
+ primaria: "#b45309", primariaHover: "#92400e", primariaSuave: "#fef3c7",
11
+ fundo: "#faf7f2", superficie: "#fffef9", texto: "#292524",
12
+ textoSecundario: "#78716c", borda: "#e7e5e4", bordaFoco: "#d97706",
13
+ },
14
+ floresta: {
15
+ primaria: "#15803d", primariaHover: "#166534", primariaSuave: "#dcfce7",
16
+ fundo: "#f7faf7", superficie: "#ffffff", texto: "#1a2e1a",
17
+ textoSecundario: "#6b7f6b", borda: "#dbe7db", bordaFoco: "#22c55e",
18
+ },
19
+ oceano: {
20
+ primaria: "#0369a1", primariaHover: "#075985", primariaSuave: "#e0f2fe",
21
+ fundo: "#f8fafc", superficie: "#ffffff", texto: "#0f172a",
22
+ textoSecundario: "#64748b", borda: "#e2e8f0", bordaFoco: "#38bdf8",
23
+ },
24
+ noturno: {
25
+ primaria: "#22d3ee", primariaHover: "#06b6d4", primariaSuave: "#164e63",
26
+ fundo: "#0f172a", superficie: "#1e293b", texto: "#e2e8f0",
27
+ textoSecundario: "#94a3b8", borda: "#334155", bordaFoco: "#22d3ee",
28
+ },
29
+ grafite: {
30
+ primaria: "#f97316", primariaHover: "#ea580c", primariaSuave: "#ffedd5",
31
+ fundo: "#18181b", superficie: "#27272a", texto: "#fafafa",
32
+ textoSecundario: "#a1a1aa", borda: "#3f3f46", bordaFoco: "#fb923c",
33
+ },
34
+ neon: {
35
+ primaria: "#a3e635", primariaHover: "#84cc16", primariaSuave: "#365314",
36
+ fundo: "#0a0a0a", superficie: "#171717", texto: "#fafafa",
37
+ textoSecundario: "#a3a3a3", borda: "#262626", bordaFoco: "#a3e635",
38
+ },
39
+ pixel: {
40
+ primaria: "#fbbf24", primariaHover: "#f59e0b", primariaSuave: "#451a03",
41
+ fundo: "#111827", superficie: "#1f2937", texto: "#f9fafb",
42
+ textoSecundario: "#9ca3af", borda: "#374151", bordaFoco: "#fbbf24",
43
+ },
44
+ };
45
+ const TIPOGRAFIAS = {
46
+ padrao: {
47
+ fonte: '"Inter", "Segoe UI", system-ui, -apple-system, sans-serif',
48
+ fonteTitulo: '"Inter", "Segoe UI", system-ui, sans-serif',
49
+ fonteMono: '"JetBrains Mono", "Fira Code", "Consolas", monospace',
50
+ },
51
+ humanista: {
52
+ fonte: '"Nunito Sans", "Segoe UI", system-ui, sans-serif',
53
+ fonteTitulo: '"Source Serif 4", Georgia, serif',
54
+ fonteMono: '"IBM Plex Mono", "Consolas", monospace',
55
+ },
56
+ tecnica: {
57
+ fonte: '"IBM Plex Sans", system-ui, sans-serif',
58
+ fonteTitulo: '"IBM Plex Mono", "Consolas", monospace',
59
+ fonteMono: '"IBM Plex Mono", "Consolas", monospace',
60
+ },
61
+ display: {
62
+ fonte: '"Inter", system-ui, sans-serif',
63
+ fonteTitulo: '"Fraunces", Georgia, serif',
64
+ fonteMono: '"JetBrains Mono", monospace',
65
+ },
66
+ };
67
+ const DENSIDADES = {
68
+ compacta: { base: "0.875rem", titulo: "1.5rem", subtitulo: "1.125rem", pequeno: "0.75rem" },
69
+ padrao: { base: "0.9375rem", titulo: "1.75rem", subtitulo: "1.25rem", pequeno: "0.8125rem" },
70
+ confortavel: { base: "1rem", titulo: "1.875rem", subtitulo: "1.375rem", pequeno: "0.875rem" },
71
+ };
72
+ const FORMAS = {
73
+ reta: { raio: "0", raioLg: "0" },
74
+ padrao: { raio: "0.5rem", raioLg: "0.75rem" },
75
+ arredondada: { raio: "0.75rem", raioLg: "1rem" },
76
+ pill: { raio: "999px", raioLg: "999px" },
77
+ };
78
+ const MOVIMENTOS = {
79
+ nenhum: "0s",
80
+ padrao: "150ms",
81
+ suave: "280ms",
82
+ };
83
+ const CHAVES_COR = {
84
+ cor_primaria: "primaria",
85
+ cor_primaria_hover: "primariaHover",
86
+ cor_primaria_suave: "primariaSuave",
87
+ cor_fundo: "fundo",
88
+ cor_superficie: "superficie",
89
+ cor_texto: "texto",
90
+ cor_texto_secundario: "textoSecundario",
91
+ cor_borda: "borda",
92
+ };
93
+ export function resolverDesignTokens(design) {
94
+ const tokens = design?.tokens ?? {};
95
+ const cores = { ...PALETAS[tokens.paleta ?? "padrao"] ?? PALETAS.padrao };
96
+ const tipografia = { ...TIPOGRAFIAS[tokens.tipografia ?? "padrao"] ?? TIPOGRAFIAS.padrao };
97
+ const escala = { ...DENSIDADES[tokens.densidade ?? "padrao"] ?? DENSIDADES.padrao };
98
+ const forma = { ...FORMAS[tokens.forma ?? "padrao"] ?? FORMAS.padrao };
99
+ const movimentoDuracao = MOVIMENTOS[tokens.movimento ?? "padrao"] ?? MOVIMENTOS.padrao;
100
+ const overrides = tokens.overrides ?? {};
101
+ for (const [chave, valor] of Object.entries(overrides)) {
102
+ const alvoCor = CHAVES_COR[chave];
103
+ if (alvoCor) {
104
+ cores[alvoCor] = valor;
105
+ }
106
+ else if (chave === "fonte") {
107
+ tipografia.fonte = valor;
108
+ }
109
+ else if (chave === "fonte_titulo") {
110
+ tipografia.fonteTitulo = valor;
111
+ }
112
+ else if (chave === "fonte_mono") {
113
+ tipografia.fonteMono = valor;
114
+ }
115
+ else if (chave === "raio_base") {
116
+ forma.raio = valor;
117
+ forma.raioLg = valor;
118
+ }
119
+ }
120
+ return { cores, tipografia, escala, forma, movimentoDuracao };
121
+ }
122
+ export function listarNomesDesign() {
123
+ return {
124
+ paletas: Object.keys(PALETAS),
125
+ tipografias: Object.keys(TIPOGRAFIAS),
126
+ densidades: Object.keys(DENSIDADES),
127
+ formas: Object.keys(FORMAS),
128
+ movimentos: Object.keys(MOVIMENTOS),
129
+ };
130
+ }
131
+ //# sourceMappingURL=design.js.map
@@ -1,3 +1,4 @@
1
+ export * from "./design.js";
1
2
  export type AlvoGeracao = "typescript" | "python" | "dart" | "lua" | "javascript" | "html" | "css" | "php" | "dotnet" | "cpp";
2
3
  export type FrameworkGeracao = "base" | "nestjs" | "fastapi";
3
4
  export interface ArquivoGerado {
@@ -1,3 +1,4 @@
1
+ export * from "./design.js";
1
2
  const TIPOS_PRIMITIVOS_SEMA = new Set([
2
3
  "Texto",
3
4
  "Numero",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@semacode/cli",
3
- "version": "3.8.5",
3
+ "version": "3.9.0",
4
4
  "description": "Local-first semantic governance CLI for AI agents, proven with Claude, Codex, zCode (GLM) and Kimi: contracts, drift, impact, docs gates, and code generation.",
5
5
  "type": "module",
6
6
  "icon": "logo.png",