Skip to content

fix(docs): restore pt-br diacritics and enforce locale content integrity - #523

Merged
rhuanbarreto merged 3 commits into
mainfrom
fix/ptbr-diacritics-i18n-content-rules
Jul 26, 2026
Merged

fix(docs): restore pt-br diacritics and enforce locale content integrity#523
rhuanbarreto merged 3 commits into
mainfrom
fix/ptbr-diacritics-i18n-content-rules

Conversation

@rhuanbarreto

Copy link
Copy Markdown
Contributor

Closes #516.

The gap was live, not hypothetical

GEN-002 has always required correct diacritics — "DO use correct diacritical marks in Portuguese — não not nao, código not codigo" — but nothing enforced it. The companion rules checked only structure: page parity, translation drift, link prefixes. So six pt-br pages sat in main with their diacritics stripped, plus a seventh with a corrupted frontmatter description.

nb hit the identical failure in #384: ~500 occurrences across 35 files, fixed in two manual passes, where the second pass caught four cases the first missed. Manual review is not a reliable detector for this.

Content

Diacritics restored across seven pt-br/examples pages — prose, headings, and frontmatter descriptions. Verified mechanically: 0 stripped forms remain, and fenced code blocks are byte-identical to main in all seven files, so no identifier, command, or path was touched.

Rules

Two additions to GEN-002-docs-i18n.rules.ts, both error:

  • i18n-encoding-corruption — accented characters written as HTML entities (å, ç) or as double-encoded UTF-8. Detection requires a 0xC2/0xC3 lead byte followed by a 0x800xBF continuation byte. A bare lead-byte scan would flag legitimate uppercase Portuguese, where à precedes an ASCII letter — ÃO already occurs twice in this tree.
  • i18n-diacritic-density — accented characters per 1000 prose letters per locale, ignoring fenced and inline code.

Thresholds come from measuring the corpus, not from taste:

healthy minimum stripped pages threshold
nb 5.9 / 1000 2
pt-br 15.2 / 1000 0.0–1.1 5

A whole-file "has at least one diacritic" check was tried and rejected. Measured against the six corrupt files it caught one: wrapper-enforcement.mdx was fully stripped in prose but contained Não inside a code sample, which defeats it. Density caught five of six, with a 14× gap between the bands.

Accented characters in the rules file are numeric code-point sets, never literals, so the check cannot be broken by the corruption it detects.

Documented limits

i18n-diacritic-density is a bulk-stripping detector, not a spell checker. It does not catch isolated stripped words in otherwise-correct prose — max-file-length.mdx scored a healthy 26.6 while its frontmatter was stripped, and only a word-level scan found it — and it skips pages under 200 prose letters. Both limits are written into GEN-002's Compliance section as review responsibilities rather than left implicit.

Verification

bun run validate green: 1675 tests, 49/49 rules, build clean. Fire-tested both directions:

Case Result
Stripped page restored from main density rule fires, names the file
å injected caught, correct line
Double-encoded ø injected caught, correct line
VALIDAÇÃO COMPLETA in pt-br not flagged

Review request

Please read the Portuguese as a native speaker. The automated checks count accents; they cannot tell a correct accent from a confidently wrong one. à versus a (crasis) and é versus e are the places to look — I added several of each. An orthography review agent returned PASS here, but its per-word annotations were fabricated, so its verdict carries no weight.

GEN-002 has always required correct diacritics ("DO use correct
diacritical marks in Portuguese -- `não` not `nao`"), but nothing
enforced it: the companion rules checked only structure (page parity,
translation drift, link prefixes). Six pt-br pages sat in main with
their diacritics stripped, and a seventh had a corrupted frontmatter
description.

The nb locale hit the identical failure in #384, where ~500 occurrences
across 35 files needed two manual passes -- the second pass caught four
cases the first missed. Manual review is not a reliable detector here.

Content fixes:
- restore diacritics across 6 pt-br/examples pages (prose, headings and
  frontmatter descriptions; code blocks and identifiers untouched)
- fix the stripped frontmatter description in max-file-length.mdx

New GEN-002 rules:
- i18n-encoding-corruption: rejects accented characters written as HTML
  entities and double-encoded UTF-8. Detection requires a 0xC2/0xC3 lead
  byte followed by a 0x80-0xBF continuation byte, so legitimate uppercase
  Portuguese (where U+00C3 precedes an ASCII letter) is not flagged.
- i18n-diacritic-density: measures accented characters per 1000 prose
  letters per locale, ignoring fenced and inline code, and fails a page
  below the locale minimum.

Thresholds come from the corpus: healthy minimums are nb 5.9 and pt-br
15.2 per 1000; stripped pages measured 0.0-1.1. Limits are nb 2 and
pt-br 5, sitting between the bands.

A whole-file "has at least one diacritic" check was rejected as too weak
-- measured against the corrupt files it caught 1 of 6, because a single
accented character anywhere, including inside a code sample, defeats it.

Accented characters in the rules file are expressed as numeric code
points, never literals, so the check cannot be broken by the corruption
it detects.

The density rule is a bulk-stripping detector, not a spell checker: it
does not catch isolated stripped words in otherwise-correct prose, and
skips pages under 200 prose letters. Both limits are documented in the
ADR as review responsibilities.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
Both are agent working process, so no mechanism can carry them. Every
other learning from this branch already has a home: the code-point idiom
lives in a comment in the rules file, and the density-over-word-list
choice with its limits is documented in GEN-002's Compliance section.

- A review agent's verdict on non-English prose is worthless and it
  fabricates the supporting detail. The orthography pass over the pt-br
  docs returned PASS while asserting accents the words do not contain.
  The existing "grep the quoted string" remedy does not apply to a claim
  about meaning, so the entry names the mechanical substitutes instead.
- Reproduce a described failure before scheduling work from it, and
  prove a zero result is a real zero. Two of three audit-derived issues
  collapsed to no work once tested, and a `\b` inside a JS template
  literal is a backspace, which hid 69 real occurrences behind a
  confident zero.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@rhuanbarreto, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7e85c446-ca2a-4d09-b20f-3bf1c400dbac

📥 Commits

Reviewing files that changed from the base of the PR and between bcb086f and 1c6b34e.

📒 Files selected for processing (10)
  • .archgate/adrs/GEN-002-docs-i18n.md
  • .archgate/adrs/GEN-002-docs-i18n.rules.ts
  • .claude/agent-memory/archgate-developer/MEMORY.md
  • docs/src/content/docs/pt-br/examples/kebab-case-filenames.mdx
  • docs/src/content/docs/pt-br/examples/max-file-length.mdx
  • docs/src/content/docs/pt-br/examples/no-banned-api.mdx
  • docs/src/content/docs/pt-br/examples/no-banned-imports.mdx
  • docs/src/content/docs/pt-br/examples/no-unapproved-deps.mdx
  • docs/src/content/docs/pt-br/examples/version-catalog.mdx
  • docs/src/content/docs/pt-br/examples/wrapper-enforcement.mdx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 26, 2026

Copy link
Copy Markdown

Deploying archgate-cli with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1c6b34e
Status: ✅  Deploy successful!
Preview URL: https://2a4042fe.archgate-cli.pages.dev
Branch Preview URL: https://fix-ptbr-diacritics-i18n-con.archgate-cli.pages.dev

View logs

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Code Coverage

Metric Value
Lines 91.3% (8354 / 9155)
Threshold 90% minimum — met
Platforms Linux + Windows

Full HTML report available in workflow artifacts.

Per-directory breakdown
Directory Coverage Lines
src/commands/ 88.7% 1966 / 2216
src/engine/ 93.8% 2230 / 2378
src/formats/ 98.7% 148 / 150
src/helpers/ 90.9% 4010 / 4411

@rhuanbarreto rhuanbarreto left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

(Posted as COMMENT because GitHub forbids REQUEST_CHANGES on one's own PR — treat this as a request-changes review.)

The approach is right: measured thresholds instead of guessed ones, corruption-proof numeric code points in the rules file, and an honest "Limits of Automated Enforcement" section. Content restoration spot-checks clean — diacritics are correct, including both crasis cases (leva à divergência, acesso direto à API) and the correct non-crasis before bare plurals (sensibilidade a maiúsculas).

One confirmed bug blocks merge: ENTITY_PATTERN misses &aacute;, &eacute;, &oacute; — the entities for á/é/ó, the three most common Portuguese acute vowels. Verified empirically (stateless .match, same flags as the rule). Details and a committable fix on the inline thread.

The other three threads are optional: a comment documenting the cp1252 gap in the mojibake heuristic, a scope note about the corruption rule scanning fenced code, and a native-speaker orthography call on não-produção.

Everything else — density rule, mojibake detection, ADR updates, memory entries — is sound and verified. Approve once the entity pattern is fixed and fire-tested with &eacute;.

Comment thread .archgate/adrs/GEN-002-docs-i18n.rules.ts Outdated
Comment thread .archgate/adrs/GEN-002-docs-i18n.rules.ts Outdated
Comment thread .archgate/adrs/GEN-002-docs-i18n.rules.ts
Comment thread docs/src/content/docs/pt-br/examples/wrapper-enforcement.mdx Outdated
ENTITY_PATTERN factored the vowel out of each entity name, so a branch
spelled `cute` could never match `&aacute;`: after `(?:a|A)` consumed the
vowel, the remainder was `acute;`. That silently dropped seven entities,
including the three most common Portuguese acute vowels, plus `&AElig;`,
whose canonical capital E the `(?:a|A)elig` branch cannot produce.

Replaced with one branch per entity name, so every name is independently
greppable and the mis-factoring cannot recur. Verified by extracting the
pattern from the source: 29/29 accented entities matched, and none of
`&lt; &gt; &amp; &quot; &nbsp; &acute; &copy;`. The standalone `&acute;`
stays excluded -- U+00B4 is a bare mark, not an accented letter, and the
numeric 192-255 range excludes it too.

The original fire-test passed because it injected `&aring;`, which lands
on a working branch. Both directions were covered but not every branch.

Also from review:
- document why the mojibake bound stops at 0xBF instead of covering
  Windows-1252, which remaps 0x80-0x9F and would need a wider range that
  false-positives on 0xC3 followed by NBSP or an ordinal indicator
- document that i18n-encoding-corruption scans fenced code while
  i18n-diacritic-density strips it, so a page demonstrating entity
  escapes would be flagged
- document that neither rule judges whether an accent is the correct one
- reword `código de não-produção` to `o código que não é de produção`,
  sidestepping post-1990 hyphenation ambiguity rather than guessing

The Windows-1252 rationale went into the ADR rather than the comment
block: inlining it pushed the block to 10 prose lines against GEN-004's
limit of 5, which the check caught.

Signed-off-by: Rhuan Barreto <rhuan@barreto.work>
@rhuanbarreto
rhuanbarreto enabled auto-merge (squash) July 26, 2026 02:50
@rhuanbarreto
rhuanbarreto merged commit db39104 into main Jul 26, 2026
23 checks passed
@rhuanbarreto
rhuanbarreto deleted the fix/ptbr-diacritics-i18n-content-rules branch July 26, 2026 02:50
@archgatebot archgatebot Bot mentioned this pull request Jul 26, 2026
rhuanbarreto pushed a commit that referenced this pull request Jul 26, 2026
# archgate

## [0.51.0](v0.50.0...v0.51.0)
(2026-07-26)

### Features

* **adrs:** enforce concise, forward-only code comments (GEN-004)
([#496](#496))
([9a114b3](9a114b3)),
references [#2123](https://github.com/archgate/cli/issues/2123)
* **adrs:** flag stray files at the repository root (GEN-005)
([#535](#535))
([6a6e765](6a6e765)),
closes [#514](#514), references
[#500](#500)
* **engine:** add ctx.readYAML and ctx.checkCase rule helpers
([#497](#497))
([c5d82c5](c5d82c5)),
closes [#490](#490), references
[#490](#490)
[#491](#491)
[#499](#499)
[#499](#499)
[#499](#499)
[#499](#499)
* report truncated ADR briefings, trim the ADR corpus 15.6%, add GEN-005
briefing budget ([#501](#501))
([a9dab40](a9dab40))

### Bug Fixes

* **docs:** relocate ADR content to clear briefing-budget warnings
([#531](#531))
([c7419b3](c7419b3))
* **docs:** restore pt-br diacritics and enforce locale content
integrity ([#523](#523))
([db39104](db39104)),
closes [#516](#516), references
[#231](#231)
* **engine:** allow symlinks that resolve inside the project root
([#500](#500))
([387bf15](387bf15))
* **engine:** reject rule-file reads through a symlinked ancestor
directory ([#499](#499))
([a555f9d](a555f9d)),
references [#497](#497)
[#491](#491)
[#497](#497)
[#497](#497)
[#497](#497)
[#497](#497)
* **engine:** scan top-level export declarations with a null source
([#493](#493))
([d07db03](d07db03)),
closes [#491](#491)
* **engine:** stop dropping AST nodes with exotic literal values
([#494](#494))
([0015542](0015542)),
closes [#493](#493)
[#493](#493)
* **lint:** resolve no-bare-env-restore by captured key and lexical
scope ([#524](#524))
([7094a3a](7094a3a)),
closes [#498](#498)
* **rules:** make ARCH-020 and ARCH-023 match ctx.ast() instead of raw
text ([#533](#533))
([ad5529b](ad5529b)),
closes [#513](#513), references
[#486](#486)
* **tests:** replace bun:test anti-patterns with idiomatic patterns
([#512](#512))
([bcb086f](bcb086f))

---
This PR was generated with
[simple-release](https://github.com/TrigenSoftware/simple-release).

<details>
<summary>📄 Cheatsheet</summary>
<br>



You can configure the bot's behavior through a pull request comment
using the `!simple-release/set-options` command.

### Command Format

````md
!simple-release/set-options

```json
{
  "bump": {},
  "publish": {}
}
```
````

### Useful Parameters

#### Bump

| Parameter | Type | Description |
|-----------|------|-------------|
| `version` | `string` | Force set specific version |
| `as` | `'major' \| 'minor' \| 'patch' \| 'prerelease'` | Release type
|
| `prerelease` | `string` | Pre-release identifier (e.g., "alpha",
"beta") |
| `firstRelease` | `boolean` | Whether this is the first release |
| `skip` | `boolean` | Skip version bump |
| `byProject` | `Record<string, object>` | Per-project bump options for
monorepos |

#### Publish

| Parameter | Type | Description |
|-----------|------|-------------|
| `skip` | `boolean` | Skip publishing |
| `access` | `'public' \| 'restricted'` | Package access level |
| `tag` | `string` | Tag for npm publication |

### Usage Examples

#### Force specific version

````md
!simple-release/set-options

```json
{
  "bump": {
    "version": "2.0.0"
  }
}
```
````

#### Force major bump

````md
!simple-release/set-options

```json
{
  "bump": {
    "as": "major"
  }
}
```
````

#### Create alpha pre-release

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "alpha"
  }
}
```
````

#### Publish with specific access and tag

````md
!simple-release/set-options

```json
{
  "bump": {
    "prerelease": "beta"
  },
  "publish": {
    "access": "public",
    "tag": "beta"
  }
}
```
````

### Custom Changelog Preamble

You can add custom markdown to the top of the changelog (right after the
version header) using the `!simple-release/set-preamble` command. The
markdown after the command line becomes the preamble.

```md
!simple-release/set-preamble

## What's new?

- The website was completely redesigned
- The new API gives you awesome possibilities
```

In a monorepo, pass the full package name after the command to target a
single package's changelog. Wrap the name in backticks so GitHub keeps
it as text instead of a mention:

```md
!simple-release/set-preamble `@your-org/core`

## Core changes

- New plugin system
```

Use one comment per package, plus one without a name for the whole
release.

### Access Restrictions

The commands can only be used by users with permissions:
- repository owner
- organization member
- collaborator

### Notes

- The last comment with `!simple-release/set-options` command takes
priority
- The last `!simple-release/set-preamble` comment per package takes
priority
- JSON must be valid, otherwise the `set-options` command will be
ignored
- Parameters apply only to the current release execution
- The commands can be updated by editing the comment or adding a new one


</details>

<!--
  Please do not edit this comment.
  simple-release-pull-request: true
  simple-release-branch-from: release
  simple-release-branch-to: main
-->

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(rules): GEN-002 should check locale content for diacritic corruption, not just parity

1 participant