speccore 6.49.7 → 6.49.9

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.
@@ -641,7 +641,7 @@ async function processBatch(tasks, state, iteration) {
641
641
  type: task.type || 'feature',
642
642
  status: 'completed',
643
643
  summary: `${task.name || task.id} 骨架已生成`,
644
- outputs: ['00-specs/', '10-backend/', '20-frontend/'],
644
+ outputs: ['00-specs/', '{platform}/(子任务)/'],
645
645
  dependencies: task.dependencies || [],
646
646
  completedAt: new Date().toISOString(),
647
647
  };
@@ -701,32 +701,51 @@ async function generateTaskSkeleton(task, iteration) {
701
701
  const specRules = await (0, spec_rules_1.loadSpecRules)();
702
702
  const techStack = await (0, spec_rules_1.loadTechStack)();
703
703
  logger_1.logger.info(` Tech Stack: ${techStack.backendFramework} + ${techStack.frontendFramework}`);
704
- // 读取后端 Spec 生成代码骨架(扫描 10-backend/{服务}/{子任务}/ 三级嵌套)
704
+ // v6.49.9+: 扫描平铺的端目录(新结构: {platform}/{subtask}/)
705
705
  const { readdir: rd } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
706
706
  const taskEntries = await rd(taskDir, { withFileTypes: true });
707
+ const platformList = await (0, spec_paths_1.parsePlatformList)();
707
708
  const backendSubtaskDirs = [];
708
- // 新结构: 10-backend/{服务名}/{子任务}/
709
- const backendCategoryDir = (0, path_1.join)(taskDir, '10-backend');
710
- if (await (0, fs_extra_1.pathExists)(backendCategoryDir)) {
711
- const serviceEntries = await rd(backendCategoryDir, { withFileTypes: true });
712
- for (const service of serviceEntries) {
713
- if (!service.isDirectory())
714
- continue;
715
- const serviceDir = (0, path_1.join)(backendCategoryDir, service.name);
716
- const subtaskEntries = await rd(serviceDir, { withFileTypes: true });
717
- for (const st of subtaskEntries) {
718
- if (st.isDirectory() && !st.name.startsWith('.')) {
719
- backendSubtaskDirs.push((0, path_1.join)(serviceDir, st.name));
720
- }
709
+ const frontendSubtaskDirs = [];
710
+ // 新结构: 所有端平铺在任务目录下
711
+ for (const entry of taskEntries) {
712
+ if (!entry.isDirectory() || entry.name.startsWith('.'))
713
+ continue;
714
+ if (!platformList.includes(entry.name))
715
+ continue;
716
+ const platDir = (0, path_1.join)(taskDir, entry.name);
717
+ const isBk = entry.name === 'backend' || entry.name.startsWith('后台') || /-(service|api|server|backend)$/i.test(entry.name);
718
+ const subEntries = await rd(platDir, { withFileTypes: true });
719
+ for (const st of subEntries) {
720
+ if (st.isDirectory() && !st.name.startsWith('.')) {
721
+ if (isBk)
722
+ backendSubtaskDirs.push((0, path_1.join)(platDir, st.name));
723
+ else
724
+ frontendSubtaskDirs.push((0, path_1.join)(platDir, st.name));
721
725
  }
722
726
  }
723
727
  }
724
- // 回退: 旧结构 10-*/ 直接在任务根目录
725
- if (backendSubtaskDirs.length === 0) {
726
- const legacy = taskEntries
727
- .filter((d) => d.isDirectory() && d.name.startsWith('10-'))
728
- .map((d) => (0, path_1.join)(taskDir, d.name));
729
- backendSubtaskDirs.push(...legacy);
728
+ // 回退: 旧结构 10-backend/ 和 20-frontend/
729
+ if (backendSubtaskDirs.length === 0 && frontendSubtaskDirs.length === 0) {
730
+ for (const catName of ['10-backend', '20-frontend']) {
731
+ const catDir = (0, path_1.join)(taskDir, catName);
732
+ if (await (0, fs_extra_1.pathExists)(catDir)) {
733
+ const svcEntries = await rd(catDir, { withFileTypes: true });
734
+ for (const svc of svcEntries) {
735
+ if (!svc.isDirectory())
736
+ continue;
737
+ const subEntries = await rd((0, path_1.join)(catDir, svc.name), { withFileTypes: true });
738
+ for (const st of subEntries) {
739
+ if (st.isDirectory() && !st.name.startsWith('.')) {
740
+ if (catName === '10-backend')
741
+ backendSubtaskDirs.push((0, path_1.join)(catDir, svc.name, st.name));
742
+ else
743
+ frontendSubtaskDirs.push((0, path_1.join)(catDir, svc.name, st.name));
744
+ }
745
+ }
746
+ }
747
+ }
748
+ }
730
749
  }
731
750
  for (const backendDir of backendSubtaskDirs) {
732
751
  const reqPath = (0, path_1.join)(taskDir, '00-specs', 'REQ.md');
@@ -770,31 +789,7 @@ async function generateTaskSkeleton(task, iteration) {
770
789
  filesUpdated++;
771
790
  }
772
791
  }
773
- // 前端各平台代码生成(扫描 20-frontend/{端名}/{子任务}/ 三级嵌套)
774
- const frontendSubtaskDirs = [];
775
- // 新结构: 20-frontend/{端名}/{子任务}/
776
- const frontendCategoryDir = (0, path_1.join)(taskDir, '20-frontend');
777
- if (await (0, fs_extra_1.pathExists)(frontendCategoryDir)) {
778
- const platformEntries = await rd(frontendCategoryDir, { withFileTypes: true });
779
- for (const plat of platformEntries) {
780
- if (!plat.isDirectory())
781
- continue;
782
- const platformDir = (0, path_1.join)(frontendCategoryDir, plat.name);
783
- const subtaskEntries = await rd(platformDir, { withFileTypes: true });
784
- for (const st of subtaskEntries) {
785
- if (st.isDirectory() && !st.name.startsWith('.')) {
786
- frontendSubtaskDirs.push((0, path_1.join)(platformDir, st.name));
787
- }
788
- }
789
- }
790
- }
791
- // 回退: 旧结构 20-*/ 直接在任务根目录
792
- if (frontendSubtaskDirs.length === 0) {
793
- const legacy = taskEntries
794
- .filter((d) => d.isDirectory() && d.name.startsWith('20-'))
795
- .map((d) => (0, path_1.join)(taskDir, d.name));
796
- frontendSubtaskDirs.push(...legacy);
797
- }
792
+ // 前端各平台代码生成(v6.49.9+: 已统一在上方扫描)
798
793
  for (const frontendDir of frontendSubtaskDirs) {
799
794
  const componentName = convertToClassName(task.name || task.id);
800
795
  const vueCode = generateVueComponent(componentName);
@@ -1000,38 +995,29 @@ async function filterByPlatform(tasks, iteration, platform) {
1000
995
  filtered.push(task);
1001
996
  continue;
1002
997
  }
1003
- // 新结构: resolveTaskDir 查找任务目录(支持类型子目录),再查 10-backend/{端}/ 或 20-frontend/{端}/
998
+ // v6.49.9+: 新结构 所有端平铺在任务目录下
1004
999
  const taskDir = await resolveTaskDir(iterDir, task.id);
1000
+ const platformDir = (0, path_1.join)(taskDir, platform);
1001
+ if (await (0, fs_extra_1.pathExists)(platformDir)) {
1002
+ filtered.push(task);
1003
+ continue;
1004
+ }
1005
+ // 回退: 旧结构 10-backend/{端}/ 或 20-frontend/{端}/
1005
1006
  const isBackend = platform === 'backend' || platform.startsWith('后台');
1006
1007
  const categoryDir = isBackend ? (0, path_1.join)(taskDir, '10-backend') : (0, path_1.join)(taskDir, '20-frontend');
1007
1008
  const serviceName = isBackend && platform === 'backend' ? 'api' : platform;
1008
- // 新结构: 10-backend/{端}/ 20-frontend/{端}/
1009
- const platformDir = (0, path_1.join)(categoryDir, serviceName);
1010
- if (await (0, fs_extra_1.pathExists)(platformDir)) {
1009
+ const legacyPlatformDir = (0, path_1.join)(categoryDir, serviceName);
1010
+ if (await (0, fs_extra_1.pathExists)(legacyPlatformDir)) {
1011
1011
  filtered.push(task);
1012
1012
  continue;
1013
1013
  }
1014
- // 回退: 扫描 category 目录下是否有匹配的子目录
1015
- try {
1016
- if (await (0, fs_extra_1.pathExists)(categoryDir)) {
1017
- const { readdir: readDir } = await Promise.resolve().then(() => __importStar(require('fs-extra')));
1018
- const entries = await readDir(categoryDir, { withFileTypes: true });
1019
- const matched = entries.find((d) => d.isDirectory() && d.name === serviceName);
1020
- if (matched) {
1021
- filtered.push(task);
1022
- continue;
1023
- }
1024
- }
1025
- }
1026
- catch { /* ignore */ }
1027
- // 旧结构回退: 10-{端}/ 或 20-{端}/ 直接在任务根目录
1014
+ // 更旧结构回退: backend/ frontend/{platform}/
1028
1015
  const subtaskPrefix = isBackend ? '10-' : '20-';
1029
1016
  const legacyDir = (0, path_1.join)(taskDir, `${subtaskPrefix}${serviceName}`);
1030
1017
  if (await (0, fs_extra_1.pathExists)(legacyDir)) {
1031
1018
  filtered.push(task);
1032
1019
  continue;
1033
1020
  }
1034
- // 更旧结构回退: frontend/{platform}/
1035
1021
  const legacyDir2 = (0, path_1.join)(taskDir, 'frontend', platform);
1036
1022
  if (await (0, fs_extra_1.pathExists)(legacyDir2))
1037
1023
  filtered.push(task);
@@ -1102,29 +1088,16 @@ async function preFlightCheck(tasks, iteration, options) {
1102
1088
  else {
1103
1089
  issues.push('缺少 TECH.md');
1104
1090
  }
1105
- // 3. Test cases(扫描子任务目录)
1091
+ // 3. Test cases(v6.49.9+: 扫描平铺的端目录)
1106
1092
  let testFound = false;
1107
- for (const catDir of ['10-backend', '20-frontend']) {
1108
- const catPath = (0, path_1.join)(taskDir, catDir);
1109
- if (await (0, fs_extra_1.pathExists)(catPath)) {
1110
- try {
1111
- for (const svc of await (0, fs_extra_1.readdir)(catPath, { withFileTypes: true })) {
1112
- if (!svc.isDirectory())
1113
- continue;
1114
- for (const sub of await (0, fs_extra_1.readdir)((0, path_1.join)(catPath, svc.name), { withFileTypes: true })) {
1115
- if (!sub.isDirectory())
1116
- continue;
1117
- const tp = (0, path_1.join)(catPath, svc.name, sub.name, 'TEST.md');
1118
- if (await (0, fs_extra_1.pathExists)(tp)) {
1119
- const test = await (0, fs_extra_1.readFile)(tp, 'utf-8');
1120
- const n = (test.match(/⬜|✅|❌/g) || []).length;
1121
- logger_1.logger.info(` 3. 测试[${svc.name}/${sub.name}]: ${n} 用例`);
1122
- testFound = true;
1123
- }
1124
- }
1125
- }
1126
- }
1127
- catch { /* 跳过 */ }
1093
+ const platDirs3 = await getPlatformSubtaskDirs(taskDir);
1094
+ for (const { platform, subtask, fullPath } of platDirs3) {
1095
+ const tp = (0, path_1.join)(fullPath, 'TEST.md');
1096
+ if (await (0, fs_extra_1.pathExists)(tp)) {
1097
+ const test = await (0, fs_extra_1.readFile)(tp, 'utf-8');
1098
+ const n = (test.match(/⬜|✅|❌/g) || []).length;
1099
+ logger_1.logger.info(` 3. 测试[${platform}/${subtask}]: ${n} 用例`);
1100
+ testFound = true;
1128
1101
  }
1129
1102
  }
1130
1103
  if (!testFound) {
@@ -1136,26 +1109,12 @@ async function preFlightCheck(tasks, iteration, options) {
1136
1109
  logger_1.logger.info(` 3. 测试: ${n} 用例`);
1137
1110
  }
1138
1111
  }
1139
- // 4. Review(扫描子任务目录)
1112
+ // 4. Review(v6.49.9+: 扫描平铺的端目录)
1140
1113
  let reviewFound = false;
1141
- for (const catDir of ['10-backend', '20-frontend']) {
1142
- const catPath = (0, path_1.join)(taskDir, catDir);
1143
- if (await (0, fs_extra_1.pathExists)(catPath)) {
1144
- try {
1145
- for (const svc of await (0, fs_extra_1.readdir)(catPath, { withFileTypes: true })) {
1146
- if (!svc.isDirectory())
1147
- continue;
1148
- for (const sub of await (0, fs_extra_1.readdir)((0, path_1.join)(catPath, svc.name), { withFileTypes: true })) {
1149
- if (!sub.isDirectory())
1150
- continue;
1151
- if (await (0, fs_extra_1.pathExists)((0, path_1.join)(catPath, svc.name, sub.name, 'REVIEW.md'))) {
1152
- logger_1.logger.info(` 4. 审查[${svc.name}/${sub.name}]: ✅`);
1153
- reviewFound = true;
1154
- }
1155
- }
1156
- }
1157
- }
1158
- catch { /* 跳过 */ }
1114
+ for (const { platform, subtask, fullPath } of platDirs3) {
1115
+ if (await (0, fs_extra_1.pathExists)((0, path_1.join)(fullPath, 'REVIEW.md'))) {
1116
+ logger_1.logger.info(` 4. 审查[${platform}/${subtask}]: ✅`);
1117
+ reviewFound = true;
1159
1118
  }
1160
1119
  }
1161
1120
  if (!reviewFound) {
@@ -1164,34 +1123,30 @@ async function preFlightCheck(tasks, iteration, options) {
1164
1123
  // 5. API(00-specs/ 优先,_shared/ 回退)
1165
1124
  const hasApiContract = await (0, fs_extra_1.pathExists)((0, path_1.join)(taskDir, '00-specs', 'API_CONTRACT.yaml')) || await (0, fs_extra_1.pathExists)((0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml'));
1166
1125
  logger_1.logger.info(` 5. 契约: ${hasApiContract ? '✅' : '⚠️'}`);
1167
- // 6. Platform(扫描 10-backend/*/ 和 20-frontend/*/ 子任务目录)
1168
- const subtaskDirs = [];
1169
- for (const catDir of ['10-backend', '20-frontend']) {
1170
- const catPath = (0, path_1.join)(taskDir, catDir);
1171
- if (await (0, fs_extra_1.pathExists)(catPath)) {
1172
- try {
1173
- const platEntries = (0, fs_1.readdirSync)(catPath, { withFileTypes: true });
1174
- for (const pe of platEntries) {
1175
- if (pe.isDirectory()) {
1176
- const stEntries = (0, fs_1.readdirSync)((0, path_1.join)(catPath, pe.name), { withFileTypes: true });
1177
- for (const st of stEntries) {
1178
- if (st.isDirectory() && !st.name.startsWith('.')) {
1179
- subtaskDirs.push(`${catDir}/${pe.name}/${st.name}`);
1126
+ // 6. Platform(v6.49.9+: 扫描平铺的端目录)
1127
+ const subtaskDirs = platDirs3.map(p => `${p.platform}/${p.subtask}`);
1128
+ // 回退: 旧结构 10-backend/ 20-frontend/
1129
+ if (subtaskDirs.length === 0) {
1130
+ for (const catDir of ['10-backend', '20-frontend']) {
1131
+ const catPath = (0, path_1.join)(taskDir, catDir);
1132
+ if (await (0, fs_extra_1.pathExists)(catPath)) {
1133
+ try {
1134
+ const platEntries = (0, fs_1.readdirSync)(catPath, { withFileTypes: true });
1135
+ for (const pe of platEntries) {
1136
+ if (pe.isDirectory()) {
1137
+ const stEntries = (0, fs_1.readdirSync)((0, path_1.join)(catPath, pe.name), { withFileTypes: true });
1138
+ for (const st of stEntries) {
1139
+ if (st.isDirectory() && !st.name.startsWith('.')) {
1140
+ subtaskDirs.push(`${catDir}/${pe.name}/${st.name}`);
1141
+ }
1180
1142
  }
1181
1143
  }
1182
1144
  }
1183
1145
  }
1146
+ catch { /* ignore */ }
1184
1147
  }
1185
- catch { /* ignore */ }
1186
1148
  }
1187
1149
  }
1188
- // 回退: 旧结构 10-*/20-*
1189
- if (subtaskDirs.length === 0) {
1190
- const legacy = (0, fs_1.readdirSync)(taskDir, { withFileTypes: true })
1191
- .filter((d) => d.isDirectory() && (d.name.startsWith('10-') || d.name.startsWith('20-')))
1192
- .map((d) => d.name);
1193
- subtaskDirs.push(...legacy);
1194
- }
1195
1150
  if (subtaskDirs.length > 0) {
1196
1151
  logger_1.logger.info(` 6. 子任务: ${subtaskDirs.join(', ')}`);
1197
1152
  }
@@ -1412,18 +1367,17 @@ function buildAgentContext(tasks, agent) {
1412
1367
  ctx += `Status: ${task.status} | Priority: ${task.priority} | Type: ${task.type}\n\n`;
1413
1368
  // Progressive loading order
1414
1369
  ctx += `**Read in this order (progressive disclosure):**\n`;
1415
- ctx += `1. \`Iteration-*/${task.id}/10-backend/{service}/{subtask}/TASK.md\` — 后端子任务概览\n`;
1416
- ctx += `2. \`Iteration-*/${task.id}/20-frontend/{platform}/{subtask}/TASK.md\` — 前端子任务概览\n`;
1417
- ctx += `3. \`Iteration-*/${task.id}/00-specs/REQ.md\` — 需求 + 验收标准\n`;
1418
- ctx += `4. \`Iteration-*/${task.id}/00-specs/TECH.md\` — 技术设计\n`;
1419
- ctx += `5. \`Iteration-*/${task.id}/00-specs/API_CONTRACT.yaml\` — API 契约 (if exists)\n\n`;
1370
+ ctx += `1. \`Iteration-*/${task.id}/{platform}/{subtask}/TASK.md\` — 子任务概览(所有端平铺)\n`;
1371
+ ctx += `2. \`Iteration-*/${task.id}/00-specs/REQ.md\` — 需求 + 验收标准\n`;
1372
+ ctx += `3. \`Iteration-*/${task.id}/00-specs/TECH.md\` — 技术设计\n`;
1373
+ ctx += `4. \`Iteration-*/${task.id}/00-specs/API_CONTRACT.yaml\` — API 契约 (if exists)\n\n`;
1420
1374
  ctx += `**Supplementary (read only if needed):**\n`;
1421
1375
  ctx += `- 子任务目录/TEST.md (测试用例) | 子任务目录/REVIEW.md (评审清单)\n`;
1422
1376
  ctx += `- 00-specs/SCHEMA.md (DB schema) | 子任务目录/ADR.md (架构决策)\n`;
1423
1377
  ctx += `- 子任务目录/RISK.md (风险+回滚) | 子任务目录/DEPS.md (依赖) | 子任务目录/MONITOR.md (监控)\n`;
1424
1378
  ctx += `- 子任务目录/ERROR_CODES.md (错误码) | 子任务目录/DEPLOY.md (部署)\n\n`;
1425
1379
  ctx += `**Frontend design docs (if exists):**\n`;
1426
- ctx += `- 20-{platform}/COMPONENT_TREE.md | ROUTES.md | STATE.md | STYLE_GUIDE.md\n\n`;
1380
+ ctx += `- {platform}/COMPONENT_TREE.md | ROUTES.md | STATE.md | STYLE_GUIDE.md\n\n`;
1427
1381
  }
1428
1382
  // 3. Global rules (load last, only if needed)
1429
1383
  ctx += `### Step N: Global rules (load last)\n`;
@@ -1447,26 +1401,13 @@ async function executionVerifyLoop(tasks, iteration, options) {
1447
1401
  if (round > 1)
1448
1402
  logger_1.logger.info(` 🔄 第 ${round} 轮修复...`);
1449
1403
  allPassed = true;
1450
- // 1. 检查 TEST.md(扫描子任务目录,合并结果)
1404
+ // 1. 检查 TEST.md(v6.49.9+: 扫描平铺的端目录)
1451
1405
  const testFiles = [];
1452
- for (const catDir of ['10-backend', '20-frontend']) {
1453
- const catPath = (0, path_1.join)(taskDir, catDir);
1454
- if (await (0, fs_extra_1.pathExists)(catPath)) {
1455
- try {
1456
- for (const svc of await (0, fs_extra_1.readdir)(catPath, { withFileTypes: true })) {
1457
- if (!svc.isDirectory())
1458
- continue;
1459
- for (const sub of await (0, fs_extra_1.readdir)((0, path_1.join)(catPath, svc.name), { withFileTypes: true })) {
1460
- if (!sub.isDirectory())
1461
- continue;
1462
- const fp = (0, path_1.join)(catPath, svc.name, sub.name, 'TEST.md');
1463
- if (await (0, fs_extra_1.pathExists)(fp))
1464
- testFiles.push(fp);
1465
- }
1466
- }
1467
- }
1468
- catch { /* 跳过 */ }
1469
- }
1406
+ const platDirsRound = await getPlatformSubtaskDirs(taskDir);
1407
+ for (const { fullPath } of platDirsRound) {
1408
+ const fp = (0, path_1.join)(fullPath, 'TEST.md');
1409
+ if (await (0, fs_extra_1.pathExists)(fp))
1410
+ testFiles.push(fp);
1470
1411
  }
1471
1412
  if (testFiles.length === 0) {
1472
1413
  // 旧结构回退
@@ -1489,26 +1430,12 @@ async function executionVerifyLoop(tasks, iteration, options) {
1489
1430
  logger_1.logger.info(` 🧪 ${label}: ${done}/${total} ✅`);
1490
1431
  }
1491
1432
  }
1492
- // 2. 检查 REVIEW.md(扫描子任务目录)
1433
+ // 2. 检查 REVIEW.md(v6.49.9+: 扫描平铺的端目录)
1493
1434
  const reviewFiles = [];
1494
- for (const catDir of ['10-backend', '20-frontend']) {
1495
- const catPath = (0, path_1.join)(taskDir, catDir);
1496
- if (await (0, fs_extra_1.pathExists)(catPath)) {
1497
- try {
1498
- for (const svc of await (0, fs_extra_1.readdir)(catPath, { withFileTypes: true })) {
1499
- if (!svc.isDirectory())
1500
- continue;
1501
- for (const sub of await (0, fs_extra_1.readdir)((0, path_1.join)(catPath, svc.name), { withFileTypes: true })) {
1502
- if (!sub.isDirectory())
1503
- continue;
1504
- const fp = (0, path_1.join)(catPath, svc.name, sub.name, 'REVIEW.md');
1505
- if (await (0, fs_extra_1.pathExists)(fp))
1506
- reviewFiles.push(fp);
1507
- }
1508
- }
1509
- }
1510
- catch { /* 跳过 */ }
1511
- }
1435
+ for (const { fullPath } of platDirsRound) {
1436
+ const fp = (0, path_1.join)(fullPath, 'REVIEW.md');
1437
+ if (await (0, fs_extra_1.pathExists)(fp))
1438
+ reviewFiles.push(fp);
1512
1439
  }
1513
1440
  if (reviewFiles.length === 0) {
1514
1441
  const legacy = (0, path_1.join)(taskDir, '99-artifacts', 'REVIEW.md');
@@ -1530,26 +1457,12 @@ async function executionVerifyLoop(tasks, iteration, options) {
1530
1457
  logger_1.logger.info(` 📋 ${label}: ${doneR}/${total} ✅`);
1531
1458
  }
1532
1459
  }
1533
- // 3. 检查 DEPLOY.md(扫描子任务目录)
1460
+ // 3. 检查 DEPLOY.md(v6.49.9+: 扫描平铺的端目录)
1534
1461
  const deployFiles = [];
1535
- for (const catDir of ['10-backend', '20-frontend']) {
1536
- const catPath = (0, path_1.join)(taskDir, catDir);
1537
- if (await (0, fs_extra_1.pathExists)(catPath)) {
1538
- try {
1539
- for (const svc of await (0, fs_extra_1.readdir)(catPath, { withFileTypes: true })) {
1540
- if (!svc.isDirectory())
1541
- continue;
1542
- for (const sub of await (0, fs_extra_1.readdir)((0, path_1.join)(catPath, svc.name), { withFileTypes: true })) {
1543
- if (!sub.isDirectory())
1544
- continue;
1545
- const fp = (0, path_1.join)(catPath, svc.name, sub.name, 'DEPLOY.md');
1546
- if (await (0, fs_extra_1.pathExists)(fp))
1547
- deployFiles.push(fp);
1548
- }
1549
- }
1550
- }
1551
- catch { /* 跳过 */ }
1552
- }
1462
+ for (const { fullPath } of platDirsRound) {
1463
+ const fp = (0, path_1.join)(fullPath, 'DEPLOY.md');
1464
+ if (await (0, fs_extra_1.pathExists)(fp))
1465
+ deployFiles.push(fp);
1553
1466
  }
1554
1467
  if (deployFiles.length === 0) {
1555
1468
  const legacy = (0, path_1.join)(taskDir, '99-artifacts', 'DEPLOY.md');
@@ -1934,4 +1847,46 @@ function readStdin() {
1934
1847
  resolve('');
1935
1848
  });
1936
1849
  }
1850
+ async function getPlatformSubtaskDirs(taskDir) {
1851
+ const result = [];
1852
+ const platformList = await (0, spec_paths_1.parsePlatformList)();
1853
+ // 新结构: 所有端平铺在任务目录下
1854
+ for (const platform of platformList) {
1855
+ const platDir = (0, path_1.join)(taskDir, platform);
1856
+ if (!(await (0, fs_extra_1.pathExists)(platDir)))
1857
+ continue;
1858
+ try {
1859
+ const entries = await (0, fs_extra_1.readdir)(platDir, { withFileTypes: true });
1860
+ for (const entry of entries) {
1861
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
1862
+ result.push({ platform, subtask: entry.name, fullPath: (0, path_1.join)(platDir, entry.name) });
1863
+ }
1864
+ }
1865
+ }
1866
+ catch { /* ignore */ }
1867
+ }
1868
+ // 回退: 旧结构 10-backend/ 和 20-frontend/
1869
+ if (result.length === 0) {
1870
+ for (const catName of ['10-backend', '20-frontend']) {
1871
+ const catDir = (0, path_1.join)(taskDir, catName);
1872
+ if (!(await (0, fs_extra_1.pathExists)(catDir)))
1873
+ continue;
1874
+ try {
1875
+ const svcEntries = await (0, fs_extra_1.readdir)(catDir, { withFileTypes: true });
1876
+ for (const svc of svcEntries) {
1877
+ if (!svc.isDirectory())
1878
+ continue;
1879
+ const subEntries = await (0, fs_extra_1.readdir)((0, path_1.join)(catDir, svc.name), { withFileTypes: true });
1880
+ for (const st of subEntries) {
1881
+ if (st.isDirectory() && !st.name.startsWith('.')) {
1882
+ result.push({ platform: `${catName}/${svc.name}`, subtask: st.name, fullPath: (0, path_1.join)(catDir, svc.name, st.name) });
1883
+ }
1884
+ }
1885
+ }
1886
+ }
1887
+ catch { /* ignore */ }
1888
+ }
1889
+ }
1890
+ return result;
1891
+ }
1937
1892
  //# sourceMappingURL=execute.js.map