speccore 6.18.4 → 6.31.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 (76) hide show
  1. package/.agents/skills/spec-execute/SKILL.md +7 -3
  2. package/dist/cli.js +1 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/analyze.d.ts +1 -0
  5. package/dist/commands/analyze.d.ts.map +1 -1
  6. package/dist/commands/analyze.js +234 -47
  7. package/dist/commands/analyze.js.map +1 -1
  8. package/dist/commands/context-output.d.ts.map +1 -1
  9. package/dist/commands/context-output.js +19 -10
  10. package/dist/commands/context-output.js.map +1 -1
  11. package/dist/commands/doc2spec.js +1 -1
  12. package/dist/commands/doc2spec.js.map +1 -1
  13. package/dist/commands/execute.d.ts.map +1 -1
  14. package/dist/commands/execute.js +373 -91
  15. package/dist/commands/execute.js.map +1 -1
  16. package/dist/commands/global-status.js +2 -2
  17. package/dist/commands/global-status.js.map +1 -1
  18. package/dist/commands/import.d.ts +1 -1
  19. package/dist/commands/import.js +2 -4
  20. package/dist/commands/import.js.map +1 -1
  21. package/dist/commands/init.d.ts.map +1 -1
  22. package/dist/commands/init.js +59 -296
  23. package/dist/commands/init.js.map +1 -1
  24. package/dist/commands/iteration/create.js +7 -10
  25. package/dist/commands/iteration/create.js.map +1 -1
  26. package/dist/commands/iteration/split.js +296 -131
  27. package/dist/commands/iteration/split.js.map +1 -1
  28. package/dist/commands/iteration-from-global.js +1 -1
  29. package/dist/commands/retro.d.ts.map +1 -1
  30. package/dist/commands/retro.js +60 -3
  31. package/dist/commands/retro.js.map +1 -1
  32. package/dist/commands/status-panel.d.ts.map +1 -1
  33. package/dist/commands/status-panel.js +24 -8
  34. package/dist/commands/status-panel.js.map +1 -1
  35. package/dist/commands/synthesize.d.ts +2 -2
  36. package/dist/commands/synthesize.js +146 -196
  37. package/dist/commands/synthesize.js.map +1 -1
  38. package/dist/commands/task/new.js +43 -26
  39. package/dist/commands/task/new.js.map +1 -1
  40. package/dist/commands/update.d.ts.map +1 -1
  41. package/dist/commands/update.js +7 -1
  42. package/dist/commands/update.js.map +1 -1
  43. package/dist/core/ai-context-generator.js +1 -1
  44. package/dist/core/analyze-engine.d.ts.map +1 -1
  45. package/dist/core/analyze-engine.js +307 -9
  46. package/dist/core/analyze-engine.js.map +1 -1
  47. package/dist/core/execution-state.d.ts +24 -0
  48. package/dist/core/execution-state.d.ts.map +1 -1
  49. package/dist/core/execution-state.js +135 -0
  50. package/dist/core/execution-state.js.map +1 -1
  51. package/dist/core/knowledge-graph.d.ts.map +1 -1
  52. package/dist/core/knowledge-graph.js +190 -65
  53. package/dist/core/knowledge-graph.js.map +1 -1
  54. package/dist/core/next-steps.js +2 -2
  55. package/dist/core/next-steps.js.map +1 -1
  56. package/dist/core/prompt-builder.d.ts.map +1 -1
  57. package/dist/core/prompt-builder.js +43 -22
  58. package/dist/core/prompt-builder.js.map +1 -1
  59. package/dist/core/quality-audit.d.ts +41 -0
  60. package/dist/core/quality-audit.d.ts.map +1 -0
  61. package/dist/core/quality-audit.js +345 -0
  62. package/dist/core/quality-audit.js.map +1 -0
  63. package/dist/core/rag-engine.d.ts.map +1 -1
  64. package/dist/core/rag-engine.js +38 -6
  65. package/dist/core/rag-engine.js.map +1 -1
  66. package/dist/core/spec-merger.d.ts.map +1 -1
  67. package/dist/core/spec-merger.js +22 -27
  68. package/dist/core/spec-merger.js.map +1 -1
  69. package/dist/core/validator.d.ts.map +1 -1
  70. package/dist/core/validator.js +43 -0
  71. package/dist/core/validator.js.map +1 -1
  72. package/dist/core/verify-engine.d.ts +1 -1
  73. package/dist/core/verify-engine.d.ts.map +1 -1
  74. package/dist/core/verify-engine.js +71 -8
  75. package/dist/core/verify-engine.js.map +1 -1
  76. package/package.json +1 -1
@@ -44,7 +44,6 @@ const task_utils_1 = require("../../utils/task-utils");
44
44
  const next_steps_1 = require("../../core/next-steps");
45
45
  const readline_1 = require("readline");
46
46
  const prompt_builder_1 = require("../../core/prompt-builder");
47
- const platform_registry_1 = require("../../core/platform-registry");
48
47
  const index_guard_1 = require("../../core/index-guard");
49
48
  /** 将名称转为目录安全的短 slug(2-4 词) */
50
49
  function slugify(name) {
@@ -525,7 +524,7 @@ async function iterationSplitCommand(options) {
525
524
  const specDir2 = (0, path_1.join)(iterationDir, '020-specs');
526
525
  const specContents = [];
527
526
  // 读取全局层扁平文件(迭代级跨端综合)
528
- for (const f of ['ANALYSIS.md', 'TECH.md', 'TEST.md', 'REVIEW.md', 'RISK.md', 'DEPS.md', 'MONITOR.md']) {
527
+ for (const f of ['ANALYSIS.md', 'TECH.md', 'TEST.md', 'REVIEW.md', 'RISK.md', 'DEPS.md', 'MONITOR.md', 'UI_SPEC.md']) {
529
528
  const fp = (0, path_1.join)(specDir2, f);
530
529
  if (await (0, fs_extra_1.pathExists)(fp)) {
531
530
  const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
@@ -891,58 +890,55 @@ async function createTaskFromSection(iterationDir, taskId, section, allPlatforms
891
890
  for (const p of taskPlatforms) {
892
891
  subtaskIdMap.set(p, generateSubtaskId(taskNum, p));
893
892
  }
894
- // ── 1. 元信息目录 ──
895
- await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '.meta'));
896
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.meta', 'type'), taskType);
897
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.meta', 'status'), 'todo');
898
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.meta', 'owner'), owner);
899
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.meta', 'created-at'), today);
900
- // ── 1a. Git 配置模板(默认继承迭代级配置) ──
901
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.meta', 'git-config'), `# 任务级 Git 配置
902
- # 以下配置覆盖迭代级 PROJECT_GRAPH.md,未配置项自动继承上一级。
903
- # 修改时去掉注释符 #,填入具体值即可。
893
+ // ── 1. 任务目录指引(功能模块分组) ──
894
+ const isResearch = taskType === 'research';
895
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, 'README.md'), isResearch
896
+ ? `# ${section.name}
904
897
 
905
- # 源分支: 继承迭代配置
906
- # 分支前缀: 继承迭代配置
907
- # 分支格式: 继承迭代配置
908
- # 自动拉取: 继承迭代配置
909
- # 远程名称: 继承迭代配置
910
- `);
911
- // ── 2. 任务目录指引 ──
912
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, 'README.md'), `# ${section.name}
913
-
914
- > 任务模块分组 — 按功能单元组织,各端并行开发
898
+ > 技术调研任务 — 产出调研文档,不产出代码
915
899
 
916
900
  ## 目录结构
917
901
 
918
902
  \`\`\`
919
903
  ${taskId}/
920
- ├── README.md <-- 本文件
921
- ├── .meta/ <-- 任务元信息(type/status/owner/created-at
922
- ├── _shared/ <-- 共享规格(所有端共用)
923
- ├── CONTEXT.md <-- 任务上下文(来源追溯 + 原始描述 + 关联任务)
924
- ├── REQ.md <-- 需求描述(API + 数据模型 + 业务规则)
925
- │ ├── TECH.md <-- 技术方案(架构/接口设计/核心逻辑)
926
- │ ├── SCHEMA.md <-- 数据库设计(如有)
927
- │ ├── API_CONTRACT.yaml <-- API 契约
928
- │ └── CHANGELOG.md <-- 变更记录
929
- ├── {端名}/ <-- 各端独立子任务(backend/admin/web/h5/...)
930
- │ ├── TASK.md <-- 子任务追踪(含子任务表、负责人、状态)
931
- │ ├── src/ <-- 源码实现
932
- │ └── tests/ <-- 测试用例
933
- ├── 99-artifacts/ <-- 执行产出
934
- │ ├── TEST.md <-- 测试用例
935
- │ ├── REVIEW.md <-- 评审清单
936
- │ ├── DEPLOY.md <-- 部署清单
937
- │ └── ...
938
- └── .issues.md <-- 问题追踪
904
+ ├── README.md 本文件(调研任务说明)
905
+ ├── _shared/ 共享上下文(CONTEXT.md
906
+ ├── 00-specs/ 核心规格(REQ.md/TECH.md)
907
+ ├── RESEARCH.md 调研报告
908
+ ├── COMPARISON.md 方案对比
909
+ └── .issues.md 问题追踪
939
910
  \`\`\`
940
911
 
941
- ## 子任务命名规则
912
+ ## AI 执行时读取规则
913
+
914
+ 运行 \`speccore execute -t ${taskId}\` 时:
915
+ - 读取 \`00-specs/REQ.md\` — 调研目标
916
+ - 读取 \`_shared/CONTEXT.md\` — 任务上下文
917
+ - 产出写入 \`RESEARCH.md\` 和 \`COMPARISON.md\`
918
+ `
919
+ : `# ${section.name}
942
920
 
943
- 每个端的子任务 ID 格式: \`Task-{父任务名}-{端名}-{hash}\`
921
+ > 功能模块分组 聚合相关子任务,共享规格与契约
944
922
 
945
- | 子任务 ID | 所属端 | 负责人 | 状态 |
923
+ ## 目录结构
924
+
925
+ \`\`\`
926
+ ${taskId}/
927
+ ├── README.md ← 本文件
928
+ ├── _shared/ ← 共享契约(API_CONTRACT.yaml + CONTEXT.md)
929
+ ├── 00-specs/ ← 模块级核心规格(REQ/TECH/SCHEMA/CHANGELOG)
930
+ ├── 10-backend/ ← 后端
931
+ │ └── {服务名}/ ← 端(如 api)
932
+ │ └── {子任务}/ ← 执行单元(.meta/TASK.md/src/tests + 产出)
933
+ ├── 20-frontend/ ← 前端
934
+ │ └── {端名}/ ← 端(如 h5/admin)
935
+ │ └── {子任务}/ ← 执行单元(.meta/TASK.md/src/tests + 前端设计 + 产出)
936
+ └── .issues.md ← 问题追踪
937
+ \`\`\`
938
+
939
+ ## 子任务列表
940
+
941
+ | 子任务 ID | 所属端/服务 | 负责人 | 状态 |
946
942
  | :--- | :--- | :--- | :--- |
947
943
  ${taskPlatforms.map((p) => `| ${subtaskIdMap.get(p)} | ${p} | ${owner} | 🔲 待开发 |`).join('\n')}
948
944
 
@@ -951,35 +947,37 @@ ${taskPlatforms.map((p) => `| ${subtaskIdMap.get(p)} | ${p} | ${owner} | 🔲
951
947
  运行 \`speccore execute -t ${taskId} --platform {端}\` 时:
952
948
 
953
949
  ### 必读(自动嵌入)
950
+ - \`00-specs/REQ.md\` — 模块需求描述
951
+ - \`00-specs/TECH.md\` — 模块技术方案
954
952
  - \`_shared/CONTEXT.md\` — 任务上下文(来源 + 关联)
955
- - \`_shared/REQ.md\` — 共享需求
956
- - \`_shared/TECH.md\` — 共享技术方案
957
- - \`{端}/TASK.md\` — 该端子任务详情
953
+ - \`_shared/API_CONTRACT.yaml\` — API 契约
954
+ - \`10-backend/{端}/{子任务}/TASK.md\` — 子任务详情
955
+ - \`.meta/type\` + \`.meta/status\` — 子任务元信息
958
956
 
959
957
  ### 参考(按需读取)
960
- - \`020-specs/\` 下的迭代全局文档(架构、技术方案等)
958
+ - \`020-specs/\` 下的迭代全局文档
961
959
  - \`.speccore/GLOBAL/\` 下的全局知识库
962
960
 
963
961
  ### 不会被读取
964
- - \`{}/src/\` 和 \`{}/tests/\` — 这是 AI **输出**代码的地方
965
- - \`99-artifacts/\` — 执行完成后自动更新
962
+ - \`{子任务}/src/\` 和 \`{子任务}/tests/\` — AI **输出**代码的地方
963
+ - \`{子任务}/TEST.md\` 等执行产出 — 执行完成后自动更新
966
964
  `);
967
- // ── 3. 共享契约 ──
965
+ // ── 3. 共享契约 + 核心规格目录 ──
968
966
  await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '_shared'));
967
+ await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '00-specs'));
969
968
  const contractYaml = generateApiContract(section);
970
969
  if (contractYaml) {
971
970
  await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml'), contractYaml);
972
971
  }
973
- // ── 3. 共享规格 _shared/(所有端共用) ──
974
- await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '_shared'));
972
+ // ── 4. 核心规格写入 00-specs/(REQ/TECH/SCHEMA/CHANGELOG) ──
975
973
  const acItems = generateAcceptanceCriteria(section);
976
974
  const aiReqContent = section._reqContent;
977
975
  // REQ.md: 优先用 AI 生成的实际内容,回退到模板
978
976
  if (aiReqContent && aiReqContent.length > 50) {
979
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'REQ.md'), `# ${section.name}\n\n${aiReqContent}\n\n## 验收标准\n\n${acItems}\n`);
977
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'REQ.md'), `# ${section.name}\n\n${aiReqContent}\n\n## 验收标准\n\n${acItems}\n`);
980
978
  }
981
979
  else {
982
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'REQ.md'), `# ${section.name}\n\n## 需求描述\n\n${section.content}\n\n## 验收标准\n\n${acItems}\n`);
980
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'REQ.md'), `# ${section.name}\n\n## 需求描述\n\n${section.content}\n\n## 验收标准\n\n${acItems}\n`);
983
981
  }
984
982
  const apiLines = section.content.split('\n').filter(l => l.includes('| GET') || l.includes('| POST') || l.includes('| PUT') || l.includes('| DELETE') || l.includes('| PATCH'));
985
983
  const apiDesc = apiLines.length > 0 ? apiLines.map(l => `- ${l.trim()}`).join('\n') : '- 待补充(从 REQ.md 提取接口列表)';
@@ -988,13 +986,13 @@ ${taskPlatforms.map((p) => `| ${subtaskIdMap.get(p)} | ${p} | ${owner} | 🔲
988
986
  const specTechContent = extractTaskTechContent(specContents, section);
989
987
  // TECH.md: 优先 AI 生成 → 回退 analyze 提取 → 回退模板
990
988
  if (aiTechContent && aiTechContent.length > 50) {
991
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'TECH.md'), `# ${section.name} - 技术方案\n\n${aiTechContent}\n`);
989
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'TECH.md'), `# ${section.name} - 技术方案\n\n${aiTechContent}\n`);
992
990
  }
993
991
  else if (specTechContent && specTechContent.length > 30) {
994
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'TECH.md'), `# ${section.name} - 技术方案\n\n> 来源: analyze → TECH.md(自动提取)\n\n${specTechContent}\n`);
992
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'TECH.md'), `# ${section.name} - 技术方案\n\n> 来源: analyze → TECH.md(自动提取)\n\n${specTechContent}\n`);
995
993
  }
996
994
  else {
997
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'TECH.md'), `# ${section.name} - 技术方案
995
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'TECH.md'), `# ${section.name} - 技术方案
998
996
 
999
997
  > ⚠️ 本文档由 split 自动生成框架,AI 执行时会自动填充。
1000
998
 
@@ -1020,13 +1018,24 @@ ${apiDesc}
1020
1018
  - 单元测试覆盖核心 Service 逻辑
1021
1019
  - 接口测试覆盖正常/异常/边界
1022
1020
  - 自动化测试通过后方可提 PR
1021
+
1022
+ ## 6. 前端 UI 设计
1023
+ <!-- AI-FILL: 页面结构、组件清单、状态管理、路由设计 -->
1024
+ ### 6.1 页面/路由
1025
+ <!-- AI-FILL: 路由表 -->
1026
+
1027
+ ### 6.2 组件清单
1028
+ <!-- AI-FILL: 组件列表及职责 -->
1029
+
1030
+ ### 6.3 状态管理
1031
+ <!-- AI-FILL: 状态字段及枚举值,需与后端保持一致 -->
1023
1032
  `);
1024
1033
  }
1025
1034
  if (section.content.match(/数据库|数据表|表结构|DDL|ALTER|建表|索引/)) {
1026
1035
  const schemaMaterial = extractRelevantSection(specContents['TECH.md'] || '', section.name, 'DDL 建表 表结构 索引 CREATE TABLE');
1027
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'SCHEMA.md'), generateSchemaTemplate(section, schemaMaterial));
1036
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'SCHEMA.md'), generateSchemaTemplate(section, schemaMaterial));
1028
1037
  }
1029
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'CHANGELOG.md'), `# ${section.name} - 变更记录
1038
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'CHANGELOG.md'), `# ${section.name} - 变更记录
1030
1039
 
1031
1040
  | 时间 | 版本 | 变更内容 | 变更人 |
1032
1041
  | :--- | :--- | :--- | :--- |
@@ -1034,100 +1043,223 @@ ${apiDesc}
1034
1043
  `);
1035
1044
  // API_CONTRACT.yaml 统一放 _shared/
1036
1045
  if (apiLines.length > 0) {
1046
+ // 从 section 内容提取每个 API 的请求/响应字段
1047
+ const sectionContent = section.content || '';
1048
+ const apiFieldMap = {};
1049
+ for (const l of apiLines) {
1050
+ const parts = l.split('|').map(p => p.trim()).filter(Boolean);
1051
+ const path = parts[1] || '/api/unknown';
1052
+ const apiIdx = sectionContent.indexOf(path);
1053
+ if (apiIdx >= 0) {
1054
+ const after = sectionContent.slice(apiIdx, apiIdx + 800);
1055
+ const fields = [];
1056
+ const fieldRegex = /[`']([a-zA-Z]\w{1,25})[`']\s*[((]?[::]?\s*(?:类型[::])?\s*([\u4e00-\u9fa5A-Za-z]+)/g;
1057
+ let fm;
1058
+ while ((fm = fieldRegex.exec(after)) !== null) {
1059
+ if (!['api', 'data', 'code', 'message', 'msg', 'status', 'ok', 'err'].includes(fm[1].toLowerCase())) {
1060
+ fields.push(`${fm[1]}: ${fm[2]}`);
1061
+ }
1062
+ }
1063
+ if (fields.length > 0)
1064
+ apiFieldMap[path] = fields.slice(0, 10);
1065
+ }
1066
+ }
1037
1067
  const contracts = apiLines.map(l => {
1038
1068
  const parts = l.split('|').map(p => p.trim()).filter(Boolean);
1039
1069
  const method = (parts[0] || 'GET').toUpperCase();
1040
1070
  const path = parts[1] || '/api/unknown';
1041
1071
  const desc = parts[2] || path;
1042
- return ` ${path}:\n ${method}:\n summary: "${desc}"\n description: "<!-- AI-FILL -->"`;
1072
+ let yaml = ` ${path}:\n ${method}:\n summary: "${desc}"`;
1073
+ if (apiFieldMap[path]) {
1074
+ yaml += `\n fields:`;
1075
+ for (const f of apiFieldMap[path]) {
1076
+ yaml += `\n - "${f}"`;
1077
+ }
1078
+ }
1079
+ yaml += `\n responses:\n "200":\n description: Success`;
1080
+ return yaml;
1043
1081
  }).join('\n');
1044
1082
  await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml'), `# ${section.name} - API Contract\n# Auto-generated from split\n\npaths:\n${contracts}\n`);
1045
1083
  }
1046
- // ── 4. 各端独立子任务目录 ──
1047
- const sharedReqContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '_shared', 'REQ.md'), 'utf-8');
1048
- const sharedTechContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '_shared', 'TECH.md'), 'utf-8');
1049
- for (const platform of taskPlatforms) {
1050
- const subtaskId = subtaskIdMap.get(platform);
1051
- const platformDir = (0, path_1.join)(taskDir, platform);
1052
- await (0, fs_extra_1.ensureDir)((0, path_1.join)(platformDir, 'src'));
1053
- await (0, fs_extra_1.ensureDir)((0, path_1.join)(platformDir, 'tests'));
1054
- // 各端 TASK.md — 含子任务表、负责人、状态
1055
- const isBackend = platform === 'backend' || platform.startsWith('后台');
1056
- const platformLabel = isBackend ? '后端' : platform;
1057
- const subtaskHours = section._hoursByPlatform?.[platform] || Math.ceil(complexity.estimatedHours / taskPlatforms.length);
1058
- await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'TASK.md'), `# ${section.name} — ${platformLabel}
1084
+ // ── 4. 子任务目录(按任务类型差异化) ──
1085
+ // 预加载执行产出模板材料(每个子任务都需要)
1086
+ const testMaterial = extractRelevantSection(specContents['TEST.md'] || '', section.name);
1087
+ const riskMaterial = extractRelevantSection(specContents['RISK.md'] || '', section.name);
1088
+ const depsMaterial = extractRelevantSection(specContents['DEPS.md'] || '', section.name);
1089
+ const monitorMaterial = extractRelevantSection(specContents['MONITOR.md'] || '', section.name);
1090
+ // ── 4a. research 类型:不创建平台/子任务目录,直接产出调研文档 ──
1091
+ if (isResearch) {
1092
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, 'RESEARCH.md'), `# ${section.name} 调研报告
1093
+
1094
+ ## 调研目标
1095
+ ${section.content}
1096
+
1097
+ ## 调研结果
1098
+ <!-- AI-FILL: 调研发现、技术选型分析 -->
1099
+
1100
+ ## 推荐方案
1101
+ <!-- AI-FILL: 推荐方案及理由 -->
1102
+
1103
+ ## 风险与建议
1104
+ <!-- AI-FILL -->
1105
+ `);
1106
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, 'COMPARISON.md'), `# ${section.name} — 方案对比
1107
+
1108
+ | 维度 | 方案 A | 方案 B | 方案 C |
1109
+ |:---|:---|:---|:---|
1110
+ | 技术栈 | | | |
1111
+ | 开发成本 | | | |
1112
+ | 性能 | | | |
1113
+ | 可维护性 | | | |
1114
+ | 社区生态 | | | |
1115
+
1116
+ ## 综合评价
1117
+ <!-- AI-FILL -->
1118
+ `);
1119
+ }
1120
+ else {
1121
+ // ── 4b. feature/bugfix/refactor:三级嵌套(前后端大类 → 端 → 子任务) ──
1122
+ // 分类平台:后端 vs 前端
1123
+ const isBackendPlatform = (p) => p === 'backend' || p.startsWith('后台');
1124
+ const getServiceName = (p) => isBackendPlatform(p) && p === 'backend' ? 'api' : p;
1125
+ const backendPlatforms = taskPlatforms.filter(isBackendPlatform);
1126
+ const frontendPlatforms = taskPlatforms.filter((p) => !isBackendPlatform(p));
1127
+ // 创建子任务的通用函数
1128
+ const createSubtask = async (subtaskDir, subtaskId, platformName, platformLabel, isBk, hours) => {
1129
+ // .meta/
1130
+ await (0, fs_extra_1.ensureDir)((0, path_1.join)(subtaskDir, '.meta'));
1131
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, '.meta', 'type'), taskType);
1132
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, '.meta', 'status'), 'todo');
1133
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, '.meta', 'owner'), owner);
1134
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, '.meta', 'created-at'), today);
1135
+ // git-config
1136
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, '.meta', 'git-config'), `# 子任务级 Git 配置(${platformLabel})
1137
+ # 以下配置覆盖迭代级 PROJECT_GRAPH.md,未配置项自动继承上一级。
1138
+ # 修改时去掉注释符 #,填入具体值即可。
1139
+
1140
+ # 源分支: 继承迭代配置
1141
+ # 分支前缀: 继承迭代配置
1142
+ # 分支格式: 继承迭代配置
1143
+ # 自动拉取: 继承迭代配置
1144
+ # 远程名称: 继承迭代配置
1145
+ `);
1146
+ // src/ + tests/
1147
+ await (0, fs_extra_1.ensureDir)((0, path_1.join)(subtaskDir, 'src'));
1148
+ await (0, fs_extra_1.ensureDir)((0, path_1.join)(subtaskDir, 'tests'));
1149
+ // TASK.md
1150
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'TASK.md'), `# ${section.name} — ${platformLabel}
1059
1151
 
1060
1152
  ## 子任务信息
1061
1153
  - **子任务 ID**: \`${subtaskId}\`
1062
1154
  - **所属模块**: \`${taskId}\`
1063
- - **端**: ${platform}
1155
+ - **端**: ${platformName}
1064
1156
  - **负责人**: ${owner}
1065
- - **状态**: 🔲 待开发
1066
- - **预计耗时**: ${subtaskHours}h
1067
-
1068
- ## 子任务列表
1069
-
1070
- | 子任务 ID | 名称 | 负责人 | 状态 | 预估工时 | 依赖 |
1071
- | :--- | :--- | :--- | :--- | :--- | :--- |
1072
- | \`${subtaskId}-01\` | ${section.name} - ${platformLabel}实现 | ${owner} | 🔲 待开发 | ${subtaskHours}h | 无 |
1157
+ - **状态**: 待开发
1158
+ - **预计耗时**: ${hours}h
1073
1159
 
1074
1160
  ## 共享规格引用
1075
- - REQ.md → ../_shared/REQ.md
1076
- - TECH.md → ../_shared/TECH.md
1161
+ - REQ.md → ../../../00-specs/REQ.md
1162
+ - TECH.md → ../../../00-specs/TECH.md
1077
1163
 
1078
1164
  ## 产出物
1079
1165
  | 产出物 | 状态 | 路径 |
1080
1166
  | :--- | :--- | :--- |
1081
- | TASK.md | ✅ | ./${platform}/TASK.md |
1082
- | src/ | ⏳ | ./${platform}/src/ |
1083
- | tests/ | ⏳ | ./${platform}/tests/ |
1167
+ | TASK.md | ✅ | ./TASK.md |
1168
+ | src/ | ⏳ | ./src/ |
1169
+ | tests/ | ⏳ | ./tests/ |
1170
+ | TEST.md | ⏳ | ./TEST.md |
1171
+ | RISK.md | ⏳ | ./RISK.md |
1084
1172
 
1085
1173
  ## 变更履历
1086
1174
  | 时间 | 变更内容 | 变更人 |
1087
1175
  | :--- | :--- | :--- |
1088
1176
  | ${today} | 创建子任务 | CLI |
1089
1177
  `);
1090
- // backend 端额外生成前端设计文档(从 analyze TECH.md 提取)
1091
- if (!isBackend) {
1092
- const feContent = extractTaskTechContent(specContents, section, platform);
1093
- await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'COMPONENT_TREE.md'), generateComponentTree(section, platform, feContent));
1094
- await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'ROUTES.md'), generateRoutesDoc(section, platform, feContent));
1095
- await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'STATE.md'), generateStateDoc(section, platform, feContent));
1096
- await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'STYLE_GUIDE.md'), generateStyleGuide(section, platform, feContent));
1178
+ // 前端设计文档(仅非 backend 端)
1179
+ if (!isBk) {
1180
+ const feContent = extractTaskTechContent(specContents, section, platformName);
1181
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'COMPONENT_TREE.md'), generateComponentTree(section, platformName, feContent));
1182
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'ROUTES.md'), generateRoutesDoc(section, platformName, feContent));
1183
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'STATE.md'), generateStateDoc(section, platformName, feContent));
1184
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'STYLE_GUIDE.md'), generateStyleGuide(section, platformName, feContent));
1185
+ }
1186
+ // 执行产出文档
1187
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'TEST.md'), generateTestOutline(section, testMaterial));
1188
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'REVIEW.md'), generateReviewChecklist(section));
1189
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'DEPLOY.md'), generateDeployChecklist(section));
1190
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'ERROR_CODES.md'), generateErrorCodes(section));
1191
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'RISK.md'), generateRiskTemplate(section, riskMaterial));
1192
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'DEPS.md'), generateDepsTemplate(section, depsMaterial));
1193
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'MONITOR.md'), generateMonitorTemplate(section, monitorMaterial));
1194
+ const adr = generateAdr(section);
1195
+ if (adr) {
1196
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(subtaskDir, 'ADR.md'), adr);
1197
+ }
1198
+ };
1199
+ // ── 后端:10-backend/{服务名}/{子任务}/ ──
1200
+ if (backendPlatforms.length > 0) {
1201
+ const backendCategoryDir = (0, path_1.join)(taskDir, '10-backend');
1202
+ for (const platform of backendPlatforms) {
1203
+ const serviceName = getServiceName(platform);
1204
+ const platformDir = (0, path_1.join)(backendCategoryDir, serviceName);
1205
+ const subtaskId = subtaskIdMap.get(platform);
1206
+ const subtaskName = slugify(section.name) || 'impl';
1207
+ const subtaskDir = (0, path_1.join)(platformDir, subtaskName);
1208
+ const subtaskHours = section._hoursByPlatform?.[platform] || Math.ceil(complexity.estimatedHours / taskPlatforms.length);
1209
+ await createSubtask(subtaskDir, subtaskId, platform, '后端', true, subtaskHours);
1210
+ }
1211
+ }
1212
+ // ── 前端:20-frontend/{端名}/{子任务}/ ──
1213
+ if (frontendPlatforms.length > 0) {
1214
+ const frontendCategoryDir = (0, path_1.join)(taskDir, '20-frontend');
1215
+ for (const platform of frontendPlatforms) {
1216
+ const platformDir = (0, path_1.join)(frontendCategoryDir, platform);
1217
+ const subtaskId = subtaskIdMap.get(platform);
1218
+ const subtaskName = slugify(section.name) || 'impl';
1219
+ const subtaskDir = (0, path_1.join)(platformDir, subtaskName);
1220
+ const subtaskHours = section._hoursByPlatform?.[platform] || Math.ceil(complexity.estimatedHours / taskPlatforms.length);
1221
+ await createSubtask(subtaskDir, subtaskId, platform, platform, false, subtaskHours);
1222
+ }
1097
1223
  }
1098
1224
  }
1099
- // 确保至少有 backend 目录(AI 未输出 backend 时自动补充)
1100
- if (!taskPlatforms.some((p) => p === 'backend' || p.startsWith('后台'))) {
1101
- await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, 'backend', 'src'));
1102
- await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, 'backend', 'tests'));
1225
+ // 确保至少有 backend 子任务(AI 未输出 backend 时自动补充,仅非 research)
1226
+ if (!isResearch && !taskPlatforms.some((p) => p === 'backend' || p.startsWith('后台'))) {
1227
+ const autoSubtaskDir = (0, path_1.join)(taskDir, '10-backend', 'api', 'impl');
1228
+ await (0, fs_extra_1.ensureDir)((0, path_1.join)(autoSubtaskDir, '.meta'));
1229
+ await (0, fs_extra_1.ensureDir)((0, path_1.join)(autoSubtaskDir, 'src'));
1230
+ await (0, fs_extra_1.ensureDir)((0, path_1.join)(autoSubtaskDir, 'tests'));
1231
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'type'), taskType);
1232
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'status'), 'todo');
1233
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'owner'), owner);
1234
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'created-at'), today);
1235
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, '.meta', 'git-config'), `# 子任务级 Git 配置(后端)
1236
+ # 以下配置覆盖迭代级 PROJECT_GRAPH.md,未配置项自动继承上一级。
1237
+ `);
1238
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'TASK.md'), `# ${section.name} — 后端(自动补充)
1239
+
1240
+ ## 子任务信息
1241
+ - **子任务 ID**: \`${taskId}-backend-auto\`
1242
+ - **所属模块**: \`${taskId}\`
1243
+ - **端**: backend
1244
+ - **负责人**: ${owner}
1245
+ - **状态**: 待开发
1246
+
1247
+ ## 共享规格引用
1248
+ - REQ.md → ../../../00-specs/REQ.md
1249
+ - TECH.md → ../../../00-specs/TECH.md
1250
+ `);
1251
+ // 自动补充的后端也需要执行产出文档
1252
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'TEST.md'), generateTestOutline(section, testMaterial));
1253
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'REVIEW.md'), generateReviewChecklist(section));
1254
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'DEPLOY.md'), generateDeployChecklist(section));
1255
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'ERROR_CODES.md'), generateErrorCodes(section));
1256
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'RISK.md'), generateRiskTemplate(section, riskMaterial));
1257
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'DEPS.md'), generateDepsTemplate(section, depsMaterial));
1258
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(autoSubtaskDir, 'MONITOR.md'), generateMonitorTemplate(section, monitorMaterial));
1103
1259
  taskPlatforms.push('backend');
1104
1260
  subtaskIdMap.set('backend', `${taskId}-backend-auto`);
1105
1261
  }
1106
- // ── 生成端注册表 _shared/PLATFORMS.md ──
1107
- const platformEntries = taskPlatforms.map((p) => ({
1108
- name: p,
1109
- subtaskId: subtaskIdMap.get(p),
1110
- owner,
1111
- }));
1112
- await (0, platform_registry_1.generatePlatformsRegistry)(taskDir, taskId, platformEntries);
1113
- // ── 5. 执行产出 99-artifacts/ ──
1114
- await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '99-artifacts'));
1115
- const testMaterial = extractRelevantSection(specContents['TEST.md'] || '', section.name);
1116
- const riskMaterial = extractRelevantSection(specContents['RISK.md'] || '', section.name);
1117
- const depsMaterial = extractRelevantSection(specContents['DEPS.md'] || '', section.name);
1118
- const monitorMaterial = extractRelevantSection(specContents['MONITOR.md'] || '', section.name);
1119
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'TEST.md'), generateTestOutline(section, testMaterial));
1120
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'REVIEW.md'), generateReviewChecklist(section));
1121
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'DEPLOY.md'), generateDeployChecklist(section));
1122
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'ERROR_CODES.md'), generateErrorCodes(section));
1123
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'RISK.md'), generateRiskTemplate(section, riskMaterial));
1124
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'DEPS.md'), generateDepsTemplate(section, depsMaterial));
1125
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'MONITOR.md'), generateMonitorTemplate(section, monitorMaterial));
1126
- const adr = generateAdr(section);
1127
- if (adr) {
1128
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'ADR.md'), adr);
1129
- }
1130
- // ── 6. 任务上下文 CONTEXT.md ──
1262
+ // 5. 任务上下文 CONTEXT.md
1131
1263
  const sourceFile = section._sourceFile || '';
1132
1264
  const topic = section._topic || slugify(section.name);
1133
1265
  // 推导 010-requirements 源路径
@@ -1153,7 +1285,7 @@ ${apiDesc}
1153
1285
  })
1154
1286
  : [];
1155
1287
  // 提取原始描述摘要(从 REQ.md 内容截取前 500 字)
1156
- const reqMdPath = (0, path_1.join)(taskDir, '_shared', 'REQ.md');
1288
+ const reqMdPath = (0, path_1.join)(taskDir, '00-specs', 'REQ.md');
1157
1289
  let originalDesc = '';
1158
1290
  if (await (0, fs_extra_1.pathExists)(reqMdPath)) {
1159
1291
  const reqContent = await (0, fs_extra_1.readFile)(reqMdPath, 'utf-8');
@@ -1161,7 +1293,7 @@ ${apiDesc}
1161
1293
  const body = reqContent.replace(/^#[^\n]*\n/, '').trim();
1162
1294
  originalDesc = body.length > 500 ? body.slice(0, 500) + '...' : body;
1163
1295
  }
1164
- await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'CONTEXT.md'), `# 任务上下文
1296
+ await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'CONTEXT.md'), `# 任务上下文
1165
1297
 
1166
1298
  ## 来源追溯
1167
1299
 
@@ -1419,8 +1551,10 @@ async function generateImpactGraph(iterationDir, sections, platforms) {
1419
1551
  const taskType = s._taskType || 'feature';
1420
1552
  const taskDir = (0, path_1.join)(iterationDir, '030-tasks', taskType, taskId);
1421
1553
  if (await (0, fs_extra_1.pathExists)(taskDir)) {
1422
- // 生成风险报告并嵌入 _shared/TECH.md(去重:只写一次)
1423
- const taskMdPath = (0, path_1.join)(taskDir, '_shared', 'TECH.md');
1554
+ // 生成风险报告并嵌入 00-specs/TECH.md(去重:只写一次)
1555
+ const taskMdPath = (await (0, fs_extra_1.pathExists)((0, path_1.join)(taskDir, '00-specs', 'TECH.md')))
1556
+ ? (0, path_1.join)(taskDir, '00-specs', 'TECH.md')
1557
+ : (0, path_1.join)(taskDir, '_shared', 'TECH.md');
1424
1558
  const riskReport = (0, risk_scorer_1.generateRiskReport)(risk);
1425
1559
  await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.risk'), riskReport);
1426
1560
  if (await (0, fs_extra_1.pathExists)(taskMdPath)) {
@@ -1572,7 +1706,9 @@ async function injectTechFromAnalysis(iterationDir, taskDir, sectionName) {
1572
1706
  const depSection = analysis.match(/### 接口依赖[\s\S]*?(?=###|$)/);
1573
1707
  if (!techSection && !dbSection && !depSection)
1574
1708
  return;
1575
- const techPath = (0, path_1.join)(taskDir, '_shared', 'TECH.md');
1709
+ const techPath = (await (0, fs_extra_1.pathExists)((0, path_1.join)(taskDir, '00-specs', 'TECH.md')))
1710
+ ? (0, path_1.join)(taskDir, '00-specs', 'TECH.md')
1711
+ : (0, path_1.join)(taskDir, '_shared', 'TECH.md');
1576
1712
  let tech = await (0, fs_extra_1.readFile)(techPath, 'utf-8');
1577
1713
  const note = '\n\n> 以下内容自动从 ANALYSIS.md 注入\n\n';
1578
1714
  if (techSection && !tech.includes(techSection[0].trim())) {
@@ -1597,6 +1733,25 @@ function generateApiContract(section) {
1597
1733
  }
1598
1734
  if (apis.length === 0)
1599
1735
  return '';
1736
+ // 从 section 内容提取每个 API 的请求/响应字段
1737
+ const fullContent = section.content || '';
1738
+ const apiFieldMap = {};
1739
+ for (const api of apis) {
1740
+ const apiIdx = fullContent.indexOf(api.path);
1741
+ if (apiIdx >= 0) {
1742
+ const after = fullContent.slice(apiIdx, apiIdx + 800);
1743
+ const fields = [];
1744
+ const fieldRegex = /[`']([a-zA-Z]\w{1,25})[`']\s*[((]?[::]?\s*(?:类型[::])?\s*([\u4e00-\u9fa5A-Za-z]+)/g;
1745
+ let fm;
1746
+ while ((fm = fieldRegex.exec(after)) !== null) {
1747
+ if (!['api', 'data', 'code', 'message', 'msg', 'status', 'ok', 'err'].includes(fm[1].toLowerCase())) {
1748
+ fields.push(`${fm[1]}: ${fm[2]}`);
1749
+ }
1750
+ }
1751
+ if (fields.length > 0)
1752
+ apiFieldMap[api.path] = fields.slice(0, 10);
1753
+ }
1754
+ }
1600
1755
  let yaml = `# ${section.name} — API Contract
1601
1756
  # Auto-generated from REQ.md
1602
1757
 
@@ -1613,7 +1768,17 @@ paths:
1613
1768
  ${api.method.toLowerCase()}:
1614
1769
  tags: [${tag}]
1615
1770
  summary: "${api.desc}"
1616
- responses:
1771
+ `;
1772
+ // 添加字段定义
1773
+ if (apiFieldMap[api.path]) {
1774
+ yaml += ` fields:
1775
+ `;
1776
+ for (const f of apiFieldMap[api.path]) {
1777
+ yaml += ` - "${f}"
1778
+ `;
1779
+ }
1780
+ }
1781
+ yaml += ` responses:
1617
1782
  "200":
1618
1783
  description: Success
1619
1784
  `;
@@ -1725,7 +1890,7 @@ async function loadSpecContents(iterationDir) {
1725
1890
  const specDir = (0, path_1.join)(iterationDir, '020-specs');
1726
1891
  if (!(await (0, fs_extra_1.pathExists)(specDir)))
1727
1892
  return specs;
1728
- for (const f of ['TECH.md', 'TEST.md', 'RISK.md', 'DEPS.md', 'MONITOR.md', 'ANALYSIS.md', 'REQUIREMENT.md']) {
1893
+ for (const f of ['TECH.md', 'TEST.md', 'RISK.md', 'DEPS.md', 'MONITOR.md', 'ANALYSIS.md', 'REQUIREMENT.md', 'UI_SPEC.md']) {
1729
1894
  const fp = (0, path_1.join)(specDir, f);
1730
1895
  if (await (0, fs_extra_1.pathExists)(fp)) {
1731
1896
  const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');