@bicorne/task-flow 0.1.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/LICENSE +21 -0
- package/README.md +252 -0
- package/SKILL.md +250 -0
- package/assets/.harnessrc +10 -0
- package/assets/PHASE-task.json.example +50 -0
- package/assets/design.md +69 -0
- package/assets/hooks.json +15 -0
- package/assets/product-requirement.md +82 -0
- package/assets/schema.json +127 -0
- package/assets/tasks.md +26 -0
- package/dist/commands/analyze.d.ts +32 -0
- package/dist/commands/analyze.js +338 -0
- package/dist/commands/archive.d.ts +11 -0
- package/dist/commands/archive.js +53 -0
- package/dist/commands/design.d.ts +38 -0
- package/dist/commands/design.js +492 -0
- package/dist/commands/extract.d.ts +31 -0
- package/dist/commands/extract.js +477 -0
- package/dist/commands/init.d.ts +24 -0
- package/dist/commands/init.js +165 -0
- package/dist/commands/merge/index.d.ts +17 -0
- package/dist/commands/merge/index.js +322 -0
- package/dist/commands/merge/merger.d.ts +18 -0
- package/dist/commands/merge/merger.js +151 -0
- package/dist/commands/merge/types.d.ts +67 -0
- package/dist/commands/merge/types.js +6 -0
- package/dist/commands/merge/validators.d.ts +14 -0
- package/dist/commands/merge/validators.js +147 -0
- package/dist/commands/merge.d.ts +7 -0
- package/dist/commands/merge.js +15 -0
- package/dist/commands/start.d.ts +32 -0
- package/dist/commands/start.js +265 -0
- package/dist/commands/status.d.ts +15 -0
- package/dist/commands/status.js +143 -0
- package/dist/commands/sync.d.ts +11 -0
- package/dist/commands/sync.js +58 -0
- package/dist/commands/tasks-gen/doc-parser.d.ts +7 -0
- package/dist/commands/tasks-gen/doc-parser.js +259 -0
- package/dist/commands/tasks-gen/generators.d.ts +33 -0
- package/dist/commands/tasks-gen/generators.js +141 -0
- package/dist/commands/tasks-gen/index.d.ts +30 -0
- package/dist/commands/tasks-gen/index.js +345 -0
- package/dist/commands/tasks-gen/parsers.d.ts +29 -0
- package/dist/commands/tasks-gen/parsers.js +272 -0
- package/dist/commands/tasks-gen/templates.d.ts +8 -0
- package/dist/commands/tasks-gen/templates.js +37 -0
- package/dist/commands/tasks-gen/types.d.ts +71 -0
- package/dist/commands/tasks-gen/types.js +17 -0
- package/dist/commands/tasks-gen/validators.d.ts +14 -0
- package/dist/commands/tasks-gen/validators.js +54 -0
- package/dist/commands/tasks.d.ts +9 -0
- package/dist/commands/tasks.js +22 -0
- package/dist/commands/worktree.d.ts +28 -0
- package/dist/commands/worktree.js +275 -0
- package/dist/hooks/check-prd-exists.d.ts +20 -0
- package/dist/hooks/check-prd-exists.js +61 -0
- package/dist/hooks/check-worktree-conflict.d.ts +34 -0
- package/dist/hooks/check-worktree-conflict.js +107 -0
- package/dist/hooks/hook-runner/executor.d.ts +18 -0
- package/dist/hooks/hook-runner/executor.js +143 -0
- package/dist/hooks/hook-runner/index.d.ts +64 -0
- package/dist/hooks/hook-runner/index.js +220 -0
- package/dist/hooks/hook-runner/loader.d.ts +23 -0
- package/dist/hooks/hook-runner/loader.js +126 -0
- package/dist/hooks/hook-runner/types.d.ts +59 -0
- package/dist/hooks/hook-runner/types.js +6 -0
- package/dist/hooks/hook-runner.d.ts +9 -0
- package/dist/hooks/hook-runner.js +30 -0
- package/dist/hooks/phase-complete-detector.d.ts +35 -0
- package/dist/hooks/phase-complete-detector.js +203 -0
- package/dist/hooks/phase-gate-validator.d.ts +76 -0
- package/dist/hooks/phase-gate-validator.js +407 -0
- package/dist/hooks/save-checkpoint.d.ts +43 -0
- package/dist/hooks/save-checkpoint.js +144 -0
- package/dist/hooks/start-mcp-servers.d.ts +50 -0
- package/dist/hooks/start-mcp-servers.js +75 -0
- package/dist/hooks/stop-mcp-servers.d.ts +40 -0
- package/dist/hooks/stop-mcp-servers.js +58 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +238 -0
- package/dist/lib/archive.d.ts +31 -0
- package/dist/lib/archive.js +226 -0
- package/dist/lib/config.d.ts +93 -0
- package/dist/lib/config.js +251 -0
- package/dist/lib/constants.d.ts +222 -0
- package/dist/lib/constants.js +247 -0
- package/dist/lib/interactive.d.ts +31 -0
- package/dist/lib/interactive.js +166 -0
- package/dist/lib/mcp-client.d.ts +156 -0
- package/dist/lib/mcp-client.js +370 -0
- package/dist/lib/state.d.ts +119 -0
- package/dist/lib/state.js +293 -0
- package/dist/slash/executor.d.ts +22 -0
- package/dist/slash/executor.js +259 -0
- package/dist/slash/index.d.ts +11 -0
- package/dist/slash/index.js +45 -0
- package/dist/slash/parser.d.ts +24 -0
- package/dist/slash/parser.js +101 -0
- package/dist/slash/registry.d.ts +22 -0
- package/dist/slash/registry.js +155 -0
- package/dist/spec/openspec-to-task/builders.d.ts +107 -0
- package/dist/spec/openspec-to-task/builders.js +138 -0
- package/dist/spec/openspec-to-task/index.d.ts +20 -0
- package/dist/spec/openspec-to-task/index.js +182 -0
- package/dist/spec/openspec-to-task/parsers.d.ts +65 -0
- package/dist/spec/openspec-to-task/parsers.js +232 -0
- package/dist/spec/openspec-to-task/types.d.ts +49 -0
- package/dist/spec/openspec-to-task/types.js +6 -0
- package/dist/spec/sync-openspec-to-task.d.ts +7 -0
- package/dist/spec/sync-openspec-to-task.js +21 -0
- package/dist/spec/sync-task-to-openspec.d.ts +27 -0
- package/dist/spec/sync-task-to-openspec.js +288 -0
- package/dist/types/ai-context.d.ts +108 -0
- package/dist/types/ai-context.js +9 -0
- package/package.json +66 -0
- package/references/AI-CONVERSATION-TUTORIAL.md +270 -0
- package/references/CLI-TUTORIAL.md +447 -0
- package/references/GIT-WORKTREE-SOP.md +109 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/design.ts
|
|
3
|
+
* Generate technical specification from PRD document
|
|
4
|
+
*
|
|
5
|
+
* This command is OPTIONAL - use it when:
|
|
6
|
+
* - The feature requires special technology choices
|
|
7
|
+
* - Complex architecture decisions are needed
|
|
8
|
+
* - Technical risks need to be documented
|
|
9
|
+
*/
|
|
10
|
+
export interface DesignOptions {
|
|
11
|
+
cwd?: string;
|
|
12
|
+
change?: string;
|
|
13
|
+
force?: boolean;
|
|
14
|
+
input?: string;
|
|
15
|
+
context?: string;
|
|
16
|
+
contextFile?: string;
|
|
17
|
+
skipPrompt?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface DesignResult {
|
|
20
|
+
success: boolean;
|
|
21
|
+
reason?: string;
|
|
22
|
+
message?: string;
|
|
23
|
+
changeName?: string;
|
|
24
|
+
changeDir?: string;
|
|
25
|
+
designPath?: string;
|
|
26
|
+
prdInfo?: Record<string, unknown>;
|
|
27
|
+
needsContext?: boolean;
|
|
28
|
+
contextPrompt?: string[];
|
|
29
|
+
}
|
|
30
|
+
declare function extractPRDInfo(prdContent: string): Record<string, unknown>;
|
|
31
|
+
export declare function generateDesign(options?: DesignOptions): Promise<DesignResult>;
|
|
32
|
+
export declare function main(): Promise<void>;
|
|
33
|
+
declare const _default: {
|
|
34
|
+
generateDesign: typeof generateDesign;
|
|
35
|
+
extractPRDInfo: typeof extractPRDInfo;
|
|
36
|
+
main: typeof main;
|
|
37
|
+
};
|
|
38
|
+
export default _default;
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* commands/design.ts
|
|
4
|
+
* Generate technical specification from PRD document
|
|
5
|
+
*
|
|
6
|
+
* This command is OPTIONAL - use it when:
|
|
7
|
+
* - The feature requires special technology choices
|
|
8
|
+
* - Complex architecture decisions are needed
|
|
9
|
+
* - Technical risks need to be documented
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
28
|
+
var ownKeys = function(o) {
|
|
29
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
30
|
+
var ar = [];
|
|
31
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
32
|
+
return ar;
|
|
33
|
+
};
|
|
34
|
+
return ownKeys(o);
|
|
35
|
+
};
|
|
36
|
+
return function (mod) {
|
|
37
|
+
if (mod && mod.__esModule) return mod;
|
|
38
|
+
var result = {};
|
|
39
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
40
|
+
__setModuleDefault(result, mod);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
})();
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.generateDesign = generateDesign;
|
|
46
|
+
exports.main = main;
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const config_1 = require("../lib/config");
|
|
50
|
+
const interactive_1 = require("../lib/interactive");
|
|
51
|
+
function parseArgs(argv) {
|
|
52
|
+
const args = {};
|
|
53
|
+
for (let i = 2; i < argv.length; i += 1) {
|
|
54
|
+
const token = argv[i] || '';
|
|
55
|
+
if (!token.startsWith('--')) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const key = token.slice(2);
|
|
59
|
+
const next = argv[i + 1] || '';
|
|
60
|
+
if (!next || next.startsWith('--')) {
|
|
61
|
+
args[key] = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
args[key] = next;
|
|
65
|
+
i += 1;
|
|
66
|
+
}
|
|
67
|
+
return args;
|
|
68
|
+
}
|
|
69
|
+
function loadTemplate() {
|
|
70
|
+
const templatePath = path.resolve(__dirname, '../../assets/design.md');
|
|
71
|
+
if (fs.existsSync(templatePath)) {
|
|
72
|
+
return fs.readFileSync(templatePath, 'utf8');
|
|
73
|
+
}
|
|
74
|
+
return `# 技术规格文档
|
|
75
|
+
|
|
76
|
+
## 文档基础信息
|
|
77
|
+
|
|
78
|
+
- **需求名称**: [待填写]
|
|
79
|
+
- **文档状态**: 草稿
|
|
80
|
+
- **技术负责人**: [待填写]
|
|
81
|
+
- **修订历史**:
|
|
82
|
+
- [日期] - v1.0 - 初始版本
|
|
83
|
+
|
|
84
|
+
## 技术选型
|
|
85
|
+
|
|
86
|
+
### 核心技术栈
|
|
87
|
+
<!-- 列出本次需求涉及的核心技术及选型理由 -->
|
|
88
|
+
|
|
89
|
+
### 新增依赖
|
|
90
|
+
<!-- 列出需要引入的新依赖库及其用途 -->
|
|
91
|
+
|
|
92
|
+
### 技术决策
|
|
93
|
+
<!-- 记录关键技术决策及其权衡 -->
|
|
94
|
+
|
|
95
|
+
## 架构设计
|
|
96
|
+
|
|
97
|
+
### 系统架构图
|
|
98
|
+
<!-- 提供系统架构图,展示各组件关系 -->
|
|
99
|
+
|
|
100
|
+
### 模块划分
|
|
101
|
+
<!-- 描述模块划分及职责边界 -->
|
|
102
|
+
|
|
103
|
+
### 数据流向
|
|
104
|
+
<!-- 描述核心数据流转路径 -->
|
|
105
|
+
|
|
106
|
+
## 接口设计
|
|
107
|
+
|
|
108
|
+
### API 接口
|
|
109
|
+
<!-- 列出需要新增或修改的 API 接口 -->
|
|
110
|
+
|
|
111
|
+
### 数据格式
|
|
112
|
+
<!-- 定义请求和响应的数据格式 -->
|
|
113
|
+
|
|
114
|
+
### 错误处理
|
|
115
|
+
<!-- 定义错误码和错误处理策略 -->
|
|
116
|
+
|
|
117
|
+
## 数据模型
|
|
118
|
+
|
|
119
|
+
### 数据库设计
|
|
120
|
+
<!-- 描述数据库表结构设计 -->
|
|
121
|
+
|
|
122
|
+
### 数据关系
|
|
123
|
+
<!-- 描述实体间的关系 -->
|
|
124
|
+
|
|
125
|
+
### 数据迁移
|
|
126
|
+
<!-- 描述数据迁移策略 -->
|
|
127
|
+
|
|
128
|
+
## 技术风险与缓解
|
|
129
|
+
|
|
130
|
+
### 已识别风险
|
|
131
|
+
<!-- 列出技术实现中的潜在风险 -->
|
|
132
|
+
|
|
133
|
+
### 缓解措施
|
|
134
|
+
<!-- 针对每个风险的缓解方案 -->
|
|
135
|
+
|
|
136
|
+
## 性能与扩展
|
|
137
|
+
|
|
138
|
+
### 性能要求
|
|
139
|
+
<!-- 描述性能指标和优化策略 -->
|
|
140
|
+
|
|
141
|
+
### 扩展性设计
|
|
142
|
+
<!-- 描述如何支持未来的扩展需求 -->
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
function toChangeName(input) {
|
|
146
|
+
const sanitized = String(input || '')
|
|
147
|
+
.trim()
|
|
148
|
+
.replace(/[^a-zA-Z0-9-_]/g, '-')
|
|
149
|
+
.replace(/-+/g, '-')
|
|
150
|
+
.replace(/^-|-$/g, '');
|
|
151
|
+
if (sanitized.includes('..') || sanitized.startsWith('/')) {
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
return sanitized;
|
|
155
|
+
}
|
|
156
|
+
function validateConfig(config) {
|
|
157
|
+
if (!config.projectRoot) {
|
|
158
|
+
return {
|
|
159
|
+
valid: false,
|
|
160
|
+
reason: 'missing-project-root',
|
|
161
|
+
message: 'Config missing projectRoot',
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
return { valid: true };
|
|
165
|
+
}
|
|
166
|
+
function extractPRDInfo(prdContent) {
|
|
167
|
+
const lines = prdContent.split(/\r?\n/);
|
|
168
|
+
const info = {
|
|
169
|
+
title: '',
|
|
170
|
+
background: '',
|
|
171
|
+
goals: [],
|
|
172
|
+
scope: [],
|
|
173
|
+
};
|
|
174
|
+
let currentSection = '';
|
|
175
|
+
let currentContent = [];
|
|
176
|
+
for (const line of lines) {
|
|
177
|
+
if (line.startsWith('# ')) {
|
|
178
|
+
info.title = line.replace(/^#\s+/, '').trim();
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (line.startsWith('## ')) {
|
|
182
|
+
if (currentSection && currentContent.length > 0) {
|
|
183
|
+
info[currentSection] = currentContent.join('\n').trim();
|
|
184
|
+
}
|
|
185
|
+
currentSection = line.replace(/^##\s+/, '').trim().toLowerCase().replace(/\s+/g, '_');
|
|
186
|
+
currentContent = [];
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (currentSection) {
|
|
190
|
+
currentContent.push(line);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (currentSection && currentContent.length > 0) {
|
|
194
|
+
info[currentSection] = currentContent.join('\n').trim();
|
|
195
|
+
}
|
|
196
|
+
return info;
|
|
197
|
+
}
|
|
198
|
+
function extractContextInfo(context) {
|
|
199
|
+
const info = {};
|
|
200
|
+
const patterns = {
|
|
201
|
+
techStack: /技术栈?[::]\s*(.+)/i,
|
|
202
|
+
framework: /框架?[::]\s*(.+)/i,
|
|
203
|
+
library: /库?[::]\s*(.+)/i,
|
|
204
|
+
performance: /性能?[::]\s*(.+)/i,
|
|
205
|
+
architecture: /架构?[::]\s*(.+)/i,
|
|
206
|
+
constraint: /约束?[::]\s*(.+)/i,
|
|
207
|
+
requirement: /要求?[::]\s*(.+)/i,
|
|
208
|
+
};
|
|
209
|
+
for (const [key, pattern] of Object.entries(patterns)) {
|
|
210
|
+
const match = context.match(pattern);
|
|
211
|
+
if (match && match[1]) {
|
|
212
|
+
info[key] = match[1].trim();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return info;
|
|
216
|
+
}
|
|
217
|
+
function readStdin() {
|
|
218
|
+
return new Promise((resolve, reject) => {
|
|
219
|
+
const chunks = [];
|
|
220
|
+
process.stdin.setEncoding('utf8');
|
|
221
|
+
process.stdin.on('readable', () => {
|
|
222
|
+
let chunk;
|
|
223
|
+
while ((chunk = process.stdin.read()) !== null) {
|
|
224
|
+
chunks.push(chunk);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
process.stdin.on('end', () => {
|
|
228
|
+
resolve(chunks.join(''));
|
|
229
|
+
});
|
|
230
|
+
process.stdin.on('error', (error) => {
|
|
231
|
+
reject(error);
|
|
232
|
+
});
|
|
233
|
+
setTimeout(() => {
|
|
234
|
+
if (chunks.length === 0) {
|
|
235
|
+
reject(new Error('Stdin timeout: no data received'));
|
|
236
|
+
}
|
|
237
|
+
}, 5000);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function checkMissingContext(contextInfo) {
|
|
241
|
+
const prompts = [];
|
|
242
|
+
if (!contextInfo.techStack && !contextInfo.framework) {
|
|
243
|
+
prompts.push('Technology stack preferences (e.g., React, Vue, Node.js)');
|
|
244
|
+
}
|
|
245
|
+
if (!contextInfo.architecture) {
|
|
246
|
+
prompts.push('Architecture constraints (e.g., microservices, monolith)');
|
|
247
|
+
}
|
|
248
|
+
if (!contextInfo.performance) {
|
|
249
|
+
prompts.push('Performance requirements (e.g., response time < 100ms)');
|
|
250
|
+
}
|
|
251
|
+
if (!contextInfo.risks) {
|
|
252
|
+
prompts.push('Known technical risks');
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
needsInfo: prompts.length > 0,
|
|
256
|
+
prompts,
|
|
257
|
+
message: prompts.length > 0
|
|
258
|
+
? `Missing context information. Please provide via --context or --context-file:\n${prompts.map((p, i) => ` ${i + 1}. ${p}`).join('\n')}`
|
|
259
|
+
: '',
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async function promptForMissingInfo() {
|
|
263
|
+
console.log('\n[DESIGN] No valid context information provided.');
|
|
264
|
+
console.log('[DESIGN] Please provide additional technical design information:\n');
|
|
265
|
+
const techStack = await (0, interactive_1.input)({
|
|
266
|
+
message: 'Technology stack preferences (optional)',
|
|
267
|
+
default: '',
|
|
268
|
+
});
|
|
269
|
+
const architecture = await (0, interactive_1.input)({
|
|
270
|
+
message: 'Architecture constraints (optional)',
|
|
271
|
+
default: '',
|
|
272
|
+
});
|
|
273
|
+
const performance = await (0, interactive_1.input)({
|
|
274
|
+
message: 'Performance requirements (optional)',
|
|
275
|
+
default: '',
|
|
276
|
+
});
|
|
277
|
+
const risks = await (0, interactive_1.input)({
|
|
278
|
+
message: 'Known technical risks (optional)',
|
|
279
|
+
default: '',
|
|
280
|
+
});
|
|
281
|
+
const parts = [];
|
|
282
|
+
if (techStack) {
|
|
283
|
+
parts.push(`技术栈: ${techStack}`);
|
|
284
|
+
}
|
|
285
|
+
if (architecture) {
|
|
286
|
+
parts.push(`架构: ${architecture}`);
|
|
287
|
+
}
|
|
288
|
+
if (performance) {
|
|
289
|
+
parts.push(`性能: ${performance}`);
|
|
290
|
+
}
|
|
291
|
+
if (risks) {
|
|
292
|
+
parts.push(`风险: ${risks}`);
|
|
293
|
+
}
|
|
294
|
+
return parts.join('\n');
|
|
295
|
+
}
|
|
296
|
+
async function generateDesign(options = {}) {
|
|
297
|
+
const config = (0, config_1.loadConfig)({ cwd: options.cwd });
|
|
298
|
+
const changeName = toChangeName(options.change);
|
|
299
|
+
if (!changeName) {
|
|
300
|
+
return {
|
|
301
|
+
success: false,
|
|
302
|
+
reason: 'missing-change',
|
|
303
|
+
message: 'Missing required argument: --change <change-name>',
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
const configValidation = validateConfig(config);
|
|
307
|
+
if (!configValidation.valid) {
|
|
308
|
+
return {
|
|
309
|
+
success: false,
|
|
310
|
+
reason: configValidation.reason,
|
|
311
|
+
message: configValidation.message || 'Unknown config validation error',
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const projectRoot = config.projectRoot || process.cwd();
|
|
315
|
+
const specRoot = config.specRootAbs || path.resolve(projectRoot, 'spec');
|
|
316
|
+
const changeDir = path.resolve(specRoot, 'changes', changeName);
|
|
317
|
+
const prdPath = path.resolve(changeDir, 'product-requirement.md');
|
|
318
|
+
const designPath = path.resolve(changeDir, 'design.md');
|
|
319
|
+
if (!fs.existsSync(prdPath)) {
|
|
320
|
+
return {
|
|
321
|
+
success: false,
|
|
322
|
+
reason: 'prd-not-found',
|
|
323
|
+
message: `product-requirement.md not found. Please create it first at: ${prdPath}`,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const prdContent = fs.readFileSync(prdPath, 'utf8');
|
|
327
|
+
const prdInfo = extractPRDInfo(prdContent);
|
|
328
|
+
let contextData;
|
|
329
|
+
if (options.contextFile) {
|
|
330
|
+
try {
|
|
331
|
+
const contextContent = fs.readFileSync(options.contextFile, 'utf8');
|
|
332
|
+
contextData = JSON.parse(contextContent);
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
336
|
+
return {
|
|
337
|
+
success: false,
|
|
338
|
+
reason: 'invalid-context-file',
|
|
339
|
+
message: `Failed to parse context file: ${errorMessage}`,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
let contextInfo = {};
|
|
344
|
+
let additionalContext = '';
|
|
345
|
+
let aiInput = options.input;
|
|
346
|
+
if (!aiInput && !process.stdin.isTTY && require.main === module) {
|
|
347
|
+
try {
|
|
348
|
+
const stdinContent = await readStdin();
|
|
349
|
+
if (stdinContent.trim()) {
|
|
350
|
+
aiInput = stdinContent;
|
|
351
|
+
console.log(`[DESIGN] Received ${stdinContent.length} bytes from stdin`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
356
|
+
console.warn(`[DESIGN] Warning: Failed to read stdin: ${errorMessage}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (options.context) {
|
|
360
|
+
contextInfo = extractContextInfo(options.context);
|
|
361
|
+
}
|
|
362
|
+
if (contextData?.project.techStack) {
|
|
363
|
+
const ts = contextData.project.techStack;
|
|
364
|
+
if (ts.language) {
|
|
365
|
+
contextInfo.language = ts.language;
|
|
366
|
+
}
|
|
367
|
+
if (ts.framework) {
|
|
368
|
+
contextInfo.framework = ts.framework;
|
|
369
|
+
}
|
|
370
|
+
if (ts.runtime) {
|
|
371
|
+
contextInfo.architecture = ts.runtime;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (contextData?.analysis?.architectureSummary) {
|
|
375
|
+
contextInfo.architecture = contextData.analysis.architectureSummary;
|
|
376
|
+
}
|
|
377
|
+
if (contextData?.task?.constraints && contextData.task.constraints.length > 0) {
|
|
378
|
+
contextInfo.constraint = contextData.task.constraints.join('; ');
|
|
379
|
+
}
|
|
380
|
+
const hasValidContext = Object.keys(contextInfo).length > 0;
|
|
381
|
+
const isTTY = process.stdin.isTTY;
|
|
382
|
+
const isMainModule = require.main === module;
|
|
383
|
+
const shouldPrompt = !options.skipPrompt && isMainModule && isTTY;
|
|
384
|
+
if (!aiInput && !hasValidContext && !options.input) {
|
|
385
|
+
if (shouldPrompt) {
|
|
386
|
+
additionalContext = await promptForMissingInfo();
|
|
387
|
+
if (additionalContext) {
|
|
388
|
+
contextInfo = extractContextInfo(additionalContext);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else if (!options.skipPrompt) {
|
|
392
|
+
const missing = checkMissingContext(contextInfo);
|
|
393
|
+
return {
|
|
394
|
+
success: false,
|
|
395
|
+
reason: 'missing-context',
|
|
396
|
+
message: missing.message,
|
|
397
|
+
needsContext: true,
|
|
398
|
+
contextPrompt: missing.prompts,
|
|
399
|
+
changeName,
|
|
400
|
+
changeDir,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
let designContent;
|
|
405
|
+
if (aiInput) {
|
|
406
|
+
designContent = aiInput;
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
designContent = loadTemplate();
|
|
410
|
+
if (prdInfo.title) {
|
|
411
|
+
designContent = designContent.replace('- **需求名称**: [待填写]', `- **需求名称**: ${prdInfo.title}`);
|
|
412
|
+
}
|
|
413
|
+
if (contextInfo.techStack) {
|
|
414
|
+
const techSection = `### 核心技术栈\n${contextInfo.techStack}`;
|
|
415
|
+
designContent = designContent.replace('<!-- 列出本次需求涉及的核心技术及选型理由 -->', `${techSection}\n<!-- 列出本次需求涉及的核心技术及选型理由 -->`);
|
|
416
|
+
}
|
|
417
|
+
if (contextInfo.architecture) {
|
|
418
|
+
const archSection = `### 架构约束\n${contextInfo.architecture}`;
|
|
419
|
+
designContent = designContent.replace('<!-- 描述模块划分及职责边界 -->', `${archSection}\n<!-- 描述模块划分及职责边界 -->`);
|
|
420
|
+
}
|
|
421
|
+
if (contextInfo.performance) {
|
|
422
|
+
const perfSection = `### 性能要求\n${contextInfo.performance}`;
|
|
423
|
+
designContent = designContent.replace('<!-- 描述性能指标和优化策略 -->', `${perfSection}\n<!-- 描述性能指标和优化策略 -->`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (fs.existsSync(designPath) && !options.force) {
|
|
427
|
+
return {
|
|
428
|
+
success: false,
|
|
429
|
+
reason: 'already-exists',
|
|
430
|
+
message: `design.md already exists: ${designPath}. Use --force to overwrite.`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
fs.writeFileSync(designPath, designContent, 'utf8');
|
|
435
|
+
}
|
|
436
|
+
catch (error) {
|
|
437
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
438
|
+
return {
|
|
439
|
+
success: false,
|
|
440
|
+
reason: 'file-write-error',
|
|
441
|
+
message: `Failed to write design.md: ${errorMessage}`,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
success: true,
|
|
446
|
+
changeName,
|
|
447
|
+
changeDir,
|
|
448
|
+
designPath,
|
|
449
|
+
prdInfo,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
async function main() {
|
|
453
|
+
const args = parseArgs(process.argv);
|
|
454
|
+
const result = await generateDesign({
|
|
455
|
+
cwd: args.cwd,
|
|
456
|
+
change: args.change,
|
|
457
|
+
force: args.force === true,
|
|
458
|
+
input: args.input,
|
|
459
|
+
context: args.context,
|
|
460
|
+
contextFile: args['context-file'],
|
|
461
|
+
skipPrompt: args['skip-prompt'] === true,
|
|
462
|
+
});
|
|
463
|
+
if (!result.success) {
|
|
464
|
+
console.error(`[DESIGN] ${result.message}`);
|
|
465
|
+
if (result.reason === 'missing-context' && result.contextPrompt) {
|
|
466
|
+
console.error('\n[DESIGN] To provide context, use one of:');
|
|
467
|
+
console.error(' --context "技术栈: React, 架构: 微前端"');
|
|
468
|
+
console.error(' --context-file /path/to/context.json');
|
|
469
|
+
console.error(' --skip-prompt (generate template without context)');
|
|
470
|
+
}
|
|
471
|
+
process.exit(1);
|
|
472
|
+
}
|
|
473
|
+
console.log('');
|
|
474
|
+
console.log('[DESIGN] Technical specification generated successfully!');
|
|
475
|
+
console.log(` Change Name: ${result.changeName}`);
|
|
476
|
+
console.log(` Directory: ${result.changeDir}`);
|
|
477
|
+
console.log(` Design: ${result.designPath}`);
|
|
478
|
+
console.log('');
|
|
479
|
+
console.log('Next steps:');
|
|
480
|
+
console.log(' 1. Review and edit design.md');
|
|
481
|
+
console.log(' 2. Run: task-flow tasks --change <change-name>');
|
|
482
|
+
console.log('');
|
|
483
|
+
process.exit(0);
|
|
484
|
+
}
|
|
485
|
+
if (require.main === module) {
|
|
486
|
+
main();
|
|
487
|
+
}
|
|
488
|
+
exports.default = {
|
|
489
|
+
generateDesign,
|
|
490
|
+
extractPRDInfo,
|
|
491
|
+
main,
|
|
492
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commands/extract.ts
|
|
3
|
+
* Extract project requirements and generate PRD document
|
|
4
|
+
*
|
|
5
|
+
* This command is AI-only - use it to summarize existing projects
|
|
6
|
+
* and generate structured Product Requirement Documents.
|
|
7
|
+
*/
|
|
8
|
+
export interface ExtractOptions {
|
|
9
|
+
cwd?: string;
|
|
10
|
+
change?: string;
|
|
11
|
+
type?: string;
|
|
12
|
+
priority?: string;
|
|
13
|
+
force?: boolean;
|
|
14
|
+
input?: string;
|
|
15
|
+
contextFile?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface ExtractResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
reason?: string;
|
|
20
|
+
message?: string;
|
|
21
|
+
changeName?: string;
|
|
22
|
+
changeDir?: string;
|
|
23
|
+
prdPath?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function extract(options?: ExtractOptions): Promise<ExtractResult>;
|
|
26
|
+
export declare function main(): Promise<void>;
|
|
27
|
+
declare const _default: {
|
|
28
|
+
extract: typeof extract;
|
|
29
|
+
main: typeof main;
|
|
30
|
+
};
|
|
31
|
+
export default _default;
|