Maintained in timewarp-architecture · Canonical file: SKILL.md
Install
npx skills add TimeWarpEngineering/timewarp-architecture --skill slice-isolation
Or copy the SKILL.md into your agent's skills directory.
Slice isolation (TWA0009)
Slices are independently removable vertical units. Folders organize humans; the namespace under SliceRoot is the law. Analyzer TWA0009 is the reactive safety net; this skill is for correct first placement so greenfield work does not couple product areas by accident.
Enforcement ships in TimeWarp.Architecture.Analyzers (and monorepo ProjectReference). Do not
re-interpret rules here — mirror the analyzer.
Detection — when to invoke
| Signal | How to find it |
|---|---|
Product area under features/ |
web-spa/features/…, web-contracts/features/… |
| Namespace under SliceRoot | …Features.<Id> (or nested …Features.Admin.Roles) |
TWA0009 / CrossSliceReference |
diagnostic text or attribute on a type |
| “Where does this page/state live?” | any new product capability before scaffolding |
Terms
| Term | Meaning |
|---|---|
| Slice | Removable vertical capability; the isolation unit |
| Feature | Informal product language only — not the analyzer’s unit name |
| Module | IModule / host DI composition — not a product slice |
Prefer saying slice in diagnostics, opt-outs, and agent prose. Namespace path may still use
…Features.* (familiar VSA / contracts layout).
SliceRoot
| Setting | Value |
|---|---|
| Default | {RootNamespace}.Features (from MSBuild RootNamespace) |
| Override | MSBuild property TimeWarpSliceRoot (must be CompilerVisibleProperty) |
Slice id = path under SliceRoot after stripping structural suffixes Pages, Components,
Application. Nested ids are the full path:
| Namespace | Slice id / tier |
|---|---|
…Features |
Substrate (bare root) |
…Features.Applications |
Platform id Applications |
…Features.Counters |
Product id Counters |
…Features.Counters.Pages |
Product id Counters (suffix stripped) |
…Features.Admin.Roles |
Product id Admin.Roles |
…Components, layouts, app root types |
Outside SliceRoot |
Folders usually mirror slices (features/counter/ ↔ …Features.Counters) but moving a file
does not change legal dependencies — only the namespace does.
Tiers (dependency rules)
| Tier | Example | May reference |
|---|---|---|
| Outside | …Components, shell, layouts |
Anything (composition free) |
| Substrate | bare …Features (BaseComponent, shared base types) |
Outside, substrate, platform — not product without opt-out |
| Platform | …Features.Applications |
Outside, substrate, platform — not product without opt-out |
| Product | …Features.Counters, …Features.Admin.Roles |
Outside, substrate, platform, own product id, contracts (other assembly). Not another product id without opt-out |
Forbidden without opt-out (TWA0009):
- product A → product B (A ≠ B)
- platform → product
- substrate → product
Free by design:
- product → platform (
Applications) — e.g.CounterPage→ApplicationState - any code → other assemblies (contracts are sharing-by-design)
- Outside → product (nav shell, shared components)
Placement matrix
| Artifact | Where |
|---|---|
| Page, state, actions, slice-local UI | Inside the product slice namespace |
App chrome / shell / empty MainLayout |
Outside SliceRoot (e.g. …Components) — see blazor-layout |
| Shared UI used by multiple slices | components/ → namespace outside SliceRoot |
| Shared API shapes | Contracts project (other assembly; free under TWA0009) |
| Host-wide modal / menu / branding state | Platform Applications |
Living anchors (timewarp-architecture template):
| Role | Path | Namespace |
|---|---|---|
| Product | web-spa/features/counter/ |
…Features.Counters |
| Nested product | web-spa/features/admin/roles/ |
…Features.Admin.Roles |
| Platform | web-spa/features/application/ |
…Features.Applications |
| Substrate | web-spa/features/base/base-component.cs |
…Features |
| Outside shell | web-spa/components/TimeWarpPage.razor |
…Components |
Greenfield scaffold workflow
- Name the slice — plural, domain-oriented (
Clients,Admin.Roles). - Folder — kebab path under the project’s
features/tree (match sibling casing). - Namespace —
{RootNamespace}.Features.{SliceId}(plural segments; nested with.). - Colocate page, state, actions, and slice-private components in that namespace (pages are
not shared infrastructure — no grab-bag
…Pagesfor product UI). - Contracts — same plural feature path under the contracts project (
web-api-contractsskill). Other assembly → free across SPA slices. - Wire nav / DI from Outside or platform — not from another product slice.
- Prefer share (Components / contracts) over
[CrossSliceReference]. - Build — fix TWA0009 by relocating code first; mute only for deliberate demo/integration edges.
Share vs opt-out
Prefer share
- Promote shared controls to
…Components. - Share API shapes via contracts assembly.
- Depend product → platform without attributes when the dependency is real host state.
Opt-out — deliberate edges only
using TimeWarp.Foundation.Features;
[CrossSliceReference(typeof(CounterState), "Living style guide exercises the counter throw-exception pipeline.")]
partial class StyleGuidePage;
Rules:
- Attribute on the referencing type (
AllowMultiple = truefor multiple foreign slices). typeof(T)maps to the target product slice; only edges into that slice are suppressed.- Reason must be non-empty (human paperwork).
- Partial-safe (semantic attributes).
- Type:
TimeWarp.Foundation.Features.CrossSliceReferenceAttribute(packageTimeWarp.Foundation.Contracts/ monorepo foundation-contracts).
Living examples:
- Style guide → Counters:
web-spa/features/style-guide/pages/StyleGuidePage.razor.cs - Auth pipeline deliberate edges: authentication code-behinds with multiple
[CrossSliceReference(...)]when needed
Limits agents must know
| Limit | Implication |
|---|---|
| Same assembly only | Contracts / NuGet metadata types never trip TWA0009 |
| Hand-written C# | GeneratedCodeAnalysisFlags.None — pure .razor without .razor.cs is not scanned; put deliberate edges on code-behind |
| Folders can lie | Namespace still owns the type |
| Structural suffixes | …Counters.Pages is still product id Counters |
Good / bad examples
| Bad | Why | Fix |
|---|---|---|
…Features.Counters type uses WeatherForecastsState |
product → product | Share via Components/contracts, or reasoned [CrossSliceReference] |
Product page in grab-bag …Pages outside any slice |
not removable with the capability | Namespace under …Features.<Id> |
Platform type reaches into Counters |
platform ↛ product | Invert dependency (product → platform) or rare opt-out on platform type |
| Mute TWA0009 globally / without reason | hides real coupling | Relocate or scoped opt-out |
| Good | Why |
|---|---|
CounterPage uses ApplicationState |
product → platform free |
TimeWarpPage in …Components |
Outside composition free |
Admin.Roles only uses own types + contracts + platform |
nested product isolation |
Agent checklist
- New capability has a slice id and matching
{Root}.Features.{Id}namespace - Page, state, and actions live in that namespace
- Shell/layout stay outside SliceRoot (or platform only when truly host state)
- Cross-slice data via Components or contracts first
- Any remaining edge has
[CrossSliceReference(typeof(T), "reason")] - No silent coupling or unexplained suppressions
Related skills and pointers
web-api-contracts— contract placement; contracts assemblies are free under TWA0009; still use plural…Features.*aligned with SPA product slicesblazor-layout— empty layout + shell; chrome outside SliceRootblazor-css-strategy— shell/component styling onlyagent-context-regions— Purpose/Design on new files (TWA0004)- AGENTS.md — TWA diagnostic table (row TWA0009)
- HowToRemoveDemoFeatures — delete Counter/EventStream demo slices
- Analyzer (source of truth):
source/analyzers/timewarp-architecture-convention-analyzers/slice-isolation-analyzer.cs - Opt-out attribute:
source/foundation/foundation-contracts/base/cross-slice-reference-attribute.cs