$npx -y skills add webflow/webflow-skills --skill review-commentsReview open comment threads on a Webflow site and triage each one.
| 1 | Review open comment threads on a Webflow site and triage each one. |
| 2 | |
| 3 | **Input:** `$ARGUMENTS` — a site name (e.g. "Workhaus"), site ID (e.g. `6808fd4eff835ee3af009d6f`), or either with the `-reply` flag (e.g. `Workhaus -reply`). |
| 4 | |
| 5 | **Flags:** |
| 6 | - `-reply` — in addition to writing the report, post a bot reply to each non-open thread. Without this flag, the skill runs in report-only mode (read-only). |
| 7 | |
| 8 | Parse `$ARGUMENTS` at the start: strip `-reply` from the input to get the site identifier, and set `replyMode = true` if `-reply` was present, `false` otherwise. |
| 9 | |
| 10 | --- |
| 11 | |
| 12 | ## Step 1 — Resolve the site |
| 13 | |
| 14 | ### If the site identifier is empty or blank — cross-site comment survey |
| 15 | |
| 16 | 1. Call `data_sites_tool > list_sites` to get all sites. Page through until all are collected. |
| 17 | 2. For each site, call `data_comments_tool > list_comment_threads` with `isResolved: false` and `limit: 100`. Page through until all unresolved threads are collected. If the API does not support `isResolved` filtering, fetch all threads and filter client-side to `isResolved === false`. Process sites in batches of 20 (batch multiple actions in a single tool call). After each batch completes, log a progress line: `Batch {N}/{total} done (sites {start}–{end}): {summary of findings, e.g. "all 0 threads" or "SiteName has X threads, rest 0"}.` |
| 18 | 3. For each site, compute: |
| 19 | - `unresolvedCount` = total unresolved threads |
| 20 | - `newestDate` = the maximum `lastUpdated` value across all unresolved threads for that site (ISO → human-readable date, e.g. "Apr 3, 2026"). If no threads, show `—`. |
| 21 | - `oldestDate` = the minimum `lastUpdated` value across all unresolved threads for that site (ISO → human-readable date). If no threads, show `—`. |
| 22 | 4. Sort sites by `unresolvedCount` descending. Take the top 10. |
| 23 | 5. Above the table, show a heading line: `### Checked {totalSiteCount} sites — {sitesWithUnresolved} have unresolved comments.` |
| 24 | 6. Display a table in this format (link just the site name to `https://webflow.com/design/{siteId}`): |
| 25 | |
| 26 | ``` |
| 27 | | Site | Unresolved Comments | Newest / Oldest | |
| 28 | |------|---------------------|-----------------| |
| 29 | | [Site Name](https://webflow.com/design/{siteId}) ({siteId}) | {N} | {newestDate} / {oldestDate} | |
| 30 | ``` |
| 31 | |
| 32 | 7. After the table, tell the user: "Run `/webflow-mcp:review-comments <site name or ID>` to review a specific site." |
| 33 | 8. Write the survey output to a file: |
| 34 | - Ensure `comment-reviews/` exists (create with `mkdir comment-reviews` if not). |
| 35 | - Filename: `comment-reviews/triage-report-{YYYY-MM-DD-HH-MM}.md` using the current local time (zero-padded). |
| 36 | - File contents: a bold H1 title `# **Webflow Comment Review**`, then a blank line, then an H3 line `### Report created on: {human-readable date, time, and timezone, e.g. "April 16, 2026 at 10:39 AM PDT"}`, then a blank line, then the H3 heading line (`### Checked …`) and the full table from steps 5–6 above, in markdown. |
| 37 | - Log the path after writing, e.g. `Report written to comment-reviews/triage-report-2026-04-16-14-30.md`. |
| 38 | 9. **Stop** — do not proceed to Step 2. |
| 39 | |
| 40 | ### If the site identifier looks like a Webflow site ID (24-char hex), use it directly. |
| 41 | |
| 42 | ### Otherwise call `data_sites_tool > list_sites` and find the site whose `displayName` matches the identifier (case-insensitive). If no match, tell the user and stop. |
| 43 | |
| 44 | --- |
| 45 | |
| 46 | ## Step 2 — Fetch all open threads |
| 47 | |
| 48 | **Always make a fresh API call here — never reuse thread data from earlier in the conversation. The user may have added or resolved comments since the last run.** |
| 49 | |
| 50 | Call `data_comments_tool > list_comment_threads` with `isResolved: false` and `limit: 100`. Page through results until all threads are collected. |
| 51 | |
| 52 | Log: `Site: {displayName}` and `Found {N} open thread(s)`. |
| 53 | |
| 54 | --- |
| 55 | |
| 56 | ## Step 3 — Build page-level element map |
| 57 | |
| 58 | From the already-fetched thread list, build a frequency map of `elementId.element → Set<pageId>` across all threads. Any `elementId.element` that appears on **2 or more distinct pages** is almost certainly the page root/body element (a real element ID would be page-scoped; only shared structural roots repeat across pages). |
| 59 | |
| 60 | No API calls needed — this is a local computation on the thread data. |
| 61 | |
| 62 | --- |
| 63 | |
| 64 | ## Step 4 — Triage each thread |
| 65 | |
| 66 | For each thread: |
| 67 | |
| 68 | ### 4a — Fetch replies |
| 69 | |
| 70 | Call `data_comments_tool > list_comment_replies` for this thread. |
| 71 | |
| 72 | ### 4b — Dedup check |
| 73 | |
| 74 | Look for replies whose `content` includes the string `— 🤖 Comment Review Agent`. |
| 75 | |
| 76 | If found, note the most recent one (`lastAgentReply`). If no human reply exists with a `createdOn` after `lastAgentReply.createdOn`, **skip this thread** (increment skipped count, continue to next thread). |
| 77 | |
| 78 | ### 4c — Compute element context |
| 79 | |
| 80 | - `elementId` = `thread.elementId?.element` |
| 81 | - `isPageLevel` = `elementId` appears on 2 or more distinct `pageId`s in the frequency map from Step 3 |
| 82 | |
| 83 | ### 4d — Classify the thread |
| 84 | |
| 85 | Use the following criteria: |
| 86 | |
| 87 | **noise** — No real design or |