@pugi/plugin-voice-rules
v0.1.0-alpha.2
Published
Pugi voice-rules plugin - injects JARGON_BAN voice rules into chat.params + system prompt (PR #932 patterns).
Maintainers
Readme
@pugi/plugin-voice-rules
Pugi brand voice enforcement plugin for the Pugi runtime. Catches em-dashes, emoji, AI attribution trailers, and the legacy product/codename in chat output, git commit messages, and PR bodies before they ship.
Part of the Pugi 1.0 soft fork sprint (ADR-0081).
Status
Day 26-30 sprint deliverable. 39 unit tests passing. Plugin loads as a Hooks object against the @pugi-ai/plugin 1.16.x contract.
Hook surface
| Hook | What it does |
|---|---|
| experimental.chat.system.transform | Prepends a compact rule digest to the system prompt so the model generates voice-aware text upfront. Capped at 1200 chars. |
| experimental.text.complete | Rewrites the assistant draft text in place. Honours enforceChat and the mode knob. |
| tool.execute.before | Intercepts bash invocations matching git commit -m "..." or gh pr create --body "...", scans the body, rewrites or blocks. |
Spec note: the command.execute.before hook only fires on slash commands and would never see a bash-tool git commit. We use tool.execute.before with a bash-tool filter instead. See the inline comment in src/index.ts for the deviation rationale.
The code-comment scope is reachable only through the exported auditText() function. Lint-mode only - there is no live Pugi hook for editor comment text.
Profiles
Two built-in profiles, plus arbitrary additive custom rules.
pugi-default(11 rules): em-dash, en-dash, emoji, Claude trailer, AI-generated trailer, legacy product name, legacy codename, AI-assistant framing, leak repo refs, default-USD vs rubles, prefer Together AI, no fake-dispatch claims.pugi-strict(14 rules): everything in default plus no-exclamation-in-commits, filler-word strip (English only), brand keyword density check on long bodies.
Custom rules merge additively by id - same id overrides the profile rule, new ids extend it.
Install
pnpm add @pugi/plugin-voice-rulesUsage
// pugi.config.ts
export default {
plugin: [
['@pugi/plugin-voice-rules', {
mode: 'warn',
voiceProfile: 'pugi-default',
enforceCommits: true,
enforceChat: true,
enforcePrBodies: true,
enforceCode: false,
}],
],
};Modes
enforce: rewrite silently. Block rules without a rewriter abort the action.warn(default): rewrite, emit a notice on the internal bus, never block.audit: log only. Never modifies text.
Audit log
Append-only JSONL at .pugi/voice-audit.jsonl. One line per rule fire. Use the file for telemetry and severity tuning over time.
Internal extension point
voiceBus is a node:events.EventEmitter exported as a module-level singleton. Other Pugi plugins can subscribe:
import { voiceBus } from '@pugi/plugin-voice-rules';
voiceBus.on('before-publish', ({ scope, verdict, rules }) => {
// verdict.matches, verdict.rewritten, verdict.notice, verdict.blocked
});Not part of the public Pugi plugin surface.
Rule reference
| ID | Severity | Scope | Source |
|---|---|---|---|
| no-em-dash | block | all | CEO directive: no em-dash anywhere |
| no-emoji | warn | commit, pr-body, code-comment | Global Pugi voice rule |
| no-claude-attribution | block | commit, pr-body | feedback_no_claude_attribution_anywhere_hard_rule |
| no-ai-generated-trailer | block | commit, pr-body | Same as above |
| no-codeforge-customer-facing | warn | chat, pr-body, code-comment | feedback_brand_is_pugi_not_codeforge |
| no-mira-customer-facing | block | chat, pr-body | feedback_persona_pugi_not_mira |
| no-ai-assistant-framing | warn | chat | Brand voice: engineer-grade tool, not AI assistant |
| no-leak-references | block | all | feedback_leak_repos_read_for_patterns |
| default-usd-not-rubles | warn | code-comment, chat | feedback_default_no_rubles_ask_language |
| prefer-together-ai-default | info | chat | feedback_pugi_backend_is_together_ai_not_local_ollama |
| no-fake-dispatch-claim | warn | chat | feedback_no_fake_dispatch_promises |
| no-commit-exclamation (strict) | info | commit | Strict profile style guide |
| no-filler-words (strict, en only) | info | chat | Strict profile style guide |
| pugi-keyword-density (strict) | warn | pr-body, chat | Strict profile: brand anchor in long bodies |
Localization
Rules can be locale-gated. The detector resolves the active locale from (in priority order) the caller-supplied override, process.env.PUGI_LOCALE, LC_ALL / LANG, then falls back to en. Rules without a locale field fire on every locale. The filler-word rule is English-only by design.
Self-coherence
The package passes its own rule engine. The single file that carries the raw brand keywords is src/__brand.ts (escape-sequence assembled), and that file is on the default whitelist. Test fixtures encode all violations as String.fromCharCode so the test file is rule-clean too.
License
MIT. See LICENSE.
