Quickstart: Universal Charter Rename

Feature: 063-universal-charter-rename Date: 2026-04-04 (revised)

Implementation Order

WP01 (core packages) ──┐
                        ├──→ WP03 (imports + build) ──┐
WP02 (CLI + runtime)  ─┘                              │
                                                       ├──→ WP07 (tests) ──┐
WP04 (doctrine + skills) → WP05 (agent artifacts)     │                    ├──→ WP08 (docs + acceptance)
                                                       │                    │
WP06 (migration overhaul) ────────────────────────────┘────────────────────┘

Start with WP01 + WP02 + WP04 in parallel, then cascade.

Rename Checklist Per File

For every renamed file: 1. git mv old_path new_path 2. Replace "constitution" → "charter" in all content (case-preserving: Constitution → Charter, CONSTITUTION → CHARTER) 3. Rename classes/functions that contain "constitution" (see data-model.md Symbol Renames) 4. Update imports in the file itself

Critical Rules

1. Old migrations become STUBS — do NOT rewrite their detect()/apply() logic; replace entire class body with no-op stub returning False/success 2. Use git mv for all renames to preserve history 3. Case-preserving replacement: constitutioncharter, ConstitutionCharter, CONSTITUTIONCHARTER 4. Test after each WP: python -m pytest tests/ -x -q (adjust for renamed test paths) 5. Content rewriting in migration: The charter-rename migration must find-replace "constitution" in generated .kittify/ files AND agent prompt bootstrap commands 6. Metadata normalization: Must run at load time BEFORE the migration loop

Old Migration Stub Template

@MigrationRegistry.register
class CharterCleanupMigration(BaseMigration):
    migration_id = "0.10.12_charter_cleanup"
    description = "Superseded by 3.1.1_charter_rename"
    target_version = "0.10.12"

    def detect(self, project_path: Path) -> bool:
        return False

    def can_apply(self, project_path: Path) -> tuple[bool, str]:
        return False, "Superseded by charter-rename migration"

    def apply(self, project_path: Path, dry_run: bool = False) -> MigrationResult:
        return MigrationResult(success=True, warnings=["Superseded by charter-rename"])

Metadata Normalization Template

# In metadata.py
_LEGACY_MIGRATION_ID_MAP: dict[str, str] = {
    "0.10.12_constitution_cleanup": "0.10.12_charter_cleanup",
    "0.13.0_update_constitution_templates": "0.13.0_update_charter_templates",
    "2.0.0_constitution_directory": "2.0.0_charter_directory",
    "2.0.2_constitution_context_bootstrap": "2.0.2_charter_context_bootstrap",
    "2.1.2_fix_constitution_doctrine_skill": "2.1.2_fix_charter_doctrine_skill",
}

def _normalize_legacy_ids(self) -> bool:
    """Rewrite constitution-era migration IDs to charter-era IDs."""
    changed = False
    for record in self.applied_migrations:
        new_id = _LEGACY_MIGRATION_ID_MAP.get(record.id)
        if new_id:
            record.id = new_id
            changed = True
    return changed

Charter-Rename Migration Template

@MigrationRegistry.register
class CharterRenameMigration(BaseMigration):
    migration_id = "3.1.1_charter_rename"
    description = "Comprehensive charter rename: migrate all constitution-era state"
    target_version = "3.1.1"

    def detect(self, project_path: Path) -> bool:
        kittify = project_path / ".kittify"
        # Layout A
        if (kittify / "constitution").exists():
            return True
        # Layout B
        if (kittify / "memory" / "constitution.md").exists():
            return True
        # Layout C
        missions = kittify / "missions"
        if missions.exists():
            for m in missions.iterdir():
                if m.is_dir() and (m / "constitution").exists():
                    return True
        return False

    def apply(self, project_path: Path, dry_run: bool = False) -> MigrationResult:
        changes = []
        kittify = project_path / ".kittify"
        charter_dir = kittify / "charter"

        # Phase 1: Layout normalization
        # ... handle Layout C, B, A, partial state

        # Phase 2: Content rewriting
        # For all text files in charter_dir:
        #   case-insensitive replace "constitution" → "charter"
        # For deployed agent prompts:
        #   replace "spec-kitty constitution context" → "spec-kitty charter context"

        # Phase 3: Agent artifact rename
        # Rename spec-kitty.constitution.md → spec-kitty.charter.md
        # Rename spec-kitty-constitution-doctrine/ → spec-kitty-charter-doctrine/

        # Phase 4: Metadata normalization
        # Rewrite old migration IDs in metadata.yaml

        return MigrationResult(success=True, changes_made=changes)

Verification Commands

# Primary acceptance gate (zero matches outside 2 bounded exception files)
rg -n -i constitution . \
  --glob '!CHANGELOG.md' \
  --glob '!kitty-specs/' \
  --glob '!src/specify_cli/upgrade/migrations/m_3_1_1_charter_rename.py' \
  --glob '!src/specify_cli/upgrade/metadata.py'

# Filename gate (zero filenames with "constitution")
find . -name '*constitution*' -not -path './kitty-specs/*' -not -path './.git/*'

# Run tests
python -m pytest tests/ -x -q

# Type checking
mypy --strict src/charter/ src/specify_cli/charter/

# Verify old migration stubs contain zero "constitution"
for f in src/specify_cli/upgrade/migrations/m_0_10_12_*.py \
         src/specify_cli/upgrade/migrations/m_0_13_0_*.py \
         src/specify_cli/upgrade/migrations/m_2_0_0_*.py \
         src/specify_cli/upgrade/migrations/m_2_0_2_*.py \
         src/specify_cli/upgrade/migrations/m_2_1_2_*.py; do
  rg -i constitution "$f" && echo "FAIL: $f still contains constitution" || true
done

Backward-Compatibility Exception Audit

After implementation, only these 2 files should contain "constitution":

FileExpected matchesJustification
src/specify_cli/upgrade/migrations/m_3_1_1_charter_rename.py~10-15Path literals to detect old filesystem state
src/specify_cli/upgrade/metadata.py5Legacy migration ID lookup keys

Any other file with "constitution" (outside CHANGELOG.md and kitty-specs/) is a bug.