- run_status() reads canonical index via summarize_index() for lifecycle,
health, and maturity aggregates; falls back to filesystem when index missing
- JSON output includes lifecycle_level_counts, health_aggregate,
maturity_distribution (or None when falling back)
- Text output shows lifecycle and health lines when index is present
- run_doctor() shows Index Health section with PDF/OCR/Note/Asset Health
counts and status per dimension
- Brownfield detection: legacy schema, old Base templates, partial OCR assets
- Fixed status_tag mapping to support 'info' status (existing bug)
- 6 new tests covering index-backed JSON output, fallback, text output,
Index Health with/without index, and mixed health counts
Phase 25-01, Tasks 2+3
- Add build_index() call at end of run_repair() when fix=True or fix_paths=True
- Add 'rebuilt' key to result dict initialization
- Add user-facing messages about repair completion and recovery path (MIG-04)
- Lazy import inside conditional block to avoid circular dependency
- Add tests: build_index called after fix, not called during dry-run,
rebuilt in result, error fallback on build_index failure
- Replace has_pdf/do_ocr/analyze/ocr_status columns with lifecycle/maturity_level/next_step
- Update filters to use lifecycle states instead of raw status combinations
- Add sort by lifecycle ascending to all views
- Update PROPERTIES_YAML and _build_base_yaml properties
- Add sort YAML rendering in _render_views_section and merge_base_views
- Update existing filter tests to match lifecycle semantics
- Add tests: lifecycle columns, removed old columns, lifecycle filters, sort, properties
- Replace Python CLI spawn with fs.readFileSync for primary path
- Single-pass aggregation of lifecycle, health, and OCR counts per D-06
- Fallback to CLI spawn when index file is missing (D-07)
- Add fs and path as top-level requires
- Access system_dir via app.plugins.plugins['paperforge'].settings
- New function summarize_index() reads canonical index and returns lifecycle,
health, and maturity aggregates
- Returns None for missing or legacy bare-list index
- 4 new tests: aggregates, missing, legacy format, empty items
Phase 25-01, Task 1
- Add TestDerivedStateFields class with 6 test methods
- Verify lifecycle/health/maturity/next_step present after full build
- Verify same fields present after incremental refresh
- Verify lifecycle is a valid state string
- Verify health dict has four dimension keys
- Verify maturity structure (level, level_name, checks, blocking)
- Verify next_step is a valid action string
- Import compute_lifecycle, compute_health, compute_maturity, compute_next_step from asset_state
- Call all four functions after entry dict construction in _build_entry()
- New fields flow automatically through build_index() and refresh_index_entry()
- Fix note_health to use ternary expression (ruff SIM108)
- All docstrings describe inputs, outputs, and derivation rules
- Module-level docstring lists all four exports
- All 26 tests pass, ruff: All checks passed
- compute_lifecycle: six progressive states (indexed → pdf_ready → fulltext_ready → deep_read_done → ai_context_ready)
- compute_health: four dimensions (pdf/ocr/note/asset) with concrete fix instructions
- compute_maturity: level 1-6 with per-check pass/fail and blocking indicator
- compute_next_step: priority-ordered recommendation (sync/ocr//pf-deep/ready)
- All 26 tests pass, pure functions with no filesystem or config imports
- Class TestComputeLifecycle: 8 tests covering indexed, pdf_ready, fulltext_ready, deep_read_done, ai_context_ready states
- Class TestComputeHealth: 4 tests covering all four health dimensions
- Class TestComputeMaturity: 6 tests covering levels 1-6 with blocking indicators
- Class TestComputeNextStep: 8 tests covering sync, ocr, /pf-deep, ready recommendations
- All 26 tests fail with ModuleNotFoundError (module not yet created)
- Import refresh_index_entry in repair.py
- Call refresh_index_entry after each successful path fix in repair_pdf_paths
- Call refresh_index_entry after each divergence fix in run_repair
- Add docstring to run_index_refresh in sync.py explaining full-rebuild default
convention vs incremental refresh by key
- Import refresh_index_entry from asset_index module
- After status sync report, refresh canonical index for every paper
- Refresh even when synced==0 (formal note content may have changed)
- Failure to refresh logs warning, does not abort the worker
- Add refresh_index_entry import from asset_index module
- Replace full rebuild (_sync.run_index_refresh) with incremental refresh per completed OCR key
- Capture done keys before queue filter to preserve them for incremental refresh
- Add ImportError fallback to full rebuild for pre-migration safety
- When no OCR completed, still run full rebuild for sync changes
- Extract _build_entry() from build_index() loop body as shared helper
- Add refresh_index_entry(vault, key) for single-entry incremental update
- Add schema_version mismatch detection in build_index()
- Lazy imports inside _build_entry() avoid circular deps with sync.py
- Legacy format detected in refresh falls back to full rebuild
- All 14 existing tests pass
- Add read_index(vault) to read existing index file safely
- Add is_legacy_format(data) to distinguish bare-list from envelope
- Add migrate_legacy_index(vault) to copy legacy index to .bak before rebuild
- Modify build_index() to call migrate_legacy_index at start
- Corrupt/missing files handled gracefully (treated as fresh build)
- Added _pfConfig cache and _refreshPfConfig() to PaperForgeSettingTab constructor
- display() now calls _refreshPfConfig() at start for fresh path config
- Config Summary section reads path values from _pfConfig (paperforge.json source)
- Prep export path hint now reads from _pfConfig.system_dir
- Non-path fields (paddleocr_api_key, zotero_data_dir) still read from plugin.settings
- Import migrate_paperforge_json from paperforge.config
- Call migrate_paperforge_json after vault resolution, before sync ops
- Log migration info; print verbose message when --verbose is set
- Add import shutil to config.py
- Add CONFIG_PATH_KEYS tuple for legacy top-level path keys
- Add migrate_paperforge_json() function with gap-fill logic
- Backs up original as paperforge.json.bak before writing
- Idempotent: no-op for already-migrated files
- Non-path top-level keys (version, agent_platform) survive migration
- Add schema_version: '2' as first key in DEFAULT_CONFIG
- Add get_paperforge_schema_version() public function
- Exclude schema_version from load_vault_config() output
- CONFIG_KEYS auto-derives from DEFAULT_CONFIG via set()
- DEFAULT_CONFIG must contain schema_version: '2'
- CONFIG_KEYS must include schema_version
- get_paperforge_schema_version() defaults to 1 when key absent
- get_paperforge_schema_version() reads explicit value
- load_vault_config() excludes schema_version from output