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.
In this repository6 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- babysit-prsSkill
- open-prSkill
- review-prSkill
- vendor-and-regenSkill
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
- Very specific: line 47
Mirror conversations/: When in doubt, copy file structure, method names, and projection patterns from helpers/conversations.py, …
- Very specific: line 51
Two-step confirm on mutations: Every mutation tool takes confirm: bool = False and runs the gate via confirm_or_preview (see step 6 below …
- Very specific: line 92
Helper class: Create frontapp_public_api_client/helpers/<resource>.py — a class extending Base with one async method per generated endpoint …
- Very specific: line 109
Add self._<resource>: <Class> | None = None in __init__.
- Very specific: line 110
Add a lazy @property named <resource> returning the helper, mirroring the existing conversations property at line ~1098.
- Very specific: line 112
Add the TYPE_CHECKING import at the top.
- Very specific: line 126
Use
confirm_or_previewfor the gate: Every mutation tool runs the same preview-then-elicit cascade — import confirm_or_preview from … - Very specific: line 167
Tests: Add helper tests at tests/test_<resource>.py (mock httpx transport, exercise the unwrap helpers end-to-end) and MCP tool tests at …
- Distinctive: line 182
Reference-resource only (issue #3 surfaces): If the resource is meant to expose data as MCP resources (not tools) — e.g.
- Distinctive: line 187
Research vertical (issue #15 webhook receiver): If the issue describes a new track (server-side webhooks, event streams) that doesn't fit …
- Distinctive: line 201
Deprecated sister vertical: When the resource is the older, deprecated half of a pair (e.g.
- Very specific: line 213
Server-side maxItems caps: When a Front request body declares maxItems: N on a list-bearing field (e.g.
- Very specific: line 230
If new spec quirks were patched in scripts/vendor_spec.py, mention it in the PR description so reviewers know.
≈ “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.
Files
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-planneragent before writing a single line of code. The plan must confirm generated module names and list-response shape (field_resultsvs 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, andtools/conversations.py. Do not invent a new shape unless the resource genuinely differs. - Two-step confirm on mutations. Every mutation tool takes
confirm: bool = Falseand runs the gate viaconfirm_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/, orclient.py— those are generated. Theblock-generated-edits.shPreToolUse hook will reject the attempt.
ASSUMES
- The conversations vertical still works as the canonical template (run
uv run poe quick-checkto confirm before starting). - The vendored spec is current. If you suspect upstream drift, run
/vendor-and-regenfirst. - A clean working tree on a feature branch, not
main.
STANDARD PATH
- Read the facts file. Open
docs/api-facts.yamland find the resource undertags.<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 factsto regenerate (rare —facts-checkruns in CI). - Plan. Invoke the
vertical-planneragent with the issue # or resource name. The planner consultsdocs/api-facts.yamlfirst, 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. - Domain projection. Create
frontapp_public_api_client/domain/<resource>.py— a Pydantic class extendingFrontappPydanticBasethat projects the response model. For epoch-second timestamps, mirror the pattern indomain/conversation.py: type the field asAwareDatetime | None, then add a@field_validator(..., mode="before")that calls a private_unix_to_datetimehelper (also defined in that module).domain/converters.pyonly carries theto_unset/unwrap_unsetUNSET helpers; it does not contain time conversion. Export fromdomain/__init__.py. - Helper class. Create
frontapp_public_api_client/helpers/<resource>.py— a class extendingBasewith one async method per generated endpoint (usetags.<resource>.endpoints[]from the facts file as the checklist). Inside each method:- Lazy-import the generated
api/<tag>/<module>.pyand the domain class. - Build kwargs dict (only set keys for non-None args, to keep
UNSETdefaults from the generated signature). - Call
<module>.asyncio_detailed(**kwargs). - Unwrap based on
list_shapefrom the facts file:field_results→getattr(parsed, "field_results", None) or []raw_array→unwrap(response)returns a list directlysingle→unwrap_as(response, <ResponseType>)mutation→is_success(response)for 202/204; otherwiseunwrap_as
- Return Pydantic domain models, not generated attrs. Export from
helpers/__init__.py.
- Lazy-import the generated
- Wire into FrontappClient. In
frontapp_client.py:- Add
self._<resource>: <Class> | None = Nonein__init__. - Add a lazy
@propertynamed<resource>returning the helper, mirroring the existingconversationsproperty at line ~1098. - Add the
TYPE_CHECKINGimport at the top.
- Add
- MCP tools. Create
frontapp_mcp_server/src/frontapp_mcp/tools/<resource>.pymirroringtools/conversations.py:- One
<Resource>SummaryPydantic 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 takeconfirm: bool = Falseand return a plain dict withpreviewandconfirmedkeys (matchingtools/conversations.py:update_conversationortools/drafts.py:create_draft_reply). Customer-facing outbound mutations always go through the drafts vertical; there is no direct- send pattern. Use
confirm_or_previewfor the gate. Every mutation tool runs the same preview-then-elicit cascade — importconfirm_or_previewfromfrontapp_mcp.tools.schemasand call it once instead of hand-rolling the 6-lineif not confirm: ... require_confirmation ... ConfirmationResult.CONFIRMEDblock. 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 mutationConfirmationResult(aStrEnumofCONFIRMED/CANCELLED/DECLINED) is now an internal detail ofconfirm_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 generatedapi/modules from a tool.
- One
- Register tools. In
frontapp_mcp_server/src/frontapp_mcp/tools/__init__.py:register_all_tools, addfrom .<resource> import register_tools as register_<resource>_toolsthenregister_<resource>_tools(mcp). - 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.
- Append the new read-tool names to
- Help resource. Update
frontapp_mcp_server/src/frontapp_mcp/resources/help.pyMarkdown to cover the new tools and parameters. This is hand-maintained — do not assume the tool docstrings are enough. - Tests. Add helper tests at
tests/test_<resource>.py(mock httpx transport, exercise the unwrap helpers end-to-end) and MCP tool tests atfrontapp_mcp_server/tests/test_<resource>_tools.py. Usehelpers/conversations.pyand existingtests/test_conversations.pyas templates. - README. Flip the API Coverage row from ⏳ to ✅.
- Validate.
uv run poe full-check. This includesfacts-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 — nonoqa, notype: 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://teammatesorfrontapp://inboxes— skip steps 5–7 and add a resource definition underfrontapp_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_draftdoes not take aconfirmparam; the draft itself is the review step.update_draftanddelete_draftstill use two-step confirm normally. - Generator quirks (
_a_infix,removes_prefix). Spec summaries that begin with "Update a …" / "Removes …" produce module names likeupdate_a_contact,removes_inbox_access. Alwaysls api/<tag>/before writing the import — do not infer the name from the resource. - Raw-array list endpoints.
/statusesand/teammatesreturn arrays directly, not wrapped infield_results. Unwrap asunwrap(response)and iterate the result directly. - Deprecated sister vertical. When the resource is the older,
deprecated half of a pair (e.g.
contact_groupssuperseded bycontact_lists), Front marks every endpoint withdeprecated: truein 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_NOTEconstant 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 theinstructions=block inserver.pyto steer agents toward the modern sibling for new work. Canonical example:tools/contact_groups.py(deprecated) mirrorstools/contact_lists.py(current) with the deprecation prefix. - Server-side maxItems caps. When a Front request body declares
maxItems: Non a list-bearing field (e.g.RemoveContactsFromList'smaxItems: 50), the helper should fail-fast withcheck_list_size_capfromhelpers/constants.py, and the MCP tool should preview-error withcap_error_message(same module). Add the cap as a named constant inhelpers/constants.pyso a future Front-side change is one edit.
After
- Open a PR with
/open-pr(the existing skill). Once CI is green, the/open-prflow 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/simplifycatches 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.
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
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.

