diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49dcf41..dd4de6c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,6 +26,9 @@ jobs: unzip -t "$zip" done + - name: Build release notes + run: ./scripts/release-notes.sh "${{ github.ref_name }}" > release-notes.md + - name: Create GitHub Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -33,4 +36,4 @@ jobs: gh release create "${{ github.ref_name }}" \ dist/skills/*.zip \ --title "${{ github.ref_name }}" \ - --generate-notes + --notes-file release-notes.md diff --git a/AGENTS.md b/AGENTS.md index 14ccf68..c8df831 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Project Overview -Public skills repository for Gravity products. Pure content repo — no build tooling or package manager required. Skills are distributed via GitHub Releases (zips). The `@gravity/skills` CLI lives in the Gravity Monorepo. +Public skills repository for Gravity products. Pure content repo — no build tooling or package manager required. Skills are distributed via the [Vercel Skills CLI](https://github.com/vercel-labs/skills) (`npx skills add gravityforms/gravityskills`) and via GitHub Releases (zips). ## Critical Warnings @@ -29,6 +29,18 @@ Public skills repository for Gravity products. Pure content repo — no build to |---------|----------|---------| | GitHub Releases (zips) | `.github/workflows/release.yml` | `v*` tag push | +**Tag only after merge to `main`.** The release workflow fires on `v*` tag push: it packs the skill zips, extracts the matching `change_log.txt` section via `scripts/release-notes.sh`, and creates the GitHub Release with that section as the notes. Never tag from a feature branch. + +## Changelog + +Record user-facing changes in `change_log.txt` (Gravity Forms core format). Add entries to the top version section in the same PR as the change — there is no fragment tooling. + +- Header: `### | ` (e.g. `### 1.0.2 | 2026-06-18`), newest version on top. +- One change per `- ` bullet, prefixed with the skill name it applies to (e.g. `gravity-forms-abilities: ...`); use `Repository:` for repo-wide changes. +- After the prefix, write in **present tense** ending in a period (e.g. `Add ...`, `Fix ...`, `Remove ...`, `Update ...`). +- Alpha-sort the full bullet lines within a version. This groups them by skill (prefix sorts first), then by verb. +- Skip internal-only changes (refactors, tests, CI, docs). + ## Skills Directory Structure Skills live in `skills/` and must follow the Agent Skills spec: diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/README.md b/README.md index 9dd5ece..f7e55fe 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Skills follow the [Agent Skills](https://agentskills.io/) format. ### Skills CLI -> Until the `@gravity/skills` CLI is published, use the [Vercel Skills CLI](https://github.com/vercel-labs/skills): +Install all skills with the [Vercel Skills CLI](https://github.com/vercel-labs/skills): ```bash npx skills add gravityforms/gravityskills diff --git a/change_log.txt b/change_log.txt new file mode 100644 index 0000000..fd8151e --- /dev/null +++ b/change_log.txt @@ -0,0 +1,11 @@ +### 1.0.2 | 2026-06-18 +- gravity-forms-abilities: Add a warning against deprecated Ready Classes in favor of the layout grid. +- gravity-forms-abilities: Add documentation for the 100-entry bulk-delete cap and the count/delete loop. +- gravity-forms-abilities: Add pricing field guidance covering product, option, quantity, shipping, and total inputTypes and submission formats. +- gravity-forms-abilities: Update the access model to the per-tool allowlist (Enable MCP plus individual tool checkboxes), replacing the read/write toggles. + +### 1.0.1 | 2026-05-13 +- gravity-forms-abilities: Add the skill — workflow guidance for Gravity Forms abilities via the WordPress Abilities API (MCP). + +### 1.0.0 | 2026-05-13 +- Repository: Add the public Gravity Skills repository framework. diff --git a/scripts/release-notes.sh b/scripts/release-notes.sh new file mode 100755 index 0000000..18ec461 --- /dev/null +++ b/scripts/release-notes.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# Prints the change_log.txt section for a given version, for use as GitHub Release notes. +# Usage: ./scripts/release-notes.sh 1.0.2 (leading "v" is stripped) +# +# Self-check: ./scripts/release-notes.sh --self-check + +set -euo pipefail + +CHANGELOG="${CHANGELOG:-change_log.txt}" + +extract() { + local version="${1#v}" file="$2" + # Print the bullet lines under "### " up to the next "### " header. + awk -v ver="$version" ' + /^### / { + if (found) exit + # Header is "### " optionally followed by " | ". + hdr = $2 + if (hdr == ver) { found = 1; next } + } + found && NF { print } + ' "$file" +} + +if [[ "${1:-}" == "--self-check" ]]; then + tmp="$(mktemp)" + cat >"$tmp" <<-'EOF' + ### 1.0.2 | 2026-06-18 + - Add b. + - Add a. + + ### 1.0.1 | 2026-05-13 + - Add the skill. + EOF + got="$(extract v1.0.2 "$tmp")" + want=$'- Add b.\n- Add a.' + [[ "$got" == "$want" ]] || { echo "FAIL latest: got [$got]" >&2; exit 1; } + got="$(extract 1.0.1 "$tmp")" + [[ "$got" == "- Add the skill." ]] || { echo "FAIL middle: got [$got]" >&2; exit 1; } + [[ -z "$(extract 9.9.9 "$tmp")" ]] || { echo "FAIL missing version not empty" >&2; exit 1; } + rm -f "$tmp" + echo "self-check passed" + exit 0 +fi + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +notes="$(extract "$1" "$CHANGELOG")" +if [[ -z "$notes" ]]; then + echo "Error: no $CHANGELOG section found for version ${1#v}" >&2 + exit 1 +fi +printf '%s\n' "$notes" diff --git a/skills/gravity-forms-abilities/SKILL.md b/skills/gravity-forms-abilities/SKILL.md index ea10296..0deeb00 100644 --- a/skills/gravity-forms-abilities/SKILL.md +++ b/skills/gravity-forms-abilities/SKILL.md @@ -34,18 +34,18 @@ All ability names use format `gravityforms/{category}-{action}`. ## Access Modes -The MCP settings page (GF Settings → MCP) controls two toggles: +The MCP settings page (GF Settings → MCP) gates access with a master switch plus a per-tool allowlist: -1. **Enable MCP** (default: off) — Gates all ability registration. When off, no `gravityforms/*` abilities appear. -2. **Write Access** (default: off) — When off, only read-only abilities are registered (12 of 25). Write abilities (create, update, delete, submit, send, add) are filtered out at registration time. +1. **Enable MCP** (default: off) — Master switch. When off, no `gravityforms/*` ability is exposed or callable, regardless of the per-tool checkboxes. +2. **Per-tool checkboxes** (default: every tool off) — When MCP is on, each ability is individually enabled or disabled via a checkbox, grouped into **Read-only Tools** and **Write & Destructive Tools** (each with a select/deselect-all control). A tool is exposed and callable only when its box is checked. **There is no blanket "read access" — read-only tools are off by default too** and must be enabled individually. -**Read-only abilities** (always available when MCP is enabled): -`forms-get`, `forms-list`, `forms-analyze-logic`, `entries-get`, `entries-search`, `entries-count`, `submissions-validate`, `feeds-list`, `notifications-list`, `notes-list`, `system-info`, `system-field-types` +So an ability is available only when **Enable MCP is on AND that specific tool is checked**. A freshly enabled integration exposes nothing until tools are opted in. -**Write abilities** (require Write Access toggle): -`forms-create`, `forms-update`, `forms-delete`, `forms-duplicate`, `entries-create`, `entries-update`, `entries-delete`, `feeds-create`, `feeds-update`, `feeds-delete`, `submissions-submit`, `notifications-send`, `notes-add` +**Read-only group** (each opt-in): `forms-get`, `forms-list`, `forms-analyze-logic`, `entries-get`, `entries-search`, `entries-count`, `submissions-validate`, `feeds-list`, `notifications-list`, `notes-list`, `system-info`, `system-field-types` -If a write ability is not available, the site admin has not enabled write access. Do not attempt workarounds — inform the user that write access must be enabled in GF Settings → MCP. +**Write & destructive group** (each opt-in): `forms-create`, `forms-update`, `forms-delete`, `forms-duplicate`, `entries-create`, `entries-update`, `entries-delete`, `feeds-create`, `feeds-update`, `feeds-delete`, `submissions-submit`, `notifications-send`, `notes-add` + +If an ability is not available (absent from discovery, or a direct call is permission-denied), the admin has not enabled that specific tool (or MCP itself is off). Do not attempt workarounds — tell the user to enable the exact tool, and Enable MCP, in GF Settings → MCP. ### Endpoint Modes @@ -218,7 +218,9 @@ See [references/entry-operations.md](references/entry-operations.md) for filter ### Bulk Deleting Entries -`entries-delete` supports bulk mode — pass `form_id` instead of `entry_id` to delete all matching entries server-side in one call. +`entries-delete` supports bulk mode — pass `form_id` instead of `entry_id` to delete all matching entries server-side. + +**Bulk calls are capped at 100 entries per call** (to avoid server timeouts). The response includes `remaining` and `capped`. When `capped: true`, loop: call `entries-count` for the updated count, then `entries-delete` again (force mode needs a fresh `DELETE {count} ENTRIES FROM FORM {form_id}` confirmation each time). Repeat until `remaining` is 0. Already-trashed entries are excluded from trash-mode bulk deletes automatically, so each call processes new entries. **Delete all entries from every form** (e.g., pre-launch cleanup): 1. Call `forms-list` — get all form IDs @@ -320,6 +322,7 @@ For CL structure details, operators, and common patterns, see [references/condit | Using `@example.com` emails in submissions | Rejected as spam by GF email field | Use realistic test domains (e.g., `@testmail.dev`) | | Passing `form_id` as top-level param on `forms-update` | Input validation error | Put form ID inside the `form` object as `id` | | Using integer `fieldId` in CL rules | Logic may not evaluate correctly | Always use string: `"fieldId": "1"` not `"fieldId": 1` | +| Using deprecated Ready Classes (`gf_left_half`, `gf_right_half`, `gf_left_third`, `gf_inline`, etc.) in `cssClass` | Deprecated since GF 2.5 — the API strips them and reports `stripped_ready_classes` in the response | NEVER use Ready Classes for layout. Use `layoutGroupId` + `layoutGridColumnSpan` — see Layout Grid in field-config reference | | Parsing raw CL from `forms-get` manually | Error-prone, misses notifications/confirmations/buttons | Use `forms-analyze-logic` instead | | Using `is_active: '0'` to pause a form | Form disappears entirely — page shows nothing | Use `scheduleForm` + `scheduleEnd` instead | | Fileupload field without `allowedExtensions`/`maxFileSize` | Accepts any file type/size — security risk | Always set `allowedExtensions` and `maxFileSize` — see field-config reference | @@ -327,7 +330,7 @@ For CL structure details, operators, and common patterns, see [references/condit | Creating a name field without `nameFormat: "advanced"` | First/Last sub-inputs render stacked vertically instead of side-by-side | Always set `"nameFormat": "advanced"` and `"size": "large"` on name fields — see field-config reference | | Passing multiselect values as comma-separated string | Data loss when values contain commas (e.g., "Atlanta, GA") | Always pass multiselect values as an **array**: `"input_1": ["Red", "Blue"]` — see field-config reference | | User asks to "create a form and add it to a page" | Cannot create WordPress pages/posts — only GF abilities exist | Create the form, then tell the user the shortcode `[gravityform id="X" title="true"]` to embed manually. Page/post creation is not currently available via the Abilities API. | -| Write ability not found (e.g., `forms-create`) | Site admin has not enabled Write Access in MCP settings | Inform the user that write access must be enabled in GF Settings → MCP before write operations are available. Do not attempt workarounds. | +| Ability not found (e.g., `forms-create`) | Site admin has not checked that specific tool (or Enable MCP is off) in MCP settings | Tell the user to enable the exact tool — and Enable MCP — in GF Settings → MCP. There is no blanket "write access" toggle; each tool is opted in individually. Do not attempt workarounds. | | `system-field-types` does not list rating/survey-style fields | The required add-on is not active on this site | Call `system-info` to check active add-ons; fall back to core fields (`radio`, `select`, `checkbox`) when add-on types are unavailable | | `entries-update` with only status/metadata | Older bridge versions could wipe omitted field values | Safest pattern is still fetch → merge → update when changing existing entries | | `entries-create` date field given in the wrong format | Searches may miss the entry later | Prefer the form's configured date format; ISO is only safe if the bridge explicitly normalizes it | @@ -347,6 +350,8 @@ For CL structure details, operators, and common patterns, see [references/condit **Layout grid**: Control field width and row grouping with `layoutGridColumnSpan` (1–12, default full width) and `layoutGroupId` (any string — fields sharing the same value render on the same row). See [references/field-config.md](references/field-config.md) §Layout Grid for patterns and natural language mapping. +**Pricing fields**: `product`, `option`, `quantity`, `shipping`, `total`. Product variants (single product, dropdown, calculation, hidden, user-defined price) are set via `inputType` on a `product` field — never as standalone types (`singleproduct`, `calculation`, etc. are not valid `type` values and are excluded from `system-field-types`). Choice-based products submit `value|price` (e.g. `"input_2": "Pro|30"` for a $30.00 choice). See [references/field-config.md](references/field-config.md) §Pricing Fields for the full pattern table and submission formats. + **Type-specific properties**: Several field types accept configuration beyond what `system-field-types` reports — `number` (rangeMin/rangeMax/numberFormat), `date` (dateFormat/dateType), `phone` (phoneFormat), `text`/`textarea` (maxLength), `fileupload` (allowedExtensions/maxFileSize/maxFiles), `consent` (checkboxLabel/description). See [references/field-config.md](references/field-config.md) for the full reference. For detailed field type tables, compound field suffix mappings, and form design patterns, see [references/field-config.md](references/field-config.md). diff --git a/skills/gravity-forms-abilities/references/entry-operations.md b/skills/gravity-forms-abilities/references/entry-operations.md index 2ea81c3..0e056c7 100644 --- a/skills/gravity-forms-abilities/references/entry-operations.md +++ b/skills/gravity-forms-abilities/references/entry-operations.md @@ -125,7 +125,7 @@ For large result sets: ## Bulk Entry Deletion -`entries-delete` supports bulk mode — pass `form_id` instead of `entry_id` to delete all entries for a form in a single call. +`entries-delete` supports bulk mode — pass `form_id` instead of `entry_id` to delete all entries for a form server-side. ### Delete all entries for one form @@ -135,7 +135,16 @@ For large result sets: } ``` -Returns: `{ "success": true, "deleted_count": 47 }` +Returns: `{ "success": true, "deleted_count": 47, "trashed": true, "remaining": 0, "capped": false, "cap": 100 }` + +### Per-call cap (100 entries) + +Bulk calls process at most 100 entries per call to avoid server timeouts. When more entries match: + +- `capped: true` and `remaining > 0` are returned +- Loop: call `entries-count` for the updated count, then `entries-delete` again, until `remaining` is 0 +- **Force mode**: each loop iteration needs a fresh confirmation phrase built from the updated count (`DELETE {count} ENTRIES FROM FORM {form_id}`) +- **Trash mode**: already-trashed entries are excluded automatically, so each call picks up where the last one left off; filtering on `status: "trash"` with trash mode is a no-op (`deleted_count: 0`) ### Delete with filter (e.g., only entries before a date) diff --git a/skills/gravity-forms-abilities/references/field-config.md b/skills/gravity-forms-abilities/references/field-config.md index 45b1cfa..d826886 100644 --- a/skills/gravity-forms-abilities/references/field-config.md +++ b/skills/gravity-forms-abilities/references/field-config.md @@ -93,6 +93,30 @@ Choice field types: `select`, `radio`, `checkbox`, `multiselect`, `image_choice` Note: Always call `system-field-types` for the authoritative, up-to-date list — add-ons may register additional types. +## Pricing Fields (`product`, `option`, `quantity`, `shipping`, `total`) + +Pricing variants are NOT standalone field types. `system-field-types` intentionally omits `singleproduct`, `hiddenproduct`, `calculation`, `price`, `donation`, and `singleshipping` — those are **`inputType` values on a `product` (or `shipping`) field**, matching the editor's "Field Type" dropdown. Never pass them as `type`. + +| What you want | Field config | +|---|---| +| Fixed-price product | `{ "type": "product", "label": "Widget", "inputType": "singleproduct", "basePrice": "$25.00" }` | +| Product chosen from a list | `{ "type": "product", "inputType": "select", "choices": [{ "text": "Pro", "value": "Pro", "price": "$30.00" }] }` (also `radio`) | +| User-defined price | `{ "type": "product", "inputType": "price" }` | +| Hidden product | `{ "type": "product", "inputType": "hiddenproduct", "basePrice": "$9.00" }` | +| Calculated price | `{ "type": "product", "inputType": "calculation", "calculationFormula": "{Qty:3} * 2" }` | +| Quantity for a product | `{ "type": "quantity", "label": "Qty", "productField": 1 }` (`productField` = the product field's ID) | +| Flat shipping | `{ "type": "shipping", "inputType": "singleshipping", "basePrice": "$5.00" }` | +| Shipping options | `{ "type": "shipping", "inputType": "select", "choices": [{ "text": "Ground", "value": "Ground", "price": "$5.00" }] }` | +| Order total | `{ "type": "total", "label": "Total" }` | + +The API applies the same defaults the editor would: bare `type: product` becomes `inputType: singleproduct`; single/hidden/calculation products get their `.1` (name) / `.2` (price) / `.3` (quantity) sub-inputs created automatically; choice-based product and shipping fields get `enablePrice` set (required — without it, priced submissions fail GF's anti-tampering state validation). + +**Submitting pricing fields:** + +- Single product: `"input_1.1": "Widget"`, `"input_1.2": "$25.00"`, and `"input_1.3": "2"` for quantity (or use a separate quantity field: `"input_3": "2"`). +- Choice-based product/shipping: submit `value|price` with the price as a plain number matching the choice — e.g. `"input_2": "Pro|30"` for a `$30.00` choice. The wrong price (or omitting `|price`) fails validation as a tampered submission. +- Total fields are computed server-side — never submit a value for them. + ## Type-Specific Configuration `system-field-types` returns generic capability flags but does **not** expose type-specific properties. The following properties are accepted by `forms-create` and `forms-update` — pass them directly on the field object. @@ -296,6 +320,8 @@ The `is` operator does not work for individual multiselect values — it matches Fields default to full-width (12 columns). To place fields side-by-side, give them the same `layoutGroupId` and set `layoutGridColumnSpan` to control width. +**NEVER use Ready Classes.** Gravity Forms' legacy CSS helper classes (`gf_left_half`, `gf_right_half`, `gf_left_third`, `gf_middle_third`, `gf_right_third`, `gf_first_quarter`…`gf_fourth_quarter`, `gf_inline`, `gf_list_*`) are deprecated since GF 2.5 and do nothing in modern themes. The abilities API strips them from `cssClass` automatically and returns a `stripped_ready_classes` list plus a `notice` when it does. The layout grid is the only supported way to control field layout. + ### Properties | Property | Type | Description |