speccore 8.3.121 → 8.3.127

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 (144) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/about.d.ts.map +1 -1
  4. package/dist/commands/about.js +16 -0
  5. package/dist/commands/about.js.map +1 -1
  6. package/dist/commands/analyze.d.ts +1 -0
  7. package/dist/commands/analyze.d.ts.map +1 -1
  8. package/dist/commands/analyze.js +198 -70
  9. package/dist/commands/analyze.js.map +1 -1
  10. package/dist/commands/change.js +14 -7
  11. package/dist/commands/change.js.map +1 -1
  12. package/dist/commands/context.js +25 -31
  13. package/dist/commands/context.js.map +1 -1
  14. package/dist/commands/deploy.d.ts.map +1 -1
  15. package/dist/commands/deploy.js +6 -1
  16. package/dist/commands/deploy.js.map +1 -1
  17. package/dist/commands/done.js +29 -28
  18. package/dist/commands/done.js.map +1 -1
  19. package/dist/commands/execute.d.ts.map +1 -1
  20. package/dist/commands/execute.js +281 -20
  21. package/dist/commands/execute.js.map +1 -1
  22. package/dist/commands/import.js +1 -1
  23. package/dist/commands/import.js.map +1 -1
  24. package/dist/commands/init.d.ts +1 -0
  25. package/dist/commands/init.d.ts.map +1 -1
  26. package/dist/commands/init.js +513 -28
  27. package/dist/commands/init.js.map +1 -1
  28. package/dist/commands/iteration/create.js +1 -1
  29. package/dist/commands/iteration/split.d.ts.map +1 -1
  30. package/dist/commands/iteration/split.js +158 -70
  31. package/dist/commands/iteration/split.js.map +1 -1
  32. package/dist/commands/iteration-from-global.js +1 -2
  33. package/dist/commands/iteration-from-global.js.map +1 -1
  34. package/dist/commands/pattern.js +30 -16
  35. package/dist/commands/pattern.js.map +1 -1
  36. package/dist/commands/pipeline.d.ts.map +1 -1
  37. package/dist/commands/pipeline.js +15 -4
  38. package/dist/commands/pipeline.js.map +1 -1
  39. package/dist/commands/retro.d.ts.map +1 -1
  40. package/dist/commands/retro.js +15 -8
  41. package/dist/commands/retro.js.map +1 -1
  42. package/dist/commands/status-panel.js +1 -1
  43. package/dist/commands/status-panel.js.map +1 -1
  44. package/dist/commands/sync.js +15 -8
  45. package/dist/commands/sync.js.map +1 -1
  46. package/dist/commands/trace.js +27 -24
  47. package/dist/commands/trace.js.map +1 -1
  48. package/dist/commands/update-env-configs.d.ts.map +1 -1
  49. package/dist/commands/update-env-configs.js +33 -5
  50. package/dist/commands/update-env-configs.js.map +1 -1
  51. package/dist/commands/update.d.ts.map +1 -1
  52. package/dist/commands/update.js +35 -0
  53. package/dist/commands/update.js.map +1 -1
  54. package/dist/commands/verify.d.ts +1 -1
  55. package/dist/commands/verify.d.ts.map +1 -1
  56. package/dist/commands/verify.js +25 -0
  57. package/dist/commands/verify.js.map +1 -1
  58. package/dist/core/ai-context-generator.js +35 -22
  59. package/dist/core/ai-context-generator.js.map +1 -1
  60. package/dist/core/ai-impact-analyzer.d.ts.map +1 -1
  61. package/dist/core/ai-impact-analyzer.js +1 -1
  62. package/dist/core/ai-impact-analyzer.js.map +1 -1
  63. package/dist/core/analyze-engine.d.ts +2 -1
  64. package/dist/core/analyze-engine.d.ts.map +1 -1
  65. package/dist/core/analyze-engine.js +217 -53
  66. package/dist/core/analyze-engine.js.map +1 -1
  67. package/dist/core/ask-engine.d.ts.map +1 -1
  68. package/dist/core/ask-engine.js +31 -13
  69. package/dist/core/ask-engine.js.map +1 -1
  70. package/dist/core/change-detection.d.ts.map +1 -1
  71. package/dist/core/change-detection.js +21 -5
  72. package/dist/core/change-detection.js.map +1 -1
  73. package/dist/core/code-scanner.d.ts +12 -3
  74. package/dist/core/code-scanner.d.ts.map +1 -1
  75. package/dist/core/code-scanner.js +376 -10
  76. package/dist/core/code-scanner.js.map +1 -1
  77. package/dist/core/context-builder.d.ts +2 -1
  78. package/dist/core/context-builder.d.ts.map +1 -1
  79. package/dist/core/context-builder.js +104 -25
  80. package/dist/core/context-builder.js.map +1 -1
  81. package/dist/core/deploy/engine.d.ts.map +1 -1
  82. package/dist/core/deploy/engine.js +21 -6
  83. package/dist/core/deploy/engine.js.map +1 -1
  84. package/dist/core/git-integration.d.ts +5 -1
  85. package/dist/core/git-integration.d.ts.map +1 -1
  86. package/dist/core/git-integration.js +52 -15
  87. package/dist/core/git-integration.js.map +1 -1
  88. package/dist/core/knowledge-graph.d.ts +2 -0
  89. package/dist/core/knowledge-graph.d.ts.map +1 -1
  90. package/dist/core/knowledge-graph.js +246 -232
  91. package/dist/core/knowledge-graph.js.map +1 -1
  92. package/dist/core/name-validator.js +1 -1
  93. package/dist/core/name-validator.js.map +1 -1
  94. package/dist/core/pipeline-test.d.ts.map +1 -1
  95. package/dist/core/pipeline-test.js +7 -4
  96. package/dist/core/pipeline-test.js.map +1 -1
  97. package/dist/core/prompt-builder.d.ts +3 -0
  98. package/dist/core/prompt-builder.d.ts.map +1 -1
  99. package/dist/core/prompt-builder.js +629 -77
  100. package/dist/core/prompt-builder.js.map +1 -1
  101. package/dist/core/rag-engine.d.ts +10 -5
  102. package/dist/core/rag-engine.d.ts.map +1 -1
  103. package/dist/core/rag-engine.js +134 -16
  104. package/dist/core/rag-engine.js.map +1 -1
  105. package/dist/core/spec-paths.d.ts +42 -2
  106. package/dist/core/spec-paths.d.ts.map +1 -1
  107. package/dist/core/spec-paths.js +207 -11
  108. package/dist/core/spec-paths.js.map +1 -1
  109. package/dist/core/structured-extractor.d.ts +67 -0
  110. package/dist/core/structured-extractor.d.ts.map +1 -1
  111. package/dist/core/structured-extractor.js +1298 -23
  112. package/dist/core/structured-extractor.js.map +1 -1
  113. package/dist/core/test-config.d.ts +1 -1
  114. package/dist/core/test-config.d.ts.map +1 -1
  115. package/dist/core/test-config.js +85 -65
  116. package/dist/core/test-config.js.map +1 -1
  117. package/dist/core/ui-verify/report-generator.d.ts.map +1 -1
  118. package/dist/core/ui-verify/report-generator.js +7 -3
  119. package/dist/core/ui-verify/report-generator.js.map +1 -1
  120. package/dist/core/ui-verify/smoke-engine.js +7 -1
  121. package/dist/core/ui-verify/smoke-engine.js.map +1 -1
  122. package/dist/core/unified-config.d.ts +43 -3
  123. package/dist/core/unified-config.d.ts.map +1 -1
  124. package/dist/core/unified-config.js +133 -13
  125. package/dist/core/unified-config.js.map +1 -1
  126. package/dist/core/unified-retrieval.d.ts.map +1 -1
  127. package/dist/core/unified-retrieval.js +41 -15
  128. package/dist/core/unified-retrieval.js.map +1 -1
  129. package/dist/core/unit-context-assembler.js +2 -2
  130. package/dist/core/unit-context-assembler.js.map +1 -1
  131. package/dist/core/validator.d.ts.map +1 -1
  132. package/dist/core/validator.js +59 -44
  133. package/dist/core/validator.js.map +1 -1
  134. package/dist/core/verify-engine.d.ts +9 -2
  135. package/dist/core/verify-engine.d.ts.map +1 -1
  136. package/dist/core/verify-engine.js +274 -108
  137. package/dist/core/verify-engine.js.map +1 -1
  138. package/dist/core/verify-report-aggregator.js +9 -20
  139. package/dist/core/verify-report-aggregator.js.map +1 -1
  140. package/dist/utils/synonyms.d.ts +24 -0
  141. package/dist/utils/synonyms.d.ts.map +1 -0
  142. package/dist/utils/synonyms.js +114 -0
  143. package/dist/utils/synonyms.js.map +1 -0
  144. package/package.json +1 -1
@@ -46,6 +46,7 @@ exports.getTaskContext = getTaskContext;
46
46
  exports.isGraphStale = isGraphStale;
47
47
  exports.incrementalUpdateKnowledgeGraph = incrementalUpdateKnowledgeGraph;
48
48
  exports.refreshKnowledgeGraph = refreshKnowledgeGraph;
49
+ exports.loadFreshKnowledgeGraph = loadFreshKnowledgeGraph;
49
50
  exports.traceDependencyChain = traceDependencyChain;
50
51
  exports.getFullTaskContext = getFullTaskContext;
51
52
  const fs_extra_1 = require("fs-extra");
@@ -309,113 +310,136 @@ async function scanBusinessCodeMappings(iterDir) {
309
310
  const specsDir = (0, path_1.join)(iterDir, '020-specs');
310
311
  if (!(await (0, fs_extra_1.pathExists)(specsDir)))
311
312
  return { entities, relations };
312
- // 扫描各端子目录
313
- const knownNonPlatformDirs = new Set(['sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research', 'staging', 'platforms', 'snapshots', 'global']);
313
+ // v8.3.122+: 同时兼容新旧两种路径结构
314
+ // 新结构: 020-specs/{feature}/{platform}/TECH.md
315
+ // 旧结构: 020-specs/{platform}/TECH.md
316
+ const knownNonPlatformDirs = new Set(['sources', 'assets', 'prototypes', 'converted', 'features', 'bugs', 'refactors', 'research', 'staging', 'platforms', 'snapshots', 'global', 'overview', 'requirements']);
314
317
  const specsEntries = await (0, fs_extra_1.readdir)(specsDir, { withFileTypes: true });
315
- for (const e of specsEntries) {
316
- if (!e.isDirectory() || e.name.startsWith('_') || e.name.startsWith('.') || knownNonPlatformDirs.has(e.name))
318
+ for (const featureEntry of specsEntries) {
319
+ if (!featureEntry.isDirectory() || featureEntry.name.startsWith('_') || featureEntry.name.startsWith('.') || knownNonPlatformDirs.has(featureEntry.name))
317
320
  continue;
318
- const platformName = e.name;
319
- const techMdPath = (0, path_1.join)(specsDir, platformName, 'TECH.md');
320
- if (!(await (0, fs_extra_1.pathExists)(techMdPath)))
321
+ const featureName = featureEntry.name;
322
+ const featureDir = (0, path_1.join)(specsDir, featureName);
323
+ // 检测是旧结构(直接有 TECH.md)还是新结构(有子目录)
324
+ const hasTechMdDirectly = await (0, fs_extra_1.pathExists)((0, path_1.join)(featureDir, 'TECH.md'));
325
+ if (hasTechMdDirectly) {
326
+ // 旧结构: 020-specs/{platform}/TECH.md — 把 featureName 当成 platformName
327
+ await processTechMdForMappings(featureDir, featureName, '', entities, relations);
328
+ }
329
+ else {
330
+ // 新结构: 020-specs/{feature}/{platform}/TECH.md
331
+ const platformEntries = await (0, fs_extra_1.readdir)(featureDir, { withFileTypes: true }).catch(() => []);
332
+ for (const platformEntry of platformEntries) {
333
+ if (!platformEntry.isDirectory() || platformEntry.name.startsWith('.') || knownNonPlatformDirs.has(platformEntry.name))
334
+ continue;
335
+ const platformDir = (0, path_1.join)(featureDir, platformEntry.name);
336
+ await processTechMdForMappings(platformDir, platformEntry.name, featureName, entities, relations);
337
+ }
338
+ }
339
+ }
340
+ return { entities, relations };
341
+ }
342
+ /** 从单个 TECH.md 中提取业务-代码映射 */
343
+ async function processTechMdForMappings(dir, platformName, featureName, entities, relations) {
344
+ const techMdPath = (0, path_1.join)(dir, 'TECH.md');
345
+ if (!(await (0, fs_extra_1.pathExists)(techMdPath)))
346
+ return;
347
+ const content = await (0, fs_extra_1.readFile)(techMdPath, 'utf-8').catch(() => '');
348
+ if (!content)
349
+ return;
350
+ // 查找「业务-代码映射」章节
351
+ const mappingSectionMatch = content.match(/##\s+业务-代码映射[\s\S]*?(?=\n##\s|$)/i);
352
+ if (!mappingSectionMatch)
353
+ return;
354
+ const sectionContent = mappingSectionMatch[0];
355
+ const lines = sectionContent.split(/\r?\n/);
356
+ let inTable = false;
357
+ let headerParsed = false;
358
+ let colIndices = { module: -1, entity: -1, relation: -1, desc: -1 };
359
+ const seenModules = new Set();
360
+ for (const line of lines) {
361
+ const cells = line.split('|').map(c => c.trim()).filter(Boolean);
362
+ if (cells.length < 2)
321
363
  continue;
322
- const content = await (0, fs_extra_1.readFile)(techMdPath, 'utf-8').catch(() => '');
323
- if (!content)
364
+ if (!headerParsed && cells.some(c => c.includes('业务模块') || c.includes('代码实体'))) {
365
+ colIndices = {
366
+ module: cells.findIndex(c => c.includes('业务模块')),
367
+ entity: cells.findIndex(c => c.includes('代码实体')),
368
+ relation: cells.findIndex(c => c.includes('关系类型')),
369
+ desc: cells.findIndex(c => c.includes('说明')),
370
+ };
371
+ if (colIndices.module < 0)
372
+ colIndices.module = 0;
373
+ if (colIndices.entity < 0)
374
+ colIndices.entity = 1;
375
+ headerParsed = true;
376
+ inTable = true;
324
377
  continue;
325
- // 查找「业务-代码映射」章节(不用 m 标志,$ 仅匹配字符串末尾)
326
- const mappingSectionMatch = content.match(/##\s+业务-代码映射[\s\S]*?(?=\n##\s|$)/i);
327
- if (!mappingSectionMatch)
378
+ }
379
+ if (cells.every(c => /^[-:]+$/.test(c)))
328
380
  continue;
329
- const sectionContent = mappingSectionMatch[0];
330
- // 解析表格
331
- const lines = sectionContent.split(/\r?\n/);
332
- let inTable = false;
333
- let headerParsed = false;
334
- let colIndices = { module: -1, entity: -1, relation: -1, desc: -1 };
335
- // 用于去重的业务模块集合
336
- const seenModules = new Set();
337
- for (const line of lines) {
338
- const cells = line.split('|').map(c => c.trim()).filter(Boolean);
339
- if (cells.length < 2)
340
- continue;
341
- // 表头行
342
- if (!headerParsed && cells.some(c => c.includes('业务模块') || c.includes('代码实体'))) {
343
- colIndices = {
344
- module: cells.findIndex(c => c.includes('业务模块')),
345
- entity: cells.findIndex(c => c.includes('代码实体')),
346
- relation: cells.findIndex(c => c.includes('关系类型')),
347
- desc: cells.findIndex(c => c.includes('说明')),
348
- };
349
- if (colIndices.module < 0)
350
- colIndices.module = 0;
351
- if (colIndices.entity < 0)
352
- colIndices.entity = 1;
353
- headerParsed = true;
354
- inTable = true;
355
- continue;
356
- }
357
- // 分隔行跳过
358
- if (cells.every(c => /^[-:]+$/.test(c)))
359
- continue;
360
- if (!inTable || !headerParsed)
361
- continue;
362
- // 数据行
363
- const moduleName = cells[colIndices.module];
364
- const codeEntity = cells[colIndices.entity];
365
- const relationType = colIndices.relation >= 0 && colIndices.relation < cells.length ? cells[colIndices.relation] : 'maps_to';
366
- const desc = colIndices.desc >= 0 && colIndices.desc < cells.length ? cells[colIndices.desc] : '';
367
- if (!moduleName || !codeEntity)
368
- continue;
369
- if (moduleName === '业务模块' || moduleName === '#')
370
- continue;
371
- // 创建业务模块实体(去重)
372
- const bizModuleId = `biz:${platformName}:${moduleName}`;
373
- if (!seenModules.has(bizModuleId)) {
374
- seenModules.add(bizModuleId);
375
- entities.push({
376
- id: bizModuleId,
377
- type: 'business_module',
378
- title: moduleName,
379
- file: `020-specs/${platformName}/TECH.md`,
380
- hash: '',
381
- mtime: '',
382
- platform: platformName,
383
- businessModule: moduleName,
384
- tags: ['business-mapping'],
385
- });
386
- // 关联到对应的 spec 实体
387
- const specId = `SPEC:${platformName}/TECH`;
388
- relations.push({
389
- from: bizModuleId,
390
- to: specId,
391
- type: 'elaborates',
392
- metadata: { source: 'tech-md-mapping' },
393
- });
394
- }
395
- // 创建代码实体(作为 business_module 的关联目标)
396
- const codeEntityId = `code:${platformName}:${codeEntity.replace(/[/\\]/g, '-')}`;
397
- if (!entities.find(e => e.id === codeEntityId)) {
398
- entities.push({
399
- id: codeEntityId,
400
- type: 'source-file',
401
- title: codeEntity,
402
- file: codeEntity,
403
- hash: '',
404
- mtime: '',
405
- platform: platformName,
406
- tags: ['code-entity', relationType],
407
- });
408
- }
409
- // 创建关系
381
+ if (!inTable || !headerParsed)
382
+ continue;
383
+ const moduleName = cells[colIndices.module];
384
+ const codeEntity = cells[colIndices.entity];
385
+ const relationType = colIndices.relation >= 0 && colIndices.relation < cells.length ? cells[colIndices.relation] : 'maps_to';
386
+ const desc = colIndices.desc >= 0 && colIndices.desc < cells.length ? cells[colIndices.desc] : '';
387
+ if (!moduleName || !codeEntity)
388
+ continue;
389
+ if (moduleName === '业务模块' || moduleName === '#')
390
+ continue;
391
+ // v8.3.122+: bizModuleId 包含 featureName 避免跨模块冲突
392
+ const bizModuleId = featureName
393
+ ? `biz:${featureName}:${platformName}:${moduleName}`
394
+ : `biz:${platformName}:${moduleName}`;
395
+ const filePath = featureName
396
+ ? `020-specs/${featureName}/${platformName}/TECH.md`
397
+ : `020-specs/${platformName}/TECH.md`;
398
+ const specId = featureName
399
+ ? `SPEC:${featureName}/${platformName}/TECH`
400
+ : `SPEC:${platformName}/TECH`;
401
+ if (!seenModules.has(bizModuleId)) {
402
+ seenModules.add(bizModuleId);
403
+ entities.push({
404
+ id: bizModuleId,
405
+ type: 'business_module',
406
+ title: moduleName,
407
+ file: filePath,
408
+ hash: '',
409
+ mtime: '',
410
+ platform: platformName,
411
+ businessModule: moduleName,
412
+ tags: ['business-mapping'],
413
+ });
410
414
  relations.push({
411
415
  from: bizModuleId,
412
- to: codeEntityId,
413
- type: relationType || 'maps_to',
414
- metadata: { description: desc, source: 'tech-md-mapping' },
416
+ to: specId,
417
+ type: 'elaborates',
418
+ metadata: { source: 'tech-md-mapping' },
419
+ });
420
+ }
421
+ const codeEntityId = featureName
422
+ ? `code:${featureName}:${platformName}:${codeEntity.replace(/[/\\]/g, '-')}`
423
+ : `code:${platformName}:${codeEntity.replace(/[/\\]/g, '-')}`;
424
+ if (!entities.find(e => e.id === codeEntityId)) {
425
+ entities.push({
426
+ id: codeEntityId,
427
+ type: 'source-file',
428
+ title: codeEntity,
429
+ file: codeEntity,
430
+ hash: '',
431
+ mtime: '',
432
+ platform: platformName,
433
+ tags: ['code-entity', relationType],
415
434
  });
416
435
  }
436
+ relations.push({
437
+ from: bizModuleId,
438
+ to: codeEntityId,
439
+ type: relationType || 'maps_to',
440
+ metadata: { description: desc, source: 'tech-md-mapping' },
441
+ });
417
442
  }
418
- return { entities, relations };
419
443
  }
420
444
  // ═══════════════════════════════════════════════════
421
445
  // 任务扫描(含子任务)
@@ -509,94 +533,84 @@ async function scanTasks(iterDir) {
509
533
  status,
510
534
  tags: [type],
511
535
  });
512
- // 扫描各端子任务(新结构: 10-backend/{service}/{subtask}/ + 20-frontend/{platform}/{subtask}/)
536
+ // v8.3.121+: 端平铺结构扫描 — {端名}/{子任务}/
537
+ const EXCLUDE_DIRS = new Set(['00-specs', '_shared', '99-artifacts', '.meta']);
513
538
  const dirEntries = await (0, fs_extra_1.readdir)(taskPath, { withFileTypes: true }).catch(() => []);
514
- const hasNewStructure = dirEntries.some(de => de.isDirectory() && (de.name === '10-backend' || de.name === '20-frontend'));
515
- if (hasNewStructure) {
516
- // 新结构:三级嵌套
517
- for (const catDir of ['10-backend', '20-frontend']) {
518
- const catPath = (0, path_1.join)(taskPath, catDir);
519
- if (!(await (0, fs_extra_1.pathExists)(catPath)))
520
- continue;
521
- const serviceEntries = await (0, fs_extra_1.readdir)(catPath, { withFileTypes: true }).catch(() => []);
522
- for (const svc of serviceEntries) {
523
- if (!svc.isDirectory())
539
+ for (const de of dirEntries) {
540
+ if (!de.isDirectory() || de.name.startsWith('.') || EXCLUDE_DIRS.has(de.name))
541
+ continue;
542
+ if ((0, task_utils_1.isTimestampBackup)(de.name))
543
+ continue;
544
+ const platformPath = (0, path_1.join)(taskPath, de.name);
545
+ const subtaskEntries = await (0, fs_extra_1.readdir)(platformPath, { withFileTypes: true }).catch(() => []);
546
+ const hasSubtasks = subtaskEntries.some((se) => se.isDirectory() && !se.name.startsWith('.') && !(0, task_utils_1.isTimestampBackup)(se.name));
547
+ if (hasSubtasks) {
548
+ // 标准端平铺结构:{端名}/{子任务}/
549
+ for (const sub of subtaskEntries) {
550
+ if (!sub.isDirectory() || sub.name.startsWith('.') || (0, task_utils_1.isTimestampBackup)(sub.name))
524
551
  continue;
525
- const subEntries = await (0, fs_extra_1.readdir)((0, path_1.join)(catPath, svc.name), { withFileTypes: true }).catch(() => []);
526
- for (const sub of subEntries) {
527
- if (!sub.isDirectory() || sub.name.startsWith('.'))
528
- continue;
529
- const subtaskPath = (0, path_1.join)(catPath, svc.name, sub.name);
530
- const platformLabel = `${svc.name}/${sub.name}`;
531
- const subtaskTaskMd = (0, path_1.join)(subtaskPath, 'TASK.md');
532
- let subTitle = `${title} — ${platformLabel}`;
533
- let subStatus = 'pending';
534
- let subTaskId = `${taskId}-${svc.name}-${sub.name}`;
535
- if (await (0, fs_extra_1.pathExists)(subtaskTaskMd)) {
536
- const subContent = await (0, fs_extra_1.readFile)(subtaskTaskMd, 'utf-8');
537
- const titleMatch = subContent.match(/^#\s+(.+)/m);
538
- if (titleMatch)
539
- subTitle = titleMatch[1].trim().slice(0, 80);
540
- const statusMatch = subContent.match(/\*\*状态\*\*[:\s]*(.+)/);
541
- if (statusMatch) {
542
- const raw = statusMatch[1].trim();
543
- if (raw.includes('已完成') || raw.includes('completed'))
544
- subStatus = 'completed';
545
- else if (raw.includes('进行中') || raw.includes('in_progress'))
546
- subStatus = 'in_progress';
547
- }
548
- const subIdMatch = subContent.match(/子任务 ID\*\*[:\s]*`(Task-[^`]+)`/);
549
- if (subIdMatch)
550
- subTaskId = subIdMatch[1];
552
+ const subtaskPath = (0, path_1.join)(platformPath, sub.name);
553
+ const platformLabel = `${de.name}/${sub.name}`;
554
+ const subtaskTaskMd = (0, path_1.join)(subtaskPath, 'TASK.md');
555
+ let subTitle = `${title} — ${platformLabel}`;
556
+ let subStatus = 'pending';
557
+ let subTaskId = `${taskId}-${de.name}-${sub.name}`;
558
+ if (await (0, fs_extra_1.pathExists)(subtaskTaskMd)) {
559
+ const subContent = await (0, fs_extra_1.readFile)(subtaskTaskMd, 'utf-8');
560
+ const titleMatch = subContent.match(/^#\s+(.+)/m);
561
+ if (titleMatch)
562
+ subTitle = titleMatch[1].trim().slice(0, 80);
563
+ const statusMatch = subContent.match(/\*\*状态\*\*[:\s]*(.+)/);
564
+ if (statusMatch) {
565
+ const raw = statusMatch[1].trim();
566
+ if (raw.includes('已完成') || raw.includes('completed'))
567
+ subStatus = 'completed';
568
+ else if (raw.includes('进行中') || raw.includes('in_progress'))
569
+ subStatus = 'in_progress';
551
570
  }
552
- // hash 回退:TASK.md → .meta/status → src/ → 空
553
- let subHash = { hash: '', mtime: '' };
554
- if (await (0, fs_extra_1.pathExists)(subtaskTaskMd)) {
555
- subHash = await fileHash(subtaskTaskMd);
571
+ const subIdMatch = subContent.match(/子任务 ID\*\*[:\s]*`(Task-[^`]+)`/);
572
+ if (subIdMatch)
573
+ subTaskId = subIdMatch[1];
574
+ }
575
+ // hash 回退:TASK.md → .meta/status → src/ → 空
576
+ let subHash = { hash: '', mtime: '' };
577
+ if (await (0, fs_extra_1.pathExists)(subtaskTaskMd)) {
578
+ subHash = await fileHash(subtaskTaskMd);
579
+ }
580
+ else {
581
+ const metaStatus = (0, path_1.join)(subtaskPath, '.meta', 'status');
582
+ if (await (0, fs_extra_1.pathExists)(metaStatus)) {
583
+ subHash = await fileHash(metaStatus);
556
584
  }
557
585
  else {
558
- const metaStatus = (0, path_1.join)(subtaskPath, '.meta', 'status');
559
- if (await (0, fs_extra_1.pathExists)(metaStatus)) {
560
- subHash = await fileHash(metaStatus);
561
- }
562
- else {
563
- const srcDir = (0, path_1.join)(subtaskPath, 'src');
564
- if (await (0, fs_extra_1.pathExists)(srcDir)) {
565
- try {
566
- const srcFiles = await (0, fs_extra_1.readdir)(srcDir);
567
- if (srcFiles.length > 0)
568
- subHash = await fileHash((0, path_1.join)(srcDir, srcFiles[0]));
569
- }
570
- catch { /* 忽略 */ }
586
+ const srcDir = (0, path_1.join)(subtaskPath, 'src');
587
+ if (await (0, fs_extra_1.pathExists)(srcDir)) {
588
+ try {
589
+ const srcFiles = await (0, fs_extra_1.readdir)(srcDir);
590
+ if (srcFiles.length > 0)
591
+ subHash = await fileHash((0, path_1.join)(srcDir, srcFiles[0]));
571
592
  }
593
+ catch { /* 忽略 */ }
572
594
  }
573
595
  }
574
- entities.push({
575
- id: subTaskId,
576
- type: 'subtask',
577
- title: subTitle,
578
- file: `030-tasks/${type === 'feature' ? '' : type + '/'}${taskId}/${catDir}/${svc.name}/${sub.name}`,
579
- hash: subHash.hash,
580
- mtime: subHash.mtime,
581
- status: subStatus,
582
- platform: platformLabel,
583
- parentTaskId: taskId,
584
- });
585
- relations.push({ from: subTaskId, to: taskId, type: 'subtask_of' });
586
596
  }
597
+ entities.push({
598
+ id: subTaskId,
599
+ type: 'subtask',
600
+ title: subTitle,
601
+ file: `030-tasks/${type === 'feature' ? '' : type + '/'}${taskId}/${de.name}/${sub.name}`,
602
+ hash: subHash.hash,
603
+ mtime: subHash.mtime,
604
+ status: subStatus,
605
+ platform: platformLabel,
606
+ parentTaskId: taskId,
607
+ });
608
+ relations.push({ from: subTaskId, to: taskId, type: 'subtask_of' });
587
609
  }
588
610
  }
589
- }
590
- else {
591
- // 旧结构:扁平平台目录
592
- for (const de of dirEntries) {
593
- if (!de.isDirectory())
594
- continue;
595
- if (de.name.startsWith('.') || de.name.startsWith('0') || de.name === '_shared' || de.name === '99-artifacts')
596
- continue;
597
- if ((0, task_utils_1.isTimestampBackup)(de.name))
598
- continue;
599
- const platformTaskMd = (0, path_1.join)(taskPath, de.name, 'TASK.md');
611
+ else {
612
+ // 兼容:端目录下无子任务,端本身作为子任务(如旧数据的单端任务)
613
+ const platformTaskMd = (0, path_1.join)(platformPath, 'TASK.md');
600
614
  let subTitle = `${title} — ${de.name}`;
601
615
  let subStatus = 'pending';
602
616
  let subTaskId = `${taskId}-${de.name}`;
@@ -622,7 +636,7 @@ async function scanTasks(iterDir) {
622
636
  subHash = await fileHash(platformTaskMd);
623
637
  }
624
638
  else {
625
- const srcDir = (0, path_1.join)(taskPath, de.name, 'src');
639
+ const srcDir = (0, path_1.join)(platformPath, 'src');
626
640
  if (await (0, fs_extra_1.pathExists)(srcDir)) {
627
641
  try {
628
642
  const srcFiles = await (0, fs_extra_1.readdir)(srcDir);
@@ -1017,41 +1031,10 @@ async function scanTaskSpecs(iterDir) {
1017
1031
  const taskDirs = await findTaskDirs(tasksDir);
1018
1032
  for (const taskDir of taskDirs) {
1019
1033
  const taskId = (0, path_1.basename)(taskDir) || '';
1020
- const subDirs = ['_shared', '00-specs'];
1021
- // 新结构: 10-backend/{service}/{subtask}/ + 20-frontend/{platform}/{subtask}/
1022
- const nestedSubDirs = [];
1023
- try {
1024
- const entries = await (0, fs_extra_1.readdir)(taskDir, { withFileTypes: true });
1025
- for (const e of entries) {
1026
- if (!e.isDirectory() || e.name.startsWith('.') || (0, task_utils_1.isTimestampBackup)(e.name))
1027
- continue;
1028
- if (e.name === '10-backend' || e.name === '20-frontend') {
1029
- // 新结构:深入两层(service/platform → subtask)
1030
- const catPath = (0, path_1.join)(taskDir, e.name);
1031
- try {
1032
- const svcEntries = await (0, fs_extra_1.readdir)(catPath, { withFileTypes: true });
1033
- for (const svc of svcEntries) {
1034
- if (!svc.isDirectory())
1035
- continue;
1036
- const subEntries = await (0, fs_extra_1.readdir)((0, path_1.join)(catPath, svc.name), { withFileTypes: true });
1037
- for (const sub of subEntries) {
1038
- if (!sub.isDirectory() || sub.name.startsWith('.'))
1039
- continue;
1040
- nestedSubDirs.push(`${e.name}/${svc.name}/${sub.name}`);
1041
- }
1042
- }
1043
- }
1044
- catch { /* 跳过 */ }
1045
- }
1046
- else if (!e.name.startsWith('0') && e.name !== '_shared' && e.name !== '99-artifacts') {
1047
- // 旧结构:扁平平台目录
1048
- subDirs.push(e.name);
1049
- }
1050
- }
1051
- }
1052
- catch { /* 跳过 */ }
1053
- // 扫描任务根下的子目录(_shared/、00-specs/、旧平台目录)
1054
- for (const subDir of subDirs) {
1034
+ // v8.3.121+: 端平铺结构扫描 — {端名}/{子任务}/
1035
+ const EXCLUDE_DIRS = new Set(['00-specs', '_shared', '99-artifacts', '.meta']);
1036
+ // 扫描任务根下的规格目录(_shared/、00-specs/)
1037
+ for (const subDir of ['_shared', '00-specs']) {
1055
1038
  const specDir = (0, path_1.join)(taskDir, subDir);
1056
1039
  if (!(await (0, fs_extra_1.pathExists)(specDir)))
1057
1040
  continue;
@@ -1074,31 +1057,44 @@ async function scanTaskSpecs(iterDir) {
1074
1057
  relations.push({ from: `TSPEC:${taskId}-${subDir}-${specType}`, to: taskId, type: 'elaborates' });
1075
1058
  }
1076
1059
  }
1077
- // 扫描新结构的子任务目录(10-backend/svc/sub/、20-frontend/plat/sub/)
1078
- for (const nestedDir of nestedSubDirs) {
1079
- const specDir = (0, path_1.join)(taskDir, nestedDir);
1080
- if (!(await (0, fs_extra_1.pathExists)(specDir)))
1081
- continue;
1082
- for (const specFile of TASK_SPEC_FILES) {
1083
- const specPath = (0, path_1.join)(specDir, specFile);
1084
- if (!(await (0, fs_extra_1.pathExists)(specPath)))
1060
+ // 扫描端平铺结构下的子任务规格文件
1061
+ try {
1062
+ const platformEntries = await (0, fs_extra_1.readdir)(taskDir, { withFileTypes: true });
1063
+ for (const pe of platformEntries) {
1064
+ if (!pe.isDirectory() || pe.name.startsWith('.') || EXCLUDE_DIRS.has(pe.name))
1085
1065
  continue;
1086
- const { hash, mtime } = await fileHash(specPath);
1087
- const title = await extractTitle(specPath);
1088
- const specType = specFile.replace('.md', '').toLowerCase();
1089
- const label = nestedDir.replace(/\//g, '-');
1090
- entities.push({
1091
- id: `TSPEC:${taskId}-${label}-${specType}`,
1092
- type: 'task-spec',
1093
- title: title || `${taskId}/${nestedDir}/${specFile}`,
1094
- file: `030-tasks/**/${taskId}/${nestedDir}/${specFile}`,
1095
- hash,
1096
- mtime,
1097
- tags: ['task-spec', specType, label],
1098
- });
1099
- relations.push({ from: `TSPEC:${taskId}-${label}-${specType}`, to: taskId, type: 'elaborates' });
1066
+ if ((0, task_utils_1.isTimestampBackup)(pe.name))
1067
+ continue;
1068
+ const platformPath = (0, path_1.join)(taskDir, pe.name);
1069
+ const subtaskEntries = await (0, fs_extra_1.readdir)(platformPath, { withFileTypes: true });
1070
+ for (const sub of subtaskEntries) {
1071
+ if (!sub.isDirectory() || sub.name.startsWith('.') || (0, task_utils_1.isTimestampBackup)(sub.name))
1072
+ continue;
1073
+ const subtaskPath = (0, path_1.join)(platformPath, sub.name);
1074
+ const nestedDir = `${pe.name}/${sub.name}`;
1075
+ const label = `${pe.name}-${sub.name}`;
1076
+ for (const specFile of TASK_SPEC_FILES) {
1077
+ const specPath = (0, path_1.join)(subtaskPath, specFile);
1078
+ if (!(await (0, fs_extra_1.pathExists)(specPath)))
1079
+ continue;
1080
+ const { hash, mtime } = await fileHash(specPath);
1081
+ const title = await extractTitle(specPath);
1082
+ const specType = specFile.replace('.md', '').toLowerCase();
1083
+ entities.push({
1084
+ id: `TSPEC:${taskId}-${label}-${specType}`,
1085
+ type: 'task-spec',
1086
+ title: title || `${taskId}/${nestedDir}/${specFile}`,
1087
+ file: `030-tasks/**/${taskId}/${nestedDir}/${specFile}`,
1088
+ hash,
1089
+ mtime,
1090
+ tags: ['task-spec', specType, label],
1091
+ });
1092
+ relations.push({ from: `TSPEC:${taskId}-${label}-${specType}`, to: taskId, type: 'elaborates' });
1093
+ }
1094
+ }
1100
1095
  }
1101
1096
  }
1097
+ catch { /* 跳过 */ }
1102
1098
  }
1103
1099
  return { entities, relations };
1104
1100
  }
@@ -1812,6 +1808,24 @@ async function incrementalUpdateKnowledgeGraph(cwd, iteration) {
1812
1808
  async function refreshKnowledgeGraph(cwd, iteration) {
1813
1809
  return incrementalUpdateKnowledgeGraph(cwd, iteration);
1814
1810
  }
1811
+ /** v8.3.125+: 加载知识图谱(自动检查过期并刷新) */
1812
+ async function loadFreshKnowledgeGraph(cwd, iteration) {
1813
+ let graph = await loadKnowledgeGraph(cwd);
1814
+ if (!graph)
1815
+ return null;
1816
+ try {
1817
+ const stale = await isGraphStale(cwd, iteration);
1818
+ if (stale) {
1819
+ graph = await refreshKnowledgeGraph(cwd, iteration);
1820
+ if (graph)
1821
+ logger_1.logger?.info?.('🧠 知识图谱已自动刷新(检测到变更)');
1822
+ }
1823
+ }
1824
+ catch {
1825
+ // 过期检查失败不阻断,使用现有图谱
1826
+ }
1827
+ return graph;
1828
+ }
1815
1829
  /**
1816
1830
  * 追踪任务的完整依赖链路(向上追溯)
1817
1831
  * 递归追踪 implements → references → depends_on 关系