# Phase 38: Workspace Stabilization — Plan

**Plan:** 38-01
**Type:** Backend infrastructure
**Requirements:** WS-01, WS-02, WS-03, WS-04, WS-05
**Estimated tasks:** 5

## Task 1: WS-01 — Direct workspace creation on first sync

**Target:** `paperforge/worker/asset_index.py`, `_build_entry()` (lines ~317-332)

Replace the "workspace-or-legacy" branching with unconditional workspace creation:

1. Delete the `if workspace_dir.exists(): ... else: ...` branching
2. Always:
   - `workspace_dir.mkdir(parents=True, exist_ok=True)`
   - `(workspace_dir / "ai").mkdir(parents=True, exist_ok=True)`
   - Read existing_text from `main_note_path` (not `note_path`)
   - Write `main_note_path` as the write target
3. Remove the `note_path` flat fallback logic
4. Keep `note_path` for backward compat in `deep_reading_status` / `deep_reading_md_path` computation (but note_path itself may not exist for brand-new papers — handle gracefully)

**Note:** `_build_entry()` is called by both `build_index()` (full rebuild) and `refresh_index_entry()` (incremental). Both paths must work with unconditional workspace creation.

## Task 2: WS-02 — fulltext_path bridge (OCR → workspace)

**Target:** `paperforge/worker/asset_index.py`, `_build_entry()`

After writing the formal note and writing paper_meta, add fulltext bridging:

1. After `write_paper_meta()`, if `ocr_status == "done"` and workspace exists:
2. Compute `source_fulltext = paths["ocr"] / entry["zotero_key"] / "fulltext.md"`
3. Compute `target_fulltext = workspace_dir / "fulltext.md"`
4. If source exists and target doesn't, copy: `shutil.copy2(source_fulltext, target_fulltext)`
5. Add `shutil` import at top of module

## Task 3: WS-03 — discussion.py reads ai_path from canonical index

**Target:** `paperforge/worker/discussion.py`

1. Find the method/function that constructs the ai/ directory path (search for `_build_ai_dir` or similar)
2. Replace independent path construction with reading from the canonical index entry
3. Pass the entry dict or `ai_path` field instead of reconstructing from zotero_key + title

## Task 4: WS-04 — migrate_to_workspace() fills fulltext gap

**Target:** `paperforge/worker/sync.py`, `migrate_to_workspace()`

After the existing migration logic (copy note + extract deep reading + create ai/ dir), add:

1. Check if OCR meta exists: `meta_path = paths["ocr"] / zotero_key / "meta.json"`
2. If meta_path exists and `meta.get("ocr_status") == "done"`:
3. Copy `paths["ocr"] / zotero_key / "fulltext.md"` → `workspace_dir / "fulltext.md"`
4. Log the copy

## Task 5: WS-05 — doctor/status validates workspace integrity

**Target:** `paperforge/worker/status.py`

Add workspace integrity checks to `run_status()` or doctor:

1. For each paper in the canonical index:
2. Check: workspace dir exists for indexed papers → if missing, report as warning
3. Check: if ocr_status=done, does workspace fulltext.md exist? → if missing, report broken link
4. Return findings in the status dict

**Verification:**
- Run full test suite: `pytest tests/ -q --tb=short`
- All WS-01 through WS-05 tests pass
- Manual check: new paper sync creates workspace directly
- Manual check: OCR done + workspace = fulltext.md exists
