From a40bbd7d5d0437cd45e75bbd2db3825f58622436 Mon Sep 17 00:00:00 2001 From: 1morr Date: Tue, 29 Sep 2026 06:56:18 +0800 Subject: [PATCH 1/4] chore: split agent rules and specs into legacy and app The root AGENTS.md and flat specs load for every session, so work in app/ would start from old-app rules. Old rules move to lib/AGENTS.md and .trellis/spec/legacy/; spec_scope keeps a task to its package's spec. --- .claude/agents/trellis-check.md | 11 +- .claude/agents/trellis-implement.md | 11 +- .../SKILL.md | 9 +- .../references/android.md | 0 .../references/runtime-state.md | 0 .../references/windows.md | 4 +- .../scripts/ax_flatten.py | 0 .../scripts/msaa_tree.ps1 | 2 +- .../scripts/smtc_probe.ps1 | 2 +- .trellis/config.yaml | 12 ++ .../spec/guides/code-reuse-thinking-guide.md | 4 +- .../spec/guides/cross-layer-thinking-guide.md | 6 +- .trellis/spec/guides/index.md | 2 +- .trellis/spec/{ => legacy}/data/index.md | 6 +- .../spec/{ => legacy}/data/persistence.md | 2 +- .trellis/spec/{ => legacy}/data/sources.md | 0 .trellis/spec/{ => legacy}/services/audio.md | 0 .../services/download-and-auth.md | 0 .trellis/spec/{ => legacy}/services/index.md | 8 +- .../services/service-conventions.md | 0 .../spec/{ => legacy}/shared/code-style.md | 2 +- .../{ => legacy}/shared/errors-and-logging.md | 0 .trellis/spec/{ => legacy}/shared/index.md | 0 .trellis/spec/{ => legacy}/testing/index.md | 2 +- .../spec/{ => legacy}/testing/static-rules.md | 2 +- .../{ => legacy}/testing/test-conventions.md | 0 .../spec/{ => legacy}/ui/i18n-and-routing.md | 0 .trellis/spec/{ => legacy}/ui/index.md | 10 +- .trellis/spec/{ => legacy}/ui/riverpod.md | 0 .trellis/spec/{ => legacy}/ui/widgets.md | 0 AGENTS.md | 137 +++++------------- docs/README.md | 6 +- docs/adr/0012-network-layer-and-accounts.md | 2 +- docs/development.md | 2 +- lib/AGENTS.md | 91 ++++++++++++ orca.yaml | 2 +- 36 files changed, 192 insertions(+), 143 deletions(-) rename .claude/skills/{verify-on-device => verify-legacy-on-device}/SKILL.md (93%) rename .claude/skills/{verify-on-device => verify-legacy-on-device}/references/android.md (100%) rename .claude/skills/{verify-on-device => verify-legacy-on-device}/references/runtime-state.md (100%) rename .claude/skills/{verify-on-device => verify-legacy-on-device}/references/windows.md (98%) rename .claude/skills/{verify-on-device => verify-legacy-on-device}/scripts/ax_flatten.py (100%) rename .claude/skills/{verify-on-device => verify-legacy-on-device}/scripts/msaa_tree.ps1 (98%) rename .claude/skills/{verify-on-device => verify-legacy-on-device}/scripts/smtc_probe.ps1 (97%) rename .trellis/spec/{ => legacy}/data/index.md (83%) rename .trellis/spec/{ => legacy}/data/persistence.md (98%) rename .trellis/spec/{ => legacy}/data/sources.md (100%) rename .trellis/spec/{ => legacy}/services/audio.md (100%) rename .trellis/spec/{ => legacy}/services/download-and-auth.md (100%) rename .trellis/spec/{ => legacy}/services/index.md (87%) rename .trellis/spec/{ => legacy}/services/service-conventions.md (100%) rename .trellis/spec/{ => legacy}/shared/code-style.md (97%) rename .trellis/spec/{ => legacy}/shared/errors-and-logging.md (100%) rename .trellis/spec/{ => legacy}/shared/index.md (100%) rename .trellis/spec/{ => legacy}/testing/index.md (95%) rename .trellis/spec/{ => legacy}/testing/static-rules.md (97%) rename .trellis/spec/{ => legacy}/testing/test-conventions.md (100%) rename .trellis/spec/{ => legacy}/ui/i18n-and-routing.md (100%) rename .trellis/spec/{ => legacy}/ui/index.md (77%) rename .trellis/spec/{ => legacy}/ui/riverpod.md (100%) rename .trellis/spec/{ => legacy}/ui/widgets.md (100%) create mode 100644 lib/AGENTS.md diff --git a/.claude/agents/trellis-check.md b/.claude/agents/trellis-check.md index 7a9b6f19b..41d35288c 100644 --- a/.claude/agents/trellis-check.md +++ b/.claude/agents/trellis-check.md @@ -26,7 +26,8 @@ Look for the `` marker in your input above. ## Context Before checking, read: -- `.trellis/spec/` - Development guidelines +- Task `task.json`'s `package` field (`legacy` or `app`) - picks the spec tree and the `AGENTS.md` below +- `.trellis/spec//` - Development guidelines - Task `prd.md` - Requirements document - Task `design.md` - Technical design (if exists) - Task `implement.md` - Execution plan (if exists) @@ -59,7 +60,7 @@ git diff # View specific changes ### Step 2: Check Against Specs and Task Artifacts -Read the task's prd.md, design.md if present, and implement.md if present, then read relevant specs in `.trellis/spec/` to check code: +Read the task's prd.md, design.md if present, and implement.md if present, then read relevant specs in `.trellis/spec//` to check code: - Does it satisfy the task requirements - Does it follow the technical design and implementation plan when present @@ -79,15 +80,15 @@ After finding issues: ### Step 4: Run Verification -FMP is a Flutter app: "lint and typecheck" is `flutter analyze`. Run, in order: +FMP is a Flutter app: "lint and typecheck" is `flutter analyze`. Read the task's `package` from `task.json` first: `legacy` verifies against `lib/AGENTS.md`, `app` against `app/AGENTS.md`. Run, in order: 1. Codegen when a model or `*.i18n.json` changed, or `*.g.dart` is missing: `dart run build_runner build`, `dart run slang`. Stale codegen fails as a missing getter that looks like a source bug. 2. `dart format lib test tool`, then `flutter analyze`. -3. The tests for every changed area: the matching rows of AGENTS.md § Verification, plus the Quality Check section of each touched `.trellis/spec//index.md`. +3. The tests for every changed area: the matching rows of the package's `AGENTS.md` § Verification / § 驗證, plus the Quality Check section of each touched `.trellis/spec///index.md`. If anything fails, fix it and re-run. -On-device verification is mandatory for user-visible changes (AGENTS.md), but it needs the emulator and the `verify-on-device` skill, which you cannot run. Never mark it passed: report it as required, with what to observe, so the main session runs it. +On-device verification is mandatory for user-visible changes (the package's `AGENTS.md`), but it needs the emulator and the `verify-legacy-on-device` skill (`legacy`) or `verify-on-device` skill (`app`), which you cannot run. Never mark it passed: report it as required, with what to observe, so the main session runs it. --- diff --git a/.claude/agents/trellis-implement.md b/.claude/agents/trellis-implement.md index 3d87fb7e5..4103aa224 100644 --- a/.claude/agents/trellis-implement.md +++ b/.claude/agents/trellis-implement.md @@ -27,14 +27,15 @@ Look for the `` marker in your input above. Before implementing, read: - `.trellis/workflow.md` - Project workflow -- `.trellis/spec/` - Development guidelines +- Task `task.json`'s `package` field (`legacy` or `app`) - picks the spec tree and the `AGENTS.md` below +- `.trellis/spec//` - Development guidelines - Task `prd.md` - Requirements document - Task `design.md` - Technical design (if exists) - Task `implement.md` - Execution plan (if exists) ## Core Responsibilities -1. **Understand specs** - Read relevant spec files in `.trellis/spec/` +1. **Understand specs** - Read relevant spec files in `.trellis/spec//` 2. **Understand task artifacts** - Read prd.md, design.md if present, and implement.md if present 3. **Implement features** - Write code following specs and task artifacts 4. **Self-check** - Ensure code quality @@ -75,13 +76,13 @@ Read the task's prd.md, design.md if present, and implement.md if present: ### 4. Verify -FMP is a Flutter app: "lint and typecheck" is `flutter analyze`. +FMP is a Flutter app: "lint and typecheck" is `flutter analyze`. Read the task's `package` from `task.json` first: `legacy` verifies against `lib/AGENTS.md` § Verification, `app` against `app/AGENTS.md` § 驗證. 1. Codegen when a model or `*.i18n.json` changed, or `*.g.dart` is missing: `dart run build_runner build`, `dart run slang`. 2. `dart format lib test tool`, then `flutter analyze`. -3. The tests named by the matching rows of AGENTS.md § Verification, plus the tests you wrote. +3. The tests named by the matching rows of the package's § Verification / § 驗證, plus the tests you wrote. -If the change is user-visible, say so in the report: on-device verification is the main session's job (`verify-on-device` skill). +If the change is user-visible, say so in the report: on-device verification is the main session's job (`verify-legacy-on-device` skill for `legacy`, `verify-on-device` for `app`). --- diff --git a/.claude/skills/verify-on-device/SKILL.md b/.claude/skills/verify-legacy-on-device/SKILL.md similarity index 93% rename from .claude/skills/verify-on-device/SKILL.md rename to .claude/skills/verify-legacy-on-device/SKILL.md index 686524e33..d3f4ec9f7 100644 --- a/.claude/skills/verify-on-device/SKILL.md +++ b/.claude/skills/verify-legacy-on-device/SKILL.md @@ -1,11 +1,12 @@ --- -name: verify-on-device +name: verify-legacy-on-device description: >- - Run FMP on the Android emulator or the Windows desktop build and verify a + Legacy-app hotfixes only (the app under `app/` uses the `verify-on-device` + skill). Run FMP on the Android emulator or the Windows desktop build and verify a change against the live app: boot the emulator, install and launch, drive the UI, read Dart logs, hot reload, and inspect runtime state. Use after every change to UI pages or widgets, playback controls, source result rendering, or - layout-affecting strings — root AGENTS.md requires an on-device check for those + layout-affecting strings — `lib/AGENTS.md` requires an on-device check for those before reporting — and whenever asked to run, screenshot, tap, type into, or observe FMP on a device or emulator. --- @@ -94,7 +95,7 @@ line in its output means the Windows accessibility tree has frozen (see node with pixel and normalized centers: ```bash -PYTHONIOENCODING=utf-8 python .claude/skills/verify-on-device/scripts/ax_flatten.py --limit 30 +PYTHONIOENCODING=utf-8 python .claude/skills/verify-legacy-on-device/scripts/ax_flatten.py --limit 30 ``` Flutter's semantics surface through uiautomator, so labels, list rows and nav diff --git a/.claude/skills/verify-on-device/references/android.md b/.claude/skills/verify-legacy-on-device/references/android.md similarity index 100% rename from .claude/skills/verify-on-device/references/android.md rename to .claude/skills/verify-legacy-on-device/references/android.md diff --git a/.claude/skills/verify-on-device/references/runtime-state.md b/.claude/skills/verify-legacy-on-device/references/runtime-state.md similarity index 100% rename from .claude/skills/verify-on-device/references/runtime-state.md rename to .claude/skills/verify-legacy-on-device/references/runtime-state.md diff --git a/.claude/skills/verify-on-device/references/windows.md b/.claude/skills/verify-legacy-on-device/references/windows.md similarity index 98% rename from .claude/skills/verify-on-device/references/windows.md rename to .claude/skills/verify-legacy-on-device/references/windows.md index e7854cde3..16cc7dea0 100644 --- a/.claude/skills/verify-on-device/references/windows.md +++ b/.claude/skills/verify-legacy-on-device/references/windows.md @@ -11,7 +11,7 @@ Narrator running. The engine answers through MSAA only (checked in `flutter_windows.dll`, Flutter 3.47.1): ```bash -S=.claude/skills/verify-on-device/scripts +S=.claude/skills/verify-legacy-on-device/scripts powershell.exe -NoProfile -ExecutionPolicy Bypass -File $S/msaa_tree.ps1 -Filter button # [push button] '查看佇列' @(3156,1228 121x49) screen rect, physical px powershell.exe -NoProfile -ExecutionPolicy Bypass -File $S/msaa_tree.ps1 -Click '查看佇列' @@ -108,7 +108,7 @@ Read SMTC through WinRT, not the media flyout (the flyout dismisses on focus change). FMP does not need to be visible: ```bash -powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude/skills/verify-on-device/scripts/smtc_probe.ps1 -AppFilter fmp +powershell.exe -NoProfile -ExecutionPolicy Bypass -File .claude/skills/verify-legacy-on-device/scripts/smtc_probe.ps1 -AppFilter fmp ``` It prints `IsNextEnabled` / `IsPreviousEnabled` / `IsPlaybackPositionEnabled` / diff --git a/.claude/skills/verify-on-device/scripts/ax_flatten.py b/.claude/skills/verify-legacy-on-device/scripts/ax_flatten.py similarity index 100% rename from .claude/skills/verify-on-device/scripts/ax_flatten.py rename to .claude/skills/verify-legacy-on-device/scripts/ax_flatten.py diff --git a/.claude/skills/verify-on-device/scripts/msaa_tree.ps1 b/.claude/skills/verify-legacy-on-device/scripts/msaa_tree.ps1 similarity index 98% rename from .claude/skills/verify-on-device/scripts/msaa_tree.ps1 rename to .claude/skills/verify-legacy-on-device/scripts/msaa_tree.ps1 index 45e20af5d..98219f413 100644 --- a/.claude/skills/verify-on-device/scripts/msaa_tree.ps1 +++ b/.claude/skills/verify-legacy-on-device/scripts/msaa_tree.ps1 @@ -10,7 +10,7 @@ # Dump, with each element's screen rectangle in physical pixels: # # powershell.exe -NoProfile -ExecutionPolicy Bypass ` -# -File .claude/skills/verify-on-device/scripts/msaa_tree.ps1 [-Proc fmp] [-Filter button] +# -File .claude/skills/verify-legacy-on-device/scripts/msaa_tree.ps1 [-Proc fmp] [-Filter button] # # Click an element by its exact accessible name (after raising the window): # diff --git a/.claude/skills/verify-on-device/scripts/smtc_probe.ps1 b/.claude/skills/verify-legacy-on-device/scripts/smtc_probe.ps1 similarity index 97% rename from .claude/skills/verify-on-device/scripts/smtc_probe.ps1 rename to .claude/skills/verify-legacy-on-device/scripts/smtc_probe.ps1 index f8ad34780..5c269b26e 100644 --- a/.claude/skills/verify-on-device/scripts/smtc_probe.ps1 +++ b/.claude/skills/verify-legacy-on-device/scripts/smtc_probe.ps1 @@ -11,7 +11,7 @@ # System.Runtime.WindowsRuntime` fails there. # # powershell.exe -NoProfile -ExecutionPolicy Bypass ` -# -File .claude/skills/verify-on-device/scripts/smtc_probe.ps1 [-AppFilter fmp] +# -File .claude/skills/verify-legacy-on-device/scripts/smtc_probe.ps1 [-AppFilter fmp] # # Exit codes: 0 = at least one session found, 1 = no sessions, 2 = WinRT failed. diff --git a/.trellis/config.yaml b/.trellis/config.yaml index c52d5bfce..de849be5b 100644 --- a/.trellis/config.yaml +++ b/.trellis/config.yaml @@ -77,6 +77,18 @@ session_auto_commit: false # Default package used when --package is not specified. # default_package: frontend +packages: + legacy: + path: . + app: + path: app +default_package: app + +# FMP: inject only the active task's package spec (default_package when no +# task is active). Without it SessionStart lists every package's spec. +session: + spec_scope: active_task + #------------------------------------------------------------------------------- # Channel worker OOM guard #------------------------------------------------------------------------------- diff --git a/.trellis/spec/guides/code-reuse-thinking-guide.md b/.trellis/spec/guides/code-reuse-thinking-guide.md index e4f706eb1..9f3469981 100644 --- a/.trellis/spec/guides/code-reuse-thinking-guide.md +++ b/.trellis/spec/guides/code-reuse-thinking-guide.md @@ -19,8 +19,8 @@ static rules. Writing a second copy is usually what the rule catches. | Backend decisions both players share | `playback_end_reason_rules.dart`, `live_edge_seek_policy.dart`, `next_media_plan.dart` | | Playlist provider refresh after a mutation | `libraryInvalidationCoordinatorProvider` | | Duration text | `DurationFormatter` | -| Widgets: images, sliders, dialogs, menus, toasts, errors | the table in `../ui/widgets.md` | -| Test doubles and waits | `test/support/`, `test/support/fakes/` (`../testing/test-conventions.md`) | +| Widgets: images, sliders, dialogs, menus, toasts, errors | the table in `../legacy/ui/widgets.md` | +| Test doubles and waits | `test/support/`, `test/support/fakes/` (`../legacy/testing/test-conventions.md`) | ## When there are two near-copies diff --git a/.trellis/spec/guides/cross-layer-thinking-guide.md b/.trellis/spec/guides/cross-layer-thinking-guide.md index 62f651fab..b458ccc9a 100644 --- a/.trellis/spec/guides/cross-layer-thinking-guide.md +++ b/.trellis/spec/guides/cross-layer-thinking-guide.md @@ -10,7 +10,7 @@ media bytes: StreamResolutionService ─► MediaHandoff ─► audio backend / ``` Each arrow is a contract. An error usually becomes a user sentence once, at the -edge (`userMessageFor` — see `../shared/errors-and-logging.md`); the import path +edge (`userMessageFor` — see `../legacy/shared/errors-and-logging.md`); the import path translates earlier (`ImportService`, the playlist import sources). ## Changes that always fan out @@ -18,7 +18,7 @@ translates earlier (`ImportService`, the playlist import sources). ### A new persisted setting 1. Field on `Settings` with a default; decide whether Isar's type default for old - rows is acceptable → migration step or not (`../data/persistence.md`). + rows is acceptable → migration step or not (`../legacy/data/persistence.md`). 2. `dart run build_runner build`. 3. Backup export + import, or an entry in `_deliberatelyExcludedSettingsFields` with a reason. @@ -48,6 +48,6 @@ relinked in the same transaction. ### Anything about credentials -Name which of the auth vocabulary terms (`services/download-and-auth.md`) applies: Stream Resolution Auth +Name which of the auth vocabulary terms (`../legacy/services/download-and-auth.md`) applies: Stream Resolution Auth (adapter request) and Media Request Credentials (byte request, empty by construction) are different arrows. Changing either needs the user's approval. diff --git a/.trellis/spec/guides/index.md b/.trellis/spec/guides/index.md index 11886ec00..957015ecc 100644 --- a/.trellis/spec/guides/index.md +++ b/.trellis/spec/guides/index.md @@ -16,4 +16,4 @@ say *what else a change touches*. ## Quality Check -- A new error path stays typed until the user-facing edge and becomes a sentence there through `userMessageFor` / `failureMessage`. The existing exceptions (the import path translating early, `e.toString()` in some `state.error`) are listed in `../shared/errors-and-logging.md`; do not extend them. +- A new error path stays typed until the user-facing edge and becomes a sentence there through `userMessageFor` / `failureMessage`. The existing exceptions (the import path translating early, `e.toString()` in some `state.error`) are listed in `../legacy/shared/errors-and-logging.md`; do not extend them. diff --git a/.trellis/spec/data/index.md b/.trellis/spec/legacy/data/index.md similarity index 83% rename from .trellis/spec/data/index.md rename to .trellis/spec/legacy/data/index.md index 964df06bf..c25413c94 100644 --- a/.trellis/spec/data/index.md +++ b/.trellis/spec/legacy/data/index.md @@ -6,7 +6,7 @@ and the shared foundation in `lib/core/` (errors, logger, constants, utils). Neither directory imports `lib/services/` or `lib/providers/` (one recorded exception, `lib/core/extensions/track_extensions.dart`, in -`test/support/layer_boundary_static_rule_test.dart`) — see AGENTS.md § Boundaries. Vocabulary for auth and media handoff is in `services/download-and-auth.md` § Auth vocabulary. +`test/support/layer_boundary_static_rule_test.dart`) — see lib/AGENTS.md § Boundaries. Vocabulary for auth and media handoff is in `services/download-and-auth.md` § Auth vocabulary. ## Guidelines @@ -21,12 +21,12 @@ Cross-layer error and logging rules: [../shared/errors-and-logging.md](../shared - [ ] Changing a source adapter → read `sources.md` and ADR 0001 (string source ids). - [ ] Touching `Track` identity, `cid` or lyrics matching → read ADR 0005 and the identity section of `persistence.md`. -- [ ] Adding or changing a persisted field → read the `kFmpSchemaVersion` dartdoc in `lib/data/database/database_migration.dart` **before** editing the model. Changing persisted schema semantics needs the user's approval first (AGENTS.md § Conventions). +- [ ] Adding or changing a persisted field → read the `kFmpSchemaVersion` dartdoc in `lib/data/database/database_migration.dart` **before** editing the model. Changing persisted schema semantics needs the user's approval first (lib/AGENTS.md § Conventions). - [ ] Anything that reaches Isar → it goes in `lib/data/repositories/` (ADR 0002). ## Quality Check -- Run the AGENTS.md § Verification rows that match the change: *Source adapters / HTTP policy*, *Isar models / migrations*. +- Run the lib/AGENTS.md § Verification rows that match the change: *Source adapters / HTTP policy*, *Isar models / migrations*. - Codegen is gitignored: after a model change run `dart run build_runner build` before tests, or a missing getter looks like a source bug. - New outbound host, `Timer.periodic`, header literal or cross-feature import → the static-rule test for it turns red; add the entry with a reason rather than working around the detector. - No `isar.` outside repositories, no concrete adapter outside `SourceManager`, no credentials on media requests (see `sources.md`). diff --git a/.trellis/spec/data/persistence.md b/.trellis/spec/legacy/data/persistence.md similarity index 98% rename from .trellis/spec/data/persistence.md rename to .trellis/spec/legacy/data/persistence.md index 8d0e28ecb..506078dea 100644 --- a/.trellis/spec/data/persistence.md +++ b/.trellis/spec/legacy/data/persistence.md @@ -57,7 +57,7 @@ Points that bite: - A new `Settings` field must also be exported and imported by backup, or listed in `_deliberatelyExcludedSettingsFields` with a reason (`test/services/static_rules/settings_backup_coverage_static_rule_test.dart`). -- Verify with the *Isar models / migrations* row of AGENTS.md § Verification; for +- Verify with the *Isar models / migrations* row of lib/AGENTS.md § Verification; for risky schema work also run `test/manual/real_db_probe.dart` against a **copy** of a real database. diff --git a/.trellis/spec/data/sources.md b/.trellis/spec/legacy/data/sources.md similarity index 100% rename from .trellis/spec/data/sources.md rename to .trellis/spec/legacy/data/sources.md diff --git a/.trellis/spec/services/audio.md b/.trellis/spec/legacy/services/audio.md similarity index 100% rename from .trellis/spec/services/audio.md rename to .trellis/spec/legacy/services/audio.md diff --git a/.trellis/spec/services/download-and-auth.md b/.trellis/spec/legacy/services/download-and-auth.md similarity index 100% rename from .trellis/spec/services/download-and-auth.md rename to .trellis/spec/legacy/services/download-and-auth.md diff --git a/.trellis/spec/services/index.md b/.trellis/spec/legacy/services/index.md similarity index 87% rename from .trellis/spec/services/index.md rename to .trellis/spec/legacy/services/index.md index aeb600724..e233d69d9 100644 --- a/.trellis/spec/services/index.md +++ b/.trellis/spec/legacy/services/index.md @@ -19,14 +19,14 @@ Error and logging rules shared with other layers: [../shared/errors-and-logging. ## Pre-Development Checklist -- [ ] Audio change → read `audio.md` and ADR 0003 (two backends). UI calls `AudioController`, never `FmpAudioService`; radio is the one exception (AGENTS.md § Boundaries). +- [ ] Audio change → read `audio.md` and ADR 0003 (two backends). UI calls `AudioController`, never `FmpAudioService`; radio is the one exception (lib/AGENTS.md § Boundaries). - [ ] Download / storage path change → read `download-and-auth.md` and ADR 0004. -- [ ] Anything that decides which credentials go where → read `download-and-auth.md` § Auth vocabulary first. Changing the auth boundary needs the user's approval (AGENTS.md § Conventions). +- [ ] Anything that decides which credentials go where → read `download-and-auth.md` § Auth vocabulary first. Changing the auth boundary needs the user's approval (lib/AGENTS.md § Conventions). - [ ] A new repeating timer, outbound host or cross-feature import → plan the static-rule entry with its reason. ## Quality Check -- Run the AGENTS.md § Verification row that matches: *Audio playback/controller/queue*, *Source adapters / HTTP policy*, or *Download pipeline*. -- Playback controls or anything the user hears/sees → on-device check with the `verify-on-device` skill. +- Run the lib/AGENTS.md § Verification row that matches: *Audio playback/controller/queue*, *Source adapters / HTTP policy*, or *Download pipeline*. +- Playback controls or anything the user hears/sees → on-device check with the `verify-legacy-on-device` skill. - New async code in a disposable service or notifier checks disposed / superseded / `ref.mounted` after an `await` that is followed by a state write. Existing code is uneven (the audio and download paths check; `ImportService` and many settings notifiers do not), so do not take an unchecked file as the pattern. - Not gated — check by hand: the provider of a new service calls its `dispose` (see `service-conventions.md` § Disposal for the existing exceptions); no cookie or token in a new log line, and a stream URL goes through `logLabel` / `redactStreamUrl`; `Platform.is*` placement; a fire-and-forget future that can fail logs its error (see `service-conventions.md` § Async guards). diff --git a/.trellis/spec/services/service-conventions.md b/.trellis/spec/legacy/services/service-conventions.md similarity index 100% rename from .trellis/spec/services/service-conventions.md rename to .trellis/spec/legacy/services/service-conventions.md diff --git a/.trellis/spec/shared/code-style.md b/.trellis/spec/legacy/shared/code-style.md similarity index 97% rename from .trellis/spec/shared/code-style.md rename to .trellis/spec/legacy/shared/code-style.md index e7b1f23d8..36da41809 100644 --- a/.trellis/spec/shared/code-style.md +++ b/.trellis/spec/legacy/shared/code-style.md @@ -18,7 +18,7 @@ ## Comments and dartdoc -- New and edited comments are Traditional Chinese (AGENTS.md § Conventions). +- New and edited comments are Traditional Chinese (lib/AGENTS.md § Conventions). The tree still holds Simplified lines; convert only lines you are editing. Log messages and identifiers are English, as are most exception messages and test names. Exceptions: the playlist import sources throw translated messages on diff --git a/.trellis/spec/shared/errors-and-logging.md b/.trellis/spec/legacy/shared/errors-and-logging.md similarity index 100% rename from .trellis/spec/shared/errors-and-logging.md rename to .trellis/spec/legacy/shared/errors-and-logging.md diff --git a/.trellis/spec/shared/index.md b/.trellis/spec/legacy/shared/index.md similarity index 100% rename from .trellis/spec/shared/index.md rename to .trellis/spec/legacy/shared/index.md diff --git a/.trellis/spec/testing/index.md b/.trellis/spec/legacy/testing/index.md similarity index 95% rename from .trellis/spec/testing/index.md rename to .trellis/spec/legacy/testing/index.md index 89306154a..a95ab4361 100644 --- a/.trellis/spec/testing/index.md +++ b/.trellis/spec/legacy/testing/index.md @@ -1,7 +1,7 @@ # Testing (`test/`, `tool/`) Applies to every test and to static-rule tests. Which tests to run for which -change is AGENTS.md § Verification; the full CI run is +change is lib/AGENTS.md § Verification; the full CI run is `flutter test --exclude-tags live`. ## Guidelines diff --git a/.trellis/spec/testing/static-rules.md b/.trellis/spec/legacy/testing/static-rules.md similarity index 97% rename from .trellis/spec/testing/static-rules.md rename to .trellis/spec/legacy/testing/static-rules.md index e08bd6f13..a544e2b85 100644 --- a/.trellis/spec/testing/static-rules.md +++ b/.trellis/spec/legacy/testing/static-rules.md @@ -3,7 +3,7 @@ A static-rule test reads `lib/` source to hold a boundary that behaviour tests cannot see. List them with `find test -name '*_static_rule_test.dart'`; each file's top dartdoc is the -rule's rationale. AGENTS.md names only the ones an agent is most likely to trip. +rule's rationale. lib/AGENTS.md names only the ones an agent is most likely to trip. Prefer a behavioural test when one can observe the rule (`20a96dc9`, `175e5d2a` replaced static rules with behaviour tests). diff --git a/.trellis/spec/testing/test-conventions.md b/.trellis/spec/legacy/testing/test-conventions.md similarity index 100% rename from .trellis/spec/testing/test-conventions.md rename to .trellis/spec/legacy/testing/test-conventions.md diff --git a/.trellis/spec/ui/i18n-and-routing.md b/.trellis/spec/legacy/ui/i18n-and-routing.md similarity index 100% rename from .trellis/spec/ui/i18n-and-routing.md rename to .trellis/spec/legacy/ui/i18n-and-routing.md diff --git a/.trellis/spec/ui/index.md b/.trellis/spec/legacy/ui/index.md similarity index 77% rename from .trellis/spec/ui/index.md rename to .trellis/spec/legacy/ui/index.md index b4dfb2f43..3de4b870a 100644 --- a/.trellis/spec/ui/index.md +++ b/.trellis/spec/legacy/ui/index.md @@ -16,14 +16,14 @@ Stack: `flutter_riverpod` 3 written by hand (no codegen, no freezed, no hooks), ## Pre-Development Checklist -- [ ] Playback controls call `AudioController` (`ref.read(audioControllerProvider.notifier)`), never `FmpAudioService` — AGENTS.md § Boundaries. -- [ ] The search page's source chips are the only source selector — AGENTS.md § Boundaries. +- [ ] Playback controls call `AudioController` (`ref.read(audioControllerProvider.notifier)`), never `FmpAudioService` — lib/AGENTS.md § Boundaries. +- [ ] The search page's source chips are the only source selector — lib/AGENTS.md § Boundaries. - [ ] Before writing a widget, check the shared-widget table in `widgets.md`; reuse wins over a new variant. -- [ ] A string that can change layout, or any user-visible change, needs on-device verification — plan for the `verify-on-device` skill. +- [ ] A string that can change layout, or any user-visible change, needs on-device verification — plan for the `verify-legacy-on-device` skill. ## Quality Check -- Run the AGENTS.md § Verification row *UI widgets/pages* (targeted `test/ui` tests + `flutter analyze`); for i18n JSON also `dart run slang`. -- On-device verification with the `verify-on-device` skill is mandatory for user-visible changes; report what was observed, or name the blocker. +- Run the lib/AGENTS.md § Verification row *UI widgets/pages* (targeted `test/ui` tests + `flutter analyze`); for i18n JSON also `dart run slang`. +- On-device verification with the `verify-legacy-on-device` skill is mandatory for user-visible changes; report what was observed, or name the blocker. - Static rules that commonly fire here (all under `test/ui/static_rules/` or `test/providers/static_rules/`): image widgets, `ScopedSlider`, watch scope, error presentation, anchored providers, Equatable `props`. When one fires, follow its failure `reason:`. - Not gated — check by hand: `ref.mounted` after awaits, `IconButton` tooltips, `AppRadius` / `AnimationDurations` instead of literals. (Using a `BuildContext` after an await without a `mounted` check is caught by `use_build_context_synchronously`, part of `flutter_lints`.) diff --git a/.trellis/spec/ui/riverpod.md b/.trellis/spec/legacy/ui/riverpod.md similarity index 100% rename from .trellis/spec/ui/riverpod.md rename to .trellis/spec/legacy/ui/riverpod.md diff --git a/.trellis/spec/ui/widgets.md b/.trellis/spec/legacy/ui/widgets.md similarity index 100% rename from .trellis/spec/ui/widgets.md rename to .trellis/spec/legacy/ui/widgets.md diff --git a/AGENTS.md b/AGENTS.md index f1e35b1ee..b5c6c0728 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,8 +1,29 @@ # AGENTS.md -FMP is a Flutter music player for Android and Windows that plays from -**Bilibili**, **YouTube** and **NetEase Cloud Music**. Human-facing docs live in -`docs/`; `docs/README.md` is the map. +FMP is a Flutter music player that plays from **Bilibili**, **YouTube** and +**NetEase Cloud Music**. It is being rewritten: the new app grows in `app/` +while the old one stays at the repo root until the cut-over PR (ADR 0008, +ADR 0026). Human-facing docs live in `docs/`; `docs/README.md` is the map. + +## Where things live + +| Path | What | Rules to read first | +|------|------|---------------------| +| `lib/`, `test/`, `tool/`, `android/`, `windows/`, `assets/`, root `pubspec.yaml` | The old app — frozen, hotfixes only | `lib/AGENTS.md` | +| `docs/adr/` | Decisions. 0008 onward is the rewrite; 0001–0007 describe the old app only | — | +| `.github/workflows/` | `ci.yml` and `release.yml` build and release the old app | — | +| `.claude/skills/` | `verify-legacy-on-device` for old-app hotfixes; `trellis-*` come with Trellis | — | +| `.trellis/` | Tasks and specs: `spec/legacy/` for the old app, `spec/guides/` shared | — | + +Claude Code loads a subdirectory's `AGENTS.md` only when it reads a file there, +so open `lib/AGENTS.md` yourself before an old-app change that touches only +`test/` or other paths outside `lib/`. + +## Decisions + +No code in `app/` without an accepted ADR covering it. A new cross-module +decision gets a new ADR (`docs/adr/template.md`); an ADR that turns out wrong +on a fact gets a one-line correction, not a rewrite of the decision. ## Issues @@ -10,103 +31,25 @@ Issues live on `1morr/FMP` and are handled with `gh`. Titles and bodies are written in Traditional Chinese (Taiwan/Hong Kong usage); identifiers, log strings, commit messages, branch and label names stay in English. -## Verification - -| Change area | Minimum | -|------------|---------| -| Audio playback/controller/queue | `flutter test test/services/audio` (+ `test/data/sources` when stream resolution changes) | -| Source adapters / HTTP policy | `flutter test test/data/sources test/services/account test/services/radio` | -| Download pipeline | `flutter test test/services/download test/providers/download` | -| Isar models / migrations | `dart run build_runner build` + `flutter test test/providers/database_migration_test.dart` | -| UI widgets/pages | targeted tests under `test/ui` + `flutter analyze` + on-device | -| i18n JSON | `dart run slang` + `flutter analyze` | - -- Generated `*.g.dart` files (Isar and slang) are gitignored. After a pull, a - branch switch or in a fresh worktree, run `dart run build_runner build` and - `dart run slang` first: stale codegen fails as a missing getter that looks - like a source bug. An Orca worktree runs them in the `orca.yaml` setup. -- A full run is `flutter test --exclude-tags live`, as in CI; `live` tests hit - the real source APIs. -- `flutter analyze` and the `dart format lib test tool` CI gate cover `tool/` - too. `tool/demo/` holds hand-run scripts against the real APIs: analysed and - formatted, never executed by CI. - -**On-device verification is mandatory for user-visible changes** — UI pages or -widgets, playback controls, how source results render, or a string that can -affect layout. Run the `verify-on-device` skill on the Android emulator (add -Windows only for Windows-specific work) and report the element, log line or -screenshot you observed. When the emulator cannot come up or the change cannot -be reached, report that blocker by name; tests alone do not count. - -## Conventions - -- Comments are Traditional Chinese. The tree is mixed — everything written - before the 2026-09 rounds is Simplified. Convert the lines you are already - editing and leave the rest: a whole-tree conversion buries every real change. -- Ask first before changing persisted schema semantics, the auth boundary, - public architecture or cross-platform behaviour in a way not already - documented. -- For questions about the running app — live field values, HTTP traffic, what - Isar actually holds — use the VM Service recipes in `docs/development.md` - § 執行期除錯. - -## Boundaries - -No test checks these; hold them yourself: - -- **Audio** — UI playback controls call `AudioController` - (`lib/services/audio/audio_provider.dart`), never `FmpAudioService`. Radio is - the one intentional exception. -- **Database** — Isar is opened only by `openFmpDatabase()`; migrations follow - the `kFmpSchemaVersion` dartdoc in `lib/data/database/database_migration.dart`. -- **Search** — the visible source chips on the search page are the only source - selector; no setting filters search behind the user's back (`db41b987`). -- **Providers** — `audio_provider.dart` declares no providers. - `audioControllerProvider` and the backend, queue and stream providers live in - `lib/providers/audio/`; collaborators such as `nowPlayingPublisherProvider`, - `playbackSideEffectsProvider` and `queueStateProvider` declare theirs beside - their class in `lib/services/audio/`. `neteaseSourceProvider` is the - **lyrics-layer** `NeteaseSource` (`lib/services/lyrics/`); the same-named data - source adapter is reached only through `SourceManager`'s narrow capabilities. - -Gated by static-rule tests. The tests hold the exception lists: add an entry -with a reason, delete it when it goes away. - -- **Layers** — `lib/core/` and `lib/data/` import nothing from `lib/services/` - or `lib/providers/`, and a new import edge between two features (a - subdirectory name under either) is recorded — - `test/support/layer_boundary_static_rule_test.dart`. -- **Isar access** — `isar.` appears only in `lib/data/repositories/` (ADR 0002) - — `test/data/static_rules/isar_boundary_static_rule_test.dart`. -- **Images** — in `lib/ui/`, only the semantic widgets in - `lib/ui/widgets/images/` load images (the `ImageLoadingService` loaders, - `Image.network` / `Image.file`, `CachedNetworkImage` / - `CachedNetworkImageProvider`, `NetworkImage` / `FileImage`) or name an - `ImageTargetSizes` tier; pages pass them a variant or a display size (#107) — - `test/ui/static_rules/ui_consistency_static_rule_test.dart`. -- **Sliders** — build `ScopedSlider`; a raw Material `Slider` freezes the - Windows accessibility tree (`docs/troubleshooting.md`) — - `test/ui/static_rules/slider_overlay_static_rule_test.dart`. -- **Test waits** — no direct `pumpEventQueue` outside - `test/support/pump_until.dart`: use its `pumpUntil` / `drainEventQueue` - (how: `.trellis/spec/testing/test-conventions.md`; #43, #55) — - `test/support/wait_convention_static_rule_test.dart`. -- **Static rules** — a test that reads `lib/` source is named - `*_static_rule_test.dart` and lives in `test/support/` or - `test//static_rules/` — - `test/support/static_rule_placement_static_rule_test.dart`. - ## Trellis -- **Rules vs patterns** — binding rules stay in this file; - `.trellis/spec//` holds how each layer's code is written and links - here instead of restating a rule. A new rule goes in exactly one of them. +- **Packages** — `.trellis/config.yaml` declares `legacy` (the repo root) and + `app`; a task's `package` picks its spec tree and its `AGENTS.md`, and + `session.spec_scope: active_task` limits SessionStart to that package + (`app` when no task is active). A flat `.trellis/spec//` directory is + injected whatever the scope, so every layer lives under a package directory and + no `index.md` sits directly in `.trellis/spec//`; only `guides/` is + shared. +- **Rules vs patterns** — binding rules stay in the package's `AGENTS.md`; + `.trellis/spec///` holds how each layer's code is written and + links there instead of restating a rule. A new rule goes in exactly one of + them. - **`trellis update`** — keep the local `.claude/agents/trellis-check.md` and - `trellis-implement.md`: their Verify steps run § Verification above. Journals - stay local because the repo is public (`.trellis/workspace/` is gitignored, - `session_auto_commit: false`; `orca.yaml` shares the main checkout's copy - with Orca worktrees); if an update re-adds a journal `merge=union` line to - `.gitattributes`, drop it. + `trellis-implement.md`: their Verify steps run the task package's + verification section. Journals stay local because the repo is public + (`.trellis/workspace/` is gitignored, `session_auto_commit: false`; + `orca.yaml` shares the main checkout's copy with Orca worktrees); if an + update re-adds a journal `merge=union` line to `.gitattributes`, drop it. - **Managed files left as shipped** — Claude Code runs the customised `.claude/agents/trellis-check.md`; the `trellis-check` skill and `.trellis/agents/check.md` are Trellis-generated and not used for FMP work. diff --git a/docs/README.md b/docs/README.md index 90abecfca..cd7026bd2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -11,15 +11,15 @@ | [開發文件](development.md) | 說明 | 理解架構與主要模組;用 VM Service 查正在跑的 app | 架構分層、Runtime 調試流程 | | [建置與發布指南](build-and-release.md) | 操作指南、參考 | 發新版本、調整 CI 或 Release 流程 | CI、產物命名、Release workflow、簽名 secrets、應用內更新資產 | | [疑難排解](troubleshooting.md) | 參考 | 看到像錯誤的建置或 runtime log,或遇到修不掉只能繞過的行為 | 新查明的噪音或已知行為 | -| [.trellis/spec/](../.trellis/spec/) | 參考 | 用 Trellis 跑任務,或想知道某一層的程式碼照什麼模式寫(英文,描述根目錄舊專案;`app/` 的 spec 從 M1 起繁中) | 某層的寫法慣例變了;有閘門的規則改在 `AGENTS.md`,spec 只連過去 | +| [.trellis/spec/legacy/](../.trellis/spec/legacy/) | 參考 | 用 Trellis 跑舊專案任務,或想知道舊專案某一層的程式碼照什麼模式寫(英文) | 某層的寫法慣例變了;有閘門的規則改在 `lib/AGENTS.md`,spec 只連過去 | | [adr/](adr/) | 說明 | 想知道某個跨模組決定「當初為什麼這樣選」 | 新的跨模組決策(決定、理由、被否決的方案) | -| [verify-on-device skill](../.claude/skills/verify-on-device/SKILL.md) | 操作指南 | 改了使用者可見行為,要做強制的實機驗證 | 模擬器啟動方式、驗證流程、裝置端限制 | +| [verify-legacy-on-device skill](../.claude/skills/verify-legacy-on-device/SKILL.md) | 操作指南 | 舊專案緊急修正改了使用者可見行為,要做強制的實機驗證 | 模擬器啟動方式、驗證流程、裝置端限制 | | [audit/](audit/) | 快照(凍結) | 對照重寫前的現況、功能勾選與效能基準 | 只允許核查更正;切換 PR 刪除(ADR 0008、0026) | ## 分工 - 單一段程式碼的理由寫在它旁邊(dartdoc 或守著它的測試);只有程式碼查不到的跨檔契約與地雷才進 `AGENTS.md`。 - 同一條規則只寫在一個地方,除非另一份文件確實有自己的讀者。 -- **語言**:新文件一律繁體中文;根目錄 `AGENTS.md` 與 `.trellis/spec/` 描述舊專案,維持英文到切換 PR;README 維持英/繁雙語。程式碼識別字、指令、檔名、log 字串、commit message 保留原文。 +- **語言**:新文件一律繁體中文;根目錄 `AGENTS.md` 是地圖,英文;`lib/AGENTS.md` 與 `.trellis/spec/legacy/` 描述舊專案,維持英文到切換 PR;`app/` 的 spec 從 M1 起繁中;README 維持英/繁雙語。程式碼識別字、指令、檔名、log 字串、commit message 保留原文。 - `.claude/skills/` 放可被 Claude Code 直接叫用的專案 skill。`.gitignore` 另外追蹤 Trellis 的接線(`.claude/` 下的 `agents/`、`commands/`、`hooks/`、`settings.json`);`.claude/` 其餘內容、`.trellis/workspace/`(session 日誌)、`.agents/` 與 `.codex/` 是本機狀態。 - 審查記錄不進 `docs/`。一輪審計的結論寫進它所描述的檔案、開成 issue,或留在 git 歷史。例外:`docs/audit/` 在重寫期間凍結保留,切換 PR 刪除。 diff --git a/docs/adr/0012-network-layer-and-accounts.md b/docs/adr/0012-network-layer-and-accounts.md index d17addce5..ae263fea6 100644 --- a/docs/adr/0012-network-layer-and-accounts.md +++ b/docs/adr/0012-network-layer-and-accounts.md @@ -13,7 +13,7 @@ - 帶不帶憑證散落在各 service 手動組 header;每個 service 各自一個 dio。 - B 站 QR 登入可能假成功;B 站刷新憑證後用舊 options 重送;網易任何非 200 都當失效並清憑證;播放用的連線偵測不到失效;secure storage 暫時讀不到就刪憑證;失效提示每次執行只跳一次。 -舊版 `CONTEXT.md`(階段三刪除,術語移到舊專案的 `.trellis/spec/services/download-and-auth.md`)記錄的原則經審計驗證仍成立,併入本 ADR:**憑證只用在向音源解析串流與 API 請求,實際抓取音訊位元組的請求一律不帶憑證**。 +舊版 `CONTEXT.md`(階段三刪除,術語移到舊專案的 `.trellis/spec/legacy/services/download-and-auth.md`)記錄的原則經審計驗證仍成立,併入本 ADR:**憑證只用在向音源解析串流與 API 請求,實際抓取音訊位元組的請求一律不帶憑證**。 ## 考慮過的選項 diff --git a/docs/development.md b/docs/development.md index a07267507..91c565189 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,6 +1,6 @@ # FMP 開發文件 -本文件面向想了解專案結構或參與開發的貢獻者。架構邊界、驗證要求與專案慣例在 [AGENTS.md](../AGENTS.md),人類貢獻者適用同一套。 +本文件面向想了解專案結構或參與開發的貢獻者。架構邊界、驗證要求與專案慣例在 [lib/AGENTS.md](../lib/AGENTS.md),人類貢獻者適用同一套。 ## 平臺分工 diff --git a/lib/AGENTS.md b/lib/AGENTS.md new file mode 100644 index 000000000..3aa97c75c --- /dev/null +++ b/lib/AGENTS.md @@ -0,0 +1,91 @@ +# AGENTS.md — legacy app + +These are the rules for the frozen old app that lives at the repo root. It +only takes hotfixes until the cut-over PR (ADR 0008). Paths below are +relative to the repo root. + +## Verification + +| Change area | Minimum | +|------------|---------| +| Audio playback/controller/queue | `flutter test test/services/audio` (+ `test/data/sources` when stream resolution changes) | +| Source adapters / HTTP policy | `flutter test test/data/sources test/services/account test/services/radio` | +| Download pipeline | `flutter test test/services/download test/providers/download` | +| Isar models / migrations | `dart run build_runner build` + `flutter test test/providers/database_migration_test.dart` | +| UI widgets/pages | targeted tests under `test/ui` + `flutter analyze` + on-device | +| i18n JSON | `dart run slang` + `flutter analyze` | + +- Generated `*.g.dart` files (Isar and slang) are gitignored. After a pull, a + branch switch or in a fresh worktree, run `dart run build_runner build` and + `dart run slang` first: stale codegen fails as a missing getter that looks + like a source bug. An Orca worktree runs them in the `orca.yaml` setup. +- A full run is `flutter test --exclude-tags live`, as in CI; `live` tests hit + the real source APIs. +- `flutter analyze` and the `dart format lib test tool` CI gate cover `tool/` + too. `tool/demo/` holds hand-run scripts against the real APIs: analysed and + formatted, never executed by CI. + +**On-device verification is mandatory for user-visible changes** — UI pages or +widgets, playback controls, how source results render, or a string that can +affect layout. Run the `verify-legacy-on-device` skill on the Android emulator (add +Windows only for Windows-specific work) and report the element, log line or +screenshot you observed. When the emulator cannot come up or the change cannot +be reached, report that blocker by name; tests alone do not count. + +## Conventions + +- Comments are Traditional Chinese. The tree is mixed — everything written + before the 2026-09 rounds is Simplified. Convert the lines you are already + editing and leave the rest: a whole-tree conversion buries every real change. +- Ask first before changing persisted schema semantics, the auth boundary, + public architecture or cross-platform behaviour in a way not already + documented. +- For questions about the running app — live field values, HTTP traffic, what + Isar actually holds — use the VM Service recipes in `docs/development.md` + § 執行期除錯. + +## Boundaries + +No test checks these; hold them yourself: + +- **Audio** — UI playback controls call `AudioController` + (`lib/services/audio/audio_provider.dart`), never `FmpAudioService`. Radio is + the one intentional exception. +- **Database** — Isar is opened only by `openFmpDatabase()`; migrations follow + the `kFmpSchemaVersion` dartdoc in `lib/data/database/database_migration.dart`. +- **Search** — the visible source chips on the search page are the only source + selector; no setting filters search behind the user's back (`db41b987`). +- **Providers** — `audio_provider.dart` declares no providers. + `audioControllerProvider` and the backend, queue and stream providers live in + `lib/providers/audio/`; collaborators such as `nowPlayingPublisherProvider`, + `playbackSideEffectsProvider` and `queueStateProvider` declare theirs beside + their class in `lib/services/audio/`. `neteaseSourceProvider` is the + **lyrics-layer** `NeteaseSource` (`lib/services/lyrics/`); the same-named data + source adapter is reached only through `SourceManager`'s narrow capabilities. + +Gated by static-rule tests. The tests hold the exception lists: add an entry +with a reason, delete it when it goes away. + +- **Layers** — `lib/core/` and `lib/data/` import nothing from `lib/services/` + or `lib/providers/`, and a new import edge between two features (a + subdirectory name under either) is recorded — + `test/support/layer_boundary_static_rule_test.dart`. +- **Isar access** — `isar.` appears only in `lib/data/repositories/` (ADR 0002) + — `test/data/static_rules/isar_boundary_static_rule_test.dart`. +- **Images** — in `lib/ui/`, only the semantic widgets in + `lib/ui/widgets/images/` load images (the `ImageLoadingService` loaders, + `Image.network` / `Image.file`, `CachedNetworkImage` / + `CachedNetworkImageProvider`, `NetworkImage` / `FileImage`) or name an + `ImageTargetSizes` tier; pages pass them a variant or a display size (#107) — + `test/ui/static_rules/ui_consistency_static_rule_test.dart`. +- **Sliders** — build `ScopedSlider`; a raw Material `Slider` freezes the + Windows accessibility tree (`docs/troubleshooting.md`) — + `test/ui/static_rules/slider_overlay_static_rule_test.dart`. +- **Test waits** — no direct `pumpEventQueue` outside + `test/support/pump_until.dart`: use its `pumpUntil` / `drainEventQueue` + (how: `.trellis/spec/legacy/testing/test-conventions.md`; #43, #55) — + `test/support/wait_convention_static_rule_test.dart`. +- **Static rules** — a test that reads `lib/` source is named + `*_static_rule_test.dart` and lives in `test/support/` or + `test//static_rules/` — + `test/support/static_rule_placement_static_rule_test.dart`. diff --git a/orca.yaml b/orca.yaml index 06078b148..8f1809573 100644 --- a/orca.yaml +++ b/orca.yaml @@ -11,7 +11,7 @@ worktree: setupAgentStartupPolicy: wait-for-setup scripts: - # `*.g.dart` 被 gitignore,新 worktree 必須先生成(AGENTS.md § Verification)。 + # `*.g.dart` 被 gitignore,新 worktree 必須先生成(lib/AGENTS.md § Verification)。 # 每行只放一個 cmd 與 bash 都成立的指令:Windows 上 Orca 用 cmd 把每行包成 # `call …` 並在失敗時中止,其他平台用 bash `set -e`。不加 `#!`,cmd 會拒跑。 # 改動這段會讓 Orca 的信任失效,要在 GUI 重新選 Always trust。 From c98e34082d505189b44606dd787f5e6b330d67e3 Mon Sep 17 00:00:00 2001 From: 1morr Date: Tue, 29 Sep 2026 06:56:24 +0800 Subject: [PATCH 2/4] docs(adr): correct facts in adrs 0015, 0021 and 0027 --- docs/adr/0015-testing-gates-and-dev-environment.md | 1 + docs/adr/0021-lyrics.md | 3 ++- docs/adr/0027-on-device-verification.md | 3 ++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/adr/0015-testing-gates-and-dev-environment.md b/docs/adr/0015-testing-gates-and-dev-environment.md index 97a5c9376..53ddcf910 100644 --- a/docs/adr/0015-testing-gates-and-dev-environment.md +++ b/docs/adr/0015-testing-gates-and-dev-environment.md @@ -86,6 +86,7 @@ Flutter 官方 flavor 與 `default-flavor`;`dorny/paths-filter` 的 monorepo - 壞的:要維護自寫的 lint 套件;fixture 會隨上游改版過時,要重錄;Debug 頁多一塊插件開發工具;flavor 的 Windows 身分、鎖與資料目錄要自己接。 - 之後要注意:`flutter analyze` 的插件診斷 bug 修好後可以拿掉 `dart analyze` 的重複步驟,但接線哨兵保留; 播放核心、歌詞、背景任務、UI、發版各自決定 `design.md` §6 標給它們的規則;`riverpod_lint` 是否已遷移到新插件系統在落地時查證。 + 更正(2026-09-29):上面「`flutter analyze` 不顯示插件診斷」引用的 flutter/flutter#193203 已於 2026-09-23 以重複關閉,正確 issue 是仍為 open 的 flutter/flutter#187999。 ## 如何確認 diff --git a/docs/adr/0021-lyrics.md b/docs/adr/0021-lyrics.md index 261afcd57..edec32d27 100644 --- a/docs/adr/0021-lyrics.md +++ b/docs/adr/0021-lyrics.md @@ -140,7 +140,8 @@ - 之後要注意: - Android 狀態列歌詞、本機歌詞檔匯入在功能凍結待辦; - Flutter 官方多視窗 API 進 stable 後再評估; - - 逐字渲染直接用 `flutter_lyric`(避開已撤回的 3.0.5)或自寫,在第一個加入逐字的里程碑決定。 + - 逐字渲染直接用 `flutter_lyric`(避開已撤回的 3.0.5)或自寫,在第一個加入逐字的里程碑決定; + - 更正(2026-09-29):上面「壞的」說 `window_manager` 0.5.x 已停止維護,不成立。0.5.2 於 2026-07-04 發版,pub.dev 沒有停止維護的標示,也沒有 0.6.0;改建在 `nativeapi` 上的是同作者的 `tray_manager` 0.7.0。 ## 如何確認 diff --git a/docs/adr/0027-on-device-verification.md b/docs/adr/0027-on-device-verification.md index 7ad392ed1..6595cabee 100644 --- a/docs/adr/0027-on-device-verification.md +++ b/docs/adr/0027-on-device-verification.md @@ -70,7 +70,8 @@ parent prd 階段三要求:規劃新平台加入時實機驗證如何擴充, - fixture 要定期重錄。 - 之後要注意: - 若測試插件的合成資料不足以涵蓋某類 UI,先補測試插件,不要改成打真實 API; - - Linux、macOS、iOS 的操作說明由各自的平台任務負責。 + - Linux、macOS、iOS 的操作說明由各自的平台任務負責; + - 更正(2026-09-29):根目錄舊專案的 verify-on-device skill 已改名 `verify-legacy-on-device`(M1 PR 1),只更新 skill 內指向自身的路徑;`verify-on-device` 這個名字留給本 ADR 決定 4 要建立的 `app/` 版 skill。 ## 如何確認 From 589730b8e7dd96de199501a16889e59a06c838ac Mon Sep 17 00:00:00 2001 From: 1morr Date: Tue, 29 Sep 2026 06:56:37 +0800 Subject: [PATCH 3/4] chore(task): start m1 pr 1 and note the spec index pitfall --- .trellis/tasks/09-26-fmp-rewrite/task.json | 2 +- .../tasks/09-28-m1-skeleton-tracer/design.md | 3 +- .../09-28-m1-skeleton-tracer/implement.md | 3 +- .../tasks/09-28-m1-skeleton-tracer/task.json | 6 ++- .../check.jsonl | 2 + .../implement.jsonl | 2 + .../09-29-split-agent-instructions/prd.md | 46 +++++++++++++++++++ .../09-29-split-agent-instructions/task.json | 26 +++++++++++ 8 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 .trellis/tasks/09-29-split-agent-instructions/check.jsonl create mode 100644 .trellis/tasks/09-29-split-agent-instructions/implement.jsonl create mode 100644 .trellis/tasks/09-29-split-agent-instructions/prd.md create mode 100644 .trellis/tasks/09-29-split-agent-instructions/task.json diff --git a/.trellis/tasks/09-26-fmp-rewrite/task.json b/.trellis/tasks/09-26-fmp-rewrite/task.json index 4e7751450..11944b9f0 100644 --- a/.trellis/tasks/09-26-fmp-rewrite/task.json +++ b/.trellis/tasks/09-26-fmp-rewrite/task.json @@ -6,7 +6,7 @@ "status": "planning", "dev_type": null, "scope": null, - "package": null, + "package": "app", "priority": "P2", "creator": "1morr", "assignee": "1morr", diff --git a/.trellis/tasks/09-28-m1-skeleton-tracer/design.md b/.trellis/tasks/09-28-m1-skeleton-tracer/design.md index 08e5bc7ab..820d26f03 100644 --- a/.trellis/tasks/09-28-m1-skeleton-tracer/design.md +++ b/.trellis/tasks/09-28-m1-skeleton-tracer/design.md @@ -39,7 +39,8 @@ ``` - package 名不用 `data` 或 `.`。研究查過:spec 目錄名取自 package 名;取 `data` 會回報 `Spec: not configured`,取 `.` 會讓掃描指到 spec 根。 - 現有的 active task(`09-26-fmp-rewrite`、本任務)在 `task.json` 補 `"package": "app"`;兩者都不寫舊專案程式碼。 -- `app/` 的 spec 在 PR 2 建 `.trellis/spec/app/index.md`,各層寫到時再加 `.trellis/spec/app//index.md`(繁中)。 +- `app/` 的 spec 只放在 `.trellis/spec/app//index.md`(繁中),各層寫到時才建。 + - 不建 `.trellis/spec/app/index.md`:`session-start.py:680-682` 會把有 `index.md` 的第一層目錄當成扁平層,不看 scope 一律注入,legacy 任務也會載入,而且底下的各層索引反而不列(PR 1 檢查時發現)。 - `.claude/agents/trellis-implement.md`、`trellis-check.md` 是本機客製檔,可以改: - 驗證步驟改成「讀 `task.json` 的 package:legacy 跑 `lib/AGENTS.md` § Verification,app 跑 `app/AGENTS.md` § 驗證」; - spec 路徑改成 `.trellis/spec///`。 diff --git a/.trellis/tasks/09-28-m1-skeleton-tracer/implement.md b/.trellis/tasks/09-28-m1-skeleton-tracer/implement.md index 29501cc56..05f8be80b 100644 --- a/.trellis/tasks/09-28-m1-skeleton-tracer/implement.md +++ b/.trellis/tasks/09-28-m1-skeleton-tracer/implement.md @@ -56,7 +56,8 @@ - [ ] 單一實例鎖(Windows)。 - [ ] 零聯網兩道防線:`dart_test.yaml`、`flutter_test_config.dart`。 - [ ] `material_ui` import 路徑的決定。 -- [ ] `app/AGENTS.md`、`.trellis/spec/app/index.md`。 +- [ ] `app/AGENTS.md`;第一個 `.trellis/spec/app//index.md`(不建 `spec/app/index.md`,design §1)。 +- [ ] `trellis-check.md`、`trellis-implement.md` 第 2 步的 format/analyze 指令依 package 分流(`app` 在 `app/` 內跑)。 - [ ] `ci.yml` 以 paths-filter 分兩半,加 `app` 的 format/analyze/test 與 `always()` 彙總;合併後在 ruleset 設彙總 job 為必要檢查。 - [ ] `orca.yaml` 加入 `app/` 的 setup。 - 測試: diff --git a/.trellis/tasks/09-28-m1-skeleton-tracer/task.json b/.trellis/tasks/09-28-m1-skeleton-tracer/task.json index 2d10bedce..4781509d2 100644 --- a/.trellis/tasks/09-28-m1-skeleton-tracer/task.json +++ b/.trellis/tasks/09-28-m1-skeleton-tracer/task.json @@ -6,7 +6,7 @@ "status": "in_progress", "dev_type": null, "scope": null, - "package": null, + "package": "app", "priority": "P2", "creator": "1morr", "assignee": "1morr", @@ -18,7 +18,9 @@ "commit": null, "pr_url": null, "subtasks": [], - "children": [], + "children": [ + "09-29-split-agent-instructions" + ], "parent": "09-26-fmp-rewrite", "relatedFiles": [], "notes": "", diff --git a/.trellis/tasks/09-29-split-agent-instructions/check.jsonl b/.trellis/tasks/09-29-split-agent-instructions/check.jsonl new file mode 100644 index 000000000..2696d5761 --- /dev/null +++ b/.trellis/tasks/09-29-split-agent-instructions/check.jsonl @@ -0,0 +1,2 @@ +{"file": ".trellis/tasks/09-28-m1-skeleton-tracer/design.md", "reason": "Section 1 describes the split in detail"} +{"file": ".trellis/tasks/09-28-m1-skeleton-tracer/research/m1-tooling-facts.md", "reason": "Part A: how Trellis packages and spec injection work"} diff --git a/.trellis/tasks/09-29-split-agent-instructions/implement.jsonl b/.trellis/tasks/09-29-split-agent-instructions/implement.jsonl new file mode 100644 index 000000000..2696d5761 --- /dev/null +++ b/.trellis/tasks/09-29-split-agent-instructions/implement.jsonl @@ -0,0 +1,2 @@ +{"file": ".trellis/tasks/09-28-m1-skeleton-tracer/design.md", "reason": "Section 1 describes the split in detail"} +{"file": ".trellis/tasks/09-28-m1-skeleton-tracer/research/m1-tooling-facts.md", "reason": "Part A: how Trellis packages and spec injection work"} diff --git a/.trellis/tasks/09-29-split-agent-instructions/prd.md b/.trellis/tasks/09-29-split-agent-instructions/prd.md new file mode 100644 index 000000000..d7e26c7ef --- /dev/null +++ b/.trellis/tasks/09-29-split-agent-instructions/prd.md @@ -0,0 +1,46 @@ +# 指令檔與 spec 分家(M1 PR 1) + +父任務:`../09-28-m1-skeleton-tracer`(擁有者決定 1;做法見父任務 `design.md` §1)。 + +## 做什麼 + +1. **根目錄 `AGENTS.md`** 換成共用版,全文照 `new-root-agents.md`(主對話定稿)。`TRELLIS:START`/`END` 之間的管理區塊原樣保留在檔尾。 +2. **`lib/AGENTS.md`**(新檔,英文): + - 原根目錄 `AGENTS.md` 的 `## Verification`、`## Conventions`、`## Boundaries` 三段原封搬來,連同段落裡的路徑與測試引用; + - 開頭加一段說明:這份是凍結舊專案的規則,只收緊急修正,路徑相對 repo 根。 +3. **spec 搬家**: + - `git mv .trellis/spec/{data,services,shared,testing,ui} .trellis/spec/legacy/`,`guides/` 留在原地; + - 更新每一處指向舊路徑的引用:spec 之間的相對連結、`lib/AGENTS.md`、`docs/README.md`、`.claude/agents/*`; + - 任務 archive 與 `docs/audit/` 是歷史紀錄,不改。 +4. **`.trellis/config.yaml`**:在 packages 註解區塊後加上 + ```yaml + packages: + legacy: + path: . + app: + path: app + default_package: app + ``` + `09-26-fmp-rewrite`、`09-28-m1-skeleton-tracer` 與本任務的 `task.json` 設 `"package": "app"`。 +5. **`.claude/agents/trellis-implement.md`、`trellis-check.md`**: + - spec 路徑寫成 `.trellis/spec///`; + - 驗證步驟改成讀 `task.json` 的 `package`:`legacy` 跑 `lib/AGENTS.md` § Verification,`app` 跑 `app/AGENTS.md` § 驗證。 +6. **skill 改名**: + - `git mv .claude/skills/verify-on-device .claude/skills/verify-legacy-on-device`; + - SKILL.md 的 `name:` 改成 `verify-legacy-on-device`,description 開頭註明只給舊專案緊急修正用; + - 內容不動,只更新 skill 內指向自身的路徑。 +7. **其他引用**: + - `orca.yaml:14` 的註解改指 `lib/AGENTS.md` § Verification; + - `docs/README.md` 地圖的 spec 與 skill 兩列、語言段落改成新路徑。 +8. **ADR 更正**,只在「之後要注意」或相關段落加一句,不改決定: + - 0015:`flutter analyze` 看不到插件診斷的 issue 改引 flutter/flutter#187999,#193203 已以重複關閉; + - 0021:`window_manager` 0.5.2 於 2026-07 發佈,pub.dev 沒有停止維護的標示; + - 0027:根目錄舊 skill 已改名 `verify-legacy-on-device`,內容不變。 + +## 驗收 + +- [ ] `rg -n "trellis/spec/(data|services|shared|testing|ui)/|skills/verify-on-device" --glob '!.trellis/tasks/**' --glob '!docs/audit/**'` 沒有結果。 +- [ ] `lib/AGENTS.md` 與原根目錄三段逐字相同,只有路徑引用的更新與開頭說明。 +- [ ] `flutter test --exclude-tags live test/support test/workflows` 全綠。 +- [ ] `python ./.trellis/scripts/get_context.py --mode packages` 列出 `legacy` 與 `app`。 +- [ ] 以 `app` 任務為 current task 模擬 SessionStart,spec 索引只剩 `guides`,不含 `legacy/*`。 diff --git a/.trellis/tasks/09-29-split-agent-instructions/task.json b/.trellis/tasks/09-29-split-agent-instructions/task.json new file mode 100644 index 000000000..f06dd7801 --- /dev/null +++ b/.trellis/tasks/09-29-split-agent-instructions/task.json @@ -0,0 +1,26 @@ +{ + "id": "split-agent-instructions", + "name": "split-agent-instructions", + "title": "指令檔與 spec 分家", + "description": "M1 PR 1: split root AGENTS.md into shared, lib/AGENTS.md and app packages; move legacy spec under .trellis/spec/legacy; rename legacy verify skill", + "status": "in_progress", + "dev_type": null, + "scope": null, + "package": "app", + "priority": "P2", + "creator": "1morr", + "assignee": "1morr", + "createdAt": "2026-09-29", + "completedAt": null, + "branch": "chore/split-agent-instructions", + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": "09-28-m1-skeleton-tracer", + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file From 2df6dc43769778ace5df217c826a28ea1bd5c0fe Mon Sep 17 00:00:00 2001 From: 1morr Date: Tue, 29 Sep 2026 06:58:08 +0800 Subject: [PATCH 4/4] chore(task): archive split-agent-instructions --- .../2026-09}/09-29-split-agent-instructions/check.jsonl | 0 .../2026-09}/09-29-split-agent-instructions/implement.jsonl | 0 .../2026-09}/09-29-split-agent-instructions/prd.md | 0 .../2026-09}/09-29-split-agent-instructions/task.json | 4 ++-- 4 files changed, 2 insertions(+), 2 deletions(-) rename .trellis/tasks/{ => archive/2026-09}/09-29-split-agent-instructions/check.jsonl (100%) rename .trellis/tasks/{ => archive/2026-09}/09-29-split-agent-instructions/implement.jsonl (100%) rename .trellis/tasks/{ => archive/2026-09}/09-29-split-agent-instructions/prd.md (100%) rename .trellis/tasks/{ => archive/2026-09}/09-29-split-agent-instructions/task.json (92%) diff --git a/.trellis/tasks/09-29-split-agent-instructions/check.jsonl b/.trellis/tasks/archive/2026-09/09-29-split-agent-instructions/check.jsonl similarity index 100% rename from .trellis/tasks/09-29-split-agent-instructions/check.jsonl rename to .trellis/tasks/archive/2026-09/09-29-split-agent-instructions/check.jsonl diff --git a/.trellis/tasks/09-29-split-agent-instructions/implement.jsonl b/.trellis/tasks/archive/2026-09/09-29-split-agent-instructions/implement.jsonl similarity index 100% rename from .trellis/tasks/09-29-split-agent-instructions/implement.jsonl rename to .trellis/tasks/archive/2026-09/09-29-split-agent-instructions/implement.jsonl diff --git a/.trellis/tasks/09-29-split-agent-instructions/prd.md b/.trellis/tasks/archive/2026-09/09-29-split-agent-instructions/prd.md similarity index 100% rename from .trellis/tasks/09-29-split-agent-instructions/prd.md rename to .trellis/tasks/archive/2026-09/09-29-split-agent-instructions/prd.md diff --git a/.trellis/tasks/09-29-split-agent-instructions/task.json b/.trellis/tasks/archive/2026-09/09-29-split-agent-instructions/task.json similarity index 92% rename from .trellis/tasks/09-29-split-agent-instructions/task.json rename to .trellis/tasks/archive/2026-09/09-29-split-agent-instructions/task.json index f06dd7801..b3206ae7f 100644 --- a/.trellis/tasks/09-29-split-agent-instructions/task.json +++ b/.trellis/tasks/archive/2026-09/09-29-split-agent-instructions/task.json @@ -3,7 +3,7 @@ "name": "split-agent-instructions", "title": "指令檔與 spec 分家", "description": "M1 PR 1: split root AGENTS.md into shared, lib/AGENTS.md and app packages; move legacy spec under .trellis/spec/legacy; rename legacy verify skill", - "status": "in_progress", + "status": "completed", "dev_type": null, "scope": null, "package": "app", @@ -11,7 +11,7 @@ "creator": "1morr", "assignee": "1morr", "createdAt": "2026-09-29", - "completedAt": null, + "completedAt": "2026-09-29", "branch": "chore/split-agent-instructions", "base_branch": "main", "worktree_path": null,