Contexto
Você é um agente de codificação encarregado de inferir tipos semânticos atômicos a partir de código TypeScript existente que hoje usa apenas primitivos (boolean, number, string, Date). Não assuma bibliotecas externas instaladas. Seu objetivo é:
- detectar candidatos a tipos semânticos;
- propor nomes canônicos no padrão
dominio.entidade.nome(com ponto); - sugerir regras e validações mínimas;
- gerar artefatos auto-contidos (sem dependências externas);
- listar até 5 novos tipos que ainda não existem no repositório e que valem a pena padronizar.
- listar até 5 novos tipos que ainda não existem no repositório e que valem a pena padronizar, que sejam os mais específicos daquele domínio e/ou entidade.
Use linguagem e comentários em português.
- Árvore de arquivos
.ts/.tsxe testes. - Nomes de variáveis, propriedades, funções, schemas, migrações, seeds.
- Strings literais, sufixos/prefixos, nomes de colunas/IDs e chamadas de API.
- Relatório em Markdown: achados, suposições e conflitos.
- Manifesto
semantic-types.manifest.jsoncom as inferências. - Stubs de tipos canônicos (arquivos
.tsauto-contidos). - Patches (diff unificado) mostrando como aplicar os tipos.
- Roadmap com até 5 tipos novos recomendados (ainda não mapeados).
Use kebab no arquivo e ponto no brand:
- Arquivo:
domains/{Domain}/{Entity}/{name}.ts - Brand interno:
{domain}.{entity}.{name}Ex.:domains/ecommerce/order/total.ts→ brandecommerce.order.total.
Detecção por prefixos e contexto de uso:
-
is*,has*,can*,should*,enable*,allow*,show* -
Exemplos canônicos:
isDone→project.task.isDonehasAllergies→health.patient.hasAllergiesshowWeekViewCalendar→ui.calendar.showWeekViewValide: coação para boolean, proibirnull/undefined(ou explicitarMaybe), e combinadores só via funções (and,or) — nunca&&direto entre brands.
Use nomes, unidades, e padrões:
-
Sufixos/palavras-chave:
total,amount,price,quantity,count,size,capacity,rate,percentage,score,weight,length,height,width,radius,duration. -
Padrões literais:
%,ms,s,min,h,kg,g,m,cm,km,px,rem,brl,usd. -
Exemplos:
totalAppointments→clinic.schedule.totalAppointments(inteiro ≥ 0)revenueTotal→ecommerce.order.revenueTotal.brl(moeda BRL)teethExtracted→dentistry.procedure.teethExtracted(inteiro 0–32)percentage→metrics.kpi.percentage(0–100 ou 0–1 — explicitar escala)durationMs→time.duration.msValide: faixas (min/max), inteireza quando nome implicar contagem, e unidade no nome canônico (ex.:.brl,.usd,.kg,.ms).
Detecte formatos:
-
email,phone,url,slug,isoCode,cpf/cnpj,uuid,id(se houver padrão). -
Regex úteis (documente no stub):
- Email (robusto suficiente):
/^[^\s@]+@[^\s@]+\.[^\s@]+$/ - URL: usar
new URL()no validador (cair no catch se inválida) - UUID v4:
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
- Email (robusto suficiente):
-
Exemplos:
customerEmail→crm.customer.emailorderId(uuid) →ecommerce.order.idcountryIso2→geo.country.iso2
Mapeie granularidade: createdAt, updatedAt, startAt, endAt, birthday, scheduledFor, dueAt.
-
Exemplos:
scheduledFor→calendar.event.scheduledAt(Date)birthday→identity.person.birthDate(Date sem hora — normalize)
Crie um utilitário interno para “brand” de semântica sem runtime:
// src/semantic/shim.ts
declare const __brand: unique symbol;
export type Brand<T, Name extends string> = T & { readonly [__brand]: Name };
export function STAMP<Name extends string>() {
return {
of: <T>(v: T) => v as Brand<T, Name>,
un: <T>(v: Brand<T, Name>) => v as unknown as T,
};
}Para tornar a criação de tipos semânticos mais intuitiva e menos verbosa, recomenda-se adicionar funções auxiliares simples, conhecidas como "makers" ou "factories", diretamente nos módulos de cada tipo. Essas funções permitem instanciações rápidas, como makeEmail('[email protected]'), evitando a necessidade de chamar métodos mais complexos como Email.of(...) em contextos cotidianos.
- Simplicidade: Reduz a curva de aprendizado e o boilerplate, especialmente em código de produção onde a validação já é implícita.
- Legibilidade: Torna o código mais declarativo, focando no valor em vez da construção do tipo.
- Compatibilidade: Pode coexistir com as funções existentes (como
ofeun), servindo como atalhos para casos comuns. - Aplicação Gradual: Facilita a migração de primitivos para tipos semânticos sem quebrar o fluxo de desenvolvimento.
- Padronização: Sempre inclua
makeem novos tipos para promover adoção rápida. - Validação: A função
makedeve herdar as validações deof, garantindo consistência. - Documentação: No README de cada domínio, exemplifique usos com
makepara onboarding. - Evolução: Se necessário, expanda
makecom overloads para aceitar múltiplos formatos (ex.:makepara datas aceitando string ou timestamp).
Essa abordagem torna os tipos semânticos mais acessíveis, acelerando a migração e reduzindo erros em projetos reais.
Adicione uma função make em cada stub de tipo, que internamente chame a função of existente. Isso mantém a validação e o branding, mas oferece uma interface mais amigável.
// domains/project/task/is-done.ts
import { Brand, STAMP } from "../../src/semantic/shim";
export type IsDone = Brand<boolean, "project.task.isDone">;
export const IsDone = (() => {
const f = STAMP<"project.task.isDone">();
return {
of: (v: unknown): IsDone => f.of(Boolean(v)),
un: (v: IsDone): boolean => f.un(v),
and: (a: IsDone, b: IsDone): IsDone => f.of(f.un(a) && f.un(b)),
make: (value: boolean): IsDone => f.of(value), // Nova função make para simplicidade
};
})();Uso: const isCompleted = IsDone.make(true); (equivalente a IsDone.of(true)).
// domains/ecommerce/order/revenue-total.brl.ts
import { Brand, STAMP } from "../../src/semantic/shim";
export type RevenueTotalBRL = Brand<number, "ecommerce.order.revenueTotal.brl">;
export const RevenueTotalBRL = (() => {
const f = STAMP<"ecommerce.order.revenueTotal.brl">();
return {
of: (v: unknown): RevenueTotalBRL => {
const n = Number(v);
if (!Number.isFinite(n) || n < 0) throw new TypeError("revenue must be >= 0");
return f.of(n);
},
un: (v: RevenueTotalBRL) => f.un(v),
add: (a: RevenueTotalBRL, b: RevenueTotalBRL): RevenueTotalBRL => f.of(f.un(a) + f.un(b)),
make: (value: number): RevenueTotalBRL => f.of(value), // Nova função make
};
})();// domains/crm/customer/email.ts
import { Brand, STAMP } from "../../src/semantic/shim";
export type CustomerEmail = Brand<string, "crm.customer.email">;
export const CustomerEmail = (() => {
const f = STAMP<"crm.customer.email">();
const emailRx = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return {
of: (v: unknown): CustomerEmail => {
const s = String(v).trim();
if (!emailRx.test(s)) throw new TypeError("invalid email");
return f.of(s);
},
un: (v: CustomerEmail) => f.un(v),
make: (value: string): CustomerEmail => f.of(value), // Nova função make
};
})();// domains/calendar/event/scheduled-at.ts
import { Brand, STAMP } from "../../src/semantic/shim";
export type ScheduledAt = Brand<Date, "calendar.event.scheduledAt">;
export const ScheduledAt = (() => {
const f = STAMP<"calendar.event.scheduledAt">();
return {
of: (v: unknown): ScheduledAt => {
const d = v instanceof Date ? v : new Date(String(v));
if (Number.isNaN(d.getTime())) throw new TypeError("invalid date");
return f.of(d);
},
un: (v: ScheduledAt) => f.un(v),
make: (value: string | Date): ScheduledAt => f.of(value), // Nova função make
};
})();semantic-types.manifest.json
{
"version": 1,
"discovered": [
{
"from": "src/modules/orders/service.ts:42",
"symbol": "revenueTotal",
"primitive": "number",
"brand": "ecommerce.order.revenueTotal.brl",
"file": "domains/ecommerce/order/revenue-total.brl.ts",
"validations": [">= 0", "finite"],
"ops": ["add", "sum"],
"confidence": 0.86
},
{
"from": "src/calendar/ui/Week.tsx:18",
"symbol": "showWeekView",
"primitive": "boolean",
"brand": "ui.calendar.showWeekView",
"file": "domains/ui/calendar/show-week-view.ts",
"validations": [],
"ops": ["and"],
"confidence": 0.78
}
],
"unmapped_candidates": [
"metrics.kpi.percentage",
"dentistry.procedure.teethExtracted",
"clinic.schedule.totalAppointments"
],
"suggest_new_top5": [
{ "brand": "metrics.kpi.percentage", "why": "encontrado `%` em labels/testes; validação [0..100]" },
{ "brand": "time.duration.ms", "why": "sufixo Ms em várias props; normalizar" },
{ "brand": "crm.customer.phoneE164", "why": "strings com dígitos e +; padronizar E.164" },
{ "brand": "ecommerce.order.taxRate", "why": "uso de `tax` proporcional; [0..1] ou [0..100]" },
{ "brand": "geo.country.iso2", "why": "ocorrência de códigos de 2 letras" }
]
}Gere um diff unificado propondo substituição local (import relativo) e uso das fábricas:
- const revenueTotal: number = order.items.reduce((sum, it) => sum + it.price, 0)
+ import { RevenueTotalBRL } from "../../domains/ecommerce/order/revenue-total.brl"
+ const revenueTotal = RevenueTotalBRL.of(
+ order.items.reduce((sum, it) => sum + it.price, 0)
+ )
- if (showWeekView && isDone) { ... }
+ import { IsDone } from "../../domains/project/task/is-done"
+ import { ShowWeekView } from "../../domains/ui/calendar/show-week-view"
+ if (IsDone.un(isDone) && ShowWeekView.un(showWeekView)) { ... }Se o projeto ainda não tem
src/semantic/shim.ts, inclua o arquivo no patch.
- Nunca substituir primitivo por tipo semântico sem stub + import.
- Apontar ambiguidade (
rate,size,code) e sugerir brand com unidade ou domínio explícitos. - Sugerir faixas: contagens (≥ 0), porcentagens (0–1 ou 0–100 — escolha e documente).
- Não criar tipos transdomínio:
ecommerce.*não reage comcrm.*(a menos que o projeto já defina). - Se detectar
anyem locais críticos, abrir nota para migração gradual com marcas semânticas.