speccore 8.3.340 → 8.3.345
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/speccore-router/SKILL.md +0 -2
- package/README.en.md +3 -11
- package/README.md +3 -13
- package/dist/cli.js +1 -187
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +583 -502
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/ask.d.ts +1 -0
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +5 -18
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +9 -7
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/done.js +1 -5
- package/dist/commands/done.js.map +1 -1
- package/dist/commands/execute.js +2 -2
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/help.js +5 -54
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +3 -31
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/taskify.d.ts.map +1 -1
- package/dist/commands/iteration/taskify.js +5 -147
- package/dist/commands/iteration/taskify.js.map +1 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +3 -5
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/update-env-configs.d.ts.map +1 -1
- package/dist/commands/update-env-configs.js +2 -1
- package/dist/commands/update-env-configs.js.map +1 -1
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +41 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/welcome.d.ts.map +1 -1
- package/dist/commands/welcome.js +37 -36
- package/dist/commands/welcome.js.map +1 -1
- package/dist/core/analyze-engine.js +3 -3
- package/dist/core/analyze-engine.js.map +1 -1
- package/dist/core/arbitration/l3-human-arbiter.js +1 -1
- package/dist/core/arbitration/l3-human-arbiter.js.map +1 -1
- package/dist/core/arbitration/verdict-generator.js +1 -1
- package/dist/core/arbitration/verdict-generator.js.map +1 -1
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +16 -42
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/code-graph/reporter.d.ts.map +1 -1
- package/dist/core/code-graph/reporter.js +3 -12
- package/dist/core/code-graph/reporter.js.map +1 -1
- package/dist/core/code-graph/requirement-linker.js +1 -1
- package/dist/core/code-graph/requirement-linker.js.map +1 -1
- package/dist/core/code-scanner.js +1 -1
- package/dist/core/code-scanner.js.map +1 -1
- package/dist/core/deploy/engine.d.ts.map +1 -1
- package/dist/core/deploy/engine.js +58 -44
- package/dist/core/deploy/engine.js.map +1 -1
- package/dist/core/environment-config.d.ts +2 -0
- package/dist/core/environment-config.d.ts.map +1 -1
- package/dist/core/environment-config.js +6 -1
- package/dist/core/environment-config.js.map +1 -1
- package/dist/core/error-diagnosis.js +2 -2
- package/dist/core/error-diagnosis.js.map +1 -1
- package/dist/core/function-map.d.ts +49 -0
- package/dist/core/function-map.d.ts.map +1 -0
- package/dist/core/function-map.js +280 -0
- package/dist/core/function-map.js.map +1 -0
- package/dist/core/git-integration.d.ts +6 -0
- package/dist/core/git-integration.d.ts.map +1 -1
- package/dist/core/git-integration.js +11 -0
- package/dist/core/git-integration.js.map +1 -1
- package/dist/core/global-knowledge.js +1 -1
- package/dist/core/index-guard.d.ts.map +1 -1
- package/dist/core/index-guard.js +4 -26
- package/dist/core/index-guard.js.map +1 -1
- package/dist/core/intent-recognition.d.ts +1 -1
- package/dist/core/intent-recognition.d.ts.map +1 -1
- package/dist/core/intent-recognition.js +5 -63
- package/dist/core/intent-recognition.js.map +1 -1
- package/dist/core/page-bundle.d.ts +25 -0
- package/dist/core/page-bundle.d.ts.map +1 -0
- package/dist/core/page-bundle.js +144 -0
- package/dist/core/page-bundle.js.map +1 -0
- package/dist/core/prompt-builder.d.ts +9 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +135 -3
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js.map +1 -1
- package/dist/core/structured-extractor.d.ts +6 -0
- package/dist/core/structured-extractor.d.ts.map +1 -1
- package/dist/core/structured-extractor.js +6 -0
- package/dist/core/structured-extractor.js.map +1 -1
- package/dist/core/unified-config.d.ts.map +1 -1
- package/dist/core/unified-config.js +29 -19
- package/dist/core/unified-config.js.map +1 -1
- package/dist/core/usage-analytics.js +1 -1
- package/dist/core/usage-analytics.js.map +1 -1
- package/package.json +1 -1
- package/templates/html/speccore-ask-guide.html +1 -1
- package/templates/html/speccore-ask-pipeline.html +1 -1
- package/templates/html/speccore-ask-result.html +1 -1
- package/templates/html/speccore-help.html +2 -2
- package/templates/speccore-yml-example.yml +24 -24
- package/dist/commands/audit.d.ts +0 -12
- package/dist/commands/audit.d.ts.map +0 -1
- package/dist/commands/audit.js +0 -540
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/backup.d.ts +0 -18
- package/dist/commands/backup.d.ts.map +0 -1
- package/dist/commands/backup.js +0 -102
- package/dist/commands/backup.js.map +0 -1
- package/dist/commands/baseline.d.ts +0 -13
- package/dist/commands/baseline.d.ts.map +0 -1
- package/dist/commands/baseline.js +0 -227
- package/dist/commands/baseline.js.map +0 -1
- package/dist/commands/chain.d.ts +0 -55
- package/dist/commands/chain.d.ts.map +0 -1
- package/dist/commands/chain.js +0 -501
- package/dist/commands/chain.js.map +0 -1
- package/dist/commands/clarify.d.ts +0 -17
- package/dist/commands/clarify.d.ts.map +0 -1
- package/dist/commands/clarify.js +0 -351
- package/dist/commands/clarify.js.map +0 -1
- package/dist/commands/code-index.d.ts +0 -21
- package/dist/commands/code-index.d.ts.map +0 -1
- package/dist/commands/code-index.js +0 -193
- package/dist/commands/code-index.js.map +0 -1
- package/dist/commands/context.d.ts +0 -10
- package/dist/commands/context.d.ts.map +0 -1
- package/dist/commands/context.js +0 -213
- package/dist/commands/context.js.map +0 -1
- package/dist/commands/current.d.ts +0 -10
- package/dist/commands/current.d.ts.map +0 -1
- package/dist/commands/current.js +0 -53
- package/dist/commands/current.js.map +0 -1
- package/dist/commands/dev.d.ts +0 -18
- package/dist/commands/dev.d.ts.map +0 -1
- package/dist/commands/dev.js +0 -854
- package/dist/commands/dev.js.map +0 -1
- package/dist/commands/diff.d.ts +0 -10
- package/dist/commands/diff.d.ts.map +0 -1
- package/dist/commands/diff.js +0 -160
- package/dist/commands/diff.js.map +0 -1
- package/dist/commands/global-status.d.ts +0 -9
- package/dist/commands/global-status.d.ts.map +0 -1
- package/dist/commands/global-status.js +0 -135
- package/dist/commands/global-status.js.map +0 -1
- package/dist/commands/graph.d.ts +0 -25
- package/dist/commands/graph.d.ts.map +0 -1
- package/dist/commands/graph.js +0 -523
- package/dist/commands/graph.js.map +0 -1
- package/dist/commands/handover.d.ts +0 -11
- package/dist/commands/handover.d.ts.map +0 -1
- package/dist/commands/handover.js +0 -170
- package/dist/commands/handover.js.map +0 -1
- package/dist/commands/health.d.ts +0 -7
- package/dist/commands/health.d.ts.map +0 -1
- package/dist/commands/health.js +0 -80
- package/dist/commands/health.js.map +0 -1
- package/dist/commands/history.d.ts +0 -9
- package/dist/commands/history.d.ts.map +0 -1
- package/dist/commands/history.js +0 -117
- package/dist/commands/history.js.map +0 -1
- package/dist/commands/impact.d.ts +0 -12
- package/dist/commands/impact.d.ts.map +0 -1
- package/dist/commands/impact.js +0 -238
- package/dist/commands/impact.js.map +0 -1
- package/dist/commands/import.d.ts +0 -19
- package/dist/commands/import.d.ts.map +0 -1
- package/dist/commands/import.js +0 -673
- package/dist/commands/import.js.map +0 -1
- package/dist/commands/knowledge.d.ts +0 -6
- package/dist/commands/knowledge.d.ts.map +0 -1
- package/dist/commands/knowledge.js +0 -94
- package/dist/commands/knowledge.js.map +0 -1
- package/dist/commands/lifecycle.d.ts +0 -9
- package/dist/commands/lifecycle.d.ts.map +0 -1
- package/dist/commands/lifecycle.js +0 -288
- package/dist/commands/lifecycle.js.map +0 -1
- package/dist/commands/merge-check.d.ts +0 -10
- package/dist/commands/merge-check.d.ts.map +0 -1
- package/dist/commands/merge-check.js +0 -183
- package/dist/commands/merge-check.js.map +0 -1
- package/dist/commands/notify.d.ts +0 -13
- package/dist/commands/notify.d.ts.map +0 -1
- package/dist/commands/notify.js +0 -63
- package/dist/commands/notify.js.map +0 -1
- package/dist/commands/rag-index.d.ts +0 -19
- package/dist/commands/rag-index.d.ts.map +0 -1
- package/dist/commands/rag-index.js +0 -286
- package/dist/commands/rag-index.js.map +0 -1
- package/dist/commands/recommend.d.ts +0 -13
- package/dist/commands/recommend.d.ts.map +0 -1
- package/dist/commands/recommend.js +0 -37
- package/dist/commands/recommend.js.map +0 -1
- package/dist/commands/refresh.d.ts +0 -21
- package/dist/commands/refresh.d.ts.map +0 -1
- package/dist/commands/refresh.js +0 -211
- package/dist/commands/refresh.js.map +0 -1
- package/dist/commands/rollback.d.ts +0 -8
- package/dist/commands/rollback.d.ts.map +0 -1
- package/dist/commands/rollback.js +0 -113
- package/dist/commands/rollback.js.map +0 -1
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -262
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/trace.d.ts +0 -12
- package/dist/commands/trace.d.ts.map +0 -1
- package/dist/commands/trace.js +0 -163
- package/dist/commands/trace.js.map +0 -1
- package/dist/commands/tracker.d.ts +0 -2
- package/dist/commands/tracker.d.ts.map +0 -1
- package/dist/commands/tracker.js +0 -28
- package/dist/commands/tracker.js.map +0 -1
- package/dist/commands/verdict.d.ts +0 -16
- package/dist/commands/verdict.d.ts.map +0 -1
- package/dist/commands/verdict.js +0 -189
- package/dist/commands/verdict.js.map +0 -1
- package/dist/commands/workspace.d.ts +0 -20
- package/dist/commands/workspace.d.ts.map +0 -1
- package/dist/commands/workspace.js +0 -142
- package/dist/commands/workspace.js.map +0 -1
- package/dist/core/constraint-set.d.ts +0 -67
- package/dist/core/constraint-set.d.ts.map +0 -1
- package/dist/core/constraint-set.js +0 -151
- package/dist/core/constraint-set.js.map +0 -1
- package/dist/core/smart-recommend.d.ts +0 -16
- package/dist/core/smart-recommend.d.ts.map +0 -1
- package/dist/core/smart-recommend.js +0 -149
- package/dist/core/smart-recommend.js.map +0 -1
- package/dist/core/task-type-registry.d.ts +0 -110
- package/dist/core/task-type-registry.d.ts.map +0 -1
- package/dist/core/task-type-registry.js +0 -334
- package/dist/core/task-type-registry.js.map +0 -1
- package/dist/utils/mermaid-render.d.ts +0 -39
- package/dist/utils/mermaid-render.d.ts.map +0 -1
- package/dist/utils/mermaid-render.js +0 -284
- package/dist/utils/mermaid-render.js.map +0 -1
package/dist/commands/analyze.js
CHANGED
|
@@ -339,47 +339,9 @@ async function analyzeCommand(options) {
|
|
|
339
339
|
// ── --from-code 模式: 从源码反推需求(两跳 IR→UR)+ 全局技术分析 ──
|
|
340
340
|
// v8.3.184+: CLI 确定性产出「逐模块 IR 骨架 + 合成 UR 骨架 + 两跳 prompt + 全局技术分析文档集」,
|
|
341
341
|
// 宿主 AI 完成语义抽象
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if (options.verify) {
|
|
346
|
-
try {
|
|
347
|
-
await verifyReverseAnchors(options.outDir || (0, path_1.join)('.speccore', 'GLOBAL', 'requirements'));
|
|
348
|
-
}
|
|
349
|
-
catch (e) {
|
|
350
|
-
logger_1.logger.error(`锚点校验失败: ${e?.message || e}`);
|
|
351
|
-
}
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
const { generateGlobalArtifacts } = await Promise.resolve().then(() => __importStar(require('../core/global-artifacts')));
|
|
355
|
-
const sources = options.source
|
|
356
|
-
? options.source.split(',').map(s => s.trim()).filter(Boolean)
|
|
357
|
-
: options.sourceScope
|
|
358
|
-
? options.sourceScope.split(',').map(s => s.trim()).filter(Boolean)
|
|
359
|
-
: [];
|
|
360
|
-
try {
|
|
361
|
-
// 1. 全局技术分析文档(TECH_STACK/CODE_INDEX/ARCHITECTURE/API_INVENTORY/DATA_MODEL/GLOSSARY/按端 OVERVIEW+TECH)
|
|
362
|
-
await generateGlobalArtifacts(sources, options.depth || 'normal');
|
|
363
|
-
// 2. 从源码反推需求(两跳 IR→UR)
|
|
364
|
-
const result = await reverseRequirementsFromCode({
|
|
365
|
-
sources,
|
|
366
|
-
depth: options.depth,
|
|
367
|
-
platforms: options.platform ? options.platform.split(',').map(p => p.trim()).filter(Boolean) : undefined,
|
|
368
|
-
outputDir: options.outDir,
|
|
369
|
-
});
|
|
370
|
-
logger_1.logger.info('');
|
|
371
|
-
logger_1.logger.info(`🎯 源码反推需求完成(status: candidate,澄清后才成 Spec)`);
|
|
372
|
-
logger_1.logger.info(` 📄 UR 骨架: ${(0, path_1.join)(result.outputDir, 'REQUIREMENT.md')}`);
|
|
373
|
-
logger_1.logger.info(` 🧩 IR 目录: ${result.irDir}(${result.irCount} 个模块)`);
|
|
374
|
-
logger_1.logger.info(` 📝 两跳指引: ${result.promptPath}`);
|
|
375
|
-
logger_1.logger.info('');
|
|
376
|
-
logger_1.logger.info(` 下一步:让宿主 AI 按 REVERSE_PROMPT.md 完成两跳语义抽象,再人工确认功能单元边界。`);
|
|
377
|
-
}
|
|
378
|
-
catch (e) {
|
|
379
|
-
logger_1.logger.error(`源码反推需求失败: ${e?.message || e}`);
|
|
380
|
-
}
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
342
|
+
// v8.3.341+: 模式早退块抽为模块级函数(渐进拆分 analyzeCommand)
|
|
343
|
+
if (options.fromCode)
|
|
344
|
+
return analyzeFromCode(options);
|
|
383
345
|
// ── --full 模式: 委托给 synthesizeCommand(原 synthesize 命令) ──
|
|
384
346
|
// v8.3.1+ 修复:--phase 不再委托给 synthesize,analyze 的 Phase 1/2 由 analyze 自己处理
|
|
385
347
|
// 原因:analyze --phase 2 应该生成端级文档({端}/TECH.md/DEV_GUIDE.md),
|
|
@@ -398,62 +360,12 @@ async function analyzeCommand(options) {
|
|
|
398
360
|
});
|
|
399
361
|
}
|
|
400
362
|
// ── --audit-fix 模式: 读取质量审计报告并生成修复指令 ──
|
|
401
|
-
if (options.auditFix)
|
|
402
|
-
|
|
403
|
-
if (!iter) {
|
|
404
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
408
|
-
const specDir = (0, path_1.join)(iterDir, '020-specs');
|
|
409
|
-
const auditPath = (0, path_1.join)(specDir, 'QUALITY_AUDIT.md');
|
|
410
|
-
if (!(await Promise.resolve().then(() => __importStar(require('fs-extra')))).pathExists(auditPath)) {
|
|
411
|
-
logger_1.logger.warn('未找到 QUALITY_AUDIT.md,请先运行 speccore analyze 生成质量审计报告');
|
|
412
|
-
return;
|
|
413
|
-
}
|
|
414
|
-
const auditContent = await (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readFile(auditPath, 'utf-8');
|
|
415
|
-
let prompt = `\n# 任务: 质量修复(基于 QUALITY_AUDIT.md)\n\n`;
|
|
416
|
-
prompt += `## 审计报告\n\n${auditContent}\n\n`;
|
|
417
|
-
prompt += `## 修复要求\n`;
|
|
418
|
-
prompt += `1. Read 上述审计报告中标记为 ❌ 和 ⚠️ 的维度\n`;
|
|
419
|
-
prompt += `2. 逐个修复对应文档中的缺失内容:\n`;
|
|
420
|
-
prompt += ` - 后端缺失: 补充 API 接口定义、数据模型、业务规则、错误码\n`;
|
|
421
|
-
prompt += ` - 前端缺失: 补充页面路由表、组件清单、字段→UI 映射、状态枚举、交互设计\n`;
|
|
422
|
-
prompt += `3. 修复后重新运行 \`speccore analyze -I ${iter}\` 重新审计\n`;
|
|
423
|
-
prompt += `4. 最多修复 2 轮,超过 2 轮仍有问题则标记为“需人工确认”\n`;
|
|
424
|
-
prompt += `5. 写入: speccore analyze --apply '{...}' -I ${iter}\n`;
|
|
425
|
-
await emitAiPrompt(iter, prompt, { mode: 'audit-fix' });
|
|
426
|
-
process.exitCode = 10;
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
363
|
+
if (options.auditFix)
|
|
364
|
+
return analyzeAuditFix(options);
|
|
429
365
|
// ── v6.75.0+: 上下文爆炸防护(--context-guard / --estimate-only)──
|
|
430
366
|
if (options.contextGuard || options.estimateOnly) {
|
|
431
|
-
|
|
432
|
-
if (!iter) {
|
|
433
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
437
|
-
const { estimateContextSize, buildSegmentationPlan, buildContextGuardPrompt } = await Promise.resolve().then(() => __importStar(require('../core/analyze-context-guard')));
|
|
438
|
-
const estimate = await estimateContextSize(iterDir, { withCode: options.withCode });
|
|
439
|
-
const plan = await buildSegmentationPlan(estimate, iterDir, { withCode: options.withCode });
|
|
440
|
-
logger_1.logger.info('');
|
|
441
|
-
logger_1.logger.info('📊 上下文大小预估报告');
|
|
442
|
-
logger_1.logger.info(`预估 Tokens: ~${estimate.estimatedTokens.toLocaleString()} (${estimate.level})`);
|
|
443
|
-
logger_1.logger.info(`推荐策略: ${estimate.recommendedStrategy}`);
|
|
444
|
-
logger_1.logger.info(`分段数: ${plan.segments.length}`);
|
|
445
|
-
if (options.estimateOnly) {
|
|
446
|
-
// 只输出预估报告到 prompt
|
|
447
|
-
const prompt = buildContextGuardPrompt(estimate, plan);
|
|
448
|
-
await emitAiPrompt(iter, prompt, { mode: 'context-guard' });
|
|
449
|
-
process.exitCode = 10;
|
|
367
|
+
if (await analyzeContextGuard(options))
|
|
450
368
|
return;
|
|
451
|
-
}
|
|
452
|
-
// context-guard 模式下,将预估信息注入后续 prompt
|
|
453
|
-
// 通过全局变量或 options 传递(简化处理:直接输出报告后继续)
|
|
454
|
-
logger_1.logger.info('');
|
|
455
|
-
logger_1.logger.info('💡 继续执行分析,建议按上述分段策略进行');
|
|
456
|
-
logger_1.logger.info('');
|
|
457
369
|
}
|
|
458
370
|
// ── v6.75.0+: 增量分析模式(--incremental / --reanalyze)──
|
|
459
371
|
if (options.incremental || options.reanalyze) {
|
|
@@ -541,399 +453,22 @@ async function analyzeCommand(options) {
|
|
|
541
453
|
return;
|
|
542
454
|
}
|
|
543
455
|
// ── v6.76.0+: 功能模块级全局分析(--module)──
|
|
544
|
-
if (options.module)
|
|
545
|
-
|
|
546
|
-
if (!iter) {
|
|
547
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
551
|
-
const moduleName = options.module;
|
|
552
|
-
const { analyzeModule, buildModuleAnalysisPrompt, listAnalyzedModules, listRequirementModules } = await Promise.resolve().then(() => __importStar(require('../core/module-analyzer')));
|
|
553
|
-
const result = await analyzeModule(iterDir, moduleName);
|
|
554
|
-
logger_1.logger.info('');
|
|
555
|
-
if (result.exists) {
|
|
556
|
-
logger_1.logger.info(`🔄 功能模块重新分析: ${moduleName}`);
|
|
557
|
-
logger_1.logger.info(` 状态: 已存在于全局文档`);
|
|
558
|
-
}
|
|
559
|
-
else {
|
|
560
|
-
logger_1.logger.info(`🆕 功能模块新增分析: ${moduleName}`);
|
|
561
|
-
logger_1.logger.info(` 状态: 尚未在全局文档中分析`);
|
|
562
|
-
// 检查需求文档中是否存在该模块
|
|
563
|
-
const reqModules = await listRequirementModules(iterDir);
|
|
564
|
-
if (!reqModules.includes(moduleName)) {
|
|
565
|
-
logger_1.logger.warn(`⚠️ 需求文档中未找到功能模块 "${moduleName}"`);
|
|
566
|
-
logger_1.logger.info(` 需求中已有的模块: ${reqModules.slice(0, 10).join(', ')}${reqModules.length > 10 ? '...' : ''}`);
|
|
567
|
-
logger_1.logger.info(` 建议: 先确认模块名称是否正确,或在 010-requirements/features/ 中创建 ${moduleName}/SOURCE.md`);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
logger_1.logger.info(` 涉及端: ${result.involvedPlatforms.join(', ') || '待确定'}`);
|
|
571
|
-
logger_1.logger.info(` 全局更新: ${result.globalUpdates.length} 项`);
|
|
572
|
-
logger_1.logger.info(` 各端更新: ${result.platformUpdates.length} 个端`);
|
|
573
|
-
logger_1.logger.info('');
|
|
574
|
-
if (options.prompt) {
|
|
575
|
-
const prompt = buildModuleAnalysisPrompt(iterDir, result, iter);
|
|
576
|
-
await emitAiPrompt(iter, prompt, { mode: 'module-analysis' });
|
|
577
|
-
process.exitCode = 10;
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
logger_1.logger.info('📋 全局文档更新:');
|
|
581
|
-
for (const up of result.globalUpdates) {
|
|
582
|
-
logger_1.logger.info(` • ${up.action}: ${up.file}`);
|
|
583
|
-
}
|
|
584
|
-
logger_1.logger.info('');
|
|
585
|
-
logger_1.logger.info('📋 各端文档更新:');
|
|
586
|
-
for (const pu of result.platformUpdates) {
|
|
587
|
-
logger_1.logger.info(` • ${pu.platform}: ${pu.files.join(', ')}`);
|
|
588
|
-
}
|
|
589
|
-
logger_1.logger.info('');
|
|
590
|
-
logger_1.logger.info('📋 下一步:');
|
|
591
|
-
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --module ${moduleName}`);
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
456
|
+
if (options.module)
|
|
457
|
+
return analyzeModuleMode(options);
|
|
594
458
|
// ── --feature 模式: 局部刷新单个功能模块 ──
|
|
595
|
-
if (options.feature)
|
|
596
|
-
|
|
597
|
-
if (!iter) {
|
|
598
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
599
|
-
return;
|
|
600
|
-
}
|
|
601
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
602
|
-
const featureName = options.feature;
|
|
603
|
-
const spinner = new logger_1.Spinner(`局部分析: ${featureName}`);
|
|
604
|
-
spinner.start();
|
|
605
|
-
const result = await (0, analyze_engine_1.analyzeSingleFeature)(iterDir, featureName);
|
|
606
|
-
if (!result) {
|
|
607
|
-
spinner.fail(`未找到功能模块: 010-requirements/features/${featureName}/SOURCE.md(旧项目为 README.md)`);
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
spinner.stop(`✅ 局部分析完成: 020-specs/features/${featureName}.md`);
|
|
611
|
-
logger_1.logger.info('');
|
|
612
|
-
// 自动刷新知识图谱
|
|
613
|
-
try {
|
|
614
|
-
const { refreshKnowledgeGraph } = await Promise.resolve().then(() => __importStar(require('../core/knowledge-graph')));
|
|
615
|
-
await refreshKnowledgeGraph(_projectRoot, iter);
|
|
616
|
-
logger_1.logger.info('🧠 知识图谱已刷新');
|
|
617
|
-
}
|
|
618
|
-
catch { }
|
|
619
|
-
logger_1.logger.info('');
|
|
620
|
-
logger_1.logger.info('📋 下一步:');
|
|
621
|
-
logger_1.logger.info(` speccore iteration taskify -i ${iter} # 重新拆分任务`);
|
|
622
|
-
logger_1.logger.info(` speccore reindex # 完整重建索引`);
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
459
|
+
if (options.feature)
|
|
460
|
+
return analyzeFeatureMode(options, _projectRoot);
|
|
625
461
|
// ── --doc 模式: 局部刷新单个类型文档(bugs/refactors/research) ──
|
|
626
|
-
if (options.doc)
|
|
627
|
-
|
|
628
|
-
if (!iter) {
|
|
629
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
633
|
-
const docPath = options.doc; // e.g. "bugs/login-timeout" or "refactors/db-pool"
|
|
634
|
-
const spinner = new logger_1.Spinner(`局部分析: ${docPath}`);
|
|
635
|
-
spinner.start();
|
|
636
|
-
const { analyzeSingleTypedDoc } = await Promise.resolve().then(() => __importStar(require('../core/analyze-engine')));
|
|
637
|
-
const result = await analyzeSingleTypedDoc(iterDir, docPath);
|
|
638
|
-
if (!result) {
|
|
639
|
-
spinner.fail(`未找到类型文档: 010-requirements/${docPath}.md`);
|
|
640
|
-
logger_1.logger.info(' 支持的格式: bugs/<slug>, refactors/<slug>, research/<slug>');
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
const typeDir = docPath.split('/')[0];
|
|
644
|
-
spinner.stop(`✅ 局部分析完成: 020-specs/${docPath}.md`);
|
|
645
|
-
logger_1.logger.info('');
|
|
646
|
-
// 自动刷新知识图谱
|
|
647
|
-
try {
|
|
648
|
-
const { refreshKnowledgeGraph } = await Promise.resolve().then(() => __importStar(require('../core/knowledge-graph')));
|
|
649
|
-
await refreshKnowledgeGraph(_projectRoot, iter);
|
|
650
|
-
logger_1.logger.info('🧠 知识图谱已刷新');
|
|
651
|
-
}
|
|
652
|
-
catch { }
|
|
653
|
-
logger_1.logger.info('');
|
|
654
|
-
logger_1.logger.info('📋 下一步:');
|
|
655
|
-
logger_1.logger.info(` speccore iteration taskify -i ${iter} # 重新拆分任务`);
|
|
656
|
-
logger_1.logger.info(` speccore reindex # 完整重建索引`);
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
462
|
+
if (options.doc)
|
|
463
|
+
return analyzeDocMode(options, _projectRoot);
|
|
659
464
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
660
465
|
// v8.3.177+: 总览生成模式(--scope=overview --phase=skeleton|full)
|
|
661
466
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
662
|
-
if (options.scope === 'overview')
|
|
663
|
-
|
|
664
|
-
if (!iter) {
|
|
665
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
669
|
-
// 扫描功能单元元数据
|
|
670
|
-
const metadata = await (0, feature_metadata_1.scanFeatureMetadata)(iterDir);
|
|
671
|
-
if (metadata.length === 0) {
|
|
672
|
-
logger_1.logger.warn('未找到功能单元,请先运行: speccore doc2spec --split');
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
logger_1.logger.info(`📊 总览生成: ${iter}`);
|
|
676
|
-
logger_1.logger.info(` 功能单元: ${metadata.length} 个`);
|
|
677
|
-
// 构建总览输入
|
|
678
|
-
const overviewInput = await (0, feature_metadata_1.buildOverviewInput)(iter, metadata);
|
|
679
|
-
const coverage = (0, feature_metadata_1.calculatePlatformCoverage)(metadata);
|
|
680
|
-
const crossApis = (0, feature_metadata_1.findCrossFeatureApis)(metadata);
|
|
681
|
-
const phase = options.phase || 'skeleton';
|
|
682
|
-
// ── apply 模式:接收 AI 结果写入总览文件 ──
|
|
683
|
-
if (options.apply) {
|
|
684
|
-
await writeOverviewFiles(iterDir, options.apply, phase);
|
|
685
|
-
logger_1.logger.success(`✅ 总览已写入: ${iterDir}/020-specs/overview/`);
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
// ── prompt 模式:生成 AI Prompt ──
|
|
689
|
-
const prompt = buildOverviewPrompt(overviewInput, coverage, crossApis, phase);
|
|
690
|
-
await emitAiPrompt(iter, prompt, { mode: 'overview' });
|
|
691
|
-
process.exitCode = 10;
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
467
|
+
if (options.scope === 'overview')
|
|
468
|
+
return analyzeOverviewMode(options);
|
|
694
469
|
// ── v8.2.0+: 功能单元聚焦分析模式(--extract-units / --unit / --consolidate / --resume-units / --auto-units)──
|
|
695
470
|
if (options.extractUnits || options.unit || options.consolidate || options.resumeUnits || options.autoUnits) {
|
|
696
|
-
|
|
697
|
-
if (!iter) {
|
|
698
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
699
|
-
return;
|
|
700
|
-
}
|
|
701
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
702
|
-
// Step 1: 提取功能单元清单
|
|
703
|
-
let units = [];
|
|
704
|
-
const unitsCachePath = (0, path_1.join)(_projectRoot, exports.UNITS_CACHE_DIR, iter, 'units.json');
|
|
705
|
-
if (await (0, fs_extra_1.pathExists)(unitsCachePath)) {
|
|
706
|
-
try {
|
|
707
|
-
units = JSON.parse(await (0, fs_extra_2.readFile)(unitsCachePath, 'utf-8'));
|
|
708
|
-
logger_1.logger.info(`📋 已加载功能单元清单: ${units.length} 个`);
|
|
709
|
-
}
|
|
710
|
-
catch { /* fall through to re-extract */ }
|
|
711
|
-
}
|
|
712
|
-
if (units.length === 0 || options.extractUnits) {
|
|
713
|
-
units = await extractIterationUnits(iterDir);
|
|
714
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(unitsCachePath));
|
|
715
|
-
await (0, fs_extra_1.writeFile)(unitsCachePath, JSON.stringify(units, null, 2));
|
|
716
|
-
logger_1.logger.info(`📋 功能单元清单已提取: ${units.length} 个`);
|
|
717
|
-
for (const u of units) {
|
|
718
|
-
logger_1.logger.info(` ${u.id}: ${u.name} (${u.source}#L${u.sourceRange.startLine})`);
|
|
719
|
-
}
|
|
720
|
-
if (options.extractUnits) {
|
|
721
|
-
logger_1.logger.info('');
|
|
722
|
-
logger_1.logger.info('📋 下一步: 逐个分析功能单元');
|
|
723
|
-
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --unit M-01`);
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
else if (options.autoUnits || options.resumeUnits) {
|
|
728
|
-
// v8.3.266+: 缓存与磁盘 features/ 权威源对照——旧缓存可能含 unitize 重切前的假单元
|
|
729
|
-
// (实测会议项目 23 个缓存单元中 6 个在 features/ 不存在),调度循环会给假单元生成
|
|
730
|
-
// Prompt 写到 features 锚点之外的目录,白写且 taskify/Phase 2 均不读。
|
|
731
|
-
const diskUnits = await extractIterationUnits(iterDir);
|
|
732
|
-
const cacheNames = new Set(units.map((u) => u.name));
|
|
733
|
-
const diskNames = new Set(diskUnits.map((u) => u.name));
|
|
734
|
-
const mismatched = diskUnits.length > 0
|
|
735
|
-
&& (diskUnits.length !== units.length || [...diskNames].some((n) => !cacheNames.has(n)));
|
|
736
|
-
if (mismatched) {
|
|
737
|
-
units = diskUnits;
|
|
738
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(unitsCachePath));
|
|
739
|
-
await (0, fs_extra_1.writeFile)(unitsCachePath, JSON.stringify(units, null, 2));
|
|
740
|
-
logger_1.logger.warn(`⚠️ 单元清单与磁盘 features/ 目录不一致,已刷新: ${units.length} 个`);
|
|
741
|
-
for (const u of units) {
|
|
742
|
-
logger_1.logger.info(` ${u.id}: ${u.name} (${u.source}#L${u.sourceRange.startLine})`);
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
// Step 2: 汇总模式(--consolidate)
|
|
747
|
-
if (options.consolidate) {
|
|
748
|
-
const analyses = await loadUnitAnalyses(iter);
|
|
749
|
-
if (analyses.length === 0) {
|
|
750
|
-
logger_1.logger.warn('⚠️ 尚未完成任何单元分析,请先运行 --unit 模式');
|
|
751
|
-
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --unit M-01`);
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
const report = await consolidateUnitAnalyses(iter, units, analyses);
|
|
755
|
-
if (options.prompt) {
|
|
756
|
-
await emitAiPrompt(iter, `${report}\n\n## 操作选项\n\n**选项 A:确认通过,写入所有文档**\n\`\`\`bash\nspeccore analyze --apply '[DOC:...]' -I ${iter}\n\`\`\`\n`, { mode: 'consolidate-report' });
|
|
757
|
-
process.exitCode = 10;
|
|
758
|
-
return;
|
|
759
|
-
}
|
|
760
|
-
if (options.apply) {
|
|
761
|
-
// 直接通过 --apply 的 [DOC:xxx] 处理逻辑写入
|
|
762
|
-
logger_1.logger.info(`📋 汇总报告已生成,共 ${analyses.length}/${units.length} 个单元`);
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
logger_1.logger.info(`📋 汇总报告(${analyses.length}/${units.length} 个单元)`);
|
|
766
|
-
logger_1.logger.info(' 使用 --prompt 查看完整报告,或 --apply 直接写入');
|
|
767
|
-
return;
|
|
768
|
-
}
|
|
769
|
-
// Step 3: 确定目标功能单元集合(--auto-units / --resume-units / --unit 可逗号分隔)
|
|
770
|
-
let targetUnitIds = [];
|
|
771
|
-
// 3a. --auto-units: 自动遍历所有待分析单元
|
|
772
|
-
if (options.autoUnits) {
|
|
773
|
-
const pending = await detectPendingUnits(iter, units);
|
|
774
|
-
if (pending.length === 0) {
|
|
775
|
-
logger_1.logger.success('🎉 所有功能单元分析已完成!');
|
|
776
|
-
logger_1.logger.info(' 运行 --consolidate 汇总为统一报告');
|
|
777
|
-
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --consolidate`);
|
|
778
|
-
// v8.3.234+: 执行完成质量报告(成功/失败清单 + 产出物评分)
|
|
779
|
-
await buildAndSaveQualityReport(iterDir, iter, units);
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
targetUnitIds = pending.map(u => u.id);
|
|
783
|
-
logger_1.logger.info(`🚀 自动遍历 ${targetUnitIds.length} 个待分析单元: ${targetUnitIds.join(', ')}`);
|
|
784
|
-
}
|
|
785
|
-
// 3b. --resume-units: 断点续跑(取第一个待分析单元)
|
|
786
|
-
if (options.resumeUnits) {
|
|
787
|
-
const pending = await detectPendingUnits(iter, units);
|
|
788
|
-
if (pending.length === 0) {
|
|
789
|
-
logger_1.logger.success('🎉 所有功能单元分析已完成!');
|
|
790
|
-
logger_1.logger.info(' 运行 --consolidate 汇总为统一报告');
|
|
791
|
-
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --consolidate`);
|
|
792
|
-
// v8.3.234+: 执行完成质量报告(成功/失败清单 + 产出物评分)
|
|
793
|
-
await buildAndSaveQualityReport(iterDir, iter, units);
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
targetUnitIds = [pending[0].id];
|
|
797
|
-
logger_1.logger.info(`🔄 断点续跑: ${pending[0].id} ${pending[0].name}(剩余 ${pending.length} 个)`);
|
|
798
|
-
}
|
|
799
|
-
// 3d. v8.3.225+: --auto-units / --resume-units 调度循环模式(方式 Y 宿主会话内闭环)
|
|
800
|
-
// CLI 每轮推进状态机:校验产出 → 已就绪自动标记并推进,未就绪输出该单元 prompt(exitCode 10)
|
|
801
|
-
// → 宿主 AI 落盘 → 再次调用本命令 → CLI 校验通过自动推进下一个单元,直到全部完成
|
|
802
|
-
if (options.autoUnits || options.resumeUnits) {
|
|
803
|
-
// v8.3.266: Qoder 环境非 TTY 下优先 SDK 派单——CLI 进程内直接调起子 Agent
|
|
804
|
-
// (独立上下文、acceptEdits 直接写文件),校验通过后自动推进下一单元,宿主全程
|
|
805
|
-
// 不参与;SDK 不可用/失败时回退标记模式(exitCode 10 宿主接力)。
|
|
806
|
-
// 收敛性:每轮单元状态必然前进(done/failed)或 SDK 失败退出,不会死循环。
|
|
807
|
-
const sdkSupported = !process.stdout.isTTY && (0, agent_adapter_1.isSdkDispatchSupported)();
|
|
808
|
-
const maxDispatchRounds = units.length * 3 + 5; // 防御性上限(每单元最多 3 次尝试 + 余量)
|
|
809
|
-
let dispatchCount = 0;
|
|
810
|
-
while (true) {
|
|
811
|
-
const loopResult = await runUnitDispatchLoop(iter, iterDir, units);
|
|
812
|
-
if (loopResult.finished) {
|
|
813
|
-
await printUnitLoopSummary(iter, iterDir, units, loopResult);
|
|
814
|
-
return;
|
|
815
|
-
}
|
|
816
|
-
const fallbackPrompt = `${loopResult.prompt}\n\n## 完成后继续\n\n落盘后运行以下命令,CLI 将校验产出并自动推进到下一个单元:\n\n\`\`\`bash\nspeccore analyze --resume-units -I ${iter}\n\`\`\`\n`;
|
|
817
|
-
if (!sdkSupported || !(await trySdkDispatchUnit(loopResult.prompt, iter, loopResult.step.name))) {
|
|
818
|
-
// 回退标记模式(非 Qoder 宿主 / TTY / SDK 失败均走此路径)
|
|
819
|
-
await emitAiPrompt(iter, fallbackPrompt, { mode: 'resume-units-loop' });
|
|
820
|
-
process.exitCode = 10;
|
|
821
|
-
return;
|
|
822
|
-
}
|
|
823
|
-
if (++dispatchCount >= maxDispatchRounds) {
|
|
824
|
-
logger_1.logger.warn(`⚠️ SDK 派单轮次超限(${maxDispatchRounds}),退出调度循环`);
|
|
825
|
-
process.exitCode = 10;
|
|
826
|
-
return;
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
// 理论不可达:while(true) 内所有路径均 return
|
|
830
|
-
}
|
|
831
|
-
// 3c. --unit: 指定单元(支持逗号分隔多个,如 M-01,M-03)
|
|
832
|
-
if (options.unit) {
|
|
833
|
-
targetUnitIds = options.unit.split(',').map(s => s.trim()).filter(Boolean);
|
|
834
|
-
// v8.3.249+: 支持目录名直接指定——unitize 切分的目录名(如 v3-service-01-门禁授权同步)
|
|
835
|
-
// 与缓存 M-ID 体系无映射,仅按 u.id 匹配会让用户面对目录名无法指定单元
|
|
836
|
-
const matchUnit = (q, list) => list.some(u => u.id === q || u.name === q);
|
|
837
|
-
let invalid = targetUnitIds.filter(id => !matchUnit(id, units));
|
|
838
|
-
// v8.3.249+: unitize/doc2spec 切分不写 units.json 缓存,新切分的单元不在缓存清单中——
|
|
839
|
-
// 回退磁盘提取(features/ 目录)合并后再匹配,避免「刚切完就找不到」的断层
|
|
840
|
-
if (invalid.length > 0) {
|
|
841
|
-
const diskUnits = await extractIterationUnits(iterDir);
|
|
842
|
-
if (diskUnits.length > units.length) {
|
|
843
|
-
units = diskUnits;
|
|
844
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(unitsCachePath));
|
|
845
|
-
await (0, fs_extra_1.writeFile)(unitsCachePath, JSON.stringify(units, null, 2));
|
|
846
|
-
logger_1.logger.info(`📋 检测到新切分单元,清单已刷新: ${units.length} 个`);
|
|
847
|
-
invalid = targetUnitIds.filter(id => !matchUnit(id, units));
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
if (invalid.length > 0) {
|
|
851
|
-
logger_1.logger.error(`未找到功能单元: ${invalid.join(', ')}`);
|
|
852
|
-
logger_1.logger.info(` 可用单元: ${units.map(u => u.id).join(', ')}`);
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
const targetUnits = targetUnitIds
|
|
857
|
-
.map(id => units.find(u => u.id === id || u.name === id))
|
|
858
|
-
.filter((u) => !!u);
|
|
859
|
-
// 单单元:保持原有行为
|
|
860
|
-
if (targetUnits.length === 1) {
|
|
861
|
-
const targetUnit = targetUnits[0];
|
|
862
|
-
if (options.prompt) {
|
|
863
|
-
const prompt = await buildUnitAnalysisPrompt(iterDir, iter, targetUnit, units);
|
|
864
|
-
await emitAiPrompt(iter, prompt, { mode: 'unit', unit: targetUnit.id });
|
|
865
|
-
process.exitCode = 10;
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
if (options.apply) {
|
|
869
|
-
// v8.3.249+: 此前为“空实现”——只打印「已接收」但 [DOC:xxx] 内容从不落盘,
|
|
870
|
-
// AI 认真提交后产物丢失,taskify 拿不到分析结果。现在真正解析写入。
|
|
871
|
-
await (0, ai_trace_1.recordAiTrace)(iter, 'apply-in', {
|
|
872
|
-
unit: targetUnit.id,
|
|
873
|
-
rawLength: options.apply.length,
|
|
874
|
-
hasDocMarkers: /\[DOC:[^\]]+\]/.test(options.apply),
|
|
875
|
-
rawPreview: options.apply.trim().slice(0, 500),
|
|
876
|
-
});
|
|
877
|
-
const docBlocks = parseDocMarkers(options.apply);
|
|
878
|
-
if (docBlocks.size === 0) {
|
|
879
|
-
await (0, ai_trace_1.recordAiTrace)(iter, 'apply-fail', { unit: targetUnit.id, reason: 'no-doc-markers', rawInput: options.apply });
|
|
880
|
-
logger_1.logger.warn('⚠️ 未检测到 [DOC:xxx] 标记,请按 prompt 格式输出');
|
|
881
|
-
return;
|
|
882
|
-
}
|
|
883
|
-
const specDir = getUnitSpecDir(iterDir, targetUnit);
|
|
884
|
-
await (0, fs_extra_1.ensureDir)(specDir);
|
|
885
|
-
let written = 0;
|
|
886
|
-
const writtenFiles = [];
|
|
887
|
-
for (const [docName, content] of docBlocks) {
|
|
888
|
-
const safeName = (0, path_1.basename)(docName);
|
|
889
|
-
if (!/^[A-Za-z_][A-Za-z0-9_-]*\.(md|yaml)$/.test(safeName))
|
|
890
|
-
continue;
|
|
891
|
-
if (content.trim().length < 50) {
|
|
892
|
-
logger_1.logger.warn(` ⏭️ 跳过 ${safeName}(内容过短)`);
|
|
893
|
-
continue;
|
|
894
|
-
}
|
|
895
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(specDir, safeName), content.trim());
|
|
896
|
-
written++;
|
|
897
|
-
writtenFiles.push(safeName);
|
|
898
|
-
}
|
|
899
|
-
await (0, ai_trace_1.recordAiTrace)(iter, 'apply-done', { unit: targetUnit.id, applied: written, files: writtenFiles });
|
|
900
|
-
logger_1.logger.success(`✅ 功能单元 ${targetUnit.id} 分析结果已写入 ${written} 个文档`);
|
|
901
|
-
logger_1.logger.info(` 📂 020-specs/${sanitizeUnitDirName(targetUnit.name)}/overview/`);
|
|
902
|
-
return;
|
|
903
|
-
}
|
|
904
|
-
logger_1.logger.info(`📋 功能单元: ${targetUnit.id} ${targetUnit.name}`);
|
|
905
|
-
logger_1.logger.info(` 来源: ${targetUnit.source}#L${targetUnit.sourceRange.startLine}`);
|
|
906
|
-
logger_1.logger.info(' 使用 --prompt 生成分析 Prompt,或 --apply 写入分析结果');
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
// 多单元:逐个输出独立 Prompt(保持每个单元独立上下文,步骤隔离)
|
|
910
|
-
if (targetUnits.length > 1) {
|
|
911
|
-
if (options.prompt) {
|
|
912
|
-
const parts = [];
|
|
913
|
-
parts.push(`# 功能单元批量分析指令(${targetUnits.length} 个单元)`);
|
|
914
|
-
parts.push('');
|
|
915
|
-
parts.push('> ⚠️ **步骤隔离要求**:每个功能单元独立分析、独立提交,不要跨单元混合内容。');
|
|
916
|
-
parts.push('> 请**逐个单元**处理:每完成一个单元,立即用对应命令提交该单元结果,再处理下一个。');
|
|
917
|
-
parts.push('');
|
|
918
|
-
for (const u of targetUnits) {
|
|
919
|
-
const p = await buildUnitAnalysisPrompt(iterDir, iter, u, units);
|
|
920
|
-
parts.push(`\n\n===== 功能单元 ${u.id}: ${u.name} =====\n`);
|
|
921
|
-
parts.push(p);
|
|
922
|
-
parts.push(`\n> 提交本单元: speccore analyze --unit ${u.id} --apply '[DOC:...]' -I ${iter}\n`);
|
|
923
|
-
}
|
|
924
|
-
process.stdout.write(parts.join('\n'));
|
|
925
|
-
process.exitCode = 10;
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
if (options.apply) {
|
|
929
|
-
logger_1.logger.warn('⚠️ 多单元模式下 --apply 请逐个单元执行(指定单个 --unit)');
|
|
930
|
-
logger_1.logger.info(` 待分析单元: ${targetUnits.map(u => u.id).join(', ')}`);
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
logger_1.logger.info(`📋 已选择 ${targetUnits.length} 个功能单元: ${targetUnits.map(u => u.id).join(', ')}`);
|
|
934
|
-
logger_1.logger.info(' 使用 --prompt 逐个生成分析 Prompt,或 --auto-units 自动遍历全部待分析单元');
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
471
|
+
return analyzeUnitModes(options, _projectRoot);
|
|
937
472
|
}
|
|
938
473
|
// v8.3.16+: 模糊匹配端名,支持多段指定(逗号分隔)
|
|
939
474
|
if (options.platform) {
|
|
@@ -2922,6 +2457,520 @@ async function analyzeCommand(options) {
|
|
|
2922
2457
|
return;
|
|
2923
2458
|
}
|
|
2924
2459
|
}
|
|
2460
|
+
/**
|
|
2461
|
+
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--from-code)
|
|
2462
|
+
* 源码反推需求(两跳 IR→UR)+ 全局技术分析
|
|
2463
|
+
*/
|
|
2464
|
+
async function analyzeFromCode(options) {
|
|
2465
|
+
const { reverseRequirementsFromCode, verifyReverseAnchors } = await Promise.resolve().then(() => __importStar(require('../core/reverse-requirement')));
|
|
2466
|
+
// v8.3.301+: 验证环——只校验锚点一致性,不重跑反推(宿主 AI 填完 UR 后运行)
|
|
2467
|
+
if (options.verify) {
|
|
2468
|
+
try {
|
|
2469
|
+
await verifyReverseAnchors(options.outDir || (0, path_1.join)('.speccore', 'GLOBAL', 'requirements'));
|
|
2470
|
+
}
|
|
2471
|
+
catch (e) {
|
|
2472
|
+
logger_1.logger.error(`锚点校验失败: ${e?.message || e}`);
|
|
2473
|
+
}
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
const { generateGlobalArtifacts } = await Promise.resolve().then(() => __importStar(require('../core/global-artifacts')));
|
|
2477
|
+
const sources = options.source
|
|
2478
|
+
? options.source.split(',').map(s => s.trim()).filter(Boolean)
|
|
2479
|
+
: options.sourceScope
|
|
2480
|
+
? options.sourceScope.split(',').map(s => s.trim()).filter(Boolean)
|
|
2481
|
+
: [];
|
|
2482
|
+
try {
|
|
2483
|
+
// 1. 全局技术分析文档(TECH_STACK/CODE_INDEX/ARCHITECTURE/API_INVENTORY/DATA_MODEL/GLOSSARY/按端 OVERVIEW+TECH)
|
|
2484
|
+
await generateGlobalArtifacts(sources, options.depth || 'normal');
|
|
2485
|
+
// 2. 从源码反推需求(两跳 IR→UR)
|
|
2486
|
+
const result = await reverseRequirementsFromCode({
|
|
2487
|
+
sources,
|
|
2488
|
+
depth: options.depth,
|
|
2489
|
+
platforms: options.platform ? options.platform.split(',').map(p => p.trim()).filter(Boolean) : undefined,
|
|
2490
|
+
outputDir: options.outDir,
|
|
2491
|
+
});
|
|
2492
|
+
logger_1.logger.info('');
|
|
2493
|
+
logger_1.logger.info(`🎯 源码反推需求完成(status: candidate,澄清后才成 Spec)`);
|
|
2494
|
+
logger_1.logger.info(` 📄 UR 骨架: ${(0, path_1.join)(result.outputDir, 'REQUIREMENT.md')}`);
|
|
2495
|
+
logger_1.logger.info(` 🧩 IR 目录: ${result.irDir}(${result.irCount} 个模块)`);
|
|
2496
|
+
logger_1.logger.info(` 📝 两跳指引: ${result.promptPath}`);
|
|
2497
|
+
logger_1.logger.info('');
|
|
2498
|
+
logger_1.logger.info(` 下一步:让宿主 AI 按 REVERSE_PROMPT.md 完成两跳语义抽象,再人工确认功能单元边界。`);
|
|
2499
|
+
}
|
|
2500
|
+
catch (e) {
|
|
2501
|
+
logger_1.logger.error(`源码反推需求失败: ${e?.message || e}`);
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--audit-fix)
|
|
2506
|
+
* 读取质量审计报告并生成修复指令
|
|
2507
|
+
*/
|
|
2508
|
+
async function analyzeAuditFix(options) {
|
|
2509
|
+
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
2510
|
+
if (!iter) {
|
|
2511
|
+
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
2512
|
+
return;
|
|
2513
|
+
}
|
|
2514
|
+
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
2515
|
+
const specDir = (0, path_1.join)(iterDir, '020-specs');
|
|
2516
|
+
const auditPath = (0, path_1.join)(specDir, 'QUALITY_AUDIT.md');
|
|
2517
|
+
if (!(await Promise.resolve().then(() => __importStar(require('fs-extra')))).pathExists(auditPath)) {
|
|
2518
|
+
logger_1.logger.warn('未找到 QUALITY_AUDIT.md,请先运行 speccore analyze 生成质量审计报告');
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
const auditContent = await (await Promise.resolve().then(() => __importStar(require('fs-extra')))).readFile(auditPath, 'utf-8');
|
|
2522
|
+
let prompt = `\n# 任务: 质量修复(基于 QUALITY_AUDIT.md)\n\n`;
|
|
2523
|
+
prompt += `## 审计报告\n\n${auditContent}\n\n`;
|
|
2524
|
+
prompt += `## 修复要求\n`;
|
|
2525
|
+
prompt += `1. Read 上述审计报告中标记为 ❌ 和 ⚠️ 的维度\n`;
|
|
2526
|
+
prompt += `2. 逐个修复对应文档中的缺失内容:\n`;
|
|
2527
|
+
prompt += ` - 后端缺失: 补充 API 接口定义、数据模型、业务规则、错误码\n`;
|
|
2528
|
+
prompt += ` - 前端缺失: 补充页面路由表、组件清单、字段→UI 映射、状态枚举、交互设计\n`;
|
|
2529
|
+
prompt += `3. 修复后重新运行 \`speccore analyze -I ${iter}\` 重新审计\n`;
|
|
2530
|
+
prompt += `4. 最多修复 2 轮,超过 2 轮仍有问题则标记为“需人工确认”\n`;
|
|
2531
|
+
prompt += `5. 写入: speccore analyze --apply '{...}' -I ${iter}\n`;
|
|
2532
|
+
await emitAiPrompt(iter, prompt, { mode: 'audit-fix' });
|
|
2533
|
+
process.exitCode = 10;
|
|
2534
|
+
}
|
|
2535
|
+
/**
|
|
2536
|
+
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--context-guard / --estimate-only)
|
|
2537
|
+
* 上下文爆炸防护。返回 true 表示已处理完毕(调用点应直接 return)。
|
|
2538
|
+
*/
|
|
2539
|
+
async function analyzeContextGuard(options) {
|
|
2540
|
+
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
2541
|
+
if (!iter) {
|
|
2542
|
+
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
2543
|
+
return true;
|
|
2544
|
+
}
|
|
2545
|
+
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
2546
|
+
const { estimateContextSize, buildSegmentationPlan, buildContextGuardPrompt } = await Promise.resolve().then(() => __importStar(require('../core/analyze-context-guard')));
|
|
2547
|
+
const estimate = await estimateContextSize(iterDir, { withCode: options.withCode });
|
|
2548
|
+
const plan = await buildSegmentationPlan(estimate, iterDir, { withCode: options.withCode });
|
|
2549
|
+
logger_1.logger.info('');
|
|
2550
|
+
logger_1.logger.info('📊 上下文大小预估报告');
|
|
2551
|
+
logger_1.logger.info(`预估 Tokens: ~${estimate.estimatedTokens.toLocaleString()} (${estimate.level})`);
|
|
2552
|
+
logger_1.logger.info(`推荐策略: ${estimate.recommendedStrategy}`);
|
|
2553
|
+
logger_1.logger.info(`分段数: ${plan.segments.length}`);
|
|
2554
|
+
if (options.estimateOnly) {
|
|
2555
|
+
// 只输出预估报告到 prompt
|
|
2556
|
+
const prompt = buildContextGuardPrompt(estimate, plan);
|
|
2557
|
+
await emitAiPrompt(iter, prompt, { mode: 'context-guard' });
|
|
2558
|
+
process.exitCode = 10;
|
|
2559
|
+
return true;
|
|
2560
|
+
}
|
|
2561
|
+
// context-guard 模式下,将预估信息注入后续 prompt
|
|
2562
|
+
// 通过全局变量或 options 传递(简化处理:直接输出报告后继续)
|
|
2563
|
+
logger_1.logger.info('');
|
|
2564
|
+
logger_1.logger.info('💡 继续执行分析,建议按上述分段策略进行');
|
|
2565
|
+
logger_1.logger.info('');
|
|
2566
|
+
return false;
|
|
2567
|
+
}
|
|
2568
|
+
/**
|
|
2569
|
+
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--module)
|
|
2570
|
+
* 功能模块级全局分析
|
|
2571
|
+
*/
|
|
2572
|
+
async function analyzeModuleMode(options) {
|
|
2573
|
+
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
2574
|
+
if (!iter) {
|
|
2575
|
+
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
2576
|
+
return;
|
|
2577
|
+
}
|
|
2578
|
+
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
2579
|
+
// 调用点已保证 options.module 非空(if (options.module)),此处断言恢复 narrowing
|
|
2580
|
+
const moduleName = options.module;
|
|
2581
|
+
const { analyzeModule, buildModuleAnalysisPrompt, listAnalyzedModules, listRequirementModules } = await Promise.resolve().then(() => __importStar(require('../core/module-analyzer')));
|
|
2582
|
+
const result = await analyzeModule(iterDir, moduleName);
|
|
2583
|
+
logger_1.logger.info('');
|
|
2584
|
+
if (result.exists) {
|
|
2585
|
+
logger_1.logger.info(`🔄 功能模块重新分析: ${moduleName}`);
|
|
2586
|
+
logger_1.logger.info(` 状态: 已存在于全局文档`);
|
|
2587
|
+
}
|
|
2588
|
+
else {
|
|
2589
|
+
logger_1.logger.info(`🆕 功能模块新增分析: ${moduleName}`);
|
|
2590
|
+
logger_1.logger.info(` 状态: 尚未在全局文档中分析`);
|
|
2591
|
+
// 检查需求文档中是否存在该模块
|
|
2592
|
+
const reqModules = await listRequirementModules(iterDir);
|
|
2593
|
+
if (!reqModules.includes(moduleName)) {
|
|
2594
|
+
logger_1.logger.warn(`⚠️ 需求文档中未找到功能模块 "${moduleName}"`);
|
|
2595
|
+
logger_1.logger.info(` 需求中已有的模块: ${reqModules.slice(0, 10).join(', ')}${reqModules.length > 10 ? '...' : ''}`);
|
|
2596
|
+
logger_1.logger.info(` 建议: 先确认模块名称是否正确,或在 010-requirements/features/ 中创建 ${moduleName}/SOURCE.md`);
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
logger_1.logger.info(` 涉及端: ${result.involvedPlatforms.join(', ') || '待确定'}`);
|
|
2600
|
+
logger_1.logger.info(` 全局更新: ${result.globalUpdates.length} 项`);
|
|
2601
|
+
logger_1.logger.info(` 各端更新: ${result.platformUpdates.length} 个端`);
|
|
2602
|
+
logger_1.logger.info('');
|
|
2603
|
+
if (options.prompt) {
|
|
2604
|
+
const prompt = buildModuleAnalysisPrompt(iterDir, result, iter);
|
|
2605
|
+
await emitAiPrompt(iter, prompt, { mode: 'module-analysis' });
|
|
2606
|
+
process.exitCode = 10;
|
|
2607
|
+
return;
|
|
2608
|
+
}
|
|
2609
|
+
logger_1.logger.info('📋 全局文档更新:');
|
|
2610
|
+
for (const up of result.globalUpdates) {
|
|
2611
|
+
logger_1.logger.info(` • ${up.action}: ${up.file}`);
|
|
2612
|
+
}
|
|
2613
|
+
logger_1.logger.info('');
|
|
2614
|
+
logger_1.logger.info('📋 各端文档更新:');
|
|
2615
|
+
for (const pu of result.platformUpdates) {
|
|
2616
|
+
logger_1.logger.info(` • ${pu.platform}: ${pu.files.join(', ')}`);
|
|
2617
|
+
}
|
|
2618
|
+
logger_1.logger.info('');
|
|
2619
|
+
logger_1.logger.info('📋 下一步:');
|
|
2620
|
+
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --module ${moduleName}`);
|
|
2621
|
+
}
|
|
2622
|
+
/**
|
|
2623
|
+
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--feature)
|
|
2624
|
+
* 局部刷新单个功能模块
|
|
2625
|
+
*/
|
|
2626
|
+
async function analyzeFeatureMode(options, projectRoot) {
|
|
2627
|
+
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
2628
|
+
if (!iter) {
|
|
2629
|
+
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
2630
|
+
return;
|
|
2631
|
+
}
|
|
2632
|
+
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
2633
|
+
// 调用点已保证 options.feature 非空,此处断言恢复 narrowing
|
|
2634
|
+
const featureName = options.feature;
|
|
2635
|
+
const spinner = new logger_1.Spinner(`局部分析: ${featureName}`);
|
|
2636
|
+
spinner.start();
|
|
2637
|
+
const result = await (0, analyze_engine_1.analyzeSingleFeature)(iterDir, featureName);
|
|
2638
|
+
if (!result) {
|
|
2639
|
+
spinner.fail(`未找到功能模块: 010-requirements/features/${featureName}/SOURCE.md(旧项目为 README.md)`);
|
|
2640
|
+
return;
|
|
2641
|
+
}
|
|
2642
|
+
spinner.stop(`✅ 局部分析完成: 020-specs/features/${featureName}.md`);
|
|
2643
|
+
logger_1.logger.info('');
|
|
2644
|
+
// 自动刷新知识图谱
|
|
2645
|
+
try {
|
|
2646
|
+
const { refreshKnowledgeGraph } = await Promise.resolve().then(() => __importStar(require('../core/knowledge-graph')));
|
|
2647
|
+
await refreshKnowledgeGraph(projectRoot, iter);
|
|
2648
|
+
logger_1.logger.info('🧠 知识图谱已刷新');
|
|
2649
|
+
}
|
|
2650
|
+
catch { }
|
|
2651
|
+
logger_1.logger.info('');
|
|
2652
|
+
logger_1.logger.info('📋 下一步:');
|
|
2653
|
+
logger_1.logger.info(` speccore iteration taskify -i ${iter} # 重新拆分任务`);
|
|
2654
|
+
logger_1.logger.info(` speccore reindex # 完整重建索引`);
|
|
2655
|
+
}
|
|
2656
|
+
/**
|
|
2657
|
+
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--doc)
|
|
2658
|
+
* 局部刷新单个类型文档(bugs/refactors/research)
|
|
2659
|
+
*/
|
|
2660
|
+
async function analyzeDocMode(options, projectRoot) {
|
|
2661
|
+
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
2662
|
+
if (!iter) {
|
|
2663
|
+
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
2667
|
+
// 调用点已保证 options.doc 非空,此处断言恢复 narrowing
|
|
2668
|
+
const docPath = options.doc; // e.g. "bugs/login-timeout" or "refactors/db-pool"
|
|
2669
|
+
const spinner = new logger_1.Spinner(`局部分析: ${docPath}`);
|
|
2670
|
+
spinner.start();
|
|
2671
|
+
const { analyzeSingleTypedDoc } = await Promise.resolve().then(() => __importStar(require('../core/analyze-engine')));
|
|
2672
|
+
const result = await analyzeSingleTypedDoc(iterDir, docPath);
|
|
2673
|
+
if (!result) {
|
|
2674
|
+
spinner.fail(`未找到类型文档: 010-requirements/${docPath}.md`);
|
|
2675
|
+
logger_1.logger.info(' 支持的格式: bugs/<slug>, refactors/<slug>, research/<slug>');
|
|
2676
|
+
return;
|
|
2677
|
+
}
|
|
2678
|
+
const typeDir = docPath.split('/')[0];
|
|
2679
|
+
spinner.stop(`✅ 局部分析完成: 020-specs/${docPath}.md`);
|
|
2680
|
+
logger_1.logger.info('');
|
|
2681
|
+
// 自动刷新知识图谱
|
|
2682
|
+
try {
|
|
2683
|
+
const { refreshKnowledgeGraph } = await Promise.resolve().then(() => __importStar(require('../core/knowledge-graph')));
|
|
2684
|
+
await refreshKnowledgeGraph(projectRoot, iter);
|
|
2685
|
+
logger_1.logger.info('🧠 知识图谱已刷新');
|
|
2686
|
+
}
|
|
2687
|
+
catch { }
|
|
2688
|
+
logger_1.logger.info('');
|
|
2689
|
+
logger_1.logger.info('📋 下一步:');
|
|
2690
|
+
logger_1.logger.info(` speccore iteration taskify -i ${iter} # 重新拆分任务`);
|
|
2691
|
+
logger_1.logger.info(` speccore reindex # 完整重建索引`);
|
|
2692
|
+
}
|
|
2693
|
+
/**
|
|
2694
|
+
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--scope=overview)
|
|
2695
|
+
* 总览生成模式(--phase=skeleton|full)
|
|
2696
|
+
*/
|
|
2697
|
+
async function analyzeOverviewMode(options) {
|
|
2698
|
+
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
2699
|
+
if (!iter) {
|
|
2700
|
+
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
2701
|
+
return;
|
|
2702
|
+
}
|
|
2703
|
+
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
2704
|
+
// 扫描功能单元元数据
|
|
2705
|
+
const metadata = await (0, feature_metadata_1.scanFeatureMetadata)(iterDir);
|
|
2706
|
+
if (metadata.length === 0) {
|
|
2707
|
+
logger_1.logger.warn('未找到功能单元,请先运行: speccore doc2spec --split');
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
logger_1.logger.info(`📊 总览生成: ${iter}`);
|
|
2711
|
+
logger_1.logger.info(` 功能单元: ${metadata.length} 个`);
|
|
2712
|
+
// 构建总览输入
|
|
2713
|
+
const overviewInput = await (0, feature_metadata_1.buildOverviewInput)(iter, metadata);
|
|
2714
|
+
const coverage = (0, feature_metadata_1.calculatePlatformCoverage)(metadata);
|
|
2715
|
+
const crossApis = (0, feature_metadata_1.findCrossFeatureApis)(metadata);
|
|
2716
|
+
const phase = options.phase || 'skeleton';
|
|
2717
|
+
// ── apply 模式:接收 AI 结果写入总览文件 ──
|
|
2718
|
+
if (options.apply) {
|
|
2719
|
+
await writeOverviewFiles(iterDir, options.apply, phase);
|
|
2720
|
+
logger_1.logger.success(`✅ 总览已写入: ${iterDir}/020-specs/overview/`);
|
|
2721
|
+
return;
|
|
2722
|
+
}
|
|
2723
|
+
// ── prompt 模式:生成 AI Prompt ──
|
|
2724
|
+
const prompt = buildOverviewPrompt(overviewInput, coverage, crossApis, phase);
|
|
2725
|
+
await emitAiPrompt(iter, prompt, { mode: 'overview' });
|
|
2726
|
+
process.exitCode = 10;
|
|
2727
|
+
}
|
|
2728
|
+
/**
|
|
2729
|
+
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--extract-units / --unit / --consolidate / --resume-units / --auto-units)
|
|
2730
|
+
* 功能单元聚焦分析
|
|
2731
|
+
*/
|
|
2732
|
+
async function analyzeUnitModes(options, projectRoot) {
|
|
2733
|
+
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
2734
|
+
if (!iter) {
|
|
2735
|
+
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
2739
|
+
// Step 1: 提取功能单元清单
|
|
2740
|
+
let units = [];
|
|
2741
|
+
const unitsCachePath = (0, path_1.join)(projectRoot, exports.UNITS_CACHE_DIR, iter, 'units.json');
|
|
2742
|
+
if (await (0, fs_extra_1.pathExists)(unitsCachePath)) {
|
|
2743
|
+
try {
|
|
2744
|
+
units = JSON.parse(await (0, fs_extra_2.readFile)(unitsCachePath, 'utf-8'));
|
|
2745
|
+
logger_1.logger.info(`📋 已加载功能单元清单: ${units.length} 个`);
|
|
2746
|
+
}
|
|
2747
|
+
catch { /* fall through to re-extract */ }
|
|
2748
|
+
}
|
|
2749
|
+
if (units.length === 0 || options.extractUnits) {
|
|
2750
|
+
units = await extractIterationUnits(iterDir);
|
|
2751
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(unitsCachePath));
|
|
2752
|
+
await (0, fs_extra_1.writeFile)(unitsCachePath, JSON.stringify(units, null, 2));
|
|
2753
|
+
logger_1.logger.info(`📋 功能单元清单已提取: ${units.length} 个`);
|
|
2754
|
+
for (const u of units) {
|
|
2755
|
+
logger_1.logger.info(` ${u.id}: ${u.name} (${u.source}#L${u.sourceRange.startLine})`);
|
|
2756
|
+
}
|
|
2757
|
+
if (options.extractUnits) {
|
|
2758
|
+
logger_1.logger.info('');
|
|
2759
|
+
logger_1.logger.info('📋 下一步: 逐个分析功能单元');
|
|
2760
|
+
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --unit M-01`);
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
else if (options.autoUnits || options.resumeUnits) {
|
|
2765
|
+
// v8.3.266+: 缓存与磁盘 features/ 权威源对照——旧缓存可能含 unitize 重切前的假单元
|
|
2766
|
+
// (实测会议项目 23 个缓存单元中 6 个在 features/ 不存在),调度循环会给假单元生成
|
|
2767
|
+
// Prompt 写到 features 锚点之外的目录,白写且 taskify/Phase 2 均不读。
|
|
2768
|
+
const diskUnits = await extractIterationUnits(iterDir);
|
|
2769
|
+
const cacheNames = new Set(units.map((u) => u.name));
|
|
2770
|
+
const diskNames = new Set(diskUnits.map((u) => u.name));
|
|
2771
|
+
const mismatched = diskUnits.length > 0
|
|
2772
|
+
&& (diskUnits.length !== units.length || [...diskNames].some((n) => !cacheNames.has(n)));
|
|
2773
|
+
if (mismatched) {
|
|
2774
|
+
units = diskUnits;
|
|
2775
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(unitsCachePath));
|
|
2776
|
+
await (0, fs_extra_1.writeFile)(unitsCachePath, JSON.stringify(units, null, 2));
|
|
2777
|
+
logger_1.logger.warn(`⚠️ 单元清单与磁盘 features/ 目录不一致,已刷新: ${units.length} 个`);
|
|
2778
|
+
for (const u of units) {
|
|
2779
|
+
logger_1.logger.info(` ${u.id}: ${u.name} (${u.source}#L${u.sourceRange.startLine})`);
|
|
2780
|
+
}
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
// Step 2: 汇总模式(--consolidate)
|
|
2784
|
+
if (options.consolidate) {
|
|
2785
|
+
const analyses = await loadUnitAnalyses(iter);
|
|
2786
|
+
if (analyses.length === 0) {
|
|
2787
|
+
logger_1.logger.warn('⚠️ 尚未完成任何单元分析,请先运行 --unit 模式');
|
|
2788
|
+
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --unit M-01`);
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2791
|
+
const report = await consolidateUnitAnalyses(iter, units, analyses);
|
|
2792
|
+
if (options.prompt) {
|
|
2793
|
+
await emitAiPrompt(iter, `${report}\n\n## 操作选项\n\n**选项 A:确认通过,写入所有文档**\n\`\`\`bash\nspeccore analyze --apply '[DOC:...]' -I ${iter}\n\`\`\`\n`, { mode: 'consolidate-report' });
|
|
2794
|
+
process.exitCode = 10;
|
|
2795
|
+
return;
|
|
2796
|
+
}
|
|
2797
|
+
if (options.apply) {
|
|
2798
|
+
// 直接通过 --apply 的 [DOC:xxx] 处理逻辑写入
|
|
2799
|
+
logger_1.logger.info(`📋 汇总报告已生成,共 ${analyses.length}/${units.length} 个单元`);
|
|
2800
|
+
return;
|
|
2801
|
+
}
|
|
2802
|
+
logger_1.logger.info(`📋 汇总报告(${analyses.length}/${units.length} 个单元)`);
|
|
2803
|
+
logger_1.logger.info(' 使用 --prompt 查看完整报告,或 --apply 直接写入');
|
|
2804
|
+
return;
|
|
2805
|
+
}
|
|
2806
|
+
// Step 3: 确定目标功能单元集合(--auto-units / --resume-units / --unit 可逗号分隔)
|
|
2807
|
+
let targetUnitIds = [];
|
|
2808
|
+
// 3a. --auto-units: 自动遍历所有待分析单元
|
|
2809
|
+
if (options.autoUnits) {
|
|
2810
|
+
const pending = await detectPendingUnits(iter, units);
|
|
2811
|
+
if (pending.length === 0) {
|
|
2812
|
+
logger_1.logger.success('🎉 所有功能单元分析已完成!');
|
|
2813
|
+
logger_1.logger.info(' 运行 --consolidate 汇总为统一报告');
|
|
2814
|
+
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --consolidate`);
|
|
2815
|
+
// v8.3.234+: 执行完成质量报告(成功/失败清单 + 产出物评分)
|
|
2816
|
+
await buildAndSaveQualityReport(iterDir, iter, units);
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
targetUnitIds = pending.map(u => u.id);
|
|
2820
|
+
logger_1.logger.info(`🚀 自动遍历 ${targetUnitIds.length} 个待分析单元: ${targetUnitIds.join(', ')}`);
|
|
2821
|
+
}
|
|
2822
|
+
// 3b. --resume-units: 断点续跑(取第一个待分析单元)
|
|
2823
|
+
if (options.resumeUnits) {
|
|
2824
|
+
const pending = await detectPendingUnits(iter, units);
|
|
2825
|
+
if (pending.length === 0) {
|
|
2826
|
+
logger_1.logger.success('🎉 所有功能单元分析已完成!');
|
|
2827
|
+
logger_1.logger.info(' 运行 --consolidate 汇总为统一报告');
|
|
2828
|
+
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --consolidate`);
|
|
2829
|
+
// v8.3.234+: 执行完成质量报告(成功/失败清单 + 产出物评分)
|
|
2830
|
+
await buildAndSaveQualityReport(iterDir, iter, units);
|
|
2831
|
+
return;
|
|
2832
|
+
}
|
|
2833
|
+
targetUnitIds = [pending[0].id];
|
|
2834
|
+
logger_1.logger.info(`🔄 断点续跑: ${pending[0].id} ${pending[0].name}(剩余 ${pending.length} 个)`);
|
|
2835
|
+
}
|
|
2836
|
+
// 3d. v8.3.225+: --auto-units / --resume-units 调度循环模式(方式 Y 宿主会话内闭环)
|
|
2837
|
+
// CLI 每轮推进状态机:校验产出 → 已就绪自动标记并推进,未就绪输出该单元 prompt(exitCode 10)
|
|
2838
|
+
// → 宿主 AI 落盘 → 再次调用本命令 → CLI 校验通过自动推进下一个单元,直到全部完成
|
|
2839
|
+
if (options.autoUnits || options.resumeUnits) {
|
|
2840
|
+
// v8.3.266: Qoder 环境非 TTY 下优先 SDK 派单——CLI 进程内直接调起子 Agent
|
|
2841
|
+
// (独立上下文、acceptEdits 直接写文件),校验通过后自动推进下一单元,宿主全程
|
|
2842
|
+
// 不参与;SDK 不可用/失败时回退标记模式(exitCode 10 宿主接力)。
|
|
2843
|
+
// 收敛性:每轮单元状态必然前进(done/failed)或 SDK 失败退出,不会死循环。
|
|
2844
|
+
const sdkSupported = !process.stdout.isTTY && (0, agent_adapter_1.isSdkDispatchSupported)();
|
|
2845
|
+
const maxDispatchRounds = units.length * 3 + 5; // 防御性上限(每单元最多 3 次尝试 + 余量)
|
|
2846
|
+
let dispatchCount = 0;
|
|
2847
|
+
while (true) {
|
|
2848
|
+
const loopResult = await runUnitDispatchLoop(iter, iterDir, units);
|
|
2849
|
+
if (loopResult.finished) {
|
|
2850
|
+
await printUnitLoopSummary(iter, iterDir, units, loopResult);
|
|
2851
|
+
return;
|
|
2852
|
+
}
|
|
2853
|
+
const fallbackPrompt = `${loopResult.prompt}\n\n## 完成后继续\n\n落盘后运行以下命令,CLI 将校验产出并自动推进到下一个单元:\n\n\`\`\`bash\nspeccore analyze --resume-units -I ${iter}\n\`\`\`\n`;
|
|
2854
|
+
if (!sdkSupported || !(await trySdkDispatchUnit(loopResult.prompt, iter, loopResult.step.name))) {
|
|
2855
|
+
// 回退标记模式(非 Qoder 宿主 / TTY / SDK 失败均走此路径)
|
|
2856
|
+
await emitAiPrompt(iter, fallbackPrompt, { mode: 'resume-units-loop' });
|
|
2857
|
+
process.exitCode = 10;
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
if (++dispatchCount >= maxDispatchRounds) {
|
|
2861
|
+
logger_1.logger.warn(`⚠️ SDK 派单轮次超限(${maxDispatchRounds}),退出调度循环`);
|
|
2862
|
+
process.exitCode = 10;
|
|
2863
|
+
return;
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
// 理论不可达:while(true) 内所有路径均 return
|
|
2867
|
+
}
|
|
2868
|
+
// 3c. --unit: 指定单元(支持逗号分隔多个,如 M-01,M-03)
|
|
2869
|
+
if (options.unit) {
|
|
2870
|
+
targetUnitIds = options.unit.split(',').map(s => s.trim()).filter(Boolean);
|
|
2871
|
+
// v8.3.249+: 支持目录名直接指定——unitize 切分的目录名(如 v3-service-01-门禁授权同步)
|
|
2872
|
+
// 与缓存 M-ID 体系无映射,仅按 u.id 匹配会让用户面对目录名无法指定单元
|
|
2873
|
+
const matchUnit = (q, list) => list.some(u => u.id === q || u.name === q);
|
|
2874
|
+
let invalid = targetUnitIds.filter(id => !matchUnit(id, units));
|
|
2875
|
+
// v8.3.249+: unitize/doc2spec 切分不写 units.json 缓存,新切分的单元不在缓存清单中——
|
|
2876
|
+
// 回退磁盘提取(features/ 目录)合并后再匹配,避免「刚切完就找不到」的断层
|
|
2877
|
+
if (invalid.length > 0) {
|
|
2878
|
+
const diskUnits = await extractIterationUnits(iterDir);
|
|
2879
|
+
if (diskUnits.length > units.length) {
|
|
2880
|
+
units = diskUnits;
|
|
2881
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(unitsCachePath));
|
|
2882
|
+
await (0, fs_extra_1.writeFile)(unitsCachePath, JSON.stringify(units, null, 2));
|
|
2883
|
+
logger_1.logger.info(`📋 检测到新切分单元,清单已刷新: ${units.length} 个`);
|
|
2884
|
+
invalid = targetUnitIds.filter(id => !matchUnit(id, units));
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
if (invalid.length > 0) {
|
|
2888
|
+
logger_1.logger.error(`未找到功能单元: ${invalid.join(', ')}`);
|
|
2889
|
+
logger_1.logger.info(` 可用单元: ${units.map(u => u.id).join(', ')}`);
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
const targetUnits = targetUnitIds
|
|
2894
|
+
.map(id => units.find(u => u.id === id || u.name === id))
|
|
2895
|
+
.filter((u) => !!u);
|
|
2896
|
+
// 单单元:保持原有行为
|
|
2897
|
+
if (targetUnits.length === 1) {
|
|
2898
|
+
const targetUnit = targetUnits[0];
|
|
2899
|
+
if (options.prompt) {
|
|
2900
|
+
const prompt = await buildUnitAnalysisPrompt(iterDir, iter, targetUnit, units);
|
|
2901
|
+
await emitAiPrompt(iter, prompt, { mode: 'unit', unit: targetUnit.id });
|
|
2902
|
+
process.exitCode = 10;
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
if (options.apply) {
|
|
2906
|
+
// v8.3.249+: 此前为“空实现”——只打印「已接收」但 [DOC:xxx] 内容从不落盘,
|
|
2907
|
+
// AI 认真提交后产物丢失,taskify 拿不到分析结果。现在真正解析写入。
|
|
2908
|
+
await (0, ai_trace_1.recordAiTrace)(iter, 'apply-in', {
|
|
2909
|
+
unit: targetUnit.id,
|
|
2910
|
+
rawLength: options.apply.length,
|
|
2911
|
+
hasDocMarkers: /\[DOC:[^\]]+\]/.test(options.apply),
|
|
2912
|
+
rawPreview: options.apply.trim().slice(0, 500),
|
|
2913
|
+
});
|
|
2914
|
+
const docBlocks = parseDocMarkers(options.apply);
|
|
2915
|
+
if (docBlocks.size === 0) {
|
|
2916
|
+
await (0, ai_trace_1.recordAiTrace)(iter, 'apply-fail', { unit: targetUnit.id, reason: 'no-doc-markers', rawInput: options.apply });
|
|
2917
|
+
logger_1.logger.warn('⚠️ 未检测到 [DOC:xxx] 标记,请按 prompt 格式输出');
|
|
2918
|
+
return;
|
|
2919
|
+
}
|
|
2920
|
+
const specDir = getUnitSpecDir(iterDir, targetUnit);
|
|
2921
|
+
await (0, fs_extra_1.ensureDir)(specDir);
|
|
2922
|
+
let written = 0;
|
|
2923
|
+
const writtenFiles = [];
|
|
2924
|
+
for (const [docName, content] of docBlocks) {
|
|
2925
|
+
const safeName = (0, path_1.basename)(docName);
|
|
2926
|
+
if (!/^[A-Za-z_][A-Za-z0-9_-]*\.(md|yaml)$/.test(safeName))
|
|
2927
|
+
continue;
|
|
2928
|
+
if (content.trim().length < 50) {
|
|
2929
|
+
logger_1.logger.warn(` ⏭️ 跳过 ${safeName}(内容过短)`);
|
|
2930
|
+
continue;
|
|
2931
|
+
}
|
|
2932
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(specDir, safeName), content.trim());
|
|
2933
|
+
written++;
|
|
2934
|
+
writtenFiles.push(safeName);
|
|
2935
|
+
}
|
|
2936
|
+
await (0, ai_trace_1.recordAiTrace)(iter, 'apply-done', { unit: targetUnit.id, applied: written, files: writtenFiles });
|
|
2937
|
+
logger_1.logger.success(`✅ 功能单元 ${targetUnit.id} 分析结果已写入 ${written} 个文档`);
|
|
2938
|
+
logger_1.logger.info(` 📂 020-specs/${sanitizeUnitDirName(targetUnit.name)}/overview/`);
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
logger_1.logger.info(`📋 功能单元: ${targetUnit.id} ${targetUnit.name}`);
|
|
2942
|
+
logger_1.logger.info(` 来源: ${targetUnit.source}#L${targetUnit.sourceRange.startLine}`);
|
|
2943
|
+
logger_1.logger.info(' 使用 --prompt 生成分析 Prompt,或 --apply 写入分析结果');
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
// 多单元:逐个输出独立 Prompt(保持每个单元独立上下文,步骤隔离)
|
|
2947
|
+
if (targetUnits.length > 1) {
|
|
2948
|
+
if (options.prompt) {
|
|
2949
|
+
const parts = [];
|
|
2950
|
+
parts.push(`# 功能单元批量分析指令(${targetUnits.length} 个单元)`);
|
|
2951
|
+
parts.push('');
|
|
2952
|
+
parts.push('> ⚠️ **步骤隔离要求**:每个功能单元独立分析、独立提交,不要跨单元混合内容。');
|
|
2953
|
+
parts.push('> 请**逐个单元**处理:每完成一个单元,立即用对应命令提交该单元结果,再处理下一个。');
|
|
2954
|
+
parts.push('');
|
|
2955
|
+
for (const u of targetUnits) {
|
|
2956
|
+
const p = await buildUnitAnalysisPrompt(iterDir, iter, u, units);
|
|
2957
|
+
parts.push(`\n\n===== 功能单元 ${u.id}: ${u.name} =====\n`);
|
|
2958
|
+
parts.push(p);
|
|
2959
|
+
parts.push(`\n> 提交本单元: speccore analyze --unit ${u.id} --apply '[DOC:...]' -I ${iter}\n`);
|
|
2960
|
+
}
|
|
2961
|
+
process.stdout.write(parts.join('\n'));
|
|
2962
|
+
process.exitCode = 10;
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
if (options.apply) {
|
|
2966
|
+
logger_1.logger.warn('⚠️ 多单元模式下 --apply 请逐个单元执行(指定单个 --unit)');
|
|
2967
|
+
logger_1.logger.info(` 待分析单元: ${targetUnits.map(u => u.id).join(', ')}`);
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
logger_1.logger.info(`📋 已选择 ${targetUnits.length} 个功能单元: ${targetUnits.map(u => u.id).join(', ')}`);
|
|
2971
|
+
logger_1.logger.info(' 使用 --prompt 逐个生成分析 Prompt,或 --auto-units 自动遍历全部待分析单元');
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2925
2974
|
/**
|
|
2926
2975
|
* v6.90.0+: 事后校验——检测并清理 020-specs/ 下 AI 绕过 --apply 创建的非法目录和文件
|
|
2927
2976
|
*
|
|
@@ -3294,7 +3343,7 @@ async function injectGraphSummary(prompt) {
|
|
|
3294
3343
|
lines.push(`- \`${s?.name}\` [${e.type}] \`${t?.name}\` (${e.confidence})`);
|
|
3295
3344
|
}
|
|
3296
3345
|
lines.push('');
|
|
3297
|
-
lines.push('> 提示:如需深入查看完整图谱,运行 `speccore
|
|
3346
|
+
lines.push('> 提示:如需深入查看完整图谱,运行 `speccore reindex` 后打开 `.speccore/code-graph/graph.html`');
|
|
3298
3347
|
const summary = lines.join('\n');
|
|
3299
3348
|
_graphSummaryCache = summary;
|
|
3300
3349
|
_graphSummaryCacheTime = Date.now();
|
|
@@ -5175,32 +5224,64 @@ sequenceDiagram
|
|
|
5175
5224
|
}
|
|
5176
5225
|
}
|
|
5177
5226
|
// v8.1.1+: Phase 2 平台文档交叉校验
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5227
|
+
// v8.3.343+: 功能模块项目的平台文档位于 {feature}/{platform}/,
|
|
5228
|
+
// 已由 computeFeatureBasedAnalyzeManifest 覆盖(上方的骨架进度检测按 feature-based 路径校验);
|
|
5229
|
+
// 此顶层校验仅适用于无功能模块的旧架构项目(020-specs/{端}/ 平铺),
|
|
5230
|
+
// feature 项目跳过,避免在 020-specs/{端}/ 顶层误建骨架文件。
|
|
5231
|
+
if (!hasFeatures) {
|
|
5232
|
+
const expectedPlatformDocs = PLATFORM_DOCS.filter(d => includeDocs.includes(d));
|
|
5233
|
+
const missingPlatformDocs = [];
|
|
5234
|
+
for (const platform of platforms) {
|
|
5235
|
+
for (const doc of expectedPlatformDocs) {
|
|
5236
|
+
const docPath = (0, path_1.join)(specDir, platform, doc);
|
|
5237
|
+
if (!await (0, fs_extra_1.pathExists)(docPath)) {
|
|
5238
|
+
missingPlatformDocs.push(`${platform}/${doc}`);
|
|
5239
|
+
}
|
|
5185
5240
|
}
|
|
5186
5241
|
}
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5242
|
+
if (missingPlatformDocs.length > 0) {
|
|
5243
|
+
logger_1.logger.warn(`⚠️ DOC_MATRIX 交叉校验发现 ${missingPlatformDocs.length} 个平台文档缺失: ${missingPlatformDocs.slice(0, 5).join(', ')}${missingPlatformDocs.length > 5 ? '...' : ''}`);
|
|
5244
|
+
logger_1.logger.info(`🔧 自动补建平台骨架文件...`);
|
|
5245
|
+
const missingPlatformEntries = (0, spec_skeleton_1.computeAnalyzeManifest)(platforms, '2', ctx.iteration)
|
|
5246
|
+
.filter(e => {
|
|
5247
|
+
const relName = e.relPath; // e.g. "overview/TECH.md" relative to specDir
|
|
5248
|
+
return missingPlatformDocs.some(mp => relName.endsWith(mp) || mp.endsWith(relName));
|
|
5249
|
+
});
|
|
5250
|
+
if (missingPlatformEntries.length > 0) {
|
|
5251
|
+
await (0, spec_skeleton_1.generateSkeleton)(specDir, missingPlatformEntries);
|
|
5252
|
+
logger_1.logger.info(`✅ 已补建 ${missingPlatformEntries.length} 个平台骨架文件,请继续填充`);
|
|
5253
|
+
const newManifest = (0, spec_skeleton_1.computeAnalyzeManifest)(platforms, undefined, ctx.iteration);
|
|
5254
|
+
perDocStatus = await (0, spec_skeleton_1.detectSkeletonProgress)(specDir, newManifest);
|
|
5255
|
+
}
|
|
5201
5256
|
}
|
|
5202
5257
|
}
|
|
5203
5258
|
}
|
|
5259
|
+
// v8.3.343+: 声明过窄检测——FUNCTION_MAP 端覆盖 × _matrix 声明交叉校验。
|
|
5260
|
+
// doc2spec 写进 _matrix 的 platforms 语义是「端级需求文档归属端」(常单端),
|
|
5261
|
+
// FUNCTION_MAP 的端覆盖语义是「单元跨端协作涉及端」(多端)。多端单元若只有
|
|
5262
|
+
// 单端端级规格,taskify 拿不到后端规格(瞎编/漏任务)、execute 代码生成不完整。
|
|
5263
|
+
// 两类缺口分别警告:missing=缺端级规格(补端分析);undeclared=规格已存在但
|
|
5264
|
+
// _matrix 未声明(taskify 的 readMatrixPlatforms 非空即采信,声明不更新则
|
|
5265
|
+
// 多端子任务依然不会生成)。策略:只读检测 + 警告,不回灌 _matrix。
|
|
5266
|
+
try {
|
|
5267
|
+
const { detectNarrowPlatformDeclarations } = await Promise.resolve().then(() => __importStar(require('../core/function-map')));
|
|
5268
|
+
const narrowGaps = await detectNarrowPlatformDeclarations(iterDir, features, featurePlatforms, platforms);
|
|
5269
|
+
const specGaps = narrowGaps.filter(g => g.missing.length > 0);
|
|
5270
|
+
const declGaps = narrowGaps.filter(g => g.undeclared.length > 0);
|
|
5271
|
+
if (specGaps.length > 0) {
|
|
5272
|
+
const gapLines = specGaps.map(g => ` ${g.feature}: _matrix 声明 [${g.declared.length > 0 ? g.declared.join(', ') : '无'}],FUNCTION_MAP 涉及 [${g.functionMap.join(', ')}],缺端级规格 [${g.missing.join(', ')}]`);
|
|
5273
|
+
logger_1.logger.warn(`⚠️ 声明过窄检测: ${specGaps.length} 个功能单元的端级规格缺失(FUNCTION_MAP 覆盖超出 _matrix 声明):\n${gapLines.join('\n')}`);
|
|
5274
|
+
logger_1.logger.info(`💡 建议: 对缺失端补端分析(analyze --apply 写 020-specs/{单元}/{端}/)`);
|
|
5275
|
+
}
|
|
5276
|
+
if (declGaps.length > 0) {
|
|
5277
|
+
const declLines = declGaps.map(g => ` ${g.feature}: _matrix 声明 [${g.declared.length > 0 ? g.declared.join(', ') : '无'}],端级规格已存在但未声明 [${g.undeclared.join(', ')}]`);
|
|
5278
|
+
logger_1.logger.warn(`⚠️ _matrix 声明滞后: ${declGaps.length} 个功能单元已补端级规格但 platforms 未同步:\n${declLines.join('\n')}`);
|
|
5279
|
+
logger_1.logger.info(`💡 建议: 更新 010-requirements/features/{单元}/_matrix.md 的 platforms 为 FUNCTION_MAP 口径(否则 taskify 仍只拆声明端子任务)`);
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
catch (e) {
|
|
5283
|
+
logger_1.logger.debug(`声明过窄检测跳过(非关键): ${e.message}`);
|
|
5284
|
+
}
|
|
5204
5285
|
}
|
|
5205
5286
|
}
|
|
5206
5287
|
catch (e) {
|