|
1 | 1 | # Copilot instructions for calsync |
2 | 2 |
|
3 | | -This repo is an Android (Kotlin) app that listens to system notifications, parses Chinese time expressions, and writes calendar events. Keep responses concise and follow the codebase’s patterns below. |
4 | | - |
5 | | -## Architecture and data flow |
6 | | -- Entry points |
7 | | - - `NotificationMonitorService` (NotificationListenerService): receives notifications, extracts title/content, and invokes `NotificationProcessor.process(...)` on a background dispatcher. |
8 | | - - `KeepAliveService` (foreground service, type specialUse): optional keep-alive, shows ongoing notification; controlled by `SettingsStore.isKeepAliveEnabled`. |
9 | | - - `CalSyncApp` (Application): window inset handling, crash capture + deferred broadcast via `CrashNotifierReceiver`. |
10 | | -- Processing pipeline (single source of truth) |
11 | | - 1) Keyword filter from `SettingsStore.getKeywords()` and optional app filter from `SettingsStore.getSelectedSourceAppPkgs()`. |
12 | | - 2) Sentence extraction with `DateTimeParser.extractAllSentencesContainingDate(...)`. |
13 | | - 3) Date-time parsing: prefer Rule-based parser with context and baseMillis; fallback to `TimeNLPAdapter` iff `SettingsStore.isTimeNLPEnabled`. |
14 | | - 4) Event insertion via `CalendarHelper.insertEvent(...)`. |
15 | | - 5) Confirmation via `NotificationUtils.sendEventCreated(...)` and in-app broadcast `NotificationUtils.ACTION_EVENT_CREATED`. |
16 | | -- Parsing contracts |
17 | | - - Public: `DateTimeParser.parseDateTime(sentence)` and `DateTimeParser.parseDateTime(context, sentence, baseMillis)` returning `ParseResult(startMillis, endMillis?, title?, location?)`. |
18 | | - - `TimeNLPAdapter.parse(text, baseMillis?) -> List<ParseSlot>`; includes small LRU cache and merges date-only/time-only units and ranges like "周五3点到5点". |
19 | | - - Relative time support like “3天后”“1个半小时后”“还剩X天/小时/分钟/秒” is handled in `DateTimeParser`. |
20 | | - |
21 | | -## Project-specific conventions |
22 | | -- Language/locale: Simplified Chinese-first UX; tests freeze a base time when checking parsing. |
23 | | -- Prefer-future behavior is tri-state via `SettingsStore.getPreferFutureOption()`: |
24 | | - - 0=Auto (heuristics), 1=Prefer future, 2=Disable (don’t roll forward). |
25 | | -- Default times: missing explicit time defaults to 09:00; evening/pm tokens map to 19:00 where appropriate. |
26 | | -- Title/location extraction uses `JiebaWrapper` and heuristics; avoid over-engineering titles—keep short and meaningful. |
27 | | -- Notification channels consolidated: confirmations use `NotificationUtils.CHANNEL_CONFIRM`; errors use `CHANNEL_ERROR`. |
28 | | -- All confirmation notifications include a delete-action handled by `EventActionReceiver` and a deep link to the created event. |
29 | | -- Do not log or toast full notification contents in production paths; rely on `NotificationCache` for brief summaries. |
30 | | - |
31 | | -## Key files to learn by example |
32 | | -- Parsing: `DateTimeParser.kt`, `TimeNLPAdapter.kt`, `timenlp/internal/*` (regex-based normalizer), `JiebaWrapper.kt`. |
33 | | -- Notification flow: `NotificationMonitorService.kt`, `NotificationHelper.kt`, `NotificationUtils.kt`, `NotificationCache.kt`. |
34 | | -- Calendar I/O: `CalendarHelper.kt`, `EventActionReceiver.kt`. |
35 | | -- Settings: `SettingsStore.kt` (keywords, selected calendars, app allowlist, prefer-future, TimeNLP toggle, relative words). |
36 | | - |
37 | | -## Build, run, test |
38 | | -- JDK 21, AGP 8.13, Kotlin 2.0.21; module `:app` targets minSdk 23, targetSdk 36; Kotlin/JVM target 11. |
39 | | -- Local build examples (PowerShell on Windows): |
40 | | - - Debug APK: `./gradlew assembleDebug` |
41 | | - - Release APK: `./gradlew assembleRelease` |
42 | | - - Unit tests: `./gradlew :app:testDebugUnitTest --no-daemon --info` |
43 | | - - If OOM, set `GRADLE_OPTS=-Xmx3g` for the process. |
44 | | -- CI: see `.github/workflows/android-build-release.yml`; it builds debug/release and attaches APKs to a GitHub Release. |
45 | | - |
46 | | -## Safe changes and patterns for agents |
47 | | -- When adding new parse rules: |
48 | | - - Extend `DateTimeParser` first; add corresponding unit tests in `app/src/test/java/top/stevezmt/calsync/` with frozen base calendars (see `DateTimeParserTest.kt`). |
49 | | - - Only fall back to `TimeNLPAdapter` after rule-based attempts. |
50 | | -- When touching notification flow: |
51 | | - - Keep work on background dispatcher; don’t block `NotificationListenerService` callbacks. |
52 | | - - Update channels via `NotificationUtils.ensureChannels`; use `safeNotify` to handle T+ permission. |
53 | | -- Calendar writes: |
54 | | - - Use `SettingsStore.getSelectedCalendarId` when set; otherwise fallback to first visible calendar; always set timezone. |
55 | | -- Respect privacy: |
56 | | - - App has no INTERNET permission; avoid adding network code. Keep logs minimal and avoid leaking sensitive notification text. |
| 3 | +Concise guide for AI agents: focus on the notification → parse → calendar flow and follow project conventions (privacy, tests, and background dispatching). |
| 4 | + |
| 5 | +## Big picture |
| 6 | +- Flow: NotificationListener (`NotificationMonitorService`) → `NotificationProcessor` → sentence extraction (`DateTimeParser.extractAllSentencesContainingDate`) → parsing (`DateTimeParser` rule set, fallback `TimeNLPAdapter` when enabled) → calendar write (`CalendarHelper.insertEvent`) → user confirmation (`NotificationUtils.sendEventCreated`). |
| 7 | +- Entry points you’ll touch most: `NotificationMonitorService`, `NotificationProcessor`, `DateTimeParser`, `TimeNLPAdapter`, `CalendarHelper`, `NotificationUtils`. |
| 8 | + |
| 9 | +## Key conventions & behaviors |
| 10 | +- Parsing API: `DateTimeParser.parseDateTime(sentence)` and `DateTimeParser.parseDateTime(context, sentence, baseMillis)` → returns `ParseResult(startMillis, endMillis?, title?, location?)`. |
| 11 | +- Prefer-future: `SettingsStore.getPreferFutureOption()` (0=auto, 1=prefer future, 2=disable). |
| 12 | +- Defaults: missing time → 09:00; evening tokens map to 19:00; relative expressions ("3天后", "还剩X小时") supported. |
| 13 | +- Title/location: lightweight extraction via `JiebaWrapper`; keep titles short and heuristic-driven. |
| 14 | +- Privacy: app has NO INTERNET permission — do not add network code or log full notification texts in production. |
| 15 | + |
| 16 | +## Tests & developer workflow |
| 17 | +- Environment: JDK 21, AGP 8.13, Kotlin 2.0.21; run via Gradle wrapper. |
| 18 | +- Common commands: |
| 19 | + - ./gradlew assembleDebug |
| 20 | + - ./gradlew assembleRelease |
| 21 | + - ./gradlew :app:testDebugUnitTest --no-daemon --info |
| 22 | + - ./gradlew connectedDebugAndroidTest |
| 23 | + - If OOM: set `GRADLE_OPTS=-Xmx3g`. |
| 24 | + - Lint: `./gradlew :app:lintDebug` and view `app/build/reports/lint-results-debug.html`. |
| 25 | + for FOSS flavor: |
| 26 | + - ./gradlew assembleFossDebug |
| 27 | + - ./gradlew :app:testFossDebugUnitTest --no-daemon --info |
| 28 | + |
| 29 | +- Testing pattern: parser rules require unit tests with a frozen base time (see `app/src/test/*/DateTimeParserTest*.kt`). Use `DateTimeParser.parseDateTime(..., baseMillis)` or test helpers like `parseWithBase`. |
| 30 | +- To run a single test class: `./gradlew :app:testDebugUnitTest --tests "top.stevezmt.calsync.DateTimeParserTest*"`. |
| 31 | + |
| 32 | +## Safe change patterns for agents |
| 33 | +- Parser changes: extend `DateTimeParser` first, add targeted unit tests under `app/src/test/java/top/stevezmt/calsync/`, assert start/end/timezone behavior with a fixed base. Only use `TimeNLPAdapter` as fallback and guard with `SettingsStore.isTimeNLPEnabled()`. |
| 34 | +- Notification flow: do heavy work on background dispatcher; never block `NotificationListenerService` callbacks. Use `NotificationUtils.ensureChannels` and `safeNotify` for notifications. |
| 35 | +- Calendar writes: prefer `SettingsStore.getSelectedCalendarId()` if set; otherwise pick first visible calendar and always set timezone when inserting. |
| 36 | +- Confirmation UI: confirmation notifications include delete-action (`EventActionReceiver`) and deep links to created events — update both when changing behavior. |
| 37 | + |
| 38 | +## Integration & third-party |
| 39 | +- `timenlp/internal/*`: regex-based normalizer; used only via `TimeNLPAdapter`. |
| 40 | + |
| 41 | +- ML Kit (full flavor) |
| 42 | + - Implementation: `app/src/full/java/top/stevezmt/calsync/MLKitStrategy.kt` (FOSS stub: `app/src/foss/java/.../MLKitStrategy.kt` returns null). |
| 43 | + - Dependency: added as `fullImplementation(libs.mlkit.entity.extraction)` (see `gradle/libs.versions.toml` and `app/build.gradle.kts`). |
| 44 | + - Behavior: uses Google ML Kit Entity Extraction (`EntityExtraction`) and `DateTimeEntity`; calls `extractor.downloadModelIfNeeded()` (via `Tasks.await`) which may trigger a model download via Google Play Services on first run. |
| 45 | + - Testing & agent guidance: |
| 46 | + - **Do not** invoke real ML Kit in unit tests; use the FOSS flavor or mock `EntityExtraction` / `EntityExtractor` in instrumentation tests. |
| 47 | + - Guard model downloads with try/catch and avoid blocking the UI thread — ML Kit calls should run on background dispatcher as parser strategies are run there. |
| 48 | + - If you change ML Kit usage, update settings/UI hints where we check for `com.google.android.gms` presence (see `SettingsActivity.kt`). |
| 49 | + |
| 50 | +- `third_party/llama.cpp` (native module) |
| 51 | + - Location: `third_party/llama.cpp/` — this is a full native C/C++ project with its own CI and `.github/copilot-instructions.md` (read that file before changing native code). |
| 52 | + - Android integration: `app/src/main/cpp/CMakeLists.txt` adds llama.cpp as a subdirectory and builds a `llama_jni` shared library; NDK version is pinned in `app/build.gradle.kts` (`ndkVersion = "29.0.13599879"`). |
| 53 | + - Build notes: changes to `llama.cpp` may require CMake/NDK updates and are often platform-specific; prefer minimal, well-tested changes. Use CPU-only defaults for Android builds; Vulkan/CUDA/Metal require extra toolchains and hardware. |
| 54 | + - Testing & agent guidance: |
| 55 | + - Run native builds with CMake and the Android NDK; ensure `LLAMA_DIR` exists (CMake will fail otherwise). |
| 56 | + - For changes inside `third_party/llama.cpp`, follow its contribution guide and run its CI locally (`ci/run.sh`) or the specific CMake build commands documented in its `.github` docs. |
| 57 | + - Avoid modifying vendored code unless necessary — prefer JNI glue (`app/src/main/cpp/llama_jni.cpp`) for app-specific logic. |
| 58 | + |
| 59 | + |
57 | 60 |
|
58 | 61 | ## Quick examples |
59 | | -- Extract sentences: `DateTimeParser.extractAllSentencesContainingDate(ctx, title + "。" + content)`. |
60 | | -- Parse with fixed base: `DateTimeParser.parseDateTime(ctx, sentence, baseMillis)`; expected to honor prefer-future option. |
61 | | -- Insert event: `CalendarHelper.insertEvent(ctx, title, desc, start, end, location)` then `NotificationUtils.sendEventCreated(...)`. |
| 62 | +- Extract: `DateTimeParser.extractAllSentencesContainingDate(ctx, title + "。" + content)` |
| 63 | +- Parse with base: `DateTimeParser.parseDateTime(ctx, sentence, baseMillis)` |
| 64 | +- Insert & notify: `CalendarHelper.insertEvent(ctx, title, desc, start, end, location)` → `NotificationUtils.sendEventCreated(...)` |
62 | 65 |
|
63 | | -If any parts of these instructions feel incomplete (e.g., missing timenlp/internal details or ROM-specific quirks you rely on), tell me what to clarify and I’ll amend this file. |
| 66 | +--- |
| 67 | +If anything here is unclear or need more examples (tests, failing-case patterns, or ROM quirks), create a new task to investigate and document it. |
0 commit comments