Skip to content

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.

LocationRole today
plugin/skills/setup/setup_planning.pydefines pluralize() and the IRREGULAR_PLURALS table inline.
plugin/scripts/audit_entities.pyduplicates 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.

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.

  1. Create plugin/lib/entity_naming/__init__.py with pluralize() and IRREGULAR_PLURALS. The location matches the lease library precedent — plugin/lib/ is the established home for shared, importable modules.
  2. Update both callers to import: from plugin.lib.entity_naming import pluralize, IRREGULAR_PLURALS (or equivalent relative import shape used elsewhere in the repo).
  3. Delete the inline definitions in setup_planning.py and audit_entities.py; drop the # mirror setup_planning.py banner.
  4. Run the existing eval suites (plugin/skills/setup/tests/run_evals.py) and the audit (plugin/scripts/audit_entities.py) to confirm no regression.
LocationKindChange
plugin/lib/entity_naming/__init__.pynewhouses pluralize() and IRREGULAR_PLURALS.
plugin/skills/setup/setup_planning.pymodifyremove inline definitions; import from the new shared module.
plugin/scripts/audit_entities.pymodifysame import refactor; remove the # mirror setup_planning.py banner.
  • 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.py still passes after the refactor.
  • AC-3: plugin/scripts/audit_entities.py runs end-to-end against this repo’s docs/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_PLURALS in the shared module is observable from both setup_planning.py and audit_entities.py without further edits.
  • Renaming pluralize() to something more accurate (it’s an irregular-plural-aware pluralizer, but pluralize is what every caller already says).
  • Promoting _singularize from plugin/validators/validate_frontmatter.py to the same module — leave that for a follow-up if a second caller appears.
  • none

Spawned by /sdlc:task-work post-mortem of T-5BAR-pluralize-recognizes-backlog-as-plural on 2026-05-21.

Captured by /sdlc:task-work on 2026-05-28. PR: pending.

  • 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.py reports OK all 6 case(s) passed after the refactor.
  • AC-3: agent-manual — ran plugin/scripts/audit_entities.py --json on the worktree and on the pre-refactor git stash-restored tree; diff returned zero (byte-identical output across both runs).
  • AC-4: auto — test_extending_irregular_plurals_is_observable in plugin/lib/entity_naming/tests/test_pluralize.py plus an explicit identity check (setup_planning.IRREGULAR_PLURALS is entity_naming.IRREGULAR_PLURALS) confirm the shared dict is the same object across both callers.
  • 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-identical audit_entities.py --json round-trip pinned AC-3 deterministically without a hand-written golden file.
  • 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 a if str(plugin_lib) not in sys.path guard, 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.

← Back to Tasks