Skip to content
Merged
10 changes: 5 additions & 5 deletions .agents/roles.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Planner must produce this packet before handing work to another role.
- Stop conditions:
```

Use `Architecture risk: possible` when the task touches module boundaries, imports, target dependencies, DI, repository or service contracts, Widget flow, Firebase or SDK placement, StorePattern boundaries, or architecture documentation.
Use `Architecture risk: possible` when the task touches module boundaries, imports, target dependencies, DI, repository or service contracts, Widget flow, `ThirdParty` package linkage, StorePattern boundaries, or architecture documentation.

## Role activation

Expand Down Expand Up @@ -249,7 +249,7 @@ Output:

Architecture Watcher is a read-only gate for DevLog boundaries.

Use it when a task touches module boundaries, file ownership, layer dependencies, DI assembly, repository or service contracts, widget data flow, Firebase dependency placement, external SDK placement, StorePattern responsibilities, or architecture documentation.
Use it when a task touches module boundaries, file ownership, layer dependencies, DI assembly, repository or service contracts, widget data flow, `ThirdParty` package linkage, StorePattern responsibilities, or architecture documentation.

Must read before reviewing:

Expand All @@ -263,7 +263,7 @@ Must inspect:
- Source imports in changed Swift files.
- Relevant `Project.swift`, `Workspace.swift`, or target dependency changes.
- Layer ownership before and after the change.
- External SDK exposure.
- `ThirdParty` package product linkage and whether it remains free of DevLog application behavior.
- Same-layer dependency injection.
- Widget, WidgetCore, and WidgetExtension boundaries when widget flow is touched.
- Presentation `StorePattern` responsibility boundaries when Presentation feature logic is touched.
Expand All @@ -272,7 +272,7 @@ Must not:

- Edit files.
- Approve ambiguous ownership by assumption.
- Treat a manifest-only target dependency as permission for a source-level architecture dependency.
- Treat a manifest-only target dependency as permission for a source-level DevLog architecture dependency. A direct `ThirdParty` dependency permits only imports of its external package products.
- Hide architecture decisions inside build-fix wording.

Output:
Expand All @@ -285,7 +285,7 @@ Output:
- Owning target:
- Dependency direction:
- Target dependency impact:
- SDK placement:
- ThirdParty linkage:
- Same-layer DI:
- Widget boundary:
- StorePattern:
Expand Down
50 changes: 29 additions & 21 deletions .agents/rules/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Use this reference with `AGENTS.md`, `.agents/rules/general.md`, and `.agents/ro

This repository is a Tuist-generated, workspace-based modular iOS app. There is no root `Package.swift`; module projects are generated from `Workspace.swift` and each module's `Project.swift`.

`ThirdParty` is an external package registry, not a DevLog application layer. It owns Swift Package declarations and product linkage only, has no DevLog target dependency, and any target including `Domain` may depend on it when that target imports an external package product.

## When to use

Read this file before work that changes any of these areas:
Expand Down Expand Up @@ -157,17 +159,18 @@ flowchart TD

| Layer | Owns | Allowed direction | Ask before |
| --- | --- | --- | --- |
| `Core` | DI primitives, logger, shared value/query types, display options, activity kinds, lightweight widget bridge values | No DevLog layer dependency | Moving domain entities into Core |
| `Domain` | entities, repository protocols, use cases | Core only | Adding Data, Infra, Persistence, Presentation, App, Widget UI, or SDK dependency |
| `Data` | 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 |
| `Infra` | 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 |
| `Persistence` | local stores, image cache, non-widget app persistence | Data, Core | Adding WidgetCore, WidgetKit reload, Widget, widget snapshot generation, or widget bridge ownership |
| `Presentation` | 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 |
| `MarkdownRenderer` | public SwiftUI renderer and reference value, internal WebKit bridge, renderer resources, TypeScript Tooling, renderer tests | system frameworks only | Adding a DevLog application layer dependency, exposing WebKit bridge types, adding another Presentation importer, or re-exporting the module |
| `Widget` | 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 |
| `App` | composition root, lifecycle, assembler wiring, app target ownership for widget extension embedding | Concrete app layers | Moving feature logic into App |
| `WidgetCore` | widget snapshot models, factories, app-group keys/defaults store, deep links, pure snapshot logic | Core | Adding Domain, Data, Infra, Persistence, Presentation, App, or Widget dependency |
| `WidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore | Calling app/domain services directly |
| `ThirdParty` | external package declarations, product linkage, marker sources | No DevLog target dependency; may be depended on by any target | Adding DevLog feature, service, adapter, or layer dependency; changing package versions or products outside the requested scope |
| `Core` | DI primitives, logger, shared value/query types, display options, activity kinds, lightweight widget bridge values | No DevLog layer dependency; `ThirdParty` when needed | Moving domain entities into Core |
| `Domain` | entities, repository protocols, use cases | Core, `ThirdParty` when needed | Adding Data, Infra, Persistence, Presentation, App, or Widget UI dependency |
| `Data` | repository implementations, DTOs, mappers, data protocols, widget repository/updater/sync contracts | Domain, Core, `ThirdParty` when needed | Adding WidgetKit, storage, WidgetCore snapshot model/factory usage, or platform implementation details; moving concrete widget handlers into Data |
| `Infra` | application infrastructure service implementations for social login, network, metadata, and messaging | Data, Core, `ThirdParty` when needed | Adding any Domain dependency or SDK service contract coupling |
| `Persistence` | local stores, image cache, non-widget app persistence | Data, Core, `ThirdParty` when needed | Adding WidgetCore, WidgetKit reload, Widget, widget snapshot generation, or widget bridge ownership |
| `Presentation` | UI, view models, coordinators, presentation state, narrow presentation-scoped platform side effects | Domain, Core, `ThirdParty` when needed | Adding Data, Infra, Persistence, or App dependency; expanding platform service ownership beyond UI-side effects |
| `MarkdownRenderer` | public SwiftUI renderer and reference value, internal WebKit bridge, renderer resources, TypeScript Tooling, renderer tests | system frameworks, `ThirdParty` when needed | Adding a DevLog application layer dependency, exposing WebKit bridge types, adding another Presentation importer, or re-exporting the module |
| `Widget` | app-side widget bridge, sync bus implementation, sync/session handlers, snapshot generation/persistence orchestration, WidgetKit reload bridge, widget assembler | Data, Core, WidgetCore, `ThirdParty` when needed | Adding Domain, Infra, Persistence, Presentation, or App dependency |
| `App` | composition root, lifecycle, assembler wiring, app target ownership for widget extension embedding | Concrete app layers, `ThirdParty` for framework linking | Moving feature logic into App |
| `WidgetCore` | widget snapshot models, factories, app-group keys/defaults store, deep links, pure snapshot logic | Core, `ThirdParty` when needed | Adding Domain, Data, Infra, Persistence, Presentation, App, or Widget dependency |
| `WidgetExtension` | WidgetKit rendering and timeline plumbing | WidgetCore, `ThirdParty` when needed | Calling app/domain services directly |

## Presentation target structure

Expand Down Expand Up @@ -286,29 +289,33 @@ flowchart TD
OnlyShared -->|No| Ask
```

## External dependency flow
## System framework and ThirdParty dependency flow

Use this flow before introducing or moving imports such as Firebase, GoogleSignIn, AuthenticationServices, UserNotifications, LinkPresentation, Network, or WidgetKit.
Use this flow before introducing or moving framework imports. A Swift Package product declared by `ThirdParty` is available to any target including `Domain`; it requires that target's direct `ThirdParty` dependency but does not change DevLog layer ownership.

```mermaid
flowchart TD
Import["External framework import"]
Firebase{"Firebase/Auth/Firestore/Functions/Messaging?"}
SocialLogin{"GoogleSignIn or AuthenticationServices?"}
Import["Framework import"]
ThirdPartyProduct{"ThirdParty package product?"}
ThirdParty["Add direct ThirdParty target dependency"]
SystemFramework{"System framework?"}
SocialLogin{"AuthenticationServices?"}
SocialLoginClassification{"Existing presentation/data cancellation/error classification?"}
NetworkMeta{"Network or LinkPresentation implementation?"}
UserNotifications{"UserNotifications?"}
WidgetKit{"WidgetKit?"}
Infra["Prefer Infra"]
Infra["Prefer Infra implementation"]
ErrorClassification["Keep narrow in Data or Presentation only when matching the existing cancellation-classification pattern"]
PresentationBadge["Allow in Presentation only for established badge/UI side effects"]
Widget["Allow in Widget for app-side snapshot update/reload orchestration"]
WidgetExtension["Allow in WidgetExtension rendering/timeline code"]
Ask["Ask user before crossing layer"]

Import --> Firebase
Firebase -->|Yes| Infra
Firebase -->|No| SocialLogin
Import --> ThirdPartyProduct
ThirdPartyProduct -->|Yes| ThirdParty
ThirdPartyProduct -->|No| SystemFramework
SystemFramework -->|No| Ask
SystemFramework -->|Yes| SocialLogin
SocialLogin -->|Login implementation| Infra
SocialLogin -->|Presentation/data error classification| SocialLoginClassification
SocialLoginClassification -->|Matches existing pattern| ErrorClassification
Expand Down Expand Up @@ -381,7 +388,7 @@ Before editing architecture code, the AI should be able to answer these question
2. What layer should own it after the change?
3. Which imports prove the current dependency direction?
4. Which target dependency will change?
5. Does the change expose an external SDK outside its current boundary?
5. Does the change add a `ThirdParty` dependency or move DevLog behavior into `ThirdParty`?
6. Does the change affect WidgetCore or WidgetExtension boundaries?
7. Is this change inside the current issue or PR scope?
8. Is user confirmation required before editing?
Expand All @@ -391,6 +398,7 @@ Before editing architecture code, the AI should be able to answer these question
- DevLog-specific rules were loaded.
- Current files and imports were inspected.
- Ambiguous architecture decisions were confirmed by the user.
- `ThirdParty` remains free of DevLog target dependencies and application behavior.
- Swift logic was preserved unless explicitly approved.
- Diff scope was checked.
- Xcode Local MCP build was used for Swift/iOS code changes.
Expand Down
2 changes: 1 addition & 1 deletion .agents/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Implementer must not proceed on `Block` or `Needs Owner Decision`.
- Changed Swift imports.
- `Workspace.swift` and relevant `Project.swift` files.
- Current owner and proposed owner.
- External SDK placement.
- `ThirdParty` package linkage and external import policy.
- Same-layer DI.
- Widget and WidgetCore boundaries when touched.
- StorePattern reducer, side effect, and run responsibility when Presentation feature logic is touched.
Expand Down
3 changes: 2 additions & 1 deletion Application/App/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let project = Project(
disableBundleAccessors: true,
disableSynthesizedResourceAccessors: true
),
packages: DevLogPackages.defaultPackages,
packages: [],
settings: .devlogProject(versionXcconfigPath: "../Shared/Version.xcconfig"),
targets: [
.target(
Expand All @@ -32,6 +32,7 @@ let project = Project(
],
dependencies: [
.project(target: "Presentation", path: "../Presentation"),
.project(target: "ThirdParty", path: "../../Libraries/ThirdParty"),
.project(target: "Persistence", path: "../Persistence"),
.project(target: "Infra", path: "../Infra"),
.project(target: "Widget", path: "../Widget"),
Expand Down
2 changes: 1 addition & 1 deletion Application/Core/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ let project = Project.devlogFramework(
versionXcconfigPath: "../Shared/Version.xcconfig",
frameworkInfoPlistPath: "../Shared/InfoPlists/Framework-Info.plist",
testsInfoPlistPath: "../Shared/InfoPlists/UnitTests-Info.plist",
packages: DevLogPackages.defaultPackages,
packages: [],
hasTests: false
)
2 changes: 1 addition & 1 deletion Application/Data/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let project = Project.devlogFramework(
versionXcconfigPath: "../Shared/Version.xcconfig",
frameworkInfoPlistPath: "../Shared/InfoPlists/Framework-Info.plist",
testsInfoPlistPath: "../Shared/InfoPlists/UnitTests-Info.plist",
packages: DevLogPackages.defaultPackages,
packages: [],
dependencies: [
.project(target: "Domain", path: "../Domain"),
.project(target: "Core", path: "../Core"),
Expand Down
2 changes: 1 addition & 1 deletion Application/Domain/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let project = Project.devlogFramework(
versionXcconfigPath: "../Shared/Version.xcconfig",
frameworkInfoPlistPath: "../Shared/InfoPlists/Framework-Info.plist",
testsInfoPlistPath: "../Shared/InfoPlists/UnitTests-Info.plist",
packages: DevLogPackages.defaultPackages,
packages: [],
dependencies: [
.project(target: "Core", path: "../Core")
],
Expand Down
8 changes: 6 additions & 2 deletions Application/Infra/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ let project = Project.devlogFramework(
versionXcconfigPath: "../Shared/Version.xcconfig",
frameworkInfoPlistPath: "../Shared/InfoPlists/Framework-Info.plist",
testsInfoPlistPath: "../Shared/InfoPlists/UnitTests-Info.plist",
packages: DevLogPackages.infraPackages,
packages: [],
dependencies: [
.project(target: "Data", path: "../Data"),
.project(target: "Core", path: "../Core"),
] + DevLogPackages.infraPackageDependencies,
.project(target: "ThirdParty", path: "../../Libraries/ThirdParty"),
],
testDependencies: [
.project(target: "ThirdParty", path: "../../Libraries/ThirdParty"),
],
hasTests: true
)
2 changes: 1 addition & 1 deletion Application/Persistence/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let project = Project.devlogFramework(
versionXcconfigPath: "../Shared/Version.xcconfig",
frameworkInfoPlistPath: "../Shared/InfoPlists/Framework-Info.plist",
testsInfoPlistPath: "../Shared/InfoPlists/UnitTests-Info.plist",
packages: DevLogPackages.defaultPackages,
packages: [],
dependencies: [
.project(target: "Data", path: "../Data"),
.project(target: "Core", path: "../Core"),
Expand Down
28 changes: 19 additions & 9 deletions Application/Presentation/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ let frameworkBuildSettings = Settings.devlog(
]
)

let thirdPartyDependency: TargetDependency = .project(
target: "ThirdParty",
path: "../../Libraries/ThirdParty"
)

let project = Project(
name: "Presentation",
options: .options(
disableBundleAccessors: true,
disableSynthesizedResourceAccessors: true
),
packages: DevLogPackages.presentationPackages,
packages: [],
settings: .devlogProject(versionXcconfigPath: versionXcconfigPath),
targets: [
.target(
Expand All @@ -42,8 +47,7 @@ let project = Project(
target: "MarkdownRenderer",
path: "../../Libraries/MarkdownRenderer"
),
.package(product: "ComposableArchitecture"),
.package(product: "OrderedCollections")
thirdPartyDependency,
],
settings: frameworkBuildSettings
),
Expand All @@ -63,7 +67,8 @@ let project = Project(
dependencies: [
.project(target: "Domain", path: "../Domain"),
.project(target: "Core", path: "../Core"),
.target(name: "PresentationShared")
.target(name: "PresentationShared"),
thirdPartyDependency,
],
settings: .devlog(
base: [
Expand Down Expand Up @@ -107,7 +112,8 @@ let project = Project(
],
dependencies: [
.target(name: "HomeTab"),
.target(name: "PresentationShared")
.target(name: "PresentationShared"),
thirdPartyDependency,
],
settings: .devlog(
base: [
Expand Down Expand Up @@ -151,7 +157,8 @@ let project = Project(
],
dependencies: [
.target(name: "TodayTab"),
.target(name: "PresentationShared")
.target(name: "PresentationShared"),
thirdPartyDependency,
],
settings: .devlog(
base: [
Expand Down Expand Up @@ -197,7 +204,8 @@ let project = Project(
.project(target: "Domain", path: "../Domain"),
.project(target: "Core", path: "../Core"),
.target(name: "NotificationTab"),
.target(name: "PresentationShared")
.target(name: "PresentationShared"),
thirdPartyDependency,
],
settings: .devlog(
base: [
Expand Down Expand Up @@ -243,7 +251,8 @@ let project = Project(
.project(target: "Domain", path: "../Domain"),
.project(target: "Core", path: "../Core"),
.target(name: "ProfileTab"),
.target(name: "PresentationShared")
.target(name: "PresentationShared"),
thirdPartyDependency,
],
settings: .devlog(
base: [
Expand Down Expand Up @@ -293,7 +302,8 @@ let project = Project(
.project(target: "Domain", path: "../Domain"),
.project(target: "Core", path: "../Core"),
.target(name: "Entry"),
.target(name: "PresentationShared")
.target(name: "PresentationShared"),
thirdPartyDependency,
],
settings: .devlog(
base: [
Expand Down
2 changes: 1 addition & 1 deletion Application/Widget/Project.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let project = Project.devlogFramework(
versionXcconfigPath: "../Shared/Version.xcconfig",
frameworkInfoPlistPath: "../Shared/InfoPlists/Framework-Info.plist",
testsInfoPlistPath: "../Shared/InfoPlists/UnitTests-Info.plist",
packages: DevLogPackages.defaultPackages,
packages: [],
dependencies: [
.project(target: "Data", path: "../Data"),
.project(target: "Core", path: "../Core"),
Expand Down
Loading
Loading