feat(sse): full language parity for output styles (es/de/fr/it/ru/zh + autoDetect) (#12289)

* feat(sse): add it/ru/zh caveman output instructions

* fix(sse): expose the dormant terse-prose translations through the catalog

* feat(sse): translate less-code to es/de/fr/it/ru/zh

* feat(sse): translate ponytail to es/de/fr/it/ru/zh

* feat(sse): translate i-have-adhd to es/de/fr/it/ru/zh

* test(sse): anchor wave-2 output-style translations to their own language

* feat(dashboard): offer every output-style language in the default-language selector

* feat(sse): let autoDetect pick the output-style instruction language

* docs(compression): consolidate the output-style tables and record full language parity
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-09-01 11:13:26 -03:00
committed by GitHub
parent fe5f4b0ef9
commit abbbca216d
11 changed files with 278 additions and 36 deletions

View File

@@ -182,22 +182,6 @@ With Stacked: 10K-2.5K tokens sent (78-95% eligible RTK+Caveman range
---
## Output Styles
Output styles inject a system prompt instruction to steer the model's writing style. They are defined in the output style catalog and support multiple languages and intensity levels (`lite`, `full`, `ultra`).
| Style | Description | Supported Languages | Levels |
| --- | --- | --- | --- |
| `terse-prose` | Drop filler/articles/hedging; keep technical substance exact. | `en`, `pt-BR`, `ja`, `id`, `vi` | `lite`, `full`, `ultra` |
| `less-code` | YAGNI ladder: smallest working change, no unrequested abstractions. | `en`, `pt-BR`, `vi`, `ja`, `id` | `lite`, `full`, `ultra` |
| `ponytail` | Lazy senior-dev discipline: climb the YAGNI ladder, fix root cause, smallest working diff. | `en`, `pt-BR`, `vi`, `ja`, `id` | `lite`, `full`, `ultra` |
| `i-have-adhd` | Action-first output: next action leads, steps numbered, one concrete next step, no preamble. | `en`, `pt-BR`, `vi`, `ja`, `id` | `lite`, `full`, `ultra` |
| `terse-cjk` | Classical-Chinese ultra-terse style (locale-gated to zh). | `zh` | `lite`, `full`, `ultra` |
Each level appends a shared boundary clause ensuring that code blocks, URLs, file paths, commands, and identifiers remain verbatim.
---
## Configuration
### Dashboard
@@ -472,11 +456,11 @@ together and are injected in catalog order.
| Style | `id` | What it does | Instruction languages |
| --- | --- | --- | --- |
| Terse prose | `terse-prose` | Drop filler/articles/hedging; keep technical substance exact. Same text as the legacy caveman output mode (referenced, not re-typed). | en, pt-BR, ja, id |
| Less code | `less-code` | YAGNI ladder: smallest working change, no unrequested abstractions. | en only (backlog: [#10426](https://github.com/diegosouzapw/OmniRoute/issues/10426)) |
| Ponytail (lazy senior dev) | `ponytail` | "The best code is the code never written": reuse > rewrite, root cause > symptom, shortest working diff. | en, pt-BR, vi, ja, id |
| I have ADHD (action-first) | `i-have-adhd` | Action first (command/path/snippet before prose), numbered bounded steps, ONE concrete next step, no preamble/recap/closers. Adapted from [ayghri/i-have-adhd](https://github.com/ayghri/i-have-adhd) (MIT). | en, pt-BR, vi, ja, id |
| Terse CJK (文言) | `terse-cjk` | Classical-Chinese ultra-terse style. | zh (locale-gated: only offered when the detected language is `zh`) |
| Terse prose | `terse-prose` | Drop filler/articles/hedging; keep technical substance exact. Same text as the legacy caveman output mode (referenced, not re-typed). | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi |
| Less code | `less-code` | YAGNI ladder: smallest working change, no unrequested abstractions. | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi |
| Ponytail (lazy senior dev) | `ponytail` | "The best code is the code never written": reuse > rewrite, root cause > symptom, shortest working diff. | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi |
| I have ADHD (action-first) | `i-have-adhd` | Action first (command/path/snippet before prose), numbered bounded steps, ONE concrete next step, no preamble/recap/closers. Adapted from [ayghri/i-have-adhd](https://github.com/ayghri/i-have-adhd) (MIT). | en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi |
| Terse CJK (文言) | `terse-cjk` | Classical-Chinese ultra-terse style. | zh (locale-gated: only offered when the resolved language is `zh`) |
Every style ships three intensity levels — `lite`, `full`, `ultra` — and every level
ends with the shared boundaries clause, which keeps code blocks, file paths, commands,
@@ -508,7 +492,11 @@ the selection as:
```
Back-compat: the legacy `outputMode: "caveman"` combo setting still works and maps to
`terse-prose`, byte-identical to the old injection in all four legacy languages.
`terse-prose`, byte-identical to the old injection in every legacy language.
Language selection: with `languageConfig.enabled` on, `autoDetect` picks the
language of the latest user message (same detector as the input engines);
turning `autoDetect` off pins `defaultLanguage`. Off → English.
The style × language matrix is pinned by
`tests/unit/compression/output-styles-i18n-matrix.test.ts`: a new style cannot ship

View File

@@ -583,7 +583,7 @@ persistence and telemetry all enumerate the catalog — there is no other list t
The instruction text must be **static and deterministic** per
`(id, level, language)` — `${SHARED_BOUNDARIES}` is the only interpolation allowed.
2. **Translate it.** Ship at least a `pt-BR` block under `i18n`; `ponytail` and
`i-have-adhd` (en, pt-BR, vi, ja, id) are the reference shape. A deliberately
`i-have-adhd` (en, pt-BR, es, de, fr, it, ru, zh, ja, id, vi) are the reference shape. A deliberately
single-language style sets `locale` instead (like `terse-cjk` → `zh`) and is then
only offered under that locale.
3. **Update the matrix guard** — add the style's languages to `BASELINE_LANGUAGES` in

View File

@@ -1580,12 +1580,12 @@ export async function handleChatCore({
await import("../services/compression/outputStyles/backCompat.ts");
const selection = resolveOutputStyleSelection(config);
if (selection.length > 0) {
const { applyOutputStyles } =
const { applyOutputStyles, resolveOutputStyleLanguage } =
await import("../services/compression/outputStyles/apply.ts");
const outputStyleLanguage =
config.languageConfig?.enabled === true
? config.languageConfig.defaultLanguage
: "en";
const outputStyleLanguage = resolveOutputStyleLanguage(
config.languageConfig,
body as Parameters<typeof resolveOutputStyleLanguage>[1]
);
outputStyleResult = applyOutputStyles(
body as Parameters<typeof applyOutputStyles>[0],
selection,

View File

@@ -69,6 +69,21 @@ export const CAVEMAN_INSTRUCTION_BY_LANGUAGE = {
full: `Trả lời cộc lốc như người tối cổ thông minh. Bỏ mạo từ, từ đệm, sáo rỗng, rào đón. Chấp nhận câu rút gọn. Dùng từ đồng nghĩa ngắn. Giữ nguyên mọi nội dung kỹ thuật, code, lỗi, URL và định danh. ${SHARED_BOUNDARIES}`,
ultra: `Trả lời cực kỳ cộc lốc. Nén tối đa. Như điện tín. Viết tắt (DB/auth/config/req/res/fn/impl), bỏ liên từ, dùng mũi tên cho quan hệ nhân quả (X → Y). Một từ nếu một từ là đủ. Không bao giờ viết tắt ký hiệu code, tên API, chuỗi lỗi, URL hoặc định danh. ${SHARED_BOUNDARIES}`,
},
it: {
lite: `Rispondi conciso. Togli riempitivi, convenevoli e incertezze. Mantieni termini tecnici, codice, errori, URL e identificatori esatti. ${SHARED_BOUNDARIES}`,
full: `Rispondi secco e compatto. Frammenti OK. Mantieni tutto il contenuto tecnico, codice, errori, URL e identificatori esatti. ${SHARED_BOUNDARIES}`,
ultra: `Rispondi ultra compatto. Usa prosa tecnica breve e abbreviazioni comuni come DB/auth/config/req/res/fn. Mai abbreviare simboli di codice, API, errori, URL o identificatori. ${SHARED_BOUNDARIES}`,
},
ru: {
lite: `Отвечай кратко. Убирай воду, любезности и оговорки. Технические термины, код, ошибки, URL и идентификаторы сохраняй точно. ${SHARED_BOUNDARIES}`,
full: `Отвечай сухо и сжато. Фрагменты допустимы. Всё техническое содержимое, код, ошибки, URL и идентификаторы сохраняй точно. ${SHARED_BOUNDARIES}`,
ultra: `Отвечай ультракратко. Короткая техническая проза и общепринятые сокращения вроде DB/auth/config/req/res/fn. Никогда не сокращай символы кода, API, строки ошибок, URL или идентификаторы. ${SHARED_BOUNDARIES}`,
},
zh: {
lite: `回答要简洁。去掉废话、客套和含糊措辞。技术术语、代码、错误、URL 和标识符保持原样。${SHARED_BOUNDARIES}`,
full: `回答干脆紧凑。可用短句。所有技术内容、代码、错误、URL 和标识符保持原样。${SHARED_BOUNDARIES}`,
ultra: `回答极度紧凑。用简短技术表述和常见缩写如 DB/auth/config/req/res/fn。绝不缩写代码符号、API 名、错误串、URL 或标识符。${SHARED_BOUNDARIES}`,
},
} as const;
const CAVEMAN_OUTPUT_MARKER = "[OmniRoute Caveman Output Mode]";

View File

@@ -1,4 +1,5 @@
import { SHARED_BOUNDARIES, shouldBypassCavemanOutputMode } from "../outputMode.ts";
import { detectCompressionLanguage } from "../languageDetector.ts";
import { OUTPUT_STYLE_IDS, outputStyleMeta } from "./catalog.ts";
export type OutputStyleLevel = "lite" | "full" | "ultra";
@@ -29,6 +30,51 @@ export interface OutputStylesResult {
appliedStyles?: OutputStyleSelectionEntry[];
}
interface OutputStyleLanguageConfig {
enabled?: boolean;
autoDetect?: boolean;
defaultLanguage?: string;
}
function lastUserText(body: ChatRequestBody): string {
const messages = Array.isArray(body.messages) ? body.messages : [];
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
if (message?.role !== "user") continue;
if (typeof message.content === "string" && message.content.trim()) return message.content;
if (Array.isArray(message.content)) {
const text = message.content
.map((part) =>
part && typeof part === "object" && typeof (part as { text?: unknown }).text === "string"
? (part as { text: string }).text
: ""
)
.join(" ")
.trim();
if (text) return text;
}
}
return "";
}
/**
* Resolve which language the output-style instructions inject in.
* Disabled → en. autoDetect → language of the latest user message (the input
* engines already use the same detector); otherwise the configured default.
*/
export function resolveOutputStyleLanguage(
languageConfig: OutputStyleLanguageConfig | undefined,
body: ChatRequestBody
): string {
if (languageConfig?.enabled !== true) return "en";
if (languageConfig.autoDetect === true) {
const text = lastUserText(body);
if (text) return detectCompressionLanguage(text);
}
return languageConfig.defaultLanguage || "en";
}
/** Single idempotency marker guarding the unified injection (D-A: one marker for all styles). */
export const OUTPUT_STYLE_MARKER = "[OmniRoute Output Styles]";

View File

@@ -39,6 +39,12 @@ export const OUTPUT_STYLE_CATALOG: Record<string, OutputStyle> = {
levels: CAVEMAN_INSTRUCTION_BY_LANGUAGE.en,
i18n: {
"pt-BR": CAVEMAN_INSTRUCTION_BY_LANGUAGE["pt-BR"],
es: CAVEMAN_INSTRUCTION_BY_LANGUAGE.es,
de: CAVEMAN_INSTRUCTION_BY_LANGUAGE.de,
fr: CAVEMAN_INSTRUCTION_BY_LANGUAGE.fr,
it: CAVEMAN_INSTRUCTION_BY_LANGUAGE.it,
ru: CAVEMAN_INSTRUCTION_BY_LANGUAGE.ru,
zh: CAVEMAN_INSTRUCTION_BY_LANGUAGE.zh,
ja: CAVEMAN_INSTRUCTION_BY_LANGUAGE.ja,
id: CAVEMAN_INSTRUCTION_BY_LANGUAGE.id,
vi: CAVEMAN_INSTRUCTION_BY_LANGUAGE.vi,
@@ -75,6 +81,36 @@ export const OUTPUT_STYLE_CATALOG: Record<string, OutputStyle> = {
full: `Bertindak seperti dev senior malas yang menerapkan YAGNI. Hanya perubahan terkecil yang berfungsi. Tanpa abstraksi yang tidak diminta, generalisasi prematur, lapisan ekstra, atau scaffolding defensif yang tidak diminta. Pakai ulang kode yang ada daripada menambah kode baru. ${SHARED_BOUNDARIES}`,
ultra: `Disiplin diff minimal. Sentuh baris sesedikit mungkin yang membuatnya berfungsi. Nol file, kelas, atau config baru kecuali sangat diperlukan. Inline daripada abstract. Tanpa tambahan "mumpung di sini". ${SHARED_BOUNDARIES}`,
},
es: {
lite: `Escribe el cambio más pequeño que satisfaga la petición. Evita abstracciones especulativas. ${SHARED_BOUNDARIES}`,
full: `Actúa como un dev senior perezoso aplicando YAGNI. Solo el cambio funcional más pequeño. Sin abstracciones no pedidas, sin generalización prematura, sin capas extra, sin andamiaje defensivo que la petición no pidió. Reutiliza código existente antes que añadir código nuevo. ${SHARED_BOUNDARIES}`,
ultra: `Disciplina de diff mínimo. Toca las menos líneas que lo hagan funcionar. Cero archivos, clases o config nuevos salvo estricta necesidad. Inline antes que abstracto. Sin extras de "ya que estamos". ${SHARED_BOUNDARIES}`,
},
de: {
lite: `Schreibe die kleinste Änderung, die die Anforderung erfüllt. Keine spekulativen Abstraktionen. ${SHARED_BOUNDARIES}`,
full: `Handle wie ein fauler Senior-Entwickler mit YAGNI. Nur die kleinste funktionierende Änderung. Keine unbestellten Abstraktionen, keine vorzeitige Generalisierung, keine Extra-Schichten, kein defensives Gerüst, das die Anforderung nicht verlangt hat. Bestehenden Code wiederverwenden statt neuen hinzufügen. ${SHARED_BOUNDARIES}`,
ultra: `Minimal-Diff-Disziplin. So wenige Zeilen anfassen wie nötig. Null neue Dateien, Klassen oder Config, außer zwingend erforderlich. Inline statt abstrakt. Keine "Wenn wir schon dabei sind"-Extras. ${SHARED_BOUNDARIES}`,
},
fr: {
lite: `Écris le plus petit changement qui satisfait la demande. Évite les abstractions spéculatives. ${SHARED_BOUNDARIES}`,
full: `Agis comme un dev senior paresseux appliquant YAGNI. Uniquement le plus petit changement fonctionnel. Pas d'abstractions non demandées, pas de généralisation prématurée, pas de couches en plus, pas d'échafaudage défensif que la demande n'a pas exigé. Réutilise le code existant plutôt que d'en ajouter. ${SHARED_BOUNDARIES}`,
ultra: `Discipline du diff minimal. Touche le moins de lignes possible pour que ça marche. Zéro nouveau fichier, classe ou config sauf stricte nécessité. Inline plutôt qu'abstrait. Pas d'extras « tant qu'on y est ». ${SHARED_BOUNDARIES}`,
},
it: {
lite: `Scrivi la modifica più piccola che soddisfa la richiesta. Evita astrazioni speculative. ${SHARED_BOUNDARIES}`,
full: `Agisci come un dev senior pigro che applica YAGNI. Solo la modifica funzionante più piccola. Niente astrazioni non richieste, niente generalizzazione prematura, niente strati extra, niente impalcature difensive che la richiesta non ha chiesto. Riusa il codice esistente invece di aggiungerne di nuovo. ${SHARED_BOUNDARIES}`,
ultra: `Disciplina del diff minimo. Tocca il minor numero di righe che lo fa funzionare. Zero nuovi file, classi o config se non strettamente necessari. Inline invece che astratto. Niente extra "già che ci siamo". ${SHARED_BOUNDARIES}`,
},
ru: {
lite: `Пиши наименьшее изменение, которое закрывает запрос. Без спекулятивных абстракций. ${SHARED_BOUNDARIES}`,
full: `Действуй как ленивый сеньор с YAGNI. Только наименьшее работающее изменение. Без незапрошенных абстракций, без преждевременного обобщения, без лишних слоёв, без защитных лесов, которых запрос не требовал. Переиспользуй существующий код вместо добавления нового. ${SHARED_BOUNDARIES}`,
ultra: `Дисциплина минимального diff. Трогай как можно меньше строк. Ноль новых файлов, классов или конфигов без строгой необходимости. Inline вместо абстракции. Без довесков «раз уж мы здесь». ${SHARED_BOUNDARIES}`,
},
zh: {
lite: `写出满足需求的最小改动。跳过投机性的抽象。${SHARED_BOUNDARIES}`,
full: `像一名践行 YAGNI 的懒惰资深开发者。只做最小的可用改动。不写未被要求的抽象,不做过早的泛化,不加多余的层,不搭需求没要的防御性脚手架。优先复用现有代码而不是新增代码。${SHARED_BOUNDARIES}`,
ultra: `最小 diff 纪律。只动让它能工作的最少行数。除非绝对必要,零新文件、新类、新配置。内联优于抽象。不做"顺手再改点"的额外事。${SHARED_BOUNDARIES}`,
},
},
},
// Ponytail (lazy-senior-dev mode) — integrated into the output-style registry
@@ -116,6 +152,36 @@ export const OUTPUT_STYLE_CATALOG: Record<string, OutputStyle> = {
full: `# Ponytail — dev senior malas\n\nKamu adalah senior developer yang malas. Malas = efisien, bukan ceroboh. Kode terbaik adalah kode yang tidak pernah ditulis.\n\nSebelum menulis kode, berhenti di anak tangga pertama yang tepat:\n1. Apakah ini perlu? (YAGNI)\n2. Sudah ada di codebase? Pakai ulang.\n3. Stdlib melakukan ini? Pakai.\n4. Fitur platform atau dep terinstal mencakup? Pakai.\n5. Bisa satu baris? Buat satu baris.\n6. Baru tulis minimum yang bekerja.\n\nPerbaiki bug = akar masalah, bukan gejala. Grep semua pemanggil fungsi yang disentuh; perbaiki fungsi bersama sekali — satu guard di sana lebih kecil daripada satu guard per pemanggil.\n\nAturan:\n- Tanpa abstraksi yang tidak diminta. Tanpa dep baru. Tanpa boilerplate.\n- Hapus > tambah. Membosankan > cerdas. Paling sedikit file.\n- Diff terpendek menang — tapi hanya setelah paham masalah.\n- Tanyai permintaan kompleks: "Kamu perlu X, atau Y mencakup?"\n- Saat dua solusi imbang, pilih yang benar untuk edge-case. ${SHARED_BOUNDARIES}`,
ultra: `# Ponytail (ultra)\nDev senior malas. Kode terbaik = tak pernah ditulis. Sebelum kode: YAGNI → pakai ulang → stdlib → platform → dep → satu baris → minimum. Perbaiki akar, bukan gejala: grep semua caller, perbaiki fungsi bersama sekali. Tanpa abstraksi tak diminta, tanpa dep baru, tanpa boilerplate. Hapus > tambah. Paling sedikit file. Diff terpendek, hanya setelah paham masalah. Tanya permintaan kompleks. Edge-case benar saat imbang. ${SHARED_BOUNDARIES}`,
},
es: {
lite: `# Ponytail (lite)\nAntes de escribir código: ¿necesita existir? ¿Ya existe aquí? ¿La stdlib o una dependencia instalada lo cubre? Solo entonces: escribe el mínimo. Reutilizar antes que reescribir. ${SHARED_BOUNDARIES}`,
full: `# Ponytail — dev senior perezoso\n\nEres un desarrollador senior perezoso. Perezoso = eficiente, no descuidado. El mejor código es el que nunca se escribió.\n\nAntes de escribir código, detente en el primer peldaño que aguante:\n1. ¿Esto necesita existir? (YAGNI)\n2. ¿Ya existe en este codebase? Reutilízalo.\n3. ¿La stdlib lo hace? Úsala.\n4. ¿Una función de la plataforma o una dependencia instalada lo cubre? Úsala.\n5. ¿Puede ser una línea? Hazlo una línea.\n6. Solo entonces: escribe el mínimo que funcione.\n\nCorregir un bug = causa raíz, no síntoma. Haz grep de cada caller de la función que tocas; corrige la función compartida una vez — un guard ahí es un diff más pequeño que uno por caller.\n\nReglas:\n- Sin abstracciones no pedidas. Sin dependencias nuevas. Sin boilerplate.\n- Borrar > añadir. Aburrido > ingenioso. Menos archivos.\n- Gana el diff más corto que funcione — pero solo después de entender el problema.\n- Cuestiona peticiones complejas: "¿Necesitas X, o Y lo cubre?"\n- Si dos soluciones empatan, elige la correcta en los edge cases. ${SHARED_BOUNDARIES}`,
ultra: `# Ponytail (ultra)\nDev senior perezoso. Mejor código = el que nunca se escribió. Antes de codificar: YAGNI → reutilizar → stdlib → plataforma → dependencia → una línea → mínimo que funcione. Corrige la causa raíz, no el síntoma: grep a cada caller, parchea la función compartida una vez. Sin abstracciones no pedidas, sin dependencias nuevas, sin boilerplate. Borrar > añadir. Menos archivos. Diff más corto, solo tras entender el problema. Cuestiona lo complejo. Ante empate, lo correcto en edge cases. ${SHARED_BOUNDARIES}`,
},
de: {
lite: `# Ponytail (lite)\nVor dem Codeschreiben: Muss das existieren? Existiert es hier schon? Deckt die Stdlib oder eine installierte Dependency es ab? Erst dann: das Minimum schreiben. Wiederverwenden statt neu schreiben. ${SHARED_BOUNDARIES}`,
full: `# Ponytail — fauler Senior-Entwickler\n\nDu bist ein fauler Senior-Entwickler. Faul = effizient, nicht nachlässig. Der beste Code ist der, der nie geschrieben wurde.\n\nBevor du Code schreibst, halte auf der ersten tragfähigen Stufe an:\n1. Muss das existieren? (YAGNI)\n2. Existiert es schon in dieser Codebase? Wiederverwenden.\n3. Kann die Stdlib das? Nutzen.\n4. Deckt ein Plattform-Feature oder eine installierte Dependency es ab? Nutzen.\n5. Geht es in einer Zeile? Mach eine Zeile draus.\n6. Erst dann: das Minimum schreiben, das funktioniert.\n\nBugfix = Ursache, nicht Symptom. Grep jeden Caller der Funktion, die du anfasst; fixe die gemeinsame Funktion einmal — ein Guard dort ist ein kleinerer Diff als einer pro Caller.\n\nRegeln:\n- Keine unbestellten Abstraktionen. Keine neuen Dependencies. Kein Boilerplate.\n- Löschen > Hinzufügen. Langweilig > clever. So wenige Dateien wie möglich.\n- Der kürzeste funktionierende Diff gewinnt — aber erst, nachdem du das Problem verstanden hast.\n- Hinterfrage komplexe Anforderungen: "Brauchst du X, oder deckt Y es ab?"\n- Bei Gleichstand die Lösung wählen, die in Edge Cases korrekt ist. ${SHARED_BOUNDARIES}`,
ultra: `# Ponytail (ultra)\nFauler Senior-Entwickler. Bester Code = nie geschrieben. Vor jedem Code: YAGNI → wiederverwenden → Stdlib → Plattform → Dependency → eine Zeile → funktionierendes Minimum. Ursache fixen, nicht Symptom: jeden Caller greppen, gemeinsame Funktion einmal patchen. Keine unbestellten Abstraktionen, keine neuen Dependencies, kein Boilerplate. Löschen > Hinzufügen. Wenigste Dateien. Kürzester funktionierender Diff, erst nach Verständnis des Problems. Komplexes hinterfragen. Bei Gleichstand: korrekt in Edge Cases. ${SHARED_BOUNDARIES}`,
},
fr: {
lite: `# Ponytail (lite)\nAvant d'écrire du code : doit-il exister ? Existe-t-il déjà ici ? La stdlib ou une dépendance installée le couvre-t-elle ? Seulement alors : écris le minimum. Réutiliser plutôt que réécrire. ${SHARED_BOUNDARIES}`,
full: `# Ponytail — dev senior paresseux\n\nTu es un développeur senior paresseux. Paresseux = efficace, pas négligent. Le meilleur code est celui qui n'a jamais été écrit.\n\nAvant d'écrire du code, arrête-toi au premier barreau qui tient :\n1. Cela doit-il exister ? (YAGNI)\n2. Existe-t-il déjà dans cette codebase ? Réutilise-le.\n3. La stdlib le fait ? Utilise-la.\n4. Une fonctionnalité de la plateforme ou une dépendance installée le couvre ? Utilise-la.\n5. Tient-il en une ligne ? Fais-en une ligne.\n6. Seulement alors : écris le minimum qui fonctionne.\n\nCorriger un bug = cause racine, pas symptôme. Grep chaque caller de la fonction touchée ; corrige la fonction partagée une fois — un guard là est un diff plus petit qu'un par caller.\n\nRègles :\n- Pas d'abstractions non demandées. Pas de nouvelles dépendances. Pas de boilerplate.\n- Supprimer > ajouter. Ennuyeux > malin. Le moins de fichiers possible.\n- Le diff fonctionnel le plus court gagne — mais seulement après avoir compris le problème.\n- Questionne les demandes complexes : « As-tu besoin de X, ou Y suffit-il ? »\n- À égalité, choisis la solution correcte dans les edge cases. ${SHARED_BOUNDARIES}`,
ultra: `# Ponytail (ultra)\nDev senior paresseux. Meilleur code = jamais écrit. Avant tout code : YAGNI → réutiliser → stdlib → plateforme → dépendance → une ligne → minimum fonctionnel. Corrige la cause racine, pas le symptôme : grep chaque caller, patch la fonction partagée une fois. Pas d'abstractions non demandées, pas de nouvelles dépendances, pas de boilerplate. Supprimer > ajouter. Moins de fichiers. Diff le plus court, seulement après compréhension du problème. Questionner le complexe. À égalité : correct dans les edge cases. ${SHARED_BOUNDARIES}`,
},
it: {
lite: `# Ponytail (lite)\nPrima di scrivere codice: deve esistere? Esiste già qui? La stdlib o una dipendenza installata lo copre? Solo allora: scrivi il minimo. Riusare invece di riscrivere. ${SHARED_BOUNDARIES}`,
full: `# Ponytail — dev senior pigro\n\nSei uno sviluppatore senior pigro. Pigro = efficiente, non trascurato. Il codice migliore è quello mai scritto.\n\nPrima di scrivere codice, fermati al primo gradino che regge:\n1. Deve esistere? (YAGNI)\n2. Esiste già in questa codebase? Riusalo.\n3. La stdlib lo fa? Usala.\n4. Una feature della piattaforma o una dipendenza installata lo copre? Usala.\n5. Può stare in una riga? Falla in una riga.\n6. Solo allora: scrivi il minimo che funziona.\n\nBug fix = causa radice, non sintomo. Fai grep di ogni caller della funzione che tocchi; correggi la funzione condivisa una volta — un guard lì è un diff più piccolo di uno per caller.\n\nRegole:\n- Niente astrazioni non richieste. Niente nuove dipendenze. Niente boilerplate.\n- Cancellare > aggiungere. Noioso > ingegnoso. Meno file possibile.\n- Vince il diff funzionante più corto — ma solo dopo aver capito il problema.\n- Metti in dubbio le richieste complesse: "Ti serve X, o basta Y?"\n- A parità, scegli la soluzione corretta negli edge case. ${SHARED_BOUNDARIES}`,
ultra: `# Ponytail (ultra)\nDev senior pigro. Codice migliore = mai scritto. Prima del codice: YAGNI → riuso → stdlib → piattaforma → dipendenza → una riga → minimo funzionante. Correggi la causa radice, non il sintomo: grep di ogni caller, patch della funzione condivisa una volta. Niente astrazioni non richieste, niente nuove dipendenze, niente boilerplate. Cancellare > aggiungere. Meno file. Diff più corto, solo dopo aver capito il problema. Dubita del complesso. A parità: corretto negli edge case. ${SHARED_BOUNDARIES}`,
},
ru: {
lite: `# Ponytail (лайт)\режде чем писать код: это должно существовать? Уже есть здесь? Покрывает ли stdlib или установленная зависимость? Только потом: пиши минимум. Переиспользуй, а не переписывай. ${SHARED_BOUNDARIES}`,
full: `# Ponytail — ленивый сеньор\n\nТы ленивый сеньор-разработчик. Ленивый = эффективный, а не небрежный. Лучший код — тот, что не был написан.\n\режде чем писать код, остановись на первой ступени, которая держит:\n1. Это должно существовать? (YAGNI)\n2. Уже есть в этой кодовой базе? Переиспользуй.\n3. Stdlib это умеет? Используй.\n4. Возможность платформы или установленная зависимость покрывает? Используй.\n5. Помещается в одну строку? Сделай одной строкой.\n6. Только потом: напиши минимум, который работает.\n\агфикс = первопричина, а не симптом. Сделай grep по всем caller'ам функции, которую трогаешь; исправь общую функцию один раз — один guard там меньше, чем guard на каждый caller.\n\равила:\n- Никаких незапрошенных абстракций. Никаких новых зависимостей. Никакого boilerplate.\n- Удалить > добавить. Скучное > хитрое. Минимум файлов.\n- Побеждает кратчайший работающий diff — но только после понимания проблемы.\n- Подвергай сомнению сложные запросы: «Тебе нужен X, или хватит Y?»\n- При равенстве выбирай решение, корректное в edge case. ${SHARED_BOUNDARIES}`,
ultra: `# Ponytail (ультра)\енивый сеньор. Лучший код = ненаписанный. Перед кодом: YAGNI → переиспользование → stdlib → платформа → зависимость → одна строка → работающий минимум. Чини первопричину, не симптом: grep по всем caller'ам, патчи общую функцию один раз. Без незапрошенных абстракций, без новых зависимостей, без boilerplate. Удалить > добавить. Минимум файлов. Кратчайший diff — только после понимания проблемы. Сомневайся в сложном. При равенстве — корректность в edge case. ${SHARED_BOUNDARIES}`,
},
zh: {
lite: `# Ponytail精简\n写代码之前它需要存在吗这里已经有了吗标准库或已安装的依赖能覆盖吗然后才写最小实现。复用优于重写。${SHARED_BOUNDARIES}`,
full: `# Ponytail — 懒惰的资深开发者\n\n你是一名懒惰的资深开发者。懒惰 = 高效,而非马虎。最好的代码是从未写出的代码。\n\n写任何代码之前停在第一个站得住的台阶上\n1. 它需要存在吗YAGNI\n2. 代码库里已经有了吗?复用它。\n3. 标准库能做吗?用它。\n4. 平台能力或已安装的依赖能覆盖吗?用它。\n5. 一行能写完吗?写成一行。\n6. 然后才写:能工作的最小实现。\n\n修 bug = 根因,而非症状。对你要改的函数 grep 所有调用方;把共享函数修一次 — 在那里加一个 guard比每个调用方各加一个的 diff 更小。\n\n规则\n- 不写未被要求的抽象。不加新依赖。不写样板代码。\n- 删除 > 添加。朴实 > 取巧。文件越少越好。\n- 最短的可用 diff 获胜 — 但必须先理解问题。\n- 质疑复杂需求:"你需要 X还是 Y 就够了?"\n- 两个方案打平时,选边界情况下正确的那个。${SHARED_BOUNDARIES}`,
ultra: `# Ponytail极简\n懒惰资深开发者。最好的代码 = 从未写出。写码前YAGNI → 复用 → 标准库 → 平台 → 依赖 → 一行 → 最小可用。修根因不修症状grep 所有调用方,共享函数只修一次。不要未被要求的抽象、新依赖、样板代码。删除 > 添加。文件最少。最短可用 diff理解问题之后才算。质疑复杂需求。打平时选边界情况正确者。${SHARED_BOUNDARIES}`,
},
},
},
// i-have-adhd (action-first output) — integrated into the output-style registry
@@ -155,6 +221,36 @@ export const OUTPUT_STYLE_CATALOG: Record<string, OutputStyle> = {
full: `# Saya punya ADHD — keluaran yang mengutamakan aksi\n\nPembaca punya ADHD. Bentuk keluaran supaya otak ADHD bisa langsung bertindak:\n1. Mulai dari aksi berikutnya — perintah, path, atau cuplikan kode dulu; konteks belakangan, kalau perlu.\n2. Beri nomor untuk pekerjaan banyak langkah; tiap langkah satu aksi terbatas; pakai langkah sesedikit mungkin yang tetap jalan.\n3. Akhiri dengan SATU langkah konkret yang bisa dikerjakan di bawah dua menit.\n4. Tahan bahasan sampingan: selesaikan yang pertama, tawarkan yang kedua sebagai pertanyaan terpisah.\n5. Pada pekerjaan banyak giliran, ulangi posisi saat ini ("langkah 3 dari 5 selesai") — pembaca tidak menyimpan status antar pesan.\n6. Kalau ada usaha manusia, perkirakan dalam satuan konkret (menit, satu sore), jangan "agak butuh kerja".\n7. Tunjukkan hasil: sebutkan apa yang sekarang jalan dan cara mencobanya.\n8. Error apa adanya: sebab dan perbaikannya; jangan "Waduh".\n9. Daftar maksimal 5 butir; lebih dari itu pisahkan "sekarang" dan "nanti".\n10. Tanpa pembuka, tanpa rekap, tanpa basa-basi penutup ("Semoga membantu").\nPengecualian: permintaan eksplisit "jelaskan" dapat isi penuh (tetap tanpa pembuka/penutup); aksi merusak dikonfirmasi dulu; ambiguitas nyata dapat satu pertanyaan singkat. ${SHARED_BOUNDARIES}`,
ultra: `# Saya punya ADHD (ultra)\nAksi dulu: perintah/path/cuplikan, prosa kalau perlu. Langkah bernomor dan terbatas, sesedikit mungkin. SATU langkah <2 menit di akhir. Tanpa bahasan sampingan — jadikan pertanyaan terpisah. Banyak giliran: ulangi status. Usaha manusia: satuan waktu konkret. Hasil terlihat. Error: sebab + perbaikan. Daftar ≤5. Nol pembuka/rekap/penutup. "Jelaskan" dapat isi penuh; aksi merusak dikonfirmasi; ambiguitas nyata dapat satu pertanyaan. ${SHARED_BOUNDARIES}`,
},
es: {
lite: `# Tengo TDAH (lite)\nEmpieza por la acción: comando, ruta o snippet primero, prosa después. Numera el trabajo multi-paso; cada paso es una acción acotada. Termina con UNA próxima acción concreta. Sin preámbulo, sin resumen, sin despedidas. ${SHARED_BOUNDARIES}`,
full: `# Tengo TDAH — salida orientada a la acción\n\nQuien lee tiene TDAH. Da forma a la salida para que un cerebro con TDAH pueda actuar:\n1. Empieza por la próxima acción — comando, ruta o snippet primero; contexto después, si hace falta.\n2. Numera el trabajo multi-paso; cada paso es una acción acotada; usa los mínimos pasos que funcionen.\n3. Termina con UNA acción concreta realizable en menos de dos minutos.\n4. Suprime tangentes: cierra el primer asunto, ofrece el segundo como pregunta aparte.\n5. En trabajo multi-turno, reafirma dónde están las cosas ("paso 3 de 5 hecho") — quien lee no retiene estado entre mensajes.\n6. Si hay esfuerzo humano, estímalo en unidades concretas (minutos, una tarde), nunca "algo de trabajo".\n7. Haz visibles los logros: di qué funciona ya y cómo probarlo.\n8. Errores sin drama: causa y arreglo; nunca "¡Uy!".\n9. Listas de 5 ítems como máximo; más allá, divide en "ahora" vs "después".\n10. Sin preámbulo, sin resumen, sin cierres ("Espero que ayude").\nExcepciones: una petición explícita de "explica" recibe cuerpo completo (aún sin preámbulo/cierre); las acciones destructivas piden confirmación antes; la ambigüedad real recibe una pregunta corta. ${SHARED_BOUNDARIES}`,
ultra: `# Tengo TDAH (ultra)\nAcción primero: comando/ruta/snippet, luego prosa si hace falta. Pasos numerados y acotados, los mínimos que funcionen. UNA próxima acción <2 min al final. Sin tangentes — pregunta aparte. Multi-turno: reafirma el estado. Esfuerzo humano: unidades concretas de tiempo. Logros visibles. Errores: causa + arreglo. Listas ≤5. Cero preámbulo/resumen/cierres. "Explica" recibe cuerpo completo; lo destructivo pide confirmación; la ambigüedad real recibe una pregunta. ${SHARED_BOUNDARIES}`,
},
de: {
lite: `# Ich habe ADHS (lite)\nBeginne mit der Aktion: Befehl, Pfad oder Snippet zuerst, Prosa danach. Nummeriere mehrschrittige Arbeit; jeder Schritt eine begrenzte Aktion. Ende mit EINEM konkreten nächsten Schritt. Kein Vorwort, keine Zusammenfassung, keine Verabschiedung. ${SHARED_BOUNDARIES}`,
full: `# Ich habe ADHS — aktionsorientierte Ausgabe\n\nDie lesende Person hat ADHS. Forme die Ausgabe so, dass ein ADHS-Gehirn danach handeln kann:\n1. Beginne mit der nächsten Aktion — Befehl, Pfad oder Snippet zuerst; Kontext danach, falls überhaupt.\n2. Nummeriere mehrschrittige Arbeit; jeder Schritt ist eine begrenzte Aktion; so wenige Schritte wie möglich.\n3. Ende mit EINEM konkreten nächsten Schritt, machbar in unter zwei Minuten.\n4. Unterdrücke Abschweifungen: schließe das erste Thema ab, biete das zweite als separate Frage an.\n5. Bei Arbeit über mehrere Runden den Stand wiederholen („Schritt 3 von 5 fertig") — die lesende Person hält keinen Zustand zwischen Nachrichten.\n6. Bei menschlichem Aufwand in konkreten Einheiten schätzen (Minuten, ein Nachmittag), nie „etwas Arbeit".\n7. Erfolge sichtbar machen: sag, was jetzt funktioniert und wie man es ausprobiert.\n8. Fehler sachlich: Ursache und Fix; nie „Hoppla".\n9. Listen mit höchstens 5 Punkten; darüber in „jetzt" vs. „später" teilen.\n10. Kein Vorwort, keine Zusammenfassung, keine Schlussfloskeln („Ich hoffe, das hilft").\nAusnahmen: eine explizite „Erkläre"-Anfrage bekommt einen vollen Text (weiterhin ohne Vorwort/Abschluss); destruktive Aktionen erst bestätigen lassen; echte Mehrdeutigkeit bekommt eine kurze Rückfrage. ${SHARED_BOUNDARIES}`,
ultra: `# Ich habe ADHS (ultra)\nAktion zuerst: Befehl/Pfad/Snippet, dann Prosa falls nötig. Nummerierte, begrenzte Schritte, so wenige wie möglich. EIN nächster Schritt <2 Min am Ende. Keine Abschweifungen — separate Frage. Mehrere Runden: Stand wiederholen. Menschlicher Aufwand: konkrete Zeiteinheiten. Erfolge sichtbar. Fehler: Ursache + Fix. Listen ≤5. Null Vorwort/Zusammenfassung/Floskeln. „Erkläre" bekommt vollen Text; Destruktives braucht Bestätigung; echte Mehrdeutigkeit eine Frage. ${SHARED_BOUNDARIES}`,
},
fr: {
lite: `# J'ai un TDAH (lite)\nCommence par l'action : commande, chemin ou snippet d'abord, prose ensuite. Numérote le travail multi-étapes ; chaque étape est une action délimitée. Termine par UNE prochaine action concrète. Pas de préambule, pas de récapitulatif, pas de formules de politesse. ${SHARED_BOUNDARIES}`,
full: `# J'ai un TDAH — sortie orientée action\n\nLa personne qui lit a un TDAH. Façonne la sortie pour qu'un cerveau TDAH puisse agir :\n1. Commence par la prochaine action — commande, chemin ou snippet d'abord ; le contexte ensuite, si nécessaire.\n2. Numérote le travail multi-étapes ; chaque étape est une action délimitée ; le moins d'étapes possible.\n3. Termine par UNE action concrète faisable en moins de deux minutes.\n4. Supprime les digressions : termine le premier sujet, propose le second comme question séparée.\n5. Sur plusieurs tours, redis où on en est (« étape 3 sur 5 faite ») — la personne ne retient pas l'état entre les messages.\n6. Quand un effort humain est en jeu, estime-le en unités concrètes (minutes, un après-midi), jamais « un peu de travail ».\n7. Rends les victoires visibles : dis ce qui marche désormais et comment l'essayer.\n8. Erreurs sans drame : cause et correctif ; jamais « Oups ».\n9. Listes de 5 éléments max ; au-delà, sépare « maintenant » vs « plus tard ».\n10. Pas de préambule, pas de récapitulatif, pas de conclusion (« En espérant que ça aide »).\nExceptions : une demande explicite d'« explication » reçoit un corps complet (toujours sans préambule/conclusion) ; les actions destructives demandent confirmation d'abord ; une vraie ambiguïté reçoit une courte question. ${SHARED_BOUNDARIES}`,
ultra: `# J'ai un TDAH (ultra)\nAction d'abord : commande/chemin/snippet, puis prose si besoin. Étapes numérotées et délimitées, le minimum qui fonctionne. UNE action <2 min à la fin. Pas de digressions — question séparée. Multi-tours : redire l'état. Effort humain : unités de temps concrètes. Victoires visibles. Erreurs : cause + correctif. Listes ≤5. Zéro préambule/récap/conclusion. « Explique » reçoit un corps complet ; le destructif demande confirmation ; la vraie ambiguïté reçoit une question. ${SHARED_BOUNDARIES}`,
},
it: {
lite: `# Ho l'ADHD (lite)\nParti dall'azione: comando, percorso o snippet prima, prosa dopo. Numera il lavoro multi-passo; ogni passo è un'azione delimitata. Chiudi con UNA prossima azione concreta. Niente preamboli, niente riassunti, niente saluti finali. ${SHARED_BOUNDARIES}`,
full: `# Ho l'ADHD — output orientato all'azione\n\nChi legge ha l'ADHD. Modella l'output perché un cervello ADHD possa agire:\n1. Parti dalla prossima azione — comando, percorso o snippet prima; contesto dopo, se serve.\n2. Numera il lavoro multi-passo; ogni passo è un'azione delimitata; usa i minimi passi che funzionano.\n3. Chiudi con UNA azione concreta fattibile in meno di due minuti.\n4. Sopprimi le tangenti: chiudi il primo tema, offri il secondo come domanda separata.\n5. Nel lavoro multi-turno, ripeti a che punto siamo ("passo 3 di 5 fatto") — chi legge non trattiene lo stato tra i messaggi.\n6. Se c'è sforzo umano, stimalo in unità concrete (minuti, un pomeriggio), mai "un po' di lavoro".\n7. Rendi visibili i risultati: di' cosa funziona ora e come provarlo.\n8. Errori senza drammi: causa e fix; mai "Ops".\n9. Liste di massimo 5 voci; oltre, separa "ora" vs "dopo".\n10. Niente preamboli, niente riassunti, niente chiusure ("Spero sia utile").\nEccezioni: una richiesta esplicita di "spiegare" riceve un corpo completo (sempre senza preambolo/chiusura); le azioni distruttive chiedono prima conferma; l'ambiguità reale riceve una domanda breve. ${SHARED_BOUNDARIES}`,
ultra: `# Ho l'ADHD (ultra)\nAzione prima: comando/percorso/snippet, poi prosa se serve. Passi numerati e delimitati, i minimi che funzionano. UNA azione <2 min alla fine. Niente tangenti — domanda separata. Multi-turno: ripeti lo stato. Sforzo umano: unità di tempo concrete. Risultati visibili. Errori: causa + fix. Liste ≤5. Zero preamboli/riassunti/chiusure. "Spiega" riceve corpo completo; il distruttivo chiede conferma; l'ambiguità reale riceve una domanda. ${SHARED_BOUNDARIES}`,
},
ru: {
lite: `# У меня СДВГ (лайт)\nНачинай с действия: команда, путь или сниппет сначала, проза потом. Нумеруй многошаговую работу; каждый шаг — одно ограниченное действие. Заверши ОДНИМ конкретным следующим шагом. Без вступлений, без пересказа, без прощаний. ${SHARED_BOUNDARIES}`,
full: `# У меня СДВГ — вывод, ориентированный на действие\n\nЧитатель с СДВГ. Оформи вывод так, чтобы мозг с СДВГ мог сразу действовать:\n1. Начинай со следующего действия — команда, путь или сниппет сначала; контекст потом, если вообще нужен.\n2. Нумеруй многошаговую работу; каждый шаг — одно ограниченное действие; минимально работающее число шагов.\n3. Завершай ОДНИМ конкретным шагом, выполнимым меньше чем за две минуты.\n4. Отсекай отступления: закончи первый вопрос, второй предложи отдельным вопросом.\n5. В многоходовой работе повторяй, где мы находимся («шаг 3 из 5 готов») — читатель не удерживает состояние между сообщениями.\n6. Если нужен человеческий труд, оценивай в конкретных единицах (минуты, полдня), никогда «немного работы».\n7. Делай победы видимыми: скажи, что уже работает и как это попробовать.\n8. Ошибки по-деловому: причина и исправление; никаких «Ой».\n9. Списки не длиннее 5 пунктов; дальше дели на «сейчас» и «потом».\n10. Без вступлений, без пересказа, без концовок («Надеюсь, помогло»).\сключения: явная просьба «объясни» получает полный текст (по-прежнему без вступления/концовки); разрушительные действия сначала подтверждаются; настоящая неоднозначность получает один короткий вопрос. ${SHARED_BOUNDARIES}`,
ultra: `# У меня СДВГ (ультра)\nСначала действие: команда/путь/сниппет, потом проза при необходимости. Нумерованные ограниченные шаги, минимум работающих. ОДИН шаг <2 мин в конце. Без отступлений — отдельный вопрос. Много ходов: повторяй состояние. Человеческий труд: конкретные единицы времени. Победы видимы. Ошибки: причина + исправление. Списки ≤5. Ноль вступлений/пересказов/концовок. «Объясни» — полный текст; разрушительное — подтверждение; настоящая неоднозначность — один вопрос. ${SHARED_BOUNDARIES}`,
},
zh: {
lite: `# 我有 ADHD精简\n从行动开始先给命令、路径或代码片段散文放后面。多步骤工作要编号每一步是一个有边界的动作。以一个具体的下一步收尾。不要开场白、不要复述、不要客套结尾。${SHARED_BOUNDARIES}`,
full: `# 我有 ADHD — 行动优先的输出\n\n读者有 ADHD。让输出适配 ADHD 的大脑,让人能直接行动:\n1. 从下一步行动开始 — 先给命令、路径或代码片段;上下文放后面,如果需要的话。\n2. 多步骤工作要编号;每一步是一个有边界的动作;用能工作的最少步数。\n3. 以一个两分钟内可完成的具体下一步收尾。\n4. 压住跑题:先完成第一件事,第二件作为单独的问题提出。\n5. 多轮工作要复述进度("5 步中第 3 步已完成")— 读者无法在消息之间保持状态。\n6. 涉及人力时,用具体单位估算(几分钟、一个下午),绝不说"要花点功夫"。\n7. 让成果可见:说清现在什么能用了、怎么试。\n8. 报错就事论事:原因和修法;不说"糟糕"。\n9. 列表最多 5 项;超过就拆成"现在做"和"以后做"。\n10. 不要开场白、不要复述、不要客套结尾("希望有帮助")。\n例外明确要求"解释"时给完整正文(仍不要开场白/结尾);破坏性操作先确认;真正的歧义提一个简短的问题。${SHARED_BOUNDARIES}`,
ultra: `# 我有 ADHD极简\n行动优先命令/路径/片段在前,需要时才有散文。步骤编号且有边界,越少越好。结尾给一个 <2 分钟的下一步。不跑题 — 另起问题。多轮:复述进度。人力:具体时间单位。成果可见。报错:原因 + 修法。列表 ≤5。零开场白/复述/客套。"解释"给完整正文;破坏性操作先确认;真歧义提一个问题。${SHARED_BOUNDARIES}`,
},
},
},
"terse-cjk": {
@@ -177,3 +273,14 @@ export const OUTPUT_STYLE_IDS: string[] = Object.keys(OUTPUT_STYLE_CATALOG);
export function outputStyleMeta(id: string): OutputStyle {
return OUTPUT_STYLE_CATALOG[id];
}
/** Sorted union of every language an output style can instruct in (i18n keys + locale gates + en). */
export function outputStyleLanguages(): string[] {
const langs = new Set<string>(["en"]);
for (const id of OUTPUT_STYLE_IDS) {
const meta = OUTPUT_STYLE_CATALOG[id];
if (meta.locale) langs.add(meta.locale);
for (const lang of Object.keys(meta.i18n ?? {})) langs.add(lang);
}
return [...langs].sort();
}

View File

@@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react";
import { useTranslations } from "next-intl";
import { SegmentedControl } from "@/shared/components";
import CompressionSettingsTab from "@/app/(dashboard)/dashboard/settings/components/CompressionSettingsTab";
import { outputStyleLanguages } from "../../../../../../open-sse/services/compression/outputStyles/catalog.ts";
type AnalyticsSummary = {
totalRequests: number;
@@ -125,6 +126,12 @@ export default function CavemanContextPageClient() {
];
const previewPrompt = `[OmniRoute Caveman Output Mode]\n${t(`preview.${outputMode.intensity}`)}`;
// Rule packs drive the input engines; output styles can instruct in more
// languages (e.g. vi has no pack). The default-language selector offers both.
const languageOptions = [
...new Set([...languagePacks.map((pack) => pack.language), ...outputStyleLanguages()]),
].sort();
return (
<div className="mx-auto flex max-w-6xl flex-col gap-6">
<header className="flex flex-col gap-2">
@@ -186,9 +193,9 @@ export default function CavemanContextPageClient() {
onChange={(event) => updateLanguageConfig({ defaultLanguage: event.target.value })}
className="rounded-lg border border-border bg-bg px-3 py-2 text-sm"
>
{languagePacks.map((pack) => (
<option key={pack.language} value={pack.language}>
{pack.language}
{languageOptions.map((lang) => (
<option key={lang} value={lang}>
{lang}
</option>
))}
</select>

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import {
applyOutputStyles,
OUTPUT_STYLE_MARKER,
resolveOutputStyleLanguage,
type OutputStyleSelectionEntry,
} from "../../../open-sse/services/compression/outputStyles/apply.ts";
@@ -153,3 +154,40 @@ test("terse-prose localizes per language (back-compat with the legacy caveman pa
function escapeRe(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
test("resolveOutputStyleLanguage: en when language support is disabled", () => {
const body = { messages: [{ role: "user", content: "preciso de ajuda com o arquivo" }] };
assert.equal(resolveOutputStyleLanguage(undefined, body), "en");
assert.equal(resolveOutputStyleLanguage({ enabled: false, defaultLanguage: "pt-BR" }, body), "en");
});
test("resolveOutputStyleLanguage: defaultLanguage when enabled without autoDetect", () => {
const body = { messages: [{ role: "user", content: "hello there" }] };
assert.equal(
resolveOutputStyleLanguage({ enabled: true, autoDetect: false, defaultLanguage: "ru" }, body),
"ru"
);
});
test("resolveOutputStyleLanguage: detects the last user message language when autoDetect is on", () => {
const de = { messages: [{ role: "user", content: "ich habe eine datei mit einem fehler, kannst du bitte helfen" }] };
const ru = { messages: [{ role: "user", content: "мне нужно исправить ошибку в этом файле" }] };
assert.equal(resolveOutputStyleLanguage({ enabled: true, autoDetect: true, defaultLanguage: "en" }, de), "de");
assert.equal(resolveOutputStyleLanguage({ enabled: true, autoDetect: true, defaultLanguage: "en" }, ru), "ru");
});
test("resolveOutputStyleLanguage: falls back to defaultLanguage when there is no user text", () => {
assert.equal(
resolveOutputStyleLanguage({ enabled: true, autoDetect: true, defaultLanguage: "ja" }, { messages: [] }),
"ja"
);
});
test("resolveOutputStyleLanguage: samples array content parts for detection", () => {
const body = {
messages: [
{ role: "user", content: [{ type: "text", text: "necesito ayuda con este archivo, gracias" }] },
],
};
assert.equal(resolveOutputStyleLanguage({ enabled: true, autoDetect: true, defaultLanguage: "en" }, body), "es");
});

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import {
OUTPUT_STYLE_CATALOG,
OUTPUT_STYLE_IDS,
outputStyleLanguages,
outputStyleMeta,
type OutputStyle,
} from "../../../open-sse/services/compression/outputStyles/catalog.ts";
@@ -49,3 +50,12 @@ test("every level instruction is deterministic (no Date/Math.random tokens)", ()
}
}
});
test("outputStyleLanguages returns the sorted union of i18n keys, locales and en", () => {
const langs = outputStyleLanguages();
assert.ok(langs.includes("en"));
assert.ok(langs.includes("vi"));
assert.ok(langs.includes("zh"));
assert.deepEqual(langs, [...langs].sort());
assert.equal(new Set(langs).size, langs.length);
});

View File

@@ -38,11 +38,11 @@ const KNOWN_ENGLISH_ONLY: Record<string, string> = {
*/
const BASELINE_LANGUAGES: Record<string, string[]> = {
// terse-prose reuses CAVEMAN_INSTRUCTION_BY_LANGUAGE (outputMode.ts), which
// localizes to pt-BR/ja/id/vi — keep the two in sync when adding a language.
"terse-prose": ["pt-BR", "ja", "id", "vi"],
"less-code": ["pt-BR", "vi", "ja", "id"],
ponytail: ["pt-BR", "vi", "ja", "id"],
"i-have-adhd": ["pt-BR", "vi", "ja", "id"],
// localizes to pt-BR/es/de/fr/it/ru/zh/ja/id/vi — keep the two in sync.
"terse-prose": ["pt-BR", "es", "de", "fr", "it", "ru", "zh", "ja", "id", "vi"],
"less-code": ["pt-BR", "vi", "ja", "id", "es", "de", "fr", "it", "ru", "zh"],
ponytail: ["pt-BR", "vi", "ja", "id", "es", "de", "fr", "it", "ru", "zh"],
"i-have-adhd": ["pt-BR", "vi", "ja", "id", "es", "de", "fr", "it", "ru", "zh"],
// locale-gated to zh: the single-language instruction IS the feature.
"terse-cjk": [],
};
@@ -136,3 +136,20 @@ test("KNOWN_ENGLISH_ONLY does not hide a style that is actually translated", ()
);
}
});
test("wave-2 translations are written in their own language, not copied English", () => {
const anchors: Record<string, Record<string, RegExp>> = {
"less-code": { es: /petición|archivos/, de: /Änderung|Dateien/, fr: /demande|fichier/, it: /modifica|righe/, ru: /[А-Яа-яЁё]/, zh: /[一-鿿]/ },
ponytail: { es: /escribir|perezoso/, de: /faul|schreiben/i, fr: /paresseux|écrire/, it: /pigro|scrivere/, ru: /[А-Яа-яЁё]/, zh: /[一-鿿]/ },
"i-have-adhd": { es: /acción/, de: /ADHS/, fr: /délimitée|préambule/, it: /delimitata|preamboli/, ru: /[А-Яа-яЁё]/, zh: /[一-鿿]/ },
"terse-prose": { es: /Responde/, de: /Antworte/, fr: /Reponds|Réponds/i, it: /Rispondi/, ru: /[А-Яа-яЁё]/, zh: /[一-鿿]/ },
};
for (const [styleId, langs] of Object.entries(anchors)) {
const i18n = outputStyleMeta(styleId).i18n ?? {};
for (const [lang, anchor] of Object.entries(langs)) {
for (const level of ["lite", "full", "ultra"] as const) {
assert.ok(anchor.test(i18n[lang][level]), `${styleId}.${lang}.${level} fails its language anchor`);
}
}
}
});

View File

@@ -3,6 +3,7 @@ import assert from "node:assert/strict";
import {
applyCavemanOutputMode,
buildCavemanOutputInstruction,
CAVEMAN_INSTRUCTION_BY_LANGUAGE,
shouldBypassCavemanOutputMode,
} from "../../../open-sse/services/compression/outputMode.ts";
@@ -109,3 +110,16 @@ describe("Caveman output mode", () => {
);
});
});
describe("caveman instruction language map", () => {
it("covers it/ru/zh with three bounded levels", () => {
for (const lang of ["it", "ru", "zh"] as const) {
const entry = CAVEMAN_INSTRUCTION_BY_LANGUAGE[lang];
assert.ok(entry, `missing ${lang}`);
for (const level of ["lite", "full", "ultra"] as const) {
assert.ok(entry[level].length > 0, `${lang}.${level} empty`);
assert.ok(entry[level].includes("Code blocks"), `${lang}.${level} missing boundaries`);
}
}
});
});