claude-plugin-wordpress-manager 2.12.2 → 2.13.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.
Files changed (29) hide show
  1. package/.claude-plugin/plugin.json +8 -3
  2. package/CHANGELOG.md +55 -0
  3. package/docs/GUIDE.md +240 -1
  4. package/docs/VALIDATION.md +341 -0
  5. package/docs/plans/2026-03-02-content-framework-architecture.md +612 -0
  6. package/docs/plans/2026-03-02-content-framework-strategic-reflections.md +228 -0
  7. package/docs/plans/2026-03-02-content-intelligence-phase2.md +560 -0
  8. package/docs/plans/2026-03-02-content-pipeline-phase1.md +456 -0
  9. package/docs/plans/2026-03-02-editorial-calendar-phase3.md +490 -0
  10. package/docs/validation/.gitkeep +0 -0
  11. package/docs/validation/dashboard.html +286 -0
  12. package/docs/validation/results.json +1705 -0
  13. package/package.json +12 -3
  14. package/scripts/run-validation.mjs +1132 -0
  15. package/servers/wp-rest-bridge/build/server.js +16 -5
  16. package/servers/wp-rest-bridge/build/tools/index.js +0 -9
  17. package/servers/wp-rest-bridge/build/tools/plugin-repository.js +23 -31
  18. package/servers/wp-rest-bridge/build/tools/schema.js +10 -2
  19. package/servers/wp-rest-bridge/build/tools/unified-content.js +10 -2
  20. package/servers/wp-rest-bridge/build/wordpress.d.ts +0 -3
  21. package/servers/wp-rest-bridge/build/wordpress.js +16 -98
  22. package/servers/wp-rest-bridge/package.json +1 -0
  23. package/skills/wp-analytics/SKILL.md +153 -0
  24. package/skills/wp-analytics/references/signals-feed-schema.md +417 -0
  25. package/skills/wp-content-pipeline/SKILL.md +461 -0
  26. package/skills/wp-content-pipeline/references/content-brief-schema.md +377 -0
  27. package/skills/wp-content-pipeline/references/site-config-schema.md +431 -0
  28. package/skills/wp-editorial-planner/SKILL.md +262 -0
  29. package/skills/wp-editorial-planner/references/editorial-schema.md +268 -0
@@ -0,0 +1,286 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>WP REST Bridge — Validation Dashboard</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0f172a; --surface: #1e293b; --border: #334155;
10
+ --text: #e2e8f0; --text-muted: #94a3b8; --text-dim: #64748b;
11
+ --passed: #22c55e; --failed: #ef4444; --error: #f97316;
12
+ --not-configured: #eab308; --skipped-write: #94a3b8; --untested: #d1d5db;
13
+ --skipped: #a78bfa; --accent: #3b82f6;
14
+ }
15
+ * { margin: 0; padding: 0; box-sizing: border-box; }
16
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; }
17
+ .container { max-width: 1400px; margin: 0 auto; padding: 24px; }
18
+
19
+ /* Header */
20
+ header { text-align: center; margin-bottom: 32px; }
21
+ header h1 { font-size: 1.75rem; font-weight: 700; margin-bottom: 8px; }
22
+ header .meta { color: var(--text-muted); font-size: 0.875rem; }
23
+ header .meta span { margin: 0 8px; }
24
+
25
+ /* Progress Bar */
26
+ .progress-wrap { background: var(--surface); border-radius: 12px; padding: 16px 20px; margin-bottom: 24px; border: 1px solid var(--border); }
27
+ .progress-label { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 0.875rem; color: var(--text-muted); }
28
+ .progress-bar { height: 12px; background: var(--border); border-radius: 6px; overflow: hidden; }
29
+ .progress-fill { height: 100%; background: linear-gradient(90deg, var(--passed), #16a34a); border-radius: 6px; transition: width 0.5s ease; }
30
+
31
+ /* Service Badges */
32
+ .services { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; justify-content: center; }
33
+ .badge { display: inline-flex; align-items: center; gap: 6px; padding: 6px 14px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; background: var(--surface); border: 1px solid var(--border); }
34
+ .badge .dot { width: 8px; height: 8px; border-radius: 50%; }
35
+ .badge.ok .dot { background: var(--passed); box-shadow: 0 0 6px var(--passed); }
36
+ .badge.no .dot { background: var(--failed); box-shadow: 0 0 6px var(--failed); }
37
+
38
+ /* Summary Cards */
39
+ .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 24px; }
40
+ .card { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 16px; text-align: center; }
41
+ .card .count { font-size: 2rem; font-weight: 700; line-height: 1; }
42
+ .card .label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; margin-top: 4px; letter-spacing: 0.05em; }
43
+ .card.passed .count { color: var(--passed); }
44
+ .card.failed .count { color: var(--failed); }
45
+ .card.error .count { color: var(--error); }
46
+ .card.not_configured .count { color: var(--not-configured); }
47
+ .card.skipped_write .count { color: var(--skipped-write); }
48
+ .card.untested .count { color: var(--untested); }
49
+
50
+ /* Filters */
51
+ .filters { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 20px; align-items: center; }
52
+ .filters input, .filters select { background: var(--surface); border: 1px solid var(--border); color: var(--text); padding: 8px 12px; border-radius: 8px; font-size: 0.875rem; }
53
+ .filters input { flex: 1; min-width: 200px; }
54
+ .filters select { min-width: 150px; }
55
+ .filters input:focus, .filters select:focus { outline: none; border-color: var(--accent); }
56
+
57
+ /* Table */
58
+ .table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 10px; }
59
+ table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
60
+ th { background: var(--surface); color: var(--text-muted); text-transform: uppercase; font-size: 0.7rem; letter-spacing: 0.08em; padding: 10px 12px; text-align: left; cursor: pointer; user-select: none; white-space: nowrap; border-bottom: 1px solid var(--border); }
61
+ th:hover { color: var(--text); }
62
+ th.sorted-asc::after { content: ' \u25B2'; }
63
+ th.sorted-desc::after { content: ' \u25BC'; }
64
+ td { padding: 8px 12px; border-bottom: 1px solid var(--border); }
65
+ tr:last-child td { border-bottom: none; }
66
+ tr:hover { background: rgba(59,130,246,0.05); }
67
+
68
+ .status-pill { display: inline-block; padding: 2px 10px; border-radius: 10px; font-size: 0.75rem; font-weight: 600; }
69
+ .status-pill.passed { background: rgba(34,197,94,0.15); color: var(--passed); }
70
+ .status-pill.failed { background: rgba(239,68,68,0.15); color: var(--failed); }
71
+ .status-pill.error { background: rgba(249,115,22,0.15); color: var(--error); }
72
+ .status-pill.not_configured { background: rgba(234,179,8,0.15); color: var(--not-configured); }
73
+ .status-pill.skipped_write { background: rgba(148,163,184,0.15); color: var(--skipped-write); }
74
+ .status-pill.skipped { background: rgba(167,139,250,0.15); color: var(--skipped); }
75
+ .status-pill.untested { background: rgba(209,213,219,0.1); color: var(--untested); }
76
+ .type-tag { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
77
+ .type-tag.read { color: var(--accent); }
78
+ .type-tag.write { color: var(--error); }
79
+ td.note { max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-dim); font-size: 0.8rem; }
80
+
81
+ /* Footer */
82
+ footer { text-align: center; margin-top: 32px; color: var(--text-dim); font-size: 0.75rem; }
83
+
84
+ /* Empty state */
85
+ .empty { text-align: center; padding: 80px 20px; color: var(--text-muted); }
86
+ .empty h2 { margin-bottom: 12px; }
87
+ .empty code { background: var(--surface); padding: 4px 10px; border-radius: 6px; font-size: 0.85rem; }
88
+
89
+ /* Responsive */
90
+ @media (max-width: 768px) {
91
+ .cards { grid-template-columns: repeat(3, 1fr); }
92
+ .filters { flex-direction: column; }
93
+ .filters input, .filters select { width: 100%; }
94
+ }
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <div class="container">
99
+ <header>
100
+ <h1>WP REST Bridge Validation Dashboard</h1>
101
+ <div class="meta" id="meta"></div>
102
+ </header>
103
+
104
+ <div id="content">
105
+ <div class="empty">
106
+ <h2>Loading...</h2>
107
+ </div>
108
+ </div>
109
+
110
+ <footer id="footer"></footer>
111
+ </div>
112
+
113
+ <script>
114
+ (async () => {
115
+ const contentEl = document.getElementById('content');
116
+ const metaEl = document.getElementById('meta');
117
+ const footerEl = document.getElementById('footer');
118
+
119
+ let data;
120
+ try {
121
+ const resp = await fetch('./results.json');
122
+ if (!resp.ok) throw new Error(resp.statusText);
123
+ data = await resp.json();
124
+ } catch {
125
+ contentEl.innerHTML = `
126
+ <div class="empty">
127
+ <h2>Nessun dato disponibile</h2>
128
+ <p style="margin-top:12px">Esegui il runner per generare i risultati:</p>
129
+ <p style="margin-top:8px"><code>node scripts/run-validation.mjs</code></p>
130
+ </div>`;
131
+ return;
132
+ }
133
+
134
+ const { meta, services, modules, summary } = data;
135
+ const allTools = Object.values(modules).flatMap(m => m.tools);
136
+
137
+ // Meta
138
+ metaEl.innerHTML = `
139
+ <span>Site: <strong>${meta.active_site}</strong></span>
140
+ <span>Tools: <strong>${meta.total_tools_registered}</strong></span>
141
+ <span>Runner: v${meta.runner_version}</span>
142
+ <span>${new Date(meta.generated_at).toLocaleString()}</span>`;
143
+
144
+ // Build all modules list and all statuses
145
+ const moduleNames = [...new Set(Object.keys(modules))].sort();
146
+ const statusNames = ['passed','failed','error','not_configured','skipped_write','skipped','untested'];
147
+
148
+ // Count tested (not untested, not skipped_write, not skipped)
149
+ const tested = allTools.filter(t => !['untested','skipped_write','skipped'].includes(t.status)).length;
150
+
151
+ let html = '';
152
+
153
+ // Progress bar
154
+ const pct = allTools.length ? Math.round((tested / allTools.length) * 100) : 0;
155
+ html += `
156
+ <div class="progress-wrap">
157
+ <div class="progress-label">
158
+ <span>Tested: ${tested} / ${allTools.length}</span>
159
+ <span>${pct}%</span>
160
+ </div>
161
+ <div class="progress-bar"><div class="progress-fill" style="width:${pct}%"></div></div>
162
+ </div>`;
163
+
164
+ // Service badges
165
+ html += '<div class="services">';
166
+ const svcLabels = { wordpress_core:'WP', woocommerce:'WC', multisite:'MS', mailchimp:'MC', buffer:'BUF', sendgrid:'SG', gsc:'GSC', ga4:'GA4', plausible:'PL', cwv:'CWV', slack:'Slack', linkedin:'LI', twitter:'TW' };
167
+ for (const [svc, info] of Object.entries(services)) {
168
+ const cls = info.configured ? 'ok' : 'no';
169
+ const label = svcLabels[svc] || svc;
170
+ html += `<span class="badge ${cls}" title="${svc}: ${info.reason || 'configured'}"><span class="dot"></span>${label}</span>`;
171
+ }
172
+ html += '</div>';
173
+
174
+ // Summary cards
175
+ html += '<div class="cards">';
176
+ for (const s of statusNames) {
177
+ const count = summary.by_status[s] || 0;
178
+ if (count === 0 && (s === 'skipped')) continue;
179
+ const label = s.replace(/_/g, ' ');
180
+ html += `<div class="card ${s}"><div class="count">${count}</div><div class="label">${label}</div></div>`;
181
+ }
182
+ html += '</div>';
183
+
184
+ // Filters
185
+ html += `
186
+ <div class="filters">
187
+ <input type="text" id="search" placeholder="Search tool name...">
188
+ <select id="filterModule"><option value="">All Modules</option>${moduleNames.map(m => `<option value="${m}">${m}</option>`).join('')}</select>
189
+ <select id="filterStatus"><option value="">All Statuses</option>${statusNames.map(s => `<option value="${s}">${s.replace(/_/g,' ')}</option>`).join('')}</select>
190
+ <select id="filterType"><option value="">All Types</option><option value="read">READ</option><option value="write">WRITE</option></select>
191
+ </div>`;
192
+
193
+ // Table
194
+ html += `
195
+ <div class="table-wrap">
196
+ <table>
197
+ <thead><tr>
198
+ <th data-col="module">Module</th>
199
+ <th data-col="name">Tool Name</th>
200
+ <th data-col="type">Type</th>
201
+ <th data-col="status">Status</th>
202
+ <th data-col="tested_at">Date</th>
203
+ <th data-col="duration_ms">Duration</th>
204
+ <th data-col="note">Note</th>
205
+ </tr></thead>
206
+ <tbody id="tbody"></tbody>
207
+ </table>
208
+ </div>`;
209
+
210
+ contentEl.innerHTML = html;
211
+ footerEl.innerHTML = `Generated: ${meta.generated_at} | Site: ${meta.active_site} | Runner v${meta.runner_version}`;
212
+
213
+ // Build flat row data
214
+ const rows = [];
215
+ for (const [mod, mdata] of Object.entries(modules)) {
216
+ for (const t of mdata.tools) {
217
+ rows.push({ module: mod, ...t, note: t.error_message || t.skip_reason || (t.duration_ms != null ? t.duration_ms + 'ms' : '') });
218
+ }
219
+ }
220
+
221
+ const tbody = document.getElementById('tbody');
222
+ const searchEl = document.getElementById('search');
223
+ const filterModuleEl = document.getElementById('filterModule');
224
+ const filterStatusEl = document.getElementById('filterStatus');
225
+ const filterTypeEl = document.getElementById('filterType');
226
+
227
+ let sortCol = 'module';
228
+ let sortDir = 'asc';
229
+
230
+ function render() {
231
+ const search = searchEl.value.toLowerCase();
232
+ const fModule = filterModuleEl.value;
233
+ const fStatus = filterStatusEl.value;
234
+ const fType = filterTypeEl.value;
235
+
236
+ let filtered = rows.filter(r => {
237
+ if (search && !r.name.toLowerCase().includes(search) && !r.module.toLowerCase().includes(search)) return false;
238
+ if (fModule && r.module !== fModule) return false;
239
+ if (fStatus && r.status !== fStatus) return false;
240
+ if (fType && r.type !== fType) return false;
241
+ return true;
242
+ });
243
+
244
+ // Sort
245
+ filtered.sort((a, b) => {
246
+ let va = a[sortCol] ?? '', vb = b[sortCol] ?? '';
247
+ if (sortCol === 'duration_ms') { va = va || 0; vb = vb || 0; }
248
+ if (typeof va === 'number') return sortDir === 'asc' ? va - vb : vb - va;
249
+ return sortDir === 'asc' ? String(va).localeCompare(String(vb)) : String(vb).localeCompare(String(va));
250
+ });
251
+
252
+ tbody.innerHTML = filtered.map(r => `
253
+ <tr>
254
+ <td>${r.module}</td>
255
+ <td><strong>${r.name}</strong></td>
256
+ <td><span class="type-tag ${r.type}">${r.type.toUpperCase()}</span></td>
257
+ <td><span class="status-pill ${r.status}">${r.status.replace(/_/g,' ')}</span></td>
258
+ <td style="color:var(--text-dim);font-size:0.8rem">${r.tested_at ? r.tested_at.split('T')[0] : ''}</td>
259
+ <td style="color:var(--text-dim);font-size:0.8rem">${r.duration_ms != null ? r.duration_ms + 'ms' : ''}</td>
260
+ <td class="note" title="${(r.note||'').replace(/"/g,'&quot;')}">${r.note || ''}</td>
261
+ </tr>`).join('');
262
+ }
263
+
264
+ // Filter listeners
265
+ searchEl.addEventListener('input', render);
266
+ filterModuleEl.addEventListener('change', render);
267
+ filterStatusEl.addEventListener('change', render);
268
+ filterTypeEl.addEventListener('change', render);
269
+
270
+ // Sort listeners
271
+ document.querySelectorAll('th[data-col]').forEach(th => {
272
+ th.addEventListener('click', () => {
273
+ const col = th.dataset.col;
274
+ if (sortCol === col) { sortDir = sortDir === 'asc' ? 'desc' : 'asc'; }
275
+ else { sortCol = col; sortDir = 'asc'; }
276
+ document.querySelectorAll('th').forEach(h => h.classList.remove('sorted-asc','sorted-desc'));
277
+ th.classList.add(sortDir === 'asc' ? 'sorted-asc' : 'sorted-desc');
278
+ render();
279
+ });
280
+ });
281
+
282
+ render();
283
+ })();
284
+ </script>
285
+ </body>
286
+ </html>