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.
- package/.agents/skills/spec-run/SKILL.md +16 -0
- package/.agents/skills/spec-task-create/SKILL.md +15 -0
- package/.agents/skills/spec-unitize/SKILL.md +16 -0
- package/README.en.md +16 -3
- package/README.md +23 -8
- package/dist/cli.js +59 -12
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts.map +1 -1
- package/dist/commands/about.js +5 -0
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +77 -5
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/ask.js +2 -2
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/code-index.d.ts.map +1 -1
- package/dist/commands/code-index.js +20 -9
- package/dist/commands/code-index.js.map +1 -1
- package/dist/commands/doc2spec.js +32 -60
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/commands/execute.d.ts +1 -0
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +15 -1
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/help.js +14 -5
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/taskify.d.ts.map +1 -1
- package/dist/commands/iteration/taskify.js +132 -7
- package/dist/commands/iteration/taskify.js.map +1 -1
- package/dist/commands/run.d.ts +1 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +193 -25
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/status-panel.d.ts +1 -0
- package/dist/commands/status-panel.d.ts.map +1 -1
- package/dist/commands/status-panel.js +180 -54
- package/dist/commands/status-panel.js.map +1 -1
- package/dist/commands/task/new.d.ts +16 -0
- package/dist/commands/task/new.d.ts.map +1 -1
- package/dist/commands/task/new.js +232 -14
- package/dist/commands/task/new.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +78 -1
- package/dist/commands/verify.js.map +1 -1
- package/dist/core/analyze-engine.d.ts.map +1 -1
- package/dist/core/analyze-engine.js +6 -10
- package/dist/core/analyze-engine.js.map +1 -1
- package/dist/core/ask-llm.d.ts +7 -0
- package/dist/core/ask-llm.d.ts.map +1 -1
- package/dist/core/ask-llm.js +45 -0
- package/dist/core/ask-llm.js.map +1 -1
- package/dist/core/code-graph/incremental-build.d.ts +2 -0
- package/dist/core/code-graph/incremental-build.d.ts.map +1 -1
- package/dist/core/code-graph/incremental-build.js +6 -5
- package/dist/core/code-graph/incremental-build.js.map +1 -1
- package/dist/core/code-graph/index.d.ts +2 -0
- package/dist/core/code-graph/index.d.ts.map +1 -1
- package/dist/core/code-graph/index.js +12 -5
- package/dist/core/code-graph/index.js.map +1 -1
- package/dist/core/code-scanner.d.ts +19 -4
- package/dist/core/code-scanner.d.ts.map +1 -1
- package/dist/core/code-scanner.js +193 -77
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/excel-extract.d.ts +16 -2
- package/dist/core/excel-extract.d.ts.map +1 -1
- package/dist/core/excel-extract.js +54 -12
- package/dist/core/excel-extract.js.map +1 -1
- package/dist/core/git-integration.d.ts +2 -1
- package/dist/core/git-integration.d.ts.map +1 -1
- package/dist/core/git-integration.js +38 -14
- package/dist/core/git-integration.js.map +1 -1
- package/dist/core/global-counters.d.ts +3 -0
- package/dist/core/global-counters.d.ts.map +1 -1
- package/dist/core/global-counters.js +21 -7
- package/dist/core/global-counters.js.map +1 -1
- package/dist/core/intent-recognition.d.ts.map +1 -1
- package/dist/core/intent-recognition.js +173 -27
- package/dist/core/intent-recognition.js.map +1 -1
- package/dist/core/plan-store.d.ts +1 -0
- package/dist/core/plan-store.d.ts.map +1 -1
- package/dist/core/plan-store.js.map +1 -1
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +168 -140
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/spec-paths.d.ts.map +1 -1
- package/dist/core/spec-paths.js +3 -0
- package/dist/core/spec-paths.js.map +1 -1
- package/dist/core/spec-skeleton.d.ts.map +1 -1
- package/dist/core/spec-skeleton.js +6 -2
- package/dist/core/spec-skeleton.js.map +1 -1
- package/dist/core/stage-aliases.d.ts +40 -0
- package/dist/core/stage-aliases.d.ts.map +1 -0
- package/dist/core/stage-aliases.js +110 -0
- package/dist/core/stage-aliases.js.map +1 -0
- package/dist/core/state.d.ts +11 -0
- package/dist/core/state.d.ts.map +1 -1
- package/dist/core/state.js +33 -2
- package/dist/core/state.js.map +1 -1
- package/dist/core/structured-extractor.d.ts.map +1 -1
- package/dist/core/structured-extractor.js +7 -0
- package/dist/core/structured-extractor.js.map +1 -1
- package/dist/core/ui-verify/index.d.ts +2 -2
- package/dist/core/ui-verify/index.d.ts.map +1 -1
- package/dist/core/ui-verify/index.js +3 -1
- package/dist/core/ui-verify/index.js.map +1 -1
- package/dist/core/ui-verify/smoke-engine.d.ts.map +1 -1
- package/dist/core/ui-verify/smoke-engine.js +36 -0
- package/dist/core/ui-verify/smoke-engine.js.map +1 -1
- package/dist/core/ui-verify/spec-generator.d.ts +32 -1
- package/dist/core/ui-verify/spec-generator.d.ts.map +1 -1
- package/dist/core/ui-verify/spec-generator.js +205 -21
- package/dist/core/ui-verify/spec-generator.js.map +1 -1
- package/dist/core/ui-verify/types.d.ts +2 -0
- package/dist/core/ui-verify/types.d.ts.map +1 -1
- package/dist/utils/synonyms.d.ts.map +1 -1
- package/dist/utils/synonyms.js +15 -0
- package/dist/utils/synonyms.js.map +1 -1
- package/package.json +3 -2
- package/templates/api-design-example.md +338 -0
- package/templates/ci/github-actions.yml +44 -0
- package/templates/database-example.md +223 -0
- package/templates/deploy-docker/README.md +284 -0
- package/templates/deploy-docker/deploy-remote.sh +79 -0
- package/templates/deploy-docker/docker-compose.yml +113 -0
- package/templates/deploy-docker/frontend.Dockerfile +43 -0
- package/templates/deploy-docker/nginx.conf +30 -0
- package/templates/deploy-examples.yaml +436 -0
- package/templates/deploy-java/Dockerfile +50 -0
- package/templates/deploy-java/README.md +212 -0
- package/templates/deploy-java/deploy.sh +120 -0
- package/templates/deploy-java/spring-boot.service +35 -0
- package/templates/html/speccore-ask-explain.html +10 -0
- package/templates/html/speccore-ask-guide.html +10 -0
- package/templates/html/speccore-ask-match.html +10 -0
- package/templates/html/speccore-ask-pipeline.html +1 -0
- package/templates/html/speccore-ask-result.html +10 -0
- package/templates/html/speccore-help.html +241 -0
- package/templates/html/speccore-setup-guide.html +289 -0
- package/templates/security-example.md +226 -0
- 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 =
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
184
|
-
|
|
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(),
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
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
|
-
|
|
394
|
-
|
|
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 =
|
|
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 =
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
const
|
|
1426
|
-
|
|
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(),
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
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(),
|
|
1472
|
-
|
|
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
|
|
1475
|
-
|
|
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
|
}
|