@pyreon/mcp 0.15.0 → 0.18.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.
@@ -0,0 +1,245 @@
1
+ //#region ../../internals/manifest/src/define.ts
2
+ /**
3
+ * Identity helper that preserves literal-type inference on the argument.
4
+ * Enables IDE autocomplete + catches typos at authoring time without
5
+ * forcing `satisfies PackageManifest` at every call site.
6
+ *
7
+ * The `const` modifier on the type parameter preserves string-literal
8
+ * narrowing (e.g. `category: 'browser'` stays `'browser'`, not `string`),
9
+ * which matters for discriminated-union consumers downstream.
10
+ *
11
+ * Performs runtime validation of deprecation metadata: any API entry
12
+ * with `stability: 'deprecated'` must include a `deprecated.removeIn`
13
+ * field. This is a policy gate — without a planned removal version,
14
+ * deprecations decay into permanent fixtures that nobody schedules
15
+ * the cleanup for. Throws loudly at module load (during gen-docs and
16
+ * test runs) so the violation surfaces immediately.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * import { defineManifest } from '@pyreon/manifest'
21
+ *
22
+ * export default defineManifest({
23
+ * name: '@pyreon/flow',
24
+ * tagline: 'Reactive flow diagrams',
25
+ * description: '...',
26
+ * category: 'browser',
27
+ * features: ['...'],
28
+ * api: [{
29
+ * name: 'createFlow',
30
+ * kind: 'function',
31
+ * signature: '<T>(config: FlowConfig<T>) => FlowInstance<T>',
32
+ * summary: '...',
33
+ * example: '...',
34
+ * }],
35
+ * })
36
+ * ```
37
+ */
38
+ function defineManifest(m) {
39
+ if (m.api) {
40
+ for (const entry of m.api) if (entry.stability === "deprecated") {
41
+ if (!entry.deprecated) throw new Error(`[Pyreon manifest] '${m.name}' API '${entry.name}' is marked stability: 'deprecated' but has no \`deprecated\` metadata. Add \`deprecated: { since: "X.Y.Z", removeIn: "X.Y.Z" }\`.`);
42
+ if (!entry.deprecated.removeIn) throw new Error(`[Pyreon manifest] '${m.name}' API '${entry.name}' is deprecated but has no \`deprecated.removeIn\`. Every deprecation must declare a planned removal version — otherwise it rots into permanent surface. Add \`removeIn: "X.Y.Z"\` to \`deprecated\` (or undeprecate the API).`);
43
+ }
44
+ }
45
+ return m;
46
+ }
47
+
48
+ //#endregion
49
+ //#region src/manifest.ts
50
+ var manifest_default = defineManifest({
51
+ name: "@pyreon/mcp",
52
+ title: "MCP Server",
53
+ tagline: "Model Context Protocol server — discoverability map, live API lookup, validation, migration, anti-pattern catalog, changelog, test-environment audit",
54
+ description: "MCP server (stdio transport) that exposes Pyreon\\'s structured knowledge to AI coding assistants (Claude Code, Cursor, etc.). Thirteen tools: `mcp_overview` (start here — markdown table of every tool with \"when to use\" + example, read straight from this manifest), `get_api` (look up any Pyreon API), `validate` (catch React + Pyreon-specific anti-patterns in a snippet), `migrate_react` (auto-convert React code), `diagnose` (parse a Pyreon error into structured fix info), `get_routes` / `get_components` (project introspection), `get_browser_smoke_status` (which packages need a browser smoke test), `get_pattern` (canonical \"how do I do X\" docs), `get_anti_patterns` (the catalog from `.claude/rules/anti-patterns.md`), `get_changelog` (recent release notes per package), `audit_test_environment` (mock-vnode test scanner — PR #197 bug class), and `audit_islands` (project-wide islands cross-file audit — duplicate names, dead islands, registry drift, nested islands, never-with-registry).",
55
+ category: "server",
56
+ features: [
57
+ "Thirteen tools covering discovery, lookup, validation, migration, diagnosis, introspection, audit",
58
+ "stdio transport — drop-in compatible with every MCP client",
59
+ "Project context cached per server instance, auto-invalidates on cwd change",
60
+ "Manifest-driven — `get_api` reads `api-reference.ts`, regenerated from package manifests",
61
+ "AST-based detectors — `validate` catches React + Pyreon-specific patterns statically",
62
+ "Real-repo audit tools (`audit_test_environment`, `audit_islands`, `get_browser_smoke_status`) walk packages/"
63
+ ],
64
+ longExample: `// .mcp/config.json — register the server with any MCP-aware client
65
+ {
66
+ "mcpServers": {
67
+ "pyreon": {
68
+ "command": "bunx",
69
+ "args": ["@pyreon/mcp"]
70
+ }
71
+ }
72
+ }
73
+
74
+ // Then from the client (Claude Code, Cursor, etc.):
75
+ // mcp_overview()
76
+ // → markdown table: tool | when_to_use | example (start here)
77
+ // get_api({ package: 'flow', symbol: 'createFlow' })
78
+ // → signature, example, common mistakes
79
+ // validate({ code: '<MyButton onClick={handler}>...' })
80
+ // → React-pattern + Pyreon-pattern diagnostics with line/col
81
+ // get_pattern({ name: 'controllable-state' })
82
+ // → canonical pattern body from docs/patterns/
83
+ // get_anti_patterns({ category: 'reactivity' })
84
+ // → reactivity foot-guns from .claude/rules/anti-patterns.md
85
+ // get_changelog({ package: 'flow', limit: 5 })
86
+ // → recent release notes filtered through ceremonial-bump removal
87
+ // audit_test_environment({ minRisk: 'medium' })
88
+ // → mock-vnode test files ranked HIGH / MEDIUM / LOW
89
+ // audit_islands({})
90
+ // → project-wide islands audit (5 cross-file foot-guns)`,
91
+ api: [
92
+ {
93
+ name: "mcp_overview",
94
+ kind: "constant",
95
+ signature: "tool: mcp_overview() → MarkdownTable",
96
+ summary: "Returns a markdown table of every registered MCP tool with a one-sentence \"when to use\" description and a one-line example. Reads from this same manifest at runtime — single source of truth (the same data feeds `api-reference.ts`, `llms-full.txt`, and `docs/docs/mcp.md`). Intended as the first call for any AI agent connecting to the server: enumerates the surface so the agent can navigate by intent (e.g. \"I need release notes\" → `get_changelog`) rather than guessing tool names from `tools/list`.",
97
+ example: `mcp_overview()
98
+ // → | Tool | When to use | Example |
99
+ // |------|-------------|---------|
100
+ // | mcp_overview | Returns a markdown table of every registered MCP tool... | mcp_overview() |
101
+ // | get_api | Look up any Pyreon API by package and symbol... | get_api({ package: 'flow', symbol: 'createFlow' }) |
102
+ // | ...`,
103
+ mistakes: ["Skipping this tool and calling `tools/list` instead — that returns names + parameter schemas but no \"when to use\" guidance, so an agent has to call multiple tools to figure out which one fits the task."],
104
+ seeAlso: ["get_api"]
105
+ },
106
+ {
107
+ name: "get_browser_smoke_status",
108
+ kind: "constant",
109
+ signature: "tool: get_browser_smoke_status — no args",
110
+ summary: "Companion to the `pyreon/require-browser-smoke-test` lint rule. Reports which browser-categorized Pyreon packages have at least one `*.browser.test.{ts,tsx}` file under `src/`. Uses the same `.claude/rules/browser-packages.json` single source of truth as the rule + the CI script. Lets an AI agent check coverage before writing a new browser package (so it adds a smoke test in the same PR) instead of discovering the failure when CI runs. Falls back with a clear message if the JSON isn't present (e.g. consumer apps that don't ship the Pyreon monorepo layout).",
111
+ example: `// Ask the MCP server:
112
+ // "which Pyreon packages are missing browser smoke coverage?"
113
+ // Tool walks packages/, matches against .claude/rules/browser-packages.json,
114
+ // returns a coverage report.`,
115
+ mistakes: ["Using the tool's output as a substitute for running the CI script — this tool only checks file existence, not the self-expiring-exemption check that `bun run lint:browser-smoke` performs"],
116
+ seeAlso: ["audit_test_environment"]
117
+ },
118
+ {
119
+ name: "get_api",
120
+ kind: "constant",
121
+ signature: "tool: get_api({ package: string; symbol: string }) → APIEntry",
122
+ summary: "Look up any Pyreon API by `package` (e.g. `\"flow\"` or `\"@pyreon/flow\"`) and `symbol` (e.g. `\"createFlow\"`). Returns the canonical signature, example, foot-gun catalogue, and cross-references — drawn from `api-reference.ts`, which is regenerated from each package\\'s `manifest.ts`. The single agent-facing entry point for \"what does this API do and how do I avoid the common mistakes.\"",
123
+ example: `// Agent-side
124
+ get_api({ package: 'flow', symbol: 'createFlow' })
125
+ get_api({ package: '@pyreon/router', symbol: 'useTypedSearchParams' })`,
126
+ seeAlso: ["validate", "get_pattern"]
127
+ },
128
+ {
129
+ name: "validate",
130
+ kind: "constant",
131
+ signature: "tool: validate({ code: string; filename?: string }) → Diagnostics[]",
132
+ summary: "Two AST-based detectors run in parallel: `detectReactPatterns` flags \"coming from React\" mistakes (`useState`, `useEffect`, `className`, `onChange` on inputs, React-package imports), and `detectPyreonPatterns` flags \"using Pyreon wrong\" mistakes (`<For>` missing `by`, props destructured at component signature, `typeof process` dev gates, raw `addEventListener`, `Date.now() + Math.random()` IDs). Diagnostics are merged + sorted by line / column for top-down reading.",
133
+ example: `validate({ code: \`
134
+ function MyComp(props) {
135
+ const { value } = props // → props-destructured
136
+ return <For each={items}>{...}</For> // → for-missing-by
137
+ }
138
+ \` })`,
139
+ seeAlso: ["get_anti_patterns", "migrate_react"]
140
+ },
141
+ {
142
+ name: "migrate_react",
143
+ kind: "constant",
144
+ signature: "tool: migrate_react({ code: string; filename?: string }) → MigrationResult",
145
+ summary: "Convert React code to idiomatic Pyreon. Handles `useState` → `signal()`, `useEffect` → `effect()`, `className` → `class`, `onChange` → `onInput`, `useMemo` → `computed()`, React imports → Pyreon imports. Reports per-edit fixable diagnostics so callers can apply or review.",
146
+ example: `migrate_react({ code: \`
147
+ import { useState, useEffect } from 'react'
148
+ function Counter() {
149
+ const [count, setCount] = useState(0)
150
+ useEffect(() => { console.log(count) }, [count])
151
+ return <button onClick={() => setCount(count + 1)}>{count}</button>
152
+ }
153
+ \` })`,
154
+ seeAlso: ["validate"]
155
+ },
156
+ {
157
+ name: "diagnose",
158
+ kind: "constant",
159
+ signature: "tool: diagnose({ error: string }) → DiagnoseResult",
160
+ summary: "Parse a Pyreon runtime / build error message into structured fix information: probable cause, recommended fix, related docs, and the `.claude/rules/anti-patterns.md` entry (if any) the error matches. Useful when an agent sees a stack trace and wants to skip the \"search the codebase for similar errors\" step.",
161
+ example: `diagnose({ error: 'Cannot redefine property X on object [object Object]' })
162
+ // → cause: configurable: false on a getter; fix: set configurable: true`,
163
+ seeAlso: ["validate", "get_anti_patterns"]
164
+ },
165
+ {
166
+ name: "get_routes",
167
+ kind: "constant",
168
+ signature: "tool: get_routes() → Route[]",
169
+ summary: "List every route in the current project — path, loader presence, guards, params, and named-route name. Walks the project source from `process.cwd()` down. Cached per server instance with auto-invalidation on `cwd` change.",
170
+ example: `get_routes()
171
+ // → [{ path: '/', name: 'home', hasLoader: true, params: [] }, ...]`,
172
+ seeAlso: ["get_components"]
173
+ },
174
+ {
175
+ name: "get_components",
176
+ kind: "constant",
177
+ signature: "tool: get_components() → ComponentInfo[]",
178
+ summary: "List every component in the current project with its props and signal usage. Same scanner as `get_routes`. Useful for an agent before generating new code that needs to reference existing components.",
179
+ example: `get_components()
180
+ // → [{ name: 'Button', file: 'src/Button.tsx', props: ['onClick', 'children'], signals: ['count'] }, ...]`,
181
+ seeAlso: ["get_routes"]
182
+ },
183
+ {
184
+ name: "get_pattern",
185
+ kind: "constant",
186
+ signature: "tool: get_pattern({ name?: string }) → PatternBody | string[]",
187
+ summary: "Fetch a canonical \"how do I do X\" pattern body from `docs/patterns/`. Eight foundational patterns ship: `dev-warnings`, `controllable-state`, `ssr-safe-hooks`, `signal-writes`, `keyed-lists`, `reactive-context`, `event-listeners`, `form-fields`. Omit `name` to list available patterns. Drop a new `docs/patterns/<slug>.md` file to add one — picked up on next call.",
188
+ example: `get_pattern({ name: 'controllable-state' })
189
+ // → full canonical pattern body
190
+ get_pattern({})
191
+ // → [{ name: 'controllable-state', summary: '...' }, ...]`,
192
+ seeAlso: ["get_anti_patterns"]
193
+ },
194
+ {
195
+ name: "get_anti_patterns",
196
+ kind: "constant",
197
+ signature: "tool: get_anti_patterns({ category?: 'reactivity' | 'jsx' | 'context' | 'architecture' | 'testing' | 'lifecycle' | 'documentation' | 'all' }) → AntiPattern[]",
198
+ summary: "Browse the anti-patterns catalog parsed from `.claude/rules/anti-patterns.md`. Each entry surfaces its `[detector: <code>]` tag inline so an agent can pair the catalog entry with the live static detector exposed by `validate`. Optional `category` filter; default returns all categories.",
199
+ example: `get_anti_patterns({ category: 'reactivity' })
200
+ // → ['Bare signal in JSX text', 'Stale closures', 'Destructuring props', ...]`,
201
+ seeAlso: ["validate", "get_pattern"]
202
+ },
203
+ {
204
+ name: "get_changelog",
205
+ kind: "constant",
206
+ signature: "tool: get_changelog({ package?: string; limit?: number; includeDependencyUpdates?: boolean; since?: string }) → ChangelogEntry[]",
207
+ summary: "Recent release notes for any `@pyreon/*` package without scraping `git log`. Parses `packages/**/CHANGELOG.md` into version entries (`{ version, changes[], dependencyUpdates[], empty }`) and returns the N most recent substantive versions (default 5). Filters out ceremonial version bumps (pure dependency-update releases with no user-facing body) by default — opt back in with `includeDependencyUpdates: true`. `since: \"0.12.0\"` returns the delta from a known floor — useful when an agent knows the version it was trained against.",
208
+ example: `get_changelog({ package: 'flow', limit: 5 })
209
+ get_changelog({ package: '@pyreon/router', since: '0.12.0' })`,
210
+ seeAlso: ["get_api"]
211
+ },
212
+ {
213
+ name: "audit_test_environment",
214
+ kind: "constant",
215
+ signature: "tool: audit_test_environment({ minRisk?: 'high' | 'medium' | 'low'; limit?: number }) → AuditReport",
216
+ summary: "Scan every `*.test.{ts,tsx}` under `packages/` for the mock-vnode anti-pattern that caused PR #197\\'s silent metadata drop. Files are classified HIGH / MEDIUM / LOW based on the balance of mock-vnode literals + helpers + helper-call sites vs real `h()` calls + `@pyreon/core` import. Three context-aware skips (helper-def vs binding discrimination, type-guard call-arg skip, template-string fixture mask) keep the false-positive rate low. Run before merging a new test file or after a framework change.",
217
+ example: `audit_test_environment({ minRisk: 'medium', limit: 10 })
218
+ // → grouped report with HIGH / MEDIUM / LOW sections`,
219
+ seeAlso: ["get_browser_smoke_status", "audit_islands"]
220
+ },
221
+ {
222
+ name: "audit_islands",
223
+ kind: "constant",
224
+ signature: "tool: audit_islands({ json?: boolean }) → IslandAuditReport",
225
+ summary: "Project-wide cross-file islands audit (PR C of the islands DX roadmap). Walks `packages/` + `examples/` and runs five detectors that auto-registry can\\'t reach (manual `hydrateIslands({...})` for non-Vite consumers / library authors) AND PR G\\'s per-file `island-never-with-registry-entry` detector misses (it only catches the same-file shape): `duplicate-name`, `never-with-registry-entry`, `registry-mismatch`, `nested-island`, `dead-island`. Each finding ships with file path + line/column + actionable fix suggestion. Companion to the `pyreon doctor --check-islands` CLI flag (same scanner, same five detectors). Run before merging an island PR; CI gate by piping `--json` and grepping `findings.length > 0`.",
226
+ example: `audit_islands({})
227
+ // → markdown-grouped report with one section per finding code
228
+
229
+ audit_islands({ json: true })
230
+ // → machine-readable { root, findings: [...], summary: {...} }`,
231
+ seeAlso: ["audit_test_environment", "get_anti_patterns"]
232
+ }
233
+ ],
234
+ gotchas: [{
235
+ label: "Project-context caching",
236
+ note: "Each `createServer()` instance maintains its own cached context (routes, components, islands). The cache auto-resets when `process.cwd()` changes between tool invocations, so the same server can operate across multiple projects in one session."
237
+ }, {
238
+ label: "Manifest-driven",
239
+ note: "`get_api` reads `api-reference.ts`, which is generated from each package\\'s `manifest.ts`. The marker-pair protocol (`<gen-docs:api-reference:start @pyreon/<name>>`) lets some packages be generated and others stay hand-written during incremental migration."
240
+ }]
241
+ });
242
+
243
+ //#endregion
244
+ export { manifest_default as default };
245
+ //# sourceMappingURL=manifest-BCmgA06z.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyreon/mcp",
3
- "version": "0.15.0",
3
+ "version": "0.18.0",
4
4
  "description": "MCP server for Pyreon — AI-powered framework assistance",
5
5
  "homepage": "https://github.com/pyreon/pyreon/tree/main/packages/mcp#readme",
6
6
  "bugs": {
@@ -47,7 +47,7 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "@modelcontextprotocol/sdk": "^1.29.0",
50
- "@pyreon/compiler": "^0.15.0",
50
+ "@pyreon/compiler": "^0.18.0",
51
51
  "zod": "^4.3.6"
52
52
  },
53
53
  "devDependencies": {
@@ -96,7 +96,7 @@ function splitBullets(sectionBody: string): string[] {
96
96
  const bullets: string[] = []
97
97
  let current: string[] = []
98
98
  for (const line of lines) {
99
- if (/^- \*\*/.test(line)) {
99
+ if (line.startsWith('- **')) {
100
100
  if (current.length > 0) bullets.push(current.join('\n').trim())
101
101
  current = [line]
102
102
  } else if (current.length > 0) {
@@ -123,7 +123,7 @@ function parseBullet(bullet: string): {
123
123
  // Pull out the detector tag if present. It can appear as:
124
124
  // ` [detector: code]`
125
125
  // ` \`[detector: code]\``
126
- const detectorMatch = /`?\[detector:\s*([a-z0-9\-\/ ]+)\]`?/i.exec(afterName)
126
+ const detectorMatch = /`?\[detector:\s*([a-z0-9\-/ ]+)\]`?/i.exec(afterName)
127
127
  const detectorCodes: string[] = []
128
128
  if (detectorMatch) {
129
129
  for (const code of detectorMatch[1]!.split('/')) {