Skip to content

feat: scene awareness (P1 + P4)#114

Merged
chenchaoyi merged 1 commit into
mainfrom
feat/scene-awareness
May 22, 2026
Merged

feat: scene awareness (P1 + P4)#114
chenchaoyi merged 1 commit into
mainfrom
feat/scene-awareness

Conversation

@chenchaoyi
Copy link
Copy Markdown
Owner

Summary

Introduces a scene concept — four named environments (home / office / public / audit) that each carry default knobs and a baseline-expectation prior. Scope of this PR is Phase 1 (scene enum + CLI/env + presence-gate per scene + title-bar chip) and Phase 4 (session_meta JSONL header + analyze consumption + LLM prompt injection). Phases 2 (scenes.yaml persistence + auto-detect) and 3 (other knobs going scene-aware) are deferred to follow-ups, per the user-confirmed plan.

Why

The 2026-05-21 EN ↔ ZH TUI audit + the user's BLE events-modal screenshot showed that one set of defaults can't cover both a home network (sparse, anomalies matter) and a corp office (dense, churn IS the baseline). And when diting analyze --for-llm hands the JSONL to ChatGPT / Claude, the LLM has no idea whether the dense BLE churn it sees is "anomalous" (home) or "expected baseline" (office) — the same data tells different stories under different priors.

Scene catalog

Scene When to use ble_presence_gate_s
home (default) apartment / own Wi-Fi, ≤ ~15 BLE, single AP 5 s — matches v1.5.0 default, no behavioural break
office corp floor, enterprise Wi-Fi, dense BLE + many BSSIDs 15 s — absorbs Continuity RPA churn baseline
public cafe / train / plane / public Wi-Fi 30 s — almost everything is passers-by
audit actively investigating (security / debug / forensics) 0 s — record every advert

Resolution precedence: --scene CLI flag > DITING_SCENE env var > home default. --ble-presence-gate D continues to override the scene's gate.

What's in this PR

  • openspec/changes/add-scene-awareness/ — proposal / design / tasks + new scenes capability + 5 delta files (cli / event-log / analyze / bluetooth-scanning / tui-shell)
  • src/diting/scene.py — module-level state (mirrors i18n.py pattern), HOME / OFFICE / PUBLIC / AUDIT constants, get_scene / set_scene / resolve_scene / scene_defaults
  • src/diting/cli.py_extract_scene_arg, _resolve_ble_presence_gate(scene_default=...), scene threaded into _run_tui and _run_monitor, help text update
  • src/diting/event_log.pyEventLogger.emit_session_meta(scene, scene_source, ssid, gateway_ip), idempotent, first-line-of-session
  • src/diting/tui.pyDitingApp accepts scene + scene_source; subtitle chip via _build_subtitle; calls emit_session_meta immediately after constructing the logger
  • src/diting/analyze.py — reader collects session_meta into Report.scenes / Report.scene_sources; scene_summary + scene_llm_context_paragraph helpers; render_markdown adds **Scene:** header line; build_llm_prompt prepends [Scene context] paragraph
  • src/diting/i18n.py — ZH scene names ( / 公司 / 公共 / 排查), chip format, error messages, help-text update
  • tests/test_scene.py (new) — 16 tests covering resolution, defaults, knob mapping
  • tests/test_event_log.py — 5 new session_meta tests (header order, idempotency, null fields, disabled no-op, all-fields)
  • tests/test_cli.py — 10 new tests (--scene parse / env / invalid / missing; scene-aware gate resolution)
  • tests/test_analyze.py — 11 new tests (session_meta consumption, multi-scene mix, source promotion, Markdown header, LLM prompt context paragraph)
  • README.md + docs/zh/README.md — new ## Scenes section
  • CHANGELOG.md + docs/zh/CHANGELOG.md## [Unreleased]### Added, four bullets, EN ↔ ZH parity
  • tests/TESTING.md + docs/zh/TESTING.md — new ### scenes section + extended event-log and analyze sections

State machine (per-session)

INIT ──(named OR _connected OR gate=0)──> PRESENT
INIT ──(anonymous advert, gate>0)──> PENDING
PENDING ──(gate elapsed)──> PRESENT  [emit seen]
PENDING ──(TTL eviction before gate)──> INIT  [silent]
PRESENT ──(TTL eviction)──> DEPARTED  [emit left]
DEPARTED ──(re-arrival OR re-eviction)──> DEPARTED  (silent, per #107)

Scene only changes the gate value used in the PENDING → PRESENT transition.

Test plan

  • uv run pytest — 826 passed (805 → 826, +21 new)
  • uv run python scripts/tui_snapshot.py --mode regression — green; ZH subtitle now renders 视图:Wi-Fi · 扫描间隔 7s · [家] and EN renders view: BLE · [home]
  • openspec validate --specs --strict — 21/21 passed
  • openspec validate add-scene-awareness --strict — valid
  • diting --help + DITING_LANG=zh diting --help both render new --scene entry
  • Real-environment smoke: capture a session with --scene office against a corp Wi-Fi, confirm the events panel has the chip + the JSONL has the session_meta line + diting analyze --for-llm injects the office prior

What this PR explicitly does NOT do

  • ~/.config/diting/scenes.yaml per-network persistence (Phase 2)
  • Auto-detect heuristic on first launch + "new network detected" banner (Phase 2)
  • Other knobs going scene-aware: roam_notify_threshold, bonjour_categories_visible, lan_inventory_default, event_throttle (Phase 3)
  • Basics modal "Scenes" section (Phase 2)

These were intentionally scoped out to keep the PR reviewable and to validate the four-scene catalog in real use before locking in more surface.

Generated with Claude Code

Introduces a `scene` concept: four named environments
(home / office / public / audit) that each carry default knobs
and a baseline-expectation prior. Scope of this PR is Phase 1
(scene enum + CLI/env + presence-gate per scene + title-bar
chip) and Phase 4 (session_meta JSONL header + analyze
consumption + LLM prompt injection). Phases 2 (scenes.yaml
persistence + auto-detect) and 3 (other knobs going
scene-aware) are deferred to follow-ups.

Why: the 2026-05-21 audit + the user's BLE event-modal screenshot
showed that one set of defaults can't cover both a home network
(sparse, anomalies matter) and a corp office (dense, churn is the
baseline). And when the JSONL gets handed to an LLM, the same
data tells different stories depending on the environment --
without scene context the LLM either misreads office churn as an
incident or misses real home-network novelty.

What changes:
- New `scenes` capability (proposal + design + tasks + spec).
- CLI: --scene SCENE / DITING_SCENE env var (cli > env > home
  default). Active scene drives `presence_gate_s` default
  (home=5s, office=15s, public=30s, audit=0s). Existing
  --ble-presence-gate D continues to win over the scene default.
- TUI: title-bar chip ([home] / [家] etc.) renders the active
  scene next to the existing view + scan interval.
- Event log: every JSONL session opens with a `session_meta`
  line carrying scene + scene_source + diting_version + ssid
  + gateway_ip + hostname. Idempotent emit; per-event lines
  unchanged. Both --log and `diting monitor` write it.
- Analyze: reader populates Report.scenes + scene_sources;
  Markdown report header surfaces the scene; multi-session
  globs aggregate the scene mix.
- --for-llm: prompt template prepends a [Scene context]
  paragraph with the scene's plain-language prior, backfilled
  with observed BSSID + BLE-identifier counts when present.
  Multi-scene bundles ask the LLM to compare across.

No behavioural break: home is the default, home=5s presence
gate matches the v1.5.0 baseline exactly. Pre-scene-aware
JSONLs degrade gracefully -- the analyzer shows "Scene:
unknown (pre-scene-aware capture)" and the LLM prompt falls
back to general priors.

Scope deferred to follow-ups (P2 + P3):
- ~/.config/diting/scenes.yaml per-network persistence
- auto-detect heuristic on first launch
- other knobs going scene-aware (roam_alert,
  bonjour_categories, lan_inventory, event_throttle)
- "new network detected -- classified as ..." banner

Tests: +21 (scene module 16, event_log session_meta 5, CLI
scene 10, analyze session_meta + LLM context 11). Snapshot
regression green. Both openspec validates clean. 826 pytest
passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chenchaoyi chenchaoyi merged commit 0aff717 into main May 22, 2026
5 checks passed
@chenchaoyi chenchaoyi deleted the feat/scene-awareness branch May 22, 2026 07:09
chenchaoyi added a commit that referenced this pull request May 22, 2026
Applies the spec deltas from add-scene-awareness (#114) into the
canonical openspec/specs/ tree:

- NEW openspec/specs/scenes/spec.md — full capability spec
  (Purpose + 3 requirements + 8 scenarios)
- MODIFIED specs/cli/spec.md — adds --scene SCENE requirement
- MODIFIED specs/event-log/spec.md — adds session_meta requirement
- MODIFIED specs/analyze/spec.md — adds session_meta consumer +
  --for-llm scene context requirements
- MODIFIED specs/bluetooth-scanning/spec.md — presence_gate_s
  default sourced from scene, --ble-presence-gate flag overrides
- MODIFIED specs/tui-shell/spec.md — adds subtitle scene chip
  requirement (EN + ZH formats)

Moves the change dir to
openspec/changes/archive/2026-05-22-add-scene-awareness/.

All artifacts done, all tasks complete, validate --specs --strict
green (22/22 — scenes is the 22nd capability).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chenchaoyi added a commit that referenced this pull request May 22, 2026
P1 (#114) shipped scene awareness but kept resolution opt-in: scenes
fired only via --scene or DITING_SCENE, defaulting to home otherwise.
User feedback in real use: "I'm clearly in an office (many same-name
APs, enterprise auth), why does diting still show [家]?" That's the
P1 contract working as designed, but not the experience the user
wants.

P2 closes the gap with two pieces that work together:

1. scenes.yaml per-network persistence -- same idea as aps.yaml.
   Optional file in cwd (DITING_SCENES_FILE overrides path),
   git-ignored, human-curated only. Maps SSID -> scene (with
   gateway_mac as fallback for SSID-collision cases like eduroam).

2. Auto-detect heuristic -- when no CLI / env / yaml decides, diting
   inspects the active Wi-Fi connection synchronously at startup
   and classifies. Rules:
   - security contains "Enterprise" (case-insensitive) -> office
   - visible_bssid_count >= 30 -> office
   - otherwise -> home
   public stays opt-in (captive-portal detection needs active probing
   we don't want to do).

Resolution precedence is now 5-tier (was 3):
1. --scene CLI flag
2. DITING_SCENE env var
3. scenes.yaml (SSID or gateway_mac match)
4. classify_environment heuristic
5. default home

scene_source field extends from {cli, env, default} to
{cli, env, yaml, auto, default} -- session_meta records exactly
which tier resolved. Analyzer and LLM bundle pick up the new
sources automatically (scene_summary already handles arbitrary
source strings).

Startup banner explains the choice when source is yaml or auto;
cli / env are silent (user knows what they asked for). One line
to stderr (so `diting monitor > log.jsonl` shells stay clean).
DITING_SCENE_QUIET=1 silences the banner.

Behavioural change: a fresh diting launch on a corp Wi-Fi now
shows [office] / [公司] without the user passing any flag. Same
on home Wi-Fi -> [home] / [家]. Upgrade-safe because the
heuristic falls to `home` whenever it can't classify, matching
the v1.5.0 / P1 default exactly.

Tests: +30 (10 heuristic, 12 scenes_config, 8 startup resolution).
856 pytest passed. Snapshot regression green. Both openspec
validates clean.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chenchaoyi chenchaoyi mentioned this pull request May 22, 2026
8 tasks
chenchaoyi added a commit that referenced this pull request May 22, 2026
Scene awareness. diting now carries an explicit notion of where
the user is right now, threaded through pollers + the JSONL
session header + the LLM prompt bundle.

Four named scenes (home / office / public / audit), each with:
  - per-scene BLE presence_gate_s default (home=5s, office=15s,
    public=30s, audit=0s)
  - per-scene LLM prior injected into --for-llm prompt template
  - chip in the TUI title bar showing the active scene

Resolution precedence (5 tiers, highest first):
  1. --scene SCENE CLI flag
  2. DITING_SCENE env var
  3. scenes.yaml SSID / gateway_mac match (per-network pinning,
     mirrors aps.yaml pattern, git-ignored, human-curated only)
  4. classify_environment heuristic on the active connection
     (WPA-Enterprise auth -> office; >= 30 BSSIDs visible ->
     office; otherwise home; public stays opt-in because
     captive-portal detection without active probing is
     unreliable)
  5. home default

scene_source field on session_meta records the tier that won:
{cli, env, yaml, auto, default}. session_meta is a new JSONL
event type written as line 1 of every diting session, carrying
scene + scene_source + diting_version + ssid + gateway_ip +
hostname (BSSID intentionally left out for PII reasons).

PRs bundled: #114 (P1 + P4 scene awareness), #115 (archive),
#116 (P2 auto-detect + scenes.yaml), #117 (archive).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant