prd-workflow-cli 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/workflows/prd-b1-planning-draft.md +45 -0
- package/.agent/workflows/prd-b2-planning-breakdown.md +53 -0
- package/.agent/workflows/prd-c1-requirement-list.md +63 -0
- package/.agent/workflows/prd-p0-project-info.md +45 -0
- package/.agent/workflows/prd-r1-review.md +45 -0
- package/.agent/workflows/prd-r2-review.md +43 -0
- package/.antigravity/rules.md +14 -0
- package/.cursorrules +26 -0
- package/bin/prd-cli.js +21 -0
- package/commands/check.js +509 -0
- package/commands/stats.js +192 -0
- package/docs/RULE-SYSTEM-ROADMAP.md +286 -0
- package/package.json +3 -1
- package/rules/index.json +614 -0
- package/rules/schemas/a2ui.schema.json +545 -0
- package/rules/schemas/rules.schema.json +221 -0
- package/scripts/inject-rules.js +167 -0
- package/templates/a2ui-standalone.html +3 -3
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "PRD Rules Index",
|
|
4
|
+
"description": "PRD-CLI 规则索引的 JSON Schema",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": [
|
|
7
|
+
"version",
|
|
8
|
+
"rules"
|
|
9
|
+
],
|
|
10
|
+
"properties": {
|
|
11
|
+
"$schema": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
"version": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
17
|
+
},
|
|
18
|
+
"lastUpdated": {
|
|
19
|
+
"type": "string",
|
|
20
|
+
"format": "date"
|
|
21
|
+
},
|
|
22
|
+
"categories": {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"additionalProperties": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"name": {
|
|
28
|
+
"type": "string"
|
|
29
|
+
},
|
|
30
|
+
"nameEn": {
|
|
31
|
+
"type": "string"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": [
|
|
35
|
+
"name"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"rules": {
|
|
40
|
+
"type": "array",
|
|
41
|
+
"items": {
|
|
42
|
+
"$ref": "#/definitions/rule"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"validators": {
|
|
46
|
+
"type": "object",
|
|
47
|
+
"additionalProperties": {
|
|
48
|
+
"$ref": "#/definitions/validator"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"scopeMapping": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"additionalProperties": {
|
|
54
|
+
"type": "string"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"definitions": {
|
|
59
|
+
"rule": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"required": [
|
|
62
|
+
"id",
|
|
63
|
+
"category",
|
|
64
|
+
"severity",
|
|
65
|
+
"scope",
|
|
66
|
+
"description",
|
|
67
|
+
"validatorType"
|
|
68
|
+
],
|
|
69
|
+
"properties": {
|
|
70
|
+
"id": {
|
|
71
|
+
"type": "string",
|
|
72
|
+
"pattern": "^[A-Z]\\d{3}$",
|
|
73
|
+
"description": "规则 ID,格式为 X000"
|
|
74
|
+
},
|
|
75
|
+
"category": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"enum": [
|
|
78
|
+
"G",
|
|
79
|
+
"D",
|
|
80
|
+
"F",
|
|
81
|
+
"S",
|
|
82
|
+
"V",
|
|
83
|
+
"I",
|
|
84
|
+
"U",
|
|
85
|
+
"C",
|
|
86
|
+
"W"
|
|
87
|
+
],
|
|
88
|
+
"description": "规则分类"
|
|
89
|
+
},
|
|
90
|
+
"severity": {
|
|
91
|
+
"type": "string",
|
|
92
|
+
"enum": [
|
|
93
|
+
"CRITICAL",
|
|
94
|
+
"HIGH",
|
|
95
|
+
"MEDIUM",
|
|
96
|
+
"LOW"
|
|
97
|
+
],
|
|
98
|
+
"description": "严重程度"
|
|
99
|
+
},
|
|
100
|
+
"scope": {
|
|
101
|
+
"type": "array",
|
|
102
|
+
"items": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"enum": [
|
|
105
|
+
"global",
|
|
106
|
+
"p0",
|
|
107
|
+
"b1",
|
|
108
|
+
"b2",
|
|
109
|
+
"plan",
|
|
110
|
+
"c0",
|
|
111
|
+
"c1",
|
|
112
|
+
"version",
|
|
113
|
+
"r1",
|
|
114
|
+
"r2",
|
|
115
|
+
"iteration",
|
|
116
|
+
"a2ui"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
"description": "适用范围"
|
|
120
|
+
},
|
|
121
|
+
"description": {
|
|
122
|
+
"type": "string",
|
|
123
|
+
"description": "规则描述"
|
|
124
|
+
},
|
|
125
|
+
"validatorType": {
|
|
126
|
+
"type": "string",
|
|
127
|
+
"enum": [
|
|
128
|
+
"program",
|
|
129
|
+
"ai-self-check",
|
|
130
|
+
"hybrid"
|
|
131
|
+
],
|
|
132
|
+
"description": "校验类型"
|
|
133
|
+
},
|
|
134
|
+
"validator": {
|
|
135
|
+
"type": [
|
|
136
|
+
"string",
|
|
137
|
+
"null"
|
|
138
|
+
],
|
|
139
|
+
"description": "校验器名称"
|
|
140
|
+
},
|
|
141
|
+
"checkLogic": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"description": "校验逻辑说明"
|
|
144
|
+
},
|
|
145
|
+
"examples": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"correct": {
|
|
149
|
+
"type": "string"
|
|
150
|
+
},
|
|
151
|
+
"wrong": {
|
|
152
|
+
"type": "string"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
"triggers": {
|
|
157
|
+
"type": "array",
|
|
158
|
+
"items": {
|
|
159
|
+
"type": "string"
|
|
160
|
+
},
|
|
161
|
+
"description": "触发关键词"
|
|
162
|
+
},
|
|
163
|
+
"savePoints": {
|
|
164
|
+
"type": "array",
|
|
165
|
+
"items": {
|
|
166
|
+
"type": "string"
|
|
167
|
+
},
|
|
168
|
+
"description": "保存触发点"
|
|
169
|
+
},
|
|
170
|
+
"questionTemplates": {
|
|
171
|
+
"type": "array",
|
|
172
|
+
"items": {
|
|
173
|
+
"type": "string"
|
|
174
|
+
},
|
|
175
|
+
"description": "质疑问题模板"
|
|
176
|
+
},
|
|
177
|
+
"checkItems": {
|
|
178
|
+
"type": "array",
|
|
179
|
+
"items": {
|
|
180
|
+
"type": "string"
|
|
181
|
+
},
|
|
182
|
+
"description": "检查项"
|
|
183
|
+
},
|
|
184
|
+
"dimensions": {
|
|
185
|
+
"type": "array",
|
|
186
|
+
"items": {
|
|
187
|
+
"type": "string"
|
|
188
|
+
},
|
|
189
|
+
"description": "检查维度"
|
|
190
|
+
},
|
|
191
|
+
"dimensionDetails": {
|
|
192
|
+
"type": "object",
|
|
193
|
+
"description": "维度详细说明"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"validator": {
|
|
198
|
+
"type": "object",
|
|
199
|
+
"required": [
|
|
200
|
+
"description",
|
|
201
|
+
"status"
|
|
202
|
+
],
|
|
203
|
+
"properties": {
|
|
204
|
+
"description": {
|
|
205
|
+
"type": "string"
|
|
206
|
+
},
|
|
207
|
+
"implementedIn": {
|
|
208
|
+
"type": "string"
|
|
209
|
+
},
|
|
210
|
+
"status": {
|
|
211
|
+
"type": "string",
|
|
212
|
+
"enum": [
|
|
213
|
+
"planned",
|
|
214
|
+
"implemented",
|
|
215
|
+
"deprecated"
|
|
216
|
+
]
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 规则注入工具
|
|
3
|
+
*
|
|
4
|
+
* 用于在 workflow 文件头部注入规则子集表
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* node scripts/inject-rules.js # 注入所有 workflow
|
|
8
|
+
* node scripts/inject-rules.js prd-c1 # 注入指定 workflow
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// 加载规则索引
|
|
15
|
+
function loadRules() {
|
|
16
|
+
const rulesPath = path.join(__dirname, '../rules/index.json');
|
|
17
|
+
return JSON.parse(fs.readFileSync(rulesPath, 'utf-8'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Scope 到 Workflow 的映射
|
|
21
|
+
const scopeToWorkflow = {
|
|
22
|
+
'p0': 'prd-p0-project-info',
|
|
23
|
+
'b1': 'prd-b1-planning-draft',
|
|
24
|
+
'b2': 'prd-b2-planning-breakdown',
|
|
25
|
+
'c1': 'prd-c1-requirement-list',
|
|
26
|
+
'r1': 'prd-r1-review',
|
|
27
|
+
'r2': 'prd-r2-review'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// 获取指定 scope 的规则
|
|
31
|
+
function getRulesForScope(rules, scope) {
|
|
32
|
+
return rules.filter(rule =>
|
|
33
|
+
rule.scope.includes(scope) || rule.scope.includes('global')
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 生成规则表格 Markdown
|
|
38
|
+
function generateRulesTable(rules, scopeName) {
|
|
39
|
+
const lines = [
|
|
40
|
+
'',
|
|
41
|
+
'---',
|
|
42
|
+
'',
|
|
43
|
+
'## 🚨 本阶段必须遵守的规则',
|
|
44
|
+
'',
|
|
45
|
+
'> ⚠️ AI 在执行任务前必须逐条确认以下规则。输出时需包含自检清单。',
|
|
46
|
+
'',
|
|
47
|
+
'| ID | 规则 | 严重程度 | 自检 |',
|
|
48
|
+
'|----|------|----------|------|'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
rules.forEach(rule => {
|
|
52
|
+
const severity = rule.severity === 'CRITICAL' ? '🔴 严重' :
|
|
53
|
+
rule.severity === 'HIGH' ? '🟠 高' :
|
|
54
|
+
rule.severity === 'MEDIUM' ? '🟡 中' : '🟢 低';
|
|
55
|
+
lines.push(`| ${rule.id} | ${rule.description} | ${severity} | ☐ |`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
lines.push('');
|
|
59
|
+
lines.push('**自检清单模板**(AI 输出时必须包含):');
|
|
60
|
+
lines.push('```');
|
|
61
|
+
lines.push(`## ✅ 规则自检 (${scopeName})`);
|
|
62
|
+
rules.forEach(rule => {
|
|
63
|
+
lines.push(`- [ ] ${rule.id}: ${rule.description.substring(0, 30)}...`);
|
|
64
|
+
});
|
|
65
|
+
lines.push('```');
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push('---');
|
|
68
|
+
lines.push('');
|
|
69
|
+
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 在 workflow 文件中注入规则表格
|
|
74
|
+
function injectRulesToWorkflow(workflowPath, rules, scopeName) {
|
|
75
|
+
const content = fs.readFileSync(workflowPath, 'utf-8');
|
|
76
|
+
|
|
77
|
+
// 检查是否已有规则表格
|
|
78
|
+
if (content.includes('## 🚨 本阶段必须遵守的规则')) {
|
|
79
|
+
console.log(` 跳过(已有规则表格): ${path.basename(workflowPath)}`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 找到第一个 # 标题的位置(跳过 frontmatter)
|
|
84
|
+
const lines = content.split('\n');
|
|
85
|
+
let insertIndex = 0;
|
|
86
|
+
let inFrontmatter = false;
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < lines.length; i++) {
|
|
89
|
+
if (lines[i].startsWith('---')) {
|
|
90
|
+
if (!inFrontmatter) {
|
|
91
|
+
inFrontmatter = true;
|
|
92
|
+
} else {
|
|
93
|
+
inFrontmatter = false;
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 找到第一个 # 开头的标题
|
|
99
|
+
if (!inFrontmatter && lines[i].startsWith('# ')) {
|
|
100
|
+
// 在标题之后插入
|
|
101
|
+
insertIndex = i + 1;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (insertIndex === 0) {
|
|
107
|
+
console.log(` 跳过(未找到标题): ${path.basename(workflowPath)}`);
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 生成规则表格
|
|
112
|
+
const rulesTable = generateRulesTable(rules, scopeName);
|
|
113
|
+
|
|
114
|
+
// 插入规则表格
|
|
115
|
+
lines.splice(insertIndex, 0, rulesTable);
|
|
116
|
+
|
|
117
|
+
fs.writeFileSync(workflowPath, lines.join('\n'));
|
|
118
|
+
console.log(` ✅ 已注入: ${path.basename(workflowPath)} (${rules.length} 条规则)`);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 主函数
|
|
123
|
+
function main() {
|
|
124
|
+
const rulesIndex = loadRules();
|
|
125
|
+
const workflowDir = path.join(__dirname, '../.agent/workflows');
|
|
126
|
+
|
|
127
|
+
console.log('🔧 规则注入工具');
|
|
128
|
+
console.log('');
|
|
129
|
+
|
|
130
|
+
let injected = 0;
|
|
131
|
+
let skipped = 0;
|
|
132
|
+
|
|
133
|
+
// 遍历所有 scope
|
|
134
|
+
for (const [scope, workflowName] of Object.entries(scopeToWorkflow)) {
|
|
135
|
+
const workflowPath = path.join(workflowDir, `${workflowName}.md`);
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(workflowPath)) {
|
|
138
|
+
console.log(` 跳过(文件不存在): ${workflowName}.md`);
|
|
139
|
+
skipped++;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const rules = getRulesForScope(rulesIndex.rules, scope);
|
|
144
|
+
|
|
145
|
+
if (injectRulesToWorkflow(workflowPath, rules, scope.toUpperCase())) {
|
|
146
|
+
injected++;
|
|
147
|
+
} else {
|
|
148
|
+
skipped++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log('');
|
|
153
|
+
console.log(`完成:注入 ${injected} 个,跳过 ${skipped} 个`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 导出函数供其他模块使用
|
|
157
|
+
module.exports = {
|
|
158
|
+
loadRules,
|
|
159
|
+
getRulesForScope,
|
|
160
|
+
generateRulesTable,
|
|
161
|
+
injectRulesToWorkflow
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// 如果直接运行脚本
|
|
165
|
+
if (require.main === module) {
|
|
166
|
+
main();
|
|
167
|
+
}
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
|
|
69
69
|
<script>
|
|
70
70
|
// UI 数据(内嵌)
|
|
71
|
-
const UI_DATA = {{JSON_DATA}};
|
|
71
|
+
const UI_DATA = {{ JSON_DATA }};
|
|
72
72
|
|
|
73
73
|
// A2UI 渲染器
|
|
74
74
|
const { ConfigProvider, Card, Button, Input, Select, Table, Tabs, Tag, Badge, Space, Row, Col, Typography, Divider, Alert, Upload, Form } = antd;
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
case 'Button': return React.createElement(Button, { key: props.key, type: props.variant === 'secondary' ? 'default' : props.variant === 'danger' ? 'primary' : 'primary', danger: props.variant === 'danger', style: { marginRight: 8 } }, props.text);
|
|
94
94
|
case 'Text': return React.createElement(Text, { key: props.key, style: { display: 'block', marginBottom: 8 } }, props.content);
|
|
95
95
|
case 'Tabs': return React.createElement(Tabs, { key: props.key, items: (props.items || []).map((item, i) => ({ key: String(i), label: item })), style: { marginBottom: 16 } });
|
|
96
|
-
case 'Table':
|
|
96
|
+
case 'Table':
|
|
97
97
|
const columns = (props.columns || []).map(col => {
|
|
98
98
|
const column = { key: col.key || col, dataIndex: col.key || col, title: col.title || col };
|
|
99
99
|
if (col.type === 'link') column.render = (text) => React.createElement('a', null, text);
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
case 'Box': return React.createElement(Card, { key: props.key, size: 'small', style: { minWidth: 120, textAlign: 'center', borderLeft: props.color ? `3px solid ${props.color}` : undefined } }, React.createElement(Text, { strong: true }, props.title), props.desc && React.createElement('div', null, React.createElement(Text, { type: 'secondary', style: { fontSize: 12 } }, props.desc)));
|
|
113
113
|
case 'Arrow': return React.createElement('div', { key: props.key, style: { color: 'white', fontSize: 24, textAlign: 'center' } }, (props.direction === 'up' ? '↑' : props.direction === 'left' ? '←' : props.direction === 'right' ? '→' : '↓'), props.label && React.createElement('span', { style: { fontSize: 12, marginLeft: 8 } }, props.label));
|
|
114
114
|
case 'Layer': return React.createElement('div', { key: props.key, style: { display: 'flex', flexWrap: 'wrap', gap: 12, justifyContent: 'center', width: '100%' } }, props.title && React.createElement('div', { style: { width: '100%', textAlign: 'center', color: 'rgba(255,255,255,0.8)', fontSize: 12, marginBottom: 8 } }, props.title), children && children.map((child, i) => renderNode({ ...child, key: i })));
|
|
115
|
-
case 'DiagramGroup': return React.createElement('div', { key: props.key, style: { background: 'rgba(255,255,255,0.1)', border: '1px dashed rgba(255,255,255,0.3)', borderRadius: 8, padding: 16, width: '100%' } }, props.title && React.createElement('div', { style: { color: 'rgba(255,255,255,0.9)', fontSize: 14, marginBottom: 12 } }, props.title), React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 12, justifyContent: 'center' } }, children && children.map((child, i) => renderNode({ ...child, key: i })));
|
|
115
|
+
case 'DiagramGroup': return React.createElement('div', { key: props.key, style: { background: 'rgba(255,255,255,0.1)', border: '1px dashed rgba(255,255,255,0.3)', borderRadius: 8, padding: 16, width: '100%' } }, props.title && React.createElement('div', { style: { color: 'rgba(255,255,255,0.9)', fontSize: 14, marginBottom: 12 } }, props.title), React.createElement('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 12, justifyContent: 'center' } }, children && children.map((child, i) => renderNode({ ...child, key: i }))));
|
|
116
116
|
default: return React.createElement(Alert, { key: props.key, type: 'warning', message: `未知组件: ${type}` });
|
|
117
117
|
}
|
|
118
118
|
};
|