speccore 8.1.2 → 8.3.2

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 (74) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +24 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/analyze.d.ts +38 -0
  5. package/dist/commands/analyze.d.ts.map +1 -1
  6. package/dist/commands/analyze.js +1107 -135
  7. package/dist/commands/analyze.js.map +1 -1
  8. package/dist/commands/change.d.ts.map +1 -1
  9. package/dist/commands/change.js +28 -0
  10. package/dist/commands/change.js.map +1 -1
  11. package/dist/commands/clarify.d.ts +6 -0
  12. package/dist/commands/clarify.d.ts.map +1 -1
  13. package/dist/commands/clarify.js +172 -14
  14. package/dist/commands/clarify.js.map +1 -1
  15. package/dist/commands/done.d.ts.map +1 -1
  16. package/dist/commands/done.js +312 -3
  17. package/dist/commands/done.js.map +1 -1
  18. package/dist/commands/execute.d.ts.map +1 -1
  19. package/dist/commands/execute.js +112 -2
  20. package/dist/commands/execute.js.map +1 -1
  21. package/dist/commands/iteration/split.d.ts.map +1 -1
  22. package/dist/commands/iteration/split.js +111 -14
  23. package/dist/commands/iteration/split.js.map +1 -1
  24. package/dist/commands/pattern.d.ts +2 -0
  25. package/dist/commands/pattern.d.ts.map +1 -1
  26. package/dist/commands/pattern.js +43 -0
  27. package/dist/commands/pattern.js.map +1 -1
  28. package/dist/commands/plan.d.ts.map +1 -1
  29. package/dist/commands/plan.js +99 -1
  30. package/dist/commands/plan.js.map +1 -1
  31. package/dist/commands/workspace.d.ts +20 -0
  32. package/dist/commands/workspace.d.ts.map +1 -0
  33. package/dist/commands/workspace.js +142 -0
  34. package/dist/commands/workspace.js.map +1 -0
  35. package/dist/core/ask-engine.d.ts.map +1 -1
  36. package/dist/core/ask-engine.js +18 -0
  37. package/dist/core/ask-engine.js.map +1 -1
  38. package/dist/core/intent-recognition.d.ts +7 -1
  39. package/dist/core/intent-recognition.d.ts.map +1 -1
  40. package/dist/core/intent-recognition.js +33 -3
  41. package/dist/core/intent-recognition.js.map +1 -1
  42. package/dist/core/knowledge-graph.d.ts +6 -1
  43. package/dist/core/knowledge-graph.d.ts.map +1 -1
  44. package/dist/core/knowledge-graph.js +268 -7
  45. package/dist/core/knowledge-graph.js.map +1 -1
  46. package/dist/core/pattern-detector.d.ts +26 -0
  47. package/dist/core/pattern-detector.d.ts.map +1 -0
  48. package/dist/core/pattern-detector.js +256 -0
  49. package/dist/core/pattern-detector.js.map +1 -0
  50. package/dist/core/prompt-builder.d.ts.map +1 -1
  51. package/dist/core/prompt-builder.js +40 -7
  52. package/dist/core/prompt-builder.js.map +1 -1
  53. package/dist/core/questions.js +1 -1
  54. package/dist/core/questions.js.map +1 -1
  55. package/dist/core/requirement-clarifier.d.ts +25 -2
  56. package/dist/core/requirement-clarifier.d.ts.map +1 -1
  57. package/dist/core/requirement-clarifier.js +205 -9
  58. package/dist/core/requirement-clarifier.js.map +1 -1
  59. package/dist/core/spec-skeleton.d.ts.map +1 -1
  60. package/dist/core/spec-skeleton.js +125 -25
  61. package/dist/core/spec-skeleton.js.map +1 -1
  62. package/dist/core/unit-context-assembler.d.ts +50 -0
  63. package/dist/core/unit-context-assembler.d.ts.map +1 -0
  64. package/dist/core/unit-context-assembler.js +442 -0
  65. package/dist/core/unit-context-assembler.js.map +1 -0
  66. package/dist/core/verify-engine.d.ts +4 -3
  67. package/dist/core/verify-engine.d.ts.map +1 -1
  68. package/dist/core/verify-engine.js +623 -24
  69. package/dist/core/verify-engine.js.map +1 -1
  70. package/dist/core/workspace-manager.d.ts +62 -0
  71. package/dist/core/workspace-manager.d.ts.map +1 -0
  72. package/dist/core/workspace-manager.js +175 -0
  73. package/dist/core/workspace-manager.js.map +1 -0
  74. package/package.json +1 -1
@@ -6,6 +6,39 @@
6
6
  * 根据项目类型(Node.js/Java/Go/Python)自动检测命令
7
7
  * 生成结构化报告到 VERIFY_REPORT.md(任务根目录)
8
8
  */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
9
42
  Object.defineProperty(exports, "__esModule", { value: true });
10
43
  exports.detectProjectType = detectProjectType;
11
44
  exports.generateReportMarkdown = generateReportMarkdown;
@@ -290,10 +323,11 @@ async function writeVerifyReport(report, outputDir) {
290
323
  return reportPath;
291
324
  }
292
325
  /**
293
- * 生成 AI 修复 Prompt
326
+ * 生成 AI 修复 Prompt(v8.2.0+ 增强版)
294
327
  * 当验证失败时,生成结构化 Prompt 让 AI 定位并修复问题
328
+ * 注入任务规格上下文、知识图谱依赖链、API 契约等
295
329
  */
296
- function generateFixPrompt(report, taskDir) {
330
+ async function generateFixPrompt(report, taskDir) {
297
331
  const failedChecks = report.checks.filter(c => c.status === 'fail');
298
332
  const warningChecks = report.checks.filter(c => c.status === 'warn');
299
333
  let prompt = `# 代码验证失败 — 请修复\n\n`;
@@ -302,6 +336,52 @@ function generateFixPrompt(report, taskDir) {
302
336
  prompt += `- 代码路径: \`${report.codePath}\`\n`;
303
337
  prompt += `- 项目类型: ${report.projectType}\n`;
304
338
  prompt += `- 验证轮次: ${report.checks.length} 项检查\n\n`;
339
+ // ── v8.2.0+: 注入任务规格上下文 ──
340
+ const specCtx = await loadFixSpecContext(taskDir);
341
+ if (specCtx.reqSummary) {
342
+ prompt += `## 需求规格摘要 (REQ.md)\n\n`;
343
+ prompt += `${specCtx.reqSummary}\n\n`;
344
+ }
345
+ if (specCtx.techSummary) {
346
+ prompt += `## 技术规格摘要 (TECH.md)\n\n`;
347
+ prompt += `${specCtx.techSummary}\n\n`;
348
+ }
349
+ // ── v8.2.0+: 注入知识图谱上下文(依赖链 + 相邻任务) ──
350
+ try {
351
+ const { loadKnowledgeGraph, traceDependencyChain } = await Promise.resolve().then(() => __importStar(require('./knowledge-graph')));
352
+ const graph = await loadKnowledgeGraph(process.cwd());
353
+ if (graph && report.taskId) {
354
+ const chains = traceDependencyChain(graph, report.taskId, 2);
355
+ if (chains.length > 0) {
356
+ prompt += `## 依赖链路(知识图谱)\n\n`;
357
+ prompt += `修复时请注意以下依赖任务的影响:\n`;
358
+ for (const chain of chains.slice(0, 5)) {
359
+ const depEntity = graph.entities[chain.path[chain.path.length - 1]];
360
+ if (depEntity) {
361
+ prompt += `- **${depEntity.title}** (${depEntity.type}) — 深度 ${chain.depth}\n`;
362
+ }
363
+ }
364
+ prompt += '\n';
365
+ }
366
+ }
367
+ }
368
+ catch { /* 知识图谱不可用则跳过 */ }
369
+ // ── v8.2.0+: 注入 API 契约 ──
370
+ try {
371
+ const contractPath = (0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml');
372
+ if (await (0, fs_extra_1.pathExists)(contractPath)) {
373
+ const content = await (0, fs_extra_1.readFile)(contractPath, 'utf-8');
374
+ if (content.trim().length > 0) {
375
+ prompt += `## API 契约 (_shared/API_CONTRACT.yaml)\n\n`;
376
+ prompt += `\`\`\`yaml
377
+ ${content.slice(0, 1200)}
378
+ \`\`\`
379
+
380
+ `;
381
+ }
382
+ }
383
+ }
384
+ catch { /* 跳过 */ }
305
385
  prompt += `## 失败项\n\n`;
306
386
  for (const check of failedChecks) {
307
387
  prompt += `### ${check.name}\n\n`;
@@ -318,13 +398,14 @@ function generateFixPrompt(report, taskDir) {
318
398
  }
319
399
  prompt += `## 要求\n\n`;
320
400
  prompt += `1. 读取上述错误信息,定位问题根因\n`;
321
- prompt += `2. 修复代码,确保:\n`;
401
+ prompt += `2. **注意依赖影响**:修复时不要破坏 API 契约中定义的接口契约\n`;
402
+ prompt += `3. 修复代码,确保:\n`;
322
403
  prompt += ` - 编译通过(无类型错误、语法错误)\n`;
323
404
  prompt += ` - Lint 通过(无代码风格问题)\n`;
324
405
  prompt += ` - 测试通过(所有测试用例绿灯)\n`;
325
406
  prompt += ` - 测试用例覆盖:检查子任务目录下的 \`TEST.md\` 中的未覆盖用例,补充实现\n`;
326
407
  prompt += ` - 评审项合规:检查子任务目录下的 \`REVIEW.md\` 中的未合规项,补充实现\n`;
327
- prompt += `3. 修复后在下方「修复记录」表格中记录:\n`;
408
+ prompt += `4. 修复后在下方「修复记录」表格中记录:\n`;
328
409
  prompt += ` - 问题描述\n`;
329
410
  prompt += ` - 修复方案\n`;
330
411
  prompt += ` - 修改的文件\n\n`;
@@ -335,11 +416,62 @@ function generateFixPrompt(report, taskDir) {
335
416
  prompt += `> 修复完成后,运行 \`speccore verify -t ${report.taskId}\` 重新验证\n`;
336
417
  return prompt;
337
418
  }
419
+ /**
420
+ * 加载修复 Prompt 所需的规格上下文
421
+ */
422
+ async function loadFixSpecContext(taskDir) {
423
+ const result = { reqSummary: '', techSummary: '' };
424
+ try {
425
+ const reqPaths = [(0, path_1.join)(taskDir, '00-specs', 'REQ.md'), (0, path_1.join)(taskDir, 'REQ.md')];
426
+ for (const p of reqPaths) {
427
+ if (await (0, fs_extra_1.pathExists)(p)) {
428
+ const content = await (0, fs_extra_1.readFile)(p, 'utf-8');
429
+ result.reqSummary = extractFixSummary(content, 1200);
430
+ break;
431
+ }
432
+ }
433
+ const techPaths = [(0, path_1.join)(taskDir, '00-specs', 'TECH.md'), (0, path_1.join)(taskDir, 'TECH.md')];
434
+ for (const p of techPaths) {
435
+ if (await (0, fs_extra_1.pathExists)(p)) {
436
+ const content = await (0, fs_extra_1.readFile)(p, 'utf-8');
437
+ result.techSummary = extractFixSummary(content, 800);
438
+ break;
439
+ }
440
+ }
441
+ }
442
+ catch { /* 静默失败 */ }
443
+ return result;
444
+ }
445
+ /** 从文档提取修复所需摘要(保留标题 + 关键段落) */
446
+ function extractFixSummary(content, maxChars) {
447
+ if (content.length <= maxChars)
448
+ return content;
449
+ const lines = content.split('\n');
450
+ const result = [];
451
+ let chars = 0;
452
+ let inCodeBlock = false;
453
+ for (const line of lines) {
454
+ if (line.startsWith('```'))
455
+ inCodeBlock = !inCodeBlock;
456
+ // 优先保留标题行和列表项
457
+ const isImportant = line.startsWith('#') || line.startsWith('- ') || line.startsWith('|');
458
+ if (chars + line.length > maxChars && !isImportant)
459
+ break;
460
+ result.push(line);
461
+ chars += line.length + 1;
462
+ if (chars >= maxChars && !inCodeBlock)
463
+ break;
464
+ }
465
+ if (result.length < lines.length) {
466
+ result.push('\n> ...(内容已截断)');
467
+ }
468
+ return result.join('\n');
469
+ }
338
470
  /**
339
471
  * 生成 SPECCORE_EXEC 标签,触发 AI 修复
340
472
  */
341
- function outputFixTag(report, taskDir, round) {
342
- const prompt = generateFixPrompt(report, taskDir);
473
+ async function outputFixTag(report, taskDir, round) {
474
+ const prompt = await generateFixPrompt(report, taskDir);
343
475
  console.log('');
344
476
  console.log(`[SPECCORE_EXEC: verify-fix round=${round} task=${report.taskId}]`);
345
477
  console.log('');
@@ -442,6 +574,142 @@ function scanCodeFiles(codePath) {
442
574
  return '';
443
575
  } }).join('\n');
444
576
  }
577
+ // ═══════════════════════════════════════════════════════════
578
+ // v8.3.0+: 结构化文档解析工具(用于质量门禁 L1 精确检查)
579
+ // ═══════════════════════════════════════════════════════════
580
+ /** 解析 Markdown 表格,返回表头 + 数据行 */
581
+ function parseMarkdownTable(content, sectionHeading) {
582
+ const headingRegex = new RegExp(`^(#{2,4}\\s+.*${sectionHeading}.*)$`, 'im');
583
+ const headingMatch = content.match(headingRegex);
584
+ if (!headingMatch)
585
+ return null;
586
+ const startIdx = content.indexOf(headingMatch[1]);
587
+ const sectionText = content.slice(startIdx, startIdx + 5000);
588
+ const lines = sectionText.split('\n');
589
+ let tableStart = -1;
590
+ for (let i = 0; i < lines.length; i++) {
591
+ if (lines[i].trim().startsWith('|')) {
592
+ tableStart = i;
593
+ break;
594
+ }
595
+ }
596
+ if (tableStart === -1)
597
+ return null;
598
+ const tableLines = [];
599
+ for (let i = tableStart; i < lines.length; i++) {
600
+ const line = lines[i].trim();
601
+ if (!line.startsWith('|'))
602
+ break;
603
+ tableLines.push(line);
604
+ }
605
+ if (tableLines.length < 3)
606
+ return null; // 需要表头 + 分隔符 + 至少一行数据
607
+ const headers = tableLines[0].split('|').map(c => c.trim()).filter(Boolean);
608
+ const rows = [];
609
+ for (let i = 2; i < tableLines.length; i++) {
610
+ const cells = tableLines[i].split('|').map(c => c.trim()).filter(Boolean);
611
+ if (cells.length >= headers.length)
612
+ rows.push(cells);
613
+ }
614
+ return { headers, rows };
615
+ }
616
+ /** 从代码中提取结构信息:函数名、类名、路由路径 */
617
+ function extractCodeStructure(allCode) {
618
+ const functions = [];
619
+ const classes = [];
620
+ const routes = [];
621
+ // 提取函数名:async function xxx( / function xxx( / const xxx =
622
+ for (const m of allCode.match(/(?:async\s+function|function)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g) || []) {
623
+ const name = m.replace(/(?:function|async\s+function)\s+/, '').replace(/\s*\($/, '');
624
+ if (name && !['if', 'while', 'for', 'switch', 'catch'].includes(name))
625
+ functions.push(name);
626
+ }
627
+ for (const m of allCode.match(/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*[=:]\s*(?:async\s*)?\(/g) || []) {
628
+ const name = m.replace(/(?:const|let|var)\s+/, '').replace(/\s*[=:].*$/, '');
629
+ if (name)
630
+ functions.push(name);
631
+ }
632
+ // 提取类名:class Xxx / interface Xxx
633
+ for (const m of allCode.match(/(?:class|interface)\s+([a-zA-Z_$][a-zA-Z0-9_$]+)/g) || []) {
634
+ const name = m.replace(/(?:class|interface)\s+/, '');
635
+ if (name)
636
+ classes.push(name);
637
+ }
638
+ // 提取路由:Express/Fastify/Koa 风格
639
+ const routePatterns = [
640
+ /\.(get|post|put|delete|patch)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
641
+ /\.(Get|Post|Put|Delete|Patch)\s*\(\s*['"`]([^'"`]+)['"`]/gi,
642
+ /@(Get|Post|Put|Delete|Patch)\s*\(['"`]([^'"`]+)['"`]/gi,
643
+ ];
644
+ for (const pattern of routePatterns) {
645
+ let match;
646
+ while ((match = pattern.exec(allCode)) !== null) {
647
+ routes.push({ method: match[1].toUpperCase(), path: match[2] });
648
+ }
649
+ }
650
+ return {
651
+ functions: [...new Set(functions)],
652
+ classes: [...new Set(classes)],
653
+ routes: [...new Map(routes.map(r => [`${r.method} ${r.path}`, r])).values()],
654
+ };
655
+ }
656
+ /** 简单 YAML 解析器(只处理 key: value 和 - list 结构) */
657
+ function simpleYamlParse(content) {
658
+ const result = {};
659
+ const lines = content.split('\n');
660
+ const stack = [{ obj: result, indent: -1 }];
661
+ let currentList = null;
662
+ for (const rawLine of lines) {
663
+ const line = rawLine.replace(/\r$/, '');
664
+ if (!line.trim() || line.trim().startsWith('#'))
665
+ continue;
666
+ const indent = line.length - line.trimStart().length;
667
+ // 弹出比当前更深的层级
668
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
669
+ stack.pop();
670
+ }
671
+ const parent = stack[stack.length - 1].obj;
672
+ if (line.trim().startsWith('- ')) {
673
+ const item = line.trim().slice(2).trim();
674
+ if (!currentList) {
675
+ currentList = [];
676
+ // 找到父对象中最后一个值,把它变成列表
677
+ const keys = Object.keys(parent);
678
+ if (keys.length > 0) {
679
+ const lastKey = keys[keys.length - 1];
680
+ if (!Array.isArray(parent[lastKey])) {
681
+ parent[lastKey] = [];
682
+ }
683
+ currentList = parent[lastKey];
684
+ }
685
+ }
686
+ if (currentList) {
687
+ if (item.includes(':')) {
688
+ const [k, ...v] = item.split(':');
689
+ currentList.push({ [k.trim()]: v.join(':').trim() });
690
+ }
691
+ else {
692
+ currentList.push(item);
693
+ }
694
+ }
695
+ }
696
+ else if (line.includes(':')) {
697
+ currentList = null;
698
+ const [key, ...valueParts] = line.trim().split(':');
699
+ const keyStr = key.trim();
700
+ const valueStr = valueParts.join(':').trim();
701
+ if (!valueStr) {
702
+ const newObj = {};
703
+ parent[keyStr] = newObj;
704
+ stack.push({ obj: newObj, indent });
705
+ }
706
+ else {
707
+ parent[keyStr] = valueStr.replace(/^['"]|['"]$/g, '');
708
+ }
709
+ }
710
+ }
711
+ return result;
712
+ }
445
713
  /**
446
714
  * 从 Markdown 中提取检查项(- [ ] / - [x] / ⬜ / ✅ / ❌ / | 行)
447
715
  */
@@ -696,18 +964,317 @@ async function checkArtifactConsistency(codePath, taskDir, filename, checkName)
696
964
  return { name: checkName, status: 'skip', duration: Date.now() - start, output: '', details: '检查失败', blocking: false };
697
965
  }
698
966
  }
967
+ // ═══════════════════════════════════════════════════════════
968
+ // v8.3.0+: L1 结构化检查 — 解析文档表格/YAML 做精确验证
969
+ // ═══════════════════════════════════════════════════════════
699
970
  /**
700
- * Spec-代码一致性检查(基础启发式)
971
+ * DEV_GUIDE.md 合规检查
972
+ * 解析改造范围清单表格 → 检查文件是否存在
973
+ * 解析接口契约表格 → 检查路由是否实现
974
+ */
975
+ async function checkDevGuideCompliance(codePath, taskDir) {
976
+ const start = Date.now();
977
+ try {
978
+ const devGuidePaths = [(0, path_1.join)(taskDir, '00-specs', 'DEV_GUIDE.md'), (0, path_1.join)(taskDir, 'DEV_GUIDE.md')];
979
+ let content = '';
980
+ for (const p of devGuidePaths) {
981
+ if (await (0, fs_extra_1.pathExists)(p)) {
982
+ content = await (0, fs_extra_1.readFile)(p, 'utf-8');
983
+ break;
984
+ }
985
+ }
986
+ if (!content) {
987
+ return { name: 'DEV_GUIDE 合规', status: 'skip', duration: Date.now() - start, output: '', details: '未找到 DEV_GUIDE.md', blocking: false };
988
+ }
989
+ const allCode = scanCodeFiles(codePath);
990
+ const codeStruct = extractCodeStructure(allCode);
991
+ const issues = [];
992
+ let checkCount = 0;
993
+ // 1. 检查改造范围清单
994
+ const scopeTable = parseMarkdownTable(content, '改造范围');
995
+ if (scopeTable) {
996
+ const typeIdx = scopeTable.headers.findIndex(h => h.includes('类型') || h.toLowerCase().includes('type'));
997
+ const fileIdx = scopeTable.headers.findIndex(h => h.includes('文件') || h.includes('目录') || h.toLowerCase().includes('file') || h.toLowerCase().includes('path'));
998
+ if (fileIdx >= 0) {
999
+ for (const row of scopeTable.rows) {
1000
+ const type = typeIdx >= 0 ? row[typeIdx] : '';
1001
+ const filePath = row[fileIdx];
1002
+ if (!filePath || filePath === '文件/目录' || filePath === '')
1003
+ continue;
1004
+ checkCount++;
1005
+ // 提取路径(去除反引号)
1006
+ const cleanPath = filePath.replace(/`/g, '').trim();
1007
+ if (!cleanPath)
1008
+ continue;
1009
+ const fullPath = (0, path_1.join)(codePath, cleanPath);
1010
+ const exists = require('fs').existsSync(fullPath);
1011
+ if (type.includes('新增') || type.toLowerCase().includes('add') || type.toLowerCase().includes('new')) {
1012
+ if (!exists)
1013
+ issues.push(`改造范围: 应新增文件不存在: ${cleanPath}`);
1014
+ }
1015
+ else if (type.includes('修改') || type.toLowerCase().includes('modify') || type.toLowerCase().includes('update')) {
1016
+ if (!exists)
1017
+ issues.push(`改造范围: 应修改文件不存在: ${cleanPath}`);
1018
+ }
1019
+ }
1020
+ }
1021
+ }
1022
+ // 2. 检查接口契约表
1023
+ const contractTable = parseMarkdownTable(content, '接口契约');
1024
+ if (contractTable) {
1025
+ const pathIdx = contractTable.headers.findIndex(h => h.includes('路径') || h.toLowerCase().includes('path'));
1026
+ const methodIdx = contractTable.headers.findIndex(h => h.includes('方法') || h.toLowerCase().includes('method'));
1027
+ if (pathIdx >= 0) {
1028
+ for (const row of contractTable.rows) {
1029
+ const apiPath = row[pathIdx];
1030
+ if (!apiPath || apiPath === '路径' || apiPath === '接口' || apiPath === '')
1031
+ continue;
1032
+ checkCount++;
1033
+ const cleanPath = apiPath.replace(/`/g, '').trim();
1034
+ const method = methodIdx >= 0 ? row[methodIdx].replace(/`/g, '').trim().toUpperCase() : '';
1035
+ // 检查代码中是否有匹配的路由
1036
+ const hasRoute = codeStruct.routes.some(r => {
1037
+ const routeMatch = cleanPath.includes(r.path) || r.path.includes(cleanPath);
1038
+ const methodMatch = !method || r.method === method || r.method === 'USE';
1039
+ return routeMatch && methodMatch;
1040
+ });
1041
+ if (!hasRoute) {
1042
+ issues.push(`接口契约: 未找到路由实现 ${method ? method + ' ' : ''}${cleanPath}`);
1043
+ }
1044
+ }
1045
+ }
1046
+ }
1047
+ if (checkCount === 0) {
1048
+ return { name: 'DEV_GUIDE 合规', status: 'skip', duration: Date.now() - start, output: '', details: 'DEV_GUIDE.md 无结构化改造范围/接口契约', blocking: false };
1049
+ }
1050
+ if (issues.length === 0) {
1051
+ return { name: 'DEV_GUIDE 合规', status: 'pass', duration: Date.now() - start, output: '', details: `${checkCount} 项全部合规`, blocking: false };
1052
+ }
1053
+ return {
1054
+ name: 'DEV_GUIDE 合规',
1055
+ status: issues.length > checkCount / 2 ? 'fail' : 'warn',
1056
+ duration: Date.now() - start,
1057
+ output: issues.slice(0, 8).map(i => ` - ${i}`).join('\n'),
1058
+ details: `${checkCount - issues.length}/${checkCount} 合规, ${issues.length} 项缺失`,
1059
+ blocking: false,
1060
+ };
1061
+ }
1062
+ catch {
1063
+ return { name: 'DEV_GUIDE 合规', status: 'skip', duration: Date.now() - start, output: '', details: '检查失败', blocking: false };
1064
+ }
1065
+ }
1066
+ /**
1067
+ * API_CONTRACT.yaml 合规检查
1068
+ * 解析 YAML 中的 paths → 检查代码中是否有对应路由实现
1069
+ */
1070
+ async function checkApiContractCompliance(codePath, taskDir) {
1071
+ const start = Date.now();
1072
+ try {
1073
+ const contractPaths = [
1074
+ (0, path_1.join)(taskDir, '_shared', 'API_CONTRACT.yaml'),
1075
+ (0, path_1.join)(taskDir, 'API_CONTRACT.yaml'),
1076
+ ];
1077
+ let content = '';
1078
+ for (const p of contractPaths) {
1079
+ if (await (0, fs_extra_1.pathExists)(p)) {
1080
+ content = await (0, fs_extra_1.readFile)(p, 'utf-8');
1081
+ break;
1082
+ }
1083
+ }
1084
+ if (!content) {
1085
+ return { name: 'API 契约合规', status: 'skip', duration: Date.now() - start, output: '', details: '未找到 API_CONTRACT.yaml', blocking: false };
1086
+ }
1087
+ const allCode = scanCodeFiles(codePath);
1088
+ const codeStruct = extractCodeStructure(allCode);
1089
+ const issues = [];
1090
+ let checkCount = 0;
1091
+ // 解析 YAML 中的 paths
1092
+ const yamlDoc = simpleYamlParse(content);
1093
+ const paths = yamlDoc.paths || {};
1094
+ for (const [apiPath, methods] of Object.entries(paths)) {
1095
+ if (typeof methods !== 'object' || methods === null)
1096
+ continue;
1097
+ for (const [method, _spec] of Object.entries(methods)) {
1098
+ if (method === 'parameters' || method === 'summary' || method === 'description')
1099
+ continue;
1100
+ checkCount++;
1101
+ const httpMethod = method.toUpperCase();
1102
+ const hasRoute = codeStruct.routes.some(r => {
1103
+ const routeMatch = apiPath.includes(r.path) || r.path.includes(apiPath);
1104
+ const methodMatch = r.method === httpMethod || r.method === 'USE';
1105
+ return routeMatch && methodMatch;
1106
+ });
1107
+ if (!hasRoute) {
1108
+ issues.push(`未找到路由实现: ${httpMethod} ${apiPath}`);
1109
+ }
1110
+ }
1111
+ }
1112
+ if (checkCount === 0) {
1113
+ return { name: 'API 契约合规', status: 'skip', duration: Date.now() - start, output: '', details: 'API_CONTRACT.yaml 无接口定义', blocking: false };
1114
+ }
1115
+ if (issues.length === 0) {
1116
+ return { name: 'API 契约合规', status: 'pass', duration: Date.now() - start, output: '', details: `${checkCount} 个接口全部有代码实现`, blocking: false };
1117
+ }
1118
+ return {
1119
+ name: 'API 契约合规',
1120
+ status: issues.length > checkCount / 2 ? 'fail' : 'warn',
1121
+ duration: Date.now() - start,
1122
+ output: issues.slice(0, 8).map(i => ` - ${i}`).join('\n'),
1123
+ details: `${checkCount - issues.length}/${checkCount} 个接口有实现, ${issues.length} 个缺失`,
1124
+ blocking: false,
1125
+ };
1126
+ }
1127
+ catch {
1128
+ return { name: 'API 契约合规', status: 'skip', duration: Date.now() - start, output: '', details: '检查失败', blocking: false };
1129
+ }
1130
+ }
1131
+ /**
1132
+ * SCHEMA.md 一致性检查
1133
+ * 解析表结构定义 → 检查代码中是否有对应实体类/字段
1134
+ */
1135
+ async function checkSchemaConsistency(codePath, taskDir) {
1136
+ const start = Date.now();
1137
+ try {
1138
+ const schemaPaths = [(0, path_1.join)(taskDir, '00-specs', 'SCHEMA.md'), (0, path_1.join)(taskDir, 'SCHEMA.md')];
1139
+ let content = '';
1140
+ for (const p of schemaPaths) {
1141
+ if (await (0, fs_extra_1.pathExists)(p)) {
1142
+ content = await (0, fs_extra_1.readFile)(p, 'utf-8');
1143
+ break;
1144
+ }
1145
+ }
1146
+ if (!content) {
1147
+ return { name: 'Schema 一致性', status: 'skip', duration: Date.now() - start, output: '', details: '未找到 SCHEMA.md', blocking: false };
1148
+ }
1149
+ const allCode = scanCodeFiles(codePath);
1150
+ const codeStruct = extractCodeStructure(allCode);
1151
+ const issues = [];
1152
+ let fieldCount = 0;
1153
+ // 解析所有表格(每个表格是一个表)
1154
+ const tableRegex = /^#{2,4}\s+(.+)$/gm;
1155
+ let m;
1156
+ while ((m = tableRegex.exec(content)) !== null) {
1157
+ const heading = m[1];
1158
+ const sectionStart = content.indexOf(m[0]);
1159
+ const sectionText = content.slice(sectionStart, sectionStart + 3000);
1160
+ const table = parseMarkdownTable(sectionText, heading);
1161
+ if (!table)
1162
+ continue;
1163
+ const fieldIdx = table.headers.findIndex(h => h.includes('字段') || h.includes('列') || h.toLowerCase().includes('field') || h.toLowerCase().includes('column'));
1164
+ if (fieldIdx < 0)
1165
+ continue;
1166
+ // 尝试从表名/heading 推断实体类名
1167
+ const possibleEntityNames = heading.split(/[^a-zA-Z0-9_]/).filter(w => w.length > 1);
1168
+ const entityMatch = codeStruct.classes.some(c => possibleEntityNames.some(n => c.toLowerCase().includes(n.toLowerCase()) || n.toLowerCase().includes(c.toLowerCase())));
1169
+ for (const row of table.rows) {
1170
+ const fieldName = row[fieldIdx];
1171
+ if (!fieldName || fieldName === '字段' || fieldName === '列名')
1172
+ continue;
1173
+ fieldCount++;
1174
+ const cleanField = fieldName.replace(/`/g, '').trim();
1175
+ if (!cleanField)
1176
+ continue;
1177
+ // 检查代码中是否有该字段名(作为类属性、变量、数据库列名)
1178
+ const snakeField = cleanField.replace(/([A-Z])/g, '_$1').toLowerCase();
1179
+ const camelField = cleanField.replace(/_([a-z])/g, (_, g) => g.toUpperCase());
1180
+ // v8.3.0+: 增强字段匹配 — 支持多种代码中的出现形式
1181
+ const fieldPatterns = [
1182
+ ` ${cleanField}:`, ` ${cleanField} =`, ` ${cleanField};`,
1183
+ ` ${cleanField}?:`, `.${cleanField}`, `'${cleanField}'`, `"${cleanField}"`,
1184
+ ` ${snakeField}:`, ` ${snakeField} =`, ` ${snakeField};`, `.${snakeField}`,
1185
+ ` ${camelField}:`, ` ${camelField} =`, ` ${camelField};`, `.${camelField}`,
1186
+ `column\s*:\s*['"\`]${cleanField}['"\`]`,
1187
+ ];
1188
+ const found = entityMatch && fieldPatterns.some(p => allCode.includes(p));
1189
+ if (!found && entityMatch) {
1190
+ issues.push(`字段未找到: ${cleanField} (表: ${heading})`);
1191
+ }
1192
+ }
1193
+ }
1194
+ if (fieldCount === 0) {
1195
+ return { name: 'Schema 一致性', status: 'skip', duration: Date.now() - start, output: '', details: 'SCHEMA.md 无表结构定义', blocking: false };
1196
+ }
1197
+ if (issues.length === 0) {
1198
+ return { name: 'Schema 一致性', status: 'pass', duration: Date.now() - start, output: '', details: `${fieldCount} 个字段全部有对应`, blocking: false };
1199
+ }
1200
+ return {
1201
+ name: 'Schema 一致性',
1202
+ status: issues.length > fieldCount / 3 ? 'warn' : 'pass',
1203
+ duration: Date.now() - start,
1204
+ output: issues.slice(0, 8).map(i => ` - ${i}`).join('\n'),
1205
+ details: `${fieldCount - issues.length}/${fieldCount} 个字段有对应, ${issues.length} 个缺失`,
1206
+ blocking: false,
1207
+ };
1208
+ }
1209
+ catch {
1210
+ return { name: 'Schema 一致性', status: 'skip', duration: Date.now() - start, output: '', details: '检查失败', blocking: false };
1211
+ }
1212
+ }
1213
+ /**
1214
+ * 知识图谱依赖一致性检查(v8.3.0+)
1215
+ * 加载知识图谱,检查上游依赖任务的接口是否在本任务代码中被正确引用
1216
+ */
1217
+ async function checkDependencyGraphConsistency(codePath, taskDir, taskId) {
1218
+ const start = Date.now();
1219
+ try {
1220
+ const { loadKnowledgeGraph, traceDependencyChain } = await Promise.resolve().then(() => __importStar(require('./knowledge-graph')));
1221
+ const graph = await loadKnowledgeGraph(process.cwd());
1222
+ if (!graph) {
1223
+ return { name: '依赖一致性', status: 'skip', duration: Date.now() - start, output: '', details: '知识图谱未加载', blocking: false };
1224
+ }
1225
+ // 提取本任务的 platform(从 taskId 或 taskDir 推断)
1226
+ const allCode = scanCodeFiles(codePath);
1227
+ const issues = [];
1228
+ let depCount = 0;
1229
+ // 追踪上游依赖(深度 1)
1230
+ const upstreamDeps = traceDependencyChain(graph, taskId, 1).filter(c => c.depth === 1);
1231
+ for (const dep of upstreamDeps) {
1232
+ const depEntity = graph.entities[dep.path[dep.path.length - 1]];
1233
+ if (!depEntity)
1234
+ continue;
1235
+ depCount++;
1236
+ // 检查代码中是否有对该依赖任务的引用(import、调用等)
1237
+ const depKeywords = depEntity.title.split(/[^a-zA-Z0-9_]/).filter(w => w.length > 2);
1238
+ const hasReference = depKeywords.some(kw => allCode.toLowerCase().includes(kw.toLowerCase()));
1239
+ // 更精确:检查是否有 import/require 引用
1240
+ const importPatterns = [
1241
+ new RegExp(`from\\s+['"\`].*${depEntity.title.toLowerCase().replace(/\s+/g, '[-_]')}['"\`]`),
1242
+ new RegExp(`import\\s+.*\\b${depKeywords[0]}\\b`),
1243
+ new RegExp(`require\\s*\\(\\s*['"\`].*${depKeywords[0]}['"\`]\\s*\\)`),
1244
+ ];
1245
+ const hasImport = importPatterns.some(p => p.test(allCode));
1246
+ if (!hasReference && !hasImport) {
1247
+ issues.push(`未引用上游依赖: ${depEntity.title} (${depEntity.type})`);
1248
+ }
1249
+ }
1250
+ if (depCount === 0) {
1251
+ return { name: '依赖一致性', status: 'skip', duration: Date.now() - start, output: '', details: '无上游依赖任务', blocking: false };
1252
+ }
1253
+ if (issues.length === 0) {
1254
+ return { name: '依赖一致性', status: 'pass', duration: Date.now() - start, output: '', details: `${depCount} 个上游依赖全部有引用`, blocking: false };
1255
+ }
1256
+ return {
1257
+ name: '依赖一致性',
1258
+ status: 'warn',
1259
+ duration: Date.now() - start,
1260
+ output: issues.slice(0, 8).map(i => ` - ${i}`).join('\n'),
1261
+ details: `${depCount - issues.length}/${depCount} 个依赖有引用, ${issues.length} 个缺失`,
1262
+ blocking: false,
1263
+ };
1264
+ }
1265
+ catch {
1266
+ return { name: '依赖一致性', status: 'skip', duration: Date.now() - start, output: '', details: '检查失败', blocking: false };
1267
+ }
1268
+ }
1269
+ /**
1270
+ * Spec-代码一致性检查(v8.3.0+ 增强版)
1271
+ * L1: 关键词匹配(保留)+ L2: 语义匹配(新增)
701
1272
  * 检查 REQ.md 中的验收标准是否在代码中有对应实现
702
1273
  */
703
1274
  async function checkSpecConsistency(codePath, taskDir) {
704
1275
  const start = Date.now();
705
1276
  try {
706
- // 读取 REQ.md 的验收标准
707
- const reqPaths = [
708
- (0, path_1.join)(taskDir, '00-specs', 'REQ.md'),
709
- (0, path_1.join)(taskDir, 'REQ.md'),
710
- ];
1277
+ const reqPaths = [(0, path_1.join)(taskDir, '00-specs', 'REQ.md'), (0, path_1.join)(taskDir, 'REQ.md')];
711
1278
  let reqContent = '';
712
1279
  for (const p of reqPaths) {
713
1280
  if (await (0, fs_extra_1.pathExists)(p)) {
@@ -723,19 +1290,39 @@ async function checkSpecConsistency(codePath, taskDir) {
723
1290
  return { name: 'Spec 一致性', status: 'skip', duration: Date.now() - start, output: '', details: 'REQ.md 无验收标准', blocking: false };
724
1291
  }
725
1292
  const allCode = scanCodeFiles(codePath);
726
- // 检查每个验收标准的关键词是否在代码中出现
1293
+ const codeStruct = extractCodeStructure(allCode);
727
1294
  const matched = [];
728
1295
  const unmatched = [];
729
1296
  for (const c of criteria) {
1297
+ // L1: 关键词匹配(保留)
730
1298
  const keywords = [
731
1299
  ...(c.match(/[\u4e00-\u9fa5]{2,}/g) || []),
732
1300
  ...(c.match(/[a-zA-Z]{3,}/g) || []),
733
1301
  ];
734
- const found = keywords.some(kw => allCode.toLowerCase().includes(kw.toLowerCase()));
735
- if (found)
1302
+ const keywordMatch = keywords.some(kw => allCode.toLowerCase().includes(kw.toLowerCase()));
1303
+ // L2: 语义匹配(v8.3.0+ 新增)
1304
+ // 提取验收标准中的动词+名词组合,与代码中的函数名做匹配
1305
+ let semanticMatch = false;
1306
+ const actionNouns = c.match(/(?:创建|删除|更新|查询|获取|发送|验证|检查|生成|导入|导出|登录|注册|注销|锁定|解锁|禁用|启用|审批|拒绝|提交|取消|支付|退款|分配|合并|拆分|复制|移动|排序|过滤|搜索|导入|导出|create|delete|update|get|fetch|send|verify|check|generate|import|export|login|register|logout|lock|unlock|disable|enable|approve|reject|submit|cancel|pay|refund|assign|merge|split|copy|move|sort|filter|search)/gi);
1307
+ if (actionNouns && actionNouns.length > 0) {
1308
+ const targetNouns = c.match(/(?:用户|订单|商品|账户|角色|权限|日志|配置|模板|通知|消息|文件|图片|视频|报表|统计|备份|恢复|任务|流程|节点|表单|字段|页面|菜单|按钮|列表|详情|搜索|筛选|排序|分页|缓存|索引|队列|定时|推送|Webhook|user|order|product|account|role|permission|log|config|template|notification|message|file|image|video|report|stat|backup|restore|task|flow|node|form|field|page|menu|button|list|detail|search|filter|sort|page|cache|index|queue|schedule|push|webhook)/gi);
1309
+ const possibleFnNames = [];
1310
+ for (const action of actionNouns) {
1311
+ const act = action.toLowerCase();
1312
+ for (const target of (targetNouns || [])) {
1313
+ const tgt = target.toLowerCase();
1314
+ // 生成可能的函数名组合
1315
+ possibleFnNames.push(`${act}${tgt}`, `${tgt}${act}`, `${act}_${tgt}`, `${tgt}_${act}`, `handle${act.charAt(0).toUpperCase() + act.slice(1)}${tgt.charAt(0).toUpperCase() + tgt.slice(1)}`);
1316
+ }
1317
+ }
1318
+ semanticMatch = codeStruct.functions.some(fn => possibleFnNames.some(p => fn.toLowerCase().includes(p.toLowerCase()) || p.toLowerCase().includes(fn.toLowerCase())));
1319
+ }
1320
+ if (keywordMatch || semanticMatch) {
736
1321
  matched.push(c);
737
- else
1322
+ }
1323
+ else {
738
1324
  unmatched.push(c);
1325
+ }
739
1326
  }
740
1327
  if (unmatched.length === 0) {
741
1328
  return { name: 'Spec 一致性', status: 'pass', duration: Date.now() - start, output: '', details: `${matched.length} 项验收标准均有代码对应`, blocking: false };
@@ -745,7 +1332,7 @@ async function checkSpecConsistency(codePath, taskDir) {
745
1332
  status: unmatched.length > matched.length ? 'fail' : 'warn',
746
1333
  duration: Date.now() - start,
747
1334
  output: `未匹配:\n${unmatched.slice(0, 8).map(u => ` - ${u}`).join('\n')}`,
748
- details: `${matched.length}/${criteria.length} 项匹配`,
1335
+ details: `${matched.length}/${criteria.length} 项匹配(含语义匹配)`,
749
1336
  blocking: false,
750
1337
  };
751
1338
  }
@@ -780,25 +1367,37 @@ async function runQualityGate(taskId, codePath, taskDir, options) {
780
1367
  // 5. 安全扫描(非阻塞)
781
1368
  logger_1.logger.info(' 🔒 安全扫描...');
782
1369
  checks.push(checkSecurity(codePath, projectType));
783
- // 6. Spec 一致性(非阻塞)
1370
+ // 6. Spec 一致性(非阻塞,L1 关键词 + L2 语义匹配)
784
1371
  logger_1.logger.info(' 📐 Spec 一致性...');
785
1372
  checks.push(await checkSpecConsistency(codePath, taskDir));
786
- // 7. 测试用例覆盖率(非阻塞,读取 TEST.md)
1373
+ // 7. DEV_GUIDE 合规(v8.3.0+,结构化检查:改造范围清单 + 接口契约)
1374
+ logger_1.logger.info(' 📋 DEV_GUIDE 合规...');
1375
+ checks.push(await checkDevGuideCompliance(codePath, taskDir));
1376
+ // 8. API 契约合规(v8.3.0+,解析 API_CONTRACT.yaml 检查接口实现)
1377
+ logger_1.logger.info(' 🔌 API 契约合规...');
1378
+ checks.push(await checkApiContractCompliance(codePath, taskDir));
1379
+ // 9. Schema 一致性(v8.3.0+,解析 SCHEMA.md 检查实体字段)
1380
+ logger_1.logger.info(' 🗄️ Schema 一致性...');
1381
+ checks.push(await checkSchemaConsistency(codePath, taskDir));
1382
+ // 10. 测试用例覆盖率(非阻塞,读取 TEST.md)
787
1383
  logger_1.logger.info(' 🧪 测试用例覆盖...');
788
1384
  checks.push(await checkTestCoverage(codePath, taskDir));
789
- // 8. 评审项合规(非阻塞,读取 REVIEW.md)
1385
+ // 11. 评审项合规(非阻塞,读取 REVIEW.md)
790
1386
  logger_1.logger.info(' 📝 评审项合规...');
791
1387
  checks.push(await checkReviewCompliance(codePath, taskDir));
792
- // 9. 部署清单检查(非阻塞,读取 DEPLOY.md)
1388
+ // 12. 部署清单检查(非阻塞,读取 DEPLOY.md)
793
1389
  logger_1.logger.info(' 🚀 部署清单...');
794
1390
  checks.push(await checkArtifactConsistency(codePath, taskDir, 'DEPLOY.md', '部署项检查'));
795
- // 10. 错误码一致性(非阻塞,读取 ERROR_CODES.md)
1391
+ // 13. 错误码一致性(非阻塞,读取 ERROR_CODES.md)
796
1392
  logger_1.logger.info(' 🔢 错误码一致性...');
797
1393
  checks.push(await checkArtifactConsistency(codePath, taskDir, 'ERROR_CODES.md', '错误码一致性'));
798
- // 11. 规格文档质量校验(v8.1.0+,检查 REQ.md/TECH.md 是否有实质内容)
1394
+ // 14. 知识图谱依赖一致性(v8.3.0+,检查上游依赖接口是否已可用)
1395
+ logger_1.logger.info(' 🔗 依赖一致性...');
1396
+ checks.push(await checkDependencyGraphConsistency(codePath, taskDir, taskId));
1397
+ // 15. 规格文档质量校验(v8.1.0+,检查 REQ.md/TECH.md 是否有实质内容)
799
1398
  logger_1.logger.info(' 📋 规格文档质量...');
800
1399
  checks.push(await checkSpecDocQuality(taskDir));
801
- // 12. 代码文件非空检查(v8.1.0+,确保 src/ 下有实际代码)
1400
+ // 16. 代码文件非空检查(v8.1.0+,确保 src/ 下有实际代码)
802
1401
  logger_1.logger.info(' 📁 代码文件检查...');
803
1402
  checks.push(await checkCodeFilesExist(codePath));
804
1403
  // 汇总