#!/usr/bin/env python3 """只读查看桌面 Blender UI → Web Blender UI 完全对标主线。 复用既有进度快照;不启动执行器、写运行状态、构建、部署或运行功能验证。 """ from __future__ import annotations import argparse import json import sys import time from view_wbv2_progress import ( FOCUS_NAMES, STATE_NAMES, WORK_NAMES, duration, interval_value, local_time, snapshot as project_snapshot, ) HEALTH_NAMES = { 'normal': '进程和心跳正常(不代表功能推进正常)', 'unknown': '缺少运行记录', 'stopped': '执行器已停止', 'process_missing': '执行器退出或 PID 已复用', 'process_unknown': '进程身份无法确认', 'heartbeat_stale': '心跳过期', 'worker_exited_pending_handoff': '工作进程退出,等待交接', } SIGNALS = { 'provider_retry_or_reconnect': '有服务失败或重连记录', 'artifact_command_active': '正在生成产物', 'candidate_refresh_due': '候选已到刷新条件', 'source_idle_over_3m': '超过三分钟未观察到源码修改(不单独判为空转)', 'source_progress_stale': '近期源码修改间隔较长', 'focus_repeated_no_progress': '重复无进展或阻塞,需要改变定位策略', } def snapshot(recent_limit=3): # Use the same bounded reads, exact gap IDs and remaining-over-completed # reconciliation as the original viewer; do not fork scheduler semantics. data = project_snapshot(recent_limit) ui = data.get('uiParity') or {} return { 'observedAt': data['observedAt'], 'task': data.get('task'), 'scope': ui.get('phase'), 'planPath': ui.get('planPath'), 'goal': ui.get('goal'), 'currentModule': ui.get('currentModule'), 'associatedModules': ui.get('associatedModules', []), 'modules': ui.get('modules', []), 'runner': data.get('runner', {}), 'provider': data.get('provider', {}), 'candidate': data.get('candidate'), 'latestImplementation': data.get('latestImplementation', {}), 'recent': data.get('recent', []), 'build': data.get('build', {}), 'focuses': data.get('progress', {}).get('focuses', []), 'alerts': data.get('alerts', []), 'notes': data.get('notes', []), 'validation': data.get('validation', 'UNKNOWN'), 'acceptance': data.get('acceptance', 'UNKNOWN'), 'basis': '只读既有交接;计数仅覆盖已登记子项,不是界面覆盖率或验收结果', } def display(data, module_id=None, details=False): print(f"桌面 Blender UI → Web Blender UI 对标 | {local_time(data['observedAt'])}") print(f"任务:{data.get('task') or '未知'}") runner = data['runner'] if runner: print(f"执行:{STATE_NAMES.get(runner.get('recordedState'), runner.get('recordedState'))};" f"{HEALTH_NAMES.get(runner.get('health'), '状态未知')}") print(f"当前第 {runner.get('cycle')} 轮 | {FOCUS_NAMES.get(runner.get('focus'), runner.get('focus'))} | " f"{WORK_NAMES.get(runner.get('workState'), runner.get('workState') or '未知')}") print(f"本轮已用 {duration(runner.get('elapsedSeconds'))};心跳 {duration(runner.get('heartbeatAgeSeconds'))};" f"最近动作 {duration(runner.get('lastActionAgeSeconds'))};源码空闲 {duration(runner.get('sourceIdleSeconds'))}") print(f"活动命令 {runner.get('activeCommandCount', 0)};构建/打包 {runner.get('activeArtifactCommands', 0)};" f"本轮观察到源码文件 {len(runner.get('observedProductionFiles') or [])} 个") if runner.get('reason'): print(f"原因:{runner['reason']}") if data['currentModule']: print(f"当前模块:{data['currentModule']}") else: print('当前精确模块未记录;执行方向关联:' + ('、'.join(data['associatedModules']) or '未记录')) provider = data['provider'] if provider: print(f"服务:失败记录 {provider.get('consecutiveFailures', 0)};重连 {'是' if provider.get('reconnecting') else '否'};" f"退避 {duration(provider.get('retrySeconds'))};错误 {provider.get('error') or '无当前分类'}") for focus in data['focuses']: if focus.get('deferredPathSeconds') or focus.get('retrySeconds'): print(f"路径等待:{focus.get('title')} / {focus.get('deferredPathId') or '当前方向'};" f"暂缓上限 {duration(focus.get('deferredPathSeconds', 0))},重试 {duration(focus.get('retrySeconds', 0))}") for signal in data['alerts']: print('执行信号:' + SIGNALS.get(signal, HEALTH_NAMES.get(signal, signal))) print('\n模块状态(只统计专属登记项;零项不表示未实现,也不表示已完成):') for module in data['modules']: if module_id and module.get('id') != module_id: continue print(f" [{module['id']}] {module.get('title', '')}:{module['status']}") if module.get('gaps'): counts = module['counts'] print(f" 登记子项:已报告实现 {counts['implemented_unverified']} / 定位 {counts['locate']} / 待实现或集成 {counts['open']}") elif module.get('progressHandoff'): handoff = module['progressHandoff'] print(f" 模块交接:已接入 {len(handoff.get('completedRoutes') or [])} 条 / " f"剩余 {len(handoff.get('remainingRoutes') or [])} 条;来源 {handoff.get('reportPath', 'manifest')}") print(f" 样式与完整入口对标:{module.get('visualParity', '未记录')};整模块尚未确认完成") if details or module_id: print(f" 路由:{module.get('route') or '尚无模块专属路由记录'}") if module.get('relatedHandoffPath'): print(f" 相关方向交接:{module['relatedHandoffPath']}(不代表整模块交接)") if module.get('ownerDocs'): print(f" 文档:{module['ownerDocs']}") for gap in module.get('gaps', []): implemented = gap.get('status') == 'implemented_unverified' phase = '定位' if gap.get('phase') == 'locate' else '实现' label = '已报告实现,未验证' if implemented else '尚未闭合' print(f" [{gap['id']}] {phase} / {label}:{gap.get('entry')}") if not implemented: finding = gap.get('latestFinding') or {} residual = gap.get('remainingSubroutes') or [] print(f" 剩余:{';'.join(residual) if residual else finding.get('reason') or gap.get('missing') or '未记录'}") print(f" 下一步:{finding.get('action') or gap.get('nextAction') or '未记录'}") if gap.get('sourcePaths'): print(' 源码落点:' + '、'.join(gap['sourcePaths'])) if module.get('progressHandoff'): handoff = module['progressHandoff'] latest = handoff.get('latestImplementation') or {} if latest: print(f" 最近补漏:{latest.get('reportPath', 'manifest')};候选 {latest.get('candidatePath', '未记录')};验证 {latest.get('validation', 'UNKNOWN')}") for route in handoff.get('remainingRoutes') or []: print(f" 模块余项:{route}") print(f" 模块下一步:{handoff.get('nextAction', '未记录')}") latest = data['latestImplementation'] latest_time = latest.get('at') print(f"\n最近源码交接:第 {latest.get('cycle', '—')} 轮 / {local_time(latest_time)}") print(' ' + (latest.get('businessEntry') or latest.get('workflow') or '尚无记录')) for row in data['recent']: print(f" [{row['cycle']}] {row.get('summary') or row.get('businessEntry') or '未提供摘要'}") candidate = data['candidate'] if candidate: print(f"\n匹配候选记录:{candidate.get('path')}") gap = candidate.get('sourceGap') threshold = candidate.get('refreshGap') pending = str(gap) if gap is not None else '未知' print(f" 待组合 {pending} 轮;刷新阈值 {threshold or '未配置'};" f"{'已到刷新条件' if candidate.get('refreshDue') else '未到已知刷新条件'}") print(f" 部署:{candidate.get('deployment') or '未知'};源码/运行对应关系未验证") else: print('\n匹配候选:尚无可读取记录') if details and data['build'].get('logPath'): print('最近构件日志:' + data['build']['logPath']) print(f"验证:{data['validation']};验收:{data['acceptance']}。不以心跳、轮次或构建判断 UI 已完全对标。") for note in data['notes']: print('提示:' + note) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--json', action='store_true', help='JSON 输出;持续模式为每行一个快照') parser.add_argument('--watch', action='store_true', help='持续刷新,Ctrl+C 退出') parser.add_argument('--interval', type=interval_value, default=10, metavar='秒', help='刷新间隔,默认 10 秒,至少 1 秒') parser.add_argument('--module', metavar='ID', help='只显示指定模块;同时展开该模块细节') parser.add_argument('--details', action='store_true', help='展开路由、缺口、下一动作、源码和报告位置') parser.add_argument('--recent', type=int, choices=range(0, 11), default=3, metavar='0..10', help='最近交接条数,默认 3') args = parser.parse_args() try: while True: data = snapshot(args.recent) if args.module: if args.module not in {item['id'] for item in data['modules']}: print(f'模块 {args.module} 未在当前可读记录中登记。', file=sys.stderr) for note in data['notes']: print(note, file=sys.stderr) return 1 if args.json: data['modules'] = [item for item in data['modules'] if item['id'] == args.module] data['moduleFilter'] = args.module if args.json: print(json.dumps(data, ensure_ascii=False, indent=None if args.watch else 2), flush=True) else: if args.watch and sys.stdout.isatty(): print('\033[2J\033[H', end='') display(data, args.module, args.details) sys.stdout.flush() if not args.watch: return 0 if data.get('task') and data.get('runner') else 1 time.sleep(args.interval) except (KeyboardInterrupt, BrokenPipeError): return 0 if __name__ == '__main__': raise SystemExit(main())