@stellisoft/stellify-mcp 0.1.34 → 0.1.36
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 +118 -2
- package/dist/index.js +98 -21
- package/package.json +1 -1
- package/server.json +2 -2
- package/skills/wordpress-import.md +238 -0
package/README.md
CHANGED
|
@@ -352,6 +352,20 @@ Search for routes/pages in the project by name.
|
|
|
352
352
|
|
|
353
353
|
---
|
|
354
354
|
|
|
355
|
+
### Views & Blade Templates
|
|
356
|
+
|
|
357
|
+
Stellify stores Blade views as elements instead of files. The root element's `name` field maps to the view name:
|
|
358
|
+
|
|
359
|
+
- Element with `name="notes.index"` → `view('notes.index', $data)`
|
|
360
|
+
- Element with `name="layouts.app"` → `@extends('layouts.app')`
|
|
361
|
+
- Element with `name="components.card"` → `<x-card>`
|
|
362
|
+
|
|
363
|
+
Use `update_element` to set the `name` on a root element after creating it with `html_to_elements`.
|
|
364
|
+
|
|
365
|
+
**Convention for reusable templates:** Attach layouts, components, and partials to a template route (e.g., `/template/app-layout`, `/template/card`) to keep them organized and editable.
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
355
369
|
### Element Tools (UI Components)
|
|
356
370
|
|
|
357
371
|
#### `create_element`
|
|
@@ -360,12 +374,80 @@ Create a new UI element. Provide either `page` (route UUID) for root elements, o
|
|
|
360
374
|
**Parameters:**
|
|
361
375
|
- `type` (required): Element type - one of:
|
|
362
376
|
- HTML5: `s-wrapper`, `s-input`, `s-form`, `s-svg`, `s-shape`, `s-media`, `s-iframe`
|
|
363
|
-
- Components: `s-
|
|
377
|
+
- Components: `s-transition`, `s-freestyle`, `s-motion`
|
|
364
378
|
- Blade: `s-directive`
|
|
365
379
|
- Shadcn/ui: `s-chart`, `s-table`, `s-combobox`, `s-accordion`, `s-calendar`, `s-contiguous`
|
|
366
380
|
- `page` (optional): UUID of the page/route (for root elements)
|
|
367
381
|
- `parent` (optional): UUID of the parent element (for child elements)
|
|
368
382
|
|
|
383
|
+
**Using `s-directive` for Blade Conditionals:**
|
|
384
|
+
|
|
385
|
+
`s-directive` elements output Blade directives (like `@if`, `@foreach`, `@endif`). They are **sibling elements** — they don't wrap children. To conditionally render content:
|
|
386
|
+
|
|
387
|
+
1. Create an `s-directive` element with a statement for the opening directive (e.g., `@if(...)`)
|
|
388
|
+
2. Create the content element(s) as the **next sibling(s)**
|
|
389
|
+
3. Create another `s-directive` element with a statement for the closing directive (e.g., `@endif`)
|
|
390
|
+
|
|
391
|
+
Example — conditionally showing an image:
|
|
392
|
+
```
|
|
393
|
+
// 1. Create statement for @if
|
|
394
|
+
create_statement_with_code({
|
|
395
|
+
file: "<file-uuid>",
|
|
396
|
+
code: "@if($item->featured_image)"
|
|
397
|
+
})
|
|
398
|
+
|
|
399
|
+
// 2. Create opening directive element and set its statement
|
|
400
|
+
create_element({ type: "s-directive", page: "<route-uuid>" })
|
|
401
|
+
update_element({ uuid: "<if-directive-uuid>", data: { "statement": "<if-statement-uuid>" } })
|
|
402
|
+
|
|
403
|
+
// 3. Create the image as the next sibling
|
|
404
|
+
html_to_elements({ page: "<route-uuid>", elements: "<img class=\"w-full\" />" })
|
|
405
|
+
// Then update with dynamic src:
|
|
406
|
+
update_element({ uuid: "<img-uuid>", data: { "srcField": "featured_image" } })
|
|
407
|
+
|
|
408
|
+
// 4. Create statement for @endif
|
|
409
|
+
create_statement_with_code({ file: "<file-uuid>", code: "@endif" })
|
|
410
|
+
|
|
411
|
+
// 5. Create closing directive element
|
|
412
|
+
create_element({ type: "s-directive", page: "<route-uuid>" })
|
|
413
|
+
update_element({ uuid: "<endif-directive-uuid>", data: { "statement": "<endif-statement-uuid>" } })
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
The three elements render in order as siblings:
|
|
417
|
+
```blade
|
|
418
|
+
@if($item->featured_image)
|
|
419
|
+
<img class="w-full" src="{{ $item->featured_image }}" />
|
|
420
|
+
@endif
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
**Using `s-directive` for Loops:**
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
// 1. Create @foreach directive
|
|
427
|
+
create_statement_with_code({ file: "<file-uuid>", code: "@foreach($posts as $item)" })
|
|
428
|
+
create_element({ type: "s-directive", page: "<route-uuid>" })
|
|
429
|
+
update_element({ uuid: "<foreach-uuid>", data: { "statement": "<foreach-statement-uuid>" } })
|
|
430
|
+
|
|
431
|
+
// 2. Create loop content (article with dynamic fields)
|
|
432
|
+
html_to_elements({ page: "<route-uuid>", elements: "<article><h2></h2><p></p></article>" })
|
|
433
|
+
// Update elements to use loop item fields:
|
|
434
|
+
update_element({ uuid: "<h2-uuid>", data: { "textField": "title" } }) // → {{ $item->title }}
|
|
435
|
+
update_element({ uuid: "<p-uuid>", data: { "textField": "excerpt" } }) // → {{ $item->excerpt }}
|
|
436
|
+
|
|
437
|
+
// 3. Create @endforeach directive
|
|
438
|
+
create_statement_with_code({ file: "<file-uuid>", code: "@endforeach" })
|
|
439
|
+
create_element({ type: "s-directive", page: "<route-uuid>" })
|
|
440
|
+
update_element({ uuid: "<endforeach-uuid>", data: { "statement": "<endforeach-statement-uuid>" } })
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Loop Item Attributes:**
|
|
444
|
+
Inside `@foreach` loops, use these attributes on elements to reference `$item`:
|
|
445
|
+
- `textField: "fieldName"` → outputs `{{ $item->fieldName }}`
|
|
446
|
+
- `hrefField: "fieldName"` → outputs `href="{{ $item->fieldName }}"`
|
|
447
|
+
- `srcField: "fieldName"` → outputs `src="{{ $item->fieldName }}"`
|
|
448
|
+
- `hrefExpression: "{{ route('posts.show', $item->slug) }}"` → for complex expressions
|
|
449
|
+
- `srcExpression`, `altExpression` → same pattern for other attributes
|
|
450
|
+
|
|
369
451
|
---
|
|
370
452
|
|
|
371
453
|
#### `update_element`
|
|
@@ -383,7 +465,18 @@ Update an existing UI element.
|
|
|
383
465
|
- `locked`: Prevent editing (boolean)
|
|
384
466
|
- `tag`: HTML tag (div, input, button, etc.)
|
|
385
467
|
- `classes`: CSS classes array `["class1", "class2"]`
|
|
386
|
-
- `text`:
|
|
468
|
+
- `text`: Static text content
|
|
469
|
+
- `statements`: Array of statement UUIDs for dynamic Blade content
|
|
470
|
+
|
|
471
|
+
**Loop item fields** (for elements inside `@foreach` loops, references `$item`):
|
|
472
|
+
- `textField`: Field name → outputs `{{ $item->fieldName }}`
|
|
473
|
+
- `hrefField`: Field name → outputs `href="{{ $item->fieldName }}"`
|
|
474
|
+
- `srcField`: Field name → outputs `src="{{ $item->fieldName }}"`
|
|
475
|
+
|
|
476
|
+
**Expression attributes** (for complex Blade expressions):
|
|
477
|
+
- `hrefExpression`: Full Blade expression for href (e.g., `"{{ route('posts.show', $item->slug) }}"`)
|
|
478
|
+
- `srcExpression`: Full Blade expression for src
|
|
479
|
+
- `altExpression`: Full Blade expression for alt
|
|
387
480
|
|
|
388
481
|
**Event handlers** (set value to method UUID):
|
|
389
482
|
- `click`: @click
|
|
@@ -441,8 +534,31 @@ Convert HTML to Stellify elements in ONE operation. This is the fastest way to b
|
|
|
441
534
|
- `elements` (required): HTML string to convert
|
|
442
535
|
- `page` (optional): Route UUID to attach elements to. Omit for Vue components.
|
|
443
536
|
- `selection` (optional): Parent element UUID to attach to (alternative to page)
|
|
537
|
+
- `file` (optional): Vue component file UUID. Pass this to auto-wire @click handlers to method UUIDs.
|
|
444
538
|
- `test` (optional): If true, returns structure without creating elements
|
|
445
539
|
|
|
540
|
+
**⚠️ CRITICAL: Multiple Root Elements**
|
|
541
|
+
|
|
542
|
+
When passing HTML with **multiple root-level elements** (e.g., `<header>`, `<main>`, `<footer>`), only the **FIRST root element** gets attached to the route via `routeParent`. Other elements are created but become **orphaned** (not attached to the route).
|
|
543
|
+
|
|
544
|
+
**Wrong approach (causes orphaned elements):**
|
|
545
|
+
```
|
|
546
|
+
html_to_elements(page: routeUUID, elements: "<header>...</header><main>...</main><footer>...</footer>")
|
|
547
|
+
// Result: Only <header> is attached to the route. <main> and <footer> are orphaned!
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**Correct approach (make separate calls for each root element):**
|
|
551
|
+
```
|
|
552
|
+
// Call 1: Header
|
|
553
|
+
html_to_elements(page: routeUUID, elements: "<header>...</header>")
|
|
554
|
+
|
|
555
|
+
// Call 2: Main content
|
|
556
|
+
html_to_elements(page: routeUUID, elements: "<main>...</main>")
|
|
557
|
+
|
|
558
|
+
// Call 3: Footer
|
|
559
|
+
html_to_elements(page: routeUUID, elements: "<footer>...</footer>")
|
|
560
|
+
```
|
|
561
|
+
|
|
446
562
|
**Features:**
|
|
447
563
|
- Parses HTML structure
|
|
448
564
|
- Creates all elements with proper nesting
|
package/dist/index.js
CHANGED
|
@@ -569,16 +569,15 @@ Use the returned UUID with html_to_elements (page parameter) or get_route for fu
|
|
|
569
569
|
},
|
|
570
570
|
{
|
|
571
571
|
name: 'create_element',
|
|
572
|
-
description: `Create a UI element. Provide page (route UUID) for root elements, or parent (element UUID) for children
|
|
572
|
+
description: `Create a UI element. Provide page (route UUID) for root elements, or parent (element UUID) for children.`,
|
|
573
573
|
inputSchema: {
|
|
574
574
|
type: 'object',
|
|
575
575
|
properties: {
|
|
576
576
|
type: {
|
|
577
577
|
type: 'string',
|
|
578
578
|
enum: [
|
|
579
|
-
's-wrapper', 's-input', 's-form', 's-svg', 's-shape', 's-media',
|
|
580
|
-
's-
|
|
581
|
-
's-directive'
|
|
579
|
+
's-wrapper', 's-input', 's-form', 's-svg', 's-shape', 's-media',
|
|
580
|
+
's-freestyle', 's-directive'
|
|
582
581
|
],
|
|
583
582
|
description: 'Element type - must be one of the valid Stellify element types',
|
|
584
583
|
},
|
|
@@ -596,7 +595,24 @@ Use the returned UUID with html_to_elements (page parameter) or get_route for fu
|
|
|
596
595
|
},
|
|
597
596
|
{
|
|
598
597
|
name: 'update_element',
|
|
599
|
-
description: `Update a UI element. Data object: tag, classes, text, event handlers (method UUIDs), classBindings.
|
|
598
|
+
description: `Update a UI element. Data object: tag, classes, text, event handlers (method UUIDs), classBindings. Set 'name' on root elements to create Blade views (e.g., name="notes.index" for view('notes.index')).
|
|
599
|
+
|
|
600
|
+
**For elements inside @foreach loops (SSR/Blade):**
|
|
601
|
+
Use these attributes to reference the loop variable (defaults to \`$item\`):
|
|
602
|
+
- \`textField\`: Field name for text content → outputs \`{{ $item->fieldName }}\`
|
|
603
|
+
- \`hrefField\`: Field name for href → outputs \`href="{{ $item->fieldName }}"\`
|
|
604
|
+
- \`srcField\`: Field name for src → outputs \`src="{{ $item->fieldName }}"\`
|
|
605
|
+
|
|
606
|
+
**For complex Blade expressions in attributes:**
|
|
607
|
+
Use expression attributes when you need more than simple field access (e.g., route helpers, method calls):
|
|
608
|
+
- \`hrefExpression\`: Blade expression for href → outputs \`href="..."\` with the expression
|
|
609
|
+
- \`srcExpression\`: Blade expression for src → outputs \`src="..."\` with the expression
|
|
610
|
+
- \`altExpression\`: Blade expression for alt → outputs \`alt="..."\` with the expression
|
|
611
|
+
|
|
612
|
+
Example: \`hrefExpression: "{{ route('posts.show', $item->slug) }}"\`
|
|
613
|
+
|
|
614
|
+
**For Blade text content:**
|
|
615
|
+
Use the \`statements\` array with statement UUIDs containing Blade code. The statement's \`code\` property will be output directly for Blade to evaluate.`,
|
|
600
616
|
inputSchema: {
|
|
601
617
|
type: 'object',
|
|
602
618
|
properties: {
|
|
@@ -685,12 +701,50 @@ Note: To reorder elements, use update_element to modify the parent element's 'da
|
|
|
685
701
|
},
|
|
686
702
|
{
|
|
687
703
|
name: 'html_to_elements',
|
|
688
|
-
description: `Convert HTML to Stellify elements.
|
|
704
|
+
description: `Convert HTML to Stellify elements.
|
|
705
|
+
|
|
706
|
+
**IMPORTANT - Choose the right approach:**
|
|
707
|
+
|
|
708
|
+
**For SSR/Blade Pages (WordPress imports, static content, layouts):**
|
|
709
|
+
- MUST pass 'page' (route UUID) - elements attach to the route for server-side rendering
|
|
710
|
+
- This is the most common use case
|
|
711
|
+
|
|
712
|
+
**For Vue Components (client-side interactivity):**
|
|
713
|
+
- Omit 'page' - elements are standalone, referenced by file's template array
|
|
714
|
+
- Returns UUIDs to use in save_file's template array
|
|
715
|
+
|
|
716
|
+
**Where elements go:**
|
|
717
|
+
- Pass 'page' (route UUID): Elements attached to the route for SSR rendering
|
|
718
|
+
- Pass 'selection' (element UUID): Elements attached as children of existing element
|
|
719
|
+
- Omit both: Elements are standalone (Vue components only) - use returned UUIDs in save_file's template array
|
|
720
|
+
|
|
721
|
+
**⚠️ CRITICAL: Multiple Root Elements Limitation**
|
|
722
|
+
When HTML contains multiple root-level elements (e.g., <header>, <main>, <footer>), only the FIRST root element gets attached to the route. Other elements become orphaned!
|
|
723
|
+
|
|
724
|
+
**WRONG:** \`html_to_elements(page: routeUUID, elements: "<header>...</header><main>...</main><footer>...</footer>")\`
|
|
725
|
+
→ Only <header> attaches to route. <main> and <footer> are orphaned!
|
|
689
726
|
|
|
690
|
-
|
|
727
|
+
**CORRECT:** Make separate calls for each root element:
|
|
728
|
+
1. \`html_to_elements(page: routeUUID, elements: "<header>...</header>")\`
|
|
729
|
+
2. \`html_to_elements(page: routeUUID, elements: "<main>...</main>")\`
|
|
730
|
+
3. \`html_to_elements(page: routeUUID, elements: "<footer>...</footer>")\`
|
|
731
|
+
|
|
732
|
+
**OR** wrap all elements in a single container div.
|
|
691
733
|
|
|
692
734
|
**@click auto-wiring:** Pass 'file' UUID to auto-resolve @click="methodName" handlers. Methods must exist in the file first.
|
|
693
735
|
|
|
736
|
+
**Blade Syntax Handling:**
|
|
737
|
+
For SSR/Blade pages, do NOT pass raw Blade expressions in text or attributes. The HTML parser stores them literally which causes rendering issues. Instead:
|
|
738
|
+
|
|
739
|
+
1. **For static HTML:** Pass clean HTML without Blade syntax, then use \`update_element\` to add dynamic behavior
|
|
740
|
+
2. **For loop content:** After creating elements, use \`update_element\` with:
|
|
741
|
+
- \`textField\`, \`hrefField\`, \`srcField\` for simple field access (outputs \`{{ $item->field }}\`)
|
|
742
|
+
- \`hrefExpression\`, \`srcExpression\`, \`altExpression\` for complex expressions
|
|
743
|
+
- \`statements\` array with statement UUIDs for text content with Blade code
|
|
744
|
+
3. **For conditionals:** Use \`s-directive\` elements as siblings (see update_element docs)
|
|
745
|
+
|
|
746
|
+
**Loop variable:** Inside \`@foreach\` loops created with \`s-directive\`, the default loop variable is \`$item\`. Use \`textField: "title"\` to output \`{{ $item->title }}\`.
|
|
747
|
+
|
|
694
748
|
Prefer SVG icons over emoji (encoding issues).`,
|
|
695
749
|
inputSchema: {
|
|
696
750
|
type: 'object',
|
|
@@ -701,11 +755,11 @@ Prefer SVG icons over emoji (encoding issues).`,
|
|
|
701
755
|
},
|
|
702
756
|
page: {
|
|
703
757
|
type: 'string',
|
|
704
|
-
description: 'Route UUID to attach elements to.
|
|
758
|
+
description: 'Route UUID to attach elements to. REQUIRED for SSR/Blade pages (WordPress imports, static content, layouts). Only omit for Vue component templates.',
|
|
705
759
|
},
|
|
706
760
|
selection: {
|
|
707
761
|
type: 'string',
|
|
708
|
-
description: 'Parent element UUID to attach to (alternative to page)',
|
|
762
|
+
description: 'Parent element UUID to attach to (alternative to page). Use when adding children to existing elements.',
|
|
709
763
|
},
|
|
710
764
|
file: {
|
|
711
765
|
type: 'string',
|
|
@@ -1154,7 +1208,7 @@ Changes are EPHEMERAL (not saved). For persistent changes, use update_element or
|
|
|
1154
1208
|
},
|
|
1155
1209
|
api: {
|
|
1156
1210
|
type: 'boolean',
|
|
1157
|
-
description: 'Generate API-style responses (default: true)',
|
|
1211
|
+
description: 'Generate API-style responses (default: true). Set to FALSE for SSR/Blade pages (controllers return views). Set to TRUE for API endpoints (controllers return JSON).',
|
|
1158
1212
|
},
|
|
1159
1213
|
},
|
|
1160
1214
|
required: ['name'],
|
|
@@ -1405,22 +1459,45 @@ const SERVER_INSTRUCTIONS = `Stellify is a coding platform where code is stored
|
|
|
1405
1459
|
|
|
1406
1460
|
- Files are stored as json. The json has a data key that references its methods (using uuids), and each method has a data key that references statements, and each statement has a data key that references clauses.
|
|
1407
1461
|
|
|
1408
|
-
##
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1462
|
+
## Choosing Between SSR Pages and Vue Components
|
|
1463
|
+
|
|
1464
|
+
**Use SSR/Blade Pages (routes + elements) when:**
|
|
1465
|
+
- Building static pages, layouts, or content pages
|
|
1466
|
+
- Importing from WordPress, HTML templates, or static sites
|
|
1467
|
+
- No client-side interactivity needed beyond links/forms
|
|
1468
|
+
- SEO is important
|
|
1469
|
+
|
|
1470
|
+
**Use Vue Components (files with type='js', extension='vue') when:**
|
|
1471
|
+
- Client-side interactivity is required (counters, toggles, dynamic forms)
|
|
1472
|
+
- Real-time updates needed
|
|
1473
|
+
- Complex state management
|
|
1474
|
+
|
|
1475
|
+
**CRITICAL DIFFERENCE:**
|
|
1476
|
+
- SSR Pages: Elements attach directly to routes. Use \`html_to_elements\` with \`page\` parameter (route UUID). Elements render via Blade.
|
|
1477
|
+
- Vue Components: Elements are standalone, referenced by file's \`template\` array. Use \`html_to_elements\` WITHOUT \`page\` parameter.
|
|
1478
|
+
|
|
1479
|
+
## Workflow for SSR Pages (most common)
|
|
1480
|
+
1. Research: Call get_project to understand current structure
|
|
1481
|
+
2. Create route: \`create_route\` for each page
|
|
1482
|
+
3. Add elements: \`html_to_elements\` with \`page\` parameter set to route UUID
|
|
1483
|
+
4. Wire controller (if needed): \`save_route\` with controller and controller_method UUIDs
|
|
1484
|
+
|
|
1485
|
+
## Workflow for Vue Components (client-side interactivity)
|
|
1486
|
+
1. Research: Call get_project to understand current structure
|
|
1487
|
+
2. **Call \`get_stellify_framework_api\`** to check for composables before writing custom code
|
|
1488
|
+
3. Create file: \`create_file\` with type='js', extension='vue'
|
|
1489
|
+
4. Create methods: \`create_method\` (with body parameter)
|
|
1490
|
+
5. Create state: \`create_statement_with_code\` for refs/reactive
|
|
1491
|
+
6. Create template: \`html_to_elements\` WITHOUT \`page\` parameter (returns UUIDs)
|
|
1492
|
+
7. **Handle mount file**: Check \`appJs\` in create_file response. If \`action_required\` exists, ask user about mount file
|
|
1493
|
+
8. Finalize: \`save_file\` with template/data/statements arrays (include frameworkImports for composables)
|
|
1494
|
+
9. Verify: Call \`get_assembled_code\` to check the output
|
|
1418
1495
|
`;
|
|
1419
1496
|
// Legacy detailed instructions preserved as comments for reference if needed
|
|
1420
1497
|
// Create MCP server
|
|
1421
1498
|
const server = new Server({
|
|
1422
1499
|
name: 'stellify-mcp',
|
|
1423
|
-
version: '0.1.
|
|
1500
|
+
version: '0.1.36',
|
|
1424
1501
|
}, {
|
|
1425
1502
|
capabilities: {
|
|
1426
1503
|
tools: {},
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"url": "https://github.com/Stellify-Software-Ltd/stellify-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.1.
|
|
9
|
+
"version": "0.1.36",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@stellisoft/stellify-mcp",
|
|
14
|
-
"version": "0.1.
|
|
14
|
+
"version": "0.1.36",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wordpress-import
|
|
3
|
+
description: "Use this skill when importing WordPress sites into Stellify. Invoke with /wordpress-import to start the guided workflow. Covers: analyzing WordPress exports, converting post types to Eloquent models, transforming PHP templates to Vue components, migrating forms and plugins, and creating routes and API endpoints."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: stellify
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# WordPress Import Skill
|
|
10
|
+
|
|
11
|
+
You are importing a WordPress site into Stellify by reading its PHP theme files and recreating the site as a structured Laravel application using the Stellify MCP tools.
|
|
12
|
+
|
|
13
|
+
**Match the rendering approach of the WordPress site.** If content is server-rendered in WordPress (which most of it will be — posts, pages, archives, navigation), create Blade templates. If a feature uses JavaScript for client-side interactivity (AJAX forms, dynamic filtering, live search, infinite scroll, modals, sliders), create a Vue component for that specific piece. The default is Blade — only reach for Vue when the WordPress source is doing something that genuinely requires JS.
|
|
14
|
+
|
|
15
|
+
## Step 1 — Identify the Active Theme
|
|
16
|
+
|
|
17
|
+
Look in `./wp-content/themes/` to find the active theme. If there are multiple themes, check for the most recently modified one, or the one with the most template files. If unsure, ask which theme to import.
|
|
18
|
+
|
|
19
|
+
Before proceeding, ask the user:
|
|
20
|
+
|
|
21
|
+
> **How do you want to handle the database?**
|
|
22
|
+
>
|
|
23
|
+
> **A) Connect to existing WordPress database** — Models map directly to the WordPress tables (`wp_posts`, `wp_users`, etc.) using the existing column names. No migrations, no data copying. Your Laravel app reads and writes to the same database WordPress uses. This is the fastest path.
|
|
24
|
+
>
|
|
25
|
+
> **B) Fresh database** — Create new Laravel migrations with clean column names (`title` instead of `post_title`, etc.). You start with an empty database and can optionally migrate content from WordPress later.
|
|
26
|
+
|
|
27
|
+
These are the only questions you may ask. Do not ask anything else.
|
|
28
|
+
|
|
29
|
+
## Step 2 — Inventory the Theme
|
|
30
|
+
|
|
31
|
+
Read the full theme directory. Categorise every file:
|
|
32
|
+
|
|
33
|
+
- **Core templates**: index.php, single.php, page.php, archive.php, search.php, 404.php, front-page.php, home.php
|
|
34
|
+
- **Structural partials**: header.php, footer.php, sidebar.php
|
|
35
|
+
- **Template parts**: anything in template-parts/ or patterns/
|
|
36
|
+
- **Functions**: functions.php (and any files it includes/requires)
|
|
37
|
+
- **Styles**: style.css, any CSS/SCSS files
|
|
38
|
+
- **Config**: theme.json if present (block theme configuration)
|
|
39
|
+
|
|
40
|
+
Print the inventory before proceeding.
|
|
41
|
+
|
|
42
|
+
## Step 3 — Analyse functions.php
|
|
43
|
+
|
|
44
|
+
Read functions.php thoroughly. Extract:
|
|
45
|
+
|
|
46
|
+
- **Custom post types** registered via `register_post_type()` → these become Laravel models
|
|
47
|
+
- **Taxonomies** registered via `register_taxonomy()` → these become models or enums
|
|
48
|
+
- **Navigation menus** registered via `register_nav_menus()` → these define nav structure
|
|
49
|
+
- **Widget areas** registered via `register_sidebar()` → note for layout
|
|
50
|
+
- **Enqueued scripts/styles** via `wp_enqueue_script/style()` → note any JS dependencies
|
|
51
|
+
- **Custom image sizes** via `add_image_size()` → note for media handling
|
|
52
|
+
- **Theme supports** via `add_theme_support()` → note post formats, thumbnails, etc.
|
|
53
|
+
- **Shortcodes** via `add_shortcode()` → these become Blade includes or components
|
|
54
|
+
- **AJAX handlers** via `wp_ajax_*` → these become API routes
|
|
55
|
+
- **Any included files** → read those too
|
|
56
|
+
|
|
57
|
+
## Step 4 — Plan the Laravel Structure
|
|
58
|
+
|
|
59
|
+
Before calling any Stellify tools, plan:
|
|
60
|
+
|
|
61
|
+
- **Models:** One per post type (Post, Page, plus any custom types from Step 3)
|
|
62
|
+
- **Controllers:** One per model with index/show actions
|
|
63
|
+
- **Routes:** Match WordPress URL structure (archive at `/`, singles at `/{slug}`, taxonomies at `/category/{slug}`)
|
|
64
|
+
- **Views:** Mirror WordPress template hierarchy — layout from header+footer, index/show views per post type, partials from template-parts
|
|
65
|
+
- **Migrations (Mode B only):** One per model with fields from WordPress core + any custom fields found in templates
|
|
66
|
+
|
|
67
|
+
## Step 5 — Build in Stellify (Order of Operations)
|
|
68
|
+
|
|
69
|
+
Execute the plan using Stellify MCP tools in this order. This order matters — parent records must exist before children.
|
|
70
|
+
|
|
71
|
+
### 5a. Create Models, Migrations & Controllers
|
|
72
|
+
|
|
73
|
+
Use the `create_resources` tool with `api: false` to generate models and controllers together. The `api: false` flag ensures controller methods return data arrays (for Blade views) rather than JSON responses.
|
|
74
|
+
|
|
75
|
+
**If Mode A (connect to existing WordPress database):**
|
|
76
|
+
|
|
77
|
+
Create resources that map to the existing WordPress tables. No migrations. Set the model's table and primary key to match WordPress.
|
|
78
|
+
|
|
79
|
+
```php
|
|
80
|
+
// Example Post model
|
|
81
|
+
class Post extends Model {
|
|
82
|
+
protected $table = 'wp_posts';
|
|
83
|
+
protected $primaryKey = 'ID';
|
|
84
|
+
|
|
85
|
+
// Scope to only published posts (not revisions, drafts, etc.)
|
|
86
|
+
public function scopePublished($query) {
|
|
87
|
+
return $query->where('post_status', 'publish')
|
|
88
|
+
->where('post_type', 'post');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Key WordPress table mappings:
|
|
94
|
+
- Posts & Pages → `wp_posts` (distinguished by `post_type` column: 'post', 'page', or custom types)
|
|
95
|
+
- Categories & Tags → `wp_terms` + `wp_term_taxonomy` + `wp_term_relationships`
|
|
96
|
+
- Users → `wp_users`
|
|
97
|
+
- Post meta / custom fields → `wp_postmeta`
|
|
98
|
+
- Comments → `wp_comments`
|
|
99
|
+
- Navigation menus → `wp_posts` with `post_type = 'nav_menu_item'` + `wp_terms` with taxonomy `nav_menu`
|
|
100
|
+
|
|
101
|
+
In Blade templates, use WordPress column names:
|
|
102
|
+
- `$post->post_title` (not `$post->title`)
|
|
103
|
+
- `$post->post_content` (not `$post->content`)
|
|
104
|
+
- `$post->post_excerpt` (not `$post->excerpt`)
|
|
105
|
+
- `$post->post_name` (this is the slug)
|
|
106
|
+
- `$post->post_date` (not `$post->published_at`)
|
|
107
|
+
- `$post->post_status` (not `$post->status`)
|
|
108
|
+
|
|
109
|
+
**If Mode B (fresh database):**
|
|
110
|
+
|
|
111
|
+
Create new resources with clean Laravel migrations for each model identified in Step 4. Use clean column names (`title`, `slug`, `content`, `excerpt`, `status`, `published_at`, etc.). After the import is complete, suggest a data migration SQL query or artisan command that maps data from the WordPress tables to the new schema.
|
|
112
|
+
|
|
113
|
+
### 5b. Refine Controller Methods
|
|
114
|
+
|
|
115
|
+
`create_resources` (Step 5a) scaffolds controllers automatically. Review and refine the generated methods to ensure they query the right data. Controller methods in Stellify return data arrays — Stellify automatically merges these into the Blade view context. For example:
|
|
116
|
+
|
|
117
|
+
```php
|
|
118
|
+
// CORRECT for Stellify — returns a data array, Stellify handles the view binding
|
|
119
|
+
public function index(): array
|
|
120
|
+
{
|
|
121
|
+
return ['posts' => Post::where('status', 'published')->latest('published_at')->paginate(10)];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public function show(Post $post): array
|
|
125
|
+
{
|
|
126
|
+
return ['post' => $post];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// WRONG — do not return JSON
|
|
130
|
+
public function index()
|
|
131
|
+
{
|
|
132
|
+
return Post::where('status', 'published')->get();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// WRONG — do not call view() directly, Stellify handles this
|
|
136
|
+
public function index()
|
|
137
|
+
{
|
|
138
|
+
return view('posts.index', compact('posts'));
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Mode A note:** WordPress stores posts, pages, and custom post types all in `wp_posts`, distinguished by the `post_type` column. Controllers must scope queries accordingly — e.g. `Post::where('post_type', 'post')->where('post_status', 'publish')`. It also stores revisions and auto-drafts in the same table, so always filter by `post_status`.
|
|
143
|
+
|
|
144
|
+
### 5c. Create Routes
|
|
145
|
+
Create route entries mapping URLs to controller methods.
|
|
146
|
+
|
|
147
|
+
### 5d. Create the Layout (layouts/app.blade.php)
|
|
148
|
+
Read header.php and footer.php. Create a single Blade layout that combines:
|
|
149
|
+
- The HTML structure from header.php (doctype, head, opening body, nav)
|
|
150
|
+
- `@yield('content')` for page content
|
|
151
|
+
- The structure from footer.php (closing elements, footer content)
|
|
152
|
+
|
|
153
|
+
Translate WordPress functions to Blade:
|
|
154
|
+
- `wp_head()` → `@vite(['resources/css/app.css', 'resources/js/app.js'])` and `<meta>` tags
|
|
155
|
+
- `wp_footer()` → nothing needed (Vite handles it)
|
|
156
|
+
- `wp_nav_menu()` → `@include('partials.nav')`
|
|
157
|
+
- `bloginfo('name')` → `{{ config('app.name') }}`
|
|
158
|
+
- `bloginfo('description')` → `{{ config('app.description', '') }}`
|
|
159
|
+
- `body_class()` → appropriate Tailwind classes
|
|
160
|
+
- `language_attributes()` → `lang="{{ str_replace('_', '-', app()->getLocale()) }}"`
|
|
161
|
+
|
|
162
|
+
### 5e. Create Blade Views
|
|
163
|
+
For each WordPress template file, read the PHP and create the equivalent Blade view using `html_to_elements`. **All views use `@extends('layouts.app')` and `@section('content')`.**
|
|
164
|
+
|
|
165
|
+
**⚠️ Multiple Root Elements:** When converting HTML with multiple root-level elements (e.g., `<header>`, `<main>`, `<footer>`), only the first root element gets attached to the route. Make separate `html_to_elements` calls for each root element.
|
|
166
|
+
|
|
167
|
+
**WordPress → Blade translation guide:**
|
|
168
|
+
|
|
169
|
+
Mode A uses WordPress column names; Mode B uses clean Laravel names.
|
|
170
|
+
|
|
171
|
+
**IMPORTANT:** Inside `@foreach` loops, use `$item` as the loop variable. This matches the Stellify assembler's expectations for `textField`, `hrefField`, `srcField` attributes.
|
|
172
|
+
|
|
173
|
+
| WordPress | Blade (Mode A) | Blade (Mode B) | Stellify Attribute |
|
|
174
|
+
|-----------|----------------|----------------|-------------------|
|
|
175
|
+
| `the_title()` | `{{ $item->post_title }}` | `{{ $item->title }}` | `textField: "title"` |
|
|
176
|
+
| `the_content()` | `{!! $item->post_content !!}` | `{!! $item->content !!}` | statement with code |
|
|
177
|
+
| `the_excerpt()` | `{{ $item->post_excerpt }}` | `{{ $item->excerpt }}` | `textField: "excerpt"` |
|
|
178
|
+
| `the_permalink()` | `{{ route('posts.show', $item->post_name) }}` | `{{ route('posts.show', $item->slug) }}` | `hrefExpression: "..."` |
|
|
179
|
+
| `the_date()` | `{{ $item->post_date->format('d M Y') }}` | `{{ $item->published_at->format('d M Y') }}` | statement with code |
|
|
180
|
+
| `the_author()` | `{{ $item->author->display_name }}` | `{{ $item->author->name }}` | statement with code |
|
|
181
|
+
| `get_template_part()` | `@include('partials.post-card', ['post' => $item])` | same | s-directive |
|
|
182
|
+
| `have_posts()` | `@foreach($posts as $item)` | same | s-directive pair |
|
|
183
|
+
|
|
184
|
+
**Conditional Rendering with `s-directive`:**
|
|
185
|
+
|
|
186
|
+
WordPress blocks that render conditionally (like `<!-- wp:post-featured-image -->`) use `s-directive` elements. See the MCP tool documentation for the sibling pattern — create an opening directive, content elements, then a closing directive as siblings.
|
|
187
|
+
|
|
188
|
+
Common WordPress conditionals to convert (use `$item` inside loops):
|
|
189
|
+
- `<!-- wp:post-featured-image -->` → `@if($item->featured_image)` ... `@endif`
|
|
190
|
+
- `<!-- wp:post-excerpt -->` → `@if($item->post_excerpt)` ... `@endif`
|
|
191
|
+
- `<!-- wp:post-comments -->` → `@if($item->comments->count() > 0)` ... `@endif`
|
|
192
|
+
- `<!-- wp:query-no-results -->` → `@if($posts->isEmpty())` ... `@endif` (outside loop)
|
|
193
|
+
|
|
194
|
+
**Iteration (The Loop):**
|
|
195
|
+
|
|
196
|
+
WordPress's "The Loop" (`have_posts() / the_post()`) maps to `@foreach` directives. Use `s-directive` elements for the opening `@foreach` and closing `@endforeach`.
|
|
197
|
+
|
|
198
|
+
**IMPORTANT - Loop Variable:** The default loop variable is `$item`. When creating elements inside a loop:
|
|
199
|
+
1. Do NOT pass raw Blade syntax to `html_to_elements` — it will be stored literally
|
|
200
|
+
2. Create clean HTML first, then use `update_element` to add dynamic attributes:
|
|
201
|
+
- `textField: "title"` → outputs `{{ $item->title }}`
|
|
202
|
+
- `hrefField: "slug"` → outputs `href="{{ $item->slug }}"`
|
|
203
|
+
- `srcField: "featured_image"` → outputs `src="{{ $item->featured_image }}"`
|
|
204
|
+
3. For complex expressions (route helpers, method calls), use:
|
|
205
|
+
- `hrefExpression: "{{ route('posts.show', $item->slug) }}"`
|
|
206
|
+
- `srcExpression: "{{ $item->featured_image }}"`
|
|
207
|
+
4. For text with Blade code, create a statement with `create_statement_with_code`, then add its UUID to the element's `statements` array via `update_element`
|
|
208
|
+
|
|
209
|
+
Example loop structure using `s-directive` siblings:
|
|
210
|
+
```
|
|
211
|
+
1. s-directive with statement: "@foreach($posts as $item)"
|
|
212
|
+
2. article element (content to repeat)
|
|
213
|
+
3. s-directive with statement: "@endforeach"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### 5f. Create Partials & Components
|
|
217
|
+
Convert template-parts/ files into Blade partials using `@include`. Static partials receive data via the second argument: `@include('partials.post-card', ['post' => $post])`. If a template part relies on JavaScript for interactivity (e.g. a slider, a filterable gallery, a live search form), create a Vue component instead and ensure it is registered and mounted in `app.js`.
|
|
218
|
+
|
|
219
|
+
### 5g. Style with Tailwind
|
|
220
|
+
Do not try to port WordPress CSS. Read the visual intent from the theme's CSS/theme.json and apply Tailwind utility classes directly in the Blade templates. For example:
|
|
221
|
+
- WordPress container → `<div class="max-w-4xl mx-auto px-4">`
|
|
222
|
+
- WordPress navigation → `<nav class="flex items-center gap-6">`
|
|
223
|
+
- WordPress post grid → `<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">`
|
|
224
|
+
|
|
225
|
+
## Step 6 — Review (Optional)
|
|
226
|
+
|
|
227
|
+
If requested, summarize what was created and note any WordPress features that couldn't be mapped.
|
|
228
|
+
|
|
229
|
+
## Important Rules
|
|
230
|
+
|
|
231
|
+
- **Blade for SSR, Vue for interactivity.** If the WordPress source is server-rendered PHP (posts, pages, archives, menus, layouts), create Blade templates using `@extends`, `@section`, `@include`, `@foreach`, `{{ }}` and `{!! !!}`. If the WordPress source uses JavaScript for interactivity (AJAX, dynamic filtering, sliders, modals, live search, infinite scroll), create a Vue component. When creating Vue components, ensure they are registered and mounted in `app.js` via `createApp` — do not just import them.
|
|
232
|
+
- **Do not ask questions** except the two permitted in Step 1 (which theme, and which database mode). Make reasonable decisions and document them.
|
|
233
|
+
- **Work file by file** — read a WordPress file, create the Stellify equivalent, move to the next.
|
|
234
|
+
- **Show progress** — print what you're reading and what you're creating as you go.
|
|
235
|
+
- **Handle block themes** — if theme.json exists and templates are in HTML with block markup (`<!-- wp:xxx -->`), parse the block structure rather than traditional PHP templates. The output is still Blade (or Vue where the block is interactive).
|
|
236
|
+
- **Style with Tailwind** — do not port WordPress CSS. Interpret the visual intent and use Tailwind utilities.
|
|
237
|
+
- **Be pragmatic** — focus on the core templates that define the site's main pages. Skip hyper-specific template variations unless they're clearly important.
|
|
238
|
+
- **Content handling depends on database mode** — In Mode A (existing DB), the content is already there via the WordPress tables. In Mode B (fresh DB), content is separate and can be migrated later.
|