新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理

后端:
- 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展
- 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通
- 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦
- AI planner/plan_hint/intent:aichat B 路线并行多轮基础
- patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环
- 压缩超时兜底(F-15 卡死根治)
- F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本
- 知识注入 DRY/skills/audit 扩展

清理:
- aichat 技术债(误报 allow/死导入/过时注释 30 项)
- URGENT.md 删除(11 项加急全解决/迁 todo)
- 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
2026-06-21 20:51:26 +08:00
parent 330bb7f505
commit bd6a41fe6e
111 changed files with 11932 additions and 1034 deletions

View File

@@ -66,14 +66,6 @@
<h2>{{ $t('projectDetail.infoTitle') }}</h2>
</div>
<div class="project-info" v-if="currentProject">
<div class="info-item">
<span class="label">{{ $t('projectDetail.sourceIdea') }}</span>
<router-link v-if="currentProject.idea_id" :to="`/ideas/${currentProject.idea_id}`" class="idea-link">
{{ $t('projectDetail.viewIdea') }}
</router-link>
<span v-else class="no-idea">{{ $t('projectDetail.ideaDeleted') }}</span>
</div>
<!-- 代码目录(绑定 + 重定位) -->
<div class="info-item">
<span class="label">{{ $t('projectDetail.codeDirLabel') }}</span>
@@ -124,6 +116,65 @@
<span class="label">{{ $t('projectDetail.projectStatus') }}</span>
<span class="status-badge" :class="currentProject.status">{{ $t(statusLabel) }}</span>
</div>
<!-- 来源灵感卡片(晋升携带评估结论回溯): idea_id 有值时显示 -->
<div v-if="currentProject.idea_id" class="source-idea-card">
<div class="source-idea-header">
<span class="source-idea-title">{{ $t('ideas.sourceIdea') }}</span>
<router-link
v-if="sourceIdea"
class="idea-link"
:to="`/ideas/${currentProject.idea_id}`"
>
{{ sourceIdea.title }}
</router-link>
<router-link
v-else
class="idea-link idea-link-dim"
:to="`/ideas/${currentProject.idea_id}`"
>
#{{ currentProject.idea_id }}
</router-link>
</div>
<!-- sourceIdea 未找到(灵感被删/ load):提示跳转 -->
<p v-if="!sourceIdea" class="source-idea-hint">{{ $t('projectDetail.ideaDeleted') }}</p>
<!-- 已评估:显示评估结论回溯 -->
<template v-else-if="sourceAiAnalysis">
<div class="source-idea-body">
<span
class="assessment-badge"
:class="assessmentClass(sourceAiAnalysis.recommendation)"
>
{{ assessmentLabel(sourceAiAnalysis.recommendation) }}
</span>
<span class="source-final-score">
{{ $t('ideas.finalScore', { score: sourceAiAnalysis.final_score.toFixed(1) }) }}
</span>
</div>
<p v-if="sourceAiAnalysis.summary" class="source-summary">{{ sourceAiAnalysis.summary }}</p>
<!-- 多维评分条(借鉴 IdeaDetail parseScores 渲染) -->
<div v-if="sourceScores.length" class="source-scores">
<div class="radar-row" v-for="dim in sourceScores" :key="dim.name">
<span class="radar-label">{{ dim.name }}</span>
<div class="radar-bar-track">
<div
class="radar-bar-fill"
:class="dim.score >= 80 ? 'fill-high' : dim.score >= 60 ? 'fill-mid' : 'fill-low'"
:style="{ width: dim.score + '%' }"
></div>
</div>
<span class="radar-value">{{ dim.score }}</span>
</div>
</div>
</template>
<!-- 灵感未评估:鼓励去评估 -->
<p v-else class="source-idea-hint">
<router-link class="idea-link" :to="`/ideas/${currentProject.idea_id}`">
{{ $t('ideas.sourceIdeaNotEvaluated') }}
</router-link>
</p>
</div>
</div>
</section>
@@ -195,6 +246,7 @@ import { useProjectStore } from '@/stores/project'
import { projectApi } from '@/api'
import { formatDate } from '@/utils/time'
import { parseStack } from '@/utils/project'
import { parseScores as parseScoresJson, assessmentClass, assessmentLabel as assessmentLabelI18n } from '@/utils/ideaEval'
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import ApprovalDialog from '@/components/project/ApprovalDialog.vue'
@@ -237,6 +289,48 @@ const stageKey = computed(() => currentProject.value?.status ?? 'planning')
const statusLabel = computed(() => projectStatusLabel(stageKey.value))
const currentStageIndex = computed(() => projectStageInfo(stageKey.value).stepIndex)
// ── 来源灵感(晋升携带评估结论回溯)──
// ProjectRecord.idea_id 存灵感 id(promote_idea 写入)。
// sourceIdea: 从 store.ideas 反查;store 未 load 时由 onMounted 守卫补 load。
const sourceIdea = computed(() =>
store.ideas.find(i => i.id === currentProject.value?.idea_id) ?? null,
)
// ai_analysis / scores 是 JSON 字符串(对齐 IdeaDetail 现有解析模式,try/catch 容错)
interface SourceAnalysis {
recommendation: string
final_score: number
summary: string
}
const sourceAiAnalysis = computed<SourceAnalysis | null>(() => {
const idea = sourceIdea.value
if (!idea?.ai_analysis) return null
try {
const parsed = JSON.parse(idea.ai_analysis) as Record<string, unknown>
if (typeof parsed !== 'object' || parsed === null) return null
const recommendation = typeof parsed.recommendation === 'string' ? parsed.recommendation : ''
const final_score = typeof parsed.final_score === 'number' ? parsed.final_score : 0
// summary 优先顶层,回退 analyst.summary(对齐 IdeaDetail historySummary 模式)
const summary = typeof parsed.summary === 'string' ? parsed.summary
: (typeof parsed.analyst === 'object' && parsed.analyst !== null
&& typeof (parsed.analyst as Record<string, unknown>).summary === 'string'
? ((parsed.analyst as Record<string, unknown>).summary as string)
: '')
if (!recommendation && !final_score && !summary) return null
return { recommendation, final_score, summary }
} catch {
return null
}
})
// 项1 DRY:ScoreDimension/parseScores/assessmentClass/assessmentLabel 抽到 @/utils/ideaEval,
// 与 IdeaDetail 共用。sourceScores 仅保留 computed(读 sourceIdea.scores → 复用纯函数解析)。
const sourceScores = computed(() => parseScoresJson(sourceIdea.value?.scores))
// assessmentClass 直接复用 ideaEval(模板直调);assessmentLabel 需注入 t,留薄包装消除重复。
const assessmentLabel = (recommendation: string): string =>
assessmentLabelI18n(t, recommendation)
// ── 任务 ──
const projectTasks = computed(() =>
store.tasks.filter(t => t.project_id === projectId.value)
@@ -410,6 +504,9 @@ onMounted(async () => {
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞
await store.loadProjects()
await store.loadTasks() // 全量加载,详情页用 computed 过滤本项目(避免覆盖全局 tasks 单例)
// 来源灵感回溯:项目有 idea_id 但 store.ideas 为空(本页未 load 过)时补拉,
// 否则 sourceIdea computed 永远找不到对应灵感记录
await store.loadIdeas()
unlisten = await store.startEventListener()
await checkPath()
})
@@ -477,6 +574,85 @@ onUnmounted(() => {
/* ===== 项目描述 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css) ===== */
.description.ai-md { font-size: 14px; color: var(--df-text); line-height: 1.6; }
/* ===== 来源灵感卡片(晋升携带评估结论回溯)===== */
.source-idea-card {
margin-top: 8px;
padding: 12px 14px;
background: var(--df-bg-raised);
border: 0.5px solid var(--df-border);
border-left: 3px solid var(--df-accent);
border-radius: var(--df-radius);
}
.source-idea-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 6px;
}
.source-idea-title { font-size: 13px; font-weight: 500; color: var(--df-text); }
.info-item a.idea-link.idea-link-dim,
.source-idea-card a.idea-link-dim { color: var(--df-text-dim); }
.source-idea-card .source-idea-hint {
font-size: 12px;
color: var(--df-text-dim);
margin: 4px 0 0;
}
.source-idea-body {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin: 6px 0;
}
.source-final-score {
font-size: 13px;
font-weight: 500;
color: var(--df-text);
}
.source-summary {
font-size: 12px;
color: var(--df-text-secondary);
line-height: 1.5;
margin: 6px 0;
}
/* assessment badge(复用 IdeaDetail 同名类的样式定义) */
.assessment-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 11px;
font-weight: 500;
}
.assessment-badge.immediate { background: var(--df-success-bg); color: var(--df-success); }
.assessment-badge.soon { background: var(--df-info-bg); color: var(--df-info); }
.assessment-badge.conditional { background: var(--df-warning-bg); color: var(--df-warning); }
.assessment-badge.revised { background: rgba(255, 217, 61, 0.2); color: var(--df-warning); }
.assessment-badge.defer { background: var(--df-danger-bg); color: var(--df-danger); }
.assessment-badge.cancel { background: var(--df-danger-bg); color: var(--df-danger); }
/* 多维评分条(复用 IdeaDetail radar-* 样式定义) */
.source-scores {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 8px;
}
.radar-row { display: flex; align-items: center; gap: 10px; }
.radar-label { font-size: 12px; color: var(--df-text-secondary); min-width: 64px; text-align: right; }
.radar-bar-track {
flex: 1; height: 8px; background: var(--df-border);
border-radius: var(--df-radius-xs); overflow: hidden;
}
.radar-bar-fill { height: 100%; border-radius: var(--df-radius-xs); transition: width 0.4s; }
.fill-high { background: var(--df-success); }
.fill-mid { background: var(--df-warning); }
.fill-low { background: var(--df-danger); }
.radar-value { font-size: 12px; font-weight: 500; min-width: 28px; text-align: right; color: var(--df-text); }
</style>
<style scoped>