T-8XI6-promote-pluralize-to-shared-helper
Status: closed/done · Impact: low · Complexity: small
Auto-generated from a /sdlc:task-work post-mortem. Review and
promote to open/ready before picking up.
pluralize() (with its IRREGULAR_PLURALS carve-out table) now exists
in two places — plugin/skills/setup/setup_planning.py and
plugin/scripts/audit_entities.py — and the audit copy carries an
explicit # mirror setup_planning.py comment. The PRINCIPLES.md
“co-locate first, promote when shared” rule says promotion happens once
a second caller appears, which is now the case. Future entity-name
additions (the next irregular plural, e.g. series → series,
feedback → feedback) have to land in two files in lockstep, and drift
between them is a silent bug class.
| Location | Role today |
|---|---|
plugin/skills/setup/setup_planning.py | defines pluralize() and the IRREGULAR_PLURALS table inline. |
plugin/scripts/audit_entities.py | duplicates both verbatim under a # mirror setup_planning.py banner. |
plugin/lib/ | established home for shared, importable Python modules (e.g. plugin/lib/lease/ and its __init__.py). The natural landing site for a shared pluralize helper. |
Proposed
Section titled “Proposed”A single pluralize() and IRREGULAR_PLURALS definition lives in a
shared module under plugin/lib/entity_naming/ (matching the
plugin/lib/<topic>/ convention established by plugin/lib/lease/).
Both setup_planning.py and audit_entities.py import it. The
# mirror setup_planning.py banner in audit_entities.py goes away.
Approach
Section titled “Approach”- Create
plugin/lib/entity_naming/__init__.pywithpluralize()andIRREGULAR_PLURALS. The location matches the lease library precedent —plugin/lib/is the established home for shared, importable modules. - Update both callers to import:
from plugin.lib.entity_naming import pluralize, IRREGULAR_PLURALS(or equivalent relative import shape used elsewhere in the repo). - Delete the inline definitions in
setup_planning.pyandaudit_entities.py; drop the# mirror setup_planning.pybanner. - Run the existing eval suites (
plugin/skills/setup/tests/run_evals.py) and the audit (plugin/scripts/audit_entities.py) to confirm no regression.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/entity_naming/__init__.py | new | houses pluralize() and IRREGULAR_PLURALS. |
plugin/skills/setup/setup_planning.py | modify | remove inline definitions; import from the new shared module. |
plugin/scripts/audit_entities.py | modify | same import refactor; remove the # mirror setup_planning.py banner. |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
command grep -rn "def pluralize" plugin/returns exactly one match (the shared module’s definition). - AC-2:
plugin/skills/setup/tests/run_evals.pystill passes after the refactor. - AC-3:
plugin/scripts/audit_entities.pyruns end-to-end against this repo’sdocs/planning/and produces output equivalent to its pre-refactor output (smoke check; the audit is read-only). - AC-4: Adding a new entry to
IRREGULAR_PLURALSin the shared module is observable from bothsetup_planning.pyandaudit_entities.pywithout further edits.
Out of scope
Section titled “Out of scope”- Renaming
pluralize()to something more accurate (it’s an irregular-plural-aware pluralizer, butpluralizeis what every caller already says). - Promoting
_singularizefromplugin/validators/validate_frontmatter.pyto the same module — leave that for a follow-up if a second caller appears.
Dependencies
Section titled “Dependencies”- none
Discovery context
Section titled “Discovery context”Spawned by /sdlc:task-work post-mortem of T-5BAR-pluralize-recognizes-backlog-as-plural on 2026-05-21.
Post-mortem
Section titled “Post-mortem”Captured by /sdlc:task-work on 2026-05-28. PR: pending.
Acceptance criteria coverage
Section titled “Acceptance criteria coverage”- AC-1: auto —
command grep -rn "def pluralize" plugin/returns exactly one match (plugin/lib/entity_naming/__init__.py:53). - AC-2: auto —
plugin/skills/setup/tests/run_evals.pyreportsOK all 6 case(s) passedafter the refactor. - AC-3: agent-manual — ran
plugin/scripts/audit_entities.py --jsonon the worktree and on the pre-refactorgit stash-restored tree;diffreturned zero (byte-identical output across both runs). - AC-4: auto —
test_extending_irregular_plurals_is_observableinplugin/lib/entity_naming/tests/test_pluralize.pyplus an explicit identity check (setup_planning.IRREGULAR_PLURALS is entity_naming.IRREGULAR_PLURALS) confirm the shared dict is the same object across both callers.
What worked
Section titled “What worked”- The
plugin/lib/convention had two prior precedents (lease/,prs_field/) and_example/as canonical reference. Authoring the new library was a near-mechanical copy of that shape. - The baseline quality gate from Step 3a meant the 232 pre-existing
drift lines surfaced as
pre-existing:informational output instead of blocking the gate — kept the focus on the refactor’s own effect (which was zero new drift). --diff-against-baseline+ the byte-identicalaudit_entities.py --jsonround-trip pinned AC-3 deterministically without a hand-written golden file.
Friction and automation gaps
Section titled “Friction and automation gaps”- Step 5b’s start_task.py rebase produced a frontmatter conflict
between the main-side start-commit (status flip + last_reviewed
bump) and the task-branch verify-stamp commit (readiness_verified_at
stamp). The two commits touch disjoint frontmatter fields but land
on the same
---block; git can’t auto-merge YAML field-by-field. Resolution was trivial (keep both edits) but had to be done by hand. — start_task.py could either: (a) re-run ensure-ready AFTER the main-side start-commit (so the stamp lands on top, no conflict), or (b) embed a YAML-aware three-way frontmatter merger for the canonical case of disjoint field edits. Worth a follow-up. - The pre-existing
sys.path.insert(0, plugin_lib)in setup_planning.py at line ~357 (lazy lease import) and my newly-added module-top bootstrap for entity_naming now duplicate the same insertion. The lazy version has aif str(plugin_lib) not in sys.pathguard, so it’s harmless, but redundant. — a small refactor to lift the bootstrap to a single helper (or hoist setup_planning.py’s lazy lease import up to module load now that the path is always inserted) would tidy this. Marginal, but a future contributor will trip over it. Skipped for this task to keep the scope tight.