preflight-mcp 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -34
- package/README.zh-CN.md +88 -35
- package/dist/evidence/dependencyGraph.js +54 -18
- package/dist/mcp/responseMeta.js +100 -0
- package/dist/server.js +346 -42
- package/dist/trace/service.js +235 -13
- package/dist/trace/store.js +109 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,47 +3,81 @@
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://nodejs.org/)
|
|
5
5
|
[](https://modelcontextprotocol.io/)
|
|
6
|
+
[](https://www.npmjs.com/package/preflight-mcp)
|
|
6
7
|
|
|
7
8
|
> **English** | [中文](./README.zh-CN.md)
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
10
|
+
**Give your AI assistant deep knowledge of any codebase — in seconds.**
|
|
11
|
+
|
|
12
|
+
Preflight-MCP creates searchable, indexed knowledge bundles from GitHub repos, so Claude/GPT/Cursor can understand your project structure, find relevant code, and trace dependencies — without copy-pasting or token limits.
|
|
13
|
+
|
|
14
|
+
## Why Preflight?
|
|
15
|
+
|
|
16
|
+
| Problem | Preflight Solution |
|
|
17
|
+
|---------|--------------------|
|
|
18
|
+
| 🤯 AI forgets your codebase context | Persistent, searchable bundles |
|
|
19
|
+
| 📋 Copy-pasting code into chat | One command: `"index this repo"` |
|
|
20
|
+
| 🔍 AI can't find related files | Full-text search + dependency graph |
|
|
21
|
+
| 🧩 Lost in large projects | Auto-generated `START_HERE.md` & `OVERVIEW.md` |
|
|
22
|
+
| 🔗 No idea what tests cover what | Trace links: code↔test↔doc |
|
|
23
|
+
|
|
24
|
+
## Demo
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
You: "Create a bundle for the repository facebook/react"
|
|
28
|
+
|
|
29
|
+
Preflight: ✅ Cloned, indexed 2,847 files, generated overview
|
|
30
|
+
|
|
31
|
+
You: "Search for 'useState' implementation"
|
|
32
|
+
|
|
33
|
+
Preflight: 📍 Found 23 matches:
|
|
34
|
+
→ packages/react/src/ReactHooks.js:24
|
|
35
|
+
→ packages/react-reconciler/src/ReactFiberHooks.js:1042
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
You: "Show me what tests cover useState"
|
|
39
|
+
|
|
40
|
+
Preflight: 🔗 Trace links:
|
|
41
|
+
→ ReactHooks.js tested_by ReactHooksTest.js
|
|
42
|
+
...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Core Features
|
|
46
|
+
|
|
47
|
+
- 🚀 **One-command indexing** — `"index owner/repo"` creates a complete knowledge bundle
|
|
48
|
+
- 🔍 **Full-text search** — SQLite FTS5 search across all code and docs
|
|
49
|
+
- 🗺️ **Dependency graph** — Visualize imports and file relationships
|
|
50
|
+
- 🔗 **Trace links** — Track code↔test↔doc relationships
|
|
51
|
+
- 📖 **Auto-generated guides** — `START_HERE.md`, `AGENTS.md`, `OVERVIEW.md`
|
|
52
|
+
- ☁️ **Cloud sync** — Multi-path mirror backup for redundancy
|
|
53
|
+
- ⚡ **15 MCP tools + 5 prompts** — Complete toolkit for code exploration
|
|
54
|
+
|
|
55
|
+
<details>
|
|
56
|
+
<summary><b>All Features (click to expand)</b></summary>
|
|
57
|
+
|
|
58
|
+
- **Progress tracking**: Real-time progress for long-running operations
|
|
59
|
+
- **Bundle integrity check**: Prevents operations on incomplete bundles
|
|
60
|
+
- **De-duplication**: Prevent duplicate bundle creation even during timeouts
|
|
61
|
+
- **Resilient GitHub fetching**: Git clone timeout + archive fallback
|
|
62
|
+
- **Offline repair**: Rebuild derived artifacts without re-fetching
|
|
63
|
+
- **Static facts extraction**: `analysis/FACTS.json` (non-LLM)
|
|
64
|
+
- **Resources**: Read bundle files via `preflight://...` URIs
|
|
65
|
+
- **Atomic operations**: Crash-safety with zero orphans
|
|
66
|
+
- **Fast deletion**: 100-300x performance improvement
|
|
67
|
+
- **Auto-cleanup**: Removes orphan bundles on startup
|
|
68
|
+
|
|
69
|
+
</details>
|
|
37
70
|
|
|
38
71
|
## Table of Contents
|
|
39
72
|
|
|
40
|
-
- [
|
|
41
|
-
- [
|
|
73
|
+
- [Why Preflight?](#why-preflight)
|
|
74
|
+
- [Demo](#demo)
|
|
75
|
+
- [Core Features](#core-features)
|
|
42
76
|
- [Quick Start](#quick-start)
|
|
43
|
-
- [Tools](#tools-
|
|
77
|
+
- [Tools](#tools-15-total)
|
|
78
|
+
- [Prompts](#prompts-5-total)
|
|
44
79
|
- [Environment Variables](#environment-variables)
|
|
45
80
|
- [Contributing](#contributing)
|
|
46
|
-
- [License](#license)
|
|
47
81
|
|
|
48
82
|
## Requirements
|
|
49
83
|
|
|
@@ -191,6 +225,8 @@ Important: **this tool is strictly read-only**.
|
|
|
191
225
|
- To update: call `preflight_update_bundle`, then search again.
|
|
192
226
|
- To repair: call `preflight_repair_bundle`, then search again.
|
|
193
227
|
|
|
228
|
+
**Deprecated parameters** (v0.2.7+): `ensureFresh`, `autoRepairIndex`, `maxAgeHours` are deprecated and will return warnings instead of errors. Use separate update/repair tools.
|
|
229
|
+
|
|
194
230
|
### `preflight_search_by_tags`
|
|
195
231
|
Search across multiple bundles filtered by tags (line-based SQLite FTS5).
|
|
196
232
|
- Triggers: "search in MCP bundles", "在MCP项目中搜索", "搜索所有agent"
|
|
@@ -206,11 +242,23 @@ Optional parameters:
|
|
|
206
242
|
|
|
207
243
|
### `preflight_evidence_dependency_graph`
|
|
208
244
|
Generate an evidence-based dependency graph. Two modes:
|
|
209
|
-
- **Target mode** (provide `target.file`): Analyze a specific file's imports and
|
|
245
|
+
- **Target mode** (provide `target.file`): Analyze a specific file's imports and references
|
|
210
246
|
- **Global mode** (omit `target`): Generate project-wide import graph of all code files
|
|
211
247
|
- Deterministic output with source ranges for edges.
|
|
212
248
|
- Uses Tree-sitter parsing when `PREFLIGHT_AST_ENGINE=wasm`; falls back to regex extraction otherwise.
|
|
213
|
-
|
|
249
|
+
|
|
250
|
+
**Edge types** (v0.2.7+):
|
|
251
|
+
- `edgeTypes: "imports"` (default): Only AST-based import edges (high confidence, recommended)
|
|
252
|
+
- `edgeTypes: "all"`: Include FTS-based reference edges (name matching, may have false positives)
|
|
253
|
+
|
|
254
|
+
**Cache transparency** (v0.2.7+):
|
|
255
|
+
- Response includes `meta.cacheInfo` with `fromCache`, `generatedAt`, `cacheAgeMs`
|
|
256
|
+
- Use `force: true` to regenerate cached global graphs
|
|
257
|
+
|
|
258
|
+
**Large file handling**:
|
|
259
|
+
- `options.maxFileSizeBytes` (default: 1MB): Skip files larger than this
|
|
260
|
+
- `options.largeFileStrategy`: `"skip"` (default) or `"truncate"`
|
|
261
|
+
- `options.excludeExtensions`: Filter out non-code files from reference search (default: `.json`, `.md`, `.txt`, `.yml`, etc.)
|
|
214
262
|
|
|
215
263
|
### `preflight_trace_upsert`
|
|
216
264
|
Create or update traceability links (code↔test, code↔doc, file↔requirement).
|
package/README.zh-CN.md
CHANGED
|
@@ -3,41 +3,81 @@
|
|
|
3
3
|
[](https://opensource.org/licenses/MIT)
|
|
4
4
|
[](https://nodejs.org/)
|
|
5
5
|
[](https://modelcontextprotocol.io/)
|
|
6
|
+
[](https://www.npmjs.com/package/preflight-mcp)
|
|
6
7
|
|
|
7
8
|
> [English](./README.md) | **中文**
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
10
|
+
**让你的 AI 助手秒懂任何代码仓库。**
|
|
11
|
+
|
|
12
|
+
Preflight-MCP 为 GitHub 仓库创建可搜索的知识库,让 Claude/GPT/Cursor 理解你的项目结构、快速定位代码、追踪依赖关系 —— 无需复制粘贴,不受 token 限制。
|
|
13
|
+
|
|
14
|
+
## 为什么需要 Preflight?
|
|
15
|
+
|
|
16
|
+
| 痛点 | Preflight 解决方案 |
|
|
17
|
+
|------|--------------------|
|
|
18
|
+
| 🤯 AI 记不住你的代码库 | 持久化、可搜索的知识包 |
|
|
19
|
+
| 📋 反复复制粘贴代码 | 一句话:「索引这个仓库」 |
|
|
20
|
+
| 🔍 AI 找不到相关文件 | 全文搜索 + 依赖图 |
|
|
21
|
+
| 🧩 大项目里迷失方向 | 自动生成 `START_HERE.md` 和 `OVERVIEW.md` |
|
|
22
|
+
| 🔗 不知道哪些测试覆盖哪些代码 | 追溯链接:代码↔测试↔文档 |
|
|
23
|
+
|
|
24
|
+
## 效果演示
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
你:「为 facebook/react 创建 bundle」
|
|
28
|
+
|
|
29
|
+
Preflight:✅ 已克隆,索引了 2,847 个文件,生成概览完成
|
|
30
|
+
|
|
31
|
+
你:「搜索 useState 的实现」
|
|
32
|
+
|
|
33
|
+
Preflight:📍 找到 23 处匹配:
|
|
34
|
+
→ packages/react/src/ReactHooks.js:24
|
|
35
|
+
→ packages/react-reconciler/src/ReactFiberHooks.js:1042
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
你:「哪些测试覆盖了 useState」
|
|
39
|
+
|
|
40
|
+
Preflight:🔗 追溯链接:
|
|
41
|
+
→ ReactHooks.js tested_by ReactHooksTest.js
|
|
42
|
+
...
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 核心功能
|
|
46
|
+
|
|
47
|
+
- 🚀 **一句话索引** — 「索引 owner/repo」即可创建完整知识包
|
|
48
|
+
- 🔍 **全文搜索** — SQLite FTS5 搜索全部代码和文档
|
|
49
|
+
- 🗺️ **依赖图** — 可视化 import 关系和文件依赖
|
|
50
|
+
- 🔗 **追溯链接** — 追踪代码↔测试↔文档关系
|
|
51
|
+
- 📖 **自动生成指南** — `START_HERE.md`、`AGENTS.md`、`OVERVIEW.md`
|
|
52
|
+
- ☁️ **云端同步** — 多路径镜像备份
|
|
53
|
+
- ⚡ **15 个 MCP 工具 + 5 个 prompts** — 完整的代码探索工具集
|
|
54
|
+
|
|
55
|
+
<details>
|
|
56
|
+
<summary><b>全部功能(点击展开)</b></summary>
|
|
57
|
+
|
|
58
|
+
- **进度追踪**:长时间操作的实时进度显示
|
|
59
|
+
- **Bundle 完整性检查**:防止对不完整 bundle 进行操作
|
|
60
|
+
- **去重机制**:即使超时也能防止重复创建
|
|
61
|
+
- **可靠的 GitHub 抓取**:git clone 超时 + archive 兜底
|
|
62
|
+
- **离线修复**:无需重新抓取即可重建派生文件
|
|
63
|
+
- **静态事实提取**:`analysis/FACTS.json`(非 LLM)
|
|
64
|
+
- **Resources**:通过 `preflight://...` URI 读取文件
|
|
65
|
+
- **原子操作**:崩溃安全,零孤儿目录
|
|
66
|
+
- **快速删除**:100-300 倍性能提升
|
|
67
|
+
- **自动清理**:启动时自动清理孤儿 bundle
|
|
68
|
+
|
|
69
|
+
</details>
|
|
70
|
+
|
|
71
|
+
## 目录
|
|
72
|
+
|
|
73
|
+
- [为什么需要 Preflight](#为什么需要-preflight)
|
|
74
|
+
- [效果演示](#效果演示)
|
|
75
|
+
- [核心功能](#核心功能)
|
|
76
|
+
- [快速开始](#quick-start)
|
|
77
|
+
- [工具](#tools-15-total)
|
|
78
|
+
- [Prompts](#prompts-5-total)
|
|
79
|
+
- [环境变量](#environment-variables)
|
|
80
|
+
- [贡献指南](#contributing)
|
|
41
81
|
|
|
42
82
|
## Requirements
|
|
43
83
|
|
|
@@ -203,10 +243,11 @@ npm run smoke
|
|
|
203
243
|
- 触发词:「搜索bundle」「在仓库中查找」「搜代码」
|
|
204
244
|
|
|
205
245
|
重要:**此工具是严格只读的**。
|
|
206
|
-
- `ensureFresh` / `maxAgeHours` 已**弃用**,提供时会报错
|
|
207
246
|
- 更新:先调用 `preflight_update_bundle`,再搜索
|
|
208
247
|
- 修复:先调用 `preflight_repair_bundle`,再搜索
|
|
209
248
|
|
|
249
|
+
**已弃用参数**(v0.2.7+):`ensureFresh`、`autoRepairIndex`、`maxAgeHours` 已弃用,使用时会返回警告(不再报错)。请使用单独的 update/repair 工具。
|
|
250
|
+
|
|
210
251
|
### `preflight_search_by_tags`
|
|
211
252
|
跨多个 bundle 按标签过滤搜索(基于行的 SQLite FTS5)。
|
|
212
253
|
- 触发词:「search in MCP bundles」「search in all bundles」「在MCP项目中搜索」「搜索所有agent」
|
|
@@ -221,10 +262,22 @@ npm run smoke
|
|
|
221
262
|
- `limit`:跨所有 bundle 的最大命中数
|
|
222
263
|
|
|
223
264
|
### `preflight_evidence_dependency_graph`
|
|
224
|
-
生成目标文件/符号的「基于证据」的依赖图(imports +
|
|
265
|
+
生成目标文件/符号的「基于证据」的依赖图(imports + references)。
|
|
225
266
|
- 输出确定性(best-effort),并为每条边提供可追溯 source range
|
|
226
267
|
- `PREFLIGHT_AST_ENGINE=wasm` 时使用 Tree-sitter;否则回退到正则抽取
|
|
227
|
-
|
|
268
|
+
|
|
269
|
+
**边类型**(v0.2.7+):
|
|
270
|
+
- `edgeTypes: "imports"`(默认):仅返回基于 AST 的 import 边(高置信度,推荐)
|
|
271
|
+
- `edgeTypes: "all"`:包含基于 FTS 的 reference 边(名称匹配,可能有误报)
|
|
272
|
+
|
|
273
|
+
**缓存透明化**(v0.2.7+):
|
|
274
|
+
- 响应包含 `meta.cacheInfo`:`fromCache`、`generatedAt`、`cacheAgeMs`
|
|
275
|
+
- 使用 `force: true` 可重新生成缓存的全局图
|
|
276
|
+
|
|
277
|
+
**大文件处理**:
|
|
278
|
+
- `options.maxFileSizeBytes`(默认:1MB):跳过超过此大小的文件
|
|
279
|
+
- `options.largeFileStrategy`:`"skip"`(默认)或 `"truncate"`
|
|
280
|
+
- `options.excludeExtensions`:从 reference 搜索中排除非代码文件(默认:`.json`、`.md`、`.txt`、`.yml` 等)
|
|
228
281
|
|
|
229
282
|
### `preflight_trace_upsert`
|
|
230
283
|
写入/更新 bundle 级 traceability links(commit↔ticket、symbol↔test、code↔doc 等)。
|
|
@@ -23,6 +23,10 @@ export const DependencyGraphInputSchema = {
|
|
|
23
23
|
'Global mode shows import relationships between all files but may be truncated for large projects.'),
|
|
24
24
|
force: z.boolean().default(false).describe('If true, regenerate the dependency graph even if cached. ' +
|
|
25
25
|
'Global mode results are cached in the bundle; use force=true to refresh.'),
|
|
26
|
+
/** Edge types to include in the result. Default: only imports (AST-based, high confidence). */
|
|
27
|
+
edgeTypes: z.enum(['imports', 'all']).default('imports').describe('Edge types to include. "imports": only AST-based import edges (high confidence, recommended). ' +
|
|
28
|
+
'"all": include FTS-based reference edges (name matching, may have false positives). ' +
|
|
29
|
+
'Default: "imports" for accuracy. Use "all" only when you need to find callers/references.'),
|
|
26
30
|
options: z
|
|
27
31
|
.object({
|
|
28
32
|
maxFiles: z.number().int().min(1).max(500).default(200),
|
|
@@ -38,8 +42,11 @@ export const DependencyGraphInputSchema = {
|
|
|
38
42
|
/** If largeFileStrategy=truncate, how many lines to read */
|
|
39
43
|
truncateLines: z.number().int().min(100).max(5000).default(500)
|
|
40
44
|
.describe('When largeFileStrategy=truncate, read this many lines. Default 500.'),
|
|
45
|
+
/** File extensions to exclude from reference search (FTS). Helps reduce false positives. */
|
|
46
|
+
excludeExtensions: z.array(z.string()).default(['.json', '.md', '.txt', '.yml', '.yaml', '.toml', '.lock'])
|
|
47
|
+
.describe('File extensions to exclude from reference/caller search. Default excludes non-code files.'),
|
|
41
48
|
})
|
|
42
|
-
.default({ maxFiles: 200, maxNodes: 300, maxEdges: 800, timeBudgetMs: 25_000, maxFileSizeBytes: 1_000_000, largeFileStrategy: 'skip', truncateLines: 500 }),
|
|
49
|
+
.default({ maxFiles: 200, maxNodes: 300, maxEdges: 800, timeBudgetMs: 25_000, maxFileSizeBytes: 1_000_000, largeFileStrategy: 'skip', truncateLines: 500, excludeExtensions: ['.json', '.md', '.txt', '.yml', '.yaml', '.toml', '.lock'] }),
|
|
43
50
|
};
|
|
44
51
|
function sha256Hex(text) {
|
|
45
52
|
return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
|
|
@@ -217,11 +224,20 @@ export async function generateDependencyGraph(cfg, rawArgs) {
|
|
|
217
224
|
try {
|
|
218
225
|
const cached = await fs.readFile(paths.depsGraphPath, 'utf8');
|
|
219
226
|
const parsed = JSON.parse(cached);
|
|
227
|
+
const cachedAt = parsed.meta.generatedAt ? new Date(parsed.meta.generatedAt).getTime() : 0;
|
|
228
|
+
const cacheAgeMs = cachedAt ? Date.now() - cachedAt : 0;
|
|
229
|
+
// Add cacheInfo to meta
|
|
230
|
+
parsed.meta.cacheInfo = {
|
|
231
|
+
fromCache: true,
|
|
232
|
+
generatedAt: parsed.meta.generatedAt,
|
|
233
|
+
cacheAgeMs,
|
|
234
|
+
hint: 'Use force=true to regenerate the graph.',
|
|
235
|
+
};
|
|
220
236
|
// Add note that this is from cache
|
|
221
237
|
parsed.signals.warnings = parsed.signals.warnings || [];
|
|
222
238
|
parsed.signals.warnings.unshift({
|
|
223
239
|
code: 'from_cache',
|
|
224
|
-
message: `Loaded from cache (generated at ${parsed.meta.generatedAt}). Use force=true to regenerate.`,
|
|
240
|
+
message: `Loaded from cache (generated at ${parsed.meta.generatedAt}, age: ${Math.round(cacheAgeMs / 1000)}s). Use force=true to regenerate.`,
|
|
225
241
|
});
|
|
226
242
|
return parsed;
|
|
227
243
|
}
|
|
@@ -777,26 +793,32 @@ export async function generateDependencyGraph(cfg, rawArgs) {
|
|
|
777
793
|
message: `Failed to read target file for import extraction: ${err instanceof Error ? err.message : String(err)}`,
|
|
778
794
|
});
|
|
779
795
|
}
|
|
780
|
-
// 2) Upstream: find
|
|
796
|
+
// 2) Upstream: find references via FTS hits (only if edgeTypes='all')
|
|
781
797
|
let searchHits = 0;
|
|
782
798
|
let filesRead = 0;
|
|
783
|
-
let
|
|
799
|
+
let referenceEdges = 0;
|
|
784
800
|
let importEdges = edges.filter((e) => e.type === 'imports').length;
|
|
785
|
-
|
|
801
|
+
const includeReferences = args.edgeTypes === 'all';
|
|
802
|
+
const excludeExtensions = new Set(args.options.excludeExtensions ?? ['.json', '.md', '.txt', '.yml', '.yaml', '.toml', '.lock']);
|
|
803
|
+
if (includeReferences && targetSymbol && targetSymbol.length >= 2) {
|
|
786
804
|
const maxHits = Math.min(500, limits.maxFiles * 5);
|
|
787
805
|
const hits = searchIndex(paths.searchDbPath, targetSymbol, 'code', maxHits, paths.rootDir);
|
|
788
806
|
searchHits = hits.length;
|
|
789
807
|
const fileLineCache = new Map();
|
|
790
808
|
for (const hit of hits) {
|
|
791
|
-
if (checkBudget('timeBudget exceeded during
|
|
809
|
+
if (checkBudget('timeBudget exceeded during reference scan'))
|
|
792
810
|
break;
|
|
793
811
|
if (edges.length >= limits.maxEdges)
|
|
794
812
|
break;
|
|
795
813
|
const hitPath = hit.path;
|
|
796
814
|
if (!hitPath || hit.kind !== 'code')
|
|
797
815
|
continue;
|
|
816
|
+
// P3: Filter out non-code files by extension
|
|
817
|
+
const hitExt = path.extname(hitPath).toLowerCase();
|
|
818
|
+
if (excludeExtensions.has(hitExt))
|
|
819
|
+
continue;
|
|
798
820
|
// Skip obvious self-reference in the same file if no symbol boundary detection.
|
|
799
|
-
// We still allow
|
|
821
|
+
// We still allow references within the same file (but avoid exploding edges).
|
|
800
822
|
// Read file lines (cache)
|
|
801
823
|
let lines = fileLineCache.get(hitPath);
|
|
802
824
|
if (!lines) {
|
|
@@ -845,19 +867,19 @@ export async function generateDependencyGraph(cfg, rawArgs) {
|
|
|
845
867
|
snippet: clampSnippet(line, 200),
|
|
846
868
|
};
|
|
847
869
|
src.snippetSha256 = sha256Hex(src.snippet ?? '');
|
|
848
|
-
const evidenceId = makeEvidenceId(['
|
|
870
|
+
const evidenceId = makeEvidenceId(['references', callerId, targetSymbolId, hitPath, String(hit.lineNo), String(call.startCol)]);
|
|
849
871
|
addEdge({
|
|
850
872
|
evidenceId,
|
|
851
873
|
kind: 'edge',
|
|
852
|
-
type: '
|
|
874
|
+
type: 'references',
|
|
853
875
|
from: callerId,
|
|
854
876
|
to: targetSymbolId,
|
|
855
877
|
method: 'heuristic',
|
|
856
|
-
confidence: 0.
|
|
878
|
+
confidence: 0.5,
|
|
857
879
|
sources: [src],
|
|
858
|
-
notes: ['
|
|
880
|
+
notes: ['reference edge is FTS name-based (may include false positives from comments/strings/docs)'],
|
|
859
881
|
});
|
|
860
|
-
|
|
882
|
+
referenceEdges++;
|
|
861
883
|
if (nodes.size >= limits.maxNodes) {
|
|
862
884
|
truncated = true;
|
|
863
885
|
truncatedReason = 'maxNodes reached';
|
|
@@ -871,21 +893,27 @@ export async function generateDependencyGraph(cfg, rawArgs) {
|
|
|
871
893
|
});
|
|
872
894
|
}
|
|
873
895
|
}
|
|
896
|
+
else if (!includeReferences && targetSymbol && targetSymbol.length >= 2) {
|
|
897
|
+
warnings.push({
|
|
898
|
+
code: 'references_skipped',
|
|
899
|
+
message: 'Reference/caller search was skipped (edgeTypes="imports"). Use edgeTypes="all" to include FTS-based reference edges (may have false positives).',
|
|
900
|
+
});
|
|
901
|
+
}
|
|
874
902
|
else {
|
|
875
903
|
warnings.push({
|
|
876
904
|
code: 'symbol_missing_or_too_short',
|
|
877
|
-
message: 'No symbol provided (or symbol too short).
|
|
905
|
+
message: 'No symbol provided (or symbol too short). Reference graph was skipped; only imports were extracted from the target file.',
|
|
878
906
|
});
|
|
879
907
|
}
|
|
880
908
|
// Post-process warnings
|
|
881
909
|
warnings.push({
|
|
882
910
|
code: 'limitations',
|
|
883
911
|
message: usedAstForImports
|
|
884
|
-
? 'This dependency graph uses deterministic parsing for imports (Tree-sitter WASM syntax AST)
|
|
885
|
-
: 'This dependency graph
|
|
912
|
+
? 'This dependency graph uses deterministic parsing for imports (Tree-sitter WASM syntax AST). Reference edges (if enabled) use FTS + name-based heuristics and may have false positives. Each edge includes method/confidence/sources for auditability.'
|
|
913
|
+
: 'This dependency graph uses regex-based import extraction. Reference edges (if enabled) use FTS + name-based heuristics. Each edge includes method/confidence/sources for auditability.',
|
|
886
914
|
});
|
|
887
915
|
// Stats
|
|
888
|
-
importEdges = edges.filter((e) => e.type === 'imports').length;
|
|
916
|
+
importEdges = edges.filter((e) => e.type === 'imports' || e.type === 'imports_resolved').length;
|
|
889
917
|
const out = {
|
|
890
918
|
meta: {
|
|
891
919
|
requestId,
|
|
@@ -901,6 +929,9 @@ export async function generateDependencyGraph(cfg, rawArgs) {
|
|
|
901
929
|
truncatedReason,
|
|
902
930
|
limits,
|
|
903
931
|
},
|
|
932
|
+
cacheInfo: {
|
|
933
|
+
fromCache: false,
|
|
934
|
+
},
|
|
904
935
|
},
|
|
905
936
|
facts: {
|
|
906
937
|
nodes: Array.from(nodes.values()),
|
|
@@ -910,7 +941,8 @@ export async function generateDependencyGraph(cfg, rawArgs) {
|
|
|
910
941
|
stats: {
|
|
911
942
|
filesRead,
|
|
912
943
|
searchHits,
|
|
913
|
-
callEdges,
|
|
944
|
+
callEdges: referenceEdges, // deprecated, use referenceEdges
|
|
945
|
+
referenceEdges,
|
|
914
946
|
importEdges,
|
|
915
947
|
},
|
|
916
948
|
warnings,
|
|
@@ -1154,6 +1186,9 @@ async function generateGlobalDependencyGraph(ctx) {
|
|
|
1154
1186
|
truncatedReason,
|
|
1155
1187
|
limits,
|
|
1156
1188
|
},
|
|
1189
|
+
cacheInfo: {
|
|
1190
|
+
fromCache: false,
|
|
1191
|
+
},
|
|
1157
1192
|
},
|
|
1158
1193
|
facts: {
|
|
1159
1194
|
nodes: Array.from(nodes.values()),
|
|
@@ -1163,7 +1198,8 @@ async function generateGlobalDependencyGraph(ctx) {
|
|
|
1163
1198
|
stats: {
|
|
1164
1199
|
filesRead: filesProcessed,
|
|
1165
1200
|
searchHits: 0,
|
|
1166
|
-
callEdges: 0,
|
|
1201
|
+
callEdges: 0, // deprecated
|
|
1202
|
+
referenceEdges: 0,
|
|
1167
1203
|
importEdges,
|
|
1168
1204
|
},
|
|
1169
1205
|
warnings,
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified response metadata for all Preflight MCP tools.
|
|
3
|
+
* Provides routing signals for LLM cost-minimization and self-healing.
|
|
4
|
+
*/
|
|
5
|
+
import crypto from 'node:crypto';
|
|
6
|
+
/**
|
|
7
|
+
* Generate a unique request ID.
|
|
8
|
+
*/
|
|
9
|
+
export function generateRequestId() {
|
|
10
|
+
return `req_${crypto.randomBytes(8).toString('hex')}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create a ResponseMeta builder for tracking execution.
|
|
14
|
+
*/
|
|
15
|
+
export function createMetaBuilder() {
|
|
16
|
+
const requestId = generateRequestId();
|
|
17
|
+
const startTime = Date.now();
|
|
18
|
+
const warnings = [];
|
|
19
|
+
const nextActions = [];
|
|
20
|
+
let truncated = false;
|
|
21
|
+
let truncatedReason;
|
|
22
|
+
let fromCache = false;
|
|
23
|
+
return {
|
|
24
|
+
requestId,
|
|
25
|
+
startTime,
|
|
26
|
+
warnings,
|
|
27
|
+
nextActions,
|
|
28
|
+
truncated,
|
|
29
|
+
truncatedReason,
|
|
30
|
+
fromCache,
|
|
31
|
+
addWarning(code, message, recoverable = true) {
|
|
32
|
+
warnings.push({ code, message, recoverable });
|
|
33
|
+
},
|
|
34
|
+
addNextAction(action) {
|
|
35
|
+
nextActions.push(action);
|
|
36
|
+
},
|
|
37
|
+
setTruncated(reason) {
|
|
38
|
+
truncated = true;
|
|
39
|
+
truncatedReason = reason;
|
|
40
|
+
},
|
|
41
|
+
setFromCache(cached) {
|
|
42
|
+
fromCache = cached;
|
|
43
|
+
},
|
|
44
|
+
build() {
|
|
45
|
+
const meta = {
|
|
46
|
+
requestId,
|
|
47
|
+
durationMs: Date.now() - startTime,
|
|
48
|
+
};
|
|
49
|
+
if (fromCache) {
|
|
50
|
+
meta.fromCache = true;
|
|
51
|
+
}
|
|
52
|
+
if (warnings.length > 0) {
|
|
53
|
+
meta.warnings = warnings;
|
|
54
|
+
}
|
|
55
|
+
if (truncated) {
|
|
56
|
+
meta.truncated = true;
|
|
57
|
+
meta.truncatedReason = truncatedReason;
|
|
58
|
+
}
|
|
59
|
+
if (nextActions.length > 0) {
|
|
60
|
+
meta.nextActions = nextActions;
|
|
61
|
+
}
|
|
62
|
+
return meta;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Common warning codes for standardized error handling.
|
|
68
|
+
*/
|
|
69
|
+
export const WarningCodes = {
|
|
70
|
+
/** Source ID format mismatch */
|
|
71
|
+
SOURCE_ID_MISMATCH: 'SOURCE_ID_MISMATCH',
|
|
72
|
+
/** No matching results found */
|
|
73
|
+
NO_MATCHES: 'NO_MATCHES',
|
|
74
|
+
/** Result truncated due to limits */
|
|
75
|
+
RESULT_TRUNCATED: 'RESULT_TRUNCATED',
|
|
76
|
+
/** Index not initialized */
|
|
77
|
+
INDEX_NOT_INITIALIZED: 'INDEX_NOT_INITIALIZED',
|
|
78
|
+
/** Evidence sources missing */
|
|
79
|
+
SOURCES_MISSING: 'SOURCES_MISSING',
|
|
80
|
+
/** Bundle not found */
|
|
81
|
+
BUNDLE_NOT_FOUND: 'BUNDLE_NOT_FOUND',
|
|
82
|
+
/** File not found in bundle */
|
|
83
|
+
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
|
|
84
|
+
/** Deprecated parameter used */
|
|
85
|
+
DEPRECATED_PARAM: 'DEPRECATED_PARAM',
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Helper to create "did you mean" suggestions for source_id mismatches.
|
|
89
|
+
*/
|
|
90
|
+
export function createDidYouMeanNextActions(bundleId, sourceType, candidates, originalSourceId) {
|
|
91
|
+
return candidates.slice(0, 5).map((candidate) => ({
|
|
92
|
+
toolName: 'preflight_trace_query',
|
|
93
|
+
paramsTemplate: {
|
|
94
|
+
bundleId,
|
|
95
|
+
source_type: sourceType,
|
|
96
|
+
source_id: candidate,
|
|
97
|
+
},
|
|
98
|
+
why: `Try "${candidate}" instead of "${originalSourceId}"`,
|
|
99
|
+
}));
|
|
100
|
+
}
|