export const meta = { name: 'feature-triage', description: '特性处置考古:code → 逆向需求 → 每个特性的现状/缺口 + git 历史归属(谁做的/谁是专家/该派给谁)→ 产出"让人来拍板"的特性处置清单(删除/补 TODO/指派专家/保留)', whenToUse: '想对一个现有代码库做"特性盘点 + 处置决策"时使用:把代码切成特性,逐特性逆向出需求与现状、探出代码文件半径、读 git 历史定专家与归属,最后产出一份让人勾选的处置清单(删除/补 TODO/指派给历史专家/保留)。args 可传 {focus, root, since, max_features}。', phases: [ { title: 'Survey', detail: '把仓库切成特性集群,给出 what-it-is 假设与 key_paths' }, { title: 'Excavate', detail: '逐特性读码:逆向需求 + 现状证据 + 代码文件探查半径(核心文件 + 调用/测试/配置/前后端对位)' }, { title: 'Verify', detail: '对抗式核验现状断言,剔除幻觉、校正状态' }, { title: 'Blame', detail: '逐特性读 git 历史:贡献者/专家/最近活跃/bus-factor/该派给谁' }, { title: 'Dispose', detail: '合成每个特性的现状+缺口+专家+建议处置+人类决策选项' }, { title: 'Board', detail: '裁判合成特性处置清单(checklist) + 指派看板 + 孤儿/bus-factor 风险' }, ], } // ---------- args ---------- const focus = (args && args.focus) ? String(args.focus) : null const root = (args && args.root) ? String(args.root) : '.' const since = (args && args.since) ? String(args.since) : null const maxFeatures = (args && args.max_features) ? Number(args.max_features) : 16 const FOCUS_LINE = focus ? `\n\n【聚焦范围】本次只重点分析与「${focus}」相关的特性,其余可略读或跳过。` : '' const SINCE_GIT = since ? ` --since='${since}'` : '' const SINCE_NOTE = since ? `(只看 ${since} 以来的提交)` : '(看全量历史)' // 大白话汇报准则(internal-comms 风格)——所有面向人的字段都按此写 const PLAIN_LANG = `\n\n【汇报语气——务必照做|internal-comms 风格】站在"用这个产品的业务方 / 老板 / 一线用户"视角用大白话写,不要 coder 黑话:\n` + `- 先给结论,再给证据,最后给下一步建议;每句话让一个不懂代码的业务负责人能看懂、能据此拍板。\n` + `- 禁止把代码术语直接当解释甩出来。file:line、函数名、router/registry/SSE/in-memory/jobstore/conftest/import 等,必须翻成"对用户/业务的后果":\n` + ` · "router 未在 app.py 注册=404" → "这个功能挂在菜单上,但点进去其实是坏的,用户根本用不了";\n` + ` · "RunManager 内存注册表多副本失忆" → "任务进度只存在单台服务器内存里,一重启或一扩容就丢,用户点了停止没反应";\n` + ` · "缺 pypinyin 依赖致中文匹配静默出错" → "少装了一个中文识别的小工具,导致输入经销商名会认错成另一家,还不报错——等于给错诊断"。\n` + `- 可保留:人名、数字、百分比、特性名、工作量档位(S/M/L)。\n` + `- 诚实标注:哪些是我们亲自看代码核实的(实证)、哪些是推断、哪些数据缺失——不要把不确定说成确定。` // ---------- schemas ---------- const FEATURE_MAP_SCHEMA = { type: 'object', additionalProperties: false, properties: { project_name: { type: 'string' }, one_line_what_it_is: { type: 'string', description: '一句话说清这个项目是什么(中文)' }, primary_users: { type: 'array', items: { type: 'string' } }, features: { type: 'array', description: `8-${maxFeatures} 个用户可感知的"特性/功能"集群(不是技术分层,而是一条条可单独保留/删除/指派的特性)`, items: { type: 'object', additionalProperties: false, properties: { key: { type: 'string', description: 'kebab-case 唯一标识' }, name: { type: 'string' }, hypothesis: { type: 'string', description: '这个特性在干什么(中文)' }, key_paths: { type: 'array', items: { type: 'string' }, description: '3-8 个代表性文件/目录路径或 glob' }, }, required: ['key', 'name', 'hypothesis', 'key_paths'], }, }, }, required: ['project_name', 'one_line_what_it_is', 'primary_users', 'features'], } const STATE_ENUM = ['shipped', 'partial', 'wip-unwired', 'dead-code', 'experimental', 'unknown'] const EXCAVATION_SCHEMA = { type: 'object', additionalProperties: false, properties: { feature_key: { type: 'string' }, files_core: { type: 'array', items: { type: 'string' }, description: '真正实现这个特性的核心文件(实读过,越具体越好)' }, files_radius: { type: 'array', description: '探查半径:与核心文件相关的外圈文件——调用方/被调方/测试/配置/前后端对位/路由注册处,各带一词关系标签', items: { type: 'object', additionalProperties: false, properties: { path: { type: 'string' }, relation: { type: 'string', description: 'caller/callee/test/config/route/frontend/backend/schema/doc 等' } }, required: ['path', 'relation'], }, }, domain_entities: { type: 'array', items: { type: 'string' } }, what_it_is: { type: 'string', description: '一句话:这个特性对用户是什么' }, explicit_requirements: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { req: { type: 'string' }, evidence: { type: 'string' } }, required: ['req', 'evidence'] }, }, implicit_requirements: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { req: { type: 'string' }, why_inferred: { type: 'string' }, evidence: { type: 'string' } }, required: ['req', 'why_inferred', 'evidence'] }, }, user_stories: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { as_a: { type: 'string' }, i_want: { type: 'string' }, so_that: { type: 'string' }, evidence: { type: 'string' } }, required: ['as_a', 'i_want', 'so_that', 'evidence'] }, }, surface_feature: { type: 'string', description: '表层功能一句话' }, current_status: { type: 'object', additionalProperties: false, properties: { state: { type: 'string', enum: STATE_ENUM, description: 'shipped=上线可用 / partial=部分可用有缺口 / wip-unwired=有码但未接线(注册/路由/消费缺失) / dead-code=孤儿死代码 / experimental=实验区 / unknown' }, evidence: { type: 'array', items: { type: 'string' }, description: '判定该状态的代码证据(file:line/路由注册/测试/404 等)' }, whats_working: { type: 'array', items: { type: 'string' } }, whats_missing_or_broken: { type: 'array', items: { type: 'string' } }, }, required: ['state', 'evidence', 'whats_working', 'whats_missing_or_broken'], }, smells: { type: 'array', items: { type: 'string' } }, }, required: ['feature_key', 'files_core', 'files_radius', 'domain_entities', 'what_it_is', 'explicit_requirements', 'implicit_requirements', 'user_stories', 'surface_feature', 'current_status'], } const VERDICT_SCHEMA = { type: 'object', additionalProperties: false, properties: { feature_key: { type: 'string' }, status_confirmed: { type: 'string', enum: STATE_ENUM, description: '核验后确认的真实状态(可能与挖掘的不同)' }, corrected_status_note: { type: 'string', description: '若改了状态,说明为何' }, grounded_points: { type: 'array', items: { type: 'string' }, description: '确有代码支撑、保留的现状/需求要点' }, ungrounded_claims: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { claim: { type: 'string' }, reason: { type: 'string' } }, required: ['claim', 'reason'] }, description: '无支撑/过度解读/把噱头当现状的断言', }, confidence: { type: 'number', description: '0-1 对该特性现状还原的可信度' }, }, required: ['feature_key', 'status_confirmed', 'grounded_points', 'ungrounded_claims', 'confidence'], } const GIT_SCHEMA = { type: 'object', additionalProperties: false, properties: { feature_key: { type: 'string' }, paths_examined: { type: 'array', items: { type: 'string' }, description: '实际跑了 git log 的路径' }, total_commits: { type: 'number' }, first_commit_date: { type: 'string', description: 'YYYY-MM-DD 或 unknown' }, last_commit_date: { type: 'string', description: 'YYYY-MM-DD 或 unknown' }, activity_trend: { type: 'string', enum: ['active', 'cooling', 'stale', 'abandoned', 'unknown'], description: '据最近提交距今判断' }, contributors: { type: 'array', description: '按提交数降序的贡献者', items: { type: 'object', additionalProperties: false, properties: { name: { type: 'string' }, email: { type: 'string' }, commits: { type: 'number' }, last_touch_date: { type: 'string' }, role_guess: { type: 'string', description: '据其改动文件猜其角色(前端/后端/算法/全栈/文档等)' }, }, required: ['name', 'commits', 'last_touch_date'], }, }, top_expert: { type: 'object', additionalProperties: false, properties: { name: { type: 'string' }, why: { type: 'string', description: '为何是专家:提交占比/独占/最近仍在改等' } }, required: ['name', 'why'], }, recommended_assignee: { type: 'object', additionalProperties: false, properties: { name: { type: 'string' }, reason: { type: 'string', description: '该派给谁继续做这个特性,及理由' } }, required: ['name', 'reason'], }, bus_factor: { type: 'object', additionalProperties: false, properties: { level: { type: 'string', enum: ['healthy', 'single-owner-risk', 'orphaned', 'unknown'] }, note: { type: 'string' } }, required: ['level', 'note'], }, notes: { type: 'array', items: { type: 'string' } }, }, required: ['feature_key', 'paths_examined', 'total_commits', 'last_commit_date', 'activity_trend', 'contributors', 'top_expert', 'recommended_assignee', 'bus_factor'], } const DISPOSITION_ENUM = ['KEEP_AS_IS', 'FINISH_BUILD_TODOS', 'ASSIGN_TO_EXPERT', 'REMOVE_DEAD', 'DECIDE_TRADEOFF'] const DISPOSITION_SCHEMA = { type: 'object', additionalProperties: false, properties: { feature_key: { type: 'string' }, feature_name: { type: 'string' }, one_line: { type: 'string', description: '这个特性对用户是什么(一句话)' }, current_status: { type: 'string', enum: STATE_ENUM }, status_evidence: { type: 'string', description: '一句话现状判据(带代码证据)' }, completeness_pct: { type: 'number', description: '0-100 粗估完成度' }, what_else_to_do: { type: 'array', description: '要让这个特性真正可用/收口还差哪些具体活', items: { type: 'object', additionalProperties: false, properties: { todo: { type: 'string' }, effort: { type: 'string', enum: ['S', 'M', 'L'], description: 'S<半天 / M<2天 / L>2天' }, blocks_or_risk: { type: 'string' }, }, required: ['todo', 'effort'], }, }, expert_owner: { type: 'object', additionalProperties: false, properties: { name: { type: 'string' }, evidence: { type: 'string' } }, required: ['name', 'evidence'] }, recommended_assignee: { type: 'object', additionalProperties: false, properties: { name: { type: 'string' }, reason: { type: 'string' } }, required: ['name', 'reason'] }, recommended_disposition: { type: 'string', enum: DISPOSITION_ENUM }, disposition_rationale: { type: 'string' }, human_decision_options: { type: 'array', description: '给人勾选的处置选项(至少 2 个):每个含动作/负责人/后果', items: { type: 'object', additionalProperties: false, properties: { action: { type: 'string', description: '如:保留 / 补完(列TODO) / 指派给X / 删除 / 降级实验 / 先调研' }, who: { type: 'string', description: '建议负责人(可空)' }, consequence: { type: 'string', description: '选它会怎样' }, }, required: ['action', 'consequence'], }, }, risk_if_ignored: { type: 'string', description: '不处置的代价' }, confidence: { type: 'number' }, }, required: ['feature_key', 'feature_name', 'one_line', 'current_status', 'status_evidence', 'completeness_pct', 'what_else_to_do', 'expert_owner', 'recommended_assignee', 'recommended_disposition', 'disposition_rationale', 'human_decision_options', 'risk_if_ignored'], } const FINAL_SCHEMA = { type: 'object', additionalProperties: false, properties: { executive_summary: { type: 'string', description: 'CEO 语言 3-5 句:项目是什么 + 特性盘点全景 + 最该先决策的几件事' }, feature_count: { type: 'number' }, readiness_note: { type: 'string', description: '整体成熟度一句话(多少已上线/多少半成品/多少死代码)' }, disposition_summary: { type: 'array', items: { type: 'object', additionalProperties: false, properties: { disposition: { type: 'string', enum: DISPOSITION_ENUM }, count: { type: 'number' }, features: { type: 'array', items: { type: 'string' } } }, required: ['disposition', 'count', 'features'] }, }, checklist: { type: 'array', description: '处置清单:每行一个特性,给人拍板', items: { type: 'object', additionalProperties: false, properties: { feature: { type: 'string' }, status: { type: 'string', enum: STATE_ENUM }, completeness_pct: { type: 'number' }, expert_owner: { type: 'string', description: '历史专家(谁做的)' }, recommended_assignee: { type: 'string', description: '建议派给谁继续' }, top_gap: { type: 'string', description: '最关键的缺口/还差什么' }, recommended_action: { type: 'string', enum: DISPOSITION_ENUM }, decision_options: { type: 'array', items: { type: 'string' }, description: '给人勾选的几个选项(含负责人)' }, confidence: { type: 'number' }, }, required: ['feature', 'status', 'completeness_pct', 'expert_owner', 'recommended_assignee', 'top_gap', 'recommended_action', 'decision_options'], }, }, assignment_board: { type: 'array', description: '按人归集:每个历史专家适合接手哪些特性', items: { type: 'object', additionalProperties: false, properties: { person: { type: 'string' }, suggested_features: { type: 'array', items: { type: 'string' } }, why: { type: 'string' } }, required: ['person', 'suggested_features', 'why'] }, }, orphans_and_bus_factor: { type: 'array', description: '孤儿特性 / 单点依赖风险', items: { type: 'object', additionalProperties: false, properties: { feature: { type: 'string' }, issue: { type: 'string' }, suggestion: { type: 'string' } }, required: ['feature', 'issue', 'suggestion'] }, }, recommended_decision_order: { type: 'array', items: { type: 'string' }, description: '建议人类先决策哪些(按风险/杠杆排序)' }, notes: { type: 'array', items: { type: 'string' }, description: '接地说明:哪些强证据、git 归属的局限、对抗核验剔除了什么' }, }, required: ['executive_summary', 'feature_count', 'readiness_note', 'disposition_summary', 'checklist', 'assignment_board', 'orphans_and_bus_factor', 'recommended_decision_order', 'notes'], } // ---------- phase 1: survey ---------- phase('Survey') log(`扫描仓库(${root}),切分用户可感知的特性集群…`) const map = await agent( `你是资深产品架构考古员。仓库根目录:${root} 。\n` + `任务:通读仓库结构(ls / glob / grep / 读 README 与关键入口/路由/前端页面),切成 8-${maxFeatures} 个**用户可感知的特性**集群——` + `每个集群是一条可以被单独"保留 / 删除 / 指派给某人 / 补完"的功能线(例如某个诊断技能、某个工作台页面、某条 IM 接入、某个算法库、某个评审界面),而不是"后端/前端"这种技术分层。\n` + `对每个特性给出:key(kebab)、name、它在干什么的中文假设、3-8 个代表性 key_paths。\n` + `同时给出 project_name、一句话 what-it-is、推断的主要用户角色。${FOCUS_LINE}`, { schema: FEATURE_MAP_SCHEMA, label: 'survey', phase: 'Survey' } ) const features = ((map && map.features) ? map.features : []).slice(0, maxFeatures) log(`切出 ${features.length} 个特性:${features.map(c => c.name).join(' / ')}`) // ---------- phase 2-5: per-feature 4-stage pipeline (no barrier) ---------- // Excavate(逆向需求+文件半径) -> Verify(对抗核验现状) -> Blame(git 归属) -> Dispose(处置+决策选项) const dispositions = await pipeline( features, // stage 1: excavate + file radius + current status (feat) => agent( `你是需求考古员 + 现状勘察员,负责特性【${feat.name}】(${feat.key})。\n假设:${feat.hypothesis}\n` + `起点关键路径:${(feat.key_paths || []).join(', ')}\n\n` + `任务:**真正打开并逐段读**这些文件,并 grep 出调用链,划出这个特性的"代码文件探查半径":\n` + `1) files_core:真正实现该特性的核心文件(实读过);\n` + `2) files_radius:外圈相关文件——谁调用它(caller)、它调用谁(callee)、测试(test)、配置(config)、路由注册处(route)、前后端对位(frontend/backend)、schema/doc,各带一词关系标签;\n` + `3) domain_entities / explicit_requirements(带证据) / implicit_requirements(带为何推断+证据) / user_stories(As a/I want/so that+证据) / surface_feature;\n` + `4) current_status:判定该特性现在到底处于什么状态——\n` + ` - shipped(路由已注册+有消费方+能跑通) / partial(部分可用有明确缺口) / wip-unwired(有码但没接线:未注册/未被消费/无入口) / dead-code(孤儿,零调用零测试/路由 404) / experimental(实验区灰度) / unknown;\n` + ` - 必须给 evidence(路由注册与否、是否有消费方、测试、404 断言、import 链等 file:line),并分别列 whats_working 与 whats_missing_or_broken;\n` + `5) smells:过度设计 / 噱头 / 半成品 / 死代码迹象。\n` + `不要凭文件名猜——拿不出代码位置的状态判定要标 unknown。中文叙述,代码标识符保留英文。`, { schema: EXCAVATION_SCHEMA, label: `dig:${feat.key}`, phase: 'Excavate' } ), // stage 2: adversarial verify of the status claims (exc, feat) => agent( `你是对抗式核验员(怀疑一切),复核特性【${feat.name}】(${feat.key}) 的现状判定:\n` + JSON.stringify({ current_status: exc.current_status, user_stories: exc.user_stories, surface_feature: exc.surface_feature }, null, 2) + '\n\n' + `任务:回到真实代码核对。重点核验"状态"是否被高估——很多"看起来 shipped"的特性其实 wip-unwired(路由没注册/没人消费/没入口)或 dead-code。\n` + `给出 status_confirmed(可与挖掘不同,改了就在 corrected_status_note 说明)、grounded_points(确有支撑的要点)、ungrounded_claims(无支撑/过度解读,给理由)、confidence(0-1)。\n` + `默认怀疑:拿不出代码位置的"可用/已上线"断言一律降级。`, { schema: VERDICT_SCHEMA, label: `verify:${feat.key}`, phase: 'Verify' } ).then(v => ({ exc, verdict: v })), // stage 3: git archaeology / ownership (uses the file radius from stage 1) (acc, feat) => { const files = [ ...((acc.exc.files_core) || []), ...((acc.exc.files_radius) || []).map(x => x.path), ].filter(Boolean) const fileList = files.length ? files : (feat.key_paths || []) return agent( `你是 git 历史考古员,给特性【${feat.name}】(${feat.key}) 定"谁做的 / 谁是专家 / 该派给谁"。仓库根目录:${root} 。\n` + `这个特性的代码文件集合:\n${fileList.map(f => ' ' + f).join('\n')}\n\n` + `任务:用 Bash 跑 git 历史${SINCE_NOTE},**只针对上面这些路径**(路径可能含 glob/目录,先用 git ls-files 确认存在再统计)。建议命令:\n` + ` git -C ${root} log${SINCE_GIT} --no-merges --date=short --pretty='%h|%an|%ae|%ad|%s' -- \n` + ` git -C ${root} log${SINCE_GIT} --no-merges --pretty='%an <%aE>' -- | sort | uniq -c | sort -rn # 贡献者排名\n` + ` git -C ${root} log${SINCE_GIT} -1 --date=short --pretty='%ad' -- # 最近一次改动\n` + ` git -C ${root} log${SINCE_GIT} --reverse -1 --date=short --pretty='%ad' -- # 首次改动\n` + `据此产出:total_commits、first/last_commit_date、activity_trend(据最近提交距今:active/cooling/stale/abandoned)、` + `contributors(按提交数降序,含 name/email/commits/last_touch_date/role_guess)、` + `top_expert(谁最懂这块及为何)、recommended_assignee(该派给谁继续做+理由——优先历史上深度参与且最近仍活跃者)、` + `bus_factor(healthy/single-owner-risk/orphaned + note)。\n` + `若路径全不存在或无提交,如实填 0 / unknown,不要编造作者。中文叙述,人名/邮箱保留原文。`, { schema: GIT_SCHEMA, label: `blame:${feat.key}`, phase: 'Blame' } ).then(g => ({ ...acc, git: g })) }, // stage 4: disposition + human decision options (acc, feat) => agent( `你是技术负责人,给特性【${feat.name}】(${feat.key}) 下"处置建议",供人类拍板。\n\n` + `逆向需求 + 现状(挖掘):\n${JSON.stringify(acc.exc, null, 2)}\n\n` + `对抗核验:\n${JSON.stringify(acc.verdict, null, 2)}\n\n` + `git 归属:\n${JSON.stringify(acc.git, null, 2)}\n\n` + `任务:综合三者,产出该特性的处置卡:\n` + `- current_status 以核验后的 status_confirmed 为准;status_evidence 一句话带证据;completeness_pct 粗估完成度;\n` + `- what_else_to_do:要让它真正可用/收口还差哪些**具体活**(每条带 effort S/M/L 与 blocks_or_risk);\n` + `- expert_owner:历史专家(谁做的,带 git 证据);recommended_assignee:该派给谁继续(优先历史专家且最近活跃);\n` + `- recommended_disposition 五选一:KEEP_AS_IS(已上线维持) / FINISH_BUILD_TODOS(值得做,补完) / ASSIGN_TO_EXPERT(派回历史专家推进) / REMOVE_DEAD(死代码/噱头,删) / DECIDE_TRADEOFF(取舍要人定);\n` + `- human_decision_options:给人勾选的 2-4 个选项(动作/负责人/后果),必须真给出"删除""补完(列TODO)""指派给<具体人名>"这类可执行项;\n` + `- risk_if_ignored:不处置的代价。\n` + `诚实:dead-code 就建议删,不要为了好看说成 partial。` + PLAIN_LANG, { schema: DISPOSITION_SCHEMA, label: `dispose:${feat.key}`, phase: 'Dispose', effort: 'high' } ) ).then(rs => rs.filter(Boolean)) log(`完成 ${dispositions.length} 个特性的处置分析,合成处置清单…`) // ---------- phase 6: board synthesis ---------- phase('Board') const corpus = JSON.stringify({ project: map ? { name: map.project_name, what: map.one_line_what_it_is, users: map.primary_users } : {}, dispositions: dispositions.map(d => ({ feature: d.feature_name, key: d.feature_key, status: d.current_status, completeness: d.completeness_pct, expert: d.expert_owner, assignee: d.recommended_assignee, disposition: d.recommended_disposition, rationale: d.disposition_rationale, what_else: d.what_else_to_do, options: d.human_decision_options, risk: d.risk_if_ignored, confidence: d.confidence, })), }, null, 2) const final = await agent( `你是首席工程裁判。下面是逐特性的处置分析(已含逆向需求现状 + git 归属 + 建议处置):\n${corpus}\n\n` + `任务:合成一份**特性处置清单(给人拍板)**,并做对抗式自检:\n` + `1) checklist:每行一个特性——status / completeness_pct / expert_owner(谁做的) / recommended_assignee(派给谁) / top_gap(最关键缺口) / recommended_action(五种处置之一) / decision_options(给人勾的几项,含具体人名);\n` + `2) disposition_summary:按五种处置归类计数;\n` + `3) assignment_board:按人归集——每个历史专家适合接手哪些特性(serve "把活派回当年做它的人"这个诉求);\n` + `4) orphans_and_bus_factor:孤儿特性 / 单点依赖风险(无人维护或仅一人懂),给建议;\n` + `5) recommended_decision_order:建议人类**先**决策哪几个(按风险/杠杆排序,如先删死代码、先派孤儿);\n` + `6) readiness_note:整体一句话(多少已上线/半成品/死代码);executive_summary 用 CEO 语言;\n` + `7) notes:接地说明——哪些结论强接地、git 归属的局限(如改名/squash 丢历史)、对抗核验剔除了什么。\n` + `落不到代码/git 证据的断言一律删或降级。` + PLAIN_LANG + `\nexecutive_summary 与 readiness_note 尤其要像跟老板口头汇报:开门见山说"现在整体什么情况、最该先拍板哪几件",全程大白话。`, { schema: FINAL_SCHEMA, label: 'board', phase: 'Board', effort: 'high' } ) return { project: map ? { name: map.project_name, what: map.one_line_what_it_is, users: map.primary_users } : null, feature_count: features.length, analyzed: dispositions.length, final, per_feature: dispositions, }