speccore 8.3.263 → 8.3.267
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/dist/commands/analyze.d.ts.map +1 -1
- package/dist/commands/analyze.js +229 -26
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/doc2spec.d.ts.map +1 -1
- package/dist/commands/doc2spec.js +28 -5
- package/dist/commands/doc2spec.js.map +1 -1
- package/dist/commands/execute.js +14 -4
- package/dist/commands/execute.js.map +1 -1
- package/dist/commands/iteration/taskify.js +7 -0
- package/dist/commands/iteration/taskify.js.map +1 -1
- package/dist/core/agent-adapter.d.ts +8 -0
- package/dist/core/agent-adapter.d.ts.map +1 -1
- package/dist/core/agent-adapter.js +49 -5
- package/dist/core/agent-adapter.js.map +1 -1
- package/dist/core/ask-host-ai.d.ts.map +1 -1
- package/dist/core/ask-host-ai.js +4 -2
- package/dist/core/ask-host-ai.js.map +1 -1
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +3 -2
- package/dist/core/prompt-builder.js.map +1 -1
- package/dist/core/requirement-clarifier.d.ts.map +1 -1
- package/dist/core/requirement-clarifier.js +4 -0
- package/dist/core/requirement-clarifier.js.map +1 -1
- package/dist/core/spec-paths.d.ts.map +1 -1
- package/dist/core/spec-paths.js +5 -0
- package/dist/core/spec-paths.js.map +1 -1
- package/dist/core/unit-context-assembler.d.ts.map +1 -1
- package/dist/core/unit-context-assembler.js +107 -4
- package/dist/core/unit-context-assembler.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"analyze.d.ts","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/analyze.js
CHANGED
|
@@ -83,6 +83,13 @@ const dispatch_loop_1 = require("../core/dispatch-loop");
|
|
|
83
83
|
const pipeline_engine_1 = require("../core/pipeline-engine");
|
|
84
84
|
const change_detection_1 = require("../core/change-detection");
|
|
85
85
|
const ai_trace_1 = require("../utils/ai-trace");
|
|
86
|
+
// v8.3.264: AI 交棒统一出口——所有 [SPECCORE_PROMPT] 输出必留 prompt-out 轨迹,
|
|
87
|
+
// 与 ai-trace 飞行记录仪的 apply-in/apply-done/apply-fail 配对,形成「发起→回传→落盘」闭环证据链。
|
|
88
|
+
// 此前 14 处输出点仅 2 处埋点,澄清链等 12 处交棒全程无痕(ai-trace/ 目录空白即因此)。
|
|
89
|
+
async function emitAiPrompt(iter, prompt, meta = {}) {
|
|
90
|
+
await (0, ai_trace_1.recordAiTrace)(iter, 'prompt-out', { promptLength: prompt.length, ...meta, prompt });
|
|
91
|
+
process.stdout.write(`[SPECCORE_PROMPT]\n${prompt}`);
|
|
92
|
+
}
|
|
86
93
|
const streaming_analyzer_1 = require("../core/streaming-analyzer");
|
|
87
94
|
const requirement_clarifier_1 = require("../core/requirement-clarifier");
|
|
88
95
|
const agents_1 = require("../core/agents");
|
|
@@ -405,7 +412,7 @@ async function analyzeCommand(options) {
|
|
|
405
412
|
prompt += `3. 修复后重新运行 \`speccore analyze -I ${iter}\` 重新审计\n`;
|
|
406
413
|
prompt += `4. 最多修复 2 轮,超过 2 轮仍有问题则标记为“需人工确认”\n`;
|
|
407
414
|
prompt += `5. 写入: speccore analyze --apply '{...}' -I ${iter}\n`;
|
|
408
|
-
|
|
415
|
+
await emitAiPrompt(iter, prompt, { mode: 'audit-fix' });
|
|
409
416
|
process.exitCode = 10;
|
|
410
417
|
return;
|
|
411
418
|
}
|
|
@@ -428,7 +435,7 @@ async function analyzeCommand(options) {
|
|
|
428
435
|
if (options.estimateOnly) {
|
|
429
436
|
// 只输出预估报告到 prompt
|
|
430
437
|
const prompt = buildContextGuardPrompt(estimate, plan);
|
|
431
|
-
|
|
438
|
+
await emitAiPrompt(iter, prompt, { mode: 'context-guard' });
|
|
432
439
|
process.exitCode = 10;
|
|
433
440
|
return;
|
|
434
441
|
}
|
|
@@ -458,7 +465,7 @@ async function analyzeCommand(options) {
|
|
|
458
465
|
logger_1.logger.info('');
|
|
459
466
|
if (options.prompt) {
|
|
460
467
|
const prompt = buildIncrementalPrompt(iterDir, analysis, iter);
|
|
461
|
-
|
|
468
|
+
await emitAiPrompt(iter, prompt, { mode: 'incremental' });
|
|
462
469
|
process.exitCode = 10;
|
|
463
470
|
return;
|
|
464
471
|
}
|
|
@@ -505,7 +512,7 @@ async function analyzeCommand(options) {
|
|
|
505
512
|
logger_1.logger.info('');
|
|
506
513
|
if (options.prompt) {
|
|
507
514
|
const prompt = buildNewPlatformPrompt(iterDir, analysis, iter);
|
|
508
|
-
|
|
515
|
+
await emitAiPrompt(iter, prompt, { mode: 'new-platform' });
|
|
509
516
|
process.exitCode = 10;
|
|
510
517
|
return;
|
|
511
518
|
}
|
|
@@ -556,7 +563,7 @@ async function analyzeCommand(options) {
|
|
|
556
563
|
logger_1.logger.info('');
|
|
557
564
|
if (options.prompt) {
|
|
558
565
|
const prompt = buildModuleAnalysisPrompt(iterDir, result, iter);
|
|
559
|
-
|
|
566
|
+
await emitAiPrompt(iter, prompt, { mode: 'module-analysis' });
|
|
560
567
|
process.exitCode = 10;
|
|
561
568
|
return;
|
|
562
569
|
}
|
|
@@ -670,7 +677,7 @@ async function analyzeCommand(options) {
|
|
|
670
677
|
}
|
|
671
678
|
// ── prompt 模式:生成 AI Prompt ──
|
|
672
679
|
const prompt = buildOverviewPrompt(overviewInput, coverage, crossApis, phase);
|
|
673
|
-
|
|
680
|
+
await emitAiPrompt(iter, prompt, { mode: 'overview' });
|
|
674
681
|
process.exitCode = 10;
|
|
675
682
|
return;
|
|
676
683
|
}
|
|
@@ -707,6 +714,25 @@ async function analyzeCommand(options) {
|
|
|
707
714
|
return;
|
|
708
715
|
}
|
|
709
716
|
}
|
|
717
|
+
else if (options.autoUnits || options.resumeUnits) {
|
|
718
|
+
// v8.3.266+: 缓存与磁盘 features/ 权威源对照——旧缓存可能含 unitize 重切前的假单元
|
|
719
|
+
// (实测会议项目 23 个缓存单元中 6 个在 features/ 不存在),调度循环会给假单元生成
|
|
720
|
+
// Prompt 写到 features 锚点之外的目录,白写且 taskify/Phase 2 均不读。
|
|
721
|
+
const diskUnits = await extractIterationUnits(iterDir);
|
|
722
|
+
const cacheNames = new Set(units.map((u) => u.name));
|
|
723
|
+
const diskNames = new Set(diskUnits.map((u) => u.name));
|
|
724
|
+
const mismatched = diskUnits.length > 0
|
|
725
|
+
&& (diskUnits.length !== units.length || [...diskNames].some((n) => !cacheNames.has(n)));
|
|
726
|
+
if (mismatched) {
|
|
727
|
+
units = diskUnits;
|
|
728
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.dirname)(unitsCachePath));
|
|
729
|
+
await (0, fs_extra_1.writeFile)(unitsCachePath, JSON.stringify(units, null, 2));
|
|
730
|
+
logger_1.logger.warn(`⚠️ 单元清单与磁盘 features/ 目录不一致,已刷新: ${units.length} 个`);
|
|
731
|
+
for (const u of units) {
|
|
732
|
+
logger_1.logger.info(` ${u.id}: ${u.name} (${u.source}#L${u.sourceRange.startLine})`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
710
736
|
// Step 2: 汇总模式(--consolidate)
|
|
711
737
|
if (options.consolidate) {
|
|
712
738
|
const analyses = await loadUnitAnalyses(iter);
|
|
@@ -717,7 +743,7 @@ async function analyzeCommand(options) {
|
|
|
717
743
|
}
|
|
718
744
|
const report = await consolidateUnitAnalyses(iter, units, analyses);
|
|
719
745
|
if (options.prompt) {
|
|
720
|
-
|
|
746
|
+
await emitAiPrompt(iter, `${report}\n\n## 操作选项\n\n**选项 A:确认通过,写入所有文档**\n\`\`\`bash\nspeccore analyze --apply '[DOC:...]' -I ${iter}\n\`\`\`\n`, { mode: 'consolidate-report' });
|
|
721
747
|
process.exitCode = 10;
|
|
722
748
|
return;
|
|
723
749
|
}
|
|
@@ -764,14 +790,33 @@ async function analyzeCommand(options) {
|
|
|
764
790
|
// CLI 每轮推进状态机:校验产出 → 已就绪自动标记并推进,未就绪输出该单元 prompt(exitCode 10)
|
|
765
791
|
// → 宿主 AI 落盘 → 再次调用本命令 → CLI 校验通过自动推进下一个单元,直到全部完成
|
|
766
792
|
if (options.autoUnits || options.resumeUnits) {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
793
|
+
// v8.3.266: Qoder 环境非 TTY 下优先 SDK 派单——CLI 进程内直接调起子 Agent
|
|
794
|
+
// (独立上下文、acceptEdits 直接写文件),校验通过后自动推进下一单元,宿主全程
|
|
795
|
+
// 不参与;SDK 不可用/失败时回退标记模式(exitCode 10 宿主接力)。
|
|
796
|
+
// 收敛性:每轮单元状态必然前进(done/failed)或 SDK 失败退出,不会死循环。
|
|
797
|
+
const sdkSupported = !process.stdout.isTTY && (0, agent_adapter_1.isSdkDispatchSupported)();
|
|
798
|
+
const maxDispatchRounds = units.length * 3 + 5; // 防御性上限(每单元最多 3 次尝试 + 余量)
|
|
799
|
+
let dispatchCount = 0;
|
|
800
|
+
while (true) {
|
|
801
|
+
const loopResult = await runUnitDispatchLoop(iter, iterDir, units);
|
|
802
|
+
if (loopResult.finished) {
|
|
803
|
+
await printUnitLoopSummary(iter, iterDir, units, loopResult);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
const fallbackPrompt = `${loopResult.prompt}\n\n## 完成后继续\n\n落盘后运行以下命令,CLI 将校验产出并自动推进到下一个单元:\n\n\`\`\`bash\nspeccore analyze --resume-units -I ${iter}\n\`\`\`\n`;
|
|
807
|
+
if (!sdkSupported || !(await trySdkDispatchUnit(loopResult.prompt, iter, loopResult.step.name))) {
|
|
808
|
+
// 回退标记模式(非 Qoder 宿主 / TTY / SDK 失败均走此路径)
|
|
809
|
+
await emitAiPrompt(iter, fallbackPrompt, { mode: 'resume-units-loop' });
|
|
810
|
+
process.exitCode = 10;
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (++dispatchCount >= maxDispatchRounds) {
|
|
814
|
+
logger_1.logger.warn(`⚠️ SDK 派单轮次超限(${maxDispatchRounds}),退出调度循环`);
|
|
815
|
+
process.exitCode = 10;
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
771
818
|
}
|
|
772
|
-
|
|
773
|
-
process.exitCode = 10;
|
|
774
|
-
return;
|
|
819
|
+
// 理论不可达:while(true) 内所有路径均 return
|
|
775
820
|
}
|
|
776
821
|
// 3c. --unit: 指定单元(支持逗号分隔多个,如 M-01,M-03)
|
|
777
822
|
if (options.unit) {
|
|
@@ -806,8 +851,7 @@ async function analyzeCommand(options) {
|
|
|
806
851
|
const targetUnit = targetUnits[0];
|
|
807
852
|
if (options.prompt) {
|
|
808
853
|
const prompt = await buildUnitAnalysisPrompt(iterDir, iter, targetUnit, units);
|
|
809
|
-
await (
|
|
810
|
-
process.stdout.write(`[SPECCORE_PROMPT]\n${prompt}`);
|
|
854
|
+
await emitAiPrompt(iter, prompt, { mode: 'unit', unit: targetUnit.id });
|
|
811
855
|
process.exitCode = 10;
|
|
812
856
|
return;
|
|
813
857
|
}
|
|
@@ -1464,7 +1508,7 @@ async function analyzeCommand(options) {
|
|
|
1464
1508
|
process.stdout.write(`[SPECCORE_CONTEXT_BUDGET: 8000]\n`);
|
|
1465
1509
|
process.stdout.write(`[SPECCORE_CONTEXT_TYPE: platform-only]\n\n`);
|
|
1466
1510
|
}
|
|
1467
|
-
|
|
1511
|
+
await emitAiPrompt(iter, finalPrompt, { mode: 'subagent-final' });
|
|
1468
1512
|
process.exitCode = 10;
|
|
1469
1513
|
return;
|
|
1470
1514
|
}
|
|
@@ -1494,7 +1538,7 @@ async function analyzeCommand(options) {
|
|
|
1494
1538
|
if (shouldClarify) {
|
|
1495
1539
|
// 需要澄清,先执行 clarify(内部已生成质量报告)
|
|
1496
1540
|
const clarifyPrompt = await buildClarifyPhasePrompt(iter);
|
|
1497
|
-
|
|
1541
|
+
await emitAiPrompt(iter, clarifyPrompt, { mode: 'clarify-phase' });
|
|
1498
1542
|
process.exitCode = 10;
|
|
1499
1543
|
return;
|
|
1500
1544
|
}
|
|
@@ -1508,7 +1552,7 @@ async function analyzeCommand(options) {
|
|
|
1508
1552
|
analyzeOptions: options,
|
|
1509
1553
|
});
|
|
1510
1554
|
const prompt = (0, prompt_builder_1.buildPromptText)(promptObj);
|
|
1511
|
-
|
|
1555
|
+
await emitAiPrompt(iter, prompt, { mode: 'analyze-host' });
|
|
1512
1556
|
process.exitCode = 10;
|
|
1513
1557
|
return;
|
|
1514
1558
|
}
|
|
@@ -1759,6 +1803,8 @@ async function analyzeCommand(options) {
|
|
|
1759
1803
|
const clarifyBlocks = parseClarifyMarkers(options.apply);
|
|
1760
1804
|
if (clarifyBlocks.size > 0 && !isGlobalScope) {
|
|
1761
1805
|
logger_1.logger.info(`📋 检测到 ${clarifyBlocks.size} 个澄清文档,先写入...`);
|
|
1806
|
+
// v8.3.267: 提前取真实功能单元名集合(只取一次),供幽灵路由判定
|
|
1807
|
+
const validFeatures = new Set(iterDir ? await (0, spec_paths_2.parseFeatureList)(iterDir) : []);
|
|
1762
1808
|
for (const [filename, content] of clarifyBlocks) {
|
|
1763
1809
|
const { parseClarifiedRequirement, buildClarifiedHeader } = await Promise.resolve().then(() => __importStar(require('../core/requirement-clarifier')));
|
|
1764
1810
|
const { content: cleaned } = parseClarifiedRequirement(content);
|
|
@@ -1767,7 +1813,25 @@ async function analyzeCommand(options) {
|
|
|
1767
1813
|
// 解析写入路径:支持新格式(overview/、{feature}/overview/)和旧格式(requirements/)
|
|
1768
1814
|
let fp;
|
|
1769
1815
|
if (filename.includes('/') || filename.includes('\\')) {
|
|
1770
|
-
|
|
1816
|
+
// v8.3.267: 幽灵路由归一化——AI 常把「文档 slug」当 feature-name 生成
|
|
1817
|
+
// [CLARIFY:overview/overview/CLARIFY.md]、[CLARIFY:admin-web/overview/CLARIFY.md]
|
|
1818
|
+
// 等标记,原样落地会产出 overview/overview/、admin-web/ 等幽灵目录。
|
|
1819
|
+
// 首段不是真实功能单元时:
|
|
1820
|
+
// 「overview」→ 全局澄清 020-specs/overview/CLARIFY.md
|
|
1821
|
+
// 其余文档 slug → 文档级黄金需求 020-specs/requirements/{slug}-clarified.md
|
|
1822
|
+
const seg = filename.split(/[\\/]/)[0];
|
|
1823
|
+
const isUnitOverview = /^[^\\/]+\/overview\/CLARIFY\.md$/i.test(filename);
|
|
1824
|
+
if (isUnitOverview && seg === 'overview') {
|
|
1825
|
+
fp = (0, path_1.join)(iterDir, '020-specs', 'overview', 'CLARIFY.md');
|
|
1826
|
+
logger_1.logger.warn(` ↪ ${filename} 首段「overview」为全局澄清,已归一化到 020-specs/overview/CLARIFY.md`);
|
|
1827
|
+
}
|
|
1828
|
+
else if (isUnitOverview && !validFeatures.has(seg)) {
|
|
1829
|
+
fp = (0, path_1.join)(iterDir, '020-specs', 'requirements', `${seg}-clarified.md`);
|
|
1830
|
+
logger_1.logger.warn(` ↪ ${filename} 首段「${seg}」非功能单元,已路由到 requirements/${seg}-clarified.md(文档级黄金需求)`);
|
|
1831
|
+
}
|
|
1832
|
+
else {
|
|
1833
|
+
fp = (0, path_1.join)(iterDir, '020-specs', filename);
|
|
1834
|
+
}
|
|
1771
1835
|
}
|
|
1772
1836
|
else {
|
|
1773
1837
|
// 兼容旧格式:无路径前缀的文件名写入 requirements/
|
|
@@ -1939,7 +2003,7 @@ async function analyzeCommand(options) {
|
|
|
1939
2003
|
logger_1.logger.info(` 1. 读取上述问题文档的当前内容`);
|
|
1940
2004
|
logger_1.logger.info(` 2. 根据质量报告中的错误信息补充/修正内容`);
|
|
1941
2005
|
logger_1.logger.info(` 3. 用 speccore global analyze --apply '{"文件路径":"修正后内容"}' 重新写入`);
|
|
1942
|
-
|
|
2006
|
+
await emitAiPrompt(options.iteration || 'GLOBAL', `\n# 质量门禁修复任务\n\n以下全局分析文档未通过质量校验,请修复后重新写入:\n\n`, { mode: 'global-gate-fix' });
|
|
1943
2007
|
for (const r of criticalErrors.slice(0, 5)) {
|
|
1944
2008
|
const fname = r.file.split('/').slice(-2).join('/');
|
|
1945
2009
|
const errs = r.issues.filter(i => i.severity === 'error').map(i => `- ${i.message}`).join('\n');
|
|
@@ -2656,8 +2720,7 @@ async function analyzeCommand(options) {
|
|
|
2656
2720
|
catch { /* ignore */ }
|
|
2657
2721
|
}
|
|
2658
2722
|
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);
|
|
2659
|
-
await (
|
|
2660
|
-
process.stdout.write(`[SPECCORE_PROMPT]\n${prompt}`);
|
|
2723
|
+
await emitAiPrompt(iter, prompt, { mode: 'analyze-main', scope: options.scope });
|
|
2661
2724
|
// v7.2.0+: 全局分析完成后输出下一步引导
|
|
2662
2725
|
if (options.scope === 'global') {
|
|
2663
2726
|
const guide = await buildGlobalAnalysisGuide(options);
|
|
@@ -6354,12 +6417,26 @@ async function extractIterationUnits(iterDir) {
|
|
|
6354
6417
|
const featuresDir = (0, path_1.join)(reqDir, 'features');
|
|
6355
6418
|
if (await (0, fs_extra_1.pathExists)(featuresDir)) {
|
|
6356
6419
|
const entries = await (0, fs_extra_2.readdir)(featuresDir, { withFileTypes: true });
|
|
6357
|
-
|
|
6420
|
+
// v8.3.267: 过滤 overview- 前缀的全局章节目录(unitize 误切产物,
|
|
6421
|
+
// 如「核心业务规则跨端通用」),防止假单元混入调度循环
|
|
6422
|
+
const dirs = entries.filter(e => e.isDirectory() && !e.name.startsWith('.') && !e.name.startsWith('_') && !e.name.startsWith('overview-'));
|
|
6358
6423
|
for (const dir of dirs) {
|
|
6359
6424
|
const readmePath = await (0, feature_source_1.resolveUnitSourcePath)((0, path_1.join)(featuresDir, dir.name));
|
|
6360
6425
|
let content = '';
|
|
6361
6426
|
if (await (0, fs_extra_1.pathExists)(readmePath)) {
|
|
6362
|
-
|
|
6427
|
+
// v8.3.267: 图片引用可能落在 500 字符截断之外,显式保留引用行,
|
|
6428
|
+
// 保证单元上下文的图片清单完整(AI 可据此 Read 原型图)
|
|
6429
|
+
const full = await (0, fs_extra_2.readFile)(readmePath, 'utf-8');
|
|
6430
|
+
content = full.slice(0, 500);
|
|
6431
|
+
const imgRefs = new Set();
|
|
6432
|
+
const imgRe = /!\[[^\]]*\]\(([^)\s]+)\)/g;
|
|
6433
|
+
let m;
|
|
6434
|
+
while ((m = imgRe.exec(full)) !== null)
|
|
6435
|
+
imgRefs.add(m[1]);
|
|
6436
|
+
const outside = [...imgRefs].filter(r => !content.includes(r));
|
|
6437
|
+
if (outside.length > 0) {
|
|
6438
|
+
content += `\n\n<!-- 原文图片引用(截断外保留)-->\n${outside.map(r => ``).join('\n')}`;
|
|
6439
|
+
}
|
|
6363
6440
|
}
|
|
6364
6441
|
units.push({
|
|
6365
6442
|
id: `M-${String(idCounter++).padStart(2, '0')}`,
|
|
@@ -6465,7 +6542,9 @@ async function loadUnitAnalyses(iteration) {
|
|
|
6465
6542
|
async function detectPendingUnits(iteration, allUnits) {
|
|
6466
6543
|
const completed = await loadUnitAnalyses(iteration);
|
|
6467
6544
|
const completedIds = new Set(completed.map(a => a.id));
|
|
6468
|
-
|
|
6545
|
+
// v8.3.266+: 用绝对路径迭代目录替代相对路径拼接——cwd 不在迭代父目录时旧写法会指向错误路径,
|
|
6546
|
+
// 导致 unitHasSkeletonOutput 兜底检查全部误判为骨架(路径不存在)
|
|
6547
|
+
const iterDir = await (0, context_1.getIterationDir)(iteration);
|
|
6469
6548
|
const pending = [];
|
|
6470
6549
|
for (const u of allUnits) {
|
|
6471
6550
|
if (!completedIds.has(u.id)) {
|
|
@@ -6859,6 +6938,7 @@ async function buildUnitLoopPrompt(iterDir, iter, unit, allUnits, attempt, lastE
|
|
|
6859
6938
|
约束:
|
|
6860
6939
|
- 每个文件内容不少于 200 字符,禁止输出空模板或骨架占位
|
|
6861
6940
|
- 写入前先创建目录;写完后回复一行:\`DONE ${unit.name}\`
|
|
6941
|
+
- 若上下文中含「需求图片/原型」或 \`\` 图片引用,请先用 Read 工具打开图片文件查看,再结合图片信息撰写规格;不要忽略图片提示
|
|
6862
6942
|
`;
|
|
6863
6943
|
if (attempt > 1 && lastError) {
|
|
6864
6944
|
extra += `
|
|
@@ -6871,6 +6951,89 @@ async function buildUnitLoopPrompt(iterDir, iter, unit, allUnits, attempt, lastE
|
|
|
6871
6951
|
}
|
|
6872
6952
|
return base + extra;
|
|
6873
6953
|
}
|
|
6954
|
+
/**
|
|
6955
|
+
* v8.3.266: 调度循环 SDK 派单——Qoder 环境非 TTY 下由 CLI 直接调起子 Agent
|
|
6956
|
+
* (独立上下文、acceptEdits 直接写文件),返回 true 表示派单成功(含自动落盘)。
|
|
6957
|
+
*/
|
|
6958
|
+
async function trySdkDispatchUnit(prompt, iter, unitName) {
|
|
6959
|
+
try {
|
|
6960
|
+
const agentCtx = {
|
|
6961
|
+
subagent: 'requirement-analyzer',
|
|
6962
|
+
iteration: iter,
|
|
6963
|
+
contextBudget: 6000,
|
|
6964
|
+
contextType: 'incremental',
|
|
6965
|
+
cwd: (0, task_utils_1.findProjectRoot)() || process.cwd(),
|
|
6966
|
+
};
|
|
6967
|
+
const dispatchResult = await (0, agent_adapter_1.dispatchSubagent)(agentCtx, prompt);
|
|
6968
|
+
if (!dispatchResult || !dispatchResult.success) {
|
|
6969
|
+
logger_1.logger.debug('[analyze] SDK 派单未成功,回退标记模式');
|
|
6970
|
+
return false;
|
|
6971
|
+
}
|
|
6972
|
+
// v8.3.266: 防御空转——success 但既没写文件也没返回可落盘内容,视为无效派单,
|
|
6973
|
+
// 立即回退标记模式(否则循环会白白耗尽该单元 3 次尝试并误标 failed)
|
|
6974
|
+
if (!dispatchResult.content && (!dispatchResult.filesChanged || dispatchResult.filesChanged.length === 0)) {
|
|
6975
|
+
logger_1.logger.warn('[analyze] SDK 派单无产出(未写文件且无回传内容),回退标记模式');
|
|
6976
|
+
return false;
|
|
6977
|
+
}
|
|
6978
|
+
logger_1.logger.info(`[analyze] 调度循环子 Agent SDK 派单成功: ${unitName}`);
|
|
6979
|
+
// v8.3.196+ 自动落盘:子 Agent 未直接写文件时,解析其返回的 JSON/[DOC:] 内容
|
|
6980
|
+
// 并路由到该单元的 overview/ 目录(applyDocContent 无单元路由,此处专用落盘)
|
|
6981
|
+
if (dispatchResult.content) {
|
|
6982
|
+
const autoApplied = await applyUnitLoopDocs(dispatchResult.content, iter, unitName);
|
|
6983
|
+
if (autoApplied > 0) {
|
|
6984
|
+
process.stdout.write(`✅ 已自动写入 ${autoApplied} 个文档到 020-specs/${sanitizeUnitDirName(unitName)}/overview/\n`);
|
|
6985
|
+
}
|
|
6986
|
+
}
|
|
6987
|
+
if (dispatchResult.filesChanged && dispatchResult.filesChanged.length > 0) {
|
|
6988
|
+
process.stdout.write(`📄 文件变更: ${dispatchResult.filesChanged.join(', ')}\n`);
|
|
6989
|
+
}
|
|
6990
|
+
return true;
|
|
6991
|
+
}
|
|
6992
|
+
catch (e) {
|
|
6993
|
+
logger_1.logger.debug(`[analyze] SDK 派单异常: ${e.message},回退标记模式`);
|
|
6994
|
+
return false;
|
|
6995
|
+
}
|
|
6996
|
+
}
|
|
6997
|
+
/**
|
|
6998
|
+
* v8.3.266: 把子 Agent 回传的 JSON/[DOC:] 内容写入指定单元的 overview/ 目录。
|
|
6999
|
+
* 文档名归一化:analyze prompt 用 [DOC:REQ.md],落盘用 REQUIREMENT.md(validateUnitSpec 校验名单)。
|
|
7000
|
+
*/
|
|
7001
|
+
async function applyUnitLoopDocs(content, iter, unitName) {
|
|
7002
|
+
const trimmed = content.trim();
|
|
7003
|
+
if (!trimmed)
|
|
7004
|
+
return 0;
|
|
7005
|
+
const iterDir = await (0, context_1.getIterationDir)(iter);
|
|
7006
|
+
const unitDir = (0, path_1.join)(iterDir, '020-specs', sanitizeUnitDirName(unitName), 'overview');
|
|
7007
|
+
let docs = {};
|
|
7008
|
+
if (trimmed.startsWith('{')) {
|
|
7009
|
+
try {
|
|
7010
|
+
const parsed = JSON.parse(trimmed);
|
|
7011
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
7012
|
+
docs = parsed;
|
|
7013
|
+
}
|
|
7014
|
+
catch { /* 回退 [DOC:] 标记 */ }
|
|
7015
|
+
}
|
|
7016
|
+
if (Object.keys(docs).length === 0) {
|
|
7017
|
+
for (const [name, c] of parseDocMarkers(trimmed))
|
|
7018
|
+
docs[name] = c;
|
|
7019
|
+
}
|
|
7020
|
+
if (Object.keys(docs).length === 0)
|
|
7021
|
+
return 0;
|
|
7022
|
+
const NAME_MAP = { 'REQ.md': 'REQUIREMENT.md' };
|
|
7023
|
+
let applied = 0;
|
|
7024
|
+
for (const [filename, docContent] of Object.entries(docs)) {
|
|
7025
|
+
if (typeof docContent !== 'string' || !docContent.trim())
|
|
7026
|
+
continue;
|
|
7027
|
+
const baseName = (filename.split('/').pop() || filename).trim();
|
|
7028
|
+
const normalized = NAME_MAP[baseName] || baseName;
|
|
7029
|
+
if (!normalized.endsWith('.md') || normalized.startsWith('.'))
|
|
7030
|
+
continue;
|
|
7031
|
+
await (0, fs_extra_1.ensureDir)(unitDir);
|
|
7032
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(unitDir, normalized), docContent);
|
|
7033
|
+
applied++;
|
|
7034
|
+
}
|
|
7035
|
+
return applied;
|
|
7036
|
+
}
|
|
6874
7037
|
/** 单元分析调度循环:构建步骤清单并推进一轮 */
|
|
6875
7038
|
async function runUnitDispatchLoop(iter, iterDir, units) {
|
|
6876
7039
|
const steps = units.map((u) => ({
|
|
@@ -6900,9 +7063,49 @@ async function printUnitLoopSummary(iter, iterDir, units, result) {
|
|
|
6900
7063
|
logger_1.logger.success(`🎉 所有 ${s.done.length} 个功能单元分析完成!`);
|
|
6901
7064
|
}
|
|
6902
7065
|
logger_1.logger.info(` 下一步: 汇总统一报告 speccore analyze --prompt -I ${iter} --consolidate`);
|
|
7066
|
+
// v8.3.266+: 循环只保证 overview/ 3 份,端级文档({feature}/{端}/)由 Phase 2 负责——
|
|
7067
|
+
// 此前完成提示不提 Phase 2,用户以为分析已闭环,taskify 时端级内容仍是骨架
|
|
7068
|
+
const platformSk = await countPlatformSkeletons(iterDir);
|
|
7069
|
+
if (platformSk.total > 0 && platformSk.skeleton > 0) {
|
|
7070
|
+
logger_1.logger.warn(`⚠️ 端级文档仍有 ${platformSk.skeleton}/${platformSk.total} 份为骨架(overview/ 之外)`);
|
|
7071
|
+
logger_1.logger.info(` Phase 2 生成各端专属文档: speccore analyze --prompt -I ${iter} --phase 2`);
|
|
7072
|
+
}
|
|
6903
7073
|
// v8.3.234+: 执行完成质量报告(成功/失败清单 + 产出物评分)落盘 QUALITY_AUDIT.md
|
|
6904
7074
|
await buildAndSaveQualityReport(iterDir, iter, units);
|
|
6905
7075
|
}
|
|
7076
|
+
/**
|
|
7077
|
+
* v8.3.266+: 统计 020-specs/{feature}/{端}/ 下仍为骨架的 .md 文件数。
|
|
7078
|
+
* 用于调度循环完成后引导 Phase 2(调度循环只保证 overview/ 3 份落盘)。
|
|
7079
|
+
*/
|
|
7080
|
+
async function countPlatformSkeletons(iterDir) {
|
|
7081
|
+
let skeleton = 0;
|
|
7082
|
+
let total = 0;
|
|
7083
|
+
try {
|
|
7084
|
+
const specDir = (0, path_1.join)(iterDir, '020-specs');
|
|
7085
|
+
if (!(await (0, fs_extra_1.pathExists)(specDir)))
|
|
7086
|
+
return { skeleton, total };
|
|
7087
|
+
const features = await (0, spec_paths_2.parseFeatureList)(iterDir);
|
|
7088
|
+
const platforms = await (0, spec_paths_2.parsePlatformList)();
|
|
7089
|
+
for (const feature of features) {
|
|
7090
|
+
for (const platform of platforms) {
|
|
7091
|
+
const pDir = (0, path_1.join)(specDir, feature, platform);
|
|
7092
|
+
if (!(await (0, fs_extra_1.pathExists)(pDir)))
|
|
7093
|
+
continue;
|
|
7094
|
+
const entries = await (0, fs_extra_2.readdir)(pDir, { withFileTypes: true }).catch(() => []);
|
|
7095
|
+
for (const entry of entries) {
|
|
7096
|
+
if (!entry.name.endsWith('.md') || !entry.isFile())
|
|
7097
|
+
continue;
|
|
7098
|
+
total++;
|
|
7099
|
+
const content = (await (0, fs_extra_2.readFile)((0, path_1.join)(pDir, entry.name), 'utf-8').catch(() => '')).trim();
|
|
7100
|
+
if (!content || content.includes('SPEC-SKELETON') || content.length < 200)
|
|
7101
|
+
skeleton++;
|
|
7102
|
+
}
|
|
7103
|
+
}
|
|
7104
|
+
}
|
|
7105
|
+
}
|
|
7106
|
+
catch { /* 统计失败不阻断主流程 */ }
|
|
7107
|
+
return { skeleton, total };
|
|
7108
|
+
}
|
|
6906
7109
|
/**
|
|
6907
7110
|
* v8.3.234+: 生成并落盘「功能单元分析质量报告」——成功/失败清单 + 每单元产出物评分。
|
|
6908
7111
|
* 落盘到 020-specs/QUALITY_AUDIT.md;无可评单元时返回 null。
|