speccore 6.0.0 → 6.1.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.
- package/dist/cli.js +24 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/analyze.js +13 -10
- package/dist/commands/analyze.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +6 -8
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/iteration/split.d.ts.map +1 -1
- package/dist/commands/iteration/split.js +132 -164
- package/dist/commands/iteration/split.js.map +1 -1
- package/dist/commands/task/new.js +12 -15
- package/dist/commands/task/new.js.map +1 -1
- package/dist/core/prompt-builder.d.ts +8 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/core/prompt-builder.js +161 -46
- package/dist/core/prompt-builder.js.map +1 -1
- package/package.json +1 -1
|
@@ -60,6 +60,15 @@ function slugify(name) {
|
|
|
60
60
|
const hash = Math.abs(name.split('').reduce((a, c) => a * 31 + c.charCodeAt(0), 7)).toString(36);
|
|
61
61
|
return hash.slice(0, 6);
|
|
62
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* 生成子任务全局唯一 ID
|
|
65
|
+
* 格式: Task-{编号}-{端名}-{hash4}
|
|
66
|
+
* 例: Task-001-backend-a3f2, Task-001-web-b7c1
|
|
67
|
+
*/
|
|
68
|
+
function generateSubtaskId(taskNum, platform) {
|
|
69
|
+
const hash = Math.abs(`${taskNum}-${platform}-${Date.now()}-${Math.random()}`.split('').reduce((a, c) => a * 31 + c.charCodeAt(0), 13)).toString(36);
|
|
70
|
+
return `Task-${taskNum}-${platform}-${hash.slice(0, 4)}`;
|
|
71
|
+
}
|
|
63
72
|
/** 粒度约束常量 */
|
|
64
73
|
const GRANULARITY_RULES = {
|
|
65
74
|
macro: { label: '粗粒度 (macro)', minHours: 20, maxHours: 80, maxApis: 15, maxTables: 5, maxPages: 5, desc: '每个任务 1-2 周,按业务方向合并' },
|
|
@@ -505,6 +514,7 @@ async function iterationSplitCommand(options) {
|
|
|
505
514
|
}
|
|
506
515
|
const specDir2 = (0, path_1.join)(iterationDir, '020-specs');
|
|
507
516
|
const specContents = [];
|
|
517
|
+
// 读取全局层扁平文件(迭代级跨端综合)
|
|
508
518
|
for (const f of ['ANALYSIS.md', 'TECH.md', 'TEST.md', 'REVIEW.md', 'RISK.md', 'DEPS.md', 'MONITOR.md']) {
|
|
509
519
|
const fp = (0, path_1.join)(specDir2, f);
|
|
510
520
|
if (await (0, fs_extra_1.pathExists)(fp)) {
|
|
@@ -514,6 +524,23 @@ async function iterationSplitCommand(options) {
|
|
|
514
524
|
}
|
|
515
525
|
}
|
|
516
526
|
}
|
|
527
|
+
// 读取各端详情(020-specs/platforms/{端}/)
|
|
528
|
+
const iterPlatformsDir = (0, path_1.join)(specDir2, 'platforms');
|
|
529
|
+
if (await (0, fs_extra_1.pathExists)(iterPlatformsDir)) {
|
|
530
|
+
const platformEntries = await (0, fs_extra_1.readdir)(iterPlatformsDir, { withFileTypes: true });
|
|
531
|
+
for (const pe of platformEntries) {
|
|
532
|
+
if (pe.isDirectory() && !pe.name.startsWith('.')) {
|
|
533
|
+
const pFiles = await (0, fs_extra_1.readdir)((0, path_1.join)(iterPlatformsDir, pe.name));
|
|
534
|
+
for (const pf of pFiles.filter((f) => f.endsWith('.md'))) {
|
|
535
|
+
const fp = (0, path_1.join)(iterPlatformsDir, pe.name, pf);
|
|
536
|
+
const content = await (0, fs_extra_1.readFile)(fp, 'utf-8');
|
|
537
|
+
if (content.trim().length > 50 && !content.trim().match(/^#+\s*\u5f85\u586b\u5145|^<!--\s*AI-FILL/m)) {
|
|
538
|
+
specContents.push({ name: `platforms/${pe.name}/${pf}`, content });
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
517
544
|
// 粒度推荐(基于 STAFFING 人数)
|
|
518
545
|
const staffing = readStaffing(iterationDir);
|
|
519
546
|
const teamSize = staffing ? staffing.length : 0;
|
|
@@ -825,76 +852,56 @@ async function createTaskFromSection(iterationDir, taskId, section, allPlatforms
|
|
|
825
852
|
// ── 2. 任务目录指引 ──
|
|
826
853
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, 'README.md'), `# ${section.name}
|
|
827
854
|
|
|
828
|
-
>
|
|
855
|
+
> 任务模块分组 — 按功能单元组织,各端并行开发
|
|
829
856
|
|
|
830
857
|
## 目录结构
|
|
831
858
|
|
|
832
859
|
\`\`\`
|
|
833
860
|
${taskId}/
|
|
834
|
-
├── README.md <--
|
|
861
|
+
├── README.md <-- 本文件
|
|
835
862
|
├── .meta/ <-- 任务元信息(type/status/owner/created-at)
|
|
836
|
-
├── _shared/ <--
|
|
837
|
-
├── 00-specs/ <-- 执行前核心规格(AI 执行时必读)
|
|
863
|
+
├── _shared/ <-- 共享规格(所有端共用)
|
|
838
864
|
│ ├── REQ.md <-- 需求描述(API + 数据模型 + 业务规则)
|
|
839
|
-
│ ├── TECH.md <--
|
|
840
|
-
│ ├──
|
|
841
|
-
│ ├──
|
|
865
|
+
│ ├── TECH.md <-- 技术方案(架构/接口设计/核心逻辑)
|
|
866
|
+
│ ├── SCHEMA.md <-- 数据库设计(如有)
|
|
867
|
+
│ ├── API_CONTRACT.yaml <-- API 契约
|
|
842
868
|
│ └── CHANGELOG.md <-- 变更记录
|
|
843
|
-
├──
|
|
844
|
-
├──
|
|
869
|
+
├── {端名}/ <-- 各端独立子任务(backend/admin/web/h5/...)
|
|
870
|
+
│ ├── TASK.md <-- 子任务追踪(含子任务表、负责人、状态)
|
|
871
|
+
│ ├── src/ <-- 源码实现
|
|
872
|
+
│ └── tests/ <-- 测试用例
|
|
845
873
|
├── 99-artifacts/ <-- 执行产出
|
|
846
|
-
│ ├──
|
|
847
|
-
│
|
|
848
|
-
│
|
|
849
|
-
│
|
|
850
|
-
│ │ └── ERROR_CODES.md <-- 错误码表(verify 检查一致性)
|
|
851
|
-
│ └── 📚 参考文档(AI/人参考,不参与自动验证)
|
|
852
|
-
│ ├── RISK.md <-- 风险评估
|
|
853
|
-
│ ├── DEPS.md <-- 依赖清单
|
|
854
|
-
│ ├── MONITOR.md <-- 监控配置
|
|
855
|
-
│ └── ADR.md <-- 架构决策记录
|
|
874
|
+
│ ├── TEST.md <-- 测试用例
|
|
875
|
+
│ ├── REVIEW.md <-- 评审清单
|
|
876
|
+
│ ├── DEPLOY.md <-- 部署清单
|
|
877
|
+
│ └── ...
|
|
856
878
|
└── .issues.md <-- 问题追踪
|
|
857
879
|
\`\`\`
|
|
858
880
|
|
|
859
|
-
##
|
|
860
|
-
|
|
861
|
-
运行 \`speccore execute -t ${taskId}\` 时,AI 会按以下顺序读取本文档:
|
|
862
|
-
|
|
863
|
-
### 必读文件(自动嵌入 AI Prompt)
|
|
881
|
+
## 子任务命名规则
|
|
864
882
|
|
|
865
|
-
|
|
866
|
-
|:---|:---|
|
|
867
|
-
| \`00-specs/REQ.md\` | 需求描述、API 列表、数据模型、业务规则 |
|
|
868
|
-
| \`00-specs/TECH.md\` | 技术方案、架构设计、接口分层、核心逻辑 |
|
|
869
|
-
| \`00-specs/TASK.md\` | 任务信息、状态追踪、产出物清单 |
|
|
870
|
-
| \`00-specs/SCHEMA.md\` | 数据库表结构、字段、索引(如有) |
|
|
871
|
-
| \`.speccore/CONSTITUTION.md\` | 技术栈、命名规范、异常码体系 |
|
|
883
|
+
每个端的子任务 ID 格式: \`Task-{编号}-{端名}-{hash}\`
|
|
872
884
|
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
|
876
|
-
|:---|:---|:---|
|
|
877
|
-
| \`99-artifacts/TEST.md\` | 测试计划(用例/边界/集成) | ✅ 检查用例覆盖率 |
|
|
878
|
-
| \`99-artifacts/REVIEW.md\` | 评审清单(安全/质量/性能) | ✅ 检查评审项合规 |
|
|
879
|
-
| \`99-artifacts/RISK.md\` | 风险评估 | ❌ 仅参考 |
|
|
880
|
-
| \`_shared/API_CONTRACT.yaml\` | API 契约(OpenAPI 格式) | ❌ 仅参考 |
|
|
881
|
-
| \`.issues.md\` | 已知问题和约束 | ❌ 仅参考 |
|
|
882
|
-
|
|
883
|
-
### 不会被 AI 读取的文件
|
|
885
|
+
| 子任务 ID | 所属端 | 负责人 | 状态 |
|
|
886
|
+
| :--- | :--- | :--- | :--- |
|
|
887
|
+
${taskPlatforms.map((p) => `| ${generateSubtaskId(taskId.replace(/^Task-/, '').split('-')[0], p)} | ${p} | ${owner} | 🔲 待开发 |`).join('\n')}
|
|
884
888
|
|
|
885
|
-
|
|
886
|
-
- \`99-artifacts/DEPLOY.md\`、\`ERROR_CODES.md\` 等 —— 执行完成后自动更新
|
|
889
|
+
## AI 执行时读取规则
|
|
887
890
|
|
|
888
|
-
|
|
891
|
+
运行 \`speccore execute -t ${taskId} --platform {端}\` 时:
|
|
889
892
|
|
|
890
|
-
|
|
893
|
+
### 必读(自动嵌入)
|
|
894
|
+
- \`_shared/REQ.md\` — 共享需求
|
|
895
|
+
- \`_shared/TECH.md\` — 共享技术方案
|
|
896
|
+
- \`{端}/TASK.md\` — 该端子任务详情
|
|
891
897
|
|
|
892
|
-
|
|
893
|
-
-
|
|
894
|
-
-
|
|
895
|
-
- 记录已知问题/约束 → 写到 \`.issues.md\`
|
|
898
|
+
### 参考(按需读取)
|
|
899
|
+
- \`020-specs/\` 下的迭代全局文档(架构、技术方案等)
|
|
900
|
+
- \`.speccore/GLOBAL/\` 下的全局知识库
|
|
896
901
|
|
|
897
|
-
|
|
902
|
+
### 不会被读取
|
|
903
|
+
- \`{端}/src/\` 和 \`{端}/tests/\` — 这是 AI **输出**代码的地方
|
|
904
|
+
- \`99-artifacts/\` — 执行完成后自动更新
|
|
898
905
|
`);
|
|
899
906
|
// ── 3. 共享契约 ──
|
|
900
907
|
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '_shared'));
|
|
@@ -902,26 +909,26 @@ AI 执行时会自动读取这些文件,作为生成代码的依据。
|
|
|
902
909
|
if (contractYaml) {
|
|
903
910
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml'), contractYaml);
|
|
904
911
|
}
|
|
905
|
-
// ── 3.
|
|
906
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '
|
|
912
|
+
// ── 3. 共享规格 _shared/(所有端共用) ──
|
|
913
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '_shared'));
|
|
907
914
|
const acItems = generateAcceptanceCriteria(section);
|
|
908
915
|
const aiReqContent = section._reqContent;
|
|
909
916
|
// REQ.md: 优先用 AI 生成的实际内容,回退到模板
|
|
910
917
|
if (aiReqContent && aiReqContent.length > 50) {
|
|
911
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '
|
|
918
|
+
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`);
|
|
912
919
|
}
|
|
913
920
|
else {
|
|
914
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '
|
|
921
|
+
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`);
|
|
915
922
|
}
|
|
916
923
|
const apiLines = section.content.split('\n').filter(l => l.includes('| GET') || l.includes('| POST') || l.includes('| PUT') || l.includes('| DELETE') || l.includes('| PATCH'));
|
|
917
924
|
const apiDesc = apiLines.length > 0 ? apiLines.map(l => `- ${l.trim()}`).join('\n') : '- 待补充(从 REQ.md 提取接口列表)';
|
|
918
925
|
const aiTechContent = section._techContent;
|
|
919
926
|
// TECH.md: 优先用 AI 生成的实际内容,回退到模板
|
|
920
927
|
if (aiTechContent && aiTechContent.length > 50) {
|
|
921
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '
|
|
928
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'TECH.md'), `# ${section.name} - 技术方案\n\n${aiTechContent}\n`);
|
|
922
929
|
}
|
|
923
930
|
else {
|
|
924
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '
|
|
931
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'TECH.md'), `# ${section.name} - 技术方案
|
|
925
932
|
|
|
926
933
|
> ⚠️ 本文档由 split 自动生成框架,AI 执行时会自动填充。
|
|
927
934
|
|
|
@@ -950,48 +957,15 @@ ${apiDesc}
|
|
|
950
957
|
`);
|
|
951
958
|
}
|
|
952
959
|
if (section.content.match(/数据库|数据表|表结构|DDL|ALTER|建表|索引/)) {
|
|
953
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '
|
|
960
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'SCHEMA.md'), generateSchemaTemplate(section));
|
|
954
961
|
}
|
|
955
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '
|
|
956
|
-
|
|
957
|
-
## 任务信息
|
|
958
|
-
- 类型: ${taskType}
|
|
959
|
-
- 状态: 🔲 待开发
|
|
960
|
-
- 优先级: ${complexity.priority}
|
|
961
|
-
- 负责人: ${owner}
|
|
962
|
-
- 预计耗时: ${complexity.estimatedHours}h${complexity.complexity !== 'medium' ? ` (${complexity.complexity === 'high' ? '高复杂度' : '低复杂度'})` : ''}
|
|
963
|
-
- 复杂度: API ${complexity.apiCount} | DB ${complexity.dbCount} | 页面 ${complexity.pageCount}
|
|
964
|
-
|
|
965
|
-
## 变更履历
|
|
966
|
-
| 时间 | 变更内容 | 变更人 |
|
|
967
|
-
| :--- | :--- | :--- |
|
|
968
|
-
| ${today} | 创建任务 | CLI |
|
|
969
|
-
|
|
970
|
-
## 产出物清单
|
|
971
|
-
| 产出物 | 状态 | 路径 | verify 自检? |
|
|
972
|
-
| :--- | :--- | :--- | :--- |
|
|
973
|
-
| REQ.md | ✅ | ./00-specs/REQ.md | ✅ Spec 一致性 |
|
|
974
|
-
| TECH.md | ✅ | ./00-specs/TECH.md | — |
|
|
975
|
-
| TASK.md | ✅ | ./00-specs/TASK.md | — |
|
|
976
|
-
| SCHEMA.md | ⏳ | ./00-specs/SCHEMA.md | — |
|
|
977
|
-
| API_CONTRACT.yaml | ✅ | ./_shared/API_CONTRACT.yaml | — |
|
|
978
|
-
| TEST.md | ⏳ | ./99-artifacts/TEST.md | ✅ 用例覆盖率 |
|
|
979
|
-
| REVIEW.md | ⏳ | ./99-artifacts/REVIEW.md | ✅ 评审项合规 |
|
|
980
|
-
| DEPLOY.md | ⏳ | ./99-artifacts/DEPLOY.md | ✅ 部署项检查 |
|
|
981
|
-
| ERROR_CODES.md | ⏳ | ./99-artifacts/ERROR_CODES.md | ✅ 错误码一致性 |
|
|
982
|
-
| ADR.md | ⏳ | ./99-artifacts/ADR.md | ❌ 仅参考 |
|
|
983
|
-
| RISK.md | ⏳ | ./99-artifacts/RISK.md | ❌ 仅参考 |
|
|
984
|
-
| DEPS.md | ⏳ | ./99-artifacts/DEPS.md | ❌ 仅参考 |
|
|
985
|
-
| MONITOR.md | ⏳ | ./99-artifacts/MONITOR.md | ❌ 仅参考 |
|
|
986
|
-
| CHANGELOG.md | ✅ | ./00-specs/CHANGELOG.md | — |
|
|
987
|
-
`);
|
|
988
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '00-specs', 'CHANGELOG.md'), `# ${section.name} - 变更记录
|
|
962
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '_shared', 'CHANGELOG.md'), `# ${section.name} - 变更记录
|
|
989
963
|
|
|
990
964
|
| 时间 | 版本 | 变更内容 | 变更人 |
|
|
991
965
|
| :--- | :--- | :--- | :--- |
|
|
992
966
|
| ${today} | v1.0 | 初始创建 | CLI |
|
|
993
967
|
`);
|
|
994
|
-
// API_CONTRACT.yaml
|
|
968
|
+
// API_CONTRACT.yaml 统一放 _shared/
|
|
995
969
|
if (apiLines.length > 0) {
|
|
996
970
|
const contracts = apiLines.map(l => {
|
|
997
971
|
const parts = l.split('|').map(p => p.trim()).filter(Boolean);
|
|
@@ -1002,7 +976,65 @@ ${apiDesc}
|
|
|
1002
976
|
}).join('\n');
|
|
1003
977
|
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`);
|
|
1004
978
|
}
|
|
1005
|
-
// ── 4.
|
|
979
|
+
// ── 4. 各端独立子任务目录 ──
|
|
980
|
+
const taskNum = taskId.replace(/^Task-/, '').split('-')[0];
|
|
981
|
+
const sharedReqContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '_shared', 'REQ.md'), 'utf-8');
|
|
982
|
+
const sharedTechContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '_shared', 'TECH.md'), 'utf-8');
|
|
983
|
+
for (const platform of taskPlatforms) {
|
|
984
|
+
const subtaskId = generateSubtaskId(taskNum, platform);
|
|
985
|
+
const platformDir = (0, path_1.join)(taskDir, platform);
|
|
986
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.join)(platformDir, 'src'));
|
|
987
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.join)(platformDir, 'tests'));
|
|
988
|
+
// 各端 TASK.md — 含子任务表、负责人、状态
|
|
989
|
+
const isBackend = platform === 'backend' || platform.startsWith('后台');
|
|
990
|
+
const platformLabel = isBackend ? '后端' : platform;
|
|
991
|
+
const subtaskHours = section._hoursByPlatform?.[platform] || Math.ceil(complexity.estimatedHours / taskPlatforms.length);
|
|
992
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'TASK.md'), `# ${section.name} — ${platformLabel}
|
|
993
|
+
|
|
994
|
+
## 子任务信息
|
|
995
|
+
- **子任务 ID**: \`${subtaskId}\`
|
|
996
|
+
- **所属模块**: \`${taskId}\`
|
|
997
|
+
- **端**: ${platform}
|
|
998
|
+
- **负责人**: ${owner}
|
|
999
|
+
- **状态**: 🔲 待开发
|
|
1000
|
+
- **预计耗时**: ${subtaskHours}h
|
|
1001
|
+
|
|
1002
|
+
## 子任务列表
|
|
1003
|
+
|
|
1004
|
+
| 子任务 ID | 名称 | 负责人 | 状态 | 预估工时 | 依赖 |
|
|
1005
|
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
1006
|
+
| \`${subtaskId}-01\` | ${section.name} - ${platformLabel}实现 | ${owner} | 🔲 待开发 | ${subtaskHours}h | 无 |
|
|
1007
|
+
|
|
1008
|
+
## 共享规格引用
|
|
1009
|
+
- REQ.md → ../_shared/REQ.md
|
|
1010
|
+
- TECH.md → ../_shared/TECH.md
|
|
1011
|
+
|
|
1012
|
+
## 产出物
|
|
1013
|
+
| 产出物 | 状态 | 路径 |
|
|
1014
|
+
| :--- | :--- | :--- |
|
|
1015
|
+
| TASK.md | ✅ | ./${platform}/TASK.md |
|
|
1016
|
+
| src/ | ⏳ | ./${platform}/src/ |
|
|
1017
|
+
| tests/ | ⏳ | ./${platform}/tests/ |
|
|
1018
|
+
|
|
1019
|
+
## 变更履历
|
|
1020
|
+
| 时间 | 变更内容 | 变更人 |
|
|
1021
|
+
| :--- | :--- | :--- |
|
|
1022
|
+
| ${today} | 创建子任务 | CLI |
|
|
1023
|
+
`);
|
|
1024
|
+
// 后端端额外生成组件/路由等前端文档
|
|
1025
|
+
if (!isBackend) {
|
|
1026
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'COMPONENT_TREE.md'), generateComponentTree(section, platform));
|
|
1027
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'ROUTES.md'), generateRoutesDoc(section, platform));
|
|
1028
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'STATE.md'), generateStateDoc(section, platform));
|
|
1029
|
+
await (0, fs_extra_1.writeFile)((0, path_1.join)(platformDir, 'STYLE_GUIDE.md'), generateStyleGuide(section, platform));
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
// 确保至少有 backend 目录
|
|
1033
|
+
if (!taskPlatforms.some((p) => p === 'backend' || p.startsWith('后台'))) {
|
|
1034
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, 'backend', 'src'));
|
|
1035
|
+
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, 'backend', 'tests'));
|
|
1036
|
+
}
|
|
1037
|
+
// ── 5. 执行产出 99-artifacts/ ──
|
|
1006
1038
|
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '99-artifacts'));
|
|
1007
1039
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'TEST.md'), generateTestOutline(section));
|
|
1008
1040
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'REVIEW.md'), generateReviewChecklist(section));
|
|
@@ -1015,71 +1047,7 @@ ${apiDesc}
|
|
|
1015
1047
|
if (adr) {
|
|
1016
1048
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '99-artifacts', 'ADR.md'), adr);
|
|
1017
1049
|
}
|
|
1018
|
-
// ──
|
|
1019
|
-
for (const platform of taskPlatforms) {
|
|
1020
|
-
if (platform === 'backend') {
|
|
1021
|
-
// 纯后端任务:直接创建 10-backend/src/ 和 10-backend/tests/
|
|
1022
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '10-backend', 'src'));
|
|
1023
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '10-backend', 'tests'));
|
|
1024
|
-
}
|
|
1025
|
-
else if (platform.startsWith('后台')) {
|
|
1026
|
-
// 后台服务任务:创建 10-backend/{service}/src/ 和 tests/
|
|
1027
|
-
const service = platform.replace(/^后台/, '').trim() || 'default';
|
|
1028
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '10-backend', service, 'src'));
|
|
1029
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '10-backend', service, 'tests'));
|
|
1030
|
-
}
|
|
1031
|
-
else {
|
|
1032
|
-
// 前端任务:创建 20-frontend/{platform}/src/ 和 tests/
|
|
1033
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '20-frontend', platform, 'src'));
|
|
1034
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '20-frontend', platform, 'tests'));
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
if (!taskPlatforms.some((p) => p === 'backend' || p.startsWith('后台'))) {
|
|
1038
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '10-backend', 'src'));
|
|
1039
|
-
await (0, fs_extra_1.ensureDir)((0, path_1.join)(taskDir, '10-backend', 'tests'));
|
|
1040
|
-
}
|
|
1041
|
-
// ── 6. 平台目录内复制核心规格 ──
|
|
1042
|
-
const reqContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '00-specs', 'REQ.md'), 'utf-8');
|
|
1043
|
-
const techContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '00-specs', 'TECH.md'), 'utf-8');
|
|
1044
|
-
const taskContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '00-specs', 'TASK.md'), 'utf-8');
|
|
1045
|
-
const testContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '99-artifacts', 'TEST.md'), 'utf-8');
|
|
1046
|
-
const reviewContent = await (0, fs_extra_1.readFile)((0, path_1.join)(taskDir, '99-artifacts', 'REVIEW.md'), 'utf-8');
|
|
1047
|
-
for (const platform of taskPlatforms) {
|
|
1048
|
-
if (platform === 'backend') {
|
|
1049
|
-
// 纯后端任务:规格写入 10-backend/ 根目录
|
|
1050
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '10-backend', 'REQ.md'), reqContent);
|
|
1051
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '10-backend', 'TECH.md'), techContent);
|
|
1052
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '10-backend', 'TASK.md'), taskContent);
|
|
1053
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '10-backend', 'TEST.md'), testContent);
|
|
1054
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '10-backend', 'REVIEW.md'), reviewContent);
|
|
1055
|
-
}
|
|
1056
|
-
else if (platform.startsWith('后台')) {
|
|
1057
|
-
// 后台服务任务:规格写入 10-backend/{service}/
|
|
1058
|
-
const service = platform.replace(/^后台/, '').trim() || platform;
|
|
1059
|
-
const svcDir = (0, path_1.join)(taskDir, '10-backend', service);
|
|
1060
|
-
await (0, fs_extra_1.ensureDir)(svcDir);
|
|
1061
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(svcDir, 'REQ.md'), reqContent);
|
|
1062
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(svcDir, 'TECH.md'), techContent);
|
|
1063
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(svcDir, 'TASK.md'), taskContent);
|
|
1064
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(svcDir, 'TEST.md'), testContent);
|
|
1065
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(svcDir, 'REVIEW.md'), reviewContent);
|
|
1066
|
-
}
|
|
1067
|
-
else {
|
|
1068
|
-
// 前端任务:规格写入 20-frontend/{platform}/
|
|
1069
|
-
const feDir = (0, path_1.join)(taskDir, '20-frontend', platform);
|
|
1070
|
-
await (0, fs_extra_1.ensureDir)(feDir);
|
|
1071
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'REQ.md'), reqContent);
|
|
1072
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'TASK.md'), taskContent);
|
|
1073
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'TEST.md'), testContent);
|
|
1074
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'REVIEW.md'), reviewContent);
|
|
1075
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'README.md'), `# ${section.name} - ${platform}\n\n前端实现目录。\n`);
|
|
1076
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'COMPONENT_TREE.md'), generateComponentTree(section, platform));
|
|
1077
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'ROUTES.md'), generateRoutesDoc(section, platform));
|
|
1078
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'STATE.md'), generateStateDoc(section, platform));
|
|
1079
|
-
await (0, fs_extra_1.writeFile)((0, path_1.join)(feDir, 'STYLE_GUIDE.md'), generateStyleGuide(section, platform));
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
// ── 7. 问题追踪 ──
|
|
1050
|
+
// ── 6. 问题追踪 ──
|
|
1083
1051
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.issues.md'), `# ${section.name} - 问题追踪\n\n> 执行过程中发现的问题记录于此。\n\n`);
|
|
1084
1052
|
}
|
|
1085
1053
|
async function updateProjectGraph(iterationDir, sections) {
|
|
@@ -1228,7 +1196,7 @@ async function strictSplitPreview(sections, platforms, iterationDir) {
|
|
|
1228
1196
|
const taskId = s._taskId || `Task-${String(i + 1).padStart(3, '0')}`;
|
|
1229
1197
|
// Determine target directory
|
|
1230
1198
|
const target = s.platform
|
|
1231
|
-
? (s.platform.startsWith('后台') ? `backend/${s.platform.replace(/^后台/, '')}` :
|
|
1199
|
+
? (s.platform.startsWith('后台') ? `backend/${s.platform.replace(/^后台/, '')}` : s.platform)
|
|
1232
1200
|
: platforms.join(' + ');
|
|
1233
1201
|
logger_1.logger.info(`── ${taskId}: ${s.name} ──`);
|
|
1234
1202
|
logger_1.logger.info(` 端: ${target}`);
|
|
@@ -1308,8 +1276,8 @@ async function generateImpactGraph(iterationDir, sections, platforms) {
|
|
|
1308
1276
|
const taskType = s._taskType || 'feature';
|
|
1309
1277
|
const taskDir = (0, path_1.join)(iterationDir, '030-tasks', taskType, taskId);
|
|
1310
1278
|
if (await (0, fs_extra_1.pathExists)(taskDir)) {
|
|
1311
|
-
// 生成风险报告并嵌入
|
|
1312
|
-
const taskMdPath = (0, path_1.join)(taskDir, '
|
|
1279
|
+
// 生成风险报告并嵌入 _shared/TECH.md(去重:只写一次)
|
|
1280
|
+
const taskMdPath = (0, path_1.join)(taskDir, '_shared', 'TECH.md');
|
|
1313
1281
|
const riskReport = (0, risk_scorer_1.generateRiskReport)(risk);
|
|
1314
1282
|
await (0, fs_extra_1.writeFile)((0, path_1.join)(taskDir, '.risk'), riskReport);
|
|
1315
1283
|
if (await (0, fs_extra_1.pathExists)(taskMdPath)) {
|
|
@@ -1458,7 +1426,7 @@ async function injectTechFromAnalysis(iterationDir, taskDir, sectionName) {
|
|
|
1458
1426
|
const depSection = analysis.match(/### 接口依赖[\s\S]*?(?=###|$)/);
|
|
1459
1427
|
if (!techSection && !dbSection && !depSection)
|
|
1460
1428
|
return;
|
|
1461
|
-
const techPath = (0, path_1.join)(taskDir, '
|
|
1429
|
+
const techPath = (0, path_1.join)(taskDir, '_shared', 'TECH.md');
|
|
1462
1430
|
let tech = await (0, fs_extra_1.readFile)(techPath, 'utf-8');
|
|
1463
1431
|
const note = '\n\n> 以下内容自动从 ANALYSIS.md 注入\n\n';
|
|
1464
1432
|
if (techSection && !tech.includes(techSection[0].trim())) {
|
|
@@ -1722,7 +1690,7 @@ function buildSplitPrompt(iteration, constitutionContent, reqContent, specConten
|
|
|
1722
1690
|
p += `一个原子任务 = 一个开发者在指定粒度内可独立完成的、有明确验收标准的最小工作单元。\n`;
|
|
1723
1691
|
p += `判定标准(全部满足):\n`;
|
|
1724
1692
|
p += `- 有独立的输入/输出(API 接口 / 页面 / 数据表)\n`;
|
|
1725
|
-
p += `-
|
|
1693
|
+
p += `- _shared/ + {端}/TASK.md 能独立写满(REQ.md + TECH.md + 各端子任务)\n`;
|
|
1726
1694
|
p += `- execute 时不强依赖其他 Task 的运行时状态\n`;
|
|
1727
1695
|
p += `- 有明确的验收标准(AC 可枚举)\n`;
|
|
1728
1696
|
p += `- 可独立提 PR、独立 review\n\n`;
|