speccore 8.3.349 → 8.3.363
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-verify/SKILL.md +22 -1
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts.map +1 -1
- package/dist/commands/about.js +36 -1
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/analyze/add-platform.d.ts +3 -0
- package/dist/commands/analyze/add-platform.d.ts.map +1 -0
- package/dist/commands/analyze/add-platform.js +82 -0
- package/dist/commands/analyze/add-platform.js.map +1 -0
- package/dist/commands/analyze/audit-fix.d.ts +3 -0
- package/dist/commands/analyze/audit-fix.d.ts.map +1 -0
- package/dist/commands/analyze/audit-fix.js +73 -0
- package/dist/commands/analyze/audit-fix.js.map +1 -0
- package/dist/commands/analyze/context-guard.d.ts +3 -0
- package/dist/commands/analyze/context-guard.d.ts.map +1 -0
- package/dist/commands/analyze/context-guard.js +75 -0
- package/dist/commands/analyze/context-guard.js.map +1 -0
- package/dist/commands/analyze/doc.d.ts +3 -0
- package/dist/commands/analyze/doc.d.ts.map +1 -0
- package/dist/commands/analyze/doc.js +77 -0
- package/dist/commands/analyze/doc.js.map +1 -0
- package/dist/commands/analyze/feature.d.ts +3 -0
- package/dist/commands/analyze/feature.d.ts.map +1 -0
- package/dist/commands/analyze/feature.js +75 -0
- package/dist/commands/analyze/feature.js.map +1 -0
- package/dist/commands/analyze/from-code.d.ts +3 -0
- package/dist/commands/analyze/from-code.d.ts.map +1 -0
- package/dist/commands/analyze/from-code.js +94 -0
- package/dist/commands/analyze/from-code.js.map +1 -0
- package/dist/commands/analyze/incremental.d.ts +3 -0
- package/dist/commands/analyze/incremental.d.ts.map +1 -0
- package/dist/commands/analyze/incremental.js +90 -0
- package/dist/commands/analyze/incremental.js.map +1 -0
- package/dist/commands/analyze/modes.d.ts +13 -0
- package/dist/commands/analyze/modes.d.ts.map +1 -0
- package/dist/commands/analyze/modes.js +147 -0
- package/dist/commands/analyze/modes.js.map +1 -0
- package/dist/commands/analyze/module.d.ts +3 -0
- package/dist/commands/analyze/module.d.ts.map +1 -0
- package/dist/commands/analyze/module.js +95 -0
- package/dist/commands/analyze/module.js.map +1 -0
- package/dist/commands/analyze/overview.d.ts +3 -0
- package/dist/commands/analyze/overview.d.ts.map +1 -0
- package/dist/commands/analyze/overview.js +125 -0
- package/dist/commands/analyze/overview.js.map +1 -0
- package/dist/commands/analyze/shared.d.ts +53 -0
- package/dist/commands/analyze/shared.d.ts.map +1 -0
- package/dist/commands/analyze/shared.js +34 -0
- package/dist/commands/analyze/shared.js.map +1 -0
- package/dist/commands/analyze/units.d.ts +46 -0
- package/dist/commands/analyze/units.d.ts.map +1 -0
- package/dist/commands/analyze/units.js +950 -0
- package/dist/commands/analyze/units.js.map +1 -0
- package/dist/commands/analyze.d.ts +6 -88
- package/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +97 -1417
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/ask.d.ts.map +1 -1
- package/dist/commands/ask.js +4 -2
- package/dist/commands/ask.js.map +1 -1
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +36 -1
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/deploy.d.ts +6 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +153 -0
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/doc2spec.d.ts +1 -1
- package/dist/commands/doc2spec.d.ts.map +1 -1
- package/dist/commands/doc2spec.js +33 -23
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/commands/doctor.js +60 -13
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/execute.d.ts.map +1 -1
- package/dist/commands/execute.js +22 -0
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/help.js +36 -1
- package/dist/commands/help.js.map +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/pipeline.d.ts.map +1 -1
- package/dist/commands/pipeline.js +15 -0
- package/dist/commands/pipeline.js.map +1 -1
- package/dist/commands/plan.js +2 -0
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +14 -3
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/prompts.d.ts +11 -1
- package/dist/commands/prompts.d.ts.map +1 -1
- package/dist/commands/prompts.js +197 -15
- package/dist/commands/prompts.js.map +1 -1
- package/dist/commands/reindex.d.ts.map +1 -1
- package/dist/commands/reindex.js +23 -0
- package/dist/commands/reindex.js.map +1 -1
- package/dist/commands/retro.d.ts.map +1 -1
- package/dist/commands/retro.js +3 -1
- package/dist/commands/retro.js.map +1 -1
- package/dist/commands/status-panel.d.ts.map +1 -1
- package/dist/commands/status-panel.js +9 -1
- package/dist/commands/status-panel.js.map +1 -1
- package/dist/commands/unitize.d.ts +2 -0
- package/dist/commands/unitize.d.ts.map +1 -1
- package/dist/commands/unitize.js +168 -6
- package/dist/commands/unitize.js.map +1 -1
- package/dist/commands/update-env-configs.d.ts.map +1 -1
- package/dist/commands/update-env-configs.js +10 -0
- package/dist/commands/update-env-configs.js.map +1 -1
- package/dist/commands/verify.d.ts +8 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +383 -16
- package/dist/commands/verify.js.map +1 -1
- package/dist/commands/welcome.d.ts.map +1 -1
- package/dist/commands/welcome.js +3 -1
- package/dist/commands/welcome.js.map +1 -1
- package/dist/core/ai-verify/plan-generator.d.ts +94 -0
- package/dist/core/ai-verify/plan-generator.d.ts.map +1 -0
- package/dist/core/ai-verify/plan-generator.js +598 -0
- package/dist/core/ai-verify/plan-generator.js.map +1 -0
- package/dist/core/ai-verify/result-parser.d.ts +27 -0
- package/dist/core/ai-verify/result-parser.d.ts.map +1 -0
- package/dist/core/ai-verify/result-parser.js +212 -0
- package/dist/core/ai-verify/result-parser.js.map +1 -0
- package/dist/core/ai-verify/scope-resolver.d.ts +34 -0
- package/dist/core/ai-verify/scope-resolver.d.ts.map +1 -0
- package/dist/core/ai-verify/scope-resolver.js +183 -0
- package/dist/core/ai-verify/scope-resolver.js.map +1 -0
- package/dist/core/ai-verify/types.d.ts +171 -0
- package/dist/core/ai-verify/types.d.ts.map +1 -0
- package/dist/core/ai-verify/types.js +9 -0
- package/dist/core/ai-verify/types.js.map +1 -0
- package/dist/core/ai-verify/visual-judge.d.ts +33 -0
- package/dist/core/ai-verify/visual-judge.d.ts.map +1 -0
- package/dist/core/ai-verify/visual-judge.js +123 -0
- package/dist/core/ai-verify/visual-judge.js.map +1 -0
- package/dist/core/ask-engine.d.ts +6 -0
- package/dist/core/ask-engine.d.ts.map +1 -1
- package/dist/core/ask-engine.js +83 -3
- package/dist/core/ask-engine.js.map +1 -1
- package/dist/core/code-graph/builder.d.ts.map +1 -1
- package/dist/core/code-graph/builder.js +93 -75
- package/dist/core/code-graph/builder.js.map +1 -1
- package/dist/core/code-graph/incremental-build.d.ts.map +1 -1
- package/dist/core/code-graph/incremental-build.js +13 -19
- package/dist/core/code-graph/incremental-build.js.map +1 -1
- package/dist/core/code-graph/index.d.ts +1 -1
- package/dist/core/code-graph/index.d.ts.map +1 -1
- package/dist/core/code-graph/index.js +15 -12
- package/dist/core/code-graph/index.js.map +1 -1
- package/dist/core/code-graph/multimodal.js +1 -1
- package/dist/core/code-graph/parser.js +1 -1
- package/dist/core/code-graph/query.d.ts +1 -1
- package/dist/core/code-graph/reporter.d.ts +1 -1
- package/dist/core/code-graph/reporter.d.ts.map +1 -1
- package/dist/core/code-graph/reporter.js +18 -10
- package/dist/core/code-graph/reporter.js.map +1 -1
- package/dist/core/code-graph/requirement-linker.d.ts.map +1 -1
- package/dist/core/code-graph/requirement-linker.js +24 -14
- package/dist/core/code-graph/requirement-linker.js.map +1 -1
- package/dist/core/code-graph/types.d.ts +1 -1
- package/dist/core/code-graph/types.js +1 -1
- package/dist/core/code-graph/visualizer.d.ts +1 -7
- package/dist/core/code-graph/visualizer.d.ts.map +1 -1
- package/dist/core/code-graph/visualizer.js +476 -49
- package/dist/core/code-graph/visualizer.js.map +1 -1
- package/dist/core/deploy/engine.d.ts +21 -0
- package/dist/core/deploy/engine.d.ts.map +1 -1
- package/dist/core/deploy/engine.js +276 -0
- package/dist/core/deploy/engine.js.map +1 -1
- package/dist/core/deploy-ledger.d.ts +12 -0
- package/dist/core/deploy-ledger.d.ts.map +1 -0
- package/dist/core/deploy-ledger.js +35 -0
- package/dist/core/deploy-ledger.js.map +1 -0
- package/dist/core/environment-config.d.ts +11 -0
- package/dist/core/environment-config.d.ts.map +1 -1
- package/dist/core/environment-config.js +4 -0
- package/dist/core/environment-config.js.map +1 -1
- package/dist/core/intent-cache.d.ts.map +1 -1
- package/dist/core/intent-cache.js +6 -0
- package/dist/core/intent-cache.js.map +1 -1
- package/dist/core/knowledge-visualizer.js +1 -1
- package/dist/core/page-bundle.d.ts +2 -0
- package/dist/core/page-bundle.d.ts.map +1 -1
- package/dist/core/page-bundle.js +18 -1
- package/dist/core/page-bundle.js.map +1 -1
- package/dist/core/plan-html.js +1 -1
- package/dist/core/platform-inference.d.ts +35 -0
- package/dist/core/platform-inference.d.ts.map +1 -1
- package/dist/core/platform-inference.js +92 -0
- package/dist/core/platform-inference.js.map +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.d.ts.map +1 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js +16 -1
- package/dist/core/prompt-plugins/analyze-graph-plugin.js.map +1 -1
- package/dist/core/prompt-plugins/execute-graph-plugin.d.ts +2 -0
- package/dist/core/prompt-plugins/execute-graph-plugin.d.ts.map +1 -1
- package/dist/core/prompt-plugins/execute-graph-plugin.js +127 -27
- package/dist/core/prompt-plugins/execute-graph-plugin.js.map +1 -1
- package/dist/core/quality-audit.d.ts.map +1 -1
- package/dist/core/quality-audit.js +18 -0
- package/dist/core/quality-audit.js.map +1 -1
- package/dist/core/reindex-engine.d.ts +20 -0
- package/dist/core/reindex-engine.d.ts.map +1 -1
- package/dist/core/reindex-engine.js +142 -0
- package/dist/core/reindex-engine.js.map +1 -1
- package/dist/core/requirement-clarifier.d.ts.map +1 -1
- package/dist/core/requirement-clarifier.js +22 -6
- package/dist/core/requirement-clarifier.js.map +1 -1
- package/dist/core/spec-paths.d.ts +5 -0
- package/dist/core/spec-paths.d.ts.map +1 -1
- package/dist/core/spec-paths.js +1 -0
- package/dist/core/spec-paths.js.map +1 -1
- package/dist/core/test-config.d.ts +7 -1
- package/dist/core/test-config.d.ts.map +1 -1
- package/dist/core/test-config.js +59 -11
- package/dist/core/test-config.js.map +1 -1
- package/dist/core/ui-verify/index.d.ts +1 -1
- package/dist/core/ui-verify/index.d.ts.map +1 -1
- package/dist/core/ui-verify/index.js +2 -1
- package/dist/core/ui-verify/index.js.map +1 -1
- package/dist/core/ui-verify/smoke-engine.js +71 -15
- package/dist/core/ui-verify/smoke-engine.js.map +1 -1
- package/dist/core/ui-verify/spec-generator.d.ts +5 -9
- package/dist/core/ui-verify/spec-generator.d.ts.map +1 -1
- package/dist/core/ui-verify/spec-generator.js +66 -3
- package/dist/core/ui-verify/spec-generator.js.map +1 -1
- package/dist/core/ui-verify/types.d.ts +25 -0
- package/dist/core/ui-verify/types.d.ts.map +1 -1
- package/dist/core/ui-verify/visual-model-client.d.ts +2 -0
- package/dist/core/ui-verify/visual-model-client.d.ts.map +1 -1
- package/dist/core/ui-verify/visual-model-client.js +4 -4
- package/dist/core/ui-verify/visual-model-client.js.map +1 -1
- package/dist/core/unified-config.d.ts +27 -0
- package/dist/core/unified-config.d.ts.map +1 -1
- package/dist/core/unified-config.js +37 -0
- package/dist/core/unified-config.js.map +1 -1
- package/package.json +1 -1
- package/templates/accounts-example.yaml +49 -0
- package/templates/html/fonts/JetBrainsMono.woff2 +0 -0
- package/templates/html/fonts/Orbitron.woff2 +0 -0
- package/templates/html/speccore-ask-explain.html +1 -1
- package/templates/html/speccore-ask-guide.html +2 -2
- package/templates/html/speccore-ask-match.html +1 -1
- package/templates/html/speccore-ask-pipeline.html +1 -1
- package/templates/html/speccore-ask-result.html +2 -2
- package/templates/html/speccore-help.html +2 -2
- package/templates/html/speccore-setup-guide.html +16 -1
- package/templates/speccore-yml-example.yml +12 -0
package/dist/commands/analyze.js
CHANGED
|
@@ -33,16 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.UNITS_CACHE_DIR = void 0;
|
|
36
|
+
exports.saveUnitAnalysis = exports.UNITS_CACHE_DIR = exports.analyzeUnitModes = void 0;
|
|
37
37
|
exports.analyzeCommand = analyzeCommand;
|
|
38
38
|
exports.buildMultiDocPrompt = buildMultiDocPrompt;
|
|
39
|
-
exports.extractIterationUnits = extractIterationUnits;
|
|
40
|
-
exports.saveUnitAnalysis = saveUnitAnalysis;
|
|
41
|
-
exports.loadUnitAnalyses = loadUnitAnalyses;
|
|
42
|
-
exports.detectPendingUnits = detectPendingUnits;
|
|
43
|
-
exports.consolidateUnitAnalyses = consolidateUnitAnalyses;
|
|
44
|
-
exports.buildUnitAnalysisPrompt = buildUnitAnalysisPrompt;
|
|
45
|
-
exports.buildAndSaveQualityReport = buildAndSaveQualityReport;
|
|
46
39
|
/**
|
|
47
40
|
* analyze — 统一分析命令
|
|
48
41
|
*
|
|
@@ -77,19 +70,19 @@ const unified_config_1 = require("../core/unified-config");
|
|
|
77
70
|
const spec_paths_2 = require("../core/spec-paths");
|
|
78
71
|
const spec_skeleton_1 = require("../core/spec-skeleton");
|
|
79
72
|
const unified_retrieval_1 = require("../core/unified-retrieval");
|
|
80
|
-
const unit_context_assembler_1 = require("../core/unit-context-assembler");
|
|
81
|
-
// v8.3.225+: 调度循环状态机(方式 Y 宿主会话内闭环)
|
|
82
|
-
const dispatch_loop_1 = require("../core/dispatch-loop");
|
|
83
73
|
const pipeline_engine_1 = require("../core/pipeline-engine");
|
|
84
74
|
const change_detection_1 = require("../core/change-detection");
|
|
85
75
|
const ai_trace_1 = require("../utils/ai-trace");
|
|
86
|
-
// v8.3.
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
76
|
+
// v8.3.359+: 渐进拆分——共享件(AI 交棒出口 / 选项类型 / 标记解析)下沉到 analyze/shared
|
|
77
|
+
const shared_1 = require("./analyze/shared");
|
|
78
|
+
// v8.3.359+: 模式处理器——物理搬迁到 analyze/ 子目录,入口经注册表分发(Strategy 模式)
|
|
79
|
+
const modes_1 = require("./analyze/modes");
|
|
80
|
+
// v8.3.359+: 单元域全家桶(类型/常量/CRUD/调度循环/质量报告/unitModes)搬迁到 ./analyze/units
|
|
81
|
+
// import 绑定本地作用域供主体使用,export 子句 re-export 保持对外兼容
|
|
82
|
+
const units_1 = require("./analyze/units");
|
|
83
|
+
Object.defineProperty(exports, "analyzeUnitModes", { enumerable: true, get: function () { return units_1.analyzeUnitModes; } });
|
|
84
|
+
Object.defineProperty(exports, "UNITS_CACHE_DIR", { enumerable: true, get: function () { return units_1.UNITS_CACHE_DIR; } });
|
|
85
|
+
Object.defineProperty(exports, "saveUnitAnalysis", { enumerable: true, get: function () { return units_1.saveUnitAnalysis; } });
|
|
93
86
|
const streaming_analyzer_1 = require("../core/streaming-analyzer");
|
|
94
87
|
const requirement_clarifier_1 = require("../core/requirement-clarifier");
|
|
95
88
|
const agents_1 = require("../core/agents");
|
|
@@ -97,8 +90,6 @@ const agents_1 = require("../core/agents");
|
|
|
97
90
|
const code_graph_1 = require("../core/code-graph");
|
|
98
91
|
// v7.2.0+: 结构化代码数据提取
|
|
99
92
|
const structured_extractor_1 = require("../core/structured-extractor");
|
|
100
|
-
// v8.3.177+: 功能单元元数据扫描 + 总览生成
|
|
101
|
-
const feature_metadata_1 = require("../core/feature-metadata");
|
|
102
93
|
/**
|
|
103
94
|
* 按 Markdown 标题合并文档内容
|
|
104
95
|
* v8.3.122+: 新内容中存在的章节替换旧章节,新章节追加,旧文档中未变动的章节保留
|
|
@@ -269,7 +260,7 @@ async function applyDocContent(content, iter) {
|
|
|
269
260
|
}
|
|
270
261
|
// 格式 2:[DOC:xxx] 标记(analyze prompt 的标准输出格式)
|
|
271
262
|
if (Object.keys(docs).length === 0) {
|
|
272
|
-
const blocks = parseDocMarkers(trimmed);
|
|
263
|
+
const blocks = (0, shared_1.parseDocMarkers)(trimmed);
|
|
273
264
|
for (const [name, c] of blocks)
|
|
274
265
|
docs[name] = c;
|
|
275
266
|
}
|
|
@@ -336,140 +327,10 @@ async function analyzeCommand(options) {
|
|
|
336
327
|
if (options.scope === 'global' && !options.iteration) {
|
|
337
328
|
options.iteration = 'GLOBAL';
|
|
338
329
|
}
|
|
339
|
-
// ──
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
// v8.3.341+: 模式早退块抽为模块级函数(渐进拆分 analyzeCommand)
|
|
343
|
-
if (options.fromCode)
|
|
344
|
-
return analyzeFromCode(options);
|
|
345
|
-
// ── --full 模式: 委托给 synthesizeCommand(原 synthesize 命令) ──
|
|
346
|
-
// v8.3.1+ 修复:--phase 不再委托给 synthesize,analyze 的 Phase 1/2 由 analyze 自己处理
|
|
347
|
-
// 原因:analyze --phase 2 应该生成端级文档({端}/TECH.md/DEV_GUIDE.md),
|
|
348
|
-
// 而 synthesize --phase 2 是跨端关系提取(CROSS_PLATFORM.md/ARCHITECTURE.md),
|
|
349
|
-
// 两者完全不同,委托会导致用户永远无法执行 analyze 的 Phase 2
|
|
350
|
-
if (options.full) {
|
|
351
|
-
const { synthesizeCommand } = await Promise.resolve().then(() => __importStar(require('./synthesize')));
|
|
352
|
-
return synthesizeCommand({
|
|
353
|
-
iteration: options.iteration,
|
|
354
|
-
withCode: options.withCode,
|
|
355
|
-
prompt: options.prompt,
|
|
356
|
-
apply: options.apply,
|
|
357
|
-
full: options.full,
|
|
358
|
-
phase: options.phase,
|
|
359
|
-
applyPhase: options.applyPhase,
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
// ── --audit-fix 模式: 读取质量审计报告并生成修复指令 ──
|
|
363
|
-
if (options.auditFix)
|
|
364
|
-
return analyzeAuditFix(options);
|
|
365
|
-
// ── v6.75.0+: 上下文爆炸防护(--context-guard / --estimate-only)──
|
|
366
|
-
if (options.contextGuard || options.estimateOnly) {
|
|
367
|
-
if (await analyzeContextGuard(options))
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
// ── v6.75.0+: 增量分析模式(--incremental / --reanalyze)──
|
|
371
|
-
if (options.incremental || options.reanalyze) {
|
|
372
|
-
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
373
|
-
if (!iter) {
|
|
374
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
378
|
-
const { runIncrementalAnalysis, buildIncrementalPrompt } = await Promise.resolve().then(() => __importStar(require('../core/incremental-analyzer')));
|
|
379
|
-
const analysis = await runIncrementalAnalysis(iterDir, { withCode: options.withCode });
|
|
380
|
-
if (!analysis.hasChanges && analysis.potentialGaps.length === 0) {
|
|
381
|
-
logger_1.logger.success('✅ 未检测到变更,所有分析产出均为最新');
|
|
382
|
-
logger_1.logger.info(' 如需强制重新分析,去掉 --incremental 参数');
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
logger_1.logger.info('');
|
|
386
|
-
logger_1.logger.info(`📊 增量分析结果: ${analysis.recommendation}`);
|
|
387
|
-
logger_1.logger.info('');
|
|
388
|
-
if (options.prompt) {
|
|
389
|
-
const prompt = buildIncrementalPrompt(iterDir, analysis, iter);
|
|
390
|
-
await emitAiPrompt(iter, prompt, { mode: 'incremental' });
|
|
391
|
-
process.exitCode = 10;
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
// 非 prompt 模式,输出摘要
|
|
395
|
-
const changedReqs = analysis.requirementChanges.filter(c => c.changeType !== 'unchanged');
|
|
396
|
-
if (changedReqs.length > 0) {
|
|
397
|
-
logger_1.logger.info(`✏️ 需求文档变更: ${changedReqs.length} 个`);
|
|
398
|
-
}
|
|
399
|
-
const changedCode = analysis.codeChanges.filter(c => c.changeType !== 'unchanged');
|
|
400
|
-
if (changedCode.length > 0) {
|
|
401
|
-
logger_1.logger.info(`✏️ 源码文件变更: ${changedCode.length} 个`);
|
|
402
|
-
}
|
|
403
|
-
if (analysis.addedPlatforms.length > 0) {
|
|
404
|
-
logger_1.logger.info(`🆕 新增端: ${analysis.addedPlatforms.join(', ')}`);
|
|
405
|
-
}
|
|
406
|
-
if (analysis.potentialGaps.length > 0) {
|
|
407
|
-
logger_1.logger.info(`⚠️ 潜在遗漏: ${analysis.potentialGaps.length} 项`);
|
|
408
|
-
for (const gap of analysis.potentialGaps) {
|
|
409
|
-
logger_1.logger.info(` • ${gap}`);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
logger_1.logger.info('');
|
|
413
|
-
logger_1.logger.info('📋 下一步:');
|
|
414
|
-
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --incremental`);
|
|
330
|
+
// ── 模式注册表分发(Strategy 模式,v8.3.359+)──
|
|
331
|
+
// 各模式 detect 谓词 + 处理器见 ./analyze/modes,注册顺序与旧 if 瀑布检测顺序一致
|
|
332
|
+
if (await (0, modes_1.dispatchAnalyzeModes)(options, _projectRoot))
|
|
415
333
|
return;
|
|
416
|
-
}
|
|
417
|
-
// ── v6.75.0+: 新增端分析模式(--add-platform)──
|
|
418
|
-
if (options.addPlatform) {
|
|
419
|
-
const iter = options.iteration || await (0, context_1.getDefaultIteration)();
|
|
420
|
-
if (!iter) {
|
|
421
|
-
logger_1.logger.error('请指定迭代: -I <iteration>');
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
425
|
-
const newPlatform = options.addPlatform;
|
|
426
|
-
const { analyzeNewPlatform, buildNewPlatformPrompt } = await Promise.resolve().then(() => __importStar(require('../core/platform-addition')));
|
|
427
|
-
const analysis = await analyzeNewPlatform(iterDir, newPlatform);
|
|
428
|
-
logger_1.logger.info('');
|
|
429
|
-
logger_1.logger.info(`🆕 新增端分析: ${newPlatform} (${analysis.platformType})`);
|
|
430
|
-
logger_1.logger.info(` 后端端: ${analysis.isBackend ? '是' : '否'}`);
|
|
431
|
-
logger_1.logger.info(` 已有端: ${analysis.existingPlatforms.join(', ')}`);
|
|
432
|
-
logger_1.logger.info(` 跨端关系: ${analysis.crossPlatformRelations.length} 个`);
|
|
433
|
-
logger_1.logger.info(` 全局更新: ${analysis.globalUpdates.length} 项`);
|
|
434
|
-
logger_1.logger.info('');
|
|
435
|
-
if (options.prompt) {
|
|
436
|
-
const prompt = buildNewPlatformPrompt(iterDir, analysis, iter);
|
|
437
|
-
await emitAiPrompt(iter, prompt, { mode: 'new-platform' });
|
|
438
|
-
process.exitCode = 10;
|
|
439
|
-
return;
|
|
440
|
-
}
|
|
441
|
-
logger_1.logger.info('📋 新端产出:');
|
|
442
|
-
for (const out of analysis.newPlatformOutputs) {
|
|
443
|
-
logger_1.logger.info(` • ${out}`);
|
|
444
|
-
}
|
|
445
|
-
logger_1.logger.info('');
|
|
446
|
-
logger_1.logger.info('📋 全局文档更新:');
|
|
447
|
-
for (const up of analysis.globalUpdates) {
|
|
448
|
-
logger_1.logger.info(` • ${up.action}: ${up.file}`);
|
|
449
|
-
}
|
|
450
|
-
logger_1.logger.info('');
|
|
451
|
-
logger_1.logger.info('📋 下一步:');
|
|
452
|
-
logger_1.logger.info(` speccore analyze --prompt -I ${iter} --add-platform ${newPlatform}`);
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
// ── v6.76.0+: 功能模块级全局分析(--module)──
|
|
456
|
-
if (options.module)
|
|
457
|
-
return analyzeModuleMode(options);
|
|
458
|
-
// ── --feature 模式: 局部刷新单个功能模块 ──
|
|
459
|
-
if (options.feature)
|
|
460
|
-
return analyzeFeatureMode(options, _projectRoot);
|
|
461
|
-
// ── --doc 模式: 局部刷新单个类型文档(bugs/refactors/research) ──
|
|
462
|
-
if (options.doc)
|
|
463
|
-
return analyzeDocMode(options, _projectRoot);
|
|
464
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
465
|
-
// v8.3.177+: 总览生成模式(--scope=overview --phase=skeleton|full)
|
|
466
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
467
|
-
if (options.scope === 'overview')
|
|
468
|
-
return analyzeOverviewMode(options);
|
|
469
|
-
// ── v8.2.0+: 功能单元聚焦分析模式(--extract-units / --unit / --consolidate / --resume-units / --auto-units)──
|
|
470
|
-
if (options.extractUnits || options.unit || options.consolidate || options.resumeUnits || options.autoUnits) {
|
|
471
|
-
return analyzeUnitModes(options, _projectRoot);
|
|
472
|
-
}
|
|
473
334
|
// v8.3.16+: 模糊匹配端名,支持多段指定(逗号分隔)
|
|
474
335
|
if (options.platform) {
|
|
475
336
|
const allPlatforms = await (0, spec_paths_2.parsePlatformList)();
|
|
@@ -746,12 +607,32 @@ async function analyzeCommand(options) {
|
|
|
746
607
|
// clarify: needed 的单元产出 CLARIFY.md,专业单元直接分析(缺什么补什么,而非人人过关)
|
|
747
608
|
const featuresDirAuto = (0, path_1.join)(iterDir, '010-requirements', 'features');
|
|
748
609
|
const featureUnitsAuto = await (0, spec_paths_2.parseFeatureList)(iterDir);
|
|
610
|
+
// v8.3.352+: 备份目录堆积提示——unitize overwrite 的时间戳备份无生命周期(可回滚副本),
|
|
611
|
+
// 堆积过多提醒用户;各消费点均按 isTimestampBackup 过滤,不影响分析本身
|
|
612
|
+
try {
|
|
613
|
+
const backupDirs = (await (0, fs_extra_2.readdir)(featuresDirAuto)).filter((n) => (0, task_utils_1.isTimestampBackup)(n));
|
|
614
|
+
if (backupDirs.length > 0) {
|
|
615
|
+
logger_1.logger.info(` 💡 features/ 下有 ${backupDirs.length} 个切分备份目录(overwrite 时保留的可回滚副本),可 speccore unitize --prune-backups 清理`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
catch { /* 目录不存在则无备份 */ }
|
|
749
619
|
if (featureUnitsAuto.length > 0) {
|
|
750
620
|
for (const unit of featureUnitsAuto) {
|
|
751
621
|
const unitDir = (0, path_1.join)(featuresDirAuto, unit);
|
|
752
622
|
const srcPath = await (0, feature_source_1.resolveUnitSourcePath)(unitDir);
|
|
753
623
|
if (!(await (0, fs_extra_1.pathExists)(srcPath)))
|
|
754
624
|
continue;
|
|
625
|
+
// v8.3.352+: 澄清状态机幂等修复——CLARIFY.md 已落盘(非骨架)即视为已澄清,
|
|
626
|
+
// 不再依赖 _matrix.md 的 clarify 标记(标记无回写通道,此前已澄清单元每次
|
|
627
|
+
// analyze 都被重复要求澄清)
|
|
628
|
+
const unitClarifyDoc = (0, path_1.join)(iterDir, '020-specs', unit, 'overview', 'CLARIFY.md');
|
|
629
|
+
if (await (0, fs_extra_1.pathExists)(unitClarifyDoc)) {
|
|
630
|
+
const cc = await (0, fs_extra_2.readFile)(unitClarifyDoc, 'utf-8');
|
|
631
|
+
if (!cc.includes(spec_skeleton_1.SKELETON_MARKER)) {
|
|
632
|
+
logger_1.logger.info(` ⏭️ ${unit} 已有澄清文档(020-specs/${unit}/overview/CLARIFY.md),跳过澄清检测`);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
755
636
|
const matrixPath = (0, path_1.join)(unitDir, '_matrix.md');
|
|
756
637
|
let level;
|
|
757
638
|
if (await (0, fs_extra_1.pathExists)(matrixPath)) {
|
|
@@ -898,6 +779,21 @@ async function analyzeCommand(options) {
|
|
|
898
779
|
const declared = [...featurePlatforms.entries()].map(([f, ps]) => `${f}→[${ps.join(',')}]`).join(';');
|
|
899
780
|
logger_1.logger.info(`📍 端覆盖矩阵收敛: ${declared}`);
|
|
900
781
|
}
|
|
782
|
+
// v8.3.352+: 孤儿 020-specs 目录检测——features 重切(单元改名/删除)后,
|
|
783
|
+
// 020-specs 下旧单元目录永久残留(QUALITY_AUDIT 会误当功能模块审计出噪音),
|
|
784
|
+
// 对比当前单元清单 warn 提示,由用户决定迁移或删除
|
|
785
|
+
try {
|
|
786
|
+
const specEntries = await (0, fs_extra_2.readdir)((0, path_1.join)(iterDirForSkeleton, '020-specs'), { withFileTypes: true });
|
|
787
|
+
const sysDirs = new Set(['overview', 'requirements']);
|
|
788
|
+
const orphanDirs = specEntries
|
|
789
|
+
.filter((e) => e.isDirectory() && !e.name.startsWith('.') && !sysDirs.has(e.name) && !features.includes(e.name))
|
|
790
|
+
.map((e) => e.name);
|
|
791
|
+
if (orphanDirs.length > 0) {
|
|
792
|
+
logger_1.logger.warn(` 🧹 检测到 ${orphanDirs.length} 个孤儿目录(020-specs 下与当前功能单元不对应,疑似重切前旧产物): ${orphanDirs.join(', ')}`);
|
|
793
|
+
logger_1.logger.warn(' 确认无价值后可手动删除;有内容价值请迁移到新单元目录');
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
catch { /* 只读检测,失败不影响主流程 */ }
|
|
901
797
|
}
|
|
902
798
|
else {
|
|
903
799
|
manifest = (0, spec_skeleton_1.computeFeatureBasedAnalyzeManifest)([], platforms, phase, iterName);
|
|
@@ -1151,7 +1047,7 @@ async function analyzeCommand(options) {
|
|
|
1151
1047
|
process.stdout.write(`[SPECCORE_CONTEXT_BUDGET: 8000]\n`);
|
|
1152
1048
|
process.stdout.write(`[SPECCORE_CONTEXT_TYPE: platform-only]\n\n`);
|
|
1153
1049
|
}
|
|
1154
|
-
await emitAiPrompt(iter, finalPrompt, { mode: 'subagent-final' });
|
|
1050
|
+
await (0, shared_1.emitAiPrompt)(iter, finalPrompt, { mode: 'subagent-final' });
|
|
1155
1051
|
process.exitCode = 10;
|
|
1156
1052
|
return;
|
|
1157
1053
|
}
|
|
@@ -1181,7 +1077,7 @@ async function analyzeCommand(options) {
|
|
|
1181
1077
|
if (shouldClarify) {
|
|
1182
1078
|
// 需要澄清,先执行 clarify(内部已生成质量报告)
|
|
1183
1079
|
const clarifyPrompt = await buildClarifyPhasePrompt(iter);
|
|
1184
|
-
await emitAiPrompt(iter, clarifyPrompt, { mode: 'clarify-phase' });
|
|
1080
|
+
await (0, shared_1.emitAiPrompt)(iter, clarifyPrompt, { mode: 'clarify-phase' });
|
|
1185
1081
|
process.exitCode = 10;
|
|
1186
1082
|
return;
|
|
1187
1083
|
}
|
|
@@ -1195,7 +1091,7 @@ async function analyzeCommand(options) {
|
|
|
1195
1091
|
analyzeOptions: options,
|
|
1196
1092
|
});
|
|
1197
1093
|
const prompt = (0, prompt_builder_1.buildPromptText)(promptObj);
|
|
1198
|
-
await emitAiPrompt(iter, prompt, { mode: 'analyze-host' });
|
|
1094
|
+
await (0, shared_1.emitAiPrompt)(iter, prompt, { mode: 'analyze-host' });
|
|
1199
1095
|
process.exitCode = 10;
|
|
1200
1096
|
return;
|
|
1201
1097
|
}
|
|
@@ -1646,7 +1542,7 @@ async function analyzeCommand(options) {
|
|
|
1646
1542
|
logger_1.logger.info(` 1. 读取上述问题文档的当前内容`);
|
|
1647
1543
|
logger_1.logger.info(` 2. 根据质量报告中的错误信息补充/修正内容`);
|
|
1648
1544
|
logger_1.logger.info(` 3. 用 speccore global analyze --apply '{"文件路径":"修正后内容"}' 重新写入`);
|
|
1649
|
-
await emitAiPrompt(options.iteration || 'GLOBAL', `\n# 质量门禁修复任务\n\n以下全局分析文档未通过质量校验,请修复后重新写入:\n\n`, { mode: 'global-gate-fix' });
|
|
1545
|
+
await (0, shared_1.emitAiPrompt)(options.iteration || 'GLOBAL', `\n# 质量门禁修复任务\n\n以下全局分析文档未通过质量校验,请修复后重新写入:\n\n`, { mode: 'global-gate-fix' });
|
|
1650
1546
|
for (const r of criticalErrors.slice(0, 5)) {
|
|
1651
1547
|
const fname = r.file.split('/').slice(-2).join('/');
|
|
1652
1548
|
const errs = r.issues.filter(i => i.severity === 'error').map(i => `- ${i.message}`).join('\n');
|
|
@@ -2009,7 +1905,7 @@ async function analyzeCommand(options) {
|
|
|
2009
1905
|
}
|
|
2010
1906
|
}
|
|
2011
1907
|
// AI 输出格式: [DOC:REQUIREMENT.md]...内容...[DOC:TECH.md]...内容...
|
|
2012
|
-
const docBlocks = parseDocMarkers(options.apply);
|
|
1908
|
+
const docBlocks = (0, shared_1.parseDocMarkers)(options.apply);
|
|
2013
1909
|
const MAX_DOCS_PER_APPLY = 3;
|
|
2014
1910
|
if (docBlocks.size > MAX_DOCS_PER_APPLY) {
|
|
2015
1911
|
logger_1.logger.error(`❌ 批量 apply 拦截: 检测到 ${docBlocks.size} 个 [DOC:xxx] 文档,超过单次上限 ${MAX_DOCS_PER_APPLY}`);
|
|
@@ -2293,7 +2189,7 @@ async function analyzeCommand(options) {
|
|
|
2293
2189
|
}
|
|
2294
2190
|
if (options.unit && options.iteration) {
|
|
2295
2191
|
try {
|
|
2296
|
-
const unitsCachePath = (0, path_1.join)(_projectRoot,
|
|
2192
|
+
const unitsCachePath = (0, path_1.join)(_projectRoot, units_1.UNITS_CACHE_DIR, options.iteration, 'units.json');
|
|
2297
2193
|
let unitName = options.unit;
|
|
2298
2194
|
if (await (0, fs_extra_1.pathExists)(unitsCachePath)) {
|
|
2299
2195
|
const allUnits = JSON.parse(await (0, fs_extra_2.readFile)(unitsCachePath, 'utf-8'));
|
|
@@ -2310,7 +2206,7 @@ async function analyzeCommand(options) {
|
|
|
2310
2206
|
risk: docs['RISK.md'] || '',
|
|
2311
2207
|
timestamp: new Date().toISOString(),
|
|
2312
2208
|
};
|
|
2313
|
-
await saveUnitAnalysis(options.iteration, unitAnalysis);
|
|
2209
|
+
await (0, units_1.saveUnitAnalysis)(options.iteration, unitAnalysis);
|
|
2314
2210
|
logger_1.logger.info(`💾 单元分析 ${options.unit} 已缓存(供 --consolidate 使用)`);
|
|
2315
2211
|
}
|
|
2316
2212
|
catch (e) {
|
|
@@ -2445,7 +2341,7 @@ async function analyzeCommand(options) {
|
|
|
2445
2341
|
catch { /* ignore */ }
|
|
2446
2342
|
}
|
|
2447
2343
|
const prompt = await buildMultiDocPrompt('analyze', { iteration: iter, scope: options.scope, withCode: options.withCode, platform: options.platform, phase: options.phase, autoMode: options.auto }, options, needsClarify ? { needsClarify, clarifyTargets } : undefined);
|
|
2448
|
-
await emitAiPrompt(iter, prompt, { mode: 'analyze-main', scope: options.scope });
|
|
2344
|
+
await (0, shared_1.emitAiPrompt)(iter, prompt, { mode: 'analyze-main', scope: options.scope });
|
|
2449
2345
|
// v7.2.0+: 全局分析完成后输出下一步引导
|
|
2450
2346
|
if (options.scope === 'global') {
|
|
2451
2347
|
const guide = await buildGlobalAnalysisGuide(options);
|
|
@@ -2461,516 +2357,15 @@ async function analyzeCommand(options) {
|
|
|
2461
2357
|
* v8.3.341+: 从 analyzeCommand 抽出的模式早退块(--from-code)
|
|
2462
2358
|
* 源码反推需求(两跳 IR→UR)+ 全局技术分析
|
|
2463
2359
|
*/
|
|
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
2360
|
/**
|
|
2657
|
-
* v8.3.
|
|
2658
|
-
* 局部刷新单个类型文档(bugs/refactors/research)
|
|
2361
|
+
* v8.3.359+: 从 analyzeCommand 抽出的模式早退块(--audit-fix)已搬迁到 ./analyze/audit-fix
|
|
2659
2362
|
*/
|
|
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
2363
|
/**
|
|
2694
|
-
* v8.3.
|
|
2695
|
-
* 总览生成模式(--phase=skeleton|full)
|
|
2364
|
+
* v8.3.359+: 总览生成模式(--scope=overview)已搬迁到 ./analyze/overview
|
|
2696
2365
|
*/
|
|
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
2366
|
/**
|
|
2729
|
-
* v8.3.
|
|
2730
|
-
* 功能单元聚焦分析
|
|
2367
|
+
* v8.3.359+: 功能单元聚焦分析模式(--extract-units / --unit / --consolidate / --resume-units / --auto-units)已搬迁到 ./analyze/units
|
|
2731
2368
|
*/
|
|
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
|
-
}
|
|
2974
2369
|
/**
|
|
2975
2370
|
* v6.90.0+: 事后校验——检测并清理 020-specs/ 下 AI 绕过 --apply 创建的非法目录和文件
|
|
2976
2371
|
*
|
|
@@ -3871,6 +3266,22 @@ async function buildMultiDocPrompt(command, ctx, options, clarifyCtx) {
|
|
|
3871
3266
|
}
|
|
3872
3267
|
await (0, structured_extractor_1.extractStructuredData)(projectRoot, filteredPaths);
|
|
3873
3268
|
structuredDataHint = '\n> 📊 **结构化数据**: 已提取到 `.speccore/cache/structured-data.json`,包含 API/Entity/Route/Component 清单\n';
|
|
3269
|
+
// v8.3.356+: 源码知识图谱增量维护——断链修复(此前构建入口零调用,
|
|
3270
|
+
// reindex 与 analyze 都不重建 code-graph,插件消费陈旧图谱)。
|
|
3271
|
+
// 无变化时仅算文件 hash 秒级返回;构建失败静默降级不阻断分析。
|
|
3272
|
+
try {
|
|
3273
|
+
const { buildCodeKnowledgeGraphIncremental } = await Promise.resolve().then(() => __importStar(require('../core/code-graph')));
|
|
3274
|
+
const { collectSourceFiles } = await Promise.resolve().then(() => __importStar(require('../core/reindex-engine')));
|
|
3275
|
+
const codeGraph = await buildCodeKnowledgeGraphIncremental({
|
|
3276
|
+
projectRoot,
|
|
3277
|
+
projectName: 'Project',
|
|
3278
|
+
preScannedFiles: await collectSourceFiles(filteredPaths.map(p => (0, path_1.join)(projectRoot, p))),
|
|
3279
|
+
});
|
|
3280
|
+
logger_1.logger.info(` 🧩 源码图谱已同步: ${codeGraph.nodes.length} 节点 / ${codeGraph.edges.length} 边`);
|
|
3281
|
+
}
|
|
3282
|
+
catch (graphError) {
|
|
3283
|
+
logger_1.logger.warn(` ⚠️ 源码图谱增量维护跳过: ${graphError?.message || graphError}`);
|
|
3284
|
+
}
|
|
3874
3285
|
}
|
|
3875
3286
|
catch (e) {
|
|
3876
3287
|
logger_1.logger.warn(` ⚠️ 结构化数据提取失败: ${e.message}`);
|
|
@@ -6460,8 +5871,8 @@ async function buildClarifyPhasePrompt(iteration) {
|
|
|
6460
5871
|
prompt += `\`\`\`\n\n`;
|
|
6461
5872
|
}
|
|
6462
5873
|
prompt += `---\n\n`;
|
|
6463
|
-
prompt += `# 📋
|
|
6464
|
-
prompt += `>
|
|
5874
|
+
prompt += `# 📋 六步迭代澄清流程\n\n`;
|
|
5875
|
+
prompt += `> 本阶段要求你先以专业角色深度分析,再与用户逐轮确认,最终写入。\n\n`;
|
|
6465
5876
|
prompt += `## Step 1: 深度专业分析\n\n`;
|
|
6466
5877
|
prompt += `对每份需求文档,分别以 **产品分析师** 和 **交互设计师** 两个角色进行深度审查:\n\n`;
|
|
6467
5878
|
prompt += `**产品分析师审查清单**:\n`;
|
|
@@ -6483,8 +5894,17 @@ async function buildClarifyPhasePrompt(iteration) {
|
|
|
6483
5894
|
prompt += `- [ ] 前后端校验规则是否一致\n`;
|
|
6484
5895
|
prompt += `- [ ] 数据刷新策略是什么(实时/手动/轮询/WebSocket)\n\n`;
|
|
6485
5896
|
prompt += `**输出**:输出一份《深度审查报告》,列出所有发现的问题和修改建议。\n\n`;
|
|
6486
|
-
prompt += `## Step 2:
|
|
6487
|
-
prompt +=
|
|
5897
|
+
prompt += `## Step 2: 关键问题访谈(先问再写)\n\n`;
|
|
5898
|
+
prompt += `先不要动笔写 Draft v1。基于 Step 1 的审查报告,提炼 **3-5 个最关键、最影响改写走向的待确认问题**,逐条向用户提问:\n\n`;
|
|
5899
|
+
prompt += `**提问纪律**:\n`;
|
|
5900
|
+
prompt += `- 只问「影响大且原文无答案」的问题:核心流程分叉、权限模型、数据边界、异常处理策略、通知触发条件等;\n`;
|
|
5901
|
+
prompt += `- 不问能从原文推断的问题——推断结果直接写进 Draft v1,由 Step 5 的批改循环纠正;\n`;
|
|
5902
|
+
prompt += `- 不问实现细节(技术选型、表结构、接口协议——那是 analyze 阶段的职责);\n`;
|
|
5903
|
+
prompt += `- 每条问题给出 2-3 个选项并标注推荐项与理由,降低用户回答成本;\n`;
|
|
5904
|
+
prompt += `- 全部问题一次性列出,等待用户逐条回答(用户可直接采纳推荐项)。\n\n`;
|
|
5905
|
+
prompt += `**输出**:问题清单。用户回答后,将答案作为硬约束进入 Step 3 生成 Draft v1。\n\n`;
|
|
5906
|
+
prompt += `## Step 3: 生成改写版(Draft v1)\n\n`;
|
|
5907
|
+
prompt += `基于 Step 1 的审查报告与 Step 2 的用户答案,生成一份 **完整的专业 PRD 文档**,要求:\n\n`;
|
|
6488
5908
|
prompt += `### 必须包含的章节\n`;
|
|
6489
5909
|
prompt += `1. **背景与目标**:为什么要做这个功能,解决什么问题,预期收益\n`;
|
|
6490
5910
|
prompt += `2. **术语表(Glossary)**:文档中所有专业术语的统一表述\n`;
|
|
@@ -6508,8 +5928,8 @@ async function buildClarifyPhasePrompt(iteration) {
|
|
|
6508
5928
|
prompt += `- 前端交互必须包含 Loading/Empty/Error 状态\n`;
|
|
6509
5929
|
prompt += `- 不要添加文档中未提及的功能\n`;
|
|
6510
5930
|
prompt += `- 如果原始描述不完整,标注「待确认」而不是编造\n\n`;
|
|
6511
|
-
prompt += `**输出**:将 Draft v1 的内容暂存,进入 Step
|
|
6512
|
-
prompt += `## Step
|
|
5931
|
+
prompt += `**输出**:将 Draft v1 的内容暂存,进入 Step 4。\n\n`;
|
|
5932
|
+
prompt += `## Step 4: 生成对比视图\n\n`;
|
|
6513
5933
|
prompt += `生成一份 **原文档 vs 改写版(Draft v1)的对比报告**,格式如下:\n\n`;
|
|
6514
5934
|
prompt += `\`\`\`\n`;
|
|
6515
5935
|
prompt += `## 需求澄清对比报告\n\n`;
|
|
@@ -6528,8 +5948,8 @@ async function buildClarifyPhasePrompt(iteration) {
|
|
|
6528
5948
|
prompt += `- 建议保留改写版的改进(原因)\n`;
|
|
6529
5949
|
prompt += `- 建议保留原文的表述(原因)\n`;
|
|
6530
5950
|
prompt += `\`\`\`\n\n`;
|
|
6531
|
-
prompt += `**输出**:将对比报告展示给用户,进入 Step
|
|
6532
|
-
prompt += `## Step
|
|
5951
|
+
prompt += `**输出**:将对比报告展示给用户,进入 Step 5。\n\n`;
|
|
5952
|
+
prompt += `## Step 5: 迭代确认(多轮对话)\n\n`;
|
|
6533
5953
|
prompt += `将 **对比报告** 和 **Draft v1** 一起展示给用户,然后:\n\n`;
|
|
6534
5954
|
prompt += `1. **询问用户意见**:\n`;
|
|
6535
5955
|
prompt += ` - "以上是我对需求文档的专业分析和改写,请检查是否有以下问题:"\n`;
|
|
@@ -6538,14 +5958,14 @@ async function buildClarifyPhasePrompt(iteration) {
|
|
|
6538
5958
|
prompt += ` - "是否有遗漏的功能点?"\n`;
|
|
6539
5959
|
prompt += ` - "是否有不需要的功能?"\n\n`;
|
|
6540
5960
|
prompt += `2. **接收用户反馈**:\n`;
|
|
6541
|
-
prompt += ` - 如果用户提出修改意见 → 生成 **Draft v2** → 重新生成对比 →
|
|
5961
|
+
prompt += ` - 如果用户提出修改意见 → 生成 **Draft v2** → 重新生成对比 → 回到本步骤第 1 条继续询问\n`;
|
|
6542
5962
|
prompt += ` - 可以迭代多轮(v1 → v2 → v3 ...),直到用户满意\n`;
|
|
6543
5963
|
prompt += ` - 每轮迭代都要生成新的对比报告,标注本轮修改\n\n`;
|
|
6544
5964
|
prompt += `3. **迭代原则**:\n`;
|
|
6545
5965
|
prompt += ` - 尊重用户决策:用户说保留原文的,保留原文\n`;
|
|
6546
5966
|
prompt += ` - 专业建议:用户遗漏的,以「建议补充」形式提出,不强加\n`;
|
|
6547
5967
|
prompt += ` - 记录取舍:每轮迭代记录「采纳/拒绝/待确认」的决策\n\n`;
|
|
6548
|
-
prompt += `## Step
|
|
5968
|
+
prompt += `## Step 6: 确认写入\n\n`;
|
|
6549
5969
|
prompt += `当用户确认 "满意,可以写入" 后:\n\n`;
|
|
6550
5970
|
prompt += `**必须为每份源文档分别生成独立的澄清文档**,使用 \`[CLARIFY:{feature-name}/overview/CLARIFY.md]\` 标记分隔:\n\n`;
|
|
6551
5971
|
prompt += `> v8.3.181+ 推荐使用新路径。旧格式 \`[CLARIFY:requirements/xxx-clarified.md]\` 仍兼容。\n\n`;
|
|
@@ -6654,22 +6074,6 @@ async function buildConfirmCheckPrompt(iteration) {
|
|
|
6654
6074
|
// ================================================================
|
|
6655
6075
|
// ================================================================
|
|
6656
6076
|
/** 解析 [DOC:filename] 标记,将统一报告拆分为多文档 Map */
|
|
6657
|
-
function parseDocMarkers(text) {
|
|
6658
|
-
const result = new Map();
|
|
6659
|
-
const markerRegex = /\[DOC:([^\]]+)\]/g;
|
|
6660
|
-
let match;
|
|
6661
|
-
const positions = [];
|
|
6662
|
-
while ((match = markerRegex.exec(text)) !== null) {
|
|
6663
|
-
positions.push({ name: match[1].trim(), index: match.index });
|
|
6664
|
-
}
|
|
6665
|
-
for (let i = 0; i < positions.length; i++) {
|
|
6666
|
-
const start = positions[i].index + `[DOC:${positions[i].name}]`.length;
|
|
6667
|
-
const end = i < positions.length - 1 ? positions[i + 1].index : text.length;
|
|
6668
|
-
const content = text.slice(start, end).trim();
|
|
6669
|
-
result.set(positions[i].name, content);
|
|
6670
|
-
}
|
|
6671
|
-
return result;
|
|
6672
|
-
}
|
|
6673
6077
|
/** 解析 [CLARIFY:filename] 标记,提取需求澄清文档 Map — v8.3.0+ */
|
|
6674
6078
|
function parseClarifyMarkers(text) {
|
|
6675
6079
|
const result = new Map();
|
|
@@ -6687,345 +6091,6 @@ function parseClarifyMarkers(text) {
|
|
|
6687
6091
|
}
|
|
6688
6092
|
return result;
|
|
6689
6093
|
}
|
|
6690
|
-
exports.UNITS_CACHE_DIR = '.speccore/cache/unit-analysis';
|
|
6691
|
-
/** 从需求文档提取功能单元清单 */
|
|
6692
|
-
async function extractIterationUnits(iterDir) {
|
|
6693
|
-
const reqDir = (0, path_1.join)(iterDir, '010-requirements');
|
|
6694
|
-
const units = [];
|
|
6695
|
-
let idCounter = 1;
|
|
6696
|
-
if (!await (0, fs_extra_1.pathExists)(reqDir))
|
|
6697
|
-
return units;
|
|
6698
|
-
// v8.3.201+: 优先读 010-requirements/features/ 的一级子目录名作为功能单元(与 doc2spec 切分目录对齐)。
|
|
6699
|
-
// 旧逻辑「递归扫描所有 .md 的 ## 标题」会误读 _matrix.md / GLOBAL_CONTEXT-*.md / CHANGELOG.md,
|
|
6700
|
-
// 反而读不到真正的功能单元目录名。
|
|
6701
|
-
const featuresDir = (0, path_1.join)(reqDir, 'features');
|
|
6702
|
-
if (await (0, fs_extra_1.pathExists)(featuresDir)) {
|
|
6703
|
-
const entries = await (0, fs_extra_2.readdir)(featuresDir, { withFileTypes: true });
|
|
6704
|
-
// v8.3.267: 过滤 overview- 前缀的全局章节目录(unitize 误切产物,
|
|
6705
|
-
// 如「核心业务规则跨端通用」),防止假单元混入调度循环
|
|
6706
|
-
const dirs = entries.filter(e => e.isDirectory() && !e.name.startsWith('.') && !e.name.startsWith('_') && !e.name.startsWith('overview-'));
|
|
6707
|
-
for (const dir of dirs) {
|
|
6708
|
-
const readmePath = await (0, feature_source_1.resolveUnitSourcePath)((0, path_1.join)(featuresDir, dir.name));
|
|
6709
|
-
let content = '';
|
|
6710
|
-
if (await (0, fs_extra_1.pathExists)(readmePath)) {
|
|
6711
|
-
// v8.3.267: 图片引用可能落在 500 字符截断之外,显式保留引用行,
|
|
6712
|
-
// 保证单元上下文的图片清单完整(AI 可据此 Read 原型图)
|
|
6713
|
-
const full = await (0, fs_extra_2.readFile)(readmePath, 'utf-8');
|
|
6714
|
-
content = full.slice(0, 500);
|
|
6715
|
-
const imgRefs = new Set();
|
|
6716
|
-
const imgRe = /!\[[^\]]*\]\(([^)\s]+)\)/g;
|
|
6717
|
-
let m;
|
|
6718
|
-
while ((m = imgRe.exec(full)) !== null)
|
|
6719
|
-
imgRefs.add(m[1]);
|
|
6720
|
-
const outside = [...imgRefs].filter(r => !content.includes(r));
|
|
6721
|
-
if (outside.length > 0) {
|
|
6722
|
-
content += `\n\n<!-- 原文图片引用(截断外保留)-->\n${outside.map(r => ``).join('\n')}`;
|
|
6723
|
-
}
|
|
6724
|
-
}
|
|
6725
|
-
units.push({
|
|
6726
|
-
id: `M-${String(idCounter++).padStart(2, '0')}`,
|
|
6727
|
-
name: dir.name,
|
|
6728
|
-
source: (0, path_1.relative)(reqDir, readmePath).replace(/\\/g, '/'),
|
|
6729
|
-
sourceRange: { startLine: 1, endLine: 1 },
|
|
6730
|
-
content,
|
|
6731
|
-
});
|
|
6732
|
-
if (idCounter > 30)
|
|
6733
|
-
break;
|
|
6734
|
-
}
|
|
6735
|
-
if (units.length > 0)
|
|
6736
|
-
return units;
|
|
6737
|
-
}
|
|
6738
|
-
const reqFiles = [];
|
|
6739
|
-
// v8.3.210+: 系统报告/验证文档(非功能单元),兜底扫描时跳过
|
|
6740
|
-
const systemDocs = new Set(['VALIDATION', 'VALIDATION_REPORT', 'VERIFICATION', 'QUALITY_REPORT', 'CLARIFY_REPORT', 'INDEX', 'README', 'CHANGELOG', 'FUNCTION_MAP', 'GLOSSARY']);
|
|
6741
|
-
const resourceDirs = new Set(['assets', 'images', 'prototypes', 'screenshots', 'sources', 'designs', 'extracted']);
|
|
6742
|
-
async function scanDir(dir) {
|
|
6743
|
-
const entries = await (0, fs_extra_2.readdir)(dir, { withFileTypes: true });
|
|
6744
|
-
for (const entry of entries) {
|
|
6745
|
-
const fullPath = (0, path_1.join)(dir, entry.name);
|
|
6746
|
-
if (entry.isDirectory()) {
|
|
6747
|
-
if (resourceDirs.has(entry.name))
|
|
6748
|
-
continue; // 不递归资源目录
|
|
6749
|
-
await scanDir(fullPath);
|
|
6750
|
-
}
|
|
6751
|
-
else if (entry.name.endsWith('.md')) {
|
|
6752
|
-
const base = entry.name.slice(0, -3);
|
|
6753
|
-
if (systemDocs.has(base) || base.startsWith('unit-') || base.startsWith('_'))
|
|
6754
|
-
continue;
|
|
6755
|
-
reqFiles.push(fullPath);
|
|
6756
|
-
}
|
|
6757
|
-
}
|
|
6758
|
-
}
|
|
6759
|
-
await scanDir(reqDir);
|
|
6760
|
-
for (const file of reqFiles) {
|
|
6761
|
-
const content = await (0, fs_extra_2.readFile)(file, 'utf-8');
|
|
6762
|
-
const lines = content.split(/\r?\n/);
|
|
6763
|
-
for (let i = 0; i < lines.length; i++) {
|
|
6764
|
-
const line = lines[i];
|
|
6765
|
-
const headingMatch = line.match(/^#{2,3}\s+(.+)$/);
|
|
6766
|
-
if (!headingMatch)
|
|
6767
|
-
continue;
|
|
6768
|
-
const name = headingMatch[1].trim();
|
|
6769
|
-
// 跳过通用标题
|
|
6770
|
-
const skipKeywords = ['需求概述', '术语表', '附录', '测试策略', '参考资料', '目录', '引言', '背景'];
|
|
6771
|
-
if (skipKeywords.some(k => name.includes(k)))
|
|
6772
|
-
continue;
|
|
6773
|
-
// 收集后续 1-15 行作为内容摘要
|
|
6774
|
-
let descLines = [];
|
|
6775
|
-
for (let j = i + 1; j < Math.min(i + 16, lines.length); j++) {
|
|
6776
|
-
const l = lines[j];
|
|
6777
|
-
if (l.match(/^#{1,3}\s+/))
|
|
6778
|
-
break;
|
|
6779
|
-
if (l.trim())
|
|
6780
|
-
descLines.push(l.trim());
|
|
6781
|
-
}
|
|
6782
|
-
units.push({
|
|
6783
|
-
id: `M-${String(idCounter++).padStart(2, '0')}`,
|
|
6784
|
-
name,
|
|
6785
|
-
source: (0, path_1.relative)(reqDir, file),
|
|
6786
|
-
sourceRange: { startLine: i + 1, endLine: i + 1 + descLines.length },
|
|
6787
|
-
content: descLines.join('\n').slice(0, 500),
|
|
6788
|
-
});
|
|
6789
|
-
if (idCounter > 30)
|
|
6790
|
-
break; // 最多识别 30 个功能单元
|
|
6791
|
-
}
|
|
6792
|
-
if (idCounter > 30)
|
|
6793
|
-
break;
|
|
6794
|
-
}
|
|
6795
|
-
return units;
|
|
6796
|
-
}
|
|
6797
|
-
/** 保存单元分析结果到缓存 */
|
|
6798
|
-
async function saveUnitAnalysis(iteration, analysis) {
|
|
6799
|
-
const cacheDir = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), exports.UNITS_CACHE_DIR, iteration);
|
|
6800
|
-
await (0, fs_extra_1.ensureDir)(cacheDir);
|
|
6801
|
-
const fp = (0, path_1.join)(cacheDir, `${analysis.id}.json`);
|
|
6802
|
-
await (0, fs_extra_1.writeFile)(fp, JSON.stringify(analysis, null, 2));
|
|
6803
|
-
}
|
|
6804
|
-
/** 读取已完成的单元分析 */
|
|
6805
|
-
async function loadUnitAnalyses(iteration) {
|
|
6806
|
-
const cacheDir = (0, path_1.join)(((0, task_utils_1.findProjectRoot)() || process.cwd()), exports.UNITS_CACHE_DIR, iteration);
|
|
6807
|
-
if (!await (0, fs_extra_1.pathExists)(cacheDir))
|
|
6808
|
-
return [];
|
|
6809
|
-
// v8.3.225+ 修复:排除 units.json 清单缓存(extractIterationUnits 产物,非单元分析结果),
|
|
6810
|
-
// 否则数组被 push 进 results 后 sort 时 a.id 为 undefined 崩溃
|
|
6811
|
-
const files = (await (0, fs_extra_2.readdir)(cacheDir)).filter(f => f.endsWith('.json') && f !== 'units.json');
|
|
6812
|
-
const results = [];
|
|
6813
|
-
for (const f of files) {
|
|
6814
|
-
try {
|
|
6815
|
-
const content = await (0, fs_extra_2.readFile)((0, path_1.join)(cacheDir, f), 'utf-8');
|
|
6816
|
-
const parsed = JSON.parse(content);
|
|
6817
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed) || !parsed.id)
|
|
6818
|
-
continue;
|
|
6819
|
-
results.push(parsed);
|
|
6820
|
-
}
|
|
6821
|
-
catch { /* skip */ }
|
|
6822
|
-
}
|
|
6823
|
-
return results.sort((a, b) => a.id.localeCompare(b.id));
|
|
6824
|
-
}
|
|
6825
|
-
/** 检测未完成的单元分析 */
|
|
6826
|
-
async function detectPendingUnits(iteration, allUnits) {
|
|
6827
|
-
const completed = await loadUnitAnalyses(iteration);
|
|
6828
|
-
const completedIds = new Set(completed.map(a => a.id));
|
|
6829
|
-
// v8.3.266+: 用绝对路径迭代目录替代相对路径拼接——cwd 不在迭代父目录时旧写法会指向错误路径,
|
|
6830
|
-
// 导致 unitHasSkeletonOutput 兜底检查全部误判为骨架(路径不存在)
|
|
6831
|
-
const iterDir = await (0, context_1.getIterationDir)(iteration);
|
|
6832
|
-
const pending = [];
|
|
6833
|
-
for (const u of allUnits) {
|
|
6834
|
-
if (!completedIds.has(u.id)) {
|
|
6835
|
-
pending.push(u);
|
|
6836
|
-
continue;
|
|
6837
|
-
}
|
|
6838
|
-
// v8.3.233+: 完成记录存在但落盘内容仍为骨架 → 仍视为待分析(兜底:
|
|
6839
|
-
// 用户可对有问题的单元再次分析,直到无空骨架)
|
|
6840
|
-
if (await unitHasSkeletonOutput(iterDir, u))
|
|
6841
|
-
pending.push(u);
|
|
6842
|
-
}
|
|
6843
|
-
return pending;
|
|
6844
|
-
}
|
|
6845
|
-
/** v8.3.233+: 检查单元落盘规格是否仍为骨架/空内容(用于 pending 判定兜底,不写缓存) */
|
|
6846
|
-
async function unitHasSkeletonOutput(iterDir, unit) {
|
|
6847
|
-
const specDir = getUnitSpecDir(iterDir, unit);
|
|
6848
|
-
for (const f of ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md']) {
|
|
6849
|
-
const fp = (0, path_1.join)(specDir, f);
|
|
6850
|
-
if (!(await (0, fs_extra_1.pathExists)(fp)))
|
|
6851
|
-
continue;
|
|
6852
|
-
const content = (await (0, fs_extra_2.readFile)(fp, 'utf-8')).trim();
|
|
6853
|
-
if (content && !content.includes('SPEC-SKELETON') && content.length >= 200)
|
|
6854
|
-
return false;
|
|
6855
|
-
}
|
|
6856
|
-
return true; // 三个候选全无效 → 视为骨架状态
|
|
6857
|
-
}
|
|
6858
|
-
/** 汇总所有单元分析为统一报告(带 [DOC:xxx] 标记) */
|
|
6859
|
-
async function consolidateUnitAnalyses(iteration, units, analyses) {
|
|
6860
|
-
const lines = [];
|
|
6861
|
-
lines.push(`# ${iteration} 迭代 Spec 统一报告`);
|
|
6862
|
-
lines.push(`> 生成时间: ${new Date().toISOString()}`);
|
|
6863
|
-
lines.push(`> 共 ${analyses.length}/${units.length} 个功能单元已完成分析`);
|
|
6864
|
-
lines.push('');
|
|
6865
|
-
// 汇总 REQUIREMENT.md
|
|
6866
|
-
lines.push('[DOC:REQUIREMENT.md]');
|
|
6867
|
-
lines.push(`# ${iteration} 需求规格`);
|
|
6868
|
-
lines.push('');
|
|
6869
|
-
lines.push('## 功能模块清单');
|
|
6870
|
-
lines.push('| 单元ID | 模块名称 | 需求来源 |');
|
|
6871
|
-
lines.push('|:---|:---|:---|');
|
|
6872
|
-
for (const u of units) {
|
|
6873
|
-
lines.push(`| ${u.id} | ${u.name} | ${u.source}#L${u.sourceRange.startLine} |`);
|
|
6874
|
-
}
|
|
6875
|
-
lines.push('');
|
|
6876
|
-
for (const a of analyses) {
|
|
6877
|
-
lines.push(`### ${a.id} ${a.name}`);
|
|
6878
|
-
lines.push(a.req.split(/\r?\n/).slice(0, 5).join('\n'));
|
|
6879
|
-
lines.push('');
|
|
6880
|
-
}
|
|
6881
|
-
lines.push('');
|
|
6882
|
-
// 汇总 ANALYSIS.md
|
|
6883
|
-
lines.push('[DOC:ANALYSIS.md]');
|
|
6884
|
-
lines.push(`# ${iteration} 架构分析`);
|
|
6885
|
-
lines.push('');
|
|
6886
|
-
for (const a of analyses) {
|
|
6887
|
-
lines.push(`## ${a.id} ${a.name}`);
|
|
6888
|
-
lines.push(a.tech);
|
|
6889
|
-
lines.push('');
|
|
6890
|
-
}
|
|
6891
|
-
lines.push('');
|
|
6892
|
-
// 汇总 FUNCTION_MAP.md
|
|
6893
|
-
lines.push('[DOC:FUNCTION_MAP.md]');
|
|
6894
|
-
lines.push(`# ${iteration} 功能映射`);
|
|
6895
|
-
lines.push('');
|
|
6896
|
-
lines.push('| 单元ID | 功能模块 | 涉及端 | 优先级 |');
|
|
6897
|
-
lines.push('|:---|:---|:---|:---|');
|
|
6898
|
-
for (const a of analyses) {
|
|
6899
|
-
lines.push(`| ${a.id} | ${a.name} | ${(a.tech.match(/端[::]\s*(.+)/)?.[1] || '待定')} | P1 |`);
|
|
6900
|
-
}
|
|
6901
|
-
lines.push('');
|
|
6902
|
-
// 汇总 TECH.md
|
|
6903
|
-
lines.push('[DOC:TECH.md]');
|
|
6904
|
-
lines.push(`# ${iteration} 技术方案`);
|
|
6905
|
-
lines.push('');
|
|
6906
|
-
for (const a of analyses) {
|
|
6907
|
-
lines.push(`## ${a.id} ${a.name}`);
|
|
6908
|
-
lines.push(a.tech);
|
|
6909
|
-
lines.push('');
|
|
6910
|
-
}
|
|
6911
|
-
lines.push('');
|
|
6912
|
-
// 汇总 TEST.md
|
|
6913
|
-
lines.push('[DOC:TEST.md]');
|
|
6914
|
-
lines.push(`# ${iteration} 测试方案`);
|
|
6915
|
-
lines.push('');
|
|
6916
|
-
for (const a of analyses) {
|
|
6917
|
-
lines.push(`## ${a.id} ${a.name}`);
|
|
6918
|
-
lines.push(a.test);
|
|
6919
|
-
lines.push('');
|
|
6920
|
-
}
|
|
6921
|
-
lines.push('');
|
|
6922
|
-
// 汇总 RISK.md
|
|
6923
|
-
lines.push('[DOC:RISK.md]');
|
|
6924
|
-
lines.push(`# ${iteration} 风险评估`);
|
|
6925
|
-
lines.push('');
|
|
6926
|
-
for (const a of analyses) {
|
|
6927
|
-
if (a.risk) {
|
|
6928
|
-
lines.push(`## ${a.id} ${a.name}`);
|
|
6929
|
-
lines.push(a.risk);
|
|
6930
|
-
lines.push('');
|
|
6931
|
-
}
|
|
6932
|
-
}
|
|
6933
|
-
lines.push('');
|
|
6934
|
-
return lines.join('\n');
|
|
6935
|
-
}
|
|
6936
|
-
/**
|
|
6937
|
-
* v8.3.249+: 构建「各端工程路径」段——从 CONSTITUTION.md 工程表解析各端源码路径。
|
|
6938
|
-
* 单元级/全量分析 prompt 共用,避免 AI 自行翻表定位源码。解析失败时返回空串不阻断。
|
|
6939
|
-
*/
|
|
6940
|
-
async function buildProjectPathsSection() {
|
|
6941
|
-
try {
|
|
6942
|
-
const projectInfoMap = await (0, spec_paths_1.parseProjectInfo)();
|
|
6943
|
-
if (projectInfoMap.size === 0)
|
|
6944
|
-
return '';
|
|
6945
|
-
const lines = [];
|
|
6946
|
-
for (const [identifier, info] of projectInfoMap) {
|
|
6947
|
-
lines.push(`- \`${identifier}\`(${info.projectType || '-'})→ \`${info.srcPath}\``);
|
|
6948
|
-
}
|
|
6949
|
-
return lines.join('\n') + '\n';
|
|
6950
|
-
}
|
|
6951
|
-
catch { /* 路径解析失败不阻断分析 */
|
|
6952
|
-
return '';
|
|
6953
|
-
}
|
|
6954
|
-
}
|
|
6955
|
-
/** 构建单个功能单元的深度分析 Prompt */
|
|
6956
|
-
async function buildUnitAnalysisPrompt(iterDir, iteration, unit, allUnits) {
|
|
6957
|
-
const unitRef = { id: unit.id, name: unit.name, content: unit.content };
|
|
6958
|
-
const allUnitRefs = allUnits.map(u => ({ id: u.id, name: u.name, content: u.content }));
|
|
6959
|
-
const ctxResult = await (0, unit_context_assembler_1.assembleUnitContext)(iterDir, iteration, unitRef, allUnitRefs, {
|
|
6960
|
-
maxTokens: 6000,
|
|
6961
|
-
maxRelatedUnits: 3,
|
|
6962
|
-
maxCodeFiles: 5,
|
|
6963
|
-
useKnowledgeGraph: true,
|
|
6964
|
-
});
|
|
6965
|
-
const prompt = `
|
|
6966
|
-
# 功能单元深度分析: ${unit.id} ${unit.name}
|
|
6967
|
-
|
|
6968
|
-
## 迭代信息
|
|
6969
|
-
- 迭代: ${iteration}
|
|
6970
|
-
- 单元ID: ${unit.id}
|
|
6971
|
-
- 来源: ${unit.source}#L${unit.sourceRange.startLine}
|
|
6972
|
-
- 全迭代共 ${allUnits.length} 个功能单元
|
|
6973
|
-
- 上下文预算: ${ctxResult.stats.estimatedTokens} tokens (核心${ctxResult.stats.coreChars}字符 + 关联${ctxResult.stats.relatedUnitChars + ctxResult.stats.codeChars}字符 + 全局${ctxResult.stats.globalChars}字符)
|
|
6974
|
-
|
|
6975
|
-
## 各端工程路径(结合源码分析时按此定位)
|
|
6976
|
-
${await buildProjectPathsSection()}
|
|
6977
|
-
|
|
6978
|
-
## 需求上下文
|
|
6979
|
-
${ctxResult.context}
|
|
6980
|
-
|
|
6981
|
-
## 分析要求
|
|
6982
|
-
|
|
6983
|
-
请对「${unit.name}」进行深度分析,输出以下文档(使用 [DOC:xxx] 标记分隔):
|
|
6984
|
-
|
|
6985
|
-
### [DOC:REQ.md] 需求规格
|
|
6986
|
-
- 功能描述(用户故事格式)
|
|
6987
|
-
- 输入/输出定义
|
|
6988
|
-
- 业务规则(R-XX 编号)
|
|
6989
|
-
- 异常场景
|
|
6990
|
-
|
|
6991
|
-
### [DOC:TECH.md] 技术方案
|
|
6992
|
-
- 数据模型/接口设计
|
|
6993
|
-
- 核心算法/逻辑
|
|
6994
|
-
- 端分工(backend/web/admin 等)
|
|
6995
|
-
- 依赖服务
|
|
6996
|
-
|
|
6997
|
-
### [DOC:TEST.md] 测试方案
|
|
6998
|
-
- 验收标准(Given-When-Then)
|
|
6999
|
-
- 边界值
|
|
7000
|
-
- 异常路径
|
|
7001
|
-
|
|
7002
|
-
### [DOC:RISK.md] 风险评估(如适用)
|
|
7003
|
-
- 技术风险
|
|
7004
|
-
- 业务风险
|
|
7005
|
-
- 缓解方案
|
|
7006
|
-
|
|
7007
|
-
## 输出格式
|
|
7008
|
-
\`\`\`
|
|
7009
|
-
[DOC:REQ.md]
|
|
7010
|
-
(内容...)
|
|
7011
|
-
|
|
7012
|
-
[DOC:TECH.md]
|
|
7013
|
-
(内容...)
|
|
7014
|
-
|
|
7015
|
-
[DOC:TEST.md]
|
|
7016
|
-
(内容...)
|
|
7017
|
-
|
|
7018
|
-
[DOC:RISK.md]
|
|
7019
|
-
(内容...)
|
|
7020
|
-
\`\`\`
|
|
7021
|
-
|
|
7022
|
-
## 重要提示
|
|
7023
|
-
- 只分析当前功能单元「${unit.name}」,不要涉及其他模块
|
|
7024
|
-
- 每个文档必须有实质内容,不能是空模板
|
|
7025
|
-
- 使用具体的技术术语,避免泛泛而谈
|
|
7026
|
-
`;
|
|
7027
|
-
return prompt.trim();
|
|
7028
|
-
}
|
|
7029
6094
|
/**
|
|
7030
6095
|
* v8.3.225+: 生成 Pipeline 当前步骤的分析 prompt(含进度标签)。
|
|
7031
6096
|
* --prompt --pipeline 初始化/恢复路径与 --apply 后自动推进路径复用,保证两处 prompt 一致。
|
|
@@ -7159,390 +6224,5 @@ async function advanceToWorkStep(engine) {
|
|
|
7159
6224
|
}
|
|
7160
6225
|
return advanced;
|
|
7161
6226
|
}
|
|
7162
|
-
//
|
|
7163
|
-
// 调度循环模式(v8.3.225+,方式 Y 宿主会话内闭环)
|
|
7164
|
-
// ═══════════════════════════════════════════════════════════
|
|
7165
|
-
/** 单元名 → 目录名安全化(兜底模式下单元名可能是标题文本) */
|
|
7166
|
-
function sanitizeUnitDirName(name) {
|
|
7167
|
-
return name.replace(/[\\/:*?"<>|\s]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 80) || 'unit';
|
|
7168
|
-
}
|
|
7169
|
-
/** 单元规格产物目录(对齐切分目录名,与 req-doc 澄清路径一致) */
|
|
7170
|
-
function getUnitSpecDir(iterDir, unit) {
|
|
7171
|
-
return (0, path_1.join)(iterDir, '020-specs', sanitizeUnitDirName(unit.name), 'overview');
|
|
7172
|
-
}
|
|
7173
|
-
/**
|
|
7174
|
-
* 校验单元分析产物:020-specs/{单元名}/overview/ 下至少一份有效规格文档。
|
|
7175
|
-
* 通过时顺带同步旧版单元分析缓存(consolidate 依赖),保持幂等。
|
|
7176
|
-
*/
|
|
7177
|
-
async function validateUnitSpec(iterDir, iter, unit) {
|
|
7178
|
-
const specDir = getUnitSpecDir(iterDir, unit);
|
|
7179
|
-
const candidates = ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md'];
|
|
7180
|
-
const found = {};
|
|
7181
|
-
for (const f of candidates) {
|
|
7182
|
-
const fp = (0, path_1.join)(specDir, f);
|
|
7183
|
-
if (!(await (0, fs_extra_1.pathExists)(fp)))
|
|
7184
|
-
continue;
|
|
7185
|
-
const content = (await (0, fs_extra_2.readFile)(fp, 'utf-8')).trim();
|
|
7186
|
-
if (!content || content.includes('SPEC-SKELETON') || content.length < 200)
|
|
7187
|
-
continue;
|
|
7188
|
-
found[f] = content;
|
|
7189
|
-
}
|
|
7190
|
-
if (Object.keys(found).length === 0) {
|
|
7191
|
-
return `020-specs/${sanitizeUnitDirName(unit.name)}/overview/ 下缺少有效的规格文档(REQUIREMENT.md / ANALYSIS.md / TECH.md 至少一份,内容 ≥200 字符且非骨架模板)`;
|
|
7192
|
-
}
|
|
7193
|
-
// 同步旧版缓存,保证 --consolidate 可读
|
|
7194
|
-
try {
|
|
7195
|
-
await saveUnitAnalysis(iter, {
|
|
7196
|
-
id: unit.id,
|
|
7197
|
-
name: unit.name,
|
|
7198
|
-
req: found['REQUIREMENT.md'] || '',
|
|
7199
|
-
tech: found['TECH.md'] || found['ANALYSIS.md'] || '',
|
|
7200
|
-
test: '',
|
|
7201
|
-
timestamp: new Date().toISOString(),
|
|
7202
|
-
});
|
|
7203
|
-
}
|
|
7204
|
-
catch { /* 缓存同步失败不阻断主流程 */ }
|
|
7205
|
-
return null;
|
|
7206
|
-
}
|
|
7207
|
-
/** 构建单元分析循环 prompt:复用现有 prompt + 落盘约束 + 重试反馈 */
|
|
7208
|
-
async function buildUnitLoopPrompt(iterDir, iter, unit, allUnits, attempt, lastError) {
|
|
7209
|
-
const base = await buildUnitAnalysisPrompt(iterDir, iter, unit, allUnits);
|
|
7210
|
-
const unitDirName = sanitizeUnitDirName(unit.name);
|
|
7211
|
-
const outputDir = `020-specs/${unitDirName}/overview/`;
|
|
7212
|
-
let extra = `
|
|
7213
|
-
|
|
7214
|
-
## 落盘方式(重要 — 请直接写文件)
|
|
7215
|
-
|
|
7216
|
-
请使用 Write 工具将以下文档直接写入迭代目录(不要只在回复中输出内容):
|
|
7217
|
-
|
|
7218
|
-
- \`${outputDir}REQUIREMENT.md\` — 需求规格(功能描述/输入输出/业务规则/异常场景)
|
|
7219
|
-
- \`${outputDir}ANALYSIS.md\` — 需求分析(现状/差距/方案要点)
|
|
7220
|
-
- \`${outputDir}TECH.md\` — 技术方案(数据模型/接口设计/端分工/依赖)
|
|
7221
|
-
|
|
7222
|
-
约束:
|
|
7223
|
-
- 每个文件内容不少于 200 字符,禁止输出空模板或骨架占位
|
|
7224
|
-
- 写入前先创建目录;写完后回复一行:\`DONE ${unit.name}\`
|
|
7225
|
-
- 若上下文中含「需求图片/原型」或 \`\` 图片引用,请先用 Read 工具打开图片文件查看,再结合图片信息撰写规格;不要忽略图片提示
|
|
7226
|
-
`;
|
|
7227
|
-
if (attempt > 1 && lastError) {
|
|
7228
|
-
extra += `
|
|
7229
|
-
## ⚠️ 上次尝试未通过校验(第 ${attempt - 1} 次尝试)
|
|
7230
|
-
|
|
7231
|
-
失败原因:${lastError}
|
|
7232
|
-
|
|
7233
|
-
请检查文件是否写入了正确路径(\`${outputDir}\`),并确保内容完整、非骨架模板。
|
|
7234
|
-
`;
|
|
7235
|
-
}
|
|
7236
|
-
return base + extra;
|
|
7237
|
-
}
|
|
7238
|
-
/**
|
|
7239
|
-
* v8.3.266: 调度循环 SDK 派单——Qoder 环境非 TTY 下由 CLI 直接调起子 Agent
|
|
7240
|
-
* (独立上下文、acceptEdits 直接写文件),返回 true 表示派单成功(含自动落盘)。
|
|
7241
|
-
*/
|
|
7242
|
-
async function trySdkDispatchUnit(prompt, iter, unitName) {
|
|
7243
|
-
try {
|
|
7244
|
-
const agentCtx = {
|
|
7245
|
-
subagent: 'requirement-analyzer',
|
|
7246
|
-
iteration: iter,
|
|
7247
|
-
contextBudget: 6000,
|
|
7248
|
-
contextType: 'incremental',
|
|
7249
|
-
cwd: (0, task_utils_1.findProjectRoot)() || process.cwd(),
|
|
7250
|
-
};
|
|
7251
|
-
const dispatchResult = await (0, agent_adapter_1.dispatchSubagent)(agentCtx, prompt);
|
|
7252
|
-
if (!dispatchResult || !dispatchResult.success) {
|
|
7253
|
-
logger_1.logger.debug('[analyze] SDK 派单未成功,回退标记模式');
|
|
7254
|
-
return false;
|
|
7255
|
-
}
|
|
7256
|
-
// v8.3.266: 防御空转——success 但既没写文件也没返回可落盘内容,视为无效派单,
|
|
7257
|
-
// 立即回退标记模式(否则循环会白白耗尽该单元 3 次尝试并误标 failed)
|
|
7258
|
-
if (!dispatchResult.content && (!dispatchResult.filesChanged || dispatchResult.filesChanged.length === 0)) {
|
|
7259
|
-
logger_1.logger.warn('[analyze] SDK 派单无产出(未写文件且无回传内容),回退标记模式');
|
|
7260
|
-
return false;
|
|
7261
|
-
}
|
|
7262
|
-
logger_1.logger.info(`[analyze] 调度循环子 Agent SDK 派单成功: ${unitName}`);
|
|
7263
|
-
// v8.3.196+ 自动落盘:子 Agent 未直接写文件时,解析其返回的 JSON/[DOC:] 内容
|
|
7264
|
-
// 并路由到该单元的 overview/ 目录(applyDocContent 无单元路由,此处专用落盘)
|
|
7265
|
-
if (dispatchResult.content) {
|
|
7266
|
-
const autoApplied = await applyUnitLoopDocs(dispatchResult.content, iter, unitName);
|
|
7267
|
-
if (autoApplied > 0) {
|
|
7268
|
-
process.stdout.write(`✅ 已自动写入 ${autoApplied} 个文档到 020-specs/${sanitizeUnitDirName(unitName)}/overview/\n`);
|
|
7269
|
-
}
|
|
7270
|
-
}
|
|
7271
|
-
if (dispatchResult.filesChanged && dispatchResult.filesChanged.length > 0) {
|
|
7272
|
-
process.stdout.write(`📄 文件变更: ${dispatchResult.filesChanged.join(', ')}\n`);
|
|
7273
|
-
}
|
|
7274
|
-
return true;
|
|
7275
|
-
}
|
|
7276
|
-
catch (e) {
|
|
7277
|
-
logger_1.logger.debug(`[analyze] SDK 派单异常: ${e.message},回退标记模式`);
|
|
7278
|
-
return false;
|
|
7279
|
-
}
|
|
7280
|
-
}
|
|
7281
|
-
/**
|
|
7282
|
-
* v8.3.266: 把子 Agent 回传的 JSON/[DOC:] 内容写入指定单元的 overview/ 目录。
|
|
7283
|
-
* 文档名归一化:analyze prompt 用 [DOC:REQ.md],落盘用 REQUIREMENT.md(validateUnitSpec 校验名单)。
|
|
7284
|
-
*/
|
|
7285
|
-
async function applyUnitLoopDocs(content, iter, unitName) {
|
|
7286
|
-
const trimmed = content.trim();
|
|
7287
|
-
if (!trimmed)
|
|
7288
|
-
return 0;
|
|
7289
|
-
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
7290
|
-
const unitDir = (0, path_1.join)(iterDir, '020-specs', sanitizeUnitDirName(unitName), 'overview');
|
|
7291
|
-
let docs = {};
|
|
7292
|
-
if (trimmed.startsWith('{')) {
|
|
7293
|
-
try {
|
|
7294
|
-
const parsed = JSON.parse(trimmed);
|
|
7295
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
7296
|
-
docs = parsed;
|
|
7297
|
-
}
|
|
7298
|
-
catch { /* 回退 [DOC:] 标记 */ }
|
|
7299
|
-
}
|
|
7300
|
-
if (Object.keys(docs).length === 0) {
|
|
7301
|
-
for (const [name, c] of parseDocMarkers(trimmed))
|
|
7302
|
-
docs[name] = c;
|
|
7303
|
-
}
|
|
7304
|
-
if (Object.keys(docs).length === 0)
|
|
7305
|
-
return 0;
|
|
7306
|
-
const NAME_MAP = { 'REQ.md': 'REQUIREMENT.md' };
|
|
7307
|
-
let applied = 0;
|
|
7308
|
-
for (const [filename, docContent] of Object.entries(docs)) {
|
|
7309
|
-
if (typeof docContent !== 'string' || !docContent.trim())
|
|
7310
|
-
continue;
|
|
7311
|
-
const baseName = (filename.split('/').pop() || filename).trim();
|
|
7312
|
-
const normalized = NAME_MAP[baseName] || baseName;
|
|
7313
|
-
if (!normalized.endsWith('.md') || normalized.startsWith('.'))
|
|
7314
|
-
continue;
|
|
7315
|
-
await (0, fs_extra_1.ensureDir)(unitDir);
|
|
7316
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(unitDir, normalized), docContent);
|
|
7317
|
-
applied++;
|
|
7318
|
-
}
|
|
7319
|
-
return applied;
|
|
7320
|
-
}
|
|
7321
|
-
/** 单元分析调度循环:构建步骤清单并推进一轮 */
|
|
7322
|
-
async function runUnitDispatchLoop(iter, iterDir, units) {
|
|
7323
|
-
const steps = units.map((u) => ({
|
|
7324
|
-
id: `unit:${sanitizeUnitDirName(u.name)}`,
|
|
7325
|
-
name: u.name,
|
|
7326
|
-
validate: () => validateUnitSpec(iterDir, iter, u),
|
|
7327
|
-
buildPrompt: ({ attempt, lastError }) => buildUnitLoopPrompt(iterDir, iter, u, units, attempt, lastError),
|
|
7328
|
-
}));
|
|
7329
|
-
return (0, dispatch_loop_1.runDispatchLoopRound)(steps, {
|
|
7330
|
-
projectRoot: (0, task_utils_1.findProjectRoot)() || process.cwd(),
|
|
7331
|
-
scope: `analyze-units:${iter}`,
|
|
7332
|
-
// v8.3.233+: 一轮内最多 3 次尝试(初始 + 2 重试);循环外 resume 还可无限续跑
|
|
7333
|
-
maxRetries: 2,
|
|
7334
|
-
});
|
|
7335
|
-
}
|
|
7336
|
-
/** 输出调度循环完成汇总 */
|
|
7337
|
-
async function printUnitLoopSummary(iter, iterDir, units, result) {
|
|
7338
|
-
const s = result.summary;
|
|
7339
|
-
if (s.failed.length > 0) {
|
|
7340
|
-
logger_1.logger.warn(`⚠️ 功能单元分析循环完成: ${s.done.length} 完成 / ${s.failed.length} 失败`);
|
|
7341
|
-
for (const id of s.failed) {
|
|
7342
|
-
logger_1.logger.warn(` ❌ ${id}: ${s.failedErrors[id] || '未知原因'}`);
|
|
7343
|
-
}
|
|
7344
|
-
logger_1.logger.info(` 重跑失败单元: speccore analyze --unit "<单元名>" --prompt -I ${iter}`);
|
|
7345
|
-
}
|
|
7346
|
-
else {
|
|
7347
|
-
logger_1.logger.success(`🎉 所有 ${s.done.length} 个功能单元分析完成!`);
|
|
7348
|
-
}
|
|
7349
|
-
logger_1.logger.info(` 下一步: 汇总统一报告 speccore analyze --prompt -I ${iter} --consolidate`);
|
|
7350
|
-
// v8.3.266+: 循环只保证 overview/ 3 份,端级文档({feature}/{端}/)由 Phase 2 负责——
|
|
7351
|
-
// 此前完成提示不提 Phase 2,用户以为分析已闭环,taskify 时端级内容仍是骨架
|
|
7352
|
-
const platformSk = await countPlatformSkeletons(iterDir);
|
|
7353
|
-
if (platformSk.total > 0 && platformSk.skeleton > 0) {
|
|
7354
|
-
logger_1.logger.warn(`⚠️ 端级文档仍有 ${platformSk.skeleton}/${platformSk.total} 份为骨架(overview/ 之外)`);
|
|
7355
|
-
logger_1.logger.info(` Phase 2 生成各端专属文档: speccore analyze --prompt -I ${iter} --phase 2`);
|
|
7356
|
-
}
|
|
7357
|
-
// v8.3.234+: 执行完成质量报告(成功/失败清单 + 产出物评分)落盘 QUALITY_AUDIT.md
|
|
7358
|
-
await buildAndSaveQualityReport(iterDir, iter, units);
|
|
7359
|
-
}
|
|
7360
|
-
/**
|
|
7361
|
-
* v8.3.266+: 统计 020-specs/{feature}/{端}/ 下仍为骨架的 .md 文件数。
|
|
7362
|
-
* 用于调度循环完成后引导 Phase 2(调度循环只保证 overview/ 3 份落盘)。
|
|
7363
|
-
*/
|
|
7364
|
-
async function countPlatformSkeletons(iterDir) {
|
|
7365
|
-
let skeleton = 0;
|
|
7366
|
-
let total = 0;
|
|
7367
|
-
try {
|
|
7368
|
-
const specDir = (0, path_1.join)(iterDir, '020-specs');
|
|
7369
|
-
if (!(await (0, fs_extra_1.pathExists)(specDir)))
|
|
7370
|
-
return { skeleton, total };
|
|
7371
|
-
const features = await (0, spec_paths_2.parseFeatureList)(iterDir);
|
|
7372
|
-
const platforms = await (0, spec_paths_2.parsePlatformList)();
|
|
7373
|
-
// v8.3.278+: 统计口径与骨架生成一致——只统计声明端(未声明单元全端兜底),
|
|
7374
|
-
// 避免把不涉及端的旧残留目录计入骨架提示
|
|
7375
|
-
const featurePlatforms = await (0, spec_skeleton_1.resolveFeaturePlatformMap)((0, path_1.join)(iterDir, '010-requirements', 'features'), features, platforms);
|
|
7376
|
-
for (const feature of features) {
|
|
7377
|
-
const declared = featurePlatforms.get(feature);
|
|
7378
|
-
const unitPlatforms = declared && declared.length > 0 ? declared : platforms;
|
|
7379
|
-
for (const platform of unitPlatforms) {
|
|
7380
|
-
const pDir = (0, path_1.join)(specDir, feature, platform);
|
|
7381
|
-
if (!(await (0, fs_extra_1.pathExists)(pDir)))
|
|
7382
|
-
continue;
|
|
7383
|
-
const entries = await (0, fs_extra_2.readdir)(pDir, { withFileTypes: true }).catch(() => []);
|
|
7384
|
-
for (const entry of entries) {
|
|
7385
|
-
if (!entry.name.endsWith('.md') || !entry.isFile())
|
|
7386
|
-
continue;
|
|
7387
|
-
total++;
|
|
7388
|
-
const content = (await (0, fs_extra_2.readFile)((0, path_1.join)(pDir, entry.name), 'utf-8').catch(() => '')).trim();
|
|
7389
|
-
if (!content || content.includes('SPEC-SKELETON') || content.length < 200)
|
|
7390
|
-
skeleton++;
|
|
7391
|
-
}
|
|
7392
|
-
}
|
|
7393
|
-
}
|
|
7394
|
-
}
|
|
7395
|
-
catch { /* 统计失败不阻断主流程 */ }
|
|
7396
|
-
return { skeleton, total };
|
|
7397
|
-
}
|
|
7398
|
-
/**
|
|
7399
|
-
* v8.3.234+: 生成并落盘「功能单元分析质量报告」——成功/失败清单 + 每单元产出物评分。
|
|
7400
|
-
* 落盘到 020-specs/QUALITY_AUDIT.md;无可评单元时返回 null。
|
|
7401
|
-
*/
|
|
7402
|
-
async function buildAndSaveQualityReport(iterDir, iter, units) {
|
|
7403
|
-
if (units.length === 0)
|
|
7404
|
-
return null;
|
|
7405
|
-
const { validateContentQuality } = await Promise.resolve().then(() => __importStar(require('../core/spec-skeleton')));
|
|
7406
|
-
const report = [];
|
|
7407
|
-
for (const u of units) {
|
|
7408
|
-
const specDir = getUnitSpecDir(iterDir, u);
|
|
7409
|
-
const rows = [];
|
|
7410
|
-
for (const f of ['REQUIREMENT.md', 'ANALYSIS.md', 'TECH.md']) {
|
|
7411
|
-
const fp = (0, path_1.join)(specDir, f);
|
|
7412
|
-
if (!(await (0, fs_extra_1.pathExists)(fp)))
|
|
7413
|
-
continue;
|
|
7414
|
-
const content = (await (0, fs_extra_2.readFile)(fp, 'utf-8')).trim();
|
|
7415
|
-
if (!content || content.includes('SPEC-SKELETON') || content.length < 200)
|
|
7416
|
-
continue;
|
|
7417
|
-
const q = await validateContentQuality(f, content, '');
|
|
7418
|
-
rows.push({ doc: f, score: q.score, issues: q.issues });
|
|
7419
|
-
}
|
|
7420
|
-
report.push({ unit: u.name, ok: rows.length > 0, rows });
|
|
7421
|
-
}
|
|
7422
|
-
const okCount = report.filter(r => r.ok).length;
|
|
7423
|
-
const failCount = report.length - okCount;
|
|
7424
|
-
const totalDocs = report.reduce((a, r) => a + r.rows.length, 0);
|
|
7425
|
-
const avgScore = totalDocs > 0 ? Math.round(report.reduce((a, r) => a + r.rows.reduce((b, row) => b + row.score, 0), 0) / totalDocs) : 0;
|
|
7426
|
-
const lines = [];
|
|
7427
|
-
lines.push('# 功能单元分析质量报告');
|
|
7428
|
-
lines.push('');
|
|
7429
|
-
lines.push(`> 生成时间: ${new Date().toISOString()}`);
|
|
7430
|
-
lines.push(`> 迭代: ${iter}`);
|
|
7431
|
-
lines.push(`> 结果: ${okCount} 成功 / ${failCount} 失败(共 ${units.length} 单元,评 ${totalDocs} 份文档,平均 ${avgScore} 分)`);
|
|
7432
|
-
lines.push('');
|
|
7433
|
-
lines.push('## 评分明细');
|
|
7434
|
-
lines.push('');
|
|
7435
|
-
lines.push('| 单元 | 状态 | 文档 | 评分 | 主要扣分项 |');
|
|
7436
|
-
lines.push('|:---|:---|:---|:---|:---|');
|
|
7437
|
-
for (const r of report) {
|
|
7438
|
-
if (r.ok) {
|
|
7439
|
-
for (const row of r.rows) {
|
|
7440
|
-
lines.push(`| ${r.unit} | ✅ | ${row.doc} | ${row.score} | ${row.issues.slice(0, 3).join(';') || '—'} |`);
|
|
7441
|
-
}
|
|
7442
|
-
}
|
|
7443
|
-
else {
|
|
7444
|
-
lines.push(`| ${r.unit} | ❌ 失败 | — | — | 缺少有效规格文档(REQUIREMENT/ANALYSIS/TECH 至少一份,内容 ≥200 字符且非骨架) |`);
|
|
7445
|
-
}
|
|
7446
|
-
}
|
|
7447
|
-
lines.push('');
|
|
7448
|
-
lines.push('## 失败单元修复');
|
|
7449
|
-
lines.push('');
|
|
7450
|
-
if (failCount === 0) {
|
|
7451
|
-
lines.push('无失败单元。');
|
|
7452
|
-
}
|
|
7453
|
-
else {
|
|
7454
|
-
lines.push('```bash');
|
|
7455
|
-
lines.push(`speccore analyze --resume-units -I ${iter}`);
|
|
7456
|
-
lines.push('```');
|
|
7457
|
-
lines.push('仍为骨架/空内容的单元会被重新纳入分析,循环直到干净。');
|
|
7458
|
-
}
|
|
7459
|
-
lines.push('');
|
|
7460
|
-
const specRoot = (0, path_1.join)(iterDir, '020-specs');
|
|
7461
|
-
await (0, fs_extra_1.ensureDir)(specRoot);
|
|
7462
|
-
const reportPath = (0, path_1.join)(specRoot, 'QUALITY_AUDIT.md');
|
|
7463
|
-
await (0, fs_extra_1.writeFile)(reportPath, lines.join('\n'));
|
|
7464
|
-
logger_1.logger.info(` 📊 质量报告已落盘: 020-specs/QUALITY_AUDIT.md(${okCount} 成功 / ${failCount} 失败,平均 ${avgScore} 分)`);
|
|
7465
|
-
return reportPath;
|
|
7466
|
-
}
|
|
7467
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
7468
|
-
// v8.3.177+: 总览生成辅助函数
|
|
7469
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
7470
|
-
/**
|
|
7471
|
-
* 构建总览生成 AI Prompt
|
|
7472
|
-
*/
|
|
7473
|
-
function buildOverviewPrompt(input, coverage, crossApis, phase) {
|
|
7474
|
-
const isSkeleton = phase === 'skeleton';
|
|
7475
|
-
let prompt = `# 任务: 生成迭代${isSkeleton ? '骨架' : '完整'}总览\n\n`;
|
|
7476
|
-
prompt += `## 输入数据(CLI 自动提取,${input.features.length} 个功能单元)\n\n`;
|
|
7477
|
-
prompt += `### 功能单元清单\n\n`;
|
|
7478
|
-
prompt += `| 功能单元 | 涉及端 | 依赖 | 摘要 |\n`;
|
|
7479
|
-
prompt += `| :--- | :--- | :--- | :--- |\n`;
|
|
7480
|
-
for (const f of input.features) {
|
|
7481
|
-
prompt += `| ${f.feature} | ${f.platforms.join(', ') || '-'} | ${f.dependencies.join(', ') || '无'} | ${f.summary.slice(0, 40)}${f.summary.length > 40 ? '...' : ''} |\n`;
|
|
7482
|
-
}
|
|
7483
|
-
prompt += `\n### 端覆盖统计\n\n`;
|
|
7484
|
-
prompt += `| 端 | 覆盖功能单元数 |\n`;
|
|
7485
|
-
prompt += `| :--- | :--- |\n`;
|
|
7486
|
-
for (const [platform, count] of Object.entries(coverage)) {
|
|
7487
|
-
prompt += `| ${platform} | ${count} |\n`;
|
|
7488
|
-
}
|
|
7489
|
-
if (crossApis.length > 0) {
|
|
7490
|
-
prompt += `\n### 跨功能单元接口\n\n`;
|
|
7491
|
-
prompt += `| 接口 | 被哪些功能引用 |\n`;
|
|
7492
|
-
prompt += `| :--- | :--- |\n`;
|
|
7493
|
-
for (const api of crossApis) {
|
|
7494
|
-
prompt += `| ${api.path} | ${api.usedBy.join(', ')} |\n`;
|
|
7495
|
-
}
|
|
7496
|
-
}
|
|
7497
|
-
if (isSkeleton) {
|
|
7498
|
-
prompt += `\n## 生成要求(骨架总览)\n\n`;
|
|
7499
|
-
prompt += `1. 生成 **功能地图**(Mermaid flowchart):展示功能单元间的依赖关系\n`;
|
|
7500
|
-
prompt += `2. 生成 **端覆盖总览表**:指出哪些端缺少功能覆盖\n`;
|
|
7501
|
-
prompt += `3. 生成 **功能单元清单**:确认所有功能单元已识别\n`;
|
|
7502
|
-
prompt += `4. 内容控制在 40-60 行\n`;
|
|
7503
|
-
prompt += `5. 使用 [DOC:OVERVIEW] 标记输出\n`;
|
|
7504
|
-
}
|
|
7505
|
-
else {
|
|
7506
|
-
prompt += `\n## 生成要求(完整总览)\n\n`;
|
|
7507
|
-
prompt += `1. 生成 **系统架构图**(Mermaid):服务拓扑 + 数据流\n`;
|
|
7508
|
-
prompt += `2. 生成 **跨功能单元接口清单**:标注哪些接口被多个功能引用\n`;
|
|
7509
|
-
prompt += `3. 生成 **实际依赖关系表**:基于接口调用关系\n`;
|
|
7510
|
-
prompt += `4. 内容控制在 60-80 行\n`;
|
|
7511
|
-
prompt += `5. 使用 [DOC:OVERVIEW] 和 [DOC:ARCHITECTURE] 标记输出\n`;
|
|
7512
|
-
}
|
|
7513
|
-
prompt += `\n## 输出格式\n\n`;
|
|
7514
|
-
prompt += `\`\`\`markdown\n`;
|
|
7515
|
-
prompt += `[DOC:OVERVIEW]\n`;
|
|
7516
|
-
prompt += `# 迭代总览(${isSkeleton ? '骨架' : '完整'})\n`;
|
|
7517
|
-
prompt += `...\n`;
|
|
7518
|
-
prompt += `\`\`\`\n`;
|
|
7519
|
-
return prompt;
|
|
7520
|
-
}
|
|
7521
|
-
/**
|
|
7522
|
-
* 写入总览文件(解析 AI 返回的 [DOC:xxx] 标记)
|
|
7523
|
-
*/
|
|
7524
|
-
async function writeOverviewFiles(iterDir, applyContent, phase) {
|
|
7525
|
-
const specDir = (0, path_1.join)(iterDir, '020-specs');
|
|
7526
|
-
const overviewDir = (0, path_1.join)(specDir, 'overview');
|
|
7527
|
-
await (0, fs_extra_1.ensureDir)(overviewDir);
|
|
7528
|
-
// 解析 [DOC:OVERVIEW] 和 [DOC:ARCHITECTURE] 标记
|
|
7529
|
-
const overviewMatch = applyContent.match(/\[DOC:OVERVIEW\]([\s\S]*?)(?=\[DOC:|$)/);
|
|
7530
|
-
const archMatch = applyContent.match(/\[DOC:ARCHITECTURE\]([\s\S]*?)(?=\[DOC:|$)/);
|
|
7531
|
-
if (overviewMatch) {
|
|
7532
|
-
const content = overviewMatch[1].trim();
|
|
7533
|
-
const fileName = phase === 'skeleton' ? 'REQUIREMENT.md' : 'REQUIREMENT.md';
|
|
7534
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(overviewDir, fileName), content);
|
|
7535
|
-
logger_1.logger.info(` 📝 已写入: overview/${fileName}`);
|
|
7536
|
-
}
|
|
7537
|
-
if (archMatch) {
|
|
7538
|
-
const content = archMatch[1].trim();
|
|
7539
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(overviewDir, 'ARCHITECTURE.md'), content);
|
|
7540
|
-
logger_1.logger.info(` 📝 已写入: overview/ARCHITECTURE.md`);
|
|
7541
|
-
}
|
|
7542
|
-
// 如果没有标记,直接写入 REQUIREMENT.md
|
|
7543
|
-
if (!overviewMatch && !archMatch) {
|
|
7544
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(overviewDir, 'REQUIREMENT.md'), applyContent.trim());
|
|
7545
|
-
logger_1.logger.info(` 📝 已写入: overview/REQUIREMENT.md`);
|
|
7546
|
-
}
|
|
7547
|
-
}
|
|
6227
|
+
// v8.3.359+: 调度循环(D 区)/ 质量报告(E 区)已搬迁到 ./analyze/units
|
|
7548
6228
|
//# sourceMappingURL=analyze.js.map
|