speccore 5.70.0 → 5.74.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 (153) hide show
  1. package/.agents/skills/spec-ask/SKILL.md +10 -0
  2. package/.agents/skills/speccore-router/SKILL.md +10 -0
  3. package/README.md +51 -15
  4. package/dist/cli.js +20 -2
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/about.d.ts.map +1 -1
  7. package/dist/commands/about.js +11 -3
  8. package/dist/commands/about.js.map +1 -1
  9. package/dist/commands/analyze.d.ts +3 -0
  10. package/dist/commands/analyze.d.ts.map +1 -1
  11. package/dist/commands/analyze.js +135 -31
  12. package/dist/commands/analyze.js.map +1 -1
  13. package/dist/commands/ask.d.ts +1 -1
  14. package/dist/commands/ask.d.ts.map +1 -1
  15. package/dist/commands/ask.js +15 -7
  16. package/dist/commands/ask.js.map +1 -1
  17. package/dist/commands/change.d.ts +5 -0
  18. package/dist/commands/change.d.ts.map +1 -1
  19. package/dist/commands/change.js +616 -27
  20. package/dist/commands/change.js.map +1 -1
  21. package/dist/commands/code-index.d.ts +18 -0
  22. package/dist/commands/code-index.d.ts.map +1 -0
  23. package/dist/commands/code-index.js +126 -0
  24. package/dist/commands/code-index.js.map +1 -0
  25. package/dist/commands/context.js +3 -3
  26. package/dist/commands/context.js.map +1 -1
  27. package/dist/commands/dashboard.d.ts.map +1 -1
  28. package/dist/commands/dashboard.js +12 -41
  29. package/dist/commands/dashboard.js.map +1 -1
  30. package/dist/commands/dev.d.ts +1 -0
  31. package/dist/commands/dev.d.ts.map +1 -1
  32. package/dist/commands/dev.js +137 -20
  33. package/dist/commands/dev.js.map +1 -1
  34. package/dist/commands/diff.js +2 -2
  35. package/dist/commands/diff.js.map +1 -1
  36. package/dist/commands/doc2spec.js +8 -8
  37. package/dist/commands/doc2spec.js.map +1 -1
  38. package/dist/commands/done.js +2 -2
  39. package/dist/commands/done.js.map +1 -1
  40. package/dist/commands/execute.d.ts +2 -0
  41. package/dist/commands/execute.d.ts.map +1 -1
  42. package/dist/commands/execute.js +271 -76
  43. package/dist/commands/execute.js.map +1 -1
  44. package/dist/commands/handover.js +1 -1
  45. package/dist/commands/handover.js.map +1 -1
  46. package/dist/commands/help.js +20 -9
  47. package/dist/commands/help.js.map +1 -1
  48. package/dist/commands/import.js +1 -1
  49. package/dist/commands/import.js.map +1 -1
  50. package/dist/commands/init.d.ts.map +1 -1
  51. package/dist/commands/init.js +105 -19
  52. package/dist/commands/init.js.map +1 -1
  53. package/dist/commands/iteration/create.d.ts.map +1 -1
  54. package/dist/commands/iteration/create.js +101 -4
  55. package/dist/commands/iteration/create.js.map +1 -1
  56. package/dist/commands/iteration/split.d.ts.map +1 -1
  57. package/dist/commands/iteration/split.js +179 -112
  58. package/dist/commands/iteration/split.js.map +1 -1
  59. package/dist/commands/iteration-from-global.js +8 -8
  60. package/dist/commands/iteration-from-global.js.map +1 -1
  61. package/dist/commands/lifecycle.d.ts.map +1 -1
  62. package/dist/commands/lifecycle.js +49 -25
  63. package/dist/commands/lifecycle.js.map +1 -1
  64. package/dist/commands/merge-check.js +1 -1
  65. package/dist/commands/merge-check.js.map +1 -1
  66. package/dist/commands/pattern.js +1 -1
  67. package/dist/commands/pattern.js.map +1 -1
  68. package/dist/commands/plan.d.ts +20 -0
  69. package/dist/commands/plan.d.ts.map +1 -1
  70. package/dist/commands/plan.js +56 -40
  71. package/dist/commands/plan.js.map +1 -1
  72. package/dist/commands/retro.d.ts +9 -0
  73. package/dist/commands/retro.d.ts.map +1 -1
  74. package/dist/commands/retro.js +63 -16
  75. package/dist/commands/retro.js.map +1 -1
  76. package/dist/commands/status-panel.d.ts.map +1 -1
  77. package/dist/commands/status-panel.js +47 -18
  78. package/dist/commands/status-panel.js.map +1 -1
  79. package/dist/commands/sync.js +6 -6
  80. package/dist/commands/sync.js.map +1 -1
  81. package/dist/commands/task/new.js +31 -16
  82. package/dist/commands/task/new.js.map +1 -1
  83. package/dist/commands/trace.js +3 -3
  84. package/dist/commands/trace.js.map +1 -1
  85. package/dist/commands/validate.d.ts.map +1 -1
  86. package/dist/commands/validate.js +25 -1
  87. package/dist/commands/validate.js.map +1 -1
  88. package/dist/commands/verify.d.ts +22 -0
  89. package/dist/commands/verify.d.ts.map +1 -0
  90. package/dist/commands/verify.js +158 -0
  91. package/dist/commands/verify.js.map +1 -0
  92. package/dist/commands/welcome.d.ts.map +1 -1
  93. package/dist/commands/welcome.js +6 -9
  94. package/dist/commands/welcome.js.map +1 -1
  95. package/dist/core/ai-context-generator.js +2 -2
  96. package/dist/core/ai-context-generator.js.map +1 -1
  97. package/dist/core/analyze-engine.d.ts +19 -0
  98. package/dist/core/analyze-engine.d.ts.map +1 -1
  99. package/dist/core/analyze-engine.js +230 -18
  100. package/dist/core/analyze-engine.js.map +1 -1
  101. package/dist/core/ask-engine.d.ts.map +1 -1
  102. package/dist/core/ask-engine.js +101 -11
  103. package/dist/core/ask-engine.js.map +1 -1
  104. package/dist/core/capabilities.js +3 -3
  105. package/dist/core/code-index-markdown.d.ts +7 -0
  106. package/dist/core/code-index-markdown.d.ts.map +1 -0
  107. package/dist/core/code-index-markdown.js +160 -0
  108. package/dist/core/code-index-markdown.js.map +1 -0
  109. package/dist/core/code-scanner.d.ts +58 -3
  110. package/dist/core/code-scanner.d.ts.map +1 -1
  111. package/dist/core/code-scanner.js +406 -24
  112. package/dist/core/code-scanner.js.map +1 -1
  113. package/dist/core/git-integration.d.ts +10 -0
  114. package/dist/core/git-integration.d.ts.map +1 -1
  115. package/dist/core/git-integration.js +99 -2
  116. package/dist/core/git-integration.js.map +1 -1
  117. package/dist/core/inbox.d.ts +120 -0
  118. package/dist/core/inbox.d.ts.map +1 -0
  119. package/dist/core/inbox.js +377 -0
  120. package/dist/core/inbox.js.map +1 -0
  121. package/dist/core/intent-cache.d.ts.map +1 -1
  122. package/dist/core/intent-cache.js +8 -2
  123. package/dist/core/intent-cache.js.map +1 -1
  124. package/dist/core/intent-recognition.d.ts.map +1 -1
  125. package/dist/core/intent-recognition.js +4 -3
  126. package/dist/core/intent-recognition.js.map +1 -1
  127. package/dist/core/name-validator.js +1 -1
  128. package/dist/core/name-validator.js.map +1 -1
  129. package/dist/core/next-steps.js +3 -3
  130. package/dist/core/next-steps.js.map +1 -1
  131. package/dist/core/plan-html.js +3 -3
  132. package/dist/core/prompt-builder.d.ts +7 -0
  133. package/dist/core/prompt-builder.d.ts.map +1 -1
  134. package/dist/core/prompt-builder.js +52 -9
  135. package/dist/core/prompt-builder.js.map +1 -1
  136. package/dist/core/resolver.d.ts +52 -0
  137. package/dist/core/resolver.d.ts.map +1 -0
  138. package/dist/core/resolver.js +257 -0
  139. package/dist/core/resolver.js.map +1 -0
  140. package/dist/core/reverse-sync.js +2 -2
  141. package/dist/core/reverse-sync.js.map +1 -1
  142. package/dist/core/risk-scorer.js +2 -2
  143. package/dist/core/risk-scorer.js.map +1 -1
  144. package/dist/core/state.d.ts.map +1 -1
  145. package/dist/core/state.js +47 -15
  146. package/dist/core/state.js.map +1 -1
  147. package/dist/core/validator.js +9 -9
  148. package/dist/core/validator.js.map +1 -1
  149. package/dist/core/verify-engine.d.ts +64 -0
  150. package/dist/core/verify-engine.d.ts.map +1 -0
  151. package/dist/core/verify-engine.js +560 -0
  152. package/dist/core/verify-engine.js.map +1 -0
  153. package/package.json +1 -1
@@ -44,6 +44,331 @@ const fs_extra_1 = require("fs-extra");
44
44
  const path_1 = require("path");
45
45
  const transaction_1 = require("../core/transaction");
46
46
  const state_1 = require("../core/state");
47
+ const resolver_1 = require("../core/resolver");
48
+ const global_counters_1 = require("../core/global-counters");
49
+ const inbox_1 = require("../core/inbox");
50
+ /**
51
+ * 解析任务目录基础路径:优先 030-tasks/,兼容旧布局
52
+ */
53
+ async function resolveTaskBase(iterDir) {
54
+ const tasksDir = (0, path_1.join)(iterDir, '030-tasks');
55
+ return (await (0, fs_extra_1.pathExists)(tasksDir)) ? tasksDir : iterDir;
56
+ }
57
+ /**
58
+ * 从 CHANGELOG.md 提取最新版本号并递增
59
+ */
60
+ async function nextVersion(changelogPath) {
61
+ if (await (0, fs_extra_1.pathExists)(changelogPath)) {
62
+ const content = await (0, fs_extra_1.readFile)(changelogPath, 'utf-8');
63
+ const versions = content.match(/v(\d+\.\d+)/g);
64
+ if (versions && versions.length > 0) {
65
+ const last = versions[versions.length - 1].replace('v', '');
66
+ const [major, minor] = last.split('.').map(Number);
67
+ return `v${major}.${minor + 1}`;
68
+ }
69
+ }
70
+ return 'v1.1';
71
+ }
72
+ /**
73
+ * 检测意图:变更已有需求 vs 新增需求
74
+ */
75
+ function detectIntent(desc) {
76
+ const lower = desc.replace(/\s+/g, '');
77
+ if (/^(新增?|加|添|创建|增加|实现|做[一个]*)/.test(lower))
78
+ return 'new';
79
+ return 'change';
80
+ }
81
+ /**
82
+ * 全量影响分析:读取每个任务的 REQ/TECH/TASK/status,分类为直接/间接/无影响
83
+ */
84
+ async function analyzeImpact(desc, iterDir, taskBase) {
85
+ const keywords = extractKeywords(desc);
86
+ const { readdir: rd } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
87
+ // 读取依赖图
88
+ const graphPath = (0, path_1.join)(iterDir, '000-overview', 'PROJECT_GRAPH.md');
89
+ const graphContent = await (0, fs_extra_1.pathExists)(graphPath) ? await (0, fs_extra_1.readFile)(graphPath, 'utf-8') : '';
90
+ // 收集所有任务详情
91
+ let entries = [];
92
+ try {
93
+ entries = await rd(taskBase, { withFileTypes: true });
94
+ }
95
+ catch {
96
+ return { directTasks: [], indirectTasks: [], unaffectedTasks: [] };
97
+ }
98
+ const allImpacts = [];
99
+ for (const entry of entries) {
100
+ if (!entry.isDirectory() || !entry.name.startsWith('Task-'))
101
+ continue;
102
+ const taskId = entry.name;
103
+ const taskDir = (0, path_1.join)(taskBase, taskId);
104
+ // 读取任务状态
105
+ let status = 'unknown';
106
+ const statusPath1 = (0, path_1.join)(taskDir, '.task-status');
107
+ const statusPath2 = (0, path_1.join)(taskDir, '.meta', 'status');
108
+ if (await (0, fs_extra_1.pathExists)(statusPath1))
109
+ status = (await (0, fs_extra_1.readFile)(statusPath1, 'utf-8')).trim();
110
+ else if (await (0, fs_extra_1.pathExists)(statusPath2))
111
+ status = (await (0, fs_extra_1.readFile)(statusPath2, 'utf-8')).trim();
112
+ // 读取任务名称
113
+ let name = taskId;
114
+ const reqPath = (0, path_1.join)(taskDir, '00-specs', 'REQ.md');
115
+ const legacyReqPath = (0, path_1.join)(taskDir, 'REQ.md');
116
+ const actualReqPath = (await (0, fs_extra_1.pathExists)(reqPath)) ? reqPath : (await (0, fs_extra_1.pathExists)(legacyReqPath)) ? legacyReqPath : null;
117
+ if (actualReqPath) {
118
+ const reqContent = await (0, fs_extra_1.readFile)(actualReqPath, 'utf-8');
119
+ const nameMatch = reqContent.match(/^#\s+(.+)$/m);
120
+ if (nameMatch)
121
+ name = nameMatch[1];
122
+ }
123
+ // 关键词匹配评分
124
+ let score = 0;
125
+ let matchedFiles = [];
126
+ const filesToCheck = [
127
+ { path: actualReqPath || '', label: 'REQ.md' },
128
+ { path: (0, path_1.join)(taskDir, '00-specs', 'TECH.md'), label: 'TECH.md' },
129
+ { path: (0, path_1.join)(taskDir, '00-specs', 'TASK.md'), label: 'TASK.md' },
130
+ ];
131
+ for (const fc of filesToCheck) {
132
+ if (!fc.path || !await (0, fs_extra_1.pathExists)(fc.path))
133
+ continue;
134
+ const content = await (0, fs_extra_1.readFile)(fc.path, 'utf-8');
135
+ for (const kw of keywords) {
136
+ if (content.includes(kw)) {
137
+ score++;
138
+ if (!matchedFiles.includes(fc.label))
139
+ matchedFiles.push(fc.label);
140
+ }
141
+ }
142
+ }
143
+ if (score > 0) {
144
+ allImpacts.push({
145
+ id: taskId,
146
+ name,
147
+ status,
148
+ level: 'direct',
149
+ reason: `关键词命中 ${score} 处 [${matchedFiles.join(', ')}]`,
150
+ affectedFiles: matchedFiles,
151
+ needReExecute: true,
152
+ needRegression: false,
153
+ });
154
+ }
155
+ else {
156
+ allImpacts.push({
157
+ id: taskId,
158
+ name,
159
+ status,
160
+ level: 'none',
161
+ reason: '',
162
+ affectedFiles: [],
163
+ needReExecute: false,
164
+ needRegression: false,
165
+ });
166
+ }
167
+ }
168
+ // 依赖图分析:直接受影响任务的上下游 → 间接影响
169
+ const directIds = new Set(allImpacts.filter(t => t.level === 'direct').map(t => t.id));
170
+ const indirectIds = new Set();
171
+ for (const directId of directIds) {
172
+ const deps = findDependentTasks(graphContent, directId);
173
+ for (const dep of deps) {
174
+ if (!directIds.has(dep)) {
175
+ indirectIds.add(dep);
176
+ }
177
+ }
178
+ // 反向查找:谁依赖了直接受影响的任务
179
+ const reverseDeps = findReverseDependencies(graphContent, directId);
180
+ for (const rd of reverseDeps) {
181
+ if (!directIds.has(rd)) {
182
+ indirectIds.add(rd);
183
+ }
184
+ }
185
+ }
186
+ // 分类结果
187
+ const directTasks = [];
188
+ const indirectTasks = [];
189
+ const unaffectedTasks = [];
190
+ for (const t of allImpacts) {
191
+ if (directIds.has(t.id)) {
192
+ directTasks.push(t);
193
+ }
194
+ else if (indirectIds.has(t.id)) {
195
+ // 补充间接影响原因
196
+ const depReasons = [];
197
+ for (const directId of directIds) {
198
+ const deps = findDependentTasks(graphContent, directId);
199
+ const rdeps = findReverseDependencies(graphContent, directId);
200
+ if (deps.includes(t.id))
201
+ depReasons.push(`依赖 ${directId}`);
202
+ if (rdeps.includes(t.id))
203
+ depReasons.push(`${directId} 依赖本任务`);
204
+ }
205
+ indirectTasks.push({
206
+ ...t,
207
+ level: 'indirect',
208
+ reason: depReasons.join('; ') || '间接关联',
209
+ needReExecute: false,
210
+ needRegression: t.status === 'done',
211
+ });
212
+ }
213
+ else {
214
+ unaffectedTasks.push(t);
215
+ }
216
+ }
217
+ return { directTasks, indirectTasks, unaffectedTasks };
218
+ }
219
+ /**
220
+ * 反向查找依赖:找出哪些任务依赖了指定任务
221
+ */
222
+ function findReverseDependencies(graphContent, taskId) {
223
+ const deps = [];
224
+ // Mermaid 格式: A[Task-001] --> B[Task-002] 表示 Task-001 → Task-002
225
+ const mermaidPattern = new RegExp(`\\[${taskId}\\]\\s*-->\\s*\\[(Task-\\d+)\\]`);
226
+ const reverseMermaidPattern = new RegExp(`\\[(Task-\\d+)\\]\\s*-->\\s*\\[${taskId}\\]`);
227
+ for (const line of graphContent.split('\n')) {
228
+ // 正向:taskId 指向别人
229
+ const m = line.match(mermaidPattern);
230
+ if (m && m[1] !== taskId && !deps.includes(m[1]))
231
+ deps.push(m[1]);
232
+ // 反向:别人指向 taskId
233
+ const rm = line.match(reverseMermaidPattern);
234
+ if (rm && rm[1] !== taskId && !deps.includes(rm[1]))
235
+ deps.push(rm[1]);
236
+ }
237
+ return deps;
238
+ }
239
+ /**
240
+ * 构建任务详情(用于澄清 Prompt 上下文)
241
+ */
242
+ async function buildTaskDetails(taskBase) {
243
+ const { readdir: rd } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
244
+ let entries = [];
245
+ try {
246
+ entries = await rd(taskBase, { withFileTypes: true });
247
+ }
248
+ catch {
249
+ return [];
250
+ }
251
+ const details = [];
252
+ for (const entry of entries) {
253
+ if (!entry.isDirectory() || !entry.name.startsWith('Task-'))
254
+ continue;
255
+ const taskId = entry.name;
256
+ const taskDir = (0, path_1.join)(taskBase, taskId);
257
+ // 状态
258
+ let status = 'unknown';
259
+ const statusPath1 = (0, path_1.join)(taskDir, '.task-status');
260
+ const statusPath2 = (0, path_1.join)(taskDir, '.meta', 'status');
261
+ if (await (0, fs_extra_1.pathExists)(statusPath1))
262
+ status = (await (0, fs_extra_1.readFile)(statusPath1, 'utf-8')).trim();
263
+ else if (await (0, fs_extra_1.pathExists)(statusPath2))
264
+ status = (await (0, fs_extra_1.readFile)(statusPath2, 'utf-8')).trim();
265
+ // REQ 摘要(取前 300 字符)
266
+ let reqSummary = '';
267
+ const reqPath = (0, path_1.join)(taskDir, '00-specs', 'REQ.md');
268
+ const legacyReqPath = (0, path_1.join)(taskDir, 'REQ.md');
269
+ const actualReqPath = (await (0, fs_extra_1.pathExists)(reqPath)) ? reqPath : (await (0, fs_extra_1.pathExists)(legacyReqPath)) ? legacyReqPath : null;
270
+ if (actualReqPath) {
271
+ const content = await (0, fs_extra_1.readFile)(actualReqPath, 'utf-8');
272
+ reqSummary = content.slice(0, 300).replace(/\n+/g, ' ').trim();
273
+ }
274
+ // TECH 摘要
275
+ let techSummary = '';
276
+ const techPath = (0, path_1.join)(taskDir, '00-specs', 'TECH.md');
277
+ if (await (0, fs_extra_1.pathExists)(techPath)) {
278
+ const content = await (0, fs_extra_1.readFile)(techPath, 'utf-8');
279
+ techSummary = content.slice(0, 200).replace(/\n+/g, ' ').trim();
280
+ }
281
+ // 名称
282
+ let name = taskId;
283
+ if (reqSummary) {
284
+ const nameMatch = reqSummary.match(/^#\s+(.+)/);
285
+ if (nameMatch)
286
+ name = nameMatch[1];
287
+ }
288
+ details.push({ id: taskId, name, reqSummary, techSummary, status, dependencies: [] });
289
+ }
290
+ // 从依赖图补充 dependencies
291
+ return details;
292
+ }
293
+ /**
294
+ * 提取关键词(2字以上的中文/英文词)
295
+ */
296
+ function extractKeywords(desc) {
297
+ const normalized = normalizeDescription(desc);
298
+ const chinese = normalized.match(/[\u4e00-\u9fa5]{2,}/g) || [];
299
+ const english = normalized.match(/[a-zA-Z]{2,}/g) || [];
300
+ return [...new Set([...chinese, ...english])];
301
+ }
302
+ /**
303
+ * 新增需求一站式处理:创建任务 → 追加需求 → 更新依赖图 → 引导分析
304
+ * 澄清结果(需求分析)持久化到 REQ.md
305
+ */
306
+ async function handleNewRequirement(desc, iteration, clarifyResult) {
307
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
308
+ const taskBase = await resolveTaskBase(iterDir);
309
+ await (0, fs_extra_1.ensureDir)(taskBase);
310
+ const { id: taskId } = await (0, global_counters_1.nextTaskId)();
311
+ const taskName = desc.replace(/^(新增?|加|创建|实现|做)/, '').replace(/[::]/g, '').trim() || taskId;
312
+ const taskDir = (0, path_1.join)(taskBase, taskId);
313
+ const specsDir = (0, path_1.join)(taskDir, '00-specs');
314
+ await (0, fs_extra_1.ensureDir)(specsDir);
315
+ const now = new Date().toISOString().split('T')[0];
316
+ const tx = new transaction_1.FileTransaction();
317
+ // 构建结构化 REQ.md(澄清 = 需求分析,结果持久化)
318
+ const structuredDesc = clarifyResult?.structuredDesc || desc;
319
+ const keyPoints = clarifyResult?.keyPoints || [];
320
+ const acceptanceCriteria = clarifyResult?.acceptanceCriteria || [];
321
+ let reqContent = `# ${taskName}\n\n`;
322
+ reqContent += `## 需求描述\n\n${structuredDesc}\n\n`;
323
+ reqContent += `## 原始输入\n\n${desc}\n\n`;
324
+ if (keyPoints.length > 0) {
325
+ reqContent += `## 功能要点\n\n`;
326
+ for (const p of keyPoints) {
327
+ reqContent += `- ${p}\n`;
328
+ }
329
+ reqContent += '\n';
330
+ }
331
+ if (acceptanceCriteria.length > 0) {
332
+ reqContent += `## 验收标准\n\n`;
333
+ for (const c of acceptanceCriteria) {
334
+ reqContent += `- [ ] ${c}\n`;
335
+ }
336
+ reqContent += '\n';
337
+ }
338
+ else {
339
+ reqContent += `## 验收标准\n\n- [ ] 功能正常\n`;
340
+ }
341
+ reqContent += `## 分析记录\n\n- 分析时间: ${now}\n- 分析方式: ${clarifyResult ? 'AI 澄清' : '本地分析'}\n`;
342
+ tx.write((0, path_1.join)(specsDir, 'REQ.md'), reqContent);
343
+ // 创建 CHANGELOG.md
344
+ tx.write((0, path_1.join)(specsDir, 'CHANGELOG.md'), `# 变更记录\n\n| 时间 | 版本 | 变更内容 | 变更人 |\n| :--- | :--- | :--- | :--- |\n| ${now} | v1.0 | 初始创建 | SpecCore |\n`);
345
+ // 创建 TASK.md
346
+ tx.write((0, path_1.join)(specsDir, 'TASK.md'), `# ${taskName}\n\n- 状态: 待开发\n- 优先级: medium\n- 类型: feature\n\n## 变更履历\n\n| 时间 | 变更内容 | 变更人 |\n| :--- | :--- | :--- |\n| ${now} | 创建任务 | SpecCore |\n`);
347
+ await tx.commit();
348
+ // 追加到 REQUIREMENT.md
349
+ const reqPath = (0, path_1.join)(iterDir, '020-specs', 'REQUIREMENT.md');
350
+ if (await (0, fs_extra_1.pathExists)(reqPath)) {
351
+ let content = await (0, fs_extra_1.readFile)(reqPath, 'utf-8');
352
+ content += `\n\n## ${taskName}\n\n${desc}\n`;
353
+ await (0, fs_extra_1.writeFile)(reqPath, content);
354
+ }
355
+ // 更新 PROJECT_GRAPH.md
356
+ const graphPath = (0, path_1.join)(iterDir, '000-overview', 'PROJECT_GRAPH.md');
357
+ if (await (0, fs_extra_1.pathExists)(graphPath)) {
358
+ let content = await (0, fs_extra_1.readFile)(graphPath, 'utf-8');
359
+ // 直接使用已生成的 taskId,避免重复计数 bug
360
+ content += `| ${taskId} | ${taskName} | feature | pending |\n`;
361
+ await (0, fs_extra_1.writeFile)(graphPath, content);
362
+ }
363
+ logger_1.logger.info('');
364
+ logger_1.logger.success(`✅ 新任务已创建: ${taskId}`);
365
+ logger_1.logger.info(` 📄 ${taskId}/00-specs/REQ.md`);
366
+ logger_1.logger.info(` 📄 ${taskId}/00-specs/TASK.md`);
367
+ logger_1.logger.info('');
368
+ logger_1.logger.info('💡 下一步:');
369
+ logger_1.logger.info(` speccore analyze --task=${taskId} # 分析技术方案`);
370
+ logger_1.logger.info(` speccore execute --task=${taskId} --force # 执行任务`);
371
+ }
47
372
  const readline_1 = require("readline");
48
373
  function promptUser(question) {
49
374
  const rl = (0, readline_1.createInterface)({ input: process.stdin, output: process.stdout });
@@ -57,7 +382,169 @@ async function changeCommand(options) {
57
382
  options.desc = options.input;
58
383
  }
59
384
  if (!options.task && !options.global) {
60
- logger_1.logger.error('请指定 Task 或使用 --global。用法: speccore change "变更描述" --task=<Task>');
385
+ // ── 智能匹配 / 新增需求(含 inbox + 附件 + 澄清)──
386
+ if (!options.desc && !options.file && options.noInbox) {
387
+ logger_1.logger.error('请提供变更描述或附件。用法: speccore change "描述" --file=xxx.md');
388
+ return;
389
+ }
390
+ const iteration = await (0, context_1.getDefaultIteration)(options.iteration);
391
+ if (!iteration) {
392
+ logger_1.logger.error('未找到活跃迭代。请先运行: speccore iteration create --name <名称>');
393
+ return;
394
+ }
395
+ // ── 1. 加载附件 ──
396
+ const allFiles = [];
397
+ // 1a. 扫描 inbox(默认启用)
398
+ if (!options.noInbox) {
399
+ await (0, inbox_1.ensureInboxDir)();
400
+ const inboxResult = await (0, inbox_1.scanInbox)({ reprocess: options.reprocess });
401
+ (0, inbox_1.logInboxScan)(inboxResult);
402
+ const actionable = [...inboxResult.newFiles, ...inboxResult.modifiedFiles];
403
+ allFiles.push(...actionable);
404
+ }
405
+ // 1b. 加载 --file 指定的文件
406
+ if (options.file) {
407
+ const { readFile: rf, stat: st } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
408
+ const filePaths = options.file.split(',').map(f => f.trim());
409
+ for (const fp of filePaths) {
410
+ const absPath = (0, path_1.join)(process.cwd(), fp);
411
+ if (!await (0, fs_extra_1.pathExists)(absPath)) {
412
+ logger_1.logger.warn(`⚠️ 文件不存在: ${fp}`);
413
+ continue;
414
+ }
415
+ const fileStat = await st(absPath);
416
+ const name = fp.split('/').pop() || fp;
417
+ const ext = name.split('.').pop()?.toLowerCase() || '';
418
+ let type = 'other';
419
+ if (['md', 'txt', 'markdown', 'json', 'yaml', 'yml', 'csv'].includes(ext))
420
+ type = 'text';
421
+ else if (['xlsx', 'xls'].includes(ext))
422
+ type = 'excel';
423
+ else if (['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg'].includes(ext))
424
+ type = 'image';
425
+ let content = '';
426
+ if (type === 'text') {
427
+ content = await rf(absPath, 'utf-8');
428
+ }
429
+ else if (type === 'excel') {
430
+ try {
431
+ const XLSX = require('xlsx');
432
+ const wb = XLSX.readFile(absPath);
433
+ const sheets = [];
434
+ for (const sn of wb.SheetNames) {
435
+ sheets.push(`## Sheet: ${sn}\n${XLSX.utils.sheet_to_csv(wb.Sheets[sn])}`);
436
+ }
437
+ content = sheets.join('\n\n');
438
+ }
439
+ catch {
440
+ content = `[Excel 解析失败]`;
441
+ }
442
+ }
443
+ else if (type === 'image') {
444
+ content = `[图片文件: ${absPath}]`;
445
+ }
446
+ else {
447
+ try {
448
+ content = await rf(absPath, 'utf-8');
449
+ }
450
+ catch {
451
+ content = `[无法读取]`;
452
+ }
453
+ }
454
+ allFiles.push({ name, path: absPath, size: fileStat.size, mtime: fileStat.mtime.toISOString(), type, content });
455
+ logger_1.logger.info(` 📎 ${name} (${fileStat.size > 1024 ? (fileStat.size / 1024).toFixed(1) + 'KB' : fileStat.size + 'B'})`);
456
+ }
457
+ }
458
+ // ── 2. 构建澄清上下文 ──
459
+ const desc = options.desc ? normalizeDescription(options.desc) : '';
460
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
461
+ const taskBase = await resolveTaskBase(iterDir);
462
+ const allTasks = await (0, state_1.scanTasks)(iteration);
463
+ const taskDetails = await buildTaskDetails(taskBase);
464
+ // ── 3. Prompt 模式:输出澄清 Prompt 到 stdout ──
465
+ if (options.prompt) {
466
+ const promptText = (0, inbox_1.buildClarifyPrompt)(desc || '(从附件分析需求)', allFiles, taskDetails);
467
+ logger_1.logger.info('[SPECCORE_PROMPT]');
468
+ process.stdout.write(promptText);
469
+ return;
470
+ }
471
+ // ── 4. Response 模式:解析 AI 澄清结果 ──
472
+ let clarifiedIntent;
473
+ let clarifiedDesc = desc;
474
+ let clarifiedTasks = [];
475
+ if (options.response) {
476
+ const parsed = (0, inbox_1.parseClarifyResponse)(options.response);
477
+ if (parsed) {
478
+ clarifiedIntent = parsed.intent;
479
+ clarifiedDesc = parsed.structuredDesc || desc;
480
+ // 从 impactReport 中提取直接影响的任务 ID
481
+ clarifiedTasks = parsed.impactReport?.directTasks?.map(t => t.id) || [];
482
+ (0, inbox_1.logClarifyResult)(parsed);
483
+ }
484
+ else {
485
+ logger_1.logger.warn('⚠️ AI 澄清结果解析失败,使用本地分析');
486
+ }
487
+ }
488
+ // ── 5. 本地意图检测(无 AI 澄清时) ──
489
+ const intent = clarifiedIntent || detectIntent(desc || allFiles.map(f => f.content).join(' '));
490
+ // ── 6. 新增需求 ──
491
+ if (intent === 'new') {
492
+ logger_1.logger.info('🆕 检测到新增需求意图');
493
+ const newDesc = clarifiedDesc || allFiles.map(f => f.name + ': ' + f.content.slice(0, 200)).join('\n');
494
+ // 传递澄清结果给 handleNewRequirement,持久化到 REQ.md
495
+ const parsed = options.response ? (0, inbox_1.parseClarifyResponse)(options.response) : null;
496
+ const clarifyOutput = parsed ? { structuredDesc: parsed.structuredDesc, keyPoints: parsed.keyPoints, acceptanceCriteria: parsed.acceptanceCriteria } : undefined;
497
+ await handleNewRequirement(newDesc, iteration, clarifyOutput);
498
+ // 标记 inbox 文件已处理
499
+ if (allFiles.length > 0) {
500
+ await (0, inbox_1.markProcessed)(allFiles, 'new', []);
501
+ }
502
+ return;
503
+ }
504
+ // ── 7. 变更:全量影响分析 ──
505
+ let impactReport;
506
+ if (clarifiedTasks.length > 0) {
507
+ // 使用 AI 澄清结果中的匹配任务构造影响报告
508
+ const directTasks = clarifiedTasks.map(tid => {
509
+ const task = allTasks.find(t => t.id === tid);
510
+ return { id: tid, name: task?.name || tid, status: 'unknown', level: 'direct', reason: 'AI 澄清匹配', affectedFiles: [], needReExecute: true, needRegression: false };
511
+ }).filter(m => allTasks.some(t => t.id === m.id));
512
+ impactReport = { directTasks, indirectTasks: [], unaffectedTasks: [] };
513
+ }
514
+ else {
515
+ // 本地全量影响分析
516
+ const matchDesc = desc || allFiles.map(f => f.content).join(' ');
517
+ impactReport = await analyzeImpact(matchDesc, iterDir, taskBase);
518
+ }
519
+ const hasImpact = impactReport.directTasks.length > 0 || impactReport.indirectTasks.length > 0;
520
+ if (!hasImpact) {
521
+ logger_1.logger.warn('未匹配到受影响任务。请指定 --task 或检查变更描述/附件。');
522
+ logger_1.logger.info('💡 如果是新增需求,请确保描述以"新增/加/创建"开头');
523
+ return;
524
+ }
525
+ // 展示影响分析报告
526
+ (0, inbox_1.logImpactReport)(impactReport);
527
+ logger_1.logger.info('');
528
+ // 对所有直接影响任务应用变更
529
+ const changeDesc = clarifiedDesc || desc;
530
+ const affectedIds = [];
531
+ for (const m of impactReport.directTasks) {
532
+ const taskOpts = { ...options, task: m.id, desc: changeDesc };
533
+ await applyTaskChange(taskOpts, iteration);
534
+ affectedIds.push(m.id);
535
+ }
536
+ // 标记 inbox 文件已处理
537
+ if (allFiles.length > 0) {
538
+ await (0, inbox_1.markProcessed)(allFiles, 'change', affectedIds);
539
+ }
540
+ // ── 8. 持久化澄清结果:迭代级 CHANGE_SUMMARY.md ──
541
+ await writeChangeSummary(iterDir, changeDesc, impactReport, affectedIds);
542
+ logger_1.logger.info('');
543
+ logger_1.logger.success(`✅ 变更已应用到 ${affectedIds.length} 个任务`);
544
+ logger_1.logger.info(` 📄 变更摘要: 020-specs/CHANGE_SUMMARY.md`);
545
+ logger_1.logger.info('');
546
+ logger_1.logger.info('💡 下一步:');
547
+ logger_1.logger.info(` speccore execute --task=${affectedIds.join(',')} --force # 重新执行`);
61
548
  return;
62
549
  }
63
550
  if (!options.desc) {
@@ -78,16 +565,24 @@ async function changeCommand(options) {
78
565
  spinner.fail('未找到活跃迭代。请先运行: speccore iteration create --name <名称>');
79
566
  return;
80
567
  }
81
- // 短 Task ID 支持: Task-001 Task-001-订单管理
568
+ // 短 Task ID 支持: 使用统一 resolver 解析
82
569
  if (options.task) {
83
- const tasks = await (0, state_1.scanTasks)(iteration);
84
- const exact = tasks.find(t => t.id === options.task);
85
- if (!exact) {
86
- const prefix = tasks.filter(t => t.id.startsWith(options.task));
87
- if (prefix.length === 1) {
88
- logger_1.logger.info(`📎 Task 短名匹配: ${options.task} → ${prefix[0].id}`);
89
- options.task = prefix[0].id;
570
+ const taskResult = await (0, resolver_1.resolveTask)(options.task, iteration);
571
+ if (taskResult.exact && taskResult.value) {
572
+ if (taskResult.matchType !== 'exact') {
573
+ const hint = (0, resolver_1.formatResolveResult)(taskResult, 'Task');
574
+ if (hint)
575
+ logger_1.logger.info(hint);
90
576
  }
577
+ options.task = taskResult.value.id;
578
+ }
579
+ else if (taskResult.candidates.length > 1) {
580
+ logger_1.logger.warn(taskResult.hint || '找到多个匹配任务,请指定更精确的名称');
581
+ return;
582
+ }
583
+ else {
584
+ logger_1.logger.warn(taskResult.hint || `Task "${options.task}" 未找到`);
585
+ return;
91
586
  }
92
587
  }
93
588
  if (options.dryRun) {
@@ -124,12 +619,33 @@ async function changeCommand(options) {
124
619
  }
125
620
  else {
126
621
  await applyTaskChange(options, iteration);
622
+ // 任务级变更也记录到 CHANGE_SUMMARY.md
623
+ if (options.task && iteration) {
624
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
625
+ const taskImpact = {
626
+ id: options.task,
627
+ name: options.task,
628
+ status: 'changed',
629
+ level: 'direct',
630
+ reason: options.desc || '任务级变更',
631
+ affectedFiles: ['REQ.md', 'CHANGELOG.md', 'TASK.md'],
632
+ needReExecute: true,
633
+ needRegression: false,
634
+ };
635
+ await writeChangeSummary(iterDir, options.desc || '任务级变更', { directTasks: [taskImpact], indirectTasks: [], unaffectedTasks: [] }, [options.task]);
636
+ }
127
637
  }
128
638
  spinner.stop('需求变更已生效');
129
639
  logger_1.logger.info('');
640
+ if (options.task) {
641
+ logger_1.logger.info(` 📄 变更摘要: 020-specs/CHANGE_SUMMARY.md`);
642
+ }
130
643
  logger_1.logger.info('下一步:');
131
644
  logger_1.logger.info(' 1. 运行 speccore validate --task=' + (options.task || '') + ' 验证完整性');
132
645
  logger_1.logger.info(' 2. 检查受影响的下游任务是否需要回归');
646
+ if (options.task) {
647
+ logger_1.logger.info(` 3. 重新执行变更任务: speccore execute --task=${options.task} --force`);
648
+ }
133
649
  }
134
650
  catch (error) {
135
651
  spinner.fail(`变更失败: ${error}`);
@@ -151,15 +667,18 @@ async function dryRunChange(options, iteration) {
151
667
  logger_1.logger.info('');
152
668
  logger_1.logger.info(`变更描述: ${options.desc}`);
153
669
  if (iteration) {
154
- const taskDir = (0, path_1.join)(process.cwd(), iteration, options.task || '');
670
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
671
+ const taskBase = await resolveTaskBase(iterDir);
672
+ const taskDir = (0, path_1.join)(taskBase, options.task || '');
155
673
  logger_1.logger.info('| 文件 | 影响描述 |');
156
674
  logger_1.logger.info('| :--- | :--- |');
157
- logger_1.logger.info(`| ${options.task}/backend/REQ.md | 需求变更 |`);
158
- logger_1.logger.info(`| ${options.task}/backend/TECH.md | 方案需调整 |`);
675
+ logger_1.logger.info(`| ${options.task}/00-specs/REQ.md | 需求变更 |`);
676
+ logger_1.logger.info(`| ${options.task}/00-specs/TECH.md | 方案需调整 |`);
677
+ logger_1.logger.info(`| ${options.task}/00-specs/CHANGELOG.md | 变更记录追加 |`);
159
678
  logger_1.logger.info(`| ${options.task}/_shared/API_CONTRACT.yaml | 接口契约可能需更新 |`);
160
679
  // 查找受影响的依赖任务
161
680
  if (await (0, fs_extra_1.pathExists)(taskDir)) {
162
- const graphPath = (0, path_1.join)(process.cwd(), iteration, '000-overview', 'PROJECT_GRAPH.md');
681
+ const graphPath = (0, path_1.join)(iterDir, '000-overview', 'PROJECT_GRAPH.md');
163
682
  if (await (0, fs_extra_1.pathExists)(graphPath)) {
164
683
  const content = await (0, fs_extra_1.readFile)(graphPath, 'utf-8');
165
684
  const deps = findDependentTasks(content, options.task || '');
@@ -179,30 +698,45 @@ async function applyTaskChange(options, iteration) {
179
698
  logger_1.logger.error('请指定 --task');
180
699
  return;
181
700
  }
182
- const taskDir = (0, path_1.join)(process.cwd(), iteration, '030-tasks', options.task);
701
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
702
+ const taskBase = await resolveTaskBase(iterDir);
703
+ const taskDir = (0, path_1.join)(taskBase, options.task);
183
704
  if (!await (0, fs_extra_1.pathExists)(taskDir)) {
184
705
  logger_1.logger.error(`任务目录不存在: ${taskDir}`);
185
706
  return;
186
707
  }
187
708
  const tx = new transaction_1.FileTransaction();
188
709
  const now = new Date().toISOString().split('T')[0];
710
+ const ver = await nextVersion((0, path_1.join)(taskDir, '00-specs', 'CHANGELOG.md'));
189
711
  // 更新 REQ.md(事务保护)
190
- const reqPath = (0, path_1.join)(taskDir, 'backend', 'REQ.md');
712
+ const reqPath = (0, path_1.join)(taskDir, '00-specs', 'REQ.md');
191
713
  if (await (0, fs_extra_1.pathExists)(reqPath)) {
192
714
  let content = await (0, fs_extra_1.readFile)(reqPath, 'utf-8');
193
- const changeNote = `\n## 变更记录\n\n| ${now} | v1.1 | ${options.desc} | SpecCore |\n`;
715
+ const changeNote = `\n## 变更记录\n\n| ${now} | ${ver} | ${options.desc} | SpecCore |\n`;
194
716
  tx.write(reqPath, content + changeNote);
195
717
  }
718
+ // 更新 CHANGELOG.md(事务保护)
719
+ const changelogPath = (0, path_1.join)(taskDir, '00-specs', 'CHANGELOG.md');
720
+ if (await (0, fs_extra_1.pathExists)(changelogPath)) {
721
+ let content = await (0, fs_extra_1.readFile)(changelogPath, 'utf-8');
722
+ const changeEntry = `| ${now} | ${ver} | ${options.desc} | SpecCore |\n`;
723
+ const updated = content.replace(/(\| :--- \| :--- \| :--- \| :--- \|)/, `$1\n${changeEntry}`);
724
+ tx.write(changelogPath, updated);
725
+ }
726
+ else {
727
+ // 如果没有 CHANGELOG.md,创建一个
728
+ tx.write(changelogPath, `# 变更记录\n\n| 时间 | 版本 | 变更内容 | 变更人 |\n| :--- | :--- | :--- | :--- |\n| ${now} | ${ver} | ${options.desc} | SpecCore |\n`);
729
+ }
196
730
  // 更新 TASK.md 变更履历(事务保护)
197
- const taskMdPath = (0, path_1.join)(taskDir, 'backend', 'TASK.md');
731
+ const taskMdPath = (0, path_1.join)(taskDir, '00-specs', 'TASK.md');
198
732
  if (await (0, fs_extra_1.pathExists)(taskMdPath)) {
199
733
  let content = await (0, fs_extra_1.readFile)(taskMdPath, 'utf-8');
200
- const changeEntry = `| ${now} | v1.1 | 需求变更: ${options.desc} | SpecCore |\n`;
734
+ const changeEntry = `| ${now} | ${ver} | 需求变更: ${options.desc} | SpecCore |\n`;
201
735
  const updated = content.replace(/(\| :--- \| :--- \| :--- \| :--- \|)/, `$1\n${changeEntry}`);
202
736
  tx.write(taskMdPath, updated);
203
737
  }
204
738
  // 同步前端各平台 TASK.md(事务保护)
205
- const frontendDir = (0, path_1.join)(taskDir, 'frontend');
739
+ const frontendDir = (0, path_1.join)(taskDir, '20-frontend');
206
740
  if (await (0, fs_extra_1.pathExists)(frontendDir)) {
207
741
  const { readdir: rd } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
208
742
  const platformDirs = await rd(frontendDir, { withFileTypes: true });
@@ -211,24 +745,34 @@ async function applyTaskChange(options, iteration) {
211
745
  const ftaskPath = (0, path_1.join)(frontendDir, pd.name, 'TASK.md');
212
746
  if (await (0, fs_extra_1.pathExists)(ftaskPath)) {
213
747
  let content = await (0, fs_extra_1.readFile)(ftaskPath, 'utf-8');
214
- const changeEntry = `| ${now} | v1.1 | 需求变更: ${options.desc} | SpecCore |\n`;
748
+ const changeEntry = `| ${now} | ${ver} | 需求变更: ${options.desc} | SpecCore |\n`;
215
749
  const updated = content.replace(/(\| :--- \| :--- \| :--- \| :--- \|)/, `$1\n${changeEntry}`);
216
750
  tx.write(ftaskPath, updated);
217
751
  }
218
752
  }
219
753
  }
220
754
  }
755
+ // 更新任务状态为 needs-rework
756
+ const metaStatusPath = (0, path_1.join)(taskDir, '.meta', 'status');
757
+ if (await (0, fs_extra_1.pathExists)(metaStatusPath)) {
758
+ const currentStatus = await (0, fs_extra_1.readFile)(metaStatusPath, 'utf-8');
759
+ if (currentStatus.trim() === 'done') {
760
+ tx.write(metaStatusPath, 'needs-rework');
761
+ logger_1.logger.info(` 📌 任务状态从 done 回退为 needs-rework`);
762
+ }
763
+ }
221
764
  // 提交事务 — 原子写入,失败回滚
222
765
  if (tx.length > 0) {
223
766
  await tx.commit();
224
767
  logger_1.logger.info(`✅ 已更新 ${tx.length} 个文件(事务保护)`);
225
- logger_1.logger.info(` ${options.task}/backend/REQ.md`);
226
- logger_1.logger.info(` ${options.task}/backend/TASK.md`);
227
- // ── 联动更新上层文档 ──
228
- if (options.requirement && options.task) {
768
+ logger_1.logger.info(` ${options.task}/00-specs/REQ.md`);
769
+ logger_1.logger.info(` ${options.task}/00-specs/CHANGELOG.md`);
770
+ logger_1.logger.info(` ${options.task}/00-specs/TASK.md`);
771
+ // ── 联动更新上层文档(默认启用,--no-sync 禁用)──
772
+ if (options.requirement !== false && options.task) {
229
773
  await syncToRequirement(iteration, options.task, options.desc);
230
774
  }
231
- if (options.analysis && options.task) {
775
+ if (options.analysis !== false && options.task) {
232
776
  await syncToAnalysis(iteration, options.task, options.desc);
233
777
  }
234
778
  }
@@ -297,11 +841,56 @@ function normalizeDescription(desc) {
297
841
  // 无法识别,原样返回但去语气词
298
842
  return desc.replace(/[了啦啊呢嗯哦哈]$/, '').trim();
299
843
  }
844
+ /**
845
+ * 同步变更到 REQUIREMENT.md(迭代聚合需求文档)
846
+ */
847
+ /**
848
+ * 将澄清结果(需求分析)持久化到迭代级 CHANGE_SUMMARY.md
849
+ */
850
+ async function writeChangeSummary(iterDir, changeDesc, impactReport, affectedIds) {
851
+ const summaryPath = (0, path_1.join)(iterDir, '020-specs', 'CHANGE_SUMMARY.md');
852
+ const now = new Date().toISOString().split('T')[0];
853
+ let content = '';
854
+ if (await (0, fs_extra_1.pathExists)(summaryPath)) {
855
+ content = await (0, fs_extra_1.readFile)(summaryPath, 'utf-8');
856
+ }
857
+ else {
858
+ content = `# 变更摘要\n\n> 需求变更的分析记录,由 speccore change 自动生成\n\n`;
859
+ }
860
+ // 追加本次变更记录
861
+ content += `\n---\n\n## ${now} — ${changeDesc}\n\n`;
862
+ // 直接影响
863
+ if (impactReport.directTasks.length > 0) {
864
+ content += `### 🔴 直接影响(需修改 Spec + 重新执行)\n\n`;
865
+ for (const t of impactReport.directTasks) {
866
+ content += `- **${t.id}** ${t.name} — ${t.reason}\n`;
867
+ if (t.affectedFiles.length > 0) {
868
+ content += ` - 受影响文件: ${t.affectedFiles.join(', ')}\n`;
869
+ }
870
+ }
871
+ content += '\n';
872
+ }
873
+ // 间接影响
874
+ if (impactReport.indirectTasks.length > 0) {
875
+ content += `### 🟡 间接影响(需回归验证)\n\n`;
876
+ for (const t of impactReport.indirectTasks) {
877
+ content += `- **${t.id}** ${t.name} — ${t.reason}\n`;
878
+ }
879
+ content += '\n';
880
+ }
881
+ // 无影响
882
+ if (impactReport.unaffectedTasks.length > 0) {
883
+ content += `### 🟢 无影响\n\n`;
884
+ content += impactReport.unaffectedTasks.map(t => t.id).join(', ') + '\n\n';
885
+ }
886
+ content += `**已应用变更的任务**: ${affectedIds.join(', ')}\n`;
887
+ await (0, fs_extra_1.writeFile)(summaryPath, content);
888
+ }
300
889
  /**
301
890
  * 同步变更到 REQUIREMENT.md(迭代聚合需求文档)
302
891
  */
303
892
  async function syncToRequirement(iteration, taskId, desc) {
304
- const iterDir = `Iteration-${iteration}`;
893
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
305
894
  const reqPath = (0, path_1.join)(iterDir, '020-specs', 'REQUIREMENT.md');
306
895
  if (!(await (0, fs_extra_1.pathExists)(reqPath))) {
307
896
  logger_1.logger.warn(` ⚠️ REQUIREMENT.md 不存在,跳过同步`);
@@ -333,7 +922,7 @@ async function syncToRequirement(iteration, taskId, desc) {
333
922
  * 同步变更到 ANALYSIS.md(技术方案文档)
334
923
  */
335
924
  async function syncToAnalysis(iteration, taskId, desc) {
336
- const iterDir = `Iteration-${iteration}`;
925
+ const iterDir = await (0, context_1.getIterationDir)(iteration);
337
926
  const analysisPath = (0, path_1.join)(iterDir, '020-specs', 'ANALYSIS.md');
338
927
  if (!(await (0, fs_extra_1.pathExists)(analysisPath))) {
339
928
  logger_1.logger.warn(` ⚠️ ANALYSIS.md 不存在,跳过同步。请先运行 speccore analyze`);