- test OCR state machine: isolate runtime (no ambient tokens), fix retry exhaustion and full cycle tests
- test migration: verify no-index flat note migration, legacy flag promotion including false overrides
- test migration: verify non-canonical filename migration via frontmatter title
- test migration: verify already-migrated workspace reconciliation from legacy records
- test asset_index: verify fulltext/deep-reading paths only advertised when files exist
- migrate_to_workspace: preserve legacy flat notes even without canonical index
- migrate_to_workspace: reconcile legacy library-record do_ocr/analyze flags including false overrides
- migrate_to_workspace: handle non-canonical legacy filenames via frontmatter title
- asset_index: only advertise fulltext_path/deep_reading_path when files exist
- asset_index: prefer note frontmatter over legacy library-records, fall back to legacy
- setup_wizard: require Python >=3.10 instead of >=3.8
- plugin main.js: align Python version messaging to 3.10+
- ocr.py: auto_analyze_after_ocr reads from formal note paths, not library-records
- Replaced 5 bare except Exception: pass blocks with logger.warning() calls
(4 per plan at lines 223, 306, 347, 355 + 1 auto-detected at line 315 via Rule 2)
- Added else clause printing [WARNING] for unhandled --fix divergence types
- Added 6 new tests: 4 caplog tests for logger.warning, 1 capsys test for [WARNING],
1 index load failure test
- All 37 repair tests pass
- Removed dead import of load_domain_config and orphaned dict comprehension
- Replaced note_ocr_status != pending guard with combined logic catching note=pending vs meta=done/failed
- Added 5 new tests (3 for condition 4 detection, 2 for dead code verification)
- HARDEN-04: Pass PADDLEOCR_API_TOKEN via env var instead of CLI --paddleocr-key
- HARDEN-05: Replace innerHTML with createEl() DOM API for directory tree rendering
- Replace CST (UTC+8) with UTC timezone using timezone.utc
- Add _escape_md() helper to escape markdown special chars in QA fields
- Wrap JSON+MD read-modify-write in filelock.FileLock with 10s timeout
- Add 5 new tests: UTC timestamp, MD escaping, CJK escaping, lock release, lock timeout
- Add 48-SUMMARY.md with full execution report
- Add 48-VERIFICATION.md with all success criteria verified
- Add deferred-items.md for 2 pre-existing OCR test failures
- Update STATE.md for phase completion
- Replace all bare 'paperforge setup' with 'paperforge setup --headless'
- Rewrite Section 3 of setup-guide.md to describe headless-only workflow
- Update command reference table at Section 7.1 to use --headless
- Update INSTALLATION.md setup command and Better BibTeX section
- Remove all 'from textual' imports (BLOCK A)
- Remove all TUI step classes, custom messages, and SetupWizardApp (BLOCK B)
- Replace main() with help-message redirecting to --headless (BLOCK C)
- Preserve: headless_setup(), EnvChecker, AGENT_CONFIGS, _find_vault, _copy_file_incremental, _merge_env_incremental
- All preserved items verified importable and functional
- Removed 'records' from expected keys in test_paperforge_paths_returns_expected_keys
- Removed 'records' assertion in test_paperforge_paths_values_match_shared_resolver
- Updated docstrings to reflect that _paperforge_paths no longer returns records key
- Removed dead parse_existing_library_record() function (no callers)
- Removed dead record_path construction and function call from run_selection_sync()
- migrate_to_workspace() docstring uses <literature_dir>/ variable references
- Print label uses generic 'literature' instead of hardcoded 'Literature/'
- ai_path_str from canonical index already uses forward slashes (per PATH-01)
- pathlib.Path on Windows handles forward slashes natively
- os.name conditional and replace('/','\\') were unnecessary noise
- PATH-03: Fix env var name from paperforgeRATURE_DIR to PAPERFORGE_LITERATURE_DIR
- PATH-02: library_records now returns control / 'library-records' matching its docstring
- PATH-04: Add skill_dir and command_dir to CONFIG_PATH_KEYS for migration coverage
- Update test_config.py assertion to use corrected env var name
- paper_root, main_note_path, fulltext_path, deep_reading_path, ai_path
now use workspace_dir.relative_to(vault) instead of f'Literature/...'
- Forward-slash normalized with .replace('\\\\', '/') for Windows portability
- record_session() creates ai/discussion.json (canonical) and ai/discussion.md (human-readable)
- Atomic writes via tempfile.NamedTemporaryFile + os.replace() for both files
- Append-only: reading existing sessions, appending new, writing back
- Paper lookup via library-records frontmatter scanning with rglob
- CLI subcommand: python -m paperforge.worker.discussion record
- CJK support via ensure_ascii=False, utf-8 encoding
- stdlib only (json, pathlib, datetime, tempfile, os, uuid, argparse, sys, re, logging)
- Move Better BibTeX auto-export to post-install step (exports dir created by setup)
- Clarify wizard overview: show resources_dir/literature_dir/control_dir hierarchy
- Fix post-install flow order: BBT export → enable plugin → dashboard → sync
- Split do_ocr and analyze marks into separate steps (OCR before deep-reading)
- Mark all literature flags via Base views, not manual file editing
- Make installer incremental: preserve existing files, only create missing ones
- Remove destructive junction/link deletion in TUI wizard
- Merge .env instead of overwriting; skip overwrite for AGENTS.md, docs, plugin
- Update TUI, headless, and plugin completion output to match corrected flow
- Add regression test for non-destructive setup behavior