Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
189 lines
11 KiB
Python
189 lines
11 KiB
Python
#!/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())
|