Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .hermes/skills/devlog-architecture-harness/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,15 @@ Ask the user before editing when:
- A shared type is being moved only because multiple modules need access.
- Firebase/Auth/Firestore/Functions/Messaging-specific logic would leave Infra.
- WidgetCore would depend on Domain, Data, Infra, Persistence, Presentation, or App.
- DevLogWidget would depend on Domain, Infra, Persistence, Presentation, or App.
- DevLogWidget would use WidgetKit for anything beyond the app-side widget reload bridge.
- Widget sync ownership would move away from the preferred split: Data contracts and snapshot repositories, DevLogWidget app-side bridge/snapshot update orchestration, WidgetCore snapshot models/factories/store contracts, and WidgetExtension rendering.
- Persistence would gain widget snapshot generation, WidgetCore, WidgetKit reload, or DevLogWidget bridge ownership.
- Presentation would depend on Data, Infra, Persistence, or App.
- Data would gain concrete SDK or storage implementation details.
- Data or Presentation would expand platform SDK usage beyond the existing narrow cancellation-classification or notification-badge patterns.
- Infra would add any Domain dependency, source import, or SDK service contract coupling.
- A same-layer dependency would be injected outside a SwiftUI `View` file in `Application/DevLogPresentation`.
- The Presentation `StorePattern` flow or reducer responsibility would change.
- A compile fix requires relaxing the intended architecture.
- The change is outside the requested issue or PR scope.
Expand Down Expand Up @@ -118,6 +125,9 @@ After completion, report only:

- Do not infer project-specific architecture policy from generic Clean Architecture rules when DevLog already has a concrete pattern.
- Do not move domain entities to Core just because multiple modules need them.
- Do not inject same-layer dependencies except from SwiftUI `View` files in `Application/DevLogPresentation` into same-layer presentation objects for UI composition.
- Do not collapse the current DevLogWidget bridge back into Data, Persistence, App, or WidgetCore without explicit boundary approval.
- Route app-side widget bridge and snapshot update orchestration through DevLogWidget.
- Do not hide architecture decisions inside build-fix wording.
- Do not broaden a modularization task into unrelated Firestore, Messaging, or UI safety edits.
- Do not mark work complete if the diff contains unrelated project-file or lockfile churn.
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,9 @@ flowchart TD
Domain["DevLogDomain\nEntities\nRepository protocols\nUse cases"]
Data["DevLogData\nRepository implementations\nDTOs\nMappers\nService/store protocols"]
Infra["DevLogInfra\nFirebase\nSocial login\nNetwork\nLink metadata\nMessaging"]
Persistence["DevLogPersistence\nUserDefaults\nImage store\nWidget snapshot persistence"]
Core["DevLogCore\nDI\nLogger\nShared value/query types\nWidget snapshot values"]
Persistence["DevLogPersistence\nUserDefaults\nImage store\nNon-widget app persistence"]
Widget["DevLogWidget\nApp-side widget bridge\nSync/session handlers\nSnapshot update orchestration\nWidgetKit reload bridge"]
Core["DevLogCore\nDI\nLogger\nShared value/query types\nLightweight widget values"]
WidgetCore["DevLogWidgetCore\nWidget snapshot models\nFactories\nApp Group constants"]
WidgetExtension["DevLogWidgetExtension\nWidgetKit UI\nProviders\nTimelines"]

Expand All @@ -88,7 +89,9 @@ flowchart TD
App --> Infra
App --> Persistence
App --> Core
App --> Widget
App --> WidgetCore
App -.-> WidgetExtension

Presentation --> Domain
Presentation --> Core
Expand All @@ -103,7 +106,10 @@ flowchart TD

Persistence --> Data
Persistence --> Core
Persistence --> WidgetCore

Widget --> Data
Widget --> Core
Widget --> WidgetCore

WidgetExtension --> WidgetCore
WidgetCore --> Core
Expand All @@ -113,16 +119,27 @@ flowchart TD

| Layer | Owns | Allowed direction | Ask before |
| --- | --- | --- | --- |
| `DevLogCore` | DI primitives, logger, shared value/query types, widget snapshot values | No DevLog layer dependency | Moving domain entities into Core |
| `DevLogCore` | DI primitives, logger, shared value/query types, display options, activity kinds, lightweight widget bridge values | No DevLog layer dependency | Moving domain entities into Core |
| `DevLogDomain` | entities, repository protocols, use cases | Core only | Adding Data, Infra, Persistence, Presentation, App, Widget UI, or SDK dependency |
| `DevLogData` | repository implementations, DTOs, mappers, data protocols | Domain, Core | Adding concrete Firebase, WidgetKit, storage, or platform implementation details |
| `DevLogInfra` | Firebase, social login, network, metadata, messaging implementations | Data, Core | Moving SDK-specific behavior out of Infra |
| `DevLogPersistence` | local stores, image cache, widget snapshot persistence | Data, Core, WidgetCore | Moving domain logic or remote SDK behavior into Persistence |
| `DevLogPresentation` | UI, view models, coordinators, presentation state | Domain, Core | Adding Data, Infra, Persistence, or App dependency |
| `DevLogApp` | composition root, lifecycle, assembler wiring | Concrete app layers | Moving feature logic into App |
| `DevLogWidgetCore` | widget data contracts and pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, or App dependency |
| `DevLogData` | repository implementations, DTOs, mappers, data protocols, widget repository/updater/sync contracts | Domain, Core | Adding concrete Firebase, GoogleSignIn, WidgetKit, storage, WidgetCore snapshot model/factory usage, or platform implementation details; moving concrete widget handlers into Data |
| `DevLogInfra` | Firebase, social login, network, metadata, messaging implementations | Data, Core | Moving SDK-specific behavior out of Infra; adding any Domain dependency, source import, or SDK service contract coupling |
| `DevLogPersistence` | local stores, image cache, non-widget app persistence | Data, Core | Adding WidgetCore, WidgetKit reload, DevLogWidget, widget snapshot generation, or widget bridge ownership |
| `DevLogPresentation` | UI, view models, coordinators, presentation state, narrow presentation-scoped platform side effects | Domain, Core | Adding Data, Infra, Persistence, or App dependency; expanding platform service ownership beyond UI-side effects |
| `DevLogWidget` | app-side widget bridge, sync bus implementation, sync/session handlers, snapshot generation/persistence orchestration, WidgetKit reload bridge, widget assembler | Data, Core, WidgetCore | Adding Domain, Infra, Persistence, Presentation, or App dependency |
| `DevLogApp` | composition root, lifecycle, assembler wiring, app target ownership for widget extension embedding | Concrete app layers | Moving feature logic into App |
| `DevLogWidgetCore` | widget snapshot models, factories, app-group keys/defaults store, deep links, pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, App, or DevLogWidget dependency |
| `DevLogWidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore | Calling app/domain services directly |

## Layer-internal dependency injection

Do not inject dependencies between types that belong to the same layer.

This rule covers initializer injection, stored-property injection, environment injection, and resolving same-layer types through `DIContainer`.

The only allowed exception is a SwiftUI `View` file in `Application/DevLogPresentation` receiving same-layer presentation objects such as a ViewModel, Coordinator, or Store for UI composition.

That exception does not apply to non-View files in Presentation, and does not apply to Core, Domain, Data, Infra, Persistence, Widget, App, WidgetCore, WidgetExtension, or Firebase functions.

## Presentation StorePattern flow

```mermaid
Expand Down Expand Up @@ -158,8 +175,9 @@ flowchart TD
CoreDomain{"Core vs Domain ownership?"}
Shared{"Moved only because shared?"}
NewDependency{"New module dependency?"}
SameLayerDI{"Same-layer dependency injection?"}
ExternalSDK{"External SDK crosses layer?"}
WidgetBoundary{"WidgetCore sees app/domain/data?"}
WidgetBoundary{"Widget sync ownership or WidgetCore boundary changes?"}
BuildShortcut{"Build fix relaxes boundary?"}
ScopeDrift{"Outside current task scope?"}
Ask["Ask user before editing"]
Expand All @@ -171,7 +189,10 @@ flowchart TD
Shared -->|Yes| Ask
Shared -->|No| NewDependency
NewDependency -->|Yes| Ask
NewDependency -->|No| ExternalSDK
NewDependency -->|No| SameLayerDI
SameLayerDI -->|Presentation View file| ExternalSDK
SameLayerDI -->|No| ExternalSDK
SameLayerDI -->|Other| Ask
ExternalSDK -->|Yes| Ask
ExternalSDK -->|No| WidgetBoundary
WidgetBoundary -->|Yes| Ask
Expand Down Expand Up @@ -216,40 +237,59 @@ Use this flow before introducing or moving imports such as Firebase, GoogleSignI
flowchart TD
Import["External framework import"]
Firebase{"Firebase/Auth/Firestore/Functions/Messaging?"}
SocialLogin{"GoogleSignIn or AuthenticationServices login implementation?"}
SocialLogin{"GoogleSignIn or AuthenticationServices?"}
SocialLoginClassification{"Existing presentation/data cancellation/error classification?"}
NetworkMeta{"Network or LinkPresentation implementation?"}
UserNotifications{"UserNotifications?"}
WidgetKit{"WidgetKit?"}
Infra["Prefer DevLogInfra"]
Persistence["Allow only for widget snapshot update/persistence if already established"]
ErrorClassification["Keep narrow in DevLogData or DevLogPresentation only when matching the existing cancellation-classification pattern"]
PresentationBadge["Allow in DevLogPresentation only for established badge/UI side effects"]
Widget["Allow in DevLogWidget for app-side snapshot update/reload orchestration"]
WidgetExtension["Allow in DevLogWidgetExtension rendering/timeline code"]
Ask["Ask user before crossing layer"]

Import --> Firebase
Firebase -->|Yes| Infra
Firebase -->|No| SocialLogin
SocialLogin -->|Yes| Infra
SocialLogin -->|Login implementation| Infra
SocialLogin -->|Presentation/data error classification| SocialLoginClassification
SocialLoginClassification -->|Matches existing pattern| ErrorClassification
SocialLoginClassification -->|New or broader behavior| Ask
SocialLogin -->|No| NetworkMeta
NetworkMeta -->|Yes| Infra
NetworkMeta -->|No| WidgetKit
NetworkMeta -->|No| UserNotifications
UserNotifications -->|Badge/UI side effect| PresentationBadge
UserNotifications -->|Push or messaging service| Infra
UserNotifications -->|Other| Ask
UserNotifications -->|No| WidgetKit
WidgetKit -->|Widget UI| WidgetExtension
WidgetKit -->|Snapshot update already established| Persistence
WidgetKit -->|App-side snapshot update/reload| Widget
WidgetKit -->|Other| Ask
```

## Widget data-flow boundary

```mermaid
flowchart LR
App["App runtime\nDomain/Data/Infra/Persistence"]
Snapshot["Snapshot generation\nPersistence + WidgetCore"]
App["App runtime\nsession and mutation events"]
WidgetBridge["DevLogWidget\nsync bus implementation\nsync/session handlers"]
DataContracts["DevLogData\nwidget repository/updater contracts"]
SnapshotInputs["DevLogData\nsnapshot input repository"]
Snapshot["DevLogWidget\nsnapshot generation/persistence\nWidgetKit reload bridge"]
WidgetModels["DevLogWidgetCore\nsnapshot models/factories/store contracts"]
AppGroup["App Group storage\nShared defaults"]
WidgetCore["WidgetCore\nSnapshot models\nFactories"]
WidgetExtension["Widget extension\nWidgetKit UI"]

App --> Snapshot
Snapshot --> AppGroup
AppGroup --> WidgetCore
WidgetCore --> WidgetExtension
App --> WidgetBridge
WidgetBridge --> DataContracts
DataContracts --> SnapshotInputs
DataContracts --> Snapshot
SnapshotInputs --> WidgetBridge
Snapshot --> WidgetModels
WidgetModels --> AppGroup
AppGroup --> WidgetModels
WidgetModels --> WidgetExtension
```

Widget UI should consume snapshot data. It should not fetch app services or domain repositories directly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ This reference holds DevLog-specific working rules that should live with the pro
- Do not force a single `objectVersion` across projects. Treat Xcode's actual save output as the source of truth.
- For synchronized-root cleanup, verify on copied files or a narrowed rule set before touching real project files.
- When changing project structure, update the Tuist manifest first and treat generated Xcode project churn as disposable output.
- Do not promote a manifest-only target dependency to an allowed architecture direction. Check source imports and ownership before updating the layer map.

## PR and review handling

Expand Down Expand Up @@ -56,28 +57,41 @@ This reference holds DevLog-specific working rules that should live with the pro
- Keep project-file, lockfile, and code changes separated when the task scope requires clean review.
- Do not broaden architecture work into unrelated Firestore, Messaging, UI, or safety edits.

## Layer-internal dependency injection

- Do not inject dependencies between types that belong to the same layer.
- This includes initializer injection, stored-property injection, environment injection, and resolving same-layer types through `DIContainer`.
- The only allowed exception is a SwiftUI `View` file in `Application/DevLogPresentation` receiving same-layer presentation objects such as a ViewModel, Coordinator, or Store for UI composition.
- The exception does not apply to non-View files in Presentation, and does not apply to Core, Domain, Data, Infra, Persistence, Widget, App, WidgetCore, WidgetExtension, or Firebase functions.

## Data, Domain, and Infra boundary

- Do not move domain entities to Core only because multiple modules need them.
- Keep protocol location and implementation layer distinct when explaining or changing boundaries.
- If a Data protocol is implemented by Infra, every type in that protocol signature must be visible to Infra.
- `DevLogInfra` should depend on Data and Core, not Domain. Do not treat a manifest-only Domain target dependency as architecture permission.
- Prefer a Data-side boundary value plus repository mapping when Infra should not import Domain.
- For example, keep the app-facing Domain query separate from an Infra-facing Data query when that avoids Domain coupling in service protocols.
- Firebase-specific error detection belongs in Infra; Data should handle domain-level errors after mapping.
- Data and Presentation currently keep narrow social-login cancellation classification in `DataLayerError` and `Error+SocialLogin`. Do not expand that into concrete login implementation or broader SDK ownership without explicit approval.

## Presentation StorePattern

- Preserve the existing `StorePattern` shape: `@MainActor`, `State`, `Action`, `SideEffect`, `send -> reduce -> run`.
- Reducers compute state and return side effects.
- I/O belongs in `run` or injected services.
- Presentation currently owns narrow notification badge side effects through `UserNotifications`. Do not expand that into push service or messaging ownership.
- Do not leave reducer-era helper methods behind after moving work into `run`.
- Before adding task cancellation or async wrappers, inspect whether the underlying operation is actually async.

## Widget flow

- Widget UI should consume snapshot data, not app/domain services.
- `DevLogWidgetCore` should stay free of Domain, Data, Infra, Persistence, Presentation, and App dependencies unless the user explicitly approves a boundary change.
- Prefer an app-driven snapshot flow: app/runtime data fetch, snapshot generation, App Group storage, WidgetCore model/factory, WidgetExtension rendering.
- `DevLogWidget` owns the app-side widget bridge: sync event bus implementation, sync event handlers, session sync handler, auth-session sync provider, snapshot generation/persistence orchestration, WidgetKit reload bridge, and `WidgetAssembler`.
- `DevLogData` owns widget-related contracts and repository implementations, including `WidgetSyncEventBus`, `WidgetSnapshotUpdater`, and `WidgetTodoSnapshotRepository`. Data should not own concrete widget handlers, WidgetCore snapshot model/factory usage, or WidgetKit reload behavior.
- `DevLogPersistence` owns local persistence, user defaults, image store, and non-widget app persistence.
- Prefer an app-driven snapshot flow: app/runtime event, DevLogWidget sync handler, Data snapshot input fetch, DevLogWidget snapshot update, App Group storage through WidgetCore contracts, WidgetExtension rendering.
- `WidgetTodoSnapshot` is a lightweight snapshot value, not a full domain `Todo`.
- Do not make `Todo.number` or `WidgetTodoSnapshot.number` non-optional without a separate saved-vs-draft model decision.
- If a widget sync flow needs one timestamp for multiple snapshots, capture `Date()` once and pass it through to avoid midnight or quarter-boundary drift.
Expand Down
Loading