speccore 8.3.285 → 8.3.295

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 (143) hide show
  1. package/.agents/skills/spec-run/SKILL.md +16 -0
  2. package/.agents/skills/spec-task-create/SKILL.md +15 -0
  3. package/.agents/skills/spec-unitize/SKILL.md +16 -0
  4. package/README.en.md +16 -3
  5. package/README.md +23 -8
  6. package/dist/cli.js +59 -12
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/about.d.ts.map +1 -1
  9. package/dist/commands/about.js +5 -0
  10. package/dist/commands/about.js.map +1 -1
  11. package/dist/commands/analyze.d.ts.map +1 -1
  12. package/dist/commands/analyze.js +77 -5
  13. package/dist/commands/analyze.js.map +1 -1
  14. package/dist/commands/ask.js +2 -2
  15. package/dist/commands/ask.js.map +1 -1
  16. package/dist/commands/code-index.d.ts.map +1 -1
  17. package/dist/commands/code-index.js +20 -9
  18. package/dist/commands/code-index.js.map +1 -1
  19. package/dist/commands/doc2spec.js +32 -60
  20. package/dist/commands/doc2spec.js.map +1 -1
  21. package/dist/commands/execute.d.ts +1 -0
  22. package/dist/commands/execute.d.ts.map +1 -1
  23. package/dist/commands/execute.js +15 -1
  24. package/dist/commands/execute.js.map +1 -1
  25. package/dist/commands/help.js +14 -5
  26. package/dist/commands/help.js.map +1 -1
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +14 -3
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/commands/iteration/taskify.d.ts.map +1 -1
  31. package/dist/commands/iteration/taskify.js +132 -7
  32. package/dist/commands/iteration/taskify.js.map +1 -1
  33. package/dist/commands/run.d.ts +1 -0
  34. package/dist/commands/run.d.ts.map +1 -1
  35. package/dist/commands/run.js +193 -25
  36. package/dist/commands/run.js.map +1 -1
  37. package/dist/commands/status-panel.d.ts +1 -0
  38. package/dist/commands/status-panel.d.ts.map +1 -1
  39. package/dist/commands/status-panel.js +180 -54
  40. package/dist/commands/status-panel.js.map +1 -1
  41. package/dist/commands/task/new.d.ts +16 -0
  42. package/dist/commands/task/new.d.ts.map +1 -1
  43. package/dist/commands/task/new.js +232 -14
  44. package/dist/commands/task/new.js.map +1 -1
  45. package/dist/commands/verify.d.ts.map +1 -1
  46. package/dist/commands/verify.js +78 -1
  47. package/dist/commands/verify.js.map +1 -1
  48. package/dist/core/analyze-engine.d.ts.map +1 -1
  49. package/dist/core/analyze-engine.js +6 -10
  50. package/dist/core/analyze-engine.js.map +1 -1
  51. package/dist/core/ask-llm.d.ts +7 -0
  52. package/dist/core/ask-llm.d.ts.map +1 -1
  53. package/dist/core/ask-llm.js +45 -0
  54. package/dist/core/ask-llm.js.map +1 -1
  55. package/dist/core/code-graph/incremental-build.d.ts +2 -0
  56. package/dist/core/code-graph/incremental-build.d.ts.map +1 -1
  57. package/dist/core/code-graph/incremental-build.js +6 -5
  58. package/dist/core/code-graph/incremental-build.js.map +1 -1
  59. package/dist/core/code-graph/index.d.ts +2 -0
  60. package/dist/core/code-graph/index.d.ts.map +1 -1
  61. package/dist/core/code-graph/index.js +12 -5
  62. package/dist/core/code-graph/index.js.map +1 -1
  63. package/dist/core/code-scanner.d.ts +19 -4
  64. package/dist/core/code-scanner.d.ts.map +1 -1
  65. package/dist/core/code-scanner.js +193 -77
  66. package/dist/core/code-scanner.js.map +1 -1
  67. package/dist/core/excel-extract.d.ts +16 -2
  68. package/dist/core/excel-extract.d.ts.map +1 -1
  69. package/dist/core/excel-extract.js +54 -12
  70. package/dist/core/excel-extract.js.map +1 -1
  71. package/dist/core/git-integration.d.ts +2 -1
  72. package/dist/core/git-integration.d.ts.map +1 -1
  73. package/dist/core/git-integration.js +38 -14
  74. package/dist/core/git-integration.js.map +1 -1
  75. package/dist/core/global-counters.d.ts +3 -0
  76. package/dist/core/global-counters.d.ts.map +1 -1
  77. package/dist/core/global-counters.js +21 -7
  78. package/dist/core/global-counters.js.map +1 -1
  79. package/dist/core/intent-recognition.d.ts.map +1 -1
  80. package/dist/core/intent-recognition.js +173 -27
  81. package/dist/core/intent-recognition.js.map +1 -1
  82. package/dist/core/plan-store.d.ts +1 -0
  83. package/dist/core/plan-store.d.ts.map +1 -1
  84. package/dist/core/plan-store.js.map +1 -1
  85. package/dist/core/prompt-builder.d.ts.map +1 -1
  86. package/dist/core/prompt-builder.js +168 -140
  87. package/dist/core/prompt-builder.js.map +1 -1
  88. package/dist/core/spec-paths.d.ts.map +1 -1
  89. package/dist/core/spec-paths.js +3 -0
  90. package/dist/core/spec-paths.js.map +1 -1
  91. package/dist/core/spec-skeleton.d.ts.map +1 -1
  92. package/dist/core/spec-skeleton.js +6 -2
  93. package/dist/core/spec-skeleton.js.map +1 -1
  94. package/dist/core/stage-aliases.d.ts +40 -0
  95. package/dist/core/stage-aliases.d.ts.map +1 -0
  96. package/dist/core/stage-aliases.js +110 -0
  97. package/dist/core/stage-aliases.js.map +1 -0
  98. package/dist/core/state.d.ts +11 -0
  99. package/dist/core/state.d.ts.map +1 -1
  100. package/dist/core/state.js +33 -2
  101. package/dist/core/state.js.map +1 -1
  102. package/dist/core/structured-extractor.d.ts.map +1 -1
  103. package/dist/core/structured-extractor.js +7 -0
  104. package/dist/core/structured-extractor.js.map +1 -1
  105. package/dist/core/ui-verify/index.d.ts +2 -2
  106. package/dist/core/ui-verify/index.d.ts.map +1 -1
  107. package/dist/core/ui-verify/index.js +3 -1
  108. package/dist/core/ui-verify/index.js.map +1 -1
  109. package/dist/core/ui-verify/smoke-engine.d.ts.map +1 -1
  110. package/dist/core/ui-verify/smoke-engine.js +36 -0
  111. package/dist/core/ui-verify/smoke-engine.js.map +1 -1
  112. package/dist/core/ui-verify/spec-generator.d.ts +32 -1
  113. package/dist/core/ui-verify/spec-generator.d.ts.map +1 -1
  114. package/dist/core/ui-verify/spec-generator.js +205 -21
  115. package/dist/core/ui-verify/spec-generator.js.map +1 -1
  116. package/dist/core/ui-verify/types.d.ts +2 -0
  117. package/dist/core/ui-verify/types.d.ts.map +1 -1
  118. package/dist/utils/synonyms.d.ts.map +1 -1
  119. package/dist/utils/synonyms.js +15 -0
  120. package/dist/utils/synonyms.js.map +1 -1
  121. package/package.json +3 -2
  122. package/templates/api-design-example.md +338 -0
  123. package/templates/ci/github-actions.yml +44 -0
  124. package/templates/database-example.md +223 -0
  125. package/templates/deploy-docker/README.md +284 -0
  126. package/templates/deploy-docker/deploy-remote.sh +79 -0
  127. package/templates/deploy-docker/docker-compose.yml +113 -0
  128. package/templates/deploy-docker/frontend.Dockerfile +43 -0
  129. package/templates/deploy-docker/nginx.conf +30 -0
  130. package/templates/deploy-examples.yaml +436 -0
  131. package/templates/deploy-java/Dockerfile +50 -0
  132. package/templates/deploy-java/README.md +212 -0
  133. package/templates/deploy-java/deploy.sh +120 -0
  134. package/templates/deploy-java/spring-boot.service +35 -0
  135. package/templates/html/speccore-ask-explain.html +10 -0
  136. package/templates/html/speccore-ask-guide.html +10 -0
  137. package/templates/html/speccore-ask-match.html +10 -0
  138. package/templates/html/speccore-ask-pipeline.html +1 -0
  139. package/templates/html/speccore-ask-result.html +10 -0
  140. package/templates/html/speccore-help.html +241 -0
  141. package/templates/html/speccore-setup-guide.html +289 -0
  142. package/templates/security-example.md +226 -0
  143. package/templates/verify-spec-example.yaml +138 -0
@@ -85,7 +85,7 @@ async function statusPanelCommand(options = {}) {
85
85
  logger_1.logger.info(`│ 项目: ${projectConfig.project.name.padEnd(37)}│`);
86
86
  if (iteration) {
87
87
  logger_1.logger.info(`│ 迭代: ${iteration.padEnd(37)}│`);
88
- const iterDir = `Iteration-${iteration}`;
88
+ const iterDir = resolveIterDir(iteration);
89
89
  // Phase detection
90
90
  const phase = await detectPhase(iterDir);
91
91
  const phaseIcon = { init: '🔧', require: '📝', analyze: '🔍', split: '📦', dev: '💻', review: '✅', done: '✨' }[phase] || '📌';
@@ -130,6 +130,37 @@ async function statusPanelCommand(options = {}) {
130
130
  logger_1.logger.info(`│ ${name}: ${pBar} ${s.done}/${s.total}`.padEnd(47) + '│');
131
131
  }
132
132
  }
133
+ // v8.3.288+: 模块统计(按 .meta/feature 分组,精确到功能模块)
134
+ const moduleStats = buildModuleStats(tasks);
135
+ if (Object.keys(moduleStats).length > 0) {
136
+ logger_1.logger.info('├──────────────────────────────────────────────┤');
137
+ logger_1.logger.info('│ 模块统计:'.padEnd(47) + '│');
138
+ for (const [name, s] of Object.entries(moduleStats)
139
+ .sort((a, b) => b[1].total - a[1].total)) {
140
+ const pBar = buildProgressBar(s.done, s.total);
141
+ logger_1.logger.info(`│ ${name}: ${pBar} ${s.done}/${s.total}`.padEnd(47) + '│');
142
+ }
143
+ }
144
+ // v8.3.293+: 逻辑分组视图(方案 A)——按 .meta/feature 分组列出任务清单,
145
+ // 解决 bugfix 任务平铺目录下 bug 多不便浏览的问题(元数据分组,不改变物理目录)。
146
+ // 触发:--group-by feature 显式启用;或 --type bugfix 时自动启用。
147
+ const groupByFeature = options.groupBy === 'feature' || (options.type || '').toLowerCase() === 'bugfix';
148
+ if (groupByFeature && Object.keys(moduleStats).length > 0) {
149
+ logger_1.logger.info('├──────────────────────────────────────────────┤');
150
+ logger_1.logger.info('│ 模块清单:'.padEnd(47) + '│');
151
+ const grouped = groupTasksByFeature(tasks);
152
+ for (const [feature, list] of grouped) {
153
+ const s = moduleStats[feature] || { total: list.length, done: 0 };
154
+ const name = feature.length > 16 ? feature.slice(0, 15) + '…' : feature;
155
+ logger_1.logger.info(`│ ▸ ${name}(${s.done}/${s.total})`.padEnd(47) + '│');
156
+ for (const t of list) {
157
+ const icon = statusIconFor(t.status);
158
+ const who = t.assignee ? ` · ${t.assignee}` : '';
159
+ const line = ` ${icon} ${t.id} [${t.status}]${who}`;
160
+ logger_1.logger.info(`│${line.slice(0, 45).padEnd(46)}│`);
161
+ }
162
+ }
163
+ }
133
164
  // Platform breakdown
134
165
  const platformCount = await countPlatforms(iterDir, tasks);
135
166
  if (platformCount.backend + platformCount.frontend > 0) {
@@ -163,40 +194,113 @@ async function statusPanelCommand(options = {}) {
163
194
  logger_1.logger.info('└──────────────────────────────────────────────┘');
164
195
  logger_1.logger.info('');
165
196
  }
197
+ // ── 共享:迭代目录归一化(context.json 存全名 Iteration-NNN,视图内只加一次前缀)──
198
+ function resolveIterDir(iteration) {
199
+ return iteration.startsWith('Iteration-') ? iteration : `Iteration-${iteration}`;
200
+ }
166
201
  // ── 共享:收集所有任务数据 ──
167
202
  async function collectTasks(iterDir) {
168
203
  const tasks = [];
169
204
  if (!(await (0, fs_extra_1.pathExists)(iterDir)))
170
205
  return tasks;
171
- const entryList = await (0, fs_extra_1.readdir)(iterDir, { withFileTypes: true });
172
- for (const e of entryList) {
173
- if (e.isDirectory() && e.name.startsWith('Task-')) {
174
- const taskPath = (0, path_1.join)(iterDir, e.name, '00-specs', 'TASK.md');
175
- try {
176
- if (await (0, fs_extra_1.pathExists)(taskPath)) {
177
- const md = await (0, fs_extra_1.readFile)(taskPath, 'utf-8');
178
- const status = (md.match(/状态: (.+)/) || [])[1] || 'pending';
179
- const type = (md.match(/类型: (.+)/) || [])[1] || 'feature';
180
- const assignee = (md.match(/负责人[::]\s*(\S+)/) || [])[1] || '';
181
- tasks.push({ id: e.name, status, type, assignee });
206
+ // v8.3.288+: 扫描根级 + 030-tasks/{type}/ 两种布局(task new 建在 030-tasks/{type}/ 下,
207
+ // 此前只扫根级导致导入的 bug 任务在面板隐形,模块统计为空)
208
+ const roots = [iterDir];
209
+ const tasksDir = (0, path_1.join)(iterDir, '030-tasks');
210
+ if (await (0, fs_extra_1.pathExists)(tasksDir)) {
211
+ const subEntries = await (0, fs_extra_1.readdir)(tasksDir, { withFileTypes: true });
212
+ for (const se of subEntries) {
213
+ if (se.isDirectory())
214
+ roots.push((0, path_1.join)(tasksDir, se.name));
215
+ }
216
+ }
217
+ for (const root of roots) {
218
+ const entryList = await (0, fs_extra_1.readdir)(root, { withFileTypes: true });
219
+ for (const e of entryList) {
220
+ if (e.isDirectory() && e.name.startsWith('Task-')) {
221
+ const taskDir = (0, path_1.join)(root, e.name);
222
+ // v8.3.288+: .meta 优先(task new 任务无 00-specs/TASK.md,状态/类型/模块都在 .meta)
223
+ let feature = '';
224
+ let metaStatus = '';
225
+ let metaType = '';
226
+ try {
227
+ const featurePath = (0, path_1.join)(taskDir, '.meta', 'feature');
228
+ if (await (0, fs_extra_1.pathExists)(featurePath))
229
+ feature = (await (0, fs_extra_1.readFile)(featurePath, 'utf-8')).trim();
230
+ const statusPath = (0, path_1.join)(taskDir, '.meta', 'status');
231
+ if (await (0, fs_extra_1.pathExists)(statusPath))
232
+ metaStatus = (await (0, fs_extra_1.readFile)(statusPath, 'utf-8')).trim();
233
+ const typePath = (0, path_1.join)(taskDir, '.meta', 'type');
234
+ if (await (0, fs_extra_1.pathExists)(typePath))
235
+ metaType = (await (0, fs_extra_1.readFile)(typePath, 'utf-8')).trim();
182
236
  }
183
- else {
184
- tasks.push({ id: e.name, status: 'pending', type: 'feature', assignee: '' });
237
+ catch { }
238
+ const taskPath = (0, path_1.join)(taskDir, '00-specs', 'TASK.md');
239
+ try {
240
+ if (await (0, fs_extra_1.pathExists)(taskPath)) {
241
+ const md = await (0, fs_extra_1.readFile)(taskPath, 'utf-8');
242
+ const status = metaStatus || (md.match(/状态: (.+)/) || [])[1] || 'pending';
243
+ const type = metaType || (md.match(/类型: (.+)/) || [])[1] || 'feature';
244
+ const assignee = (md.match(/负责人[::]\s*(\S+)/) || [])[1] || '';
245
+ tasks.push({ id: e.name, status, type, assignee, feature, dir: taskDir });
246
+ }
247
+ else {
248
+ tasks.push({ id: e.name, status: metaStatus || 'pending', type: metaType || 'feature', assignee: '', feature, dir: taskDir });
249
+ }
250
+ }
251
+ catch {
252
+ tasks.push({ id: e.name, status: metaStatus || 'pending', type: metaType || 'feature', assignee: '', feature, dir: taskDir });
185
253
  }
186
- }
187
- catch {
188
- tasks.push({ id: e.name, status: 'pending', type: 'feature', assignee: '' });
189
254
  }
190
255
  }
191
256
  }
192
257
  return tasks;
193
258
  }
259
+ // v8.3.288+: 按功能模块分组统计(模块 × 任务数/完成数),供看板与终端精确呈现
260
+ function buildModuleStats(tasks) {
261
+ const stats = {};
262
+ for (const t of tasks) {
263
+ const feature = (t.feature || '未分类').trim() || '未分类';
264
+ if (!stats[feature])
265
+ stats[feature] = { total: 0, done: 0 };
266
+ stats[feature].total++;
267
+ if (t.status.includes('completed') || t.status.includes('完成'))
268
+ stats[feature].done++;
269
+ }
270
+ return stats;
271
+ }
272
+ // v8.3.293+: 任务按功能模块分组(组按任务数降序,组内按任务 ID 排序),供逻辑分组视图使用
273
+ function groupTasksByFeature(tasks) {
274
+ const groups = new Map();
275
+ for (const t of tasks) {
276
+ const feature = (t.feature || '未分类').trim() || '未分类';
277
+ if (!groups.has(feature))
278
+ groups.set(feature, []);
279
+ groups.get(feature).push(t);
280
+ }
281
+ return [...groups.entries()]
282
+ .sort((a, b) => b[1].length - a[1].length)
283
+ .map(([feature, list]) => [feature, list.sort((x, y) => (x.id < y.id ? -1 : x.id > y.id ? 1 : 0))]);
284
+ }
285
+ // v8.3.293+: 任务状态 → 终端图标(与生命周期看板同色系)
286
+ function statusIconFor(status) {
287
+ const st = status || '';
288
+ if (/完成|completed|done/i.test(st))
289
+ return '🟢';
290
+ if (/开发|doing|in_progress|进行/i.test(st))
291
+ return '🔵';
292
+ if (/测试|testing/i.test(st))
293
+ return '🟡';
294
+ if (/审查|review/i.test(st))
295
+ return '🟣';
296
+ return '🔲';
297
+ }
194
298
  // ── 共享:按平台统计 ──
195
299
  async function countPlatforms(iterDir, tasks) {
196
300
  let backend = 0, frontend = 0;
197
301
  for (const t of tasks) {
198
302
  try {
199
- const entries = await (0, fs_extra_1.readdir)((0, path_1.join)(iterDir, t.id), { withFileTypes: true });
303
+ const entries = await (0, fs_extra_1.readdir)(t.dir || (0, path_1.join)(iterDir, t.id), { withFileTypes: true });
200
304
  if (entries.some((e) => e.name === 'backend'))
201
305
  backend++;
202
306
  if (entries.some((e) => e.name === 'frontend'))
@@ -306,7 +410,7 @@ async function filterByPlatform(iterDir, tasks, platform) {
306
410
  const { join } = require('path');
307
411
  const result = [];
308
412
  for (const t of tasks) {
309
- const taskDir = join(iterDir, t.id);
413
+ const taskDir = t.dir || join(iterDir, t.id);
310
414
  try {
311
415
  const entries = await readdir(taskDir, { withFileTypes: true });
312
416
  if (platform === 'backend' && entries.some((e) => e.name === 'backend'))
@@ -328,7 +432,7 @@ async function exportStatus(config, projectConfig, iteration, format, options =
328
432
  phases: {},
329
433
  };
330
434
  if (iteration) {
331
- const iterDir = (0, path_1.join)(process.cwd(), 'Iteration-' + iteration);
435
+ const iterDir = (0, path_1.join)(process.cwd(), resolveIterDir(iteration));
332
436
  const phase = await require('./status-panel').defaultPhase(iterDir);
333
437
  data.phase = phase;
334
438
  // ── 读取迭代时间范围 ──
@@ -380,23 +484,25 @@ async function exportStatus(config, projectConfig, iteration, format, options =
380
484
  }
381
485
  const tasks = [];
382
486
  if (await (0, fs_extra_1.pathExists)(iterDir)) {
383
- const entryList = await (0, fs_extra_1.readdir)(iterDir, { withFileTypes: true });
384
- for (const e of entryList) {
385
- if (e.isDirectory() && e.name.startsWith('Task-')) {
386
- const taskPath = (0, path_1.join)(iterDir, e.name, '00-specs', 'TASK.md');
487
+ // v8.3.288+: 复用 collectTasks 多根扫描(含 030-tasks/{type}/ 布局与 .meta/feature),
488
+ // 此前只扫迭代根级导致 task new 导入的 bug 任务在看板隐形
489
+ const base = await collectTasks(iterDir);
490
+ for (const t of base) {
491
+ const item = { id: t.id, status: t.status, type: t.type, assignee: t.assignee || '', feature: t.feature, created: '', estimate: 0, delay: false };
492
+ // 补读 00-specs/TASK.md 的扩展字段(task new 任务无此文件,保持空值)
493
+ try {
494
+ const taskPath = (0, path_1.join)(t.dir, '00-specs', 'TASK.md');
387
495
  if (await (0, fs_extra_1.pathExists)(taskPath)) {
388
496
  const md = await (0, fs_extra_1.readFile)(taskPath, 'utf-8');
389
- const status = (md.match(/状态: (.+)/) || [])[1] || 'pending';
390
- const type = (md.match(/类型: (.+)/) || [])[1] || 'feature';
391
- const created = (md.match(/创建日期[::]?\s*(\d{4}-\d{2}-\d{2})/) || md.match(/创建:\s*(\d{4}-\d{2}-\d{2})/) || [])[1] || '';
497
+ item.created = (md.match(/创建日期[::]?\s*(\d{4}-\d{2}-\d{2})/) || md.match(/创建:\s*(\d{4}-\d{2}-\d{2})/) || [])[1] || '';
392
498
  const estimate = (md.match(/预估[工时::]?\s*(\d+)\s*[hH小时]/) || md.match(/预计耗时[::]?\s*(\d+)/) || [])[1] || '';
393
- const delay = (md.match(/延期\|DELAY/i) || []).length > 0;
394
- tasks.push({ id: e.name, status, type, created, estimate: estimate ? parseInt(estimate) : 0, delay, assignee: (md.match(/负责人[::]\s*(\S+)/) || [])[1] || '' });
395
- }
396
- else {
397
- tasks.push({ id: e.name, status: 'pending' });
499
+ item.estimate = estimate ? parseInt(estimate, 10) : 0;
500
+ item.delay = (md.match(/延期\|DELAY/i) || []).length > 0;
501
+ item.assignee = item.assignee || (md.match(/负责人[::]\s*(\S+)/) || [])[1] || '';
398
502
  }
399
503
  }
504
+ catch { }
505
+ tasks.push(item);
400
506
  }
401
507
  }
402
508
  data.tasks = tasks;
@@ -481,6 +587,7 @@ async function exportStatus(config, projectConfig, iteration, format, options =
481
587
  pd2[t.assignee].features++;
482
588
  pd2[t.assignee].estHours += t.estimate || 0;
483
589
  }
590
+ data.moduleStats = buildModuleStats(filtered);
484
591
  // 人员→平台映射
485
592
  data.personPlatforms = await buildPersonPlatforms(iterDir, filtered);
486
593
  data.personDetail = pd2;
@@ -523,6 +630,8 @@ async function exportStatus(config, projectConfig, iteration, format, options =
523
630
  personDetail[t.assignee].estHours += t.estimate || 0;
524
631
  }
525
632
  data.personDetail = personDetail;
633
+ // v8.3.288+: 模块统计(按 .meta/feature 分组)
634
+ data.moduleStats = buildModuleStats(tasks);
526
635
  // 人员→平台映射
527
636
  data.personPlatforms = await buildPersonPlatforms(iterDir, tasks);
528
637
  // 每人任务清单
@@ -603,7 +712,7 @@ async function buildPersonPlatforms(iterDir, tasks) {
603
712
  if (!t.assignee || map[t.assignee])
604
713
  continue;
605
714
  try {
606
- const entries = await readdir(join(iterDir, t.id), { withFileTypes: true });
715
+ const entries = await readdir(t.dir || join(iterDir, t.id), { withFileTypes: true });
607
716
  const platforms = [];
608
717
  // 扫描平铺的端目录
609
718
  const { parsePlatformList } = await Promise.resolve().then(() => __importStar(require('../core/spec-paths')));
@@ -664,6 +773,18 @@ function buildHtmlDashboard(data) {
664
773
  '</div>';
665
774
  }).join('');
666
775
  const hasAssignees = assigneeCards.length > 0;
776
+ // v8.3.288+: 模块统计(按 .meta/feature 分组,供精确项目统计)
777
+ const moduleStats = data.moduleStats || {};
778
+ const moduleRows = Object.entries(moduleStats)
779
+ .sort((a, b) => b[1].total - a[1].total)
780
+ .map(([name, s]) => {
781
+ const pct = s.total > 0 ? Math.round(s.done / s.total * 100) : 0;
782
+ const barW = s.total > 0 ? Math.round(s.done / s.total * 100) : 0;
783
+ return '<tr><td style="font-weight:600">' + name + '</td><td>' + s.total + '</td><td>' + s.done + '</td>' +
784
+ '<td><span style="color:' + (pct === 100 ? 'var(--green)' : 'var(--cyan)') + '">' + pct + '%</span></td>' +
785
+ '<td><div style="height:4px;background:rgba(255,255,255,.05);border-radius:2px;overflow:hidden;width:120px">' +
786
+ '<div style="width:' + barW + '%;height:100%;background:linear-gradient(90deg,var(--cyan),var(--green))"></div></div></td></tr>';
787
+ }).join('');
667
788
  const totalAi = data.totalAiTime || 0;
668
789
  const totalHuman = data.totalHumanTime || 0;
669
790
  const totalReview = data.totalReviewTime || 0;
@@ -1174,6 +1295,9 @@ td.code{font-family:'JetBrains Mono',monospace;color:var(--text);font-weight:600
1174
1295
  </div>
1175
1296
  </div>
1176
1297
 
1298
+ ${moduleRows ? '<div class="panel" style="margin-bottom:20px"><div class="panel-title">MODULE BREAKDOWN · 模块统计</div>' +
1299
+ '<table><thead><tr><th>所属模块</th><th>总任务</th><th>完成</th><th>完成率</th><th>进度</th></tr></thead><tbody>' + moduleRows + '</tbody></table></div>' : ''}
1300
+
1177
1301
  ${hasAssignees ? '<div class="panel" style="margin-bottom:20px"><div class="panel-title">TEAM DETAILS</div>' +
1178
1302
  // ── 按平台分组的汇总表 ──
1179
1303
  (() => {
@@ -1386,7 +1510,7 @@ async function showHealthReport(config, iteration) {
1386
1510
  logger_1.logger.info("无活跃迭代");
1387
1511
  return;
1388
1512
  }
1389
- const iterDir = `Iteration-${iteration}`;
1513
+ const iterDir = resolveIterDir(iteration);
1390
1514
  const tasks = await scanTaskDirs(iterDir);
1391
1515
  let totalTasks = tasks.length, completed = 0, hasTest = 0, hasReview = 0;
1392
1516
  for (const t of tasks) {
@@ -1416,14 +1540,15 @@ async function showLifecycleBoard(config, iteration, opts) {
1416
1540
  logger_1.logger.info("无活跃迭代");
1417
1541
  return;
1418
1542
  }
1419
- const iterDir = `Iteration-${iteration}`;
1420
- const tasks = await scanTaskDirs(iterDir);
1421
- logger_1.logger.info('\\n📋 任务生命周期 — ' + iteration);
1422
- const states = { pending: '🔲', in_progress: '🔵', testing: '🟡', review: '🟣', done: '🟢' };
1423
- for (const t of tasks) {
1424
- const status = await getTaskStatus(iterDir, t);
1425
- const icon = states[status] || '⚪';
1426
- logger_1.logger.info(' ' + icon + ' ' + t + ' [' + status + ']');
1543
+ const iterDir = resolveIterDir(iteration);
1544
+ // v8.3.293+: 改用 collectTasks 多根扫描(含 030-tasks/{type}/ 布局与 .meta/status),
1545
+ // 修复此前只扫迭代根级 Task- 目录、读旧 .task-status 文件导致 bugfix 等任务在看板隐形
1546
+ const taskList = await collectTasks(iterDir);
1547
+ logger_1.logger.info('\n📋 任务生命周期 — ' + iteration);
1548
+ for (const t of taskList) {
1549
+ const status = t.status || 'pending';
1550
+ const icon = statusIconFor(status);
1551
+ logger_1.logger.info(' ' + icon + ' ' + t.id + ' [' + status + ']');
1427
1552
  }
1428
1553
  logger_1.logger.info('');
1429
1554
  logger_1.logger.info(' 🔲待开始 → 🔵开发中 → 🟡测试中 → 🟣审查中 → 🟢已完成');
@@ -1448,15 +1573,16 @@ async function getTaskStatus(iterDir, task) {
1448
1573
  }
1449
1574
  // ── Collect Health Data for export ──
1450
1575
  async function collectHealthData(iteration) {
1451
- const iterDir = (0, path_1.join)(process.cwd(), "迭代-" + iteration);
1452
- const tasks = await scanTaskDirs(iterDir);
1453
- let total = tasks.length, completed = 0, hasTest = 0, hasReview = 0;
1454
- for (const t of tasks) {
1455
- if (await isTaskDone(iterDir, t))
1576
+ const iterDir = (0, path_1.join)(process.cwd(), resolveIterDir(iteration));
1577
+ // v8.3.293+: 多根扫描(含 030-tasks/{type}/),修复旧 scanTaskDirs 只扫根级导致漏计
1578
+ const taskList = await collectTasks(iterDir);
1579
+ let total = taskList.length, completed = 0, hasTest = 0, hasReview = 0;
1580
+ for (const t of taskList) {
1581
+ if (/完成|completed|done/i.test(t.status || ''))
1456
1582
  completed++;
1457
- if (await taskHasFile(iterDir, t, 'TEST.md'))
1583
+ if (await taskHasFile(iterDir, t.id, 'TEST.md'))
1458
1584
  hasTest++;
1459
- if (await taskHasFile(iterDir, t, 'REVIEW.md'))
1585
+ if (await taskHasFile(iterDir, t.id, 'REVIEW.md'))
1460
1586
  hasReview++;
1461
1587
  }
1462
1588
  const donePct = total > 0 ? Math.round(completed / total * 100) : 0;
@@ -1468,12 +1594,12 @@ async function collectHealthData(iteration) {
1468
1594
  }
1469
1595
  // ── Collect Lifecycle Data for export ──
1470
1596
  async function collectLifecycleData(iteration) {
1471
- const iterDir = (0, path_1.join)(process.cwd(), "迭代-" + iteration);
1472
- const taskDirs = await scanTaskDirs(iterDir);
1597
+ const iterDir = (0, path_1.join)(process.cwd(), resolveIterDir(iteration));
1598
+ // v8.3.293+: 多根扫描(含 030-tasks/{type}/),修复旧 scanTaskDirs 只扫根级导致漏计
1599
+ const taskList = await collectTasks(iterDir);
1473
1600
  const tasks = [];
1474
- for (const t of taskDirs) {
1475
- const status = await getTaskStatus(iterDir, t);
1476
- tasks.push({ id: t, status });
1601
+ for (const t of taskList) {
1602
+ tasks.push({ id: t.id, status: t.status || 'pending' });
1477
1603
  }
1478
1604
  return { tasks, iteration };
1479
1605
  }