- Add new subcommands: convert, preflight, server, templates, webpage - Migrate config from bin/config.json to ~/.config/mytoolkit - Fix expand_bookmarks to modify writer objects instead of reader - Improve md_to_pdf with CJK bookmark support - Update README and project metadata
120 lines
4.2 KiB
Python
120 lines
4.2 KiB
Python
"""Pre-flight check for work-order consistency and system health."""
|
||
|
||
import json
|
||
import os
|
||
import re
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import click
|
||
|
||
|
||
@click.command(name="preflight")
|
||
def preflight_cmd():
|
||
"""Run pre-flight checks before executing work orders."""
|
||
issues = []
|
||
warnings = []
|
||
|
||
click.secho("Running pre-flight checks...", fg="cyan")
|
||
click.echo("")
|
||
|
||
# === Check 1: work-order.md vs checkpoint JSON consistency ===
|
||
work_order = Path.home() / "workspace/assistant/work-order.md"
|
||
|
||
if work_order.exists():
|
||
content = work_order.read_text()
|
||
|
||
# Find progress numbers like "进度: X/Y 步骤"
|
||
progress_match = re.search(r"进度[::]\s*(\d+)/(\d+)\s*步骤", content)
|
||
if progress_match:
|
||
wo_current = int(progress_match.group(1))
|
||
wo_total = int(progress_match.group(2))
|
||
|
||
# Find checkpoint files referenced in work-order
|
||
cp_match = re.search(r"进度档[::]\s*(\S+)", content)
|
||
if cp_match:
|
||
cp_path = Path.home() / "workspace" / cp_match.group(1)
|
||
if cp_path.exists():
|
||
try:
|
||
cp = json.loads(cp_path.read_text())
|
||
cp_current = cp.get("currentStep", 0)
|
||
cp_total = cp.get("totalSteps", 0)
|
||
cp_completed = len(cp.get("completedSteps", []))
|
||
|
||
if wo_current != cp_current or wo_total != cp_total:
|
||
issues.append(
|
||
f"进度不一致: 工作单 {wo_current}/{wo_total} vs 进度档 {cp_current}/{cp_total}"
|
||
)
|
||
elif cp_current != cp_completed + 1 and cp_current <= cp_total:
|
||
# currentStep should be next step to do (completed + 1)
|
||
# unless all completed
|
||
pass
|
||
except json.JSONDecodeError:
|
||
warnings.append(f"进度档 JSON 损坏: {cp_path}")
|
||
|
||
# Check for duplicate step numbers in step list
|
||
step_lines = re.findall(r"-\s*\[[x ]\]\s*\d+\.", content)
|
||
step_nums = []
|
||
for line in step_lines:
|
||
m = re.search(r"(\d+)\.", line)
|
||
if m:
|
||
step_nums.append(int(m.group(1)))
|
||
|
||
seen = set()
|
||
for num in step_nums:
|
||
if num in seen:
|
||
issues.append(f"步骤 {num} 在清单中重复出现")
|
||
seen.add(num)
|
||
|
||
# Check for gaps in step numbering
|
||
if step_nums:
|
||
expected = list(range(1, max(step_nums) + 1))
|
||
missing = [n for n in expected if n not in seen]
|
||
if missing:
|
||
warnings.append(f"步骤编号缺失: {missing}")
|
||
else:
|
||
warnings.append("工作单文件不存在")
|
||
|
||
# === Check 2: .zshrc PATH references ===
|
||
zshrc = Path.home() / ".zshrc"
|
||
if zshrc.exists():
|
||
zshrc_content = zshrc.read_text()
|
||
# Find paths in PATH exports (lines starting with export PATH=)
|
||
for line in zshrc_content.splitlines():
|
||
if not line.strip().startswith("export PATH="):
|
||
continue
|
||
refs = re.findall(r"/Users/\S+", line)
|
||
for ref in refs:
|
||
full_path = Path(ref).expanduser()
|
||
if not full_path.exists():
|
||
issues.append(f".zshrc PATH 引用失效: {ref}")
|
||
else:
|
||
warnings.append(".zshrc 不存在")
|
||
|
||
# === Check 3: Workspace symlink health ===
|
||
workspace = Path.home() / "workspace"
|
||
if workspace.exists():
|
||
for item in workspace.iterdir():
|
||
if item.is_symlink():
|
||
if not item.exists():
|
||
issues.append(f"符号链接失效: {item.name} -> {os.readlink(item)}")
|
||
|
||
# === Report ===
|
||
click.echo("-" * 50)
|
||
if issues:
|
||
click.secho(f"发现 {len(issues)} 个问题:", fg="red")
|
||
for issue in issues:
|
||
click.secho(f" [x] {issue}", fg="red")
|
||
else:
|
||
click.secho("未发现严重问题", fg="green")
|
||
|
||
if warnings:
|
||
click.secho(f"\n{len(warnings)} 个警告:", fg="yellow")
|
||
for w in warnings:
|
||
click.secho(f" [!] {w}", fg="yellow")
|
||
|
||
click.echo("-" * 50)
|
||
|
||
if issues:
|
||
sys.exit(1)
|