Skip to content

Commit 8dd04c5

Browse files
authored
[#561] 하네스의 의존성 방향 지시문을 수정한다 (#566)
* chore: 아키텍처 하네스 모듈 경계 갱신 * chore: 같은 레이어 내부 DI 제한 지침 추가 * refactor: Infra Domain 타깃 의존성 제거 * docs: Tuist 모듈 그래프 갱신 * chore: Widget 중심 위젯 브리지 소유권 지침 정리
1 parent e2ddc56 commit 8dd04c5

6 files changed

Lines changed: 111 additions & 33 deletions

File tree

.hermes/skills/devlog-architecture-harness/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,15 @@ Ask the user before editing when:
7575
- A shared type is being moved only because multiple modules need access.
7676
- Firebase/Auth/Firestore/Functions/Messaging-specific logic would leave Infra.
7777
- WidgetCore would depend on Domain, Data, Infra, Persistence, Presentation, or App.
78+
- DevLogWidget would depend on Domain, Infra, Persistence, Presentation, or App.
79+
- DevLogWidget would use WidgetKit for anything beyond the app-side widget reload bridge.
80+
- 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.
81+
- Persistence would gain widget snapshot generation, WidgetCore, WidgetKit reload, or DevLogWidget bridge ownership.
7882
- Presentation would depend on Data, Infra, Persistence, or App.
7983
- Data would gain concrete SDK or storage implementation details.
84+
- Data or Presentation would expand platform SDK usage beyond the existing narrow cancellation-classification or notification-badge patterns.
85+
- Infra would add any Domain dependency, source import, or SDK service contract coupling.
86+
- A same-layer dependency would be injected outside a SwiftUI `View` file in `Application/DevLogPresentation`.
8087
- The Presentation `StorePattern` flow or reducer responsibility would change.
8188
- A compile fix requires relaxing the intended architecture.
8289
- The change is outside the requested issue or PR scope.
@@ -118,6 +125,9 @@ After completion, report only:
118125

119126
- Do not infer project-specific architecture policy from generic Clean Architecture rules when DevLog already has a concrete pattern.
120127
- Do not move domain entities to Core just because multiple modules need them.
128+
- Do not inject same-layer dependencies except from SwiftUI `View` files in `Application/DevLogPresentation` into same-layer presentation objects for UI composition.
129+
- Do not collapse the current DevLogWidget bridge back into Data, Persistence, App, or WidgetCore without explicit boundary approval.
130+
- Route app-side widget bridge and snapshot update orchestration through DevLogWidget.
121131
- Do not hide architecture decisions inside build-fix wording.
122132
- Do not broaden a modularization task into unrelated Firestore, Messaging, or UI safety edits.
123133
- Do not mark work complete if the diff contains unrelated project-file or lockfile churn.

.hermes/skills/devlog-architecture-harness/references/devlog-architecture-flow.md

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,9 @@ flowchart TD
7777
Domain["DevLogDomain\nEntities\nRepository protocols\nUse cases"]
7878
Data["DevLogData\nRepository implementations\nDTOs\nMappers\nService/store protocols"]
7979
Infra["DevLogInfra\nFirebase\nSocial login\nNetwork\nLink metadata\nMessaging"]
80-
Persistence["DevLogPersistence\nUserDefaults\nImage store\nWidget snapshot persistence"]
81-
Core["DevLogCore\nDI\nLogger\nShared value/query types\nWidget snapshot values"]
80+
Persistence["DevLogPersistence\nUserDefaults\nImage store\nNon-widget app persistence"]
81+
Widget["DevLogWidget\nApp-side widget bridge\nSync/session handlers\nSnapshot update orchestration\nWidgetKit reload bridge"]
82+
Core["DevLogCore\nDI\nLogger\nShared value/query types\nLightweight widget values"]
8283
WidgetCore["DevLogWidgetCore\nWidget snapshot models\nFactories\nApp Group constants"]
8384
WidgetExtension["DevLogWidgetExtension\nWidgetKit UI\nProviders\nTimelines"]
8485
@@ -88,7 +89,9 @@ flowchart TD
8889
App --> Infra
8990
App --> Persistence
9091
App --> Core
92+
App --> Widget
9193
App --> WidgetCore
94+
App -.-> WidgetExtension
9295
9396
Presentation --> Domain
9497
Presentation --> Core
@@ -103,7 +106,10 @@ flowchart TD
103106
104107
Persistence --> Data
105108
Persistence --> Core
106-
Persistence --> WidgetCore
109+
110+
Widget --> Data
111+
Widget --> Core
112+
Widget --> WidgetCore
107113
108114
WidgetExtension --> WidgetCore
109115
WidgetCore --> Core
@@ -113,16 +119,27 @@ flowchart TD
113119

114120
| Layer | Owns | Allowed direction | Ask before |
115121
| --- | --- | --- | --- |
116-
| `DevLogCore` | DI primitives, logger, shared value/query types, widget snapshot values | No DevLog layer dependency | Moving domain entities into Core |
122+
| `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 |
117123
| `DevLogDomain` | entities, repository protocols, use cases | Core only | Adding Data, Infra, Persistence, Presentation, App, Widget UI, or SDK dependency |
118-
| `DevLogData` | repository implementations, DTOs, mappers, data protocols | Domain, Core | Adding concrete Firebase, WidgetKit, storage, or platform implementation details |
119-
| `DevLogInfra` | Firebase, social login, network, metadata, messaging implementations | Data, Core | Moving SDK-specific behavior out of Infra |
120-
| `DevLogPersistence` | local stores, image cache, widget snapshot persistence | Data, Core, WidgetCore | Moving domain logic or remote SDK behavior into Persistence |
121-
| `DevLogPresentation` | UI, view models, coordinators, presentation state | Domain, Core | Adding Data, Infra, Persistence, or App dependency |
122-
| `DevLogApp` | composition root, lifecycle, assembler wiring | Concrete app layers | Moving feature logic into App |
123-
| `DevLogWidgetCore` | widget data contracts and pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, or App dependency |
124+
| `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 |
125+
| `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 |
126+
| `DevLogPersistence` | local stores, image cache, non-widget app persistence | Data, Core | Adding WidgetCore, WidgetKit reload, DevLogWidget, widget snapshot generation, or widget bridge ownership |
127+
| `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 |
128+
| `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 |
129+
| `DevLogApp` | composition root, lifecycle, assembler wiring, app target ownership for widget extension embedding | Concrete app layers | Moving feature logic into App |
130+
| `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 |
124131
| `DevLogWidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore | Calling app/domain services directly |
125132

133+
## Layer-internal dependency injection
134+
135+
Do not inject dependencies between types that belong to the same layer.
136+
137+
This rule covers initializer injection, stored-property injection, environment injection, and resolving same-layer types through `DIContainer`.
138+
139+
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.
140+
141+
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.
142+
126143
## Presentation StorePattern flow
127144

128145
```mermaid
@@ -158,8 +175,9 @@ flowchart TD
158175
CoreDomain{"Core vs Domain ownership?"}
159176
Shared{"Moved only because shared?"}
160177
NewDependency{"New module dependency?"}
178+
SameLayerDI{"Same-layer dependency injection?"}
161179
ExternalSDK{"External SDK crosses layer?"}
162-
WidgetBoundary{"WidgetCore sees app/domain/data?"}
180+
WidgetBoundary{"Widget sync ownership or WidgetCore boundary changes?"}
163181
BuildShortcut{"Build fix relaxes boundary?"}
164182
ScopeDrift{"Outside current task scope?"}
165183
Ask["Ask user before editing"]
@@ -171,7 +189,10 @@ flowchart TD
171189
Shared -->|Yes| Ask
172190
Shared -->|No| NewDependency
173191
NewDependency -->|Yes| Ask
174-
NewDependency -->|No| ExternalSDK
192+
NewDependency -->|No| SameLayerDI
193+
SameLayerDI -->|Presentation View file| ExternalSDK
194+
SameLayerDI -->|No| ExternalSDK
195+
SameLayerDI -->|Other| Ask
175196
ExternalSDK -->|Yes| Ask
176197
ExternalSDK -->|No| WidgetBoundary
177198
WidgetBoundary -->|Yes| Ask
@@ -216,40 +237,59 @@ Use this flow before introducing or moving imports such as Firebase, GoogleSignI
216237
flowchart TD
217238
Import["External framework import"]
218239
Firebase{"Firebase/Auth/Firestore/Functions/Messaging?"}
219-
SocialLogin{"GoogleSignIn or AuthenticationServices login implementation?"}
240+
SocialLogin{"GoogleSignIn or AuthenticationServices?"}
241+
SocialLoginClassification{"Existing presentation/data cancellation/error classification?"}
220242
NetworkMeta{"Network or LinkPresentation implementation?"}
243+
UserNotifications{"UserNotifications?"}
221244
WidgetKit{"WidgetKit?"}
222245
Infra["Prefer DevLogInfra"]
223-
Persistence["Allow only for widget snapshot update/persistence if already established"]
246+
ErrorClassification["Keep narrow in DevLogData or DevLogPresentation only when matching the existing cancellation-classification pattern"]
247+
PresentationBadge["Allow in DevLogPresentation only for established badge/UI side effects"]
248+
Widget["Allow in DevLogWidget for app-side snapshot update/reload orchestration"]
224249
WidgetExtension["Allow in DevLogWidgetExtension rendering/timeline code"]
225250
Ask["Ask user before crossing layer"]
226251
227252
Import --> Firebase
228253
Firebase -->|Yes| Infra
229254
Firebase -->|No| SocialLogin
230-
SocialLogin -->|Yes| Infra
255+
SocialLogin -->|Login implementation| Infra
256+
SocialLogin -->|Presentation/data error classification| SocialLoginClassification
257+
SocialLoginClassification -->|Matches existing pattern| ErrorClassification
258+
SocialLoginClassification -->|New or broader behavior| Ask
231259
SocialLogin -->|No| NetworkMeta
232260
NetworkMeta -->|Yes| Infra
233-
NetworkMeta -->|No| WidgetKit
261+
NetworkMeta -->|No| UserNotifications
262+
UserNotifications -->|Badge/UI side effect| PresentationBadge
263+
UserNotifications -->|Push or messaging service| Infra
264+
UserNotifications -->|Other| Ask
265+
UserNotifications -->|No| WidgetKit
234266
WidgetKit -->|Widget UI| WidgetExtension
235-
WidgetKit -->|Snapshot update already established| Persistence
267+
WidgetKit -->|App-side snapshot update/reload| Widget
236268
WidgetKit -->|Other| Ask
237269
```
238270

239271
## Widget data-flow boundary
240272

241273
```mermaid
242274
flowchart LR
243-
App["App runtime\nDomain/Data/Infra/Persistence"]
244-
Snapshot["Snapshot generation\nPersistence + WidgetCore"]
275+
App["App runtime\nsession and mutation events"]
276+
WidgetBridge["DevLogWidget\nsync bus implementation\nsync/session handlers"]
277+
DataContracts["DevLogData\nwidget repository/updater contracts"]
278+
SnapshotInputs["DevLogData\nsnapshot input repository"]
279+
Snapshot["DevLogWidget\nsnapshot generation/persistence\nWidgetKit reload bridge"]
280+
WidgetModels["DevLogWidgetCore\nsnapshot models/factories/store contracts"]
245281
AppGroup["App Group storage\nShared defaults"]
246-
WidgetCore["WidgetCore\nSnapshot models\nFactories"]
247282
WidgetExtension["Widget extension\nWidgetKit UI"]
248283
249-
App --> Snapshot
250-
Snapshot --> AppGroup
251-
AppGroup --> WidgetCore
252-
WidgetCore --> WidgetExtension
284+
App --> WidgetBridge
285+
WidgetBridge --> DataContracts
286+
DataContracts --> SnapshotInputs
287+
DataContracts --> Snapshot
288+
SnapshotInputs --> WidgetBridge
289+
Snapshot --> WidgetModels
290+
WidgetModels --> AppGroup
291+
AppGroup --> WidgetModels
292+
WidgetModels --> WidgetExtension
253293
```
254294

255295
Widget UI should consume snapshot data. It should not fetch app services or domain repositories directly.

.hermes/skills/devlog-architecture-harness/references/devlog-workflow-rules.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ This reference holds DevLog-specific working rules that should live with the pro
2929
- Do not force a single `objectVersion` across projects. Treat Xcode's actual save output as the source of truth.
3030
- For synchronized-root cleanup, verify on copied files or a narrowed rule set before touching real project files.
3131
- When changing project structure, update the Tuist manifest first and treat generated Xcode project churn as disposable output.
32+
- Do not promote a manifest-only target dependency to an allowed architecture direction. Check source imports and ownership before updating the layer map.
3233

3334
## PR and review handling
3435

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

60+
## Layer-internal dependency injection
61+
62+
- Do not inject dependencies between types that belong to the same layer.
63+
- This includes initializer injection, stored-property injection, environment injection, and resolving same-layer types through `DIContainer`.
64+
- 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.
65+
- 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.
66+
5967
## Data, Domain, and Infra boundary
6068

6169
- Do not move domain entities to Core only because multiple modules need them.
6270
- Keep protocol location and implementation layer distinct when explaining or changing boundaries.
6371
- If a Data protocol is implemented by Infra, every type in that protocol signature must be visible to Infra.
72+
- `DevLogInfra` should depend on Data and Core, not Domain. Do not treat a manifest-only Domain target dependency as architecture permission.
6473
- Prefer a Data-side boundary value plus repository mapping when Infra should not import Domain.
6574
- For example, keep the app-facing Domain query separate from an Infra-facing Data query when that avoids Domain coupling in service protocols.
6675
- Firebase-specific error detection belongs in Infra; Data should handle domain-level errors after mapping.
76+
- 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.
6777

6878
## Presentation StorePattern
6979

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

7687
## Widget flow
7788

7889
- Widget UI should consume snapshot data, not app/domain services.
7990
- `DevLogWidgetCore` should stay free of Domain, Data, Infra, Persistence, Presentation, and App dependencies unless the user explicitly approves a boundary change.
80-
- Prefer an app-driven snapshot flow: app/runtime data fetch, snapshot generation, App Group storage, WidgetCore model/factory, WidgetExtension rendering.
91+
- `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`.
92+
- `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.
93+
- `DevLogPersistence` owns local persistence, user defaults, image store, and non-widget app persistence.
94+
- 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.
8195
- `WidgetTodoSnapshot` is a lightweight snapshot value, not a full domain `Todo`.
8296
- Do not make `Todo.number` or `WidgetTodoSnapshot.number` non-optional without a separate saved-vs-draft model decision.
8397
- 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.

0 commit comments

Comments
 (0)