preflight-mcp 0.7.2 → 0.7.4

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 (43) hide show
  1. package/README.md +9 -2
  2. package/README.zh-CN.md +1 -1
  3. package/dist/analysis/call-graph/adapters/base-adapter.js +193 -0
  4. package/dist/analysis/deep.js +4 -4
  5. package/dist/analysis/languages/go-analyzer.js +35 -15
  6. package/dist/analysis/pattern-analyzer.js +10 -0
  7. package/dist/analysis/scoring-config.js +118 -0
  8. package/dist/bundle/analysis-helpers.js +210 -0
  9. package/dist/bundle/cleanup.js +109 -3
  10. package/dist/bundle/dedup/fingerprint.js +57 -0
  11. package/dist/bundle/dedup/index.js +200 -0
  12. package/dist/bundle/deduplicator.js +318 -0
  13. package/dist/bundle/list.js +81 -0
  14. package/dist/bundle/manifest.js +129 -0
  15. package/dist/bundle/repo-ingest.js +210 -0
  16. package/dist/bundle/service.js +35 -997
  17. package/dist/bundle/storage.js +174 -0
  18. package/dist/bundle/utils.js +126 -0
  19. package/dist/bundle/validation.js +126 -0
  20. package/dist/config.js +8 -0
  21. package/dist/evidence/dependencyGraph.js +4 -4
  22. package/dist/logging/context.js +103 -0
  23. package/dist/logging/index.js +14 -0
  24. package/dist/logging/logger.js +66 -24
  25. package/dist/logging/types.js +30 -0
  26. package/dist/modal/processors/base-processor.js +2 -0
  27. package/dist/modal/utils/json-parser.js +1 -1
  28. package/dist/search/cache.js +274 -0
  29. package/dist/search/sqliteFts.js +48 -3
  30. package/dist/server/optimized-server.js +18 -2
  31. package/dist/server/tools/analysisTools.js +631 -0
  32. package/dist/server/tools/bundleTools.js +1404 -0
  33. package/dist/server/tools/callGraphTools.js +163 -0
  34. package/dist/server/tools/index.js +10 -0
  35. package/dist/server/tools/modalTools.js +91 -0
  36. package/dist/server/tools/searchTools.js +247 -0
  37. package/dist/server/tools/traceTools.js +231 -0
  38. package/dist/server/tools/types.js +5 -0
  39. package/dist/server.js +86 -2859
  40. package/dist/tools/analyzeModal.js +1 -1
  41. package/dist/tools/searchAndRead.js +52 -3
  42. package/dist/trace/suggest.js +341 -254
  43. package/package.json +6 -3
package/README.md CHANGED
@@ -86,7 +86,7 @@ Preflight: 📄 Parsed design-spec.pdf (45 pages)
86
86
  - 📖 **Auto-generated guides** — `START_HERE.md`, `AGENTS.md`, `OVERVIEW.md`
87
87
  - ☁️ **Cloud sync** — Multi-path mirror backup for redundancy
88
88
  - 🧠 **EDDA (Evidence-Driven Deep Analysis)** — Auto-generate auditable claims with evidence
89
- - ⚡ **21 MCP tools + 6 prompts** — Streamlined toolkit optimized for LLM use
89
+ - ⚡ **22 MCP tools + 6 prompts** — Streamlined toolkit optimized for LLM use
90
90
  - 🧠 **Intelligent routing** — Auto-suggest tools based on task
91
91
  - 🔗 **Call graph analysis** — Function-level dependency tracking (v0.7.2)
92
92
 
@@ -199,7 +199,7 @@ This will:
199
199
  "Search for architecture diagrams in the bundle"
200
200
  ```
201
201
 
202
- ## Tools (25 active)
202
+ ## Tools (22 active)
203
203
 
204
204
  ### Call Graph Tools (NEW v0.7.2)
205
205
 
@@ -426,6 +426,13 @@ Response includes:
426
426
  ### Analysis & Evidence
427
427
  - `PREFLIGHT_ANALYSIS_MODE`: `none` | `quick` | `full` (default: `full`)
428
428
  - `PREFLIGHT_AST_ENGINE`: `wasm` (default) or `native`
429
+ - `PREFLIGHT_DEEP_ANALYSIS_MAX_OVERVIEW_CHARS`: max chars for overview summary (default: 800)
430
+ - `PREFLIGHT_DEFAULT_SEARCH_CONTEXT_LINES`: context lines for search excerpts (default: 30)
431
+
432
+ ### Performance Tuning
433
+ - `PREFLIGHT_MANIFEST_CACHE_TTL_MS`: manifest cache TTL in ms (default: 300000 = 5 min)
434
+ - `PREFLIGHT_MANIFEST_CACHE_MAX_SIZE`: max manifest cache entries (default: 100)
435
+ - `PREFLIGHT_TASK_CLEANUP_DELAY_MS`: task cleanup delay after completion (default: 60000 = 1 min)
429
436
 
430
437
  ### Built-in HTTP API
431
438
  - `PREFLIGHT_HTTP_ENABLED`: enable/disable REST API (default: true)
package/README.zh-CN.md CHANGED
@@ -217,7 +217,7 @@ npm run smoke
217
217
  - 列表与清理逻辑只接受 UUID v4 作为 bundleId
218
218
  - 会自动过滤 `#recycle`、`tmp`、`.deleting` 等非 bundle 目录
219
219
 
220
- ## Tools (25 active)
220
+ ## Tools (22 active)
221
221
 
222
222
  ### 调用图工具 (v0.7.2 新增)
223
223
 
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Base Language Adapter for Call Graph Analysis
3
+ *
4
+ * Provides common functionality for all language adapters:
5
+ * - File and cache management
6
+ * - Common utility methods
7
+ * - Abstract method declarations for language-specific implementations
8
+ *
9
+ * This base class was extracted to reduce code duplication across adapters.
10
+ *
11
+ * @module analysis/call-graph/adapters/base-adapter
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ import { createNodeId, } from '../types.js';
16
+ // ============================================================================
17
+ // Base Adapter Abstract Class
18
+ // ============================================================================
19
+ /**
20
+ * Abstract base class for language-specific call graph adapters.
21
+ * Provides common caching and utility functionality.
22
+ */
23
+ export class BaseLanguageAdapter {
24
+ /** Project root path */
25
+ rootPath = '';
26
+ /** Cache for file contents */
27
+ fileCache = new Map();
28
+ /** Whether the adapter has been initialized */
29
+ initialized = false;
30
+ // ============================================================================
31
+ // Lifecycle Methods
32
+ // ============================================================================
33
+ /**
34
+ * Initialize the adapter with the project root path.
35
+ * Override in subclasses to perform language-specific initialization.
36
+ */
37
+ async initialize(rootPath) {
38
+ this.rootPath = rootPath;
39
+ this.initialized = true;
40
+ }
41
+ /**
42
+ * Shutdown the adapter and clean up resources.
43
+ * Override in subclasses to perform language-specific cleanup.
44
+ */
45
+ async shutdown() {
46
+ this.fileCache.clear();
47
+ this.initialized = false;
48
+ }
49
+ // ============================================================================
50
+ // File Support
51
+ // ============================================================================
52
+ /**
53
+ * Check if a file is supported by this adapter based on extension.
54
+ */
55
+ supportsFile(filePath) {
56
+ const ext = path.extname(filePath).toLowerCase();
57
+ return this.supportedExtensions.includes(ext);
58
+ }
59
+ // ============================================================================
60
+ // File Cache Management
61
+ // ============================================================================
62
+ /**
63
+ * Read file content with caching.
64
+ * Returns null if file cannot be read.
65
+ */
66
+ readFileCached(filePath) {
67
+ // Check cache first
68
+ const cached = this.fileCache.get(filePath);
69
+ if (cached !== undefined) {
70
+ return cached;
71
+ }
72
+ // Read from disk
73
+ try {
74
+ const content = fs.readFileSync(filePath, 'utf-8');
75
+ this.fileCache.set(filePath, content);
76
+ return content;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ }
82
+ /**
83
+ * Clear the file cache, optionally for a specific file.
84
+ */
85
+ clearCache(filePath) {
86
+ if (filePath) {
87
+ this.fileCache.delete(filePath);
88
+ }
89
+ else {
90
+ this.fileCache.clear();
91
+ }
92
+ }
93
+ /**
94
+ * Invalidate cache for files matching a pattern.
95
+ */
96
+ invalidateCacheMatching(pattern) {
97
+ let cleared = 0;
98
+ for (const key of this.fileCache.keys()) {
99
+ if (pattern.test(key)) {
100
+ this.fileCache.delete(key);
101
+ cleared++;
102
+ }
103
+ }
104
+ return cleared;
105
+ }
106
+ // ============================================================================
107
+ // Path Utilities
108
+ // ============================================================================
109
+ /**
110
+ * Convert absolute path to relative path from root.
111
+ */
112
+ toRelativePath(absolutePath) {
113
+ return path.relative(this.rootPath, absolutePath);
114
+ }
115
+ /**
116
+ * Convert relative path to absolute path from root.
117
+ */
118
+ toAbsolutePath(relativePath) {
119
+ if (path.isAbsolute(relativePath)) {
120
+ return relativePath;
121
+ }
122
+ return path.join(this.rootPath, relativePath);
123
+ }
124
+ /**
125
+ * Normalize path separators to forward slashes.
126
+ */
127
+ normalizePath(filePath) {
128
+ return filePath.replace(/\\/g, '/');
129
+ }
130
+ // ============================================================================
131
+ // Source Location Utilities
132
+ // ============================================================================
133
+ /**
134
+ * Create a source location object.
135
+ */
136
+ createLocation(filePath, line, column, endLine, endColumn) {
137
+ return {
138
+ filePath,
139
+ line,
140
+ column,
141
+ endLine,
142
+ endColumn,
143
+ };
144
+ }
145
+ /**
146
+ * Generate a unique node ID for a symbol.
147
+ */
148
+ generateNodeId(filePath, line, column, name) {
149
+ return createNodeId(filePath, line, column, name);
150
+ }
151
+ // ============================================================================
152
+ // Optional Override Points
153
+ // ============================================================================
154
+ /**
155
+ * Check if the adapter is ready to process requests.
156
+ * Override in subclasses that need async initialization.
157
+ */
158
+ isReady() {
159
+ return this.initialized;
160
+ }
161
+ /**
162
+ * Ensure the adapter is initialized before processing requests.
163
+ * Throws an error if not initialized.
164
+ */
165
+ ensureInitialized() {
166
+ if (!this.isReady()) {
167
+ throw new Error(`${this.language} adapter not initialized. Call initialize() first.`);
168
+ }
169
+ }
170
+ }
171
+ // ============================================================================
172
+ // Helper Exports
173
+ // ============================================================================
174
+ /**
175
+ * Common symbol kind mapping for languages that use similar conventions.
176
+ */
177
+ export const COMMON_SYMBOL_KINDS = {
178
+ function: 'function',
179
+ method: 'method',
180
+ constructor: 'constructor',
181
+ getter: 'getter',
182
+ setter: 'setter',
183
+ class: 'class',
184
+ interface: 'interface',
185
+ module: 'module',
186
+ enum: 'enum',
187
+ };
188
+ /**
189
+ * Check if a name follows common export conventions (starts with uppercase).
190
+ */
191
+ export function isExportedByConvention(name) {
192
+ return /^[A-Z]/.test(name);
193
+ }
@@ -7,8 +7,9 @@ import { createEmptyCoverageReport, isCoverageSufficient, } from '../types/evide
7
7
  * Build a deep analysis result from individual component results.
8
8
  * This is called by the server after gathering data from each source.
9
9
  */
10
- export function buildDeepAnalysis(bundleId, components) {
10
+ export function buildDeepAnalysis(bundleId, components, options) {
11
11
  const { tree, search, deps, traces, overviewContent, testInfo, focusPath, focusQuery, errors = [] } = components;
12
+ const maxOverviewChars = options?.maxOverviewChars ?? 800;
12
13
  // Build coverage report
13
14
  const coverageReport = createEmptyCoverageReport();
14
15
  if (tree) {
@@ -30,9 +31,8 @@ export function buildDeepAnalysis(bundleId, components) {
30
31
  const lines = overviewContent.overview.split('\n');
31
32
  const contentLines = [];
32
33
  let charCount = 0;
33
- const MAX_CHARS = 800; // Limit to ~200 tokens
34
34
  for (const line of lines) {
35
- if (charCount >= MAX_CHARS)
35
+ if (charCount >= maxOverviewChars)
36
36
  break;
37
37
  // Skip empty lines at start and title lines
38
38
  if (contentLines.length === 0 && (line.trim() === '' || line.startsWith('#')))
@@ -42,7 +42,7 @@ export function buildDeepAnalysis(bundleId, components) {
42
42
  }
43
43
  if (contentLines.length > 0) {
44
44
  summaryParts.push(contentLines.join('\n'));
45
- if (charCount >= MAX_CHARS) {
45
+ if (charCount >= maxOverviewChars) {
46
46
  summaryParts.push('...(truncated, see OVERVIEW.md for full content)');
47
47
  }
48
48
  }
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { createModuleLogger } from '../../logging/logger.js';
13
13
  import { extractExtensionPointsWasm, } from '../../ast/treeSitter.js';
14
+ import { INTERFACE_BASE_SCORE, INTERFACE_METHODS_HIGH_BONUS, INTERFACE_METHODS_LOW_BONUS, INTERFACE_EMPTY_SCORE, INTERFACE_EMBEDDING_BONUS, INTERFACE_HANDLER_BONUS, INTERFACE_PLUGIN_BONUS, CONSTRAINT_BASE_SCORE, CONSTRAINT_MEMBERS_HIGH_BONUS, CONSTRAINT_MEMBERS_LOW_BONUS, CONSTRAINT_HIGH_MEMBER_THRESHOLD, CONSTRAINT_LOW_MEMBER_THRESHOLD, MAX_SCORE, } from '../scoring-config.js';
14
15
  const logger = createModuleLogger('go-analyzer');
15
16
  // ============================================================================
16
17
  // Go Analyzer Class
@@ -24,6 +25,17 @@ export class GoAnalyzer {
24
25
  // Try tree-sitter first for accurate AST parsing
25
26
  const treeSitterResult = await extractExtensionPointsWasm(filePath, content);
26
27
  if (treeSitterResult && treeSitterResult.extensionPoints.length > 0) {
28
+ // Check if tree-sitter found methods in interfaces
29
+ const hasMethodsInInterfaces = treeSitterResult.extensionPoints.some(p => p.kind === 'interface' && p.methods && p.methods.length > 0);
30
+ // If tree-sitter found interfaces but no methods, fall back to regex
31
+ // as tree-sitter might have failed to parse the methods
32
+ if (!hasMethodsInInterfaces) {
33
+ const hasInterfaces = treeSitterResult.extensionPoints.some(p => p.kind === 'interface');
34
+ if (hasInterfaces) {
35
+ logger.debug('Tree-sitter found interfaces without methods, falling back to regex');
36
+ return this.analyzeContentWithRegex(content, filePath);
37
+ }
38
+ }
27
39
  return this.convertTreeSitterResult(treeSitterResult.extensionPoints, filePath);
28
40
  }
29
41
  // Fallback to regex-based parsing
@@ -434,37 +446,45 @@ export class GoAnalyzer {
434
446
  }
435
447
  return 'enum-options';
436
448
  }
449
+ /**
450
+ * Calculate extensibility score for a Go interface.
451
+ * See src/analysis/scoring-config.ts for scoring constant documentation.
452
+ */
437
453
  scoreInterface(iface) {
438
- let score = 60; // Base score for exported interfaces
454
+ let score = INTERFACE_BASE_SCORE;
439
455
  // More methods = more extensible
440
456
  if (iface.methods.length >= 3)
441
- score += 15;
457
+ score += INTERFACE_METHODS_HIGH_BONUS;
442
458
  else if (iface.methods.length >= 1)
443
- score += 10;
444
- // Empty interface (any) is very extensible
459
+ score += INTERFACE_METHODS_LOW_BONUS;
460
+ // Empty interface (any) is very extensible but too generic
445
461
  if (iface.methods.length === 0 && iface.embedded.length === 0) {
446
- score = 50; // Lower score - too generic
462
+ score = INTERFACE_EMPTY_SCORE;
447
463
  }
448
464
  // Embedding other interfaces suggests composition pattern
449
465
  if (iface.embedded.length > 0)
450
- score += 10;
466
+ score += INTERFACE_EMBEDDING_BONUS;
451
467
  // Common extension patterns
452
468
  const nameLower = iface.name.toLowerCase();
453
469
  if (nameLower.includes('handler') || nameLower.includes('processor')) {
454
- score += 15;
470
+ score += INTERFACE_HANDLER_BONUS;
455
471
  }
456
472
  if (nameLower.includes('plugin') || nameLower.includes('provider')) {
457
- score += 20;
473
+ score += INTERFACE_PLUGIN_BONUS;
458
474
  }
459
- return Math.min(score, 100);
475
+ return Math.min(score, MAX_SCORE);
460
476
  }
477
+ /**
478
+ * Calculate extensibility score for a Go type constraint.
479
+ * See src/analysis/scoring-config.ts for scoring constant documentation.
480
+ */
461
481
  scoreConstraint(memberCount) {
462
- let score = 50;
463
- if (memberCount >= 5)
464
- score += 20;
465
- else if (memberCount >= 3)
466
- score += 10;
467
- return Math.min(score, 100);
482
+ let score = CONSTRAINT_BASE_SCORE;
483
+ if (memberCount >= CONSTRAINT_HIGH_MEMBER_THRESHOLD)
484
+ score += CONSTRAINT_MEMBERS_HIGH_BONUS;
485
+ else if (memberCount >= CONSTRAINT_LOW_MEMBER_THRESHOLD)
486
+ score += CONSTRAINT_MEMBERS_LOW_BONUS;
487
+ return Math.min(score, MAX_SCORE);
468
488
  }
469
489
  }
470
490
  // ============================================================================
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import * as fs from 'node:fs/promises';
12
12
  import { createModuleLogger } from '../logging/logger.js';
13
+ import { getConfig } from '../config.js';
13
14
  const logger = createModuleLogger('pattern-analyzer');
14
15
  /**
15
16
  * Design hint patterns to detect.
@@ -70,8 +71,13 @@ const INTERFACE_EXTENSION_PATTERNS = [
70
71
  * Analyzer for design patterns and comments in source code.
71
72
  */
72
73
  export class PatternAnalyzer {
74
+ strictMode;
75
+ constructor(options) {
76
+ this.strictMode = options?.strictMode ?? getConfig().strictMode;
77
+ }
73
78
  /**
74
79
  * Analyze a file for design hints.
80
+ * In strict mode, errors are thrown instead of being logged and returning empty results.
75
81
  */
76
82
  async analyzeFile(filePath, content) {
77
83
  const results = [];
@@ -101,6 +107,10 @@ export class PatternAnalyzer {
101
107
  }
102
108
  catch (error) {
103
109
  logger.error(`Failed to analyze ${filePath}`, error instanceof Error ? error : undefined);
110
+ // In strict mode, propagate errors instead of returning empty results
111
+ if (this.strictMode) {
112
+ throw error;
113
+ }
104
114
  return [];
105
115
  }
106
116
  }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Scoring Configuration Constants
3
+ *
4
+ * This file contains all scoring-related constants used in static analysis.
5
+ * Centralizing these values makes them easier to tune and document.
6
+ *
7
+ * @module analysis/scoring-config
8
+ */
9
+ // ============================================================================
10
+ // Interface Scoring (Go Analyzer)
11
+ // ============================================================================
12
+ /**
13
+ * Base score for exported interfaces.
14
+ * Starting point before applying bonuses.
15
+ */
16
+ export const INTERFACE_BASE_SCORE = 60;
17
+ /**
18
+ * Score bonus for interfaces with 3+ methods.
19
+ * More methods typically indicate more extensible APIs.
20
+ */
21
+ export const INTERFACE_METHODS_HIGH_BONUS = 15;
22
+ /**
23
+ * Score bonus for interfaces with 1-2 methods.
24
+ * Moderate extensibility indication.
25
+ */
26
+ export const INTERFACE_METHODS_LOW_BONUS = 10;
27
+ /**
28
+ * Score for empty interfaces (any type).
29
+ * Lower than base because they're too generic.
30
+ */
31
+ export const INTERFACE_EMPTY_SCORE = 50;
32
+ /**
33
+ * Score bonus for interfaces embedding other interfaces.
34
+ * Suggests composition pattern usage.
35
+ */
36
+ export const INTERFACE_EMBEDDING_BONUS = 10;
37
+ /**
38
+ * Score bonus for handler/processor named interfaces.
39
+ * Common extensibility patterns.
40
+ */
41
+ export const INTERFACE_HANDLER_BONUS = 15;
42
+ /**
43
+ * Score bonus for plugin/provider named interfaces.
44
+ * Strong extensibility indication.
45
+ */
46
+ export const INTERFACE_PLUGIN_BONUS = 20;
47
+ // ============================================================================
48
+ // Type Constraint Scoring (Go Analyzer)
49
+ // ============================================================================
50
+ /**
51
+ * Base score for type constraints.
52
+ */
53
+ export const CONSTRAINT_BASE_SCORE = 50;
54
+ /**
55
+ * Score bonus for constraints with 5+ members.
56
+ * More members indicate more comprehensive type bounds.
57
+ */
58
+ export const CONSTRAINT_MEMBERS_HIGH_BONUS = 20;
59
+ /**
60
+ * Score bonus for constraints with 3-4 members.
61
+ */
62
+ export const CONSTRAINT_MEMBERS_LOW_BONUS = 10;
63
+ /**
64
+ * Member count threshold for high bonus.
65
+ */
66
+ export const CONSTRAINT_HIGH_MEMBER_THRESHOLD = 5;
67
+ /**
68
+ * Member count threshold for low bonus.
69
+ */
70
+ export const CONSTRAINT_LOW_MEMBER_THRESHOLD = 3;
71
+ // ============================================================================
72
+ // General Scoring Limits
73
+ // ============================================================================
74
+ /**
75
+ * Maximum possible score for any item.
76
+ */
77
+ export const MAX_SCORE = 100;
78
+ /**
79
+ * Minimum score threshold for including items in results.
80
+ */
81
+ export const MIN_SCORE_THRESHOLD = 0;
82
+ // ============================================================================
83
+ // TypeScript/JavaScript Scoring (for future use)
84
+ // ============================================================================
85
+ /**
86
+ * Base score for exported TypeScript interfaces.
87
+ */
88
+ export const TS_INTERFACE_BASE_SCORE = 60;
89
+ /**
90
+ * Base score for exported TypeScript types.
91
+ */
92
+ export const TS_TYPE_BASE_SCORE = 50;
93
+ /**
94
+ * Score bonus for discriminated unions.
95
+ */
96
+ export const TS_DISCRIMINATED_UNION_BONUS = 25;
97
+ // ============================================================================
98
+ // Python Scoring (for future use)
99
+ // ============================================================================
100
+ /**
101
+ * Base score for Python ABC classes.
102
+ */
103
+ export const PY_ABC_BASE_SCORE = 60;
104
+ /**
105
+ * Base score for Python Protocol classes.
106
+ */
107
+ export const PY_PROTOCOL_BASE_SCORE = 65;
108
+ // ============================================================================
109
+ // Rust Scoring (for future use)
110
+ // ============================================================================
111
+ /**
112
+ * Base score for Rust traits.
113
+ */
114
+ export const RUST_TRAIT_BASE_SCORE = 60;
115
+ /**
116
+ * Score bonus for traits with default implementations.
117
+ */
118
+ export const RUST_TRAIT_DEFAULT_IMPL_BONUS = 10;