From 35dbdd2f99cce6e2be187de5a424c6aaf5cecefe Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Jul 2026 18:41:58 +0000 Subject: [PATCH 1/3] docs: make repo more agent-friendly Add agent documentation hub, 22 Cursor skills, CI architecture guards, and fix layer import violations by moving cross-feature providers to shared/providers/. Co-authored-by: Youri Bontekoe --- .cursor/rules/architecture.md | 29 ++ .cursor/rules/kynos.md | 21 +- .cursor/rules/pr-workflow.md | 25 ++ .cursor/skills/add-feature/SKILL.md | 44 +++ .cursor/skills/ai-isolate/SKILL.md | 46 +++ .../skills/audits/audit-ai-isolate/SKILL.md | 28 ++ .../skills/audits/audit-architecture/SKILL.md | 24 ++ .../skills/audits/audit-biomechanics/SKILL.md | 22 ++ .../audits/audit-dart-modernization/SKILL.md | 22 ++ .../audits/audit-documentation/SKILL.md | 23 ++ .../skills/audits/audit-health-data/SKILL.md | 22 ++ .../skills/audits/audit-performance/SKILL.md | 27 ++ .cursor/skills/audits/audit-privacy/SKILL.md | 29 ++ .../audits/audit-test-coverage/SKILL.md | 27 ++ .../skills/audits/audit-ui-polish/SKILL.md | 23 ++ .cursor/skills/fix-ci/SKILL.md | 40 +++ .cursor/skills/health-data/SKILL.md | 45 +++ .cursor/skills/linear-kynos/SKILL.md | 23 ++ .cursor/skills/onboard-agent/SKILL.md | 41 +++ .cursor/skills/open-pr/SKILL.md | 48 +++ .cursor/skills/privacy-audit/SKILL.md | 43 +++ .cursor/skills/regenerate-codemap/SKILL.md | 32 ++ .cursor/skills/ui-design-system/SKILL.md | 54 +++ .cursor/skills/validate-change/SKILL.md | 38 +++ .cursor/skills/wire-repository/SKILL.md | 52 +++ .gemini/skills/linear-kynos.md | 13 +- .github/pull_request_template.md | 21 ++ .github/workflows/ci.yml | 8 + AGENTS.md | 13 +- CLAUDE.md | 21 +- CODEMAP.md | 320 +++++++++++++----- CONTRIBUTING.md | 49 +++ README.md | 16 + docs/agents/README.md | 113 +++++++ docs/agents/common-patterns.md | 92 +++++ docs/agents/file-templates.md | 178 ++++++++++ docs/agents/troubleshooting.md | 98 ++++++ docs/workflow_prompts.md | 77 ++++- lib/app/router.dart | 2 +- .../providers/coach_chat_seed_provider.dart | 19 +- .../providers/model_setup_provider.dart | 123 +------ .../providers/model_setup_state.dart | 18 +- .../presentation/pages/dashboard_page.dart | 2 +- .../providers/dashboard_summary_provider.dart | 2 +- .../providers/post_run_debrief_provider.dart | 2 +- .../providers/onboarding_provider.dart | 28 +- .../pages/on_device_model_picker_page.dart | 4 +- .../presentation/pages/settings_page.dart | 2 +- .../apple_health_export_preview_card.dart | 2 +- .../widgets/gpx_import_preview_card.dart | 2 +- .../providers/health_import_provider.dart | 3 - .../settings/providers/settings_provider.dart | 187 +--------- .../providers/ai_repository_providers.dart | 4 +- .../providers/coach_chat_seed_provider.dart | 18 + .../providers/coach_chat_seed_provider.g.dart | 0 .../providers/health_import_providers.dart | 7 + .../providers/model_setup_provider.dart | 121 +++++++ .../providers/model_setup_provider.g.dart | 0 lib/shared/providers/model_setup_state.dart | 17 + lib/shared/providers/onboarding_provider.dart | 27 ++ .../providers/onboarding_provider.g.dart | 0 lib/shared/providers/settings_provider.dart | 186 ++++++++++ .../providers/settings_provider.g.dart | 0 scripts/check_architecture.sh | 46 +++ 64 files changed, 2166 insertions(+), 503 deletions(-) create mode 100644 .cursor/rules/architecture.md create mode 100644 .cursor/rules/pr-workflow.md create mode 100644 .cursor/skills/add-feature/SKILL.md create mode 100644 .cursor/skills/ai-isolate/SKILL.md create mode 100644 .cursor/skills/audits/audit-ai-isolate/SKILL.md create mode 100644 .cursor/skills/audits/audit-architecture/SKILL.md create mode 100644 .cursor/skills/audits/audit-biomechanics/SKILL.md create mode 100644 .cursor/skills/audits/audit-dart-modernization/SKILL.md create mode 100644 .cursor/skills/audits/audit-documentation/SKILL.md create mode 100644 .cursor/skills/audits/audit-health-data/SKILL.md create mode 100644 .cursor/skills/audits/audit-performance/SKILL.md create mode 100644 .cursor/skills/audits/audit-privacy/SKILL.md create mode 100644 .cursor/skills/audits/audit-test-coverage/SKILL.md create mode 100644 .cursor/skills/audits/audit-ui-polish/SKILL.md create mode 100644 .cursor/skills/fix-ci/SKILL.md create mode 100644 .cursor/skills/health-data/SKILL.md create mode 100644 .cursor/skills/linear-kynos/SKILL.md create mode 100644 .cursor/skills/onboard-agent/SKILL.md create mode 100644 .cursor/skills/open-pr/SKILL.md create mode 100644 .cursor/skills/privacy-audit/SKILL.md create mode 100644 .cursor/skills/regenerate-codemap/SKILL.md create mode 100644 .cursor/skills/ui-design-system/SKILL.md create mode 100644 .cursor/skills/validate-change/SKILL.md create mode 100644 .cursor/skills/wire-repository/SKILL.md create mode 100644 .github/pull_request_template.md create mode 100644 CONTRIBUTING.md create mode 100644 docs/agents/README.md create mode 100644 docs/agents/common-patterns.md create mode 100644 docs/agents/file-templates.md create mode 100644 docs/agents/troubleshooting.md create mode 100644 lib/shared/providers/coach_chat_seed_provider.dart rename lib/{features/coach_chat => shared}/providers/coach_chat_seed_provider.g.dart (100%) create mode 100644 lib/shared/providers/model_setup_provider.dart rename lib/{features/coach_chat => shared}/providers/model_setup_provider.g.dart (100%) create mode 100644 lib/shared/providers/model_setup_state.dart create mode 100644 lib/shared/providers/onboarding_provider.dart rename lib/{features/onboarding => shared}/providers/onboarding_provider.g.dart (100%) create mode 100644 lib/shared/providers/settings_provider.dart rename lib/{features/settings => shared}/providers/settings_provider.g.dart (100%) create mode 100755 scripts/check_architecture.sh diff --git a/.cursor/rules/architecture.md b/.cursor/rules/architecture.md new file mode 100644 index 0000000..0e247ca --- /dev/null +++ b/.cursor/rules/architecture.md @@ -0,0 +1,29 @@ +# Architecture (always apply) + +Layer dependency direction — **never invert**: + +``` +features/ → domain/ ← infrastructure/ +features/ → shared/providers/ → domain + infrastructure +``` + +## Banned imports + +| From | Must NOT import | +|------|-----------------| +| `lib/features/` | `package:kynos/infrastructure/` | +| `lib/features/` | another feature's `providers/` | +| `lib/domain/` | `package:flutter`, `flutter_riverpod`, `riverpod` | + +## Enforcement + +```bash +bash scripts/check_architecture.sh +``` + +## When violating for DI + +Move bindings to `lib/shared/providers/` and re-export types if features need them. +See `/wire-repository` skill. + +Reference: [AGENTS.md §5](../../AGENTS.md#5-dependency-rule-strictly-enforced) diff --git a/.cursor/rules/kynos.md b/.cursor/rules/kynos.md index dd155b8..2dcbe6a 100644 --- a/.cursor/rules/kynos.md +++ b/.cursor/rules/kynos.md @@ -1,9 +1,20 @@ # KYNOS agent rules +Read [docs/agents/README.md](../../docs/agents/README.md) for the full agent hub. + +## Always + 1. Read [CODEMAP.md](../../CODEMAP.md) before editing code. 2. Follow [AGENTS.md](../../AGENTS.md) for architecture and PR workflow. -3. Never import `infrastructure/` from `features/` — use `shared/providers/`. -4. Never import one feature's `providers/` from another feature. -5. Keep hand-written files under ~250 lines; split into `presentation/widgets/`. -6. Run `dart run tool/generate_codemap.dart` after structural changes. -7. PR titles must use Conventional Commits: `type` or `type(scope): subject` with a lowercase subject (e.g. `fix(ios): healthkit snackbar shows settings hint`). CI rejects non-conforming titles. +3. Use Cursor skills in `.cursor/skills/` — `/onboard-agent`, `/add-feature`, `/validate-change`, `/open-pr`. +4. Never import `infrastructure/` from `features/` — use `shared/providers/`. +5. Never import one feature's `providers/` from another feature. +6. Keep hand-written files under ~250 lines; split into `presentation/widgets/`. +7. Run `dart run tool/generate_codemap.dart` after structural changes. +8. PR titles: Conventional Commits with lowercase subject — CI enforces this. + +## Key files + +- `lib/app/router.dart` — routes (`Routes` constants) +- `lib/shared/providers/` — DI composition root +- `docs/agents/file-templates.md` — scaffolds for new files diff --git a/.cursor/rules/pr-workflow.md b/.cursor/rules/pr-workflow.md new file mode 100644 index 0000000..a3f32e5 --- /dev/null +++ b/.cursor/rules/pr-workflow.md @@ -0,0 +1,25 @@ +# PR workflow (always apply) + +A task is **not complete** until the PR is merged or explicitly closed. + +## Before opening + +Run `/validate-change` — all checks must pass including `flutter build web`. + +## PR requirements + +- Target: `main` +- Title: Conventional Commits — `type` or `type(scope): subject`, lowercase subject +- Branch (agents): `cursor/-d1bd` +- Fill [.github/pull_request_template.md](../../.github/pull_request_template.md) +- Attach visual proof for UI changes + +## After opening — mandatory + +1. Monitor review comments (humans, CodeRabbit, CI) +2. Fix, push, re-validate +3. Reply on each comment thread +4. 👍 every addressed comment +5. Do not abandon the PR + +Reference: [AGENTS.md §4](../../AGENTS.md#4-pull-request--review-process) diff --git a/.cursor/skills/add-feature/SKILL.md b/.cursor/skills/add-feature/SKILL.md new file mode 100644 index 0000000..aca0574 --- /dev/null +++ b/.cursor/skills/add-feature/SKILL.md @@ -0,0 +1,44 @@ +--- +name: add-feature +description: Add a new feature vertical slice to KYNOS. Use when creating a new screen, tab, page, or feature folder under lib/features/. +--- + +# Add Feature + +## When to Use + +- "Add a feature", "new screen", "new tab", "new page" +- Creating `lib/features//` + +## Checklist (AGENTS.md §17) + +1. **Page** — `lib/features//presentation/pages/_page.dart` +2. **Provider** — `lib/features//providers/_provider.dart` (`@riverpod`) +3. **Use-case** — `lib/domain/usecases//` +4. **Repository interface** — `lib/domain/repositories/` (if new data source) +5. **Entity** — `lib/domain/entities/` with `@freezed` (if needed) +6. **Infrastructure impl** — `lib/infrastructure/` (if new platform integration) +7. **DI provider** — `lib/shared/providers/` binding infra → Riverpod +8. **Route** — register in `lib/app/router.dart` (`Routes` constant) +9. **Tests** — unit test for use-case, widget smoke test for page +10. **Codemap** — `dart run tool/generate_codemap.dart` + +## Scaffold Templates + +See [docs/agents/file-templates.md](../../docs/agents/file-templates.md). + +## UI Rules + +- Import design system: `package:kynos/core/theme/theme.dart` +- Use `KynosCard`, `MetricTile`, `Gap(tokens.Spacing.*)` — never raw numbers +- Loading: shimmer (`KynosLoadingLine`, `MetricTile(value: null)`), not spinners in cards + +## Validation + +```bash +dart run build_runner build --delete-conflicting-outputs +dart run tool/generate_codemap.dart +flutter analyze +flutter test +flutter build web +``` diff --git a/.cursor/skills/ai-isolate/SKILL.md b/.cursor/skills/ai-isolate/SKILL.md new file mode 100644 index 0000000..0f6cdcd --- /dev/null +++ b/.cursor/skills/ai-isolate/SKILL.md @@ -0,0 +1,46 @@ +--- +name: ai-isolate +description: Work on KYNOS on-device AI inference and the background isolate bridge. Use when editing Gemma, LiteRT-LM, coach chat streaming, or AI repository code. +paths: + - "lib/infrastructure/ai/**" + - "lib/features/coach_chat/**" + - "lib/shared/providers/ai*.dart" +--- + +# AI Isolate + +## When to Use + +- Editing AI inference, Gemma model loading, or coach chat streaming +- Touching `ai_isolate_bridge.dart` or `IsolateAiCoachRepository` + +## Key Files + +| File | Purpose | +|------|---------| +| `lib/infrastructure/ai/gemma/ai_isolate_bridge.dart` | Isolate message protocol | +| `lib/infrastructure/ai/isolate_ai_coach_repository.dart` | Repository impl | +| `lib/domain/repositories/ai_coach_repository.dart` | Domain contract | +| `lib/shared/providers/ai_repository_providers.dart` | DI binding | +| `lib/core/constants/app_constants.dart` | RAM budgets, thermal thresholds | + +## Rules (AGENTS.md §10) + +- **All LLM inference runs in a Background Isolate** — never on the UI thread +- Features access AI only via `AiCoachRepository` through `shared/providers/` +- Thermal / RAM checks before loading model +- UI streaming via `Stream` — avoid full-page rebuilds on each token + +## RAM Probe + +Use `GemmaDeviceRamProbe` via `ai_repository_providers.dart` export — features must not import infrastructure directly. + +## Validation + +```bash +flutter analyze +flutter test test/infrastructure/ai/ +flutter build web +``` + +See [docs/architecture/ai_isolate_design.md](../../docs/architecture/ai_isolate_design.md). diff --git a/.cursor/skills/audits/audit-ai-isolate/SKILL.md b/.cursor/skills/audits/audit-ai-isolate/SKILL.md new file mode 100644 index 0000000..3f6114d --- /dev/null +++ b/.cursor/skills/audits/audit-ai-isolate/SKILL.md @@ -0,0 +1,28 @@ +--- +name: audit-ai-isolate +description: Audit KYNOS AI isolate bridge for memory leaks, streaming efficiency, and thermal handling. Explicit invocation only — use /audit-ai-isolate. +disable-model-invocation: true +--- + +# Audit AI Isolate + +**Reference:** [docs/workflow_prompts.md §4](../../docs/workflow_prompts.md) · AGENTS.md §10 + +## Instructions + +1. Review `lib/infrastructure/ai/gemma/ai_isolate_bridge.dart` for leak paths +2. Verify isolate killed and port closed on `CoachChatPage` dispose +3. Confirm token streaming uses `Stream` — no full-page rebuilds per token +4. Check thermal/RAM gating before model load (`AppConstants`) +5. Test OOM handling on <6GB RAM devices + +## Key Files + +- `ai_isolate_bridge.dart` +- `isolate_ai_coach_repository.dart` +- `docs/architecture/ai_isolate_design.md` + +## Proof of Work + +- Video: smooth streaming while scrolling UI +- Isolate RAM consumption log during inference diff --git a/.cursor/skills/audits/audit-architecture/SKILL.md b/.cursor/skills/audits/audit-architecture/SKILL.md new file mode 100644 index 0000000..aa64f18 --- /dev/null +++ b/.cursor/skills/audits/audit-architecture/SKILL.md @@ -0,0 +1,24 @@ +--- +name: audit-architecture +description: Audit KYNOS Clean Architecture layer imports and domain purity. Explicit invocation only — use /audit-architecture for recurring architecture integrity checks. +disable-model-invocation: true +--- + +# Audit Architecture + +**Reference:** [docs/workflow_prompts.md §1](../../docs/workflow_prompts.md) · AGENTS.md §5 + +## Instructions + +1. Run `bash scripts/check_architecture.sh` +2. Grep `lib/features/` for `package:kynos/infrastructure/` imports +3. Grep `lib/domain/` for `package:flutter`, `flutter_riverpod`, `riverpod` +4. Grep `lib/features/` for cross-feature `providers/` imports +5. Identify files >250 lines in CODEMAP hot files section +6. Fix violations: extract to use-cases, move DI to `shared/providers/` + +## Proof of Work + +- `flutter analyze` — 0 issues +- `bash scripts/check_architecture.sh` — passes +- List of violations found and fixed (or confirm clean) diff --git a/.cursor/skills/audits/audit-biomechanics/SKILL.md b/.cursor/skills/audits/audit-biomechanics/SKILL.md new file mode 100644 index 0000000..236e79f --- /dev/null +++ b/.cursor/skills/audits/audit-biomechanics/SKILL.md @@ -0,0 +1,22 @@ +--- +name: audit-biomechanics +description: Audit KYNOS biomechanics regression model for numerical stability and cold-start handling. Explicit invocation only — use /audit-biomechanics. +disable-model-invocation: true +--- + +# Audit Biomechanics + +**Reference:** [docs/workflow_prompts.md §8](../../docs/workflow_prompts.md) + +## Instructions + +1. Check multivariate regression for collinearity (cadence vs power) +2. Verify β-coefficient persistence via Protocol Buffers (atomic save/load) +3. Implement or verify confidence score for stride length prediction +4. Handle cold start (1–2 data points) gracefully +5. Ensure regression math is pure Dart (isolate-safe) + +## Proof of Work + +- Unit test: OLS prediction vs reference dataset +- Screenshot: Nexus Lab showing β-coefficients and confidence diff --git a/.cursor/skills/audits/audit-dart-modernization/SKILL.md b/.cursor/skills/audits/audit-dart-modernization/SKILL.md new file mode 100644 index 0000000..7039899 --- /dev/null +++ b/.cursor/skills/audits/audit-dart-modernization/SKILL.md @@ -0,0 +1,22 @@ +--- +name: audit-dart-modernization +description: Audit KYNOS for modern Dart 3 patterns, lint upgrades, and build_runner optimization. Explicit invocation only — use /audit-dart-modernization. +disable-model-invocation: true +--- + +# Audit Dart Modernization + +**Reference:** [docs/workflow_prompts.md §10](../../docs/workflow_prompts.md) + +## Instructions + +1. Review `analysis_options.yaml` — enable `strict-casts`, `unawaited_futures` if safe +2. Replace legacy patterns with pattern matching on sealed `Failure` classes +3. Use records where they simplify repository return types +4. Optimize `build.yaml` to reduce `build_runner` scan scope +5. Remove deprecated plugin/API usage + +## Proof of Work + +- `flutter analyze` clean with any new strict rules +- Before/after snippet showing idiomatic Dart 3 refactor diff --git a/.cursor/skills/audits/audit-documentation/SKILL.md b/.cursor/skills/audits/audit-documentation/SKILL.md new file mode 100644 index 0000000..f869221 --- /dev/null +++ b/.cursor/skills/audits/audit-documentation/SKILL.md @@ -0,0 +1,23 @@ +--- +name: audit-documentation +description: Audit KYNOS documentation for drift against code and generate ADRs when needed. Explicit invocation only — use /audit-documentation. +disable-model-invocation: true +--- + +# Audit Documentation + +**Reference:** [docs/workflow_prompts.md §9](../../docs/workflow_prompts.md) + +## Instructions + +1. Read `docs/architecture/*.md` and compare to actual `lib/` code +2. Verify isolate protocol docs match `ai_isolate_bridge.dart` +3. Check AGENTS.md §17, CODEMAP, and folder_structure.md for consistency +4. Generate ADR (`docs/architecture/adr-XXX.md`) for significant decisions +5. Audit DartDoc on public `domain/` APIs + +## Proof of Work + +- Report on doc/code drift items found and fixed +- New or updated ADR if applicable +- Run `dart run tool/generate_codemap.dart` if structure changed diff --git a/.cursor/skills/audits/audit-health-data/SKILL.md b/.cursor/skills/audits/audit-health-data/SKILL.md new file mode 100644 index 0000000..132b76a --- /dev/null +++ b/.cursor/skills/audits/audit-health-data/SKILL.md @@ -0,0 +1,22 @@ +--- +name: audit-health-data +description: Audit KYNOS health data aggregation, normalization, and empty-state UX. Explicit invocation only — use /audit-health-data. +disable-model-invocation: true +--- + +# Audit Health Data + +**Reference:** [docs/workflow_prompts.md §7](../../docs/workflow_prompts.md) + +## Instructions + +1. Review `HealthKitRepository` sleep segment aggregation across midnight +2. Verify HRV (SDNN) normalization for AI coach context +3. Test permission-denied and zero-data empty states +4. Confirm sealed `Failure` hierarchy for errors (AGENTS.md §9) +5. Add unit tests for split sleep, DST edge cases + +## Proof of Work + +- Unit tests for complex data scenarios +- Screenshot: dashboard empty state or permission UI diff --git a/.cursor/skills/audits/audit-performance/SKILL.md b/.cursor/skills/audits/audit-performance/SKILL.md new file mode 100644 index 0000000..9d6ed76 --- /dev/null +++ b/.cursor/skills/audits/audit-performance/SKILL.md @@ -0,0 +1,27 @@ +--- +name: audit-performance +description: Audit KYNOS for 120Hz jank, unnecessary rebuilds, and GPU overdraw. Explicit invocation only — use /audit-performance. +disable-model-invocation: true +--- + +# Audit Performance + +**Reference:** [docs/workflow_prompts.md §2](../../docs/workflow_prompts.md) · AGENTS.md §14 + +## Instructions + +1. Analyze `DashboardPage`, `NexusLab`, lists for rebuild triggers +2. Ensure `ref.select` for granular provider subscriptions (e.g. `hrvMs` not whole `HealthSummary`) +3. Check `GlassCard` / `BackdropFilter` overdraw when multiple glass cards visible +4. Add `const` constructors and `RepaintBoundary` where appropriate +5. Verify `ListView.builder` for long lists + +## Targets + +- Frame budget: 8.3ms at 120Hz +- No full-page rebuilds on streaming AI tokens + +## Proof of Work + +- Screen recording with Performance Overlay (bars below 8.3ms) +- Optimization log: widgets converted to `const` or `ref.select` diff --git a/.cursor/skills/audits/audit-privacy/SKILL.md b/.cursor/skills/audits/audit-privacy/SKILL.md new file mode 100644 index 0000000..f99ae7f --- /dev/null +++ b/.cursor/skills/audits/audit-privacy/SKILL.md @@ -0,0 +1,29 @@ +--- +name: audit-privacy +description: Audit KYNOS for Zero-Knowledge privacy violations and biometric data leaks. Explicit invocation only — use /audit-privacy. +disable-model-invocation: true +--- + +# Audit Privacy + +**Reference:** [docs/workflow_prompts.md §3](../../docs/workflow_prompts.md) · AGENTS.md §11 + +## Instructions + +1. Scan for `print()`, `debugPrint()`, `logger` with health/biometric objects +2. Grep sensitive field names in log statements: `hrvMs`, `rhrBpm`, `HealthSummary`, etc. +3. Trace data path from `HealthKitRepository` to UI — no network interceptors capturing biometrics +4. Verify `AppConstants.zeroKnowledgeMode == true` +5. Confirm export flows are user-initiated only + +## Commands + +```bash +rg 'logger\.(d|i|w|e|f)\(.*(HealthSummary|hrvMs|rhrBpm)' lib/ +rg 'print\(|debugPrint\(' lib/ +``` + +## Proof of Work + +- Clean ripgrep report +- `zeroKnowledgeMode` confirmed true diff --git a/.cursor/skills/audits/audit-test-coverage/SKILL.md b/.cursor/skills/audits/audit-test-coverage/SKILL.md new file mode 100644 index 0000000..b241f25 --- /dev/null +++ b/.cursor/skills/audits/audit-test-coverage/SKILL.md @@ -0,0 +1,27 @@ +--- +name: audit-test-coverage +description: Audit KYNOS test coverage gaps in domain use-cases and shared widgets. Explicit invocation only — use /audit-test-coverage. +disable-model-invocation: true +--- + +# Audit Test Coverage + +**Reference:** [docs/workflow_prompts.md §6](../../docs/workflow_prompts.md) · AGENTS.md §13 + +## Instructions + +1. Run `flutter test --coverage` +2. Identify use-cases in `lib/domain/usecases/` with <100% line coverage +3. Add `mocktail` fakes for repository contracts +4. Add widget smoke tests for `lib/shared/widgets/` +5. Ensure every bug fix has a regression test + +## Rules + +- Widget tests: `ProviderScope` with overridden fakes — never real repos +- `test/` mirrors `lib/` structure + +## Proof of Work + +- `flutter test` — 0 failures +- Coverage summary showing improvement in target area diff --git a/.cursor/skills/audits/audit-ui-polish/SKILL.md b/.cursor/skills/audits/audit-ui-polish/SKILL.md new file mode 100644 index 0000000..a5f597e --- /dev/null +++ b/.cursor/skills/audits/audit-ui-polish/SKILL.md @@ -0,0 +1,23 @@ +--- +name: audit-ui-polish +description: Audit KYNOS UI for glassmorphism quality, haptics, motion design, and design token compliance. Explicit invocation only — use /audit-ui-polish. +disable-model-invocation: true +--- + +# Audit UI Polish + +**Reference:** [docs/workflow_prompts.md §5](../../docs/workflow_prompts.md) · AGENTS.md §14 + +## Instructions + +1. Review `GlassCard` in light and dark mode +2. Verify haptics: `HapticFeedback.mediumImpact()` on primary actions +3. Check `go_router` transitions feel fluid +4. Run `bash scripts/check_design_system.sh` +5. Confirm spacing uses `tokens.Spacing.*`, colors from `AppTheme` + +## Proof of Work + +- Screenshots: light vs dark mode +- Video: transitions and haptic triggers +- Design system script passes diff --git a/.cursor/skills/fix-ci/SKILL.md b/.cursor/skills/fix-ci/SKILL.md new file mode 100644 index 0000000..051ab5e --- /dev/null +++ b/.cursor/skills/fix-ci/SKILL.md @@ -0,0 +1,40 @@ +--- +name: fix-ci +description: Diagnose and fix KYNOS CI failures on a pull request. Use when CI checks fail, build_runner drift is reported, or analyze/test/build-web jobs fail. +--- + +# Fix CI + +## When to Use + +- CI failed on your PR +- "build_runner out of sync", "analyze failed", "design system check failed" + +## CI Jobs and Fixes + +| Job | Common cause | Fix | +|-----|--------------|-----| +| `validate-pr-title` | Non-conventional or capitalized subject | Rename PR: `feat: lowercase subject` | +| `bootstrap` / build_runner | Generated files stale | `dart run build_runner build --delete-conflicting-outputs` then commit `*.g.dart` / `*.freezed.dart` | +| `analyze` | Lint/type errors | `flutter analyze` locally, fix issues | +| `analyze` / design system | Raw `Gap(N)`, `Colors.*` in features | Use `tokens.Spacing.*`, `context.kynosTheme` | +| `analyze` / architecture | Feature imports infrastructure | Move binding to `shared/providers/` | +| `analyze` / CODEMAP | Structural change without regen | `dart run tool/generate_codemap.dart`, commit CODEMAP.md | +| `test` | Failing tests | `flutter test`, fix or update tests | +| `build-web` | Web compile error | `flutter build web` locally | + +## Workflow + +1. Read the failed job log +2. Reproduce locally with the matching command +3. Fix, commit, push +4. Wait for CI green +5. Comment on PR if the failure was non-obvious + +## Re-validate + +```bash +flutter analyze && flutter test && flutter build web +bash scripts/check_design_system.sh +bash scripts/check_architecture.sh +``` diff --git a/.cursor/skills/health-data/SKILL.md b/.cursor/skills/health-data/SKILL.md new file mode 100644 index 0000000..b726062 --- /dev/null +++ b/.cursor/skills/health-data/SKILL.md @@ -0,0 +1,45 @@ +--- +name: health-data +description: Work on KYNOS health data integrations — HealthKit, Health Connect, imports, and health providers. Use when editing biometric data flow or health repositories. +paths: + - "lib/infrastructure/health/**" + - "lib/shared/providers/health*.dart" + - "lib/domain/entities/health*.dart" +--- + +# Health Data + +## When to Use + +- Editing HealthKit / Health Connect integration +- Health import (GPX, Apple Health export) +- Health providers or `HealthSummary` aggregation + +## Key Files + +| File | Purpose | +|------|---------| +| `lib/infrastructure/health/` | Platform health integrations | +| `lib/domain/repositories/health_repository.dart` | Domain contract | +| `lib/shared/providers/health_providers.dart` | Health DI | +| `lib/shared/providers/health_import_providers.dart` | Import use-cases and parse exports | +| `lib/domain/entities/health_summary.dart` | Daily biometric summary | + +## Rules + +- Error handling: sealed `Failure` hierarchy — never throw across layers +- Return `({T? value, Failure? failure})` at repository boundaries +- Features use `shared/providers/` — never import `infrastructure/health/` directly +- Handle empty data and permission-denied states with clear UX (no blank screens) + +## Privacy + +Biometric data must not appear in logs. See `/privacy-audit` and AGENTS.md §11. + +## Validation + +```bash +flutter test test/domain/usecases/health/ +flutter test test/infrastructure/health/ +flutter analyze +``` diff --git a/.cursor/skills/linear-kynos/SKILL.md b/.cursor/skills/linear-kynos/SKILL.md new file mode 100644 index 0000000..27dfcfa --- /dev/null +++ b/.cursor/skills/linear-kynos/SKILL.md @@ -0,0 +1,23 @@ +--- +name: linear-kynos +description: Manage Linear issues for the KYNOS project. Use when creating issues, listing tickets, updating status, or any Linear-related task for this repository. +--- + +# Linear KYNOS + +## Project Context + +- **Organization:** kynosai +- **Project ID:** kynos-050ce0ec6007 +- **URL:** https://linear.app/kynosai/project/kynos-050ce0ec6007/overview + +## Instructions + +- When listing issues or checking tasks, prioritize the KYNOS project (`kynos-050ce0ec6007`) +- When creating issues, assign to the KYNOS project +- Include file/feature context from this repo in issue descriptions +- Link related PRs and CODEMAP entry points when applicable + +## Canonical Docs + +For code changes triggered by Linear issues, follow [docs/agents/README.md](../../docs/agents/README.md) and use `/add-feature` or `/validate-change` as appropriate. diff --git a/.cursor/skills/onboard-agent/SKILL.md b/.cursor/skills/onboard-agent/SKILL.md new file mode 100644 index 0000000..2955c24 --- /dev/null +++ b/.cursor/skills/onboard-agent/SKILL.md @@ -0,0 +1,41 @@ +--- +name: onboard-agent +description: Onboard to the KYNOS repository. Use when starting your first task, when unfamiliar with the codebase, or when the user asks how agents should work in this repo. +--- + +# Onboard Agent + +## When to Use + +- First task in this repository +- User asks "how do I contribute" or "where do I start" +- You need the read order, bootstrap commands, or PR definition of done + +## Read Order + +1. [docs/agents/README.md](../../docs/agents/README.md) — hub +2. [CODEMAP.md](../../CODEMAP.md) — file locations, feature index +3. [AGENTS.md](../../AGENTS.md) — full rules and PR lifecycle + +## Bootstrap + +```bash +cp .env.example .env +flutter pub get +dart run build_runner build --delete-conflicting-outputs +dart run tool/generate_codemap.dart +flutter analyze +flutter test +``` + +## Key Rules + +- Features **never** import `infrastructure/` — use `shared/providers/` +- Features **never** import another feature's `providers/` +- Hand-written files stay under ~250 lines +- PR titles: Conventional Commits with lowercase subject +- Agent branches: `cursor/-d1bd` + +## Before Finishing Any Task + +Run `/validate-change`, open a PR with `/open-pr`, and monitor review until merged. diff --git a/.cursor/skills/open-pr/SKILL.md b/.cursor/skills/open-pr/SKILL.md new file mode 100644 index 0000000..dcaf6ba --- /dev/null +++ b/.cursor/skills/open-pr/SKILL.md @@ -0,0 +1,48 @@ +--- +name: open-pr +description: Open and shepherd a KYNOS pull request. Use when creating a PR, pushing a branch, or monitoring review feedback until merge. +--- + +# Open PR + +## When to Use + +- "Open a PR", "create pull request", "push and open PR" +- After `/validate-change` passes + +## Before Opening + +Run `/validate-change` — all checks must pass. + +## Branch and Push + +```bash +git checkout -b cursor/-d1bd +git add -A && git commit -m "feat: descriptive message" +git push -u origin cursor/-d1bd +``` + +## PR Title (CI enforced) + +Format: `type` or `type(scope): subject` — **lowercase subject** + +Examples: +- `feat: add coach readiness widgets` +- `fix(ios): healthkit permission snackbar shows settings hint` +- `docs: make repo more agent-friendly` + +Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert` + +## PR Body + +Use the [.github/pull_request_template.md](../../.github/pull_request_template.md) checklist. + +## After Opening — Mandatory Monitoring + +1. Poll for review comments (humans, CodeRabbit, CI failures) +2. Fix issues, push, re-run validation +3. Reply on each comment thread explaining what changed +4. React 👍 on every addressed comment +5. Do **not** abandon the PR until merged or explicitly closed + +See [AGENTS.md §4](../../AGENTS.md#4-pull-request--review-process). diff --git a/.cursor/skills/privacy-audit/SKILL.md b/.cursor/skills/privacy-audit/SKILL.md new file mode 100644 index 0000000..d9a5c74 --- /dev/null +++ b/.cursor/skills/privacy-audit/SKILL.md @@ -0,0 +1,43 @@ +--- +name: privacy-audit +description: Audit KYNOS for Zero-Knowledge privacy violations. Use when checking biometric data leaks, logging of health data, or cloud sync compliance. +paths: + - "lib/**" +--- + +# Privacy Audit + +## When to Use + +- Reviewing code that touches health, GPS, or biometric data +- Before merging features with network calls or logging +- Explicit `/privacy-audit` or `/audit-privacy` invocation + +## Invariant (AGENTS.md §11) + +- **No biometric data leaves the device** unless user explicitly exports +- `AppConstants.zeroKnowledgeMode` must remain `true` +- Cloud sync requires ZK proofs — never raw data + +## Checks + +1. **Logging leaks** — grep for sensitive types in log calls: + +```bash +rg 'logger\.(d|i|w|e|f)\(.*(HealthSummary|hrvMs|rhrBpm|AthleteProfile)' lib/ +rg 'print\(|debugPrint\(' lib/ +``` + +2. **Network paths** — trace HTTP calls; ensure no raw health/GPS payloads +3. **Export flows** — user-initiated only (`LocalExportService`) +4. **Constants** — verify `zeroKnowledgeMode == true` in `app_constants.dart` + +## Self-Reflection + +"If a malicious actor had app logs, could they reconstruct heart rate history?" → Must be **No**. + +## Proof of Work + +- Clean ripgrep report for sensitive field names in logging +- `flutter analyze` passes +- Confirm `AppConstants.zeroKnowledgeMode == true` diff --git a/.cursor/skills/regenerate-codemap/SKILL.md b/.cursor/skills/regenerate-codemap/SKILL.md new file mode 100644 index 0000000..31ca82e --- /dev/null +++ b/.cursor/skills/regenerate-codemap/SKILL.md @@ -0,0 +1,32 @@ +--- +name: regenerate-codemap +description: Regenerate CODEMAP.md after structural changes under lib/. Use when adding features, moving files, or CODEMAP CI drift check fails. +--- + +# Regenerate Codemap + +## When to Use + +- Added/removed/renamed files under `lib/` +- CI reports CODEMAP.md is out of date +- After completing `/add-feature` + +## Command + +```bash +dart run tool/generate_codemap.dart +``` + +This updates the auto-generated sections between `` in [CODEMAP.md](../../CODEMAP.md): +- Layer map (per-file line counts and class summaries) +- Hot files (>250 lines) + +## Commit + +If `git diff CODEMAP.md` shows changes, commit them with your feature work. + +## Verify + +```bash +git diff --exit-code CODEMAP.md # should exit 0 after commit +``` diff --git a/.cursor/skills/ui-design-system/SKILL.md b/.cursor/skills/ui-design-system/SKILL.md new file mode 100644 index 0000000..9a48ab4 --- /dev/null +++ b/.cursor/skills/ui-design-system/SKILL.md @@ -0,0 +1,54 @@ +--- +name: ui-design-system +description: Apply KYNOS UI design system conventions. Use when editing feature screens, shared widgets, spacing, typography, loading states, or charts. +paths: + - "lib/features/**" + - "lib/shared/widgets/**" +--- + +# UI Design System + +## When to Use + +- Editing screens in `lib/features/` or widgets in `lib/shared/widgets/` +- Adding metrics, cards, loading states, or charts + +## Imports + +```dart +import 'package:kynos/core/theme/theme.dart'; +import 'package:kynos/shared/widgets/widgets.dart'; +import 'package:gap/gap.dart'; +import 'package:kynos/core/theme/spacing.dart' as tokens; +``` + +Access tokens: `context.kynosTheme` + +## Widgets + +| Widget | Use for | +|--------|---------| +| `KynosCard` | Default content container | +| `MetricTile` | 2-column metric grids (`value: null` → shimmer) | +| `KynosSectionHeader` | Uppercase section labels | +| `KynosHeroBanner` | Coloured tab hero banners | +| `KynosChip` | Badges, metric chips | +| `KynosLoadingLine` | In-card async loading (shimmer) | +| `KynosSkeleton` | Full-page loading placeholders | +| `GlassCard` | Coach bubbles, floating nav only | +| `RunCard` | Workout session list items | + +## Rules + +- Spacing: `Gap(tokens.Spacing.sm)` — **never** `Gap(8)` or raw numbers +- Typography: `Theme.of(context).textTheme` or `context.kynosTheme` — no inline `GoogleFonts` in features +- Metrics: `kynosTheme.metricValueStyle` (DM Mono) +- Loading: shimmer skeletons, not spinners inside cards +- Charts: `fl_chart` only +- Colors: `context.kynosTheme` / `AppTheme` — never raw `Colors.red` + +## CI Check + +```bash +bash scripts/check_design_system.sh +``` diff --git a/.cursor/skills/validate-change/SKILL.md b/.cursor/skills/validate-change/SKILL.md new file mode 100644 index 0000000..f1b5c2d --- /dev/null +++ b/.cursor/skills/validate-change/SKILL.md @@ -0,0 +1,38 @@ +--- +name: validate-change +description: Run the full KYNOS validation suite before opening a PR. Use before committing, before opening a PR, or when the user asks to verify changes. +--- + +# Validate Change + +## When to Use + +- Before opening a PR +- After completing implementation +- User asks "does it build" or "run tests" + +## Commands (run in order) + +```bash +cp .env.example .env +flutter pub get +dart run build_runner build --delete-conflicting-outputs +dart run tool/generate_codemap.dart +flutter analyze +flutter test +bash scripts/check_design_system.sh +bash scripts/check_architecture.sh +flutter build web +``` + +## If Generated Files Changed + +Commit any `*.g.dart`, `*.freezed.dart`, and `CODEMAP.md` updates. + +## UI Changes + +Attach visual proof: screen recording or screenshots of changed screens. + +## Definition of Done + +All commands pass with zero errors. See [AGENTS.md §4.7](../../AGENTS.md#47-definition-of-done-for-a-pr). diff --git a/.cursor/skills/wire-repository/SKILL.md b/.cursor/skills/wire-repository/SKILL.md new file mode 100644 index 0000000..8387820 --- /dev/null +++ b/.cursor/skills/wire-repository/SKILL.md @@ -0,0 +1,52 @@ +--- +name: wire-repository +description: Wire a repository through Clean Architecture layers in KYNOS. Use when adding DI providers, repository implementations, or connecting domain use-cases to infrastructure. +--- + +# Wire Repository + +## When to Use + +- "Wire a repository", "add provider", "DI binding", "connect use-case" +- Touching `lib/shared/providers/` or `lib/infrastructure/` + +## Layer Flow + +``` +domain/repositories/_repository.dart ← abstract interface +domain/usecases// ← business logic +infrastructure//_repository.dart ← platform impl +shared/providers/_providers.dart ← Riverpod binding +features//providers/ ← feature-scoped state only +``` + +## Steps + +1. Define `abstract interface class` in `lib/domain/repositories/` +2. Implement in `lib/infrastructure/` — return `({T? value, Failure? failure})` at boundaries +3. Create or extend provider in `lib/shared/providers/`: + +```dart +final myRepositoryProvider = Provider((ref) { + return MyRepositoryImpl(/* deps from other providers */); +}); +``` + +4. Feature providers import `shared/providers/` — **never** `infrastructure/` +5. Use-case tests with `mocktail` fakes of the repository interface + +## Re-export Pattern + +When features need infrastructure types (parse results, probes), re-export from `shared/providers/`: + +```dart +export 'package:kynos/infrastructure/...' show SomeType; +``` + +## Validation + +```bash +bash scripts/check_architecture.sh +flutter analyze +flutter test +``` diff --git a/.gemini/skills/linear-kynos.md b/.gemini/skills/linear-kynos.md index 256a527..2021a82 100644 --- a/.gemini/skills/linear-kynos.md +++ b/.gemini/skills/linear-kynos.md @@ -1,14 +1,5 @@ # Linear KYNOS Project Skill -This skill provides context for the KYNOS project on Linear. It ensures that any Linear-related tasks (creating issues, listing tickets, updating status) are focused on the KYNOS project by default. +> **Canonical skill:** [`.cursor/skills/linear-kynos/SKILL.md`](../../.cursor/skills/linear-kynos/SKILL.md) -## Project Context -- **Organization:** kynosai -- **Project ID:** kynos-050ce0ec6007 -- **URL:** https://linear.app/kynosai/project/kynos-050ce0ec6007/overview - -## Instructions -- When the user asks to "list issues" or "check tasks," prioritize issues within the KYNOS project (`kynos-050ce0ec6007`). -- When creating a new issue, ensure it is assigned to the KYNOS project if applicable. -- Use the Linear MCP tools (e.g., `linear_search_issues`, `linear_create_issue`) to interact with the API. -- If an issue is related to a specific file or feature in this repository, include that context in the Linear issue description. +This file is a stub for Gemini CLI compatibility. Use the Cursor skill above for full instructions. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..11cb092 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,21 @@ +## Summary + + + +## Checklist + +- [ ] `flutter analyze` — zero issues +- [ ] `flutter test` — zero failures +- [ ] `dart run build_runner build --delete-conflicting-outputs` — generated files committed if changed +- [ ] `dart run tool/generate_codemap.dart` — CODEMAP.md updated if `lib/` structure changed +- [ ] `bash scripts/check_design_system.sh` — passes +- [ ] `bash scripts/check_architecture.sh` — passes +- [ ] `flutter build web` — succeeds +- [ ] Visual proof attached (screenshots or screen recording for UI changes) +- [ ] PR title uses [Conventional Commits](https://www.conventionalcommits.org/) with lowercase subject + +## Agent notes + + + +See [AGENTS.md §4](AGENTS.md#4-pull-request--review-process) for the full PR lifecycle. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d8f0f5d..bfe4b60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,6 +105,14 @@ jobs: - name: Check design system conventions run: bash scripts/check_design_system.sh + - name: Check architecture layer imports + run: bash scripts/check_architecture.sh + + - name: Check CODEMAP freshness + run: | + dart run tool/generate_codemap.dart + git diff --exit-code CODEMAP.md || (echo "CODEMAP.md is out of date. Run: dart run tool/generate_codemap.dart" && exit 1) + test: needs: bootstrap if: ${{ !contains(github.event.head_commit.message, '[skip ci]') }} diff --git a/AGENTS.md b/AGENTS.md index 65ae7bc..fc129bc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -389,11 +389,14 @@ Barrel import: `package:kynos/shared/widgets/widgets.dart`. 2. Create `lib/features//providers/_provider.dart` 3. Add use-case in `lib/domain/usecases//` 4. Add repository interface in `lib/domain/repositories/` -5. Add data model with `@freezed` in `lib/data/models/` -6. Implement repository in `lib/data/repositories/` or `lib/infrastructure/` -7. Register route in `lib/app/router.dart` -8. Write unit tests for use-case and widget smoke test for page -9. Open PR, monitor review, 👍 all addressed comments +5. Add entity with `@freezed` in `lib/domain/entities/` (if needed) +6. Implement repository in `lib/infrastructure/` +7. Register provider in `lib/shared/providers/` (DI binding) +8. Register route in `lib/app/router.dart` +9. Write unit tests for use-case and widget smoke test for page +10. Run `dart run tool/generate_codemap.dart` and open PR, monitor review, 👍 all addressed comments + +See also [docs/agents/file-templates.md](docs/agents/file-templates.md) and the `/add-feature` Cursor skill. --- diff --git a/CLAUDE.md b/CLAUDE.md index a29da29..333bde9 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,12 +1,13 @@ # KYNOS — Claude Code Instructions ## Project Overview -KYNOS is a Flutter (iOS/Android) running coach app powered by on-device AI -(Gemma 4 via LiteRT-LM). Privacy-first, Zero-Knowledge architecture. +KYNOS is a privacy-first Flutter running coach app (iOS / Android / Web) +powered by on-device AI (Gemma via LiteRT-LM). Zero-Knowledge architecture. ## Start Here -1. **[CODEMAP.md](CODEMAP.md)** — feature index, entry points, hot files -2. **[AGENTS.md](AGENTS.md)** — full architectural rules and PR workflow +1. **[docs/agents/README.md](docs/agents/README.md)** — agent hub (skills, validation, PR checklist) +2. **[CODEMAP.md](CODEMAP.md)** — feature index, entry points, hot files +3. **[AGENTS.md](AGENTS.md)** — full architectural rules and PR workflow ## Architecture Rules - Clean Architecture: `domain` ← `infrastructure`, with `shared/providers/` as DI @@ -26,11 +27,22 @@ dart run build_runner build --delete-conflicting-outputs dart run tool/generate_codemap.dart # refresh CODEMAP.md auto sections flutter analyze flutter test +flutter build web # mandatory before PR (see AGENTS.md §16) flutter run -d "iPhone 16 Pro" ``` +## PR Workflow (mandatory) + +- PR titles: Conventional Commits — `type` or `type(scope): subject` with lowercase subject +- Branch naming for agents: `cursor/-d1bd` +- After opening a PR: monitor review, address all feedback, 👍 every resolved comment +- Attach visual proof for UI changes (screenshots or screen recording) +- See [AGENTS.md §4](AGENTS.md#4-pull-request--review-process) for full lifecycle + ## Key Files +- `docs/agents/README.md` — agent documentation hub - `CODEMAP.md` — agent navigation index +- `.cursor/skills/` — reusable agent workflows - `lib/app/router.dart` — all routes - `lib/shared/providers/` — repository DI bindings - `lib/infrastructure/ai/gemma/ai_isolate_bridge.dart` — isolate protocol @@ -42,3 +54,4 @@ flutter run -d "iPhone 16 Pro" - Write to `.g.dart` or `.freezed.dart` files manually - Add `print()` — use `logger` package - Commit `assets/models/*.task` — model weights are gitignored (>2 GB) +- Open a PR and walk away — review follow-up is mandatory diff --git a/CODEMAP.md b/CODEMAP.md index 7db5e78..9d42097 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -6,6 +6,7 @@ | Task | Start here | |------|------------| +| Agent onboarding | [`docs/agents/README.md`](docs/agents/README.md) | | Add a route | [`lib/app/router.dart`](lib/app/router.dart) | | Add a feature | [`lib/features//`](lib/features/) + AGENTS.md §17 | | Wire a repository | [`lib/shared/providers/`](lib/shared/providers/) | @@ -90,147 +91,308 @@ There is **no `data/` layer yet**. Repository implementations live in `infrastru | File | Lines | Summary | |------|------:|---------| -| `lib/app/app.dart` | 24 | KynosApp | -| `lib/app/router.dart` | 53 | All named route paths — single source of truth. | -| `lib/app/shell_page.dart` | 171 | ShellPage, _ShellState, _BottomBar, … | -| `lib/core/constants/app_constants.dart` | 51 | Compile-time constants for KYNOS. | -| `lib/core/errors/failures.dart` | 29 | Base class for all domain-level failures in KYNOS. | -| `lib/core/theme/app_theme.dart` | 180 | AppTheme | +| `lib/app/app.dart` | 27 | KynosApp | +| `lib/app/not_found_page.dart` | 46 | NotFoundPage | +| `lib/app/page_transitions.dart` | 100 | Route transition presets for [GoRouter] [pageBuilder] callbacks. | +| `lib/app/router.dart` | 207 | _RouterRefreshNotifier | +| `lib/app/shell_navigation_scope.dart` | 41 | ShellNavigationScope, DashboardTab | +| `lib/app/shell_page.dart` | 157 | ShellPage, _ShellPageState, _AnimatedShellBody, … | +| `lib/core/constants/app_constants.dart` | 54 | Compile-time constants for KYNOS. | +| `lib/core/constants/gamification_constants.dart` | 21 | Gameplay tuning for the Trail Run character mini-game. | +| `lib/core/constants/imported_workout_ids.dart` | 10 | Prefix and helpers for locally imported workout identifiers. | +| `lib/core/errors/failures.dart` | 33 | Base class for all domain-level failures in KYNOS. | +| `lib/core/theme/app_theme.dart` | 191 | AppTheme | | `lib/core/theme/colors.dart` | 82 | KYNOS colour palette — iOS system colours, Apple Fitness aesthetic. | | `lib/core/theme/elevation.dart` | 33 | Shadow presets for the KYNOS design system. | -| `lib/core/theme/kynos_theme_extension.dart` | 185 | KynosThemeExtension | -| `lib/core/theme/layout.dart` | 39 | Layout constants for consistent page structure. | +| `lib/core/theme/kynos_theme_extension.dart` | 229 | KynosThemeExtension | +| `lib/core/theme/layout.dart` | 69 | Layout constants for consistent page structure. | +| `lib/core/theme/liquid_glass_tokens.dart` | 54 | Design tokens for Apple Liquid Glass materials — blur, vibrancy, specular edges. | +| `lib/core/theme/motion.dart` | 17 | Design token: motion durations and curves shared across the app. | | `lib/core/theme/spacing.dart` | 20 | Design token: spacing scale (4-point grid). | -| `lib/core/theme/theme.dart` | 10 | KYNOS design system — barrel export. | -| `lib/core/theme/typography.dart` | 149 | Named text styles for the KYNOS design system. | +| `lib/core/theme/theme.dart` | 12 | KYNOS design system — barrel export. | +| `lib/core/theme/typography.dart` | 171 | Named text styles for the KYNOS design system. | +| `lib/domain/catalog/on_device_model_catalog.dart` | 100 | Curated catalog of LiteRT-LM models supported by KYNOS on-device coach. | +| `lib/domain/entities/ai_inference_backend.dart` | 6 | Where the last coach inference ran. | +| `lib/domain/entities/ai_task_kind.dart` | 9 | Classification for hybrid local/cloud AI routing. | | `lib/domain/entities/athlete_profile.dart` | 55 | AthleteProfile | -| `lib/domain/entities/chat_message.dart` | 44 | ChatMessage | +| `lib/domain/entities/chat_message.dart` | 80 | ChatMessage | +| `lib/domain/entities/cloud_data_level.dart` | 9 | How much health context may be included in OpenRouter prompts. | +| `lib/domain/entities/dashboard/dashboard_summary.dart` | 60 | DashboardSummary | +| `lib/domain/entities/gamification/activity_resources.dart` | 39 | ActivityResources | +| `lib/domain/entities/gamification/adventure_session.dart` | 95 | AdventureSession | | `lib/domain/entities/gamification/character_class.dart` | 156 | Dart module | | `lib/domain/entities/gamification/character_stats.dart` | 128 | CharacterStats | | `lib/domain/entities/gamification/earned_title.dart` | 64 | EarnedTitle | -| `lib/domain/entities/gamification/quest.dart` | 147 | Quest | +| `lib/domain/entities/gamification/encounter_state.dart` | 95 | EncounterState | +| `lib/domain/entities/gamification/quest.dart` | 201 | QuestObjective, Quest | | `lib/domain/entities/gamification/runner_character.dart` | 134 | RunnerCharacter | +| `lib/domain/entities/gamification/trail_node.dart` | 51 | TrailNode | | `lib/domain/entities/gamification/xp_gain.dart` | 21 | XpGain | | `lib/domain/entities/health_summary.dart` | 114 | HealthSummary | | `lib/domain/entities/insights/insight_confidence.dart` | 14 | Dart module | | `lib/domain/entities/insights/today_insights.dart` | 41 | TodayInsights | | `lib/domain/entities/insights/training_insights.dart` | 33 | TrainingInsights | +| `lib/domain/entities/on_device_model.dart` | 39 | OnDeviceModel | +| `lib/domain/entities/openrouter_model.dart` | 51 | OpenRouterModelPricing, OpenRouterModel | +| `lib/domain/entities/openrouter_model_filters.dart` | 112 | OpenRouterModelFilters | | `lib/domain/entities/workout_route_point.dart` | 27 | WorkoutRoutePoint | | `lib/domain/entities/workout_session.dart` | 37 | WorkoutSession | -| `lib/domain/repositories/ai_coach_repository.dart` | 28 | Streaming response chunk from the on-device LLM. | -| `lib/domain/repositories/ai_model_repository.dart` | 16 | Contract for managing the lifecycle of the on-device AI model file. | +| `lib/domain/repositories/ai_coach_repository.dart` | 31 | Streaming response chunk from the on-device or cloud LLM. | +| `lib/domain/repositories/ai_model_repository.dart` | 25 | Contract for managing the lifecycle of the on-device AI model file. | | `lib/domain/repositories/biomechanics_repository.dart` | 40 | BiomechanicsSample | -| `lib/domain/repositories/character_repository.dart` | 11 | Dart module | +| `lib/domain/repositories/character_repository.dart` | 15 | Dart module | +| `lib/domain/repositories/cloud_ai_repository.dart` | 10 | Cloud inference backend for BYOK OpenRouter requests. | | `lib/domain/repositories/gamekit_repository.dart` | 42 | Dart module | -| `lib/domain/repositories/health_repository.dart` | 31 | Contract for accessing biometric data from the platform health store. | +| `lib/domain/repositories/health_repository.dart` | 39 | Contract for accessing biometric data from the platform health store. | +| `lib/domain/repositories/openrouter_models_repository.dart` | 12 | Contract for fetching the OpenRouter model catalog. | | `lib/domain/usecases/gamification/assign_character_class_usecase.dart` | 146 | AssignCharacterClassUseCase | +| `lib/domain/usecases/gamification/compute_activity_resources_usecase.dart` | 50 | ComputeActivityResourcesUseCase | | `lib/domain/usecases/gamification/compute_xp_usecase.dart` | 122 | ComputeXpUseCase | -| `lib/domain/usecases/gamification/generate_daily_quests_usecase.dart` | 239 | GenerateDailyQuestsUseCase | +| `lib/domain/usecases/gamification/evaluate_quest_progress_usecase.dart` | 65 | EvaluateQuestProgressUseCase | +| `lib/domain/usecases/gamification/generate_daily_quests_usecase.dart` | 298 | GenerateDailyQuestsUseCase | +| `lib/domain/usecases/gamification/generate_daily_trail_usecase.dart` | 60 | GenerateDailyTrailUseCase | +| `lib/domain/usecases/gamification/resolve_encounter_turn_usecase.dart` | 217 | EncounterTurnResult, ResolveEncounterTurnUseCase | +| `lib/domain/usecases/health/import_apple_health_export_usecase.dart` | 144 | ImportAppleHealthExportResult, ImportAppleHealthExportUseCase | +| `lib/domain/usecases/health/import_workout_usecase.dart` | 48 | ImportWorkoutUseCase | +| `lib/domain/usecases/insights/generate_post_run_debrief_usecase.dart` | 98 | PostRunDebrief, GeneratePostRunDebriefUseCase | | `lib/domain/usecases/insights/generate_today_insights_usecase.dart` | 69 | GenerateTodayInsightsUseCase | -| `lib/domain/usecases/insights/generate_training_insights_usecase.dart` | 294 | GenerateTrainingInsightsUseCase | +| `lib/domain/usecases/insights/generate_training_insights_usecase.dart` | 296 | GenerateTrainingInsightsUseCase | | `lib/domain/usecases/insights/today_insights_deterministic.dart` | 145 | Builds deterministic today insights from health metrics. | -| `lib/domain/usecases/insights/today_insights_model_refiner.dart` | 126 | TodayInsightsModelRefiner | +| `lib/domain/usecases/insights/today_insights_model_refiner.dart` | 128 | TodayInsightsModelRefiner | | `lib/domain/usecases/nexus_lab/calibrate_gait_model_usecase.dart` | 155 | CalibrateGaitModelUseCase, _SteadyStateExtractionTool | -| `lib/domain/utils/readiness_score.dart` | 73 | Dart module | -| `lib/features/character/presentation/pages/character_page.dart` | 156 | CharacterPage, EmptyCharacterState | +| `lib/domain/utils/acwr.dart` | 38 | Acute:chronic workload ratio from daily running distance. | +| `lib/domain/utils/ai_inference_error_policy.dart` | 149 | Classifies on-device LLM failures and maps them to user-facing copy. | +| `lib/domain/utils/ai_task_router.dart` | 37 | Routes AI tasks to local Gemma or OpenRouter cloud backends. | +| `lib/domain/utils/cloud_prompt_sanitizer.dart` | 29 | Strips sensitive fields from cloud-bound prompts. | +| `lib/domain/utils/coach_fallback_reply.dart` | 28 | Deterministic coach reply when on-device Gemma is unavailable. | +| `lib/domain/utils/gemma_device_capability.dart` | 62 | On-device Gemma inference tier based on device resources. | +| `lib/domain/utils/gemma_inference_limits.dart` | 19 | Token budgets keyed by on-device Gemma inference tier. | +| `lib/domain/utils/health_context_formatter.dart` | 57 | Formats [HealthSummary] rows for LLM prompts without raw GPS or device IDs. | +| `lib/domain/utils/metric_trends.dart` | 85 | MetricDelta | +| `lib/domain/utils/personal_bests.dart` | 63 | Finds short personal-best callouts for dashboard chips. | +| `lib/domain/utils/readiness_score.dart` | 123 | ReadinessDimensions | +| `lib/domain/utils/run_streak.dart` | 65 | Dart module | +| `lib/domain/utils/seeded_roll.dart` | 17 | Deterministic 32-bit roll safe for VM and JavaScript (no >53-bit intermediates). | +| `lib/domain/utils/weekly_momentum.dart` | 88 | WeeklyMomentum | +| `lib/features/character/presentation/pages/character_page.dart` | 235 | CharacterPage, EmptyCharacterState | +| `lib/features/character/presentation/widgets/activity_resources_bar.dart` | 99 | ActivityResourcesBar, _ResourceTile | | `lib/features/character/presentation/widgets/character_hero_card.dart` | 92 | CharacterHeroCard | | `lib/features/character/presentation/widgets/character_shield_icon.dart` | 35 | CharacterShieldPainter | -| `lib/features/character/presentation/widgets/gamekit_panel.dart` | 99 | GameKitPanel, GameKitButton | -| `lib/features/character/presentation/widgets/quest_card.dart` | 188 | QuestPanel, QuestCard | +| `lib/features/character/presentation/widgets/encounter_panel.dart` | 118 | EncounterPanel | +| `lib/features/character/presentation/widgets/gamekit_panel.dart` | 104 | GameKitPanel, GameKitButton | +| `lib/features/character/presentation/widgets/quest_card.dart` | 254 | QuestPanel, QuestCard | | `lib/features/character/presentation/widgets/signatory_power_card.dart` | 68 | SignatoryPowerCard | | `lib/features/character/presentation/widgets/stats_panel.dart` | 96 | StatsPanel, StatRow | | `lib/features/character/presentation/widgets/titles_panel.dart` | 37 | TitlesPanel | +| `lib/features/character/presentation/widgets/trail_map.dart` | 210 | TrailMap, _TrailNodeDot, _TrailNodeDotState | +| `lib/features/character/presentation/widgets/trail_run_game_panel.dart` | 68 | TrailRunGamePanel | | `lib/features/character/presentation/widgets/xp_bar.dart` | 69 | XpBar | -| `lib/features/character/providers/character_provider.dart` | 29 | Loads the persisted [RunnerCharacter], creating one via class assignment | -| `lib/features/character/providers/quest_provider.dart` | 69 | QuestNotifier | -| `lib/features/coach_chat/presentation/pages/coach_chat_page.dart` | 110 | CoachChatPage, _CoachChatPageState | -| `lib/features/coach_chat/presentation/widgets/assistant_bubble.dart` | 37 | AssistantBubble | -| `lib/features/coach_chat/presentation/widgets/chat_input_bar.dart` | 53 | ChatInputBar | -| `lib/features/coach_chat/presentation/widgets/coach_chat_app_bar.dart` | 74 | CoachChatAppBar, OnDeviceBadge | -| `lib/features/coach_chat/presentation/widgets/message_list.dart` | 100 | MessageList, MessageBubble, CoachChatEmptyState | -| `lib/features/coach_chat/presentation/widgets/model_setup_screen.dart` | 74 | ModelSetupScreen | +| `lib/features/character/providers/adventure_provider.dart` | 251 | AdventureViewState, AdventureSessionNotifier | +| `lib/features/character/providers/character_provider.dart` | 1 | Dart module | +| `lib/features/character/providers/quest_provider.dart` | 48 | QuestNotifier | +| `lib/features/coach_chat/presentation/pages/coach_chat_page.dart` | 221 | CoachChatPage, _CoachChatPageState | +| `lib/features/coach_chat/presentation/widgets/animated_message_entrance.dart` | 63 | AnimatedMessageEntrance, _AnimatedMessageEntranceState | +| `lib/features/coach_chat/presentation/widgets/assistant_bubble.dart` | 137 | AssistantBubble | +| `lib/features/coach_chat/presentation/widgets/chat_input_bar.dart` | 64 | ChatInputBar | +| `lib/features/coach_chat/presentation/widgets/coach_chat_app_bar.dart` | 100 | CoachChatAppBar, _InferenceBadge | +| `lib/features/coach_chat/presentation/widgets/glass_suggestion_chip.dart` | 89 | GlassSuggestionChip, _GlassSuggestionChipState | +| `lib/features/coach_chat/presentation/widgets/message_list.dart` | 198 | MessageList, _MessageListState, MessageBubble, … | +| `lib/features/coach_chat/presentation/widgets/model_setup_screen.dart` | 107 | ModelSetupScreen | +| `lib/features/coach_chat/presentation/widgets/streaming_text_pulse.dart` | 67 | StreamingTextPulse, _StreamingTextPulseState | | `lib/features/coach_chat/presentation/widgets/typing_indicator.dart` | 52 | TypingIndicator, _TypingIndicatorState | -| `lib/features/coach_chat/providers/coach_chat_provider.dart` | 79 | CoachChatNotifier | -| `lib/features/coach_chat/providers/model_setup_provider.dart` | 35 | ModelSetupNotifier | -| `lib/features/dashboard/presentation/pages/dashboard_page.dart` | 98 | DashboardPage | -| `lib/features/dashboard/presentation/pages/run_history_page.dart` | 126 | RunHistoryPage | -| `lib/features/dashboard/presentation/pages/run_route_page.dart` | 166 | RunRoutePage, _RouteContent, _AppleRouteMap, … | -| `lib/features/dashboard/presentation/widgets/activity_ring.dart` | 82 | ActivityRing, RingPainter | -| `lib/features/dashboard/presentation/widgets/connect_healthkit_card.dart` | 73 | ConnectHealthkitCard | -| `lib/features/dashboard/presentation/widgets/health_metrics_grid.dart` | 93 | HealthMetricsGrid | -| `lib/features/dashboard/presentation/widgets/readiness_card.dart` | 142 | ReadinessCard, ConfidenceBadgeRow | -| `lib/features/dashboard/presentation/widgets/today_insight_cards.dart` | 185 | TodayInsightCards, ActionCompactCard, _ActionCompactCardState, … | -| `lib/features/dashboard/providers/ai_insight_provider.dart` | 58 | Generates a single coaching sentence from today's health data using the | +| `lib/features/coach_chat/providers/coach_chat_provider.dart` | 324 | CoachChatNotifier, LastAiInferenceBackend | +| `lib/features/coach_chat/providers/coach_chat_seed_provider.dart` | 1 | Dart module | +| `lib/features/coach_chat/providers/model_setup_provider.dart` | 2 | Dart module | +| `lib/features/coach_chat/providers/model_setup_state.dart` | 1 | Dart module | +| `lib/features/coach_chat/utils/chat_history_codec.dart` | 74 | Serialises coach chat history to SharedPreferences JSON. | +| `lib/features/dashboard/presentation/pages/dashboard_page.dart` | 347 | DashboardPage, _DashboardPageState | +| `lib/features/dashboard/presentation/pages/run_history_page.dart` | 138 | RunHistoryPage | +| `lib/features/dashboard/presentation/pages/run_route_missing_page.dart` | 53 | RunRouteMissingPage | +| `lib/features/dashboard/presentation/pages/run_route_page.dart` | 267 | RunRoutePage, _RunRouteScaffold, _RouteContent, … | +| `lib/features/dashboard/presentation/widgets/activity_ring.dart` | 214 | ActivityRing, AnimatedActivityRing, _AnimatedActivityRingState, … | +| `lib/features/dashboard/presentation/widgets/character_glance_card.dart` | 89 | CharacterGlanceCard | +| `lib/features/dashboard/presentation/widgets/coach_insight_card.dart` | 214 | CoachInsightCard, _CoachInsightContent, _AcwrOnlyCard, … | +| `lib/features/dashboard/presentation/widgets/connect_healthkit_card.dart` | 88 | ConnectHealthkitCard | +| `lib/features/dashboard/presentation/widgets/daily_quest_teaser.dart` | 129 | DailyQuestTeaser | +| `lib/features/dashboard/presentation/widgets/dashboard_header_sliver.dart` | 109 | DashboardHeaderSliver | +| `lib/features/dashboard/presentation/widgets/gait_teaser_card.dart` | 94 | GaitTeaserCard | +| `lib/features/dashboard/presentation/widgets/health_metrics_grid.dart` | 254 | HealthMetricsGrid | +| `lib/features/dashboard/presentation/widgets/hrv_sparkline.dart` | 68 | HrvSparkline | +| `lib/features/dashboard/presentation/widgets/last_run_preview.dart` | 76 | LastRunPreview | +| `lib/features/dashboard/presentation/widgets/metric_detail_sheet.dart` | 185 | MetricDetailSheet, _MetricMeta | +| `lib/features/dashboard/presentation/widgets/readiness_card.dart` | 216 | ReadinessCard, _ReadinessCardContent, ConfidenceBadgeRow | +| `lib/features/dashboard/presentation/widgets/trend_carousel.dart` | 96 | TrendCarousel, _TrendCarouselState, _TrendPage | +| `lib/features/dashboard/presentation/widgets/week_momentum_card.dart` | 191 | WeekMomentumCard | +| `lib/features/dashboard/presentation/widgets/what_changed_chips.dart` | 45 | WhatChangedChips | +| `lib/features/dashboard/providers/dashboard_summary_provider.dart` | 60 | Dart module | +| `lib/features/dashboard/providers/post_run_debrief_provider.dart` | 114 | PostRunDebriefState, PostRunDebriefNotifier | | `lib/features/dashboard/providers/today_insights_provider.dart` | 54 | TodayInsightsState | -| `lib/features/nexus_lab/presentation/nexus_lab_page.dart` | 202 | NexusLabPage, _Content, _LoadingState, … | -| `lib/features/onboarding/presentation/onboarding_page.dart` | 230 | OnboardingItem, OnboardingPage, _OnboardingPageState, … | -| `lib/features/onboarding/providers/onboarding_provider.dart` | 28 | OnboardingCompleted | -| `lib/features/settings/presentation/pages/settings_page.dart` | 179 | SettingsPage, _SwitchTile, _DropdownTile, … | -| `lib/features/settings/providers/settings_provider.dart` | 57 | SettingsState, Settings | -| `lib/features/training/presentation/pages/training_page.dart` | 139 | TrainingPage | -| `lib/features/training/presentation/widgets/chart_placeholder.dart` | 15 | ChartPlaceholder | -| `lib/features/training/presentation/widgets/hrv_chart.dart` | 83 | HrvChart | -| `lib/features/training/presentation/widgets/load_chart.dart` | 87 | LoadChart | -| `lib/features/training/presentation/widgets/past_runs_list.dart` | 35 | PastRunsList | +| `lib/features/nexus_lab/presentation/nexus_lab_page.dart` | 211 | NexusLabPage, _Content, _LoadingState, … | +| `lib/features/onboarding/presentation/onboarding_page.dart` | 279 | OnboardingItem, OnboardingPage, _OnboardingPageState, … | +| `lib/features/onboarding/providers/onboarding_provider.dart` | 1 | Dart module | +| `lib/features/settings/presentation/on_device_model_selection_result.dart` | 10 | OnDeviceModelSelectionResult | +| `lib/features/settings/presentation/pages/health_import_page.dart` | 118 | HealthImportPage | +| `lib/features/settings/presentation/pages/manual_run_page.dart` | 167 | ManualRunPage, _ManualRunPageState | +| `lib/features/settings/presentation/pages/on_device_model_picker_page.dart` | 107 | OnDeviceModelPickerPage, _OnDeviceModelPickerPageState | +| `lib/features/settings/presentation/pages/openrouter_model_picker_page.dart` | 215 | OpenRouterModelPickerPage | +| `lib/features/settings/presentation/pages/settings_page.dart` | 600 | SettingsPage, _SettingsPageState, _SwitchTile, … | +| `lib/features/settings/presentation/widgets/apple_health_export_preview_card.dart` | 104 | AppleHealthExportPreviewCard | +| `lib/features/settings/presentation/widgets/gpx_import_preview_card.dart` | 82 | GpxImportPreviewCard | +| `lib/features/settings/presentation/widgets/health_import_preview_row.dart` | 27 | HealthImportPreviewRow | +| `lib/features/settings/presentation/widgets/health_import_progress_card.dart` | 58 | HealthImportProgressCard | +| `lib/features/settings/presentation/widgets/on_device_model_card.dart` | 98 | OnDeviceModelCard | +| `lib/features/settings/presentation/widgets/openrouter_model_card.dart` | 80 | OpenRouterModelCard | +| `lib/features/settings/presentation/widgets/settings_appearance_section.dart` | 39 | SettingsAppearanceSection | +| `lib/features/settings/providers/health_import_provider.dart` | 280 | HealthImportState, HealthImport | +| `lib/features/settings/providers/manual_run_provider.dart` | 104 | ManualRunState, ManualRun | +| `lib/features/settings/providers/openrouter_models_provider.dart` | 121 | OpenRouterCatalogState, OpenRouterCatalog | +| `lib/features/settings/providers/settings_provider.dart` | 1 | Dart module | +| `lib/features/training/presentation/pages/training_page.dart` | 172 | TrainingPage | +| `lib/features/training/presentation/widgets/past_runs_list.dart` | 46 | PastRunsList | | `lib/features/training/presentation/widgets/training_chip.dart` | 33 | TrainingChip | -| `lib/features/training/presentation/widgets/training_insight_cards.dart` | 74 | TrainingInsightsCards | +| `lib/features/training/presentation/widgets/training_insight_cards.dart` | 78 | TrainingInsightsCards | | `lib/features/training/presentation/widgets/training_insight_list_card.dart` | 28 | TrainingInsightListCard | | `lib/features/training/presentation/widgets/training_insight_text_card.dart` | 34 | TrainingInsightTextCard | | `lib/features/training/presentation/widgets/trend_cards.dart` | 76 | TrendCards | | `lib/features/training/presentation/widgets/weekly_stats_grid.dart` | 90 | WeeklyStatsGrid | | `lib/features/training/providers/training_insights_provider.dart` | 56 | TrainingInsightsState | -| `lib/infrastructure/ai/ai_infrastructure_providers.dart` | 20 | Provides the [AiCoachRepository] singleton. | +| `lib/infrastructure/ai/ai_infrastructure_providers.dart` | 8 | Provides the [AiModelRepository] singleton. | | `lib/infrastructure/ai/biomechanics/biomechanics_coefficients_protobuf.dart` | 169 | BiomechanicsCoefficientsProtobuf | -| `lib/infrastructure/ai/biomechanics/on_device_biomechanics_repository.dart` | 234 | OnDeviceBiomechanicsRepository | -| `lib/infrastructure/ai/gemma/ai_isolate_bridge.dart` | 2 | Dart module | -| `lib/infrastructure/ai/gemma/ai_isolate_entrypoint.dart` | 163 | Dart module | -| `lib/infrastructure/ai/gemma/ai_isolate_messages.dart` | 91 | AiInitRequest, AiChatRequest, AiTrainRegressionRequest, … | +| `lib/infrastructure/ai/biomechanics/on_device_biomechanics_repository.dart` | 238 | OnDeviceBiomechanicsRepository | +| `lib/infrastructure/ai/gemma/ai_isolate_bridge.dart` | 3 | Dart module | +| `lib/infrastructure/ai/gemma/ai_isolate_entrypoint.dart` | 160 | Dart module | +| `lib/infrastructure/ai/gemma/ai_isolate_messages.dart` | 121 | AiInitRequest, AiChatRequest, AiReloadChatRequest, … | | `lib/infrastructure/ai/gemma/ai_regression_math.dart` | 99 | Fits multivariate regression coefficients for stride length prediction. | -| `lib/infrastructure/ai/on_device_ai_coach_repository.dart` | 58 | OnDeviceAiCoachRepository | -| `lib/infrastructure/ai/on_device_model_repository.dart` | 20 | OnDeviceModelRepository | -| `lib/infrastructure/gamification/character_persistence_repository.dart` | 86 | CharacterPersistenceRepository | +| `lib/infrastructure/ai/gemma/coach_prompt_builder.dart` | 13 | Builds the user turn for coach chat (system instruction lives on [InferenceChat]). | +| `lib/infrastructure/ai/gemma/gemma_device_ram_probe.dart` | 51 | Dart module | +| `lib/infrastructure/ai/gemma/gemma_inference_session.dart` | 55 | Dart module | +| `lib/infrastructure/ai/gemma/gemma_runtime.dart` | 80 | Central bootstrap for flutter_gemma 1.x opt-in inference engines. | +| `lib/infrastructure/ai/gemma/gemma_runtime_tier.dart` | 18 | Resolves the current on-device Gemma tier from platform RAM and thermal probes. | +| `lib/infrastructure/ai/gemma/gemma_thermal_probe.dart` | 19 | Reads platform thermal / power-save signals for conservative Gemma routing. | +| `lib/infrastructure/ai/gemma/on_device_model_installer.dart` | 20 | Maps domain [OnDeviceModel] specs to flutter_gemma install APIs. | +| `lib/infrastructure/ai/gemma/regression_isolate_entrypoint.dart` | 42 | Lightweight background worker for gait regression math (no LLM deps). | +| `lib/infrastructure/ai/hybrid_ai_coach_repository.dart` | 157 | HybridAiCoachConfig, HybridAiCoachRepository | +| `lib/infrastructure/ai/isolate_ai_coach_repository.dart` | 339 | IsolateAiCoachRepository | +| `lib/infrastructure/ai/on_device_model_repository.dart` | 94 | OnDeviceModelRepository | +| `lib/infrastructure/ai/openrouter/openrouter_api_client.dart` | 181 | OpenRouterApiClient | +| `lib/infrastructure/ai/openrouter/openrouter_cloud_ai_repository.dart` | 24 | OpenRouterCloudAiRepository | +| `lib/infrastructure/ai/openrouter/openrouter_models_repository_impl.dart` | 46 | OpenRouterModelsRepositoryImpl | +| `lib/infrastructure/ai/secure_api_key_storage.dart` | 41 | SecureApiKeyStorage | +| `lib/infrastructure/gamification/character_persistence_repository.dart` | 118 | CharacterPersistenceRepository | | `lib/infrastructure/gamification/gamekit_repository_impl.dart` | 80 | GameKitRepositoryImpl | | `lib/infrastructure/health/apple_workout_route_channel.dart` | 36 | AppleWorkoutRouteChannel | +| `lib/infrastructure/health/composite_health_repository.dart` | 189 | CompositeHealthRepository | +| `lib/infrastructure/health/drift_imported_health_store.dart` | 177 | DriftImportedHealthStore | | `lib/infrastructure/health/health_data_aggregator.dart` | 173 | _DailyAccumulator | -| `lib/infrastructure/health/health_infrastructure_providers.dart` | 13 | Platform binding for Apple HealthKit. | -| `lib/infrastructure/health/health_kit_repository.dart` | 151 | HealthKitRepository | +| `lib/infrastructure/health/health_infrastructure_providers.dart` | 28 | Dart module | +| `lib/infrastructure/health/health_kit_repository.dart` | 251 | HealthKitRepository | | `lib/infrastructure/health/health_kit_workout_mapper.dart` | 110 | Returns true when [point] represents a running workout. | +| `lib/infrastructure/health/import/apple_health_date_parser.dart` | 22 | Parses timestamps from Apple Health `export.xml` attributes. | +| `lib/infrastructure/health/import/apple_health_export_isolate.dart` | 30 | Parses Apple Health zip archives off the UI isolate. | +| `lib/infrastructure/health/import/apple_health_export_parser.dart` | 296 | AppleHealthWorkoutImport, AppleHealthExportParseResult, AppleHealthExportParser | +| `lib/infrastructure/health/import/apple_health_record_aggregator.dart` | 250 | AppleHealthRecordAggregator, _DailyAccumulator | +| `lib/infrastructure/health/import/apple_health_unit_converter.dart` | 62 | Converts Apple Health export units into KYNOS canonical units. | +| `lib/infrastructure/health/import/apple_health_workout_builder.dart` | 48 | AppleHealthWorkoutBuilder | +| `lib/infrastructure/health/import/apple_health_xml_event_stream.dart` | 35 | Chunk size for streaming `export.xml` without loading the full file. | +| `lib/infrastructure/health/import/gpx_workout_parser.dart` | 114 | GpxParseResult, GpxWorkoutParser | +| `lib/infrastructure/health/imported_health_connection_native.dart` | 14 | Dart module | +| `lib/infrastructure/health/imported_health_connection_stub.dart` | 5 | Dart module | +| `lib/infrastructure/health/imported_health_database.dart` | 59 | ImportedWorkouts, ImportedRoutePoints, ImportedDailySummaries, … | +| `lib/infrastructure/health/imported_health_database_providers.dart` | 8 | Dart module | +| `lib/infrastructure/health/imported_health_repository.dart` | 109 | ImportedHealthRepository | +| `lib/infrastructure/health/imported_health_store.dart` | 124 | Contract for locally persisted imported workouts. | +| `lib/infrastructure/health/imported_summary_merger.dart` | 54 | Merges imported daily summaries, preferring stored metrics and adding | +| `lib/infrastructure/health/imported_workout_summary_aggregator.dart` | 59 | _DayRollup | +| `lib/infrastructure/health/platform_imported_health_store_native.dart` | 8 | Dart module | +| `lib/infrastructure/health/platform_imported_health_store_web.dart` | 8 | Dart module | +| `lib/infrastructure/health/prefs_imported_health_store.dart` | 162 | PrefsImportedHealthStore | | `lib/infrastructure/privacy/local_sync_privacy_guard.dart` | 58 | LocalSyncPrivacyGuard | -| `lib/main.dart` | 29 | Dart module | +| `lib/main.dart` | 36 | Dart module | +| `lib/shared/constants/hero_tags.dart` | 8 | Shared Hero tag constants for cross-route transitions. | | `lib/shared/providers/ai_insights_usecase_provider.dart` | 23 | Dart module | -| `lib/shared/providers/ai_repository_providers.dart` | 4 | Dart module | +| `lib/shared/providers/ai_reconnect_provider.dart` | 14 | AiReconnectState | +| `lib/shared/providers/ai_repository_providers.dart` | 65 | Dart module | | `lib/shared/providers/biomechanics_provider.dart` | 20 | Dart module | -| `lib/shared/providers/gamification_providers.dart` | 37 | Dart module | -| `lib/shared/providers/health_providers.dart` | 110 | HealthPermissionsNotifier | +| `lib/shared/providers/character_providers.dart` | 29 | Loads the persisted [RunnerCharacter], creating one via class assignment | +| `lib/shared/providers/coach_chat_seed_provider.dart` | 18 | CoachChatSeed | +| `lib/shared/providers/daily_quests_provider.dart` | 60 | Dart module | +| `lib/shared/providers/gamification_providers.dart` | 61 | Dart module | +| `lib/shared/providers/health_import_providers.dart` | 23 | Dart module | +| `lib/shared/providers/health_providers.dart` | 148 | HealthPermissionsNotifier, ImportedHealthDataNotifier | | `lib/shared/providers/health_repository_provider.dart` | 15 | Dart module | +| `lib/shared/providers/huggingface_token_provider.dart` | 25 | HuggingFaceTokenManager | +| `lib/shared/providers/measurable_quest_sync_provider.dart` | 79 | MeasurableQuestSync | +| `lib/shared/providers/model_setup_provider.dart` | 121 | MissingHuggingFaceTokenException, ModelSetupNotifier | +| `lib/shared/providers/model_setup_state.dart` | 17 | ModelSetupState | | `lib/shared/providers/nexus_lab_provider.dart` | 74 | NexusLabState, NexusLabNotifier | +| `lib/shared/providers/onboarding_provider.dart` | 27 | OnboardingCompleted | +| `lib/shared/providers/openrouter_api_key_provider.dart` | 28 | OpenRouterApiKeyManager | +| `lib/shared/providers/settings_provider.dart` | 186 | SettingsState, Settings | +| `lib/shared/providers/shared_preferences_provider.dart` | 12 | Synchronously accessible [SharedPreferences] — override at app startup. | +| `lib/shared/providers/training_insights_provider.dart` | 1 | Dart module | +| `lib/shared/providers/workout_session_lookup_provider.dart` | 16 | Resolves a [WorkoutSession] by id for deep-linked run routes. | | `lib/shared/utils/date_label.dart` | 20 | Formats today's date as "Mon, Jan 5". | +| `lib/shared/utils/health_permission_feedback.dart` | 14 | User-facing copy for health permission flows — never expose raw exceptions. | +| `lib/shared/utils/health_platform_labels.dart` | 41 | Platform-aware labels for health data sources. | | `lib/shared/utils/insight_text_formatter.dart` | 48 | Formats coaching insight text for concise, beginner-friendly UI labels. | -| `lib/shared/widgets/gait_model_card.dart` | 225 | GaitModelCard, _CoefficientsRow, GaitModelCardAsync | -| `lib/shared/widgets/glass_card.dart` | 64 | GlassCard | -| `lib/shared/widgets/insight_expandable_card.dart` | 197 | InsightExpandableCard, _InsightExpandableCardState, InsightTextExpandableCard, … | +| `lib/shared/utils/navigation_utils.dart` | 11 | Pops the current route or navigates to [fallbackRoute] when nothing to pop. | +| `lib/shared/utils/picked_file_bytes.dart` | 2 | Dart module | +| `lib/shared/utils/picked_file_bytes_io.dart` | 15 | Dart module | +| `lib/shared/utils/picked_file_bytes_web.dart` | 9 | Dart module | +| `lib/shared/utils/run_date_label.dart` | 18 | Formats a workout date for run cards and Hero transitions. | +| `lib/shared/utils/url_opener.dart` | 20 | Opens an external URL in the platform browser. | +| `lib/shared/widgets/ai_lifecycle_guard.dart` | 74 | AiLifecycleGuard, _AiLifecycleGuardState | +| `lib/shared/widgets/animated_async_content.dart` | 84 | AnimatedAsyncContent, FadeInOnAppear, _FadeInOnAppearState | +| `lib/shared/widgets/animated_progress_bar.dart` | 43 | AnimatedProgressBar | +| `lib/shared/widgets/charts/chart_placeholder.dart` | 15 | ChartPlaceholder | +| `lib/shared/widgets/charts/hrv_chart.dart` | 86 | HrvChart | +| `lib/shared/widgets/charts/load_chart.dart` | 90 | LoadChart | +| `lib/shared/widgets/gait_model_card.dart` | 229 | GaitModelCard, _CoefficientsRow, GaitModelCardAsync | +| `lib/shared/widgets/glass_card.dart` | 43 | GlassCard | +| `lib/shared/widgets/insight_expandable_card.dart` | 221 | InsightExpandableCard, _InsightExpandableCardState, InsightTextExpandableCard, … | +| `lib/shared/widgets/kynos_bottom_nav.dart` | 192 | KynosBottomNavItem, KynosBottomNav, _NavBarItem | | `lib/shared/widgets/kynos_card.dart` | 57 | KynosCard | | `lib/shared/widgets/kynos_chip.dart` | 128 | KynosChip, _MetricChip, _AccentChip | -| `lib/shared/widgets/kynos_hero_banner.dart` | 110 | KynosHeroBanner | -| `lib/shared/widgets/kynos_loading_line.dart` | 53 | KynosLoadingLine | +| `lib/shared/widgets/kynos_hero_banner.dart` | 125 | KynosHeroBanner | +| `lib/shared/widgets/kynos_inline_error_card.dart` | 53 | KynosInlineErrorCard | +| `lib/shared/widgets/kynos_loading_line.dart` | 59 | KynosLoadingLine | +| `lib/shared/widgets/kynos_page_dots.dart` | 45 | KynosPageDots | | `lib/shared/widgets/kynos_privacy_footer.dart` | 25 | KynosPrivacyFooter | | `lib/shared/widgets/kynos_section_header.dart` | 21 | KynosSectionHeader | +| `lib/shared/widgets/kynos_section_row.dart` | 38 | KynosSectionRow | | `lib/shared/widgets/kynos_skeleton.dart` | 41 | KynosSkeleton | -| `lib/shared/widgets/kynos_user_bubble.dart` | 45 | KynosUserBubble | -| `lib/shared/widgets/metric_tile.dart` | 91 | MetricTile | -| `lib/shared/widgets/nav_icon.dart` | 190 | NavIconPainter | -| `lib/shared/widgets/run_card.dart` | 115 | RunCard | -| `lib/shared/widgets/widgets.dart` | 14 | KYNOS shared widgets — barrel export. | +| `lib/shared/widgets/kynos_user_bubble.dart` | 57 | KynosUserBubble | +| `lib/shared/widgets/liquid_glass_button.dart` | 142 | LiquidGlassButton, LiquidGlassIconButton | +| `lib/shared/widgets/liquid_glass_surface.dart` | 103 | LiquidGlassSurface | +| `lib/shared/widgets/metric_tile.dart` | 131 | MetricTile | +| `lib/shared/widgets/nav_icon.dart` | 241 | NavIconDefinition, NavIconPainter | +| `lib/shared/widgets/responsive_center.dart` | 30 | ResponsiveCenter | +| `lib/shared/widgets/run_card.dart` | 125 | RunCard | +| `lib/shared/widgets/widgets.dart` | 23 | KYNOS shared widgets — barrel export. | ## Hot Files (do not grow) | File | Lines | Target | |------|------:|--------| -| `lib/domain/usecases/insights/generate_training_insights_usecase.dart` | 294 | Split if > 250 lines | +| `lib/features/settings/presentation/pages/settings_page.dart` | 600 | Split if > 250 lines | +| `lib/features/dashboard/presentation/pages/dashboard_page.dart` | 347 | Split if > 250 lines | +| `lib/infrastructure/ai/isolate_ai_coach_repository.dart` | 339 | Split if > 250 lines | +| `lib/features/coach_chat/providers/coach_chat_provider.dart` | 324 | Split if > 250 lines | +| `lib/domain/usecases/gamification/generate_daily_quests_usecase.dart` | 298 | Split if > 250 lines | +| `lib/domain/usecases/insights/generate_training_insights_usecase.dart` | 296 | Split if > 250 lines | +| `lib/infrastructure/health/import/apple_health_export_parser.dart` | 296 | Split if > 250 lines | +| `lib/features/settings/providers/health_import_provider.dart` | 280 | Split if > 250 lines | +| `lib/features/onboarding/presentation/onboarding_page.dart` | 279 | Split if > 250 lines | +| `lib/features/dashboard/presentation/pages/run_route_page.dart` | 267 | Split if > 250 lines | +| `lib/features/character/presentation/widgets/quest_card.dart` | 254 | Split if > 250 lines | +| `lib/features/dashboard/presentation/widgets/health_metrics_grid.dart` | 254 | Split if > 250 lines | +| `lib/features/character/providers/adventure_provider.dart` | 251 | Split if > 250 lines | +| `lib/infrastructure/health/health_kit_repository.dart` | 251 | Split if > 250 lines | -_Generated by `dart run tool/generate_codemap.dart` — 131 hand-written Dart files._ +_Generated by `dart run tool/generate_codemap.dart` — 278 hand-written Dart files._ + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..22b01e4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# Contributing to KYNOS + +Thank you for contributing! This document is a short guide for human contributors. +**AI agents** should start at [docs/agents/README.md](docs/agents/README.md). + +## Before You Code + +1. Read [CODEMAP.md](CODEMAP.md) to find the right files +2. Read [AGENTS.md](AGENTS.md) for architecture rules and PR workflow +3. For UI work, follow the design system in AGENTS.md §14 + +## Architecture + +``` +features/ → domain/ ← infrastructure/ +features/ → shared/providers/ → domain + infrastructure +``` + +- `domain/` is pure Dart — no Flutter or Riverpod imports +- Features never import `infrastructure/` directly — use `shared/providers/` +- Features never import another feature's `providers/` + +See [docs/architecture/folder_structure.md](docs/architecture/folder_structure.md) for rationale. + +## Development Setup + +```bash +cp .env.example .env +flutter pub get +dart run build_runner build --delete-conflicting-outputs +dart run tool/generate_codemap.dart +flutter analyze +flutter test +``` + +## Pull Requests + +- Target branch: `main` +- Title format: Conventional Commits — e.g. `feat: add coach readiness widgets` +- Fill out the PR template checklist +- Attach visual proof for UI changes +- Address all review feedback before merge + +Agent branches use the pattern `cursor/-d1bd`. + +## Full Rules + +[AGENTS.md](AGENTS.md) is the single source of truth for architecture, testing, +validation, and the mandatory PR review process. diff --git a/README.md b/README.md index 4fe0ab8..de9b7db 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,22 @@ See [CODEMAP.md](CODEMAP.md) for feature entry points and [docs/architecture/fol --- +## For AI Agents + +| Step | Resource | +|------|----------| +| 1. Onboard | [docs/agents/README.md](docs/agents/README.md) — hub, skills index, validation | +| 2. Navigate | [CODEMAP.md](CODEMAP.md) — feature entry points, hot files | +| 3. Follow rules | [AGENTS.md](AGENTS.md) — architecture, PR lifecycle, design system | +| 4. Quick ref | [CLAUDE.md](CLAUDE.md) — bootstrap commands | +| 5. Skills | [`.cursor/skills/`](.cursor/skills/) — `/add-feature`, `/validate-change`, `/open-pr`, etc. | + +**Branch naming:** `cursor/-d1bd` + +**Before opening a PR:** `flutter analyze`, `flutter test`, `flutter build web`, regenerate CODEMAP if `lib/` structure changed. + +--- + ## Getting Started ```bash diff --git a/docs/agents/README.md b/docs/agents/README.md new file mode 100644 index 0000000..4539be1 --- /dev/null +++ b/docs/agents/README.md @@ -0,0 +1,113 @@ +# KYNOS — Agent Documentation Hub + +> **Start here** if you are an AI agent or automated contributor working in this repository. + +## Read Order + +1. **[CODEMAP.md](../../CODEMAP.md)** — where things live (features, routes, hot files) +2. **[AGENTS.md](../../AGENTS.md)** — architecture rules, PR lifecycle, validation +3. **[CLAUDE.md](../../CLAUDE.md)** — quick-reference bootstrap commands + +## Cursor Skills + +Skills live in [`.cursor/skills/`](../../.cursor/skills/). Invoke with `/skill-name` or let the agent auto-select. + +### Workflow skills + +| Skill | When to use | +|-------|-------------| +| `/onboard-agent` | First task in this repo | +| `/add-feature` | New screen, tab, or vertical slice | +| `/wire-repository` | New repository or DI provider | +| `/validate-change` | Before opening a PR | +| `/open-pr` | Create and shepherd a pull request | +| `/fix-ci` | CI check failed on your PR | +| `/regenerate-codemap` | After structural changes under `lib/` | + +### Domain skills (file-scoped) + +| Skill | Scope | +|-------|-------| +| `/ui-design-system` | `lib/features/**`, `lib/shared/widgets/**` | +| `/ai-isolate` | AI inference and coach chat | +| `/health-data` | HealthKit / Health Connect integrations | +| `/privacy-audit` | Zero-knowledge and biometric leak checks | +| `/linear-kynos` | Linear issue management for this project | + +### Audit skills (explicit invocation only) + +Heavy recurring audits — see [workflow_prompts.md](../workflow_prompts.md) for the full index: + +| Skill | Purpose | +|-------|---------| +| `/audit-architecture` | Layer import violations, domain purity | +| `/audit-performance` | 120 Hz jank, `ref.select`, const widgets | +| `/audit-privacy` | Biometric data leak detection | +| `/audit-ai-isolate` | Isolate bridge lifecycle | +| `/audit-ui-polish` | Glassmorphism, motion, haptics | +| `/audit-test-coverage` | Domain use-case and widget test gaps | +| `/audit-health-data` | Health aggregation accuracy | +| `/audit-biomechanics` | Regression model stability | +| `/audit-documentation` | ADR and doc drift | +| `/audit-dart-modernization` | Dart 3 patterns and lint upgrades | + +## Bootstrap + +```bash +cp .env.example .env +flutter pub get +dart run build_runner build --delete-conflicting-outputs +dart run tool/generate_codemap.dart +flutter analyze +flutter test +``` + +## Validation Before PR + +```bash +flutter analyze +flutter test +dart run build_runner build --delete-conflicting-outputs +dart run tool/generate_codemap.dart +bash scripts/check_design_system.sh +bash scripts/check_architecture.sh +flutter build web +``` + +Attach visual proof (screenshots or screen recording) for UI changes. + +## Branch Naming + +Agent branches: `cursor/-d1bd` + +## Definition of Done (PR) + +A PR is complete only when **all** of the following are true: + +- [ ] All CI checks pass +- [ ] Every review comment thread is resolved +- [ ] Every addressed comment has a 👍 reaction +- [ ] No unresolved "Request changes" reviews remain +- [ ] Visual proof of UI changes is attached + +See [AGENTS.md §4](../../AGENTS.md#4-pull-request--review-process) for the full PR lifecycle. + +## Reference Docs + +| Doc | Purpose | +|-----|---------| +| [common-patterns.md](common-patterns.md) | Riverpod, routes, error handling snippets | +| [troubleshooting.md](troubleshooting.md) | Common agent mistakes and fixes | +| [file-templates.md](file-templates.md) | Scaffold templates for new files | +| [folder_structure.md](../architecture/folder_structure.md) | Layer rationale | +| [ai_isolate_design.md](../architecture/ai_isolate_design.md) | Isolate bridge protocol | +| [workflow_prompts.md](../workflow_prompts.md) | Advanced audit prompt index | + +## Architecture (one-liner) + +``` +features/ → domain/ ← infrastructure/ +features/ → shared/providers/ → domain + infrastructure +``` + +**Never** import `infrastructure/` from `features/`. **Never** import one feature's `providers/` from another feature. diff --git a/docs/agents/common-patterns.md b/docs/agents/common-patterns.md new file mode 100644 index 0000000..cb27bfa --- /dev/null +++ b/docs/agents/common-patterns.md @@ -0,0 +1,92 @@ +# Common Patterns + +Concise snippets for recurring KYNOS patterns. See [file-templates.md](file-templates.md) for full scaffolds. + +## Riverpod feature provider + +```dart +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'my_feature_provider.g.dart'; + +@riverpod +class MyFeature extends _$MyFeature { + @override + FutureOr build() async { + final useCase = ref.watch(myUseCaseProvider); + final result = await useCase(); + if (result.failure != null) { + throw result.failure!; + } + return MyState(data: result.value); + } +} +``` + +Run `dart run build_runner build --delete-conflicting-outputs` after adding `@riverpod`. + +## AsyncValue in UI + +```dart +final state = ref.watch(myFeatureProvider); + +return state.when( + data: (data) => MyContent(data: data), + loading: () => const KynosSkeleton(), + error: (error, _) => KynosCard(child: Text(error.toString())), +); +``` + +## Repository return type + +```dart +Future<({HealthSummary? summary, Failure? failure})> getToday(); +``` + +Never throw across layer boundaries — return `Failure` in the record. + +## Route registration + +In `lib/app/router.dart`: + +```dart +// Add to Routes class: +static const myFeature = '/my-feature'; + +// Add GoRoute in routerProvider: +GoRoute( + path: Routes.myFeature, + builder: (context, state) => const MyFeaturePage(), +), +``` + +## DI provider in shared/providers + +```dart +final myRepositoryProvider = Provider((ref) { + return MyRepositoryImpl( + healthStore: ref.watch(healthStoreProvider), + ); +}); +``` + +Features import `shared/providers/` — never `infrastructure/`. + +## Invalidate related providers + +After mutating health data: + +```dart +invalidateHealthProviders(ref); // from health_providers.dart +``` + +## Design system spacing + +```dart +import 'package:gap/gap.dart'; +import 'package:kynos/core/theme/spacing.dart' as tokens; + +const Gap(tokens.Spacing.md), +``` + +Never use `Gap(16)` or raw `SizedBox` for semantic spacing. diff --git a/docs/agents/file-templates.md b/docs/agents/file-templates.md new file mode 100644 index 0000000..e47265e --- /dev/null +++ b/docs/agents/file-templates.md @@ -0,0 +1,178 @@ +# File Templates + +Copy-paste scaffolds for new KYNOS files. Replace `` placeholders. + +## Feature page + +`lib/features//presentation/pages/_page.dart` + +```dart +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:gap/gap.dart'; +import 'package:kynos/core/theme/spacing.dart' as tokens; +import 'package:kynos/core/theme/theme.dart'; +import 'package:kynos/shared/widgets/widgets.dart'; + +class Page extends ConsumerWidget { + const Page({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final theme = context.kynosTheme; + + return Scaffold( + body: SafeArea( + child: ListView( + padding: const EdgeInsets.all(tokens.Spacing.lg), + children: [ + Text('', style: theme.pageTitleStyle), + const Gap(tokens.Spacing.md), + const KynosCard(child: Text('Content')), + ], + ), + ), + ); + } +} +``` + +## Feature provider + +`lib/features//providers/_provider.dart` + +```dart +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part '_provider.g.dart'; + +@riverpod +class extends _$ { + @override + FutureOr build() {} +} +``` + +## Repository interface + +`lib/domain/repositories/_repository.dart` + +```dart +import 'package:kynos/core/errors/failures.dart'; + +abstract interface class Repository { + Future<({String? value, Failure? failure})> doSomething(); +} +``` + +## Use-case + +`lib/domain/usecases//_usecase.dart` + +```dart +import 'package:kynos/core/errors/failures.dart'; +import 'package:kynos/domain/repositories/_repository.dart'; + +class UseCase { + const UseCase(this._repository); + + final Repository _repository; + + Future<({String? value, Failure? failure})> call() { + return _repository.doSomething(); + } +} +``` + +## Infrastructure repository + +`lib/infrastructure//_repository_impl.dart` + +```dart +import 'package:kynos/core/errors/failures.dart'; +import 'package:kynos/domain/repositories/_repository.dart'; + +class RepositoryImpl implements Repository { + @override + Future<({String? value, Failure? failure})> doSomething() async { + try { + // platform integration + return (value: 'ok', failure: null); + } on Object catch (e) { + return (value: null, failure: StorageFailure(e.toString())); + } + } +} +``` + +## Shared DI provider + +`lib/shared/providers/_providers.dart` + +```dart +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:kynos/domain/repositories/_repository.dart'; +import 'package:kynos/infrastructure//_repository_impl.dart'; + +final RepositoryProvider = Provider<Repository>((ref) { + return RepositoryImpl(); +}); +``` + +## Use-case test + +`test/domain/usecases//_usecase_test.dart` + +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:kynos/domain/usecases//_usecase.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockRepository extends Mock implements Repository {} + +void main() { + late MockRepository repository; + late UseCase useCase; + + setUp(() { + repository = MockRepository(); + useCase = UseCase(repository); + }); + + test('returns value on success', () async { + when(() => repository.doSomething()) + .thenAnswer((_) async => (value: 'ok', failure: null)); + + final result = await useCase(); + + expect(result.value, 'ok'); + expect(result.failure, isNull); + }); +} +``` + +## Widget smoke test + +`test/features//presentation/pages/_page_test.dart` + +```dart +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:kynos/features//presentation/pages/_page.dart'; + +void main() { + testWidgets('Page renders', (tester) async { + await tester.pumpWidget( + const ProviderScope(child: MaterialApp(home: Page())), + ); + expect(find.text(''), findsOneWidget); + }); +} +``` + +After creating providers, run: + +```bash +dart run build_runner build --delete-conflicting-outputs +dart run tool/generate_codemap.dart +``` diff --git a/docs/agents/troubleshooting.md b/docs/agents/troubleshooting.md new file mode 100644 index 0000000..af53fa3 --- /dev/null +++ b/docs/agents/troubleshooting.md @@ -0,0 +1,98 @@ +# Troubleshooting + +Common issues agents hit in KYNOS and how to fix them. + +## build_runner / generated files out of sync + +**CI error:** "Generated files are out of sync" + +```bash +dart run build_runner build --delete-conflicting-outputs +git add lib/**/*.g.dart lib/**/*.freezed.dart +git commit -m "chore: regenerate build_runner outputs" +``` + +## CODEMAP.md stale + +**CI error:** CODEMAP drift after structural changes + +```bash +dart run tool/generate_codemap.dart +git add CODEMAP.md +``` + +Or use `/regenerate-codemap`. + +## PR title rejected + +**CI error:** `validate-pr-title` failed + +Use Conventional Commits with **lowercase** subject: +- Good: `feat: add coach readiness widgets` +- Bad: `Feat: Add Coach Widgets` + +## Feature imported infrastructure + +**CI error:** `check_architecture.sh` failed + +Move the binding to `lib/shared/providers/` and re-export types if the feature needs them: + +```dart +// shared/providers/my_providers.dart +export 'package:kynos/infrastructure/foo/bar.dart' show BarType; +``` + +Feature files import from `shared/providers/` only. + +## Design system check failed + +**CI error:** Raw `Gap(N)` or `Colors.*` in features + +- Replace `Gap(8)` with `Gap(tokens.Spacing.sm)` +- Replace `Colors.red` with `context.kynosTheme` semantic colors +- Move inline `GoogleFonts` to `core/theme/typography.dart` + +```bash +bash scripts/check_design_system.sh +``` + +## flutter analyze errors after provider change + +1. Run build_runner +2. Check `part 'file.g.dart';` matches generated file name +3. Ensure `@riverpod` class extends `_$ClassName` + +## flutter build web fails + +Common causes: +- Missing `kIsWeb` guard for platform-only code +- Importing `dart:io` without conditional import + +Run locally: `flutter build web` and fix reported errors. + +## Cross-feature provider import + +Features must not import another feature's `providers/`. Extract shared state to `lib/shared/providers/`. + +## Domain layer has Flutter import + +Move UI code to `features/` or `shared/widgets/`. `domain/` must be pure Dart. + +## Tests fail with real platform APIs + +Override providers in tests: + +```dart +await tester.pumpWidget( + ProviderScope( + overrides: [ + healthRepositoryProvider.overrideWithValue(fakeHealthRepo), + ], + child: const MyApp(), + ), +); +``` + +## Agent abandoned PR + +AGENTS.md requires monitoring until merge. Return to the PR, address feedback, 👍 resolved comments, re-run `/validate-change`. diff --git a/docs/workflow_prompts.md b/docs/workflow_prompts.md index 928da40..42dbfaf 100644 --- a/docs/workflow_prompts.md +++ b/docs/workflow_prompts.md @@ -1,12 +1,35 @@ # KYNOS — Advanced Continuous Improvement Agents -These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recurring basis. They enforce high-level engineering standards and require **empirical proof of work**. +> **Agent hub:** [docs/agents/README.md](agents/README.md) · **Skills:** `.cursor/skills/audits/` + +These prompts are designed to be run by AI agents on a recurring basis. Each prompt +has a corresponding Cursor skill under `.cursor/skills/audits/` — invoke with +`/audit-`. Skills use `disable-model-invocation: true` so they only run when +explicitly requested. + +**Architecture (actual):** `features/ → domain/ ← infrastructure/` with +`shared/providers/` as the DI composition root. There is **no `data/` layer yet**. + +| # | Prompt | Skill | AGENTS.md reference | +|---|--------|-------|---------------------| +| 1 | Architectural Integrity Sentinel | `/audit-architecture` | §5 Dependency Rule | +| 2 | 120Hz ProMotion Performance | `/audit-performance` | §6 State, §14 UI | +| 3 | Zero-Knowledge Privacy Guardian | `/audit-privacy` | §11 Privacy | +| 4 | AI Coach Isolate Specialist | `/audit-ai-isolate` | §10 On-Device AI | +| 5 | Glassmorphism & Motion Designer | `/audit-ui-polish` | §14 UI Components | +| 6 | Automated Quality & Test Coverage | `/audit-test-coverage` | §13 Testing | +| 7 | Health Data Integrity Specialist | `/audit-health-data` | §9 Error Handling | +| 8 | Biomechanics Model Tuner | `/audit-biomechanics` | §10 On-Device AI | +| 9 | Documentation & ADR Governance | `/audit-documentation` | §2 Repository Map | +| 10 | Modern Dart & Flutter Optimization | `/audit-dart-modernization` | §15 What NOT to Do | + +All audits require **empirical proof of work** (see each skill for specifics). --- -## 1. The Architectural Integrity Sentinel (Rule #1 Enforcement) +## 1. The Architectural Integrity Sentinel (AGENTS.md §5) **Role:** Senior Software Architect -**Objective:** Continuously audit and reinforce the `Feature -> Domain <- Data` dependency rule. +**Objective:** Continuously audit and reinforce the `features → domain ← infrastructure` dependency rule. ### **Technical Instructions:** 1. **Audit Phase:** Map all imports in `lib/features/`. Use `grep` or static analysis to find any import that bypasses the `domain` layer (e.g., a Feature importing a concrete Repository or another Feature's internal widgets). @@ -20,11 +43,13 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu ### **Proof of Work (Mandatory):** - **Build Proof:** Output of a successful `flutter analyze` showing 0 errors/warnings. -- **Architectural Proof:** A clean `grep` log showing that `lib/domain/` has no prohibited imports. +- **Architectural Proof:** A clean `bash scripts/check_architecture.sh` run. + +**Skill:** `.cursor/skills/audits/audit-architecture/SKILL.md` --- -## 2. The 120Hz ProMotion Performance Agent (Rule #10 Optimization) +## 2. The 120Hz ProMotion Performance Agent (AGENTS.md §14) **Role:** Graphics & Performance Engineer **Objective:** Proactively hunt for jank and ensure 120Hz scrolling fidelity. @@ -42,9 +67,11 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu - **Visual Proof:** A screen recording of the app running with the **Performance Overlay** enabled. The bars must stay consistently below the 8.3ms line. - **Optimization Log:** A list of widgets converted to `const` or optimized via `ref.select`. +**Skill:** `.cursor/skills/audits/audit-performance/SKILL.md` + --- -## 3. The Zero-Knowledge Privacy Guardian (Rule #7 Auditor) +## 3. The Zero-Knowledge Privacy Guardian (AGENTS.md §11) **Role:** Security & Privacy Specialist **Objective:** Ensure biometric data never leaves the device or enters a log stream. @@ -62,9 +89,11 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu - **Audit Proof:** A comprehensive `grep` or `ripgrep` report showing that sensitive variable names (e.g., `hrvMs`, `rhrBpm`) never appear in logging statements. - **Build Proof:** Verify `AppConstants.zeroKnowledgeMode == true`. +**Skill:** `.cursor/skills/audits/audit-privacy/SKILL.md` + --- -## 4. The AI Coach Isolate Specialist (Rule #6 Bridge) +## 4. The AI Coach Isolate Specialist (AGENTS.md §10) **Role:** Systems & Concurrency Engineer **Objective:** Evolve the background isolate bridge for Gemma LLM inference. @@ -82,29 +111,33 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu - **Visual Proof:** A video showing a chat session where the text streams smoothly while the user is actively scrolling or interacting with other UI elements. - **Isolate Log:** Proof of the background isolate's RAM consumption during inference. +**Skill:** `.cursor/skills/audits/audit-ai-isolate/SKILL.md` + --- -## 5. The Glassmorphism & Motion Designer (UX Polish) +## 5. The Glassmorphism & Motion Designer (AGENTS.md §14) **Role:** High-End UI/UX Developer **Objective:** Maintain the "Liquid Glass" aesthetic and interactive feedback loops. ### **Technical Instructions:** -1. **Visual Iteration:** Refine the `GlassCard` and `GlassCard` borders. Ensure the tinting matches the latest Apple Fitness iOS 18 design language. +1. **Visual Iteration:** Refine the `GlassCard` borders. Ensure the tinting matches the latest Apple Fitness iOS 18 design language. 2. **Interactive Feedback:** Implement `HapticFeedback.mediumImpact()` on primary actions and `selectionClick()` on scrolling elements. 3. **Motion Design:** Ensure all transitions (e.g., moving from Today to Lab) use `go_router` custom transitions that feel "fluid" rather than "mechanical." ### **Agent Self-Reflection & Evaluation:** - "Does the Glassmorphism look premium in both Light and Dark modes?" - "Is the haptic feedback purposeful, or is it just 'noise'?" -- "Did I follow the spacing tokens in `core/theme/spacing.dart` strictly (Rule #10)?" +- "Did I follow the spacing tokens in `core/theme/spacing.dart` strictly?" ### **Proof of Work (Mandatory):** - **Visual Proof:** High-resolution screenshots (Light vs Dark mode) and a video demonstrating the fluid transitions and haptic feedback triggers. - **Thematic Proof:** Verification that all colors used are from the `AppTheme` palette. +**Skill:** `.cursor/skills/audits/audit-ui-polish/SKILL.md` + --- -## 6. The Automated Quality & Test Coverage Agent (Rule #9) +## 6. The Automated Quality & Test Coverage Agent (AGENTS.md §13) **Role:** SDET / Quality Engineer **Objective:** Continuously shrink the "Untested Code" surface area. @@ -122,9 +155,11 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu - **Test Proof:** Output of `flutter test` showing 0 failures. - **Coverage Proof:** An HTML coverage report (or summary) showing increased percentage in the target feature. +**Skill:** `.cursor/skills/audits/audit-test-coverage/SKILL.md` + --- -## 7. The Health Data Integrity Specialist (v0.2 Evolve) +## 7. The Health Data Integrity Specialist **Role:** Data Engineer / Health Specialist **Objective:** Improve the robustness and accuracy of biometric data processing. @@ -136,15 +171,17 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu ### **Agent Self-Reflection & Evaluation:** - "Does the app handle a user having 0 health data gracefully? (No empty grey screens)." - "Is the data aggregation mathematically accurate for users who sleep across midnight?" -- "Did I follow Rule #5 for error handling (Sealed Failure hierarchy)?" +- "Did I follow AGENTS.md §9 for error handling (Sealed Failure hierarchy)?" ### **Proof of Work (Mandatory):** - **Logic Proof:** Unit tests covering complex data scenarios (e.g., split sleep, daylight savings shifts). - **Visual Proof:** Screenshot of the Dashboard showing clear "Empty State" or "Permission Required" UI. +**Skill:** `.cursor/skills/audits/audit-health-data/SKILL.md` + --- -## 8. The Biomechanics Model Tuner (v0.4 Numerical Agent) +## 8. The Biomechanics Model Tuner **Role:** Data Scientist / Biomechanics Engineer **Objective:** Ensure the on-device regression model is numerically stable and accurate. @@ -154,7 +191,7 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu 3. **Validation:** Implement a "Confidence Score" for the predicted stride length. ### **Agent Self-Reflection & Evaluation:** -- "Is the regression math pure enough to run in a background isolate (Rule #6)?" +- "Is the regression math pure enough to run in a background isolate?" - "Did I handle the 'Cold Start' problem where the user has only 1 or 2 data points?" - "Is the Protocol Buffer schema versioned for future updates?" @@ -162,6 +199,8 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu - **Mathematical Proof:** A unit test verifying the OLS prediction against a Python-verified reference dataset. - **Visual Proof:** A screenshot of the "Nexus Lab" showing the current β-coefficients and model confidence. +**Skill:** `.cursor/skills/audits/audit-biomechanics/SKILL.md` + --- ## 9. The Documentation & ADR Governance Agent @@ -171,7 +210,7 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu ### **Technical Instructions:** 1. **Synchronization Audit:** Read all `.md` files in `docs/architecture/`. Compare the "Isolate Protocol" or "Folder Structure" documentation with the actual code in `lib/`. 2. **ADR Generation:** If a significant technical decision was made (e.g., switching from Isar to Drift), generate a new ADR (`docs/architecture/adr-XXX.md`). -3. **API Docs:** Ensure all `public` methods in `domain` and `data` have proper DartDoc comments. +3. **API Docs:** Ensure all `public` methods in `domain/` have proper DartDoc comments. ### **Agent Self-Reflection & Evaluation:** - "If a new engineer joined today, could they understand the 'Why' behind our Isolate bridge from the docs?" @@ -180,6 +219,8 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu ### **Proof of Work (Mandatory):** - **Doc Proof:** A new or updated ADR file and a report on DartDoc coverage for the modified layers. +**Skill:** `.cursor/skills/audits/audit-documentation/SKILL.md` + --- ## 10. The Modern Dart & Flutter Optimization Agent @@ -187,7 +228,7 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu **Objective:** Continuous adoption of the latest Dart/Flutter features for code safety. ### **Technical Instructions:** -1. **Language Evolution:** Proactively adopt new Dart features (e.g., Records, Pattern Matching, Macros) where they simplify the code (e.g., in `Failure` handling or `JsonSerializable` boilerplate). +1. **Language Evolution:** Proactively adopt new Dart features (e.g., Records, Pattern Matching) where they simplify the code (e.g., in `Failure` handling). 2. **Linting Upgrades:** Audit `analysis_options.yaml`. Enable stricter linting rules as the Dart ecosystem evolves (e.g., `strict-casts`, `unawaited_futures`). 3. **Build Speed:** Optimize the `build_runner` configuration to reduce generation time. Use `build.yaml` to exclude unnecessary files from the scanning process. @@ -198,3 +239,5 @@ These prompts are designed to be run by AI agents (Gemini CLI, Claude) on a recu ### **Proof of Work (Mandatory):** - **Build Proof:** A screenshot showing a significantly faster `build_runner` execution or a clean `flutter analyze` with new strict rules. - **Code Proof:** A 'Before vs After' snippet demonstrating a refactor to a more idiomatic Dart 3 pattern. + +**Skill:** `.cursor/skills/audits/audit-dart-modernization/SKILL.md` diff --git a/lib/app/router.dart b/lib/app/router.dart index 6088506..24cbe66 100644 --- a/lib/app/router.dart +++ b/lib/app/router.dart @@ -12,12 +12,12 @@ import 'package:kynos/features/dashboard/presentation/pages/run_route_missing_pa import 'package:kynos/features/dashboard/presentation/pages/run_route_page.dart'; import 'package:kynos/features/nexus_lab/presentation/nexus_lab_page.dart'; import 'package:kynos/features/onboarding/presentation/onboarding_page.dart'; -import 'package:kynos/features/onboarding/providers/onboarding_provider.dart'; import 'package:kynos/features/settings/presentation/pages/health_import_page.dart'; import 'package:kynos/features/settings/presentation/pages/manual_run_page.dart'; import 'package:kynos/features/settings/presentation/pages/on_device_model_picker_page.dart'; import 'package:kynos/features/settings/presentation/pages/openrouter_model_picker_page.dart'; import 'package:kynos/features/settings/presentation/pages/settings_page.dart'; +import 'package:kynos/shared/providers/onboarding_provider.dart'; /// All named route paths — single source of truth. abstract final class Routes { diff --git a/lib/features/coach_chat/providers/coach_chat_seed_provider.dart b/lib/features/coach_chat/providers/coach_chat_seed_provider.dart index 23a1dd6..c9235d8 100644 --- a/lib/features/coach_chat/providers/coach_chat_seed_provider.dart +++ b/lib/features/coach_chat/providers/coach_chat_seed_provider.dart @@ -1,18 +1 @@ -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'coach_chat_seed_provider.g.dart'; - -/// Optional seed message injected when opening coach chat from the dashboard. -@Riverpod(keepAlive: true) -class CoachChatSeed extends _$CoachChatSeed { - @override - String? build() => null; - - void setSeed(String? message) => state = message; - - String? consumeSeed() { - final seed = state; - state = null; - return seed; - } -} +export 'package:kynos/shared/providers/coach_chat_seed_provider.dart'; diff --git a/lib/features/coach_chat/providers/model_setup_provider.dart b/lib/features/coach_chat/providers/model_setup_provider.dart index 93d4ec1..3b28262 100644 --- a/lib/features/coach_chat/providers/model_setup_provider.dart +++ b/lib/features/coach_chat/providers/model_setup_provider.dart @@ -1,121 +1,2 @@ -import 'package:kynos/domain/catalog/on_device_model_catalog.dart'; -import 'package:kynos/domain/repositories/ai_model_repository.dart'; -import 'package:kynos/features/coach_chat/providers/model_setup_state.dart'; -import 'package:kynos/features/settings/providers/settings_provider.dart'; -import 'package:kynos/shared/providers/ai_repository_providers.dart'; -import 'package:kynos/shared/providers/huggingface_token_provider.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'model_setup_provider.g.dart'; - -class MissingHuggingFaceTokenException implements Exception { - MissingHuggingFaceTokenException(this.modelName); - - final String modelName; - - @override - String toString() => - 'A HuggingFace access token is required to download $modelName. ' - 'Add your token in Settings → AI & Cloud, or choose a public model ' - 'such as Qwen3 0.6B.'; -} - -@Riverpod(keepAlive: true) -class ModelSetupNotifier extends _$ModelSetupNotifier { - bool _installInProgress = false; - - @override - AsyncValue build() => - const AsyncData(ModelSetupState(phase: ModelSetupPhase.checking)); - - Future checkAndInstall({bool force = false}) async { - if (_installInProgress) return; - if (!force && state.value?.isReady == true) return; - _installInProgress = true; - try { - await _checkAndInstallImpl(); - } finally { - _installInProgress = false; - } - } - - Future _checkAndInstallImpl() async { - state = const AsyncLoading(); - try { - final cloudReady = await ref.read(isCloudCoachConfiguredProvider.future); - if (cloudReady) { - state = const AsyncData(ModelSetupState(phase: ModelSetupPhase.ready)); - return; - } - - final settings = ref.read(settingsProvider); - final model = OnDeviceModelCatalog.byId(settings.selectedLocalModelId); - - String? hfToken; - if (model.requiresHuggingFaceToken) { - hfToken = await ref.read(huggingFaceTokenManagerProvider.future); - if (hfToken == null || hfToken.isEmpty) { - throw MissingHuggingFaceTokenException(model.name); - } - } - - state = AsyncData( - ModelSetupState( - phase: ModelSetupPhase.checking, - progressMessage: 'Initialising ${model.name}…', - ), - ); - - final AiModelRepository repo = ref.read(aiModelRepositoryProvider); - final initResult = await repo.initialize(huggingFaceToken: hfToken); - if (initResult.failure != null) { - state = AsyncError(initResult.failure!, StackTrace.current); - return; - } - - final installedId = settings.installedLocalModelId; - if (installedId != null) { - GemmaRuntime.markInstalled(installedId); - } - - if (repo.hasActiveModel && settings.isSelectedLocalModelInstalled) { - state = const AsyncData(ModelSetupState(phase: ModelSetupPhase.ready)); - return; - } - - await GemmaRuntime.evictLegacyModelsIfNeeded(); - - if (repo.hasActiveModel && settings.isSelectedLocalModelInstalled) { - state = const AsyncData(ModelSetupState(phase: ModelSetupPhase.ready)); - return; - } - - state = AsyncData( - ModelSetupState( - phase: ModelSetupPhase.downloading, - progressMessage: - 'Downloading ${model.name} — this may take several minutes on Wi‑Fi.', - ), - ); - - final installResult = await repo.install(model, token: hfToken); - if (installResult.failure != null) { - state = AsyncError(installResult.failure!, StackTrace.current); - return; - } - if (!repo.hasActiveModel) { - state = AsyncError( - StateError( - '${model.name} install completed but model is not active.', - ), - StackTrace.current, - ); - return; - } - await ref.read(settingsProvider.notifier).markLocalModelInstalled(model.id); - state = const AsyncData(ModelSetupState(phase: ModelSetupPhase.ready)); - } catch (e, st) { - state = AsyncError(e, st); - } - } -} +export 'package:kynos/shared/providers/model_setup_provider.dart'; +export 'package:kynos/shared/providers/model_setup_state.dart'; diff --git a/lib/features/coach_chat/providers/model_setup_state.dart b/lib/features/coach_chat/providers/model_setup_state.dart index 8accb4f..eec32ce 100644 --- a/lib/features/coach_chat/providers/model_setup_state.dart +++ b/lib/features/coach_chat/providers/model_setup_state.dart @@ -1,17 +1 @@ -enum ModelSetupPhase { - checking, - downloading, - ready, -} - -class ModelSetupState { - const ModelSetupState({ - required this.phase, - this.progressMessage, - }); - - final ModelSetupPhase phase; - final String? progressMessage; - - bool get isReady => phase == ModelSetupPhase.ready; -} +export 'package:kynos/shared/providers/model_setup_state.dart'; diff --git a/lib/features/dashboard/presentation/pages/dashboard_page.dart b/lib/features/dashboard/presentation/pages/dashboard_page.dart index e39ac0d..f332251 100644 --- a/lib/features/dashboard/presentation/pages/dashboard_page.dart +++ b/lib/features/dashboard/presentation/pages/dashboard_page.dart @@ -9,7 +9,6 @@ import 'package:kynos/core/theme/theme.dart'; import 'package:kynos/domain/entities/dashboard/dashboard_summary.dart'; import 'package:kynos/domain/entities/health_summary.dart'; import 'package:kynos/domain/utils/readiness_score.dart'; -import 'package:kynos/features/coach_chat/providers/coach_chat_seed_provider.dart'; import 'package:kynos/features/dashboard/presentation/widgets/character_glance_card.dart'; import 'package:kynos/features/dashboard/presentation/widgets/coach_insight_card.dart'; import 'package:kynos/features/dashboard/presentation/widgets/connect_healthkit_card.dart'; @@ -25,6 +24,7 @@ import 'package:kynos/features/dashboard/providers/dashboard_summary_provider.da import 'package:kynos/features/dashboard/providers/post_run_debrief_provider.dart'; import 'package:kynos/features/dashboard/providers/today_insights_provider.dart'; import 'package:kynos/shared/providers/character_providers.dart'; +import 'package:kynos/shared/providers/coach_chat_seed_provider.dart'; import 'package:kynos/shared/providers/daily_quests_provider.dart'; import 'package:kynos/shared/providers/health_providers.dart'; import 'package:kynos/shared/providers/nexus_lab_provider.dart'; diff --git a/lib/features/dashboard/providers/dashboard_summary_provider.dart b/lib/features/dashboard/providers/dashboard_summary_provider.dart index 88426ee..ea94ffa 100644 --- a/lib/features/dashboard/providers/dashboard_summary_provider.dart +++ b/lib/features/dashboard/providers/dashboard_summary_provider.dart @@ -4,7 +4,7 @@ import 'package:kynos/domain/usecases/insights/today_insights_deterministic.dart import 'package:kynos/domain/utils/personal_bests.dart'; import 'package:kynos/domain/utils/run_streak.dart'; import 'package:kynos/domain/utils/weekly_momentum.dart'; -import 'package:kynos/features/character/providers/character_provider.dart'; +import 'package:kynos/shared/providers/character_providers.dart'; import 'package:kynos/shared/providers/health_providers.dart'; import 'package:kynos/shared/providers/nexus_lab_provider.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/lib/features/dashboard/providers/post_run_debrief_provider.dart b/lib/features/dashboard/providers/post_run_debrief_provider.dart index c913dfa..f8d87d6 100644 --- a/lib/features/dashboard/providers/post_run_debrief_provider.dart +++ b/lib/features/dashboard/providers/post_run_debrief_provider.dart @@ -1,8 +1,8 @@ import 'package:kynos/domain/entities/health_summary.dart'; import 'package:kynos/domain/usecases/gamification/compute_xp_usecase.dart'; import 'package:kynos/domain/usecases/insights/generate_post_run_debrief_usecase.dart'; -import 'package:kynos/features/character/providers/character_provider.dart'; import 'package:kynos/shared/providers/ai_repository_providers.dart'; +import 'package:kynos/shared/providers/character_providers.dart'; import 'package:kynos/shared/providers/gamification_providers.dart'; import 'package:kynos/shared/providers/health_providers.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; diff --git a/lib/features/onboarding/providers/onboarding_provider.dart b/lib/features/onboarding/providers/onboarding_provider.dart index 35073fa..caa3367 100644 --- a/lib/features/onboarding/providers/onboarding_provider.dart +++ b/lib/features/onboarding/providers/onboarding_provider.dart @@ -1,27 +1 @@ -import 'package:kynos/shared/providers/shared_preferences_provider.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'onboarding_provider.g.dart'; - -@riverpod -class OnboardingCompleted extends _$OnboardingCompleted { - static const _key = 'onboarding_completed'; - - @override - bool build() { - final prefs = ref.watch(sharedPreferencesProvider); - return prefs.getBool(_key) ?? false; - } - - Future completeOnboarding() async { - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setBool(_key, true); - state = true; - } - - Future resetOnboarding() async { - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setBool(_key, false); - state = false; - } -} +export 'package:kynos/shared/providers/onboarding_provider.dart'; diff --git a/lib/features/settings/presentation/pages/on_device_model_picker_page.dart b/lib/features/settings/presentation/pages/on_device_model_picker_page.dart index c33b652..61ee36e 100644 --- a/lib/features/settings/presentation/pages/on_device_model_picker_page.dart +++ b/lib/features/settings/presentation/pages/on_device_model_picker_page.dart @@ -7,12 +7,12 @@ import 'package:kynos/core/theme/kynos_theme_extension.dart'; import 'package:kynos/core/theme/spacing.dart' as tokens; import 'package:kynos/domain/catalog/on_device_model_catalog.dart'; import 'package:kynos/domain/entities/on_device_model.dart'; -import 'package:kynos/features/coach_chat/providers/model_setup_provider.dart'; import 'package:kynos/features/settings/presentation/on_device_model_selection_result.dart'; import 'package:kynos/features/settings/presentation/widgets/on_device_model_card.dart'; import 'package:kynos/features/settings/providers/settings_provider.dart'; -import 'package:kynos/infrastructure/ai/gemma/gemma_device_ram_probe.dart'; import 'package:kynos/shared/providers/ai_reconnect_provider.dart'; +import 'package:kynos/shared/providers/ai_repository_providers.dart'; +import 'package:kynos/shared/providers/model_setup_provider.dart'; import 'package:kynos/shared/widgets/kynos_section_header.dart'; class OnDeviceModelPickerPage extends ConsumerStatefulWidget { diff --git a/lib/features/settings/presentation/pages/settings_page.dart b/lib/features/settings/presentation/pages/settings_page.dart index cf7802a..07243a2 100644 --- a/lib/features/settings/presentation/pages/settings_page.dart +++ b/lib/features/settings/presentation/pages/settings_page.dart @@ -8,12 +8,12 @@ import 'package:kynos/core/constants/app_constants.dart'; import 'package:kynos/core/theme/kynos_theme_extension.dart'; import 'package:kynos/core/theme/spacing.dart' as tokens; import 'package:kynos/domain/entities/cloud_data_level.dart'; -import 'package:kynos/features/onboarding/providers/onboarding_provider.dart'; import 'package:kynos/features/settings/presentation/on_device_model_selection_result.dart'; import 'package:kynos/features/settings/presentation/widgets/settings_appearance_section.dart'; import 'package:kynos/features/settings/providers/settings_provider.dart'; import 'package:kynos/shared/providers/health_providers.dart'; import 'package:kynos/shared/providers/huggingface_token_provider.dart'; +import 'package:kynos/shared/providers/onboarding_provider.dart'; import 'package:kynos/shared/providers/openrouter_api_key_provider.dart'; import 'package:kynos/shared/utils/health_permission_feedback.dart'; import 'package:kynos/shared/utils/health_platform_labels.dart'; diff --git a/lib/features/settings/presentation/widgets/apple_health_export_preview_card.dart b/lib/features/settings/presentation/widgets/apple_health_export_preview_card.dart index 1255125..3bbc7db 100644 --- a/lib/features/settings/presentation/widgets/apple_health_export_preview_card.dart +++ b/lib/features/settings/presentation/widgets/apple_health_export_preview_card.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:kynos/core/theme/spacing.dart' as tokens; import 'package:kynos/features/settings/presentation/widgets/health_import_preview_row.dart'; -import 'package:kynos/infrastructure/health/import/apple_health_export_parser.dart'; +import 'package:kynos/shared/providers/health_import_providers.dart'; import 'package:kynos/shared/widgets/kynos_card.dart'; import 'package:kynos/shared/widgets/kynos_loading_line.dart'; diff --git a/lib/features/settings/presentation/widgets/gpx_import_preview_card.dart b/lib/features/settings/presentation/widgets/gpx_import_preview_card.dart index 6de2a0e..75f3913 100644 --- a/lib/features/settings/presentation/widgets/gpx_import_preview_card.dart +++ b/lib/features/settings/presentation/widgets/gpx_import_preview_card.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:kynos/core/theme/spacing.dart' as tokens; import 'package:kynos/features/settings/presentation/widgets/health_import_preview_row.dart'; -import 'package:kynos/infrastructure/health/import/gpx_workout_parser.dart'; +import 'package:kynos/shared/providers/health_import_providers.dart'; import 'package:kynos/shared/widgets/kynos_card.dart'; import 'package:kynos/shared/widgets/kynos_loading_line.dart'; diff --git a/lib/features/settings/providers/health_import_provider.dart b/lib/features/settings/providers/health_import_provider.dart index 856ed09..e9ea26e 100644 --- a/lib/features/settings/providers/health_import_provider.dart +++ b/lib/features/settings/providers/health_import_provider.dart @@ -3,9 +3,6 @@ import 'dart:convert'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:kynos/domain/entities/workout_session.dart'; -import 'package:kynos/infrastructure/health/import/apple_health_export_isolate.dart'; -import 'package:kynos/infrastructure/health/import/apple_health_export_parser.dart'; -import 'package:kynos/infrastructure/health/import/gpx_workout_parser.dart'; import 'package:kynos/shared/providers/health_import_providers.dart'; import 'package:kynos/shared/providers/health_providers.dart'; import 'package:kynos/shared/utils/picked_file_bytes.dart'; diff --git a/lib/features/settings/providers/settings_provider.dart b/lib/features/settings/providers/settings_provider.dart index 3e0e68b..423f166 100644 --- a/lib/features/settings/providers/settings_provider.dart +++ b/lib/features/settings/providers/settings_provider.dart @@ -1,186 +1 @@ -import 'package:flutter/material.dart'; -import 'package:kynos/domain/catalog/on_device_model_catalog.dart'; -import 'package:kynos/domain/entities/cloud_data_level.dart'; -import 'package:kynos/shared/providers/shared_preferences_provider.dart'; -import 'package:riverpod_annotation/riverpod_annotation.dart'; - -part 'settings_provider.g.dart'; - -class SettingsState { - const SettingsState({ - required this.isDarkMode, - required this.languageCode, - required this.cloudTasksEnabled, - required this.cloudDataLevel, - required this.selectedCloudModelId, - required this.selectedCloudModelName, - required this.selectedLocalModelId, - required this.selectedLocalModelName, - required this.installedLocalModelId, - }); - - final bool isDarkMode; - final String languageCode; - final bool cloudTasksEnabled; - final CloudDataLevel cloudDataLevel; - final String? selectedCloudModelId; - final String? selectedCloudModelName; - final String selectedLocalModelId; - final String selectedLocalModelName; - final String? installedLocalModelId; - - ThemeMode get themeMode => isDarkMode ? ThemeMode.dark : ThemeMode.light; - - bool get hasSelectedCloudModel => - selectedCloudModelId != null && selectedCloudModelId!.isNotEmpty; - - bool get isSelectedLocalModelInstalled => - installedLocalModelId != null && - installedLocalModelId == selectedLocalModelId; -} - -@Riverpod(keepAlive: true) -class Settings extends _$Settings { - static const _themeKey = 'isDarkMode'; - static const _languageKey = 'languageCode'; - static const _cloudTasksKey = 'cloudTasksEnabled'; - static const _cloudDataLevelKey = 'cloudDataLevel'; - static const _cloudModelIdKey = 'selectedCloudModelId'; - static const _cloudModelNameKey = 'selectedCloudModelName'; - static const _localModelIdKey = 'selectedLocalModelId'; - static const _localModelNameKey = 'selectedLocalModelName'; - static const _installedLocalModelIdKey = 'installedLocalModelId'; - - @override - SettingsState build() { - final prefs = ref.watch(sharedPreferencesProvider); - final levelName = prefs.getString(_cloudDataLevelKey); - final defaultModel = - OnDeviceModelCatalog.byId(OnDeviceModelCatalog.defaultModelId); - return SettingsState( - isDarkMode: prefs.getBool(_themeKey) ?? false, - languageCode: prefs.getString(_languageKey) ?? 'en', - cloudTasksEnabled: prefs.getBool(_cloudTasksKey) ?? false, - cloudDataLevel: CloudDataLevel.values.firstWhere( - (l) => l.name == levelName, - orElse: () => CloudDataLevel.minimal, - ), - selectedCloudModelId: prefs.getString(_cloudModelIdKey), - selectedCloudModelName: prefs.getString(_cloudModelNameKey), - selectedLocalModelId: - prefs.getString(_localModelIdKey) ?? defaultModel.id, - selectedLocalModelName: - prefs.getString(_localModelNameKey) ?? defaultModel.name, - installedLocalModelId: prefs.getString(_installedLocalModelIdKey), - ); - } - - Future updateThemeMode(bool isDark) async { - if (state.isDarkMode == isDark) return; - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setBool(_themeKey, isDark); - state = _copy(state, isDarkMode: isDark); - } - - Future updateLanguage(String newLanguageCode) async { - if (state.languageCode == newLanguageCode) return; - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setString(_languageKey, newLanguageCode); - state = _copy(state, languageCode: newLanguageCode); - } - - Future updateCloudTasksEnabled(bool enabled) async { - if (state.cloudTasksEnabled == enabled) return; - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setBool(_cloudTasksKey, enabled); - state = _copy(state, cloudTasksEnabled: enabled); - } - - Future updateCloudDataLevel(CloudDataLevel level) async { - if (state.cloudDataLevel == level) return; - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setString(_cloudDataLevelKey, level.name); - state = _copy(state, cloudDataLevel: level); - } - - Future updateSelectedCloudModel({ - required String id, - required String name, - }) async { - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setString(_cloudModelIdKey, id); - await prefs.setString(_cloudModelNameKey, name); - state = _copy( - state, - selectedCloudModelId: id, - selectedCloudModelName: name, - ); - } - - Future updateSelectedLocalModel({ - required String id, - required String name, - }) async { - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setString(_localModelIdKey, id); - await prefs.setString(_localModelNameKey, name); - state = _copy( - state, - selectedLocalModelId: id, - selectedLocalModelName: name, - ); - } - - Future markLocalModelInstalled(String id) async { - final prefs = ref.read(sharedPreferencesProvider); - await prefs.setString(_installedLocalModelIdKey, id); - state = _copy(state, installedLocalModelId: id); - } - - Future clearSelectedCloudModel() async { - final prefs = ref.read(sharedPreferencesProvider); - await prefs.remove(_cloudModelIdKey); - await prefs.remove(_cloudModelNameKey); - state = SettingsState( - isDarkMode: state.isDarkMode, - languageCode: state.languageCode, - cloudTasksEnabled: state.cloudTasksEnabled, - cloudDataLevel: state.cloudDataLevel, - selectedCloudModelId: null, - selectedCloudModelName: null, - selectedLocalModelId: state.selectedLocalModelId, - selectedLocalModelName: state.selectedLocalModelName, - installedLocalModelId: state.installedLocalModelId, - ); - } - - SettingsState _copy( - SettingsState current, { - bool? isDarkMode, - String? languageCode, - bool? cloudTasksEnabled, - CloudDataLevel? cloudDataLevel, - String? selectedCloudModelId, - String? selectedCloudModelName, - String? selectedLocalModelId, - String? selectedLocalModelName, - String? installedLocalModelId, - }) { - return SettingsState( - isDarkMode: isDarkMode ?? current.isDarkMode, - languageCode: languageCode ?? current.languageCode, - cloudTasksEnabled: cloudTasksEnabled ?? current.cloudTasksEnabled, - cloudDataLevel: cloudDataLevel ?? current.cloudDataLevel, - selectedCloudModelId: - selectedCloudModelId ?? current.selectedCloudModelId, - selectedCloudModelName: - selectedCloudModelName ?? current.selectedCloudModelName, - selectedLocalModelId: - selectedLocalModelId ?? current.selectedLocalModelId, - selectedLocalModelName: - selectedLocalModelName ?? current.selectedLocalModelName, - installedLocalModelId: - installedLocalModelId ?? current.installedLocalModelId, - ); - } -} +export 'package:kynos/shared/providers/settings_provider.dart'; diff --git a/lib/shared/providers/ai_repository_providers.dart b/lib/shared/providers/ai_repository_providers.dart index 911983c..25f8995 100644 --- a/lib/shared/providers/ai_repository_providers.dart +++ b/lib/shared/providers/ai_repository_providers.dart @@ -2,15 +2,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:kynos/domain/repositories/ai_coach_repository.dart'; import 'package:kynos/domain/repositories/cloud_ai_repository.dart'; import 'package:kynos/domain/repositories/openrouter_models_repository.dart'; -import 'package:kynos/features/settings/providers/settings_provider.dart'; import 'package:kynos/infrastructure/ai/hybrid_ai_coach_repository.dart'; import 'package:kynos/infrastructure/ai/isolate_ai_coach_repository.dart'; import 'package:kynos/infrastructure/ai/openrouter/openrouter_cloud_ai_repository.dart'; import 'package:kynos/infrastructure/ai/openrouter/openrouter_models_repository_impl.dart'; import 'package:kynos/shared/providers/openrouter_api_key_provider.dart'; +import 'package:kynos/shared/providers/settings_provider.dart'; export 'package:kynos/infrastructure/ai/ai_infrastructure_providers.dart' show aiModelRepositoryProvider; +export 'package:kynos/infrastructure/ai/gemma/gemma_device_ram_probe.dart' + show GemmaDeviceRamProbe; export 'package:kynos/infrastructure/ai/gemma/gemma_runtime.dart' show GemmaRuntime; diff --git a/lib/shared/providers/coach_chat_seed_provider.dart b/lib/shared/providers/coach_chat_seed_provider.dart new file mode 100644 index 0000000..23a1dd6 --- /dev/null +++ b/lib/shared/providers/coach_chat_seed_provider.dart @@ -0,0 +1,18 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'coach_chat_seed_provider.g.dart'; + +/// Optional seed message injected when opening coach chat from the dashboard. +@Riverpod(keepAlive: true) +class CoachChatSeed extends _$CoachChatSeed { + @override + String? build() => null; + + void setSeed(String? message) => state = message; + + String? consumeSeed() { + final seed = state; + state = null; + return seed; + } +} diff --git a/lib/features/coach_chat/providers/coach_chat_seed_provider.g.dart b/lib/shared/providers/coach_chat_seed_provider.g.dart similarity index 100% rename from lib/features/coach_chat/providers/coach_chat_seed_provider.g.dart rename to lib/shared/providers/coach_chat_seed_provider.g.dart diff --git a/lib/shared/providers/health_import_providers.dart b/lib/shared/providers/health_import_providers.dart index 23afb5d..ef1d359 100644 --- a/lib/shared/providers/health_import_providers.dart +++ b/lib/shared/providers/health_import_providers.dart @@ -3,6 +3,13 @@ import 'package:kynos/domain/usecases/health/import_apple_health_export_usecase. import 'package:kynos/domain/usecases/health/import_workout_usecase.dart'; import 'package:kynos/infrastructure/health/health_infrastructure_providers.dart'; +export 'package:kynos/infrastructure/health/import/apple_health_export_isolate.dart' + show parseAppleHealthZipAsync; +export 'package:kynos/infrastructure/health/import/apple_health_export_parser.dart' + show AppleHealthExportParseResult; +export 'package:kynos/infrastructure/health/import/gpx_workout_parser.dart' + show GpxParseResult, GpxWorkoutParser; + final importWorkoutUseCaseProvider = Provider((ref) { return ImportWorkoutUseCase(ref.watch(importedHealthStoreProvider)); }); diff --git a/lib/shared/providers/model_setup_provider.dart b/lib/shared/providers/model_setup_provider.dart new file mode 100644 index 0000000..1d7c26e --- /dev/null +++ b/lib/shared/providers/model_setup_provider.dart @@ -0,0 +1,121 @@ +import 'package:kynos/domain/catalog/on_device_model_catalog.dart'; +import 'package:kynos/domain/repositories/ai_model_repository.dart'; +import 'package:kynos/shared/providers/ai_repository_providers.dart'; +import 'package:kynos/shared/providers/huggingface_token_provider.dart'; +import 'package:kynos/shared/providers/model_setup_state.dart'; +import 'package:kynos/shared/providers/settings_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'model_setup_provider.g.dart'; + +class MissingHuggingFaceTokenException implements Exception { + MissingHuggingFaceTokenException(this.modelName); + + final String modelName; + + @override + String toString() => + 'A HuggingFace access token is required to download $modelName. ' + 'Add your token in Settings → AI & Cloud, or choose a public model ' + 'such as Qwen3 0.6B.'; +} + +@Riverpod(keepAlive: true) +class ModelSetupNotifier extends _$ModelSetupNotifier { + bool _installInProgress = false; + + @override + AsyncValue build() => + const AsyncData(ModelSetupState(phase: ModelSetupPhase.checking)); + + Future checkAndInstall({bool force = false}) async { + if (_installInProgress) return; + if (!force && state.value?.isReady == true) return; + _installInProgress = true; + try { + await _checkAndInstallImpl(); + } finally { + _installInProgress = false; + } + } + + Future _checkAndInstallImpl() async { + state = const AsyncLoading(); + try { + final cloudReady = await ref.read(isCloudCoachConfiguredProvider.future); + if (cloudReady) { + state = const AsyncData(ModelSetupState(phase: ModelSetupPhase.ready)); + return; + } + + final settings = ref.read(settingsProvider); + final model = OnDeviceModelCatalog.byId(settings.selectedLocalModelId); + + String? hfToken; + if (model.requiresHuggingFaceToken) { + hfToken = await ref.read(huggingFaceTokenManagerProvider.future); + if (hfToken == null || hfToken.isEmpty) { + throw MissingHuggingFaceTokenException(model.name); + } + } + + state = AsyncData( + ModelSetupState( + phase: ModelSetupPhase.checking, + progressMessage: 'Initialising ${model.name}…', + ), + ); + + final AiModelRepository repo = ref.read(aiModelRepositoryProvider); + final initResult = await repo.initialize(huggingFaceToken: hfToken); + if (initResult.failure != null) { + state = AsyncError(initResult.failure!, StackTrace.current); + return; + } + + final installedId = settings.installedLocalModelId; + if (installedId != null) { + GemmaRuntime.markInstalled(installedId); + } + + if (repo.hasActiveModel && settings.isSelectedLocalModelInstalled) { + state = const AsyncData(ModelSetupState(phase: ModelSetupPhase.ready)); + return; + } + + await GemmaRuntime.evictLegacyModelsIfNeeded(); + + if (repo.hasActiveModel && settings.isSelectedLocalModelInstalled) { + state = const AsyncData(ModelSetupState(phase: ModelSetupPhase.ready)); + return; + } + + state = AsyncData( + ModelSetupState( + phase: ModelSetupPhase.downloading, + progressMessage: + 'Downloading ${model.name} — this may take several minutes on Wi‑Fi.', + ), + ); + + final installResult = await repo.install(model, token: hfToken); + if (installResult.failure != null) { + state = AsyncError(installResult.failure!, StackTrace.current); + return; + } + if (!repo.hasActiveModel) { + state = AsyncError( + StateError( + '${model.name} install completed but model is not active.', + ), + StackTrace.current, + ); + return; + } + await ref.read(settingsProvider.notifier).markLocalModelInstalled(model.id); + state = const AsyncData(ModelSetupState(phase: ModelSetupPhase.ready)); + } catch (e, st) { + state = AsyncError(e, st); + } + } +} diff --git a/lib/features/coach_chat/providers/model_setup_provider.g.dart b/lib/shared/providers/model_setup_provider.g.dart similarity index 100% rename from lib/features/coach_chat/providers/model_setup_provider.g.dart rename to lib/shared/providers/model_setup_provider.g.dart diff --git a/lib/shared/providers/model_setup_state.dart b/lib/shared/providers/model_setup_state.dart new file mode 100644 index 0000000..8accb4f --- /dev/null +++ b/lib/shared/providers/model_setup_state.dart @@ -0,0 +1,17 @@ +enum ModelSetupPhase { + checking, + downloading, + ready, +} + +class ModelSetupState { + const ModelSetupState({ + required this.phase, + this.progressMessage, + }); + + final ModelSetupPhase phase; + final String? progressMessage; + + bool get isReady => phase == ModelSetupPhase.ready; +} diff --git a/lib/shared/providers/onboarding_provider.dart b/lib/shared/providers/onboarding_provider.dart new file mode 100644 index 0000000..35073fa --- /dev/null +++ b/lib/shared/providers/onboarding_provider.dart @@ -0,0 +1,27 @@ +import 'package:kynos/shared/providers/shared_preferences_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'onboarding_provider.g.dart'; + +@riverpod +class OnboardingCompleted extends _$OnboardingCompleted { + static const _key = 'onboarding_completed'; + + @override + bool build() { + final prefs = ref.watch(sharedPreferencesProvider); + return prefs.getBool(_key) ?? false; + } + + Future completeOnboarding() async { + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setBool(_key, true); + state = true; + } + + Future resetOnboarding() async { + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setBool(_key, false); + state = false; + } +} diff --git a/lib/features/onboarding/providers/onboarding_provider.g.dart b/lib/shared/providers/onboarding_provider.g.dart similarity index 100% rename from lib/features/onboarding/providers/onboarding_provider.g.dart rename to lib/shared/providers/onboarding_provider.g.dart diff --git a/lib/shared/providers/settings_provider.dart b/lib/shared/providers/settings_provider.dart new file mode 100644 index 0000000..3e0e68b --- /dev/null +++ b/lib/shared/providers/settings_provider.dart @@ -0,0 +1,186 @@ +import 'package:flutter/material.dart'; +import 'package:kynos/domain/catalog/on_device_model_catalog.dart'; +import 'package:kynos/domain/entities/cloud_data_level.dart'; +import 'package:kynos/shared/providers/shared_preferences_provider.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'settings_provider.g.dart'; + +class SettingsState { + const SettingsState({ + required this.isDarkMode, + required this.languageCode, + required this.cloudTasksEnabled, + required this.cloudDataLevel, + required this.selectedCloudModelId, + required this.selectedCloudModelName, + required this.selectedLocalModelId, + required this.selectedLocalModelName, + required this.installedLocalModelId, + }); + + final bool isDarkMode; + final String languageCode; + final bool cloudTasksEnabled; + final CloudDataLevel cloudDataLevel; + final String? selectedCloudModelId; + final String? selectedCloudModelName; + final String selectedLocalModelId; + final String selectedLocalModelName; + final String? installedLocalModelId; + + ThemeMode get themeMode => isDarkMode ? ThemeMode.dark : ThemeMode.light; + + bool get hasSelectedCloudModel => + selectedCloudModelId != null && selectedCloudModelId!.isNotEmpty; + + bool get isSelectedLocalModelInstalled => + installedLocalModelId != null && + installedLocalModelId == selectedLocalModelId; +} + +@Riverpod(keepAlive: true) +class Settings extends _$Settings { + static const _themeKey = 'isDarkMode'; + static const _languageKey = 'languageCode'; + static const _cloudTasksKey = 'cloudTasksEnabled'; + static const _cloudDataLevelKey = 'cloudDataLevel'; + static const _cloudModelIdKey = 'selectedCloudModelId'; + static const _cloudModelNameKey = 'selectedCloudModelName'; + static const _localModelIdKey = 'selectedLocalModelId'; + static const _localModelNameKey = 'selectedLocalModelName'; + static const _installedLocalModelIdKey = 'installedLocalModelId'; + + @override + SettingsState build() { + final prefs = ref.watch(sharedPreferencesProvider); + final levelName = prefs.getString(_cloudDataLevelKey); + final defaultModel = + OnDeviceModelCatalog.byId(OnDeviceModelCatalog.defaultModelId); + return SettingsState( + isDarkMode: prefs.getBool(_themeKey) ?? false, + languageCode: prefs.getString(_languageKey) ?? 'en', + cloudTasksEnabled: prefs.getBool(_cloudTasksKey) ?? false, + cloudDataLevel: CloudDataLevel.values.firstWhere( + (l) => l.name == levelName, + orElse: () => CloudDataLevel.minimal, + ), + selectedCloudModelId: prefs.getString(_cloudModelIdKey), + selectedCloudModelName: prefs.getString(_cloudModelNameKey), + selectedLocalModelId: + prefs.getString(_localModelIdKey) ?? defaultModel.id, + selectedLocalModelName: + prefs.getString(_localModelNameKey) ?? defaultModel.name, + installedLocalModelId: prefs.getString(_installedLocalModelIdKey), + ); + } + + Future updateThemeMode(bool isDark) async { + if (state.isDarkMode == isDark) return; + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setBool(_themeKey, isDark); + state = _copy(state, isDarkMode: isDark); + } + + Future updateLanguage(String newLanguageCode) async { + if (state.languageCode == newLanguageCode) return; + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setString(_languageKey, newLanguageCode); + state = _copy(state, languageCode: newLanguageCode); + } + + Future updateCloudTasksEnabled(bool enabled) async { + if (state.cloudTasksEnabled == enabled) return; + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setBool(_cloudTasksKey, enabled); + state = _copy(state, cloudTasksEnabled: enabled); + } + + Future updateCloudDataLevel(CloudDataLevel level) async { + if (state.cloudDataLevel == level) return; + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setString(_cloudDataLevelKey, level.name); + state = _copy(state, cloudDataLevel: level); + } + + Future updateSelectedCloudModel({ + required String id, + required String name, + }) async { + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setString(_cloudModelIdKey, id); + await prefs.setString(_cloudModelNameKey, name); + state = _copy( + state, + selectedCloudModelId: id, + selectedCloudModelName: name, + ); + } + + Future updateSelectedLocalModel({ + required String id, + required String name, + }) async { + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setString(_localModelIdKey, id); + await prefs.setString(_localModelNameKey, name); + state = _copy( + state, + selectedLocalModelId: id, + selectedLocalModelName: name, + ); + } + + Future markLocalModelInstalled(String id) async { + final prefs = ref.read(sharedPreferencesProvider); + await prefs.setString(_installedLocalModelIdKey, id); + state = _copy(state, installedLocalModelId: id); + } + + Future clearSelectedCloudModel() async { + final prefs = ref.read(sharedPreferencesProvider); + await prefs.remove(_cloudModelIdKey); + await prefs.remove(_cloudModelNameKey); + state = SettingsState( + isDarkMode: state.isDarkMode, + languageCode: state.languageCode, + cloudTasksEnabled: state.cloudTasksEnabled, + cloudDataLevel: state.cloudDataLevel, + selectedCloudModelId: null, + selectedCloudModelName: null, + selectedLocalModelId: state.selectedLocalModelId, + selectedLocalModelName: state.selectedLocalModelName, + installedLocalModelId: state.installedLocalModelId, + ); + } + + SettingsState _copy( + SettingsState current, { + bool? isDarkMode, + String? languageCode, + bool? cloudTasksEnabled, + CloudDataLevel? cloudDataLevel, + String? selectedCloudModelId, + String? selectedCloudModelName, + String? selectedLocalModelId, + String? selectedLocalModelName, + String? installedLocalModelId, + }) { + return SettingsState( + isDarkMode: isDarkMode ?? current.isDarkMode, + languageCode: languageCode ?? current.languageCode, + cloudTasksEnabled: cloudTasksEnabled ?? current.cloudTasksEnabled, + cloudDataLevel: cloudDataLevel ?? current.cloudDataLevel, + selectedCloudModelId: + selectedCloudModelId ?? current.selectedCloudModelId, + selectedCloudModelName: + selectedCloudModelName ?? current.selectedCloudModelName, + selectedLocalModelId: + selectedLocalModelId ?? current.selectedLocalModelId, + selectedLocalModelName: + selectedLocalModelName ?? current.selectedLocalModelName, + installedLocalModelId: + installedLocalModelId ?? current.installedLocalModelId, + ); + } +} diff --git a/lib/features/settings/providers/settings_provider.g.dart b/lib/shared/providers/settings_provider.g.dart similarity index 100% rename from lib/features/settings/providers/settings_provider.g.dart rename to lib/shared/providers/settings_provider.g.dart diff --git a/scripts/check_architecture.sh b/scripts/check_architecture.sh new file mode 100755 index 0000000..7fec882 --- /dev/null +++ b/scripts/check_architecture.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Clean Architecture layer import checks. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT" + +status=0 + +fail() { + echo "ERROR: $1" + status=1 +} + +if ! command -v rg >/dev/null 2>&1; then + fail 'ripgrep (rg) is required but not installed.' + exit "$status" +fi + +if rg 'package:kynos/infrastructure/' lib/features/ --glob '!**/*_test.dart' -q; then + fail 'features/ must not import infrastructure/ — use shared/providers/.' + rg 'package:kynos/infrastructure/' lib/features/ --glob '!**/*_test.dart' || true +fi + +if rg 'package:(flutter|flutter_riverpod|riverpod)' lib/domain/ -q; then + fail 'domain/ must not import Flutter or Riverpod.' + rg 'package:(flutter|flutter_riverpod|riverpod)' lib/domain/ || true +fi + +# Cross-feature provider imports: features/X importing features/Y/providers/ +if rg "package:kynos/features/[^/]+/providers/" lib/features/ --glob '!**/*_test.dart' -q; then + # Allow same-feature imports; flag imports where feature folder differs + while IFS= read -r match; do + file="${match%%:*}" + import_line="${match#*:}" + feature_dir="$(echo "$file" | sed -n 's#lib/features/\([^/]*\)/.*#\1#p')" + imported_feature="$(echo "$import_line" | sed -n "s/.*package:kynos\\/features\\/\\([^/]*\\)\\/providers.*/\\1/p")" + if [[ -n "$feature_dir" && -n "$imported_feature" && "$feature_dir" != "$imported_feature" ]]; then + fail "Cross-feature provider import: $file imports $imported_feature/providers/" + echo "$match" + status=1 + fi + done < <(rg 'package:kynos/features/[^/]+/providers/' lib/features/ --glob '!**/*_test.dart' || true) +fi + +exit "$status" From 859d1921635f69e01c12d9a0255030392993cef4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Jul 2026 18:48:46 +0000 Subject: [PATCH 2/3] fix(ci): normalize CODEMAP.md EOF in codemap generator Co-authored-by: Youri Bontekoe --- CODEMAP.md | 4 ---- tool/generate_codemap.dart | 12 ++++++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/CODEMAP.md b/CODEMAP.md index 9d42097..057affd 100644 --- a/CODEMAP.md +++ b/CODEMAP.md @@ -392,7 +392,3 @@ There is **no `data/` layer yet**. Repository implementations live in `infrastru _Generated by `dart run tool/generate_codemap.dart` — 278 hand-written Dart files._ - - - - diff --git a/tool/generate_codemap.dart b/tool/generate_codemap.dart index 5e32e4e..9a97c37 100644 --- a/tool/generate_codemap.dart +++ b/tool/generate_codemap.dart @@ -98,12 +98,12 @@ void main() { exit(1); } - final updated = existing.replaceRange( - start, - end + _endMarker.length, - buffer.toString().trimRight(), - ); - codemapFile.writeAsStringSync('$updated\n'); + final before = existing.substring(0, start); + final autoSection = buffer.toString().trimRight(); + + // Normalize EOF — do not preserve trailing blank lines after the end marker. + final updated = '$before$autoSection\n'; + codemapFile.writeAsStringSync(updated); print('Updated $codemapPath (${files.length} files indexed).'); } From 3556cf946f4721d3cbb074badf7d1dbd6f130d0a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 6 Jul 2026 19:06:05 +0000 Subject: [PATCH 3/3] fix(docs): correct skill link paths and template imports Co-authored-by: Youri Bontekoe --- .cursor/rules/architecture.md | 2 +- .cursor/skills/audits/audit-performance/SKILL.md | 2 +- .cursor/skills/audits/audit-privacy/SKILL.md | 7 ++++++- .cursor/skills/audits/audit-test-coverage/SKILL.md | 6 +++--- .cursor/skills/audits/audit-ui-polish/SKILL.md | 2 +- .cursor/skills/health-data/SKILL.md | 2 ++ .cursor/skills/linear-kynos/SKILL.md | 2 +- .cursor/skills/onboard-agent/SKILL.md | 6 +++--- .cursor/skills/open-pr/SKILL.md | 4 ++-- .cursor/skills/regenerate-codemap/SKILL.md | 2 +- .cursor/skills/validate-change/SKILL.md | 2 +- CONTRIBUTING.md | 2 +- docs/agents/file-templates.md | 5 ++++- docs/agents/troubleshooting.md | 2 +- 14 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.cursor/rules/architecture.md b/.cursor/rules/architecture.md index 0e247ca..ea566e8 100644 --- a/.cursor/rules/architecture.md +++ b/.cursor/rules/architecture.md @@ -2,7 +2,7 @@ Layer dependency direction — **never invert**: -``` +```text features/ → domain/ ← infrastructure/ features/ → shared/providers/ → domain + infrastructure ``` diff --git a/.cursor/skills/audits/audit-performance/SKILL.md b/.cursor/skills/audits/audit-performance/SKILL.md index 9d6ed76..30af8a8 100644 --- a/.cursor/skills/audits/audit-performance/SKILL.md +++ b/.cursor/skills/audits/audit-performance/SKILL.md @@ -6,7 +6,7 @@ disable-model-invocation: true # Audit Performance -**Reference:** [docs/workflow_prompts.md §2](../../docs/workflow_prompts.md) · AGENTS.md §14 +**Reference:** [docs/workflow_prompts.md §2](../../../../docs/workflow_prompts.md) · AGENTS.md §14 ## Instructions diff --git a/.cursor/skills/audits/audit-privacy/SKILL.md b/.cursor/skills/audits/audit-privacy/SKILL.md index f99ae7f..9da272d 100644 --- a/.cursor/skills/audits/audit-privacy/SKILL.md +++ b/.cursor/skills/audits/audit-privacy/SKILL.md @@ -6,7 +6,7 @@ disable-model-invocation: true # Audit Privacy -**Reference:** [docs/workflow_prompts.md §3](../../docs/workflow_prompts.md) · AGENTS.md §11 +**Reference:** [docs/workflow_prompts.md §3](../../../../docs/workflow_prompts.md) · AGENTS.md §11 ## Instructions @@ -19,7 +19,12 @@ disable-model-invocation: true ## Commands ```bash +# Single-line logger calls with sensitive payloads rg 'logger\.(d|i|w|e|f)\(.*(HealthSummary|hrvMs|rhrBpm)' lib/ + +# Multi-line logger calls (e.g. named arguments split across lines) +rg -U 'logger\.(d|i|w|e|f)\([\s\S]*?(HealthSummary|hrvMs|rhrBpm)' lib/ + rg 'print\(|debugPrint\(' lib/ ``` diff --git a/.cursor/skills/audits/audit-test-coverage/SKILL.md b/.cursor/skills/audits/audit-test-coverage/SKILL.md index b241f25..8d2d367 100644 --- a/.cursor/skills/audits/audit-test-coverage/SKILL.md +++ b/.cursor/skills/audits/audit-test-coverage/SKILL.md @@ -6,7 +6,7 @@ disable-model-invocation: true # Audit Test Coverage -**Reference:** [docs/workflow_prompts.md §6](../../docs/workflow_prompts.md) · AGENTS.md §13 +**Reference:** [docs/workflow_prompts.md §6](../../../../docs/workflow_prompts.md) · AGENTS.md §13 ## Instructions @@ -23,5 +23,5 @@ disable-model-invocation: true ## Proof of Work -- `flutter test` — 0 failures -- Coverage summary showing improvement in target area +- `flutter test --coverage` — 0 failures; attach `coverage/lcov.info` or an HTML/summary report for the target area +- Coverage summary showing measurable improvement in the target area (not just a green test run) diff --git a/.cursor/skills/audits/audit-ui-polish/SKILL.md b/.cursor/skills/audits/audit-ui-polish/SKILL.md index a5f597e..ee66f5d 100644 --- a/.cursor/skills/audits/audit-ui-polish/SKILL.md +++ b/.cursor/skills/audits/audit-ui-polish/SKILL.md @@ -6,7 +6,7 @@ disable-model-invocation: true # Audit UI Polish -**Reference:** [docs/workflow_prompts.md §5](../../docs/workflow_prompts.md) · AGENTS.md §14 +**Reference:** [docs/workflow_prompts.md §5](../../../../docs/workflow_prompts.md) · AGENTS.md §14 ## Instructions diff --git a/.cursor/skills/health-data/SKILL.md b/.cursor/skills/health-data/SKILL.md index b726062..3a9d1dc 100644 --- a/.cursor/skills/health-data/SKILL.md +++ b/.cursor/skills/health-data/SKILL.md @@ -5,6 +5,8 @@ paths: - "lib/infrastructure/health/**" - "lib/shared/providers/health*.dart" - "lib/domain/entities/health*.dart" + - "lib/domain/repositories/health*.dart" + - "lib/domain/usecases/health/**" --- # Health Data diff --git a/.cursor/skills/linear-kynos/SKILL.md b/.cursor/skills/linear-kynos/SKILL.md index 27dfcfa..2422bfa 100644 --- a/.cursor/skills/linear-kynos/SKILL.md +++ b/.cursor/skills/linear-kynos/SKILL.md @@ -20,4 +20,4 @@ description: Manage Linear issues for the KYNOS project. Use when creating issue ## Canonical Docs -For code changes triggered by Linear issues, follow [docs/agents/README.md](../../docs/agents/README.md) and use `/add-feature` or `/validate-change` as appropriate. +For code changes triggered by Linear issues, follow [docs/agents/README.md](../../../docs/agents/README.md) and use `/add-feature` or `/validate-change` as appropriate. diff --git a/.cursor/skills/onboard-agent/SKILL.md b/.cursor/skills/onboard-agent/SKILL.md index 2955c24..8eccc65 100644 --- a/.cursor/skills/onboard-agent/SKILL.md +++ b/.cursor/skills/onboard-agent/SKILL.md @@ -13,9 +13,9 @@ description: Onboard to the KYNOS repository. Use when starting your first task, ## Read Order -1. [docs/agents/README.md](../../docs/agents/README.md) — hub -2. [CODEMAP.md](../../CODEMAP.md) — file locations, feature index -3. [AGENTS.md](../../AGENTS.md) — full rules and PR lifecycle +1. [docs/agents/README.md](../../../docs/agents/README.md) — hub +2. [CODEMAP.md](../../../CODEMAP.md) — file locations, feature index +3. [AGENTS.md](../../../AGENTS.md) — full rules and PR lifecycle ## Bootstrap diff --git a/.cursor/skills/open-pr/SKILL.md b/.cursor/skills/open-pr/SKILL.md index dcaf6ba..dc32085 100644 --- a/.cursor/skills/open-pr/SKILL.md +++ b/.cursor/skills/open-pr/SKILL.md @@ -35,7 +35,7 @@ Allowed types: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `buil ## PR Body -Use the [.github/pull_request_template.md](../../.github/pull_request_template.md) checklist. +Use the [.github/pull_request_template.md](../../../.github/pull_request_template.md) checklist. ## After Opening — Mandatory Monitoring @@ -45,4 +45,4 @@ Use the [.github/pull_request_template.md](../../.github/pull_request_template.m 4. React 👍 on every addressed comment 5. Do **not** abandon the PR until merged or explicitly closed -See [AGENTS.md §4](../../AGENTS.md#4-pull-request--review-process). +See [AGENTS.md §4](../../../AGENTS.md#4-pull-request--review-process). diff --git a/.cursor/skills/regenerate-codemap/SKILL.md b/.cursor/skills/regenerate-codemap/SKILL.md index 31ca82e..f6e87e9 100644 --- a/.cursor/skills/regenerate-codemap/SKILL.md +++ b/.cursor/skills/regenerate-codemap/SKILL.md @@ -17,7 +17,7 @@ description: Regenerate CODEMAP.md after structural changes under lib/. Use when dart run tool/generate_codemap.dart ``` -This updates the auto-generated sections between `` in [CODEMAP.md](../../CODEMAP.md): +This updates the auto-generated sections between `` in [CODEMAP.md](../../../CODEMAP.md): - Layer map (per-file line counts and class summaries) - Hot files (>250 lines) diff --git a/.cursor/skills/validate-change/SKILL.md b/.cursor/skills/validate-change/SKILL.md index f1b5c2d..845181f 100644 --- a/.cursor/skills/validate-change/SKILL.md +++ b/.cursor/skills/validate-change/SKILL.md @@ -35,4 +35,4 @@ Attach visual proof: screen recording or screenshots of changed screens. ## Definition of Done -All commands pass with zero errors. See [AGENTS.md §4.7](../../AGENTS.md#47-definition-of-done-for-a-pr). +All commands pass with zero errors. See [AGENTS.md §4.7](../../../AGENTS.md#47-definition-of-done-for-a-pr). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 22b01e4..61e595d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ Thank you for contributing! This document is a short guide for human contributor ## Architecture -``` +```text features/ → domain/ ← infrastructure/ features/ → shared/providers/ → domain + infrastructure ``` diff --git a/docs/agents/file-templates.md b/docs/agents/file-templates.md index e47265e..216e0a1 100644 --- a/docs/agents/file-templates.md +++ b/docs/agents/file-templates.md @@ -42,6 +42,8 @@ class Page extends ConsumerWidget { `lib/features//providers/_provider.dart` ```dart +import 'dart:async'; + import 'package:riverpod_annotation/riverpod_annotation.dart'; part '_provider.g.dart'; @@ -156,8 +158,9 @@ void main() { `test/features//presentation/pages/_page_test.dart` ```dart -import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:kynos/features//presentation/pages/_page.dart'; void main() { diff --git a/docs/agents/troubleshooting.md b/docs/agents/troubleshooting.md index af53fa3..3dd93e5 100644 --- a/docs/agents/troubleshooting.md +++ b/docs/agents/troubleshooting.md @@ -8,7 +8,7 @@ Common issues agents hit in KYNOS and how to fix them. ```bash dart run build_runner build --delete-conflicting-outputs -git add lib/**/*.g.dart lib/**/*.freezed.dart +git add 'lib/**/*.g.dart' 'lib/**/*.freezed.dart' git commit -m "chore: regenerate build_runner outputs" ```