utils/focus_project/focus_project/_paths.py
Krzysztof kuhy Rudnicki 4c1ffd900b focus_project: interview milestones, evidence
Project.kind (focus default; old rows load unchanged) plus interview
fields test_at, invite_url, trips, weekend_answers. Interview budget
30 min weekdays / 120 Sat-Sun, exempt from daily_floor, unlock clamped
after the 0.7.0 history ratchet. Milestones are generated (rules,
trip_ask, phone, mock, slot), never stored. Append-only evidence store
behind the _paths seam. Calendar dropped: slot = slot entry + slot_confirm.

Failure paths:
- unanswered trip question: fail closed (reads as a trip, phone due)
- late no-trip answer cannot erase an overdue phone milestone: closed
- phone source missing/stale/crashed: unknown, locks: fail closed
- trip_ask answer: evidence row first, then registry; a crash between
  leaves the question open: fail closed
- add_trip/answer_weekend loosening: ValueError, nothing written
- owed_lock with unreadable registry or evidence: fail open, logged
  (focus-guard's unreadable-registry rule); test day never locks
- status() milestones with unreadable evidence: empty list, logged
- torn or non-utf8 evidence lines: skipped and logged

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWZUTC9Zzdzsx7VW7dm
2026-11-10 15:39:30 +01:00

48 lines
1.2 KiB
Python

# Copyright (c) 2026 Krzysztof Rudnicki
"""Where the data lives, resolved on every call so tests can redirect it."""
from __future__ import annotations
import os
from pathlib import Path
ENV_VAR = "FOCUS_PROJECT_DATA"
def data_dir() -> Path:
"""``$FOCUS_PROJECT_DATA`` or ``~/.local/share/focus_project``."""
override = os.environ.get(ENV_VAR)
if override:
return Path(override)
return Path.home() / ".local" / "share" / "focus_project"
def projects_path() -> Path:
"""The registry file; this package is its only writer."""
return data_dir() / "projects.json"
def activity_path() -> Path:
"""The append-only activity log."""
return data_dir() / "activity.jsonl"
def lock_path() -> Path:
"""The registry's writer lock."""
return data_dir() / "projects.lock"
def evidence_dir() -> Path:
"""Interview-prep evidence: one jsonl per project plus copied files."""
return data_dir() / "evidence"
def evidence_path(project_id: str) -> Path:
"""The append-only evidence rows of one project."""
return evidence_dir() / f"{project_id}.jsonl"
def evidence_files_dir(project_id: str) -> Path:
"""Where evidence files (screenshots) of one project are copied."""
return evidence_dir() / project_id