checkpoint before checking out feat/message-hub
This commit is contained in:
716
docs/superpowers/plans/2026-07-25-autorodo-web.md
Normal file
716
docs/superpowers/plans/2026-07-25-autorodo-web.md
Normal file
@@ -0,0 +1,716 @@
|
||||
# AutoRDO Web 工作台 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 落地 Axhub 原型 `autorodo-web`:粘贴素材 → 复制 AutoRDO 清洗提示词 → 粘回初稿 → 按需求分页确认待确认并回填描述 → 填写优先级/标签/提交部门/提交人 → 一键复制 YunxiaoPMapp 多条记录需求提示词。
|
||||
|
||||
**Architecture:** 纯函数放在原型内 `lib/`(解析初稿、出题回填、拼提示词、识别部门/提交人),UI 为三步工作台(React + OneOS V2 Token)。MVP 不连云效、不嵌 OCR/ASR;Cursor 往返靠剪贴板。预留 `agentAdapter` 接口形状供组织 Agent。
|
||||
|
||||
**Tech Stack:** React + TypeScript(Axhub Make 原型)、Vitest、OneOS V2(`oneos-ds-tokens.css` + `UIComponents` 按需)、`PrototypeAnnotationHost`。
|
||||
|
||||
**Spec:** [docs/superpowers/specs/2026-07-25-autorodo-web-design.md](../specs/2026-07-25-autorodo-web-design.md)
|
||||
|
||||
---
|
||||
|
||||
## 文件结构(锁定)
|
||||
|
||||
| 路径 | 职责 |
|
||||
|------|------|
|
||||
| `src/prototypes/autorodo-web/lib/types.ts` | 需求稿、待确认点、元数据、步骤状态类型 |
|
||||
| `src/prototypes/autorodo-web/lib/parseDraft.ts` | 解析粘回的 AutoRDO Markdown → `RequirementDraft[]` |
|
||||
| `src/prototypes/autorodo-web/lib/parseDraft.test.ts` | 解析单测 |
|
||||
| `src/prototypes/autorodo-web/lib/applyAnswer.ts` | 选择题/补充 → 回填描述、消待确认 |
|
||||
| `src/prototypes/autorodo-web/lib/applyAnswer.test.ts` | 回填单测 |
|
||||
| `src/prototypes/autorodo-web/lib/buildCleanPrompt.ts` | 步骤 1 清洗提示词 |
|
||||
| `src/prototypes/autorodo-web/lib/buildRecordPrompt.ts` | 步骤 3 YunxiaoPMapp 多条口令 |
|
||||
| `src/prototypes/autorodo-web/lib/buildRecordPrompt.test.ts` | 上报词单测 |
|
||||
| `src/prototypes/autorodo-web/lib/inferMeta.ts` | 从原文推断提交部门/提交人 |
|
||||
| `src/prototypes/autorodo-web/lib/inferMeta.test.ts` | 推断单测 |
|
||||
| `src/prototypes/autorodo-web/lib/agentAdapter.ts` | 预留 `clean` / `record` 接口类型(MVP 空实现) |
|
||||
| `src/prototypes/autorodo-web/lib/clipboard.ts` | `navigator.clipboard.writeText` 封装 |
|
||||
| `src/prototypes/autorodo-web/lib/pendingQuestions.ts` | 从「待确认」文案生成选择题选项(词典摘要常量) |
|
||||
| `src/prototypes/autorodo-web/components/StepPaste.tsx` | 步骤 1 UI |
|
||||
| `src/prototypes/autorodo-web/components/StepConfirm.tsx` | 步骤 2 UI |
|
||||
| `src/prototypes/autorodo-web/components/StepExport.tsx` | 步骤 3 UI |
|
||||
| `src/prototypes/autorodo-web/AutorodoWebApp.tsx` | 三步状态机主壳 |
|
||||
| `src/prototypes/autorodo-web/index.tsx` | 入口 `@name` + AnnotationHost |
|
||||
| `src/prototypes/autorodo-web/style.css` | 布局与触控样式 |
|
||||
| `src/prototypes/autorodo-web/annotation-source.json` | 标注目录 |
|
||||
| `src/prototypes/autorodo-web/.spec/requirements-prd.md` | AutoPRD |
|
||||
| `src/prototypes/autorodo-web/.spec/confirm-flow.md` | 确认与回填规则全文 |
|
||||
| `src/resources/prd/autorodo-web-autoprd.md` | PRD 副本(若项目惯例需要) |
|
||||
| 修改 `.axhub/make/project.json` | 注册原型(若 Make 未自动发现) |
|
||||
| 修改 `~/.cursor/skills/YunxiaoPMapp` + 项目内副本 | 记录需求解析四字段(Task 8,可同迭代或紧随) |
|
||||
|
||||
**不放入 `src/common/`**:逻辑仅本原型使用,避免污染公共层。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 类型 + parseDraft + 单测
|
||||
|
||||
**Files:**
|
||||
- Create: `src/prototypes/autorodo-web/lib/types.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/parseDraft.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/parseDraft.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写入类型**
|
||||
|
||||
```ts
|
||||
// src/prototypes/autorodo-web/lib/types.ts
|
||||
export type Priority = '紧急' | '高' | '中' | '低';
|
||||
|
||||
export type RequirementMeta = {
|
||||
priority: Priority | '';
|
||||
tags: string[];
|
||||
submitDept: string;
|
||||
submitter: string;
|
||||
titleKind: '新增' | '优化' | '';
|
||||
};
|
||||
|
||||
export type PendingItem = {
|
||||
id: string;
|
||||
raw: string;
|
||||
question: string;
|
||||
options: string[];
|
||||
resolved: boolean;
|
||||
answer?: string;
|
||||
};
|
||||
|
||||
export type RequirementDraft = {
|
||||
index: number;
|
||||
title: string;
|
||||
description: string;
|
||||
pendings: PendingItem[];
|
||||
meta: RequirementMeta;
|
||||
confirmed: boolean;
|
||||
};
|
||||
|
||||
export type WizardStep = 1 | 2 | 3;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 写失败单测(多条 + 待确认)**
|
||||
|
||||
```ts
|
||||
// src/prototypes/autorodo-web/lib/parseDraft.test.ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseAutorodoDraft } from './parseDraft';
|
||||
|
||||
const SAMPLE = `共 2 条
|
||||
|
||||
### 1
|
||||
## 原始诉求(AutoRDO)
|
||||
|
||||
**标题**:合同台账批量导出
|
||||
|
||||
**描述**:
|
||||
在租赁合同台账支持批量导出
|
||||
|
||||
待确认:
|
||||
- 导出范围未说明
|
||||
|
||||
### 2
|
||||
## 原始诉求(AutoRDO)
|
||||
|
||||
**标题**:证照到期提醒
|
||||
|
||||
**描述**:
|
||||
调整证照到期提醒口径
|
||||
|
||||
待确认:
|
||||
无
|
||||
`;
|
||||
|
||||
describe('parseAutorodoDraft', () => {
|
||||
it('splits multiple requirements and pending items', () => {
|
||||
const list = parseAutorodoDraft(SAMPLE);
|
||||
expect(list).toHaveLength(2);
|
||||
expect(list[0].title).toContain('合同台账');
|
||||
expect(list[0].pendings).toHaveLength(1);
|
||||
expect(list[0].pendings[0].raw).toContain('导出范围');
|
||||
expect(list[1].pendings).toHaveLength(0);
|
||||
expect(list[1].confirmed).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 跑测确认失败**
|
||||
|
||||
Run: `npx vitest run src/prototypes/autorodo-web/lib/parseDraft.test.ts`
|
||||
Expected: FAIL(模块不存在或 `parseAutorodoDraft` 未定义)
|
||||
|
||||
- [ ] **Step 4: 实现解析**
|
||||
|
||||
```ts
|
||||
// src/prototypes/autorodo-web/lib/parseDraft.ts
|
||||
import type { RequirementDraft, PendingItem, RequirementMeta } from './types';
|
||||
|
||||
const emptyMeta = (): RequirementMeta => ({
|
||||
priority: '',
|
||||
tags: [],
|
||||
submitDept: '',
|
||||
submitter: '',
|
||||
titleKind: '',
|
||||
});
|
||||
|
||||
function parsePendings(block: string, reqIndex: number): PendingItem[] {
|
||||
const m = block.match(/待确认[::]\s*([\s\S]*?)(?=\n###|\n##\s*原始诉求|$)/);
|
||||
if (!m) return [];
|
||||
const body = m[1].trim();
|
||||
if (!body || body === '无') return [];
|
||||
const lines = body
|
||||
.split('\n')
|
||||
.map((l) => l.replace(/^[-*•\d.、)\s]+/, '').trim())
|
||||
.filter(Boolean);
|
||||
return lines.map((raw, i) => ({
|
||||
id: `r${reqIndex}-p${i}`,
|
||||
raw,
|
||||
question: raw,
|
||||
options: [],
|
||||
resolved: false,
|
||||
}));
|
||||
}
|
||||
|
||||
export function parseAutorodoDraft(md: string): RequirementDraft[] {
|
||||
const parts = md.split(/(?=^###\s*\d+)/m).filter((p) => /原始诉求(AutoRDO)/.test(p) || /\*\*标题\*\*/.test(p));
|
||||
const chunks =
|
||||
parts.length > 0
|
||||
? parts
|
||||
: md.includes('**标题**')
|
||||
? [md]
|
||||
: [];
|
||||
|
||||
return chunks.map((chunk, index) => {
|
||||
const title = (chunk.match(/\*\*标题\*\*[::]\s*(.+)/)?.[1] ?? '').trim();
|
||||
const descMatch = chunk.match(/\*\*描述\*\*[::]\s*\n?([\s\S]*?)(?=\n待确认|$)/);
|
||||
const description = (descMatch?.[1] ?? '').trim();
|
||||
const pendings = parsePendings(chunk, index);
|
||||
return {
|
||||
index,
|
||||
title,
|
||||
description,
|
||||
pendings,
|
||||
meta: emptyMeta(),
|
||||
confirmed: pendings.length === 0,
|
||||
};
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 跑测确认通过**
|
||||
|
||||
Run: `npx vitest run src/prototypes/autorodo-web/lib/parseDraft.test.ts`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 6: Commit**(仅当用户要求提交时执行;否则跳过所有 Commit 步骤)
|
||||
|
||||
```bash
|
||||
git add src/prototypes/autorodo-web/lib/types.ts src/prototypes/autorodo-web/lib/parseDraft.ts src/prototypes/autorodo-web/lib/parseDraft.test.ts
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(autorodo-web): add AutoRDO draft parser and types
|
||||
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: applyAnswer + pendingQuestions
|
||||
|
||||
**Files:**
|
||||
- Create: `src/prototypes/autorodo-web/lib/applyAnswer.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/applyAnswer.test.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/pendingQuestions.ts`
|
||||
|
||||
- [ ] **Step 1: 写回填单测**
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { applyPendingAnswer } from './applyAnswer';
|
||||
import type { RequirementDraft } from './types';
|
||||
|
||||
const base: RequirementDraft = {
|
||||
index: 0,
|
||||
title: '合同台账批量导出',
|
||||
description: '在租赁合同台账支持批量导出\n导出范围:待确认',
|
||||
pendings: [
|
||||
{
|
||||
id: 'r0-p0',
|
||||
raw: '导出范围未说明',
|
||||
question: '导出范围指什么?',
|
||||
options: ['当前筛选结果', '全部合同', '仅勾选行'],
|
||||
resolved: false,
|
||||
},
|
||||
],
|
||||
meta: { priority: '', tags: [], submitDept: '', submitter: '', titleKind: '' },
|
||||
confirmed: false,
|
||||
};
|
||||
|
||||
describe('applyPendingAnswer', () => {
|
||||
it('merges answer into description and marks pending resolved', () => {
|
||||
const next = applyPendingAnswer(base, 'r0-p0', '当前筛选结果');
|
||||
expect(next.pendings[0].resolved).toBe(true);
|
||||
expect(next.pendings[0].answer).toBe('当前筛选结果');
|
||||
expect(next.description).toContain('当前筛选结果');
|
||||
expect(next.description).not.toMatch(/导出范围:待确认/);
|
||||
expect(next.confirmed).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 跑测确认失败**
|
||||
|
||||
Run: `npx vitest run src/prototypes/autorodo-web/lib/applyAnswer.test.ts`
|
||||
Expected: FAIL
|
||||
|
||||
- [ ] **Step 3: 实现 applyAnswer + 简易出题**
|
||||
|
||||
```ts
|
||||
// applyAnswer.ts
|
||||
import type { RequirementDraft } from './types';
|
||||
|
||||
export function applyPendingAnswer(
|
||||
draft: RequirementDraft,
|
||||
pendingId: string,
|
||||
answer: string,
|
||||
): RequirementDraft {
|
||||
const pendings = draft.pendings.map((p) =>
|
||||
p.id === pendingId ? { ...p, resolved: true, answer: answer.trim() } : p,
|
||||
);
|
||||
const hit = draft.pendings.find((p) => p.id === pendingId);
|
||||
let description = draft.description;
|
||||
if (hit) {
|
||||
const label = hit.raw.replace(/未说明|待确认|不清楚/g, '').trim() || hit.raw;
|
||||
if (/待确认/.test(description)) {
|
||||
description = description.replace(/待确认/g, answer.trim());
|
||||
} else {
|
||||
description = `${description.replace(/\s+$/, '')}\n${label}:${answer.trim()}`;
|
||||
}
|
||||
}
|
||||
const confirmed = pendings.every((p) => p.resolved);
|
||||
return { ...draft, description, pendings, confirmed };
|
||||
}
|
||||
|
||||
// pendingQuestions.ts — MVP:关键词启发式;无匹配则「是 / 否 / 需补充」
|
||||
export function buildOptionsForPending(raw: string): { question: string; options: string[] } {
|
||||
if (/导出范围|范围/.test(raw)) {
|
||||
return {
|
||||
question: '「导出范围」指什么?',
|
||||
options: ['当前筛选结果', '全部数据', '仅勾选行'],
|
||||
};
|
||||
}
|
||||
if (/模块|归属/.test(raw)) {
|
||||
return {
|
||||
question: '归属哪个业务模块?',
|
||||
options: ['租赁合同管理', '车辆管理', '证照管理', '还车应结款'],
|
||||
};
|
||||
}
|
||||
return {
|
||||
question: raw.endsWith('?') || raw.endsWith('?') ? raw : `${raw}?`,
|
||||
options: ['确认按原文理解', '暂不纳入本需求', '需文字补充'],
|
||||
};
|
||||
}
|
||||
|
||||
export function enrichPendingsWithQuestions(draft: RequirementDraft): RequirementDraft {
|
||||
return {
|
||||
...draft,
|
||||
pendings: draft.pendings.map((p) => {
|
||||
const q = buildOptionsForPending(p.raw);
|
||||
return { ...p, question: q.question, options: q.options };
|
||||
}),
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
在 `parseAutorodoDraft` 返回前对每条调用 `enrichPendingsWithQuestions`,或在 UI 粘回后统一 enrich(二选一,推荐 UI 粘回后调用,保持 parse 纯净)。
|
||||
|
||||
- [ ] **Step 4: 跑测确认通过**
|
||||
|
||||
Run: `npx vitest run src/prototypes/autorodo-web/lib/applyAnswer.test.ts`
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 5: Commit**(用户要求时)
|
||||
|
||||
---
|
||||
|
||||
### Task 3: inferMeta + buildCleanPrompt + buildRecordPrompt
|
||||
|
||||
**Files:**
|
||||
- Create: `src/prototypes/autorodo-web/lib/inferMeta.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/inferMeta.test.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/buildCleanPrompt.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/buildRecordPrompt.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/buildRecordPrompt.test.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/clipboard.ts`
|
||||
- Create: `src/prototypes/autorodo-web/lib/agentAdapter.ts`
|
||||
|
||||
- [ ] **Step 1: inferMeta 单测**
|
||||
|
||||
```ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { inferSubmitMeta } from './inferMeta';
|
||||
|
||||
describe('inferSubmitMeta', () => {
|
||||
it('detects dept and person from free text', () => {
|
||||
const r = inferSubmitMeta('业务管理组张三反馈:合同导出不好用');
|
||||
expect(r.submitDept).toMatch(/业务/);
|
||||
expect(r.submitter).toContain('张三');
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 实现 inferMeta**
|
||||
|
||||
```ts
|
||||
const DEPT_PATTERNS = [
|
||||
/(?:提交部门|部门)[::]\s*([^\s,,;;]+)/,
|
||||
/(业务管理组|产品部|安全部|运营部|财务部|技术部)/,
|
||||
];
|
||||
const PERSON_PATTERNS = [
|
||||
/(?:提交人|反馈人|提出人)[::]\s*([^\s,,;;]+)/,
|
||||
/@([\u4e00-\u9fa5]{2,4})/,
|
||||
/([\u4e00-\u9fa5]{2,4})反馈/,
|
||||
];
|
||||
|
||||
export function inferSubmitMeta(text: string): { submitDept: string; submitter: string } {
|
||||
let submitDept = '';
|
||||
let submitter = '';
|
||||
for (const re of DEPT_PATTERNS) {
|
||||
const m = text.match(re);
|
||||
if (m?.[1]) {
|
||||
submitDept = m[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const re of PERSON_PATTERNS) {
|
||||
const m = text.match(re);
|
||||
if (m?.[1]) {
|
||||
submitter = m[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { submitDept, submitter };
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: buildRecordPrompt 单测 + 实现**
|
||||
|
||||
```ts
|
||||
// buildRecordPrompt.test.ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { buildYunxiaoRecordPrompt } from './buildRecordPrompt';
|
||||
import type { RequirementDraft } from './types';
|
||||
|
||||
const one: RequirementDraft = {
|
||||
index: 0,
|
||||
title: '合同台账批量导出',
|
||||
description: '在租赁合同台账支持按筛选结果批量导出 Excel',
|
||||
pendings: [],
|
||||
meta: {
|
||||
priority: '中',
|
||||
tags: ['租赁合同'],
|
||||
submitDept: '产品部',
|
||||
submitter: '王冕',
|
||||
titleKind: '新增',
|
||||
},
|
||||
confirmed: true,
|
||||
};
|
||||
|
||||
describe('buildYunxiaoRecordPrompt', () => {
|
||||
it('includes priority tags dept submitter', () => {
|
||||
const text = buildYunxiaoRecordPrompt([one]);
|
||||
expect(text).toContain('YunxiaoPMapp');
|
||||
expect(text).toContain('记录需求:【新增】合同台账批量导出');
|
||||
expect(text).toContain('优先级=中');
|
||||
expect(text).toContain('标签=租赁合同');
|
||||
expect(text).toContain('提交部门=产品部');
|
||||
expect(text).toContain('提交人=王冕');
|
||||
expect(text).toContain('推进至=暂不推进');
|
||||
});
|
||||
|
||||
it('throws when meta incomplete', () => {
|
||||
expect(() =>
|
||||
buildYunxiaoRecordPrompt([{ ...one, meta: { ...one.meta, submitter: '' } }]),
|
||||
).toThrow(/提交人/);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```ts
|
||||
// buildRecordPrompt.ts
|
||||
import type { RequirementDraft } from './types';
|
||||
|
||||
export function metaComplete(d: RequirementDraft): boolean {
|
||||
const m = d.meta;
|
||||
return Boolean(
|
||||
m.priority && m.tags.length > 0 && m.submitDept.trim() && m.submitter.trim() && m.titleKind,
|
||||
);
|
||||
}
|
||||
|
||||
export function buildYunxiaoRecordPrompt(drafts: RequirementDraft[]): string {
|
||||
for (const d of drafts) {
|
||||
if (!d.confirmed) throw new Error(`需求 ${d.index + 1} 仍有未确认项`);
|
||||
if (!metaComplete(d)) throw new Error(`需求 ${d.index + 1} 缺少优先级/标签/提交部门/提交人/类型`);
|
||||
}
|
||||
const lines = drafts.map((d) => {
|
||||
const title = `【${d.meta.titleKind}】${d.title.replace(/^【(?:新增|优化)】/, '')}`;
|
||||
const tags = d.meta.tags.join('、');
|
||||
return `记录需求:${title};描述=${d.description.replace(/\n/g, ' ')};优先级=${d.meta.priority};标签=${tags};提交部门=${d.meta.submitDept};提交人=${d.meta.submitter};推进至=暂不推进`;
|
||||
});
|
||||
return [
|
||||
'YunxiaoPMapp',
|
||||
'请按下列条目依次记录需求;每条保持独立,不要合并。',
|
||||
'',
|
||||
...lines,
|
||||
].join('\n');
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// buildCleanPrompt.ts
|
||||
export function buildCleanPrompt(payload: {
|
||||
text: string;
|
||||
attachmentNames: string[];
|
||||
}): string {
|
||||
const files =
|
||||
payload.attachmentNames.length > 0
|
||||
? `附件:${payload.attachmentNames.join('、')}(请用多模态识读图/语音内容)`
|
||||
: '附件:无';
|
||||
return [
|
||||
'AutoRDO',
|
||||
'请按 AutoRDO 规则清洗下列素材:多条独立诉求拆成多份;提炼标题与书面描述;不确定处标「待确认」;描述无结尾句号。',
|
||||
files,
|
||||
'',
|
||||
'—— 素材开始 ——',
|
||||
payload.text.trim() || '(仅附件,无正文)',
|
||||
'—— 素材结束 ——',
|
||||
].join('\n');
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// clipboard.ts
|
||||
export async function copyText(text: string): Promise<void> {
|
||||
await navigator.clipboard.writeText(text);
|
||||
}
|
||||
|
||||
// agentAdapter.ts
|
||||
export type CleanRequest = { text: string; attachmentNames: string[] };
|
||||
export type RecordRequest = { drafts: import('./types').RequirementDraft[] };
|
||||
|
||||
/** MVP:未接组织 Agent;后续替换实现即可 */
|
||||
export const agentAdapter = {
|
||||
async clean(_req: CleanRequest): Promise<null> {
|
||||
return null;
|
||||
},
|
||||
async record(_req: RecordRequest): Promise<null> {
|
||||
return null;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 跑全部 lib 单测**
|
||||
|
||||
Run: `npx vitest run src/prototypes/autorodo-web/lib`
|
||||
Expected: 全部 PASS
|
||||
|
||||
- [ ] **Step 5: Commit**(用户要求时)
|
||||
|
||||
---
|
||||
|
||||
### Task 4: 原型壳 + 步骤 1 UI
|
||||
|
||||
**Files:**
|
||||
- Create: `src/prototypes/autorodo-web/index.tsx`
|
||||
- Create: `src/prototypes/autorodo-web/AutorodoWebApp.tsx`
|
||||
- Create: `src/prototypes/autorodo-web/style.css`
|
||||
- Create: `src/prototypes/autorodo-web/components/StepPaste.tsx`
|
||||
- Create: `src/prototypes/autorodo-web/annotation-source.json`(先最小目录)
|
||||
- Modify: `.axhub/make/project.json`(若启动后未出现在列表则补注册)
|
||||
|
||||
前置:Read `src/resources/design-system/DESIGN.md`(Token、触控 ≥44px、禁止原生 select)。
|
||||
|
||||
- [ ] **Step 1: 入口与主壳骨架**
|
||||
|
||||
```tsx
|
||||
// index.tsx
|
||||
/**
|
||||
* @name AutoRDO 需求清洗工作台
|
||||
*/
|
||||
import './style.css';
|
||||
import '../../../resources/design-system/oneos-ds-tokens.css';
|
||||
import React from 'react';
|
||||
import { PrototypeAnnotationHost } from '../../common/prototype-annotation-host';
|
||||
import type { AnnotationSourceDocument } from '@axhub/annotation';
|
||||
import annotationSourceDocument from './annotation-source.json';
|
||||
import { AutorodoWebApp } from './AutorodoWebApp';
|
||||
|
||||
export default function AutorodoWebEntry() {
|
||||
return (
|
||||
<>
|
||||
<AutorodoWebApp />
|
||||
<PrototypeAnnotationHost
|
||||
source={annotationSourceDocument as AnnotationSourceDocument}
|
||||
options={{}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
`AutorodoWebApp`:左侧步骤 1/2/3;`step` state;步骤 1 渲染 `StepPaste`。
|
||||
|
||||
- [ ] **Step 2: StepPaste**
|
||||
|
||||
- contentEditable 或 textarea 接收粘贴;`onPaste` 收集 `clipboardData.files`(image/*、audio/*)为附件名列表(原型阶段可用 File.name,不上传)
|
||||
- 按钮「确认并复制清洗提示词」→ `buildCleanPrompt` + `copyText`;toast「已复制,请粘到 Cursor 跑 AutoRDO」
|
||||
- 可选 checkbox「粘贴后自动复制」(默认关)
|
||||
- 控件:优先 `V2` 封装;按钮 min-height 44px
|
||||
|
||||
- [ ] **Step 3: 本地预览**
|
||||
|
||||
Run: 打开 Make 预览 `/prototypes/autorodo-web`
|
||||
Expected: 能粘贴、点确认后剪贴板为 AutoRDO 提示词
|
||||
|
||||
- [ ] **Step 4: Commit**(用户要求时)
|
||||
|
||||
---
|
||||
|
||||
### Task 5: 步骤 2 确认 UI
|
||||
|
||||
**Files:**
|
||||
- Create: `src/prototypes/autorodo-web/components/StepConfirm.tsx`
|
||||
- Modify: `src/prototypes/autorodo-web/AutorodoWebApp.tsx`
|
||||
|
||||
- [ ] **Step 1: 粘回区 + 解析**
|
||||
|
||||
- textarea「粘回 AutoRDO 初稿」
|
||||
- 按钮「解析初稿」→ `parseAutorodoDraft` → `enrichPendingsWithQuestions` → 对每条 `inferSubmitMeta(description + title)` 预填 meta
|
||||
- Pill 切换需求 index
|
||||
|
||||
- [ ] **Step 2: 左右分栏确认**
|
||||
|
||||
- 左:标题(可编辑)+ 描述(只读展示实时稿)
|
||||
- 右:当前未解决 pending;单选 options;补充 input;「确认并下一题」调用 `applyPendingAnswer`
|
||||
- 元数据:优先级四选一;标签用可增删 chip(自由输入 MVP);提交部门/提交人 input;类型 新增/优化
|
||||
- 全部 confirmed 且 `metaComplete` 后可进步骤 3
|
||||
|
||||
- [ ] **Step 3: 手动验收路径**
|
||||
|
||||
1. 粘贴 Task1 SAMPLE 初稿
|
||||
2. 答完题后描述含答案且无「待确认」
|
||||
3. 填齐元数据
|
||||
|
||||
- [ ] **Step 4: Commit**(用户要求时)
|
||||
|
||||
---
|
||||
|
||||
### Task 6: 步骤 3 导出 + 样式打磨
|
||||
|
||||
**Files:**
|
||||
- Create: `src/prototypes/autorodo-web/components/StepExport.tsx`
|
||||
- Modify: `style.css`、`AutorodoWebApp.tsx`
|
||||
|
||||
- [ ] **Step 1: StepExport**
|
||||
|
||||
- 预览 `buildYunxiaoRecordPrompt(drafts)`
|
||||
- 「一键复制上报提示词」
|
||||
- 「复制定稿 Markdown」(输出多份已确认 `## 原始诉求(AutoRDO)`,待确认:无)
|
||||
- 错误态:捕获 throw,页面内提示缺哪条哪字段
|
||||
|
||||
- [ ] **Step 2: 响应式**
|
||||
|
||||
- ≤767px:步骤条顶置;确认区改为上下堆叠;触控 ≥44px;正文 ≥14px
|
||||
|
||||
- [ ] **Step 3: 预览验收**
|
||||
|
||||
全路径走通:粘贴 → 复制清洗词 →(模拟)粘回 SAMPLE → 确认 → 复制上报词,粘贴到文本编辑器检查字段齐全。
|
||||
|
||||
- [ ] **Step 4: Commit**(用户要求时)
|
||||
|
||||
---
|
||||
|
||||
### Task 7: AutoPRD + 标注 + 导航同步
|
||||
|
||||
**Files:**
|
||||
- Create: `src/prototypes/autorodo-web/.spec/confirm-flow.md`
|
||||
- Create: `src/prototypes/autorodo-web/.spec/requirements-prd.md`
|
||||
- Create: `src/resources/prd/autorodo-web-autoprd.md`(与 PRD 同步)
|
||||
- Modify: `annotation-source.json`(产品需求说明节点)
|
||||
|
||||
- [ ] **Step 1: 写 confirm-flow.md**
|
||||
|
||||
含:判定顺序表、前置条件、数据源(粘回 Markdown / 本地启发式选项)、用户可见结果、标明未接真 API。
|
||||
|
||||
- [ ] **Step 2: 写 requirements-prd.md**
|
||||
|
||||
按 oneos-autoprd 结构:总览/目标/边界/角色/用户故事(起点→运作→闭环)/验收;链到 `confirm-flow.md`。
|
||||
|
||||
- [ ] **Step 3: 同步标注与导航**
|
||||
|
||||
```bash
|
||||
npm run nav:sync -- --prototype autorodo-web --note "新增 AutoRDO 网页清洗与云效上报提示词工作台"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**(用户要求时)
|
||||
|
||||
---
|
||||
|
||||
### Task 8: YunxiaoPMapp 消费四字段(Skill 扩展)
|
||||
|
||||
**Files:**
|
||||
- Modify: `~/.claude/skills/YunxiaoPMapp/references/commands.md`(及项目 `.cursor` / `.claude` 同步副本)
|
||||
- Modify: YunxiaoPMapp `SKILL.md` 记录需求参数说明
|
||||
- 可选:`references/record-meta-fields.md` 新文件
|
||||
|
||||
- [ ] **Step 1: 文档约定口令字段**
|
||||
|
||||
在口令面增加:
|
||||
|
||||
```text
|
||||
记录需求:…;优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=…
|
||||
```
|
||||
|
||||
说明:网页上报词已带齐时,**禁止**再 Plan 重复问这四项(除非缺省);缺省则仍问。
|
||||
|
||||
- [ ] **Step 2: 实现侧**
|
||||
|
||||
若现有 live 脚本已支持 priority/tag:把提交部门/提交人映射到云效字段(查 `runtime-ids.json`;未验证字段则先写入描述尾部 `提交部门/提交人`,并在文档标明「字段 ID 待验证」)。
|
||||
|
||||
- [ ] **Step 3: 与网页联调**
|
||||
|
||||
用步骤 3 复制出的提示词在 Cursor 跑一次「记录需求」(测试项目或 dry-run 策略按团队惯例)。
|
||||
|
||||
- [ ] **Step 4: Commit**(用户要求时)
|
||||
|
||||
---
|
||||
|
||||
## 规格覆盖自检
|
||||
|
||||
| Spec 要求 | Task |
|
||||
|-----------|------|
|
||||
| 粘贴文/图/语音 + 复制清洗词 | 4 |
|
||||
| 粘回初稿 + 分页确认 + 回填 | 1,2,5 |
|
||||
| 优先级/标签/部门/提交人 | 3,5,6 |
|
||||
| 一键 Yunxiao 多条口令 | 3,6 |
|
||||
| 预留组织 Agent | 3 `agentAdapter` |
|
||||
| AutoPRD / 业务规则文档 | 7 |
|
||||
| Yunxiao 消费四字段 | 8 |
|
||||
| 不直连云效 / 无 OCR | 全程遵守 |
|
||||
|
||||
---
|
||||
|
||||
## 执行说明
|
||||
|
||||
- 所有 **Commit 步骤默认跳过**,除非用户明确要求提交。
|
||||
- Task 8 可与 Task 4–7 并行由另一会话做;网页 MVP 以 Task 1–7 为可演示闭环(上报词已含四字段,即使 Skill 暂未写入云效自定义字段,口令仍可用于人工/后续 Skill)。
|
||||
|
||||
---
|
||||
|
||||
Plan complete and saved to `docs/superpowers/plans/2026-07-25-autorodo-web.md`.
|
||||
|
||||
**两种执行方式:**
|
||||
|
||||
1. **Subagent-Driven(推荐)** — 每任务开新子代理,任务间复核,迭代快
|
||||
2. **Inline Execution** — 本会话按 executing-plans 连续执行,设检查点
|
||||
|
||||
要哪一种?
|
||||
168
docs/superpowers/specs/2026-07-25-autorodo-web-design.md
Normal file
168
docs/superpowers/specs/2026-07-25-autorodo-web-design.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# AutoRDO Web 工作台 · 设计规格
|
||||
|
||||
**日期**:2026-07-25
|
||||
**状态**:已确认(2026-07-25)
|
||||
**范围**:网页粘贴与确认交互 + AutoRDO Skill 对齐 + YunxiaoPMapp 多条上报提示词生成
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标
|
||||
|
||||
为产品经理提供网页工作台:直接粘贴**文本 / 图片 / 语音**,经 Cursor + AutoRDO 清洗后,在网页内对待确认项做选择题确认,答案自动并入需求描述,最后**一键复制**可喂给 Cursor + **YunxiaoPMapp** 的多条「记录需求」提示词,实现快速批量建单。
|
||||
|
||||
不替代 AutoRDO / YunxiaoPMapp Skill;网页负责编排与确认交互,清洗与写云效仍由 Cursor Agent 执行(MVP)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 已确认决策
|
||||
|
||||
| 项 | 决策 |
|
||||
|---|---|
|
||||
| 形态 | 网页 + Skill **双轨** |
|
||||
| Agent | MVP = **Cursor Agent**;预留组织自建 Agent 接口 |
|
||||
| 多模态 | 网页粘贴展示;图/语音识读由 **Cursor 多模态**完成 |
|
||||
| 清洗往返 | Cursor 洗完后,把带「待确认」的初稿 **粘回网页** 再出题 |
|
||||
| 确认结构 | 按**需求条**分页;条内**逐点**选择题;选项外可输入补充 |
|
||||
| 回填 | 每确认一点 → **自动写入**该条描述,消掉对应待确认 |
|
||||
| 云效 | **不**直连云效;一键复制上报提示词 → Cursor 调 YunxiaoPMapp |
|
||||
| 元数据 | 每条含 **优先级、标签、提交部门、提交人**(部门/人可内容预填,可改) |
|
||||
|
||||
---
|
||||
|
||||
## 3. 端到端流程
|
||||
|
||||
```text
|
||||
① 网页文本域粘贴 文/图/语音(支持多行、编号列表、表格行、空行分段)
|
||||
↓ 确认(可配置为粘贴后自动)
|
||||
② 剪贴板 = AutoRDO 清洗提示词 → 粘到 Cursor 跑 AutoRDO
|
||||
↓
|
||||
③ 将初稿(含待确认)粘回网页步骤 2
|
||||
↓
|
||||
④ 按需求 1/N 分页;条内逐点选择题 + 可选补充输入
|
||||
→ 答案实时并入描述;填写优先级/标签/提交部门/提交人
|
||||
↓ 全部确认且元数据齐全
|
||||
⑤ 一键复制「YunxiaoPMapp 多条记录需求」提示词
|
||||
↓
|
||||
⑥ 粘到 Cursor → YunxiaoPMapp 按条创建多条云效需求
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 信息架构(三步工作台)
|
||||
|
||||
### 步骤 1 · 粘贴素材
|
||||
|
||||
- 统一粘贴区:文本 + 图片/语音附件芯片预览
|
||||
- 主操作:「确认并复制清洗提示词」(可选:粘贴后自动复制)
|
||||
- 清洗提示词须明示:调用 AutoRDO、多条拆解规则、图/语音由模型识读
|
||||
|
||||
### 步骤 2 · 确认待确认
|
||||
|
||||
- 粘回区:粘贴 Cursor 返回的 `## 原始诉求(AutoRDO)` 多份稿
|
||||
- 解析为需求列表;顶部 Pill:需求 1/N
|
||||
- 左:当前标题 + 描述(确认后实时回填高亮)
|
||||
- 右:当前待确认题(单选)+「选项不含时自行补充」输入框
|
||||
- 同页元数据(每条必填):
|
||||
- 优先级:紧急 / 高 / 中 / 低
|
||||
- 标签:云效模块标签(可多选;与 YunxiaoPMapp 标签口径对齐)
|
||||
- 提交部门:输入;若原文可识别则预填
|
||||
- 提交人:输入;若原文可识别则预填
|
||||
- 缺任一元数据 → 禁止进入步骤 3 的一键复制
|
||||
|
||||
### 步骤 3 · 上报云效
|
||||
|
||||
- 展示已生成的上报提示词预览
|
||||
- 主按钮:一键复制上报提示词
|
||||
- 次按钮:复制定稿 Markdown(可选)
|
||||
- 默认 `推进至=暂不推进`
|
||||
|
||||
---
|
||||
|
||||
## 5. 提示词契约
|
||||
|
||||
### 5.1 清洗提示词(步骤 1 → Cursor)
|
||||
|
||||
要点:
|
||||
|
||||
- 点名使用 `$AutoRDO`
|
||||
- 输入含粘贴的文本摘要 + 附件说明(图/语音文件名)
|
||||
- 要求:多条独立诉求拆成多份;不确定标「待确认」;标题+描述;无结尾句号
|
||||
- 输出格式对齐 AutoRDO Skill 的 `## 原始诉求(AutoRDO)`
|
||||
|
||||
### 5.2 上报提示词(步骤 3 → Cursor)
|
||||
|
||||
```text
|
||||
YunxiaoPMapp
|
||||
请按下列条目依次记录需求;每条保持独立,不要合并。
|
||||
|
||||
记录需求:【新增|优化】{标题};描述={已确认描述};优先级={紧急|高|中|低};标签={标签};提交部门={部门};提交人={提交人};推进至=暂不推进
|
||||
|
||||
记录需求:…(下一条)
|
||||
```
|
||||
|
||||
约束:
|
||||
|
||||
- 一条需求一行(或一块)口令,便于 Skill 拆条
|
||||
- 描述已无「待确认」、无结尾句号
|
||||
- 标题含 `【新增】` / `【优化】`(确认阶段或清洗结果已定)
|
||||
- 网页不写云效;由 YunxiaoPMapp 在 Cursor 侧执行建单
|
||||
|
||||
> 注:当前 YunxiaoPMapp 口令面以标题/描述/推进至为主;网页侧**先行产出**优先级/标签/提交部门/提交人字段,便于 Skill 后续消费。若 Skill 尚未落这四字段的写入逻辑,实现阶段需同步扩展 YunxiaoPMapp「记录需求」解析(另项)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 与现有 Skill 对齐
|
||||
|
||||
| 能力 | 来源 | 网页职责 |
|
||||
|---|---|---|
|
||||
| 多条拆解、标题描述、待确认 | AutoRDO | 解析初稿、出题、回填 |
|
||||
| 选择题确认 | AutoRDO `confirm-pending` | 在网页复现同等交互,替代 Cursor Plan 点选 |
|
||||
| 模块/部门词典 | AutoRDO `oneos-domain` | 生成选项与标签候选时参照(实现时可内嵌摘要或提示词约束) |
|
||||
| 建单写云效 | YunxiaoPMapp | 仅生成口令;不直连 API |
|
||||
|
||||
预留接口(组织 Agent 阶段):
|
||||
|
||||
- `POST /clean` ← 素材 → 初稿(替代步骤 ② 人工往返)
|
||||
- `POST /record` ← 已确认多条 → 建单(替代步骤 ⑥)
|
||||
|
||||
MVP 不实现上述 API,只在代码边界留 adapter 形状。
|
||||
|
||||
---
|
||||
|
||||
## 7. 交付物(实现阶段)
|
||||
|
||||
1. Axhub 原型页(建议 id:`autorodo-web` 或等价命名)
|
||||
- OneOS V2 设计规范;PC + H5
|
||||
- 三步工作台 + 粘贴往返 + 确认 + 元数据 + 复制口令
|
||||
2. AutoRDO Skill:可选补充「网页往返」口令说明(不改变清洗规则)
|
||||
3. YunxiaoPMapp:扩展记录需求解析以消费 `优先级/标签/提交部门/提交人`(若尚未支持)
|
||||
4. 本规格对应实现计划(writing-plans)
|
||||
|
||||
---
|
||||
|
||||
## 8. 非目标(MVP)
|
||||
|
||||
- 网页直连云效 API
|
||||
- 网页内嵌 OCR/ASR
|
||||
- localhost 桥接 Cursor(自动少粘贴)
|
||||
- 完整 AutoPRD 十章生成
|
||||
|
||||
---
|
||||
|
||||
## 9. 验收要点
|
||||
|
||||
1. 粘贴多行/编号/表格/空行分段素材后,能复制出可用的 AutoRDO 清洗提示词
|
||||
2. 粘回含多条「待确认」的初稿后,按需求分页、条内逐题确认,描述实时更新
|
||||
3. 选项外补充可替换本题答案并回填描述
|
||||
4. 每条可填/预填提交部门、提交人,以及优先级、标签;缺一不可复制上报词
|
||||
5. 一键复制的上报提示词可直接粘到 Cursor,驱动 YunxiaoPMapp 多条建单(在 Skill 已支持对应字段的前提下)
|
||||
6. 页面不发起云效写操作
|
||||
|
||||
---
|
||||
|
||||
## 10. 开放项(实现前可再定)
|
||||
|
||||
- 原型正式目录名 / 导航挂载位置
|
||||
- 标签候选项是写死 catalog 摘要,还是自由输入 + Skill 侧校验
|
||||
- 「粘贴后自动复制清洗提示词」默认开或关
|
||||
- YunxiaoPMapp 四字段写入是否与本原型同迭代交付
|
||||
Reference in New Issue
Block a user