fix(agent): G2缺口指纹超长静默丢失+fallback交付件跨仓库污染+setup_repos空目录崩溃
- _check_orchestration_gaps: G1环/G2指纹改短哈希(_short_fingerprint)——原实现拼接全部未依赖任务ID(15任务≈400字符)超pipeline_pm_notices.fingerprint VARCHAR(255)→INSERT DataError(1406)被catch记debug→PM告警静默丢失,脚手架先批deploy_test提前启动(pbls 09-15实测)
- _build_fallback_deliverable: git扫描只报本任务write_file写入过的仓库——原实现扫全工作空间apps/modules所有git仓库,16天前元景遗留的apps/scense脏工作区被算进M1a交付件→QC误判越权改动构成退回理由(pbls实测)
- _setup_repos: project_dir空串守卫——os.makedirs('')抛Errno 2且中断clone,日志高频刷屏
This commit is contained in:
parent
5fe5c238ec
commit
e774cb238b
@ -1418,7 +1418,12 @@ async def _commit_repos_after_approve(space_dir, project_name='', title=''):
|
||||
async def _setup_repos(sor, space_dir, project_id):
|
||||
"""clone 项目关联仓库到机构工作空间:应用→apps/、模块→modules/;项目目录→projects/{项目名}/。"""
|
||||
project_dir = await _get_project_dir(sor, project_id)
|
||||
await _ensure_project_repo(project_dir)
|
||||
# 守卫:取不到项目目录(sd_projects 记录缺 directory_name/name,或 space_dir 解析失败)时
|
||||
# 原实现把空串传进 _ensure_project_repo → os.makedirs('') 抛
|
||||
# FileNotFoundError [Errno 2] No such file or directory: ''(日志高频刷屏,
|
||||
# 且整个 setup_repos 中断,后续 clone 也不执行)。空目录直接跳过项目仓库步骤。
|
||||
if project_dir:
|
||||
await _ensure_project_repo(project_dir)
|
||||
repos = await _get_project_repos(sor, project_id)
|
||||
results = []
|
||||
for repo in repos:
|
||||
@ -1587,7 +1592,7 @@ async def _check_orchestration_gaps(sor, project_id, task, next_role='', next_ta
|
||||
continue
|
||||
if color[m] == GRAY:
|
||||
cyc = path[path.index(m):] if m in path else [m]
|
||||
fp = "G1:cycle:" + ",".join(sorted(cyc))
|
||||
fp = _short_fingerprint("G1:cycle", *sorted(cyc))
|
||||
if fp in _cyc_seen:
|
||||
continue
|
||||
_cyc_seen.add(fp)
|
||||
@ -1617,7 +1622,7 @@ async def _check_orchestration_gaps(sor, project_id, task, next_role='', next_ta
|
||||
if miss_tasks:
|
||||
miss_ids = [m['id'] for m in miss_tasks]
|
||||
gaps.append({
|
||||
'kind': 'G2', 'fingerprint': "G2:" + a['id'] + ":" + ",".join(sorted(miss_ids)),
|
||||
'kind': 'G2', 'fingerprint': _short_fingerprint("G2", a['id'], *sorted(miss_ids)),
|
||||
'task_id': a['id'], 'fix_ids': miss_ids,
|
||||
'text': (f"G2 应用级 develop「{a['title']}」({a['id']}) 未依赖以下未完成的模块任务:"
|
||||
+ "、".join(f"{m['title']}({m['id']})" for m in miss_tasks)
|
||||
@ -1654,6 +1659,21 @@ async def _check_orchestration_gaps(sor, project_id, task, next_role='', next_ta
|
||||
return gaps
|
||||
|
||||
|
||||
def _short_fingerprint(prefix, *parts):
|
||||
"""缺口指纹定长化:prefix + 内容 md5 前 16 位。
|
||||
|
||||
2026-09-15 pbls 实测:G2 指纹把全部未依赖任务 ID 拼进字符串(15 个任务 ≈ 400 字符),
|
||||
超出 pipeline_pm_notices.fingerprint VARCHAR(255) → INSERT DataError(1406),
|
||||
而 _save_gap_notices 只 catch 记 debug → 告警静默丢失,PM 从未看到
|
||||
「应用级 develop 未依赖模块任务」→ 脚手架先批、deploy_test 在编码未完成时启动
|
||||
(正是 G2 要防的事故)。指纹只服务 (tenant_id, fingerprint) 去重,不需要可读,
|
||||
哈希后长度恒定,与依赖数量无关。
|
||||
"""
|
||||
import hashlib
|
||||
body = ":".join(str(p) for p in parts if p)
|
||||
return f"{prefix}:{hashlib.md5(body.encode('utf-8')).hexdigest()[:16]}"
|
||||
|
||||
|
||||
def _gap_texts(gaps):
|
||||
"""结构化缺口 → 纯文本列表(日志用)。"""
|
||||
return [g['text'] for g in (gaps or []) if isinstance(g, dict) and g.get('text')]
|
||||
@ -2263,32 +2283,41 @@ def _validate_deliverable_type(role, deliverable_type, allowed_types):
|
||||
f"(如 {allowed_types[0]})。")
|
||||
|
||||
|
||||
async def _build_fallback_deliverable(space_dir, written_files, role):
|
||||
"""循环结束未 deliver 时,检测 write_file/git 实际产出,构造真实交付件给 QC(而非占位符「Agent未产出交付件」)。"""
|
||||
async def _build_fallback_deliverable(space_dir, written_files, role, task_id=''):
|
||||
"""循环结束未 deliver 时,检测 write_file/git 实际产出,构造真实交付件给 QC(而非占位符「Agent未产出交付件」)。
|
||||
|
||||
2026-09-15 pbls 修复:git 扫描只报告「本任务 write_file 实际写入过的仓库」。
|
||||
原实现把机构工作空间 apps/ + modules/ 下所有 git 仓库的脏状态都塞进交付件,
|
||||
历史遗留的脏工作区(实测 apps/scense 的 12 项变更全是 16 天前别的任务留下的)
|
||||
被当成本任务产出 → QC 误判「越权改动其它应用仓库」构成独立退回理由。
|
||||
本任务没写过文件的仓库与本任务无关,不进交付件。
|
||||
"""
|
||||
lines = []
|
||||
if written_files:
|
||||
lines.append("本任务执行期间 write_file 实际写入的文件:")
|
||||
for f in sorted(set(written_files)):
|
||||
for f in sorted(written_files):
|
||||
rel = os.path.relpath(f, space_dir) if f.startswith(space_dir) else f
|
||||
lines.append("- " + rel)
|
||||
# 扫描 apps/ + modules/ + projects/ 下的 git 仓库工作区变更
|
||||
for sub in ('apps', 'modules', 'projects'):
|
||||
base = os.path.join(space_dir, sub)
|
||||
if not os.path.isdir(base):
|
||||
# 只扫描本任务实际写入过的仓库(write_file 落点所属),不扫全工作空间
|
||||
touched_repos = set()
|
||||
for f in written_files or []:
|
||||
rp = f if os.path.isabs(f) else os.path.join(space_dir, f)
|
||||
rel = os.path.relpath(rp, space_dir)
|
||||
parts = rel.split(os.sep)
|
||||
if len(parts) >= 2 and parts[0] in ('apps', 'modules', 'projects'):
|
||||
touched_repos.add(os.path.join(space_dir, parts[0], parts[1]))
|
||||
for rp in sorted(touched_repos):
|
||||
if not os.path.isdir(os.path.join(rp, '.git')):
|
||||
continue
|
||||
entries = ([(p, p) for p in sorted(os.listdir(base))] if sub == 'projects'
|
||||
else [(f"{sub}/{n}", n) for n in sorted(os.listdir(base))])
|
||||
for label, name in entries:
|
||||
rp = os.path.join(base, name)
|
||||
if os.path.isdir(os.path.join(rp, '.git')):
|
||||
try:
|
||||
r = await _run_shell('git status --short', rp, 10)
|
||||
out = (r.get('stdout') or '').strip()
|
||||
if out:
|
||||
lines.append(f"git 仓库 [{label}] 工作区变更:")
|
||||
lines.append(out[:2000])
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
r = await _run_shell('git status --short', rp, 10)
|
||||
out = (r.get('stdout') or '').strip()
|
||||
if out:
|
||||
label = os.path.relpath(rp, space_dir)
|
||||
lines.append(f"git 仓库 [{label}] 工作区变更(仅本任务写入过的仓库):")
|
||||
lines.append(out[:2000])
|
||||
except Exception:
|
||||
pass
|
||||
if not lines:
|
||||
lines.append("(本轮未检测到 write_file 或 git 变更——agent 确实未产出)")
|
||||
return {
|
||||
@ -2718,7 +2747,7 @@ async def role_agent_run(project_id, role, agent_id=None, model_name=None):
|
||||
|
||||
if not deliverable:
|
||||
# 兜底:循环结束未 deliver,检测 write_file/git 实际产出构造真实交付件(而非占位符)
|
||||
deliverable = await _build_fallback_deliverable(space_dir, written_files, role)
|
||||
deliverable = await _build_fallback_deliverable(space_dir, written_files, role, task_id=task_id)
|
||||
|
||||
# ── 处理产出 ──
|
||||
from appPublic.uniqueID import getID
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user