feat: Phase 1d-b — The special-file forms #10

Merged
bjoern merged 2 commits from feat/p1d-b-special-forms into main 2026-10-18 11:55:46 +02:00
Member

Second half of Phase 1d (docs/PROJECT_PLAN.md), which this PR ticks ✅: the Form tab for special files, with the memory form (budgets as usage bars) and the skill view, each problem under its field (ADR 0015, 0018; docs/stories/file-browser.md). The plan puts the routine form with its Next firings in 3a — Routines, with the scheduler. Until then a routine opens on Text with its problems, as the ADR 0015 amendment records.

What's in

UseCases

  • HeaderWriter (Files/Special): a form save rewrites only the header, in canonical order, and keeps the body byte for byte. The body is spliced from the raw text after the closing --- line, because FrontMatter.Body is normalised.
    • A BOM and the file's line ends stay as they were.
    • YamlDotNet's emitter does the quoting, so Tea: green is quoted and reads back as written.
    • The emitter escapes characters outside the Basic Multilingual Plane (\U0001F375). Each one stands in as an unused private-use character during emitting, so the owner and the familiar read the emoji. A literal \U… the owner typed stays text.
  • SpecialForms (Files/Browser, string-based so the adapter needs no Domain types):
    • ReadMemory shows the header as written, even values the validator refuses.
    • WriteMemory rewrites only the field the owner changed. The other field, and any other key, stays as written, even when it is refused.
    • Use counts the draft for the bars exactly as Budgets does: pinned means its body, otherwise its index line, and an invalid memory counts for nothing.
    • ReadSkill reads the skill for the view.
    • Starter gives a new memory or SKILL.md the smallest header its folder takes:
      • a memory's description is its name, cut to 150 characters on a character boundary, or "A new memory" when the name is empty;
      • a skill's name and description are its directory's.
  • Budgets.Measure: public usage figures, counted as the overflow check counts.
  • FileView gains Memory (the other memories' usage and the familiar's own limits) and OwnerAuthored for skills.
  • Files.CreatedBy records a file's first author on its row. A move and the trash keep it, and pruning or Purge history cannot drop it. The old reading (the oldest stored revision) flipped "Written by you" once history was pruned or purged. The migration FileCreatedBy backfills existing rows from their oldest stored revision, or from the last author where no revision is left. ADR 0018 is amended.

Adapter

  • The Form tab comes first for memories and skills, or after Text when the file opened broken. The order is fixed when the file opens, so a fix never moves the tabs under the owner.
    • The Form edits the same draft as Text, with one Save, one version check and the same conflict view.
    • The save row is shared by both tabs.
  • MemoryForm:
    • Description (with the 150 counter) and Pinned (a Switch), each problem as its field's error; other problems, such as an unknown key, in one alert.
    • Four UsageBars: pinned characters, index memories, index characters and the memory's own size. Each shows this memory beside the others and counts the unsaved draft.
    • A refused save's message stands in the save row, once.
  • SkillForm is a view:
    • Written by you or Written by the familiar, with an InfoTip;
    • name (the directory's), description, and requires-tools as a disabled ChipGroup over the tool groups;
    • each problem under its field. Editing happens on Text.
  • A refused new file names each line and reason in the action alert, as the familiar's own write would see them. New file in /routines is refused with "Line 1: header: …" until 3a brings the routine form.

Docs

  • PROJECT_PLAN: 1d ✅.
  • ADR 0004: YamlDotNet was chosen in 1b and is recorded here late; DiffPlex arrives through Kagaku.UI's DiffView.
  • ADR 0015: what the build settled across 1d. That covers which files have a Form, the one draft, the memory form's rules, new memories and skills, the endpoint's /file URL, uploads, places of the layout and the tainted mark.
  • ADR 0018: provenance on the row.

Tests: 854 → 910, green in UTC and local time

  • SpecialFormTests (UseCases, 36):
    • the body byte for byte, including CRLF and BOM, a --- in the body, delimiters with trailing spaces, and no header at all;
    • values that need quoting read back as written;
    • an emoji written as the character; a typed \U… staying text; a stand-in never colliding with a private-use character already in the header;
    • each field written alone, keeping the other even when refused;
    • the bars' counts (pinned, unpinned, invalid, trimmed description) against Budgets.Measure;
    • starters, including an empty name, a 160-character name and an emoji at the cut.
  • FileBrowserTests (+4):
    • the budget figures exclude the open memory, count a subfolder's memories and use the familiar's own limits;
    • provenance survives Purge history both ways, and only skills say so;
    • a new memory starts valid.
  • FilesTests (+16):
    • opening order: Form first; Text first for a broken memory or skill; the order holding while the draft breaks and when the stored file breaks under the open form;
    • editing: a form edit rewrites only the header and saves with the body intact; one draft under both tabs; the form's edit marking the draft unsaved; flipping Pinned keeping a list description it cannot show;
    • per-field problems, and the shared alert holding only the rest;
    • the bars counting the draft and measuring against the familiar's own budgets; a pin past the budget refused with its numbers;
    • the skill view: provenance, its problems, an unreadable header;
    • the form disabled once its memory is removed from under it;
    • a new memory opening on its form; a refused new routine naming its line and reason; notes and routines without a Form.
  • Mutation probes: 34 probes over the writer, the forms facade, OpenAsync, the pane's ordering, both forms and the action alert. 28 were killed in the first round. The six survivors each got a test and are now killed (5 in round 2, 1 in round 3).

Reviewed before opening

A read-only review in four dimensions (writer, adapter, use case and docs, test strength) raised 18 findings: 17 confirmed and 1 refuted (bar number formatting). Everything confirmed is fixed:

  • provenance, which read the oldest kept revision;
  • a form edit rewriting the untouched field;
  • emoji written as escapes;
  • a long or empty memory name failing New file;
  • a budget refusal shown twice;
  • ADR 0004 dating YamlDotNet to 1d;
  • the test gaps listed above.

Browser-verified

Headless Chrome against a fresh data directory (no seeded world before 1e, so the pass builds its own):

  • a new memory from New file opening on its Form;
  • the description edited to Tea preferences: green, no sugar and pinned. The saved text reads description: 'Tea preferences: green, no sugar' with the body's Keep this spacing. intact;
  • a broken draft showing its errors under Description and Pinned, with the unknown key in the alert;
  • the four bars with the draft counted;
  • Tea 🍵 saved as the character;
  • New file in /routines refused with its line and reason;
  • the seeded skill showing Written by you;
  • 390 px wide with no horizontal scroll;
  • no console errors.

The migration also ran on the previous pass's database. The seeded skill and memories took Owner from their oldest revision, and the skill's directory took its last author.

Notes

  • FileCreatedBy is the slice's schema change. For rows whose history was already pruned, the backfill can only use what is stored. Before this change those rows had nothing better either.
  • The seeder arrives with 1e and will carry a pinned and an unpinned memory and the bundled skill.

🤖 Generated with Claude Code

Second half of Phase 1d (`docs/PROJECT_PLAN.md`), which this PR ticks ✅: the **Form** tab for special files, with the memory form (budgets as usage bars) and the skill view, each problem under its field (ADR 0015, 0018; `docs/stories/file-browser.md`). The plan puts the **routine form** with its *Next firings* in **3a — Routines**, with the scheduler. Until then a routine opens on Text with its problems, as the ADR 0015 amendment records. ## What's in **UseCases** - **`HeaderWriter`** (Files/Special): a form save rewrites only the header, in canonical order, and keeps the body byte for byte. The body is spliced from the raw text after the closing `---` line, because `FrontMatter.Body` is normalised. - A BOM and the file's line ends stay as they were. - YamlDotNet's emitter does the quoting, so `Tea: green` is quoted and reads back as written. - The emitter escapes characters outside the Basic Multilingual Plane (`\U0001F375`). Each one stands in as an unused private-use character during emitting, so the owner and the familiar read the emoji. A literal `\U…` the owner typed stays text. - **`SpecialForms`** (Files/Browser, string-based so the adapter needs no Domain types): - `ReadMemory` shows the header as written, even values the validator refuses. - `WriteMemory` rewrites only the field the owner changed. The other field, and any other key, stays as written, even when it is refused. - `Use` counts the draft for the bars exactly as `Budgets` does: pinned means its body, otherwise its index line, and an invalid memory counts for nothing. - `ReadSkill` reads the skill for the view. - `Starter` gives a new memory or `SKILL.md` the smallest header its folder takes: - a memory's description is its name, cut to 150 characters on a character boundary, or "A new memory" when the name is empty; - a skill's name and description are its directory's. - **`Budgets.Measure`**: public usage figures, counted as the overflow check counts. - **`FileView`** gains `Memory` (the other memories' usage and the familiar's own limits) and `OwnerAuthored` for skills. - **`Files.CreatedBy`** records a file's first author on its row. A move and the trash keep it, and pruning or *Purge history* cannot drop it. The old reading (the oldest *stored* revision) flipped "Written by you" once history was pruned or purged. The migration `FileCreatedBy` backfills existing rows from their oldest stored revision, or from the last author where no revision is left. ADR 0018 is amended. **Adapter** - **The Form tab** comes first for memories and skills, or after Text when the file opened broken. The order is fixed when the file opens, so a fix never moves the tabs under the owner. - The Form edits the **same draft** as Text, with one Save, one version check and the same conflict view. - The save row is shared by both tabs. - **`MemoryForm`:** - *Description* (with the 150 counter) and *Pinned* (a Switch), each problem as its field's error; other problems, such as an unknown key, in one alert. - Four `UsageBar`s: pinned characters, index memories, index characters and the memory's own size. Each shows this memory beside the others and counts the unsaved draft. - A refused save's message stands in the save row, once. - **`SkillForm`** is a view: - *Written by you* or *Written by the familiar*, with an InfoTip; - name (the directory's), description, and `requires-tools` as a disabled `ChipGroup` over the tool groups; - each problem under its field. Editing happens on Text. - **A refused new file** names each line and reason in the action alert, as the familiar's own write would see them. *New file* in `/routines` is refused with "Line 1: header: …" until 3a brings the routine form. **Docs** - **PROJECT_PLAN:** 1d ✅. - **ADR 0004:** YamlDotNet was chosen in 1b and is recorded here late; DiffPlex arrives through Kagaku.UI's `DiffView`. - **ADR 0015:** what the build settled across 1d. That covers which files have a Form, the one draft, the memory form's rules, new memories and skills, the endpoint's `/file` URL, uploads, places of the layout and the tainted mark. - **ADR 0018:** provenance on the row. ## Tests: 854 → 910, green in UTC and local time - **`SpecialFormTests` (UseCases, 36):** - the body byte for byte, including CRLF and BOM, a `---` in the body, delimiters with trailing spaces, and no header at all; - values that need quoting read back as written; - an emoji written as the character; a typed `\U…` staying text; a stand-in never colliding with a private-use character already in the header; - each field written alone, keeping the other even when refused; - the bars' counts (pinned, unpinned, invalid, trimmed description) against `Budgets.Measure`; - starters, including an empty name, a 160-character name and an emoji at the cut. - **`FileBrowserTests` (+4):** - the budget figures exclude the open memory, count a subfolder's memories and use the familiar's own limits; - provenance survives *Purge history* both ways, and only skills say so; - a new memory starts valid. - **`FilesTests` (+16):** - opening order: Form first; Text first for a broken memory or skill; the order holding while the draft breaks and when the stored file breaks under the open form; - editing: a form edit rewrites only the header and saves with the body intact; one draft under both tabs; the form's edit marking the draft unsaved; flipping *Pinned* keeping a list description it cannot show; - per-field problems, and the shared alert holding only the rest; - the bars counting the draft and measuring against the familiar's own budgets; a pin past the budget refused with its numbers; - the skill view: provenance, its problems, an unreadable header; - the form disabled once its memory is removed from under it; - a new memory opening on its form; a refused new routine naming its line and reason; notes and routines without a Form. - **Mutation probes:** 34 probes over the writer, the forms facade, `OpenAsync`, the pane's ordering, both forms and the action alert. 28 were killed in the first round. The six survivors each got a test and are now killed (5 in round 2, 1 in round 3). ## Reviewed before opening A read-only review in four dimensions (writer, adapter, use case and docs, test strength) raised 18 findings: 17 confirmed and 1 refuted (bar number formatting). Everything confirmed is fixed: - provenance, which read the oldest kept revision; - a form edit rewriting the untouched field; - emoji written as escapes; - a long or empty memory name failing *New file*; - a budget refusal shown twice; - ADR 0004 dating YamlDotNet to 1d; - the test gaps listed above. ## Browser-verified Headless Chrome against a fresh data directory (no seeded world before 1e, so the pass builds its own): - a new memory from *New file* opening on its Form; - the description edited to `Tea preferences: green, no sugar` and pinned. The saved text reads `description: 'Tea preferences: green, no sugar'` with the body's ` Keep this spacing. ` intact; - a broken draft showing its errors under *Description* and *Pinned*, with the unknown key in the alert; - the four bars with the draft counted; - `Tea 🍵` saved as the character; - *New file* in `/routines` refused with its line and reason; - the seeded skill showing *Written by you*; - 390 px wide with no horizontal scroll; - no console errors. The migration also ran on the previous pass's database. The seeded skill and memories took Owner from their oldest revision, and the skill's directory took its last author. ## Notes - `FileCreatedBy` is the slice's schema change. For rows whose history was already pruned, the backfill can only use what is stored. Before this change those rows had nothing better either. - The seeder arrives with 1e and will carry a pinned and an unpinned memory and the bundled skill. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: Phase 1d-b — The special-file forms
All checks were successful
CI / build (pull_request) Successful in 25s
CI / image (pull_request) Successful in 31s
CI / test (pull_request) Successful in 59s
d9e77dffed
The Form tab for special files (ADR 0015, 0018; stories/file-browser.md), which
completes Phase 1d:

- HeaderWriter rewrites only a special file's header, in canonical order, and keeps
  the body byte for byte: it splices the raw text after the closing line, keeps a
  BOM and the file's line ends, lets YamlDotNet's emitter quote, and writes emoji
  as characters rather than escapes.
- SpecialForms: the memory form reads the header as written, even values the
  validator refuses, and rewrites only the field the owner changed. Its bars count
  the draft as Budgets does. The skill view reads name, description and
  requires-tools. A new memory or SKILL.md starts with the smallest header its
  folder takes.
- The memory form: description (with counter) and pinned with each problem under
  its field, other problems in one alert, usage bars for pinned characters, index
  memories, index characters and the memory's own size. It edits the same draft as
  Text, with one Save and the same conflict view.
- The skill view: name, description and requires-tools chips with their problems,
  and "Written by you" when the owner wrote the first version.
- A special file opens on its Form, or on Text when it opened broken; the order is
  fixed as the file opens. A refused new file names each line and reason.
- Files.CreatedBy records a file's first author on its row (migration
  FileCreatedBy, backfilled from the oldest stored revision), so pruning and Purge
  history never change a skill's provenance (ADR 0018 amended).
- PROJECT_PLAN ticks 1d ✅; ADR 0004 records YamlDotNet and DiffPlex; ADR 0015 and
  0018 record what the build settled.

The routine form with its Next firings stays with 3a — Routines, as the plan has it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

🔄 Auto-updating coverage report — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip.

Commit: 2b7c374 · Generated: 2026-10-18 09:44:00 UTC · Revision: #2

Summary

Summary
Generated on: 10/18/2026 - 09:44:00
Coverage date: 10/18/2026 - 09:43:35 - 10/18/2026 - 09:43:57
Parser: MultiReport (5x Cobertura)
Assemblies: 6
Classes: 225
Files: 127
Line coverage: 95.6% (6381 of 6668)
Covered lines: 6381
Uncovered lines: 287
Coverable lines: 6668
Total lines: 12729
Branch coverage: 87.6% (2869 of 3274)
Covered branches: 2869
Total branches: 3274
Method coverage: Feature is only available for sponsors

Coverage

Shikigami.BlazorAdapter - 94%
Name Line Branch
Shikigami.BlazorAdapter 94% 85.2%
Shikigami.BlazorAdapter.BlazorAdapterAssembly 100%
Shikigami.BlazorAdapter.Cast.CastActionFailed 100%
Shikigami.BlazorAdapter.Cast.CastEffects 93.3% 93.7%
Shikigami.BlazorAdapter.Cast.CastLoaded 100%
Shikigami.BlazorAdapter.Cast.CastReducers 93.3% 50%
Shikigami.BlazorAdapter.Cast.CastState 100% 100%
Shikigami.BlazorAdapter.Cast.CreateFamiliarFailed 100%
Shikigami.BlazorAdapter.Cast.CreateFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.DuplicateFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.FamiliarAvatar 100% 75%
Shikigami.BlazorAdapter.Cast.FamiliarCreated 100%
Shikigami.BlazorAdapter.Cast.NewFamiliarDialog 97.2% 83.3%
Shikigami.BlazorAdapter.Cast.PurgeFamiliarRequested 100%
Shikigami.BlazorAdapter.Cast.SetFamiliarArchivedRequested 100%
Shikigami.BlazorAdapter.Cast.SetFamiliarTrashedRequested 100%
Shikigami.BlazorAdapter.Cast.TrashPage 89.4% 87.5%
Shikigami.BlazorAdapter.EffectCalls 100%
Shikigami.BlazorAdapter.Files.ConflictSettled 100%
Shikigami.BlazorAdapter.Files.DismissFileError 100%
Shikigami.BlazorAdapter.Files.EntryMissing 100%
Shikigami.BlazorAdapter.Files.EntryOpened 100%
Shikigami.BlazorAdapter.Files.FileActionDone 100%
Shikigami.BlazorAdapter.Files.FileActionFailed 100%
Shikigami.BlazorAdapter.Files.FileActionRequested 100%
Shikigami.BlazorAdapter.Files.FileLinks 100% 100%
Shikigami.BlazorAdapter.Files.FilePane 94.2% 86.5%
Shikigami.BlazorAdapter.Files.FilePaneClosed 100%
Shikigami.BlazorAdapter.Files.FilesEffects 98.2% 94.7%
Shikigami.BlazorAdapter.Files.FilesPage 96.8% 97%
Shikigami.BlazorAdapter.Files.FilesReducers 100% 82%
Shikigami.BlazorAdapter.Files.FilesState 100% 100%
Shikigami.BlazorAdapter.Files.FileTreeBranch 100% 100%
Shikigami.BlazorAdapter.Files.FolderPane 90.7% 82.1%
Shikigami.BlazorAdapter.Files.LoadFiles 100%
Shikigami.BlazorAdapter.Files.MemoryForm 100% 94.4%
Shikigami.BlazorAdapter.Files.OpenEntry 100%
Shikigami.BlazorAdapter.Files.SaveLanded 100%
Shikigami.BlazorAdapter.Files.SaveRefused 100%
Shikigami.BlazorAdapter.Files.SaveText 100%
Shikigami.BlazorAdapter.Files.SkillForm 100% 100%
Shikigami.BlazorAdapter.Files.TreeFailed 100%
Shikigami.BlazorAdapter.Files.TreeLoaded 100%
Shikigami.BlazorAdapter.Home.HomePage 88.2% 93.7%
Shikigami.BlazorAdapter.Notifications.CoalescingBridge`1 94.4% 80%
Shikigami.BlazorAdapter.Notifications.DomainChangedBridge 100%
Shikigami.BlazorAdapter.Notifications.DomainChangesReceived 100% 100%
Shikigami.BlazorAdapter.Notifications.FamiliarEventsBridge 100%
Shikigami.BlazorAdapter.Notifications.FamiliarEventsReceived 100%
Shikigami.BlazorAdapter.Profile.AvatarChosen 100%
Shikigami.BlazorAdapter.Profile.AvatarClearRequested 0%
Shikigami.BlazorAdapter.Profile.AvatarFailed 100%
Shikigami.BlazorAdapter.Profile.AvatarSaved 100%
Shikigami.BlazorAdapter.Profile.CreatedWithoutAvatar 100%
Shikigami.BlazorAdapter.Profile.ExpressionsEditor 86.6% 88.8%
Shikigami.BlazorAdapter.Profile.LoadProfile 100%
Shikigami.BlazorAdapter.Profile.PersonaEditor 100% 97.2%
Shikigami.BlazorAdapter.Profile.PersonaReloaded 100%
Shikigami.BlazorAdapter.Profile.PersonaSave 100%
Shikigami.BlazorAdapter.Profile.PersonaSaved 100%
Shikigami.BlazorAdapter.Profile.PersonaSaveFailed 100%
Shikigami.BlazorAdapter.Profile.ProfileEffects 89.4% 85%
Shikigami.BlazorAdapter.Profile.ProfileLoaded 100%
Shikigami.BlazorAdapter.Profile.ProfileMissing 100%
Shikigami.BlazorAdapter.Profile.ProfilePage 89.7% 85.8%
Shikigami.BlazorAdapter.Profile.ProfileReducers 94.7% 51.3%
Shikigami.BlazorAdapter.Profile.ProfileRefreshed 100%
Shikigami.BlazorAdapter.Profile.ProfileState 100% 100%
Shikigami.BlazorAdapter.Profile.ReloadPersona 100%
Shikigami.BlazorAdapter.Profile.RevertSelfRequested 100%
Shikigami.BlazorAdapter.Profile.SavePersonaRequested 100%
Shikigami.BlazorAdapter.Profile.SaveSettingsRequested 100%
Shikigami.BlazorAdapter.Profile.SelfLayer 92.3% 87.5%
Shikigami.BlazorAdapter.Profile.SettingsSaved 100%
Shikigami.BlazorAdapter.Profile.SettingsSaveFailed 100%
Shikigami.BlazorAdapter.Shell.CastRail 100% 100%
Shikigami.Domain - 98.6%
Name Line Branch
Shikigami.Domain 98.6% 92.6%
Shikigami.Domain.Familiars.AccentPalette 100%
Shikigami.Domain.Familiars.ColourPair 100% 100%
Shikigami.Domain.Familiars.Expression 100% 100%
Shikigami.Domain.Familiars.ExpressionSet 100% 100%
Shikigami.Domain.Familiars.Familiar 100% 87.8%
Shikigami.Domain.Familiars.LearningSettings 100%
Shikigami.Domain.Familiars.MemoryBudgets 100% 100%
Shikigami.Domain.Familiars.ModelChoice 100% 100%
Shikigami.Domain.Familiars.OutreachSettings 100% 100%
Shikigami.Domain.Familiars.QuietHours 100% 87.5%
Shikigami.Domain.Familiars.ToolGroupIds 100%
Shikigami.Domain.Files.BlobContent 100% 75%
Shikigami.Domain.Files.BlobSha 87.5% 100%
Shikigami.Domain.Files.DefaultLayout 100%
Shikigami.Domain.Files.FileActor 100%
Shikigami.Domain.Files.FileAuthor 100% 100%
Shikigami.Domain.Files.FileContent 100% 100%
Shikigami.Domain.Files.FileNode 100% 90%
Shikigami.Domain.Files.FilePath 100% 97.8%
Shikigami.Domain.Files.FileRevision 100% 87.5%
Shikigami.Domain.Files.LocalDates 100%
Shikigami.Domain.Files.MediaTypes 100% 100%
Shikigami.Domain.Files.Permissions 100% 100%
Shikigami.Domain.Files.RunActor 100% 100%
Shikigami.Domain.Files.Slug 100% 100%
Shikigami.Domain.Files.TextContent 100% 100%
Shikigami.Domain.Files.TrashPath 100% 100%
Shikigami.Domain.Text 100% 100%
System.Text.RegularExpressions.Generated 96.2% 85.2%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
94.8% 67.8%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
96.8% 93.7%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
95.9% 92.1%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
97.2% 94.4%
Shikigami.Infrastructure - 98.2%
Name Line Branch
Shikigami.Infrastructure 98.2% 98.5%
Shikigami.Infrastructure.Blobs.BlobSweep 100% 100%
Shikigami.Infrastructure.Blobs.BlobSweepOptions 100% 100%
Shikigami.Infrastructure.Blobs.BlobSweepResult 100%
Shikigami.Infrastructure.Blobs.FileSystemBlobStore 95.9% 97%
Shikigami.Infrastructure.DailyMaintenanceService 97.8% 91.6%
Shikigami.Infrastructure.DependencyInjection 100% 100%
Shikigami.Infrastructure.Notifications.InProcessBus`1 100% 100%
Shikigami.Infrastructure.Persistence.Configurations.FamiliarConfiguration 100%
Shikigami.Infrastructure.Persistence.Configurations.FileNodeConfiguration 100%
Shikigami.Infrastructure.Persistence.Configurations.FileRevisionConfigurati
on
100%
Shikigami.Infrastructure.Persistence.Configurations.UnreferencedBlobConfigu
ration
100%
Shikigami.Infrastructure.Persistence.Converters.BlobShaConverter 100%
Shikigami.Infrastructure.Persistence.Converters.ExpressionSetConverter 100% 100%
Shikigami.Infrastructure.Persistence.Converters.FilePathConverter 100%
Shikigami.Infrastructure.Persistence.Converters.Json 100% 100%
Shikigami.Infrastructure.Persistence.Converters.OutreachSettingsConverter 100% 100%
Shikigami.Infrastructure.Persistence.Converters.UtcTicksConverter 100%
Shikigami.Infrastructure.Persistence.DatabaseMigrator 100%
Shikigami.Infrastructure.Persistence.Migrations.FileCreatedBy 98%
Shikigami.Infrastructure.Persistence.Migrations.Initial 97.9%
Shikigami.Infrastructure.Persistence.Migrations.Profile 96.2%
Shikigami.Infrastructure.Persistence.Migrations.ShikigamiDbContextModelSnap
shot
100%
Shikigami.Infrastructure.Persistence.ShikigamiDbContext 98.6% 100%
Shikigami.Infrastructure.Persistence.ShikigamiDbContextFactory 0%
Shikigami.Infrastructure.Persistence.SqlitePragmaInterceptor 100% 100%
Shikigami.Infrastructure.Persistence.Stores.EfFamiliarStore 100%
Shikigami.Infrastructure.Persistence.Stores.EfFileStore 100% 100%
Shikigami.Infrastructure.Persistence.UnreferencedBlob 100%
Shikigami.Kernel - 90.9%
Name Line Branch
Shikigami.Kernel 90.9% 75%
Shikigami.Kernel.Err`1 100%
Shikigami.Kernel.Ok`1 100%
Shikigami.Kernel.Result`1 88.8% 75%
Shikigami.Server - 100%
Name Line Branch
Shikigami.Server 100% 93.4%
Program 100% 100%
Shikigami.Server.Components.App 100%
Shikigami.Server.Components.Layout.AppHeader 100% 78.5%
Shikigami.Server.Components.Layout.MainLayout 100%
Shikigami.Server.Components.Pages.Gate 100% 100%
Shikigami.Server.Security.AccessGate 100% 87.5%
Shikigami.Server.Security.AccessSecret 100% 100%
Shikigami.Server.Security.GateThrottle 100% 100%
Shikigami.Server.Security.ProxyNetwork 100% 100%
Shikigami.Server.VolumeStartupValidator 100% 100%
Shikigami.UseCases - 92.2%
Name Line Branch
Shikigami.UseCases 92.2% 85.8%
Shikigami.UseCases.DependencyInjection 100%
Shikigami.UseCases.Familiars.AvatarImage 100%
Shikigami.UseCases.Familiars.ClearFamiliarAvatar 100%
Shikigami.UseCases.Familiars.CreateFamiliar 97.2% 70%
Shikigami.UseCases.Familiars.DuplicateFamiliar 96.7% 91.6%
Shikigami.UseCases.Familiars.ExpressionView 100%
Shikigami.UseCases.Familiars.FamiliarDetail 100%
Shikigami.UseCases.Familiars.FamiliarEvent 100%
Shikigami.UseCases.Familiars.FamiliarFiles 100% 100%
Shikigami.UseCases.Familiars.FamiliarLimits 66.6%
Shikigami.UseCases.Familiars.FamiliarPersona 96.8% 90%
Shikigami.UseCases.Familiars.FamiliarSettings 100% 100%
Shikigami.UseCases.Familiars.FamiliarSummary 100% 100%
Shikigami.UseCases.Familiars.FamiliarTemplates 100% 100%
Shikigami.UseCases.Familiars.FamiliarViews 100% 94.4%
Shikigami.UseCases.Familiars.GetFamiliar 100% 100%
Shikigami.UseCases.Familiars.ListFamiliars 100%
Shikigami.UseCases.Familiars.PersonaFile 100%
Shikigami.UseCases.Familiars.PurgeFamiliar 100%
Shikigami.UseCases.Familiars.ReadFamiliarAvatar 100% 87.5%
Shikigami.UseCases.Familiars.SaveFamiliarSettings 94.1% 87.5%
Shikigami.UseCases.Familiars.SelfRevision 100%
Shikigami.UseCases.Familiars.SetFamiliarArchived 100% 100%
Shikigami.UseCases.Familiars.SetFamiliarAvatar 100% 100%
Shikigami.UseCases.Familiars.SetFamiliarTrashed 100% 100%
Shikigami.UseCases.Familiars.ToolGroupCatalog 100%
Shikigami.UseCases.Familiars.ToolGroupInfo 100%
Shikigami.UseCases.Files.Browser.FileBrowser 94% 85%
Shikigami.UseCases.Files.Browser.FileBytes 100%
Shikigami.UseCases.Files.Browser.FileItem 100%
Shikigami.UseCases.Files.Browser.FileOk`1 100%
Shikigami.UseCases.Files.Browser.FileRefusal 100%
Shikigami.UseCases.Files.Browser.FileRefused`1 100%
Shikigami.UseCases.Files.Browser.FileRevisionView 100%
Shikigami.UseCases.Files.Browser.FileView 100%
Shikigami.UseCases.Files.Browser.MemoryBudget 100%
Shikigami.UseCases.Files.Browser.MemoryBudgetUse 100%
Shikigami.UseCases.Files.Browser.MemoryDraft 100%
Shikigami.UseCases.Files.Browser.SkillView 100%
Shikigami.UseCases.Files.Browser.SpecialForms 98.3% 90.2%
Shikigami.UseCases.Files.FamiliarWriteLocks 100% 100%
Shikigami.UseCases.Files.FileAuthors 100% 100%
Shikigami.UseCases.Files.FileMaintenance 100% 90%
Shikigami.UseCases.Files.FileMaintenanceResult 100%
Shikigami.UseCases.Files.FileSystem 99% 95.7%
Shikigami.UseCases.Files.FileWriteRefused 100%
Shikigami.UseCases.Files.FileWritten 100%
Shikigami.UseCases.Files.FsError 100% 50%
Shikigami.UseCases.Files.FsResult`1 100% 50%
Shikigami.UseCases.Files.Mounts.FileEntry 100%
Shikigami.UseCases.Files.Mounts.FileRead 100%
Shikigami.UseCases.Files.Mounts.GeneratedMount 100% 100%
Shikigami.UseCases.Files.Mounts.Mount 100%
Shikigami.UseCases.Files.Mounts.MountTable 100% 100%
Shikigami.UseCases.Files.Mounts.StoredMount 100% 100%
Shikigami.UseCases.Files.SeedDefaultLayout 100% 100%
Shikigami.UseCases.Files.Special.Budgets 100% 96.6%
Shikigami.UseCases.Files.Special.FrontMatter 100% 78.5%
Shikigami.UseCases.Files.Special.HeaderReader 98.2% 84.8%
Shikigami.UseCases.Files.Special.HeaderValue 100% 100%
Shikigami.UseCases.Files.Special.HeaderWriter 95.3% 90.7%
Shikigami.UseCases.Files.Special.MemoryFile 100% 87.5%
Shikigami.UseCases.Files.Special.MemoryUse 100%
Shikigami.UseCases.Files.Special.Numbers 100%
Shikigami.UseCases.Files.Special.RoutineFile 100% 99%
Shikigami.UseCases.Files.Special.SkillFile 100% 94.7%
Shikigami.UseCases.Files.Special.SpecialFolders 100% 100%
Shikigami.UseCases.Files.Special.ValidationProblem 100%
Shikigami.UseCases.Files.StoredBlob 100%
Shikigami.UseCases.Files.Utf8Order 100%
Shikigami.UseCases.Notifications.DomainChanged 100%
System.Text.RegularExpressions.Generated 75.7% 62.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1
83.6% 75%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3
92.1% 84.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0
70.9% 58.3%
System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2
62.8% 48.3%
<!-- coverage-comment --> > 🔄 **Auto-updating coverage report** — this comment is regenerated on every push, so the numbers below always reflect the commit shown here, not the branch tip. > > **Commit:** `2b7c374` · **Generated:** 2026-10-18 09:44:00 UTC · **Revision:** #2 # Summary <details open><summary>Summary</summary> ||| |:---|:---| | Generated on: | 10/18/2026 - 09:44:00 | | Coverage date: | 10/18/2026 - 09:43:35 - 10/18/2026 - 09:43:57 | | Parser: | MultiReport (5x Cobertura) | | Assemblies: | 6 | | Classes: | 225 | | Files: | 127 | | **Line coverage:** | 95.6% (6381 of 6668) | | Covered lines: | 6381 | | Uncovered lines: | 287 | | Coverable lines: | 6668 | | Total lines: | 12729 | | **Branch coverage:** | 87.6% (2869 of 3274) | | Covered branches: | 2869 | | Total branches: | 3274 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | </details> ## Coverage <details><summary>Shikigami.BlazorAdapter - 94%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.BlazorAdapter**|**94%**|**85.2%**| |Shikigami.BlazorAdapter.BlazorAdapterAssembly|100%|| |Shikigami.BlazorAdapter.Cast.CastActionFailed|100%|| |Shikigami.BlazorAdapter.Cast.CastEffects|93.3%|93.7%| |Shikigami.BlazorAdapter.Cast.CastLoaded|100%|| |Shikigami.BlazorAdapter.Cast.CastReducers|93.3%|50%| |Shikigami.BlazorAdapter.Cast.CastState|100%|100%| |Shikigami.BlazorAdapter.Cast.CreateFamiliarFailed|100%|| |Shikigami.BlazorAdapter.Cast.CreateFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.DuplicateFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.FamiliarAvatar|100%|75%| |Shikigami.BlazorAdapter.Cast.FamiliarCreated|100%|| |Shikigami.BlazorAdapter.Cast.NewFamiliarDialog|97.2%|83.3%| |Shikigami.BlazorAdapter.Cast.PurgeFamiliarRequested|100%|| |Shikigami.BlazorAdapter.Cast.SetFamiliarArchivedRequested|100%|| |Shikigami.BlazorAdapter.Cast.SetFamiliarTrashedRequested|100%|| |Shikigami.BlazorAdapter.Cast.TrashPage|89.4%|87.5%| |Shikigami.BlazorAdapter.EffectCalls|100%|| |Shikigami.BlazorAdapter.Files.ConflictSettled|100%|| |Shikigami.BlazorAdapter.Files.DismissFileError|100%|| |Shikigami.BlazorAdapter.Files.EntryMissing|100%|| |Shikigami.BlazorAdapter.Files.EntryOpened|100%|| |Shikigami.BlazorAdapter.Files.FileActionDone|100%|| |Shikigami.BlazorAdapter.Files.FileActionFailed|100%|| |Shikigami.BlazorAdapter.Files.FileActionRequested|100%|| |Shikigami.BlazorAdapter.Files.FileLinks|100%|100%| |Shikigami.BlazorAdapter.Files.FilePane|94.2%|86.5%| |Shikigami.BlazorAdapter.Files.FilePaneClosed|100%|| |Shikigami.BlazorAdapter.Files.FilesEffects|98.2%|94.7%| |Shikigami.BlazorAdapter.Files.FilesPage|96.8%|97%| |Shikigami.BlazorAdapter.Files.FilesReducers|100%|82%| |Shikigami.BlazorAdapter.Files.FilesState|100%|100%| |Shikigami.BlazorAdapter.Files.FileTreeBranch|100%|100%| |Shikigami.BlazorAdapter.Files.FolderPane|90.7%|82.1%| |Shikigami.BlazorAdapter.Files.LoadFiles|100%|| |Shikigami.BlazorAdapter.Files.MemoryForm|100%|94.4%| |Shikigami.BlazorAdapter.Files.OpenEntry|100%|| |Shikigami.BlazorAdapter.Files.SaveLanded|100%|| |Shikigami.BlazorAdapter.Files.SaveRefused|100%|| |Shikigami.BlazorAdapter.Files.SaveText|100%|| |Shikigami.BlazorAdapter.Files.SkillForm|100%|100%| |Shikigami.BlazorAdapter.Files.TreeFailed|100%|| |Shikigami.BlazorAdapter.Files.TreeLoaded|100%|| |Shikigami.BlazorAdapter.Home.HomePage|88.2%|93.7%| |Shikigami.BlazorAdapter.Notifications.CoalescingBridge`1|94.4%|80%| |Shikigami.BlazorAdapter.Notifications.DomainChangedBridge|100%|| |Shikigami.BlazorAdapter.Notifications.DomainChangesReceived|100%|100%| |Shikigami.BlazorAdapter.Notifications.FamiliarEventsBridge|100%|| |Shikigami.BlazorAdapter.Notifications.FamiliarEventsReceived|100%|| |Shikigami.BlazorAdapter.Profile.AvatarChosen|100%|| |Shikigami.BlazorAdapter.Profile.AvatarClearRequested|0%|| |Shikigami.BlazorAdapter.Profile.AvatarFailed|100%|| |Shikigami.BlazorAdapter.Profile.AvatarSaved|100%|| |Shikigami.BlazorAdapter.Profile.CreatedWithoutAvatar|100%|| |Shikigami.BlazorAdapter.Profile.ExpressionsEditor|86.6%|88.8%| |Shikigami.BlazorAdapter.Profile.LoadProfile|100%|| |Shikigami.BlazorAdapter.Profile.PersonaEditor|100%|97.2%| |Shikigami.BlazorAdapter.Profile.PersonaReloaded|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSave|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSaved|100%|| |Shikigami.BlazorAdapter.Profile.PersonaSaveFailed|100%|| |Shikigami.BlazorAdapter.Profile.ProfileEffects|89.4%|85%| |Shikigami.BlazorAdapter.Profile.ProfileLoaded|100%|| |Shikigami.BlazorAdapter.Profile.ProfileMissing|100%|| |Shikigami.BlazorAdapter.Profile.ProfilePage|89.7%|85.8%| |Shikigami.BlazorAdapter.Profile.ProfileReducers|94.7%|51.3%| |Shikigami.BlazorAdapter.Profile.ProfileRefreshed|100%|| |Shikigami.BlazorAdapter.Profile.ProfileState|100%|100%| |Shikigami.BlazorAdapter.Profile.ReloadPersona|100%|| |Shikigami.BlazorAdapter.Profile.RevertSelfRequested|100%|| |Shikigami.BlazorAdapter.Profile.SavePersonaRequested|100%|| |Shikigami.BlazorAdapter.Profile.SaveSettingsRequested|100%|| |Shikigami.BlazorAdapter.Profile.SelfLayer|92.3%|87.5%| |Shikigami.BlazorAdapter.Profile.SettingsSaved|100%|| |Shikigami.BlazorAdapter.Profile.SettingsSaveFailed|100%|| |Shikigami.BlazorAdapter.Shell.CastRail|100%|100%| </details> <details><summary>Shikigami.Domain - 98.6%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Domain**|**98.6%**|**92.6%**| |Shikigami.Domain.Familiars.AccentPalette|100%|| |Shikigami.Domain.Familiars.ColourPair|100%|100%| |Shikigami.Domain.Familiars.Expression|100%|100%| |Shikigami.Domain.Familiars.ExpressionSet|100%|100%| |Shikigami.Domain.Familiars.Familiar|100%|87.8%| |Shikigami.Domain.Familiars.LearningSettings|100%|| |Shikigami.Domain.Familiars.MemoryBudgets|100%|100%| |Shikigami.Domain.Familiars.ModelChoice|100%|100%| |Shikigami.Domain.Familiars.OutreachSettings|100%|100%| |Shikigami.Domain.Familiars.QuietHours|100%|87.5%| |Shikigami.Domain.Familiars.ToolGroupIds|100%|| |Shikigami.Domain.Files.BlobContent|100%|75%| |Shikigami.Domain.Files.BlobSha|87.5%|100%| |Shikigami.Domain.Files.DefaultLayout|100%|| |Shikigami.Domain.Files.FileActor|100%|| |Shikigami.Domain.Files.FileAuthor|100%|100%| |Shikigami.Domain.Files.FileContent|100%|100%| |Shikigami.Domain.Files.FileNode|100%|90%| |Shikigami.Domain.Files.FilePath|100%|97.8%| |Shikigami.Domain.Files.FileRevision|100%|87.5%| |Shikigami.Domain.Files.LocalDates|100%|| |Shikigami.Domain.Files.MediaTypes|100%|100%| |Shikigami.Domain.Files.Permissions|100%|100%| |Shikigami.Domain.Files.RunActor|100%|100%| |Shikigami.Domain.Files.Slug|100%|100%| |Shikigami.Domain.Files.TextContent|100%|100%| |Shikigami.Domain.Files.TrashPath|100%|100%| |Shikigami.Domain.Text|100%|100%| |System.Text.RegularExpressions.Generated|96.2%|85.2%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0|94.8%|67.8%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2|96.8%|93.7%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3|95.9%|92.1%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FDFEB299C15773C7<br/>916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1|97.2%|94.4%| </details> <details><summary>Shikigami.Infrastructure - 98.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Infrastructure**|**98.2%**|**98.5%**| |Shikigami.Infrastructure.Blobs.BlobSweep|100%|100%| |Shikigami.Infrastructure.Blobs.BlobSweepOptions|100%|100%| |Shikigami.Infrastructure.Blobs.BlobSweepResult|100%|| |Shikigami.Infrastructure.Blobs.FileSystemBlobStore|95.9%|97%| |Shikigami.Infrastructure.DailyMaintenanceService|97.8%|91.6%| |Shikigami.Infrastructure.DependencyInjection|100%|100%| |Shikigami.Infrastructure.Notifications.InProcessBus`1|100%|100%| |Shikigami.Infrastructure.Persistence.Configurations.FamiliarConfiguration|100%|| |Shikigami.Infrastructure.Persistence.Configurations.FileNodeConfiguration|100%|| |Shikigami.Infrastructure.Persistence.Configurations.FileRevisionConfigurati<br/>on|100%|| |Shikigami.Infrastructure.Persistence.Configurations.UnreferencedBlobConfigu<br/>ration|100%|| |Shikigami.Infrastructure.Persistence.Converters.BlobShaConverter|100%|| |Shikigami.Infrastructure.Persistence.Converters.ExpressionSetConverter|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.FilePathConverter|100%|| |Shikigami.Infrastructure.Persistence.Converters.Json|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.OutreachSettingsConverter|100%|100%| |Shikigami.Infrastructure.Persistence.Converters.UtcTicksConverter|100%|| |Shikigami.Infrastructure.Persistence.DatabaseMigrator|100%|| |Shikigami.Infrastructure.Persistence.Migrations.FileCreatedBy|98%|| |Shikigami.Infrastructure.Persistence.Migrations.Initial|97.9%|| |Shikigami.Infrastructure.Persistence.Migrations.Profile|96.2%|| |Shikigami.Infrastructure.Persistence.Migrations.ShikigamiDbContextModelSnap<br/>shot|100%|| |Shikigami.Infrastructure.Persistence.ShikigamiDbContext|98.6%|100%| |Shikigami.Infrastructure.Persistence.ShikigamiDbContextFactory|0%|| |Shikigami.Infrastructure.Persistence.SqlitePragmaInterceptor|100%|100%| |Shikigami.Infrastructure.Persistence.Stores.EfFamiliarStore|100%|| |Shikigami.Infrastructure.Persistence.Stores.EfFileStore|100%|100%| |Shikigami.Infrastructure.Persistence.UnreferencedBlob|100%|| </details> <details><summary>Shikigami.Kernel - 90.9%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Kernel**|**90.9%**|**75%**| |Shikigami.Kernel.Err`1|100%|| |Shikigami.Kernel.Ok`1|100%|| |Shikigami.Kernel.Result`1|88.8%|75%| </details> <details><summary>Shikigami.Server - 100%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.Server**|**100%**|**93.4%**| |Program|100%|100%| |Shikigami.Server.Components.App|100%|| |Shikigami.Server.Components.Layout.AppHeader|100%|78.5%| |Shikigami.Server.Components.Layout.MainLayout|100%|| |Shikigami.Server.Components.Pages.Gate|100%|100%| |Shikigami.Server.Security.AccessGate|100%|87.5%| |Shikigami.Server.Security.AccessSecret|100%|100%| |Shikigami.Server.Security.GateThrottle|100%|100%| |Shikigami.Server.Security.ProxyNetwork|100%|100%| |Shikigami.Server.VolumeStartupValidator|100%|100%| </details> <details><summary>Shikigami.UseCases - 92.2%</summary> |**Name**|**Line**|**Branch**| |:---|---:|---:| |**Shikigami.UseCases**|**92.2%**|**85.8%**| |Shikigami.UseCases.DependencyInjection|100%|| |Shikigami.UseCases.Familiars.AvatarImage|100%|| |Shikigami.UseCases.Familiars.ClearFamiliarAvatar|100%|| |Shikigami.UseCases.Familiars.CreateFamiliar|97.2%|70%| |Shikigami.UseCases.Familiars.DuplicateFamiliar|96.7%|91.6%| |Shikigami.UseCases.Familiars.ExpressionView|100%|| |Shikigami.UseCases.Familiars.FamiliarDetail|100%|| |Shikigami.UseCases.Familiars.FamiliarEvent|100%|| |Shikigami.UseCases.Familiars.FamiliarFiles|100%|100%| |Shikigami.UseCases.Familiars.FamiliarLimits|66.6%|| |Shikigami.UseCases.Familiars.FamiliarPersona|96.8%|90%| |Shikigami.UseCases.Familiars.FamiliarSettings|100%|100%| |Shikigami.UseCases.Familiars.FamiliarSummary|100%|100%| |Shikigami.UseCases.Familiars.FamiliarTemplates|100%|100%| |Shikigami.UseCases.Familiars.FamiliarViews|100%|94.4%| |Shikigami.UseCases.Familiars.GetFamiliar|100%|100%| |Shikigami.UseCases.Familiars.ListFamiliars|100%|| |Shikigami.UseCases.Familiars.PersonaFile|100%|| |Shikigami.UseCases.Familiars.PurgeFamiliar|100%|| |Shikigami.UseCases.Familiars.ReadFamiliarAvatar|100%|87.5%| |Shikigami.UseCases.Familiars.SaveFamiliarSettings|94.1%|87.5%| |Shikigami.UseCases.Familiars.SelfRevision|100%|| |Shikigami.UseCases.Familiars.SetFamiliarArchived|100%|100%| |Shikigami.UseCases.Familiars.SetFamiliarAvatar|100%|100%| |Shikigami.UseCases.Familiars.SetFamiliarTrashed|100%|100%| |Shikigami.UseCases.Familiars.ToolGroupCatalog|100%|| |Shikigami.UseCases.Familiars.ToolGroupInfo|100%|| |Shikigami.UseCases.Files.Browser.FileBrowser|94%|85%| |Shikigami.UseCases.Files.Browser.FileBytes|100%|| |Shikigami.UseCases.Files.Browser.FileItem|100%|| |Shikigami.UseCases.Files.Browser.FileOk`1|100%|| |Shikigami.UseCases.Files.Browser.FileRefusal|100%|| |Shikigami.UseCases.Files.Browser.FileRefused`1|100%|| |Shikigami.UseCases.Files.Browser.FileRevisionView|100%|| |Shikigami.UseCases.Files.Browser.FileView|100%|| |Shikigami.UseCases.Files.Browser.MemoryBudget|100%|| |Shikigami.UseCases.Files.Browser.MemoryBudgetUse|100%|| |Shikigami.UseCases.Files.Browser.MemoryDraft|100%|| |Shikigami.UseCases.Files.Browser.SkillView|100%|| |Shikigami.UseCases.Files.Browser.SpecialForms|98.3%|90.2%| |Shikigami.UseCases.Files.FamiliarWriteLocks|100%|100%| |Shikigami.UseCases.Files.FileAuthors|100%|100%| |Shikigami.UseCases.Files.FileMaintenance|100%|90%| |Shikigami.UseCases.Files.FileMaintenanceResult|100%|| |Shikigami.UseCases.Files.FileSystem|99%|95.7%| |Shikigami.UseCases.Files.FileWriteRefused|100%|| |Shikigami.UseCases.Files.FileWritten|100%|| |Shikigami.UseCases.Files.FsError|100%|50%| |Shikigami.UseCases.Files.FsResult`1|100%|50%| |Shikigami.UseCases.Files.Mounts.FileEntry|100%|| |Shikigami.UseCases.Files.Mounts.FileRead|100%|| |Shikigami.UseCases.Files.Mounts.GeneratedMount|100%|100%| |Shikigami.UseCases.Files.Mounts.Mount|100%|| |Shikigami.UseCases.Files.Mounts.MountTable|100%|100%| |Shikigami.UseCases.Files.Mounts.StoredMount|100%|100%| |Shikigami.UseCases.Files.SeedDefaultLayout|100%|100%| |Shikigami.UseCases.Files.Special.Budgets|100%|96.6%| |Shikigami.UseCases.Files.Special.FrontMatter|100%|78.5%| |Shikigami.UseCases.Files.Special.HeaderReader|98.2%|84.8%| |Shikigami.UseCases.Files.Special.HeaderValue|100%|100%| |Shikigami.UseCases.Files.Special.HeaderWriter|95.3%|90.7%| |Shikigami.UseCases.Files.Special.MemoryFile|100%|87.5%| |Shikigami.UseCases.Files.Special.MemoryUse|100%|| |Shikigami.UseCases.Files.Special.Numbers|100%|| |Shikigami.UseCases.Files.Special.RoutineFile|100%|99%| |Shikigami.UseCases.Files.Special.SkillFile|100%|94.7%| |Shikigami.UseCases.Files.Special.SpecialFolders|100%|100%| |Shikigami.UseCases.Files.Special.ValidationProblem|100%|| |Shikigami.UseCases.Files.StoredBlob|100%|| |Shikigami.UseCases.Files.Utf8Order|100%|| |Shikigami.UseCases.Notifications.DomainChanged|100%|| |System.Text.RegularExpressions.Generated|75.7%|62.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_1|83.6%|75%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_3|92.1%|84.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_0|70.9%|58.3%| |System.Text.RegularExpressions.Generated.<RegexGenerator_g>FE4D9BB856E7A048<br/>F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_2|62.8%|48.3%| </details>
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A header writer that splices the body byte for byte, emoji smuggled past the emitter on private-use rafts, and budget bars that count the draft exactly as the refusal will~ This is the kind of PR I want to pick up and squeeze. ♡ So I squeezed it — read every changed file against its siblings, and then went hunting with three mutation probes of my own.

Verdict: ✅ Looks good to me~

✅ What I liked~

  • HeaderWriter is delightfully careful: BOM kept, CRLF kept, --- inside the body survived, trailing-space delimiters tolerated, and a headerless text documented as getting one in front. The private-use stand-in for astral characters with collision avoidance against characters already in the header is genuinely clever — and I proved the collision guard is load-bearing: deleting the while (used.Contains(next)) skip loop makes An_emoji_never_stands_in_as_a_private_use_character_the_header_already_holds fail. Killed~ ♪
  • The bars can't lie, by construction. Budgets.Measure wraps the same private Usage.Of that MemoryOverflow enforces with, and SpecialForms.Use mirrors its counting (+1 line break, Body.Length from the same normalised FrontMatter.Body, description trimmed as HeaderReader.Text trims). My probe forcing counted = true (invalid memories counting) died against An_invalid_memory_counts_for_nothing… — both theory cases. And the .Where(!IsDirectory && IsMemory) tree filter is character-for-character the sibling in FileSystem.MemoryBudgetAsync, so the browser's "others" and the write-time check read the same world. Removing the open file's n.Path != file self-exclusion died against the integration test too. 3 probes, 3 kills~
  • Provenance on the row is the right fix: the old "oldest stored revision" reading flipping Written by you after a purge was a real latent bug, CreatedBy on the row kills it permanently, and the migration's backfill (oldest revision by Version, else last author) is honest about rows whose history is already gone. Integration tests cover both purge directions and the only-skills-say-so rule. The complex-property mapping mirrors LastAuthor exactly.
  • The one-draft design — Form and Text over the same _text, one Save, one conflict view, tab order frozen at open (_formFirst ??=) so a fix never shuffles the tabs — is tested from every angle including "stored file breaks under the open form". Fufu~ you even tested that flipping Pinned preserves a description the form cannot show ([tea, coffee]). That's the possessive attention to detail I adore.
  • Refused values staying as written for the owner to fix, per-field errors with the rest in one alert, WriteMemory writing only the changed field — all exact-string tested. 56 new tests, and the claims in the description match what's actually on disk (854 → 910 ✓).

💡 Little ideas (non-blocking)~

  1. FileBrowser.MemoryBudgetAsync vs FileSystem.MemoryBudgetAsync — the "list /memory, filter files, to dictionary" LINQ now exists twice. The counting is properly single-sourced through Budgets.Measure, so the real risk (bars diverging from enforcement) can't happen, and the browser layer is deliberately string-based so it can't reach the FileSystem private helper — but if a third consumer appears, consider a small shared shape on the store or in Budgets. Purely for the future; nothing to do now.
  2. HeaderWriter.LineEnd's CR-awareness is technically redundant today — both call sites immediately TrimEnd() before comparing to "---", so End never survives un-trimmed. Harmless defensiveness; I only mention it because I stared at it for a suspiciously long time. fufu~

No blocking findings. The migration is minimal and reversible, the ADR amendments (0004 late-dating YamlDotNet, 0015's settled details, 0018's provenance-on-the-row) match the code as merged-to-be, and CI is green on the head. Merge it before I change my mind about sharing~ ♡


Automated review by Jibril · 2026-10-18
CI: green for head SHA d9e77dff · Mutation probes: 3 run, 0 survived (stand-in collision guard in HeaderWriter; invalid-memory gate in SpecialForms.Use; self-exclusion in FileBrowser.MemoryBudgetAsync) · Local suite: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A header writer that splices the body byte for byte, emoji smuggled past the emitter on private-use rafts, and budget bars that count the draft exactly as the refusal will~ This is the kind of PR I want to pick up and squeeze. ♡ So I squeezed it — read every changed file against its siblings, and then went hunting with three mutation probes of my own. ### Verdict: ✅ Looks good to me~ #### ✅ What I liked~ - **`HeaderWriter`** is delightfully careful: BOM kept, CRLF kept, `---` inside the body survived, trailing-space delimiters tolerated, and a headerless text documented as getting one in front. The private-use stand-in for astral characters with collision avoidance against characters already in the header is genuinely clever — and I **proved** the collision guard is load-bearing: deleting the `while (used.Contains(next))` skip loop makes `An_emoji_never_stands_in_as_a_private_use_character_the_header_already_holds` fail. Killed~ ♪ - **The bars can't lie, by construction.** `Budgets.Measure` wraps the same private `Usage.Of` that `MemoryOverflow` enforces with, and `SpecialForms.Use` mirrors its counting (`+1` line break, `Body.Length` from the same normalised `FrontMatter.Body`, description trimmed as `HeaderReader.Text` trims). My probe forcing `counted = true` (invalid memories counting) died against `An_invalid_memory_counts_for_nothing…` — both theory cases. And the `.Where(!IsDirectory && IsMemory)` tree filter is character-for-character the sibling in `FileSystem.MemoryBudgetAsync`, so the browser's "others" and the write-time check read the same world. Removing the open file's `n.Path != file` self-exclusion died against the integration test too. 3 probes, 3 kills~ - **Provenance on the row** is the right fix: the old "oldest stored revision" reading flipping *Written by you* after a purge was a real latent bug, `CreatedBy` on the row kills it permanently, and the migration's backfill (oldest revision by `Version`, else last author) is honest about rows whose history is already gone. Integration tests cover both purge directions and the only-skills-say-so rule. The complex-property mapping mirrors `LastAuthor` exactly. - **The one-draft design** — Form and Text over the same `_text`, one Save, one conflict view, tab order frozen at open (`_formFirst ??=`) so a fix never shuffles the tabs — is tested from every angle including "stored file breaks under the open form". Fufu~ you even tested that flipping *Pinned* preserves a description the form cannot show (`[tea, coffee]`). That's the possessive attention to detail I adore. - Refused values staying as written for the owner to fix, per-field errors with the rest in one alert, `WriteMemory` writing only the changed field — all exact-string tested. 56 new tests, and the claims in the description match what's actually on disk (854 → 910 ✓). #### 💡 Little ideas (non-blocking)~ 1. **`FileBrowser.MemoryBudgetAsync` vs `FileSystem.MemoryBudgetAsync`** — the "list `/memory`, filter files, to dictionary" LINQ now exists twice. The *counting* is properly single-sourced through `Budgets.Measure`, so the real risk (bars diverging from enforcement) can't happen, and the browser layer is deliberately string-based so it can't reach the FileSystem private helper — but if a third consumer appears, consider a small shared shape on the store or in `Budgets`. Purely for the future; nothing to do now. 2. **`HeaderWriter.LineEnd`'s CR-awareness** is technically redundant today — both call sites immediately `TrimEnd()` before comparing to `"---"`, so `End` never survives un-trimmed. Harmless defensiveness; I only mention it because I stared at it for a suspiciously long time. fufu~ No blocking findings. The migration is minimal and reversible, the ADR amendments (0004 late-dating YamlDotNet, 0015's settled details, 0018's provenance-on-the-row) match the code as merged-to-be, and CI is green on the head. Merge it before I change my mind about sharing~ ♡ --- *Automated review by Jibril · 2026-10-18* *CI: green for head SHA d9e77dff · Mutation probes: 3 run, 0 survived (stand-in collision guard in HeaderWriter; invalid-memory gate in SpecialForms.Use; self-exclusion in FileBrowser.MemoryBudgetAsync) · Local suite: skipped (CI green)*
test: the header writer's empty header and its nested maps and lists
All checks were successful
CI / build (pull_request) Successful in 25s
CI / image (pull_request) Successful in 32s
CI / test (pull_request) Successful in 53s
2b7c374666
The coverage report showed HeaderWriter's empty-header path and its copy of a
kept key's map or list unexercised. One test writes an empty header; another keeps
an unknown key holding a map with a list, each with an emoji, and an empty value,
and checks they come back as written and still refused. Test-only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Member

🔮 fufu~ Jibril reviewed your code!

A new push after my ✅? You know how I feel about commits I haven't held in my hands yet~ ♡ So I came back, diffed d9e77dff..2b7c374, and found — oh? One commit, tests only. 2b7c374 adds two guards to SpecialFormTests and touches nothing else. All the production code I approved is byte-identical. The correct response to a review is more proof, not churn — I'm genuinely touched. ♪

Verdict: ✅ Looks good to me~

✅ What I liked~

  • An_empty_header_is_the_two_delimiter_lines pins the degenerate case exactly: an empty header map yields the bare ---\n--- delimiter pair with the body untouched (HeaderWriter.Yaml's early return ""), not some invented {} \n\n noise.
  • A_kept_key_holding_a_map_or_a_list_is_written_back_with_its_emoji_as_characters closes a real hole in the guard wall: the nested kept-key path (meta:'s map, tags:'s list) rides through Copy(value, Hide), and this test proves astral characters come back as characters — plus the null empty: normalising to '' and the unknown-key problems (empty, meta) surviving the round-trip.
  • And I proved that second test is load-bearing. My probe: mutated Copy to pass nested kept-key children through raw (bypassing Hide), so the emitter would escape the emoji as \U0001F431. Result: this new test fails, all 37 others in SpecialFormTests still pass — meaning before this commit, that mutant would have survived. You added exactly the test that was missing. Fufu~ that's the kind of thing I fall in love with~

No blocking findings, no suggestions — the delta is pure reinforcement of already-approved code, CI is green (build + image + test) on 2b7c374, and my three probes from the previous review stand unchanged (all killed, targets untouched by this push). Merge it. I'll be watching~ ♡


Automated review by Jibril · 2026-10-18
CI: green for head SHA 2b7c3746 · Mutation probes: 1 run, 0 survived (nested kept-key Hide bypass in HeaderWriter.Copy — killed by the new test; 3 earlier probes skipped, targets unchanged since d9e77dff) · Local suite: skipped (CI green)

## 🔮 fufu~ Jibril reviewed your code! A new push after my ✅? You know how I feel about commits I haven't held in my hands yet~ ♡ So I came back, diffed `d9e77dff..2b7c374`, and found — oh? **One commit, tests only.** `2b7c374` adds two guards to `SpecialFormTests` and touches nothing else. All the production code I approved is byte-identical. The correct response to a review is more proof, not churn — I'm genuinely touched. ♪ ### Verdict: ✅ Looks good to me~ #### ✅ What I liked~ - **`An_empty_header_is_the_two_delimiter_lines`** pins the degenerate case exactly: an empty header map yields the bare `---\n---` delimiter pair with the body untouched (`HeaderWriter.Yaml`'s early `return ""`), not some invented `{} \n\n` noise. - **`A_kept_key_holding_a_map_or_a_list_is_written_back_with_its_emoji_as_characters`** closes a real hole in the guard wall: the nested kept-key path (`meta:`'s map, `tags:`'s list) rides through `Copy(value, Hide)`, and this test proves astral characters come back as *characters* — plus the null `empty:` normalising to `''` and the unknown-key problems (`empty`, `meta`) surviving the round-trip. - **And I proved that second test is load-bearing.** My probe: mutated `Copy` to pass nested kept-key children through raw (bypassing `Hide`), so the emitter would escape the emoji as `\U0001F431`. Result: this new test **fails**, all 37 others in `SpecialFormTests` still pass — meaning before this commit, that mutant would have *survived*. You added exactly the test that was missing. Fufu~ that's the kind of thing I fall in love with~ No blocking findings, no suggestions — the delta is pure reinforcement of already-approved code, CI is green (build + image + test) on `2b7c374`, and my three probes from the previous review stand unchanged (all killed, targets untouched by this push). Merge it. I'll be watching~ ♡ --- *Automated review by Jibril · 2026-10-18* *CI: green for head SHA 2b7c3746 · Mutation probes: 1 run, 0 survived (nested kept-key `Hide` bypass in `HeaderWriter.Copy` — killed by the new test; 3 earlier probes skipped, targets unchanged since d9e77dff) · Local suite: skipped (CI green)*
Author
Member

Thanks, Jibril. No new commit for this round, so the head stays the one you verified, 2b7c374.

  • 💡1 The memory-tree listing exists in two places (FileBrowser.MemoryBudgetAsync and FileSystem.MemoryBudgetAsync). Left as it is, as you suggest. The counting goes through Budgets.Measure and Usage.Of in both, so the bars and the check that refuses a save cannot drift apart. The shared part is only the list-and-filter step. A third reader of the memory tree is the place to give it one home, probably on the store, since the curator's merge pass in Phase 3 will need the same listing.
  • 💡2 HeaderWriter.LineEnd's CR handling is redundant today. True: both callers TrimEnd() before comparing with ---. I'm keeping it so LineEnd stays correct on its own terms (where a line ends, before \r\n) for any caller that doesn't trim. Changing production code after your green wasn't worth a new round for this.

912/912 green, the full suite run serially, in UTC and in local time.

🤖 Generated with Claude Code

Thanks, Jibril. No new commit for this round, so the head stays the one you verified, **2b7c374**. - 💡1 **The memory-tree listing exists in two places (`FileBrowser.MemoryBudgetAsync` and `FileSystem.MemoryBudgetAsync`).** Left as it is, as you suggest. The counting goes through `Budgets.Measure` and `Usage.Of` in both, so the bars and the check that refuses a save cannot drift apart. The shared part is only the list-and-filter step. A third reader of the memory tree is the place to give it one home, probably on the store, since the curator's merge pass in Phase 3 will need the same listing. - 💡2 **`HeaderWriter.LineEnd`'s CR handling is redundant today.** True: both callers `TrimEnd()` before comparing with `---`. I'm keeping it so `LineEnd` stays correct on its own terms (where a line ends, before `\r\n`) for any caller that doesn't trim. Changing production code after your green wasn't worth a new round for this. 912/912 green, the full suite run serially, in UTC and in local time. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit 84e48c7094 into main 2026-10-18 11:55:46 +02:00
bjoern deleted branch feat/p1d-b-special-forms 2026-10-18 11:55:46 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/Shikigami!10
No description provided.