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 CHANGED
@@ -3,47 +3,81 @@
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org/)
5
5
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io/)
6
+ [![npm version](https://img.shields.io/npm/v/preflight-mcp)](https://www.npmjs.com/package/preflight-mcp)
6
7
 
7
8
  > **English** | [中文](./README.zh-CN.md)
8
9
 
9
- An MCP (Model Context Protocol) **stdio** server that creates evidence-based preflight bundles for GitHub repositories and library documentation.
10
-
11
- Each bundle contains:
12
- - A local copy of repo docs + code (normalized text)
13
- - A lightweight **full-text search index** (SQLite FTS5)
14
- - Agent-facing entry files: `START_HERE.md`, `AGENTS.md`, and `OVERVIEW.md` (factual-only, with evidence pointers)
15
-
16
- ## Features
17
-
18
- - **15 MCP tools** to create/update/repair/search/read bundles, generate evidence graphs, and manage trace links
19
- - **5 MCP prompts** for interactive guidance: menu, analyze guide, search guide, manage guide, trace guide
20
- - **LLM-friendly outputs**: After bundle creation, prompts user to generate dependency graph for deeper analysis
21
- - **Proactive trace links**: LLM automatically discovers and records code↔test, code↔doc relationships
22
- - **Auto-export trace.json**: Trace links are automatically exported to JSON for direct LLM reading (no API needed)
23
- - **Progress tracking**: Real-time progress reporting for long-running operations (create/update bundles)
24
- - **Bundle integrity check**: Prevents operations on incomplete bundles with helpful error messages
25
- - **De-duplication with in-progress lock**: Prevent duplicate bundle creation even during MCP timeouts
26
- - **Global dependency graph**: Generate project-wide import relationship graphs
27
- - **Batch file reading**: Read all key bundle files in a single call
28
- - **Resilient GitHub fetching**: configurable git clone timeout + GitHub archive (zipball) fallback
29
- - **Offline repair**: rebuild missing/empty derived artifacts (index/guides/overview) without re-fetching
30
- - **Static facts extraction** via `analysis/FACTS.json` (non-LLM)
31
- - **Resources** to read bundle files via `preflight://...` URIs
32
- - **Multi-path mirror backup** for cloud storage redundancy
33
- - **Resilient storage** with automatic failover when mounts are unavailable
34
- - **Atomic bundle creation** with crash-safety and zero orphans
35
- - **Fast background deletion** with 100-300x performance improvement
36
- - **Auto-cleanup** on startup for historical orphan bundles
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
- - [Requirements](#requirements)
41
- - [Installation](#installation)
73
+ - [Why Preflight?](#why-preflight)
74
+ - [Demo](#demo)
75
+ - [Core Features](#core-features)
42
76
  - [Quick Start](#quick-start)
43
- - [Tools](#tools-12-total)
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 callers
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
- - Emits `imports` edges (file → module) and `imports_resolved` edges (file → internal file).
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
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
4
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org/)
5
5
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-blue)](https://modelcontextprotocol.io/)
6
+ [![npm version](https://img.shields.io/npm/v/preflight-mcp)](https://www.npmjs.com/package/preflight-mcp)
6
7
 
7
8
  > [English](./README.md) | **中文**
8
9
 
9
- 一个 MCP (Model Context Protocol) **stdio** 服务器,用于为 GitHub 仓库与库文档生成"基于证据"的 preflight bundles。
10
-
11
- 每个 bundle 包含:
12
- - 仓库文档 + 代码的本地副本(规范化文本)
13
- - 轻量级 **全文搜索索引**(SQLite FTS5)
14
- - 面向 Agent 的入口文件:`START_HERE.md`、`AGENTS.md`、`OVERVIEW.md`(仅事实,带证据指针)
15
-
16
- ## Features
17
-
18
- - **15 MCP 工具**:create/update/repair/search/evidence/trace/read/cleanup(外加 resources)
19
- - **5 MCP prompts**:交互式引导(菜单、分析指南、搜索指南、管理指南、追溯指南)
20
- - **去重**:避免对相同的规范化输入重复索引
21
- - **可靠的 GitHub 获取**:可配置 git clone 超时 + GitHub archive(zipball)兜底
22
- - **离线修复**:无需重新抓取,重建缺失/为空的派生物(index/guides/overview)
23
- - **静态事实提取**:生成 `analysis/FACTS.json`(非 LLM)
24
- - **Resources**:通过 `preflight://...` URI 读取 bundle 文件
25
- - **多路径镜像备份**:云存储冗余
26
- - **弹性存储**:挂载点不可用时自动故障转移
27
- - **原子创建 + 零孤儿**:临时目录 + 原子重命名,崩溃安全
28
- - **后台快速删除**:<100ms 响应,实际删除在后台进行
29
- - **启动自动清理**:历史孤儿目录自动清理(非阻塞)
30
-
31
- ## Table of Contents
32
-
33
- - [Requirements](#requirements)
34
- - [Installation](#installation)
35
- - [Quick Start](#quick-start)
36
- - [Architecture](#architecture)
37
- - [Tools](#tools-12-total)
38
- - [Environment Variables](#environment-variables)
39
- - [Contributing](#contributing)
40
- - [License](#license)
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 + callers)。
265
+ 生成目标文件/符号的「基于证据」的依赖图(imports + references)。
225
266
  - 输出确定性(best-effort),并为每条边提供可追溯 source range
226
267
  - `PREFLIGHT_AST_ENGINE=wasm` 时使用 Tree-sitter;否则回退到正则抽取
227
- - 既输出 `imports`(file → module),也会在可解析时输出 `imports_resolved`(file → file)
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 callers via FTS hits
796
+ // 2) Upstream: find references via FTS hits (only if edgeTypes='all')
781
797
  let searchHits = 0;
782
798
  let filesRead = 0;
783
- let callEdges = 0;
799
+ let referenceEdges = 0;
784
800
  let importEdges = edges.filter((e) => e.type === 'imports').length;
785
- if (targetSymbol && targetSymbol.length >= 2) {
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 caller scan'))
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 calls within the same file (but avoid exploding edges).
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(['calls', callerId, targetSymbolId, hitPath, String(hit.lineNo), String(call.startCol)]);
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: 'calls',
874
+ type: 'references',
853
875
  from: callerId,
854
876
  to: targetSymbolId,
855
877
  method: 'heuristic',
856
- confidence: 0.6,
878
+ confidence: 0.5,
857
879
  sources: [src],
858
- notes: ['call edge is name-based (no type/overload resolution)'],
880
+ notes: ['reference edge is FTS name-based (may include false positives from comments/strings/docs)'],
859
881
  });
860
- callEdges++;
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). Upstream call graph was skipped; only imports were extracted from the target file.',
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) plus heuristics for callers (FTS + name-based). Results may be incomplete and are not type-resolved. Each edge includes method/confidence/sources for auditability.'
885
- : 'This dependency graph is generated with deterministic heuristics (FTS + regex). Calls/imports may be incomplete and are not type-resolved. Each edge includes method/confidence/sources for auditability.',
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
+ }