speccore 6.40.1 → 6.41.0

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.
Files changed (38) hide show
  1. package/dist/cli.js +7 -4
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/analyze.d.ts.map +1 -1
  4. package/dist/commands/analyze.js +79 -65
  5. package/dist/commands/analyze.js.map +1 -1
  6. package/dist/commands/dev.d.ts.map +1 -1
  7. package/dist/commands/dev.js +4 -3
  8. package/dist/commands/dev.js.map +1 -1
  9. package/dist/commands/iteration/create.d.ts.map +1 -1
  10. package/dist/commands/iteration/create.js +4 -2
  11. package/dist/commands/iteration/create.js.map +1 -1
  12. package/dist/commands/iteration/split.d.ts.map +1 -1
  13. package/dist/commands/iteration/split.js +35 -12
  14. package/dist/commands/iteration/split.js.map +1 -1
  15. package/dist/commands/iteration-from-global.d.ts.map +1 -1
  16. package/dist/commands/iteration-from-global.js +3 -2
  17. package/dist/commands/iteration-from-global.js.map +1 -1
  18. package/dist/commands/status-panel.d.ts.map +1 -1
  19. package/dist/commands/status-panel.js +7 -4
  20. package/dist/commands/status-panel.js.map +1 -1
  21. package/dist/core/ai-context-generator.js +1 -1
  22. package/dist/core/ai-context-generator.js.map +1 -1
  23. package/dist/core/analyze-engine.d.ts.map +1 -1
  24. package/dist/core/analyze-engine.js +155 -25
  25. package/dist/core/analyze-engine.js.map +1 -1
  26. package/dist/core/next-steps.js +1 -1
  27. package/dist/core/next-steps.js.map +1 -1
  28. package/dist/core/prompt-builder.d.ts.map +1 -1
  29. package/dist/core/prompt-builder.js +12 -1
  30. package/dist/core/prompt-builder.js.map +1 -1
  31. package/dist/core/quality-audit.d.ts.map +1 -1
  32. package/dist/core/quality-audit.js +29 -4
  33. package/dist/core/quality-audit.js.map +1 -1
  34. package/dist/core/spec-paths.d.ts +15 -0
  35. package/dist/core/spec-paths.d.ts.map +1 -0
  36. package/dist/core/spec-paths.js +42 -0
  37. package/dist/core/spec-paths.js.map +1 -0
  38. package/package.json +1 -1
@@ -30,6 +30,7 @@ const git_integration_1 = require("./git-integration");
30
30
  const rag_engine_1 = require("./rag-engine");
31
31
  const knowledge_graph_1 = require("./knowledge-graph");
32
32
  const quality_audit_1 = require("./quality-audit");
33
+ const spec_paths_1 = require("./spec-paths");
33
34
  // ================================================================
34
35
  // 主入口
35
36
  // ================================================================
@@ -1172,7 +1173,7 @@ async function writePerPlatform(iterDir, report, filename) {
1172
1173
  .map(e => e.name);
1173
1174
  if (features.length === 0)
1174
1175
  return;
1175
- // 平台列表从 CONSTITUTION 获取,默认四端
1176
+ // 平台列表从 CONSTITUTION 获取(不再硬编码默认值)
1176
1177
  const platforms = await detectPlatformsFromConstitution();
1177
1178
  for (const platform of platforms) {
1178
1179
  const platformDir = (0, path_1.join)(specsBase, platform);
@@ -1626,14 +1627,65 @@ async function supplementAnalysis(input) {
1626
1627
  }
1627
1628
  /** 已检测到的后端平台列表(从 CONSTITUTION.md 解析) */
1628
1629
  let _detectedBackendPlatforms = [];
1629
- /** 从 CONSTITUTION.md 提取平台列表(支持中文端名 + 工程名映射) */
1630
+ /** 从 CONSTITUTION.md 动态提取的端名映射(技术栈标题解析) */
1631
+ let _dynamicPlatformAliases = {};
1632
+ /**
1633
+ * 从 CONSTITUTION.md 技术栈章节标题提取端名信息。
1634
+ * 匹配模式: ### 中文端名 (English Name)
1635
+ * 例如: ### 后台管理端 (Admin Dashboard) → { chinese: '后台管理端', english: 'Admin Dashboard' }
1636
+ */
1637
+ function parseTechStackHeaders(content) {
1638
+ const results = [];
1639
+ const lines = content.split('\n');
1640
+ for (const line of lines) {
1641
+ // 匹配: ### 后台管理端 (Admin Dashboard) 或 ### H5 移动端 (Mobile H5)
1642
+ const match = line.match(/^###\s+(.+?)\s*\(([^)]+)\)/);
1643
+ if (match) {
1644
+ results.push({
1645
+ chinese: match[1].trim(),
1646
+ english: match[2].trim(),
1647
+ fullTitle: line.trim(),
1648
+ });
1649
+ }
1650
+ }
1651
+ return results;
1652
+ }
1653
+ /**
1654
+ * 将技术栈标题解析结果合并到动态别名映射中。
1655
+ * 这样 inferPlatformFromPathOrContent 也能使用 CONSTITUTION.md 定义的端名。
1656
+ */
1657
+ function buildDynamicAliasesFromTechStack(techStackEntries) {
1658
+ const aliases = {};
1659
+ for (const entry of techStackEntries) {
1660
+ const normalized = normalizeToStandardPlatform(entry.chinese) || normalizeToStandardPlatform(entry.english);
1661
+ if (!normalized)
1662
+ continue;
1663
+ if (!aliases[normalized]) {
1664
+ aliases[normalized] = [];
1665
+ }
1666
+ // 添加中文名、英文名、全标题作为别名
1667
+ const newAliases = [entry.chinese, entry.english, entry.fullTitle];
1668
+ for (const a of newAliases) {
1669
+ const lower = a.toLowerCase();
1670
+ if (!aliases[normalized].includes(lower)) {
1671
+ aliases[normalized].push(lower);
1672
+ }
1673
+ }
1674
+ }
1675
+ return aliases;
1676
+ }
1677
+ /** 从 CONSTITUTION.md 提取平台列表(两层确定性匹配)
1678
+ * Layer 1: 表格「对应需求端」列(用户显式声明)
1679
+ * Layer 2: 技术栈章节标题 ### 中文端名 (English Name)
1680
+ * ⚠️ 不再提供硬编码默认值 — 端列表应由 AI 根据项目实际情况判断
1681
+ */
1630
1682
  async function detectPlatformsFromConstitution() {
1631
1683
  try {
1632
1684
  const constitutionPath = (0, path_1.join)(process.cwd(), '.speccore', 'CONSTITUTION.md');
1633
1685
  if (require('fs').existsSync(constitutionPath)) {
1634
1686
  const content = require('fs').readFileSync(constitutionPath, 'utf-8');
1635
1687
  const lines = content.split('\n');
1636
- // 1. 先尝试从表头定位「对应需求端」和「工程名」列的索引
1688
+ // ── Layer 1: 表格「对应需求端」列 ──
1637
1689
  let headerRowIndex = -1;
1638
1690
  let headerCells = [];
1639
1691
  for (let i = 0; i < lines.length; i++) {
@@ -1649,22 +1701,29 @@ async function detectPlatformsFromConstitution() {
1649
1701
  const platforms = [];
1650
1702
  const backendPlatforms = [];
1651
1703
  const seen = new Set();
1652
- // 解析数据行(跳过头部和分隔线)
1653
1704
  for (let i = headerRowIndex + 2; i < lines.length; i++) {
1654
1705
  const line = lines[i].trim();
1655
- if (!line.startsWith('|') || line.match(/^\|[\s:-]+/))
1706
+ // 分隔行跳过(如 | :--- | :--- |)
1707
+ if (line.match(/^\|[\s:-]+/))
1708
+ continue;
1709
+ // 空行跳过(表格内可能有空行)
1710
+ if (line === '')
1656
1711
  continue;
1712
+ // 非表格行 → 当前表格结束,终止读取
1713
+ if (!line.startsWith('|'))
1714
+ break;
1657
1715
  const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
1658
1716
  const projectName = cells[0] || '';
1659
1717
  const platformChinese = cells[5] || cells[cells.length - 1] || '';
1718
+ // 跳过空值和占位符(如「待填写」)
1660
1719
  if (!platformChinese || !projectName)
1661
1720
  continue;
1662
- // 归一化:中文端名 → 标准端名
1721
+ if (/待填写|待补充|TODO|TBD|N\/A/i.test(platformChinese))
1722
+ continue;
1663
1723
  const normalized = normalizeToStandardPlatform(platformChinese);
1664
1724
  if (normalized && !seen.has(normalized)) {
1665
1725
  seen.add(normalized);
1666
1726
  platforms.push(normalized);
1667
- // 判断前后端:从工程名或中文端名推断
1668
1727
  const isBackend = /service|server|api|backend|后台|服务|后端/i.test(projectName) ||
1669
1728
  /后台|服务|后端/.test(platformChinese);
1670
1729
  if (isBackend) {
@@ -1674,18 +1733,52 @@ async function detectPlatformsFromConstitution() {
1674
1733
  }
1675
1734
  if (platforms.length > 0) {
1676
1735
  _detectedBackendPlatforms = backendPlatforms;
1736
+ // Layer 1 成功:也解析技术栈标题来构建动态别名
1737
+ const techStackEntries = parseTechStackHeaders(content);
1738
+ if (techStackEntries.length > 0) {
1739
+ _dynamicPlatformAliases = buildDynamicAliasesFromTechStack(techStackEntries);
1740
+ }
1741
+ return platforms;
1742
+ }
1743
+ }
1744
+ // ── Layer 2: 技术栈章节标题 ### 中文端名 (English Name) ──
1745
+ const techStackEntries = parseTechStackHeaders(content);
1746
+ if (techStackEntries.length > 0) {
1747
+ const platforms = [];
1748
+ const backendPlatforms = [];
1749
+ const seen = new Set();
1750
+ for (const entry of techStackEntries) {
1751
+ const normalized = normalizeToStandardPlatform(entry.chinese) || normalizeToStandardPlatform(entry.english);
1752
+ if (!normalized || seen.has(normalized))
1753
+ continue;
1754
+ seen.add(normalized);
1755
+ platforms.push(normalized);
1756
+ // 判断前后端
1757
+ const isBackend = /service|server|api|backend|后台|服务|后端/i.test(entry.chinese + ' ' + entry.english);
1758
+ if (isBackend) {
1759
+ backendPlatforms.push(normalized);
1760
+ }
1761
+ }
1762
+ if (platforms.length > 0) {
1763
+ _detectedBackendPlatforms = backendPlatforms;
1764
+ _dynamicPlatformAliases = buildDynamicAliasesFromTechStack(techStackEntries);
1765
+ logger_1.logger.info(` 📋 从技术栈标题检测到 ${platforms.length} 个端: ${platforms.join(', ')}`);
1677
1766
  return platforms;
1678
1767
  }
1679
1768
  }
1680
- // 2. 回退:简单正则匹配(兼容旧格式)
1769
+ // Layer 2.5: 简单正则匹配(兼容旧格式)
1681
1770
  const match = content.match(/对应需求端[||]\s*([a-z,\s]+)/i);
1682
1771
  if (match) {
1683
- return match[1].split(/[,,]/).map((s) => s.trim()).filter(Boolean);
1772
+ const result = match[1].split(/[,,]/).map((s) => s.trim()).filter(Boolean);
1773
+ if (result.length > 0)
1774
+ return result;
1684
1775
  }
1685
1776
  }
1686
1777
  }
1687
1778
  catch { }
1688
- return ['app', 'h5', 'miniapp', 'admin']; // 默认四端
1779
+ // ⚠️ 不再硬编码默认端列表 端列表应由 AI 根据 CONSTITUTION.md + 需求文档判断
1780
+ // 如果 Layer 1 和 Layer 2 都无法检测到端,返回空数组,由 AI 在 prompt 中自行发现
1781
+ return [];
1689
1782
  }
1690
1783
  /**
1691
1784
  * 将中文端名/工程名归一化为标准端名
@@ -1693,11 +1786,28 @@ async function detectPlatformsFromConstitution() {
1693
1786
  */
1694
1787
  function normalizeToStandardPlatform(name) {
1695
1788
  const nameLower = name.toLowerCase().trim();
1789
+ // 【v6.40.2 修复】两阶段最长匹配策略:
1790
+ // Phase 1: 精确匹配(name === alias),最长优先
1791
+ // Phase 2: 包含匹配(name.includes(alias) 或 alias.includes(name)),最长优先
1792
+ // 避免「后台服务端」被短别名「后台」先匹配到 admin
1793
+ // 避免「移动端」被更长别名「移动端app」误匹配到 app
1794
+ const allPairs = [];
1696
1795
  for (const [standardName, aliases] of Object.entries(PLATFORM_ALIAS_MAP)) {
1697
1796
  for (const alias of aliases) {
1698
- if (nameLower === alias || nameLower.includes(alias) || alias.includes(nameLower)) {
1699
- return standardName;
1700
- }
1797
+ allPairs.push({ alias: alias.toLowerCase(), platform: standardName });
1798
+ }
1799
+ }
1800
+ allPairs.sort((a, b) => b.alias.length - a.alias.length);
1801
+ // Phase 1: 精确匹配
1802
+ for (const pair of allPairs) {
1803
+ if (nameLower === pair.alias) {
1804
+ return pair.platform;
1805
+ }
1806
+ }
1807
+ // Phase 2: 包含匹配
1808
+ for (const pair of allPairs) {
1809
+ if (nameLower.includes(pair.alias) || pair.alias.includes(nameLower)) {
1810
+ return pair.platform;
1701
1811
  }
1702
1812
  }
1703
1813
  // 无法映射时,返回清理后的原始值
@@ -1749,7 +1859,12 @@ async function generateSpecsFromRequirements(reqPaths, iteration, specDir) {
1749
1859
  if (unclassifiedFiles.length > 0) {
1750
1860
  logger_1.logger.info('');
1751
1861
  logger_1.logger.info(` ❓ 发现 ${unclassifiedFiles.length} 个文档无法自动识别所属端`);
1752
- logger_1.logger.info(` 💡 可用端列表: ${platforms.join(', ')}`);
1862
+ if (platforms.length > 0) {
1863
+ logger_1.logger.info(` 💡 可用端列表: ${platforms.join(', ')}`);
1864
+ }
1865
+ else {
1866
+ logger_1.logger.info(` 💡 CONSTITUTION.md 未检测到端列表,将由 AI 根据需求文档内容判断`);
1867
+ }
1753
1868
  logger_1.logger.info('');
1754
1869
  for (const file of unclassifiedFiles) {
1755
1870
  const fileName = file.path.split(/[/\\]/).pop() || file.path;
@@ -1850,15 +1965,19 @@ async function generateSpecsFromRequirements(reqPaths, iteration, specDir) {
1850
1965
  await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'UI_SPEC.md'), uiContent);
1851
1966
  }
1852
1967
  }
1853
- // 4. 写入全局文件(覆盖空模板,不覆盖已有实质内容的文件)
1968
+ // 4. 写入全局文件到 global/ 子目录(v6.41.0+ 新路径)
1854
1969
  let withContent = 0;
1855
1970
  let skipped = 0;
1856
- await (0, fs_extra_1.ensureDir)(specDir);
1971
+ const globalDir = (0, path_1.join)(specDir, spec_paths_1.GLOBAL_SPECS_DIR);
1972
+ await (0, fs_extra_1.ensureDir)(globalDir);
1857
1973
  for (const f of files) {
1858
- const filePath = (0, path_1.join)(specDir, f.filename);
1859
- // 如果文件已存在且有实质内容(>50 非模板字符),跳过
1860
- if (await (0, fs_extra_1.pathExists)(filePath)) {
1861
- const existing = await (0, fs_extra_1.readFile)(filePath, 'utf-8');
1974
+ // 写入路径:始终使用 global/ 子目录
1975
+ const filePath = (0, path_1.join)(globalDir, f.filename);
1976
+ // 覆盖检查:优先检查新路径,回退检查旧路径(根目录)
1977
+ const existingPath = await (0, fs_extra_1.pathExists)(filePath) ? filePath :
1978
+ await (0, fs_extra_1.pathExists)((0, path_1.join)(specDir, f.filename)) ? (0, path_1.join)(specDir, f.filename) : null;
1979
+ if (existingPath) {
1980
+ const existing = await (0, fs_extra_1.readFile)(existingPath, 'utf-8');
1862
1981
  const meaningful = stripTemplateNoise(existing);
1863
1982
  if (meaningful.length > 50) {
1864
1983
  skipped++;
@@ -2684,17 +2803,28 @@ function inferPlatformFromPathOrContent(filePath, content, platforms) {
2684
2803
  return platform;
2685
2804
  }
2686
2805
  }
2687
- // 【新增】2. 语义映射匹配:尝试将内容中的中文端名映射到标准端名
2806
+ // 2. 语义映射匹配:硬编码 PLATFORM_ALIAS_MAP + CONSTITUTION.md 动态别名
2688
2807
  const firstLines = content.split('\n').slice(0, 50).join('\n');
2689
- for (const [standardPlatform, aliases] of Object.entries(PLATFORM_ALIAS_MAP)) {
2690
- // 只检查这个标准端名是否在 platforms 列表中
2808
+ // 合并硬编码映射和 CONSTITUTION.md 动态提取的别名
2809
+ const mergedAliases = { ...PLATFORM_ALIAS_MAP };
2810
+ for (const [platform, aliases] of Object.entries(_dynamicPlatformAliases)) {
2811
+ if (!mergedAliases[platform]) {
2812
+ mergedAliases[platform] = [];
2813
+ }
2814
+ for (const a of aliases) {
2815
+ if (!mergedAliases[platform].includes(a)) {
2816
+ mergedAliases[platform].push(a);
2817
+ }
2818
+ }
2819
+ }
2820
+ for (const [standardPlatform, aliases] of Object.entries(mergedAliases)) {
2691
2821
  if (!platforms.includes(standardPlatform))
2692
2822
  continue;
2693
- // 检查是否有别名出现在内容中
2694
2823
  for (const alias of aliases) {
2695
2824
  const aliasPattern = new RegExp(alias, 'i');
2696
2825
  if (aliasPattern.test(firstLines)) {
2697
- logger_1.logger.info(` 🔄 语义映射: "${alias}" "${standardPlatform}"`);
2826
+ const source = PLATFORM_ALIAS_MAP[standardPlatform]?.includes(alias) ? '静态映射' : 'CONSTITUTION动态';
2827
+ logger_1.logger.info(` 🔄 语义映射(${source}): "${alias}" → "${standardPlatform}"`);
2698
2828
  return standardPlatform;
2699
2829
  }
2700
2830
  }