Skip to content

feat(scene): auto-detect + scenes.yaml persistence (P2)#116

Merged
chenchaoyi merged 1 commit into
mainfrom
feat/scene-autodetect-and-persistence
May 22, 2026
Merged

feat(scene): auto-detect + scenes.yaml persistence (P2)#116
chenchaoyi merged 1 commit into
mainfrom
feat/scene-autodetect-and-persistence

Conversation

@chenchaoyi
Copy link
Copy Markdown
Owner

Summary

P1 (#114) shipped scene awareness but kept resolution opt-in — scenes fired only via --scene or DITING_SCENE, defaulting to home otherwise. Real-world feedback the moment P1 merged: "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. Default behaviour flips from "always home unless told otherwise" → "diting figures it out."

Two pieces working 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. Diting NEVER writes back. Maps SSID → scene, with gateway_mac as fallback for SSID-collision cases (eduroam everywhere, multiple homes with the same SSID).

networks:
  - ssid: HomeNet
    scene: home
  - ssid: Meituan
    scene: office
  - gateway_mac: 14:51:7e:71:5a:1a
    scene: office

2. Auto-detect heuristic

When no CLI / env / yaml decides, diting inspects the active Wi-Fi connection synchronously at startup. Pure-function classifier, no probes, no phone-home:

Rule Condition → Scene
1 security contains "Enterprise" (case-insensitive) office
2 visible_bssid_count >= 30 (CoreWLAN scan cache) office
3 otherwise home

public stays opt-in (captive-portal detection without active probing is unreliable).

Resolution precedence (5-tier, was 3)

1. --scene SCENE                       (cli)
2. DITING_SCENE env var                (env)
3. scenes.yaml SSID/gateway_mac match  (yaml)   ← new
4. Auto-detect heuristic               (auto)   ← new
5. Default home                        (default)

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

Banner

When source is yaml or auto, one line to stderr explains the choice:

auto-detected scene: office (WPA2 Enterprise auth)
pinned scene: office (matched "Meituan" in scenes.yaml)

CLI / env are silent (user knows what they asked for). DITING_SCENE_QUIET=1 silences for scripts.

What's in this PR

  • openspec/changes/add-scene-autodetect-and-persistence/ — proposal / design / tasks + 3 spec deltas (scenes / event-log / tui-shell)
  • src/diting/scenes_config.py (new) — SceneAssignment, SceneRegistry, load_scenes_registry, lookup helpers
  • src/diting/scene.pySOURCE_YAML / SOURCE_AUTO constants, classify_environment heuristic
  • src/diting/cli.py_resolve_scene_at_startup (5-tier resolver), _gateway_mac_for_router_ip (ARP lookup), _emit_scene_banner
  • src/diting/i18n.py — EN + ZH banner strings
  • scenes.example.yaml (new, repo root)
  • .gitignore — adds scenes.yaml
  • tests/test_scenes_config.py (new) — 12 tests
  • tests/test_scene.py — 10 new heuristic tests
  • tests/test_cli.py — 8 new startup + banner tests
  • README.md + docs/zh/README.md — extended ## Scenes with auto-detect / yaml subsections
  • CHANGELOG.md + docs/zh/CHANGELOG.md## [Unreleased]### Added
  • tests/TESTING.md + docs/zh/TESTING.md — 3 new rows under scenes

Test plan

  • uv run pytest — 856 passed (was 826 in P1; +30 new)
  • uv run python scripts/tui_snapshot.py --mode regression — green
  • openspec validate --specs --strict — 22/22 passed
  • openspec validate add-scene-autodetect-and-persistence --strict — valid
  • Real-environment smoke: launch diting on Meituan corp Wi-Fi without flags, confirm banner reads auto-detected scene: office (WPA2 Enterprise auth) and chip shows [公司] / [office]
  • Repeat with scenes.yaml pinning Meituan → office, confirm banner switches to pinned scene: office (matched "Meituan" in scenes.yaml)

What this PR does NOT do

  • Public auto-detection. Open Wi-Fi exists in homes, offices, and public spaces; can't distinguish without active probing. Still --scene public.
  • Mid-session reclassification. Scene fixed at startup. Roaming home → office mid-session keeps the original scene until next launch.
  • Other scene-aware knobs (roam_alert, bonjour_categories, lan_inventory, event_throttle). Still P3.
  • diting writing back to scenes.yaml. Human-curated only.

Generated with Claude Code

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 merged commit 7659d83 into main May 22, 2026
5 checks passed
@chenchaoyi chenchaoyi deleted the feat/scene-autodetect-and-persistence branch May 22, 2026 10:28
chenchaoyi added a commit that referenced this pull request May 22, 2026
Applies the spec deltas from #116 into the canonical
openspec/specs/ tree:

- MODIFIED specs/scenes/spec.md — resolution precedence
  expanded from 3 tiers (cli / env / default) to 5 tiers
  (cli / env / yaml / auto / default); 6 new scenarios; new
  documentation of fall-through behaviour for invalid env.
- ADDED specs/scenes/spec.md — `scenes.yaml SHALL map networks
  to scenes` requirement (4 scenarios covering missing file,
  SSID match, gateway_mac precedence, invalid entry handling).
- ADDED specs/scenes/spec.md — `Auto-detect heuristic SHALL
  classify from observable network signals` requirement (5
  scenarios covering Enterprise auth, BSSID density, sparse
  network, open network not auto-classifying as public).
- MODIFIED specs/event-log/spec.md — session_meta
  `scene_source` field enum extended from {cli, env, default}
  to {cli, env, yaml, auto, default}; 2 new scenarios for the
  yaml + auto sources.
- ADDED specs/tui-shell/spec.md — `Scene classification SHALL
  print a one-line banner at startup` requirement (4
  scenarios covering auto banner, yaml banner, explicit-flag
  silence, DITING_SCENE_QUIET).

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

All artifacts done, all tasks complete, validate --specs
--strict green (22/22).

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