Work Packages: Identity-Aware CLI Event Sync

Inputs: Design documents from /kitty-specs/032-identity-aware-cli-event-sync/ Prerequisites: plan.md, spec.md, data-model.md, research.md, contracts/ Target Branch: 2.x

Organization: Fine-grained subtasks (Txxx) roll up into work packages (WPxx). Each work package is independently deliverable and testable.

MVP Scope (Minimum Deliverable): WP01 + WP02 + WP04 (identity in events + auto-start runtime). Post-MVP / Nice-to-have: WP03 (team slug), WP05 (duplicate emissions), WP06 (integration tests).


Work Package WP01: ProjectIdentity Module (Priority: P0, MVP) 🎯 Foundation

Goal: Create the ProjectIdentity dataclass with generation, atomic persistence, and graceful backfill. Independent Test: spec-kitty init creates config.yaml with valid project_uuid, project_slug, and node_id. Prompt: tasks/WP01-project-identity-module.md Estimated Size: ~350 lines

Included Subtasks

  • ✅ T001 Create ProjectIdentity dataclass in sync/project_identity.py
  • ✅ T002 Implement identity generation helpers (uuid4, slug from dir/remote, node_id)
  • ✅ T003 Implement atomic config.yaml persistence (temp file + rename)
  • ✅ T004 Implement graceful backfill via ensure_identity()
  • ✅ T005 Add read-only fallback (in-memory identity with warning)
  • ✅ T006 Write unit tests in tests/sync/test_project_identity.py

Implementation Notes

1. Use uuid.uuid4() for project_uuid; use sync.clock.generate_node_id() for stable node_id 2. Derive project_slug from git remote origin URL (if available) or directory name 3. Atomic writes: write to .kittify/config.yaml.tmp, then os.replace() to final path 4. Config schema: add project: section with uuid, slug, node_id keys

Parallel Opportunities

  • T001-T002 can be developed together (dataclass + helpers)
  • T006 tests can be written in parallel once API is designed

Dependencies

  • None (foundation package)

Risks & Mitigations

RiskMitigation
Config corruption on write failureAtomic write pattern (temp + rename)
Race condition with concurrent processesFirst-write-wins; subsequent reads use persisted value
Read-only filesystemFallback to in-memory identity with warning

Work Package WP02: Emitter Identity Injection (Priority: P0, MVP)

Goal: Inject project_uuid and project_slug into every event envelope in EventEmitter._emit(). Independent Test: Emit an event via emitter.emit_wp_status_changed() and verify event dict contains project_uuid. Prompt: tasks/WP02-emitter-identity-injection.md Estimated Size: ~300 lines

Included Subtasks

  • ✅ T007 Import ProjectIdentity into sync/emitter.py
  • ✅ T008 Add identity injection in EventEmitter._emit()
  • ✅ T009 Add validation: warn and queue-only if identity missing
  • ✅ T010 Update get_emitter() to call ensure_identity() on first access
  • ✅ T011 Update tests/sync/test_event_emission.py with identity verification

Implementation Notes

1. Call get_project_identity() at start of _emit() (not in each emit_* method) 2. Add project_uuid as string, project_slug as optional string 3. Validation: if project_uuid is None, log warning and skip WebSocket send 4. Identity resolution happens once per emitter lifetime (cached in instance)

Parallel Opportunities

  • T011 tests can be written once API is designed

Dependencies

  • Depends on WP01 (ProjectIdentity module must exist)

Risks & Mitigations

RiskMitigation
Performance overheadCache identity in emitter; resolve once per lifetime
Circular importLazy import ProjectIdentity in emitter module

Work Package WP03: AuthClient Team Slug (Priority: P2, Post-MVP)

Goal: Add get_team_slug() method to AuthClient and store team_slug on login. Independent Test: After spec-kitty auth login, AuthClient().get_team_slug() returns the team slug. Prompt: tasks/WP03-authclient-team-slug.md Estimated Size: ~250 lines

Included Subtasks

  • ✅ T012 Add get_team_slug() method to AuthClient
  • ✅ T013 Store team_slug in credentials during login flow
  • ✅ T014 Handle unauthenticated case (return "local")
  • ✅ T015 Write tests for get_team_slug in tests/sync/test_auth.py

Implementation Notes

1. Team slug comes from SaaS during OAuth/login flow 2. Store in same credentials file as access_token 3. If not authenticated or team_slug missing, return "local" (default) 4. EventEmitter already calls _get_team_slug() which expects this method

Parallel Opportunities

  • Can run in parallel with WP02 (different files, no conflicts)

Dependencies

  • None (can start after WP01, but parallel with WP02)

Risks & Mitigations

RiskMitigation
SaaS doesn't provide team_slugGracefully default to "local"
Credentials file format changeUse versioned schema, migrate if needed

Work Package WP04: SyncRuntime Lazy Singleton (Priority: P0, MVP)

Goal: Create SyncRuntime with lazy startup on first get_emitter() call. Independent Test: Call get_emitter() twice; verify BackgroundSyncService starts only once. Prompt: tasks/WP04-sync-runtime-lazy-singleton.md Estimated Size: ~350 lines

Included Subtasks

  • ✅ T016 Create SyncRuntime dataclass in sync/runtime.py
  • ✅ T017 Implement lazy singleton get_runtime() function
  • ✅ T018 Start BackgroundSyncService unconditionally in SyncRuntime.start()
  • ✅ T019 Connect WebSocketClient only if authenticated
  • ✅ T020 Add atexit handler for graceful shutdown
  • ✅ T021 Write tests in tests/sync/test_runtime.py

Implementation Notes

1. Module-level _runtime: SyncRuntime | None = None 2. get_runtime() creates and starts on first call (idempotent) 3. Check sync.auto_start in .kittify/config.yaml; default True if missing/invalid 4. Use atexit.register() to call runtime.stop() on process exit 5. Wire into get_emitter(): call get_runtime(), create emitter, then runtime.attach_emitter(emitter)

Parallel Opportunities

  • T021 tests can be developed alongside implementation

Dependencies

  • Depends on WP02 (emitter must call runtime on get_emitter)

Risks & Mitigations

RiskMitigation
Runtime not stopping cleanlyatexit handler + explicit stop() API
Startup latencyKeep startup async-light; defer WebSocket connect

Work Package WP05: Fix Duplicate Emissions (Priority: P2, Post-MVP)

Goal: Remove duplicate WPStatusChanged events from implement.py and accept.py. Independent Test: Run spec-kitty implement WP01; verify exactly ONE WPStatusChanged event emitted. Prompt: tasks/WP05-fix-duplicate-emissions.md Estimated Size: ~300 lines

Included Subtasks

  • ✅ T022 Audit implement.py on 2.x branch for emission points
  • ✅ T023 Consolidate to single emission in implement.py
  • ✅ T024 Audit accept.py on 2.x branch for emission points
  • ✅ T025 Consolidate to single emission in accept.py
  • ✅ T026 Add test verifying single emission per command

Implementation Notes

1. Work on 2.x branch - these files have event emissions that main doesn't have 2. Find all emit_wp_status_changed() calls in each file 3. Keep the most appropriate one (usually end of successful flow) 4. Remove or guard duplicates (some may be in error paths) 5. Test by mocking emitter and counting calls

Parallel Opportunities

  • T022-T023 (implement.py) and T024-T025 (accept.py) can run in parallel

Dependencies

  • Depends on WP04 (runtime must be working for proper testing)

Risks & Mitigations

RiskMitigation
Removing wrong emissionAudit carefully; keep emission at success point
Breaking error reportingEnsure error paths still emit ErrorLogged if needed

Work Package WP06: Integration Tests (Priority: P2, Post-MVP)

Goal: End-to-end tests validating the full identity-aware sync flow. Independent Test: Full test suite passes with identity in all events. Prompt: tasks/WP06-integration-tests.md Estimated Size: ~400 lines

Included Subtasks

  • ✅ T027 Create tests/integration/test_sync_e2e.py with fixtures
  • ✅ T028 Test: init -> implement -> event contains project_uuid
  • ✅ T029 Test: unauthenticated graceful degradation (queue only)
  • ✅ T030 Test: config backfill on existing project without identity
  • ✅ T031 Test: read-only repo fallback (in-memory identity)
  • ✅ T032 Test: single emission per command (no duplicates)

Implementation Notes

1. Use pytest fixtures for temporary repos and config files 2. Mock WebSocket for isolation; verify queue contents 3. Test both fresh init and migration (existing config without identity) 4. For read-only test, use os.chmod() to make config.yaml read-only

Parallel Opportunities

  • Tests are independent; can split across multiple test files if needed

Dependencies

  • Depends on WP01, WP02, WP04, WP05 (all modules must be working)

Risks & Mitigations

RiskMitigation
Flaky tests from timingUse proper async waits; avoid sleeps
Test isolation issuesEach test gets fresh temp directory

Dependency & Execution Summary

WP01: ProjectIdentity (foundation)
  ↓
WP02: Emitter injection ─┬─ WP03: AuthClient team_slug (parallel)
                         ↓
WP04: SyncRuntime lazy singleton
  ↓
WP05: Fix duplicate emissions
  ↓
WP06: Integration tests

Execution Order: 1. WP01 (foundation, must complete first) 2. WP02 + WP03 (parallel wave) 3. WP04 (depends on WP02) 4. WP05 (depends on WP04) 5. WP06 (depends on all)

MVP Scope: WP01 + WP02 + WP04 (identity in events, auto-start runtime)

Full Scope: All 6 WPs (includes team_slug, duplicate fixes, integration tests)


Subtask Index (Reference)

IDSummaryWPPriorityParallel?
T001Create ProjectIdentity dataclassWP01P0No
T002Implement identity generation helpersWP01P0No
T003Implement atomic config persistenceWP01P0No
T004Implement graceful backfillWP01P0No
T005Add read-only fallbackWP01P0No
T006Write project_identity testsWP01P0Yes
T007Import ProjectIdentity into emitterWP02P0No
T008Add identity injection in _emit()WP02P0No
T009Add validation for missing identityWP02P0No
T010Update get_emitter() for identityWP02P0No
T011Update emission tests with identityWP02P0Yes
T012Add get_team_slug() to AuthClientWP03P1Yes
T013Store team_slug on loginWP03P1No
T014Handle unauthenticated caseWP03P1No
T015Write get_team_slug testsWP03P1Yes
T016Create SyncRuntime dataclassWP04P1No
T017Implement lazy singleton get_runtime()WP04P1No
T018Start BackgroundSyncServiceWP04P1No
T019Connect WebSocketClient if authWP04P1No
T020Add atexit handlerWP04P1No
T021Write runtime testsWP04P1Yes
T022Audit implement.py emissionsWP05P2Yes
T023Consolidate implement.py emissionWP05P2No
T024Audit accept.py emissionsWP05P2Yes
T025Consolidate accept.py emissionWP05P2No
T026Test single emission per commandWP05P2Yes
T027Create e2e test fixturesWP06P2No
T028Test init -> implement flowWP06P2Yes
T029Test unauthenticated degradationWP06P2Yes
T030Test config backfillWP06P2Yes
T031Test read-only fallbackWP06P2Yes
T032Test no duplicate emissionsWP06P2Yes

<!-- status-model:start -->

Canonical Status (Generated)

<!-- status-model:end -->

  • WP01: done
  • WP02: done
  • WP03: done
  • WP04: done
  • WP05: done
  • WP06: done