Formal Copilot review instructions + cross-agent guidelines (recognizers, YAML config, general practices) - #2211
Formal Copilot review instructions + cross-agent guidelines (recognizers, YAML config, general practices)#2211omri374 wants to merge 10 commits into
Conversation
Recognizers are tested by constructing them in Python. Nothing tests the path users take, which is enabling them in a registry YAML. 61 of the 86 entries in default_recognizers.yaml ship disabled, so their constructors are never exercised from configuration at all. Enabling every entry in isolation shows three that raise on construction (UsMbiRecognizer, KrBrnRecognizer, KrDriverLicenseRecognizer: __init__ rejects the 'name' key the loader passes), and 31 that load nothing because their languages are excluded by the top-level supported_languages filter, silently. Adds a required configuration-path test, plus guidance drawn from recurring review findings: enabled-by-default framed as false-positive surface rather than geography, score bands matching the codebase, substring context matching, exact score assertions, lookalike negatives, and a backward-compatibility section. Also corrects the SSN test example, which used 123-45-6789 as a true positive. That value is on the recognizer's sample-SSN denylist and returns no results.
Coverage report (presidio-anonymizer)Click to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Coverage report (presidio-structured)Click to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||
Coverage report (presidio-cli)Click to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||
Clarified scoring criteria for strong patterns in the instructions.
There was a problem hiding this comment.
Pull request overview
Updates the repository’s Copilot contributor guidance for Presidio recognizers to require configuration-path testing (enabling recognizers via YAML and loading through RecognizerRegistryProvider) and to align recognizer/testing guidance with current loader behavior and maintainer practices.
Changes:
- Adds a new requirement that new recognizers include at least one test which enables the recognizer in a YAML registry config and asserts detection through
RecognizerRegistryProvider. - Refines recognizer design/testing guidance (score bands, thresholds vs hard context requirements, substring context matching behavior, and exact-score assertions).
- Fixes the SSN documentation example to avoid denylisted sample SSNs and adds troubleshooting/backward-compatibility guidance.
Coverage report (presidio-image-redactor)Click to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||
Coverage report (presidio-analyzer)Click to see where and how coverage changed
The report is truncated to 25 files out of 80. To see the full report, please visit the workflow summary page. This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (5)
.github/copilot-instructions.md:65
- The repo already has a non-full-name country directory (
presidio_analyzer/predefined_recognizers/country_specific/thai/), so stating that onlyus/ukare exceptions is inaccurate and can confuse contributors. Either listthaias an existing exception or relax the rule to match current layout.
Directory names are the full lowercase country name (`south_africa`, `philippines`,
`canada`), not the ISO country code. The only exceptions are the pre-existing `us`
and `uk` directories. Do not add new abbreviated directories.
.github/copilot-instructions.md:108
- The
LemmaContextAwareEnhancerconstructor parameter iscontext_matching_mode, notmatching_mode; using the wrong name here will lead to incorrect guidance/snippets.
**Context words are matched as substrings.** `LemmaContextAwareEnhancer` defaults to
`matching_mode="substring"`, so short context words fire on unrelated tokens.
.github/copilot-instructions.md:69
- This language-code guidance conflicts with current code/config:
default_recognizers.yamlincludeskrinsupported_languagesfor Korean recognizers andKrPassportRecognizerdefaultssupported_language="kr". Clarify thatkris a legacy literal tag and that ISO 639-1 (ko) should be used to avoid recognizers being filtered out when users pass standard language codes.
Language codes are different: `supported_language` and the YAML `supported_languages`
key take ISO 639-1 language codes (`ko` for Korean), not country codes (`kr`). A
mismatch here produces a recognizer that never loads.
.github/copilot-instructions.md:100
UsBankRecognizeruses score 0.05 for its 8-17 digit pattern, which falls in the table’s “very weak” band; calling it a “weak score” here is misleading given the new score-band guidance. Consider stating the exact score instead to keep the example consistent.
Compare against existing recognizers before choosing: `UsPassportRecognizer` uses
0.05 for nine bare digits, `UsBankRecognizer` uses a weak score for 8-17 digits.
.github/copilot-instructions.md:202
- The configuration-path test example references an undefined
nlp_enginevariable, so it won’t run as written. Defining a lightweight engine (e.g.,NoOpNlpEngine) in the snippet makes it copy/pasteable and avoids requiring spaCy model downloads for pattern-only recognizers.
registry = RecognizerRegistryProvider(
conf_file=conf
).create_recognizer_registry()
analyzer = AnalyzerEngine(registry=registry, nlp_engine=nlp_engine)
Replaces the enabled-by-default criteria, which used the presence of a checksum as a gate. Most patterns have no checksum: about 40% of the predefined PatternRecognizer subclasses do not override validate_result, and that is the right choice when the entity has no verifiable structure. Coincidental matches are also not the problem. A generic pattern scored at 0.05 costs nothing, because a threshold removes it while context or validation can still lift a real match. The disqualifier for shipping enabled is a coincidental match that arrives at a score no threshold can separate from a true positive. Adds a section covering the hooks as a generic capability: True replaces the score with MAX_SCORE, False drops the result, None leaves the pattern score alone. Spells out that a check which is only mandatory across part of the entity's range can promote but never invalidate, so it inflates coincidental matches to full confidence while genuine lookalikes keep the base score. The previous wording compressed this into one unclear sentence.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/copilot-instructions.md:65
- The guidance says the only abbreviated country-specific directory names are
usanduk, but the current codebase also includescountry_specific/thai/(e.g.,th_tnin_recognizer.py), so the statement is inaccurate and could mislead contributors.
Directory names are the full lowercase country name (`south_africa`, `philippines`,
`canada`), not the ISO country code. The only exceptions are the pre-existing `us`
and `uk` directories. Do not add new abbreviated directories.
59a7f1e to
f594c3d
Compare
Adds a repo-local code review skill that captures the recognizer testing, scoring, and backward-compatibility practices established in PR #2211. The skill classifies a diff (recognizer change vs. shared-class change) and applies matching checklists. Its load-bearing rule: any PR adding or changing a recognizer must include a configuration-path test that loads the recognizer through RecognizerRegistryProvider and asserts detection, since predefined recognizers ship enabled: false and are otherwise never exercised on the path users actually take. A references file provides the test template and the full list of defects the test catches. Also covers score-band calibration, validate_result promotion pitfalls, context-substring matching, exact-score/lookalike-negative test requirements, and backward-compatibility review for changes to shared library classes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ck825cANTDYre5UCfmpYE
Adds .github/instructions/recognizer-review.instructions.md, a review-only, path-scoped GitHub Copilot custom-instructions file. It applies to recognizer sources, default_recognizers.yaml, and recognizer tests, and directs Copilot code review to require a RecognizerRegistryProvider configuration-path test, check construction-path agreement and backward compatibility, and enforce the score-calibration, validate_result, context-word, and test-quality rules captured in the recognizer-pr-review skill. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ck825cANTDYre5UCfmpYE
- Use context_matching_mode (the actual LemmaContextAwareEnhancer constructor parameter) instead of matching_mode. - Correct the directory-naming note: thai/ (and us/uk) are pre-existing short forms not to imitate, rather than claiming only us/uk are exceptions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ck825cANTDYre5UCfmpYE
…layout Reorganize per GitHub's repository-custom-instructions guidance: - .github/copilot-instructions.md: slimmed to repo-wide general engineering practices (backward compatibility, security, cross-component rules, testing and documentation standards, review posture). - .github/instructions/recognizers.instructions.md: path-scoped rules for adding or modifying PII recognizers (config-path testing, score calibration, validation hooks, context words, companion updates). - .github/instructions/yaml-config.instructions.md: new path-scoped rules for the pydantic YAML-configuration layer (schema/constructor sync, extra and exclude_none discipline, parse-time validation, schema backward compatibility). - AGENTS.md: authoring-time guidance for coding agents (Claude Code, Cursor, Copilot coding agent), with CLAUDE.md importing it. - Remove .claude/skills/recognizer-pr-review and the superseded recognizer-review.instructions.md in favor of the above. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ck825cANTDYre5UCfmpYE
- Reorder recognizer review priorities: pattern accuracy (specificity, context, checksum, documented source) first, then testing, then the rest. - Correct enabled-by-default guidance: global recognizers generally ship enabled when their false-positive rate is low; country-specific default to disabled. - Future-proof the schema/constructor sync rule: check for mismatches in new contributions rather than hard-coding today's 'extra' behavior. - AGENTS.md: allow deterministic anonymization as an explicit documented opt-in, add a Security bullet, and reference the path-scoped instruction files instead of duplicating their content. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013ck825cANTDYre5UCfmpYE
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.
Suppressed comments (9)
AGENTS.md:66
- The top-level language filter emits a warning when it skips an enabled recognizer; it does not filter silently. This should match the loader behavior so authoring agents do not infer that no diagnostic is available.
instructions file). Non-English recognizers must set the top-level
`supported_languages` in the test config — it defaults to `["en"]` and
silently filters everything else.
.github/instructions/recognizers.instructions.md:77
- The loader logs a warning when a recognizer's language is excluded by the global set, so describing this as silent is factually incorrect and can trigger false review feedback about missing diagnostics.
**Non-English recognizers:** the top-level `supported_languages` key acts as a
global filter and the shipped default is `["en"]`. A recognizer supporting only
`de` will not load from that config — silently. The test config and the PR
description must state the required top-level languages.
.github/instructions/recognizers.instructions.md:55
- This template fails when adapted to the required non-English case:
AnalyzerEnginedefaults its own languages to["en"]and rejects a registry configured for (for example)["de"]. Pass the registry languages explicitly so the mandated configuration-path test works for every language.
This issue also appears on line 74 of the same file.
analyzer = AnalyzerEngine(registry=registry, nlp_engine=nlp_engine)
.github/instructions/yaml-config.instructions.md:58
- Nested config
model_dump()overrides are not invoked whenRecognizerRegistryConfigserializes them; the provider instead stripsNonecentrally in_prepare_recognizer_kwargs(lines 295–299). Requiring per-model overrides would therefore produce false review findings; document the actual centralized invariant and its provider-path test.
Models whose dump is passed to a constructor override `model_dump` with
`exclude_none=True`, so a field omitted in YAML preserves the constructor
default instead of overriding it with an explicit `None`. Any new pass-through
config model must do the same; flag one that doesn't — it silently clobbers
constructor defaults, which is this layer's sneakiest backward-compatibility
AGENTS.md:47
- The language mismatch is not silent:
_is_language_supported_globallyemits a warning naming the recognizer and both language sets before skipping it. Correcting this avoids teaching agents to report a missing diagnostic that already exists.
This issue also appears on line 64 of the same file.
2. **Use ISO 639-1 language codes** (`ko` for Korean, never `kr`) — a
mismatch loads nothing, silently.
AGENTS.md:51
supported_entityis not a universally required constructor kwarg:_prepare_recognizer_kwargsdeliberately adapts singular/plural entity fields to the inspected signature. Requiring every recognizer to accept the singular form conflicts with valid multi-entity constructors; list only the applicable loader fields.
3. **Make the constructor loader-compatible**: accept the YAML loader's
kwargs (`name`, `supported_entity`, `context`, ...) and forward them to
the base class, or the recognizer crashes the whole registry the moment a
user enables it.
.github/copilot-instructions.md:90
- This blanket rule contradicts Presidio's supported
encryptoperator and the hash operator's documented, explicit fixed-salt mode for referential integrity. It would make reviewers flag valid opt-in use cases; require a secure non-reversible default while separately scrutinizing documented reversible or deterministic opt-ins.
- **Reversible or weak anonymization** — deterministic hashing is reversible
via rainbow tables; use random/unpredictable replacement values that don't
preserve PII characteristics.
.github/copilot-instructions.md:165
- This E2E command fails on a clean checkout:
uv synccreated an unactivated analyzer environment, whilee2e-testshas only its ownrequirements.txt, so plainpytestand its dependencies are not installed. Include the E2E environment setup documented indocs/development.md:162-176.
# E2E
docker compose up --build -d && cd e2e-tests && pytest -v
.github/copilot-instructions.md:99
- The stated adversarial input actually matches, so it does not force the nested quantifiers to exhaust alternatives. The exponential behavior appears on a long non-match such as
aaaa...c; using that example is important so agents write an effective regression test.
- Avoid catastrophic regex backtracking (`(a+)+b` is O(2^n) on `aaaa...b`);
test patterns against long adversarial strings.
Change Description
Restructures Presidio's AI guidance into the formal layout GitHub recommends for repository custom instructions, so that (a) the Copilot PR review agent gives accurate, path-scoped feedback before a human reviewer arrives, and (b) coding agents (Claude Code, Cursor, Copilot coding agent) follow the same rules at authoring time.
.github/copilot-instructions.md.github/instructions/recognizers.instructions.mddefault_recognizers.yaml, recognizer tests.github/instructions/yaml-config.instructions.mdinput_validation/,recognizer_registry/,conf/, config testsextraandexclude_nonediscipline, parse-time validation with actionable messages, schema backward compatibilityAGENTS.md(+CLAUDE.mdimporting it)Copilot code review reads the repo-wide file plus whichever path-scoped files match the diff, so a docs-only PR is not reviewed against recognizer checklists. Since July 2026 it reads instructions from the PR's head branch, so this PR's own Copilot review runs under these instructions — a built-in validation pass.
Why
Recognizers are tested by constructing them in Python. Nothing tests the path users take, which is flipping
enabled: truein a registry YAML.default_recognizers.yamlenabled: falseEnabling every entry in isolation, with the entry's own languages, surfaces defects that Python-only tests cannot see:
The loader passes the YAML
namekey to the constructor. These three classes do not accept it, so enabling any of them raises and takes down construction of the whole registry. Reproduced throughRecognizerRegistryProvider.AzureAILanguageRecognizerandKrPassportRecognizershare the gap but are not reachable from the shipped config.Two more in the same family:
supported_languagesis["en"]. Settingenabled: trueon any of them loads nothing, with no error or warning.KrPassportRecognizerdefaults tosupported_language="kr". Its four siblings useko, and the YAML lists both codes for those siblings.The YAML-configuration instructions are grounded in the
input_validationlayer's real failure modes:PredefinedRecognizerConfigsilently ignores unknown YAML keys (pydantic'sextra="ignore"default), so a constructor kwarg without a schema field is dropped with no error — the gapLangExtractRecognizerConfigexists to close — and kwargs models must dump withexclude_none=Trueor omitted YAML fields clobber constructor defaults.What changed
copilot-instructions.mdinto the four files above; recognizer and configuration depth moved to path-scoped files, general practices stayed repo-wide. Guidance corrected in earlier revisions of this PR (score bands, substring context matching, enabled-by-default criteria, denylisted SSN sample values, exact-score assertions, backward-compatibility section) carries over into the new files.AGENTS.md+CLAUDE.mdso coding agents get authoring-time guidance; previously the repo had none..claude/skills/recognizer-pr-review/(superseded byAGENTS.md+ the instructions files, which Copilot code review and Claude Code both read).Follow-ups, not in this PR
name: Optional[str] = Nonein its constructor. Small and separable.CHANGELOG.md, but open and merged PRs do. Either a CI check should enforce it or the rule should be dropped, since an unenforced rule teaches contributors to skim the file. Left unchanged here because the decision is yours.Checklist