@stellisoft/stellify-mcp 0.1.36 → 0.1.38

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/dist/index.js CHANGED
@@ -600,16 +600,24 @@ Use the returned UUID with html_to_elements (page parameter) or get_route for fu
600
600
  **For elements inside @foreach loops (SSR/Blade):**
601
601
  Use these attributes to reference the loop variable (defaults to \`$item\`):
602
602
  - \`textField\`: Field name for text content → outputs \`{{ $item->fieldName }}\`
603
- - \`hrefField\`: Field name for href → outputs \`href="{{ $item->fieldName }}"\`
603
+ - \`hrefField\`: Field name for href → outputs \`href="{{ $item->fieldName }}"\` (field value ONLY, no prefix)
604
604
  - \`srcField\`: Field name for src → outputs \`src="{{ $item->fieldName }}"\`
605
605
 
606
+ **For hrefs with path prefixes (IMPORTANT):**
607
+ \`hrefField\` outputs ONLY the field value. There is NO \`hrefPrefix\` attribute.
608
+ For links like \`/post/slug-here\`, you MUST use \`hrefExpression\`:
609
+ - \`hrefExpression: "/post/{{ $item->slug }}"\` → outputs \`href="/post/{{ $item->slug }}"\`
610
+ - \`hrefExpression: "/category/{{ $item->slug }}"\` → outputs \`href="/category/{{ $item->slug }}"\`
611
+
606
612
  **For complex Blade expressions in attributes:**
607
- Use expression attributes when you need more than simple field access (e.g., route helpers, method calls):
613
+ Use expression attributes when you need more than simple field access:
608
614
  - \`hrefExpression\`: Blade expression for href → outputs \`href="..."\` with the expression
609
615
  - \`srcExpression\`: Blade expression for src → outputs \`src="..."\` with the expression
610
616
  - \`altExpression\`: Blade expression for alt → outputs \`alt="..."\` with the expression
611
617
 
612
- Example: \`hrefExpression: "{{ route('posts.show', $item->slug) }}"\`
618
+ Examples:
619
+ - Path prefix: \`hrefExpression: "/post/{{ $item->slug }}"\`
620
+ - Route helper: \`hrefExpression: "{{ route('posts.show', $item->slug) }}"\`
613
621
 
614
622
  **For Blade text content:**
615
623
  Use the \`statements\` array with statement UUIDs containing Blade code. The statement's \`code\` property will be output directly for Blade to evaluate.`,
@@ -739,10 +747,14 @@ For SSR/Blade pages, do NOT pass raw Blade expressions in text or attributes. Th
739
747
  1. **For static HTML:** Pass clean HTML without Blade syntax, then use \`update_element\` to add dynamic behavior
740
748
  2. **For loop content:** After creating elements, use \`update_element\` with:
741
749
  - \`textField\`, \`hrefField\`, \`srcField\` for simple field access (outputs \`{{ $item->field }}\`)
742
- - \`hrefExpression\`, \`srcExpression\`, \`altExpression\` for complex expressions
750
+ - \`hrefExpression\`, \`srcExpression\`, \`altExpression\` for paths with prefixes or complex expressions
743
751
  - \`statements\` array with statement UUIDs for text content with Blade code
744
752
  3. **For conditionals:** Use \`s-directive\` elements as siblings (see update_element docs)
745
753
 
754
+ **IMPORTANT - Links with path prefixes:**
755
+ \`hrefField\` outputs ONLY the field value with no prefix. There is NO \`hrefPrefix\` attribute.
756
+ For links like \`/post/my-slug\`, use \`hrefExpression: "/post/{{ $item->slug }}"\` instead.
757
+
746
758
  **Loop variable:** Inside \`@foreach\` loops created with \`s-directive\`, the default loop variable is \`$item\`. Use \`textField: "title"\` to output \`{{ $item->title }}\`.
747
759
 
748
760
  Prefer SVG icons over emoji (encoding issues).`,
@@ -1497,7 +1509,7 @@ const SERVER_INSTRUCTIONS = `Stellify is a coding platform where code is stored
1497
1509
  // Create MCP server
1498
1510
  const server = new Server({
1499
1511
  name: 'stellify-mcp',
1500
- version: '0.1.36',
1512
+ version: '0.1.38',
1501
1513
  }, {
1502
1514
  capabilities: {
1503
1515
  tools: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stellisoft/stellify-mcp",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "mcpName": "io.github.MattStellisoft/stellify-mcp",
5
5
  "description": "MCP server for Stellify - AI-native code generation platform",
6
6
  "main": "dist/index.js",
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.36",
9
+ "version": "0.1.38",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@stellisoft/stellify-mcp",
14
- "version": "0.1.36",
14
+ "version": "0.1.38",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },
@@ -1,238 +1,317 @@
1
1
  ---
2
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."
3
+ description: "Use this skill when importing WordPress sites into Stellify. Invoke with /wordpress-import to analyse a WordPress project and generate resources for custom post types, taxonomies, and fields. Assumes standard WordPress models (Post, Page, Category, Tag, User, Media) are already scaffolded via Stellify."
4
4
  license: MIT
5
+ icon: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><title>wordpress</title><path d="M3.42,12C3.42,10.76 3.69,9.58 4.16,8.5L8.26,19.72C5.39,18.33 3.42,15.4 3.42,12M17.79,11.57C17.79,12.3 17.5,13.15 17.14,14.34L16.28,17.2L13.18,8L14.16,7.9C14.63,7.84 14.57,7.16 14.11,7.19C14.11,7.19 12.72,7.3 11.82,7.3L9.56,7.19C9.1,7.16 9.05,7.87 9.5,7.9L10.41,8L11.75,11.64L9.87,17.27L6.74,8L7.73,7.9C8.19,7.84 8.13,7.16 7.67,7.19C7.67,7.19 6.28,7.3 5.38,7.3L4.83,7.29C6.37,4.96 9,3.42 12,3.42C14.23,3.42 16.27,4.28 17.79,5.67H17.68C16.84,5.67 16.24,6.4 16.24,7.19C16.24,7.9 16.65,8.5 17.08,9.2C17.41,9.77 17.79,10.5 17.79,11.57M12.15,12.75L14.79,19.97L14.85,20.09C13.96,20.41 13,20.58 12,20.58C11.16,20.58 10.35,20.46 9.58,20.23L12.15,12.75M19.53,7.88C20.2,9.11 20.58,10.5 20.58,12C20.58,15.16 18.86,17.93 16.31,19.41L18.93,11.84C19.42,10.62 19.59,9.64 19.59,8.77L19.53,7.88M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,21.54C17.26,21.54 21.54,17.26 21.54,12C21.54,6.74 17.26,2.46 12,2.46C6.74,2.46 2.46,6.74 2.46,12C2.46,17.26 6.74,21.54 12,21.54Z" /></svg>
5
6
  metadata:
6
7
  author: stellify
7
8
  ---
8
9
 
9
10
  # WordPress Import Skill
10
11
 
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
+ You are importing a WordPress site into Stellify. This skill focuses on **analysing what's custom** about the WordPress site and generating only those elements. Standard WordPress functionality (Posts, Pages, Categories, Tags, Users, Media) is handled by Stellify's WordPress scaffold.
12
13
 
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
+ ## Step 1Verify WordPress Scaffold
14
15
 
15
- ## Step 1 Identify the Active Theme
16
+ Before proceeding, check that the Stellify WordPress scaffold has been run. Look for the presence of standard WordPress models in the Laravel project:
16
17
 
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
+ - `app/Models/Post.php`
19
+ - `app/Models/Page.php`
20
+ - `app/Models/Category.php`
21
+ - `app/Models/Tag.php`
18
22
 
19
- Before proceeding, ask the user:
23
+ If these don't exist, instruct the user:
20
24
 
21
- > **How do you want to handle the database?**
25
+ > **WordPress scaffold required**
22
26
  >
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.
27
+ > Before importing, you need to run the Stellify WordPress scaffold. This creates the standard models, controllers, and routes that every WordPress site uses.
24
28
  >
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.
29
+ > Run: `[scaffold command TBD]`
30
+ >
31
+ > Let me know when this is complete.
41
32
 
42
- ## Step 3 Analyse functions.php
33
+ Do not proceed until the scaffold is in place.
43
34
 
44
- Read functions.php thoroughly. Extract:
35
+ ## Step 2 — Analyse the WordPress Project
45
36
 
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
37
+ Perform a comprehensive analysis of the WordPress site to understand what's custom. This produces a report before any code generation happens.
56
38
 
57
- ## Step 4 — Plan the Laravel Structure
39
+ ### 2a. Identify the Active Theme
58
40
 
59
- Before calling any Stellify tools, plan:
41
+ 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.
60
42
 
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
43
+ Note whether it's a classic PHP theme or a block theme (presence of `theme.json` and HTML templates).
44
+
45
+ ### 2b. Scan for Custom Post Types & Taxonomies
46
+
47
+ Read `functions.php` and any files it includes/requires. Extract:
48
+
49
+ - **Custom post types** via `register_post_type()` — capture name, labels, supports array, and any custom rewrite rules
50
+ - **Custom taxonomies** via `register_taxonomy()` — capture name, which post types it attaches to, hierarchical setting
51
+
52
+ Also check for CPT plugins that store configuration differently:
53
+ - Custom Post Type UI stores config in `wp_options` under `cptui_post_types` and `cptui_taxonomies`
54
+ - Pods, Toolset, and similar plugins have their own storage patterns
55
+
56
+ ### 2c. Scan Plugins Directory
57
+
58
+ List the contents of `wp-content/plugins/` and categorise:
59
+
60
+ **Functionality plugins** (affect what the site does — need attention):
61
+ - WooCommerce — e-commerce (major rebuild)
62
+ - Advanced Custom Fields / ACF Pro — custom fields
63
+ - Gravity Forms / Contact Form 7 / WPForms — forms
64
+ - The Events Calendar — event post type
65
+ - Custom plugin folders (not from wordpress.org)
66
+
67
+ **Low priority** (usually don't need porting):
68
+ - Yoast SEO / Rank Math — SEO meta (can note fields but not critical)
69
+ - Caching plugins (W3 Total Cache, WP Rocket)
70
+ - Security plugins (Wordfence, Sucuri)
71
+ - Backup plugins
72
+
73
+ **Builder plugins** (affect how templates work):
74
+ - Elementor / Elementor Pro
75
+ - WPBakery / Visual Composer
76
+ - Divi Builder
77
+ - Beaver Builder
78
+
79
+ Note: If a builder plugin is present, templates may be stored in the database rather than theme files.
80
+
81
+ ### 2d. Database Analysis
82
+
83
+ If database access is available (direct connection or SQL dump), run these diagnostic queries:
84
+
85
+ ```sql
86
+ -- Custom post types in use (beyond standard post/page/attachment)
87
+ SELECT post_type, COUNT(*) as count
88
+ FROM wp_posts
89
+ WHERE post_status = 'publish'
90
+ AND post_type NOT IN ('post', 'page', 'attachment', 'revision', 'nav_menu_item', 'wp_template', 'wp_template_part', 'wp_global_styles', 'wp_navigation')
91
+ GROUP BY post_type
92
+ ORDER BY count DESC;
93
+
94
+ -- Custom taxonomies in use (beyond standard category/post_tag)
95
+ SELECT taxonomy, COUNT(*) as term_count
96
+ FROM wp_term_taxonomy
97
+ WHERE taxonomy NOT IN ('category', 'post_tag', 'nav_menu', 'link_category', 'post_format', 'wp_theme', 'wp_template_part_area')
98
+ GROUP BY taxonomy
99
+ ORDER BY term_count DESC;
100
+
101
+ -- Custom meta keys (reveals ACF fields, custom meta)
102
+ -- Excludes WordPress internal keys (prefixed with _)
103
+ SELECT meta_key, COUNT(*) as usage_count
104
+ FROM wp_postmeta
105
+ WHERE meta_key NOT LIKE '\_%'
106
+ GROUP BY meta_key
107
+ ORDER BY usage_count DESC
108
+ LIMIT 30;
109
+
110
+ -- ACF field groups (if ACF is used)
111
+ SELECT post_title, post_name
112
+ FROM wp_posts
113
+ WHERE post_type = 'acf-field-group'
114
+ AND post_status = 'publish';
115
+
116
+ -- Shortcodes in content (indicates embedded functionality)
117
+ SELECT post_type, COUNT(*) as posts_with_shortcodes
118
+ FROM wp_posts
119
+ WHERE post_content REGEXP '\\[[a-zA-Z0-9_-]+.*\\]'
120
+ AND post_status = 'publish'
121
+ GROUP BY post_type;
122
+
123
+ -- Gutenberg blocks in use (for block themes)
124
+ SELECT
125
+ SUBSTRING_INDEX(SUBSTRING_INDEX(post_content, '<!-- wp:', -1), ' ', 1) as block_type,
126
+ COUNT(*) as usage_count
127
+ FROM wp_posts
128
+ WHERE post_content LIKE '%<!-- wp:%'
129
+ AND post_status = 'publish'
130
+ GROUP BY block_type
131
+ ORDER BY usage_count DESC
132
+ LIMIT 20;
133
+ ```
66
134
 
67
- ## Step 5 Build in Stellify (Order of Operations)
135
+ ### 2e. Produce Analysis Report
68
136
 
69
- Execute the plan using Stellify MCP tools in this order. This order matters — parent records must exist before children.
137
+ Present a structured summary before proceeding:
70
138
 
71
- ### 5a. Create Models, Migrations & Controllers
139
+ ```
140
+ ## WordPress Site Analysis
141
+
142
+ ### Theme
143
+ - Name: theme-name
144
+ - Type: Classic PHP / Block theme
145
+ - Template files: [count]
146
+
147
+ ### Content Summary
148
+ - Posts: [count]
149
+ - Pages: [count]
150
+ - [custom-type]: [count]
151
+
152
+ ### Custom Post Types
153
+ | Name | Slug | Supports | Count |
154
+ |------|------|----------|-------|
155
+ | Portfolio | project | title, editor, thumbnail | 34 |
156
+ | Testimonial | testimonial | title, editor | 12 |
157
+
158
+ ### Custom Taxonomies
159
+ | Name | Slug | Attached To | Hierarchical | Terms |
160
+ |------|------|-------------|--------------|-------|
161
+ | Project Type | project_type | project | Yes | 5 |
162
+
163
+ ### Custom Fields (non-standard meta keys)
164
+ | Key | Used By | Count |
165
+ |-----|---------|-------|
166
+ | project_client | project | 34 |
167
+ | project_url | project | 34 |
168
+ | testimonial_company | testimonial | 12 |
169
+
170
+ ### Plugins Requiring Attention
171
+ - Advanced Custom Fields Pro — field definitions above
172
+ - Contact Form 7 — 2 forms found
173
+
174
+ ### Content Flags
175
+ - 15 posts contain shortcodes (will need processing during content migration)
176
+ - Page builder detected: Elementor (some layouts stored in database)
177
+
178
+ ### Generation Plan
179
+ **Will generate:**
180
+ - Model: Project (custom post type)
181
+ - Model: Testimonial (custom post type)
182
+ - Model: ProjectType (custom taxonomy)
183
+ - Add fields to models: client, url, company
184
+
185
+ **Standard (already scaffolded):**
186
+ - Post, Page, Category, Tag, User, Media
187
+
188
+ **Manual attention needed:**
189
+ - Contact forms (2) — recreate in Laravel
190
+ - Shortcodes in content — process during migration
191
+ - Elementor layouts — extract design intent from theme templates
192
+ ```
72
193
 
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.
194
+ After presenting the report, ask:
74
195
 
75
- **If Mode A (connect to existing WordPress database):**
196
+ > Does this analysis look correct? Ready to proceed with generating the custom resources?
76
197
 
77
- Create resources that map to the existing WordPress tables. No migrations. Set the model's table and primary key to match WordPress.
198
+ ## Step 3 Generate Custom Resources
78
199
 
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
- ```
200
+ For each custom post type and taxonomy identified, create the necessary Laravel resources using Stellify MCP tools.
92
201
 
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`
202
+ ### 3a. Custom Post Type Models
100
203
 
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`)
204
+ For each custom post type, use `create_resources` with `api: false`:
108
205
 
109
- **If Mode B (fresh database):**
206
+ **Model requirements:**
207
+ - Table name matching the post type slug (e.g., `projects`, `testimonials`)
208
+ - Standard fields: `title`, `slug`, `content`, `excerpt`, `status`, `published_at`, `author_id`
209
+ - Custom fields identified from meta analysis
210
+ - `getRouteKeyName()` returning `'slug'` for URL-friendly routing
211
+ - Relationships to custom taxonomies
110
212
 
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.
213
+ **Migration requirements:**
214
+ - All standard fields plus custom fields
215
+ - Foreign keys to users table for author
216
+ - Indexes on slug, status, published_at
112
217
 
113
- ### 5b. Refine Controller Methods
218
+ **Controller requirements:**
219
+ - `index()` returning paginated published items
220
+ - `show($model)` returning single item
221
+ - Return arrays (Stellify convention), not views or JSON
114
222
 
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:
223
+ Example model structure:
116
224
 
117
225
  ```php
118
- // CORRECT for Stellify — returns a data array, Stellify handles the view binding
119
- public function index(): array
226
+ class Project extends Model
120
227
  {
121
- return ['posts' => Post::where('status', 'published')->latest('published_at')->paginate(10)];
122
- }
228
+ protected $fillable = [
229
+ 'title', 'slug', 'content', 'excerpt',
230
+ 'status', 'published_at', 'author_id',
231
+ 'client', 'url', // custom fields
232
+ ];
233
+
234
+ protected $casts = [
235
+ 'published_at' => 'datetime',
236
+ ];
237
+
238
+ public function getRouteKeyName(): string
239
+ {
240
+ return 'slug';
241
+ }
123
242
 
124
- public function show(Post $post): array
125
- {
126
- return ['post' => $post];
127
- }
243
+ public function author(): BelongsTo
244
+ {
245
+ return $this->belongsTo(User::class, 'author_id');
246
+ }
128
247
 
129
- // WRONG do not return JSON
130
- public function index()
131
- {
132
- return Post::where('status', 'published')->get();
133
- }
248
+ public function projectTypes(): BelongsToMany
249
+ {
250
+ return $this->belongsToMany(ProjectType::class);
251
+ }
134
252
 
135
- // WRONG — do not call view() directly, Stellify handles this
136
- public function index()
137
- {
138
- return view('posts.index', compact('posts'));
253
+ public function scopePublished($query)
254
+ {
255
+ return $query->where('status', 'published');
256
+ }
139
257
  }
140
258
  ```
141
259
 
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)
260
+ ### 3b. Custom Taxonomy Models
152
261
 
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()) }}"`
262
+ For each custom taxonomy:
161
263
 
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')`.**
264
+ **Model requirements:**
265
+ - Table name matching taxonomy slug (e.g., `project_types`)
266
+ - Fields: `name`, `slug`, `description`, `parent_id` (if hierarchical)
267
+ - `getRouteKeyName()` returning `'slug'`
268
+ - Relationship to associated post types via pivot table
164
269
 
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.
270
+ **Migration requirements:**
271
+ - Main taxonomy table
272
+ - Pivot table linking to each associated post type (e.g., `project_project_type`)
166
273
 
167
- **WordPress Blade translation guide:**
274
+ ### 3c. Routes
168
275
 
169
- Mode A uses WordPress column names; Mode B uses clean Laravel names.
276
+ Create routes following WordPress URL conventions where sensible:
170
277
 
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):**
278
+ ```php
279
+ // Custom post type archives and singles
280
+ Route::get('/projects', [ProjectController::class, 'index'])->name('projects.index');
281
+ Route::get('/projects/{project}', [ProjectController::class, 'show'])->name('projects.show');
195
282
 
196
- WordPress's "The Loop" (`have_posts() / the_post()`) maps to `@foreach` directives. Use `s-directive` elements for the opening `@foreach` and closing `@endforeach`.
283
+ // Custom taxonomy archives
284
+ Route::get('/project-type/{projectType}', [ProjectTypeController::class, 'show'])->name('project-types.show');
285
+ ```
197
286
 
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`
287
+ ## Step 4 Document Manual Tasks
208
288
 
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
- ```
289
+ Create a summary of items requiring manual attention:
215
290
 
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`.
291
+ ### Forms
292
+ List each form found with fields and purpose. The user will need to recreate these using Laravel form handling or a package like Laravel Livewire.
218
293
 
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">`
294
+ ### Shortcodes in Content
295
+ List shortcodes found and their apparent purpose. These will need to be:
296
+ 1. Processed during content migration (find/replace with HTML)
297
+ 2. Or converted to Blade components if they need to remain dynamic
224
298
 
225
- ## Step 6 — Review (Optional)
299
+ ### Plugin Functionality
300
+ For significant plugins (WooCommerce, membership plugins, booking systems), note that this functionality needs separate planning and is outside the scope of this import.
226
301
 
227
- If requested, summarize what was created and note any WordPress features that couldn't be mapped.
302
+ ### Content Migration
303
+ Note that content migration is a separate step. The user will need to:
304
+ 1. Export content from WordPress (WP-CLI, plugin, or direct SQL)
305
+ 2. Transform data to match new schema
306
+ 3. Import into Laravel database
307
+ 4. Process/clean embedded shortcodes
308
+ 5. Migrate media files and update URLs
228
309
 
229
310
  ## Important Rules
230
311
 
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.
312
+ - **Analysis first** Always complete the full analysis before generating any code
313
+ - **Only generate custom elements** Standard WordPress models come from the scaffold
314
+ - **Fresh database only** — This skill assumes a clean Laravel database, not connecting to WordPress tables
315
+ - **No template conversion** — Visual design/styling is handled by a separate skill
316
+ - **Document unknowns** — Flag anything that can't be automatically converted
317
+ - **Be pragmatic** — Focus on post types and taxonomies actually in use (have content), not just registered