diff --git a/.blueprint/manifest.yaml b/.blueprint/manifest.yaml index 5dfa0b9..33764e1 100644 --- a/.blueprint/manifest.yaml +++ b/.blueprint/manifest.yaml @@ -381,8 +381,8 @@ modules: - id: agent-infrastructure name: Agent 友好基础设施生成 area: backend - status: planned - progress: 0 + status: done + progress: 100 position: [3750, 0] edges: diff --git a/.blueprint/modules/agent-infrastructure.md b/.blueprint/modules/agent-infrastructure.md index 399c341..28006b0 100644 --- a/.blueprint/modules/agent-infrastructure.md +++ b/.blueprint/modules/agent-infrastructure.md @@ -105,15 +105,15 @@ - files: src-tauri/src/commands/agent_infra.rs, src-tauri/src/db.rs, src/lib/commands.ts - acceptance: `scan_doc_freshness(project_path, project_id)` 用 `silent_cmd("git").current_dir(project_path)` 读取契约路径 vs `docs/ai-context/project-context.yaml` 的最后 commit 时间戳,project-context.yaml 不存在直接返回 RED;结果写入 `doc_freshness_cache` 表(migration 补齐);前端封装就绪 -### 📋 D. 栈确认 UI + 生成按钮 -- status: todo +### ✅ D. 栈确认 UI + 生成按钮 +- status: done - complexity: M - depends: A, B - files: src/components/dashboard/AgentInfraPanel.tsx, src/components/dashboard/BlueprintModal.tsx - acceptance: BlueprintModal 新增"Agent 基础设施"区块,展示 A 返回的检测结果(可编辑 contract 路径)、"生成三件套"按钮调用 B、展示写入结果(written/skipped/failed);UI 无类型错误 -### 🔵 E. 看板腐化指示器 -- status: in_progress +### ✅ E. 看板腐化指示器 +- status: done - complexity: S - depends: C - files: src/components/dashboard/ProjectCard.tsx, src/lib/commands.ts diff --git a/src/components/dashboard/AgentInfraPanel.tsx b/src/components/dashboard/AgentInfraPanel.tsx new file mode 100644 index 0000000..3908ded --- /dev/null +++ b/src/components/dashboard/AgentInfraPanel.tsx @@ -0,0 +1,221 @@ +import { useState } from "react"; +import { + scanAgentInfraStack, + generateAgentInfraFiles, + type AgentInfraStack, + type ContractInput, + type FileResult, +} from "../../lib/commands"; + +const CONFIDENCE_BADGE: Record = { + high: "bg-green-50 text-green-700 border-green-200", + medium: "bg-amber-50 text-amber-700 border-amber-200", + low: "bg-gray-50 text-gray-500 border-gray-200", +}; +const CONFIDENCE_LABEL: Record = { + high: "确认", + medium: "推断", + low: "弱信号", +}; + +const ACTION_ICON: Record = { + written: "✓", + updated: "↻", + skipped: "○", + failed: "✗", +}; +const ACTION_COLOR: Record = { + written: "text-green-600", + updated: "text-blue-600", + skipped: "text-gray-400", + failed: "text-red-600", +}; + +export function AgentInfraPanel({ + projectPath, + projectId, +}: { + projectPath: string; + projectId?: string; +}) { + const [loading, setLoading] = useState(false); + const [generating, setGenerating] = useState(false); + const [stack, setStack] = useState(null); + const [contracts, setContracts] = useState([]); + const [report, setReport] = useState(null); + const [error, setError] = useState(null); + + async function handleScan() { + setLoading(true); + setError(null); + setReport(null); + try { + const result = await scanAgentInfraStack(projectPath); + setStack(result); + setContracts( + result.detected_contracts.map((c) => ({ + label: c.label, + paths: [...c.paths], + doc: c.doc, + })) + ); + } catch (e) { + setError(String(e)); + } finally { + setLoading(false); + } + } + + function removeContract(idx: number) { + setContracts((prev) => prev.filter((_, i) => i !== idx)); + } + + async function handleGenerate() { + if (!contracts.length) return; + setGenerating(true); + setError(null); + try { + const result = await generateAgentInfraFiles(projectPath, contracts); + setReport(result.files); + } catch (e) { + setError(String(e)); + } finally { + setGenerating(false); + } + } + + return ( +
+ {/* 标题行 */} +
+

+ Agent 基础设施 +

+ {stack && ( +
+ {stack.context_yaml_exists ? ( + + yaml ✓ + + ) : ( + + yaml 未生成 + + )} + {stack.meta_rules_present && ( + + 元规则 ✓ + + )} +
+ )} +
+ +

+ 扫描技术栈,生成三件套: + project-context.yaml + 、元规则注入、 + doc-freshness.md +

+ + {/* 扫描按钮 */} + {!stack && ( + + )} + + {/* 扫描结果 + 可编辑契约列表 */} + {stack && ( +
+ {contracts.length === 0 ? ( +

+ 未检测到框架特征,可直接生成空模板 +

+ ) : ( +
+

+ 检测到 {contracts.length} 个外部契约(可移除不需要的) +

+ {contracts.map((c, idx) => { + const original = stack.detected_contracts[idx]; + const conf = original?.confidence ?? "medium"; + return ( +
+
+
+ + {c.label} + + + {CONFIDENCE_LABEL[conf] ?? conf} + +
+

+ {c.paths.join(", ")} +

+
+ +
+ ); + })} +
+ )} + + {/* 重新扫描 + 生成 */} +
+ + +
+
+ )} + + {/* 生成结果 */} + {report && ( +
+

生成结果

+ {report.map((f) => ( +

+ + {ACTION_ICON[f.action] ?? "?"} + + {f.path} + {f.detail} +

+ ))} +
+ )} + + {error && ( +

{error}

+ )} +
+ ); +} diff --git a/src/components/dashboard/BlueprintModal.tsx b/src/components/dashboard/BlueprintModal.tsx index 9eee326..9ac4196 100644 --- a/src/components/dashboard/BlueprintModal.tsx +++ b/src/components/dashboard/BlueprintModal.tsx @@ -1,4 +1,5 @@ import { useState, useCallback, useMemo, useEffect, useRef } from "react"; +import { AgentInfraPanel } from "./AgentInfraPanel"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { listen } from "@tauri-apps/api/event"; import { @@ -1545,6 +1546,9 @@ function GovernancePanel({ )} + {/* ── Agent 基础设施(接入包 v2)───────────────────────── */} + + {/* 项目信息 */}

{projectName}

diff --git a/src/components/dashboard/ProjectCard.tsx b/src/components/dashboard/ProjectCard.tsx index 0f53354..8460deb 100644 --- a/src/components/dashboard/ProjectCard.tsx +++ b/src/components/dashboard/ProjectCard.tsx @@ -1,6 +1,6 @@ import { useState, useRef, useEffect } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { getActivity, getSettings, getProjectTools, openEditor, openTerminal, launchTool, gitStatus, gitFetch, gitPull, gitPush, gitUpstreamBehind, githubGetToken, githubGetAccount, gitCreateBranch, gitListBranches, gitCheckoutBranch, fetchActionsRuns, parseRepoPath, getBlueprintStatus, getBuffStatus, type Project, type Space, type ActionsRun, type BlueprintStatus, type BuffStatus } from "../../lib/commands"; +import { getActivity, getSettings, getProjectTools, openEditor, openTerminal, launchTool, gitStatus, gitFetch, gitPull, gitPush, gitUpstreamBehind, githubGetToken, githubGetAccount, gitCreateBranch, gitListBranches, gitCheckoutBranch, fetchActionsRuns, parseRepoPath, getBlueprintStatus, getBuffStatus, getDocFreshnessCache, type Project, type Space, type ActionsRun, type BlueprintStatus, type BuffStatus, type CachedFreshness } from "../../lib/commands"; import { useUIStore } from "../../store/ui"; import { PriorityBadge, StatusBadge } from "../ui/Badge"; import { ProjectToolsModal } from "./ProjectToolsModal"; @@ -214,6 +214,13 @@ export function ProjectCard({ project: p, spacesJson }: Props) { retry: false, }); + const { data: freshnessCache } = useQuery({ + queryKey: ["docFreshness", p.id], + queryFn: () => getDocFreshnessCache(p.id), + staleTime: 60000, + retry: false, + }); + // PR 链接:从 repo_url 解析出 GitHub 路径 const prUrl = (() => { if (!p.repo_url || !gitStat) return null; @@ -401,6 +408,20 @@ export function ProjectCard({ project: p, spacesJson }: Props) { Buff )} + {/* 文档腐化评分指示器(仅有缓存时显示) */} + {freshnessCache && ( + + )}