lllin000_PaperForge/REVIEW.md
Research Assistant aff3cfe622 fix(plugin): update Vitest imports after src/ inline refactor
- Add named exports for testable functions (resolvePythonExecutable, getPluginVersion, etc.)
- Update test imports from ../src/*.js to ../main.js
- Fix repair action test: disabled flag was removed when ACTIONS was inlined
2026-05-10 10:48:06 +08:00

269 lines
11 KiB
Markdown

---
phase: pr-3-review
reviewed: 2026-05-08T12:00:00Z
depth: deep
files_reviewed: 21
files_reviewed_list:
- AGENTS.md
- README.md
- docs/INSTALLATION.md
- docs/setup-guide.md
- manifest.json
- paperforge/__init__.py
- paperforge/ocr_diagnostics.py
- paperforge/plugin/main.js
- paperforge/plugin/manifest.json
- paperforge/plugin/styles.css
- paperforge/plugin/versions.json
- paperforge/worker/status.py
- paperforge/worker/sync.py
- pyproject.toml
- scripts/bump.py
- tests/conftest.py
- tests/test_asset_index_integration.py
- tests/test_asset_state.py
- tests/test_command_docs.py
- tests/test_migration.py
- tests/test_plugin_install_bootstrap.py
findings:
critical: 0
warning: 6
info: 3
total: 9
status: issues_found
---
# PR #3: Code Review Report
**Reviewed:** 2026-05-08T12:00:00Z
**Depth:** deep (cross-file + call-chain + type consistency)
**Files Reviewed:** 21
**Status:** issues_found (6 WARNING, 3 INFO, 0 BLOCKER)
## Summary
Pull Request #3 ("milestone/v1.12-clean") delivers the "plugin runtime closure" feature set: interpreter override, consistent subprocess resolution via `resolvePythonExecutable()`, Dashboard workflow closure (OCR queue add/remove, once-per-session privacy warning, `/pf-deep` command copy with agent platform label), and stronger `doctor` diagnostics. It also consolidates the deep-reading data model to main-note-only and removes stale `docs/` files (INSTALLATION.md, setup-guide.md).
**Overall assessment: NEARLY MERGE READY.** The code is logically sound, the architectural design is clean, and tests pass (501 passed, 2 skipped — both pre-existing platform-specific skips unrelated to this PR). However, 6 WARNING-level issues should be addressed before merging. No BLOCKER-level issues were found.
---
## Checklist Results
| Item | Verdict |
|------|---------|
| **Diff clean?** | YES — No .planning artifacts, no unrelated changes, doc deletions are intentional |
| **Code logically sound for runtime closure?** | YES — Interpreter resolution, runtime health, Dashboard DASH-01/02/03 workflow closure all achieved |
| **Obvious bugs, regressions, missing error handling?** | 6 WARNING issues found (see below) |
| **Test suite sufficient?** | YES — 501 passed, 2 skipped (both pre-existing platform skips in `test_pdf_resolver.py`) |
| **Version alignment clean?** | YES — All three version sources aligned at `1.4.17rc3` |
| **Cross-file consistency?** | 1 issue: `versions.json` retroactively changed minAppVersion for an old release |
---
## Warnings
### WR-01: Hardcoded stale fallback version strings
**File:** `paperforge/plugin/main.js:1482`, `paperforge/plugin/main.js:1728`
**Issue:** Two places hardcode `'1.4.17rc2'` as a fallback when `manifest.version` is falsy:
```javascript
// Line 1482
const ver = this.plugin.manifest.version || '1.4.17rc2';
// Line 1728
const ver = this.manifest.version || '1.4.17rc2';
```
The version has been bumped to `1.4.17rc3` everywhere else, but these fallbacks remain at `rc2`. If `manifest.version` is ever undefined (corrupted settings, race condition during load), the wrong version tag would be passed to `pip install`. While unlikely in practice, this is a maintenance magnet that will inevitably be missed on future bumps.
**Fix:** Replace with a dynamic reference or a generic fallback:
```javascript
const ver = this.plugin.manifest.version;
// If ver is falsy, don't proceed with sync at all
if (!ver) {
new Notice('[!!] Cannot sync: plugin version unknown', 6000);
return;
}
```
Or at minimum, align with the current version:
```javascript
const ver = this.plugin.manifest.version || '1.4.17rc3';
```
---
### WR-02: versions.json retroactively changed minAppVersion for old release 1.4.3
**File:** `paperforge/plugin/versions.json:2`
**Issue:** The entry `"1.4.3": "1.0.0"` was changed to `"1.4.3": "1.9.0"`.
In Obsidian's plugin update mechanism, `versions.json` maps each plugin version to the minimum Obsidian version required **for that specific release**. Old entries must remain unchanged — they represent the requirements that were valid at the time of that release. Version `1.4.3` did NOT require Obsidian `1.9.0`.
Only the **new** version entry (`1.4.17rc3`) should map to `"1.9.0"`. The old `1.4.3` entry should stay at `"1.0.0"`.
```diff
{
- "1.4.3": "1.9.0",
+ "1.4.3": "1.0.0",
"1.4.17rc3": "1.9.0"
}
```
---
### WR-03: JS `resolvePythonExecutable` only has Windows-style venv paths
**File:** `paperforge/plugin/main.js:884-887`
**Issue:** The JavaScript `resolvePythonExecutable()` function hardcodes Windows-only venv paths:
```javascript
const venvCandidates = [
path.join(vaultPath, '.paperforge-test-venv', 'Scripts', 'python.exe'),
path.join(vaultPath, '.venv', 'Scripts', 'python.exe'),
path.join(vaultPath, 'venv', 'Scripts', 'python.exe'),
];
```
On macOS/Linux (where Obsidian/Electron also runs), virtualenv Python binaries live in `bin/python`, not `Scripts/python.exe`. The Python counterpart in `status.py` (`_resolve_plugin_interpreter`) correctly handles this with an `os.name == "nt"` check. The JavaScript version does not, meaning venv detection silently falls through on POSIX systems.
**Fix:** Add platform-aware venv paths:
```javascript
const isWin = process.platform === 'win32';
const venvCandidates = isWin ? [
path.join(vaultPath, '.paperforge-test-venv', 'Scripts', 'python.exe'),
path.join(vaultPath, '.venv', 'Scripts', 'python.exe'),
path.join(vaultPath, 'venv', 'Scripts', 'python.exe'),
] : [
path.join(vaultPath, '.paperforge-test-venv', 'bin', 'python'),
path.join(vaultPath, '.venv', 'bin', 'python'),
path.join(vaultPath, 'venv', 'bin', 'python'),
];
```
---
### WR-04: `execFileSync` in synchronous code path can block UI thread
**File:** `paperforge/plugin/main.js:897-914`
**Issue:** `resolvePythonExecutable()` uses `execFileSync` (synchronous) to test system candidates, with a 5-second timeout per candidate, up to 3 candidates. This function is called during **synchronous rendering** of both the Settings tab (`display()`) and the Dashboard (`_renderGlobalMode()`).
If Python is not properly installed and `py -3`, `python`, or `python3` each time out, the Obsidian render thread blocks for up to 15 seconds. This causes a visible UI freeze.
**Fix:** Two options:
1. **Async-first design**: Make `resolvePythonExecutable` async, cache the resolved interpreter, and use the cached value during rendering. The system candidate probe runs once asynchronously.
2. **Use `execFile` instead of `execFileSync`**: Replace the system candidate loop with an async approach:
```javascript
// Return cached result if already probed
if (resolvePythonExecutable._cache) return resolvePythonExecutable._cache;
// During synchronous rendering, skip the execFileSync probe entirely
// and let the async probe update the cache later.
```
---
### WR-05: Bare `Exception` catch in `_read_plugin_data`
**File:** `paperforge/worker/status.py:1963`
**Issue:** The `_read_plugin_data` function catches bare `Exception`, which can suppress `KeyboardInterrupt`, `MemoryError`, and other system-level exceptions:
```python
except (json.JSONDecodeError, OSError, Exception):
return {}
```
Since `OSError` already covers the `PermissionError`/`FileNotFoundError` cases, the bare `Exception` is redundant and dangerous.
**Fix:**
```python
except (json.JSONDecodeError, OSError):
return {}
```
---
### WR-06: Zotero data directory changed from optional to required without migration path
**Files:** `paperforge/plugin/main.js` (setup wizard validation, step 4 logic, summary page)
**Issue:** The Zotero data directory was previously optional (with placeholder text "可选,用于自动检测 PDF"). It is now required, with strict validation: not empty, exists, is a directory, and contains `storage/` subdirectory.
Existing users who completed setup with `zotero_data_dir` left empty will encounter setup wizard validation failures on their next reconfiguration visit. There is no migration logic to detect this state and prompt the user, nor an auto-discovery mechanism to fill in a likely path.
**Fix:** Add a one-time migration notice or auto-discovery:
```javascript
// On settings load, if zotero_data_dir is empty, attempt auto-discovery:
if (!s.zotero_data_dir) {
const candidates = [
path.join(os.homedir(), 'Zotero'),
path.join(os.homedir(), 'Zotero', 'storage'),
];
for (const c of candidates) {
if (fs.existsSync(path.join(c, 'storage'))) {
s.zotero_data_dir = c;
break;
}
}
}
```
---
## Info
### IN-01: Repetitive `require('fs')` calls in method bodies
**File:** `paperforge/plugin/main.js` — methods `_validatePythonOverride`, `_syncRuntime`, `_preCheck`, `_validateSetup`, etc.
**Issue:** `require('fs')`, `require('path')`, and `require('node:child_process')` are called at the top of individual methods instead of once at module scope. This is wasteful (Node caches `require` calls, so there's no runtime penalty) but is a readability/maintainability concern.
**Suggestion:** Move all `require()` calls to module scope at the top of the file.
---
### IN-02: `run_doctor()` return value semantics changed
**File:** `paperforge/worker/status.py` (near line 2290)
**Issue:** The return value of `run_doctor()` changed semantics:
- **Before:** returned `1` if `fix_map` had entries (any issue with a suggested fix, including warnings)
- **After:** returns `1 if has_fail else 0` (only hard failures)
Callers (CI scripts, `paperforge doctor` CLI handler) may rely on exit code `1` for any actionable issue. Verify that the CLI handler for `doctor` checks for warnings separately. If not, a warnings-only state would now produce exit code 0 when it previously produced 1.
---
### IN-03: `versions.json` missing trailing newline
**File:** `paperforge/plugin/versions.json`
**Issue:** The file ends without a trailing newline (`\n` at EOF). Minor POSIX compatibility concern — some tools (e.g., `diff`, `cat`) warn about missing trailing newlines.
---
## Conclusion
**Verdict: CONDITIONAL MERGE** — fix the 6 WARNING issues first.
The three most important fixes are:
1. **WR-01** — Fix hardcoded `'1.4.17rc2'` fallbacks (quick fix, prevents latent version drift)
2. **WR-02** — Restore `"1.4.3": "1.0.0"` in versions.json (data integrity for Obsidian update mechanism)
3. **WR-05** — Remove bare `Exception` catch in `_read_plugin_data` (defensive coding)
WR-03 and WR-04 are cross-platform correctness issues; WR-06 is a UX gap for existing users. None block the merge but should be documented as known limitations.
The structural design of the PR is sound. The interpreter resolution refactoring (returning `{path, source, extraArgs}` instead of a bare string) is cleanly applied across all call sites. The Dashboard workflow closure (DASH-01/02/03) is well-implemented with proper state management. The deep-reading model consolidation to main-note-only is consistent across JS plugin, Python backend, and tests.
---
_Reviewed: 2026-05-08T12:00:00Z_
_Reviewer: VT-OS/OPENCODE (gsd-code-reviewer)_
_Depth: deep_