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.
Files changed (138) hide show
  1. package/README.en.md +63 -210
  2. package/README.md +108 -252
  3. package/dist/cli.js +224 -300
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/analyze.d.ts +5 -0
  6. package/dist/commands/analyze.d.ts.map +1 -1
  7. package/dist/commands/analyze.js +235 -543
  8. package/dist/commands/analyze.js.map +1 -1
  9. package/dist/commands/ask.d.ts +8 -0
  10. package/dist/commands/ask.d.ts.map +1 -0
  11. package/dist/commands/ask.js +293 -0
  12. package/dist/commands/ask.js.map +1 -0
  13. package/dist/commands/bugfix.d.ts +4 -0
  14. package/dist/commands/bugfix.d.ts.map +1 -1
  15. package/dist/commands/bugfix.js +122 -2
  16. package/dist/commands/bugfix.js.map +1 -1
  17. package/dist/commands/change.d.ts +2 -0
  18. package/dist/commands/change.d.ts.map +1 -1
  19. package/dist/commands/change.js +37 -2
  20. package/dist/commands/change.js.map +1 -1
  21. package/dist/commands/completion.js +1 -1
  22. package/dist/commands/completion.js.map +1 -1
  23. package/dist/commands/dev.d.ts +2 -0
  24. package/dist/commands/dev.d.ts.map +1 -1
  25. package/dist/commands/dev.js +175 -1
  26. package/dist/commands/dev.js.map +1 -1
  27. package/dist/commands/doc2spec.d.ts +10 -0
  28. package/dist/commands/doc2spec.d.ts.map +1 -0
  29. package/dist/commands/{word2spec.js → doc2spec.js} +55 -19
  30. package/dist/commands/doc2spec.js.map +1 -0
  31. package/dist/commands/done.d.ts.map +1 -1
  32. package/dist/commands/done.js +47 -0
  33. package/dist/commands/done.js.map +1 -1
  34. package/dist/commands/execute.d.ts +1 -0
  35. package/dist/commands/execute.d.ts.map +1 -1
  36. package/dist/commands/execute.js +118 -11
  37. package/dist/commands/execute.js.map +1 -1
  38. package/dist/commands/help-quick.js +2 -2
  39. package/dist/commands/help-quick.js.map +1 -1
  40. package/dist/commands/help.d.ts +3 -2
  41. package/dist/commands/help.d.ts.map +1 -1
  42. package/dist/commands/help.js +372 -51
  43. package/dist/commands/help.js.map +1 -1
  44. package/dist/commands/import.d.ts +1 -0
  45. package/dist/commands/import.d.ts.map +1 -1
  46. package/dist/commands/import.js +289 -15
  47. package/dist/commands/import.js.map +1 -1
  48. package/dist/commands/init.d.ts +0 -1
  49. package/dist/commands/init.d.ts.map +1 -1
  50. package/dist/commands/init.js +177 -283
  51. package/dist/commands/init.js.map +1 -1
  52. package/dist/commands/iteration/create.d.ts +1 -0
  53. package/dist/commands/iteration/create.d.ts.map +1 -1
  54. package/dist/commands/iteration/create.js +1 -0
  55. package/dist/commands/iteration/create.js.map +1 -1
  56. package/dist/commands/iteration/split.d.ts +1 -0
  57. package/dist/commands/iteration/split.d.ts.map +1 -1
  58. package/dist/commands/iteration/split.js +240 -7
  59. package/dist/commands/iteration/split.js.map +1 -1
  60. package/dist/commands/plan.d.ts +1 -0
  61. package/dist/commands/plan.d.ts.map +1 -1
  62. package/dist/commands/plan.js +25 -1
  63. package/dist/commands/plan.js.map +1 -1
  64. package/dist/commands/pr.d.ts +1 -0
  65. package/dist/commands/pr.d.ts.map +1 -1
  66. package/dist/commands/pr.js +94 -22
  67. package/dist/commands/pr.js.map +1 -1
  68. package/dist/commands/schedule.d.ts +27 -0
  69. package/dist/commands/schedule.d.ts.map +1 -0
  70. package/dist/commands/schedule.js +203 -0
  71. package/dist/commands/schedule.js.map +1 -0
  72. package/dist/commands/status-panel.d.ts +8 -1
  73. package/dist/commands/status-panel.d.ts.map +1 -1
  74. package/dist/commands/status-panel.js +1095 -5
  75. package/dist/commands/status-panel.js.map +1 -1
  76. package/dist/commands/task/new.d.ts +4 -0
  77. package/dist/commands/task/new.d.ts.map +1 -1
  78. package/dist/commands/task/new.js +87 -0
  79. package/dist/commands/task/new.js.map +1 -1
  80. package/dist/core/analyze-engine.d.ts +32 -0
  81. package/dist/core/analyze-engine.d.ts.map +1 -0
  82. package/dist/core/analyze-engine.js +935 -0
  83. package/dist/core/analyze-engine.js.map +1 -0
  84. package/dist/core/capabilities.d.ts +12 -0
  85. package/dist/core/capabilities.d.ts.map +1 -0
  86. package/dist/core/capabilities.js +58 -0
  87. package/dist/core/capabilities.js.map +1 -0
  88. package/dist/core/code-scanner.d.ts.map +1 -1
  89. package/dist/core/code-scanner.js +4 -1
  90. package/dist/core/code-scanner.js.map +1 -1
  91. package/dist/core/intent-recognition.d.ts +1 -1
  92. package/dist/core/intent-recognition.d.ts.map +1 -1
  93. package/dist/core/intent-recognition.js +5 -5
  94. package/dist/core/intent-recognition.js.map +1 -1
  95. package/dist/core/next-steps.js +1 -1
  96. package/dist/core/next-steps.js.map +1 -1
  97. package/dist/core/schedule-engine.d.ts +25 -0
  98. package/dist/core/schedule-engine.d.ts.map +1 -0
  99. package/dist/core/schedule-engine.js +179 -0
  100. package/dist/core/schedule-engine.js.map +1 -0
  101. package/dist/core/schedule-store.d.ts +104 -0
  102. package/dist/core/schedule-store.d.ts.map +1 -0
  103. package/dist/core/schedule-store.js +188 -0
  104. package/dist/core/schedule-store.js.map +1 -0
  105. package/dist/core/schemas/iteration.schema.d.ts +4 -4
  106. package/dist/core/schemas/task.schema.d.ts +2 -2
  107. package/package.json +7 -13
  108. package/dist/commands/demo.d.ts +0 -10
  109. package/dist/commands/demo.d.ts.map +0 -1
  110. package/dist/commands/demo.js +0 -151
  111. package/dist/commands/demo.js.map +0 -1
  112. package/dist/commands/goal.d.ts +0 -15
  113. package/dist/commands/goal.d.ts.map +0 -1
  114. package/dist/commands/goal.js +0 -232
  115. package/dist/commands/goal.js.map +0 -1
  116. package/dist/commands/new-task.d.ts +0 -15
  117. package/dist/commands/new-task.d.ts.map +0 -1
  118. package/dist/commands/new-task.js +0 -208
  119. package/dist/commands/new-task.js.map +0 -1
  120. package/dist/commands/platform-add.d.ts +0 -12
  121. package/dist/commands/platform-add.d.ts.map +0 -1
  122. package/dist/commands/platform-add.js +0 -111
  123. package/dist/commands/platform-add.js.map +0 -1
  124. package/dist/commands/retro.d.ts +0 -10
  125. package/dist/commands/retro.d.ts.map +0 -1
  126. package/dist/commands/retro.js +0 -131
  127. package/dist/commands/retro.js.map +0 -1
  128. package/dist/commands/spec.d.ts +0 -8
  129. package/dist/commands/spec.d.ts.map +0 -1
  130. package/dist/commands/spec.js +0 -159
  131. package/dist/commands/spec.js.map +0 -1
  132. package/dist/commands/template-add.d.ts +0 -12
  133. package/dist/commands/template-add.d.ts.map +0 -1
  134. package/dist/commands/template-add.js +0 -65
  135. package/dist/commands/template-add.js.map +0 -1
  136. package/dist/commands/word2spec.d.ts +0 -8
  137. package/dist/commands/word2spec.d.ts.map +0 -1
  138. package/dist/commands/word2spec.js.map +0 -1
@@ -2,596 +2,288 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.analyzeCommand = analyzeCommand;
4
4
  /**
5
- * analyze — 需求分析 + 源码对标 + 技术方案确认
6
- * 流程:读取 REQUIREMENT.md → 分析 CODE_INDEX/ARCHITECTURE → 产出 ANALYSIS.md
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 code_scanner_1 = require("../core/code-scanner");
23
+ const analyze_engine_1 = require("../core/analyze-engine");
15
24
  async function analyzeCommand(options) {
16
- let spinner = new logger_1.Spinner('正在分析需求...');
25
+ // 手动解析 argv (Commander.js 偶发不传递部分选项)
26
+ parseArgv(options);
27
+ const spinner = new logger_1.Spinner('正在分析...');
17
28
  spinner.start();
18
29
  try {
19
- const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
20
- if (!iteration) {
21
- spinner.fail('无效期次');
22
- return;
23
- }
24
- const iterDir = `期次-${iteration}`;
25
- // ── Per-task analyze mode ──
26
- if (options.task) {
27
- spinner.stop();
28
- await perTaskAnalyze(iterDir, options.task);
29
- return;
30
- }
31
- const reqPath = (0, path_1.join)(iterDir, '00-需求文档', 'REQUIREMENT.md');
32
- if (!(await (0, fs_extra_1.pathExists)(reqPath))) {
33
- spinner.fail(`未找到需求文档: ${reqPath}`);
34
- logger_1.logger.info('请先运行: speccore word2spec --files "..." -i ' + iteration);
35
- return;
36
- }
37
- const reqContent = await (0, fs_extra_1.readFile)(reqPath, 'utf-8');
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
- // ── 3. 架构分析 ──
79
- spinner.stop();
80
- spinner = new logger_1.Spinner('分析架构影响...');
81
- spinner.start();
82
- const archImpact = await analyzeArchitecture(cwd, reqContent);
83
- // ── 4. 生成分析报告 ──
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
- spinner = new logger_1.Spinner('生成分析报告...');
86
- spinner.start();
87
- const report = buildAnalysisReport(iteration, issues, codeMatches, archImpact);
88
- spinner.stop(`📊 报告已生成 (${report.length} 字符)`);
89
- // ── 4.5. 摘要预览 + 确认 ──
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
- const blockerCount = issues.filter(i => i.severity === 'blocker').length;
93
- const warnCount = issues.filter(i => i.severity === 'warning').length;
94
- if (blockerCount > 0)
95
- logger_1.logger.info(` 🔴 阻断问题: ${blockerCount} 个`);
96
- logger_1.logger.info(` 🟡 待确认: ${warnCount} 个`);
97
- logger_1.logger.info(` 📁 涉及仓库: ${codeMatches.length} 个`);
98
- logger_1.logger.info(` 影响模块: ${archImpact.modules?.length || 0} 个`);
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
- const ask = (q) => {
102
- return new Promise(resolve => {
103
- const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
104
- rl.question(q, (a) => { rl.close(); resolve(a); });
105
- });
106
- };
107
- const answer = (await ask(' 保存报告?[y]保存 [N]取消: ')).toLowerCase();
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
- logger_1.logger.info('\n✅ 未发现阻断问题,可以继续拆分任务。');
121
- }
122
- // Show question checklist
123
- const questions = await (0, question_checklist_1.extractQuestions)(iterDir);
124
- if (questions.length > 0) {
125
- (0, question_checklist_1.showQuestionChecklist)(questions, '需求分析待确认');
126
- }
127
- (0, next_steps_1.showNextSteps)('analyze');
128
- }
129
- catch (error) {
130
- spinner.fail(`分析失败: ${error}`);
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
- else {
156
- inTable = false;
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
- // 4. 匹配 CODE_INDEX 中的仓库
248
- for (const repo of repos) {
249
- for (const keyword of keywords) {
250
- if (repo.name.toLowerCase().includes(keyword.toLowerCase()) ||
251
- repo.url.toLowerCase().includes(keyword.toLowerCase())) {
252
- matches.push({
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
- const seen = new Set();
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
- // 2. 检查跨模块依赖
302
- const apiCalls = reqContent.match(/\/api\/[a-zA-Z0-9/-]+/g) || [];
303
- const uniquePaths = [...new Set(apiCalls)];
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
- * 单任务分析 读取任务的 REQ.md,完善 TECH/TEST/REVIEW
150
+ * 任务级文档补全 (原 perTaskAnalyze 逻辑)
430
151
  */
431
- async function perTaskAnalyze(iterDir, taskId) {
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.error(`Task 未找到: ${taskId}`);
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
- const spinner = new logger_1.Spinner(`分析 ${taskEntry.name}`);
447
- spinner.start();
448
- try {
449
- const reqPath = (0, path_1.join)(backendDir, 'REQ.md');
450
- let reqContent = '';
451
- if (await (0, fs_extra_1.pathExists)(reqPath))
452
- reqContent = await (0, fs_extra_1.readFile)(reqPath, 'utf-8');
453
- const changes = [];
454
- // Enrich TECH.md
455
- const techPath = (0, path_1.join)(backendDir, 'TECH.md');
456
- let techContent = '';
457
- if (await (0, fs_extra_1.pathExists)(techPath)) {
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
- // Enrich TEST.md
476
- const testPath = (0, path_1.join)(backendDir, 'TEST.md');
477
- if (await (0, fs_extra_1.pathExists)(testPath)) {
478
- const testContent = await (0, fs_extra_1.readFile)(testPath, 'utf-8');
479
- if (!testContent.includes('## 补充分析')) {
480
- const items = [];
481
- if (reqContent.includes('POST') || reqContent.includes('创建'))
482
- items.push('[ ] 正常参数 + 异常参数测试');
483
- if (reqContent.includes('GET') || reqContent.includes('查询'))
484
- items.push('[ ] 分页 / 筛选 / 空结果测试');
485
- if (reqContent.includes('DELETE') || reqContent.includes('删除'))
486
- items.push('[ ] 删除确认 + 级联处理');
487
- if (reqContent.includes('权限') || reqContent.includes('RBAC'))
488
- items.push('[ ] 无权限访问 + 越权检测');
489
- if (reqContent.includes('批量') || reqContent.includes('导出'))
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
- // Enrich REVIEW.md
496
- const reviewPath = (0, path_1.join)(backendDir, 'REVIEW.md');
497
- if (await (0, fs_extra_1.pathExists)(reviewPath)) {
498
- const reviewContent = await (0, fs_extra_1.readFile)(reviewPath, 'utf-8');
499
- if (!reviewContent.includes('## 本任务专项检查')) {
500
- const items = [];
501
- if (reqContent.includes('POST') || reqContent.includes('创建'))
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
- spinner.stop();
512
- if (changes.length === 0) {
513
- logger_1.logger.info(`\n ✅ ${taskEntry.name} 已是最新,无需更新`);
514
- return;
515
- }
516
- // ── Show preview ──
517
- logger_1.logger.info(`\n╔══════════════════════════════════════════╗`);
518
- logger_1.logger.info(`║ 📋 ${taskEntry.name} 分析结果预览 ║`);
519
- logger_1.logger.info(`╚══════════════════════════════════════════╝\n`);
520
- for (const c of changes) {
521
- logger_1.logger.info(` 📄 ${c.file} 新增「${c.section}」:`);
522
- for (const item of c.items) {
523
- logger_1.logger.info(` ${item}`);
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
- // ── Confirm ──
528
- const ask = (q) => {
529
- return new Promise(resolve => {
530
- const rl = require('readline').createInterface({ input: process.stdin, output: process.stdout });
531
- rl.question(q, (a) => { rl.close(); resolve(a); });
532
- });
533
- };
534
- const answer = (await ask(` → 确认写入?[y] 确认覆盖 [N] 取消: `)).toLowerCase();
535
- if (answer !== 'y' && answer !== 'yes') {
536
- logger_1.logger.info('\n ❌ 已取消,文档未修改\n');
537
- return;
538
- }
539
- // ── Apply changes ──
540
- for (const c of changes) {
541
- if (c.file === 'TECH.md') {
542
- let notes = `\n\n---\n\n## ${c.section}\n\n> 自动生成,可反复修改\n\n`;
543
- notes += c.items.map(i => `- ${i}`).join('\n') + '\n';
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
- catch (error) {
563
- spinner.fail(`分析失败: ${error}`);
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
- async function checkConstitution(iterDir) {
567
- const constitutionPath = (0, path_1.join)(process.cwd(), ".speccore", "PROJECT", "CONSTITUTION.md");
568
- if (!(await (0, fs_extra_1.pathExists)(constitutionPath)))
569
- return [];
570
- const constitution = await (0, fs_extra_1.readFile)(constitutionPath, "utf-8");
571
- const reqPath = (0, path_1.join)(iterDir, "00-需求文档", "REQUIREMENT.md");
572
- if (!(await (0, fs_extra_1.pathExists)(reqPath)))
573
- return [];
574
- const req = await (0, fs_extra_1.readFile)(reqPath, "utf-8");
575
- const violations = [];
576
- // Check: RESTful requirement
577
- if (constitution.includes("RESTful") && req.includes("GraphQL")) {
578
- violations.push("[违反] 宪法要求 RESTful API,但需求中包含 GraphQL 引用");
579
- }
580
- if (constitution.includes("RESTful") && req.includes("WebSocket") && !req.includes("REST")) {
581
- violations.push("[警告] 宪法要求 RESTful,建议确认 WebSocket 使用场景");
582
- }
583
- // Check: language consistency
584
- if (constitution.includes("Java") && req.includes("Python Flask")) {
585
- violations.push("[违反] 宪法指定 Java,但技术方案使用 Python");
586
- }
587
- // Check: test requirement
588
- if (constitution.includes("单元测试")) {
589
- violations.push("[提醒] 宪法要求单元测试,请确认 TEST.md 已完善");
590
- }
591
- // Check: PR review requirement
592
- if (constitution.includes("PR 审查")) {
593
- violations.push("[提醒] 宪法要求 PR 审查,开发完成后需创建 PR");
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