diff --git a/README.md b/README.md index 042fcaf759..e3dd80936e 100644 --- a/README.md +++ b/README.md @@ -403,7 +403,7 @@ claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp -> **Why use many token when few token do trick?** Every request passes through OmniRoute's compression pipeline **transparently** — no client changes. It stacks ideas from [RTK](https://github.com/rtk-ai/rtk) and [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 51K+). +> **Why use many token when few token do trick?** Every request passes through OmniRoute's compression pipeline **transparently** — no client changes. It stacks ideas from [RTK](https://github.com/rtk-ai/rtk), [Caveman](https://github.com/JuliusBrussee/caveman) (⭐ 51K+), and [Troglodita](https://github.com/leninejunior/troglodita) (PT-BR). | Mode | Savings | Best for | | ------------------------------ | ---------- | --------------------------- | @@ -422,6 +422,14 @@ claude mcp add-server omniroute --type http --url http://localhost:20128/api/mcp > > **Same answer. 72% fewer tokens. Zero accuracy loss.** ✅ +**PT-BR example — [Troglodita](https://github.com/leninejunior/troglodita) mode:** + +> **Antes (42 tokens):** _"O problema é que o componente está re-renderizando porque uma nova referência de objeto está sendo criada em cada ciclo de renderização. Eu recomendaria usar useMemo."_ +> +> **Depois (12 tokens):** _"Re-render: ref nova cada ciclo (objeto inline recriado). Usar `useMemo`."_ +> +> **Mesma resposta. ~70% menos tokens. Precisão técnica intacta.** ✅ +
### 📖 How it works — pipeline, architecture & savings math @@ -938,6 +946,8 @@ Special thanks to **[Caveman](https://github.com/JuliusBrussee/caveman)** by **[ Special thanks to **[RTK - Rust Token Killer](https://github.com/rtk-ai/rtk)** by **[RTK AI](https://github.com/rtk-ai)** — the high-performance command-output compression project whose terminal, build, test, git, and tool-output filtering model inspired OmniRoute's RTK engine, JSON filter DSL, raw-output recovery, and stacked RTK → Caveman compression pipeline. +Special thanks to **[Troglodita](https://github.com/leninejunior/troglodita)** by **[Lenine Júnior](https://github.com/leninejunior)** — the PT-BR token compression project ("por que gastar muitos tokens quando poucos resolve?") whose Portuguese-native rules power OmniRoute's pt-BR language pack: pleonasm reduction, filler removal tuned for Brazilian Portuguese grammar, and technical abbreviations for the dev BR community. +
## 📄 License diff --git a/docs/compression/COMPRESSION_LANGUAGE_PACKS.md b/docs/compression/COMPRESSION_LANGUAGE_PACKS.md index bfa445a27b..1a0e794ed6 100644 --- a/docs/compression/COMPRESSION_LANGUAGE_PACKS.md +++ b/docs/compression/COMPRESSION_LANGUAGE_PACKS.md @@ -24,12 +24,14 @@ Current shipped packs (verified against `rules/` directory contents): | ------------------- | -------------- | --------------------------------------------------- | | English | `rules/en/` | `context`, `dedup`, `filler`, `structural`, `ultra` | | Spanish | `rules/es/` | `context`, `dedup`, `filler`, `structural`, `ultra` | -| Portuguese (Brazil) | `rules/pt-BR/` | `context`, `filler`, `structural` | +| Portuguese (Brazil) | `rules/pt-BR/` | `context`, `dedup`, `filler`, `structural`, `ultra` | | German | `rules/de/` | `context`, `filler`, `structural` | | French | `rules/fr/` | `context`, `filler`, `structural` | | Japanese | `rules/ja/` | `context`, `filler`, `structural` | -> **Parity note:** `en` and `es` packs have the full 5 categories; `pt-BR`, `de`, `fr`, `ja` ship 3 categories. The missing `dedup` and `ultra` categories silently fall back to the English built-ins. Contributions welcome to add `dedup.json` and `ultra.json` for the smaller packs. +> **Parity note:** `en`, `es`, and `pt-BR` packs have the full 5 categories; `de`, `fr`, `ja` ship 3 categories. The missing `dedup` and `ultra` categories silently fall back to the English built-ins. Contributions welcome to add `dedup.json` and `ultra.json` for the smaller packs. +> +> The `pt-BR` pack is based on **[Troglodita](https://github.com/leninejunior/troglodita)** by Lenine Júnior — a compression system designed from scratch for Brazilian Portuguese grammar (pleonasm reduction, PT-BR filler removal, technical abbreviations for the dev BR community). > > The canonical category list and per-category schema live in [`open-sse/services/compression/rules/_schema.json`](../../open-sse/services/compression/rules/_schema.json) (JSON Schema draft 2020-12). diff --git a/open-sse/services/compression/rules/pt-BR/context.json b/open-sse/services/compression/rules/pt-BR/context.json index c0f8ed6538..90ed2d58f0 100644 --- a/open-sse/services/compression/rules/pt-BR/context.json +++ b/open-sse/services/compression/rules/pt-BR/context.json @@ -4,35 +4,66 @@ "rules": [ { "name": "pt_context_setup", - "pattern": "\\b(?:segue o codigo|segue o código|abaixo esta o codigo|abaixo está o código|este e o codigo|este é o código)\\b\\s*[:.]?\\s*", + "pattern": "\\b(?:segue o codigo|segue o código|abaixo esta o codigo|abaixo está o código|este e o codigo|este é o código|aqui está o código)\\b\\s*[:.]?\\s*", "replacement": "Código:", "context": "user", "category": "context", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Code introduction framing → direct label" }, { "name": "pt_intent", - "pattern": "\\b(?:o que eu quero fazer e|o que eu quero fazer é|meu objetivo e|meu objetivo é|a ideia e|a ideia é)\\b\\s*", - "replacement": "Objetivo:", + "pattern": "\\b(?:o que eu quero fazer e|o que eu quero fazer é|meu objetivo e|meu objetivo é|a ideia e|a ideia é|o que preciso é)\\b\\s*", + "replacement": "Objetivo: ", "context": "user", "category": "context", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Intent declaration → label" }, { "name": "pt_question_directive", - "pattern": "\\b(?:voce pode explicar por que|você pode explicar por que|pode mostrar como|poderia mostrar como)\\b\\s*", + "pattern": "\\b(?:voce pode explicar por que|você pode explicar por que|pode mostrar como|poderia mostrar como|me explica como|me explica por que)\\b\\s*", "replacement": "Explique ", "context": "user", "category": "context", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Question framing → direct imperative" }, { "name": "pt_background", - "pattern": "\\b(?:como voce sabe|como você sabe|como falamos antes|como mencionei antes)\\b[,.]?\\s*", + "pattern": "\\b(?:como voce sabe|como você sabe|como falamos antes|como mencionei antes|como já disse|conforme mencionado)\\b[,.]?\\s*", "replacement": "", "context": "all", "category": "context", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Background references — context is already in conversation" + }, + { + "name": "pt_error_framing", + "pattern": "\\b(?:o erro que voce está vendo|o erro que você está vendo|esse erro acontece porque|esse erro ocorre porque|o motivo desse erro é)\\b\\s*", + "replacement": "Erro: ", + "context": "assistant", + "category": "context", + "minIntensity": "lite", + "description": "Error explanation padding → diagnostic label" + }, + { + "name": "pt_solution_framing", + "pattern": "\\b(?:para resolver isso|para corrigir isso|a solução é|a correção é|o que voce precisa fazer é|o que você precisa fazer é)\\b[,:]?\\s*", + "replacement": "Resolver: ", + "context": "assistant", + "category": "context", + "minIntensity": "full", + "description": "Solution introduction → action label" + }, + { + "name": "pt_step_framing", + "pattern": "\\b(?:primeiro voce precisa|primeiro você precisa|o primeiro passo é|em seguida voce deve|em seguida você deve)\\b\\s*", + "replacement": "1. ", + "context": "assistant", + "category": "context", + "minIntensity": "full", + "description": "Step-by-step padding → numbered list" } ] } diff --git a/open-sse/services/compression/rules/pt-BR/dedup.json b/open-sse/services/compression/rules/pt-BR/dedup.json new file mode 100644 index 0000000000..fa1c0d08aa --- /dev/null +++ b/open-sse/services/compression/rules/pt-BR/dedup.json @@ -0,0 +1,51 @@ +{ + "language": "pt-BR", + "category": "dedup", + "rules": [ + { + "name": "pt_repeated_context", + "pattern": "\\b(?:como falamos antes|como mencionamos|como vimos anteriormente|conforme discutido|como já expliquei)\\b[,.]?\\s*", + "replacement": "Ver acima. ", + "context": "all", + "category": "dedup", + "minIntensity": "lite", + "description": "References to earlier discussion" + }, + { + "name": "pt_repeated_question", + "pattern": "\\b(?:mesma pergunta de antes|já perguntei isso|é a mesma dúvida|repito a pergunta)\\b[,.]?\\s*", + "replacement": "[mesma pergunta] ", + "context": "user", + "category": "dedup", + "minIntensity": "lite", + "description": "Repeated question markers" + }, + { + "name": "pt_reestablished_context", + "pattern": "\\b(?:voltando ao código acima|voltando ao que falamos|retomando o assunto|voltando ao ponto)\\b[,.]?\\s*", + "replacement": "Re: ", + "context": "all", + "category": "dedup", + "minIntensity": "lite", + "description": "Context re-establishment" + }, + { + "name": "pt_summary", + "pattern": "\\b(?:resumindo o que discutimos|pra resumir|em resumo|resumidamente|recapitulando)\\b[,.]?\\s*", + "replacement": "Resumo: ", + "context": "assistant", + "category": "dedup", + "minIntensity": "lite", + "description": "Summary framing" + }, + { + "name": "pt_reiteration", + "pattern": "\\b(?:como eu disse|como mencionei|reitero que|repito que|novamente)\\b[,.]?\\s*", + "replacement": "", + "context": "all", + "category": "dedup", + "minIntensity": "lite", + "description": "Self-reiteration markers — content already in context" + } + ] +} diff --git a/open-sse/services/compression/rules/pt-BR/filler.json b/open-sse/services/compression/rules/pt-BR/filler.json index a9ce881656..6763e9bf6b 100644 --- a/open-sse/services/compression/rules/pt-BR/filler.json +++ b/open-sse/services/compression/rules/pt-BR/filler.json @@ -8,63 +8,144 @@ "replacement": "", "context": "all", "category": "filler", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Formal request framing — unnecessary in direct communication" }, { "name": "pt_pleasantries", - "pattern": "\\b(?:ola|olá|oi|bom dia|boa tarde|boa noite|obrigado|obrigada|valeu)\\b[,.!?\\s]*", + "pattern": "\\b(?:ola|olá|oi|bom dia|boa tarde|boa noite|obrigado|obrigada|valeu|tchau|até mais)\\b[,.!?\\s]*", "replacement": "", "context": "all", "category": "filler", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Greetings and farewells — zero information content" + }, + { + "name": "pt_amenities", + "pattern": "\\b(?:claro|com certeza|fico feliz em ajudar|seria um prazer|sem problemas|fique à vontade|fique a vontade|não se preocupe|nao se preocupe)\\b[!,.\\s]*", + "replacement": "", + "context": "assistant", + "category": "filler", + "minIntensity": "lite", + "description": "Assistant amenities that add no technical value" }, { "name": "pt_hedging", - "pattern": "\\b(?:eu acho que|eu acredito que|parece que|talvez|provavelmente|possivelmente)\\b\\s*", + "pattern": "\\b(?:eu acho que|eu acredito que|parece que|talvez|provavelmente|possivelmente|aparentemente)\\b\\s*", "replacement": "", "context": "all", "category": "filler", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Uncertainty hedging — if uncertain, state the fact directly" + }, + { + "name": "pt_hesitations", + "pattern": "\\b(?:talvez valha a pena|seria interessante considerar|pode ser que|não sei se|a depender do caso)\\b\\s*", + "replacement": "", + "context": "all", + "category": "filler", + "minIntensity": "lite", + "description": "Extended hesitation phrases common in PT-BR technical writing" }, { "name": "pt_filler_adverbs", - "pattern": "\\b(?:basicamente|essencialmente|na verdade|literalmente|simplesmente|atualmente)\\b\\s*", + "pattern": "\\b(?:basicamente|essencialmente|na verdade|literalmente|simplesmente|atualmente|obviamente|claramente|naturalmente|certamente|definitivamente)\\b\\s*", "replacement": "", "context": "all", "category": "filler", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Filler adverbs — 'muletas' that pad without adding meaning" + }, + { + "name": "pt_emphasis_fillers", + "pattern": "\\b(?:vale ressaltar|vale mencionar|vale notar|vale lembrar|é importante notar que|é importante mencionar que|convém destacar que)\\b\\s*", + "replacement": "", + "context": "all", + "category": "filler", + "minIntensity": "lite", + "description": "Emphasis markers — if it's worth noting, just note it" }, { "name": "pt_self_reference", - "pattern": "^(?:eu estou tentando|estou tentando|eu quero|eu preciso|gostaria de)\\b\\s*", + "pattern": "^(?:eu estou tentando|estou tentando|eu quero|eu preciso|gostaria de|estou precisando)\\b\\s*", "replacement": "", "context": "user", "category": "filler", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "User self-reference at start of sentence" }, { "name": "pt_verbose_request", - "pattern": "\\b(?:voce poderia me explicar|você poderia me explicar|poderia detalhar|gostaria que voce explicasse|gostaria que você explicasse)\\b\\s*", + "pattern": "\\b(?:voce poderia me explicar|você poderia me explicar|poderia detalhar|gostaria que voce explicasse|gostaria que você explicasse|me explica)\\b\\s*", "replacement": "Explique ", "context": "user", "category": "filler", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Verbose request phrasing → direct imperative" }, { "name": "pt_qualifiers", - "pattern": "\\b(?:um pouco|meio que|tipo assim|de certa forma|mais ou menos)\\b\\s*", + "pattern": "\\b(?:um pouco|meio que|tipo assim|de certa forma|mais ou menos|de alguma forma|de alguma maneira)\\b\\s*", "replacement": "", "context": "all", "category": "filler", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Vague qualifiers that weaken statements" }, { "name": "pt_softeners", - "pattern": "\\b(?:se possivel|se possível|quando puder|sem pressa|so queria|só queria)\\b[,.!?\\s]*", + "pattern": "\\b(?:se possivel|se possível|quando puder|sem pressa|so queria|só queria|se não for incomodo|se não for incômodo)\\b[,.!?\\s]*", "replacement": "", "context": "all", "category": "filler", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Request softeners — unnecessary in AI interaction" + }, + { + "name": "pt_redundancies", + "pattern": "\\b(?:subir pra cima|descer pra baixo|voltar atrás|a razão é porque|o motivo é porque|entrar pra dentro|sair pra fora)\\b", + "replacement": "", + "replacementMap": { + "subir pra cima": "subir", + "descer pra baixo": "descer", + "voltar atrás": "voltar", + "a razão é porque": "porque", + "o motivo é porque": "porque", + "entrar pra dentro": "entrar", + "sair pra fora": "sair" + }, + "flags": "gi", + "context": "all", + "category": "filler", + "minIntensity": "lite", + "description": "Pleonasms — redundant phrases native to PT-BR speech" + }, + { + "name": "pt_linking_verbs", + "pattern": "\\b(?:está sendo|foi feito|tem que ser|precisa ser|deve ser feito|foi realizado)\\b\\s*", + "replacement": "", + "replacementMap": { + "está sendo": "é", + "foi feito": "", + "tem que ser": "deve", + "precisa ser": "deve", + "deve ser feito": "fazer", + "foi realizado": "" + }, + "flags": "gi", + "context": "all", + "category": "filler", + "minIntensity": "full", + "description": "Verbose linking verb constructions → compact form" + }, + { + "name": "pt_explanation_padding", + "pattern": "\\b(?:o que acontece é que|o que está acontecendo é que|o problema é que|a questão é que|o ponto é que)\\b\\s*", + "replacement": "", + "context": "assistant", + "category": "filler", + "minIntensity": "full", + "description": "Explanation padding before the actual content" } ] } diff --git a/open-sse/services/compression/rules/pt-BR/structural.json b/open-sse/services/compression/rules/pt-BR/structural.json index 8144ef2b6e..7209024764 100644 --- a/open-sse/services/compression/rules/pt-BR/structural.json +++ b/open-sse/services/compression/rules/pt-BR/structural.json @@ -4,27 +4,75 @@ "rules": [ { "name": "pt_purpose", - "pattern": "\\b(?:a fim de|com o objetivo de|para poder)\\b\\s*", + "pattern": "\\b(?:a fim de|com o objetivo de|para poder|com a finalidade de|com o intuito de)\\b\\s*", "replacement": "para ", "context": "all", "category": "structural", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Verbose purpose connectors → 'para'" }, { "name": "pt_connectors", - "pattern": "\\b(?:alem disso|além disso|adicionalmente|outrossim)\\b\\s*", + "pattern": "\\b(?:alem disso|além disso|adicionalmente|outrossim|não obstante|por outro lado)\\b[,]?\\s*", "replacement": "também ", "context": "all", "category": "structural", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Verbose additive connectors" }, { "name": "pt_emphasis", - "pattern": "\\b(?:muito|realmente|extremamente|bastante)\\s+(?=[a-záéíóúâêôãõç])", + "pattern": "\\b(?:muito|realmente|extremamente|bastante|totalmente|completamente|absolutamente)\\s+(?=[a-záéíóúâêôãõç])", "replacement": "", "context": "all", "category": "structural", - "minIntensity": "lite" + "minIntensity": "lite", + "description": "Intensity adverbs before adjectives — rarely add precision" + }, + { + "name": "pt_passive_voice", + "pattern": "\\b(?:é necessário que|é preciso que|é importante que|é fundamental que|é essencial que)\\b\\s*", + "replacement": "Deve ", + "context": "all", + "category": "structural", + "minIntensity": "full", + "description": "Impersonal passive constructions → direct imperative" + }, + { + "name": "pt_causality_verbose", + "pattern": "\\b(?:isso acontece porque|isso ocorre porque|isso se deve ao fato de que|a causa disso é que)\\b\\s*", + "replacement": "Causa: ", + "context": "assistant", + "category": "structural", + "minIntensity": "full", + "description": "Verbose causality explanation → diagnostic label" + }, + { + "name": "pt_consequence", + "pattern": "\\b(?:como consequência|como resultado|por conta disso|em decorrência disso|por causa disso)\\b[,]?\\s*", + "replacement": "→ ", + "context": "all", + "category": "structural", + "minIntensity": "full", + "description": "Consequence connectors → arrow notation (troglodita style)" + }, + { + "name": "pt_comparison_verbose", + "pattern": "\\b(?:em comparação com|quando comparado com|se compararmos com|diferente de)\\b\\s*", + "replacement": "vs ", + "context": "all", + "category": "structural", + "minIntensity": "full", + "description": "Verbose comparisons → compact 'vs'" + }, + { + "name": "pt_conclusion", + "pattern": "\\b(?:sendo assim|portanto|dessa forma|desse modo|consequentemente|em suma|resumindo)\\b[,]?\\s*", + "replacement": "∴ ", + "context": "all", + "category": "structural", + "minIntensity": "full", + "description": "Conclusion connectors → therefore symbol" } ] } diff --git a/open-sse/services/compression/rules/pt-BR/ultra.json b/open-sse/services/compression/rules/pt-BR/ultra.json new file mode 100644 index 0000000000..606cc22457 --- /dev/null +++ b/open-sse/services/compression/rules/pt-BR/ultra.json @@ -0,0 +1,192 @@ +{ + "language": "pt-BR", + "category": "ultra", + "rules": [ + { + "name": "pt_terse_leader_phrases", + "pattern": "^(?:eu vou|vou|eu posso|posso|deixa eu|deixe-me|vamos|a gente pode)\\s+(?=[a-záéíóúâêôãõç])", + "replacement": "", + "flags": "gm", + "context": "all", + "category": "terse", + "minIntensity": "full", + "description": "Leader phrases at sentence start — PT-BR equivalent of 'I'll/Let me'" + }, + { + "name": "pt_ultra_banco_de_dados", + "pattern": "\\bbanco de dados\\b", + "replacement": "BD", + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "banco de dados → BD" + }, + { + "name": "pt_ultra_configuracao", + "pattern": "\\b(?:configuração|configurações)\\b", + "replacement": "config", + "replacementMap": { + "configuração": "config", + "configurações": "configs" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "configuração → config" + }, + { + "name": "pt_ultra_funcao", + "pattern": "\\b(?:função|funções)\\b", + "replacement": "fn", + "replacementMap": { + "função": "fn", + "funções": "fns" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "função → fn" + }, + { + "name": "pt_ultra_requisicao", + "pattern": "\\b(?:requisição|requisições)\\b", + "replacement": "req", + "replacementMap": { + "requisição": "req", + "requisições": "reqs" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "requisição → req" + }, + { + "name": "pt_ultra_resposta", + "pattern": "\\bresposta\\b", + "replacement": "res", + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "resposta → res" + }, + { + "name": "pt_ultra_implementacao", + "pattern": "\\b(?:implementação|implementações)\\b", + "replacement": "impl", + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "implementação → impl" + }, + { + "name": "pt_ultra_autenticacao", + "pattern": "\\b(?:autenticação|autenticações)\\b", + "replacement": "auth", + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "autenticação → auth" + }, + { + "name": "pt_ultra_autorizacao", + "pattern": "\\b(?:autorização|autorizações)\\b", + "replacement": "authz", + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "autorização → authz" + }, + { + "name": "pt_ultra_aplicacao", + "pattern": "\\b(?:aplicação|aplicações)\\b", + "replacement": "app", + "replacementMap": { + "aplicação": "app", + "aplicações": "apps" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "aplicação → app" + }, + { + "name": "pt_ultra_dependencia", + "pattern": "\\b(?:dependência|dependências)\\b", + "replacement": "dep", + "replacementMap": { + "dependência": "dep", + "dependências": "deps" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "dependência → dep" + }, + { + "name": "pt_ultra_repositorio", + "pattern": "\\b(?:repositório|repositórios)\\b", + "replacement": "repo", + "replacementMap": { + "repositório": "repo", + "repositórios": "repos" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "repositório → repo" + }, + { + "name": "pt_ultra_variavel", + "pattern": "\\b(?:variável|variáveis)\\b", + "replacement": "var", + "replacementMap": { + "variável": "var", + "variáveis": "vars" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "variável → var" + }, + { + "name": "pt_ultra_componente", + "pattern": "\\b(?:componente|componentes)\\b", + "replacement": "comp", + "replacementMap": { + "componente": "comp", + "componentes": "comps" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "componente → comp" + }, + { + "name": "pt_ultra_diretorio", + "pattern": "\\b(?:diretório|diretórios)\\b", + "replacement": "dir", + "replacementMap": { + "diretório": "dir", + "diretórios": "dirs" + }, + "flags": "gi", + "context": "all", + "category": "ultra", + "minIntensity": "ultra", + "description": "diretório → dir" + } + ] +}