fix: remove deprecated control_dir from doctor/setup/repair; enable repair button; fix UTF-8 encoding in doctor output

This commit is contained in:
Research Assistant 2026-05-10 01:50:24 +08:00
parent 33bc401cc7
commit 15d849f7e6
11 changed files with 30 additions and 55 deletions

View file

@ -1,7 +1,7 @@
{
"id": "paperforge",
"name": "PaperForge",
"version": "1.4.17rc4",
"version": "1.4.17",
"minAppVersion": "1.9.0",
"description": "PaperForge — Zotero literature pipeline. Sync PDFs, run OCR, and read with AI-assisted deep reading.",
"author": "Lin Zhaoxuan",

View file

@ -309,11 +309,6 @@ def build_parser() -> argparse.ArgumentParser:
metavar="NAME",
help="Literature directory name (default: Literature)",
)
p_setup.add_argument(
"--control-dir",
metavar="NAME",
help="Control directory name (default: LiteratureControl)",
)
p_setup.add_argument(
"--base-dir",
metavar="NAME",
@ -497,7 +492,6 @@ def main(argv: list[str] | None = None) -> int:
"system_dir": getattr(args, "system_dir", None) or "System",
"resources_dir": getattr(args, "resources_dir", None) or "Resources",
"literature_dir": getattr(args, "literature_dir", None) or "Literature",
"control_dir": getattr(args, "control_dir", None) or "LiteratureControl",
"base_dir": getattr(args, "base_dir", None) or "Bases",
}
plan = SetupPlan(

View file

@ -57,16 +57,17 @@ def run(args: argparse.Namespace) -> int:
)
# ── Build PFResult ──
divergent = result.get("divergent", 0)
path_errors = result.get("path_errors", {})
divergent: list = result.get("divergent", [])
path_errors: dict = result.get("path_errors", {})
divergent_count = len(divergent)
path_error_total = path_errors.get("total", 0)
has_issues = bool(divergent) or path_error_total > 0
has_issues = divergent_count > 0 or path_error_total > 0
pf_error = None
if has_issues:
pf_error = PFError(
code=ErrorCode.VALIDATION_ERROR,
message=f"Repair found {divergent} divergences and {path_error_total} path errors",
message=f"Repair found {divergent_count} divergences and {path_error_total} path errors",
details=result,
)

View file

@ -165,8 +165,6 @@ const ACTIONS = [
cmd: "repair",
args: ["--fix", "--fix-paths"],
okMsg: "Repair complete",
disabled: true,
disabledMsg: "Repair Issues will be available in a future update.",
},
{
id: "paperforge-copy-context",

View file

@ -1,7 +1,7 @@
{
"id": "paperforge",
"name": "PaperForge",
"version": "1.4.17rc4",
"version": "1.4.17",
"minAppVersion": "1.9.0",
"description": "PaperForge — Zotero literature pipeline. Sync PDFs, run OCR, and read with AI-assisted deep reading.",
"author": "Lin Zhaoxuan",

View file

@ -1,5 +1,5 @@
{
"1.4.3": "1.0.0",
"1.4.17rc3": "1.9.0",
"1.4.17rc4": "1.9.0"
"1.4.17": "1.9.0"
}

View file

@ -13,7 +13,7 @@ from paperforge.setup import SetupStepResult
class ConfigWriter:
"""Write paperforge.json atomically using tempfile + os.replace."""
REQUIRED_KEYS = ["system_dir", "resources_dir", "literature_dir", "control_dir"]
REQUIRED_KEYS = ["system_dir", "resources_dir", "literature_dir"]
def __init__(self, vault: Path):
self.vault = vault

View file

@ -16,7 +16,7 @@ class VaultInitializer:
"system_dir": "99_System",
"resources_dir": "03_Resources",
"literature_dir": "Literature",
"control_dir": "99_System/PaperForge",
# control_dir 已淘汰 (v2.1+ workspace 架构)
}
def __init__(self, vault: Path, config: dict):
@ -29,7 +29,7 @@ class VaultInitializer:
self.vault / "paperforge.json",
]
for key in ("system_dir", "resources_dir", "literature_dir", "control_dir"):
for key in ("system_dir", "resources_dir", "literature_dir"):
rel = self.config.get(key, self.DEFAULT_DIRS.get(key, ""))
if rel:
dirs_to_create.append(self.vault / rel)

View file

@ -423,7 +423,6 @@ def _substitute_vars(
system_dir: str,
resources_dir: str,
literature_dir: str,
control_dir: str,
base_dir: str,
skill_dir: str,
prefix: str = "/",
@ -433,7 +432,6 @@ def _substitute_vars(
("<system_dir>", system_dir),
("<resources_dir>", resources_dir),
("<literature_dir>", literature_dir),
("<control_dir>", control_dir),
("<base_dir>", base_dir),
("<skill_dir>", skill_dir),
("<prefix>", prefix),
@ -519,7 +517,6 @@ def _deploy_skill_directory(
system_dir: str,
resources_dir: str,
literature_dir: str,
control_dir: str,
base_dir: str,
prefix: str = "/",
) -> list[str]:
@ -543,7 +540,7 @@ def _deploy_skill_directory(
skill_dst.mkdir(parents=True, exist_ok=True)
text = skill_file.read_text(encoding="utf-8")
text = _substitute_vars(
text, system_dir, resources_dir, literature_dir, control_dir, base_dir, skill_dir, prefix
text, system_dir, resources_dir, literature_dir, base_dir, skill_dir, prefix
)
_write_text_incremental(skill_dst / "SKILL.md", text)
imported.append(skill_name)
@ -573,7 +570,6 @@ def _deploy_flat_command(
system_dir: str,
resources_dir: str,
literature_dir: str,
control_dir: str,
base_dir: str,
skill_dir: str,
) -> list[str]:
@ -589,7 +585,7 @@ def _deploy_flat_command(
command_dst.mkdir(parents=True, exist_ok=True)
for f in command_src.glob("pf-*.md"):
text = f.read_text(encoding="utf-8")
text = _substitute_vars(text, system_dir, resources_dir, literature_dir, control_dir, base_dir, skill_dir)
text = _substitute_vars(text, system_dir, resources_dir, literature_dir, base_dir, skill_dir)
_write_text_incremental(command_dst / f.name, text)
imported.append(f.stem)
@ -603,7 +599,6 @@ def _deploy_rules_file(
system_dir: str,
resources_dir: str,
literature_dir: str,
control_dir: str,
base_dir: str,
skill_dir_path: str,
) -> list[str]:
@ -638,7 +633,6 @@ def headless_setup(
system_dir: str = "System",
resources_dir: str = "Resources",
literature_dir: str = "Literature",
control_dir: str = "LiteratureControl",
base_dir: str = "Bases",
zotero_data: str | None = None,
skip_checks: bool = False,
@ -657,7 +651,6 @@ def headless_setup(
system_dir: System directory name.
resources_dir: Resources directory name.
literature_dir: Literature subdirectory name.
control_dir: Control subdirectory name.
base_dir: Base directory name.
zotero_data: Zotero data directory (auto-detect if None).
skip_checks: Skip environment validation.
@ -770,7 +763,6 @@ def headless_setup(
"system_dir": system_dir,
"resources_dir": resources_dir,
"literature_dir": literature_dir,
"control_dir": control_dir,
"base_dir": base_dir,
}
@ -867,7 +859,6 @@ def headless_setup(
system_dir,
resources_dir,
literature_dir,
control_dir,
base_dir,
skill_dir,
)
@ -879,7 +870,6 @@ def headless_setup(
system_dir,
resources_dir,
literature_dir,
control_dir,
base_dir,
prefix,
)
@ -891,7 +881,6 @@ def headless_setup(
system_dir,
resources_dir,
literature_dir,
control_dir,
base_dir,
skill_dir,
)
@ -904,7 +893,6 @@ def headless_setup(
system_dir,
resources_dir,
literature_dir,
control_dir,
base_dir,
prefix,
)
@ -965,7 +953,6 @@ def headless_setup(
("<system_dir>", system_dir),
("<resources_dir>", resources_dir),
("<literature_dir>", literature_dir),
("<control_dir>", control_dir),
("<base_dir>", base_dir),
("<skill_dir>", skill_dir),
]:

View file

@ -354,6 +354,21 @@ def _query_resolved_module(interp: str, extra_args: list[str], module_name: str)
return None
if sys.platform == "win32":
try:
sys.stdout.reconfigure(encoding="utf-8")
except AttributeError:
import codecs
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.buffer)
try:
import ctypes
ctypes.windll.kernel32.SetConsoleOutputCP(65001)
except Exception:
pass
def run_doctor(vault: Path, verbose: bool = False, json_output: bool = False) -> int:
"""Validate PaperForge setup and report by category.
@ -491,16 +506,7 @@ def run_doctor(vault: Path, verbose: bool = False, json_output: bool = False) ->
"Vault 结构", "fail", f"resources_dir 不存在: {cfg['resources_dir']}", f"运行: mkdir {cfg['resources_dir']}"
)
control_dir = resources_dir / cfg.get("control_dir", "LiteratureControl")
if control_dir.exists():
add_check("Vault 结构", "pass", f"control_dir 存在: {cfg.get('control_dir', 'LiteratureControl')}")
else:
add_check(
"Vault 结构",
"fail",
f"control_dir 不存在: {cfg.get('control_dir', 'LiteratureControl')}",
f"运行: mkdir {cfg['resources_dir']}/{cfg.get('control_dir', 'LiteratureControl')}",
)
# control_dir 已完全淘汰 (v2.1+ workspace 架构不再需要 LiteratureControl)
zotero_link = system_dir / "Zotero"
if zotero_link.exists():
@ -798,17 +804,6 @@ def run_doctor(vault: Path, verbose: bool = False, json_output: bool = False) ->
except Exception:
pass
# LRD-05: Stale record detection in control directory
_lr_dir = paths.get("control")
if _lr_dir and _lr_dir.exists():
_stale_count = sum(1 for _ in _lr_dir.rglob("*.md"))
if _stale_count > 0:
add_check(
"Index Health",
"warn",
f"{_stale_count} stale record(s) found in control directory",
"Review and remove stale files from the control directory",
)
else:
add_check("Index Health", "info", "No canonical index -- run `paperforge sync` to generate")
@ -1114,7 +1109,6 @@ def run_status(vault: Path, verbose: bool = False, json_output: bool = False) ->
print(f"- system_dir: {cfg['system_dir']}")
print(f"- resources_dir: {cfg['resources_dir']}")
print(f"- literature_dir: {cfg['literature_dir']}")
print(f"- control_dir: {cfg['control_dir']}")
print(f"- exports: {len(export_files)} JSON file(s)")
print(f"- domains: {len(config.get('domains', []))}")
print(f"- formal_notes: {record_count}")

View file

@ -52,6 +52,7 @@ def cli_invoker(vault_builder):
cmd,
capture_output=True,
text=True,
encoding="utf-8",
timeout=60,
input=input_text,
env=base_env,