Implements North Star H4 — update-surviving persona customization: an override layer for personas that survives mosaic update, a baseline⊕override resolver, and a mosaic fleet persona CLI.
Override layer location & merge semantics
Baseline:<mosaicHome>/fleet/roles/*.md — reseeded on every mosaic update (delivers new baseline personas).
Override:<mosaicHome>/fleet/roles.local/*.md — PRESERVE-protected; wins over baseline of the same class and may add entirely new classes with no baseline.
resolvePersona(klass) — override file if roles.local/ defines the class (match by inline `class:` marker, filename fallback), else baseline; null if neither.
listPersonaClasses() — UNION of baseline + override classes.
personaStatus() — classifies each class as baseline / overridden / custom.
The inline-class:/LIBRARY/filename extraction is now a single shared helper extractClassesFromDir() in fleet-personas.ts. fleet-profiles.ts imports it; its old listPersonaClasses(rolesDir) is a thin delegate. Profile roster validation now resolves against the override-aware union, so a profile can reference a user-customized or user-added persona.
Baseline fleet/roles/ continues to reseed normally; fleet/roles.local/ is excluded from rsync --delete, so user overrides survive update.
CLI — mosaic fleet persona <list|show|customize>
persona list [--json] — every class with status + domain.
persona show <class> [--json] — the RESOLVED persona and which layer it came from.
persona customize <class> — copy baseline into roles.local/ (no-clobber if present); --new scaffolds a brand-new persona; auto-creates roles.local/.
Tests (vitest)
fleet-personas.spec.ts: override-wins, custom-add, status classification, AC-NS-7 update-survival simulation (re-seed baseline roles/ while leaving roles.local/ untouched → override still wins, custom class still resolves), and fleet-profiles validation accepting an override-only persona. All 594 mosaic tests pass; typecheck + lint clean.
## What
Implements North Star **H4 — update-surviving persona customization**: an override layer for personas that survives `mosaic update`, a baseline⊕override resolver, and a `mosaic fleet persona` CLI.
## Override layer location & merge semantics
- **Baseline:** `<mosaicHome>/fleet/roles/*.md` — reseeded on every `mosaic update` (delivers new baseline personas).
- **Override:** `<mosaicHome>/fleet/roles.local/*.md` — PRESERVE-protected; **wins** over baseline of the same class and may **add** entirely new classes with no baseline.
- Resolver (`packages/mosaic/src/commands/fleet-personas.ts`):
- `resolvePersona(klass)` — override file if `roles.local/` defines the class (match by inline `` `class:` `` marker, filename fallback), else baseline; `null` if neither.
- `listPersonaClasses()` — UNION of baseline + override classes.
- `personaStatus()` — classifies each class as `baseline` / `overridden` / `custom`.
## DRY with #660
The inline-`class:`/LIBRARY/filename extraction is now a single shared helper `extractClassesFromDir()` in `fleet-personas.ts`. `fleet-profiles.ts` imports it; its old `listPersonaClasses(rolesDir)` is a thin delegate. Profile roster validation now resolves against the override-aware union, so a profile can reference a user-customized or user-added persona.
## install.sh — AC-NS-7 guarantee
Added `fleet/roles.local` to `PRESERVE_PATHS`:
```
PRESERVE_PATHS=(... "fleet/backlog" "fleet/roles.local")
```
Baseline `fleet/roles/` continues to reseed normally; `fleet/roles.local/` is excluded from rsync `--delete`, so user overrides survive update.
## CLI — `mosaic fleet persona <list|show|customize>`
- `persona list [--json]` — every class with status + domain.
- `persona show <class> [--json]` — the RESOLVED persona and which layer it came from.
- `persona customize <class>` — copy baseline into `roles.local/` (no-clobber if present); `--new` scaffolds a brand-new persona; auto-creates `roles.local/`.
## Tests (vitest)
`fleet-personas.spec.ts`: override-wins, custom-add, status classification, **AC-NS-7 update-survival simulation** (re-seed baseline roles/ while leaving roles.local/ untouched → override still wins, custom class still resolves), and fleet-profiles validation accepting an override-only persona. All 594 mosaic tests pass; typecheck + lint clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Add a PRESERVE-protected persona override layer at <mosaicHome>/fleet/roles.local/
that survives `mosaic update` while baseline fleet/roles/ keeps reseeding.
- fleet-personas.ts: shared class-extraction (single source of truth, DRY with
fleet-profiles.ts), resolvePersona (override wins, baseline fallback),
listPersonaClasses (baseline ⊕ override union), personaStatus
(baseline/overridden/custom), and the `fleet persona list|show|customize` CLI.
- fleet-profiles.ts: roster validation now uses the override-aware union so a
profile can reference a user-customized or user-ADDED persona; the old
listPersonaClasses(rolesDir) is kept as a thin delegate to the shared helper.
- install.sh: add fleet/roles.local to PRESERVE_PATHS (AC-NS-7 guarantee).
- specs: override-wins, custom-add, status classification, AC-NS-7
update-survival simulation, and profile-validation-accepts-custom-persona.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What
Implements North Star H4 — update-surviving persona customization: an override layer for personas that survives
mosaic update, a baseline⊕override resolver, and amosaic fleet personaCLI.Override layer location & merge semantics
<mosaicHome>/fleet/roles/*.md— reseeded on everymosaic update(delivers new baseline personas).<mosaicHome>/fleet/roles.local/*.md— PRESERVE-protected; wins over baseline of the same class and may add entirely new classes with no baseline.packages/mosaic/src/commands/fleet-personas.ts):resolvePersona(klass)— override file ifroles.local/defines the class (match by inline`class:`marker, filename fallback), else baseline;nullif neither.listPersonaClasses()— UNION of baseline + override classes.personaStatus()— classifies each class asbaseline/overridden/custom.DRY with #660
The inline-
class:/LIBRARY/filename extraction is now a single shared helperextractClassesFromDir()infleet-personas.ts.fleet-profiles.tsimports it; its oldlistPersonaClasses(rolesDir)is a thin delegate. Profile roster validation now resolves against the override-aware union, so a profile can reference a user-customized or user-added persona.install.sh — AC-NS-7 guarantee
Added
fleet/roles.localtoPRESERVE_PATHS:Baseline
fleet/roles/continues to reseed normally;fleet/roles.local/is excluded from rsync--delete, so user overrides survive update.CLI —
mosaic fleet persona <list|show|customize>persona list [--json]— every class with status + domain.persona show <class> [--json]— the RESOLVED persona and which layer it came from.persona customize <class>— copy baseline intoroles.local/(no-clobber if present);--newscaffolds a brand-new persona; auto-createsroles.local/.Tests (vitest)
fleet-personas.spec.ts: override-wins, custom-add, status classification, AC-NS-7 update-survival simulation (re-seed baseline roles/ while leaving roles.local/ untouched → override still wins, custom class still resolves), and fleet-profiles validation accepting an override-only persona. All 594 mosaic tests pass; typecheck + lint clean.🤖 Generated with Claude Code