speccore 8.3.162 → 8.3.164
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 +8 -0
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +102 -14
- package/dist/commands/iteration/split.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"AAwNA,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AA+ID,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"split.d.ts","sourceRoot":"","sources":["../../../src/commands/iteration/split.ts"],"names":[],"mappings":"AAwNA,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAEhB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AA+ID,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmjCzF"}
|
|
@@ -505,6 +505,14 @@ async function iterationSplitCommand(options) {
|
|
|
505
505
|
const iterDir = (0, path_1.join)(iterDirFull, '030-tasks');
|
|
506
506
|
await (0, fs_extra_1.ensureDir)(iterDir);
|
|
507
507
|
const backups = [];
|
|
508
|
+
// v8.3.163+: AI 上下文中禁用 --force(质量门禁和安全限制不可被 AI 自行绕过)
|
|
509
|
+
const isAiCtx = !process.stdout.isTTY;
|
|
510
|
+
if (options.force && isAiCtx) {
|
|
511
|
+
logger_1.logger.error('❌ AI 上下文中禁止使用 --force 参数');
|
|
512
|
+
logger_1.logger.info(' 质量门禁(任务数限制、拆分粒度校验)不可被 AI 自行绕过');
|
|
513
|
+
logger_1.logger.info(' 如需强制继续,请在交互式终端中执行此命令');
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
508
516
|
// v8.3.160+: 智能提取 JSON(支持 JSON 后附带 [PENDING] / [SPECCORE_EXEC] 标记)
|
|
509
517
|
function extractJsonArray(text) {
|
|
510
518
|
const result = { json: text };
|
|
@@ -674,20 +682,29 @@ async function iterationSplitCommand(options) {
|
|
|
674
682
|
if (sections.length > MAX_TASKS_HARD && options.force) {
|
|
675
683
|
logger_1.logger.warn(` ⚠️ 任务数 ${sections.length} 超出建议上限 ${MAX_TASKS_HARD},--force 已启用,继续...`);
|
|
676
684
|
}
|
|
677
|
-
//
|
|
678
|
-
|
|
679
|
-
const
|
|
685
|
+
// v8.3.162+: 逐功能单元×端校验:每个功能单元的每个端最多 3 个任务
|
|
686
|
+
// 拆分策略:功能单元先按端拆分,每个端内的子任务最多 3 个
|
|
687
|
+
const MAX_TASKS_PER_UNIT_PLATFORM = 3;
|
|
688
|
+
const unitPlatformTaskCount = {};
|
|
680
689
|
let missingFunctionalUnit = 0;
|
|
681
|
-
// 按 functionalUnit
|
|
690
|
+
// 按 functionalUnit × platform 分组统计
|
|
682
691
|
for (let i = 0; i < sections.length; i++) {
|
|
683
692
|
const task = tasks[i];
|
|
684
693
|
const unitName = task.functionalUnit;
|
|
694
|
+
const scopes = Array.isArray(task.scope) ? task.scope : [];
|
|
685
695
|
if (!unitName) {
|
|
686
696
|
missingFunctionalUnit++;
|
|
687
|
-
|
|
697
|
+
// 未标注功能单元的任务,按 scope 分别计入 __missing__
|
|
698
|
+
for (const platform of scopes) {
|
|
699
|
+
const key = `__missing__|${platform}`;
|
|
700
|
+
unitPlatformTaskCount[key] = (unitPlatformTaskCount[key] || 0) + 1;
|
|
701
|
+
}
|
|
688
702
|
}
|
|
689
703
|
else {
|
|
690
|
-
|
|
704
|
+
for (const platform of scopes) {
|
|
705
|
+
const key = `${unitName}|${platform}`;
|
|
706
|
+
unitPlatformTaskCount[key] = (unitPlatformTaskCount[key] || 0) + 1;
|
|
707
|
+
}
|
|
691
708
|
}
|
|
692
709
|
}
|
|
693
710
|
// functionalUnit 缺失警告
|
|
@@ -696,23 +713,24 @@ async function iterationSplitCommand(options) {
|
|
|
696
713
|
logger_1.logger.warn(` 💡 AI 未按约束填写功能单元,校验可能不准确`);
|
|
697
714
|
logger_1.logger.warn(` 💡 建议重新执行 split,确保 Prompt 包含 functionalUnit 要求`);
|
|
698
715
|
}
|
|
699
|
-
//
|
|
716
|
+
// 检查每个功能单元×端的任务数
|
|
700
717
|
let hasOverSplit = false;
|
|
701
|
-
for (const [
|
|
702
|
-
if (count >
|
|
718
|
+
for (const [key, count] of Object.entries(unitPlatformTaskCount)) {
|
|
719
|
+
if (count > MAX_TASKS_PER_UNIT_PLATFORM) {
|
|
703
720
|
if (!hasOverSplit) {
|
|
704
|
-
logger_1.logger.error(`\n ❌
|
|
721
|
+
logger_1.logger.error(`\n ❌ 检测到过度拆分!某些功能单元在单个端内拆出过多任务:`);
|
|
705
722
|
hasOverSplit = true;
|
|
706
723
|
}
|
|
707
|
-
const
|
|
708
|
-
|
|
724
|
+
const [unitName, platform] = key.split('|');
|
|
725
|
+
const displayUnit = unitName === '__missing__' ? '(未标注功能单元)' : unitName;
|
|
726
|
+
logger_1.logger.error(` 📌 "${displayUnit}" 在 "${platform}" 端拆出了 ${count} 个任务(上限 ${MAX_TASKS_PER_UNIT_PLATFORM})`);
|
|
709
727
|
}
|
|
710
728
|
}
|
|
711
729
|
if (hasOverSplit) {
|
|
712
|
-
logger_1.logger.error(` 💡
|
|
730
|
+
logger_1.logger.error(` 💡 核心原则:功能单元先按端拆分,每个端内默认 1 个任务,最多 3 个`);
|
|
713
731
|
logger_1.logger.error(` 🔧 建议操作:`);
|
|
714
732
|
logger_1.logger.error(` 1. 重新执行 split,并告诉 AI:"任务太多,请合并相关功能"`);
|
|
715
|
-
logger_1.logger.error(` 2. 或者手动编辑 .speccore/prompts/split-suggestion-${iter}.md,明确要求"
|
|
733
|
+
logger_1.logger.error(` 2. 或者手动编辑 .speccore/prompts/split-suggestion-${iter}.md,明确要求"每个功能单元的每个端最多拆 2 个任务"`);
|
|
716
734
|
logger_1.logger.error(` 3. 如果确实需要这么多任务,使用 --force 跳过检查(不推荐)`);
|
|
717
735
|
if (!options.force) {
|
|
718
736
|
logger_1.logger.info('\n ℹ️ 如需强制继续,添加 --force 参数');
|
|
@@ -720,6 +738,76 @@ async function iterationSplitCommand(options) {
|
|
|
720
738
|
}
|
|
721
739
|
logger_1.logger.warn(' ⚠️ --force 已启用,继续创建所有任务...');
|
|
722
740
|
}
|
|
741
|
+
// v8.3.164+: 内容质量校验 — 拦截空壳任务(reqContent/techContent 实质内容不足)
|
|
742
|
+
// 问题:AI 返回的 reqContent/techContent 虽然有长度,但全是 AI-FILL 占位符和模板
|
|
743
|
+
const MIN_SUBSTANTIVE_CHARS = 80; // 实质内容最低字符数
|
|
744
|
+
const MAX_EMPTY_SHELL_RATIO = 0.5; // 空壳任务占比上限
|
|
745
|
+
let emptyShellCount = 0;
|
|
746
|
+
const emptyShellTasks = [];
|
|
747
|
+
for (let i = 0; i < sections.length; i++) {
|
|
748
|
+
const sec = sections[i];
|
|
749
|
+
const reqContent = sec._reqContent;
|
|
750
|
+
const techContent = sec._techContent;
|
|
751
|
+
// 计算实质内容字符数(排除 HTML 注释、AI-FILL 标记、空白行、markdown 格式符号)
|
|
752
|
+
const calcSubstantive = (text) => {
|
|
753
|
+
if (!text || text.trim().length === 0)
|
|
754
|
+
return 0;
|
|
755
|
+
// 去除 HTML 注释
|
|
756
|
+
let cleaned = text.replace(/<!--[\s\S]*?-->/g, '');
|
|
757
|
+
// 去除 AI-FILL 标记行
|
|
758
|
+
cleaned = cleaned.replace(/AI-FILL[\s\S]*?$/gm, '');
|
|
759
|
+
// 去除 markdown 标题符号
|
|
760
|
+
cleaned = cleaned.replace(/^#+\s*/gm, '');
|
|
761
|
+
// 去除表格分隔符
|
|
762
|
+
cleaned = cleaned.replace(/^[\|\-:\s]+$/gm, '');
|
|
763
|
+
// 去除空白行
|
|
764
|
+
cleaned = cleaned.replace(/^\s*$/gm, '');
|
|
765
|
+
// 去除 markdown 列表符号
|
|
766
|
+
cleaned = cleaned.replace(/^[-*+]\s+/gm, '');
|
|
767
|
+
// 去除代码块标记
|
|
768
|
+
cleaned = cleaned.replace(/^```\w*$/gm, '');
|
|
769
|
+
// 去除纯英文占位符(如 "TBD", "TODO", "待补充" 等)
|
|
770
|
+
cleaned = cleaned.replace(/\b(TBD|TODO|FIXME|HACK|XXX|待补充|待实现|待填写|待完善|待确认|待定)\b/gi, '');
|
|
771
|
+
// 计算剩余实质字符(中文字符 + 英文单词)
|
|
772
|
+
const cnChars = (cleaned.match(/[\u4e00-\u9fa5]/g) || []).length;
|
|
773
|
+
const enWords = (cleaned.match(/[a-zA-Z]{2,}/g) || []).length;
|
|
774
|
+
return cnChars + enWords;
|
|
775
|
+
};
|
|
776
|
+
const reqSubstantive = calcSubstantive(reqContent);
|
|
777
|
+
const techSubstantive = calcSubstantive(techContent);
|
|
778
|
+
// 判定为空壳:req 和 tech 都缺乏实质内容
|
|
779
|
+
if (reqSubstantive < MIN_SUBSTANTIVE_CHARS && techSubstantive < MIN_SUBSTANTIVE_CHARS) {
|
|
780
|
+
emptyShellCount++;
|
|
781
|
+
emptyShellTasks.push(`"${sec.name}" (req=${reqSubstantive}, tech=${techSubstantive})`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const emptyRatio = sections.length > 0 ? emptyShellCount / sections.length : 0;
|
|
785
|
+
if (emptyShellCount > 0) {
|
|
786
|
+
if (emptyRatio > MAX_EMPTY_SHELL_RATIO) {
|
|
787
|
+
logger_1.logger.error(`\n ❌ 内容质量拦截:${emptyShellCount}/${sections.length} 个任务为空壳(占比 ${Math.round(emptyRatio * 100)}%,上限 ${Math.round(MAX_EMPTY_SHELL_RATIO * 100)}%)`);
|
|
788
|
+
logger_1.logger.error(` 💡 空壳任务特征:reqContent/techContent 全是 AI-FILL 占位符、模板内容或 "待补充"`);
|
|
789
|
+
logger_1.logger.error(` 🔧 建议操作:`);
|
|
790
|
+
logger_1.logger.error(` 1. 重新执行 split,并告诉 AI:"请为每个任务生成完整的需求规格和技术方案,不要只输出骨架"`);
|
|
791
|
+
logger_1.logger.error(` 2. 或者检查 analyze 阶段的 020-specs/ 是否有足够内容供 split 提取`);
|
|
792
|
+
for (const t of emptyShellTasks.slice(0, 5))
|
|
793
|
+
logger_1.logger.error(` • ${t}`);
|
|
794
|
+
if (emptyShellTasks.length > 5)
|
|
795
|
+
logger_1.logger.error(` ... 还有 ${emptyShellTasks.length - 5} 个`);
|
|
796
|
+
if (!options.force) {
|
|
797
|
+
logger_1.logger.info('\n ℹ️ 如需强制继续,添加 --force 参数');
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
logger_1.logger.warn(' ⚠️ --force 已启用,继续创建空壳任务...');
|
|
801
|
+
}
|
|
802
|
+
else {
|
|
803
|
+
logger_1.logger.warn(`\n ⚠️ 内容质量警告:${emptyShellCount}/${sections.length} 个任务为空壳`);
|
|
804
|
+
for (const t of emptyShellTasks.slice(0, 3))
|
|
805
|
+
logger_1.logger.warn(` • ${t}`);
|
|
806
|
+
if (emptyShellTasks.length > 3)
|
|
807
|
+
logger_1.logger.warn(` ... 还有 ${emptyShellTasks.length - 3} 个`);
|
|
808
|
+
logger_1.logger.warn(` 💡 这些任务将在 execute 阶段依赖 AI 现场生成内容,可能增加开发时间`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
723
811
|
// 交互模式判断:显式 --interactive 或 stdin 是 TTY(--force 时跳过交互,直接执行)
|
|
724
812
|
const isInteractive = (options.interactive || process.stdin.isTTY) && !options.force;
|
|
725
813
|
// 非交互模式:显示任务总览摘要
|