speccore 8.3.285 → 8.3.295

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 (143) hide show
  1. package/.agents/skills/spec-run/SKILL.md +16 -0
  2. package/.agents/skills/spec-task-create/SKILL.md +15 -0
  3. package/.agents/skills/spec-unitize/SKILL.md +16 -0
  4. package/README.en.md +16 -3
  5. package/README.md +23 -8
  6. package/dist/cli.js +59 -12
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/about.d.ts.map +1 -1
  9. package/dist/commands/about.js +5 -0
  10. package/dist/commands/about.js.map +1 -1
  11. package/dist/commands/analyze.d.ts.map +1 -1
  12. package/dist/commands/analyze.js +77 -5
  13. package/dist/commands/analyze.js.map +1 -1
  14. package/dist/commands/ask.js +2 -2
  15. package/dist/commands/ask.js.map +1 -1
  16. package/dist/commands/code-index.d.ts.map +1 -1
  17. package/dist/commands/code-index.js +20 -9
  18. package/dist/commands/code-index.js.map +1 -1
  19. package/dist/commands/doc2spec.js +32 -60
  20. package/dist/commands/doc2spec.js.map +1 -1
  21. package/dist/commands/execute.d.ts +1 -0
  22. package/dist/commands/execute.d.ts.map +1 -1
  23. package/dist/commands/execute.js +15 -1
  24. package/dist/commands/execute.js.map +1 -1
  25. package/dist/commands/help.js +14 -5
  26. package/dist/commands/help.js.map +1 -1
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +14 -3
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/commands/iteration/taskify.d.ts.map +1 -1
  31. package/dist/commands/iteration/taskify.js +132 -7
  32. package/dist/commands/iteration/taskify.js.map +1 -1
  33. package/dist/commands/run.d.ts +1 -0
  34. package/dist/commands/run.d.ts.map +1 -1
  35. package/dist/commands/run.js +193 -25
  36. package/dist/commands/run.js.map +1 -1
  37. package/dist/commands/status-panel.d.ts +1 -0
  38. package/dist/commands/status-panel.d.ts.map +1 -1
  39. package/dist/commands/status-panel.js +180 -54
  40. package/dist/commands/status-panel.js.map +1 -1
  41. package/dist/commands/task/new.d.ts +16 -0
  42. package/dist/commands/task/new.d.ts.map +1 -1
  43. package/dist/commands/task/new.js +232 -14
  44. package/dist/commands/task/new.js.map +1 -1
  45. package/dist/commands/verify.d.ts.map +1 -1
  46. package/dist/commands/verify.js +78 -1
  47. package/dist/commands/verify.js.map +1 -1
  48. package/dist/core/analyze-engine.d.ts.map +1 -1
  49. package/dist/core/analyze-engine.js +6 -10
  50. package/dist/core/analyze-engine.js.map +1 -1
  51. package/dist/core/ask-llm.d.ts +7 -0
  52. package/dist/core/ask-llm.d.ts.map +1 -1
  53. package/dist/core/ask-llm.js +45 -0
  54. package/dist/core/ask-llm.js.map +1 -1
  55. package/dist/core/code-graph/incremental-build.d.ts +2 -0
  56. package/dist/core/code-graph/incremental-build.d.ts.map +1 -1
  57. package/dist/core/code-graph/incremental-build.js +6 -5
  58. package/dist/core/code-graph/incremental-build.js.map +1 -1
  59. package/dist/core/code-graph/index.d.ts +2 -0
  60. package/dist/core/code-graph/index.d.ts.map +1 -1
  61. package/dist/core/code-graph/index.js +12 -5
  62. package/dist/core/code-graph/index.js.map +1 -1
  63. package/dist/core/code-scanner.d.ts +19 -4
  64. package/dist/core/code-scanner.d.ts.map +1 -1
  65. package/dist/core/code-scanner.js +193 -77
  66. package/dist/core/code-scanner.js.map +1 -1
  67. package/dist/core/excel-extract.d.ts +16 -2
  68. package/dist/core/excel-extract.d.ts.map +1 -1
  69. package/dist/core/excel-extract.js +54 -12
  70. package/dist/core/excel-extract.js.map +1 -1
  71. package/dist/core/git-integration.d.ts +2 -1
  72. package/dist/core/git-integration.d.ts.map +1 -1
  73. package/dist/core/git-integration.js +38 -14
  74. package/dist/core/git-integration.js.map +1 -1
  75. package/dist/core/global-counters.d.ts +3 -0
  76. package/dist/core/global-counters.d.ts.map +1 -1
  77. package/dist/core/global-counters.js +21 -7
  78. package/dist/core/global-counters.js.map +1 -1
  79. package/dist/core/intent-recognition.d.ts.map +1 -1
  80. package/dist/core/intent-recognition.js +173 -27
  81. package/dist/core/intent-recognition.js.map +1 -1
  82. package/dist/core/plan-store.d.ts +1 -0
  83. package/dist/core/plan-store.d.ts.map +1 -1
  84. package/dist/core/plan-store.js.map +1 -1
  85. package/dist/core/prompt-builder.d.ts.map +1 -1
  86. package/dist/core/prompt-builder.js +168 -140
  87. package/dist/core/prompt-builder.js.map +1 -1
  88. package/dist/core/spec-paths.d.ts.map +1 -1
  89. package/dist/core/spec-paths.js +3 -0
  90. package/dist/core/spec-paths.js.map +1 -1
  91. package/dist/core/spec-skeleton.d.ts.map +1 -1
  92. package/dist/core/spec-skeleton.js +6 -2
  93. package/dist/core/spec-skeleton.js.map +1 -1
  94. package/dist/core/stage-aliases.d.ts +40 -0
  95. package/dist/core/stage-aliases.d.ts.map +1 -0
  96. package/dist/core/stage-aliases.js +110 -0
  97. package/dist/core/stage-aliases.js.map +1 -0
  98. package/dist/core/state.d.ts +11 -0
  99. package/dist/core/state.d.ts.map +1 -1
  100. package/dist/core/state.js +33 -2
  101. package/dist/core/state.js.map +1 -1
  102. package/dist/core/structured-extractor.d.ts.map +1 -1
  103. package/dist/core/structured-extractor.js +7 -0
  104. package/dist/core/structured-extractor.js.map +1 -1
  105. package/dist/core/ui-verify/index.d.ts +2 -2
  106. package/dist/core/ui-verify/index.d.ts.map +1 -1
  107. package/dist/core/ui-verify/index.js +3 -1
  108. package/dist/core/ui-verify/index.js.map +1 -1
  109. package/dist/core/ui-verify/smoke-engine.d.ts.map +1 -1
  110. package/dist/core/ui-verify/smoke-engine.js +36 -0
  111. package/dist/core/ui-verify/smoke-engine.js.map +1 -1
  112. package/dist/core/ui-verify/spec-generator.d.ts +32 -1
  113. package/dist/core/ui-verify/spec-generator.d.ts.map +1 -1
  114. package/dist/core/ui-verify/spec-generator.js +205 -21
  115. package/dist/core/ui-verify/spec-generator.js.map +1 -1
  116. package/dist/core/ui-verify/types.d.ts +2 -0
  117. package/dist/core/ui-verify/types.d.ts.map +1 -1
  118. package/dist/utils/synonyms.d.ts.map +1 -1
  119. package/dist/utils/synonyms.js +15 -0
  120. package/dist/utils/synonyms.js.map +1 -1
  121. package/package.json +3 -2
  122. package/templates/api-design-example.md +338 -0
  123. package/templates/ci/github-actions.yml +44 -0
  124. package/templates/database-example.md +223 -0
  125. package/templates/deploy-docker/README.md +284 -0
  126. package/templates/deploy-docker/deploy-remote.sh +79 -0
  127. package/templates/deploy-docker/docker-compose.yml +113 -0
  128. package/templates/deploy-docker/frontend.Dockerfile +43 -0
  129. package/templates/deploy-docker/nginx.conf +30 -0
  130. package/templates/deploy-examples.yaml +436 -0
  131. package/templates/deploy-java/Dockerfile +50 -0
  132. package/templates/deploy-java/README.md +212 -0
  133. package/templates/deploy-java/deploy.sh +120 -0
  134. package/templates/deploy-java/spring-boot.service +35 -0
  135. package/templates/html/speccore-ask-explain.html +10 -0
  136. package/templates/html/speccore-ask-guide.html +10 -0
  137. package/templates/html/speccore-ask-match.html +10 -0
  138. package/templates/html/speccore-ask-pipeline.html +1 -0
  139. package/templates/html/speccore-ask-result.html +10 -0
  140. package/templates/html/speccore-help.html +241 -0
  141. package/templates/html/speccore-setup-guide.html +289 -0
  142. package/templates/security-example.md +226 -0
  143. package/templates/verify-spec-example.yaml +138 -0
@@ -69,6 +69,7 @@ const content_delivery_1 = require("./content-delivery");
69
69
  // v8.3.183+: HTML 原型渲染(Playwright 截图 + accessibility tree,替代纯文本)
70
70
  const html_prototype_1 = require("./html-prototype");
71
71
  // v8.3.122+: execute 时深入读取关联源码
72
+ // v8.3.3xx+: 消费前兜底建索引(ensureCodeIndexFresh)+ 迭代源码 scope 解析
72
73
  const code_scanner_1 = require("./code-scanner");
73
74
  // v8.3.125+: 同义词扩展,解决中英文/缩写关键词匹配失败
74
75
  const synonyms_1 = require("../utils/synonyms");
@@ -1560,12 +1561,13 @@ function getInstruction(command, context) {
1560
1561
  '## 前端实现要求(Layer 3)',
1561
1562
  '如果任务涉及前端各端(admin/H5/小程序/App),还需:',
1562
1563
  '1. 按 UI_SPEC.md 中的字段→UI 映射实现每个页面',
1563
- '2. 按路由表创建页面组件和路由配置',
1564
- '3. 实现状态枚举的前端展示(与后端数据模型一致)',
1565
- '4. 实现表单校验规则(与后端校验规则一致)',
1566
- '5. 实现页面四态:加载中/正常/空态/错误态',
1567
- '6. 实现响应式适配(按 UI_SPEC.md 中的断点和布局策略)',
1568
- '7. 前端组件必须与后端 API 响应字段一一对应,不能硬编码',
1564
+ '2. **按 UI_SPEC.md「元素标识约定」给组件添加 data-testid 属性**(命名遵循约定的 {模块}-{页面}-{元素} 格式,页面容器以 xxx-page 结尾)——冒烟测试的 VERIFY_SPEC 断言依赖这些属性定位元素,缺失会导致冒烟失败',
1565
+ '3. 按路由表创建页面组件和路由配置',
1566
+ '4. 实现状态枚举的前端展示(与后端数据模型一致)',
1567
+ '5. 实现表单校验规则(与后端校验规则一致)',
1568
+ '6. 实现页面四态:加载中/正常/空态/错误态',
1569
+ '7. 实现响应式适配(按 UI_SPEC.md 中的断点和布局策略)',
1570
+ '8. 前端组件必须与后端 API 响应字段一一对应,不能硬编码',
1569
1571
  '',
1570
1572
  '## 🤖 自动模式指令',
1571
1573
  '',
@@ -1682,23 +1684,64 @@ async function buildPrompt(command, options) {
1682
1684
  try {
1683
1685
  const iterDir = await (0, context_1.getIterationDir)(options.iteration);
1684
1686
  if (iterDir) {
1685
- const reqIndex = (0, path_1.join)(iterDir, '010-requirements', 'INDEX.md');
1686
- if (await (0, fs_extra_1.pathExists)(reqIndex)) {
1687
- const idxContent = await (0, fs_extra_1.readFile)(reqIndex, 'utf-8');
1688
- const titleMatch = idxContent.match(/^#\s+(.+)$/m);
1689
- if (titleMatch)
1690
- searchQuery = titleMatch[1].trim();
1687
+ // 优先级:010-requirements/INDEX.md → features/INDEX.md → 需求文档标题(跳过 README.md 规范说明)
1688
+ // 否则 README.md 的「目录规范」标题会污染查询词,导致源码匹配全空;
1689
+ // 泛化标题(功能单元索引/目录规范等)同样丢弃,回退迭代名(含业务语义)
1690
+ const reqIndexCandidates = [
1691
+ (0, path_1.join)(iterDir, '010-requirements', 'INDEX.md'),
1692
+ (0, path_1.join)(iterDir, '010-requirements', 'features', 'INDEX.md'),
1693
+ ];
1694
+ const isGenericTitle = (t) => /(目录|索引|规范|说明|README|指南|总览|报告|审查|清单|质量)/i.test(t);
1695
+ let found = false;
1696
+ for (const idxPath of reqIndexCandidates) {
1697
+ if (await (0, fs_extra_1.pathExists)(idxPath)) {
1698
+ const idxContent = await (0, fs_extra_1.readFile)(idxPath, 'utf-8');
1699
+ const titleMatch = idxContent.match(/^#\s+(.+)$/m);
1700
+ if (titleMatch) {
1701
+ const t = titleMatch[1].trim();
1702
+ if (!isGenericTitle(t)) {
1703
+ searchQuery = t;
1704
+ found = true;
1705
+ break;
1706
+ }
1707
+ }
1708
+ }
1691
1709
  }
1692
- else {
1693
- // 尝试读取第一个需求文档
1710
+ if (!found) {
1711
+ // 兜底:features/ 下第一个 SOURCE.md 的标题(业务语义最强,如「预订管理」)
1712
+ const featuresDir = (0, path_1.join)(iterDir, '010-requirements', 'features');
1713
+ const featureDirs = (await (0, fs_extra_1.readdir)(featuresDir, { withFileTypes: true }).catch(() => []))
1714
+ .filter((e) => e.isDirectory())
1715
+ .map((e) => e.name);
1716
+ for (const fd of featureDirs) {
1717
+ const sourcePath = (0, path_1.join)(featuresDir, fd, 'SOURCE.md');
1718
+ if (!(await (0, fs_extra_1.pathExists)(sourcePath)))
1719
+ continue;
1720
+ const sourceContent = await (0, fs_extra_1.readFile)(sourcePath, 'utf-8');
1721
+ const titleMatch = sourceContent.match(/^#\s+(.+)$/m);
1722
+ if (titleMatch) {
1723
+ const t = titleMatch[1].trim();
1724
+ if (!isGenericTitle(t)) {
1725
+ searchQuery = t;
1726
+ found = true;
1727
+ break;
1728
+ }
1729
+ }
1730
+ }
1731
+ }
1732
+ if (!found) {
1733
+ // 尝试读取第一个需求文档(跳过 README.md 目录规范说明与报告类文档)
1694
1734
  const reqDir = (0, path_1.join)(iterDir, '010-requirements');
1695
1735
  const reqFiles = (await (0, fs_extra_1.readdir)(reqDir).catch(() => []))
1696
- .filter(f => f.endsWith('.md') && !f.startsWith('.'));
1736
+ .filter(f => f.endsWith('.md') && !f.startsWith('.') && !/^(README|QUALITY_REPORT|CLARIFY_REPORT)$/i.test(f.replace(/\.md$/, '')));
1697
1737
  if (reqFiles.length > 0) {
1698
1738
  const firstReq = await (0, fs_extra_1.readFile)((0, path_1.join)(reqDir, reqFiles[0]), 'utf-8');
1699
1739
  const titleMatch = firstReq.match(/^#\s+(.+)$/m);
1700
- if (titleMatch)
1701
- searchQuery = titleMatch[1].trim();
1740
+ if (titleMatch) {
1741
+ const t = titleMatch[1].trim();
1742
+ if (!isGenericTitle(t))
1743
+ searchQuery = t;
1744
+ }
1702
1745
  }
1703
1746
  }
1704
1747
  }
@@ -1768,7 +1811,6 @@ async function buildPrompt(command, options) {
1768
1811
  // 这是最高优先级的改进:用结构化卡片替代原始源码,Token 效率提升 10-20 倍
1769
1812
  let structuredDataPath = (0, path_1.join)(cwd, '.speccore', 'cache', 'structured-data.json');
1770
1813
  let structured = null;
1771
- let structuredCardsInjected = false;
1772
1814
  // 优先尝试平台分段文件
1773
1815
  if (options.platform) {
1774
1816
  const platformPath = (0, path_1.join)(cwd, '.speccore', 'cache', `structured-data.${options.platform}.json`);
@@ -1980,140 +2022,123 @@ async function buildPrompt(command, options) {
1980
2022
  });
1981
2023
  const cardChars = cardLines.join('\n').length;
1982
2024
  logger_1.logger?.info?.(` 📇 结构化卡片: ${matchedApis.length} API + ${matchedEntities.length} Entity + ${matchedComponents.length} Component + ${matchedRoutes.length} Route + ${matchedDtos.length} DTO + ${matchedServices.length} Service (${Math.round(cardChars / 1000)}K 字符)`);
1983
- structuredCardsInjected = true;
1984
2025
  }
1985
2026
  }
1986
2027
  catch { /* 结构化数据解析失败不阻断 */ }
1987
2028
  }
1988
- const codeMatches = await (0, code_scanner_1.findRelevantCode)(searchQuery, 15, undefined, options.iteration, options.task);
2029
+ // v8.3.3xx+: 消费前兜底——索引缺失/过期时自动重建 + 无索引告警
2030
+ // 按迭代解析源码 scope,与 analyze-engine 消费路径保持一致;
2031
+ // 否则兜底构建会落到 DEFAULT_SCOPE(项目根 src/ 等),索引空壳、查询无结果
2032
+ const codeIndexScope = options.iteration
2033
+ ? (await (0, code_scanner_1.resolveIterationScope)(options.iteration).catch(() => undefined))
2034
+ : undefined;
2035
+ await (0, code_scanner_1.ensureCodeIndexFresh)(codeIndexScope);
2036
+ const codeMatches = await (0, code_scanner_1.findRelevantCode)(searchQuery, 20, codeIndexScope, options.iteration, options.task);
1989
2037
  if (codeMatches.length > 0) {
1990
- // v8.3.124+: 如果已注入结构化卡片,减少原始源码的文件数和长度
1991
- const sourceMaxFiles = structuredCardsInjected ? 8 : 15;
1992
- const sourceMaxBytes = structuredCardsInjected ? 60000 : 100000;
1993
- const sourceContents = await (0, code_scanner_1.readRelevantSource)(codeMatches, sourceMaxBytes, sourceMaxFiles);
1994
- const sourceKeys = Object.keys(sourceContents);
1995
- if (sourceKeys.length > 0) {
1996
- // v8.3.122+: execute 时注入知识图谱任务上下文
1997
- // v8.3.125+: 自动检查过期并刷新
1998
- if (command === 'execute' && options.iteration && options.task) {
1999
- try {
2000
- const kg = await (0, knowledge_graph_1.loadFreshKnowledgeGraph)(cwd, options.iteration);
2001
- if (kg) {
2002
- const ctx = (0, knowledge_graph_1.getFullTaskContext)(kg, options.task);
2003
- const ctxLines = ['## 🔗 任务关联上下文(知识图谱)', ''];
2004
- if (ctx.requirement)
2005
- ctxLines.push(`- **上游需求**: ${ctx.requirement.title} (${ctx.requirement.file})`);
2006
- if (ctx.parentTask)
2007
- ctxLines.push(`- **父任务**: ${ctx.parentTask.title}`);
2008
- if (ctx.dependsOn.length > 0)
2009
- ctxLines.push(`- **依赖任务**(需先完成): ${ctx.dependsOn.map(d => d.title).join(', ')}`);
2010
- if (ctx.downstreamTasks.length > 0)
2011
- ctxLines.push(`- **下游任务**(依赖本任务): ${ctx.downstreamTasks.map(d => d.title).join(', ')}`);
2012
- if (ctx.relatedSpecs.length > 0) {
2013
- ctxLines.push(`- **关联规格文档**:`);
2014
- for (const spec of ctx.relatedSpecs) {
2015
- ctxLines.push(` - ${spec.title}: \`${spec.file}\``);
2016
- }
2017
- }
2018
- if (ctx.dependencyChain.length > 0) {
2019
- ctxLines.push(`- **依赖链路**: ${ctx.dependencyChain.map(c => c.taskName).join(' → ')}`);
2038
+ // v8.3.122+: execute 时注入知识图谱任务上下文
2039
+ // v8.3.125+: 自动检查过期并刷新
2040
+ if (command === 'execute' && options.iteration && options.task) {
2041
+ try {
2042
+ const kg = await (0, knowledge_graph_1.loadFreshKnowledgeGraph)(cwd, options.iteration);
2043
+ if (kg) {
2044
+ const ctx = (0, knowledge_graph_1.getFullTaskContext)(kg, options.task);
2045
+ const ctxLines = ['## 🔗 任务关联上下文(知识图谱)', ''];
2046
+ if (ctx.requirement)
2047
+ ctxLines.push(`- **上游需求**: ${ctx.requirement.title} (${ctx.requirement.file})`);
2048
+ if (ctx.parentTask)
2049
+ ctxLines.push(`- **父任务**: ${ctx.parentTask.title}`);
2050
+ if (ctx.dependsOn.length > 0)
2051
+ ctxLines.push(`- **依赖任务**(需先完成): ${ctx.dependsOn.map(d => d.title).join(', ')}`);
2052
+ if (ctx.downstreamTasks.length > 0)
2053
+ ctxLines.push(`- **下游任务**(依赖本任务): ${ctx.downstreamTasks.map(d => d.title).join(', ')}`);
2054
+ if (ctx.relatedSpecs.length > 0) {
2055
+ ctxLines.push(`- **关联规格文档**:`);
2056
+ for (const spec of ctx.relatedSpecs) {
2057
+ ctxLines.push(` - ${spec.title}: \`${spec.file}\``);
2020
2058
  }
2021
- extraSpecs.push({
2022
- name: '🔗 任务关联上下文',
2023
- path: 'kg-context.md',
2024
- content: ctxLines.join('\n'),
2025
- });
2026
2059
  }
2027
- }
2028
- catch { /* 知识图谱加载失败不阻断 */ }
2029
- }
2030
- // v8.3.123+: analyze 时注入知识图谱迭代级上下文
2031
- // v8.3.125+: 自动检查过期并刷新
2032
- if (command === 'analyze' && options.iteration) {
2033
- try {
2034
- const kg = await (0, knowledge_graph_1.loadFreshKnowledgeGraph)(cwd, options.iteration);
2035
- if (kg) {
2036
- const compactCtx = (0, context_builder_1.buildCompactContext)(kg, {});
2037
- if (compactCtx) {
2038
- extraSpecs.push({
2039
- name: '🧠 知识图谱摘要',
2040
- path: 'kg-summary.md',
2041
- content: compactCtx,
2042
- });
2043
- }
2060
+ if (ctx.dependencyChain.length > 0) {
2061
+ ctxLines.push(`- **依赖链路**: ${ctx.dependencyChain.map(c => c.taskName).join(' → ')}`);
2044
2062
  }
2063
+ extraSpecs.push({
2064
+ name: '🔗 任务关联上下文',
2065
+ path: 'kg-context.md',
2066
+ content: ctxLines.join('\n'),
2067
+ });
2045
2068
  }
2046
- catch { /* 知识图谱加载失败不阻断 */ }
2047
2069
  }
2048
- // 按匹配得分排序注入源码,并附加匹配原因
2049
- const sortedMatches = [...codeMatches].sort((a, b) => b.score - a.score);
2050
- for (const match of sortedMatches.slice(0, sourceMaxFiles)) {
2051
- const content = sourceContents[match.file];
2052
- if (!content)
2053
- continue;
2054
- const reasons = [];
2055
- if (match.score >= 50)
2056
- reasons.push(`@spec 注释直接关联 (score:${match.score})`);
2057
- else if (match.score >= 40)
2058
- reasons.push(`知识图谱 source-file 实体关联 (score:${match.score})`);
2059
- else if (match.score >= 35)
2060
- reasons.push(`业务模块 codeEntities 关联 (score:${match.score})`);
2061
- else if (match.score >= 25)
2062
- reasons.push(`Spec 技术关键词匹配 (score:${match.score})`);
2063
- else
2064
- reasons.push(`关键词匹配 (score:${match.score})`);
2065
- if (match.exports.length > 0)
2066
- reasons.push(`导出: ${match.exports.join(', ')}`);
2067
- if (match.apis.length > 0)
2068
- reasons.push(`API: ${match.apis.join(', ')}`);
2069
- extraSpecs.push({
2070
- name: `🔍 ${(0, path_1.basename)(match.file)}`,
2071
- path: match.file,
2072
- content: `## 关联源码: ${match.file}\n> **匹配原因**: ${reasons.join(' | ')}\n\n\`\`\`${match.file.split('.').pop() || 'ts'}\n${content}\n\`\`\``,
2073
- });
2074
- }
2075
- logger_1.logger?.info?.(` 💻 深入源码: ${sourceKeys.length} 个完整文件已注入 (${Math.round(Object.values(sourceContents).reduce((a, c) => a + c.length, 0) / 1000)}K 字符)`);
2076
- // v8.3.125+: 可选阅读清单 — 让宿主 AI 按需深入读取未预加载的关联文件
2077
- // 解决 "8文件/60KB 限制导致大型功能读不全" 的问题
2078
- const unreadMatches = sortedMatches.slice(sourceMaxFiles);
2079
- if (unreadMatches.length > 0) {
2080
- const optionalLines = [
2081
- '## 📖 可选阅读清单(宿主 AI 按需读取)',
2082
- '> 以下文件与当前任务高度相关,但因 Token 预算限制未完整预加载到上下文中。',
2083
- '> 如果你的 IDE(Cursor / Claude Code / Windsurf)支持文件读取,可按需打开以下文件深入理解业务逻辑:',
2084
- '',
2085
- ];
2086
- for (const match of unreadMatches.slice(0, 15)) {
2087
- const reasons = [];
2088
- if (match.score >= 50)
2089
- reasons.push('@spec 注释直接关联');
2090
- else if (match.score >= 40)
2091
- reasons.push('知识图谱关联');
2092
- else if (match.score >= 35)
2093
- reasons.push('业务模块关联');
2094
- else if (match.score >= 25)
2095
- reasons.push('技术关键词匹配');
2096
- else
2097
- reasons.push('关键词匹配');
2098
- if (match.exports.length > 0)
2099
- reasons.push(`导出 ${match.exports.join(', ')}`);
2100
- if (match.apis.length > 0)
2101
- reasons.push(`API ${match.apis.join(', ')}`);
2102
- optionalLines.push(`- \`${match.file}\` — ${reasons.join(' | ')} (score:${match.score})`);
2103
- }
2104
- if (unreadMatches.length > 15) {
2105
- optionalLines.push(`\n... 还有 ${unreadMatches.length - 15} 个关联文件未列出`);
2070
+ catch { /* 知识图谱加载失败不阻断 */ }
2071
+ }
2072
+ // v8.3.123+: analyze 时注入知识图谱迭代级上下文
2073
+ // v8.3.125+: 自动检查过期并刷新
2074
+ if (command === 'analyze' && options.iteration) {
2075
+ try {
2076
+ const kg = await (0, knowledge_graph_1.loadFreshKnowledgeGraph)(cwd, options.iteration);
2077
+ if (kg) {
2078
+ const compactCtx = (0, context_builder_1.buildCompactContext)(kg, {});
2079
+ if (compactCtx) {
2080
+ extraSpecs.push({
2081
+ name: '🧠 知识图谱摘要',
2082
+ path: 'kg-summary.md',
2083
+ content: compactCtx,
2084
+ });
2085
+ }
2106
2086
  }
2107
- optionalLines.push('\n---');
2108
- optionalLines.push('💡 **使用方式**: 如果上述预加载源码不足以理解实现细节,请打开对应文件查看完整代码。');
2109
- extraSpecs.push({
2110
- name: '📖 可选阅读清单',
2111
- path: 'optional-reading.md',
2112
- content: optionalLines.join('\n'),
2113
- });
2114
- logger_1.logger?.info?.(` 📖 可选阅读清单: ${unreadMatches.length} 个关联文件(宿主 AI 可按需读取)`);
2115
2087
  }
2088
+ catch { /* 知识图谱加载失败不阻断 */ }
2089
+ }
2090
+ // v8.3.3xx+: 第三层——CLI 只导航不拼全文。
2091
+ // 注入「相关源码阅读清单 + 宿主 AI 按需深读协议」,
2092
+ // 全文阅读交给宿主 AI 的 Read / Grep / LSP 工具按需执行。
2093
+ const sortedMatches = [...codeMatches].sort((a, b) => b.score - a.score);
2094
+ const listLines = [
2095
+ '## 📖 相关源码阅读清单(CLI 导航 + 宿主 AI 按需深读)',
2096
+ '',
2097
+ '> 以下文件由代码索引相关性导航得出,按匹配得分降序排列。',
2098
+ '> **CLI 不拼入源码全文**——一个端动辄几百个文件,全量拼入会撑爆上下文或截断失真。',
2099
+ '> 请用你的 Read / Grep / LSP 工具按需打开深读。',
2100
+ '',
2101
+ '| 优先级 | 文件 | 匹配原因 | 代码线索 |',
2102
+ '|:---|:---|:---|:---|',
2103
+ ];
2104
+ for (const match of sortedMatches.slice(0, 15)) {
2105
+ const reasons = [];
2106
+ if (match.score >= 50)
2107
+ reasons.push('@spec 注释直接关联');
2108
+ else if (match.score >= 40)
2109
+ reasons.push('知识图谱 source-file 实体关联');
2110
+ else if (match.score >= 35)
2111
+ reasons.push('业务模块 codeEntities 关联');
2112
+ else if (match.score >= 25)
2113
+ reasons.push('Spec 技术关键词匹配');
2114
+ else
2115
+ reasons.push('关键词匹配');
2116
+ const clues = [];
2117
+ if (match.exports.length > 0)
2118
+ clues.push(`导出 \`${match.exports.slice(0, 4).join(' ')}\``);
2119
+ if (match.apis.length > 0)
2120
+ clues.push(`API \`${match.apis.slice(0, 3).join(' ')}\``);
2121
+ const priority = match.score >= 40 ? 'P0' : (match.score >= 25 ? 'P1' : 'P2');
2122
+ listLines.push(`| ${priority} | \`${match.file}\` | ${reasons.join(' / ')} (score:${match.score}) | ${clues.join(';') || '—'} |`);
2123
+ }
2124
+ if (sortedMatches.length > 15) {
2125
+ listLines.push(`| … | 其余 ${sortedMatches.length - 15} 个关联文件 | 运行 \`speccore code-index --show\` 查看全量索引 | — |`);
2116
2126
  }
2127
+ listLines.push('');
2128
+ listLines.push('### 🔍 深读协议(宿主 AI 执行)');
2129
+ listLines.push('');
2130
+ listLines.push('1. **先读 P0**:用 Read 打开优先级最高的 2-3 个文件,理解核心实现与数据结构');
2131
+ listLines.push('2. **符号追踪**:对清单中的导出符号用 LSP(goToDefinition / findReferences)追踪定义与调用点');
2132
+ listLines.push('3. **路径检索**:对 API 路径 / 表名用 Grep 全仓检索关联实现');
2133
+ listLines.push('4. **按需扩展**:从已读文件的 import 出发追踪依赖链;任务涉及哪个文件,就深读哪个文件');
2134
+ listLines.push('5. **禁止要求拼全文**:不要要求 CLI 把源码全文塞进 prompt——CLI 的职责是导航,全文阅读由你按需执行');
2135
+ listLines.push('');
2136
+ extraSpecs.push({
2137
+ name: '📖 相关源码阅读清单',
2138
+ path: 'reading-list.md',
2139
+ content: listLines.join('\n'),
2140
+ });
2141
+ logger_1.logger?.info?.(` 📖 相关源码阅读清单: ${sortedMatches.length} 个关联文件(CLI 导航,宿主 AI 按需深读)`);
2117
2142
  }
2118
2143
  }
2119
2144
  catch (e) {
@@ -2214,6 +2239,9 @@ async function buildPrompt(command, options) {
2214
2239
  feature: options.analyzeOptions.feature,
2215
2240
  };
2216
2241
  const rawPrompt = await buildMultiDocPrompt('analyze', ctx, options.analyzeOptions);
2242
+ // v8.3.3xx+: rawPrompt 路由会丢弃常规 extraSpecs,阅读清单(CLI 导航 + 宿主 AI 深读)单独追加
2243
+ const readingList = extraSpecs.find(s => s.name === '📖 相关源码阅读清单');
2244
+ const finalRawPrompt = readingList ? `${rawPrompt}\n\n${readingList.content}` : rawPrompt;
2217
2245
  return {
2218
2246
  marker: '[SPECCORE_PROMPT]',
2219
2247
  version: '1.0',
@@ -2228,7 +2256,7 @@ async function buildPrompt(command, options) {
2228
2256
  extraSpecs: [],
2229
2257
  instruction: '',
2230
2258
  outputHint: '请返回 Markdown 格式的分析结果',
2231
- rawPrompt,
2259
+ rawPrompt: finalRawPrompt,
2232
2260
  subagent: options.subagent || DEFAULT_SUBAGENT_MAP[command],
2233
2261
  };
2234
2262
  }