236 lines
9.1 KiB
Markdown
236 lines
9.1 KiB
Markdown
---
|
|
phase: 04-gsd-integration
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- src/gsd_openclaw/user_context.py
|
|
- src/gsd_openclaw/__init__.py
|
|
- tests/test_user_context.py
|
|
- tests/conftest.py
|
|
autonomous: true
|
|
requirements:
|
|
- BRDG-06
|
|
user_setup: []
|
|
must_haves:
|
|
truths:
|
|
- "User's project context persists across chat sessions"
|
|
- "User context is stored in isolated per-user files"
|
|
- "User context writes are atomic and crash-safe"
|
|
- "Each user has independent project tracking"
|
|
artifacts:
|
|
- path: "src/gsd_openclaw/user_context.py"
|
|
provides: "UserContextManager class for per-user project tracking"
|
|
min_lines: 100
|
|
- path: "tests/test_user_context.py"
|
|
provides: "Tests for UserContextManager"
|
|
exports: ["TestUserContextManager", "TestAtomicWrites", "TestMultiUser"]
|
|
key_links:
|
|
- from: "src/gsd_openclaw/user_context.py"
|
|
to: "~/gsd-workspaces/.users/{user_id}/context.json"
|
|
via: "atomic write pattern (tempfile + os.replace)"
|
|
pattern: "tempfile\\.mkstemp|os\\.replace"
|
|
---
|
|
|
|
<objective>
|
|
Create UserContextManager class for per-user project context tracking with atomic persistence.
|
|
|
|
Purpose: Enable users to have persistent project context across chat sessions, supporting the `/gsd:use` command requirement.
|
|
|
|
Output: UserContextManager class with load/save operations, atomic writes, and multi-user isolation.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@/home/starmen1265/.claude/get-shit-done/workflows/execute-plan.md
|
|
@/home/starmen1265/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/04-gsd-integration/04-CONTEXT.md
|
|
@.planning/phases/04-gsd-integration/04-RESEARCH.md
|
|
|
|
<interfaces>
|
|
<!-- Key types and contracts from existing codebase -->
|
|
|
|
From src/gsd_openclaw/state_manager.py (atomic write pattern to follow):
|
|
```python
|
|
def save_state(state: SessionState, state_path: Path) -> None:
|
|
"""Save session state to file atomically with backup rotation."""
|
|
# 1. Create parent directories if missing
|
|
state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
# 2. Rotate backup
|
|
# 3. Write to temp file in same directory
|
|
fd, temp_path = tempfile.mkstemp(dir=state_path.parent, suffix='.tmp')
|
|
# 4. Atomic replace
|
|
os.replace(temp_path, state_path)
|
|
```
|
|
|
|
From src/gsd_openclaw/response_formatter.py:
|
|
```python
|
|
@dataclass
|
|
class CardButton:
|
|
"""Interactive button for Feishu Card actions."""
|
|
text: str
|
|
value: Dict[str, Any]
|
|
button_type: str = "primary" # primary, default, danger
|
|
```
|
|
|
|
User context storage location (from CONTEXT.md):
|
|
- Location: `~/gsd-workspaces/.users/{user_id}/context.json`
|
|
- Format: JSON, atomic writes (same as Phase 1 state.json)
|
|
|
|
User context data structure (from CONTEXT.md):
|
|
- current_project: str - User's current project
|
|
- recent_projects: List[str] - Recent 5 projects
|
|
- last_active_at: str - ISO timestamp
|
|
- preferences: dict - Optional settings
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 1: Write UserContext dataclass and UserContextManager interface</name>
|
|
<files>src/gsd_openclaw/user_context.py, tests/test_user_context.py</files>
|
|
<behavior>
|
|
- Test 1: UserContext can be created with all fields (user_id, current_project, recent_projects, last_active_at, preferences)
|
|
- Test 2: UserContext.to_dict() returns complete dict with all fields
|
|
- Test 3: UserContext.from_dict() reconstructs from dict, ignoring unknown fields
|
|
- Test 4: UserContextManager.__init__ sets users_root and creates context_path correctly
|
|
- Test 5: UserContextManager.load() returns None for new users (no existing context)
|
|
- Test 6: UserContextManager.load() returns UserContext for existing context file
|
|
</behavior>
|
|
<action>
|
|
Create UserContext dataclass and UserContextManager class skeleton in src/gsd_openclaw/user_context.py:
|
|
|
|
1. Create UserContext dataclass with fields:
|
|
- schema_version: str = "1.0"
|
|
- user_id: str = ""
|
|
- current_project: str = ""
|
|
- recent_projects: List[str] = field(default_factory=list)
|
|
- last_active_at: str = ""
|
|
- preferences: dict = field(default_factory=dict)
|
|
|
|
2. Add to_dict() and from_dict() methods (follow SessionState pattern exactly)
|
|
|
|
3. Create UserContextManager class:
|
|
- USERS_ROOT = Path.home() / "gsd-workspaces" / ".users"
|
|
- __init__(self, user_id: str, users_root: Optional[Path] = None)
|
|
- context_path property: users_root / user_id / "context.json"
|
|
- load() -> Optional[UserContext] method
|
|
- save(context: UserContext) -> None method (stub for next task)
|
|
|
|
4. Create tests/test_user_context.py with TestUserContextManager class
|
|
|
|
5. Follow state_manager.py patterns exactly for consistency
|
|
</action>
|
|
<verify>
|
|
<automated>pytest tests/test_user_context.py -x -v</automated>
|
|
</verify>
|
|
<done>UserContext dataclass and UserContextManager skeleton exist with passing tests</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 2: Implement atomic save with backup rotation</name>
|
|
<files>src/gsd_openclaw/user_context.py, tests/test_user_context.py</files>
|
|
<behavior>
|
|
- Test 1: save() creates file atomically (no temp files remain after save)
|
|
- Test 2: save() creates parent directories if missing
|
|
- Test 3: save() rotates backup (context.json -> context.json.bak)
|
|
- Test 4: save() overwrites old backup on subsequent saves
|
|
- Test 5: save/load roundtrip preserves all data
|
|
- Test 6: load() falls back to backup when main file is corrupted
|
|
</behavior>
|
|
<action>
|
|
Implement UserContextManager.save() with atomic write pattern:
|
|
|
|
1. Follow state_manager.py save_state() pattern exactly:
|
|
- Create parent directories: context_path.parent.mkdir(parents=True, exist_ok=True)
|
|
- Rotate backup: if context_path exists, rename to context.json.bak
|
|
- Write to temp file: tempfile.mkstemp(dir=context_path.parent, suffix='.tmp')
|
|
- Atomic replace: os.replace(temp_path, context_path)
|
|
- Exception handling: clean up temp file on failure
|
|
|
|
2. Add TestAtomicWrites class to tests/test_user_context.py with tests for:
|
|
- Atomic file creation
|
|
- Parent directory creation
|
|
- Backup rotation
|
|
- Cleanup on failure
|
|
|
|
3. Ensure load() has backup fallback (same as load_state in state_manager.py)
|
|
|
|
4. Add imports: tempfile, os, json
|
|
</action>
|
|
<verify>
|
|
<automated>pytest tests/test_user_context.py::TestAtomicWrites -x -v</automated>
|
|
</verify>
|
|
<done>UserContextManager.save() implements atomic writes with backup rotation, matching state_manager.py pattern</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>Task 3: Implement multi-user isolation and convenience methods</name>
|
|
<files>src/gsd_openclaw/user_context.py, tests/test_user_context.py, tests/conftest.py, src/gsd_openclaw/__init__.py</files>
|
|
<behavior>
|
|
- Test 1: Two users have completely isolated context files
|
|
- Test 2: get_current_project() returns current_project or None
|
|
- Test 3: set_current_project() updates current_project and adds to recent_projects
|
|
- Test 4: set_current_project() maintains only last 5 recent projects
|
|
- Test 5: get_recent_projects() returns list of recent projects
|
|
- Test 6: New user gets default context created on first access
|
|
</behavior>
|
|
<action>
|
|
Add convenience methods and multi-user support:
|
|
|
|
1. Add to UserContextManager:
|
|
- get_current_project() -> Optional[str]: Returns current_project or None
|
|
- set_current_project(project: str) -> None:
|
|
* Creates default context if none exists
|
|
* Updates current_project
|
|
* Adds to recent_projects (max 5, remove oldest if needed)
|
|
* Updates last_active_at to now
|
|
* Calls save()
|
|
- get_recent_projects() -> List[str]: Returns recent_projects list
|
|
- create_default_context() -> UserContext: Creates new context with defaults
|
|
|
|
2. Add TestMultiUser class to tests/test_user_context.py:
|
|
- Test isolated context paths for different users
|
|
- Test changes to one user don't affect another
|
|
|
|
3. Add fixtures to tests/conftest.py:
|
|
- tmp_users_root(tmp_path): Temp directory for user contexts
|
|
- user_context_manager(tmp_users_root): UserContextManager with temp root
|
|
|
|
4. Update src/gsd_openclaw/__init__.py:
|
|
- Add: from gsd_openclaw.user_context import UserContext, UserContextManager
|
|
- Add to __all__: "UserContext", "UserContextManager"
|
|
</action>
|
|
<verify>
|
|
<automated>pytest tests/test_user_context.py::TestMultiUser tests/test_user_context.py::TestUserContextManager -x -v</automated>
|
|
</verify>
|
|
<done>UserContextManager has convenience methods, multi-user isolation works, exports added to __init__.py</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- All tests in tests/test_user_context.py pass
|
|
- UserContext follows SessionState pattern for consistency
|
|
- Atomic write pattern matches state_manager.py exactly
|
|
- Multi-user isolation verified through tests
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- UserContextManager class exists with load/save/get_current_project/set_current_project methods
|
|
- Atomic writes with backup rotation work correctly
|
|
- Multi-user isolation verified by tests
|
|
- Exports added to __init__.py
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/04-gsd-integration/04-01-SUMMARY.md`
|
|
</output>
|