speccore 5.19.2 → 5.22.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/README.en.md +63 -210
- package/README.md +108 -252
- package/dist/cli.js +224 -300
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts +5 -0
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +235 -543
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/ask.d.ts +8 -0
- package/dist/commands/ask.d.ts.map +1 -0
- package/dist/commands/ask.js +293 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/bugfix.d.ts +4 -0
- package/dist/commands/bugfix.d.ts.map +1 -1
- package/dist/commands/bugfix.js +122 -2
- package/dist/commands/bugfix.js.map +1 -1
- package/dist/commands/change.d.ts +2 -0
- package/dist/commands/change.d.ts.map +1 -1
- package/dist/commands/change.js +37 -2
- package/dist/commands/change.js.map +1 -1
- package/dist/commands/completion.js +1 -1
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/dev.d.ts +2 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +175 -1
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/doc2spec.d.ts +10 -0
- package/dist/commands/doc2spec.d.ts.map +1 -0
- package/dist/commands/{word2spec.js → doc2spec.js} +55 -19
- package/dist/commands/doc2spec.js.map +1 -0
- package/dist/commands/done.d.ts.map +1 -1
- package/dist/commands/done.js +47 -0
- package/dist/commands/done.js.map +1 -1
- package/dist/commands/execute.d.ts +1 -0
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +118 -11
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/help-quick.js +2 -2
- package/dist/commands/help-quick.js.map +1 -1
- package/dist/commands/help.d.ts +3 -2
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +372 -51
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/import.d.ts +1 -0
- package/dist/commands/import.d.ts.map +1 -1
- package/dist/commands/import.js +289 -15
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +0 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +177 -283
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/create.d.ts +1 -0
- package/dist/commands/iteration/create.d.ts.map +1 -1
- package/dist/commands/iteration/create.js +1 -0
- package/dist/commands/iteration/create.js.map +1 -1
- package/dist/commands/iteration/split.d.ts +1 -0
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +240 -7
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/commands/plan.d.ts +1 -0
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +25 -1
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/pr.d.ts +1 -0
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +94 -22
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/schedule.d.ts +27 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +203 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/status-panel.d.ts +8 -1
- package/dist/commands/status-panel.d.ts.map +1 -1
- package/dist/commands/status-panel.js +1095 -5
- package/dist/commands/status-panel.js.map +1 -1
- package/dist/commands/task/new.d.ts +4 -0
- package/dist/commands/task/new.d.ts.map +1 -1
- package/dist/commands/task/new.js +87 -0
- package/dist/commands/task/new.js.map +1 -1
- package/dist/core/analyze-engine.d.ts +32 -0
- package/dist/core/analyze-engine.d.ts.map +1 -0
- package/dist/core/analyze-engine.js +935 -0
- package/dist/core/analyze-engine.js.map +1 -0
- package/dist/core/capabilities.d.ts +12 -0
- package/dist/core/capabilities.d.ts.map +1 -0
- package/dist/core/capabilities.js +58 -0
- package/dist/core/capabilities.js.map +1 -0
- package/dist/core/code-scanner.d.ts.map +1 -1
- package/dist/core/code-scanner.js +4 -1
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/intent-recognition.d.ts +1 -1
- package/dist/core/intent-recognition.d.ts.map +1 -1
- package/dist/core/intent-recognition.js +5 -5
- package/dist/core/intent-recognition.js.map +1 -1
- package/dist/core/next-steps.js +1 -1
- package/dist/core/next-steps.js.map +1 -1
- package/dist/core/schedule-engine.d.ts +25 -0
- package/dist/core/schedule-engine.d.ts.map +1 -0
- package/dist/core/schedule-engine.js +179 -0
- package/dist/core/schedule-engine.js.map +1 -0
- package/dist/core/schedule-store.d.ts +104 -0
- package/dist/core/schedule-store.d.ts.map +1 -0
- package/dist/core/schedule-store.js +188 -0
- package/dist/core/schedule-store.js.map +1 -0
- package/dist/core/schemas/iteration.schema.d.ts +4 -4
- package/dist/core/schemas/task.schema.d.ts +2 -2
- package/package.json +7 -13
- package/dist/commands/demo.d.ts +0 -10
- package/dist/commands/demo.d.ts.map +0 -1
- package/dist/commands/demo.js +0 -151
- package/dist/commands/demo.js.map +0 -1
- package/dist/commands/goal.d.ts +0 -15
- package/dist/commands/goal.d.ts.map +0 -1
- package/dist/commands/goal.js +0 -232
- package/dist/commands/goal.js.map +0 -1
- package/dist/commands/new-task.d.ts +0 -15
- package/dist/commands/new-task.d.ts.map +0 -1
- package/dist/commands/new-task.js +0 -208
- package/dist/commands/new-task.js.map +0 -1
- package/dist/commands/platform-add.d.ts +0 -12
- package/dist/commands/platform-add.d.ts.map +0 -1
- package/dist/commands/platform-add.js +0 -111
- package/dist/commands/platform-add.js.map +0 -1
- package/dist/commands/retro.d.ts +0 -10
- package/dist/commands/retro.d.ts.map +0 -1
- package/dist/commands/retro.js +0 -131
- package/dist/commands/retro.js.map +0 -1
- package/dist/commands/spec.d.ts +0 -8
- package/dist/commands/spec.d.ts.map +0 -1
- package/dist/commands/spec.js +0 -159
- package/dist/commands/spec.js.map +0 -1
- package/dist/commands/template-add.d.ts +0 -12
- package/dist/commands/template-add.d.ts.map +0 -1
- package/dist/commands/template-add.js +0 -65
- package/dist/commands/template-add.js.map +0 -1
- package/dist/commands/word2spec.d.ts +0 -8
- package/dist/commands/word2spec.d.ts.map +0 -1
- package/dist/commands/word2spec.js.map +0 -1
package/dist/commands/analyze.js
CHANGED
|
@@ -2,596 +2,288 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.analyzeCommand = analyzeCommand;
|
|
4
4
|
/**
|
|
5
|
-
* analyze —
|
|
6
|
-
*
|
|
5
|
+
* analyze — 统一分析命令
|
|
6
|
+
*
|
|
7
|
+
* 支持:
|
|
8
|
+
* - 需求分析: --req docs/a.md docs/b.md
|
|
9
|
+
* - 代码分析: --src backend/src frontend/src
|
|
10
|
+
* - 联合分析: --src backend/src --req docs/req.md
|
|
11
|
+
*
|
|
12
|
+
* 输出范围:
|
|
13
|
+
* - global → .speccore/GLOBAL/ 全局架构/代码健康
|
|
14
|
+
* - iteration → 期次-XX/00-需求文档/ (默认)
|
|
15
|
+
* - task → 期次-XX/Task-NN/ 单任务深化
|
|
7
16
|
*/
|
|
8
17
|
const fs_extra_1 = require("fs-extra");
|
|
9
18
|
const path_1 = require("path");
|
|
10
19
|
const logger_1 = require("../utils/logger");
|
|
11
|
-
const question_checklist_1 = require("../core/question-checklist");
|
|
12
20
|
const context_1 = require("../core/context");
|
|
21
|
+
const question_checklist_1 = require("../core/question-checklist");
|
|
13
22
|
const next_steps_1 = require("../core/next-steps");
|
|
14
|
-
const
|
|
23
|
+
const analyze_engine_1 = require("../core/analyze-engine");
|
|
15
24
|
async function analyzeCommand(options) {
|
|
16
|
-
|
|
25
|
+
// 手动解析 argv (Commander.js 偶发不传递部分选项)
|
|
26
|
+
parseArgv(options);
|
|
27
|
+
const spinner = new logger_1.Spinner('正在分析...');
|
|
17
28
|
spinner.start();
|
|
18
29
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const cwd = process.cwd();
|
|
39
|
-
// ── 1. 需求完整性扫描 ──
|
|
40
|
-
spinner.stop();
|
|
41
|
-
spinner = new logger_1.Spinner('扫描需求完整性...');
|
|
42
|
-
spinner.start();
|
|
43
|
-
const issues = await scanCompleteness(reqContent);
|
|
44
|
-
// ── 2. 智能源码扫描 ──
|
|
45
|
-
spinner.stop();
|
|
46
|
-
spinner = new logger_1.Spinner('扫描源码结构...');
|
|
47
|
-
spinner.start();
|
|
48
|
-
// Build/refresh code index if needed
|
|
49
|
-
if (await (0, code_scanner_1.isIndexStale)()) {
|
|
50
|
-
const count = await (0, code_scanner_1.buildCodeIndex)();
|
|
51
|
-
logger_1.logger.info(` 📁 索引 ${count} 个源码文件`);
|
|
52
|
-
}
|
|
53
|
-
spinner.stop();
|
|
54
|
-
spinner = new logger_1.Spinner('匹配相关源码...');
|
|
55
|
-
spinner.start();
|
|
56
|
-
const rawMatches = await (0, code_scanner_1.findRelevantCode)(reqContent, 15);
|
|
57
|
-
const codeMatches = rawMatches.map(m => ({
|
|
58
|
-
path: m.file,
|
|
59
|
-
matchType: 'api',
|
|
60
|
-
reason: m.apis.join(', ') || m.exports.join(', ') || `score: ${m.score}`,
|
|
61
|
-
}));
|
|
62
|
-
if (codeMatches.length > 0) {
|
|
63
|
-
spinner.stop();
|
|
64
|
-
logger_1.logger.info(` 🔗 匹配到 ${codeMatches.length} 个相关文件:`);
|
|
65
|
-
for (const m of rawMatches.slice(0, 5)) {
|
|
66
|
-
logger_1.logger.info(` ${m.file} (score: ${m.score})`);
|
|
30
|
+
// ── 应用默认值 ──
|
|
31
|
+
options.depth = options.depth || 'normal';
|
|
32
|
+
// ── 解析逗号分隔的输入 ──
|
|
33
|
+
const sources = options.source
|
|
34
|
+
? options.source.split(',').map(s => s.trim()).filter(Boolean)
|
|
35
|
+
: [];
|
|
36
|
+
const requirements = options.requirements
|
|
37
|
+
? options.requirements.split(',').map(s => s.trim()).filter(Boolean)
|
|
38
|
+
: [];
|
|
39
|
+
// ── 解析 scope ──
|
|
40
|
+
const scope = options.scope ||
|
|
41
|
+
(options.task ? 'task' : 'iteration');
|
|
42
|
+
// ── 解析 iteration ──
|
|
43
|
+
let iteration = options.iteration;
|
|
44
|
+
if (scope === 'iteration' || scope === 'task') {
|
|
45
|
+
iteration = await (0, context_1.getDefaultIteration)(options.iteration);
|
|
46
|
+
if (!iteration) {
|
|
47
|
+
spinner.fail('无效期次: 请用 -i 指定');
|
|
48
|
+
return;
|
|
67
49
|
}
|
|
68
|
-
// Read relevant source for analysis
|
|
69
|
-
spinner = new logger_1.Spinner('读取相关源码...');
|
|
70
|
-
spinner.start();
|
|
71
|
-
const sources = await (0, code_scanner_1.readRelevantSource)(rawMatches, 50000);
|
|
72
|
-
spinner.stop();
|
|
73
|
-
logger_1.logger.info(` 📖 读取了 ${Object.keys(sources).length} 个源码文件`);
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
logger_1.logger.info(' ⚠️ 未匹配到源码。配置扫描范围: .speccore/SETTINGS.md → code_scope');
|
|
77
50
|
}
|
|
78
|
-
// ──
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
51
|
+
// ── 构建输入 ──
|
|
52
|
+
const input = {
|
|
53
|
+
sources,
|
|
54
|
+
requirements,
|
|
55
|
+
scope,
|
|
56
|
+
iteration,
|
|
57
|
+
taskId: options.task,
|
|
58
|
+
depth: options.depth || 'normal',
|
|
59
|
+
output: options.output,
|
|
60
|
+
};
|
|
84
61
|
spinner.stop();
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
62
|
+
// ── 显示分析配置 ──
|
|
63
|
+
logger_1.logger.info('');
|
|
64
|
+
logger_1.logger.info('╔══════════════════════════════════════════╗');
|
|
65
|
+
logger_1.logger.info('║ 📊 SpecCore 分析引擎 ║');
|
|
66
|
+
logger_1.logger.info('╚══════════════════════════════════════════╝');
|
|
67
|
+
logger_1.logger.info('');
|
|
68
|
+
logger_1.logger.info(` 🎯 范围: ${scope === 'global' ? '全局' : scope === 'task' ? '任务' : '期次'}`);
|
|
69
|
+
if (iteration)
|
|
70
|
+
logger_1.logger.info(` 📅 期次: ${iteration}`);
|
|
71
|
+
if (options.task)
|
|
72
|
+
logger_1.logger.info(` 📋 任务: ${options.task}`);
|
|
73
|
+
if (sources.length > 0)
|
|
74
|
+
logger_1.logger.info(` 📁 源码: ${sources.join(', ')}`);
|
|
75
|
+
if (requirements.length > 0)
|
|
76
|
+
logger_1.logger.info(` 📄 需求: ${requirements.join(', ')}`);
|
|
77
|
+
logger_1.logger.info(` 🔍 深度: ${input.depth}`);
|
|
78
|
+
logger_1.logger.info('');
|
|
79
|
+
// ── 执行分析 ──
|
|
80
|
+
const runSpinner = new logger_1.Spinner('分析中...');
|
|
81
|
+
runSpinner.start();
|
|
82
|
+
const result = await (0, analyze_engine_1.runAnalysis)(input);
|
|
83
|
+
runSpinner.stop('分析完成');
|
|
84
|
+
// ── 摘要 ──
|
|
90
85
|
logger_1.logger.info('');
|
|
91
86
|
logger_1.logger.info('📊 分析摘要:');
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
logger_1.logger.info(`
|
|
97
|
-
logger_1.logger.info(`
|
|
98
|
-
logger_1.logger.info(`
|
|
99
|
-
logger_1.logger.info(` 详细报告: ${(0, path_1.join)(iterDir, '00-需求文档', options.output || 'ANALYSIS.md')}`);
|
|
87
|
+
if (result.summary.blockers > 0)
|
|
88
|
+
logger_1.logger.info(` 🔴 阻断: ${result.summary.blockers} 个`);
|
|
89
|
+
logger_1.logger.info(` ⚠️ 问题: ${result.summary.issues} 个`);
|
|
90
|
+
logger_1.logger.info(` 📁 扫描文件: ${result.summary.filesAnalyzed} 个`);
|
|
91
|
+
logger_1.logger.info(` 🔗 API: ${result.summary.apisFound} 个`);
|
|
92
|
+
logger_1.logger.info(` ⚡ 风险: ${result.summary.risks} 个`);
|
|
93
|
+
logger_1.logger.info(` 📝 报告: ${result.outputPath}`);
|
|
100
94
|
logger_1.logger.info('');
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
109
|
-
logger_1.logger.info('\n ❌ 已取消,报告未保存\n');
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
const outputPath = (0, path_1.join)(iterDir, '00-需求文档', options.output || 'ANALYSIS.md');
|
|
113
|
-
await (0, fs_extra_1.writeFile)(outputPath, report);
|
|
114
|
-
logger_1.logger.info(`\n ✅ 分析完成 → ${outputPath}\n`);
|
|
115
|
-
if (issues.some(i => i.severity === 'blocker')) {
|
|
116
|
-
logger_1.logger.warn('\n⚠️ 存在阻断问题,建议解决后再拆分任务。');
|
|
117
|
-
logger_1.logger.info(` 详细报告: ${outputPath}`);
|
|
95
|
+
// ── 确认保存 ──
|
|
96
|
+
const isAuto = options.auto !== false; // --auto 是默认行为
|
|
97
|
+
if (isAuto) {
|
|
98
|
+
// 非交互: 直接保存
|
|
99
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(result.outputPath));
|
|
100
|
+
await (0, fs_extra_1.writeFile)(result.outputPath, result.report);
|
|
101
|
+
logger_1.logger.info(` ✅ 报告已保存 → ${result.outputPath}`);
|
|
118
102
|
}
|
|
119
|
-
else {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
throw error;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
async function scanCompleteness(content) {
|
|
135
|
-
const issues = [];
|
|
136
|
-
const lines = content.split('\n');
|
|
137
|
-
// 1. 检查接口表格是否完整
|
|
138
|
-
let inTable = false;
|
|
139
|
-
let tableRowCount = 0;
|
|
140
|
-
let incompleteRows = [];
|
|
141
|
-
for (const line of lines) {
|
|
142
|
-
if (line.match(/^\|.*\|.*\|.*\|$/)) {
|
|
143
|
-
if (line.includes(':---')) {
|
|
144
|
-
inTable = true;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (inTable) {
|
|
148
|
-
tableRowCount++;
|
|
149
|
-
const cells = line.split('|').map(c => c.trim()).filter(Boolean);
|
|
150
|
-
if (cells.length < 3 || cells.some(c => !c)) {
|
|
151
|
-
incompleteRows.push(` - ${line.trim()}`);
|
|
152
|
-
}
|
|
103
|
+
else if (options.ask) {
|
|
104
|
+
// 交互确认
|
|
105
|
+
const ask = (q) => {
|
|
106
|
+
return new Promise(resolve => {
|
|
107
|
+
const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
|
|
108
|
+
rl.question(q, (a) => { rl.close(); resolve(a); });
|
|
109
|
+
});
|
|
110
|
+
};
|
|
111
|
+
const answer = (await ask(' → 保存报告?[y]保存 [N]取消: ')).toLowerCase();
|
|
112
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
113
|
+
logger_1.logger.info('\n ❌ 已取消,报告未保存\n');
|
|
114
|
+
return;
|
|
153
115
|
}
|
|
116
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(result.outputPath));
|
|
117
|
+
await (0, fs_extra_1.writeFile)(result.outputPath, result.report);
|
|
118
|
+
logger_1.logger.info(`\n ✅ 分析完成 → ${result.outputPath}\n`);
|
|
154
119
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
if (tableRowCount === 0) {
|
|
160
|
-
issues.push({
|
|
161
|
-
severity: 'warning',
|
|
162
|
-
category: '接口定义',
|
|
163
|
-
message: '需求中未检测到接口表格。建议补充「方法 | 路径 | 说明」格式的接口定义。',
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
if (incompleteRows.length > 0) {
|
|
167
|
-
issues.push({
|
|
168
|
-
severity: 'blocker',
|
|
169
|
-
category: '接口定义',
|
|
170
|
-
message: `以下接口行缺少必填字段(方法/路径/说明):\n${incompleteRows.join('\n')}`,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
// 2. 检查是否有空 section(只有标题,无内容)
|
|
174
|
-
const sections = content.match(/^(#{2,3})\s+(.+)$/gm) || [];
|
|
175
|
-
let prevIdx = 0;
|
|
176
|
-
for (let i = 0; i < sections.length; i++) {
|
|
177
|
-
const idx = content.indexOf(sections[i], prevIdx);
|
|
178
|
-
const nextIdx = i + 1 < sections.length
|
|
179
|
-
? content.indexOf(sections[i + 1], idx + 1)
|
|
180
|
-
: content.length;
|
|
181
|
-
const sectionContent = content.substring(idx + sections[i].length, nextIdx).trim();
|
|
182
|
-
if (!sectionContent || sectionContent.length < 20) {
|
|
183
|
-
issues.push({
|
|
184
|
-
severity: 'warning',
|
|
185
|
-
category: '内容完整性',
|
|
186
|
-
message: `章节「${sections[i].replace(/^#+\s*/, '')}」内容过少(${sectionContent.length} 字符),可能需要补充`,
|
|
187
|
-
location: sections[i],
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
prevIdx = idx;
|
|
191
|
-
}
|
|
192
|
-
// 3. 检查是否缺少关键章节
|
|
193
|
-
const hasInterfaces = content.includes('接口') || content.includes('API');
|
|
194
|
-
const hasDataModel = content.includes('数据模型') || content.includes('数据表') || content.includes('数据库');
|
|
195
|
-
if (!hasInterfaces) {
|
|
196
|
-
issues.push({
|
|
197
|
-
severity: 'warning',
|
|
198
|
-
category: '内容完整性',
|
|
199
|
-
message: '未找到接口定义章节。建议添加「接口定义」部分。',
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
if (!hasDataModel) {
|
|
203
|
-
issues.push({
|
|
204
|
-
severity: 'info',
|
|
205
|
-
category: '内容完整性',
|
|
206
|
-
message: '未找到数据模型/数据表描述。如需数据库变更,建议补充。',
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
return issues;
|
|
210
|
-
}
|
|
211
|
-
async function matchCode(cwd, reqContent) {
|
|
212
|
-
const matches = [];
|
|
213
|
-
// 1. 读取 CODE_INDEX 获取所有仓库
|
|
214
|
-
const codeIndexPath = (0, path_1.join)(cwd, '.speccore', 'GLOBAL', 'CODE_INDEX.md');
|
|
215
|
-
let repos = [];
|
|
216
|
-
if (await (0, fs_extra_1.pathExists)(codeIndexPath)) {
|
|
217
|
-
const codeIndex = await (0, fs_extra_1.readFile)(codeIndexPath, 'utf-8');
|
|
218
|
-
const tableRows = codeIndex.match(/^\| .* \|.* \|.* \|/gm) || [];
|
|
219
|
-
for (const row of tableRows) {
|
|
220
|
-
const cells = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
221
|
-
if (cells.length >= 3 && !cells[0].includes('---') && cells[0] !== '项目名') {
|
|
222
|
-
repos.push({ name: cells[0], url: cells[1] || '', branch: cells[2] || '' });
|
|
120
|
+
// ── 下一步提示 (仅 iteration 范围) ──
|
|
121
|
+
if (scope === 'iteration') {
|
|
122
|
+
if (result.summary.blockers > 0) {
|
|
123
|
+
logger_1.logger.warn('\n⚠️ 存在阻断问题,建议解决后再拆分任务。');
|
|
223
124
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// 2. 提取需求中的关键词(路径、模块名、表名)
|
|
227
|
-
const keywords = extractKeywords(reqContent);
|
|
228
|
-
// 3. 读本地源代码目录匹配
|
|
229
|
-
const srcDirs = ['src', 'app', 'lib', 'services', 'controllers', 'models', 'routes'];
|
|
230
|
-
for (const dir of srcDirs) {
|
|
231
|
-
const dirPath = (0, path_1.join)(cwd, dir);
|
|
232
|
-
if (!(await (0, fs_extra_1.pathExists)(dirPath)))
|
|
233
|
-
continue;
|
|
234
|
-
const files = await (0, fs_extra_1.readdir)(dirPath);
|
|
235
|
-
for (const keyword of keywords) {
|
|
236
|
-
const matched = files.filter(f => f.toLowerCase().includes(keyword.toLowerCase()));
|
|
237
|
-
if (matched.length > 0) {
|
|
238
|
-
matches.push({
|
|
239
|
-
repo: `${dir}/`,
|
|
240
|
-
relevance: 'high',
|
|
241
|
-
reason: `匹配关键词「${keyword}」: ${matched.join(', ')}`,
|
|
242
|
-
files: matched,
|
|
243
|
-
});
|
|
125
|
+
else {
|
|
126
|
+
logger_1.logger.info('\n✅ 未发现阻断问题,可以继续拆分任务。');
|
|
244
127
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
repo: repo.name,
|
|
254
|
-
relevance: 'high',
|
|
255
|
-
reason: `仓库名匹配关键词「${keyword}」`,
|
|
256
|
-
});
|
|
128
|
+
// 显示待确认清单
|
|
129
|
+
try {
|
|
130
|
+
const iterDir = `期次-${iteration}`;
|
|
131
|
+
const questions = await (0, question_checklist_1.extractQuestions)(iterDir);
|
|
132
|
+
if (questions.length > 0) {
|
|
133
|
+
(0, question_checklist_1.showQuestionChecklist)(questions, '需求分析待确认');
|
|
134
|
+
}
|
|
135
|
+
(0, next_steps_1.showNextSteps)('analyze');
|
|
257
136
|
}
|
|
137
|
+
catch { /* 非关键步骤 */ }
|
|
258
138
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
return matches.filter(m => {
|
|
263
|
-
const key = m.repo + m.reason;
|
|
264
|
-
if (seen.has(key))
|
|
265
|
-
return false;
|
|
266
|
-
seen.add(key);
|
|
267
|
-
return true;
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
function extractKeywords(content) {
|
|
271
|
-
// 从需求和接口路径中提取关键词
|
|
272
|
-
const keywords = [];
|
|
273
|
-
// 提取接口路径中的模块名
|
|
274
|
-
const pathMatches = content.match(/\/api\/\w+\/(\w+)/g) || [];
|
|
275
|
-
for (const path of pathMatches) {
|
|
276
|
-
const parts = path.split('/');
|
|
277
|
-
const module = parts[parts.length - 1] || parts[parts.length - 2];
|
|
278
|
-
if (module)
|
|
279
|
-
keywords.push(module);
|
|
280
|
-
}
|
|
281
|
-
// 提取英文模块名
|
|
282
|
-
const moduleMatches = content.match(/\b([A-Z][a-z]+(?:[A-Z][a-z]+)+)\b/g) || [];
|
|
283
|
-
keywords.push(...moduleMatches);
|
|
284
|
-
// 提取中文关键词(名词短语)
|
|
285
|
-
const cnMatches = content.match(/(?:用户|订单|支付|商品|消息|权限|日志|报表|配置|审计|通知|审批|工作流|搜索|推荐|统计|监控|调度|网关|缓存|队列)/g) || [];
|
|
286
|
-
keywords.push(...cnMatches);
|
|
287
|
-
return [...new Set(keywords)].slice(0, 20);
|
|
288
|
-
}
|
|
289
|
-
async function analyzeArchitecture(cwd, reqContent) {
|
|
290
|
-
const impact = { modules: [], newDependencies: [], risks: [] };
|
|
291
|
-
// 1. 读取架构文档
|
|
292
|
-
const archPath = (0, path_1.join)(cwd, '.speccore', 'GLOBAL', 'ARCHITECTURE.md');
|
|
293
|
-
if (await (0, fs_extra_1.pathExists)(archPath)) {
|
|
294
|
-
const archContent = await (0, fs_extra_1.readFile)(archPath, 'utf-8');
|
|
295
|
-
// 检查需求是否涉及现有模块
|
|
296
|
-
const moduleSection = archContent.match(/## 模块|## 系统边界|## 服务列表/);
|
|
297
|
-
if (moduleSection) {
|
|
298
|
-
impact.modules.push('⚠️ 需要对照 ARCHITECTURE.md 确认影响范围');
|
|
139
|
+
// ── Task 范围: 也检查是否补全 TECH/TEST/REVIEW ──
|
|
140
|
+
if (scope === 'task' && options.task && iteration) {
|
|
141
|
+
await enrichTaskDocs(iteration, options.task, requirements);
|
|
299
142
|
}
|
|
300
143
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
impact.modules.push(...uniquePaths.map(p => `接口: ${p}`));
|
|
305
|
-
// 3. 新依赖检测
|
|
306
|
-
if (reqContent.includes('消息队列') || reqContent.includes('MQ') || reqContent.includes('Kafka')) {
|
|
307
|
-
impact.newDependencies.push('消息队列');
|
|
308
|
-
}
|
|
309
|
-
if (reqContent.includes('缓存') || reqContent.includes('Redis') || reqContent.includes('Cache')) {
|
|
310
|
-
impact.newDependencies.push('缓存服务 (Redis)');
|
|
311
|
-
}
|
|
312
|
-
if (reqContent.includes('OSS') || reqContent.includes('对象存储') || reqContent.includes('文件上传')) {
|
|
313
|
-
impact.newDependencies.push('对象存储 (OSS/S3)');
|
|
314
|
-
}
|
|
315
|
-
if (reqContent.includes('WebSocket') || reqContent.includes('实时') || reqContent.includes('推送')) {
|
|
316
|
-
impact.newDependencies.push('WebSocket 实时通信');
|
|
317
|
-
}
|
|
318
|
-
if (reqContent.includes('定时') || reqContent.includes('调度') || reqContent.includes('Cron')) {
|
|
319
|
-
impact.newDependencies.push('定时任务调度');
|
|
320
|
-
}
|
|
321
|
-
if (reqContent.includes('ES') || reqContent.includes('搜索') || reqContent.includes('全文')) {
|
|
322
|
-
impact.newDependencies.push('搜索引擎 (Elasticsearch)');
|
|
323
|
-
}
|
|
324
|
-
// 4. 风险检测
|
|
325
|
-
if (reqContent.includes('批量删除') || reqContent.includes('批量操作')) {
|
|
326
|
-
impact.risks.push('存在批量操作,需考虑事务一致性和性能');
|
|
327
|
-
}
|
|
328
|
-
if (reqContent.includes('导出') || reqContent.includes('报表')) {
|
|
329
|
-
impact.risks.push('存在导出/报表功能,需考虑大数据量时的内存和超时');
|
|
330
|
-
}
|
|
331
|
-
if (reqContent.includes('权限') || reqContent.includes('角色') || reqContent.includes('RBAC')) {
|
|
332
|
-
impact.risks.push('涉及权限变更,需确认 RBAC 模型兼容性');
|
|
333
|
-
}
|
|
334
|
-
return impact;
|
|
335
|
-
}
|
|
336
|
-
// ================================================================
|
|
337
|
-
// 生成分析报告
|
|
338
|
-
// ================================================================
|
|
339
|
-
function buildAnalysisReport(iteration, issues, codeMatches, archImpact) {
|
|
340
|
-
const now = new Date().toISOString().split('T')[0];
|
|
341
|
-
const blockerCount = issues.filter(i => i.severity === 'blocker').length;
|
|
342
|
-
const warningCount = issues.filter(i => i.severity === 'warning').length;
|
|
343
|
-
let report = `# 需求分析报告\n\n`;
|
|
344
|
-
report += `> 期次: ${iteration} | 分析时间: ${now} | 状态: ${blockerCount > 0 ? '🔴 有阻断' : '🟢 可拆分'}\n\n`;
|
|
345
|
-
report += `---\n\n`;
|
|
346
|
-
// 1. 完整性检查
|
|
347
|
-
report += `## 1. 需求完整性检查\n\n`;
|
|
348
|
-
report += `| 严重度 | 分类 | 问题 |\n`;
|
|
349
|
-
report += `| :--- | :--- | :--- |\n`;
|
|
350
|
-
for (const issue of issues) {
|
|
351
|
-
const icon = issue.severity === 'blocker' ? '🔴' : issue.severity === 'warning' ? '🟡' : 'ℹ️';
|
|
352
|
-
report += `| ${icon} ${issue.severity} | ${issue.category} | ${issue.message.replace(/\n/g, '<br>')} |\n`;
|
|
353
|
-
}
|
|
354
|
-
if (issues.length === 0) {
|
|
355
|
-
report += `| ✅ | - | 未发现明显问题 |\n`;
|
|
356
|
-
}
|
|
357
|
-
report += `\n`;
|
|
358
|
-
// 2. 源码对标
|
|
359
|
-
report += `## 2. 源码对标\n\n`;
|
|
360
|
-
if (codeMatches.length > 0) {
|
|
361
|
-
report += `| 仓库/目录 | 关联度 | 原因 |\n`;
|
|
362
|
-
report += `| :--- | :--- | :--- |\n`;
|
|
363
|
-
for (const m of codeMatches) {
|
|
364
|
-
report += `| ${m.repo} | ${m.relevance} | ${m.reason} |\n`;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
else {
|
|
368
|
-
report += `> ⚠️ 未匹配到相关源码。请确认 CODE_INDEX.md 是否已配置。\n`;
|
|
369
|
-
}
|
|
370
|
-
report += `\n`;
|
|
371
|
-
// 3. 架构影响
|
|
372
|
-
report += `## 3. 架构影响\n\n`;
|
|
373
|
-
if (archImpact.modules.length > 0) {
|
|
374
|
-
report += `### 涉及模块\n`;
|
|
375
|
-
for (const m of archImpact.modules) {
|
|
376
|
-
report += `- ${m}\n`;
|
|
377
|
-
}
|
|
378
|
-
report += `\n`;
|
|
379
|
-
}
|
|
380
|
-
if (archImpact.newDependencies.length > 0) {
|
|
381
|
-
report += `### 新增依赖\n`;
|
|
382
|
-
for (const d of archImpact.newDependencies) {
|
|
383
|
-
report += `- [ ] ${d}\n`;
|
|
384
|
-
}
|
|
385
|
-
report += `\n`;
|
|
386
|
-
}
|
|
387
|
-
if (archImpact.risks.length > 0) {
|
|
388
|
-
report += `### ⚠️ 风险提示\n`;
|
|
389
|
-
for (const r of archImpact.risks) {
|
|
390
|
-
report += `- ${r}\n`;
|
|
391
|
-
}
|
|
392
|
-
report += `\n`;
|
|
393
|
-
}
|
|
394
|
-
// 4. 待确认清单
|
|
395
|
-
report += `## 4. 待确认清单\n\n`;
|
|
396
|
-
report += `> 以下问题需要团队在拆分任务前确认。\n\n`;
|
|
397
|
-
for (const issue of issues.filter(i => i.severity !== 'info')) {
|
|
398
|
-
report += `- [ ] ${issue.message.replace(/\n/g, ' ').slice(0, 100)}\n`;
|
|
399
|
-
}
|
|
400
|
-
for (const risk of archImpact.risks) {
|
|
401
|
-
report += `- [ ] ${risk}\n`;
|
|
402
|
-
}
|
|
403
|
-
if (archImpact.newDependencies.length > 0) {
|
|
404
|
-
report += `- [ ] 确认新增依赖的引入方案和排期\n`;
|
|
144
|
+
catch (error) {
|
|
145
|
+
spinner.fail(`分析失败: ${error.message || error}`);
|
|
146
|
+
throw error;
|
|
405
147
|
}
|
|
406
|
-
report += `\n---\n\n`;
|
|
407
|
-
report += `## 5. 技术方案(待填写)\n\n`;
|
|
408
|
-
report += `> 以下由技术负责人/开发团队填写,确认后执行 \`speccore iteration split\`。\n\n`;
|
|
409
|
-
report += `### 技术选型\n\n`;
|
|
410
|
-
report += `| 模块 | 技术方案 | 负责人 | 预计工时 |\n`;
|
|
411
|
-
report += `| :--- | :--- | :--- | :--- |\n`;
|
|
412
|
-
report += `| | | | |\n\n`;
|
|
413
|
-
report += `### 数据库变更\n\n`;
|
|
414
|
-
report += `| 表名 | 变更类型 | 说明 |\n`;
|
|
415
|
-
report += `| :--- | :--- | :--- |\n`;
|
|
416
|
-
report += `| | | |\n\n`;
|
|
417
|
-
report += `### 接口依赖\n\n`;
|
|
418
|
-
report += `| 调用方 | 被调用方 | 接口 | 说明 |\n`;
|
|
419
|
-
report += `| :--- | :--- | :--- | :--- |\n`;
|
|
420
|
-
report += `| | | | |\n\n`;
|
|
421
|
-
report += `### 确认签字\n\n`;
|
|
422
|
-
report += `- [ ] 需求确认无遗漏\n`;
|
|
423
|
-
report += `- [ ] 技术方案评审通过\n`;
|
|
424
|
-
report += `- [ ] 工时评估合理\n`;
|
|
425
|
-
report += `- [ ] 可以开始拆分任务\n`;
|
|
426
|
-
return report;
|
|
427
148
|
}
|
|
428
149
|
/**
|
|
429
|
-
*
|
|
150
|
+
* 任务级文档补全 (原 perTaskAnalyze 逻辑)
|
|
430
151
|
*/
|
|
431
|
-
async function
|
|
152
|
+
async function enrichTaskDocs(iteration, taskId, reqFiles) {
|
|
432
153
|
const { readdirSync } = require('fs');
|
|
154
|
+
const iterDir = `期次-${iteration}`;
|
|
155
|
+
if (!(await (0, fs_extra_1.pathExists)(iterDir)))
|
|
156
|
+
return;
|
|
433
157
|
const entries = readdirSync(iterDir, { withFileTypes: true });
|
|
434
158
|
const taskEntry = entries.find((e) => e.isDirectory() && e.name.startsWith(taskId));
|
|
435
159
|
if (!taskEntry) {
|
|
436
|
-
logger_1.logger.
|
|
437
|
-
logger_1.logger.info('可用任务:');
|
|
438
|
-
for (const e of entries) {
|
|
439
|
-
if (e.isDirectory() && e.name.startsWith('Task-'))
|
|
440
|
-
logger_1.logger.info(` - ${e.name}`);
|
|
441
|
-
}
|
|
160
|
+
logger_1.logger.info(` ℹ️ 未找到任务目录 ${taskId},跳过文档补全`);
|
|
442
161
|
return;
|
|
443
162
|
}
|
|
444
163
|
const fullTaskDir = (0, path_1.join)(iterDir, taskEntry.name);
|
|
445
164
|
const backendDir = (0, path_1.join)(fullTaskDir, 'backend');
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
techContent = await (0, fs_extra_1.readFile)(techPath, 'utf-8');
|
|
459
|
-
if (!techContent.includes('## 分析建议')) {
|
|
460
|
-
const items = [];
|
|
461
|
-
const apis = (reqContent.match(/\/api\/[a-zA-Z0-9\/-]+/g) || []).map(a => a.trim());
|
|
462
|
-
if (apis.length > 0) {
|
|
463
|
-
items.push(`检测到 ${apis.length} 个 API:`);
|
|
464
|
-
for (const api of [...new Set(apis)])
|
|
465
|
-
items.push(` \`${api}\``);
|
|
466
|
-
}
|
|
467
|
-
if (reqContent.match(/数据库|表|DDL/))
|
|
468
|
-
items.push('涉及数据库变更,请补充 DDL');
|
|
469
|
-
if (reqContent.match(/权限|RBAC|鉴权/))
|
|
470
|
-
items.push('涉及权限控制,注意鉴权边界');
|
|
471
|
-
if (items.length > 0)
|
|
472
|
-
changes.push({ file: 'TECH.md', section: '分析建议', items });
|
|
473
|
-
}
|
|
165
|
+
if (!(await (0, fs_extra_1.pathExists)(backendDir)))
|
|
166
|
+
return;
|
|
167
|
+
let reqContent = '';
|
|
168
|
+
// 读取任务 REQ 或传入的需求文件
|
|
169
|
+
const taskReqPath = (0, path_1.join)(backendDir, 'REQ.md');
|
|
170
|
+
if (await (0, fs_extra_1.pathExists)(taskReqPath)) {
|
|
171
|
+
reqContent = await require('fs-extra').readFile(taskReqPath, 'utf-8');
|
|
172
|
+
}
|
|
173
|
+
else if (reqFiles.length > 0) {
|
|
174
|
+
for (const f of reqFiles) {
|
|
175
|
+
if (await (0, fs_extra_1.pathExists)(f))
|
|
176
|
+
reqContent += await require('fs-extra').readFile(f, 'utf-8') + '\n';
|
|
474
177
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
items.push('[ ] 大数据量 + 超时处理');
|
|
491
|
-
if (items.length > 0)
|
|
492
|
-
changes.push({ file: 'TEST.md', section: '补充分析', items });
|
|
178
|
+
}
|
|
179
|
+
if (!reqContent)
|
|
180
|
+
return;
|
|
181
|
+
// 补全 TECH.md
|
|
182
|
+
const techPath = (0, path_1.join)(backendDir, 'TECH.md');
|
|
183
|
+
let techContent = '';
|
|
184
|
+
if (await (0, fs_extra_1.pathExists)(techPath)) {
|
|
185
|
+
techContent = await require('fs-extra').readFile(techPath, 'utf-8');
|
|
186
|
+
if (!techContent.includes('## 分析建议')) {
|
|
187
|
+
const items = [];
|
|
188
|
+
const apis = (reqContent.match(/\/api\/[a-zA-Z0-9\/-]+/g) || []).map((a) => a.trim());
|
|
189
|
+
if (apis.length > 0) {
|
|
190
|
+
items.push(`检测到 ${apis.length} 个 API:`);
|
|
191
|
+
for (const api of [...new Set(apis)])
|
|
192
|
+
items.push(` \`${api}\``);
|
|
493
193
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
items.push('[ ] 参数校验 + 幂等性处理');
|
|
503
|
-
if (reqContent.includes('数据库') || reqContent.includes('表'))
|
|
504
|
-
items.push('[ ] 索引覆盖 + 迁移脚本可回滚');
|
|
505
|
-
if (reqContent.includes('权限') || reqContent.includes('RBAC'))
|
|
506
|
-
items.push('[ ] 鉴权注解/中间件正确配置');
|
|
507
|
-
if (items.length > 0)
|
|
508
|
-
changes.push({ file: 'REVIEW.md', section: '本任务专项检查', items });
|
|
194
|
+
if (reqContent.match(/数据库|表|DDL/))
|
|
195
|
+
items.push('涉及数据库变更,请补充 DDL');
|
|
196
|
+
if (reqContent.match(/权限|RBAC|鉴权/))
|
|
197
|
+
items.push('涉及权限控制,注意鉴权边界');
|
|
198
|
+
if (items.length > 0) {
|
|
199
|
+
techContent += `\n\n---\n\n## 分析建议\n\n> 自动生成\n\n${items.map(i => `- ${i}`).join('\n')}\n`;
|
|
200
|
+
await (0, fs_extra_1.writeFile)(techPath, techContent);
|
|
201
|
+
logger_1.logger.info(` 📄 更新 TECH.md`);
|
|
509
202
|
}
|
|
510
203
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
204
|
+
}
|
|
205
|
+
// 补全 TEST.md
|
|
206
|
+
const testPath = (0, path_1.join)(backendDir, 'TEST.md');
|
|
207
|
+
if (await (0, fs_extra_1.pathExists)(testPath)) {
|
|
208
|
+
let testContent = await require('fs-extra').readFile(testPath, 'utf-8');
|
|
209
|
+
if (!testContent.includes('## 补充分析')) {
|
|
210
|
+
const items = [];
|
|
211
|
+
if (reqContent.includes('POST') || reqContent.includes('创建'))
|
|
212
|
+
items.push('[ ] 正常参数 + 异常参数测试');
|
|
213
|
+
if (reqContent.includes('GET') || reqContent.includes('查询'))
|
|
214
|
+
items.push('[ ] 分页 / 筛选 / 空结果测试');
|
|
215
|
+
if (reqContent.includes('DELETE') || reqContent.includes('删除'))
|
|
216
|
+
items.push('[ ] 删除确认 + 级联处理');
|
|
217
|
+
if (reqContent.includes('权限') || reqContent.includes('RBAC'))
|
|
218
|
+
items.push('[ ] 无权限访问 + 越权检测');
|
|
219
|
+
if (items.length > 0) {
|
|
220
|
+
testContent += `\n\n---\n\n## 补充分析\n${items.join('\n')}\n`;
|
|
221
|
+
await (0, fs_extra_1.writeFile)(testPath, testContent);
|
|
222
|
+
logger_1.logger.info(` 📄 更新 TEST.md`);
|
|
524
223
|
}
|
|
525
|
-
logger_1.logger.info('');
|
|
526
224
|
}
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
await (0, fs_extra_1.writeFile)(techPath, techContent + notes);
|
|
545
|
-
}
|
|
546
|
-
if (c.file === 'TEST.md') {
|
|
547
|
-
let notes = `\n\n---\n\n## ${c.section}\n`;
|
|
548
|
-
notes += c.items.join('\n') + '\n';
|
|
549
|
-
await (0, fs_extra_1.writeFile)(testPath, (await (0, fs_extra_1.readFile)(testPath, 'utf-8')) + notes);
|
|
550
|
-
}
|
|
551
|
-
if (c.file === 'REVIEW.md') {
|
|
552
|
-
let notes = `\n\n---\n\n## ${c.section}\n`;
|
|
553
|
-
notes += c.items.join('\n') + '\n';
|
|
554
|
-
await (0, fs_extra_1.writeFile)(reviewPath, (await (0, fs_extra_1.readFile)(reviewPath, 'utf-8')) + notes);
|
|
225
|
+
}
|
|
226
|
+
// 补全 REVIEW.md
|
|
227
|
+
const reviewPath = (0, path_1.join)(backendDir, 'REVIEW.md');
|
|
228
|
+
if (await (0, fs_extra_1.pathExists)(reviewPath)) {
|
|
229
|
+
let reviewContent = await require('fs-extra').readFile(reviewPath, 'utf-8');
|
|
230
|
+
if (!reviewContent.includes('## 本任务专项检查')) {
|
|
231
|
+
const items = [];
|
|
232
|
+
if (reqContent.includes('POST') || reqContent.includes('创建'))
|
|
233
|
+
items.push('[ ] 参数校验 + 幂等性处理');
|
|
234
|
+
if (reqContent.includes('数据库') || reqContent.includes('表'))
|
|
235
|
+
items.push('[ ] 索引覆盖 + 迁移脚本可回滚');
|
|
236
|
+
if (reqContent.includes('权限') || reqContent.includes('RBAC'))
|
|
237
|
+
items.push('[ ] 鉴权注解/中间件正确配置');
|
|
238
|
+
if (items.length > 0) {
|
|
239
|
+
reviewContent += `\n\n---\n\n## 本任务专项检查\n${items.join('\n')}\n`;
|
|
240
|
+
await (0, fs_extra_1.writeFile)(reviewPath, reviewContent);
|
|
241
|
+
logger_1.logger.info(` 📄 更新 REVIEW.md`);
|
|
555
242
|
}
|
|
556
243
|
}
|
|
557
|
-
logger_1.logger.info(`\n ✅ ${taskEntry.name} 分析完成,已更新 ${changes.length} 个文件`);
|
|
558
|
-
logger_1.logger.info(' 💡 可反复运行完善:');
|
|
559
|
-
logger_1.logger.info(` speccore analyze --task=${taskId} --iteration=${iterDir.replace('期次-', '')}`);
|
|
560
|
-
logger_1.logger.info('');
|
|
561
244
|
}
|
|
562
|
-
|
|
563
|
-
|
|
245
|
+
// 创建缺失文件
|
|
246
|
+
const templates = [
|
|
247
|
+
['RISK.md', `# 风险评估\n\n> analyze | ${new Date().toISOString().split('T')[0]}\n\n## 风险矩阵\n| 风险 | 可能 | 影响 | 缓解 |\n| :--- | :--- | :--- | :--- |\n| 兼容性 | 中 | 高 | 版本号+测试 |\n\n## 回滚\n1. 触发: 线上错误率 > 1%\n2. 步骤: git revert → 重部署\n`],
|
|
248
|
+
['DEPS.md', `# 依赖清单\n\n## 上游依赖\n| 服务 | 版本 | 用途 |\n| :--- | :--- | :--- |\n| _待补充_ | — | — |\n`],
|
|
249
|
+
['MONITOR.md', `# 监控\n\n## 关键指标\n| 指标 | 阈值 | 级别 |\n| :--- | :--- | :--- |\n| 成功率 | <99.9% | P1 |\n| P99延迟 | >1000ms | P2 |\n`],
|
|
250
|
+
];
|
|
251
|
+
for (const [filename, content] of templates) {
|
|
252
|
+
const fp = (0, path_1.join)(backendDir, filename);
|
|
253
|
+
if (!(await (0, fs_extra_1.pathExists)(fp))) {
|
|
254
|
+
await (0, fs_extra_1.writeFile)(fp, content);
|
|
255
|
+
logger_1.logger.info(` 📄 创建 ${filename}`);
|
|
256
|
+
}
|
|
564
257
|
}
|
|
565
258
|
}
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
259
|
+
/**
|
|
260
|
+
* 从 process.argv 手动解析选项 (Commander.js 偶发不传递部分选项)
|
|
261
|
+
*/
|
|
262
|
+
function parseArgv(options) {
|
|
263
|
+
const argv = process.argv;
|
|
264
|
+
const strFlags = [
|
|
265
|
+
[['--iteration', '-i', '-I'], (v) => { options.iteration = v; }],
|
|
266
|
+
[['--task', '-t'], (v) => { options.task = v; }],
|
|
267
|
+
[['--scope'], (v) => { options.scope = v; }],
|
|
268
|
+
[['--src', '--source'], (v) => { options.source = v; }],
|
|
269
|
+
[['--req', '--requirements'], (v) => { options.requirements = v; }],
|
|
270
|
+
[['--output', '-o'], (v) => { options.output = v; }],
|
|
271
|
+
[['--depth'], (v) => { options.depth = v; }],
|
|
272
|
+
];
|
|
273
|
+
for (let i = 0; i < argv.length; i++) {
|
|
274
|
+
const arg = argv[i];
|
|
275
|
+
for (const [flags, setter] of strFlags) {
|
|
276
|
+
for (const flag of flags) {
|
|
277
|
+
// --flag value
|
|
278
|
+
if (arg === flag && i + 1 < argv.length) {
|
|
279
|
+
setter(argv[i + 1]);
|
|
280
|
+
}
|
|
281
|
+
// --flag=value
|
|
282
|
+
if (arg.startsWith(flag + '=')) {
|
|
283
|
+
setter(arg.slice(flag.length + 1));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
594
287
|
}
|
|
595
|
-
return violations;
|
|
596
288
|
}
|
|
597
289
|
//# sourceMappingURL=analyze.js.map
|