fix: Windows兼容性修复 + P1-24弱模型优化 + 3.2.9b禁止降级方案 + AirRvr三层审查放行标准

- lock.py: 跨平台进程锁(Unix fcntl / Windows msvcrt / O_CREAT|O_EXCL降级)
- eng_mode.py/eng_orchestrator.py: hasattr(os, "getloadavg") Windows防护
- arc_mode.py: 路径分隔符 replace("\\", "/") Windows兼容
- deploy_runtime.py: 修复语法错误(清理 import tempfile 残留)
- P1-24(3.2.18): AMBIGUOUS_VERBS歧义词检测 + SAFE_VERBS安全动词 + validate_task_description()
- TaskNode.keep_constraints 保留约束字段 + JSON序列化
- _build_graph_from_todo 返回歧义警告 + Arc自检集成
- 3.2.9b: FORBIDDEN_DEGRADATION_PATTERNS + check_forbidden_degradation()
- AirRvr三层审查放行标准: ReviewVerdict + evaluate_review_pass() + is_forbidden_pass_reason()
- commands/arc.md: 弱模型安全重写(操作类型拆分+保留约束+自检)
- commands/do.md/eng.md/rvr.md: 禁止降级方案 + 三层审查标准
- 测试: 7个新测试 + 74全量通过

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AirPlan
2026-06-15 09:58:29 +08:00
parent 6130478c96
commit a60d1a0c04
13 changed files with 486 additions and 42 deletions

View File

@@ -107,19 +107,22 @@ def _export_task_graph_json(graph: TaskGraph, path: Path) -> None:
"filesDirs": n.files_dirs, "doneWhen": n.done_when,
"inDegree": n.in_degree, "outEdges": n.out_edges,
"writeSet": n.write_set, "testRequired": n.test_required,
"adrRefs": n.adr_refs} # P1-21
"adrRefs": n.adr_refs, # P1-21
"keepConstraints": n.keep_constraints} # P1-24
for nid, n in graph.nodes.items()},
"edges": [{"source": e.source, "target": e.target, "kind": e.kind} for e in graph.edges],
}
atomic_json_write(path, data)
def _build_graph_from_todo(todo_path: Path) -> tuple[TaskGraph, list[str]]:
"""从 todo.md 构建初始 DAG返回图和未满足 Done When 条件的任务列表。"""
def _build_graph_from_todo(todo_path: Path) -> tuple[TaskGraph, list[str], list[str]]:
"""从 todo.md 构建初始 DAG返回图Done When 违规列表、歧义词警告列表。"""
from air_runtime.todo_parser import parse_tasks
from air_runtime.review import validate_task_description
tasks = parse_tasks(todo_path)
graph = TaskGraph()
violations = [] # P1-19.1: 记录 Done When 不含"测试通过"的任务
ambiguity_warnings = [] # P1-24: 弱模型优化 — 歧义词警告
for t in tasks:
# P1-21: 从 todo.md ADR 列提取 adr_refs
@@ -137,10 +140,13 @@ def _build_graph_from_todo(todo_path: Path) -> tuple[TaskGraph, list[str]]:
if t.done_when and "测试通过" not in t.done_when:
violations.append(t.task_id)
# P1-24: 弱模型优化 — 检测歧义词
ambiguity_warnings.extend(validate_task_description(t.task_id, t.task))
# P1-19.1: 注入边界测试任务
_inject_boundary_tests(graph, tasks)
return graph, violations
return graph, violations, ambiguity_warnings
def _inject_boundary_tests(graph: TaskGraph, tasks: list) -> None:
@@ -159,8 +165,8 @@ def _inject_boundary_tests(graph: TaskGraph, tasks: list) -> None:
for fd in t.files_dirs.split(","):
fd = fd.strip()
if fd:
# 取第一级目录作为模块名
parts = fd.split("/")
# 取第一级目录作为模块名 (跨平台路径解析)
parts = fd.replace("\\", "/").split("/")
if len(parts) > 1:
module = parts[0]
else:
@@ -238,7 +244,7 @@ def parallel_review_mode(project_root: Path, todo_path: Path) -> dict:
review_md_path.write_text(render_review_markdown(review), encoding="utf-8")
# 构建 DAG包含边界测试任务注入
graph, done_when_violations = _build_graph_from_todo(todo_path)
graph, done_when_violations, ambiguity_warnings = _build_graph_from_todo(todo_path)
for edge_info in review.to_dict().get("edges", []):
graph.add_edge(Edge(source=edge_info["source"], target=edge_info["target"],
kind=edge_info.get("kind", "dependency")))
@@ -264,6 +270,7 @@ def parallel_review_mode(project_root: Path, todo_path: Path) -> dict:
"conflicts": [c.to_dict() for c in review.conflicts],
"serializationPoints": review.serialization_points,
"doneWhenViolations": done_when_violations, # P1-19.1: Done When 不含"测试通过"的任务
"ambiguityWarnings": ambiguity_warnings, # P1-24: 弱模型优化 — 歧义词警告
"boundaryTestTasks": [n.id for n in graph.nodes.values() if n.test_required],
"safetyWarnings": safety_warnings, # INV-16: 弱模型安全警告
}
@@ -292,6 +299,7 @@ def parallel_review_mode(project_root: Path, todo_path: Path) -> dict:
"parallel_group_count": len(review.parallel_groups),
"conflict_count": len(review.conflicts),
"done_when_violations": done_when_violations,
"ambiguity_warnings": ambiguity_warnings,
"boundary_test_task_count": len([n for n in graph.nodes.values() if n.test_required])}
@@ -301,7 +309,7 @@ def incremental_replan_mode(project_root: Path, todo_path: Path, previous_graph_
_ensure_dirs(paths)
review = build_parallel_review(todo_path)
new_graph, _ = _build_graph_from_todo(todo_path)
new_graph, _, _ = _build_graph_from_todo(todo_path)
for edge_info in review.to_dict().get("edges", []):
new_graph.add_edge(Edge(source=edge_info["source"], target=edge_info["target"],
kind=edge_info.get("kind", "dependency")))

View File

@@ -370,13 +370,15 @@ def monitor_engine(project_root: Path) -> dict:
else:
ready_to_merge += 1 if worker.get("status") == "done" else 0
# 资源压力检测
try:
load = os.getloadavg()[0]
cpu_count = os.cpu_count() or 4
resource_pressure = load > cpu_count * 2
except OSError:
resource_pressure = False
# 资源压力检测 (Unix only; Windows 上不可用)
resource_pressure = False
if hasattr(os, "getloadavg"):
try:
load = os.getloadavg()[0]
cpu_count = os.cpu_count() or 4
resource_pressure = load > cpu_count * 2
except OSError:
pass
# P1-21: ADR 变更自动检测
adr_changes = _detect_adr_changes(project_root, state)

View File

@@ -51,6 +51,8 @@ class AdaptivePoller:
def _resource_pressure() -> bool:
if not hasattr(os, "getloadavg"):
return False
try:
load = os.getloadavg()[0]
cpu = os.cpu_count() or 4