agentleFS
dougborgdougborg/frontapp-openapi-client/.claude/skills/new-vertical/SKILL.md

new-vertical

Skill0 starsChanged 7 months agoMITArchived repositoryView on GitHub

Scaffold a new resource vertical end-to-end (helper + Pydantic domain + MCP tools + tests + docs). Use when starting a new "client.<resource>" surface such as drafts (#14), contacts (#2), messages (#4), tags/inboxes (#5). Requires the `vertical-planner` agent to produce a plan first.

The file

Show its rules and text

We split this skill into the rules it gives an agent

13 rules. About 0 are common, 3 are distinctive, and 10 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈3

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 10

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Mirror conversations/: When in doubt, copy file structure, method names, and projection patterns from helpers/conversations.py, …

    line 47
  2. Very specific:

    Two-step confirm on mutations: Every mutation tool takes confirm: bool = False and runs the gate via confirm_or_preview (see step 6 below …

    line 51
  3. Very specific:

    Helper class: Create frontapp_public_api_client/helpers/<resource>.py — a class extending Base with one async method per generated endpoint …

    line 92
  4. Very specific:

    Add self._<resource>: <Class> | None = None in __init__.

    line 109
  5. Very specific:

    Add a lazy @property named <resource> returning the helper, mirroring the existing conversations property at line ~1098.

    line 110
  6. Very specific:

    Add the TYPE_CHECKING import at the top.

    line 112
  7. Very specific:

    Use confirm_or_preview for the gate: Every mutation tool runs the same preview-then-elicit cascade — import confirm_or_preview from …

    line 126
  8. Very specific:

    Tests: Add helper tests at tests/test_<resource>.py (mock httpx transport, exercise the unwrap helpers end-to-end) and MCP tool tests at …

    line 167
  9. Distinctive:

    Reference-resource only (issue #3 surfaces): If the resource is meant to expose data as MCP resources (not tools) — e.g.

    line 182
  10. Distinctive:

    Research vertical (issue #15 webhook receiver): If the issue describes a new track (server-side webhooks, event streams) that doesn't fit …

    line 187
  11. Distinctive:

    Deprecated sister vertical: When the resource is the older, deprecated half of a pair (e.g.

    line 201
  12. Very specific:

    Server-side maxItems caps: When a Front request body declares maxItems: N on a list-bearing field (e.g.

    line 213
  13. Very specific:

    If new spec quirks were patched in scripts/vendor_spec.py, mention it in the PR description so reviewers know.

    line 230

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

New Vertical Scaffolding

Take a resource (issue # or name) from "not implemented" to "shipped behind a green agent-check," following the canonical conversations vertical as the template.

PURPOSE

Encode the stereotyped vertical-shipping motion as a single repeatable workflow so the resource-specific bits are the only thing the human (or coding agent) has to think about. This is the harness for issues #2, #4, #5, #14, and any future client.<resource> surface.

CRITICAL

  • Plan first. Invoke the vertical-planner agent before writing a single line of code. The plan must confirm generated module names and list-response shape (field_results vs raw array). Mid-implementation surprises waste a full debug cycle.
  • Mirror conversations/. When in doubt, copy file structure, method names, and projection patterns from helpers/conversations.py, domain/conversation.py, and tools/conversations.py. Do not invent a new shape unless the resource genuinely differs.
  • Two-step confirm on mutations. Every mutation tool takes confirm: bool = False and runs the gate via confirm_or_preview (see step 6 below for the canonical shape). Exception: drafts (#14) use the same gate but the draft itself IS the review step (no programmatic send). Confirm against the issue before deviating.
  • Never edit files under frontapp_public_api_client/api/, frontapp_public_api_client/models/, or client.py — those are generated. The block-generated-edits.sh PreToolUse hook will reject the attempt.

ASSUMES

  • The conversations vertical still works as the canonical template (run uv run poe quick-check to confirm before starting).
  • The vendored spec is current. If you suspect upstream drift, run /vendor-and-regen first.
  • A clean working tree on a feature branch, not main.

STANDARD PATH

  1. Read the facts file. Open docs/api-facts.yaml and find the resource under tags.<resource>. This single Read tells you: every generated module name (with quirks like _a_ infix flagged), HTTP method + path, response type, list shape (field_results / raw_array / single / mutation), and whether helper/domain wiring already exists. If the resource isn't listed, uv run poe facts to regenerate (rare — facts-check runs in CI).
  2. Plan. Invoke the vertical-planner agent with the issue # or resource name. The planner consults docs/api-facts.yaml first, then reads issue context and the canonical conversations templates to produce a structured plan. Do not proceed without a plan that names every generated module to wrap and flags any quirks.
  3. Domain projection. Create frontapp_public_api_client/domain/<resource>.py — a Pydantic class extending FrontappPydanticBase that projects the response model. For epoch-second timestamps, mirror the pattern in domain/conversation.py: type the field as AwareDatetime | None, then add a @field_validator(..., mode="before") that calls a private _unix_to_datetime helper (also defined in that module). domain/converters.py only carries the to_unset / unwrap_unset UNSET helpers; it does not contain time conversion. Export from domain/__init__.py.
  4. Helper class. Create frontapp_public_api_client/helpers/<resource>.py — a class extending Base with one async method per generated endpoint (use tags.<resource>.endpoints[] from the facts file as the checklist). Inside each method:
    • Lazy-import the generated api/<tag>/<module>.py and the domain class.
    • Build kwargs dict (only set keys for non-None args, to keep UNSET defaults from the generated signature).
    • Call <module>.asyncio_detailed(**kwargs).
    • Unwrap based on list_shape from the facts file:
      • field_results → getattr(parsed, "field_results", None) or []
      • raw_array → unwrap(response) returns a list directly
      • single → unwrap_as(response, <ResponseType>)
      • mutation → is_success(response) for 202/204; otherwise unwrap_as
    • Return Pydantic domain models, not generated attrs. Export from helpers/__init__.py.
  5. Wire into FrontappClient. In frontapp_client.py:
    • Add self._<resource>: <Class> | None = None in __init__.
    • Add a lazy @property named <resource> returning the helper, mirroring the existing conversations property at line ~1098.
    • Add the TYPE_CHECKING import at the top.
  6. MCP tools. Create frontapp_mcp_server/src/frontapp_mcp/tools/<resource>.py mirroring tools/conversations.py:
    • One <Resource>Summary Pydantic projection for compact LLM responses.
    • def register_tools(mcp: FastMCP) -> None: with one nested function per tool, decorated with @mcp.tool(name=..., description=...).
    • Reads return a list[<Resource>Summary]. Mutations take confirm: bool = False and return a plain dict with preview and confirmed keys (matching tools/conversations.py:update_conversation or tools/drafts.py:create_draft_reply). Customer-facing outbound mutations always go through the drafts vertical; there is no direct- send pattern.
    • Use confirm_or_preview for the gate. Every mutation tool runs the same preview-then-elicit cascade — import confirm_or_preview from frontapp_mcp.tools.schemas and call it once instead of hand-rolling the 6-line if not confirm: ... require_confirmation ... ConfirmationResult.CONFIRMED block. Canonical shape:

      preview = {"action": "...", ...}
      gate = await confirm_or_preview(
          context,
          preview=preview,
          confirm=confirm,
          elicit_message=f"Concrete prompt the user sees?",
      )
      if gate is not None:
          return gate
      # ... proceed with the mutation

      ConfirmationResult (a StrEnum of CONFIRMED/CANCELLED/ DECLINED) is now an internal detail of confirm_or_preview. Only import it directly if your tool needs to branch differently between declined vs cancelled (rare).

    • Tools call get_services(context).client.<resource>.<method>(...). Never reach into the generated api/ modules from a tool.
  7. Register tools. In frontapp_mcp_server/src/frontapp_mcp/tools/__init__.py:register_all_tools, add from .<resource> import register_tools as register_<resource>_tools then register_<resource>_tools(mcp).
  8. Cache + instructions. In frontapp_mcp_server/src/frontapp_mcp/server.py:
    • Append the new read-tool names to _READ_ONLY_TOOLS.
    • Extend the instructions= block with a section for the new resource — domain model description + tool selection guide + any search-syntax differences.
  9. Help resource. Update frontapp_mcp_server/src/frontapp_mcp/resources/help.py Markdown to cover the new tools and parameters. This is hand-maintained — do not assume the tool docstrings are enough.
  10. Tests. Add helper tests at tests/test_<resource>.py (mock httpx transport, exercise the unwrap helpers end-to-end) and MCP tool tests at frontapp_mcp_server/tests/test_<resource>_tools.py. Use helpers/conversations.py and existing tests/test_conversations.py as templates.
  11. README. Flip the API Coverage row from ⏳ to ✅.
  12. Validate. uv run poe full-check. This includes facts-check, so if you somehow regenerated the client during this work, the facts file must also be regenerated (uv run poe facts). Fix all errors at the source — no noqa, no type: ignore, no skips.

EDGE CASES

  • Reference-resource only (issue #3 surfaces). If the resource is meant to expose data as MCP resources (not tools) — e.g. frontapp://teammates or frontapp://inboxes — skip steps 5–7 and add a resource definition under frontapp_mcp_server/src/frontapp_mcp/resources/ instead. Help text still needs updating.
  • Research vertical (issue #15 webhook receiver). If the issue describes a new track (server-side webhooks, event streams) that doesn't fit the helper-+-domain-+-tools shape, escalate to the human before scaffolding — this skill assumes a CRUD-shaped resource.
  • Drafts invert confirm (issue #14). create_draft does not take a confirm param; the draft itself is the review step. update_draft and delete_draft still use two-step confirm normally.
  • Generator quirks (_a_ infix, removes_ prefix). Spec summaries that begin with "Update a …" / "Removes …" produce module names like update_a_contact, removes_inbox_access. Always ls api/<tag>/ before writing the import — do not infer the name from the resource.
  • Raw-array list endpoints. /statuses and /teammates return arrays directly, not wrapped in field_results. Unwrap as unwrap(response) and iterate the result directly.
  • Deprecated sister vertical. When the resource is the older, deprecated half of a pair (e.g. contact_groups superseded by contact_lists), Front marks every endpoint with deprecated: true in the spec and may share request body models with the modern sibling. Ship the vertical anyway for workspaces still using the deprecated surface, but: (a) keep a _DEPRECATION_NOTE constant in the MCP tool module and prefix every tool description with it; (b) reuse the modern sibling's domain projection if the wire shape is identical (avoids a duplicate <Resource> class); (c) extend the instructions= block in server.py to steer agents toward the modern sibling for new work. Canonical example: tools/contact_groups.py (deprecated) mirrors tools/contact_lists.py (current) with the deprecation prefix.
  • Server-side maxItems caps. When a Front request body declares maxItems: N on a list-bearing field (e.g. RemoveContactsFromList's maxItems: 50), the helper should fail-fast with check_list_size_cap from helpers/constants.py, and the MCP tool should preview-error with cap_error_message (same module). Add the cap as a named constant in helpers/constants.py so a future Front-side change is one edit.

After

  • Open a PR with /open-pr (the existing skill). Once CI is green, the /open-pr flow runs /simplify (self-review pass) and /review-pr (handle external feedback) before declaring the PR ready — both are routine steps for verticals, not optional polish. Vertical PRs in particular tend to surface real type-annotation bugs and DRY misses that /simplify catches in one round.
  • Resolve the related issue (or note follow-up gaps in a comment).
  • If new spec quirks were patched in scripts/vendor_spec.py, mention it in the PR description so reviewers know.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.