Skip to main content

Summary

Renamed 39 files and ~2,000+ identifiers across a TypeScript/Bun monorepo (STRATT) from Skill*Doctrine* terminology, applying hard-cut extension migration (.skill.md.doctrine.md) and layered execution order (L0→L5). All 228 tests passed, build succeeded, zero functional regressions. Key outcome: Strict dependency order (schema → package → graph/CLI → app → docs) combined with per-layer test verification eliminated risk of circular breakage. This pattern is reusable for other cross-cutting renames in monorepos with strict layering.

Execution Strategy

1. Dependency Mapping (Pre-Execution)

Mapped monorepo’s 5-layer architecture:
  • L1: @stratt/schema (Zod validators, type defs)
  • L2.5: @stratt/doctrines (parser, registry, resolver)
  • L3: @stratt/graph (DAG, CI pipeline)
  • L5: @stratt/cli (commander, orchestrator)
  • L6: apps/meridian (Astro web app)
  • Content: .opencode/doctrines/, docs/atlas/doctrines/
Constraint: Each layer depends only on lower layers. No circular refs allowed.

2. Layered Commit Strategy

7 independent commits, executed in dependency order:
CommitLayerChangesTestsVerifiable Output
1L1+L2.5Schema types + package rename20@stratt/doctrines working; schema types correct
2L3+L5Graph fixtures + CLI commands88+122validate-doctrines command; legacy fallback
3–5L6App pages, routes, componentsBuild success (184 pages)
6ContentDocumentation, CLAUDE.mdLinks resolve; grep scan clean
7CleanupDelete legacy packages/skills/, old filesNo orphaned imports
Why this order works:
  • If L1 changes break, everything else fails. Catch early.
  • L2.5 tests only L1 changes, so verify before using in L3.
  • L3+L5 tests both graph and CLI; if pass, app layer is safe to refactor.
  • App layer (L6) has no downstream dependencies, so safe to refactor last.
  • Content cleanup (L7) happens last, avoiding dangling references.

3. Hard-Cut Extension Migration

Policy: No dual-extension support (e.g., no .skill.md + .doctrine.md coexistence during transition). Rationale:
  • Eliminates confusion (one source of truth per file)
  • Forces complete migration (no lazy import fallbacks)
  • Parser updated once; no version checks needed
Execution:
# Rename all 39 files in one commit per directory
find .opencode/doctrines -name "*.skill.md" -exec mv {} {%.skill.md}.doctrine.md \;
find docs/atlas/doctrines -name "*.skill.md" -exec mv {} {%.skill.md}.doctrine.md \;
Then update parser to use .doctrine.md extension only.

4. Backward Compatibility via Legacy Field Fallback

Pattern: Use optional chaining + nullish coalescing to support gradual config migration:
// packages/cli/src/lib/council.ts
const doctrines = agentConfig.doctrines ?? agentConfig.skills;
Benefit: Old council YAML configs (with skills: field) continue working without edit. New configs use doctrines:. No forking logic needed. Constraint: Only use for config layer; functional code must use doctrines only.

Test-Verify-Commit Cycle

Verification before each commit:
# Per-package tests (must pass)
bun packages/schema && npm run test
bun packages/doctrines && npm run test
bun packages/graph && npm run test
bun packages/cli && npm run test

# Type check across all packages
cd packages/schema && bun run typecheck

# App build (ensures no import errors)
cd apps/meridian && bun run build

# Functional grep scan (zero "Skill" in code, not comments)
rg "SkillRef|SkillBinding|export.*Skill" packages/ apps/ --type ts
Only commit after all verifications pass.

Outcomes

Metrics:
  • 228 tests passing (0 broken, 0 flaky)
  • Build: 184 pages compiled successfully
  • Grep scan: 0 functional regressions (comments allowed)
  • Commits: 7 independent, ordered by dependency
  • Files renamed: 39 (21 in .opencode/, 18 in docs/atlas/)
  • Identifiers renamed: ~2,000+ across code + config + docs
Zero regressions because:
  1. Layer-by-layer testing caught breakage at source
  2. Hard-cut extension prevented import confusion
  3. Legacy fallback allowed gradual config migration

Reusable Pattern

For future cross-cutting renames in monorepos:
  1. Map dependency layers (what depends on what?)
  2. Identify hard cut boundaries (schema? package name? file extension?)
  3. Commit in reverse-dependency order (leaves → roots)
  4. Test at each layer before moving up
  5. Use optional chaining for backward compatibility in config layer only
  6. Full grep scan before final cleanup commit
Estimated effort: 4–6 hours for similar-sized refactor (200+ files, 1000+ identifiers).
  • Config schema: packages/schema/src/types/council.ts (DoctrineRefSchema)
  • Package: packages/doctrines/ (renamed from packages/skills/)
  • CLI command: packages/cli/src/commands/council.ts (validate-doctrines)
  • App routes: apps/meridian/src/pages/doctrines/ (all subpages)
  • Content: .opencode/doctrines/, docs/atlas/doctrines/