- Add migrate_to_workspace() function to sync.py that copies flat notes to workspace directories
- Extract ## 🔍 精读 section into deep-reading.md in workspace
- Create ai/ directory in each paper workspace
- Wire migration into run_index_refresh() before build_index()
- Update _build_entry() in asset_index.py to write notes to workspace path when workspace dir exists
- Preserve flat path fallback for backward compatibility when workspace dir does not exist
- Add 'context' to _COMMAND_REGISTRY for dynamic module loading
- Add context subparser with key (nargs='?'), --domain, --collection, --all
- Dispatch in main() to paperforge.commands.context.run(args)
- D-06: always outputs JSON, no --json flag needed
- run() reads canonical index, filters by key/domain/collection/all
- _format_context_entry wraps entries with _provenance and _ai_readiness blocks
- AIC-04: blocking explanation when lifecycle != 'ai_context_ready'
- AIC-02: single key outputs single JSON object with provenance
- AIC-03: --domain and --collection output JSON arrays
- D-01: canonical index entry IS the AI context -- no separate pack format
- 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)