project-structure-and-packages

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Project structure & packages

项目结构与 package

The layout is a feedback loop, not decoration: structure exists so that "what imports Flutter?", "what has no test?", and "does this feature reach into another?" are answerable by
grep
and
ls
, not by trust. Default to one Flutter package (the app), organised feature-first under
lib/features/
over a shared foundation. Reach for a second, pure-Dart package only when a body of logic earns the isolation. A
pubspec.yaml
is the audit artifact — a package cannot import what it does not declare, so the dependency list is the layer boundary.
This skill OWNS the physical directory tree (D1). Other skills reference it rather than drawing their own.
Read the reference for the task at hand:
  • references/single-package-layout.md
    — the default one-package feature-first tree, the shared-foundation folders, placement-by-failure, the deliberately-absent list.
  • references/layering-and-dependencies.md
    — the downward-only DAG, feature isolation, pubspec-as-audit, the compile-firewall insight.
  • references/workspace-and-packages.md
    — when (and how) to extract a pure-Dart package: barrel-over-src,
    resolution: workspace
    , naming the core generically.
Run
scripts/check_structure.sh
and
scripts/check_import_boundaries.sh
before a PR. The purity check auto-detects only pure roots named
*_core
; a pure package with a plain generic name (
money
,
scheduling
) must be passed explicitly, e.g.
PURE_DIR=packages/money scripts/check_import_boundaries.sh
.
目录布局是一种反馈闭环,而不是装饰:建立结构,是为了让“哪些代码导入了 Flutter?”“哪些内容没有测试?”“这个 feature 是否伸入了另一个 feature?”这些问题能够通过
grep
和
ls
回答,而不是依赖信任。默认采用一个 Flutter package(应用本身),在 shared foundation 之上,以 feature-first 方式组织到
lib/features/
下。只有当某一部分逻辑确实值得隔离时,才引入第二个 pure-Dart package。
pubspec.yaml
是 audit artifact——package 无法导入未声明的依赖,因此依赖列表就是layer boundary。
此 skill 拥有物理目录树(D1)的所有权。其他 skill 引用该目录树,而不是各自定义一套。
针对当前任务阅读参考文档:
  • references/single-package-layout.md
    ——默认的单 package、feature-first 目录树,shared-foundation 目录,按 failure 归属放置对象,以及有意缺省的目录清单。
  • references/layering-and-dependencies.md
    ——向下单向 DAG、feature isolation、pubspec audit 以及 compile-firewall 机制。
  • references/workspace-and-packages.md
    ——何时以及如何提取 pure-Dart package:barrel-over-src、
    resolution: workspace
    ,以及 core 的通用命名方式。
在提交 PR 前运行
scripts/check_structure.sh
和
scripts/check_import_boundaries.sh
。purity check 只会自动检测名称以
*_core
结尾的 pure root;如果 pure package 使用普通通用名称(
money
、
scheduling
),则必须显式传入,例如
PURE_DIR=packages/money scripts/check_import_boundaries.sh
。

Non-negotiable rules

不可协商的规则

  1. Single package by default. A small-to-medium app is one Flutter package; its
    pubspec.lock
    is committed (it is an app, not a library). Do not add
    packages/
    , Melos, or a Dart workspace until a body of pure logic genuinely earns isolation (see
    references/workspace-and-packages.md
    ). Multiplying packages before there is anything to multiply buys nothing and costs days.
  2. Feature-first inside
    lib/features/
    , over a shared foundation.
    Each screen/surface is a folder under
    lib/features/<feature>/
    (
    presentation/
    +
    <feature>_notifier.dart
    ; optional
    application/
    ,
    domain/
    , rarely
    data/
    ). The cross-cutting foundation splits by technical role at the top level —
    core/
    (pure),
    data/
    (repositories + DB),
    services/
    (side-effect ports),
    routing/
    ,
    theme/
    ,
    l10n/
    — because those files serve every feature.
    main.dart
    is a thin entry that calls
    bootstrap()
    ;
    bootstrap.dart
    is the composition root;
    app.dart
    builds
    MaterialApp.router
    .
  3. No grab-bags, no unnamed nesting. There is no hard depth cap — feature-first naturally nests (
    features/orders/presentation/
    ). The rule is that every folder has a named responsibility: no
    utils/
    /
    helpers/
    /
    common/
    /
    misc/
    , no grab-bag
    shared/
    .
    core/
    is the SANCTIONED pure-foundation home, not a junk drawer.
  4. Place a class in the layer whose failure it owns. Talks to the DB →
    data/
    . Talks to a platform channel →
    services/native/
    . A pure value type / calculator / the Clock seam →
    core/
    (or a feature's
    domain/
    if truly feature-local). Everything else → the feature that renders it. This is a deterministic rule, not taste.
  5. pubspec.yaml
    is the audit artifact — declare the audit-minimal dependency set.
    An unused dependency is a review reject: it muddies the claim the manifest makes. The narrowest dep list keeps each layer boundary provable by reading one file.
  6. Dependencies point one way, downward only; features never import features.
    features → services/data → core
    . No lower layer imports an upper one; no shared code imports the composition root. A feature folder NEVER imports another feature folder — share via
    core
    /
    data
    /
    services
    or navigate by route (see
    navigation-and-routing
    ). A cycle is a design defect: the analyzer resolves the whole graph in one context, so an upward or cross-feature import is an error, not a runtime surprise.
  7. A pure-Dart core depends on
    meta
    only — the missing Flutter import is a compile firewall.
    In-app,
    lib/core/
    holds only pure foundation (value objects,
    Result
    /
    Failure
    , the Clock seam, calculators) and reads no wall clock. Time comes from
    package:clock
    's
    Clock
    (ambient
    clock
    , or a
    Clock
    param), never
    DateTime.now()
    , never a bespoke
    ClockService
    — see
    service-boundary-and-native
    . When such logic is extracted to a package, the empty-of-Flutter manifest makes a stray
    Widget
    or
    DateTime.now()
    a build error.
  8. One public barrel over a private
    lib/src/
    — for PACKAGES only.
    A shared package exposes exactly one library
    lib/<package>.dart
    that exports the stable surface; everything else lives under
    lib/src/
    , which no other package may import. The app package uses neither barrels nor
    lib/src/
    : it has no external importers, and barrels add analyzer cost plus circular-import risk for zero benefit.
  9. No junk-drawer folders. No
    utils/
    ,
    helpers/
    ,
    common/
    ,
    misc/
    , or a grab-bag
    shared/
    . A helper either lives beside the type it serves (a same-file extension) or is a named service/value type with a real home.
    core/
    is explicitly NOT a junk drawer — it is the named pure-foundation layer.
  10. Tests mirror
    lib/
    1:1.
    test/data/order_repository_test.dart
    for
    lib/data/order_repository.dart
    . "What has no test?" then reduces to a diff of two
    ls
    outputs. Cross-cutting assertions that belong to no single file get their own folder (
    test/policy/
    ); shared fakes go in
    test/support/
    .
  11. One primary declaration per file; filename = that declaration in
    snake_case
    .
    order_repository.dart
    holds
    OrderRepository
    . The one exception: a
    sealed
    hierarchy lives in a single file, because that file is the closed set the compiler enforces.
  12. Package imports everywhere; never mix with relative.
    always_use_package_imports
    . Mixing
    package:
    and relative paths lets the same member resolve two ways, producing two distinct runtime types. Package imports also survive file moves and are greppable.
  1. 默认使用单 package。 小型到中型应用只使用一个 Flutter package;其
    pubspec.lock
    必须提交到版本控制(它是 app,而不是 library)。不要添加
    packages/
    、Melos 或 Dart workspace,除非某部分 pure logic 确实值得隔离(参见
    references/workspace-and-packages.md
    )。在还没有可拆分内容之前增加 package 只会一无所获,却会耗费数日。
  2. 在
    lib/features/
    内采用 feature-first,而不是按 shared foundation 组织。
    每个 screen/surface 都是
    lib/features/<feature>/
    下的一个目录(
    presentation/
    +
    <feature>_notifier.dart
    ;可选
    application/
    、
    domain/
    ,很少使用
    data/
    )。跨 feature 的 shared foundation 在顶层按技术角色拆分——
    core/
    (pure)、
    data/
    (repository + DB)、
    services/
    (side-effect port)、
    routing/
    、
    theme/
    、
    l10n/
    ——因为这些文件会服务所有 feature。
    main.dart
    是调用
    bootstrap()
    的精简入口;
    bootstrap.dart
    是 composition root;
    app.dart
    构建
    MaterialApp.router
    。
  3. 禁止杂物袋和未命名嵌套。 目录深度没有硬性上限——feature-first 自然会产生嵌套结构(
    features/orders/presentation/
    )。规则在于每个目录都有明确命名的职责:不得使用
    utils/
    、
    helpers/
    、
    common/
    、
    misc/
    ,也不得使用 grab-bag
    shared/
    。
    core/
    是唯一获准的 pure-foundation 目录,而不是 junk drawer。
  4. 将类放在其负责 failure 类型的 layer。 与 DB 通信 →
    data/
    。与 platform channel 通信 →
    services/native/
    。Pure value type、calculator 或 Clock seam →
    core/
    (如果确实仅属于某个 feature,也可以放在该 feature 的
    domain/
    )。其他所有内容 → 负责渲染它的 feature。这是一条确定性规则,而不是凭个人偏好决定。
  5. pubspec.yaml
    是 audit artifact——声明满足审计的最小依赖集。
    未使用的依赖会直接导致审查拒绝:它会模糊 manifest 所表达的声明。最窄的依赖列表可以让你仅通过阅读一个文件就证明各 layer boundary。
  6. 依赖只能单向指向下方;feature 之间绝不互相导入。
    features → services/data → core
    。低层不得导入高层;shared code 不得导入 composition root。Feature 目录绝不导入另一个 feature 目录——应通过
    core
    /
    data
    /
    services
    共享,或者通过 route 进行导航(参见
    navigation-and-routing
    )。Cycle 属于设计缺陷:analyzer 会在同一个 context 中解析整个 graph,因此向上导入或跨 feature 导入是错误,而不只是运行时才会出现的意外。
  7. Pure-Dart core 仅依赖
    meta
    ——缺少 Flutter import 就构成 compile firewall。
    在 app 内,
    lib/core/
    只存放 pure foundation(value object、
    Result
    /
    Failure
    、Clock seam、calculator),并且不读取 wall clock。时间来自
    package:clock
    的
    Clock
    (ambient
    clock
    ,或
    Clock
    参数),绝不能使用
    DateTime.now()
    ,也不能创建定制的
    ClockService
    ——参见
    service-boundary-and-native
    。当这类逻辑被提取为 package 时,不含 Flutter 的 manifest 会让误用的
    Widget
    或
    DateTime.now()
    直接触发 build error。
  8. 一个公共 barrel 覆盖私有
    lib/src/
    ——仅适用于 PACKAGES。
    Shared package 只暴露一个 library
    lib/<package>.dart
    ,用于导出稳定 API;其他所有内容都位于
    lib/src/
    下,任何其他 package 都不得导入。App package 两者都不使用:barrel 和
    lib/src/
    :它没有外部 importer,而 barrel 会增加 analyzer cost 和 circular-import risk,却没有带来任何收益。
  9. 禁止 junk-drawer 目录。 不得使用
    utils/
    、
    helpers/
    、
    common/
    、
    misc/
    或 grab-bag
    shared/
    。Helper 要么放在其服务类型的旁边(例如同文件 extension),要么作为具有明确归属的命名 service/value type。
    core/
    明确不是 junk drawer——它是经过命名的 pure-foundation layer。
  10. 测试与
    lib/
    一一对应。
    test/data/order_repository_test.dart
    对应
    lib/data/order_repository.dart
    。这样,“哪些内容没有测试?”就简化为比较两次
    ls
    输出的差异。不属于任何单个文件的跨领域断言放入独立目录(
    test/policy/
    );shared fake 放入
    test/support/
    。
  11. 每个文件只有一个主要声明;文件名等于该声明并使用
    snake_case
    。
    order_repository.dart
    保存
    OrderRepository
    。唯一的例外是
    sealed
    hierarchy,因为该文件本身就是编译器强制约束的封闭集合。
  12. 所有位置都使用 package import;绝不能混用相对 import。 启用
    always_use_package_imports
    。混用
    package:
    和相对路径会让同一个成员通过两种方式解析,产生两个不同的 runtime type。Package import 在文件移动后依然有效,而且便于 grep。

Default single-package tree

默认单 package 目录树

├── analysis_options.yaml   # the lint safety net (see lint-and-style-config)
├── pubspec.yaml            # pubspec.lock is COMMITTED — this is an app
├── lib/
│   ├── main.dart           # thin entry: runs bootstrap(). Nothing else.
│   ├── bootstrap.dart      # composition root: build infra, override providers, error net, runApp (see app-startup-and-bootstrap)
│   ├── app.dart            # MaterialApp.router, theme wiring, ProviderScope child
│   ├── core/               # PURE foundation, NO Flutter: value objects, Result/Failure, Clock seam, calculators
│   ├── data/               # shared data layer: Drift db, DAOs, repositories, row→model mappers
│   ├── services/           # injectable side-effect ports + impls; native/ quarantines MethodChannel
│   │   └── native/
│   ├── routing/            # the single go_router config, typed routes, guards (see navigation-and-routing)
│   ├── theme/              # ThemeExtension token sets, ColorScheme (see design-system-structure)
│   ├── l10n/               # generated AppLocalizations + ARB (see i18n-rtl-l10n)
│   └── features/           # BY FEATURE — one folder per surface
│       └── orders/
│           ├── presentation/
│           │   ├── orders_screen.dart
│           │   ├── orders_notifier.dart   # the ViewModel: a Riverpod Notifier (see state-management-riverpod)
│           │   └── widgets/
│           │       └── order_tile.dart
│           ├── application/               # OPTIONAL: use-cases spanning repositories
│           └── domain/                    # OPTIONAL: feature-local models
├── test/                   # mirrors lib/ 1:1
└── integration_test/       # only what unit/widget tests cannot reach
A feature's
data/
folder is usually ABSENT — features read the shared repositories in
lib/data/
. See
references/single-package-layout.md
for the full tree and the deliberately-absent list.
├── analysis_options.yaml   # the lint safety net (see lint-and-style-config)
├── pubspec.yaml            # pubspec.lock is COMMITTED — this is an app
├── lib/
│   ├── main.dart           # thin entry: runs bootstrap(). Nothing else.
│   ├── bootstrap.dart      # composition root: build infra, override providers, error net, runApp (see app-startup-and-bootstrap)
│   ├── app.dart            # MaterialApp.router, theme wiring, ProviderScope child
│   ├── core/               # PURE foundation, NO Flutter: value objects, Result/Failure, Clock seam, calculators
│   ├── data/               # shared data layer: Drift db, DAOs, repositories, row→model mappers
│   ├── services/           # injectable side-effect ports + impls; native/ quarantines MethodChannel
│   │   └── native/
│   ├── routing/            # the single go_router config, typed routes, guards (see navigation-and-routing)
│   ├── theme/              # ThemeExtension token sets, ColorScheme (see design-system-structure)
│   ├── l10n/               # generated AppLocalizations + ARB (see i18n-rtl-l10n)
│   └── features/           # BY FEATURE — one folder per surface
│       └── orders/
│           ├── presentation/
│           │   ├── orders_screen.dart
│           │   ├── orders_notifier.dart   # the ViewModel: a Riverpod Notifier (see state-management-riverpod)
│           │   └── widgets/
│           │       └── order_tile.dart
│           ├── application/               # OPTIONAL: use-cases spanning repositories
│           └── domain/                    # OPTIONAL: feature-local models
├── test/                   # mirrors lib/ 1:1
└── integration_test/       # only what unit/widget tests cannot reach
Feature 的
data/
目录通常不存在——feature 会读取
lib/data/
中的 shared repository。完整目录树和有意缺省的目录清单见
references/single-package-layout.md
。

Placement decision procedure

放置对象决策流程

dart
// Q: where does OrderRepository go?  A: it owns DB failure → lib/data/
class OrderRepository {
  OrderRepository(this._db);
  final AppDatabase _db;

  // Rows never escape this class; the UI is owed a value type, not a raw row.
  Future<List<Order>> loadAll() async {
    final rows = await _db.selectOrders();          // package:drift confined here
    return rows.map(Order.fromRow).toList();
  }
}
dart
// Q: where does PaymentChannel go?  A: it constructs a MethodChannel → lib/services/native/
// One `grep -rn MethodChannel lib/` outside services/native/ is a review failure.
class PaymentChannel {
  static const _channel = MethodChannel('app/payment');
  Future<bool> canPay() async => await _channel.invokeMethod('canPay') ?? false;
}
  • A widget or model reused by 2+ features moves DOWN to
    lib/core/
    (pure) or
    lib/data/
    , or up to a named shared-widget home — never into one feature's folder that another then imports.
  • A new surface gets its own folder under
    lib/features/
    even for a single file — consistency beats saving a line. Register its route into
    routing/
    (see
    scaffold-feature-module
    ,
    navigation-and-routing
    ).
  • Read platform/accessibility state (
    MediaQuery.textScalerOf
    ) in the widget at build time, not via a helper file or a provider —
    MediaQuery
    is already reactive. App state via Riverpod; platform state via
    BuildContext
    . See
    accessibility-as-code
    .
dart
// Q: where does OrderRepository go?  A: it owns DB failure → lib/data/
class OrderRepository {
  OrderRepository(this._db);
  final AppDatabase _db;

  // Rows never escape this class; the UI is owed a value type, not a raw row.
  Future<List<Order>> loadAll() async {
    final rows = await _db.selectOrders();          // package:drift confined here
    return rows.map(Order.fromRow).toList();
  }
}
dart
// Q: where does PaymentChannel go?  A: it constructs a MethodChannel → lib/services/native/
// One `grep -rn MethodChannel lib/` outside services/native/ is a review failure.
class PaymentChannel {
  static const _channel = MethodChannel('app/payment');
  Future<bool> canPay() async => await _channel.invokeMethod('canPay') ?? false;
}
  • 被 2 个及以上 feature 复用的 widget 或 model 应向下移动到
    lib/core/
    (pure)或
    lib/data/
    ,或者向上移动到命名明确的 shared-widget 目录;绝不能放在某个 feature 的目录中再由其他 feature 导入。
  • 新 surface 即使只有一个文件,也要在
    lib/features/
    下拥有自己的目录——一致性比节省一行代码更重要。将其 route 注册到
    routing/
    (参见
    scaffold-feature-module
    、
    navigation-and-routing
    )。
  • 在 widget build 时读取 platform/accessibility state(
    MediaQuery.textScalerOf
    ),不要通过 helper 文件或 provider 获取——
    MediaQuery
    本身已经是 reactive。App state 使用 Riverpod;platform state 使用
    BuildContext
    。参见
    accessibility-as-code
    。

When to extract a package (multi-package / workspace)

何时提取 package(multi-package / workspace)

Extract a pure-Dart package the moment a body of logic satisfies all of: it is genuinely UI-free, it is worth testing headlessly in milliseconds, and the "imports no Flutter" guarantee is worth making a build error. Typical: money/units math, entities, deterministic algorithms. Below that bar it stays in
lib/core/
.
yaml
undefined
当某部分逻辑同时满足以下所有条件时,应立即提取为 pure-Dart package:它真正不依赖 UI;它值得进行毫秒级的 headless testing;并且值得将“imports no Flutter”的保证变成 build error。典型例子包括 money/unit 计算、entity 和确定性 algorithm。未达到这一标准的逻辑应继续保留在
lib/core/
中。
yaml
undefined

packages/domain_core/pubspec.yaml — the manifest IS the purity audit

packages/domain_core/pubspec.yaml — the manifest IS the purity audit

name: domain_core # a GENERIC name — never the brand, so a rename never churns the core publish_to: none resolution: workspace # member of the root workspace (modern successor to path:/overrides) environment: sdk: ^3.6.0 dependencies: meta: ^1.15.0 # the ONLY dependency. No flutter, no dart:io, no dart:ui, no clock. dev_dependencies: test: ^1.25.0 # plain
dart test
— NOT flutter_test

The root `pubspec.yaml` lists members under `workspace:`; there is **one** committed `pubspec.lock` at the root. The public barrel `lib/domain_core.dart` exports the stable surface; internals stay under `lib/src/`. Full ritual, dependency matrix, and gates in `references/workspace-and-packages.md`.

> **When multi-package (workspace) only.** Everything in this section — `packages/`, `resolution: workspace`, barrels, `lib/src/` — applies solely once you have decided to split. A single-package app does none of it.
name: domain_core # a GENERIC name — never the brand, so a rename never churns the core publish_to: none resolution: workspace # member of the root workspace (modern successor to path:/overrides) environment: sdk: ^3.6.0 dependencies: meta: ^1.15.0 # the ONLY dependency. No flutter, no dart:io, no dart:ui, no clock. dev_dependencies: test: ^1.25.0 # plain
dart test
— NOT flutter_test

根 `pubspec.yaml` 在 `workspace:` 下列出 member;根目录只提交**一个** `pubspec.lock`。公共 barrel `lib/domain_core.dart` 导出稳定 API;内部实现保留在 `lib/src/` 下。完整规范流程、依赖矩阵和门禁见 `references/workspace-and-packages.md`。

> **仅适用于 multi-package(workspace)。** 本节中的所有内容——`packages/`、`resolution: workspace`、barrels、`lib/src/`——仅在你决定拆分之后才适用。单 package 应用不采用其中任何一项。

Anti-patterns

反模式

  • Splitting the shared foundation feature-first (
    orders/
    ,
    settings/
    each with their own DB + repositories). The entity graph is shared; keep repositories/DB in
    lib/data/
    and pure types in
    lib/core/
    . Feature-first applies inside
    lib/features/
    , not to the data graph.
  • One feature folder importing another feature folder. It couples two surfaces that should meet only via
    core
    /
    data
    /
    services
    or a route. Lift the shared piece down, or navigate.
  • utils/
    ,
    helpers/
    ,
    common/
    ,
    misc/
    , or a grab-bag
    shared/
    .
    A junk drawer; every candidate belongs beside its owner or as a named type. (
    core/
    is fine — it is the named pure-foundation layer.)
  • Barrels or
    lib/src/
    in the single app package.
    No external importer justifies them; they add analyzer cost and circular-import risk.
    lib/src/
    is a PACKAGE convention only.
  • Constructing a
    MethodChannel
    inside a widget or repository.
    It cannot be faked and cannot be found in review. Quarantine it in
    services/native/
    .
  • Adding
    flutter
    ,
    dart:io
    , or
    DateTime.now()
    to
    lib/core/
    or a pure core package.
    It destroys the compile firewall and the headless test tier; inject the
    Clock
    (
    package:clock
    ) and seed instead.
  • path:
    +
    dependency_overrides
    instead of
    resolution: workspace
    .
    Defeats single-context resolution and hides the version clashes a workspace exists to surface.
  • Naming the core package after the brand (
    myapp_core
    ). A future rename churns every import. Name it for what it holds (
    domain_core
    ,
    money
    ).
  • A speculative dependency "for later." An unused edge muddies the audit and is a review reject.
  • Mixing
    package:
    and relative imports.
    Two import paths → two runtime types for one class.
  • 按 feature-first 拆分 shared foundation(
    orders/
    、
    settings/
    各自拥有 DB 和 repository)。Entity graph 是共享的;repository 和 DB 应保留在
    lib/data/
    ,pure type 应保留在
    lib/core/
    。Feature-first 适用于
    lib/features/
    内部,而不适用于 data graph。
  • 一个 feature 目录导入另一个 feature 目录。 这会耦合两个本应只通过
    core
    /
    data
    /
    services
    或 route 交互的 surface。应将共享部分向下提取,或者通过导航交互。
  • 使用
    utils/
    、
    helpers/
    、
    common/
    、
    misc/
    或 grab-bag
    shared/
    。
    这些都是 junk drawer;每个候选对象都应放在其 owner 旁边,或作为具有明确归属的命名 type。(
    core/
    没有问题——它是经过命名的 pure-foundation layer。)
  • 在单 app package 中使用 barrels 或
    lib/src/
    。
    没有外部 importer 来证明其必要性;它们只会增加 analyzer cost 和 circular-import risk。
    lib/src/
    仅是 PACKAGE 约定。
  • 在 widget 或 repository 内构造
    MethodChannel
    。
    它无法被 fake,也无法在审查时准确定位。必须将其隔离在
    services/native/
    中。
  • 向
    lib/core/
    或 pure core package 添加
    flutter
    、
    dart:io
    或
    DateTime.now()
    。
    这会破坏 compile firewall 和 headless test tier;应注入
    Clock
    (
    package:clock
    )并提供 seed。
  • 使用
    path:
    +
    dependency_overrides
    ,而不是
    resolution: workspace
    。
    这会破坏 single-context resolution,并隐藏 workspace 原本要暴露的版本冲突。
  • 使用品牌名称命名 core package(
    myapp_core
    )。未来的重命名会 churn 所有 import。应根据其承载的内容命名(
    domain_core
    、
    money
    )。
  • 添加“以后可能用到”的 speculative dependency。 未使用的依赖边会模糊 audit,并直接导致审查拒绝。
  • 混用
    package:
    和相对 import。
    两种 import 路径会让同一个 class 产生两个 runtime type。

Definition of done

完成定义

  • Default is a single Flutter package;
    pubspec.lock
    committed; no
    packages/
    /Melos/workspace unless a pure body of logic earned it.
  • Top level is the shared foundation (
    core/ data/ services/ routing/ theme/ l10n/
    ); features live under
    lib/features/<feature>/
    ;
    main.dart
    only runs
    bootstrap()
    .
  • No feature folder imports another feature folder; sharing happens via
    core
    /
    data
    /
    services
    or a route.
  • Every class sits in the layer whose failure it owns;
    MethodChannel
    only in
    services/native/
    ; rows never escape
    data/
    ; pure foundation and the Clock seam in
    core/
    .
  • No
    utils/
    /
    helpers/
    /
    common/
    /
    misc/
    /grab-bag-
    shared/
    folder anywhere;
    core/
    is present as the named pure-foundation layer, not a catch-all.
  • No
    lib/src/
    or barrel in the app package (they are PACKAGE conventions only).
  • test/
    mirrors
    lib/
    1:1; shared fakes in
    test/support/
    , cross-cutting checks in
    test/policy/
    .
  • One primary declaration per file, filename = declaration in
    snake_case
    (sealed hierarchy the sole exception);
    always_use_package_imports
    .
  • If a package was extracted: it is a
    resolution: workspace
    member, generically named, with one public barrel over
    lib/src/
    ; a pure core declares
    meta
    only (no
    flutter
    /
    dart:io
    /
    dart:ui
    /clock); one root
    pubspec.lock
    .
  • Dependency graph points downward only; no cross-package
    lib/src/
    import; deps are audit-minimal with no unused edge.
  • scripts/check_structure.sh
    and
    scripts/check_import_boundaries.sh
    pass.
  • 默认使用单个 Flutter package;提交
    pubspec.lock
    ;除非某部分 pure logic 确实值得隔离,否则不使用
    packages/
    、Melos 或 workspace。
  • 顶层为 shared foundation(
    core/
    、
    data/
    、
    services/
    、
    routing/
    、
    theme/
    、
    l10n/
    );feature 位于
    lib/features/<feature>/
    ;
    main.dart
    只运行
    bootstrap()
    。
  • Feature 目录之间不相互导入;共享通过
    core
    /
    data
    /
    services
    或 route 完成。
  • 每个 class 都位于其负责 failure 类型的 layer;
    MethodChannel
    只出现在
    services/native/
    ;row 不得逃逸
    data/
    ;pure foundation 和 Clock seam 位于
    core/
    。
  • 任何位置都没有
    utils/
    、
    helpers/
    、
    common/
    、
    misc/
    或 grab-bag
    shared/
    目录;
    core/
    作为经过命名的 pure-foundation layer 存在,而不是 catch-all。
  • App package 中没有
    lib/src/
    或 barrel;它们仅是 PACKAGE 约定。
  • test/
    与
    lib/
    一一对应;shared fake 位于
    test/support/
    ,跨领域检查位于
    test/policy/
    。
  • 每个文件只有一个主要声明,文件名等于声明名并使用
    snake_case
    (
    sealed
    hierarchy 是唯一例外);启用
    always_use_package_imports
    。
  • 如果提取了 package:它是
    resolution: workspace
    member,名称通用,使用一个公共 barrel 覆盖
    lib/src/
    ;pure core 仅声明
    meta
    (不包含
    flutter
    /
    dart:io
    /
    dart:ui
    /clock);根目录只有一个
    pubspec.lock
    。
  • 依赖 graph 仅向下指向;不存在跨 package 的
    lib/src/
    import;依赖满足最小审计要求,不存在未使用的依赖边。
  • scripts/check_structure.sh
    和
    scripts/check_import_boundaries.sh
    检查通过。

Related skills

相关 skill

  • flutter-architecture
    — the layered MVVM this folder shape realises (features-as-folders, foundations-as-packages, single write-path repositories); it keeps layer semantics and references this tree.
  • scaffold-feature-module
    — the fixed anatomy of one
    lib/features/<feature>/
    folder (View + 1:1
    <feature>_notifier.dart
    Notifier + scoped providers) and how it registers a route.
  • app-startup-and-bootstrap
    — owns
    main.dart
    /
    bootstrap.dart
    , the composition root ordering, and the global error net.
  • navigation-and-routing
    — owns
    lib/routing/
    (go_router config, typed routes, guards) that features register into.
  • naming-conventions
    — the role suffixes (
    Screen
    /
    Notifier
    /
    Repository
    /
    Service
    /
    Gateway
    ) and the file=primary-declaration rule referenced above.
  • dependency-hygiene
    — pubspec/lock mechanics, caret ranges, auditing a transitive tree before adding a dep.
  • codegen-and-toolchain
    — per-package
    build.yaml
    scoping and the commit-vs-gitignore decision for generated code.
  • lint-and-style-config
    — the
    analysis_options.yaml
    that promotes
    always_use_package_imports
    and silent-failure lints to errors.
  • service-boundary-and-native
    — the
    services/native/
    MethodChannel quarantine, the injectable Service/Gateway ports, and the
    Clock
    -via-
    clockProvider
    time seam.
  • value-objects-money-and-units
    — the canonical resident of
    lib/core/
    or an extracted pure-Dart core.
  • flutter-architecture
    ——实现该目录形态的分层 MVVM(features-as-folders、foundations-as-packages、single write-path repositories);它保留 layer 语义并引用此目录树。
  • scaffold-feature-module
    ——定义单个
    lib/features/<feature>/
    目录的固定结构(View + 1:1
    <feature>_notifier.dart
    Notifier + scoped provider),以及如何注册 route。
  • app-startup-and-bootstrap
    ——负责
    main.dart
    /
    bootstrap.dart
    、composition root 顺序和全局 error net。
  • navigation-and-routing
    ——负责
    lib/routing/
    (go_router config、typed route、guard),feature 会向其中注册。
  • naming-conventions
    ——定义角色后缀(
    Screen
    /
    Notifier
    /
    Repository
    /
    Service
    /
    Gateway
    )以及上文引用的 file=primary-declaration 规则。
  • dependency-hygiene
    ——pubspec/lock 机制、caret range,以及添加依赖前审计 transitive tree。
  • codegen-and-toolchain
    ——按 package 设置
    build.yaml
    scope,以及决定 generated code 应提交还是加入 gitignore。
  • lint-and-style-config
    ——通过
    analysis_options.yaml
    将
    always_use_package_imports
    和 silent-failure lint 提升为 error。
  • service-boundary-and-native
    ——
    services/native/
    MethodChannel 隔离、可注入的 Service/Gateway port,以及通过
    clockProvider
    获取时间的 Clock seam。
  • value-objects-money-and-units
    ——位于
    lib/core/
    或已提取 pure-Dart core 中的标准 value object。

References

参考资料