@pyreon/mcp 0.5.0 → 0.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/mcp",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "MCP server for Pyreon — AI-powered framework assistance",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,7 +42,7 @@
42
42
  "prepublishOnly": "bun run build"
43
43
  },
44
44
  "dependencies": {
45
- "@pyreon/compiler": "workspace:*",
45
+ "@pyreon/compiler": "0.5.2",
46
46
  "@modelcontextprotocol/sdk": "^1.27.1",
47
47
  "zod": "^3.25.76"
48
48
  },
@@ -1,215 +1,10 @@
1
1
  /**
2
- * Project scanner extracts route, component, and island information from source files.
2
+ * Re-export the unified project scanner from @pyreon/compiler.
3
3
  */
4
-
5
- import * as fs from "node:fs"
6
- import * as path from "node:path"
7
-
8
- export interface RouteInfo {
9
- path: string
10
- name?: string | undefined
11
- component?: string | undefined
12
- hasLoader: boolean
13
- hasGuard: boolean
14
- params: string[]
15
- }
16
-
17
- export interface ComponentInfo {
18
- name: string
19
- file: string
20
- hasSignals: boolean
21
- signalNames: string[]
22
- props: string[]
23
- }
24
-
25
- export interface IslandInfo {
26
- name: string
27
- file: string
28
- hydrate: string
29
- }
30
-
31
- export interface ProjectContext {
32
- framework: "pyreon"
33
- version: string
34
- generatedAt: string
35
- routes: RouteInfo[]
36
- components: ComponentInfo[]
37
- islands: IslandInfo[]
38
- }
39
-
40
- export function generateContext(cwd: string): ProjectContext {
41
- const files = collectSourceFiles(cwd)
42
- const version = readVersion(cwd)
43
-
44
- return {
45
- framework: "pyreon",
46
- version,
47
- generatedAt: new Date().toISOString(),
48
- routes: extractRoutes(files, cwd),
49
- components: extractComponents(files, cwd),
50
- islands: extractIslands(files, cwd),
51
- }
52
- }
53
-
54
- function collectSourceFiles(cwd: string): string[] {
55
- const results: string[] = []
56
- const extensions = new Set([".tsx", ".jsx", ".ts", ".js"])
57
- const ignoreDirs = new Set(["node_modules", "dist", "lib", ".pyreon", ".git", "build"])
58
-
59
- // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: simple recursive walker
60
- function walk(dir: string): void {
61
- let entries: fs.Dirent[]
62
- try {
63
- entries = fs.readdirSync(dir, { withFileTypes: true })
64
- } catch {
65
- return
66
- }
67
- for (const entry of entries) {
68
- if (entry.name.startsWith(".") && entry.isDirectory()) continue
69
- if (ignoreDirs.has(entry.name) && entry.isDirectory()) continue
70
- const fullPath = path.join(dir, entry.name)
71
- if (entry.isDirectory()) {
72
- walk(fullPath)
73
- } else if (entry.isFile() && extensions.has(path.extname(entry.name))) {
74
- results.push(fullPath)
75
- }
76
- }
77
- }
78
-
79
- walk(cwd)
80
- return results
81
- }
82
-
83
- function extractRoutes(files: string[], _cwd: string): RouteInfo[] {
84
- const routes: RouteInfo[] = []
85
-
86
- for (const file of files) {
87
- let code: string
88
- try {
89
- code = fs.readFileSync(file, "utf-8")
90
- } catch {
91
- continue
92
- }
93
-
94
- const routeArrayRe =
95
- /(?:createRouter\s*\(\s*\[|(?:const|let)\s+routes\s*(?::\s*RouteRecord\[\])?\s*=\s*\[)([\s\S]*?)\]/g
96
- let match: RegExpExecArray | null
97
- for (match = routeArrayRe.exec(code); match; match = routeArrayRe.exec(code)) {
98
- const block = match[1] ?? ""
99
- const routeObjRe = /path\s*:\s*["']([^"']+)["']/g
100
- let routeMatch: RegExpExecArray | null
101
- for (routeMatch = routeObjRe.exec(block); routeMatch; routeMatch = routeObjRe.exec(block)) {
102
- const routePath = routeMatch[1] ?? ""
103
- const surroundingStart = Math.max(0, routeMatch.index - 50)
104
- const surroundingEnd = Math.min(block.length, routeMatch.index + 200)
105
- const surrounding = block.slice(surroundingStart, surroundingEnd)
106
-
107
- routes.push({
108
- path: routePath,
109
- name: surrounding.match(/name\s*:\s*["']([^"']+)["']/)?.[1],
110
- hasLoader: /loader\s*:/.test(surrounding),
111
- hasGuard: /beforeEnter\s*:|beforeLeave\s*:/.test(surrounding),
112
- params: extractParams(routePath),
113
- })
114
- }
115
- }
116
- }
117
-
118
- return routes
119
- }
120
-
121
- function extractComponents(files: string[], cwd: string): ComponentInfo[] {
122
- const components: ComponentInfo[] = []
123
-
124
- for (const file of files) {
125
- let code: string
126
- try {
127
- code = fs.readFileSync(file, "utf-8")
128
- } catch {
129
- continue
130
- }
131
-
132
- const componentRe =
133
- /(?:export\s+)?(?:const|function)\s+([A-Z]\w*)\s*(?::\s*ComponentFn<[^>]+>\s*)?=?\s*\(?(?:\s*\{?\s*([^)]*?)\s*\}?\s*)?\)?\s*(?:=>|{)/g
134
- let match: RegExpExecArray | null
135
-
136
- for (match = componentRe.exec(code); match; match = componentRe.exec(code)) {
137
- const name = match[1] ?? "Unknown"
138
- const propsStr = match[2] ?? ""
139
- const props = propsStr
140
- .split(/[,;]/)
141
- .map((p) => p.trim().replace(/[{}]/g, "").trim().split(":")[0]?.split("=")[0]?.trim() ?? "")
142
- .filter((p) => p && p !== "props")
143
-
144
- const bodyStart = match.index + match[0].length
145
- const body = code.slice(bodyStart, Math.min(code.length, bodyStart + 2000))
146
- const signalNames: string[] = []
147
- const signalRe = /(?:const|let)\s+(\w+)\s*=\s*signal\s*[<(]/g
148
- let sigMatch: RegExpExecArray | null
149
- for (sigMatch = signalRe.exec(body); sigMatch; sigMatch = signalRe.exec(body)) {
150
- if (sigMatch[1]) signalNames.push(sigMatch[1])
151
- }
152
-
153
- components.push({
154
- name,
155
- file: path.relative(cwd, file),
156
- hasSignals: signalNames.length > 0,
157
- signalNames,
158
- props,
159
- })
160
- }
161
- }
162
-
163
- return components
164
- }
165
-
166
- function extractIslands(files: string[], cwd: string): IslandInfo[] {
167
- const islands: IslandInfo[] = []
168
-
169
- for (const file of files) {
170
- let code: string
171
- try {
172
- code = fs.readFileSync(file, "utf-8")
173
- } catch {
174
- continue
175
- }
176
-
177
- const islandRe =
178
- /island\s*\(\s*\(\)\s*=>\s*import\(.+?\)\s*,\s*\{[^}]*name\s*:\s*["']([^"']+)["'][^}]*?(?:hydrate\s*:\s*["']([^"']+)["'])?[^}]*\}/g
179
- let match: RegExpExecArray | null
180
- for (match = islandRe.exec(code); match; match = islandRe.exec(code)) {
181
- if (match[1]) {
182
- islands.push({
183
- name: match[1],
184
- file: path.relative(cwd, file),
185
- hydrate: match[2] ?? "load",
186
- })
187
- }
188
- }
189
- }
190
-
191
- return islands
192
- }
193
-
194
- function extractParams(routePath: string): string[] {
195
- const params: string[] = []
196
- const paramRe = /:(\w+)\??/g
197
- let match: RegExpExecArray | null
198
- for (match = paramRe.exec(routePath); match; match = paramRe.exec(routePath)) {
199
- if (match[1]) params.push(match[1])
200
- }
201
- return params
202
- }
203
-
204
- function readVersion(cwd: string): string {
205
- try {
206
- const pkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8"))
207
- const deps: Record<string, unknown> = { ...pkg.dependencies, ...pkg.devDependencies }
208
- for (const [name, ver] of Object.entries(deps)) {
209
- if (name.startsWith("@pyreon/") && typeof ver === "string") return ver.replace(/^[\^~]/, "")
210
- }
211
- return (pkg.version as string) || "unknown"
212
- } catch {
213
- return "unknown"
214
- }
215
- }
4
+ export {
5
+ type ComponentInfo,
6
+ generateContext,
7
+ type IslandInfo,
8
+ type ProjectContext,
9
+ type RouteInfo,
10
+ } from "@pyreon/compiler"