speccore 5.70.0 → 5.74.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/.agents/skills/spec-ask/SKILL.md +10 -0
  2. package/.agents/skills/speccore-router/SKILL.md +10 -0
  3. package/README.md +51 -15
  4. package/dist/cli.js +20 -2
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/about.d.ts.map +1 -1
  7. package/dist/commands/about.js +11 -3
  8. package/dist/commands/about.js.map +1 -1
  9. package/dist/commands/analyze.d.ts +3 -0
  10. package/dist/commands/analyze.d.ts.map +1 -1
  11. package/dist/commands/analyze.js +135 -31
  12. package/dist/commands/analyze.js.map +1 -1
  13. package/dist/commands/ask.d.ts +1 -1
  14. package/dist/commands/ask.d.ts.map +1 -1
  15. package/dist/commands/ask.js +15 -7
  16. package/dist/commands/ask.js.map +1 -1
  17. package/dist/commands/change.d.ts +5 -0
  18. package/dist/commands/change.d.ts.map +1 -1
  19. package/dist/commands/change.js +616 -27
  20. package/dist/commands/change.js.map +1 -1
  21. package/dist/commands/code-index.d.ts +18 -0
  22. package/dist/commands/code-index.d.ts.map +1 -0
  23. package/dist/commands/code-index.js +126 -0
  24. package/dist/commands/code-index.js.map +1 -0
  25. package/dist/commands/context.js +3 -3
  26. package/dist/commands/context.js.map +1 -1
  27. package/dist/commands/dashboard.d.ts.map +1 -1
  28. package/dist/commands/dashboard.js +12 -41
  29. package/dist/commands/dashboard.js.map +1 -1
  30. package/dist/commands/dev.d.ts +1 -0
  31. package/dist/commands/dev.d.ts.map +1 -1
  32. package/dist/commands/dev.js +137 -20
  33. package/dist/commands/dev.js.map +1 -1
  34. package/dist/commands/diff.js +2 -2
  35. package/dist/commands/diff.js.map +1 -1
  36. package/dist/commands/doc2spec.js +8 -8
  37. package/dist/commands/doc2spec.js.map +1 -1
  38. package/dist/commands/done.js +2 -2
  39. package/dist/commands/done.js.map +1 -1
  40. package/dist/commands/execute.d.ts +2 -0
  41. package/dist/commands/execute.d.ts.map +1 -1
  42. package/dist/commands/execute.js +271 -76
  43. package/dist/commands/execute.js.map +1 -1
  44. package/dist/commands/handover.js +1 -1
  45. package/dist/commands/handover.js.map +1 -1
  46. package/dist/commands/help.js +20 -9
  47. package/dist/commands/help.js.map +1 -1
  48. package/dist/commands/import.js +1 -1
  49. package/dist/commands/import.js.map +1 -1
  50. package/dist/commands/init.d.ts.map +1 -1
  51. package/dist/commands/init.js +105 -19
  52. package/dist/commands/init.js.map +1 -1
  53. package/dist/commands/iteration/create.d.ts.map +1 -1
  54. package/dist/commands/iteration/create.js +101 -4
  55. package/dist/commands/iteration/create.js.map +1 -1
  56. package/dist/commands/iteration/split.d.ts.map +1 -1
  57. package/dist/commands/iteration/split.js +179 -112
  58. package/dist/commands/iteration/split.js.map +1 -1
  59. package/dist/commands/iteration-from-global.js +8 -8
  60. package/dist/commands/iteration-from-global.js.map +1 -1
  61. package/dist/commands/lifecycle.d.ts.map +1 -1
  62. package/dist/commands/lifecycle.js +49 -25
  63. package/dist/commands/lifecycle.js.map +1 -1
  64. package/dist/commands/merge-check.js +1 -1
  65. package/dist/commands/merge-check.js.map +1 -1
  66. package/dist/commands/pattern.js +1 -1
  67. package/dist/commands/pattern.js.map +1 -1
  68. package/dist/commands/plan.d.ts +20 -0
  69. package/dist/commands/plan.d.ts.map +1 -1
  70. package/dist/commands/plan.js +56 -40
  71. package/dist/commands/plan.js.map +1 -1
  72. package/dist/commands/retro.d.ts +9 -0
  73. package/dist/commands/retro.d.ts.map +1 -1
  74. package/dist/commands/retro.js +63 -16
  75. package/dist/commands/retro.js.map +1 -1
  76. package/dist/commands/status-panel.d.ts.map +1 -1
  77. package/dist/commands/status-panel.js +47 -18
  78. package/dist/commands/status-panel.js.map +1 -1
  79. package/dist/commands/sync.js +6 -6
  80. package/dist/commands/sync.js.map +1 -1
  81. package/dist/commands/task/new.js +31 -16
  82. package/dist/commands/task/new.js.map +1 -1
  83. package/dist/commands/trace.js +3 -3
  84. package/dist/commands/trace.js.map +1 -1
  85. package/dist/commands/validate.d.ts.map +1 -1
  86. package/dist/commands/validate.js +25 -1
  87. package/dist/commands/validate.js.map +1 -1
  88. package/dist/commands/verify.d.ts +22 -0
  89. package/dist/commands/verify.d.ts.map +1 -0
  90. package/dist/commands/verify.js +158 -0
  91. package/dist/commands/verify.js.map +1 -0
  92. package/dist/commands/welcome.d.ts.map +1 -1
  93. package/dist/commands/welcome.js +6 -9
  94. package/dist/commands/welcome.js.map +1 -1
  95. package/dist/core/ai-context-generator.js +2 -2
  96. package/dist/core/ai-context-generator.js.map +1 -1
  97. package/dist/core/analyze-engine.d.ts +19 -0
  98. package/dist/core/analyze-engine.d.ts.map +1 -1
  99. package/dist/core/analyze-engine.js +230 -18
  100. package/dist/core/analyze-engine.js.map +1 -1
  101. package/dist/core/ask-engine.d.ts.map +1 -1
  102. package/dist/core/ask-engine.js +101 -11
  103. package/dist/core/ask-engine.js.map +1 -1
  104. package/dist/core/capabilities.js +3 -3
  105. package/dist/core/code-index-markdown.d.ts +7 -0
  106. package/dist/core/code-index-markdown.d.ts.map +1 -0
  107. package/dist/core/code-index-markdown.js +160 -0
  108. package/dist/core/code-index-markdown.js.map +1 -0
  109. package/dist/core/code-scanner.d.ts +58 -3
  110. package/dist/core/code-scanner.d.ts.map +1 -1
  111. package/dist/core/code-scanner.js +406 -24
  112. package/dist/core/code-scanner.js.map +1 -1
  113. package/dist/core/git-integration.d.ts +10 -0
  114. package/dist/core/git-integration.d.ts.map +1 -1
  115. package/dist/core/git-integration.js +99 -2
  116. package/dist/core/git-integration.js.map +1 -1
  117. package/dist/core/inbox.d.ts +120 -0
  118. package/dist/core/inbox.d.ts.map +1 -0
  119. package/dist/core/inbox.js +377 -0
  120. package/dist/core/inbox.js.map +1 -0
  121. package/dist/core/intent-cache.d.ts.map +1 -1
  122. package/dist/core/intent-cache.js +8 -2
  123. package/dist/core/intent-cache.js.map +1 -1
  124. package/dist/core/intent-recognition.d.ts.map +1 -1
  125. package/dist/core/intent-recognition.js +4 -3
  126. package/dist/core/intent-recognition.js.map +1 -1
  127. package/dist/core/name-validator.js +1 -1
  128. package/dist/core/name-validator.js.map +1 -1
  129. package/dist/core/next-steps.js +3 -3
  130. package/dist/core/next-steps.js.map +1 -1
  131. package/dist/core/plan-html.js +3 -3
  132. package/dist/core/prompt-builder.d.ts +7 -0
  133. package/dist/core/prompt-builder.d.ts.map +1 -1
  134. package/dist/core/prompt-builder.js +52 -9
  135. package/dist/core/prompt-builder.js.map +1 -1
  136. package/dist/core/resolver.d.ts +52 -0
  137. package/dist/core/resolver.d.ts.map +1 -0
  138. package/dist/core/resolver.js +257 -0
  139. package/dist/core/resolver.js.map +1 -0
  140. package/dist/core/reverse-sync.js +2 -2
  141. package/dist/core/reverse-sync.js.map +1 -1
  142. package/dist/core/risk-scorer.js +2 -2
  143. package/dist/core/risk-scorer.js.map +1 -1
  144. package/dist/core/state.d.ts.map +1 -1
  145. package/dist/core/state.js +47 -15
  146. package/dist/core/state.js.map +1 -1
  147. package/dist/core/validator.js +9 -9
  148. package/dist/core/validator.js.map +1 -1
  149. package/dist/core/verify-engine.d.ts +64 -0
  150. package/dist/core/verify-engine.d.ts.map +1 -0
  151. package/dist/core/verify-engine.js +560 -0
  152. package/dist/core/verify-engine.js.map +1 -0
  153. package/package.json +1 -1
@@ -1,12 +1,63 @@
1
+ export interface CodeFile {
2
+ path: string;
3
+ language: string;
4
+ exports: string[];
5
+ apis: string[];
6
+ imports: string[];
7
+ lastModified: number;
8
+ lines: number;
9
+ endpoint: string;
10
+ module: string;
11
+ }
12
+ export interface EndpointInfo {
13
+ name: string;
14
+ rootPath: string;
15
+ techStack: string;
16
+ entryFile: string;
17
+ fileCount: number;
18
+ frameworks: string[];
19
+ }
20
+ export interface ModuleInfo {
21
+ name: string;
22
+ path: string;
23
+ endpoint: string;
24
+ fileCount: number;
25
+ coreFiles: string[];
26
+ exports: string[];
27
+ dependencies: string[];
28
+ }
29
+ export interface GitCorrelation {
30
+ files: string[];
31
+ count: number;
32
+ pattern: string;
33
+ }
34
+ export interface CodeIndex {
35
+ updatedAt: string;
36
+ files: CodeFile[];
37
+ endpoints: EndpointInfo[];
38
+ modules: ModuleInfo[];
39
+ correlations: GitCorrelation[];
40
+ gitStats: {
41
+ totalCommits: number;
42
+ analyzedCommits: number;
43
+ };
44
+ }
1
45
  /**
2
46
  * 扫描项目目录,构建代码索引
3
47
  * scope: 扫描范围 ("src/:backend/**" 表示只扫 backend 下的 src)
4
48
  */
5
- export declare function buildCodeIndex(scope?: string): Promise<number>;
6
49
  /**
7
- * 根据需求内容查找匹配的源码文件
50
+ * 全量扫描构建代码索引
8
51
  */
9
- export declare function findRelevantCode(requirements: string, limit?: number): Promise<{
52
+ export declare function buildCodeIndex(scope?: string, incremental?: boolean): Promise<number>;
53
+ /**
54
+ * 根据需求内容查找匹配的源码文件(解耦设计:从完整索引中按 scope 筛选)
55
+ *
56
+ * v2 增强:
57
+ * - 端配额:每个 endpoint 最多占 limit 的 40%,保证多端多样性
58
+ * - API 契约:加载 API_CONTRACT.yaml,命中契约路径的文件加分
59
+ */
60
+ export declare function findRelevantCode(requirements: string, limit?: number, scope?: string): Promise<{
10
61
  file: string;
11
62
  exports: string[];
12
63
  apis: string[];
@@ -23,4 +74,8 @@ export declare function readRelevantSource(matches: {
23
74
  * 检查索引是否过期 (超过 1 小时)
24
75
  */
25
76
  export declare function isIndexStale(): Promise<boolean>;
77
+ /**
78
+ * 加载完整索引(含端、模块、联动)
79
+ */
80
+ export declare function loadFullIndex(): Promise<CodeIndex | null>;
26
81
  //# sourceMappingURL=code-scanner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"code-scanner.d.ts","sourceRoot":"","sources":["../../src/core/code-scanner.ts"],"names":[],"mappings":"AA+BA;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBpE;AA2FD;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,MAAM,EACpB,KAAK,GAAE,MAAW,GACjB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAmC/E;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAC1C,QAAQ,GAAE,MAAc,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAsBjC;AAmBD;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAKrD"}
1
+ {"version":3,"file":"code-scanner.d.ts","sourceRoot":"","sources":["../../src/core/code-scanner.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B,QAAQ,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7D;AA8BD;;;GAGG;AACH;;GAEG;AACH,wBAAsB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,WAAW,GAAE,OAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAoClG;AAqHD;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,YAAY,EAAE,MAAM,EACpB,KAAK,GAAE,MAAW,EAClB,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAsF/E;AAyBD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,EAC1C,QAAQ,GAAE,MAAc,GACvB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAsBjC;AAmBD;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAKrD;AAED;;GAEG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAE/D"}
@@ -4,54 +4,99 @@ exports.buildCodeIndex = buildCodeIndex;
4
4
  exports.findRelevantCode = findRelevantCode;
5
5
  exports.readRelevantSource = readRelevantSource;
6
6
  exports.isIndexStale = isIndexStale;
7
+ exports.loadFullIndex = loadFullIndex;
7
8
  /**
8
- * code-scanner — 智能源码发现 + 范围控制
9
+ * code-scanner — 智能源码发现 + 范围控制 + 多端识别
9
10
  *
10
11
  * 策略:
11
12
  * 1. 首次: 扫描项目结构生成索引缓存 (.speccore/cache/code-structure.json)
12
13
  * 2. 分析时: 从需求提取关键词 → 匹配源码 → 只读相关文件
13
- * 3. 增量: 仅读取 .speccore/SETTINGS.md 中配置的目录范围
14
+ * 3. 增量: 对比 lastModified 只更新变化文件
15
+ * 4. 多端: 自动识别 frontend/backend/mobile/cli/shared 等端
16
+ * 5. git 联动: 分析 git log 找出高频共同变更的文件组
14
17
  */
15
18
  const fs_extra_1 = require("fs-extra");
16
19
  const path_1 = require("path");
20
+ const child_process_1 = require("child_process");
17
21
  const CACHE_DIR = (0, path_1.join)('.speccore', 'cache');
18
22
  const INDEX_PATH = (0, path_1.join)(CACHE_DIR, 'code-structure.json');
19
23
  const DEFAULT_SCOPE = ['src/', 'app/', 'lib/', 'pkg/'];
24
+ // ── 端识别规则 ──
25
+ const ENDPOINT_PATTERNS = {
26
+ frontend: {
27
+ patterns: ['src/web', 'src/frontend', 'src/client', 'src/ui', 'src/pages', 'src/views', 'src/components', 'web/', 'frontend/', 'app/'],
28
+ techHints: { 'react': 'React', 'vue': 'Vue', 'angular': 'Angular', 'svelte': 'Svelte', 'next': 'Next.js', 'nuxt': 'Nuxt' },
29
+ },
30
+ backend: {
31
+ patterns: ['src/server', 'src/backend', 'src/api', 'src/routes', 'src/controllers', 'src/services', 'server/', 'backend/', 'api/'],
32
+ techHints: { 'express': 'Express', 'koa': 'Koa', 'fastify': 'Fastify', 'nestjs': 'NestJS', 'spring': 'Spring', 'gin': 'Gin' },
33
+ },
34
+ mobile: {
35
+ patterns: ['src/mobile', 'src/native', 'src/app', 'ios/', 'android/', 'mobile/'],
36
+ techHints: { 'react-native': 'React Native', 'flutter': 'Flutter', 'expo': 'Expo' },
37
+ },
38
+ cli: {
39
+ patterns: ['src/cli', 'src/commands', 'src/cmd', 'bin/', 'cli/'],
40
+ techHints: { 'commander': 'Commander', 'yargs': 'Yargs', 'cobra': 'Cobra', 'click': 'Click' },
41
+ },
42
+ shared: {
43
+ patterns: ['src/shared', 'src/common', 'src/utils', 'src/lib', 'shared/', 'common/', 'lib/'],
44
+ techHints: {},
45
+ },
46
+ };
20
47
  /**
21
48
  * 扫描项目目录,构建代码索引
22
49
  * scope: 扫描范围 ("src/:backend/**" 表示只扫 backend 下的 src)
23
50
  */
24
- async function buildCodeIndex(scope) {
51
+ /**
52
+ * 全量扫描构建代码索引
53
+ */
54
+ async function buildCodeIndex(scope, incremental = false) {
25
55
  await (0, fs_extra_1.ensureDir)(CACHE_DIR);
56
+ // 增量模式:加载现有索引
57
+ let existingIndex = null;
58
+ if (incremental) {
59
+ existingIndex = await loadIndex();
60
+ }
26
61
  const dirs = scope ? scope.split(',').map(s => s.trim()) : DEFAULT_SCOPE;
27
- const files = [];
62
+ const files = existingIndex ? [...existingIndex.files] : [];
63
+ const processedPaths = new Set(files.map(f => f.path));
28
64
  for (const dir of dirs) {
29
65
  if (!(await (0, fs_extra_1.pathExists)(dir)))
30
66
  continue;
31
- await scanDirectory(dir, dir, files);
67
+ await scanDirectory(dir, dir, files, processedPaths, incremental);
32
68
  }
69
+ // 多端识别
70
+ const endpoints = detectEndpoints(files);
71
+ // 模块分组
72
+ const modules = groupModules(files, endpoints);
73
+ // git 变更联动分析
74
+ const { correlations, gitStats } = await analyzeGitCorrelations(files);
33
75
  const index = {
34
76
  updatedAt: new Date().toISOString(),
35
77
  files,
78
+ endpoints,
79
+ modules,
80
+ correlations,
81
+ gitStats,
36
82
  };
37
83
  await (0, fs_extra_1.writeFile)(INDEX_PATH, JSON.stringify(index, null, 2));
38
84
  return files.length;
39
85
  }
40
- async function scanDirectory(root, dir, result) {
86
+ async function scanDirectory(root, dir, result, processedPaths, incremental) {
41
87
  try {
42
88
  const entries = await (0, fs_extra_1.readdir)(dir, { withFileTypes: true });
43
89
  for (const entry of entries) {
44
90
  const fullPath = (0, path_1.join)(dir, entry.name);
45
- // 跳过 node_modules, .git, dist
91
+ // 跳过 node_modules, .git, dist, outputs 等
46
92
  if (entry.name.startsWith('.') && entry.name !== '.env')
47
93
  continue;
48
- if (['node_modules', 'dist', 'build', 'target', '__pycache__'].includes(entry.name))
94
+ if (['node_modules', 'dist', 'build', 'target', '__pycache__', 'outputs', '.speccore', 'templates', 'docs', 'tests', 'examples'].includes(entry.name))
49
95
  continue;
50
96
  if (entry.isDirectory()) {
51
- // 限制深度: 5层
52
97
  const depth = fullPath.split('/').length - root.split('/').length;
53
- if (depth < 5)
54
- await scanDirectory(root, fullPath, result);
98
+ if (depth < 6)
99
+ await scanDirectory(root, fullPath, result, processedPaths, incremental);
55
100
  }
56
101
  else if (entry.isFile()) {
57
102
  const ext = entry.name.split('.').pop() || '';
@@ -64,19 +109,42 @@ async function scanDirectory(root, dir, result) {
64
109
  if (!lang)
65
110
  continue;
66
111
  try {
67
- const content = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
68
112
  const st = await (0, fs_extra_1.stat)(fullPath);
69
- // 提取导出
113
+ const relPath = (0, path_1.relative)(process.cwd(), fullPath);
114
+ // 增量模式:跳过未修改的文件
115
+ if (incremental && processedPaths) {
116
+ const existing = result.find(f => f.path === relPath);
117
+ if (existing && existing.lastModified >= st.mtimeMs)
118
+ continue;
119
+ // 文件已修改,移除旧记录
120
+ if (existing) {
121
+ const idx = result.indexOf(existing);
122
+ result.splice(idx, 1);
123
+ }
124
+ }
125
+ const content = await (0, fs_extra_1.readFile)(fullPath, 'utf-8');
70
126
  const exports = extractExports(content, lang);
71
- // 提取 API 路径
72
127
  const apis = extractApis(content, lang);
73
- result.push({
74
- path: (0, path_1.relative)(process.cwd(), fullPath),
128
+ const imports = extractImports(content, lang);
129
+ const lines = content.split('\n').length;
130
+ const endpoint = detectEndpoint(relPath);
131
+ const module = detectModule(relPath);
132
+ const fileEntry = {
133
+ path: relPath,
75
134
  language: lang,
76
135
  exports,
77
136
  apis,
137
+ imports,
78
138
  lastModified: st.mtimeMs,
79
- });
139
+ lines,
140
+ endpoint,
141
+ module,
142
+ };
143
+ // 增量模式替换旧记录
144
+ if (incremental && processedPaths) {
145
+ processedPaths.add(relPath);
146
+ }
147
+ result.push(fileEntry);
80
148
  }
81
149
  catch {
82
150
  // 跳过二进制/不可读文件
@@ -131,15 +199,27 @@ function extractApis(content, lang) {
131
199
  return [...new Set(apis)];
132
200
  }
133
201
  /**
134
- * 根据需求内容查找匹配的源码文件
202
+ * 根据需求内容查找匹配的源码文件(解耦设计:从完整索引中按 scope 筛选)
203
+ *
204
+ * v2 增强:
205
+ * - 端配额:每个 endpoint 最多占 limit 的 40%,保证多端多样性
206
+ * - API 契约:加载 API_CONTRACT.yaml,命中契约路径的文件加分
135
207
  */
136
- async function findRelevantCode(requirements, limit = 10) {
208
+ async function findRelevantCode(requirements, limit = 10, scope // 查询时过滤:如 'src/commands,src/core'
209
+ ) {
137
210
  const index = await loadIndex();
138
211
  if (!index)
139
212
  return [];
213
+ // scope 过滤:只考虑指定目录下的文件
214
+ const scopeDirs = scope ? scope.split(',').map(s => s.trim()).filter(Boolean) : [];
215
+ const filesToSearch = scopeDirs.length > 0
216
+ ? index.files.filter(f => scopeDirs.some(dir => f.path.startsWith(dir)))
217
+ : index.files;
218
+ // ── L3: 加载 API 契约路径(用于加分) ──
219
+ const contractApis = await loadContractApiPaths();
140
220
  const keywords = extractKeywords(requirements);
141
221
  const scored = [];
142
- for (const f of index.files) {
222
+ for (const f of filesToSearch) {
143
223
  let score = 0;
144
224
  // 文件名匹配
145
225
  for (const kw of keywords) {
@@ -150,6 +230,10 @@ async function findRelevantCode(requirements, limit = 10) {
150
230
  for (const api of f.apis) {
151
231
  if (requirements.includes(api))
152
232
  score += 20;
233
+ // L3: 命中 API 契约路径额外加分
234
+ if (contractApis.some(cp => api.includes(cp) || cp.includes(api))) {
235
+ score += 15;
236
+ }
153
237
  }
154
238
  // 导出匹配
155
239
  for (const exp of f.exports) {
@@ -158,19 +242,86 @@ async function findRelevantCode(requirements, limit = 10) {
158
242
  score += 5;
159
243
  }
160
244
  }
245
+ // L3: 关键词命中契约中的 API 描述也加分
246
+ for (const cp of contractApis) {
247
+ for (const kw of keywords) {
248
+ if (cp.toLowerCase().includes(kw.toLowerCase()))
249
+ score += 3;
250
+ }
251
+ }
161
252
  if (score > 0)
162
253
  scored.push({ file: f, score });
163
254
  }
164
- return scored
165
- .sort((a, b) => b.score - a.score)
166
- .slice(0, limit)
167
- .map(s => ({
255
+ // ── L2: 端配额 — 每端最多占 limit 的 40%,保证多端多样性 ──
256
+ const endpointCap = Math.max(2, Math.ceil(limit * 0.4));
257
+ const byEndpoint = new Map();
258
+ for (const s of scored) {
259
+ const ep = s.file.endpoint || 'common';
260
+ if (!byEndpoint.has(ep))
261
+ byEndpoint.set(ep, []);
262
+ byEndpoint.get(ep).push(s);
263
+ }
264
+ // 每端内按分数降序
265
+ for (const arr of byEndpoint.values()) {
266
+ arr.sort((a, b) => b.score - a.score);
267
+ }
268
+ // 轮询取结果,每端每轮取一个,直到满 limit
269
+ const result = [];
270
+ const endpointCounts = new Map();
271
+ const sortedEndpoints = [...byEndpoint.entries()]
272
+ .sort((a, b) => (b[1][0]?.score || 0) - (a[1][0]?.score || 0));
273
+ let filled = 0;
274
+ while (filled < limit) {
275
+ let added = false;
276
+ for (const [ep, items] of sortedEndpoints) {
277
+ if (filled >= limit)
278
+ break;
279
+ const count = endpointCounts.get(ep) || 0;
280
+ if (count >= endpointCap)
281
+ continue;
282
+ const idx = count;
283
+ if (idx < items.length) {
284
+ result.push(items[idx]);
285
+ endpointCounts.set(ep, count + 1);
286
+ filled++;
287
+ added = true;
288
+ }
289
+ }
290
+ if (!added)
291
+ break;
292
+ }
293
+ return result.map(s => ({
168
294
  file: s.file.path,
169
295
  exports: s.file.exports.slice(0, 5),
170
296
  apis: s.file.apis,
171
297
  score: s.score,
172
298
  }));
173
299
  }
300
+ // L3: 从项目中的 API_CONTRACT.yaml 文件加载 API 路径
301
+ // 搜索 .speccore/ 和 Iteration-xxx/Task-xxx/_shared/ 下的契约文件
302
+ async function loadContractApiPaths() {
303
+ const paths = [];
304
+ try {
305
+ const { glob } = require('glob');
306
+ const files = await glob('.speccore/**/API_CONTRACT.yaml');
307
+ const iterFiles = await glob('Iteration-*/**/API_CONTRACT.yaml');
308
+ const allFiles = [...files, ...iterFiles];
309
+ for (const f of allFiles) {
310
+ if (!await (0, fs_extra_1.pathExists)(f))
311
+ continue;
312
+ const content = await (0, fs_extra_1.readFile)(f, 'utf-8');
313
+ // 提取 YAML 中的路径: /api/xxx: 格式
314
+ const matches = content.match(/^\s{2,4}(\/[\w/{}.-]+)\s*:/gm) || [];
315
+ for (const m of matches) {
316
+ const p = m.trim().replace(/:$/, '').trim();
317
+ if (p.startsWith('/') && !paths.includes(p))
318
+ paths.push(p);
319
+ }
320
+ }
321
+ }
322
+ catch { }
323
+ return paths;
324
+ }
174
325
  /**
175
326
  * 读取匹配到的源码文件内容(用于分析注入)
176
327
  */
@@ -223,4 +374,235 @@ async function isIndexStale() {
223
374
  const age = Date.now() - new Date(index.updatedAt).getTime();
224
375
  return age > 3600000; // 1 hour
225
376
  }
377
+ /**
378
+ * 加载完整索引(含端、模块、联动)
379
+ */
380
+ async function loadFullIndex() {
381
+ return loadIndex();
382
+ }
383
+ // ── 端识别 ──
384
+ function detectEndpoint(filePath) {
385
+ const normalized = filePath.replace(/\\/g, '/').toLowerCase();
386
+ for (const [endpoint, config] of Object.entries(ENDPOINT_PATTERNS)) {
387
+ for (const pattern of config.patterns) {
388
+ if (normalized.includes(pattern.toLowerCase()))
389
+ return endpoint;
390
+ }
391
+ }
392
+ return 'common';
393
+ }
394
+ function detectModule(filePath) {
395
+ const parts = filePath.replace(/\\/g, '/').split('/');
396
+ // 找到 src/ 之后的第一层目录作为模块名
397
+ const srcIdx = parts.findIndex(p => ['src', 'app', 'lib', 'pkg'].includes(p));
398
+ if (srcIdx >= 0 && srcIdx + 1 < parts.length) {
399
+ return parts[srcIdx + 1];
400
+ }
401
+ // 没有 src 则取第一层目录
402
+ return parts[0] || 'root';
403
+ }
404
+ function detectEndpoints(files) {
405
+ const endpointMap = new Map();
406
+ for (const f of files) {
407
+ const ep = f.endpoint;
408
+ if (!endpointMap.has(ep))
409
+ endpointMap.set(ep, []);
410
+ endpointMap.get(ep).push(f);
411
+ }
412
+ const result = [];
413
+ for (const [name, epFiles] of endpointMap) {
414
+ if (epFiles.length === 0)
415
+ continue;
416
+ // 检测技术栈
417
+ const frameworks = [];
418
+ const patterns = ENDPOINT_PATTERNS[name];
419
+ if (patterns) {
420
+ for (const [hint, label] of Object.entries(patterns.techHints)) {
421
+ if (epFiles.some(f => f.imports.some(i => i.includes(hint)) || f.path.includes(hint))) {
422
+ frameworks.push(label);
423
+ }
424
+ }
425
+ }
426
+ // 入口文件猜测
427
+ const entryCandidates = epFiles.filter(f => f.path.includes('index.') || f.path.includes('main.') || f.path.includes('app.'));
428
+ const entryFile = entryCandidates.length > 0 ? entryCandidates[0].path : (epFiles[0]?.path || '');
429
+ // 根路径
430
+ const paths = epFiles.map(f => f.path.split('/'));
431
+ const rootParts = [];
432
+ if (paths.length > 0) {
433
+ const minLen = Math.min(...paths.map(p => p.length));
434
+ for (let i = 0; i < minLen - 1; i++) {
435
+ const vals = new Set(paths.map(p => p[i]));
436
+ if (vals.size === 1)
437
+ rootParts.push(paths[0][i]);
438
+ else
439
+ break;
440
+ }
441
+ }
442
+ const techStack = frameworks.length > 0 ? frameworks.join(' + ') : (epFiles[0]?.language || 'unknown');
443
+ result.push({
444
+ name,
445
+ rootPath: rootParts.join('/') || '.',
446
+ techStack,
447
+ entryFile,
448
+ fileCount: epFiles.length,
449
+ frameworks,
450
+ });
451
+ }
452
+ return result.sort((a, b) => b.fileCount - a.fileCount);
453
+ }
454
+ // ── 模块分组 ──
455
+ function groupModules(files, endpoints) {
456
+ const moduleMap = new Map();
457
+ for (const f of files) {
458
+ const key = `${f.endpoint}:${f.module}`;
459
+ if (!moduleMap.has(key))
460
+ moduleMap.set(key, []);
461
+ moduleMap.get(key).push(f);
462
+ }
463
+ const result = [];
464
+ for (const [key, modFiles] of moduleMap) {
465
+ if (modFiles.length === 0)
466
+ continue;
467
+ const [endpoint, moduleName] = key.split(':');
468
+ // 核心文件:按导出数排序 top 3
469
+ const sorted = [...modFiles].sort((a, b) => b.exports.length - a.exports.length);
470
+ const coreFiles = sorted.slice(0, 3).map(f => f.path);
471
+ // 所有导出
472
+ const allExports = [...new Set(modFiles.flatMap(f => f.exports))].slice(0, 30);
473
+ // 依赖分析:看 import 指向哪些其他模块
474
+ const deps = new Set();
475
+ for (const f of modFiles) {
476
+ for (const imp of f.imports) {
477
+ const target = files.find(tf => tf.path.includes(imp.replace(/^\.\.?\//, '')) || imp.includes((0, path_1.basename)(tf.path, '.ts')));
478
+ if (target && target.module !== moduleName) {
479
+ deps.add(target.module);
480
+ }
481
+ }
482
+ }
483
+ const commonPath = modFiles.map(f => f.path).reduce((a, b) => {
484
+ const pa = a.split('/'), pb = b.split('/');
485
+ const common = [];
486
+ for (let i = 0; i < Math.min(pa.length, pb.length); i++) {
487
+ if (pa[i] === pb[i])
488
+ common.push(pa[i]);
489
+ else
490
+ break;
491
+ }
492
+ return common.join('/');
493
+ });
494
+ result.push({
495
+ name: moduleName,
496
+ path: commonPath || modFiles[0].path,
497
+ endpoint,
498
+ fileCount: modFiles.length,
499
+ coreFiles,
500
+ exports: allExports,
501
+ dependencies: [...deps].slice(0, 10),
502
+ });
503
+ }
504
+ return result.sort((a, b) => b.fileCount - a.fileCount);
505
+ }
506
+ // ── git 变更联动分析 ──
507
+ async function analyzeGitCorrelations(files) {
508
+ const correlations = [];
509
+ let totalCommits = 0;
510
+ let analyzedCommits = 0;
511
+ try {
512
+ // 取最近 100 次提交的变更文件
513
+ const gitLog = (0, child_process_1.execSync)('git log --name-only --pretty=format:"---COMMIT---" -100 2>/dev/null', { encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024, timeout: 15000 });
514
+ // 解析每个 commit 的文件列表
515
+ const commitFiles = [];
516
+ let current = [];
517
+ for (const line of gitLog.split('\n')) {
518
+ if (line === '---COMMIT---') {
519
+ if (current.length > 0)
520
+ commitFiles.push(current);
521
+ current = [];
522
+ }
523
+ else if (line.trim()) {
524
+ current.push(line.trim());
525
+ }
526
+ }
527
+ if (current.length > 0)
528
+ commitFiles.push(current);
529
+ totalCommits = commitFiles.length;
530
+ analyzedCommits = Math.min(totalCommits, 100);
531
+ // 统计文件对共同出现次数
532
+ const pairCount = new Map();
533
+ for (const cf of commitFiles) {
534
+ // 只关注已索引的文件
535
+ const indexed = cf.filter(f => files.some(idx => idx.path === f));
536
+ if (indexed.length < 2 || indexed.length > 10)
537
+ continue;
538
+ // 按模块分组
539
+ const modules = [...new Set(indexed.map(f => {
540
+ const idx = files.find(fi => fi.path === f);
541
+ return idx ? idx.module : '';
542
+ }))].filter(Boolean);
543
+ if (modules.length >= 2) {
544
+ const key = modules.sort().join(' ↔ ');
545
+ pairCount.set(key, (pairCount.get(key) || 0) + 1);
546
+ }
547
+ }
548
+ // 取 top 10 高频联动
549
+ const sorted = [...pairCount.entries()]
550
+ .sort((a, b) => b[1] - a[1])
551
+ .slice(0, 10);
552
+ for (const [pattern, count] of sorted) {
553
+ if (count >= 2) {
554
+ correlations.push({
555
+ files: pattern.split(' ↔ '),
556
+ count,
557
+ pattern: `改 ${pattern.split(' ↔ ')[0]} → ${Math.round(count / analyzedCommits * 100)}% 概率联动 ${pattern.split(' ↔ ')[1]}`,
558
+ });
559
+ }
560
+ }
561
+ }
562
+ catch {
563
+ // git 不可用时跳过
564
+ }
565
+ return { correlations, gitStats: { totalCommits, analyzedCommits } };
566
+ }
567
+ // ── 导入提取 ──
568
+ function extractImports(content, lang) {
569
+ const imports = [];
570
+ if (lang === 'typescript' || lang === 'javascript') {
571
+ // import ... from 'xxx'
572
+ const matches = content.match(/import\s+.*?from\s+['"]([^'"]+)['"]/g) || [];
573
+ for (const m of matches) {
574
+ const path = m.match(/['"]([^'"]+)['"]/)?.[1];
575
+ if (path)
576
+ imports.push(path);
577
+ }
578
+ // require('xxx')
579
+ const reqMatches = content.match(/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g) || [];
580
+ for (const m of reqMatches) {
581
+ const path = m.match(/['"]([^'"]+)['"]/)?.[1];
582
+ if (path)
583
+ imports.push(path);
584
+ }
585
+ }
586
+ else if (lang === 'python') {
587
+ const matches = content.match(/(?:import|from)\s+(\S+)/g) || [];
588
+ for (const m of matches) {
589
+ const mod = m.replace(/^(?:import|from)\s+/, '');
590
+ if (mod)
591
+ imports.push(mod);
592
+ }
593
+ }
594
+ else if (lang === 'java') {
595
+ const matches = content.match(/import\s+([\w.]+);/g) || [];
596
+ for (const m of matches) {
597
+ imports.push(m.replace('import ', '').replace(';', ''));
598
+ }
599
+ }
600
+ else if (lang === 'go') {
601
+ const matches = content.match(/"([\w./]+)"/g) || [];
602
+ for (const m of matches) {
603
+ imports.push(m.replace(/"/g, ''));
604
+ }
605
+ }
606
+ return [...new Set(imports)].slice(0, 30);
607
+ }
226
608
  //# sourceMappingURL=code-scanner.js.map