project-graph-mcp 1.5.0 → 2.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 +171 -31
- package/docs/img/explorer-compact.jpg +0 -0
- package/docs/img/explorer-expanded.jpg +0 -0
- package/package.json +12 -8
- package/src/.project-graph-cache.json +1 -1
- package/src/analysis/analysis-cache.js +7 -0
- package/src/analysis/complexity.js +14 -0
- package/src/analysis/custom-rules.js +36 -0
- package/src/analysis/db-analysis.js +9 -0
- package/src/analysis/dead-code.js +19 -0
- package/src/analysis/full-analysis.js +18 -0
- package/src/analysis/jsdoc-checker.js +24 -0
- package/src/analysis/jsdoc-generator.js +10 -0
- package/src/analysis/large-files.js +11 -0
- package/src/analysis/outdated-patterns.js +12 -0
- package/src/analysis/similar-functions.js +16 -0
- package/src/analysis/test-annotations.js +21 -0
- package/src/analysis/type-checker.js +8 -0
- package/src/analysis/undocumented.js +14 -0
- package/src/cli/cli-handlers.js +4 -0
- package/src/cli/cli.js +5 -0
- package/src/compact/.project-graph-cache.json +1 -0
- package/src/compact/ai-context.js +7 -0
- package/src/compact/compact-migrate.js +17 -0
- package/src/compact/compact.js +18 -0
- package/src/compact/compress.js +14 -0
- package/src/compact/ctx-to-jsdoc.js +29 -0
- package/src/compact/doc-dialect.js +30 -0
- package/src/compact/expand.js +37 -0
- package/src/compact/framework-references.js +5 -0
- package/src/compact/instructions.js +3 -0
- package/src/compact/mode-config.js +8 -0
- package/src/compact/validate-pipeline.js +9 -0
- package/src/core/event-bus.js +9 -0
- package/src/core/filters.js +14 -0
- package/src/core/graph-builder.js +12 -0
- package/src/core/parser.js +31 -0
- package/src/core/workspace.js +8 -0
- package/src/lang/lang-go.js +17 -0
- package/src/lang/lang-python.js +12 -0
- package/src/lang/lang-sql.js +23 -0
- package/src/lang/lang-typescript.js +9 -0
- package/src/lang/lang-utils.js +4 -0
- package/src/mcp/mcp-server.js +17 -0
- package/src/mcp/tool-defs.js +3 -0
- package/src/mcp/tools.js +25 -0
- package/src/network/backend-lifecycle.js +19 -0
- package/src/network/backend.js +5 -0
- package/src/network/local-gateway.js +23 -0
- package/src/network/mdns.js +13 -0
- package/src/network/server.js +10 -0
- package/src/network/web-server.js +34 -0
- package/web/.project-graph-cache.json +1 -0
- package/web/app.js +17 -0
- package/web/components/code-block.js +3 -0
- package/web/components/quick-open.js +5 -0
- package/web/dashboard-state.js +3 -0
- package/web/dashboard.html +27 -0
- package/web/dashboard.js +8 -0
- package/web/highlight.js +13 -0
- package/web/index.html +35 -0
- package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.js +4 -0
- package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
- package/web/panels/EventItem/EventItem.css.js +1 -0
- package/web/panels/EventItem/EventItem.js +4 -0
- package/web/panels/EventItem/EventItem.tpl.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.js +5 -0
- package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
- package/web/panels/ProjectList/ProjectList.css.js +1 -0
- package/web/panels/ProjectList/ProjectList.js +4 -0
- package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
- package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
- package/web/panels/code-viewer.js +5 -0
- package/web/panels/ctx-panel.js +4 -0
- package/web/panels/dep-graph.js +6 -0
- package/web/panels/file-tree.js +188 -0
- package/web/panels/health-panel.js +3 -0
- package/web/panels/live-monitor.js +3 -0
- package/web/state.js +17 -0
- package/web/style.css +157 -0
- package/references/symbiote-3x.md +0 -834
- package/src/ai-context.js +0 -113
- package/src/analysis-cache.js +0 -155
- package/src/cli-handlers.js +0 -271
- package/src/cli.js +0 -95
- package/src/compact.js +0 -207
- package/src/complexity.js +0 -237
- package/src/compress.js +0 -319
- package/src/ctx-to-jsdoc.js +0 -514
- package/src/custom-rules.js +0 -584
- package/src/db-analysis.js +0 -194
- package/src/dead-code.js +0 -468
- package/src/doc-dialect.js +0 -716
- package/src/filters.js +0 -227
- package/src/framework-references.js +0 -177
- package/src/full-analysis.js +0 -470
- package/src/graph-builder.js +0 -299
- package/src/instructions.js +0 -73
- package/src/jsdoc-checker.js +0 -351
- package/src/jsdoc-generator.js +0 -203
- package/src/lang-go.js +0 -285
- package/src/lang-python.js +0 -197
- package/src/lang-sql.js +0 -309
- package/src/lang-typescript.js +0 -190
- package/src/lang-utils.js +0 -124
- package/src/large-files.js +0 -163
- package/src/mcp-server.js +0 -675
- package/src/mode-config.js +0 -127
- package/src/outdated-patterns.js +0 -296
- package/src/parser.js +0 -662
- package/src/server.js +0 -28
- package/src/similar-functions.js +0 -279
- package/src/test-annotations.js +0 -323
- package/src/tool-defs.js +0 -793
- package/src/tools.js +0 -470
- package/src/type-checker.js +0 -188
- package/src/undocumented.js +0 -259
- package/src/workspace.js +0 -70
- /package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +0 -0
- /package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +0 -0
package/src/filters.js
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Filter Configuration for Project Graph
|
|
3
|
-
* Manages excludes, includes, and gitignore parsing
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync, existsSync } from 'fs';
|
|
7
|
-
import { join, relative } from 'path';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Default directories to exclude
|
|
11
|
-
*/
|
|
12
|
-
const DEFAULT_EXCLUDES = [
|
|
13
|
-
'node_modules',
|
|
14
|
-
'dist',
|
|
15
|
-
'build',
|
|
16
|
-
'coverage',
|
|
17
|
-
'.next',
|
|
18
|
-
'.nuxt',
|
|
19
|
-
'.output',
|
|
20
|
-
'__pycache__',
|
|
21
|
-
'.cache',
|
|
22
|
-
'.turbo',
|
|
23
|
-
'out',
|
|
24
|
-
];
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Default file patterns to exclude
|
|
28
|
-
*/
|
|
29
|
-
const DEFAULT_EXCLUDE_PATTERNS = [
|
|
30
|
-
'*.test.js',
|
|
31
|
-
'*.spec.js',
|
|
32
|
-
'*.min.js',
|
|
33
|
-
'*.bundle.js',
|
|
34
|
-
'*.d.ts',
|
|
35
|
-
'.project-graph-cache.json',
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// Current filter configuration (mutable via MCP)
|
|
39
|
-
let config = {
|
|
40
|
-
excludeDirs: [...DEFAULT_EXCLUDES],
|
|
41
|
-
excludePatterns: [...DEFAULT_EXCLUDE_PATTERNS],
|
|
42
|
-
includeHidden: false,
|
|
43
|
-
useGitignore: true,
|
|
44
|
-
gitignorePatterns: [],
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Get current filter configuration
|
|
49
|
-
* @returns {Object}
|
|
50
|
-
*/
|
|
51
|
-
export function getFilters() {
|
|
52
|
-
return { ...config };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Update filter configuration
|
|
57
|
-
* @param {Object} updates
|
|
58
|
-
* @returns {Object}
|
|
59
|
-
*/
|
|
60
|
-
export function setFilters(updates) {
|
|
61
|
-
if (updates.excludeDirs !== undefined) {
|
|
62
|
-
config.excludeDirs = updates.excludeDirs;
|
|
63
|
-
}
|
|
64
|
-
if (updates.excludePatterns !== undefined) {
|
|
65
|
-
config.excludePatterns = updates.excludePatterns;
|
|
66
|
-
}
|
|
67
|
-
if (updates.includeHidden !== undefined) {
|
|
68
|
-
config.includeHidden = updates.includeHidden;
|
|
69
|
-
}
|
|
70
|
-
if (updates.useGitignore !== undefined) {
|
|
71
|
-
config.useGitignore = updates.useGitignore;
|
|
72
|
-
}
|
|
73
|
-
return getFilters();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Add directories to exclude list
|
|
78
|
-
* @param {string[]} dirs
|
|
79
|
-
* @returns {Object}
|
|
80
|
-
*/
|
|
81
|
-
export function addExcludes(dirs) {
|
|
82
|
-
config.excludeDirs = [...new Set([...config.excludeDirs, ...dirs])];
|
|
83
|
-
return getFilters();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Remove directories from exclude list
|
|
88
|
-
* @param {string[]} dirs
|
|
89
|
-
* @returns {Object}
|
|
90
|
-
*/
|
|
91
|
-
export function removeExcludes(dirs) {
|
|
92
|
-
config.excludeDirs = config.excludeDirs.filter(d => !dirs.includes(d));
|
|
93
|
-
return getFilters();
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Reset filters to defaults
|
|
98
|
-
* @returns {Object}
|
|
99
|
-
*/
|
|
100
|
-
export function resetFilters() {
|
|
101
|
-
config = {
|
|
102
|
-
excludeDirs: [...DEFAULT_EXCLUDES],
|
|
103
|
-
excludePatterns: [...DEFAULT_EXCLUDE_PATTERNS],
|
|
104
|
-
includeHidden: false,
|
|
105
|
-
useGitignore: true,
|
|
106
|
-
gitignorePatterns: [],
|
|
107
|
-
};
|
|
108
|
-
return getFilters();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Parse .gitignore file
|
|
113
|
-
* @param {string} rootDir
|
|
114
|
-
* @returns {string[]}
|
|
115
|
-
*/
|
|
116
|
-
export function parseGitignore(rootDir) {
|
|
117
|
-
const gitignorePath = join(rootDir, '.gitignore');
|
|
118
|
-
|
|
119
|
-
if (!existsSync(gitignorePath)) {
|
|
120
|
-
return [];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const content = readFileSync(gitignorePath, 'utf-8');
|
|
125
|
-
const patterns = content
|
|
126
|
-
.split('\n')
|
|
127
|
-
.map(line => line.trim())
|
|
128
|
-
.filter(line => line && !line.startsWith('#'))
|
|
129
|
-
.map(line => line.replace(/\/$/, '')); // Remove trailing slashes
|
|
130
|
-
|
|
131
|
-
config.gitignorePatterns = patterns;
|
|
132
|
-
return patterns;
|
|
133
|
-
} catch (e) {
|
|
134
|
-
return [];
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Check if a directory should be excluded
|
|
140
|
-
* @param {string} dirName - Directory name (not path)
|
|
141
|
-
* @param {string} relativePath - Relative path from root
|
|
142
|
-
* @returns {boolean}
|
|
143
|
-
*/
|
|
144
|
-
export function shouldExcludeDir(dirName, relativePath = '') {
|
|
145
|
-
// Check hidden directories
|
|
146
|
-
if (!config.includeHidden && dirName.startsWith('.')) {
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Check default excludes
|
|
151
|
-
if (config.excludeDirs.includes(dirName)) {
|
|
152
|
-
return true;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Check gitignore patterns
|
|
156
|
-
if (config.useGitignore) {
|
|
157
|
-
for (const pattern of config.gitignorePatterns) {
|
|
158
|
-
if (matchGitignorePattern(pattern, dirName, relativePath)) {
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Check if a file should be excluded
|
|
169
|
-
* @param {string} fileName
|
|
170
|
-
* @param {string} relativePath
|
|
171
|
-
* @returns {boolean}
|
|
172
|
-
*/
|
|
173
|
-
export function shouldExcludeFile(fileName, relativePath = '') {
|
|
174
|
-
// Check exclude patterns
|
|
175
|
-
for (const pattern of config.excludePatterns) {
|
|
176
|
-
if (matchWildcard(pattern, fileName)) {
|
|
177
|
-
return true;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Check gitignore patterns
|
|
182
|
-
if (config.useGitignore) {
|
|
183
|
-
for (const pattern of config.gitignorePatterns) {
|
|
184
|
-
if (matchGitignorePattern(pattern, fileName, relativePath)) {
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
/**
|
|
194
|
-
* Match simple wildcard pattern (*.js, *.test.js)
|
|
195
|
-
* @param {string} pattern
|
|
196
|
-
* @param {string} str
|
|
197
|
-
* @returns {boolean}
|
|
198
|
-
*/
|
|
199
|
-
function matchWildcard(pattern, str) {
|
|
200
|
-
const regex = pattern
|
|
201
|
-
.replace(/\./g, '\\.')
|
|
202
|
-
.replace(/\*/g, '.*');
|
|
203
|
-
return new RegExp(`^${regex}$`).test(str);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Match gitignore pattern
|
|
208
|
-
* @param {string} pattern
|
|
209
|
-
* @param {string} name
|
|
210
|
-
* @param {string} relativePath
|
|
211
|
-
* @returns {boolean}
|
|
212
|
-
*/
|
|
213
|
-
function matchGitignorePattern(pattern, name, relativePath) {
|
|
214
|
-
// Simple matching: exact name or wildcard
|
|
215
|
-
if (pattern === name) return true;
|
|
216
|
-
|
|
217
|
-
// Pattern with wildcard
|
|
218
|
-
if (pattern.includes('*')) {
|
|
219
|
-
return matchWildcard(pattern, name);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Pattern in path
|
|
223
|
-
const fullPath = relativePath ? `${relativePath}/${name}` : name;
|
|
224
|
-
if (fullPath.includes(pattern)) return true;
|
|
225
|
-
|
|
226
|
-
return false;
|
|
227
|
-
}
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Framework Reference System
|
|
3
|
-
* Loads framework-specific AI references from GitHub (with caching) or local files
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync, readdirSync, existsSync, writeFileSync } from 'fs';
|
|
7
|
-
import { join, basename, dirname } from 'path';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { detectProjectRuleSets } from './custom-rules.js';
|
|
10
|
-
|
|
11
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const REFERENCES_DIR = join(__dirname, '..', 'references');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Remote sources for framework references
|
|
16
|
-
* Maps reference name to raw GitHub URL
|
|
17
|
-
*/
|
|
18
|
-
const REMOTE_SOURCES = {
|
|
19
|
-
'symbiote-3x': 'https://raw.githubusercontent.com/symbiotejs/symbiote.js/main/AI_REFERENCE.md',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/** @type {Map<string, {content: string, fetchedAt: number}>} */
|
|
23
|
-
const cache = new Map();
|
|
24
|
-
|
|
25
|
-
/** Cache TTL: 1 hour */
|
|
26
|
-
const CACHE_TTL = 60 * 60 * 1000;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Fetch reference from GitHub with caching
|
|
30
|
-
* Falls back to local file if fetch fails
|
|
31
|
-
* @param {string} name - Reference name
|
|
32
|
-
* @returns {Promise<{content: string, source: string}>}
|
|
33
|
-
*/
|
|
34
|
-
async function fetchReference(name) {
|
|
35
|
-
const url = REMOTE_SOURCES[name];
|
|
36
|
-
const localPath = join(REFERENCES_DIR, `${name}.md`);
|
|
37
|
-
|
|
38
|
-
// Check in-memory cache
|
|
39
|
-
const cached = cache.get(name);
|
|
40
|
-
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
|
|
41
|
-
return { content: cached.content, source: 'cache' };
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Try fetching from GitHub
|
|
45
|
-
if (url) {
|
|
46
|
-
try {
|
|
47
|
-
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
|
48
|
-
if (response.ok) {
|
|
49
|
-
const content = await response.text();
|
|
50
|
-
cache.set(name, { content, fetchedAt: Date.now() });
|
|
51
|
-
|
|
52
|
-
// Update local file as backup
|
|
53
|
-
try {
|
|
54
|
-
writeFileSync(localPath, content, 'utf-8');
|
|
55
|
-
} catch (e) {
|
|
56
|
-
// Write failure is non-critical
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return { content, source: `github (${url})` };
|
|
60
|
-
}
|
|
61
|
-
} catch (e) {
|
|
62
|
-
// Fetch failed — fall back to local
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Fall back to local file
|
|
67
|
-
if (existsSync(localPath)) {
|
|
68
|
-
const content = readFileSync(localPath, 'utf-8');
|
|
69
|
-
cache.set(name, { content, fetchedAt: Date.now() });
|
|
70
|
-
return { content, source: 'local' };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return { content: '', source: 'not_found' };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Map ruleset names to reference names
|
|
78
|
-
*/
|
|
79
|
-
const RULESET_TO_REFERENCE = {
|
|
80
|
-
'symbiote-3x': 'symbiote-3x',
|
|
81
|
-
'symbiote-2x': 'symbiote-3x',
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* List available framework references (local + remote)
|
|
86
|
-
* @returns {string[]}
|
|
87
|
-
*/
|
|
88
|
-
function listAvailable() {
|
|
89
|
-
const names = new Set(Object.keys(REMOTE_SOURCES));
|
|
90
|
-
|
|
91
|
-
if (existsSync(REFERENCES_DIR)) {
|
|
92
|
-
for (const f of readdirSync(REFERENCES_DIR)) {
|
|
93
|
-
if (f.endsWith('.md')) {
|
|
94
|
-
names.add(basename(f, '.md'));
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return [...names];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Get framework reference content
|
|
104
|
-
* @param {Object} options
|
|
105
|
-
* @param {string} [options.framework] - Explicit framework name
|
|
106
|
-
* @param {string} [options.path] - Project path for auto-detection
|
|
107
|
-
* @returns {Promise<Object>}
|
|
108
|
-
*/
|
|
109
|
-
export async function getFrameworkReference(options = {}) {
|
|
110
|
-
const available = listAvailable();
|
|
111
|
-
|
|
112
|
-
// Explicit framework requested
|
|
113
|
-
if (options.framework) {
|
|
114
|
-
if (!available.includes(options.framework)) {
|
|
115
|
-
return {
|
|
116
|
-
error: `Framework reference '${options.framework}' not found`,
|
|
117
|
-
available,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const { content, source } = await fetchReference(options.framework);
|
|
122
|
-
if (!content) {
|
|
123
|
-
return { error: `Failed to load reference '${options.framework}'`, available };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return {
|
|
127
|
-
framework: options.framework,
|
|
128
|
-
source,
|
|
129
|
-
lines: content.split('\n').length,
|
|
130
|
-
content,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Auto-detect from project path
|
|
135
|
-
if (options.path) {
|
|
136
|
-
const { detected, reasons } = detectProjectRuleSets(options.path);
|
|
137
|
-
|
|
138
|
-
const matchedRefs = [];
|
|
139
|
-
for (const ruleset of detected) {
|
|
140
|
-
const refName = RULESET_TO_REFERENCE[ruleset];
|
|
141
|
-
if (refName && available.includes(refName) && !matchedRefs.includes(refName)) {
|
|
142
|
-
matchedRefs.push(refName);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (matchedRefs.length === 0) {
|
|
147
|
-
return {
|
|
148
|
-
error: 'No framework references found for this project',
|
|
149
|
-
detected,
|
|
150
|
-
reasons,
|
|
151
|
-
available,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const results = await Promise.all(matchedRefs.map(fetchReference));
|
|
156
|
-
const contents = results.map(r => r.content).filter(Boolean);
|
|
157
|
-
const sources = results.map(r => r.source);
|
|
158
|
-
|
|
159
|
-
return {
|
|
160
|
-
frameworks: matchedRefs,
|
|
161
|
-
sources,
|
|
162
|
-
detected: { rulesets: detected, reasons },
|
|
163
|
-
lines: contents.reduce((sum, c) => sum + c.split('\n').length, 0),
|
|
164
|
-
content: contents.join('\n\n---\n\n'),
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// No framework specified — list available
|
|
169
|
-
return {
|
|
170
|
-
error: 'Specify framework name or path for auto-detection',
|
|
171
|
-
available: available.map(name => ({
|
|
172
|
-
name,
|
|
173
|
-
remote: !!REMOTE_SOURCES[name],
|
|
174
|
-
url: REMOTE_SOURCES[name] ?? null,
|
|
175
|
-
})),
|
|
176
|
-
};
|
|
177
|
-
}
|