prd-workflow-cli 1.4.1 → 2.0.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-a1-scan.md +133 -0
- package/.agent/workflows/prd-a2ui-guide.md +6 -6
- package/.agent/workflows/prd-b-planning.md +135 -0
- package/.agent/workflows/prd-it-biz.md +56 -0
- package/.agent/workflows/prd-it-dev.md +163 -0
- package/.agent/workflows/prd-r2-review.md +104 -409
- package/.antigravity/rules.md +50 -265
- package/.cursorrules +57 -371
- package/GUIDE.md +147 -240
- package/README.md +170 -362
- package/bin/prd-cli.js +19 -12
- package/commands/baseline.js +174 -293
- package/commands/freeze-checks.js +424 -0
- package/commands/init.js +97 -162
- package/commands/it.js +286 -0
- package/commands/iteration.js +7 -91
- package/commands/planning.js +149 -517
- package/commands/review.js +78 -50
- package/commands/status.js +29 -38
- package/commands/upgrade.js +20 -0
- package/commands/version.js +222 -200
- package/package.json +2 -2
- package/rules/index.json +26 -27
- package/rules/schemas/rules.schema.json +1 -2
- package/templates/it-biz.md +141 -0
- package/templates/it-dev.md +237 -0
- package/templates//344/270/232/345/212/241/351/234/200/346/261/202.md +141 -0
- package/templates//346/212/200/346/234/257/350/247/204/346/240/274.md +237 -0
- package/.agent/workflows/prd-b1-planning-draft.md +0 -614
- package/.agent/workflows/prd-b2-planning-breakdown.md +0 -828
- package/.agent/workflows/prd-c1-requirement-list.md +0 -286
- package/.agent/workflows/prd-r1-review.md +0 -503
package/commands/init.js
CHANGED
|
@@ -28,7 +28,7 @@ module.exports = async function (projectName) {
|
|
|
28
28
|
console.log(chalk.yellow(` 你正在尝试在 PRD 项目中创建子项目 "${projectName}"。`));
|
|
29
29
|
console.log('');
|
|
30
30
|
console.log(chalk.cyan(' 建议操作:'));
|
|
31
|
-
console.log(chalk.gray(' 1. 如果要在当前项目工作,直接使用 prd baseline create
|
|
31
|
+
console.log(chalk.gray(' 1. 如果要在当前项目工作,直接使用 prd baseline create 产品定义 等命令'));
|
|
32
32
|
console.log(chalk.gray(' 2. 如果确实要创建独立新项目,请先 cd 到其他目录'));
|
|
33
33
|
console.log(chalk.gray(' 3. 如果要更新规则文件,请运行: prd upgrade'));
|
|
34
34
|
console.log('');
|
|
@@ -38,10 +38,10 @@ module.exports = async function (projectName) {
|
|
|
38
38
|
|
|
39
39
|
console.log(chalk.blue(`正在${isCurrentDir ? '在当前目录' : '创建项目: ' + projectName}初始化...`));
|
|
40
40
|
|
|
41
|
-
//
|
|
41
|
+
// 创建项目目录结构(使用新的中文命名)
|
|
42
42
|
const directories = [
|
|
43
43
|
'00_项目总览',
|
|
44
|
-
'01_
|
|
44
|
+
'01_基线', // 原 01_产品基线
|
|
45
45
|
'02_迭代记录',
|
|
46
46
|
'98_对话归档',
|
|
47
47
|
'99_归档区/历史参考与废弃文档',
|
|
@@ -56,8 +56,9 @@ module.exports = async function (projectName) {
|
|
|
56
56
|
const config = {
|
|
57
57
|
projectName: displayName,
|
|
58
58
|
createdAt: new Date().toISOString(),
|
|
59
|
+
cliVersion: '2.0.0',
|
|
59
60
|
currentIteration: 0,
|
|
60
|
-
workflow: '
|
|
61
|
+
workflow: '基线 → 规划 → IT → 版本',
|
|
61
62
|
stages: {
|
|
62
63
|
baseline: { completed: false, documents: [] },
|
|
63
64
|
planning: { completed: false, documents: [] },
|
|
@@ -83,7 +84,7 @@ module.exports = async function (projectName) {
|
|
|
83
84
|
help: 'prd --help'
|
|
84
85
|
},
|
|
85
86
|
dependencies: {
|
|
86
|
-
'prd-workflow-cli': '^
|
|
87
|
+
'prd-workflow-cli': '^2.0.0'
|
|
87
88
|
}
|
|
88
89
|
};
|
|
89
90
|
|
|
@@ -93,147 +94,89 @@ module.exports = async function (projectName) {
|
|
|
93
94
|
{ spaces: 2 }
|
|
94
95
|
);
|
|
95
96
|
|
|
96
|
-
//
|
|
97
|
-
const
|
|
97
|
+
// 创建项目信息模板(原 P0)
|
|
98
|
+
const projectInfoTemplate = `# 项目信息
|
|
98
99
|
|
|
99
100
|
**创建时间**: ${new Date().toLocaleString('zh-CN')}
|
|
100
101
|
**项目名称**: ${displayName}
|
|
101
|
-
**文档状态**: 草案
|
|
102
102
|
|
|
103
103
|
---
|
|
104
104
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- 明确项目是否应该存在
|
|
109
|
-
- 确认项目目标是否成立
|
|
110
|
-
- 识别关键干系人
|
|
111
|
-
|
|
112
|
-
**填写要求**:
|
|
113
|
-
- 只填写事实,不填愿景
|
|
114
|
-
- 目标要可检验
|
|
115
|
-
- 干系人要具体到人
|
|
105
|
+
> ⚠️ **这是业务的"宪法"** - 只记录代码无法表达的决策
|
|
106
|
+
>
|
|
107
|
+
> 功能清单由 AI 扫描代码自动生成(见代码快照),无需在此重复
|
|
116
108
|
|
|
117
109
|
---
|
|
118
110
|
|
|
119
|
-
## 1.
|
|
111
|
+
## 1. 产品定位
|
|
120
112
|
|
|
121
|
-
|
|
113
|
+
**一句话说明这个产品是什么**:
|
|
114
|
+
<!-- 例如:面向企业用户的项目管理工具 -->
|
|
122
115
|
|
|
123
|
-
**项目全称**: ${displayName}
|
|
124
116
|
|
|
125
|
-
|
|
126
|
-
<!--
|
|
117
|
+
**核心价值主张**:
|
|
118
|
+
<!-- 用户为什么选择你而不是竞品? -->
|
|
127
119
|
|
|
128
|
-
**所属产品线**:
|
|
129
|
-
<!-- 例如:核心业务系统、辅助工具、创新试点 -->
|
|
130
|
-
|
|
131
|
-
**项目级别**:
|
|
132
|
-
- [ ] 战略级(公司级重点)
|
|
133
|
-
- [ ] 业务级(部门级重点)
|
|
134
|
-
- [ ] 支撑级(基础能力)
|
|
135
120
|
|
|
136
121
|
---
|
|
137
122
|
|
|
138
|
-
## 2.
|
|
123
|
+
## 2. 边界声明
|
|
139
124
|
|
|
140
|
-
### 2.1
|
|
125
|
+
### 2.1 明确不做的事情
|
|
141
126
|
|
|
142
|
-
|
|
143
|
-
<!-- 不超过 3 个核心问题 -->
|
|
144
|
-
1.
|
|
145
|
-
2.
|
|
146
|
-
3.
|
|
127
|
+
<!-- 这是最重要的部分!列出被拒绝的需求/方向 -->
|
|
147
128
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
129
|
+
| 不做的事情 | 原因 |
|
|
130
|
+
|-----------|------|
|
|
131
|
+
| 例:第三方登录 | 数据安全考虑 |
|
|
132
|
+
| 例:移动端 App | 资源限制,优先 Web |
|
|
133
|
+
| | |
|
|
153
134
|
|
|
154
|
-
### 2.2
|
|
135
|
+
### 2.2 核心约束(红线)
|
|
155
136
|
|
|
156
|
-
|
|
157
|
-
<!-- 时机/背景/触发因素 -->
|
|
137
|
+
<!-- 不可妥协的限制 -->
|
|
158
138
|
|
|
159
|
-
|
|
160
|
-
|
|
139
|
+
- [ ] 安全约束: ___________
|
|
140
|
+
- [ ] 合规约束: ___________
|
|
141
|
+
- [ ] 性能约束: ___________
|
|
142
|
+
- [ ] 其他: ___________
|
|
161
143
|
|
|
162
144
|
---
|
|
163
145
|
|
|
164
|
-
## 3.
|
|
165
|
-
|
|
166
|
-
### 3.1 核心干系人
|
|
167
|
-
|
|
168
|
-
**PM(产品负责人)**:
|
|
169
|
-
- 姓名: ____________
|
|
170
|
-
- 职责: 项目最终决策
|
|
171
|
-
- 联系方式: ____________
|
|
172
|
-
|
|
173
|
-
**技术负责人**:
|
|
174
|
-
- 姓名: ____________
|
|
175
|
-
- 职责: 技术可行性把关
|
|
176
|
-
- 联系方式: ____________
|
|
177
|
-
|
|
178
|
-
**业务方**:
|
|
179
|
-
- 部门: ____________
|
|
180
|
-
- 联系人: ____________
|
|
181
|
-
- 职责: 业务需求确认
|
|
182
|
-
|
|
183
|
-
### 3.2 相关方
|
|
146
|
+
## 3. 责任人
|
|
184
147
|
|
|
185
|
-
|
|
186
|
-
|
|
148
|
+
| 角色 | 姓名 | 职责 |
|
|
149
|
+
|-----|------|------|
|
|
150
|
+
| **产品负责人 (PM)** | _____ | 最终决策 |
|
|
151
|
+
| **技术负责人** | _____ | 技术可行性 |
|
|
152
|
+
| **业务方** | _____ | 业务验收 |
|
|
187
153
|
|
|
188
154
|
---
|
|
189
155
|
|
|
190
|
-
## 4.
|
|
156
|
+
## 4. 成功标准
|
|
191
157
|
|
|
192
|
-
|
|
158
|
+
<!-- 如何判断项目成功?必须可衡量 -->
|
|
193
159
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
**已知的资源限制**:
|
|
200
|
-
<!-- 人力/预算/技术限制 -->
|
|
201
|
-
|
|
202
|
-
### 4.3 依赖条件
|
|
203
|
-
|
|
204
|
-
**项目依赖**:
|
|
205
|
-
<!-- 需要其他项目/系统先完成什么? -->
|
|
160
|
+
| 指标 | 当前值 | 目标值 | 截止日期 |
|
|
161
|
+
|-----|-------|-------|---------|
|
|
162
|
+
| 例:注册转化率 | 30% | 60% | 2024-Q2 |
|
|
163
|
+
| | | | |
|
|
206
164
|
|
|
207
165
|
---
|
|
208
166
|
|
|
209
|
-
##
|
|
210
|
-
|
|
211
|
-
**当前状态**: 初始化
|
|
212
|
-
**当前迭代**: 0 轮
|
|
213
|
-
**下一步**: 创建 A 类基线文档
|
|
214
|
-
|
|
215
|
-
---
|
|
167
|
+
## PM 确认
|
|
216
168
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
- [ ]
|
|
220
|
-
- [ ] 干系人已确认
|
|
221
|
-
- [ ] 约束条件已记录
|
|
222
|
-
- [ ] 可以开始基线建立
|
|
169
|
+
- [ ] 边界声明已明确
|
|
170
|
+
- [ ] 责任人已确认
|
|
171
|
+
- [ ] 成功标准可衡量
|
|
223
172
|
|
|
224
173
|
**PM 签字**: _____________
|
|
225
174
|
**日期**: _____________
|
|
226
|
-
|
|
227
|
-
---
|
|
228
|
-
|
|
229
|
-
## 备注
|
|
230
|
-
|
|
231
|
-
<!-- 其他需要说明的重要信息 -->
|
|
232
175
|
`;
|
|
233
176
|
|
|
234
177
|
await fs.writeFile(
|
|
235
|
-
path.join(projectPath, '00_
|
|
236
|
-
|
|
178
|
+
path.join(projectPath, '00_项目总览/项目信息.md'),
|
|
179
|
+
projectInfoTemplate
|
|
237
180
|
);
|
|
238
181
|
|
|
239
182
|
// 复制工作流模板
|
|
@@ -294,73 +237,74 @@ module.exports = async function (projectName) {
|
|
|
294
237
|
// 创建 .a2ui 目录(用于临时预览数据)
|
|
295
238
|
await fs.ensureDir(path.join(projectPath, '.a2ui'));
|
|
296
239
|
|
|
297
|
-
// 创建 README
|
|
240
|
+
// 创建 README(使用新的中文命名)
|
|
298
241
|
const readme = `# ${displayName}
|
|
299
242
|
|
|
300
|
-
本项目采用规范化的产品需求管理流程
|
|
243
|
+
本项目采用规范化的产品需求管理流程 (PRD-CLI v2.0.0)
|
|
301
244
|
|
|
302
245
|
## 📁 目录结构
|
|
303
246
|
|
|
304
247
|
\`\`\`
|
|
305
248
|
${displayName}/
|
|
306
|
-
├── 00_项目总览/ #
|
|
307
|
-
|
|
308
|
-
├──
|
|
309
|
-
│ ├──
|
|
310
|
-
│ ├──
|
|
311
|
-
│ └──
|
|
249
|
+
├── 00_项目总览/ # 项目信息
|
|
250
|
+
│ └── 项目信息.md
|
|
251
|
+
├── 01_基线/ # 产品基线
|
|
252
|
+
│ ├── 产品定义.md # PM 填写
|
|
253
|
+
│ ├── 代码快照.md # AI 扫描生成
|
|
254
|
+
│ └── 用户反馈.md # AI 整理
|
|
255
|
+
├── 02_迭代记录/ # 各轮迭代
|
|
256
|
+
│ └── 第01轮迭代/
|
|
257
|
+
│ ├── 需求规划.md # PM + AI 对话
|
|
258
|
+
│ ├── 规划冻结.md # 自动生成
|
|
259
|
+
│ ├── IT/ # 用户故事
|
|
260
|
+
│ │ └── IT-001-功能名/
|
|
261
|
+
│ │ ├── 业务需求.md
|
|
262
|
+
│ │ └── 技术规格.md
|
|
263
|
+
│ └── 版本发布.md # 自动生成
|
|
312
264
|
└── 99_归档区/ # 历史文档归档
|
|
313
265
|
\`\`\`
|
|
314
266
|
|
|
315
267
|
## 🔄 工作流程
|
|
316
268
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
2. **B 类 - 需求规划** (02_迭代记录/第N轮迭代/)
|
|
324
|
-
- R1: 规划前审视(启动条件检查)
|
|
325
|
-
- B1: 需求规划草案
|
|
326
|
-
- B2: 规划拆解与范围界定
|
|
327
|
-
- R1: 规划审视(冻结前审查)
|
|
328
|
-
- B3: 规划冻结归档
|
|
329
|
-
|
|
330
|
-
3. **C 类 - 版本需求** (02_迭代记录/第N轮迭代/)
|
|
331
|
-
- R2: 版本审视
|
|
332
|
-
- C0: 版本范围声明
|
|
333
|
-
- C1: 版本需求清单
|
|
334
|
-
- C3: 版本冻结归档
|
|
269
|
+
\`\`\`
|
|
270
|
+
基线阶段 → 规划阶段 → IT阶段 → 版本阶段
|
|
271
|
+
↓ ↓ ↓ ↓
|
|
272
|
+
AI生成 PM+AI对话 PM+AI对话 自动生成
|
|
273
|
+
\`\`\`
|
|
335
274
|
|
|
336
|
-
## 🛠️
|
|
275
|
+
## 🛠️ 常用命令
|
|
337
276
|
|
|
338
277
|
\`\`\`bash
|
|
339
278
|
# 查看项目状态
|
|
340
279
|
prd status
|
|
341
280
|
|
|
342
281
|
# 创建基线文档
|
|
343
|
-
prd baseline create
|
|
282
|
+
prd baseline create 产品定义
|
|
283
|
+
prd baseline create 代码快照
|
|
284
|
+
prd baseline create 用户反馈
|
|
344
285
|
|
|
345
286
|
# 开始新迭代
|
|
346
287
|
prd iteration new
|
|
347
288
|
|
|
348
289
|
# 创建规划文档
|
|
349
|
-
prd plan create
|
|
290
|
+
prd plan create
|
|
350
291
|
|
|
351
|
-
#
|
|
352
|
-
prd review r1
|
|
353
|
-
|
|
354
|
-
# 冻结规划
|
|
292
|
+
# 冻结规划(自动审视)
|
|
355
293
|
prd plan freeze
|
|
294
|
+
|
|
295
|
+
# 创建 IT 用户故事
|
|
296
|
+
prd it create "功能名称"
|
|
297
|
+
|
|
298
|
+
# 冻结版本(自动审视)
|
|
299
|
+
prd version freeze
|
|
356
300
|
\`\`\`
|
|
357
301
|
|
|
358
|
-
## 📝
|
|
302
|
+
## 📝 核心原则
|
|
359
303
|
|
|
360
|
-
- **
|
|
361
|
-
-
|
|
362
|
-
-
|
|
363
|
-
-
|
|
304
|
+
- **PM 决策,AI 执行**:AI 不替 PM 做决策
|
|
305
|
+
- **对话驱动**:文档通过对话逐步完成,不一次填充
|
|
306
|
+
- **审视内化**:审视作为动作内化到 freeze 命令中
|
|
307
|
+
- **防止幻觉**:AI 不编造技术细节
|
|
364
308
|
|
|
365
309
|
---
|
|
366
310
|
创建时间: ${new Date().toLocaleString('zh-CN')}
|
|
@@ -376,38 +320,29 @@ prd plan freeze
|
|
|
376
320
|
|
|
377
321
|
// 显示 AI 集成信息
|
|
378
322
|
console.log(chalk.bold('🤖 AI 集成已配置:'));
|
|
379
|
-
console.log(chalk.gray(' ✓ .agent/workflows/ - PRD
|
|
323
|
+
console.log(chalk.gray(' ✓ .agent/workflows/ - PRD 工作流指引'));
|
|
380
324
|
console.log(chalk.gray(' ✓ .cursorrules - Cursor AI 规则'));
|
|
381
325
|
console.log(chalk.gray(' ✓ .antigravity/ - Antigravity AI 规则'));
|
|
382
326
|
console.log(chalk.gray(' ✓ a2ui-viewer/ - A2UI 界面预览器'));
|
|
383
|
-
console.log(chalk.gray(' ✓ .a2ui/ - A2UI 临时数据目录'));
|
|
384
327
|
console.log('');
|
|
385
328
|
console.log(chalk.yellow(' 💡 现在你可以直接与 AI 助手对话,AI 已经知道如何协助你完成 PRD 流程!'));
|
|
386
|
-
console.log(chalk.gray(' 例如:告诉 AI "我要创建一个新项目的需求文档"'));
|
|
387
|
-
console.log(chalk.gray(' 启动界面预览:运行 prd ui'));
|
|
388
329
|
console.log('');
|
|
389
330
|
|
|
390
|
-
console.log(chalk.bold('📋
|
|
331
|
+
console.log(chalk.bold('📋 下一步操作:'));
|
|
391
332
|
console.log('');
|
|
392
333
|
if (!isCurrentDir) {
|
|
393
|
-
console.log(chalk.cyan('
|
|
394
|
-
console.log(`
|
|
334
|
+
console.log(chalk.cyan('1. 进入项目目录'));
|
|
335
|
+
console.log(` cd ${displayName}`);
|
|
395
336
|
console.log('');
|
|
396
|
-
console.log(chalk.cyan('
|
|
337
|
+
console.log(chalk.cyan('2. 完善项目信息'));
|
|
397
338
|
} else {
|
|
398
|
-
console.log(chalk.cyan('
|
|
339
|
+
console.log(chalk.cyan('1. 完善项目信息'));
|
|
399
340
|
}
|
|
400
|
-
console.log(chalk.gray('
|
|
401
|
-
console.log(chalk.
|
|
402
|
-
console.log(chalk.yellow(' ⚠️ 必须完成 P0 填写后才能开始创建 A 类基线文档'));
|
|
403
|
-
console.log('');
|
|
404
|
-
console.log(chalk.cyan(`第 ${isCurrentDir ? '2' : '3'} 步: 创建 A0 基线文档`));
|
|
405
|
-
console.log(' prd baseline create A0 # P0 填写完成后执行');
|
|
341
|
+
console.log(chalk.gray(' 文件位置: 00_项目总览/项目信息.md'));
|
|
342
|
+
console.log(chalk.yellow(' ⚠️ 必须完成项目信息后才能开始创建基线文档'));
|
|
406
343
|
console.log('');
|
|
407
|
-
|
|
408
|
-
console.log(
|
|
409
|
-
console.log(chalk.gray(' 当 CLI 包有新版本时,运行以下命令同步更新项目规则:'));
|
|
410
|
-
console.log(chalk.cyan(' npm update -g prd-workflow-cli && prd upgrade'));
|
|
344
|
+
console.log(chalk.cyan(`${isCurrentDir ? '2' : '3'}. 创建产品定义`));
|
|
345
|
+
console.log(' prd baseline create 产品定义');
|
|
411
346
|
console.log('');
|
|
412
347
|
|
|
413
348
|
} catch (error) {
|
package/commands/it.js
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* IT (INVEST) 管理命令
|
|
7
|
+
* 替代原来的 C1 大文档,每个 IT 是一个独立的用户故事
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
module.exports = async function (action, name, options = {}) {
|
|
11
|
+
const configPath = path.join(process.cwd(), '.prd-config.json');
|
|
12
|
+
|
|
13
|
+
if (!await fs.pathExists(configPath)) {
|
|
14
|
+
console.log(chalk.red('✗ 当前目录不是一个 PRD 项目'));
|
|
15
|
+
console.log('请先运行: prd init <项目名>');
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const config = await fs.readJSON(configPath);
|
|
20
|
+
|
|
21
|
+
if (action === 'create') {
|
|
22
|
+
await createIT(config, name, options);
|
|
23
|
+
} else if (action === 'list') {
|
|
24
|
+
await listITs(config, options);
|
|
25
|
+
} else if (action === 'show') {
|
|
26
|
+
await showIT(config, name, options);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(chalk.red('✗ 未知的操作'));
|
|
29
|
+
console.log('可用操作: create, list, show');
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
async function createIT(config, name, options = {}) {
|
|
34
|
+
if (config.currentIteration === 0) {
|
|
35
|
+
console.log(chalk.red('✗ 请先创建迭代'));
|
|
36
|
+
console.log('运行: prd iteration new');
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!name) {
|
|
41
|
+
console.log(chalk.red('✗ 请提供 IT 名称'));
|
|
42
|
+
console.log('示例: prd it create 用户反馈');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const iterationDir = path.join(
|
|
47
|
+
process.cwd(),
|
|
48
|
+
'02_迭代记录',
|
|
49
|
+
`第${String(config.currentIteration).padStart(2, '0')}轮迭代`
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// 检查规划冻结是否存在(支持新旧文件名)
|
|
53
|
+
const freezePath = path.join(iterationDir, '规划冻结.md');
|
|
54
|
+
const oldB3Path = path.join(iterationDir, 'B3_规划冻结归档.md');
|
|
55
|
+
if (!await fs.pathExists(freezePath) && !await fs.pathExists(oldB3Path)) {
|
|
56
|
+
console.log(chalk.red('✗ 请先完成规划冻结'));
|
|
57
|
+
console.log('运行: prd plan freeze');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const itDir = path.join(iterationDir, 'IT');
|
|
62
|
+
await fs.ensureDir(itDir);
|
|
63
|
+
|
|
64
|
+
// 获取下一个 IT 编号
|
|
65
|
+
const existingITs = await fs.readdir(itDir);
|
|
66
|
+
const itNumbers = existingITs
|
|
67
|
+
.filter(dir => dir.startsWith('IT-'))
|
|
68
|
+
.map(dir => parseInt(dir.split('-')[1]))
|
|
69
|
+
.filter(n => !isNaN(n));
|
|
70
|
+
|
|
71
|
+
const nextNumber = itNumbers.length > 0 ? Math.max(...itNumbers) + 1 : 1;
|
|
72
|
+
const itId = `IT-${String(nextNumber).padStart(3, '0')}`;
|
|
73
|
+
const itFolderName = `${itId}-${name}`;
|
|
74
|
+
const itPath = path.join(itDir, itFolderName);
|
|
75
|
+
|
|
76
|
+
if (await fs.pathExists(itPath)) {
|
|
77
|
+
console.log(chalk.yellow(`⚠ IT 已存在: ${itFolderName}`));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await fs.ensureDir(itPath);
|
|
82
|
+
|
|
83
|
+
// 读取模板(优先使用新的中文模板)
|
|
84
|
+
let bizTemplatePath = path.join(__dirname, '../templates/业务需求.md');
|
|
85
|
+
let devTemplatePath = path.join(__dirname, '../templates/技术规格.md');
|
|
86
|
+
|
|
87
|
+
// 兼容旧模板
|
|
88
|
+
if (!await fs.pathExists(bizTemplatePath)) {
|
|
89
|
+
bizTemplatePath = path.join(__dirname, '../templates/it-biz.md');
|
|
90
|
+
}
|
|
91
|
+
if (!await fs.pathExists(devTemplatePath)) {
|
|
92
|
+
devTemplatePath = path.join(__dirname, '../templates/it-dev.md');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const bizTemplate = await fs.readFile(bizTemplatePath, 'utf-8');
|
|
96
|
+
const devTemplate = await fs.readFile(devTemplatePath, 'utf-8');
|
|
97
|
+
|
|
98
|
+
// 替换模板变量
|
|
99
|
+
const createTime = new Date().toLocaleString('zh-CN');
|
|
100
|
+
const replacements = {
|
|
101
|
+
'{{IT_ID}}': itId,
|
|
102
|
+
'{{IT_NAME}}': name,
|
|
103
|
+
'{{CREATE_TIME}}': createTime
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
let bizContent = bizTemplate;
|
|
107
|
+
let devContent = devTemplate;
|
|
108
|
+
|
|
109
|
+
Object.entries(replacements).forEach(([key, value]) => {
|
|
110
|
+
bizContent = bizContent.replace(new RegExp(key, 'g'), value);
|
|
111
|
+
devContent = devContent.replace(new RegExp(key, 'g'), value);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// 生成文件(使用中文文件名)
|
|
115
|
+
const bizFilePath = path.join(itPath, '业务需求.md');
|
|
116
|
+
const devFilePath = path.join(itPath, '技术规格.md');
|
|
117
|
+
|
|
118
|
+
await fs.writeFile(bizFilePath, bizContent);
|
|
119
|
+
await fs.writeFile(devFilePath, devContent);
|
|
120
|
+
|
|
121
|
+
console.log(chalk.green(`✓ 创建 IT: ${itFolderName}\n`));
|
|
122
|
+
console.log(chalk.cyan(`📁 位置: ${itPath}`));
|
|
123
|
+
console.log(chalk.gray(` 业务需求.md`));
|
|
124
|
+
console.log(chalk.gray(` 技术规格.md`));
|
|
125
|
+
console.log('');
|
|
126
|
+
console.log(chalk.bold('下一步:'));
|
|
127
|
+
console.log('1. 填写业务需求.md(与 AI 对话)');
|
|
128
|
+
console.log('2. 填写技术规格.md(技术负责人补充)');
|
|
129
|
+
console.log('3. 查看所有 IT: prd it list');
|
|
130
|
+
console.log('');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function listITs(config, options = {}) {
|
|
134
|
+
if (config.currentIteration === 0) {
|
|
135
|
+
console.log(chalk.red('✗ 尚未创建迭代'));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const iterationDir = path.join(
|
|
140
|
+
process.cwd(),
|
|
141
|
+
'02_迭代记录',
|
|
142
|
+
`第${String(config.currentIteration).padStart(2, '0')}轮迭代`
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const itDir = path.join(iterationDir, 'IT');
|
|
146
|
+
|
|
147
|
+
if (!await fs.pathExists(itDir)) {
|
|
148
|
+
console.log(chalk.yellow('尚未创建任何 IT'));
|
|
149
|
+
console.log('运行: prd it create <名称>');
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const its = await fs.readdir(itDir);
|
|
154
|
+
const itFolders = its.filter(name => name.startsWith('IT-'));
|
|
155
|
+
|
|
156
|
+
if (itFolders.length === 0) {
|
|
157
|
+
console.log(chalk.yellow('尚未创建任何 IT'));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(chalk.bold.cyan(`\n=== 当前迭代 IT 列表 ( 共 ${itFolders.length} 个 ) ===\n`));
|
|
162
|
+
|
|
163
|
+
// 模板文件用于对比
|
|
164
|
+
const bizTemplatePath = path.join(__dirname, '../templates/it-biz.md');
|
|
165
|
+
const devTemplatePath = path.join(__dirname, '../templates/it-dev.md');
|
|
166
|
+
const bizTemplate = await fs.readFile(bizTemplatePath, 'utf-8');
|
|
167
|
+
const devTemplate = await fs.readFile(devTemplatePath, 'utf-8');
|
|
168
|
+
// 提取模板特征(用于简单判断是否修改)
|
|
169
|
+
const bizFeature = "### 1. 用户故事";
|
|
170
|
+
const devFeature = "### 1.1 用户故事";
|
|
171
|
+
|
|
172
|
+
for (const itFolder of itFolders) {
|
|
173
|
+
const itPath = path.join(itDir, itFolder);
|
|
174
|
+
const itId = itFolder.split('-').slice(0, 2).join('-');
|
|
175
|
+
const bizPath = path.join(itPath, `${itId}-BIZ.md`);
|
|
176
|
+
const devPath = path.join(itPath, `${itId}-DEV.md`);
|
|
177
|
+
|
|
178
|
+
const hasBiz = await fs.pathExists(bizPath);
|
|
179
|
+
const hasDev = await fs.pathExists(devPath);
|
|
180
|
+
|
|
181
|
+
let bizStatus = chalk.gray('缺失');
|
|
182
|
+
let devStatus = chalk.gray('缺失');
|
|
183
|
+
|
|
184
|
+
if (hasBiz) {
|
|
185
|
+
const content = await fs.readFile(bizPath, 'utf-8');
|
|
186
|
+
// 简单判断:如果内容长度比模板由明显变化,或者关键部分被修改
|
|
187
|
+
// 这里用简单逻辑:只要文件存在且不仅仅是模板替换后的初始状态
|
|
188
|
+
// 更好的方式是检查是否有 "[用户角色]" 这样的占位符
|
|
189
|
+
const isDefault = content.includes('[用户角色]');
|
|
190
|
+
bizStatus = isDefault ? chalk.yellow('待填写') : chalk.green('已填写');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (hasDev) {
|
|
194
|
+
const content = await fs.readFile(devPath, 'utf-8');
|
|
195
|
+
const isDefault = content.includes('<!-- 从 BIZ 复制 -->');
|
|
196
|
+
devStatus = isDefault ? chalk.yellow('待填写') : chalk.green('已填写');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log(chalk.bold(`${itFolder}`));
|
|
200
|
+
console.log(` BIZ: ${bizStatus}`);
|
|
201
|
+
console.log(` DEV: ${devStatus}`);
|
|
202
|
+
console.log(chalk.gray('-'.repeat(40)));
|
|
203
|
+
}
|
|
204
|
+
console.log('');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function showIT(config, idOrName, options = {}) {
|
|
208
|
+
if (!idOrName) {
|
|
209
|
+
console.log(chalk.red('✗ 请提供 IT 编号或名称'));
|
|
210
|
+
console.log('示例: prd it show 001');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (config.currentIteration === 0) {
|
|
215
|
+
console.log(chalk.red('✗ 尚未创建迭代'));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const iterationDir = path.join(
|
|
220
|
+
process.cwd(),
|
|
221
|
+
'02_迭代记录',
|
|
222
|
+
`第${String(config.currentIteration).padStart(2, '0')}轮迭代`
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
const itDir = path.join(iterationDir, 'IT');
|
|
226
|
+
|
|
227
|
+
if (!await fs.pathExists(itDir)) {
|
|
228
|
+
console.log(chalk.yellow('尚未创建任何 IT'));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const its = await fs.readdir(itDir);
|
|
233
|
+
const targetIT = its.find(name =>
|
|
234
|
+
name.includes(idOrName) || name.startsWith(`IT-${idOrName}`)
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (!targetIT) {
|
|
238
|
+
console.log(chalk.red(`✗ 未找到 IT: ${idOrName}`));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const itPath = path.join(itDir, targetIT);
|
|
243
|
+
console.log(chalk.bold.cyan(`\n=== ${targetIT} ===\n`));
|
|
244
|
+
|
|
245
|
+
// BIZ 文件信息
|
|
246
|
+
const itId = targetIT.split('-').slice(0, 2).join('-');
|
|
247
|
+
const bizFileName = `${itId}-BIZ.md`;
|
|
248
|
+
const bizPath = path.join(itPath, bizFileName);
|
|
249
|
+
|
|
250
|
+
if (await fs.pathExists(bizPath)) {
|
|
251
|
+
const content = await fs.readFile(bizPath, 'utf-8');
|
|
252
|
+
const isDefault = content.includes('[用户角色]');
|
|
253
|
+
const status = isDefault ? chalk.yellow('待填写') : chalk.green('已填写');
|
|
254
|
+
console.log(`📄 ${chalk.bold('BIZ 业务需求')} (${bizFileName})`);
|
|
255
|
+
console.log(` 状态: ${status}`);
|
|
256
|
+
console.log(` 路径: ${bizPath}`);
|
|
257
|
+
} else {
|
|
258
|
+
console.log(`📄 ${chalk.bold('BIZ 业务需求')} (${bizFileName})`);
|
|
259
|
+
console.log(` 状态: ${chalk.red('缺失')}`);
|
|
260
|
+
}
|
|
261
|
+
console.log('');
|
|
262
|
+
|
|
263
|
+
// DEV 文件信息
|
|
264
|
+
const devFileName = `${itId}-DEV.md`;
|
|
265
|
+
const devPath = path.join(itPath, devFileName);
|
|
266
|
+
|
|
267
|
+
if (await fs.pathExists(devPath)) {
|
|
268
|
+
const content = await fs.readFile(devPath, 'utf-8');
|
|
269
|
+
const isDefault = content.includes('<!-- 从 BIZ 复制 -->');
|
|
270
|
+
const status = isDefault ? chalk.yellow('待填写') : chalk.green('已填写');
|
|
271
|
+
console.log(`🛠️ ${chalk.bold('DEV 功能规格')} (${devFileName})`);
|
|
272
|
+
console.log(` 状态: ${status}`);
|
|
273
|
+
console.log(` 路径: ${devPath}`);
|
|
274
|
+
} else {
|
|
275
|
+
console.log(`🛠️ ${chalk.bold('DEV 功能规格')} (${devFileName})`);
|
|
276
|
+
console.log(` 状态: ${chalk.red('缺失')}`);
|
|
277
|
+
}
|
|
278
|
+
console.log('');
|
|
279
|
+
|
|
280
|
+
// 操作提示
|
|
281
|
+
console.log(chalk.gray('-'.repeat(40)));
|
|
282
|
+
console.log(chalk.bold('提示:'));
|
|
283
|
+
console.log(`- 编辑业务需求: code "${bizPath}"`);
|
|
284
|
+
console.log(`- 编辑开发规格: code "${devPath}"`);
|
|
285
|
+
console.log('');
|
|
286
|
+
}
|