preflight-mcp 0.1.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 ADDED
@@ -0,0 +1,208 @@
1
+ # preflight-mcp
2
+
3
+ > **English** | [中文](./README.zh-CN.md)
4
+
5
+ An MCP (Model Context Protocol) **stdio** server
6
+
7
+ Each bundle contains:
8
+ - A local copy of repo docs + code (normalized text)
9
+ - A lightweight **full-text search index** (SQLite FTS5)
10
+ - Agent-facing entry files: `START_HERE.md`, `AGENTS.md`, and `OVERVIEW.md` (factual-only, with evidence pointers)
11
+
12
+ ## What you get
13
+ - **10 Tools** to create/update/search/verify/read bundles
14
+ - **Static facts extraction** via `analysis/FACTS.json` (non-LLM)
15
+ - **Evidence-based verification** to reduce hallucinations
16
+ - **Resources** to read bundle files via `preflight://...` URIs
17
+ - **Multi-path mirror backup** for cloud storage redundancy
18
+ - **Resilient storage** with automatic failover when mounts are unavailable
19
+
20
+ ## Requirements
21
+ - Node.js >= 18
22
+ - `git` available on PATH
23
+
24
+ ## Install
25
+ Local dev:
26
+ - `npm install`
27
+ - `npm run build`
28
+
29
+ Publish install (after you publish to npm):
30
+ - `npm install -g preflight-mcp`
31
+
32
+ ## Use (stdio MCP server)
33
+ This server communicates over stdin/stdout, so you typically run it via an MCP host (e.g. mcp-hub).
34
+
35
+ Example MCP host command:
36
+ - `preflight-mcp`
37
+
38
+ Or, for local dev:
39
+ - `node dist/index.js`
40
+
41
+ ## Smoke test
42
+ Runs an end-to-end stdio client that:
43
+ - spawns the server
44
+ - calls `preflight_create_bundle`
45
+ - reads `preflight://bundles` and `START_HERE.md`
46
+ - searches the bundle
47
+ - calls `preflight_update_bundle`
48
+
49
+ Command:
50
+ - `npm run smoke`
51
+
52
+ Note: the smoke test clones `octocat/Hello-World` from GitHub, so it needs internet access.
53
+
54
+ ## Tools (10 total)
55
+
56
+ ### `preflight_list_bundles`
57
+ List bundle IDs in storage.
58
+ - Triggers: "show bundles", "查看bundle", "有哪些bundle"
59
+
60
+ ### `preflight_create_bundle`
61
+ Create a new bundle from one or more inputs.
62
+ - Triggers: "index this repo", "学习这个项目", "创建bundle"
63
+
64
+ Input (example):
65
+ - `repos`: `[{ kind: "github", repo: "owner/repo" }, { kind: "deepwiki", url: "https://deepwiki.com/owner/repo" }]`
66
+ - `libraries`: `["nextjs", "react"]` (Context7; optional)
67
+ - `topics`: `["routing", "api"]` (Context7 topic filter; optional)
68
+
69
+ ### `preflight_read_file`
70
+ Read a file from bundle (OVERVIEW.md, START_HERE.md, AGENTS.md, or any repo file).
71
+ - Triggers: "查看概览", "项目概览", "看README"
72
+
73
+ ### `preflight_bundle_info`
74
+ Get bundle details: repos, update time, stats.
75
+ - Triggers: "bundle详情", "仓库信息"
76
+
77
+ ### `preflight_delete_bundle`
78
+ Delete/remove a bundle permanently.
79
+ - Triggers: "删除bundle", "移除仓库"
80
+
81
+ ### `preflight_update_bundle`
82
+ Refresh/sync a bundle with latest repo changes.
83
+ - Triggers: "更新bundle", "同步仓库", "刷新索引"
84
+
85
+ Optional parameters:
86
+ - `checkOnly`: If true, only check for updates without applying.
87
+ - `force`: If true, force rebuild even if no changes detected.
88
+
89
+ ### `preflight_update_all_bundles`
90
+ Batch update all bundles at once.
91
+ - Triggers: "批量更新", "全部刷新"
92
+
93
+ ### `preflight_search_bundle`
94
+ Full-text search across ingested docs/code (line-based SQLite FTS5).
95
+ - Triggers: "搜索bundle", "在仓库中查找", "搜代码"
96
+
97
+ Optional parameters:
98
+ - `ensureFresh`: If true, check if bundle needs update before searching.
99
+ - `maxAgeHours`: Max age in hours before triggering auto-update (default: 24).
100
+
101
+ ### `preflight_search_by_tags`
102
+ Search across multiple bundles filtered by tags (line-based SQLite FTS5).
103
+ - Triggers: "search in MCP bundles", "search in all bundles", "在MCP项目中搜索", "搜索所有agent"
104
+
105
+ Optional parameters:
106
+ - `tags`: Filter bundles by tags (e.g., `["mcp", "agents"]`)
107
+ - `scope`: Search scope (`docs`, `code`, or `all`)
108
+ - `limit`: Max total hits across all bundles
109
+
110
+ ### `preflight_verify_claim`
111
+ Find evidence for a claim/statement in bundle.
112
+ - Triggers: "验证说法", "找证据", "这个对吗"
113
+
114
+ Optional parameters:
115
+ - `ensureFresh`: If true, check if bundle needs update before verifying.
116
+ - `maxAgeHours`: Max age in hours before triggering auto-update (default: 24).
117
+
118
+ ## Resources
119
+ ### `preflight://bundles`
120
+ Static JSON listing of bundles and their main entry files.
121
+
122
+ ### `preflight://bundle/{bundleId}/file/{encodedPath}`
123
+ Read a specific file inside a bundle.
124
+
125
+ Examples:
126
+ - `preflight://bundle/<id>/file/START_HERE.md`
127
+ - `preflight://bundle/<id>/file/repos%2Fowner%2Frepo%2Fnorm%2FREADME.md`
128
+
129
+ ## Environment variables
130
+ ### Storage
131
+ - `PREFLIGHT_STORAGE_DIR`: bundle storage dir (default: `~/.preflight-mcp/bundles`)
132
+ - `PREFLIGHT_STORAGE_DIRS`: **multi-path mirror backup** (semicolon-separated, e.g., `D:\cloud1\preflight;E:\cloud2\preflight`)
133
+ - `PREFLIGHT_TMP_DIR`: temp checkout dir (default: OS temp `preflight-mcp/`)
134
+ - `PREFLIGHT_MAX_FILE_BYTES`: max bytes per file (default: 512 KiB)
135
+ - `PREFLIGHT_MAX_TOTAL_BYTES`: max bytes per repo ingest (default: 50 MiB)
136
+
137
+ ### Analysis
138
+ - `PREFLIGHT_ANALYSIS_MODE`: Static analysis mode - `none` or `quick` (default: `quick`). Generates `analysis/FACTS.json`.
139
+
140
+ ### GitHub & Context7
141
+ - `GITHUB_TOKEN`: optional; used for GitHub API/auth patterns (currently not required for public repos)
142
+ - `CONTEXT7_API_KEY`: optional; enables higher Context7 limits (runs without a key but may be rate-limited)
143
+ - `CONTEXT7_MCP_URL`: optional; defaults to Context7 MCP endpoint
144
+
145
+ ## Bundle layout (on disk)
146
+ Inside a bundle directory:
147
+ - `manifest.json`
148
+ - `START_HERE.md`
149
+ - `AGENTS.md`
150
+ - `OVERVIEW.md`
151
+ - `indexes/search.sqlite3`
152
+ - **`analysis/FACTS.json`** (static analysis)
153
+ - `repos/<owner>/<repo>/raw/...`
154
+ - `repos/<owner>/<repo>/norm/...`
155
+ - `deepwiki/<owner>/<repo>/norm/index.md` (DeepWiki sources)
156
+ - `deepwiki/<owner>/<repo>/meta.json`
157
+ - `libraries/context7/<...>/meta.json`
158
+ - `libraries/context7/<...>/docs-page-1.md` (or `topic-<topic>-page-1.md`)
159
+
160
+ ## Multi-device sync & mirror backup
161
+
162
+ If you work from multiple computers or want redundant cloud backups:
163
+
164
+ ### Single path (simple)
165
+ ```powershell
166
+ # Windows
167
+ $env:PREFLIGHT_STORAGE_DIR = "D:\OneDrive\preflight-bundles"
168
+ ```
169
+ ```bash
170
+ # macOS/Linux
171
+ export PREFLIGHT_STORAGE_DIR="$HOME/Dropbox/preflight-bundles"
172
+ ```
173
+
174
+ ### Multi-path mirror (redundancy)
175
+ Writes to all paths, reads from first available:
176
+ ```powershell
177
+ # Windows - semicolon separated
178
+ $env:PREFLIGHT_STORAGE_DIRS = "D:\OneDrive\preflight;E:\GoogleDrive\preflight"
179
+ ```
180
+ ```bash
181
+ # macOS/Linux
182
+ export PREFLIGHT_STORAGE_DIRS="$HOME/OneDrive/preflight:$HOME/Dropbox/preflight"
183
+ ```
184
+
185
+ ### MCP host config (Claude Desktop)
186
+ ```json
187
+ {
188
+ "mcpServers": {
189
+ "preflight": {
190
+ "command": "node",
191
+ "args": ["path/to/preflight-mcp/dist/index.js"],
192
+ "env": {
193
+ "PREFLIGHT_STORAGE_DIRS": "D:\\cloud1\\preflight;E:\\cloud2\\preflight"
194
+ }
195
+ }
196
+ }
197
+ }
198
+ ```
199
+
200
+ ### Resilient storage features
201
+ - **Auto-failover**: If primary path is unavailable, automatically uses first available backup
202
+ - **Mirror sync**: All writes are mirrored to available backup paths
203
+ - **Mount recovery**: When a path comes back online, it syncs automatically on next write
204
+ - **Non-blocking**: Unavailable paths are skipped without errors
205
+
206
+ ### Important notes
207
+ - **Avoid concurrent access**: Only use on one machine at a time (SQLite conflicts)
208
+ - **Wait for sync**: After updates, wait for cloud sync before switching machines
@@ -0,0 +1,406 @@
1
+ # preflight-mcp
2
+
3
+ > [English](./README.md) | **中文**
4
+
5
+ 一个 MCP (Model Context Protocol) **stdio** 服务器
6
+
7
+ ## 📦 Bundle 包含内容
8
+
9
+ 每个 bundle 包含:
10
+ - 仓库文档 + 代码的本地副本(规范化文本)
11
+ - 轻量级**全文搜索索引**(SQLite FTS5)
12
+ - 面向 AI Agent 的入口文件:`START_HERE.md`、`AGENTS.md` 和 `OVERVIEW.md`(仅事实,带证据指针)
13
+
14
+ ## ✨ 功能特性
15
+
16
+ - **10 个工具** - 创建/更新/搜索/验证/读取 bundles
17
+ - **静态事实提取** - 生成 `analysis/FACTS.json`(非 LLM)
18
+ - **基于证据的校验** - 用证据定位来减少幻觉
19
+ - **资源访问** - 通过 `preflight://...` URI 读取 bundle 文件
20
+ - **多路径镜像备份** - 云存储冗余
21
+ - **弹性存储** - 挂载点不可用时自动故障转移
22
+ - **任务调度系统** - 自动化的 bundle 更新和存储清理
23
+ - **压缩系统** - 支持 Gzip、Brotli、Deflate
24
+ - **结构化日志** - 完整的日志记录和监控
25
+
26
+ ---
27
+
28
+ ## 🔧 系统要求
29
+
30
+ - Node.js >= 18
31
+ - `git` 命令可用(在 PATH 中)
32
+
33
+ ---
34
+
35
+ ## 📥 安装
36
+
37
+ ### 本地开发
38
+ ```bash
39
+ npm install
40
+ npm run build
41
+ ```
42
+
43
+ ### 全局安装(发布到 npm 后)
44
+ ```bash
45
+ npm install -g preflight-mcp
46
+ ```
47
+
48
+ ---
49
+
50
+ ## 🚀 使用方法
51
+
52
+ ### 作为 MCP 服务器运行
53
+ 此服务器通过 stdin/stdout 通信,通常通过 MCP 主机运行(如 mcp-hub)。
54
+
55
+ ```bash
56
+ # 直接运行
57
+ preflight-mcp
58
+
59
+ # 或本地开发
60
+ node dist/index.js
61
+ ```
62
+
63
+ ### 运行测试
64
+ ```bash
65
+ # 运行单元测试
66
+ npm test
67
+
68
+ # 运行 smoke 测试(端到端)
69
+ npm run smoke
70
+
71
+ # 类型检查
72
+ npm run typecheck
73
+ ```
74
+
75
+ > **注意**: smoke 测试需要从 GitHub 克隆 `octocat/Hello-World`,需要网络访问。
76
+
77
+ ---
78
+
79
+ ## 🛠️ 工具列表(共 10 个)
80
+
81
+ ### 1. `preflight_list_bundles`
82
+ 列出存储中的所有 bundle ID。
83
+
84
+ **触发词**: "show bundles"、"查看bundle"、"有哪些bundle"、"列出仓库"
85
+
86
+ ---
87
+
88
+ ### 2. `preflight_create_bundle`
89
+ 从一个或多个输入创建新的 bundle。
90
+
91
+ **触发词**: "index this repo"、"学习这个项目"、"创建bundle"、"添加GitHub项目"
92
+
93
+ **输入示例**:
94
+ ```json
95
+ {
96
+ "repos": [
97
+ { "kind": "github", "repo": "owner/repo" },
98
+ { "kind": "deepwiki", "url": "https://deepwiki.com/owner/repo" }
99
+ ],
100
+ "libraries": ["nextjs", "react"],
101
+ "topics": ["routing", "api"]
102
+ }
103
+ ```
104
+
105
+ ---
106
+
107
+ ### 3. `preflight_read_file`
108
+ 从 bundle 读取文件(OVERVIEW.md、START_HERE.md、AGENTS.md 或任何仓库文件)。
109
+
110
+ **触发词**: "查看概览"、"项目概览"、"看README"、"show overview"
111
+
112
+ ---
113
+
114
+ ### 4. `preflight_bundle_info`
115
+ 获取 bundle 详情:仓库、更新时间、统计信息。
116
+
117
+ **触发词**: "bundle详情"、"仓库信息"、"bundle info"
118
+
119
+ ---
120
+
121
+ ### 5. `preflight_delete_bundle`
122
+ 永久删除/移除一个 bundle。
123
+
124
+ **触发词**: "删除bundle"、"移除仓库"、"delete bundle"
125
+
126
+ ---
127
+
128
+ ### 6. `preflight_update_bundle`
129
+ 刷新/同步 bundle 与最新的仓库更改。
130
+
131
+ **触发词**: "更新bundle"、"同步仓库"、"刷新索引"
132
+
133
+ **可选参数**:
134
+ - `checkOnly`: 如果为 true,仅检查更新不应用
135
+ - `force`: 如果为 true,即使没有检测到更改也强制重建
136
+
137
+ ---
138
+
139
+ ### 7. `preflight_update_all_bundles`
140
+ 批量更新所有 bundles。
141
+
142
+ **触发词**: "批量更新"、"全部刷新"、"更新所有bundle"
143
+
144
+ ---
145
+
146
+ ### 8. `preflight_search_bundle`
147
+ 在已导入的文档/代码中进行全文搜索(基于行的 SQLite FTS5)。
148
+
149
+ **触发词**: "搜索bundle"、"在仓库中查找"、"搜代码"、"搜文档"
150
+
151
+ **可选参数**:
152
+ - `ensureFresh`: 如果为 true,搜索前检查 bundle 是否需要更新
153
+ - `maxAgeHours`: 触发自动更新前的最大小时数(默认: 24)
154
+
155
+ ---
156
+
157
+ ### 9. `preflight_search_by_tags`
158
+ 按标签筛选后跨多个 bundle 搜索(基于行的 SQLite FTS5)。
159
+
160
+ **触发词**: "在MCP项目中搜索"、"搜索所有agent"、"search in MCP bundles"
161
+
162
+ **可选参数**:
163
+ - `tags`: 标签过滤(例如 `["mcp", "agents"]`)
164
+ - `scope`: 搜索范围(`docs` / `code` / `all`)
165
+ - `limit`: 跨 bundle 的总命中数量上限
166
+
167
+ ---
168
+
169
+ ### 10. `preflight_verify_claim`
170
+ 在 bundle 中查找声明/陈述的证据。
171
+
172
+ **触发词**: "验证说法"、"找证据"、"这个对吗"、"有没有依据"
173
+
174
+ **可选参数**:
175
+ - `ensureFresh`: 如果为 true,验证前检查 bundle 是否需要更新
176
+ - `maxAgeHours`: 触发自动更新前的最大小时数(默认: 24)
177
+
178
+ ---
179
+
180
+ ## 📚 资源
181
+
182
+ ### `preflight://bundles`
183
+ bundles 及其主要入口文件的静态 JSON 列表。
184
+
185
+ ### `preflight://bundle/{bundleId}/file/{encodedPath}`
186
+ 读取 bundle 内的特定文件。
187
+
188
+ **示例**:
189
+ - `preflight://bundle/<id>/file/START_HERE.md`
190
+ - `preflight://bundle/<id>/file/repos%2Fowner%2Frepo%2Fnorm%2FREADME.md`
191
+
192
+ ---
193
+
194
+ ## ⚙️ 环境变量
195
+
196
+ ### 存储配置
197
+ | 变量 | 说明 | 默认值 |
198
+ |------|------|--------|
199
+ | `PREFLIGHT_STORAGE_DIR` | bundle 存储目录 | `~/.preflight-mcp/bundles` |
200
+ | `PREFLIGHT_STORAGE_DIRS` | 多路径镜像备份(分号分隔) | - |
201
+ | `PREFLIGHT_TMP_DIR` | 临时检出目录 | OS temp `preflight-mcp/` |
202
+ | `PREFLIGHT_MAX_FILE_BYTES` | 每个文件的最大字节数 | 512 KiB |
203
+ | `PREFLIGHT_MAX_TOTAL_BYTES` | 每个仓库导入的最大字节数 | 50 MiB |
204
+
205
+ ### 分析配置
206
+ || 变量 | 说明 | 默认值 |
207
+ ||------|------|--------|
208
+ || `PREFLIGHT_ANALYSIS_MODE` | 静态分析模式:`none`、`quick`(生成 `analysis/FACTS.json`) | `quick` |
209
+
210
+ ### GitHub & Context7
211
+ | 变量 | 说明 | 默认值 |
212
+ |------|------|--------|
213
+ | `GITHUB_TOKEN` | GitHub API 令牌(公开仓库不需要) | - |
214
+ | `CONTEXT7_API_KEY` | Context7 API 密钥 | - |
215
+ | `CONTEXT7_MCP_URL` | Context7 MCP 端点 | 默认端点 |
216
+
217
+ ---
218
+
219
+ ## 📁 Bundle 目录结构
220
+
221
+ ```
222
+ bundle-id/
223
+ ├── manifest.json # Bundle 元数据
224
+ ├── START_HERE.md # 入口指南
225
+ ├── AGENTS.md # Agent 指南
226
+ ├── OVERVIEW.md # 项目概览
227
+ ├── indexes/
228
+ │ └── search.sqlite3 # FTS5 搜索索引
229
+ ├── analysis/
230
+ │ ├── FACTS.json # 静态分析结果
231
+ ├── repos/
232
+ │ └── <owner>/<repo>/
233
+ │ ├── raw/... # 原始文件
234
+ │ └── norm/... # 规范化文件
235
+ ├── deepwiki/
236
+ │ └── <owner>/<repo>/
237
+ │ ├── norm/index.md
238
+ │ └── meta.json
239
+ └── libraries/
240
+ └── context7/
241
+ ├── meta.json
242
+ └── docs-page-1.md
243
+ ```
244
+
245
+ ---
246
+
247
+ ## 🔄 多设备同步与镜像备份
248
+
249
+ ### 单路径(简单)
250
+ ```powershell
251
+ # Windows
252
+ $env:PREFLIGHT_STORAGE_DIR = "D:\OneDrive\preflight-bundles"
253
+ ```
254
+
255
+ ```bash
256
+ # macOS/Linux
257
+ export PREFLIGHT_STORAGE_DIR="$HOME/Dropbox/preflight-bundles"
258
+ ```
259
+
260
+ ### 多路径镜像(冗余)
261
+ 写入所有路径,从第一个可用路径读取:
262
+
263
+ ```powershell
264
+ # Windows - 分号分隔
265
+ $env:PREFLIGHT_STORAGE_DIRS = "D:\OneDrive\preflight;E:\GoogleDrive\preflight"
266
+ ```
267
+
268
+ ```bash
269
+ # macOS/Linux
270
+ export PREFLIGHT_STORAGE_DIRS="$HOME/OneDrive/preflight:$HOME/Dropbox/preflight"
271
+ ```
272
+
273
+ ### MCP 主机配置(Claude Desktop)
274
+ ```json
275
+ {
276
+ "mcpServers": {
277
+ "preflight": {
278
+ "command": "node",
279
+ "args": ["path/to/preflight-mcp/dist/index.js"],
280
+ "env": {
281
+ "PREFLIGHT_STORAGE_DIRS": "D:\\cloud1\\preflight;E:\\cloud2\\preflight"
282
+ }
283
+ }
284
+ }
285
+ }
286
+ ```
287
+
288
+ ### 弹性存储特性
289
+ - **自动故障转移**: 主路径不可用时,自动使用第一个可用的备份
290
+ - **镜像同步**: 所有写入都镜像到可用的备份路径
291
+ - **挂载恢复**: 路径重新上线时,下次写入时自动同步
292
+ - **非阻塞**: 不可用的路径会被静默跳过
293
+
294
+ ### 重要说明
295
+ - **避免并发访问**: 同一时间只在一台机器上使用(避免 SQLite 冲突)
296
+ - **等待同步**: 更新后,在切换机器前等待云同步完成
297
+
298
+ ---
299
+
300
+ ## 🏗️ 项目架构
301
+
302
+ ```
303
+ src/
304
+ ├── index.ts # 入口点
305
+ ├── server.ts # MCP 服务器主文件
306
+ ├── config.ts # 配置管理
307
+ ├── core/
308
+ │ └── scheduler.ts # 任务调度系统
309
+ ├── jobs/
310
+ │ ├── bundle-auto-update-job.ts # 自动更新任务
311
+ │ ├── health-check-job.ts # 健康检查任务
312
+ │ └── storage-cleanup-job.ts # 存储清理任务
313
+ ├── storage/
314
+ │ ├── storage-adapter.ts # 存储抽象层
315
+ │ └── compression.ts # 压缩系统
316
+ ├── logging/
317
+ │ └── logger.ts # 结构化日志
318
+ ├── server/
319
+ │ └── optimized-server.ts # 优化服务器集成
320
+ ├── bundle/
321
+ │ ├── service.ts # Bundle 服务
322
+ │ ├── analysis.ts # 静态分析(FACTS.json)
323
+ │ ├── facts.ts # 事实提取
324
+ │ └── ... # 其他 bundle 相关模块
325
+ ├── search/
326
+ │ └── sqliteFts.ts # SQLite FTS5 搜索
327
+ └── mcp/
328
+ └── uris.ts # URI 处理
329
+ ```
330
+
331
+ ---
332
+
333
+ ## 🧪 测试
334
+
335
+ 项目包含完整的测试套件(28 个测试):
336
+
337
+ ```bash
338
+ # 运行所有测试
339
+ npm test
340
+
341
+ # 测试覆盖范围:
342
+ # - 调度器系统 (3 tests)
343
+ # - Bundle 自动更新任务 (2 tests)
344
+ # - 存储清理任务 (2 tests)
345
+ # - 健康检查任务 (2 tests)
346
+ # - 存储适配器系统 (4 tests)
347
+ # - 压缩系统 (5 tests)
348
+ # - 日志系统 (3 tests)
349
+ # - 优化服务器集成 (4 tests)
350
+ # - 性能基准测试 (2 tests)
351
+ # - 集成测试 (1 test)
352
+ ```
353
+
354
+ ---
355
+
356
+ ## 📊 本次更新内容
357
+
358
+ ### 新增功能
359
+ 1. **任务调度系统** - 基于 node-cron 的自动化任务调度
360
+ 2. **自动化任务**:
361
+ - Bundle 自动更新(每小时检查)
362
+ - 存储清理(每天凌晨 2 点)
363
+ - 健康检查(每 30 分钟)
364
+ 3. **存储抽象层** - 支持本地和 S3 存储
365
+ 4. **压缩系统** - 支持 Gzip、Brotli、Deflate
366
+ 5. **结构化日志** - 多级别、文件轮转、彩色输出
367
+ 6. **优化服务器** - 统一管理接口
368
+ 7. **完整测试套件** - 28 个 Jest 测试
369
+
370
+ ### 修复问题
371
+ - ESM 模块兼容性问题
372
+ - TypeScript 类型错误
373
+ - 存储适配器 require 改为 import
374
+ - Logger mtime Promise 处理
375
+ - 错误类型转换
376
+
377
+ ### 依赖更新
378
+ - 新增: `node-cron`, `@types/node-cron`
379
+ - 新增开发依赖: `jest`, `ts-jest`, `@jest/globals`, `@types/jest`
380
+
381
+ ---
382
+
383
+ ## 📝 开发命令
384
+
385
+ ```bash
386
+ # 开发模式(热重载)
387
+ npm run dev
388
+
389
+ # 构建
390
+ npm run build
391
+
392
+ # 类型检查
393
+ npm run typecheck
394
+
395
+ # 运行测试
396
+ npm test
397
+
398
+ # Smoke 测试
399
+ npm run smoke
400
+ ```
401
+
402
+ ---
403
+
404
+ ## 📄 许可证
405
+
406
+ MIT
@@ -0,0 +1,91 @@
1
+ import path from 'node:path';
2
+ import { extractBundleFacts, writeFacts } from './facts.js';
3
+ import { logger } from '../logging/logger.js';
4
+ /**
5
+ * Run static analysis on a bundle
6
+ */
7
+ export async function analyzeBundleStatic(params) {
8
+ if (params.mode === 'none') {
9
+ return {};
10
+ }
11
+ try {
12
+ const facts = await extractBundleFacts({
13
+ bundleRoot: params.bundleRoot,
14
+ repos: params.repos,
15
+ });
16
+ const factsPath = path.join(params.bundleRoot, 'analysis', 'FACTS.json');
17
+ await writeFacts(factsPath, facts);
18
+ return { facts };
19
+ }
20
+ catch (err) {
21
+ logger.error('Analysis failed', err instanceof Error ? err : undefined);
22
+ return {
23
+ error: err instanceof Error ? err.message : String(err),
24
+ };
25
+ }
26
+ }
27
+ /**
28
+ * Generate a quick text summary from facts
29
+ */
30
+ export function generateQuickSummary(facts) {
31
+ const sections = [];
32
+ sections.push('# Quick Analysis Summary\n');
33
+ sections.push(`Generated: ${facts.timestamp}\n`);
34
+ // Languages
35
+ if (facts.languages.length > 0) {
36
+ sections.push('## Languages');
37
+ for (const lang of facts.languages) {
38
+ sections.push(`- ${lang.language}: ${lang.fileCount} files (${lang.extensions.join(', ')})`);
39
+ }
40
+ sections.push('');
41
+ }
42
+ // Frameworks
43
+ if (facts.frameworks.length > 0) {
44
+ sections.push('## Detected Frameworks');
45
+ sections.push(facts.frameworks.map((f) => `- ${f}`).join('\n'));
46
+ sections.push('');
47
+ }
48
+ // Entry points
49
+ if (facts.entryPoints.length > 0) {
50
+ sections.push('## Entry Points');
51
+ for (const ep of facts.entryPoints) {
52
+ sections.push(`- [${ep.type}] ${ep.file}`);
53
+ sections.push(` Evidence: ${ep.evidence}`);
54
+ }
55
+ sections.push('');
56
+ }
57
+ // Dependencies
58
+ if (facts.dependencies.runtime.length > 0 || facts.dependencies.dev.length > 0) {
59
+ sections.push(`## Dependencies (${facts.dependencies.manager})`);
60
+ if (facts.dependencies.runtime.length > 0) {
61
+ sections.push(`### Runtime (${facts.dependencies.runtime.length})`);
62
+ for (const dep of facts.dependencies.runtime.slice(0, 20)) {
63
+ sections.push(`- ${dep.name}${dep.version ? ` ${dep.version}` : ''}`);
64
+ }
65
+ if (facts.dependencies.runtime.length > 20) {
66
+ sections.push(`- ... and ${facts.dependencies.runtime.length - 20} more`);
67
+ }
68
+ }
69
+ if (facts.dependencies.dev.length > 0) {
70
+ sections.push(`### Development (${facts.dependencies.dev.length})`);
71
+ for (const dep of facts.dependencies.dev.slice(0, 10)) {
72
+ sections.push(`- ${dep.name}${dep.version ? ` ${dep.version}` : ''}`);
73
+ }
74
+ if (facts.dependencies.dev.length > 10) {
75
+ sections.push(`- ... and ${facts.dependencies.dev.length - 10} more`);
76
+ }
77
+ }
78
+ sections.push('');
79
+ }
80
+ // File structure
81
+ sections.push('## File Structure');
82
+ sections.push(`- Total files: ${facts.fileStructure.totalFiles}`);
83
+ sections.push(`- Documentation: ${facts.fileStructure.totalDocs}`);
84
+ sections.push(`- Code: ${facts.fileStructure.totalCode}`);
85
+ sections.push(`- Has tests: ${facts.fileStructure.hasTests ? 'Yes' : 'No'}`);
86
+ sections.push(`- Has config files: ${facts.fileStructure.hasConfig ? 'Yes' : 'No'}`);
87
+ if (facts.fileStructure.topLevelDirs.length > 0) {
88
+ sections.push(`- Top-level directories: ${facts.fileStructure.topLevelDirs.join(', ')}`);
89
+ }
90
+ return sections.join('\n') + '\n';
91
+ }