|
| 1 | +# DevLog Architecture Flow |
| 2 | + |
| 3 | +## Purpose |
| 4 | + |
| 5 | +This reference defines the DevLog-specific harness flow for AI-assisted architecture work. |
| 6 | + |
| 7 | +The goal is not to make the AI decide more architecture policy. The goal is to make the AI stop before it makes project-specific architecture decisions that should be confirmed by the user. |
| 8 | + |
| 9 | +Use this reference with `AGENTS.md` and `.hermes/skills/devlog-architecture-harness/SKILL.md`. |
| 10 | + |
| 11 | +This repository is an Xcode workspace-based modular iOS app. There is no root `Package.swift`; modules are separate `.xcodeproj` entries under `DevLog.xcworkspace`. |
| 12 | + |
| 13 | +## High-level harness flow |
| 14 | + |
| 15 | +```mermaid |
| 16 | +flowchart TD |
| 17 | + Request["User request"] |
| 18 | + LoadRules["Load DevLog rules"] |
| 19 | + LoadContext["Inspect current repository context"] |
| 20 | + Classify["Classify change type"] |
| 21 | + Boundary["Check layer boundary"] |
| 22 | + Ambiguous{"Architecture boundary ambiguous?"} |
| 23 | + Ask["Ask user before editing"] |
| 24 | + Plan["Prepare narrow edit plan"] |
| 25 | + Edit["Apply scoped change"] |
| 26 | + Verify["Verify build or docs state"] |
| 27 | + Diff["Inspect final diff scope"] |
| 28 | + Record["Report decision and result"] |
| 29 | +
|
| 30 | + Request --> LoadRules |
| 31 | + LoadRules --> LoadContext |
| 32 | + LoadContext --> Classify |
| 33 | + Classify --> Boundary |
| 34 | + Boundary --> Ambiguous |
| 35 | + Ambiguous -->|Yes| Ask |
| 36 | + Ask --> Boundary |
| 37 | + Ambiguous -->|No| Plan |
| 38 | + Plan --> Edit |
| 39 | + Edit --> Verify |
| 40 | + Verify --> Diff |
| 41 | + Diff --> Record |
| 42 | +``` |
| 43 | + |
| 44 | +## Change classification |
| 45 | + |
| 46 | +```mermaid |
| 47 | +flowchart TD |
| 48 | + Change["Requested change"] |
| 49 | + ImportOnly{"Only import/access fallout?"} |
| 50 | + BoundaryMove{"Moves ownership or dependency?"} |
| 51 | + SDKPlacement{"Changes external SDK placement?"} |
| 52 | + DIChange{"Changes assembler or DI ownership?"} |
| 53 | + WidgetFlow{"Changes widget data flow?"} |
| 54 | + Mechanical["Mechanical change"] |
| 55 | + Architecture["Architecture change"] |
| 56 | + Ambiguous["Ambiguous change"] |
| 57 | +
|
| 58 | + Change --> ImportOnly |
| 59 | + ImportOnly -->|Yes| Mechanical |
| 60 | + ImportOnly -->|No| BoundaryMove |
| 61 | + BoundaryMove -->|Yes| Architecture |
| 62 | + BoundaryMove -->|No| SDKPlacement |
| 63 | + SDKPlacement -->|Yes| Architecture |
| 64 | + SDKPlacement -->|No| DIChange |
| 65 | + DIChange -->|Yes| Architecture |
| 66 | + DIChange -->|No| WidgetFlow |
| 67 | + WidgetFlow -->|Yes| Architecture |
| 68 | + WidgetFlow -->|No| Ambiguous |
| 69 | +``` |
| 70 | + |
| 71 | +## DevLog layer map |
| 72 | + |
| 73 | +```mermaid |
| 74 | +flowchart TD |
| 75 | + App["DevLogApp\nComposition root\nApp lifecycle\nAssembler wiring"] |
| 76 | + Presentation["DevLogPresentation\nSwiftUI views\nViewModels\nCoordinators\nUI state"] |
| 77 | + Domain["DevLogDomain\nEntities\nRepository protocols\nUse cases"] |
| 78 | + Data["DevLogData\nRepository implementations\nDTOs\nMappers\nService/store protocols"] |
| 79 | + 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"] |
| 82 | + WidgetCore["DevLogWidgetCore\nWidget snapshot models\nFactories\nApp Group constants"] |
| 83 | + WidgetExtension["DevLogWidgetExtension\nWidgetKit UI\nProviders\nTimelines"] |
| 84 | +
|
| 85 | + App --> Presentation |
| 86 | + App --> Domain |
| 87 | + App --> Data |
| 88 | + App --> Infra |
| 89 | + App --> Persistence |
| 90 | + App --> Core |
| 91 | + App --> WidgetCore |
| 92 | +
|
| 93 | + Presentation --> Domain |
| 94 | + Presentation --> Core |
| 95 | +
|
| 96 | + Domain --> Core |
| 97 | +
|
| 98 | + Data --> Domain |
| 99 | + Data --> Core |
| 100 | +
|
| 101 | + Infra --> Data |
| 102 | + Infra --> Core |
| 103 | +
|
| 104 | + Persistence --> Data |
| 105 | + Persistence --> Core |
| 106 | + Persistence --> WidgetCore |
| 107 | +
|
| 108 | + WidgetExtension --> WidgetCore |
| 109 | + WidgetCore --> Core |
| 110 | +``` |
| 111 | + |
| 112 | +## Boundary rules |
| 113 | + |
| 114 | +| Layer | Owns | Allowed direction | Ask before | |
| 115 | +| --- | --- | --- | --- | |
| 116 | +| `DevLogCore` | DI primitives, logger, shared value/query types, widget snapshot values | No DevLog layer dependency | Moving domain entities into Core | |
| 117 | +| `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 | +| `DevLogWidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore | Calling app/domain services directly | |
| 125 | + |
| 126 | +## Presentation Store flow |
| 127 | + |
| 128 | +```mermaid |
| 129 | +flowchart LR |
| 130 | + View["SwiftUI View"] |
| 131 | + ViewModel["ViewModel / Store"] |
| 132 | + Send["send(Action)"] |
| 133 | + Reduce["reduce(with:)"] |
| 134 | + State["State update"] |
| 135 | + SideEffect["SideEffect"] |
| 136 | + Run["run(SideEffect)"] |
| 137 | + Service["Injected use case or service"] |
| 138 | +
|
| 139 | + View --> ViewModel |
| 140 | + ViewModel --> Send |
| 141 | + Send --> Reduce |
| 142 | + Reduce --> State |
| 143 | + Reduce --> SideEffect |
| 144 | + SideEffect --> Run |
| 145 | + Run --> Service |
| 146 | + Service --> Send |
| 147 | +``` |
| 148 | + |
| 149 | +Preserve this flow unless the user explicitly asks to change the Presentation architecture. Reducers compute state and return side effects. I/O belongs in `run` or injected services. |
| 150 | + |
| 151 | +## Ambiguity gate |
| 152 | + |
| 153 | +The AI must stop and ask the user when it reaches any of these points. |
| 154 | + |
| 155 | +```mermaid |
| 156 | +flowchart TD |
| 157 | + Check["Architecture decision needed"] |
| 158 | + CoreDomain{"Core vs Domain ownership?"} |
| 159 | + Shared{"Moved only because shared?"} |
| 160 | + NewDependency{"New module dependency?"} |
| 161 | + ExternalSDK{"External SDK crosses layer?"} |
| 162 | + WidgetBoundary{"WidgetCore sees app/domain/data?"} |
| 163 | + BuildShortcut{"Build fix relaxes boundary?"} |
| 164 | + ScopeDrift{"Outside current task scope?"} |
| 165 | + Ask["Ask user before editing"] |
| 166 | + Proceed["Proceed with scoped edit"] |
| 167 | +
|
| 168 | + Check --> CoreDomain |
| 169 | + CoreDomain -->|Yes| Ask |
| 170 | + CoreDomain -->|No| Shared |
| 171 | + Shared -->|Yes| Ask |
| 172 | + Shared -->|No| NewDependency |
| 173 | + NewDependency -->|Yes| Ask |
| 174 | + NewDependency -->|No| ExternalSDK |
| 175 | + ExternalSDK -->|Yes| Ask |
| 176 | + ExternalSDK -->|No| WidgetBoundary |
| 177 | + WidgetBoundary -->|Yes| Ask |
| 178 | + WidgetBoundary -->|No| BuildShortcut |
| 179 | + BuildShortcut -->|Yes| Ask |
| 180 | + BuildShortcut -->|No| ScopeDrift |
| 181 | + ScopeDrift -->|Yes| Ask |
| 182 | + ScopeDrift -->|No| Proceed |
| 183 | +``` |
| 184 | + |
| 185 | +## Core vs Domain decision flow |
| 186 | + |
| 187 | +Use this flow when deciding whether a type belongs in Core or Domain. |
| 188 | + |
| 189 | +```mermaid |
| 190 | +flowchart TD |
| 191 | + Type["Type under review"] |
| 192 | + DomainMeaning{"Represents business/domain meaning?"} |
| 193 | + QueryOrPrimitive{"Generic query, option, logger, DI, or shared primitive?"} |
| 194 | + UsedByWidget{"Needed by WidgetCore snapshot contract?"} |
| 195 | + OnlyShared{"Only reason is multiple modules need it?"} |
| 196 | + Domain["Keep or place in DevLogDomain"] |
| 197 | + Core["Keep or place in DevLogCore"] |
| 198 | + Ask["Ask user"] |
| 199 | +
|
| 200 | + Type --> DomainMeaning |
| 201 | + DomainMeaning -->|Yes| Domain |
| 202 | + DomainMeaning -->|No| QueryOrPrimitive |
| 203 | + QueryOrPrimitive -->|Yes| Core |
| 204 | + QueryOrPrimitive -->|No| UsedByWidget |
| 205 | + UsedByWidget -->|Yes| Core |
| 206 | + UsedByWidget -->|No| OnlyShared |
| 207 | + OnlyShared -->|Yes| Ask |
| 208 | + OnlyShared -->|No| Ask |
| 209 | +``` |
| 210 | + |
| 211 | +## External dependency flow |
| 212 | + |
| 213 | +Use this flow before introducing or moving imports such as Firebase, GoogleSignIn, AuthenticationServices, UserNotifications, LinkPresentation, Network, or WidgetKit. |
| 214 | + |
| 215 | +```mermaid |
| 216 | +flowchart TD |
| 217 | + Import["External framework import"] |
| 218 | + Firebase{"Firebase/Auth/Firestore/Functions/Messaging?"} |
| 219 | + SocialLogin{"GoogleSignIn or AuthenticationServices login implementation?"} |
| 220 | + NetworkMeta{"Network or LinkPresentation implementation?"} |
| 221 | + WidgetKit{"WidgetKit?"} |
| 222 | + Infra["Prefer DevLogInfra"] |
| 223 | + Persistence["Allow only for widget snapshot update/persistence if already established"] |
| 224 | + WidgetExtension["Allow in DevLogWidgetExtension rendering/timeline code"] |
| 225 | + Ask["Ask user before crossing layer"] |
| 226 | +
|
| 227 | + Import --> Firebase |
| 228 | + Firebase -->|Yes| Infra |
| 229 | + Firebase -->|No| SocialLogin |
| 230 | + SocialLogin -->|Yes| Infra |
| 231 | + SocialLogin -->|No| NetworkMeta |
| 232 | + NetworkMeta -->|Yes| Infra |
| 233 | + NetworkMeta -->|No| WidgetKit |
| 234 | + WidgetKit -->|Widget UI| WidgetExtension |
| 235 | + WidgetKit -->|Snapshot update already established| Persistence |
| 236 | + WidgetKit -->|Other| Ask |
| 237 | +``` |
| 238 | + |
| 239 | +## Widget data-flow boundary |
| 240 | + |
| 241 | +```mermaid |
| 242 | +flowchart LR |
| 243 | + App["App runtime\nDomain/Data/Infra/Persistence"] |
| 244 | + Snapshot["Snapshot generation\nPersistence + WidgetCore"] |
| 245 | + AppGroup["App Group storage\nShared defaults"] |
| 246 | + WidgetCore["WidgetCore\nSnapshot models\nFactories"] |
| 247 | + WidgetExtension["Widget extension\nWidgetKit UI"] |
| 248 | +
|
| 249 | + App --> Snapshot |
| 250 | + Snapshot --> AppGroup |
| 251 | + AppGroup --> WidgetCore |
| 252 | + WidgetCore --> WidgetExtension |
| 253 | +``` |
| 254 | + |
| 255 | +Widget UI should consume snapshot data. It should not fetch app services or domain repositories directly. |
| 256 | + |
| 257 | +## Verification flow |
| 258 | + |
| 259 | +```mermaid |
| 260 | +flowchart TD |
| 261 | + Changed["Files changed"] |
| 262 | + Swift{"Swift/iOS project code changed?"} |
| 263 | + Docs{"Docs or harness only?"} |
| 264 | + Xcode["Build with Xcode Local MCP"] |
| 265 | + Diff["Inspect git diff scope"] |
| 266 | + NoBuild["No iOS build required"] |
| 267 | + Report["Report verification result"] |
| 268 | +
|
| 269 | + Changed --> Swift |
| 270 | + Swift -->|Yes| Xcode |
| 271 | + Swift -->|No| Docs |
| 272 | + Docs -->|Yes| NoBuild |
| 273 | + Docs -->|No| Diff |
| 274 | + Xcode --> Diff |
| 275 | + NoBuild --> Diff |
| 276 | + Diff --> Report |
| 277 | +``` |
| 278 | + |
| 279 | +## Required working notes |
| 280 | + |
| 281 | +Before editing architecture code, the AI should be able to answer these questions: |
| 282 | + |
| 283 | +1. What layer owns the changed concept today? |
| 284 | +2. What layer should own it after the change? |
| 285 | +3. Which imports prove the current dependency direction? |
| 286 | +4. Which target dependency will change? |
| 287 | +5. Does the change expose an external SDK outside its current boundary? |
| 288 | +6. Does the change affect WidgetCore or WidgetExtension boundaries? |
| 289 | +7. Is this change inside the current issue or PR scope? |
| 290 | +8. Is user confirmation required before editing? |
| 291 | + |
| 292 | +## Completion checklist |
| 293 | + |
| 294 | +- DevLog-specific rules were loaded. |
| 295 | +- Current files and imports were inspected. |
| 296 | +- Ambiguous architecture decisions were confirmed by the user. |
| 297 | +- Swift logic was preserved unless explicitly approved. |
| 298 | +- Diff scope was checked. |
| 299 | +- Xcode Local MCP build was used for Swift/iOS code changes. |
| 300 | +- Docs-only or harness-only changes were reported as such, without claiming app build verification. |
0 commit comments